Add application insights and cookie banner to Storybook (#155)
This commit is contained in:
Родитель
430c147a54
Коммит
a874dcb3b6
|
@ -75,6 +75,7 @@ jobs:
|
||||||
working-directory: ./packages/storybook
|
working-directory: ./packages/storybook
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}
|
||||||
|
TELEMETRY_INSTRUMENTATION_KEY: ${{ secrets.TELEMETRY_INSTRUMENTATION_KEY }}
|
||||||
|
|
||||||
# Create a GitHub issue if the CI failed when running on the `main` branch
|
# Create a GitHub issue if the CI failed when running on the `main` branch
|
||||||
- name: Create issue if main branch CI failed
|
- name: Create issue if main branch CI failed
|
||||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -29,7 +29,8 @@ module.exports = {
|
||||||
// Note: This triggers babel to retranspile all package dependency files during webpack's compilation step.
|
// Note: This triggers babel to retranspile all package dependency files during webpack's compilation step.
|
||||||
config.resolve.alias = {
|
config.resolve.alias = {
|
||||||
...(config.resolve.alias || {}),
|
...(config.resolve.alias || {}),
|
||||||
"@azure/communication-ui": path.resolve(__dirname, "../../communication-ui/src")
|
"@azure/communication-ui": path.resolve(__dirname, "../../communication-ui/src"),
|
||||||
|
"@azure/acs-chat-selector": path.resolve(__dirname, "../../acs-chat-selector/src")
|
||||||
}
|
}
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
<script src="https://wcpstatic.microsoft.com/mscc/lib/v2/wcp-consent.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
const cookieBannerId = 'cookie-banner';
|
||||||
|
const cookieBannerContainer = document.createElement('div');
|
||||||
|
cookieBannerContainer.id = cookieBannerId;
|
||||||
|
document.body.insertBefore(cookieBannerContainer, document.body.firstChild);
|
||||||
|
|
||||||
|
function onConsentChanged(categoryPreferences) {
|
||||||
|
window.cookieConsentChanged && window.cookieConsentChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
function initCallback(err, _siteConsent) {
|
||||||
|
if (!err) {
|
||||||
|
siteConsent = _siteConsent; // siteConsent is used to get the current consent
|
||||||
|
window.cookieConsentChanged && window.cookieConsentChanged(); // call callback now cookie library has finished initializing
|
||||||
|
} else {
|
||||||
|
console.log(`Error initializing WcpConsent: ${err}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.WcpConsent && WcpConsent.init('en-US', cookieBannerId, initCallback, onConsentChanged, WcpConsent.themes.light);
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -1,5 +1,8 @@
|
||||||
import { create } from '@storybook/theming';
|
import { create } from '@storybook/theming';
|
||||||
import { addons } from '@storybook/addons';
|
import { addons } from '@storybook/addons';
|
||||||
|
import { initTelemetry } from './telemetry';
|
||||||
|
|
||||||
|
initTelemetry();
|
||||||
|
|
||||||
addons.setConfig({
|
addons.setConfig({
|
||||||
theme: create({
|
theme: create({
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
import { analyticsCookieConsentObtained } from "./telemetry";
|
||||||
|
|
||||||
|
describe('storybook telemetry util tests', () => {
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// reset cookie consent window variables
|
||||||
|
(window as any).siteConsent = undefined;
|
||||||
|
})
|
||||||
|
|
||||||
|
describe ('test analyticsCookieConsentObtained', () => {
|
||||||
|
|
||||||
|
test('analyticsCookieConsentObtained returns false if telemetry library is not initialized', () => {
|
||||||
|
// set cookie consent library to uninitialized
|
||||||
|
(window as any).siteConsent = undefined;
|
||||||
|
|
||||||
|
const result = analyticsCookieConsentObtained();
|
||||||
|
|
||||||
|
expect(result).toEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('analyticsCookieConsentObtained returns false if telemetry library is initialized but consent has not been obtained', () => {
|
||||||
|
// set cookie consent library to uninitialized
|
||||||
|
(window as any).siteConsent = {
|
||||||
|
isConsentRequired: true,
|
||||||
|
getConsent: () => ({
|
||||||
|
Required: true,
|
||||||
|
Analytics: false
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = analyticsCookieConsentObtained();
|
||||||
|
|
||||||
|
expect(result).toEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('analyticsCookieConsentObtained returns true if telemetry library is initialized and consent has been obtained', () => {
|
||||||
|
// set cookie consent library to uninitialized
|
||||||
|
(window as any).siteConsent = {
|
||||||
|
isConsentRequired: true,
|
||||||
|
getConsent: () => ({
|
||||||
|
Required: true,
|
||||||
|
Analytics: true
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = analyticsCookieConsentObtained();
|
||||||
|
|
||||||
|
expect(result).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('analyticsCookieConsentObtained returns true if telemetry library is initialized but consent is not required', () => {
|
||||||
|
// set cookie consent library to uninitialized
|
||||||
|
(window as any).siteConsent = {
|
||||||
|
isConsentRequired: false,
|
||||||
|
getConsent: () => ({
|
||||||
|
Required: false,
|
||||||
|
Analytics: false
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = analyticsCookieConsentObtained();
|
||||||
|
|
||||||
|
expect(result).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
})
|
||||||
|
});
|
|
@ -0,0 +1,68 @@
|
||||||
|
import { ApplicationInsights } from '@microsoft/applicationinsights-web'
|
||||||
|
import { ReactPlugin } from '@microsoft/applicationinsights-react-js';
|
||||||
|
import { createBrowserHistory } from "history";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if we have the necessary cookie consent to allow the app insights library to make use of cookies
|
||||||
|
*/
|
||||||
|
export const analyticsCookieConsentObtained = (): boolean => {
|
||||||
|
return !!(window as any).siteConsent && // has telemetry library been initialized
|
||||||
|
(!(window as any).siteConsent.isConsentRequired || // check if we need collect consent in this region
|
||||||
|
(window as any).siteConsent.getConsent().Analytics); // check if we have consent to collect analytics telemetry
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start telemetry collection and watch for cookie consent changes.
|
||||||
|
*/
|
||||||
|
export const initTelemetry = () => {
|
||||||
|
const appInsightsInstance = startTelemetry(analyticsCookieConsentObtained());
|
||||||
|
|
||||||
|
if (appInsightsInstance) {
|
||||||
|
createCookieChangedCallback(appInsightsInstance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup the window.cookieConsentChanged that is called when the cookie banner's onConsentChanged is called.
|
||||||
|
* @param applicationInsightsInstance application instance that enables or disables appInsight's cookie manager
|
||||||
|
*/
|
||||||
|
const createCookieChangedCallback = (applicationInsightsInstance: ApplicationInsights) => {
|
||||||
|
(window as any).cookieConsentChanged = () => {
|
||||||
|
const analyticsCookieConsent = analyticsCookieConsentObtained();
|
||||||
|
applicationInsightsInstance.getCookieMgr().setEnabled(analyticsCookieConsent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start app insights tracking telemetry
|
||||||
|
* @param cookieConsent do we have consent to collect cookies for analytics purposes
|
||||||
|
* @returns the created instance of the application insights library
|
||||||
|
*/
|
||||||
|
const startTelemetry = (cookieConsent: boolean): ApplicationInsights | undefined => {
|
||||||
|
const instrumentationKey = process.env.TELEMETRY_INSTRUMENTATION_KEY;
|
||||||
|
if (!instrumentationKey) {
|
||||||
|
console.warn('No telemetry instrumentationKey provided. Telemetry collection is disabled.')
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize telemetry react plugin
|
||||||
|
const browserHistory = createBrowserHistory({ window });
|
||||||
|
var reactPlugin = new ReactPlugin();
|
||||||
|
|
||||||
|
|
||||||
|
// Initialize and start collecting telemetry
|
||||||
|
const appInsights = new ApplicationInsights({
|
||||||
|
config: {
|
||||||
|
disableCookiesUsage: !cookieConsent,
|
||||||
|
instrumentationKey,
|
||||||
|
enableAutoRouteTracking: true,
|
||||||
|
extensions: [reactPlugin],
|
||||||
|
extensionConfig: {
|
||||||
|
[reactPlugin.identifier]: { history: browserHistory }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
appInsights.loadAppInsights();
|
||||||
|
|
||||||
|
return appInsights;
|
||||||
|
}
|
|
@ -105,10 +105,10 @@ module.exports = {
|
||||||
// restoreMocks: false,
|
// restoreMocks: false,
|
||||||
|
|
||||||
// The root directory that Jest should scan for tests and modules within
|
// The root directory that Jest should scan for tests and modules within
|
||||||
rootDir: './stories',
|
rootDir: '.',
|
||||||
|
|
||||||
// A list of paths to directories that Jest should use to search for files in
|
// A list of paths to directories that Jest should use to search for files in
|
||||||
// roots: ['stories'],
|
roots: ['stories', '.storybook'],
|
||||||
|
|
||||||
// Allows you to use a custom runner instead of Jest's default test runner
|
// Allows you to use a custom runner instead of Jest's default test runner
|
||||||
// runner: "jest-runner",
|
// runner: "jest-runner",
|
||||||
|
@ -161,7 +161,7 @@ module.exports = {
|
||||||
// A map from regular expressions to paths to transformers
|
// A map from regular expressions to paths to transformers
|
||||||
transform: {
|
transform: {
|
||||||
'^.+\\.(ts)x?$': 'ts-jest',
|
'^.+\\.(ts)x?$': 'ts-jest',
|
||||||
'^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '<rootDir>/../jest/fileTransform.js'
|
'^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '<rootDir>/jest/fileTransform.js'
|
||||||
},
|
},
|
||||||
|
|
||||||
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
|
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
|
||||||
|
|
|
@ -30,11 +30,14 @@
|
||||||
"@fluentui/react-icons-northstar": "^0.51.2",
|
"@fluentui/react-icons-northstar": "^0.51.2",
|
||||||
"@fluentui/react-northstar": "^0.51.2",
|
"@fluentui/react-northstar": "^0.51.2",
|
||||||
"@fluentui/react-theme-provider": "^0.18.0",
|
"@fluentui/react-theme-provider": "^0.18.0",
|
||||||
"classnames": "^2.2.6",
|
"@microsoft/applicationinsights-react-js": "~3.0.5",
|
||||||
"react-aria-live": "^2.0.5",
|
"@microsoft/applicationinsights-web": "~2.6.1",
|
||||||
"react-linkify": "^1.0.0-alpha",
|
|
||||||
"@uifabric/react-hooks": "~7.13.11",
|
"@uifabric/react-hooks": "~7.13.11",
|
||||||
"copy-to-clipboard": "~3.3.1"
|
"classnames": "^2.2.6",
|
||||||
|
"copy-to-clipboard": "~3.3.1",
|
||||||
|
"history": "~5.0.0",
|
||||||
|
"react-aria-live": "^2.0.5",
|
||||||
|
"react-linkify": "^1.0.0-alpha"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "^16.13.1",
|
"react": "^16.13.1",
|
||||||
|
@ -46,16 +49,16 @@
|
||||||
"@mdx-js/react": "^1.6.22",
|
"@mdx-js/react": "^1.6.22",
|
||||||
"@microsoft/api-documenter": "~7.12.11",
|
"@microsoft/api-documenter": "~7.12.11",
|
||||||
"@microsoft/api-extractor": "~7.13.2",
|
"@microsoft/api-extractor": "~7.13.2",
|
||||||
"@storybook/addon-actions": "^6.1.1",
|
"@storybook/addon-actions": "~6.1.1",
|
||||||
"@storybook/addon-docs": "^6.1.18",
|
"@storybook/addon-docs": "~6.1.18",
|
||||||
"@storybook/addon-essentials": "^6.1.1",
|
"@storybook/addon-essentials": "~6.1.1",
|
||||||
"@storybook/addon-knobs": "^6.1.9",
|
"@storybook/addon-knobs": "~6.1.9",
|
||||||
"@storybook/addon-links": "^6.1.1",
|
"@storybook/addon-links": "~6.1.1",
|
||||||
"@storybook/addon-storyshots": "^6.1.6",
|
"@storybook/addon-storyshots": "~6.1.6",
|
||||||
"@storybook/node-logger": "^6.1.1",
|
"@storybook/node-logger": "~6.1.1",
|
||||||
"@storybook/react": "^6.1.1",
|
"@storybook/react": "~6.1.1",
|
||||||
"@storybook/storybook-deployer": "^2.8.7",
|
"@storybook/storybook-deployer": "~2.8.7",
|
||||||
"@storybook/theming": "^6.1.10",
|
"@storybook/theming": "~6.1.10",
|
||||||
"@testing-library/jest-dom": "^5.11.4",
|
"@testing-library/jest-dom": "^5.11.4",
|
||||||
"@testing-library/react-hooks": "^3.4.2",
|
"@testing-library/react-hooks": "^3.4.2",
|
||||||
"@types/classnames": "^2.2.11",
|
"@types/classnames": "^2.2.11",
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Meta } from '@storybook/react/types-6-0';
|
||||||
|
import { PrimaryButton } from '@fluentui/react';
|
||||||
|
|
||||||
|
function openManageCookiesModal(): void {
|
||||||
|
(window as any).parent.siteConsent?.manageConsent();
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ManageCookies: () => JSX.Element = () => {
|
||||||
|
const manageCookiesRequired = (window as any).parent.siteConsent?.isConsentRequired;
|
||||||
|
const buttonText = manageCookiesRequired ? 'Manage Cookies' : 'Manage Cookies unavailable';
|
||||||
|
return <PrimaryButton text={buttonText} onClick={openManageCookiesModal} disabled={!manageCookiesRequired} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: `Settings/Manage Cookies`
|
||||||
|
} as Meta;
|
|
@ -1000,6 +1000,59 @@ exports[`storybook snapshot tests Storyshots Examples/Themes Teams Theme Compone
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`storybook snapshot tests Storyshots Settings/Manage Cookies Manage Cookies 1`] = `
|
||||||
|
<div
|
||||||
|
className="css-75 root-0 body-1"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="css-75"
|
||||||
|
data-uses-unhanded-props={true}
|
||||||
|
dir="ltr"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"alignItems": "center",
|
||||||
|
"display": "flex",
|
||||||
|
"height": "100vh",
|
||||||
|
"justifyContent": "center",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
aria-disabled={true}
|
||||||
|
className="ms-Button ms-Button--primary is-disabled root-2"
|
||||||
|
data-is-focusable={false}
|
||||||
|
disabled={true}
|
||||||
|
onClick={[Function]}
|
||||||
|
onKeyDown={[Function]}
|
||||||
|
onKeyPress={[Function]}
|
||||||
|
onKeyUp={[Function]}
|
||||||
|
onMouseDown={[Function]}
|
||||||
|
onMouseUp={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className="ms-Button-flexContainer flexContainer-3"
|
||||||
|
data-automationid="splitbuttonprimary"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className="ms-Button-textContainer textContainer-4"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className="ms-Button-label label-6"
|
||||||
|
id="id__0"
|
||||||
|
>
|
||||||
|
Manage Cookies unavailable
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`storybook snapshot tests Storyshots UI Components/GridLayout Grid Layout Component 1`] = `
|
exports[`storybook snapshot tests Storyshots UI Components/GridLayout Grid Layout Component 1`] = `
|
||||||
<div
|
<div
|
||||||
className="css-75 root-0 body-1"
|
className="css-75 root-0 body-1"
|
||||||
|
|
|
@ -25,6 +25,6 @@
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"moduleResolution": "node"
|
"moduleResolution": "node"
|
||||||
},
|
},
|
||||||
"include": ["stories/**/*"],
|
"include": ["stories/**/*", "./.storybook/**/*.test.ts"],
|
||||||
"exclude": ["dist", "node_modules"]
|
"exclude": ["dist", "node_modules"]
|
||||||
}
|
}
|
||||||
|
|
Загрузка…
Ссылка в новой задаче