From a0806b1e3f8c7c5333e65565a76ff7134acbd2bb Mon Sep 17 00:00:00 2001 From: BidishaMS <97606074+BidishaMS@users.noreply.github.com> Date: Tue, 23 Apr 2024 10:39:21 +0530 Subject: [PATCH] [Telemetry] Adding x-ms-user-agent in Dataverse calls to check the client surface (Vscode Web/Desktop) (#911) added user agent --- src/common/Utils.ts | 11 ++++++++++- src/common/copilot/dataverseMetadata.ts | 3 +++ src/web/client/WebExtensionContext.ts | 10 +++++----- .../client/common/authenticationProvider.ts | 19 ++++++++++++++++++- src/web/client/dal/remoteFetchProvider.ts | 6 +++--- src/web/client/dal/remoteSaveProvider.ts | 4 ++-- src/web/client/services/etagHandlerService.ts | 6 +++--- .../integration/WebExtensionContext.test.ts | 8 ++++---- .../integration/remoteSaveProvider.test.ts | 7 ++++++- 9 files changed, 54 insertions(+), 20 deletions(-) diff --git a/src/common/Utils.ts b/src/common/Utils.ts index 1175a759..8ed0aa12 100644 --- a/src/common/Utils.ts +++ b/src/common/Utils.ts @@ -5,7 +5,7 @@ import * as vscode from "vscode"; -import { EXTENSION_ID, SETTINGS_EXPERIMENTAL_STORE_NAME } from "../client/constants"; +import { EXTENSION_ID, EXTENSION_NAME, SETTINGS_EXPERIMENTAL_STORE_NAME } from "../client/constants"; import { CUSTOM_TELEMETRY_FOR_POWER_PAGES_SETTING_NAME } from "./OneDSLoggerTelemetry/telemetryConstants"; export function getSelectedCode(editor: vscode.TextEditor): string { @@ -123,3 +123,12 @@ export function isCustomTelemetryEnabled():boolean { .get(CUSTOM_TELEMETRY_FOR_POWER_PAGES_SETTING_NAME); return isCustomTelemetryEnabled as boolean; } + +export function getUserAgent(): string { + const userAgent = "{product}/{product-version} {comment}"; + + return userAgent + .replace("{product}", EXTENSION_NAME) + .replace("{product-version}", getExtensionVersion()) + .replace("{comment}", "(" + getExtensionType()+'; )'); +} \ No newline at end of file diff --git a/src/common/copilot/dataverseMetadata.ts b/src/common/copilot/dataverseMetadata.ts index d2d391ba..4d6290b0 100644 --- a/src/common/copilot/dataverseMetadata.ts +++ b/src/common/copilot/dataverseMetadata.ts @@ -13,6 +13,7 @@ import { CopilotDataverseMetadataFailureEvent, CopilotDataverseMetadataSuccessEv import { getEntityMetadata } from "../../web/client/utilities/fileAndEntityUtil"; import { DOMParser } from "@xmldom/xmldom"; import { ATTRIBUTE_CLASSID, ATTRIBUTE_DATAFIELD_NAME, ATTRIBUTE_DESCRIPTION, ControlClassIdMap, SYSTEFORMS_API_PATH } from "./constants"; +import { getUserAgent } from "../Utils"; declare const IS_DESKTOP: string | undefined; @@ -25,6 +26,7 @@ export async function getEntityColumns(entityName: string, orgUrl: string, apiTo headers: { 'Content-Type': "application/json", Authorization: `Bearer ${apiToken}`, + "x-ms-user-agent": getUserAgent() }, }; @@ -57,6 +59,7 @@ export async function getFormXml(entityName: string, formName: string, orgUrl: headers: { 'Content-Type': "application/json", Authorization: `Bearer ${apiToken}`, + "x-ms-user-agent": getUserAgent() }, }; diff --git a/src/web/client/WebExtensionContext.ts b/src/web/client/WebExtensionContext.ts index 04584071..80e978b6 100644 --- a/src/web/client/WebExtensionContext.ts +++ b/src/web/client/WebExtensionContext.ts @@ -6,7 +6,7 @@ import * as vscode from "vscode"; import { dataverseAuthentication, - getCommonHeaders, + getCommonHeadersForDataverse, } from "./common/authenticationProvider"; import * as Constants from "./common/constants"; import { @@ -360,7 +360,7 @@ class WebExtensionContext implements IWebExtensionContext { Constants.queryParameters.WEBSITE_ID ) as string; - const headers = getCommonHeaders(this._dataverseAccessToken); + const headers = getCommonHeadersForDataverse(this._dataverseAccessToken); // Populate shared workspace for Co-Presence await this.populateSharedWorkspace(headers, dataverseOrgUrl, websiteId); @@ -482,7 +482,7 @@ class WebExtensionContext implements IWebExtensionContext { requestSentAtTime = new Date().getTime(); const response = await this._concurrencyHandler.handleRequest(requestUrl, { - headers: getCommonHeaders(accessToken), + headers: getCommonHeadersForDataverse(accessToken), }); if (!response?.ok) { throw new Error(JSON.stringify(response)); @@ -548,7 +548,7 @@ class WebExtensionContext implements IWebExtensionContext { requestSentAtTime = new Date().getTime(); const response = await this._concurrencyHandler.handleRequest(requestUrl, { - headers: getCommonHeaders(accessToken), + headers: getCommonHeadersForDataverse(accessToken), }); if (!response?.ok) { throw new Error(JSON.stringify(response)); @@ -610,7 +610,7 @@ class WebExtensionContext implements IWebExtensionContext { requestSentAtTime = new Date().getTime(); const response = await this._concurrencyHandler.handleRequest(requestUrl, { - headers: getCommonHeaders(accessToken), + headers: getCommonHeadersForDataverse(accessToken), }); if (!response?.ok) { diff --git a/src/web/client/common/authenticationProvider.ts b/src/web/client/common/authenticationProvider.ts index 85c01323..414f5ddb 100644 --- a/src/web/client/common/authenticationProvider.ts +++ b/src/web/client/common/authenticationProvider.ts @@ -18,9 +18,10 @@ import { ERRORS, showErrorDialog } from "./errorHandler"; import { ITelemetry } from "../../../client/telemetry/ITelemetry"; import { sendTelemetryEvent } from "../../../common/copilot/telemetry/copilotTelemetry"; import { CopilotLoginFailureEvent, CopilotLoginSuccessEvent } from "../../../common/copilot/telemetry/telemetryConstants"; +import { getUserAgent } from "../../../common/Utils"; -export function getCommonHeaders( +export function getCommonHeadersForDataverse( accessToken: string, useOctetStreamContentType?: boolean ) { @@ -32,6 +33,22 @@ export function getCommonHeaders( accept: "application/json", "OData-MaxVersion": "4.0", "OData-Version": "4.0", + "x-ms-user-agent": getUserAgent() + }; +} + +export function getCommonHeaders( + accessToken: string, + useOctetStreamContentType?: boolean +) { + return { + authorization: "Bearer " + accessToken, + "content-type": useOctetStreamContentType + ? "application/octet-stream" + : "application/json; charset=utf-8", + accept: "application/json", + "OData-MaxVersion": "4.0", + "OData-Version": "4.0" }; } diff --git a/src/web/client/dal/remoteFetchProvider.ts b/src/web/client/dal/remoteFetchProvider.ts index a8829df2..4cdbc957 100644 --- a/src/web/client/dal/remoteFetchProvider.ts +++ b/src/web/client/dal/remoteFetchProvider.ts @@ -15,7 +15,7 @@ import { setFileContent, } from "../utilities/commonUtil"; import { getCustomRequestURL, getMappingEntityContent, getMetadataInfo, getMappingEntityId, getMimeType, getRequestURL } from "../utilities/urlBuilderUtil"; -import { getCommonHeaders } from "../common/authenticationProvider"; +import { getCommonHeadersForDataverse } from "../common/authenticationProvider"; import * as Constants from "../common/constants"; import { ERRORS, showErrorDialog } from "../common/errorHandler"; import { PortalsFS } from "./fileSystemProvider"; @@ -94,7 +94,7 @@ async function fetchFromDataverseAndCreateFiles( requestSentAtTime = new Date().getTime(); const response = await WebExtensionContext.concurrencyHandler.handleRequest(requestUrl, { headers: { - ...getCommonHeaders( + ...getCommonHeadersForDataverse( WebExtensionContext.dataverseAccessToken ), Prefer: `odata.maxpagesize=${Constants.MAX_ENTITY_FETCH_COUNT}, odata.include-annotations="Microsoft.Dynamics.CRM.*"`, @@ -506,7 +506,7 @@ async function fetchMappingEntityContent( requestSentAtTime = new Date().getTime(); const response = await WebExtensionContext.concurrencyHandler.handleRequest(requestUrl, { - headers: getCommonHeaders(accessToken), + headers: getCommonHeadersForDataverse(accessToken), }); if (!response.ok) { diff --git a/src/web/client/dal/remoteSaveProvider.ts b/src/web/client/dal/remoteSaveProvider.ts index 230a8b93..ae108f1b 100644 --- a/src/web/client/dal/remoteSaveProvider.ts +++ b/src/web/client/dal/remoteSaveProvider.ts @@ -5,7 +5,7 @@ import { RequestInit } from "node-fetch"; import * as vscode from "vscode"; -import { getCommonHeaders } from "../common/authenticationProvider"; +import { getCommonHeadersForDataverse } from "../common/authenticationProvider"; import { BAD_REQUEST, MIMETYPE, queryParameters } from "../common/constants"; import { showErrorDialog } from "../common/errorHandler"; import { FileData } from "../context/fileData"; @@ -87,7 +87,7 @@ async function getSaveParameters( webFileV2 ); - saveCallParameters.requestInit.headers = getCommonHeaders( + saveCallParameters.requestInit.headers = getCommonHeadersForDataverse( accessToken, useOctetStreamContentType(entityName, attributePath.source) ); diff --git a/src/web/client/services/etagHandlerService.ts b/src/web/client/services/etagHandlerService.ts index e43ff182..5b3d298d 100644 --- a/src/web/client/services/etagHandlerService.ts +++ b/src/web/client/services/etagHandlerService.ts @@ -5,7 +5,7 @@ import * as vscode from "vscode"; import { RequestInit } from "node-fetch"; -import { getCommonHeaders } from "../common/authenticationProvider"; +import { getCommonHeadersForDataverse } from "../common/authenticationProvider"; import { httpMethod, ODATA_ETAG, queryParameters } from "../common/constants"; import { IAttributePath } from "../common/interfaces"; import { PortalsFS } from "../dal/fileSystemProvider"; @@ -53,7 +53,7 @@ export class EtagHandlerService { try { const requestInit: RequestInit = { method: httpMethod.GET, - headers: getCommonHeaders( + headers: getCommonHeadersForDataverse( WebExtensionContext.dataverseAccessToken ), }; @@ -165,7 +165,7 @@ export class EtagHandlerService { try { const requestInit: RequestInit = { method: httpMethod.GET, - headers: getCommonHeaders( + headers: getCommonHeadersForDataverse( WebExtensionContext.dataverseAccessToken ), }; diff --git a/src/web/client/test/integration/WebExtensionContext.test.ts b/src/web/client/test/integration/WebExtensionContext.test.ts index d773947a..491727bc 100644 --- a/src/web/client/test/integration/WebExtensionContext.test.ts +++ b/src/web/client/test/integration/WebExtensionContext.test.ts @@ -15,7 +15,7 @@ import * as authenticationProvider from "../../common/authenticationProvider"; import { telemetryEventNames } from "../../telemetry/constants"; import * as schemaHelperUtil from "../../utilities/schemaHelperUtil"; import * as urlBuilderUtil from "../../utilities/urlBuilderUtil"; -import { getCommonHeaders } from "../../common/authenticationProvider"; +import { getCommonHeadersForDataverse } from "../../common/authenticationProvider"; import { IAttributePath } from "../../common/interfaces"; describe("WebExtensionContext", () => { @@ -532,7 +532,7 @@ describe("WebExtensionContext", () => { //#endregion //#region Fetch - const header = getCommonHeaders(accessToken); + const header = getCommonHeadersForDataverse(accessToken); assert.callCount(_mockFetch, 3); const firstFetchCall = _mockFetch.getCalls()[0]; expect(firstFetchCall.args[0], requestUrl); @@ -669,7 +669,7 @@ describe("WebExtensionContext", () => { //#endregion //#region Fetch - const header = getCommonHeaders(accessToken); + const header = getCommonHeadersForDataverse(accessToken); assert.calledThrice(_mockFetch); const firstFetchCall = _mockFetch.getCalls()[0]; expect(firstFetchCall.args[0], requestUrl); @@ -759,7 +759,7 @@ describe("WebExtensionContext", () => { assert.calledOnceWithExactly(dataverseAuthentication, ORG_URL, true); //#region Fetch - const header = getCommonHeaders(accessToken); + const header = getCommonHeadersForDataverse(accessToken); assert.calledThrice(_mockFetch); const firstFetchCall = _mockFetch.getCalls()[0]; expect(firstFetchCall.args[0], requestUrl); diff --git a/src/web/client/test/integration/remoteSaveProvider.test.ts b/src/web/client/test/integration/remoteSaveProvider.test.ts index da8166af..5a299db1 100644 --- a/src/web/client/test/integration/remoteSaveProvider.test.ts +++ b/src/web/client/test/integration/remoteSaveProvider.test.ts @@ -15,6 +15,7 @@ import { BAD_REQUEST } from "../../common/constants"; import * as urlBuilderUtil from "../../utilities/urlBuilderUtil"; import { IAttributePath } from "../../common/interfaces"; import { telemetryEventNames } from "../../telemetry/constants"; +import { getUserAgent } from "../../../../common/Utils"; describe("remoteSaveProvider", () => { afterEach(() => { @@ -93,6 +94,7 @@ describe("remoteSaveProvider", () => { accept: "application/json", "OData-MaxVersion": "4.0", "OData-Version": "4.0", + "x-ms-user-agent": getUserAgent() }, }); @@ -211,6 +213,7 @@ describe("remoteSaveProvider", () => { accept: "application/json", "OData-MaxVersion": "4.0", "OData-Version": "4.0", + "x-ms-user-agent": getUserAgent() }, }; const fetchCalls = _mockFetch.getCalls(); @@ -288,6 +291,7 @@ describe("remoteSaveProvider", () => { accept: "application/json", "OData-MaxVersion": "4.0", "OData-Version": "4.0", + "x-ms-user-agent": getUserAgent() }, }); assert.calledOnce(getColumnContent); @@ -377,7 +381,8 @@ describe("remoteSaveProvider", () => { accept: "application/json", "OData-MaxVersion": "4.0", "OData-Version": "4.0", - 'x-ms-file-name': 'testfilename' + 'x-ms-file-name': 'testfilename', + "x-ms-user-agent": getUserAgent() }, } );