diff --git a/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts b/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts index faf428e5..9eeef793 100644 --- a/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts +++ b/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts @@ -20,6 +20,9 @@ import { v4 as uuidv4 } from 'uuid'; import { ITelemetry } from '../../OneDSLoggerTelemetry/telemetry/ITelemetry'; import { orgChangeErrorEvent, orgChangeEvent } from '../../../client/OrgChangeNotifier'; import { createSite } from './site-creation/PowerPagesCreateSite'; +import { ArtemisService } from '../../services/ArtemisService'; +import { ServiceEndpointCategory } from '../../services/Constants'; +import { PPAPIService } from '../../services/PPAPIService'; export class PowerPagesChatParticipant { private static instance: PowerPagesChatParticipant | null = null; @@ -31,6 +34,7 @@ export class PowerPagesChatParticipant { private readonly _disposables: vscode.Disposable[] = []; private cachedEndpoint: IIntelligenceAPIEndpointInformation | null = null; private powerPagesAgentSessionId: string; + private serviceEndpointStamp: ServiceEndpointCategory | undefined; private orgID: string | undefined; private orgUrl: string | undefined; @@ -145,6 +149,20 @@ export class PowerPagesChatParticipant { switch (request.command) { case 'create-site': { stream.progress('Generating a new Power Pages site...'); + + const artemisResponse = await ArtemisService.getArtemisResponse(this.orgID, this.telemetry, this.powerPagesAgentSessionId); + + if (!artemisResponse) { + stream.markdown('Failed to get the Artemis response. Please try again later.'); + return { + metadata: { + command: '' + } + }; + } + + this.serviceEndpointStamp = artemisResponse.stamp; + try { const result = await createSite( intelligenceAPIEndpointInfo.intelligenceEndpoint, @@ -155,13 +173,29 @@ export class PowerPagesChatParticipant { stream, this.telemetry ); - stream.markdown(`Site created successfully with portal:`); - console.log(result); - return { - metadata: { - command: 'create-site', - } - }; + + if(!result) { + stream.markdown('Failed to create the site. Please try again later.'); + return { + metadata: { + command: '' + } + }; + } + + + await PPAPIService.createWebsite(this.serviceEndpointStamp, this.environmentID, this.orgID, result.siteName, 1033, result.websiteId, this.telemetry); + + const websiteResponse = await PPAPIService.getWebsiteDetailsById(this.serviceEndpointStamp, this.environmentID, result.websiteId, this.telemetry); + + if(websiteResponse) { + stream.markdown(`Site created successfully with portal: ${websiteResponse.websiteUrl}`); + return { + metadata: { + command: 'create-site', + } + }; + } } catch (error) { stream.markdown('Failed to create the site. Please try again later.'); return { @@ -171,6 +205,7 @@ export class PowerPagesChatParticipant { }; } } + break; // case 'create-site': { // //TODO: Update the strings // stream.progress('Generating a new Power Pages site...'); diff --git a/src/common/services/ArtemisService.ts b/src/common/services/ArtemisService.ts index 94c54a71..9410e853 100644 --- a/src/common/services/ArtemisService.ts +++ b/src/common/services/ArtemisService.ts @@ -8,56 +8,52 @@ import { COPILOT_UNAVAILABLE } from "../copilot/constants"; import { ITelemetry } from "../OneDSLoggerTelemetry/telemetry/ITelemetry"; import { sendTelemetryEvent } from "../copilot/telemetry/copilotTelemetry"; import { CopilotArtemisFailureEvent, CopilotArtemisSuccessEvent } from "../copilot/telemetry/telemetryConstants"; -import { BAPServiceStamp as BAPAPIEndpointStamp } from "./Constants"; -import { IArtemisAPIOrgResponse, IArtemisServiceEndpointInformation, IIntelligenceAPIEndpointInformation } from "./Interfaces"; +import { ServiceEndpointCategory } from "./Constants"; +import { IArtemisAPIOrgResponse, IArtemisServiceEndpointInformation, IArtemisServiceResponse, IIntelligenceAPIEndpointInformation } from "./Interfaces"; import { isCopilotDisabledInGeo, isCopilotSupportedInGeo } from "../copilot/utils/copilotUtil"; import { BAPService } from "./BAPService"; export class ArtemisService { public static async getIntelligenceEndpoint(orgId: string, telemetry: ITelemetry, sessionID: string, environmentId: string): Promise { - const artemisResponses = await ArtemisService.fetchArtemisResponse(orgId, telemetry, sessionID); + const artemisResponse = await ArtemisService.getArtemisResponse(orgId, telemetry, sessionID); - if (artemisResponses === null || artemisResponses.length === 0) { + if (artemisResponse === null) { return { intelligenceEndpoint: null, geoName: null, crossGeoDataMovementEnabledPPACFlag: false }; } + const { geoName, environment, clusterNumber } = artemisResponse.response as unknown as IArtemisAPIOrgResponse; + sendTelemetryEvent(telemetry, { eventName: CopilotArtemisSuccessEvent, copilotSessionId: sessionID, geoName: String(geoName), orgId: orgId }); - const artemisResponse = artemisResponses[0]; - if (artemisResponse !== null) { - const { geoName, environment, clusterNumber } = artemisResponse.response as unknown as IArtemisAPIOrgResponse; - const endpointStamp = artemisResponse.stamp; - sendTelemetryEvent(telemetry, { eventName: CopilotArtemisSuccessEvent, copilotSessionId: sessionID, geoName: String(geoName), orgId: orgId }); + const crossGeoDataMovementEnabledPPACFlag = await BAPService.getCrossGeoCopilotDataMovementEnabledFlag(artemisResponse.stamp, telemetry, environmentId); - const crossGeoDataMovementEnabledPPACFlag = await BAPService.getCrossGeoCopilotDataMovementEnabledFlag(artemisResponse.stamp, telemetry, environmentId); - - if (isCopilotDisabledInGeo().includes(geoName)) { - return { intelligenceEndpoint: COPILOT_UNAVAILABLE, geoName: geoName, crossGeoDataMovementEnabledPPACFlag: crossGeoDataMovementEnabledPPACFlag }; - } - else if (crossGeoDataMovementEnabledPPACFlag === true) { - // Do nothing - we can make this call cross geo - } - else if (!isCopilotSupportedInGeo().includes(geoName)) { - return { intelligenceEndpoint: COPILOT_UNAVAILABLE, geoName: geoName, crossGeoDataMovementEnabledPPACFlag: crossGeoDataMovementEnabledPPACFlag }; - } - - const intelligenceEndpoint = `https://aibuildertextapiservice.${geoName}-${'il' + clusterNumber}.gateway.${environment}.island.powerapps.com/v1.0/${orgId}/appintelligence/chat` - - return { intelligenceEndpoint: intelligenceEndpoint, geoName: geoName, crossGeoDataMovementEnabledPPACFlag: crossGeoDataMovementEnabledPPACFlag, endpointStamp: endpointStamp }; + if (isCopilotDisabledInGeo().includes(geoName)) { + return { intelligenceEndpoint: COPILOT_UNAVAILABLE, geoName: geoName, crossGeoDataMovementEnabledPPACFlag: crossGeoDataMovementEnabledPPACFlag }; + } + else if (crossGeoDataMovementEnabledPPACFlag === true) { + // Do nothing - we can make this call cross geo + } + else if (!isCopilotSupportedInGeo().includes(geoName)) { + return { intelligenceEndpoint: COPILOT_UNAVAILABLE, geoName: geoName, crossGeoDataMovementEnabledPPACFlag: crossGeoDataMovementEnabledPPACFlag }; } - return { intelligenceEndpoint: null, geoName: null, crossGeoDataMovementEnabledPPACFlag: false }; + const intelligenceEndpoint = `https://aibuildertextapiservice.${geoName}-${'il' + clusterNumber}.gateway.${environment}.island.powerapps.com/v1.0/${orgId}/appintelligence/chat` + + return { intelligenceEndpoint: intelligenceEndpoint, geoName: geoName, crossGeoDataMovementEnabledPPACFlag: crossGeoDataMovementEnabledPPACFlag }; } // Function to fetch Artemis response - public static async fetchArtemisResponse(orgId: string, telemetry: ITelemetry, sessionID = '') { + public static async getArtemisResponse(orgId: string, telemetry: ITelemetry, sessionID: string): Promise { const endpointDetails = ArtemisService.convertGuidToUrls(orgId); + const artemisResponses = await ArtemisService.fetchIslandInfo(endpointDetails, telemetry, sessionID); - const artemisResponse = await ArtemisService.fetchIslandInfo(endpointDetails, telemetry, sessionID); + if (artemisResponses === null || artemisResponses.length === 0) { + return null; + } - return artemisResponse; + return artemisResponses[0]; } - static async fetchIslandInfo(endpointDetails: IArtemisServiceEndpointInformation[], telemetry: ITelemetry, sessionID: string) { + static async fetchIslandInfo(endpointDetails: IArtemisServiceEndpointInformation[], telemetry: ITelemetry, sessionID: string): Promise { const requestInit: RequestInit = { method: 'GET', @@ -71,7 +67,7 @@ export class ArtemisService { if (!response.ok) { throw new Error('Request failed'); } - return { stamp: endpointDetail.stamp, response: await response.json() }; + return { stamp: endpointDetail.stamp, response: await response.json() as IArtemisAPIOrgResponse }; } catch (error) { return null; } @@ -79,14 +75,13 @@ export class ArtemisService { const results = await Promise.all(promises); const successfulResponses = results.filter(result => result !== null && result.response !== null); - return successfulResponses; + return successfulResponses as IArtemisServiceResponse[]; } catch (error) { sendTelemetryEvent(telemetry, { eventName: CopilotArtemisFailureEvent, copilotSessionId: sessionID, error: error as Error }) return null; } } - /** * @param orgId * @returns urls @@ -110,13 +105,13 @@ export class ArtemisService { const dodUrl = `https://${domain}.${nonProdSegment}.organization.api.appsplatform.us/gateway/cluster?app-version=1`; return [ - { stamp: BAPAPIEndpointStamp.TEST, endpoint: tstUrl }, - { stamp: BAPAPIEndpointStamp.PREPROD, endpoint: preprodUrl }, - { stamp: BAPAPIEndpointStamp.PROD, endpoint: prodUrl }, - { stamp: BAPAPIEndpointStamp.GCC, endpoint: gccUrl }, - { stamp: BAPAPIEndpointStamp.HIGH, endpoint: highUrl }, - { stamp: BAPAPIEndpointStamp.MOONCAKE, endpoint: mooncakeUrl }, - { stamp: BAPAPIEndpointStamp.DOD, endpoint: dodUrl }, + { stamp: ServiceEndpointCategory.TEST, endpoint: tstUrl }, + { stamp: ServiceEndpointCategory.PREPROD, endpoint: preprodUrl }, + { stamp: ServiceEndpointCategory.PROD, endpoint: prodUrl }, + { stamp: ServiceEndpointCategory.GCC, endpoint: gccUrl }, + { stamp: ServiceEndpointCategory.HIGH, endpoint: highUrl }, + { stamp: ServiceEndpointCategory.MOONCAKE, endpoint: mooncakeUrl }, + { stamp: ServiceEndpointCategory.DOD, endpoint: dodUrl }, ]; } diff --git a/src/common/services/AuthenticationProvider.ts b/src/common/services/AuthenticationProvider.ts index c918ff3f..3b061562 100644 --- a/src/common/services/AuthenticationProvider.ts +++ b/src/common/services/AuthenticationProvider.ts @@ -18,7 +18,9 @@ import { VSCODE_EXTENSION_GRAPH_CLIENT_AUTHENTICATION_COMPLETED, VSCODE_EXTENSION_BAP_SERVICE_AUTHENTICATION_COMPLETED, VSCODE_EXTENSION_BAP_SERVICE_AUTHENTICATION_FAILED, - VSCODE_EXTENSION_DECODE_JWT_TOKEN_FAILED + VSCODE_EXTENSION_DECODE_JWT_TOKEN_FAILED, + VSCODE_EXTENSION_PPAPI_WEBSITES_AUTHENTICATION_FAILED, + VSCODE_EXTENSION_PPAPI_WEBSITES_AUTHENTICATION_COMPLETED } from "./TelemetryConstants"; import { ERROR_CONSTANTS } from "../ErrorConstants"; import { BAP_SERVICE_SCOPE_DEFAULT, INTELLIGENCE_SCOPE_DEFAULT, PROVIDER_ID, SCOPE_OPTION_CONTACTS_READ, SCOPE_OPTION_DEFAULT, SCOPE_OPTION_OFFLINE_ACCESS, SCOPE_OPTION_USERS_READ_BASIC_ALL } from "./Constants"; @@ -292,3 +294,57 @@ export function getOIDFromToken(token: string, telemetry: ITelemetry) { } return ""; } + +export async function powerPlatformAPIAuthentication( + telemetry: ITelemetry, + ppapiEndpoint: string, + firstTimeAuth = false +): Promise { + let accessToken = ""; + try { + //https://api.preprod.powerplatform.com/powerpages/environments/1538a5fc-6583-ef69-8a14-99808eaa981a/websites?api-version=2022-03-01-preview + //get the endpoint till .com + ppapiEndpoint = ppapiEndpoint.split("/powerpages")[0]; + const PPAPI_WEBSITES_SERVICE_SCOPE_DEFAULT = `${ppapiEndpoint}${SCOPE_OPTION_DEFAULT}`; + let session = await vscode.authentication.getSession( + PROVIDER_ID, + [PPAPI_WEBSITES_SERVICE_SCOPE_DEFAULT], + { silent: true } + ); + + if (!session) { + session = await vscode.authentication.getSession( + PROVIDER_ID, + [PPAPI_WEBSITES_SERVICE_SCOPE_DEFAULT], + { createIfNone: true } + ); + } + + accessToken = session?.accessToken ?? ""; + if (!accessToken) { + throw new Error(ERROR_CONSTANTS.NO_ACCESS_TOKEN); + } + + if (firstTimeAuth) { + sendTelemetryEvent(telemetry, { + eventName: VSCODE_EXTENSION_PPAPI_WEBSITES_AUTHENTICATION_COMPLETED, + userId: + session?.account.id.split("/").pop() ?? + session?.account.id ?? + "", + }); + } + } catch (error) { + showErrorDialog( + vscode.l10n.t( + "Authorization Failed. Please run again to authorize it" + ), + vscode.l10n.t("There was a permissions problem with the server") + ); + sendTelemetryEvent(telemetry, + { eventName: VSCODE_EXTENSION_PPAPI_WEBSITES_AUTHENTICATION_FAILED, errorMsg: (error as Error).message } + ) + } + + return accessToken; +} diff --git a/src/common/services/BAPService.ts b/src/common/services/BAPService.ts index 05f9b960..e57c2225 100644 --- a/src/common/services/BAPService.ts +++ b/src/common/services/BAPService.ts @@ -6,12 +6,12 @@ import { ITelemetry } from "../OneDSLoggerTelemetry/telemetry/ITelemetry"; import { bapServiceAuthentication, getCommonHeaders } from "./AuthenticationProvider"; import { VSCODE_EXTENSION_GET_CROSS_GEO_DATA_MOVEMENT_ENABLED_FLAG_COMPLETED, VSCODE_EXTENSION_GET_CROSS_GEO_DATA_MOVEMENT_ENABLED_FLAG_FAILED } from "./TelemetryConstants"; -import { BAPServiceStamp, BAP_API_VERSION, BAP_SERVICE_COPILOT_CROSS_GEO_FLAG_RELATIVE_URL, BAP_SERVICE_ENDPOINT } from "./Constants"; +import { BAP_API_VERSION, BAP_SERVICE_COPILOT_CROSS_GEO_FLAG_RELATIVE_URL, BAP_SERVICE_ENDPOINT, ServiceEndpointCategory } from "./Constants"; import { sendTelemetryEvent } from "../copilot/telemetry/copilotTelemetry"; import { getBAPEndpoint } from "../utilities/Utils"; export class BAPService { - public static async getCrossGeoCopilotDataMovementEnabledFlag(serviceEndpointStamp: BAPServiceStamp, telemetry: ITelemetry, environmentId: string): Promise { + public static async getCrossGeoCopilotDataMovementEnabledFlag(serviceEndpointStamp: ServiceEndpointCategory, telemetry: ITelemetry, environmentId: string): Promise { try { const accessToken = await bapServiceAuthentication(telemetry, true); @@ -34,7 +34,7 @@ export class BAPService { return false; } - static async getBAPCopilotCrossGeoFlagEndpoint(serviceEndpointStamp: BAPServiceStamp, telemetry: ITelemetry, environmentId: string): Promise { + static async getBAPCopilotCrossGeoFlagEndpoint(serviceEndpointStamp: ServiceEndpointCategory, telemetry: ITelemetry, environmentId: string): Promise { const bapEndpoint = await getBAPEndpoint(serviceEndpointStamp, telemetry); diff --git a/src/common/services/Constants.ts b/src/common/services/Constants.ts index dfae5263..a439be99 100644 --- a/src/common/services/Constants.ts +++ b/src/common/services/Constants.ts @@ -12,13 +12,20 @@ export const SCOPE_OPTION_CONTACTS_READ = "Contacts.Read"; export const SCOPE_OPTION_USERS_READ_BASIC_ALL = "User.ReadBasic.All"; export const SCOPE_OPTION_DEFAULT = "/.default"; +// BAP API constants export const BAP_API_VERSION = '2021-04-01'; -export const BAP_SERVICE_SCOPE_DEFAULT = "https://api.bap.microsoft.com/.default";//"https://management.core.windows.net/.default"; +export const BAP_SERVICE_SCOPE_DEFAULT = "https://api.bap.microsoft.com/.default"; export const BAP_SERVICE_ENDPOINT = `{rootURL}/providers/Microsoft.BusinessAppPlatform/`; export const BAP_SERVICE_COPILOT_CROSS_GEO_FLAG_RELATIVE_URL = `scopes/admin/environments/{environmentID}?$expand=properties/copilotPolicies&api-version={apiVersion}`; export const BAP_ENVIRONMENT_LIST_URL = `scopes/admin/environments?api-version=2021-04-01&select=name,properties.displayName,properties.linkedEnvironmentMetadata`; -export enum BAPServiceStamp { +// PPAPI constants +export const PPAPI_WEBSITES_API_VERSION = '2022-03-01-preview'; +export const PPAPI_WEBSITES_SERVICE_SCOPE_DEFAULT = "https://api.powerplatform.com/.default"; +export const PPAPI_WEBSITES_ENDPOINT = `{rootURL}/powerpages/environments/{environmentId}/websites`; + +export enum ServiceEndpointCategory { + NONE = "", TEST = "test", PREPROD = "preprod", PROD = "prod", @@ -27,3 +34,8 @@ export enum BAPServiceStamp { MOONCAKE = "mooncake", DOD = "dod", } + +export enum WebsiteApplicationType { + Production = "Production", + Trial = "Trial", +} diff --git a/src/common/services/Interfaces.ts b/src/common/services/Interfaces.ts index 02b76e45..e32457a3 100644 --- a/src/common/services/Interfaces.ts +++ b/src/common/services/Interfaces.ts @@ -3,15 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. */ -import { BAPServiceStamp } from "./Constants"; +import { ServiceEndpointCategory, WebsiteApplicationType } from "./Constants"; export interface IArtemisServiceEndpointInformation { - stamp: BAPServiceStamp; + stamp: ServiceEndpointCategory; endpoint: string; } export interface IArtemisServiceResponse { - stamp: BAPServiceStamp; + stamp: ServiceEndpointCategory; response: IArtemisAPIOrgResponse; } @@ -25,9 +25,25 @@ export interface IArtemisAPIOrgResponse { clusterType: string, } +export interface IArtemisServiceResponse { + stamp: ServiceEndpointCategory; + response: IArtemisAPIOrgResponse; +} + export interface IIntelligenceAPIEndpointInformation { intelligenceEndpoint: string | null, geoName: string | null, - crossGeoDataMovementEnabledPPACFlag: boolean, - endpointStamp?: BAPServiceStamp + crossGeoDataMovementEnabledPPACFlag: boolean +} + +export interface IWebsiteDetails { + websiteUrl: string; + dataverseInstanceUrl: string; + dataverseOrganizationId: string; + environmentId: string; + id: string; + siteVisibility: string; + tenantId: string; + websiteRecordId: string; + type: WebsiteApplicationType; } diff --git a/src/common/services/PPAPIService.ts b/src/common/services/PPAPIService.ts new file mode 100644 index 00000000..673021b9 --- /dev/null +++ b/src/common/services/PPAPIService.ts @@ -0,0 +1,109 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +import { ITelemetry } from "../OneDSLoggerTelemetry/telemetry/ITelemetry"; +import { getCommonHeaders, powerPlatformAPIAuthentication } from "./AuthenticationProvider"; +import { VSCODE_EXTENSION_GET_CROSS_GEO_DATA_MOVEMENT_ENABLED_FLAG_FAILED, VSCODE_EXTENSION_GET_PPAPI_WEBSITES_ENDPOINT_UNSUPPORTED_REGION, VSCODE_EXTENSION_PPAPI_CREATE_WEBSITE_COMPLETED, VSCODE_EXTENSION_PPAPI_CREATE_WEBSITE_FAILED, VSCODE_EXTENSION_PPAPI_GET_WEBSITE_BY_ID_COMPLETED } from "./TelemetryConstants"; +import { ServiceEndpointCategory, PPAPI_WEBSITES_ENDPOINT, PPAPI_WEBSITES_API_VERSION } from "./Constants"; +import { sendTelemetryEvent } from "../copilot/telemetry/copilotTelemetry"; +import { IWebsiteDetails } from "./Interfaces"; + +export class PPAPIService { + public static async getWebsiteDetailsById(serviceEndpointStamp: ServiceEndpointCategory, environmentId: string, websitePreviewId: string, telemetry: ITelemetry): Promise { // websitePreviewId aka portalId + + try { + const ppapiEndpoint = await PPAPIService.getPPAPIServiceEndpoint(serviceEndpointStamp, telemetry, environmentId); + const accessToken = await powerPlatformAPIAuthentication(telemetry, ppapiEndpoint, true); + const response = await fetch(ppapiEndpoint, { + method: 'GET', + headers: getCommonHeaders(accessToken) + }); + + if (response.ok) { + const websiteDetails = await response.json() as unknown as IWebsiteDetails; + sendTelemetryEvent(telemetry, { eventName: VSCODE_EXTENSION_PPAPI_GET_WEBSITE_BY_ID_COMPLETED, orgUrl: websiteDetails.dataverseInstanceUrl }); + return websiteDetails; + } + } + catch (error) { + sendTelemetryEvent(telemetry, { eventName: VSCODE_EXTENSION_GET_CROSS_GEO_DATA_MOVEMENT_ENABLED_FLAG_FAILED, errorMsg: (error as Error).message }); + } + + return null; + } + + public static async createWebsite(serviceEndpointStamp: ServiceEndpointCategory, + environmentId: string, + orgId: string, + websiteName: string, + websiteLanguage: number, + websiteId: string, + telemetry: ITelemetry) { // websitePreviewId aka portalId + + const ppapiEndpoint = await PPAPIService.getPPAPIServiceEndpoint(serviceEndpointStamp, telemetry, environmentId); + console.log(ppapiEndpoint); + + try { + console.log("Creating website"); + const accessToken = await powerPlatformAPIAuthentication(telemetry, ppapiEndpoint, true); + const siteSuffix = websiteId; + websiteName = websiteName.replace(/\s+/g, '-'); // Replace spaces with '-' in website name + const response = await fetch(ppapiEndpoint, { + method: 'POST', + headers: getCommonHeaders(accessToken), + body: JSON.stringify({ + dataverseOrganizationId: orgId, + name: websiteName, + selectedBaseLanguage: websiteLanguage, + subDomain: websiteName + `-${siteSuffix.slice(siteSuffix.length - 6, siteSuffix.length)}`, // Replace spaces with '-' in website name + templateName: "DefaultPortalTemplate", + websiteRecordId: siteSuffix // If this ID is passed package installation is not done and portal is associated with the passed ID - we should use this option + }) + }); + + if (response.ok) { + sendTelemetryEvent(telemetry, { eventName: VSCODE_EXTENSION_PPAPI_CREATE_WEBSITE_COMPLETED, data: `environmentId:${environmentId}, orgId:${orgId}, websiteName:${websiteName}` }); + } + else { + sendTelemetryEvent(telemetry, { eventName: VSCODE_EXTENSION_PPAPI_CREATE_WEBSITE_FAILED, errorMsg: `Failed to create website. Response status: ${response.status}` }); + } + } + catch (error) { + sendTelemetryEvent(telemetry, { eventName: VSCODE_EXTENSION_PPAPI_CREATE_WEBSITE_FAILED, errorMsg: (error as Error).message }); + } + + return null; + } + + static async getPPAPIServiceEndpoint(serviceEndpointStamp: ServiceEndpointCategory, telemetry: ITelemetry, environmentId: string, websitePreviewId?: string): Promise { + + let ppapiEndpoint = ""; + + switch (serviceEndpointStamp) { + case ServiceEndpointCategory.TEST: + ppapiEndpoint = "https://api.test.powerplatform.com"; + break; + case ServiceEndpointCategory.PREPROD: + ppapiEndpoint = "https://api.preprod.powerplatform.com"; + break; + case ServiceEndpointCategory.PROD: + ppapiEndpoint = "https://api.powerplatform.com"; + break; + // All below endpoints are not supported yet + case ServiceEndpointCategory.DOD: + case ServiceEndpointCategory.GCC: + case ServiceEndpointCategory.HIGH: + case ServiceEndpointCategory.MOONCAKE: + default: + sendTelemetryEvent(telemetry, { eventName: VSCODE_EXTENSION_GET_PPAPI_WEBSITES_ENDPOINT_UNSUPPORTED_REGION, data: serviceEndpointStamp }); + break; + } + + return PPAPI_WEBSITES_ENDPOINT.replace("{rootURL}", ppapiEndpoint) + .replace("{environmentId}", environmentId) + + (websitePreviewId ? `/${websitePreviewId}` : '') + + `?api-version=${PPAPI_WEBSITES_API_VERSION}`; + } +} diff --git a/src/common/services/TelemetryConstants.ts b/src/common/services/TelemetryConstants.ts index 3300bfd3..be53bc3c 100644 --- a/src/common/services/TelemetryConstants.ts +++ b/src/common/services/TelemetryConstants.ts @@ -15,7 +15,14 @@ export const VSCODE_EXTENSION_GRAPH_CLIENT_AUTHENTICATION_FAILED = "VSCodeExtens export const VSCODE_EXTENSION_GRAPH_CLIENT_AUTHENTICATION_COMPLETED = "VSCodeExtensionGraphClientAuthenticationCompleted"; export const VSCODE_EXTENSION_BAP_SERVICE_AUTHENTICATION_FAILED = "VSCodeExtensionBAPServiceAuthenticationFailed"; export const VSCODE_EXTENSION_BAP_SERVICE_AUTHENTICATION_COMPLETED = "VSCodeExtensionBAPServiceAuthenticationCompleted"; +export const VSCODE_EXTENSION_PPAPI_WEBSITES_AUTHENTICATION_FAILED = "VSCodeExtensionPPAPIWebsitesAuthenticationFailed"; +export const VSCODE_EXTENSION_PPAPI_WEBSITES_AUTHENTICATION_COMPLETED = "VSCodeExtensionPPAPIWebsitesAuthenticationCompleted"; export const VSCODE_EXTENSION_GET_CROSS_GEO_DATA_MOVEMENT_ENABLED_FLAG_FAILED = "VSCodeExtensionGetCrossGeoDataMovementEnabledFlagFailed"; export const VSCODE_EXTENSION_GET_CROSS_GEO_DATA_MOVEMENT_ENABLED_FLAG_COMPLETED = "VSCodeExtensionGetCrossGeoDataMovementEnabledFlagCompleted"; export const VSCODE_EXTENSION_GET_BAP_ENDPOINT_UNSUPPORTED_REGION = "VSCodeExtensionGetBAPEndpointUnsupportedRegion"; +export const VSCODE_EXTENSION_GET_PPAPI_WEBSITES_ENDPOINT_UNSUPPORTED_REGION = "VSCodeExtensionGetPPAPIWebsitesEndpointUnsupportedRegion"; export const VSCODE_EXTENSION_DECODE_JWT_TOKEN_FAILED = "VSCodeExtensionDecodeJWTTokenFailed"; +export const VSCODE_EXTENSION_PPAPI_GET_WEBSITE_BY_ID_COMPLETED = "VSCodeExtensionPPAPIGetWebsiteByIdCompleted"; +export const VSCODE_EXTENSION_PPAPI_GET_WEBSITE_BY_ID_FAILED = "VSCodeExtensionPPAPIGetWebsiteByIdFailed"; +export const VSCODE_EXTENSION_PPAPI_CREATE_WEBSITE_COMPLETED = "VSCodeExtensionPPAPICreateWebsiteCompleted"; +export const VSCODE_EXTENSION_PPAPI_CREATE_WEBSITE_FAILED = "VSCodeExtensionPPAPICreateWebsiteFailed"; diff --git a/src/common/utilities/Utils.ts b/src/common/utilities/Utils.ts index 336a7611..43268426 100644 --- a/src/common/utilities/Utils.ts +++ b/src/common/utilities/Utils.ts @@ -13,7 +13,7 @@ import { sendTelemetryEvent } from "../copilot/telemetry/copilotTelemetry"; import { getDisabledOrgList, getDisabledTenantList } from "../copilot/utils/copilotUtil"; import { CopilotNotAvailable, CopilotNotAvailableECSConfig } from "../copilot/telemetry/telemetryConstants"; import { bapServiceAuthentication } from "../services/AuthenticationProvider"; -import { BAP_ENVIRONMENT_LIST_URL, BAP_SERVICE_ENDPOINT, BAPServiceStamp } from "../services/Constants"; +import { BAP_ENVIRONMENT_LIST_URL, BAP_SERVICE_ENDPOINT, ServiceEndpointCategory} from "../services/Constants"; export function getSelectedCode(editor: vscode.TextEditor): string { if (!editor) { @@ -190,7 +190,7 @@ export function checkCopilotAvailability( } //API call to get env list for a org -export async function getEnvList(telemetry: ITelemetry, endpointStamp: BAPServiceStamp): Promise<{ envId: string, envDisplayName: string }[]> { +export async function getEnvList(telemetry: ITelemetry, endpointStamp: ServiceEndpointCategory): Promise<{ envId: string, envDisplayName: string }[]> { const envInfo: { envId: string, envDisplayName: string }[] = []; try { const bapAuthToken = await bapServiceAuthentication(telemetry, true); @@ -220,24 +220,24 @@ export async function getEnvList(telemetry: ITelemetry, endpointStamp: BAPServic } } -export function getBAPEndpoint(serviceEndpointStamp: BAPServiceStamp, telemetry: ITelemetry): string { +export function getBAPEndpoint(serviceEndpointStamp: ServiceEndpointCategory, telemetry: ITelemetry): string { let bapEndpoint = ""; switch (serviceEndpointStamp) { - case BAPServiceStamp.TEST: + case ServiceEndpointCategory.TEST: bapEndpoint = "https://test.api.bap.microsoft.com"; break; - case BAPServiceStamp.PREPROD: + case ServiceEndpointCategory.PREPROD: bapEndpoint = "https://preprod.api.bap.microsoft.com"; break; - case BAPServiceStamp.PROD: + case ServiceEndpointCategory.PROD: bapEndpoint = "https://api.bap.microsoft.com"; break; // All below endpoints are not supported yet - case BAPServiceStamp.DOD: - case BAPServiceStamp.GCC: - case BAPServiceStamp.HIGH: - case BAPServiceStamp.MOONCAKE: + case ServiceEndpointCategory.DOD: + case ServiceEndpointCategory.GCC: + case ServiceEndpointCategory.HIGH: + case ServiceEndpointCategory.MOONCAKE: default: sendTelemetryEvent(telemetry, { eventName: "VSCODE_EXTENSION_GET_BAP_ENDPOINT_UNSUPPORTED_REGION", data: serviceEndpointStamp }); break; diff --git a/src/web/client/extension.ts b/src/web/client/extension.ts index a65c2a69..619dde70 100644 --- a/src/web/client/extension.ts +++ b/src/web/client/extension.ts @@ -650,8 +650,8 @@ function isActiveDocument(fileFsPath: string): boolean { } async function fetchArtemisData(orgId: string): Promise { - const artemisResponse = await ArtemisService.fetchArtemisResponse(orgId, WebExtensionContext.telemetry.getTelemetryReporter()); - if (artemisResponse === null || artemisResponse.length === 0) { + const artemisResponse = await ArtemisService.getArtemisResponse(orgId, WebExtensionContext.telemetry.getTelemetryReporter(), ""); + if (artemisResponse === null || artemisResponse.response === null) { WebExtensionContext.telemetry.sendErrorTelemetry( webExtensionTelemetryEventNames.WEB_EXTENSION_ARTEMIS_RESPONSE_FAILED, fetchArtemisData.name,