diff --git a/l10n/bundle.l10n.json b/l10n/bundle.l10n.json
index c08f1ed2..6aba81ae 100644
--- a/l10n/bundle.l10n.json
+++ b/l10n/bundle.l10n.json
@@ -7,7 +7,9 @@
"You are editing a live, public site ": "You are editing a live, public site ",
"Preview site": "Preview site",
"Open in Power Pages studio": "Open in Power Pages studio",
+ "Preview site URL is not available": "Preview site URL is not available",
"Opening preview site...": "Opening preview site...",
+ "Power Pages studio URL is not available": "Power Pages studio URL is not available",
"Microsoft wants your feeback": "Microsoft wants your feeback",
"Check the URL and verify the parameters are correct": "Check the URL and verify the parameters are correct",
"Unable to complete the request": "Unable to complete the request",
diff --git a/loc/translations-export/vscode-powerplatform.xlf b/loc/translations-export/vscode-powerplatform.xlf
index 79905571..8ff6b43c 100644
--- a/loc/translations-export/vscode-powerplatform.xlf
+++ b/loc/translations-export/vscode-powerplatform.xlf
@@ -263,6 +263,9 @@ The {3} represents Dataverse Environment's Organization ID (GUID)
{0} represents the environment name
+
+
+
{0} represents the version number
@@ -270,6 +273,9 @@ The {3} represents Dataverse Environment's Organization ID (GUID)
+
+
+
The {0} represents the profile type (Admin vs Dataverse)
diff --git a/src/client/extension.ts b/src/client/extension.ts
index 12def86a..4020da1b 100644
--- a/src/client/extension.ts
+++ b/src/client/extension.ts
@@ -193,8 +193,8 @@ export async function activate(
const orgID = orgDetails.OrgId;
const artemisResponse = await fetchArtemisResponse(orgID, _telemetry);
if (artemisResponse) {
- const { geoName } = artemisResponse[0];
- oneDSLoggerWrapper.instantiate(geoName);
+ const { geoName, geoLongName } = artemisResponse[0];
+ oneDSLoggerWrapper.instantiate(geoName, geoLongName);
oneDSLoggerWrapper.getLogger().traceInfo(telemetryEventNames.DESKTOP_EXTENSION_INIT_CONTEXT, {...orgDetails, orgGeo: geoName});
}
})
diff --git a/src/common/ArtemisService.ts b/src/common/ArtemisService.ts
index c4656a6a..777f3c35 100644
--- a/src/common/ArtemisService.ts
+++ b/src/common/ArtemisService.ts
@@ -32,8 +32,8 @@ export async function getIntelligenceEndpoint(orgId: string, telemetry: ITelemet
// Function to fetch Artemis response
export async function fetchArtemisResponse(orgId: string, telemetry: ITelemetry, sessionID = '') {
- const { tstUrl, preprodUrl, prodUrl } = convertGuidToUrls(orgId);
- const endpoints = [tstUrl, preprodUrl, prodUrl];
+ const { tstUrl, preprodUrl, prodUrl, gccUrl, highUrl, mooncakeUrl, dodUrl } = convertGuidToUrls(orgId);
+ const endpoints = [tstUrl, preprodUrl, prodUrl, gccUrl, highUrl, mooncakeUrl, dodUrl];
const artemisResponse = await fetchIslandInfo(endpoints, telemetry, sessionID);
@@ -87,10 +87,18 @@ export function convertGuidToUrls(orgId: string) {
const tstUrl = `https://${domain}.${nonProdSegment}.organization.api.test.powerplatform.com/gateway/cluster?api-version=1`;
const preprodUrl = `https://${domain}.${nonProdSegment}.organization.api.preprod.powerplatform.com/gateway/cluster?api-version=1`;
const prodUrl = `https://${domainProd}.${prodSegment}.organization.api.powerplatform.com/gateway/cluster?api-version=1`;
+ const gccUrl = `https://${domain}.${nonProdSegment}.organization.api.gov.powerplatform.microsoft.us/gateway/cluster?api-version=1`;
+ const highUrl = `https://${domain}.${nonProdSegment}.organization.api.high.powerplatform.microsoft.us/gateway/cluster?api-version=1`;
+ const mooncakeUrl = `https://${domain}.${nonProdSegment}.organization.api.powerplatform.partner.microsoftonline.cn/gateway/cluster?app-version=1`;
+ const dodUrl = `https://${domain}.${nonProdSegment}.organization.api.appsplatform.us/gateway/cluster?app-version=1`;
return {
tstUrl,
preprodUrl,
- prodUrl
+ prodUrl,
+ gccUrl,
+ highUrl,
+ mooncakeUrl,
+ dodUrl
};
}
diff --git a/src/web/client/common/authenticationProvider.ts b/src/common/AuthenticationProvider.ts
similarity index 75%
rename from src/web/client/common/authenticationProvider.ts
rename to src/common/AuthenticationProvider.ts
index 24837e98..8a1ff646 100644
--- a/src/web/client/common/authenticationProvider.ts
+++ b/src/common/AuthenticationProvider.ts
@@ -4,8 +4,6 @@
*/
import * as vscode from "vscode";
-import WebExtensionContext from "../WebExtensionContext";
-import { telemetryEventNames } from "../telemetry/constants";
import {
INTELLIGENCE_SCOPE_DEFAULT,
PROVIDER_ID,
@@ -13,12 +11,22 @@ import {
SCOPE_OPTION_DEFAULT,
SCOPE_OPTION_OFFLINE_ACCESS,
SCOPE_OPTION_USERS_READ_BASIC_ALL,
-} from "./constants";
-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";
+} from "../web/client/common/constants";
+import { showErrorDialog } from "../web/client/common/errorHandler";
+import { ITelemetry } from "../client/telemetry/ITelemetry";
+import { sendTelemetryEvent } from "./copilot/telemetry/copilotTelemetry";
+import { CopilotLoginFailureEvent, CopilotLoginSuccessEvent } from "./copilot/telemetry/telemetryConstants";
+import { getUserAgent } from "./Utils";
+import {
+ VSCODE_EXTENSION_DATAVERSE_AUTHENTICATION_COMPLETED,
+ VSCODE_EXTENSION_DATAVERSE_AUTHENTICATION_FAILED,
+ VSCODE_EXTENSION_NPS_AUTHENTICATION_COMPLETED,
+ VSCODE_EXTENSION_NPS_AUTHENTICATION_FAILED,
+ VSCODE_EXTENSION_NPS_AUTHENTICATION_STARTED,
+ VSCODE_EXTENSION_GRAPH_CLIENT_AUTHENTICATION_FAILED,
+ VSCODE_EXTENSION_GRAPH_CLIENT_AUTHENTICATION_COMPLETED
+} from "./TelemetryConstants";
+import { ERRORS } from "./ErrorConstants";
export function getCommonHeadersForDataverse(
@@ -85,6 +93,7 @@ export async function intelligenceAPIAuthentication(telemetry: ITelemetry, sessi
}
export async function dataverseAuthentication(
+ telemetry: ITelemetry,
dataverseOrgURL: string,
firstTimeAuth = false
): Promise<{ accessToken: string, userId: string }> {
@@ -119,25 +128,25 @@ export async function dataverseAuthentication(
}
if (firstTimeAuth) {
- WebExtensionContext.telemetry.sendInfoTelemetry(
- telemetryEventNames.WEB_EXTENSION_DATAVERSE_AUTHENTICATION_COMPLETED,
+ sendTelemetryEvent(telemetry,
{
+ eventName: VSCODE_EXTENSION_DATAVERSE_AUTHENTICATION_COMPLETED,
userId: userId
}
);
}
} catch (error) {
- const authError = (error as Error)?.message;
showErrorDialog(
vscode.l10n.t(
"Authorization Failed. Please run again to authorize it"
),
vscode.l10n.t("There was a permissions problem with the server")
);
- WebExtensionContext.telemetry.sendErrorTelemetry(
- telemetryEventNames.WEB_EXTENSION_DATAVERSE_AUTHENTICATION_FAILED,
- dataverseAuthentication.name,
- authError
+ sendTelemetryEvent(
+ telemetry, {
+ eventName: VSCODE_EXTENSION_DATAVERSE_AUTHENTICATION_FAILED,
+ error: error as Error
+ }
);
}
@@ -145,11 +154,12 @@ export async function dataverseAuthentication(
}
export async function npsAuthentication(
+ telemetry: ITelemetry,
cesSurveyAuthorizationEndpoint: string
): Promise {
let accessToken = "";
- WebExtensionContext.telemetry.sendInfoTelemetry(
- telemetryEventNames.NPS_AUTHENTICATION_STARTED
+ sendTelemetryEvent(telemetry,
+ { eventName: VSCODE_EXTENSION_NPS_AUTHENTICATION_STARTED }
);
try {
const session = await vscode.authentication.getSession(
@@ -161,21 +171,22 @@ export async function npsAuthentication(
if (!accessToken) {
throw new Error(ERRORS.NO_ACCESS_TOKEN);
}
- WebExtensionContext.telemetry.sendInfoTelemetry(
- telemetryEventNames.NPS_AUTHENTICATION_COMPLETED
+ sendTelemetryEvent(telemetry,
+ { eventName: VSCODE_EXTENSION_NPS_AUTHENTICATION_COMPLETED }
);
} catch (error) {
- const authError = (error as Error)?.message;
showErrorDialog(
vscode.l10n.t(
"Authorization Failed. Please run again to authorize it"
),
vscode.l10n.t("There was a permissions problem with the server")
);
- WebExtensionContext.telemetry.sendErrorTelemetry(
- telemetryEventNames.NPS_AUTHENTICATION_FAILED,
- npsAuthentication.name,
- authError
+ sendTelemetryEvent(
+ telemetry,
+ {
+ eventName: VSCODE_EXTENSION_NPS_AUTHENTICATION_FAILED,
+ error: error as Error
+ }
);
}
@@ -183,6 +194,7 @@ export async function npsAuthentication(
}
export async function graphClientAuthentication(
+ telemetry: ITelemetry,
firstTimeAuth = false
): Promise {
let accessToken = "";
@@ -213,29 +225,24 @@ export async function graphClientAuthentication(
}
if (firstTimeAuth) {
- WebExtensionContext.telemetry.sendInfoTelemetry(
- telemetryEventNames.WEB_EXTENSION_GRAPH_CLIENT_AUTHENTICATION_COMPLETED,
- {
- userId:
- session?.account.id.split("/").pop() ??
- session?.account.id ??
- "",
- }
- );
+ sendTelemetryEvent(telemetry, {
+ eventName: VSCODE_EXTENSION_GRAPH_CLIENT_AUTHENTICATION_COMPLETED,
+ userId:
+ session?.account.id.split("/").pop() ??
+ session?.account.id ??
+ "",
+ });
}
} catch (error) {
- const authError = (error as Error)?.message;
showErrorDialog(
vscode.l10n.t(
"Authorization Failed. Please run again to authorize it"
),
vscode.l10n.t("There was a permissions problem with the server")
);
- WebExtensionContext.telemetry.sendErrorTelemetry(
- telemetryEventNames.WEB_EXTENSION_GRAPH_CLIENT_AUTHENTICATION_FAILED,
- graphClientAuthentication.name,
- authError
- );
+ sendTelemetryEvent(telemetry,
+ { eventName: VSCODE_EXTENSION_GRAPH_CLIENT_AUTHENTICATION_FAILED, error: error as Error }
+ )
}
return accessToken;
diff --git a/src/common/ErrorConstants.ts b/src/common/ErrorConstants.ts
new file mode 100644
index 00000000..92bace75
--- /dev/null
+++ b/src/common/ErrorConstants.ts
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ */
+
+
+export const ERRORS = {
+ SUBURI_EMPTY: "SubURI value for entity file is empty",
+ NO_ACCESS_TOKEN: "No access token was created",
+ PORTAL_FOLDER_NAME_EMPTY: "portalFolderName value for entity file is empty",
+ ATTRIBUTES_EMPTY: "Entity file attribute or extension field empty",
+ WORKSPACE_INITIAL_LOAD: "There was a problem opening the workspace",
+ WORKSPACE_INITIAL_LOAD_DESC: "Try refreshing the browser",
+ UNKNOWN_APP: "Unable to find that app",
+ AUTHORIZATION_FAILED:
+ "Authorization Failed. Please run again to authorize it",
+ FILE_NOT_FOUND: "The file was not found",
+ RETRY_LIMIT_EXCEEDED: "Unable to complete that operation",
+ RETRY_LIMIT_EXCEEDED_DESC: "You've exceeded the retry limit ",
+ PRECONDITION_CHECK_FAILED: "The precondition check did not work",
+ PRECONDITION_CHECK_FAILED_DESC: "Try again",
+ SERVER_ERROR_RETRY_LATER: "There was a problem with the server",
+ SERVER_ERROR_RETRY_LATER_DESC: "Please try again in a minute or two",
+ SERVER_ERROR_PERMISSION_DENIED:
+ "There was a permissions problem with the server",
+ SERVER_ERROR_PERMISSION_DENIED_DESC: "Please try again in a minute or two",
+ EMPTY_RESPONSE: "There was no response",
+ EMPTY_RESPONSE_DESC: "Try again",
+ THRESHOLD_LIMIT_EXCEEDED: "Threshold for dataverse api",
+ THRESHOLD_LIMIT_EXCEEDED_DESC:
+ "You’ve exceeded the threshold rate limit for the Dataverse API",
+ BAD_REQUEST: "Unable to complete the request",
+ BAD_REQUEST_DESC:
+ "One or more attribute names have been changed or removed. Contact your admin.",
+ BACKEND_ERROR: "There’s a problem on the back end",
+ SERVICE_UNAVAILABLE: "There’s a problem connecting to Dataverse",
+ SERVICE_ERROR: "There’s a problem connecting to Dataverse",
+ INVALID_ARGUMENT: "One or more commands are invalid or malformed",
+ BACKEND_ERROR_DESC: "Try again",
+ SERVICE_UNAVAILABLE_DESC: "Try again",
+ SERVICE_ERROR_DESC: "Try again",
+ INVALID_ARGUMENT_DESC: "Check the parameters and try again",
+ MANDATORY_PARAMETERS_NULL: "The workspace is not available ",
+ MANDATORY_PARAMETERS_NULL_DESC:
+ "Check the URL and verify the parameters are correct",
+ FILE_NAME_NOT_SET: "That file is not available",
+ FILE_NAME_NOT_SET_DESC:
+ "The metadata may have changed in the Dataverse side. Contact your admin. {message_attribute}",
+ FILE_NAME_EMPTY: "File name is empty",
+ FILE_ID_EMPTY: "File ID is empty",
+ LANGUAGE_CODE_ID_VALUE_NULL: "Language code ID is empty",
+ LANGUAGE_CODE_EMPTY: "Language code is empty",
+ BULKHEAD_LIMITS_EXCEEDED: "Bulkhead queue limits exceeded",
+ NPS_FAILED_AUTH: "Failed to authenticate with NPS"
+};
diff --git a/src/common/OneDSLoggerTelemetry/oneDSLogger.ts b/src/common/OneDSLoggerTelemetry/oneDSLogger.ts
index b35d2d04..6ede8564 100644
--- a/src/common/OneDSLoggerTelemetry/oneDSLogger.ts
+++ b/src/common/OneDSLoggerTelemetry/oneDSLogger.ts
@@ -83,7 +83,7 @@ export class OneDSLogger implements ITelemetryLogger {
},
};
- public constructor(geo?: string) {
+ public constructor(geo?:string, geoLongName?:string ) {
this.appInsightsCore = new AppInsightsCore();
this.postChannel = new PostChannel();
@@ -93,7 +93,7 @@ export class OneDSLogger implements ITelemetryLogger {
httpXHROverride: this.fetchHttpXHROverride,
};
- const instrumentationSetting: IInstrumentationSettings = OneDSLogger.getInstrumentationSettings(geo); // Need to replace with actual data
+ const instrumentationSetting : IInstrumentationSettings= OneDSLogger.getInstrumentationSettings(geo, geoLongName); // Need to replace with actual data
// Configure App insights core to send to collector
const coreConfig: IExtendedConfiguration = {
@@ -136,12 +136,26 @@ export class OneDSLogger implements ITelemetryLogger {
}
}
- private static getInstrumentationSettings(geo?: string): IInstrumentationSettings {
- const buildRegion: string = region;
- const instrumentationSettings: IInstrumentationSettings = {
+ private static getInstrumentationSettings(geo?:string, geoLongName?: string): IInstrumentationSettings {
+ const buildRegion:string = region;
+ const instrumentationSettings:IInstrumentationSettings = {
endpointURL: 'https://self.pipe.aria.int.microsoft.com/OneCollector/1.0/',
instrumentationKey: 'ffdb4c99ca3a4ad5b8e9ffb08bf7da0d-65357ff3-efcd-47fc-b2fd-ad95a52373f4-7402'
};
+ switch(geoLongName){
+ case 'usgov':
+ geo = 'gov';
+ break;
+ case 'usgovhigh':
+ geo = 'high';
+ break;
+ case 'usdod':
+ geo = 'dod';
+ break;
+ case 'china':
+ geo = 'mooncake';
+ break;
+ }
switch (buildRegion) {
case 'tie':
case 'test':
@@ -161,7 +175,7 @@ export class OneDSLogger implements ITelemetryLogger {
case 'ae':
case 'kr':
instrumentationSettings.endpointURL = 'https://us-mobile.events.data.microsoft.com/OneCollector/1.0/',
- instrumentationSettings.instrumentationKey = '197418c5cb8c4426b201f9db2e87b914-87887378-2790-49b0-9295-51f43b6204b1-7172'
+ instrumentationSettings.instrumentationKey = '197418c5cb8c4426b201f9db2e87b914-87887378-2790-49b0-9295-51f43b6204b1-7172'
break;
case 'eu':
case 'uk':
@@ -170,21 +184,30 @@ export class OneDSLogger implements ITelemetryLogger {
case 'no':
case 'ch':
instrumentationSettings.endpointURL = 'https://eu-mobile.events.data.microsoft.com/OneCollector/1.0/',
- instrumentationSettings.instrumentationKey = '197418c5cb8c4426b201f9db2e87b914-87887378-2790-49b0-9295-51f43b6204b1-7172'
+ instrumentationSettings.instrumentationKey = '197418c5cb8c4426b201f9db2e87b914-87887378-2790-49b0-9295-51f43b6204b1-7172'
+ break;
+ case 'gov':
+ instrumentationSettings.endpointURL = 'https://tb.events.data.microsoft.com/OneCollector/1.0/',
+ instrumentationSettings.instrumentationKey = '2f217cb8f40440eeb8b0aa80a2be2f7e-e0ec7b51-d1bb-4d8c-83b1-cc77aaba9009-7472'
+ break;
+ case 'high':
+ instrumentationSettings.endpointURL = 'https://tb.events.data.microsoft.com/OneCollector/1.0/',
+ instrumentationSettings.instrumentationKey = '4a07e143372c46aabf3841dc4f0ef795-a753031e-2005-4282-9451-a086fea4234a-6942'
+ break;
+ case 'dod':
+ instrumentationSettings.endpointURL = 'https://pf.events.data.microsoft.com/OneCollector/1.0/',
+ instrumentationSettings.instrumentationKey = 'af47f3d608774379a53fa07cf36362ea-69701588-1aad-43ee-8b52-f71125849774-6656'
+ break;
+ case 'mooncake':
+ instrumentationSettings.endpointURL = 'https://collector.azure.cn/OneCollector/1.0/',
+ instrumentationSettings.instrumentationKey = 'f9b6e63b5e394453ba8f58f7a7b9aea7-f38fcfa2-eb34-48bc-9ae2-61fba4abbd39-7390' //prod key;
break;
default:
instrumentationSettings.endpointURL = 'https://us-mobile.events.data.microsoft.com/OneCollector/1.0/',
- instrumentationSettings.instrumentationKey = '197418c5cb8c4426b201f9db2e87b914-87887378-2790-49b0-9295-51f43b6204b1-7172'
+ instrumentationSettings.instrumentationKey = '197418c5cb8c4426b201f9db2e87b914-87887378-2790-49b0-9295-51f43b6204b1-7172'
break;
}
break;
- case 'gov':
- case 'high':
- case 'dod':
- case 'mooncake':
- instrumentationSettings.endpointURL = '',
- instrumentationSettings.instrumentationKey = '' //prod key;
- break;
case 'ex':
case 'rx':
default:
diff --git a/src/common/OneDSLoggerTelemetry/oneDSLoggerWrapper.ts b/src/common/OneDSLoggerTelemetry/oneDSLoggerWrapper.ts
index 6a85c269..47c034e9 100644
--- a/src/common/OneDSLoggerTelemetry/oneDSLoggerWrapper.ts
+++ b/src/common/OneDSLoggerTelemetry/oneDSLoggerWrapper.ts
@@ -14,8 +14,8 @@ export class oneDSLoggerWrapper {
private static instance: oneDSLoggerWrapper;
private static oneDSLoggerIntance: OneDSLogger;
- private constructor(geo?: string) {
- oneDSLoggerWrapper.oneDSLoggerIntance = new OneDSLogger(geo);
+ private constructor(geo?: string, geoLongName?: string) {
+ oneDSLoggerWrapper.oneDSLoggerIntance = new OneDSLogger(geo, geoLongName);
}
@@ -23,8 +23,8 @@ export class oneDSLoggerWrapper {
return this.instance;
}
- static instantiate(geo?: string) {
- oneDSLoggerWrapper.instance = new oneDSLoggerWrapper(geo);
+ static instantiate(geo?:string, geoLongName?: string){
+ oneDSLoggerWrapper.instance = new oneDSLoggerWrapper(geo, geoLongName);
}
/// Trace info log
diff --git a/src/common/TelemetryConstants.ts b/src/common/TelemetryConstants.ts
new file mode 100644
index 00000000..6cc6353f
--- /dev/null
+++ b/src/common/TelemetryConstants.ts
@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ */
+
+// Telemetry Event Names
+export const VSCODE_EXTENSION_DATAVERSE_AUTHENTICATION_STARTED = "VSCodeExtensionDataVerseAuthenticationStarted";
+export const VSCODE_EXTENSION_DATAVERSE_AUTHENTICATION_FAILED = "VSCodeExtensionDataVerseAuthenticationFailed";
+export const VSCODE_EXTENSION_DATAVERSE_AUTHENTICATION_MISSING = "VSCodeExtensionDataVerseAuthenticationMissing";
+export const VSCODE_EXTENSION_DATAVERSE_AUTHENTICATION_COMPLETED = "VSCodeExtensionDataVerseAuthenticationCompleted";
+export const VSCODE_EXTENSION_NPS_AUTHENTICATION_STARTED = "VSCodeExtensionNPSAuthenticationStarted";
+export const VSCODE_EXTENSION_NPS_AUTHENTICATION_COMPLETED = "VSCodeExtensionNPSAuthenticationCompleted";
+export const VSCODE_EXTENSION_NPS_AUTHENTICATION_FAILED = "VSCodeExtensionNPSAuthenticationFailed";
+export const VSCODE_EXTENSION_GRAPH_CLIENT_AUTHENTICATION_FAILED = "VSCodeExtensionGraphClientAuthenticationFailed";
+export const VSCODE_EXTENSION_GRAPH_CLIENT_AUTHENTICATION_COMPLETED = "VSCodeExtensionGraphClientAuthenticationCompleted";
diff --git a/src/common/Utils.ts b/src/common/Utils.ts
index 8ed0aa12..0eddc9e2 100644
--- a/src/common/Utils.ts
+++ b/src/common/Utils.ts
@@ -7,6 +7,8 @@
import * as vscode from "vscode";
import { EXTENSION_ID, EXTENSION_NAME, SETTINGS_EXPERIMENTAL_STORE_NAME } from "../client/constants";
import { CUSTOM_TELEMETRY_FOR_POWER_PAGES_SETTING_NAME } from "./OneDSLoggerTelemetry/telemetryConstants";
+import { PacWrapper } from "../client/pac/PacWrapper";
+import { AUTH_CREATE_FAILED, AUTH_CREATE_MESSAGE, PAC_SUCCESS } from "./copilot/constants";
export function getSelectedCode(editor: vscode.TextEditor): string {
if (!editor) {
@@ -131,4 +133,22 @@ export function getUserAgent(): string {
.replace("{product}", EXTENSION_NAME)
.replace("{product-version}", getExtensionVersion())
.replace("{comment}", "(" + getExtensionType()+'; )');
-}
\ No newline at end of file
+}
+
+export async function createAuthProfileExp(pacWrapper: PacWrapper | undefined) {
+ const userOrgUrl = await showInputBoxAndGetOrgUrl();
+ if (!userOrgUrl) {
+ return;
+ }
+
+ if(!pacWrapper){
+ vscode.window.showErrorMessage(AUTH_CREATE_FAILED);
+ return;
+ }
+
+ const pacAuthCreateOutput = await showProgressWithNotification(vscode.l10n.t(AUTH_CREATE_MESSAGE), async () => { return await pacWrapper?.authCreateNewAuthProfileForOrg(userOrgUrl) });
+ if (pacAuthCreateOutput && pacAuthCreateOutput.Status !== PAC_SUCCESS) {
+ vscode.window.showErrorMessage(AUTH_CREATE_FAILED);
+ return;
+ }
+}
diff --git a/src/common/copilot/PowerPagesCopilot.ts b/src/common/copilot/PowerPagesCopilot.ts
index 6893cbcd..e5049451 100644
--- a/src/common/copilot/PowerPagesCopilot.ts
+++ b/src/common/copilot/PowerPagesCopilot.ts
@@ -6,13 +6,13 @@
import * as vscode from "vscode";
import { sendApiRequest } from "./IntelligenceApiService";
-import { dataverseAuthentication, intelligenceAPIAuthentication } from "../../web/client/common/authenticationProvider";
+import { dataverseAuthentication, intelligenceAPIAuthentication } from "../AuthenticationProvider";
import { v4 as uuidv4 } from 'uuid'
import { PacWrapper } from "../../client/pac/PacWrapper";
import { ITelemetry } from "../../client/telemetry/ITelemetry";
import { ADX_ENTITYFORM, ADX_ENTITYLIST, AUTH_CREATE_FAILED, AUTH_CREATE_MESSAGE, AuthProfileNotFound, COPILOT_UNAVAILABLE, CopilotDisclaimer, CopilotStylePathSegments, DataverseEntityNameMap, EXPLAIN_CODE, EntityFieldMap, FieldTypeMap, PAC_SUCCESS, SELECTED_CODE_INFO, SELECTED_CODE_INFO_ENABLED, THUMBS_DOWN, THUMBS_UP, UserPrompt, WebViewMessage, sendIconSvg } from "./constants";
import { IActiveFileParams, IActiveFileData, IOrgInfo } from './model';
-import { escapeDollarSign, getLastThreePartsOfFileName, getNonce, getSelectedCode, getSelectedCodeLineRange, getUserName, openWalkthrough, showConnectedOrgMessage, showInputBoxAndGetOrgUrl, showProgressWithNotification } from "../Utils";
+import { createAuthProfileExp, escapeDollarSign, getLastThreePartsOfFileName, getNonce, getSelectedCode, getSelectedCodeLineRange, getUserName, openWalkthrough, showConnectedOrgMessage, showInputBoxAndGetOrgUrl, showProgressWithNotification } from "../Utils";
import { CESUserFeedback } from "./user-feedback/CESSurvey";
import { ActiveOrgOutput } from "../../client/pac/PacTypes";
import { CopilotWalkthroughEvent, CopilotCopyCodeToClipboardEvent, CopilotInsertCodeToEditorEvent, CopilotLoadedEvent, CopilotOrgChangedEvent, CopilotUserFeedbackThumbsDownEvent, CopilotUserFeedbackThumbsUpEvent, CopilotUserPromptedEvent, CopilotCodeLineCountEvent, CopilotClearChatEvent, CopilotNotAvailable, CopilotExplainCode, CopilotExplainCodeSize, CopilotNotAvailableECSConfig } from "./telemetry/telemetryConstants";
@@ -109,7 +109,7 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider {
);
this._disposables.push(
- orgChangeErrorEvent(async () => await this.createAuthProfileExp())
+ orgChangeErrorEvent(async () => await createAuthProfileExp(this._pacWrapper))
);
if (orgInfo) {
@@ -131,19 +131,7 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider {
if (pacOutput && pacOutput.Status === PAC_SUCCESS) {
this.handleOrgChangeSuccess(pacOutput.Results);
} else if (this._view?.visible) {
- await this.createAuthProfileExp();
- }
- }
-
- private async createAuthProfileExp() {
- const userOrgUrl = await showInputBoxAndGetOrgUrl();
- if (!userOrgUrl) {
- return;
- }
- const pacAuthCreateOutput = await showProgressWithNotification(vscode.l10n.t(AUTH_CREATE_MESSAGE), async () => { return await this._pacWrapper?.authCreateNewAuthProfileForOrg(userOrgUrl) });
- if (pacAuthCreateOutput && pacAuthCreateOutput.Status !== PAC_SUCCESS) {
- vscode.window.showErrorMessage(AUTH_CREATE_FAILED);
- return;
+ await createAuthProfileExp(this._pacWrapper)
}
}
@@ -373,7 +361,7 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider {
if (activeFileParams.dataverseEntity == ADX_ENTITYFORM || activeFileParams.dataverseEntity == ADX_ENTITYLIST) {
metadataInfo = await getEntityName(telemetry, sessionID, activeFileParams.dataverseEntity);
- const dataverseToken = (await dataverseAuthentication(activeOrgUrl, true)).accessToken;
+ const dataverseToken = (await dataverseAuthentication(telemetry, activeOrgUrl, true)).accessToken;
if (activeFileParams.dataverseEntity == ADX_ENTITYFORM) {
const formColumns = await getFormXml(metadataInfo.entityName, metadataInfo.formName, activeOrgUrl, dataverseToken, telemetry, sessionID);
diff --git a/src/common/copilot/telemetry/ITelemetry.ts b/src/common/copilot/telemetry/ITelemetry.ts
index 8702df1e..ee5a327e 100644
--- a/src/common/copilot/telemetry/ITelemetry.ts
+++ b/src/common/copilot/telemetry/ITelemetry.ts
@@ -7,7 +7,7 @@ export interface IProDevCopilotTelemetryData {
eventName: string,
durationInMills?: number,
exception?: Error,
- copilotSessionId: string,
+ copilotSessionId?: string,
orgId?: string,
FeedbackId?: string
error?: Error,
@@ -21,4 +21,6 @@ export interface IProDevCopilotTelemetryData {
tokenSize?: string
isSuggestedPrompt?: string;
subScenario?: string;
+ userId?: string;
+ errorMsg?: string;
}
diff --git a/src/common/copilot/telemetry/telemetryConstants.ts b/src/common/copilot/telemetry/telemetryConstants.ts
index c9723806..9baddb0d 100644
--- a/src/common/copilot/telemetry/telemetryConstants.ts
+++ b/src/common/copilot/telemetry/telemetryConstants.ts
@@ -36,3 +36,4 @@ export const CopilotNotAvailable = 'CopilotNotAvailable';
export const CopilotNotAvailableECSConfig = 'CopilotNotAvailableECSConfig';
export const CopilotExplainCode = 'CopilotExplainCode';
export const CopilotExplainCodeSize = 'CopilotExplainCodeSize';
+export const CopilotNpsAuthenticationCompleted = "CopilotNpsAuthenticationCompleted";
diff --git a/src/common/copilot/user-feedback/CESSurvey.ts b/src/common/copilot/user-feedback/CESSurvey.ts
index 2b2a4dd9..025315ed 100644
--- a/src/common/copilot/user-feedback/CESSurvey.ts
+++ b/src/common/copilot/user-feedback/CESSurvey.ts
@@ -4,20 +4,21 @@
*/
import * as vscode from "vscode";
-import { npsAuthentication } from "../../../web/client/common/authenticationProvider";
+import { npsAuthentication } from "../../AuthenticationProvider";
import { SurveyConstants } from "../../../web/client/common/constants";
import fetch from "node-fetch";
import { getNonce } from "../../Utils";
import { ITelemetry } from "../../../client/telemetry/ITelemetry";
-import { CopilotUserFeedbackFailureEvent, CopilotUserFeedbackSuccessEvent } from "../telemetry/telemetryConstants";
+import { CopilotNpsAuthenticationCompleted, CopilotUserFeedbackFailureEvent, CopilotUserFeedbackSuccessEvent } from "../telemetry/telemetryConstants";
import { sendTelemetryEvent } from "../telemetry/copilotTelemetry";
import { IFeedbackData } from "../model";
import { EUROPE_GEO, UK_GEO } from "../constants";
+import { ERRORS } from "../../ErrorConstants";
let feedbackPanel: vscode.WebviewPanel | undefined;
-export async function CESUserFeedback(context: vscode.ExtensionContext, sessionId: string, userID: string, thumbType: string, telemetry: ITelemetry, geoName: string, messageScenario: string, tenantId?: string) {
+export async function CESUserFeedback(context: vscode.ExtensionContext, sessionId: string, userID: string, thumbType: string, telemetry: ITelemetry, geoName: string, messageScenario: string, tenantId?: string) {
if (feedbackPanel) {
feedbackPanel.dispose();
@@ -35,7 +36,13 @@ export async function CESUserFeedback(context: vscode.ExtensionContext, sessionI
const feedbackData = initializeFeedbackData(sessionId, vscode.env.uiKind === vscode.UIKind.Web, geoName, messageScenario, tenantId);
- const apiToken: string = await npsAuthentication(SurveyConstants.AUTHORIZATION_ENDPOINT);
+ const apiToken: string = await npsAuthentication(telemetry, SurveyConstants.AUTHORIZATION_ENDPOINT);
+
+ if (apiToken) {
+ sendTelemetryEvent(telemetry, { eventName: CopilotNpsAuthenticationCompleted, feedbackType: thumbType, copilotSessionId: sessionId });
+ } else {
+ sendTelemetryEvent(telemetry, { eventName: CopilotUserFeedbackFailureEvent, feedbackType: thumbType, copilotSessionId: sessionId, error: new Error(ERRORS.NPS_FAILED_AUTH) });
+ }
const endpointUrl = useEUEndpoint(geoName) ? `https://europe.ces.microsoftcloud.com/api/v1/portalsdesigner/Surveys/powerpageschatgpt/Feedbacks?userId=${userID}` :
`https://world.ces.microsoftcloud.com/api/v1/portalsdesigner/Surveys/powerpageschatgpt/Feedbacks?userId=${userID}`;
@@ -79,7 +86,7 @@ function getWebviewURIs(context: vscode.ExtensionContext, feedbackPanel: vscode.
return { feedbackCssUri, feedbackJsUri };
}
-function initializeFeedbackData(sessionId: string, isWebExtension: boolean, geoName: string, messageScenario: string, tenantId?: string): IFeedbackData {
+function initializeFeedbackData(sessionId: string, isWebExtension: boolean, geoName: string, messageScenario: string, tenantId?: string): IFeedbackData {
const feedbackData: IFeedbackData = {
TenantId: tenantId ? tenantId : '',
Geo: geoName,
diff --git a/src/web/client/WebExtensionContext.ts b/src/web/client/WebExtensionContext.ts
index 13778f90..b3166d48 100644
--- a/src/web/client/WebExtensionContext.ts
+++ b/src/web/client/WebExtensionContext.ts
@@ -7,7 +7,7 @@ import * as vscode from "vscode";
import {
dataverseAuthentication,
getCommonHeadersForDataverse,
-} from "./common/authenticationProvider";
+} from "../../common/AuthenticationProvider";
import * as Constants from "./common/constants";
import {
getDataSourcePropertiesMap,
@@ -371,6 +371,7 @@ class WebExtensionContext implements IWebExtensionContext {
Constants.queryParameters.ORG_URL
) as string;
const { accessToken, userId } = await dataverseAuthentication(
+ this._telemetry.getTelemetryReporter(),
dataverseOrgUrl,
firstTimeAuth
);
@@ -396,6 +397,15 @@ class WebExtensionContext implements IWebExtensionContext {
this._dataverseAccessToken = accessToken;
this._userId = userId;
+
+ if (firstTimeAuth) {
+ this._telemetry.sendInfoTelemetry(
+ telemetryEventNames.WEB_EXTENSION_DATAVERSE_AUTHENTICATION_COMPLETED,
+ {
+ userId: userId
+ }
+ );
+ }
}
public async updateFileDetailsInContext(
diff --git a/src/web/client/common/errorHandler.ts b/src/web/client/common/errorHandler.ts
index 5e2658a9..b209d2d1 100644
--- a/src/web/client/common/errorHandler.ts
+++ b/src/web/client/common/errorHandler.ts
@@ -10,55 +10,6 @@ import { telemetryEventNames } from "../telemetry/constants";
import { PORTALS_FOLDER_NAME_DEFAULT, queryParameters } from "./constants";
import { isMultifileEnabled } from "../utilities/commonUtil";
-export const ERRORS = {
- SUBURI_EMPTY: "SubURI value for entity file is empty",
- NO_ACCESS_TOKEN: "No access token was created",
- PORTAL_FOLDER_NAME_EMPTY: "portalFolderName value for entity file is empty",
- ATTRIBUTES_EMPTY: "Entity file attribute or extension field empty",
- WORKSPACE_INITIAL_LOAD: "There was a problem opening the workspace",
- WORKSPACE_INITIAL_LOAD_DESC: "Try refreshing the browser",
- UNKNOWN_APP: "Unable to find that app",
- AUTHORIZATION_FAILED:
- "Authorization Failed. Please run again to authorize it",
- FILE_NOT_FOUND: "The file was not found",
- RETRY_LIMIT_EXCEEDED: "Unable to complete that operation",
- RETRY_LIMIT_EXCEEDED_DESC: "You've exceeded the retry limit ",
- PRECONDITION_CHECK_FAILED: "The precondition check did not work",
- PRECONDITION_CHECK_FAILED_DESC: "Try again",
- SERVER_ERROR_RETRY_LATER: "There was a problem with the server",
- SERVER_ERROR_RETRY_LATER_DESC: "Please try again in a minute or two",
- SERVER_ERROR_PERMISSION_DENIED:
- "There was a permissions problem with the server",
- SERVER_ERROR_PERMISSION_DENIED_DESC: "Please try again in a minute or two",
- EMPTY_RESPONSE: "There was no response",
- EMPTY_RESPONSE_DESC: "Try again",
- THRESHOLD_LIMIT_EXCEEDED: "Threshold for dataverse api",
- THRESHOLD_LIMIT_EXCEEDED_DESC:
- "You’ve exceeded the threshold rate limit for the Dataverse API",
- BAD_REQUEST: "Unable to complete the request",
- BAD_REQUEST_DESC:
- "One or more attribute names have been changed or removed. Contact your admin.",
- BACKEND_ERROR: "There’s a problem on the back end",
- SERVICE_UNAVAILABLE: "There’s a problem connecting to Dataverse",
- SERVICE_ERROR: "There’s a problem connecting to Dataverse",
- INVALID_ARGUMENT: "One or more commands are invalid or malformed",
- BACKEND_ERROR_DESC: "Try again",
- SERVICE_UNAVAILABLE_DESC: "Try again",
- SERVICE_ERROR_DESC: "Try again",
- INVALID_ARGUMENT_DESC: "Check the parameters and try again",
- MANDATORY_PARAMETERS_NULL: "The workspace is not available ",
- MANDATORY_PARAMETERS_NULL_DESC:
- "Check the URL and verify the parameters are correct",
- FILE_NAME_NOT_SET: "That file is not available",
- FILE_NAME_NOT_SET_DESC:
- "The metadata may have changed in the Dataverse side. Contact your admin. {message_attribute}",
- FILE_NAME_EMPTY: "File name is empty",
- FILE_ID_EMPTY: "File ID is empty",
- LANGUAGE_CODE_ID_VALUE_NULL: "Language code ID is empty",
- LANGUAGE_CODE_EMPTY: "Language code is empty",
- BULKHEAD_LIMITS_EXCEEDED: "Bulkhead queue limits exceeded",
-};
-
export function showErrorDialog(errorString: string, detailMessage?: string) {
const options = { detail: detailMessage, modal: true };
vscode.window.showErrorMessage(errorString, options);
diff --git a/src/web/client/dal/concurrencyHandler.ts b/src/web/client/dal/concurrencyHandler.ts
index 3874b299..4bb218fc 100644
--- a/src/web/client/dal/concurrencyHandler.ts
+++ b/src/web/client/dal/concurrencyHandler.ts
@@ -6,9 +6,9 @@
import { BulkheadRejectedError, bulkhead } from 'cockatiel';
import fetch, { RequestInfo, RequestInit } from "node-fetch";
import { MAX_CONCURRENT_REQUEST_COUNT, MAX_CONCURRENT_REQUEST_QUEUE_COUNT } from '../common/constants';
-import { ERRORS } from '../common/errorHandler';
import WebExtensionContext from "../WebExtensionContext";
import { telemetryEventNames } from '../telemetry/constants';
+import { ERRORS } from '../../../common/ErrorConstants';
export class ConcurrencyHandler {
private _bulkhead = bulkhead(MAX_CONCURRENT_REQUEST_COUNT, MAX_CONCURRENT_REQUEST_QUEUE_COUNT);
@@ -32,4 +32,4 @@ export class ConcurrencyHandler {
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/web/client/dal/fileSystemProvider.ts b/src/web/client/dal/fileSystemProvider.ts
index fa96e42d..dacc62f6 100644
--- a/src/web/client/dal/fileSystemProvider.ts
+++ b/src/web/client/dal/fileSystemProvider.ts
@@ -13,7 +13,6 @@ import {
import WebExtensionContext from "../WebExtensionContext";
import { fetchDataFromDataverseAndUpdateVFS } from "./remoteFetchProvider";
import { saveData } from "./remoteSaveProvider";
-import { ERRORS } from "../common/errorHandler";
import { telemetryEventNames } from "../telemetry/constants";
import { getFolderSubUris } from "../utilities/folderHelperUtility";
import { EtagHandlerService } from "../services/etagHandlerService";
@@ -32,6 +31,7 @@ import {
} from "../utilities/fileAndEntityUtil";
import { getImageFileContent, getRangeForMultilineMatch, isImageFileSupportedForEdit, isVersionControlEnabled, updateFileContentInFileDataMap } from "../utilities/commonUtil";
import { IFileInfo, ISearchQueryMatch, ISearchQueryResults } from "../common/interfaces";
+import { ERRORS } from "../../../common/ErrorConstants";
export class File implements vscode.FileStat {
type: vscode.FileType;
diff --git a/src/web/client/dal/remoteFetchProvider.ts b/src/web/client/dal/remoteFetchProvider.ts
index 4cdbc957..99168707 100644
--- a/src/web/client/dal/remoteFetchProvider.ts
+++ b/src/web/client/dal/remoteFetchProvider.ts
@@ -15,9 +15,9 @@ import {
setFileContent,
} from "../utilities/commonUtil";
import { getCustomRequestURL, getMappingEntityContent, getMetadataInfo, getMappingEntityId, getMimeType, getRequestURL } from "../utilities/urlBuilderUtil";
-import { getCommonHeadersForDataverse } from "../common/authenticationProvider";
+import { getCommonHeadersForDataverse } from "../../../common/AuthenticationProvider";
import * as Constants from "../common/constants";
-import { ERRORS, showErrorDialog } from "../common/errorHandler";
+import { showErrorDialog } from "../common/errorHandler";
import { PortalsFS } from "./fileSystemProvider";
import {
encodeAsBase64,
@@ -32,6 +32,7 @@ import { EntityMetadataKeyCore, SchemaEntityMetadata, folderExportType, schemaEn
import { getEntityNameForExpandedEntityContent, getRequestUrlForEntities } from "../utilities/folderHelperUtility";
import { IAttributePath, IFileInfo } from "../common/interfaces";
import { portal_schema_V2 } from "../schema/portalSchema";
+import { ERRORS } from "../../../common/ErrorConstants";
export async function fetchDataFromDataverseAndUpdateVFS(
portalFs: PortalsFS,
diff --git a/src/web/client/dal/remoteSaveProvider.ts b/src/web/client/dal/remoteSaveProvider.ts
index ae108f1b..dc7db03a 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 { getCommonHeadersForDataverse } 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";
@@ -231,4 +231,4 @@ async function saveDataToDataverse(
throw error;
}
}
-}
\ No newline at end of file
+}
diff --git a/src/web/client/extension.ts b/src/web/client/extension.ts
index 2250b2c8..168d6ac4 100644
--- a/src/web/client/extension.ts
+++ b/src/web/client/extension.ts
@@ -308,6 +308,12 @@ export function processWorkspaceStateChanges(context: vscode.ExtensionContext) {
if (entityInfo.entityId && entityInfo.entityName) {
context.workspaceState.update(document.uri.fsPath, entityInfo);
WebExtensionContext.updateVscodeWorkspaceState(document.uri.fsPath, entityInfo);
+
+ if (isCoPresenceEnabled() && tab.input instanceof vscode.TabInputCustom) {
+ // sending message to webworker event listener for Co-Presence feature
+ sendingMessageToWebWorkerForCoPresence(entityInfo);
+ WebExtensionContext.quickPickProvider.refresh();
+ }
}
}
});
diff --git a/src/web/client/services/NPSService.ts b/src/web/client/services/NPSService.ts
index b4a2a928..5ee290c4 100644
--- a/src/web/client/services/NPSService.ts
+++ b/src/web/client/services/NPSService.ts
@@ -4,7 +4,7 @@
*/
import jwt_decode from 'jwt-decode';
-import { npsAuthentication } from "../common/authenticationProvider";
+import { npsAuthentication } from "../../../common/AuthenticationProvider";
import { SurveyConstants, httpMethod, queryParameters } from '../common/constants';
import { RequestInit } from 'node-fetch'
import WebExtensionContext from '../WebExtensionContext';
@@ -12,85 +12,90 @@ import { telemetryEventNames } from '../telemetry/constants';
import { getCurrentDataBoundary } from '../utilities/dataBoundary';
export class NPSService {
- public static getCesHeader(accessToken: string) {
- return {
- authorization: "Bearer " + accessToken,
- Accept: 'application/json',
- 'Content-Type': 'application/json',
- };
- }
+ public static getCesHeader(accessToken: string) {
+ return {
+ authorization: "Bearer " + accessToken,
+ Accept: 'application/json',
+ 'Content-Type': 'application/json',
+ };
+ }
- public static getNpsSurveyEndpoint(): string {
- const region = WebExtensionContext.urlParametersMap?.get(queryParameters.REGION)?.toLowerCase();
- const dataBoundary = getCurrentDataBoundary();
- let npsSurveyEndpoint = '';
- switch (region) {
- case 'tie':
- case 'test':
- case 'preprod':
- switch (dataBoundary) {
- case 'eu':
- npsSurveyEndpoint = 'https://europe.tip1.ces.microsoftcloud.com';
- break;
- default:
- npsSurveyEndpoint = 'https://world.tip1.ces.microsoftcloud.com';
- }
- break;
- case 'prod':
- case 'preview':
- switch (dataBoundary) {
- case 'eu':
- npsSurveyEndpoint = 'https://europe.ces.microsoftcloud.com';
- break;
- default:
- npsSurveyEndpoint = 'https://world.ces.microsoftcloud.com';
+ public static getNpsSurveyEndpoint(): string {
+ const region = WebExtensionContext.urlParametersMap?.get(queryParameters.REGION)?.toLowerCase();
+ const dataBoundary = getCurrentDataBoundary();
+ let npsSurveyEndpoint = '';
+ switch (region) {
+ case 'tie':
+ case 'test':
+ case 'preprod':
+ switch (dataBoundary) {
+ case 'eu':
+ npsSurveyEndpoint = 'https://europe.tip1.ces.microsoftcloud.com';
+ break;
+ default:
+ npsSurveyEndpoint = 'https://world.tip1.ces.microsoftcloud.com';
+ }
+ break;
+ case 'prod':
+ case 'preview':
+ switch (dataBoundary) {
+ case 'eu':
+ npsSurveyEndpoint = 'https://europe.ces.microsoftcloud.com';
+ break;
+ default:
+ npsSurveyEndpoint = 'https://world.ces.microsoftcloud.com';
+ }
+ break;
+ case 'gov':
+ case 'high':
+ case 'dod':
+ case 'mooncake':
+ npsSurveyEndpoint = 'https://world.ces.microsoftcloud.com';
+ break;
+ case 'ex':
+ case 'rx':
+ default:
+ break;
}
- break;
- case 'gov':
- case 'high':
- case 'dod':
- case 'mooncake':
- npsSurveyEndpoint = 'https://world.ces.microsoftcloud.com';
- break;
- case 'ex':
- case 'rx':
- default:
- break;
+
+ return npsSurveyEndpoint;
}
- return npsSurveyEndpoint;
- }
+ public static async setEligibility() {
+ try {
- public static async setEligibility() {
- try {
+ const baseApiUrl = this.getNpsSurveyEndpoint();
+ const accessToken: string = await npsAuthentication(WebExtensionContext.telemetry.getTelemetryReporter(), SurveyConstants.AUTHORIZATION_ENDPOINT);
- const baseApiUrl = this.getNpsSurveyEndpoint();
- const accessToken: string = await npsAuthentication(SurveyConstants.AUTHORIZATION_ENDPOINT);
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- const parsedToken = jwt_decode(accessToken) as any;
- WebExtensionContext.setUserId(parsedToken?.oid)
- const apiEndpoint = `${baseApiUrl}/api/v1/${SurveyConstants.TEAM_NAME}/Eligibilities/${SurveyConstants.SURVEY_NAME}?userId=${parsedToken?.oid}&eventName=${SurveyConstants.EVENT_NAME}&tenantId=${parsedToken.tid}`;
- const requestInitPost: RequestInit = {
- method: httpMethod.POST,
- body: '{}',
- headers: NPSService.getCesHeader(accessToken)
- };
- const requestSentAtTime = new Date().getTime();
- const response = await WebExtensionContext.concurrencyHandler.handleRequest(apiEndpoint, requestInitPost);
- const result = await response?.json();
- if (result?.Eligibility) {
- WebExtensionContext.telemetry.sendAPISuccessTelemetry(
- telemetryEventNames.NPS_USER_ELIGIBLE,
- "NPS Api",
- httpMethod.POST,
- new Date().getTime() - requestSentAtTime,
- this.setEligibility.name
- );
- WebExtensionContext.setNPSEligibility(true);
- WebExtensionContext.setFormsProEligibilityId(result?.FormsProEligibilityId);
- }
- } catch (error) {
- WebExtensionContext.telemetry.sendErrorTelemetry(telemetryEventNames.NPS_API_FAILED, this.setEligibility.name,(error as Error)?.message, error as Error);
+ if (accessToken) {
+ WebExtensionContext.telemetry.sendInfoTelemetry(telemetryEventNames.WEB_EXTENSION_NPS_AUTHENTICATION_COMPLETED);
+ }
+
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const parsedToken = jwt_decode(accessToken) as any;
+ WebExtensionContext.setUserId(parsedToken?.oid)
+ const apiEndpoint = `${baseApiUrl}/api/v1/${SurveyConstants.TEAM_NAME}/Eligibilities/${SurveyConstants.SURVEY_NAME}?userId=${parsedToken?.oid}&eventName=${SurveyConstants.EVENT_NAME}&tenantId=${parsedToken.tid}`;
+ const requestInitPost: RequestInit = {
+ method: httpMethod.POST,
+ body: '{}',
+ headers: NPSService.getCesHeader(accessToken)
+ };
+ const requestSentAtTime = new Date().getTime();
+ const response = await WebExtensionContext.concurrencyHandler.handleRequest(apiEndpoint, requestInitPost);
+ const result = await response?.json();
+ if (result?.Eligibility) {
+ WebExtensionContext.telemetry.sendAPISuccessTelemetry(
+ telemetryEventNames.WEB_EXTENSION_NPS_USER_ELIGIBLE,
+ "NPS Api",
+ httpMethod.POST,
+ new Date().getTime() - requestSentAtTime,
+ this.setEligibility.name
+ );
+ WebExtensionContext.setNPSEligibility(true);
+ WebExtensionContext.setFormsProEligibilityId(result?.FormsProEligibilityId);
+ }
+ } catch (error) {
+ WebExtensionContext.telemetry.sendErrorTelemetry(telemetryEventNames.WEB_EXTENSION_NPS_API_FAILED, this.setEligibility.name, (error as Error)?.message, error as Error);
+ }
}
- }
-}
\ No newline at end of file
+}
diff --git a/src/web/client/services/etagHandlerService.ts b/src/web/client/services/etagHandlerService.ts
index 5b3d298d..75178787 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 { getCommonHeadersForDataverse } 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";
diff --git a/src/web/client/services/graphClientService.ts b/src/web/client/services/graphClientService.ts
index ce6c4945..bb97a6f5 100644
--- a/src/web/client/services/graphClientService.ts
+++ b/src/web/client/services/graphClientService.ts
@@ -5,7 +5,7 @@
import path from "path";
import WebExtensionContext from "../WebExtensionContext";
-import { getCommonHeaders, graphClientAuthentication } from "../common/authenticationProvider";
+import { getCommonHeaders, graphClientAuthentication } from "../../../common/AuthenticationProvider";
import * as Constants from "../common/constants";
import { telemetryEventNames } from "../telemetry/constants";
@@ -21,7 +21,20 @@ export class GraphClientService {
}
public async graphClientAuthentication(firstTimeAuth = false) {
- const accessToken = await graphClientAuthentication(firstTimeAuth);
+ const accessToken = await graphClientAuthentication(WebExtensionContext.telemetry.getTelemetryReporter(), firstTimeAuth);
+ if (!accessToken) {
+ WebExtensionContext.telemetry.sendErrorTelemetry(
+ telemetryEventNames.WEB_EXTENSION_GRAPH_CLIENT_AUTHENTICATION_FAILED,
+ graphClientAuthentication.name
+ );
+ }
+
+ if (firstTimeAuth && accessToken) {
+ WebExtensionContext.telemetry.sendInfoTelemetry(
+ telemetryEventNames.WEB_EXTENSION_GRAPH_CLIENT_AUTHENTICATION_COMPLETED
+ );
+ }
+
this._graphToken = accessToken;
}
diff --git a/src/web/client/telemetry/constants.ts b/src/web/client/telemetry/constants.ts
index 730612f6..76399dd5 100644
--- a/src/web/client/telemetry/constants.ts
+++ b/src/web/client/telemetry/constants.ts
@@ -35,13 +35,13 @@ export enum telemetryEventNames {
WEB_EXTENSION_ENTITY_CONTENT_CHANGED = "WebExtensionEntityConentChanged",
WEB_EXTENSION_ENTITY_CONTENT_SAME = "WebExtensionEntityContentSame",
WEB_EXTENSION_ENTITY_CONTENT_UNEXPECTED_RESPONSE = "WebExtensionEntityContentUnexpectedResponse",
- NPS_AUTHENTICATION_STARTED = "WebExtensionNPSAuthenticationStarted",
- NPS_AUTHENTICATION_COMPLETED = "WebExtensionNPSAuthenticationCompleted",
- NPS_AUTHENTICATION_FAILED = "WebExtensionNPSAuthenticationFailed",
- NPS_USER_ELIGIBLE = "WebExtensionUserIsEligible",
- NPS_API_FAILED = "WebExtensionNPSApiFailed",
- RENDER_NPS = "WebExtensionNPSRenderSurveyForm",
- RENDER_NPS_FAILED = "WebExtensionNPSRenderSurveyFormFailed",
+ WEB_EXTENSION_NPS_AUTHENTICATION_STARTED = "WebExtensionNPSAuthenticationStarted",
+ WEB_EXTENSION_NPS_AUTHENTICATION_COMPLETED = "WebExtensionNPSAuthenticationCompleted",
+ WEB_EXTENSION_NPS_AUTHENTICATION_FAILED = "WebExtensionNPSAuthenticationFailed",
+ WEB_EXTENSION_NPS_USER_ELIGIBLE = "WebExtensionUserIsEligible",
+ WEB_EXTENSION_NPS_API_FAILED = "WebExtensionNPSApiFailed",
+ WEB_EXTENSION_RENDER_NPS = "WebExtensionNPSRenderSurveyForm",
+ WEB_EXTENSION_RENDER_NPS_FAILED = "WebExtensionNPSRenderSurveyFormFailed",
WEB_EXTENSION_CREATE_ENTITY_FOLDER = "WebExtensionCreateEntityFolder",
WEB_EXTENSION_FILE_HAS_DIRTY_CHANGES = "WebExtensionFileHasDirtyChanges",
WEB_EXTENSION_DIFF_VIEW_TRIGGERED = "WebExtensionDiffViewTriggered",
diff --git a/src/web/client/test/integration/AuthenticationProvider.test.ts b/src/web/client/test/integration/AuthenticationProvider.test.ts
index 04ec0c01..76fec74d 100644
--- a/src/web/client/test/integration/AuthenticationProvider.test.ts
+++ b/src/web/client/test/integration/AuthenticationProvider.test.ts
@@ -8,12 +8,14 @@ import { expect } from "chai";
import {
dataverseAuthentication,
getCommonHeaders,
-} from "../../common/authenticationProvider";
+} from "../../../../common/AuthenticationProvider";
import vscode from "vscode";
-import WebExtensionContext from "../../WebExtensionContext";
-import { telemetryEventNames } from "../../telemetry/constants";
import * as errorHandler from "../../common/errorHandler";
import { oneDSLoggerWrapper } from "../../../../common/OneDSLoggerTelemetry/oneDSLoggerWrapper";
+import * as copilotTelemetry from "../../../../common/copilot/telemetry/copilotTelemetry";
+import { WebExtensionTelemetry } from "../../telemetry/webExtensionTelemetry";
+import { vscodeExtAppInsightsResourceProvider } from "../../../../common/telemetry-generated/telemetryConfiguration";
+import { VSCODE_EXTENSION_DATAVERSE_AUTHENTICATION_FAILED } from "../../../../common/TelemetryConstants";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let traceError: any
@@ -32,6 +34,16 @@ describe("Authentication Provider", () => {
// Restore the default sandbox here
sinon.restore();
});
+
+ const webExtensionTelemetry = new WebExtensionTelemetry();
+ const appInsightsResource =
+ vscodeExtAppInsightsResourceProvider.GetAppInsightsResourceForDataBoundary(
+ undefined
+ );
+ webExtensionTelemetry.setTelemetryReporter("", "", appInsightsResource);
+
+ const telemetry = webExtensionTelemetry.getTelemetryReporter();
+
it("getHeader", () => {
const accessToken = "f068ee9f-a010-47b9-b1e1-7e6353730e7d";
const result = getCommonHeaders(accessToken);
@@ -54,7 +66,7 @@ describe("Authentication Provider", () => {
scopes: [],
});
- const result = await dataverseAuthentication(dataverseOrgURL);
+ const result = await dataverseAuthentication(telemetry, dataverseOrgURL);
sinon.assert.calledOnce(_mockgetSession);
expect(result.accessToken).eq("f068ee9f-a010-47b9-b1e1-7e6353730e7d");
expect(result.userId).empty;
@@ -76,26 +88,21 @@ describe("Authentication Provider", () => {
const showErrorDialog = sinon.spy(errorHandler, "showErrorDialog");
- const sendErrorTelemetry = sinon.spy(
- WebExtensionContext.telemetry,
- "sendErrorTelemetry"
+ const sendTelemetryEvent = sinon.spy(
+ copilotTelemetry,
+ "sendTelemetryEvent"
);
//Act
- await dataverseAuthentication(dataverseOrgURL);
+ await dataverseAuthentication(telemetry, dataverseOrgURL);
sinon.assert.calledWith(
showErrorDialog,
"Authorization Failed. Please run again to authorize it"
);
- sinon.assert.calledWith(
- sendErrorTelemetry,
- telemetryEventNames.WEB_EXTENSION_DATAVERSE_AUTHENTICATION_FAILED
- );
-
+ sinon.assert.calledOnce(sendTelemetryEvent);
sinon.assert.calledOnce(showErrorDialog);
- sinon.assert.calledOnce(sendErrorTelemetry);
sinon.assert.calledOnce(_mockgetSession);
});
@@ -107,21 +114,22 @@ describe("Authentication Provider", () => {
.stub(await vscode.authentication, "getSession")
.throws({ message: errorMessage });
- const sendError = sinon.spy(
- WebExtensionContext.telemetry,
- "sendErrorTelemetry"
+ const sendTelemetryEvent = sinon.spy(
+ copilotTelemetry,
+ "sendTelemetryEvent"
);
// Act
- const result = await dataverseAuthentication(dataverseOrgURL);
+ const result = await dataverseAuthentication(telemetry, dataverseOrgURL);
//Assert
- sinon.assert.calledOnce(sendError);
+ sinon.assert.calledOnce(sendTelemetryEvent);
sinon.assert.calledWith(
- sendError,
- telemetryEventNames.WEB_EXTENSION_DATAVERSE_AUTHENTICATION_FAILED,
- dataverseAuthentication.name,
- errorMessage
+ sendTelemetryEvent,
+ telemetry, {
+ eventName: VSCODE_EXTENSION_DATAVERSE_AUTHENTICATION_FAILED,
+ error: { message: errorMessage } as Error
+ }
);
sinon.assert.calledOnce(_mockgetSession);
expect(result.accessToken).empty;
diff --git a/src/web/client/test/integration/WebExtensionContext.test.ts b/src/web/client/test/integration/WebExtensionContext.test.ts
index 9274379d..05b25fcb 100644
--- a/src/web/client/test/integration/WebExtensionContext.test.ts
+++ b/src/web/client/test/integration/WebExtensionContext.test.ts
@@ -11,11 +11,11 @@ import WebExtensionContext from "../../WebExtensionContext";
import { schemaKey, schemaEntityKey } from "../../schema/constants";
import * as portalSchemaReader from "../../schema/portalSchemaReader";
import * as Constants from "../../common/constants";
-import * as authenticationProvider from "../../common/authenticationProvider";
+import * as authenticationProvider from "../../../../common/AuthenticationProvider";
import { telemetryEventNames } from "../../telemetry/constants";
import * as schemaHelperUtil from "../../utilities/schemaHelperUtil";
import * as urlBuilderUtil from "../../utilities/urlBuilderUtil";
-import { getCommonHeadersForDataverse } from "../../common/authenticationProvider";
+import { getCommonHeadersForDataverse } from "../../../../common/AuthenticationProvider";
import { IAttributePath } from "../../common/interfaces";
describe("WebExtensionContext", () => {
@@ -463,7 +463,7 @@ describe("WebExtensionContext", () => {
);
expect(WebExtensionContext.dataverseAccessToken).eq(accessToken);
- assert.calledOnceWithExactly(dataverseAuthentication, ORG_URL, true);
+ assert.calledOnceWithExactly(dataverseAuthentication, WebExtensionContext.telemetry.getTelemetryReporter(), ORG_URL, true);
assert.callCount(sendAPISuccessTelemetry, 3);
assert.calledOnceWithExactly(
getLcidCodeMap,
@@ -641,7 +641,7 @@ describe("WebExtensionContext", () => {
);
expect(WebExtensionContext.dataverseAccessToken).eq(accessToken);
- assert.calledOnceWithExactly(dataverseAuthentication, ORG_URL, true);
+ assert.calledOnceWithExactly(dataverseAuthentication, WebExtensionContext.telemetry.getTelemetryReporter(), ORG_URL, true);
assert.notCalled(getLcidCodeMap);
assert.notCalled(getWebsiteIdToLcidMap);
assert.notCalled(getPortalLanguageIdToLcidMap);
@@ -757,7 +757,7 @@ describe("WebExtensionContext", () => {
);
expect(WebExtensionContext.dataverseAccessToken).eq(accessToken);
- assert.calledOnceWithExactly(dataverseAuthentication, ORG_URL, true);
+ assert.calledOnceWithExactly(dataverseAuthentication, WebExtensionContext.telemetry.getTelemetryReporter(), ORG_URL, true);
//#region Fetch
const header = getCommonHeadersForDataverse(accessToken);
assert.calledThrice(_mockFetch);
diff --git a/src/web/client/test/integration/remoteFetchProvider.test.ts b/src/web/client/test/integration/remoteFetchProvider.test.ts
index ac66efd1..79d84bc6 100644
--- a/src/web/client/test/integration/remoteFetchProvider.test.ts
+++ b/src/web/client/test/integration/remoteFetchProvider.test.ts
@@ -15,7 +15,7 @@ import { schemaEntityKey, schemaKey } from "../../schema/constants";
import * as urlBuilderUtil from "../../utilities/urlBuilderUtil";
import * as commonUtil from "../../utilities/commonUtil";
import { expect } from "chai";
-import * as authenticationProvider from "../../common/authenticationProvider";
+import * as authenticationProvider from "../../../../common/AuthenticationProvider";
import { telemetryEventNames } from "../../telemetry/constants";
describe("remoteFetchProvider", () => {
@@ -209,7 +209,7 @@ describe("remoteFetchProvider", () => {
assert.callCount(writeFile, 3);
assert.calledOnce(updateSingleFileUrisInContext);
- assert.callCount(sendInfoTelemetry, 4);
+ assert.callCount(sendInfoTelemetry, 5);
assert.callCount(sendAPISuccessTelemetry, 4);
});
@@ -399,7 +399,7 @@ describe("remoteFetchProvider", () => {
expect(updateFileDetailsInContextCalls[1].args[7], "false");
assert.callCount(writeFile, 3);
- assert.callCount(sendInfoTelemetry, 6);
+ assert.callCount(sendInfoTelemetry, 7);
assert.calledOnce(executeCommand);
assert.callCount(sendAPISuccessTelemetry, 4);
});
@@ -1145,7 +1145,7 @@ describe("remoteFetchProvider", () => {
assert.callCount(writeFile, 1);
assert.calledOnce(updateSingleFileUrisInContext);
- assert.callCount(sendInfoTelemetry, 4);
+ assert.callCount(sendInfoTelemetry, 5);
assert.callCount(sendAPISuccessTelemetry, 5);
});
});
diff --git a/src/web/client/webViews/NPSWebView.ts b/src/web/client/webViews/NPSWebView.ts
index 35fcdb40..455af31c 100644
--- a/src/web/client/webViews/NPSWebView.ts
+++ b/src/web/client/webViews/NPSWebView.ts
@@ -46,7 +46,7 @@ export class NPSWebView {
const formsProEligibilityId =
WebExtensionContext.formsProEligibilityId;
WebExtensionContext.telemetry.sendInfoTelemetry(
- telemetryEventNames.RENDER_NPS
+ telemetryEventNames.WEB_EXTENSION_RENDER_NPS
);
return `
@@ -64,7 +64,7 @@ export class NPSWebView {
`;
} catch (error) {
WebExtensionContext.telemetry.sendErrorTelemetry(
- telemetryEventNames.RENDER_NPS_FAILED,
+ telemetryEventNames.WEB_EXTENSION_RENDER_NPS_FAILED,
this._getHtml.name,
(error as Error)?.message
);
diff --git a/src/web/client/webViews/QuickPickProvider.ts b/src/web/client/webViews/QuickPickProvider.ts
index cf2e6507..a2ea7ed7 100644
--- a/src/web/client/webViews/QuickPickProvider.ts
+++ b/src/web/client/webViews/QuickPickProvider.ts
@@ -23,14 +23,18 @@ export class QuickPickProvider {
}
public refresh() {
- if (vscode.window.activeTextEditor) {
- const fileFsPath = vscode.window.activeTextEditor.document.uri.fsPath;
- const entityInfo: IEntityInfo = {
- entityId: getFileEntityId(fileFsPath),
- entityName: getFileEntityName(fileFsPath),
- rootWebPageId: getFileRootWebPageId(fileFsPath),
- };
- this.updateQuickPickItems(entityInfo);
+ const tabGroup = vscode.window.tabGroups;
+ if (tabGroup.activeTabGroup && tabGroup.activeTabGroup.activeTab) {
+ const tab = tabGroup.activeTabGroup.activeTab;
+ if (tab.input instanceof vscode.TabInputCustom || tab.input instanceof vscode.TabInputText) {
+ const fileFsPath = tab.input.uri.fsPath;
+ const entityInfo: IEntityInfo = {
+ entityId: getFileEntityId(fileFsPath),
+ entityName: getFileEntityName(fileFsPath),
+ rootWebPageId: getFileRootWebPageId(fileFsPath),
+ };
+ this.updateQuickPickItems(entityInfo);
+ }
}
}