From bd923c630fbe838a118b4865b786e9bc1edfc991 Mon Sep 17 00:00:00 2001 From: tyaginidhi Date: Mon, 29 Apr 2024 15:18:56 +0530 Subject: [PATCH] ECS support infra - Introducing ECS feature flag read call via a common module (#778) * ECS skeleton folder * ignore eslint for now * Update client logic and setup way to introduce new feature gates config read * Address nits * Update ECS API call to support more client configs * Make appname generic as well * take the telemetry changes later * update the constant app name for power pages * Update interface class for ecs feature flag filters and request feature config definition parameters * Update interface definitions * remove unused code * cleanup * minor renaming changes * Update src/common/ecs-features/ecsFeatureClient.ts Co-authored-by: Abdelmoumen Bouabdallah * Update src/common/ecs-features/ecsFeatureClient.ts Co-authored-by: Abdelmoumen Bouabdallah * Update src/common/ecs-features/constants.ts Co-authored-by: Abdelmoumen Bouabdallah * Address comments and update functions for extensibility * Call ecs client and make call for loading config * updated path names * update calling and comments * fix test cases * fix auth test cases * fix test * fix test * update for test case --------- Co-authored-by: Abdelmoumen Bouabdallah --- src/common/copilot/PowerPagesCopilot.ts | 8 +-- src/common/ecs-features/constants.ts | 14 ++++ src/common/ecs-features/ecsFeatureClient.ts | 50 ++++++++++++++ .../ecs-features/ecsFeatureFlagFilters.ts | 29 ++++++++ src/common/ecs-features/ecsFeatureGates.ts | 17 +++++ .../ecs-features/ecsFeatureProperties.ts | 68 +++++++++++++++++++ src/common/ecs-features/ecsFeatureUtil.ts | 38 +++++++++++ src/web/client/WebExtensionContext.ts | 4 +- .../client/common/authenticationProvider.ts | 13 ++-- src/web/client/extension.ts | 36 +++++++--- .../AuthenticationProvider.test.ts | 11 +-- .../integration/WebExtensionContext.test.ts | 12 ++-- .../integration/remoteFetchProvider.test.ts | 20 +++--- src/web/client/utilities/commonUtil.ts | 1 + 14 files changed, 278 insertions(+), 43 deletions(-) create mode 100644 src/common/ecs-features/constants.ts create mode 100644 src/common/ecs-features/ecsFeatureClient.ts create mode 100644 src/common/ecs-features/ecsFeatureFlagFilters.ts create mode 100644 src/common/ecs-features/ecsFeatureGates.ts create mode 100644 src/common/ecs-features/ecsFeatureProperties.ts create mode 100644 src/common/ecs-features/ecsFeatureUtil.ts diff --git a/src/common/copilot/PowerPagesCopilot.ts b/src/common/copilot/PowerPagesCopilot.ts index fb22cf84..351d75e4 100644 --- a/src/common/copilot/PowerPagesCopilot.ts +++ b/src/common/copilot/PowerPagesCopilot.ts @@ -277,11 +277,11 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider { if (feedbackValue === THUMBS_UP) { - sendTelemetryEvent(this.telemetry, { eventName: CopilotUserFeedbackThumbsUpEvent, copilotSessionId: sessionID, orgId: orgID, subScenario: String(messageScenario)}); + sendTelemetryEvent(this.telemetry, { eventName: CopilotUserFeedbackThumbsUpEvent, copilotSessionId: sessionID, orgId: orgID, subScenario: String(messageScenario) }); CESUserFeedback(this._extensionContext, sessionID, userID, THUMBS_UP, this.telemetry, this.geoName as string, messageScenario, tenantId) } else if (feedbackValue === THUMBS_DOWN) { - sendTelemetryEvent(this.telemetry, { eventName: CopilotUserFeedbackThumbsDownEvent, copilotSessionId: sessionID, orgId: orgID, subScenario: String(messageScenario)}); + sendTelemetryEvent(this.telemetry, { eventName: CopilotUserFeedbackThumbsDownEvent, copilotSessionId: sessionID, orgId: orgID, subScenario: String(messageScenario) }); CESUserFeedback(this._extensionContext, sessionID, userID, THUMBS_DOWN, this.telemetry, this.geoName as string, messageScenario, tenantId) } break; @@ -363,12 +363,12 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider { this.sendMessageToWebview({ type: 'userName', value: userName }); let metadataInfo = { entityName: '', formName: '' }; - let componentInfo : string[] = []; + let componentInfo: string[] = []; if (activeFileParams.dataverseEntity == ADX_ENTITYFORM || activeFileParams.dataverseEntity == ADX_ENTITYLIST) { metadataInfo = await getEntityName(telemetry, sessionID, activeFileParams.dataverseEntity); - const dataverseToken = await dataverseAuthentication(activeOrgUrl, true); + const dataverseToken = (await dataverseAuthentication(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/ecs-features/constants.ts b/src/common/ecs-features/constants.ts new file mode 100644 index 00000000..b3480f9a --- /dev/null +++ b/src/common/ecs-features/constants.ts @@ -0,0 +1,14 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +export const ECS_REQUEST_URL_TEMPLATE = "https://ecs.office.com/config/v1"; + +export const PowerPagesClientName = 'PortalsMakerExperiences'; // Project name in ECS Portal, Do not change this +export type ProjectTeam = typeof PowerPagesClientName; +export const PowerPagesAppName = 'powerpages-microsoft-com'; // Project name in ECS Portal, Do not change this +export type ProjectName = typeof PowerPagesAppName; + +export const VisualStudioCodeDevInsidersUrl = 'https://insiders.vscode.dev/power/portal'; // VScode dev insiders/pre-prod env +export const VisualStudioCodeDevUrl = 'https://vscode.dev/power/portal'; // VScode dev GA/prod env diff --git a/src/common/ecs-features/ecsFeatureClient.ts b/src/common/ecs-features/ecsFeatureClient.ts new file mode 100644 index 00000000..57b66da8 --- /dev/null +++ b/src/common/ecs-features/ecsFeatureClient.ts @@ -0,0 +1,50 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +import TelemetryReporter from "@vscode/extension-telemetry"; +import { ITelemetry } from "../../client/telemetry/ITelemetry"; +import { createECSRequestURL } from "./ecsFeatureUtil"; +import { ECSFeatureDefinition as ECSFeatureProperties } from "./ecsFeatureProperties"; +import { ECSAPIFeatureFlagFilters } from "./ecsFeatureFlagFilters"; + +export abstract class ECSFeaturesClient { + private static _ecsConfig: Record; + private static _featuresConfig = {}; + + // Initialize ECSFeatureClient - any client config can be fetched with utility function like below + // EnableMultifileVscodeWeb.getConfig().enableMultifileVscodeWeb + public static async init(telemetry: ITelemetry | TelemetryReporter, filters: ECSAPIFeatureFlagFilters, clientName: string) { + if (this._ecsConfig) return; + + const requestURL = createECSRequestURL(filters, clientName); + try { + const response = await fetch(requestURL, { + method: 'GET' + }); + if (!response.ok) { + throw new Error('Request failed'); + } + const result = await response.json(); + // Update telemetry in other PR + // telemetry.sendTelemetryEvent('ECSConfig', {}); + + // Initialize ECS config + this._ecsConfig = result[clientName]; + } catch (error) { + // Log error + // telemetry.sendTelemetryEvent('ECSConfigError', {}); + } + } + + public static getConfig, TeamName extends string>( + feature: ECSFeatureProperties + ) { + if (Object.keys(this._featuresConfig).length === 0) { + this._featuresConfig = this._ecsConfig && feature.extractECSFeatureFlagConfig?.(this._ecsConfig as TConfig); + } + + return Object.keys(this._featuresConfig).length === 0 ? feature.fallback : this._featuresConfig; + } +} diff --git a/src/common/ecs-features/ecsFeatureFlagFilters.ts b/src/common/ecs-features/ecsFeatureFlagFilters.ts new file mode 100644 index 00000000..e21381a9 --- /dev/null +++ b/src/common/ecs-features/ecsFeatureFlagFilters.ts @@ -0,0 +1,29 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +export interface ECSAPIFeatureFlagFilters { + /** + * Application AppName + * @example powerpages-microsoft-com + */ + AppName: string; + + /** The AAD user object ID or MSA. */ + UserID: string; + + /** The AAD tenant object ID. */ + TenantID: string; + + /** Unique Dataverse Environment ID */ + EnvID: string; + + /** + * Deployment region + * @example test, preview, prod + */ + Region: string; + + // TBD - more API call filters to be added later +} diff --git a/src/common/ecs-features/ecsFeatureGates.ts b/src/common/ecs-features/ecsFeatureGates.ts new file mode 100644 index 00000000..da22f7db --- /dev/null +++ b/src/common/ecs-features/ecsFeatureGates.ts @@ -0,0 +1,17 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +import { PowerPagesClientName } from './constants'; +import { getFeatureConfigs } from './ecsFeatureUtil'; + +export const { + feature: EnableMultifileVscodeWeb +} = getFeatureConfigs({ + teamName: PowerPagesClientName, + description: 'Enable multiple file view in Visual Studio Code Web', + fallback: { + enableMultifileVscodeWeb: false, + }, +}); diff --git a/src/common/ecs-features/ecsFeatureProperties.ts b/src/common/ecs-features/ecsFeatureProperties.ts new file mode 100644 index 00000000..42492846 --- /dev/null +++ b/src/common/ecs-features/ecsFeatureProperties.ts @@ -0,0 +1,68 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +/** + * Interface representing a Feature config in ECS. + */ +export interface ECSFeatureDefinition, TeamName extends string> { + /** + * Name of the Team owning the feature. Must match the "clientTeam" name configured in ECS. + */ + teamName: TeamName; + + /** + * Fallback config to be used while loading or if configs are unavailable. + */ + fallback: TConfig; + + /** + * Brief description of the feature (used for tracking/cleanup purposes) + */ + description?: string; + + /** + * Callback to extract the Feature-specific config from the Team config. + * @param config overall config for the Team. + */ + extractECSFeatureFlagConfig(config: TTeamConfig): Partial; +} + +export type ECSFeatureInfo, TeamName extends string> = Omit< + ECSFeatureDefinition, + 'extractECSFeatureFlagConfig' +>; + +/** + * Creates a Feature object based on the feature definition that includes fallback flags and the team owning the feature. + * @param featureInfo Feature definition specifying the fallback flags and the team owning the feature. + */ +export function createECSFeatureDefinition, TeamName extends string>( + featureInfo: ECSFeatureInfo +): ECSFeatureDefinition { + return { + ...featureInfo, + extractECSFeatureFlagConfig: (config) => extractECSFeatureFlagConfig(config, Object.keys(featureInfo.fallback)), + }; +} + +/** + * Extracts the feature-specific config, based on the provided keys, from the overall Project Team config + * @param teamConfig the overall config object for the Project Team + * @param keys property names to extract from the Project Team config + */ +function extractECSFeatureFlagConfig, TeamConfig extends TConfig>( + teamConfig: TeamConfig, + keys: (keyof TConfig)[] +): Partial { + const config: Partial = {}; + + for (const key of keys) { + if (typeof teamConfig[key] !== 'undefined') { + config[key] = teamConfig[key]; + } + } + + return config; +} diff --git a/src/common/ecs-features/ecsFeatureUtil.ts b/src/common/ecs-features/ecsFeatureUtil.ts new file mode 100644 index 00000000..d8fb297c --- /dev/null +++ b/src/common/ecs-features/ecsFeatureUtil.ts @@ -0,0 +1,38 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +import { PowerPagesClientName, ECS_REQUEST_URL_TEMPLATE } from "./constants"; +import { ECSFeaturesClient } from "./ecsFeatureClient"; +import { ECSAPIFeatureFlagFilters } from "./ecsFeatureFlagFilters"; +import { ECSFeatureDefinition, ECSFeatureInfo, createECSFeatureDefinition } from "./ecsFeatureProperties"; + +export function createECSRequestURL(filters: ECSAPIFeatureFlagFilters, clientName = PowerPagesClientName): string { + const queryParams: { [key: string]: string } = { + AppName: filters.AppName, + EnvironmentID: filters.EnvID, + UserID: filters.UserID, + TenantID: filters.TenantID, + region: filters.Region + }; + + const queryString = Object.keys(queryParams) + .map(key => `${key}=${encodeURIComponent(queryParams[key])}`) + .join('&'); + + return `${ECS_REQUEST_URL_TEMPLATE}/${clientName}/1.0.0.0?${queryString}`; +} + +export function getFeatureConfigs, TeamName extends string>(featureInfo: ECSFeatureInfo) { + type EnhancedFeature = ECSFeatureDefinition & { + getConfig: () => Partial; + }; + + const feature = createECSFeatureDefinition(featureInfo) as EnhancedFeature; + feature.getConfig = () => ECSFeaturesClient.getConfig(feature); + + return { + feature: feature, + }; +} diff --git a/src/web/client/WebExtensionContext.ts b/src/web/client/WebExtensionContext.ts index 80e978b6..13778f90 100644 --- a/src/web/client/WebExtensionContext.ts +++ b/src/web/client/WebExtensionContext.ts @@ -370,7 +370,7 @@ class WebExtensionContext implements IWebExtensionContext { const dataverseOrgUrl = this.urlParametersMap.get( Constants.queryParameters.ORG_URL ) as string; - const accessToken: string = await dataverseAuthentication( + const { accessToken, userId } = await dataverseAuthentication( dataverseOrgUrl, firstTimeAuth ); @@ -378,6 +378,7 @@ class WebExtensionContext implements IWebExtensionContext { if (accessToken.length === 0) { // re-set all properties to default values this._dataverseAccessToken = ""; + this._userId = ""; this._websiteIdToLanguage = new Map(); this._websiteLanguageIdToPortalLanguageMap = new Map< string, @@ -394,6 +395,7 @@ class WebExtensionContext implements IWebExtensionContext { } this._dataverseAccessToken = accessToken; + this._userId = userId; } public async updateFileDetailsInContext( diff --git a/src/web/client/common/authenticationProvider.ts b/src/web/client/common/authenticationProvider.ts index 414f5ddb..24837e98 100644 --- a/src/web/client/common/authenticationProvider.ts +++ b/src/web/client/common/authenticationProvider.ts @@ -87,8 +87,9 @@ export async function intelligenceAPIAuthentication(telemetry: ITelemetry, sessi export async function dataverseAuthentication( dataverseOrgURL: string, firstTimeAuth = false -): Promise { +): Promise<{ accessToken: string, userId: string }> { let accessToken = ""; + let userId = ""; try { let session = await vscode.authentication.getSession( PROVIDER_ID, @@ -110,6 +111,9 @@ export async function dataverseAuthentication( } accessToken = session?.accessToken ?? ""; + userId = session?.account.id.split("/").pop() ?? + session?.account.id ?? + ""; if (!accessToken) { throw new Error(ERRORS.NO_ACCESS_TOKEN); } @@ -118,10 +122,7 @@ export async function dataverseAuthentication( WebExtensionContext.telemetry.sendInfoTelemetry( telemetryEventNames.WEB_EXTENSION_DATAVERSE_AUTHENTICATION_COMPLETED, { - userId: - session?.account.id.split("/").pop() ?? - session?.account.id ?? - "", + userId: userId } ); } @@ -140,7 +141,7 @@ export async function dataverseAuthentication( ); } - return accessToken; + return { accessToken, userId }; } export async function npsAuthentication( diff --git a/src/web/client/extension.ts b/src/web/client/extension.ts index dba6862d..2250b2c8 100644 --- a/src/web/client/extension.ts +++ b/src/web/client/extension.ts @@ -40,7 +40,9 @@ import { fetchArtemisResponse } from "../../common/ArtemisService"; import { oneDSLoggerWrapper } from "../../common/OneDSLoggerTelemetry/oneDSLoggerWrapper"; import { GeoNames } from "../../common/OneDSLoggerTelemetry/telemetryConstants"; import { sendingMessageToWebWorkerForCoPresence } from "./utilities/collaborationUtils"; -import {IPortalWebExtensionInitQueryParametersTelemetryData} from "./telemetry/webExtensionTelemetryInterface"; +import { ECSFeaturesClient } from "../../common/ecs-features/ecsFeatureClient"; +import { PowerPagesAppName, PowerPagesClientName } from "../../common/ecs-features/constants"; +import { IPortalWebExtensionInitQueryParametersTelemetryData } from "./telemetry/webExtensionTelemetryInterface"; export function activate(context: vscode.ExtensionContext): void { // setup telemetry @@ -114,7 +116,7 @@ export function activate(context: vscode.ExtensionContext): void { logOneDSLogger(queryParamsMap); const orgId = queryParamsMap.get(queryParameters.ORG_ID) as string; const orgGeo = await fetchArtemisData(orgId); - WebExtensionContext.telemetry.sendInfoTelemetry(telemetryEventNames.WEB_EXTENSION_ORG_GEO,{orgGeo: orgGeo}); + WebExtensionContext.telemetry.sendInfoTelemetry(telemetryEventNames.WEB_EXTENSION_ORG_GEO, { orgGeo: orgGeo }); oneDSLoggerWrapper.instantiate(orgGeo); WebExtensionContext.telemetry.sendExtensionInitPathParametersTelemetry( @@ -146,6 +148,18 @@ export function activate(context: vscode.ExtensionContext): void { }, async () => { await portalsFS.readDirectory(WebExtensionContext.rootDirectory, true); + + // Initialize ECS config in webExtensionContext + await ECSFeaturesClient.init(WebExtensionContext.telemetry.getTelemetryReporter(), + { + AppName: PowerPagesAppName, + EnvID: queryParamsMap.get(queryParameters.ENV_ID) as string, + UserID: WebExtensionContext.userId, + TenantID: queryParamsMap.get(queryParameters.TENANT_ID) as string, + Region: queryParamsMap.get(queryParameters.REGION) as string + }, + PowerPagesClientName); + registerCopilot(context); processWillStartCollaboration(context); } @@ -629,14 +643,14 @@ function isActiveDocument(fileFsPath: string): boolean { ); } -async function fetchArtemisData(orgId: string) : Promise { - const artemisResponse = await fetchArtemisResponse(orgId, WebExtensionContext.telemetry.getTelemetryReporter()); - if (!artemisResponse) { - // Todo: Log in error telemetry. Runtime maintains another table for this kind of failure. We should do the same. - return ''; - } +async function fetchArtemisData(orgId: string): Promise { + const artemisResponse = await fetchArtemisResponse(orgId, WebExtensionContext.telemetry.getTelemetryReporter()); + if (!artemisResponse) { + // Todo: Log in error telemetry. Runtime maintains another table for this kind of failure. We should do the same. + return ''; + } - return artemisResponse[0].geoName as string; + return artemisResponse[0].geoName as string; } async function logArtemisTelemetry() { @@ -646,7 +660,7 @@ async function logArtemisTelemetry() { queryParameters.ORG_ID ) as string - const geoName= fetchArtemisData(orgId); + const geoName = fetchArtemisData(orgId); WebExtensionContext.telemetry.sendInfoTelemetry(telemetryEventNames.WEB_EXTENSION_ARTEMIS_RESPONSE, { orgId: orgId, geoName: String(geoName) }); } catch (error) { @@ -657,7 +671,7 @@ async function logArtemisTelemetry() { } } -function logOneDSLogger (queryParamsMap: Map) { +function logOneDSLogger(queryParamsMap: Map) { const telemetryData: IPortalWebExtensionInitQueryParametersTelemetryData = { eventName: telemetryEventNames.WEB_EXTENSION_INIT_QUERY_PARAMETERS, properties: { diff --git a/src/web/client/test/integration/AuthenticationProvider.test.ts b/src/web/client/test/integration/AuthenticationProvider.test.ts index 13539099..04ec0c01 100644 --- a/src/web/client/test/integration/AuthenticationProvider.test.ts +++ b/src/web/client/test/integration/AuthenticationProvider.test.ts @@ -13,7 +13,7 @@ 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 { oneDSLoggerWrapper } from "../../../../common/OneDSLoggerTelemetry/oneDSLoggerWrapper"; // eslint-disable-next-line @typescript-eslint/no-explicit-any let traceError: any @@ -56,7 +56,8 @@ describe("Authentication Provider", () => { const result = await dataverseAuthentication(dataverseOrgURL); sinon.assert.calledOnce(_mockgetSession); - expect(accessToken).eq(result); + expect(result.accessToken).eq("f068ee9f-a010-47b9-b1e1-7e6353730e7d"); + expect(result.userId).empty; }); it("dataverseAuthentication_return_blank_if_accessToken_is_null", async () => { @@ -81,7 +82,7 @@ describe("Authentication Provider", () => { ); //Act - const result = await dataverseAuthentication(dataverseOrgURL); + await dataverseAuthentication(dataverseOrgURL); sinon.assert.calledWith( showErrorDialog, @@ -96,7 +97,6 @@ describe("Authentication Provider", () => { sinon.assert.calledOnce(showErrorDialog); sinon.assert.calledOnce(sendErrorTelemetry); sinon.assert.calledOnce(_mockgetSession); - expect(result).empty; }); it("dataverseAuthentication_return_blank_if_exception_thrown", async () => { @@ -124,6 +124,7 @@ describe("Authentication Provider", () => { errorMessage ); sinon.assert.calledOnce(_mockgetSession); - expect(result).empty; + expect(result.accessToken).empty; + expect(result.userId).empty; }); }); diff --git a/src/web/client/test/integration/WebExtensionContext.test.ts b/src/web/client/test/integration/WebExtensionContext.test.ts index 491727bc..9274379d 100644 --- a/src/web/client/test/integration/WebExtensionContext.test.ts +++ b/src/web/client/test/integration/WebExtensionContext.test.ts @@ -100,7 +100,7 @@ describe("WebExtensionContext", () => { [Constants.queryParameters.ORG_URL, "PowerPages.com"], ]); - stub(authenticationProvider, "dataverseAuthentication").resolves(""); + stub(authenticationProvider, "dataverseAuthentication").resolves({ accessToken: "", userId: "" }); const telemetry = WebExtensionContext.telemetry; const sendErrorTelemetry = stub(telemetry, "sendErrorTelemetry"); @@ -144,7 +144,7 @@ describe("WebExtensionContext", () => { ]); stub(authenticationProvider, "dataverseAuthentication").resolves( - accessToken + { accessToken: accessToken, userId: "" } ); WebExtensionContext.setWebExtensionContext( @@ -322,7 +322,7 @@ describe("WebExtensionContext", () => { [Constants.queryParameters.ORG_URL, "PowerPages.com"], ]); - stub(authenticationProvider, "dataverseAuthentication").resolves(""); + stub(authenticationProvider, "dataverseAuthentication").resolves({ accessToken: "", userId: "" }); const noPermissions = stub(vscode.FileSystemError, "NoPermissions"); const telemetry = WebExtensionContext.telemetry; const sendErrorTelemetry = stub(telemetry, "sendErrorTelemetry"); @@ -370,7 +370,7 @@ describe("WebExtensionContext", () => { const dataverseAuthentication = stub( authenticationProvider, "dataverseAuthentication" - ).resolves(accessToken); + ).resolves({ accessToken: accessToken, userId: "" }); const getCustomRequestURL = stub( urlBuilderUtil, @@ -576,7 +576,7 @@ describe("WebExtensionContext", () => { const dataverseAuthentication = stub( authenticationProvider, "dataverseAuthentication" - ).resolves(accessToken); + ).resolves({ accessToken: accessToken, userId: "" }); const getCustomRequestURL = stub( urlBuilderUtil, @@ -704,7 +704,7 @@ describe("WebExtensionContext", () => { const dataverseAuthentication = stub( authenticationProvider, "dataverseAuthentication" - ).resolves(accessToken); + ).resolves({ accessToken: accessToken, userId: "" }); const getCustomRequestURL = stub( urlBuilderUtil, diff --git a/src/web/client/test/integration/remoteFetchProvider.test.ts b/src/web/client/test/integration/remoteFetchProvider.test.ts index 4b4c8834..ac66efd1 100644 --- a/src/web/client/test/integration/remoteFetchProvider.test.ts +++ b/src/web/client/test/integration/remoteFetchProvider.test.ts @@ -67,7 +67,7 @@ describe("remoteFetchProvider", () => { const accessToken = "ae3308da-d75b-4666-bcb8-8f33a3dd8a8d"; stub(authenticationProvider, "dataverseAuthentication").resolves( - accessToken + { accessToken: accessToken, userId: "" } ); const _mockFetch = stub(fetch, "default").resolves({ @@ -259,7 +259,7 @@ describe("remoteFetchProvider", () => { const accessToken = "ae3308da-d75b-4666-bcb8-8f33a3dd8a8d"; stub(authenticationProvider, "dataverseAuthentication").resolves( - accessToken + { accessToken: accessToken, userId: "" } ); const _mockFetch = stub(fetch, "default").resolves({ @@ -454,7 +454,7 @@ describe("remoteFetchProvider", () => { const accessToken = "ae3308da-d75b-4666-bcb8-8f33a3dd8a8d"; stub(authenticationProvider, "dataverseAuthentication").resolves( - accessToken + { accessToken: accessToken, userId: "" } ); const portalFs = new PortalsFS(); @@ -544,7 +544,7 @@ describe("remoteFetchProvider", () => { const accessToken = "ae3308da-d75b-4666-bcb8-8f33a3dd8a8d"; stub(authenticationProvider, "dataverseAuthentication").resolves( - accessToken + { accessToken: accessToken, userId: "" } ); const portalFs = new PortalsFS(); @@ -641,7 +641,7 @@ describe("remoteFetchProvider", () => { const accessToken = "ae3308da-d75b-4666-bcb8-8f33a3dd8a8d"; stub(authenticationProvider, "dataverseAuthentication").resolves( - accessToken + { accessToken: accessToken, userId: "" } ); const portalFs = new PortalsFS(); @@ -742,7 +742,7 @@ describe("remoteFetchProvider", () => { const accessToken = "ae3308da-d75b-4666-bcb8-8f33a3dd8a8d"; stub(authenticationProvider, "dataverseAuthentication").resolves( - accessToken + { accessToken: accessToken, userId: "" } ); const portalFs = new PortalsFS(); @@ -845,7 +845,7 @@ describe("remoteFetchProvider", () => { const accessToken = "ae3308da-d75b-4666-bcb8-8f33a3dd8a8d"; stub(authenticationProvider, "dataverseAuthentication").resolves( - accessToken + { accessToken: accessToken, userId: "" } ); const portalFs = new PortalsFS(); @@ -949,7 +949,7 @@ describe("remoteFetchProvider", () => { const accessToken = "ae3308da-d75b-4666-bcb8-8f33a3dd8a8d"; stub(authenticationProvider, "dataverseAuthentication").resolves( - accessToken + { accessToken: accessToken, userId: "" } ); const portalFs = new PortalsFS(); @@ -1047,7 +1047,7 @@ describe("remoteFetchProvider", () => { const accessToken = "ae3308da-d75b-4666-bcb8-8f33a3dd8a8d"; stub(authenticationProvider, "dataverseAuthentication").resolves( - accessToken + { accessToken: accessToken, userId: "" } ); const _mockFetch = stub(fetch, 'default').callsFake((url) => { @@ -1135,7 +1135,7 @@ describe("remoteFetchProvider", () => { true, // eslint-disable-next-line @typescript-eslint/no-explicit-any {} - ); + ); assert.calledWith(convertContentToUint8Array, // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/src/web/client/utilities/commonUtil.ts b/src/web/client/utilities/commonUtil.ts index 1106fb0f..8dfeacbe 100644 --- a/src/web/client/utilities/commonUtil.ts +++ b/src/web/client/utilities/commonUtil.ts @@ -224,6 +224,7 @@ export function getBackToStudioURL() { .replace("{.region}", region.toLowerCase() === STUDIO_PROD_REGION ? "" : `.${WebExtensionContext.urlParametersMap.get(queryParameters.REGION) as string}`) .replace("{webSiteId}", WebExtensionContext.urlParametersMap.get(queryParameters.WEBSITE_ID) as string); } + export function getSupportedImageFileExtensionsForEdit() { return ['png', 'jpg', 'webp', 'bmp', 'tga', 'ico', 'jpeg', 'bmp', 'dib', 'jif', 'jpe', 'tpic']; // Luna paint supported image file extensions }