Pass RenderSurvey API parameter data & Added telemetry events/Localization (#457)

Passing proper parameter
Added telemetry
Added localization
This commit is contained in:
BidishaMS 2023-02-23 15:38:32 +05:30 коммит произвёл GitHub
Родитель eef7f10b43
Коммит c91f580806
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
10 изменённых файлов: 126 добавлений и 49 удалений

Просмотреть файл

@ -337,6 +337,11 @@ The {2} represents Solution's Version number</note>
<source xml:lang="en">Try again</source>
</trans-unit>
</body></file>
<file original="./web/client/webViews/NPSWebView" source-language="en" datatype="plaintext"><body>
<trans-unit id="microsoft-powerapps-portals.webExtension.npsSurvey.desc">
<source xml:lang="en">Microsoft wants your feeback</source>
</trans-unit>
</body></file>
<file original="./web/client/test/integration/errorHandler.test" source-language="en" datatype="plaintext"><body>
<trans-unit id="microsoft-powerapps-portals.webExtension.init.app-not-found">
<source xml:lang="en">Unable to find that app</source>

Просмотреть файл

@ -3,24 +3,15 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*/
// "use strict";
// const path = require('path');
// const cancel = path.resolve('../src/web/client/assets/cancel.svg');
/* eslint-disable no-undef */
function renderSurvey(parentElementId,FirstName, LastName, locale, environmentId, geo, UserId, TenantId, prompted)
function renderSurvey(TenantId,UserId, EnvironmentId,Geo,ProductVersion,Culture,DeviceType,UrlReferrer)
{
// eslint-disable-next-line no-undef
const se = new window['SurveyEmbed']("v4j5cvGGr0GRqy180BHbRytFqxSnvs1AqKx-mFT6qLBUOE5POUVGTVRDUDI1SEVaOFVaV1RGM0k4VyQlQCN0PWcu",
"https://customervoice.microsoft.com/","https://mfpembedcdnmsit.azureedge.net/mfpembedcontmsit","true");
const context = {
"First Name": FirstName,
"Last Name": LastName,
"locale": locale,
"environmentId": environmentId,
"geo": geo,
"UserId": UserId,
"TenantId": TenantId,
"prompted": prompted,
TenantId,UserId, EnvironmentId,Geo,ProductVersion,Culture,DeviceType,UrlReferrer
};
se.renderPopup(context);
}
@ -61,14 +52,19 @@ function applyCustomStyles() {
// eslint-disable-next-line no-undef
const crossButtonDiv = document.getElementById('MfpEmbed_CrossButton');
if (crossButtonDiv) {
// TODO: need to render cancel button
// crossButtonDiv.setAttribute('src',cancel.toString());
crossButtonDiv.style.width = '14px';
crossButtonDiv.style.height = '14px';
crossButtonDiv.style.boxSizing = 'unset';
crossButtonDiv.style.cursor = 'pointer';
crossButtonDiv.remove();
const cancelSvgDiv = document.createElement("div");
cancelSvgDiv.innerHTML = `<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.1016 1.60156L8.20312 7.5L14.1016 13.3984L13.3984 14.1016L7.5 8.20312L1.60156 14.1016L0.898438 13.3984L6.79688 7.5L0.898438 1.60156L1.60156 0.898438L7.5 6.79688L13.3984 0.898438L14.1016 1.60156Z" fill="#323130"/>
</svg>`;
cancelSvgDiv.style.width = '14px';
cancelSvgDiv.style.height = '14px';
cancelSvgDiv.style.boxSizing = 'unset';
cancelSvgDiv.style.cursor = 'pointer';
iconDiv.appendChild(cancelSvgDiv)
}
// eslint-disable-next-line no-undef
const iFrame = document.getElementById('MfpEmbed_Popup_Iframe');
if (iFrame) {
@ -108,8 +104,16 @@ function applyCustomStyles() {
}
function loadSurvey(){
// TODO: Replace by actual value
renderSurvey("surveyDiv", "Bert", "Hair", "en-US", "123", "IND", "bert.hair@contoso.com", "12345", "Product Overview");
const el = document.querySelector("#npsContext");
const tenantId = el.dataset.tid;
const userId = el.dataset.uid;
const envId = el.dataset.envId;
const geo = el.dataset.geo;
const culture = el.dataset.culture;
const productVersion = el.dataset.productVersion;
const urlReferrer = el.dataset.urlReferrer;
const deviceType = el.dataset.deviceType;
renderSurvey(tenantId,userId,envId,geo,productVersion,culture,deviceType,urlReferrer);
resizeSurvey();
applyCustomStyles();
}

Просмотреть файл

@ -63,6 +63,7 @@ class WebExtensionContext implements IWebExtensionContext {
private _currentSchemaVersion: string;
private _telemetry: WebExtensionTelemetry;
private _npsEligibility: boolean;
private _userId: string;
public get schemaDataSourcePropertiesMap() { return this._schemaDataSourcePropertiesMap; }
public get schemaEntitiesMap() { return this._schemaEntitiesMap; }
@ -82,6 +83,7 @@ class WebExtensionContext implements IWebExtensionContext {
public get currentSchemaVersion() { return this._currentSchemaVersion; }
public get telemetry() { return this._telemetry; }
public get npsEligibility() { return this._npsEligibility; }
public get userId() { return this._userId; }
constructor() {
this._schemaDataSourcePropertiesMap = new Map<string, string>();
@ -102,6 +104,7 @@ class WebExtensionContext implements IWebExtensionContext {
this._currentSchemaVersion = "";
this._telemetry = new WebExtensionTelemetry();
this._npsEligibility = false;
this._userId = "";
}
public setWebExtensionContext(entityName: string, entityId: string, queryParamsMap: Map<string, string>) {
@ -269,6 +272,9 @@ class WebExtensionContext implements IWebExtensionContext {
public setNPSEligibility(eligibility: boolean) {
this._npsEligibility = eligibility;
}
public setUserId(uid: string) {
this._userId = uid;
}
}
export default new WebExtensionContext();

Просмотреть файл

@ -1,3 +0,0 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.1016 1.60156L8.20312 7.5L14.1016 13.3984L13.3984 14.1016L7.5 8.20312L1.60156 14.1016L0.898438 13.3984L6.79688 7.5L0.898438 1.60156L1.60156 0.898438L7.5 6.79688L13.3984 0.898438L14.1016 1.60156Z" fill="#323130"/>
</svg>

До

Ширина:  |  Высота:  |  Размер: 328 B

Просмотреть файл

@ -38,15 +38,19 @@ export enum queryParameters {
REFERRER = 'referrer',
SITE_VISIBILITY = 'sitevisibility',
WEBSITE_NAME = 'websitename',
ORG_URL = 'orgurl'
ORG_URL = 'orgurl',
REGION = 'region',
ENV_ID = 'envId',
GEO = 'geo'
}
export enum httpMethod {
PATCH = 'PATCH',
GET = 'GET'
GET = 'GET',
POST = 'POST'
}
export enum CESSurvey {
export enum SurveyConstants {
TEAM_NAME = 'PowerPages',
SURVEY_NAME = 'PowerPages-NPS',
EVENT_NAME = 'vsCodeWeb',

Просмотреть файл

@ -5,9 +5,10 @@
import jwt_decode from 'jwt-decode';
import { npsAuthentication } from "../common/authenticationProvider";
import {CESSurvey} from '../common/constants';
import {SurveyConstants, httpMethod, queryParameters} from '../common/constants';
import fetch,{RequestInit} from 'node-fetch'
import WebExtensionContext from '../WebExtensionContext';
import { telemetryEventNames } from '../telemetry/constants';
export class NPSService{
public static getCesHeader(accessToken: string) {
@ -19,21 +20,29 @@ export class NPSService{
}
public static async getEligibility() {
const baseApiUrl = "https://ces-int.microsoftcloud.com/api/v1"; // TODO: change int to prod based on the query params from Studio
const accessToken: string = await npsAuthentication(CESSurvey.AUTHORIZATION_ENDPOINT);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const parsedToken = jwt_decode(accessToken) as any;
const apiEndpoint = `${baseApiUrl}/${CESSurvey.TEAM_NAME}/Eligibilities/${CESSurvey.SURVEY_NAME}?userId=${parsedToken?.oid}&eventName=${CESSurvey.EVENT_NAME}&tenantId=${parsedToken.tid}`;
const requestInitPost: RequestInit = {
method: 'POST',
body:'{}',
headers:NPSService.getCesHeader(accessToken)
};
const response = await fetch(apiEndpoint, requestInitPost);
const result = await response?.json();
// TODO: telemetry
if( result?.eligibility){
WebExtensionContext.setNPSEligibility(true);
const region = WebExtensionContext.urlParametersMap?.get(queryParameters.REGION)
const baseApiUrl = region === 'test' ? "https://ces-int.microsoftcloud.com/api/v1": "https://ces.microsoftcloud.com/api/v1";
try{
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}/${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 fetch(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);
WebExtensionContext.setNPSEligibility(true);
}
}catch(error){
WebExtensionContext.telemetry.sendErrorTelemetry(telemetryEventNames.NPS_API_FAILED, (error as Error)?.message);
}
}
}

Просмотреть файл

@ -30,5 +30,8 @@ export enum telemetryEventNames {
WEB_EXTENSION_ENTITY_CONTENT_SAME = 'WebExtensionEntityContentSame',
NPS_AUTHENTICATION_STARTED = 'WebExtensionNPSAuthenticationStarted',
NPS_AUTHENTICATION_COMPLETED = 'WebExtensionNPSAuthenticationCompleted',
NPS_AUTHENTICATION_FAILED = 'WebExtensionNPSAuthenticationFailed'
NPS_AUTHENTICATION_FAILED = 'WebExtensionNPSAuthenticationFailed',
NPS_USER_ELIGIBLE = "WebExtensionUserIsEligible",
NPS_API_FAILED = "WebExtensionNPSApiFailed",
RENDER_NPS = 'WebExtensionNPSRenderSurveyForm'
}

Просмотреть файл

@ -0,0 +1,35 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*/
export function getDeviceType(): string {
if (isMobileDevice()) {
return 'Mobile';
} else if (isTabletDevice()) {
return 'Tablet';
} else {
return 'Desktop';
}
}
/**
* Get a flag indicating whether or not the user's device is a mobile (but NOT tablet) device
*
* Regex taken from is-mobile package
*/
export function isMobileDevice(): boolean {
const mobileRE = /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series[46]0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i;
return mobileRE.test(navigator.userAgent);
}
/**
* Get a flag indicating whether or not the user's device is a tablet device
*
* Regexes taken from is-mobile package
*/
export function isTabletDevice(): boolean {
const tabletRE = /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series[46]0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino|android|ipad|playbook|silk/i;
return tabletRE.test(navigator.userAgent);
}

Просмотреть файл

@ -4,6 +4,12 @@
*/
import * as vscode from 'vscode';
import WebExtensionContext from '../WebExtensionContext';
import { queryParameters } from "../common/constants";
import { getDeviceType } from '../utilities/deviceType';
import { telemetryEventNames } from '../telemetry/constants';
import * as nls from 'vscode-nls';
const localize: nls.LocalizeFunc = nls.loadMessageBundle();
export class NPSWebView {
private readonly _webviewPanel: vscode.WebviewPanel;
@ -14,21 +20,29 @@ export class NPSWebView {
}
private _getHtml() {
const nonce = getNonce();
const nonce = getNonce();
const mainJs = this.extensionResourceUrl('media','main.js');
const tid = WebExtensionContext.urlParametersMap?.get(queryParameters.TENANT_ID);
const uid = WebExtensionContext.userId;
const envId = WebExtensionContext.urlParametersMap?.get(queryParameters.ENV_ID);
const geo = WebExtensionContext.urlParametersMap?.get(queryParameters.GEO);
const culture = vscode.env.language;
const productVersion = process?.env?.BUILD_NAME;
const urlReferrer = window?.location?.href;
const deviceType = getDeviceType();
WebExtensionContext.telemetry.sendInfoTelemetry(telemetryEventNames.RENDER_NPS);
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Test CES Survey</title>
<script data-main="scripts/app" src="scripts/require.js"></script>
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; frame-src https://customervoice.microsoft.com/ ; img-src https://mfpembedcdnmsit.azureedge.net/mfpembedcontmsit/cross.svg ; style-src https://mfpembedcdnmsit.azureedge.net/mfpembedcontmsit/Embed.css 'nonce-${nonce}';script-src https://mfpembedcdnmsit.azureedge.net/mfpembedcontmsit/Embed.js 'nonce-${nonce}';">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; frame-src https://customervoice.microsoft.com/ ; img-src * 'self' data: https:; style-src https://mfpembedcdnmsit.azureedge.net/mfpembedcontmsit/Embed.css 'nonce-${nonce}';script-src https://mfpembedcdnmsit.azureedge.net/mfpembedcontmsit/Embed.js 'nonce-${nonce}';">
</head>
<body>
<div id="surveyDiv"></div>
<script src="https://mfpembedcdnmsit.azureedge.net/mfpembedcontmsit/Embed.js" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" href="https://mfpembedcdnmsit.azureedge.net/mfpembedcontmsit/Embed.css" />
<script nonce="${nonce}" type="module" src="${mainJs}"></script>
<script id="npsContext" data-urlReferrer = "${urlReferrer}" data-img="./src/web/client/assets/cancel.svg" data-tid="${tid}" data-uid="${uid}" data-envId="${envId}" data-geo="${geo}" data-deviceType ="${deviceType}" data-culture ="${culture}" data-productVersion ="${productVersion}" nonce="${nonce}" type="module" src="${mainJs}"></script>
</body>
</html>`;
}
@ -40,7 +54,7 @@ export class NPSWebView {
public static createOrShow(extensionUri: vscode.Uri): NPSWebView {
const webview = vscode.window.createWebviewPanel(
'testCESSurvey',
"Test CES Survey",
localize("microsoft-powerapps-portals.webExtension.npsSurvey.desc", "Microsoft wants your feeback"),
{viewColumn:vscode.ViewColumn.One,
preserveFocus:false
},

Просмотреть файл

@ -5,7 +5,7 @@
"sourceMap": true,
"outDir": "out",
"rootDir": "src",
"lib": [ "es2019", "WebWorker", "DOM.Iterable" ],
"lib": [ "es2019", "WebWorker", "DOM.Iterable","DOM" ],
"noImplicitAny": true,
"strictPropertyInitialization": true,
"strict": true,