├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── SECURITY.md ├── SUPPORT.md ├── babel.config.js ├── extensions └── microsoft-employee-experience │ ├── package.json │ ├── src │ ├── AuthClient │ │ ├── AuthClient.ts │ │ ├── AuthClient.types.ts │ │ └── index.ts │ ├── Buttons │ │ ├── ActionButton.ts │ │ ├── DefaultButton.ts │ │ ├── IButtonProps.ts │ │ ├── IconButton.ts │ │ ├── PrimaryButton.ts │ │ ├── withButtonClickLogging.tsx │ │ └── withButtonClickLogging.types.ts │ ├── Checkbox │ │ ├── Checkbox.tsx │ │ ├── Checkbox.types.ts │ │ └── index.ts │ ├── CodeSplitter │ │ ├── CodeSplitter.tsx │ │ ├── CodeSplitter.types.ts │ │ └── index.ts │ ├── ComponentProvider.ts │ ├── Context.tsx │ ├── DatePicker │ │ ├── DatePicker.tsx │ │ ├── DatePicker.types.ts │ │ └── index.ts │ ├── Dropdown │ │ ├── Dropdown.tsx │ │ ├── Dropdown.types.ts │ │ └── index.ts │ ├── GraphClient.ts │ ├── HttpClient.ts │ ├── IAuthClient.ts │ ├── IComponentConfig.ts │ ├── IComponentProps.ts │ ├── IDefaultState.ts │ ├── IEmployeeExperienceContext.ts │ ├── IGraphClient.ts │ ├── IHttpClient.ts │ ├── IReducerRegistry.ts │ ├── ITelemetryClient.ts │ ├── ITelemetryContext.ts │ ├── IUTPConfig.ts │ ├── IUsageClient.ts │ ├── IUser.ts │ ├── InputGroup.ts │ ├── Link │ │ ├── Link.tsx │ │ ├── Link.types.ts │ │ ├── Link.utils.ts │ │ └── index.ts │ ├── OnBuildOnce │ │ ├── OnBuildOnce.tsx │ │ └── index.ts │ ├── Persona │ │ ├── Persona.tsx │ │ ├── Persona.types.ts │ │ └── index.ts │ ├── ReducerRegistry.ts │ ├── RouteComponentProvider.tsx │ ├── Shell │ │ ├── Shell.styled.ts │ │ ├── Shell.tsx │ │ └── index.ts │ ├── StoreBuilder.ts │ ├── TelemetryClient.ts │ ├── TelemetryEvents.ts │ ├── TextField │ │ ├── TextField.tsx │ │ ├── TextField.types.ts │ │ └── index.ts │ ├── UsageClient.ts │ ├── UsageTelemetry │ │ ├── Config │ │ │ └── UsageTelemetryConfig.ts │ │ ├── Helpers │ │ │ ├── CuppSchemaMapper.ts │ │ │ ├── Usage.helper.ts │ │ │ └── UsageEventName.ts │ │ ├── UsageEvent.ts │ │ ├── UsageEvent │ │ │ ├── AwareEvent.ts │ │ │ ├── SytemEvent.ts │ │ │ └── UserEvent.ts │ │ ├── UsageFeatureProps.ts │ │ ├── UsageLog.ts │ │ ├── UsageTracker.ts │ │ ├── UserAttribute.ts │ │ └── index.ts │ ├── UsageTelemetryHelper.ts │ ├── WebpackConfigs │ │ ├── WebpackConfigs.ts │ │ ├── WebpackConfigs.utils.ts │ │ └── index.ts │ ├── useDynamicReducer.ts │ ├── useGraphPhoto.ts │ ├── useLoginOnStartup.ts │ ├── usePageTitle.ts │ ├── usePageTracking.ts │ ├── useUsageTelemetry.ts │ ├── useUser.ts │ └── withStore.tsx │ └── tsconfig.json ├── generators └── microsoft-employee-experience-generator │ ├── .gitattributes │ ├── .gitignore │ ├── README.md │ ├── examples │ ├── .eslintrc.js │ ├── .gitignore │ ├── .npmignore │ ├── .npmrc │ ├── .nvmrc │ ├── README.md │ ├── babel.config.js │ ├── global.d.ts │ ├── jest.config.js │ ├── package-lock.json │ ├── package.json │ ├── prettier.config.js │ ├── src │ │ ├── App.tsx │ │ ├── Components │ │ │ ├── Header │ │ │ │ ├── Header.styled.ts │ │ │ │ ├── Header.tsx │ │ │ │ ├── Header.types.ts │ │ │ │ └── index.ts │ │ │ ├── Main │ │ │ │ ├── Main.ts │ │ │ │ └── index.ts │ │ │ └── Nav │ │ │ │ ├── Nav.tsx │ │ │ │ ├── Nav.types.ts │ │ │ │ ├── index.ts │ │ │ │ └── useCoherenceNavGroups.ts │ │ ├── Routes.tsx │ │ ├── Samples │ │ │ ├── AdalClient.ts │ │ │ ├── BuildOnceAuthClient.ts │ │ │ ├── CodeSplitting │ │ │ │ ├── CodeSplitting.tsx │ │ │ │ └── MyProfile.tsx │ │ │ ├── DynamicReduxHooks │ │ │ │ └── DynamicReduxHooks.tsx │ │ │ ├── DynamicSubRoutes │ │ │ │ ├── DynamicRouteParamConsumer.tsx │ │ │ │ ├── DynamicSubRoutes.tsx │ │ │ │ └── config.js │ │ │ ├── MSALClient.ts │ │ │ └── Shared │ │ │ │ ├── Layout.ts │ │ │ │ ├── SharedExample.action-types.ts │ │ │ │ ├── SharedExample.actions.ts │ │ │ │ ├── SharedExample.reducer.ts │ │ │ │ ├── SharedExample.sagas.ts │ │ │ │ └── SharedExample.types.ts │ │ ├── ShellWithStore.tsx │ │ ├── navConfig.ts │ │ └── useHeaderConfig.tsx │ ├── tsconfig.json │ └── webpack.config.js │ ├── package.json │ └── yo │ ├── .yo-rc.json │ ├── generators │ └── app │ │ └── index.js │ └── package.json ├── jest.config.js ├── lage.config.js ├── lerna.json ├── package.json ├── packages ├── micro-frontend-react-redux │ ├── package.json │ ├── src │ │ ├── ComponentProvider.ts │ │ ├── Context.tsx │ │ ├── IComponentConfig.ts │ │ ├── IComponentProps.ts │ │ ├── IDefaultState.ts │ │ ├── IReducerRegistry.ts │ │ ├── IReduxContext.ts │ │ ├── IStoreBuilder.ts │ │ ├── IStoreBuilderResult.ts │ │ ├── InjectReduxContext.ts │ │ ├── ReducerRegistry.ts │ │ ├── StoreBuilder.ts │ │ ├── WebpackConfigs.ts │ │ └── useDynamicReducer.ts │ └── tsconfig.json └── micro-frontend-react │ ├── package.json │ ├── src │ ├── ComponentProvider │ │ ├── ComponentProvider.tsx │ │ ├── ComponentProvider.types.ts │ │ └── index.ts │ ├── Context.tsx │ ├── IComponentConfig.ts │ ├── IComponentProps.ts │ └── WebpackConfigs │ │ ├── WebpackConfigs.ts │ │ ├── WebpackConfigs.utils.ts │ │ └── index.ts │ └── tsconfig.json ├── prettier.config.js ├── samples ├── sample-react-host │ ├── global.d.ts │ ├── package.json │ ├── src │ │ ├── App.tsx │ │ └── Home.tsx │ ├── tsconfig.json │ └── webpack.config.js ├── sample-react-micro-frontend │ ├── global.d.ts │ ├── package.json │ ├── src │ │ └── MicroFrontendApp.tsx │ ├── tsconfig.json │ └── webpack.config.js ├── sample-react-redux-host │ ├── global.d.ts │ ├── package.json │ ├── public │ │ └── api │ │ │ └── user.json │ ├── src │ │ ├── App.tsx │ │ ├── DummyHttpClient.ts │ │ ├── Home.tsx │ │ └── SampleHostReduxStore.ts │ ├── tsconfig.json │ └── webpack.config.js └── sample-react-redux-micro-frontend │ ├── global.d.ts │ ├── package.json │ ├── src │ ├── MicroFrontendApp.tsx │ └── SampleMicroFrontendReduxStore.ts │ ├── tsconfig.json │ └── webpack.config.js └── test-result.config.js /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.js 3 | samples/*.ts 4 | lib 5 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | extends: [ 4 | 'plugin:@typescript-eslint/recommended', 5 | 'plugin:prettier/recommended', 6 | 'plugin:react/recommended', 7 | 'prettier', 8 | ], 9 | parserOptions: { 10 | ecmaVersion: 2018, 11 | sourceType: 'module', 12 | ecmaFeatures: { 13 | jsx: true, 14 | }, 15 | }, 16 | plugins: ['react-hooks'], 17 | settings: { 18 | react: { 19 | version: 'detect', 20 | }, 21 | }, 22 | rules: { 23 | '@typescript-eslint/interface-name-prefix': 0, 24 | 'react-hooks/rules-of-hooks': 'error', 25 | 'react-hooks/exhaustive-deps': 'warn', 26 | }, 27 | }; 28 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | # OS specific 64 | .DS_Store 65 | 66 | # Build artifacts 67 | lib/ 68 | !**/__mocks__/**/lib 69 | debug/ 70 | bin/ 71 | bundles/ 72 | **/public/index.html 73 | 74 | # editor specific 75 | .idea 76 | .vs/ 77 | .swp 78 | 79 | 80 | # test results 81 | test-results.trx 82 | 83 | # App specific 84 | generator/generators/app/templates/examples 85 | generator/generators/app/templates/myhub 86 | yarn.lock 87 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Micro-Frontend! 2 | 3 | This is an open source library that shares a set of utilities that can be used to support Micro-Frontend architecture in your React.js applications. 4 | 5 | While the proper documentation is being prepared, please see the following samples: 6 | 7 | ## React.js 8 | 9 | * [Host application](https://github.com/microsoft/microfrontend/blob/main/samples/sample-react-host/src/App.tsx) 10 | * The host application provides shared "Context" 11 | * The host application loads Micro-Frontend applications using or `ComponentProvider` with runtime configuration 12 | ``` tsx 13 | // Load a micro-frontend anywhere on the screen 14 | 20 | ``` 21 | * [Micro-Frontend application](https://github.com/microsoft/microfrontend/blob/main/samples/sample-react-micro-frontend/src/MicroFrontendApp.tsx) 22 | * Micro-Frontends are components that are developed and deployed in isolation 23 | * Micro-Frontends will receive receive the "Context" when mounted 24 | 25 | ## React.js with Redux 26 | * [Host application with Redux](https://github.com/microsoft/micro-frontend/blob/main/samples/sample-react-redux-host/src/App.tsx) 27 | * Setup host application with various redux extensions such as redux-sagas, redux-logger, redux-persist 28 | * Host's "Context" now includes utilities for Redux operations 29 | * [Micro-Frontend application with Redux](https://github.com/microsoft/micro-frontend/blob/main/samples/sample-react-redux-micro-frontend/src/MicroFrontendApp.tsx) 30 | * Retrieve data from host application's Redux store 31 | * Register new reducer and saga to the host application's Redux store 32 | 33 | ## Contributing 34 | 35 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 36 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 37 | the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. 38 | 39 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide 40 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions 41 | provided by the bot. You will only need to do this once across all repos using our CLA. 42 | 43 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 44 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 45 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 46 | 47 | ## Trademarks 48 | 49 | This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft 50 | trademarks or logos is subject to and must follow 51 | [Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general). 52 | Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. 53 | Any use of third-party trademarks or logos are subject to those third-party's policies. 54 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). 40 | 41 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # TODO: The maintainer of this repo has not yet edited this file 2 | 3 | **REPO OWNER**: Do you want Customer Service & Support (CSS) support for this product/project? 4 | 5 | - **No CSS support:** Fill out this template with information about how to file issues and get help. 6 | - **Yes CSS support:** Fill out an intake form at [aka.ms/spot](https://aka.ms/spot). CSS will work with/help you to determine next steps. More details also available at [aka.ms/onboardsupport](https://aka.ms/onboardsupport). 7 | - **Not sure?** Fill out a SPOT intake as though the answer were "Yes". CSS will help you decide. 8 | 9 | *Then remove this first heading from this SUPPORT.MD file before publishing your repo.* 10 | 11 | # Support 12 | 13 | ## How to file issues and get help 14 | 15 | This project uses GitHub Issues to track bugs and feature requests. Please search the existing 16 | issues before filing new issues to avoid duplicates. For new issues, file your bug or 17 | feature request as a new Issue. 18 | 19 | For help and questions about using this project, please **REPO MAINTAINER: INSERT INSTRUCTIONS HERE 20 | FOR HOW TO ENGAGE REPO OWNERS OR COMMUNITY FOR HELP. COULD BE A STACK OVERFLOW TAG OR OTHER 21 | CHANNEL. WHERE WILL YOU HELP PEOPLE?**. 22 | 23 | ## Microsoft Support Policy 24 | 25 | Support for this **PROJECT or PRODUCT** is limited to the resources listed above. 26 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | test: { 4 | plugins: ['@babel/plugin-transform-modules-commonjs'], 5 | }, 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@micro-frontend-react/employee-experience", 3 | "version": "1.0.15", 4 | "description": "Micro-Frontend React Extension for Microsoft Employee Experience Team", 5 | "main": "index.js", 6 | "author": "Microsoft", 7 | "license": "MIT", 8 | "module": "lib/index.js", 9 | "types": "lib/index.d.ts", 10 | "files": [ 11 | "lib", 12 | "package.json" 13 | ], 14 | "scripts": { 15 | "clean": "rimraf lib", 16 | "build": "tsc -p tsconfig.json", 17 | "release": "npm publish" 18 | }, 19 | "dependencies": { 20 | "@micro-frontend-react/redux": "^1.0.15" 21 | }, 22 | "peerDependencies": { 23 | "@azure/msal-browser": ">= ^2.22.0 < 3", 24 | "@fluentui/react": ">= ^8.56.1 < 9", 25 | "@microsoft/applicationinsights-web": ">= ^2.7.3 < 3", 26 | "axios": ">= ^0.26.0 < 1", 27 | "styled-components": ">= ^5.3.0 < 6", 28 | "uuid": ">= ^8.3.2 < 9" 29 | }, 30 | "publishConfig": { 31 | "access": "public", 32 | "registry": "https://registry.npmjs.org/" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/AuthClient/AuthClient.types.ts: -------------------------------------------------------------------------------- 1 | import { AccountInfo } from '@azure/msal-browser'; 2 | 3 | export interface IAuthClientOptions { 4 | onLogin?(): void; 5 | onLoginFailed?(): void; 6 | onLogout?(): void; 7 | onLogoutFailed?(): void; 8 | onMultipleAccountFound?: (users: AccountInfo[]) => AccountInfo; 9 | onAcquireTokenError?: (e: Error, scopes: string | string[]) => void; 10 | onGetUser?: (token: AccountInfo) => T; 11 | } 12 | 13 | export interface IIDTokenClaim { 14 | family_name?: string; 15 | given_name?: string; 16 | oid?: string; 17 | } 18 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/AuthClient/index.ts: -------------------------------------------------------------------------------- 1 | export * from './AuthClient'; 2 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/Buttons/ActionButton.ts: -------------------------------------------------------------------------------- 1 | import { ComponentType } from 'react'; 2 | import { ActionButton as FabricActionButton } from '@fluentui/react/lib/Button'; 3 | import { withButtonClickLogging } from './withButtonClickLogging'; 4 | import { IButtonProps } from './IButtonProps'; 5 | 6 | export const ActionButton = withButtonClickLogging(FabricActionButton as ComponentType); 7 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/Buttons/DefaultButton.ts: -------------------------------------------------------------------------------- 1 | import { ComponentType } from 'react'; 2 | import { DefaultButton as FabricDefaultButton } from '@fluentui/react/lib/Button'; 3 | import { IButtonProps } from './IButtonProps'; 4 | import { withButtonClickLogging } from './withButtonClickLogging'; 5 | 6 | export const DefaultButton = withButtonClickLogging( 7 | FabricDefaultButton as ComponentType 8 | ); 9 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/Buttons/IButtonProps.ts: -------------------------------------------------------------------------------- 1 | import { IButtonProps as IFabricButtonProps } from '@fluentui/react/lib/Button'; 2 | 3 | export interface IButtonProps extends Pick> { 4 | title: string; 5 | } 6 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/Buttons/IconButton.ts: -------------------------------------------------------------------------------- 1 | import { ComponentType } from 'react'; 2 | import { IconButton as FabricIconButton } from '@fluentui/react/lib/Button'; 3 | import { withButtonClickLogging } from './withButtonClickLogging'; 4 | import { IButtonProps } from './IButtonProps'; 5 | 6 | export const IconButton = withButtonClickLogging(FabricIconButton as ComponentType); 7 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/Buttons/PrimaryButton.ts: -------------------------------------------------------------------------------- 1 | import { ComponentType } from 'react'; 2 | import { PrimaryButton as FabricPrimaryButton } from '@fluentui/react/lib/Button'; 3 | import { IButtonProps } from './IButtonProps'; 4 | import { withButtonClickLogging } from './withButtonClickLogging'; 5 | 6 | export const PrimaryButton = withButtonClickLogging(FabricPrimaryButton as ComponentType); 7 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/Buttons/withButtonClickLogging.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Context } from '../Context'; 3 | import { IEmployeeExperienceContext } from '../IEmployeeExperienceContext'; 4 | import { IWithButtonClickLoggingProps } from './withButtonClickLogging.types'; 5 | import { UserEvent, EventType, UsageEventName } from '../UsageTelemetry'; 6 | 7 | // eslint-disable-next-line @typescript-eslint/ban-types 8 | export function withButtonClickLogging( 9 | WrappedComponent: React.ComponentType 10 | ): React.ComponentType { 11 | const displayName = WrappedComponent.displayName || WrappedComponent.name; 12 | 13 | function ComponentWithClickLogging(props: T & IWithButtonClickLoggingProps): React.ReactElement { 14 | const { aiEventName, ...restProps } = props; 15 | 16 | const { telemetryClient, telemetryContext } = React.useContext( 17 | Context as React.Context 18 | ); 19 | const handleClicked = React.useCallback( 20 | (e: never): void => { 21 | const buttonEvent: UserEvent = { 22 | type: EventType.User, 23 | eventName: UsageEventName.ButtonClicked, 24 | subFeature: props.usageEvent.subFeature, 25 | feature: props.usageEvent.feature, 26 | subFeatureLevel2: props.usageEvent.subFeatureLevel2, 27 | featureLocation: props.usageEvent.featureLocation, 28 | }; 29 | telemetryClient.trackEvent(buttonEvent, { 30 | ...telemetryContext, 31 | aiEventName, 32 | buttonTitle: restProps.title, 33 | buttonText: restProps.text, 34 | ...(props.logCustomProperties?.() || {}), 35 | }); 36 | 37 | if (restProps.onClick) restProps.onClick(e); 38 | }, 39 | // eslint-disable-next-line react-hooks/exhaustive-deps 40 | [telemetryClient, telemetryContext, aiEventName, restProps] 41 | ); 42 | const rest = restProps as T; 43 | return ; 44 | } 45 | 46 | ComponentWithClickLogging.displayName = displayName; 47 | 48 | return ComponentWithClickLogging; 49 | } 50 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/Buttons/withButtonClickLogging.types.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { BaseButton } from '@fluentui/react/lib/Button'; 3 | import { UsageFeatureProps } from '../UsageTelemetry'; 4 | 5 | export interface IWithButtonClickLoggingProps { 6 | onClick?(e: React.MouseEvent): void; 7 | 8 | aiEventName?: string; 9 | text?: string; 10 | title: string; 11 | usageEvent: UsageFeatureProps; 12 | logCustomProperties?: () => { 13 | [key: string]: unknown; 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/Checkbox/Checkbox.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Checkbox as FabricCheckbox } from '@fluentui/react/lib/Checkbox'; 3 | import { IEmployeeExperienceContext } from '../IEmployeeExperienceContext'; 4 | import { ICheckboxProps } from './Checkbox.types'; 5 | import { Context } from '../Context'; 6 | import { UserEvent, UsageEventName, EventType } from '../UsageTelemetry'; 7 | 8 | export function Checkbox(props: ICheckboxProps): React.ReactElement { 9 | const { onChange, name, usageEvent } = props; 10 | const { telemetryClient } = React.useContext(Context as React.Context); 11 | 12 | const handleChanged = ( 13 | e: React.FormEvent | undefined, 14 | value: boolean | undefined 15 | ): void => { 16 | const checkedEvent: UserEvent = { 17 | eventName: UsageEventName.CheckBoxChanged, 18 | type: EventType.User, 19 | businessTransactionId: value?.toString() || 'null', 20 | ...usageEvent, 21 | }; 22 | 23 | const customProps = props.logCustomProperties?.() || {}; 24 | telemetryClient.trackEvent(checkedEvent, customProps); 25 | 26 | onChange(name, value as boolean); 27 | }; 28 | 29 | return ; 30 | } 31 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/Checkbox/Checkbox.types.ts: -------------------------------------------------------------------------------- 1 | import { ICheckboxProps as IFabricCheckboxProps } from '@fluentui/react/lib/Checkbox'; 2 | import { UsageFeatureProps } from '../UsageTelemetry'; 3 | 4 | export interface ICheckboxProps extends Pick> { 5 | name: string; 6 | 7 | onChange(name: string, value: boolean): void; 8 | 9 | usageEvent: UsageFeatureProps; 10 | logCustomProperties?: () => { 11 | [key: string]: unknown; 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/Checkbox/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Checkbox'; 2 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/CodeSplitter/CodeSplitter.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { ICodeSplitterProps, ICodeSplitterState } from './CodeSplitter.types'; 3 | 4 | export class CodeSplitter extends React.Component, ICodeSplitterState> { 5 | public state: ICodeSplitterState = { 6 | Component: null, 7 | }; 8 | 9 | public async componentDidMount(): Promise { 10 | const { import: asyncImport, name } = this.props; 11 | 12 | const result = await asyncImport(); 13 | this.setState({ 14 | Component: result[name], 15 | }); 16 | } 17 | 18 | public render(): JSX.Element | null { 19 | const { props } = this.props; 20 | const Component: React.ComponentType | null = this.state.Component; 21 | if (!Component) return null; 22 | 23 | return ; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/CodeSplitter/CodeSplitter.types.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface ICodeSplitterProps { 4 | name: string; 5 | 6 | import(): Promise<{ [key: string]: React.ComponentType }>; 7 | 8 | props?: T; 9 | } 10 | 11 | export interface ICodeSplitterState { 12 | Component: React.ComponentType | null; 13 | } 14 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/CodeSplitter/index.ts: -------------------------------------------------------------------------------- 1 | export * from './CodeSplitter'; 2 | export * from './CodeSplitter.types'; 3 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/ComponentProvider.ts: -------------------------------------------------------------------------------- 1 | export * from '@micro-frontend-react/core/lib/ComponentProvider'; 2 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/Context.tsx: -------------------------------------------------------------------------------- 1 | export * from '@micro-frontend-react/core/lib/Context'; 2 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/DatePicker/DatePicker.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { DatePicker as FabricDatePicker } from '@fluentui/react/lib/DatePicker'; 3 | import { IEmployeeExperienceContext } from '../IEmployeeExperienceContext'; 4 | import { IDatePickerProps } from './DatePicker.types'; 5 | import { Context } from '../Context'; 6 | import { UserEvent, UsageEventName, EventType } from '../UsageTelemetry'; 7 | 8 | export function DatePicker(props: IDatePickerProps): React.ReactElement { 9 | const { onChange, name, label, onClick, usageEvent } = props; 10 | const { telemetryClient, telemetryContext } = React.useContext(Context as React.Context); 11 | 12 | const handleDateSelected = (date: Date | null | undefined): void => { 13 | const datePickerEvent: UserEvent = { 14 | eventName: UsageEventName.DatePickerDateSelected, 15 | type: EventType.User, 16 | businessTransactionId: date?.toDateString() || 'null', 17 | ...usageEvent, 18 | }; 19 | const customProps = props.logCustomProperties?.() || {}; 20 | telemetryClient.trackEvent(datePickerEvent, customProps); 21 | onChange(name, date || null); 22 | }; 23 | 24 | const handleClicked = (e: React.MouseEvent): void => { 25 | const datePickerEvent: UserEvent = { 26 | eventName: UsageEventName.DatePickerClicked, 27 | type: EventType.User, 28 | ...usageEvent, 29 | }; 30 | telemetryClient.trackEvent(datePickerEvent, { 31 | ...telemetryContext, 32 | type: 'DatePicker', 33 | label, 34 | name, 35 | ...(props.logCustomProperties?.() || {}), 36 | }); 37 | 38 | onClick && onClick(e); 39 | }; 40 | 41 | return ; 42 | } 43 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/DatePicker/DatePicker.types.ts: -------------------------------------------------------------------------------- 1 | import { IDatePickerProps as IFabricDatePickerProps } from '@fluentui/react/lib/DatePicker'; 2 | import { UsageFeatureProps } from '../UsageTelemetry'; 3 | 4 | export interface IDatePickerProps 5 | extends Pick> { 6 | name: string; 7 | 8 | onChange(name: string, value: Date | null): void; 9 | 10 | usageEvent: UsageFeatureProps; 11 | logCustomProperties?: () => { 12 | [key: string]: unknown; 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/DatePicker/index.ts: -------------------------------------------------------------------------------- 1 | export * from './DatePicker'; 2 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/Dropdown/Dropdown.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Dropdown as FabricDropdown, IDropdownOption } from '@fluentui/react/lib/Dropdown'; 3 | import { IEmployeeExperienceContext } from '../IEmployeeExperienceContext'; 4 | import { IDropdownProps } from './Dropdown.types'; 5 | import { Context } from '../Context'; 6 | import { UserEvent, UsageEventName, EventType } from '../UsageTelemetry'; 7 | 8 | export function Dropdown(props: IDropdownProps): React.ReactElement { 9 | const { onChange, name, label, onFocus, usageEvent } = props; 10 | const { telemetryClient, telemetryContext } = React.useContext(Context as React.Context); 11 | 12 | const handleChanged = (e: React.FormEvent, option: IDropdownOption | undefined): void => { 13 | const dropDownEvent: UserEvent = { 14 | eventName: UsageEventName.DropdownSelected, 15 | type: EventType.User, 16 | businessTransactionId: option?.key.toString(), 17 | ...usageEvent, 18 | }; 19 | telemetryClient.trackEvent(dropDownEvent, { 20 | ...telemetryContext, 21 | type: 'Dropdown', 22 | label, 23 | name, 24 | key: option?.key.toString(), 25 | ...(props.logCustomProperties?.() || {}), 26 | }); 27 | if (option) onChange(name, option.key); 28 | }; 29 | 30 | const handleFocused = (e: React.FocusEvent): void => { 31 | const dropDownEvent: UserEvent = { 32 | eventName: UsageEventName.DropdownFocused, 33 | type: EventType.User, 34 | ...usageEvent, 35 | }; 36 | telemetryClient.trackEvent(dropDownEvent, { 37 | ...telemetryContext, 38 | type: 'Dropdown', 39 | label, 40 | name, 41 | ...(props.logCustomProperties?.() || {}), 42 | }); 43 | 44 | onFocus && onFocus(e); 45 | }; 46 | 47 | return ; 48 | } 49 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/Dropdown/Dropdown.types.ts: -------------------------------------------------------------------------------- 1 | import { IDropdownProps as IFabricDropdownProps } from '@fluentui/react/lib/Dropdown'; 2 | import { UsageFeatureProps } from '../UsageTelemetry'; 3 | 4 | export interface IDropdownProps extends Pick> { 5 | name: string; 6 | 7 | onChange(name: string, value: string | number): void; 8 | 9 | usageEvent: UsageFeatureProps; 10 | logCustomProperties?: () => { 11 | [key: string]: unknown; 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/Dropdown/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Dropdown'; 2 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/GraphClient.ts: -------------------------------------------------------------------------------- 1 | import { IGraphClient, GraphPhotoSize, IGraphClientOptions } from './IGraphClient'; 2 | import { IHttpClient } from './IHttpClient'; 3 | 4 | export class GraphClient implements IGraphClient { 5 | private readonly httpClient: IHttpClient; 6 | private readonly graphBaseUrl: string; 7 | private readonly graphResourceUri: string; 8 | 9 | public constructor(httpClient: IHttpClient, options?: IGraphClientOptions) { 10 | this.httpClient = httpClient; 11 | 12 | this.graphBaseUrl = options?.baseUrl ?? 'https://graph.microsoft.com/v1.0'; 13 | this.graphResourceUri = options?.resourceUri ?? 'https://graph.microsoft.com'; 14 | } 15 | 16 | public async getPhoto(upn: string, size: GraphPhotoSize = undefined): Promise { 17 | return new Promise(async (resolve, reject): Promise => { 18 | try { 19 | let url = `${this.graphBaseUrl}/users/${upn}`; 20 | url = `${url}/${size ? `photos/${size}x${size}/$value` : 'photo/$value'}`; 21 | 22 | const { data } = await this.httpClient.request( 23 | { 24 | url, 25 | resource: this.graphResourceUri, 26 | responseType: 'blob', 27 | }, 28 | { silentError: true } 29 | ); 30 | 31 | const reader = new FileReader(); 32 | reader.onload = (): void => { 33 | if (reader.result) { 34 | resolve(reader.result.toString()); 35 | } else { 36 | reject(); 37 | } 38 | }; 39 | reader.readAsDataURL(data); 40 | } catch { 41 | resolve(null); 42 | } 43 | }); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/IAuthClient.ts: -------------------------------------------------------------------------------- 1 | import { IUser } from './IUser'; 2 | 3 | export interface IAuthClient { 4 | readonly authContext: unknown; 5 | 6 | login(loginOptions?: ILoginOptions): Promise; 7 | 8 | logOut(): Promise; 9 | 10 | getUser(): Promise; 11 | 12 | getUserId(): Promise; 13 | 14 | isLoggedIn(): Promise; 15 | 16 | acquireToken(resourceOrScopes: string | string[]): Promise; 17 | } 18 | 19 | export interface ILoginOptions { 20 | scopes?: string[]; 21 | } 22 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/IComponentConfig.ts: -------------------------------------------------------------------------------- 1 | export * from '@micro-frontend-react/core/lib/IComponentConfig'; 2 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/IComponentProps.ts: -------------------------------------------------------------------------------- 1 | export * from '@micro-frontend-react/core/lib/IComponentProps'; 2 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/IDefaultState.ts: -------------------------------------------------------------------------------- 1 | export * from '@micro-frontend-react/redux/lib/IDefaultState'; 2 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/IEmployeeExperienceContext.ts: -------------------------------------------------------------------------------- 1 | import { IReduxContext } from '@micro-frontend-react/redux/lib/IReduxContext'; 2 | import { IAuthClient } from './IAuthClient'; 3 | import { IGraphClient } from './IGraphClient'; 4 | import { IHttpClient } from './IHttpClient'; 5 | import { ITelemetryClient } from './ITelemetryClient'; 6 | import { ITelemetryContext } from './ITelemetryContext'; 7 | import { IUsageClient } from './IUsageClient'; 8 | 9 | export interface IEmployeeExperienceContext extends IReduxContext { 10 | authClient: IAuthClient; 11 | httpClient: IHttpClient; 12 | graphClient: IGraphClient; 13 | usageClient: IUsageClient; 14 | telemetryClient: ITelemetryClient; 15 | telemetryContext: ITelemetryContext; 16 | appName?: string; 17 | } 18 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/IGraphClient.ts: -------------------------------------------------------------------------------- 1 | export type GraphPhotoSize = 48 | 64 | 96 | 120 | 240 | 360 | 432 | 504 | 648 | undefined; 2 | 3 | export interface IGraphClient { 4 | getPhoto(upn: string, size?: GraphPhotoSize): Promise; 5 | } 6 | 7 | export interface IGraphClientOptions { 8 | baseUrl: string; 9 | resourceUri: string; 10 | } 11 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/IHttpClient.ts: -------------------------------------------------------------------------------- 1 | import { ICustomProperties } from '@microsoft/applicationinsights-web'; 2 | import { AxiosRequestConfig } from 'axios'; 3 | import { ITelemetryClient } from './ITelemetryClient'; 4 | import { IAuthClient } from './IAuthClient'; 5 | import { SystemEvent } from './UsageTelemetry'; 6 | 7 | export interface IHttpClient { 8 | request( 9 | request: IHttpClientRequest, 10 | options?: IHttpClientRequestOption, 11 | event?: SystemEvent 12 | ): Promise>; 13 | 14 | get( 15 | url: string, 16 | request?: IHttpClientRequest, 17 | options?: IHttpClientRequestOption, 18 | event?: SystemEvent 19 | ): Promise>; 20 | 21 | post( 22 | url: string, 23 | request?: IHttpClientRequest, 24 | options?: IHttpClientRequestOption, 25 | event?: SystemEvent 26 | ): Promise>; 27 | 28 | delete( 29 | url: string, 30 | request?: IHttpClientRequest, 31 | options?: IHttpClientRequestOption, 32 | event?: SystemEvent 33 | ): Promise>; 34 | 35 | put( 36 | url: string, 37 | request?: IHttpClientRequest, 38 | options?: IHttpClientRequestOption, 39 | event?: SystemEvent 40 | ): Promise>; 41 | 42 | patch( 43 | url: string, 44 | request?: IHttpClientRequest, 45 | options?: IHttpClientRequestOption, 46 | event?: SystemEvent 47 | ): Promise>; 48 | 49 | getChildInstance(telemetryClient: ITelemetryClient, authClient: IAuthClient): IHttpClient; 50 | } 51 | 52 | export interface IHttpClientOption extends IHttpClientRequestOption { 53 | logPayload?: boolean; 54 | correlationIdHeaderName?: string; 55 | } 56 | 57 | export interface IHttpHeader { 58 | [key: string]: string; 59 | } 60 | 61 | export interface IHttpClientRequest extends AxiosRequestConfig { 62 | resource?: string | string[]; 63 | accessToken?: string; 64 | correlationIdHeaderName?: string; 65 | header?: IHttpHeader; 66 | } 67 | 68 | export interface IHttpClientRequestOption { 69 | silentError?: boolean; 70 | customTelemetryProperties?: ICustomProperties; 71 | disableCorrelationId?: boolean; 72 | } 73 | 74 | export interface IHttpClientResult { 75 | status: number; 76 | statusText: string; 77 | data: T; 78 | headers: IHttpHeader; 79 | } 80 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/IReducerRegistry.ts: -------------------------------------------------------------------------------- 1 | export * from '@micro-frontend-react/redux/lib/IReducerRegistry'; 2 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/ITelemetryClient.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IAppInsights, 3 | IConfig, 4 | IDependencyTelemetry, 5 | IConfiguration, 6 | IEventTelemetry, 7 | IPageViewTelemetry, 8 | ITelemetryItem, 9 | ICustomProperties, 10 | } from '@microsoft/applicationinsights-web'; 11 | import { IUTPConfig } from './IUTPConfig'; 12 | import { UsageTelemetryConfig } from './UsageTelemetry'; 13 | import { UsageEvent } from './UsageTelemetry'; 14 | import { ITelemetryContext } from './ITelemetryContext'; 15 | 16 | export type CustomProperties = { 17 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 18 | [key: string]: any; 19 | }; 20 | 21 | export type Measurements = { 22 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 23 | [key: string]: any; 24 | }; 25 | 26 | export type TelemetryConfig = IConfig & 27 | IConfiguration & 28 | IUTPConfig & { 29 | defaultProperties?: ICustomProperties; 30 | usageTelemetryConfig?: UsageTelemetryConfig; 31 | }; 32 | 33 | export interface ITelemetryClient extends Omit { 34 | trackDependencyData(dependency: IDependencyTelemetry): void; 35 | 36 | getChildInstance( 37 | config?: TelemetryConfig, 38 | correlationId?: string, 39 | telemetryContext?: ITelemetryContext 40 | ): ITelemetryClient; 41 | 42 | setContext(context: ITelemetryContext): void; 43 | 44 | getCorrelationId(): string; 45 | 46 | setAuthenticatedUserContext(authenticatedUserId: string, accountId?: string, storeInCookie?: boolean): void; 47 | 48 | /** 49 | * @deprecated Logs that a page, or similar container was displayed to the user. 50 | * @param {IPageViewTelemetry} pageView 51 | * @memberof Initialization 52 | */ 53 | trackPageView(pageView?: IPageViewTelemetry): void; 54 | 55 | /** 56 | * Logs that a page, or similar container was displayed to the user. 57 | * @param {IPageViewTelemetry} pageView 58 | * @memberof Initialization 59 | */ 60 | /** 61 | * @deprecated Use stopTrackEvent( name: UsageEvent ... 62 | */ 63 | stopTrackEvent(name: string, properties?: Record, measurements?: Record): unknown; 64 | 65 | stopTrackEvent( 66 | usageEvent: UsageEvent, 67 | props?: Record, 68 | measures?: Record 69 | ): UsageEvent | undefined; 70 | 71 | /** 72 | * @deprecated Use startTrackEvent( usageEvent: UsageEvent ... 73 | */ 74 | startTrackEvent(name: string): unknown; 75 | 76 | startTrackEvent(usageEvent: UsageEvent): unknown; 77 | 78 | trackCustomEvent(event: IEventTelemetry, customProperties?: ICustomProperties): void; 79 | 80 | /** 81 | * @deprecated Use trackEvent( name: UsageEvent ... 82 | * Log a user action or other occurrence. 83 | * 84 | * @param {IEventTelemetry} event Identifies the event. Events with the same name are counted and can be charted in Metric Explorer. 85 | * @param {object} customProperties Property bag to log OBJECTS and their key/value pairs into "properties" (works with sub-objects) 86 | */ 87 | trackEvent( 88 | event: IEventTelemetry, 89 | customProperties?: { 90 | [key: string]: unknown; 91 | } 92 | ): unknown; 93 | 94 | /** 95 | * Log a user action or other occurrence. 96 | * 97 | * @param {IEventTelemetry & UsageEvent} event Identifies the event. Events with the same name are counted and can be charted in Metric Explorer. 98 | * @param {object} customProperties Property bag to log OBJECTS and their key/value pairs into "properties" (works with sub-objects) 99 | */ 100 | trackEvent(event: IEventTelemetry & UsageEvent, customProperties?: ICustomProperties): unknown; 101 | 102 | /** 103 | * Log a user action or other occurrence. 104 | * 105 | * @param {UsageEvent} event Identifies the event. Events with the same name are counted and can be charted in Metric Explorer. 106 | * @param {object} customProperties Property bag to log OBJECTS and their key/value pairs into "properties" (works with sub-objects) 107 | */ 108 | trackEvent( 109 | event: UsageEvent, 110 | customProperties?: { 111 | [key: string]: unknown; 112 | } 113 | ): unknown; 114 | 115 | /** 116 | * Log a user action or other occurrence. 117 | * 118 | * @param {telemetryInitializer} function that receives the telemetry item and adds/updates the attributes of the telemetry data. 119 | */ 120 | addTelemetryInitializer(telemetryInitializer: (item: ITelemetryItem) => boolean | void): void; 121 | } 122 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/ITelemetryContext.ts: -------------------------------------------------------------------------------- 1 | import { UsageEvent, UserAttribute, UsageTelemetryConfig } from './UsageTelemetry'; 2 | 3 | export interface ITelemetryContext { 4 | sourceComponent: string; 5 | sourceScript: string; 6 | setUsageEvent: (usageEvent: UsageEvent) => UsageEvent; 7 | setUsageUser: (usageUser: UserAttribute) => UserAttribute; 8 | setUsageConfig: (usageConfig: UsageTelemetryConfig) => void; 9 | usageUser: (usageUser: UserAttribute) => UserAttribute; 10 | getChildContext: (appname: string, source: string) => ITelemetryContext; 11 | } 12 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/IUTPConfig.ts: -------------------------------------------------------------------------------- 1 | export interface IUTPConfig { 2 | UTPConfig?: { 3 | EnvironmentName: string; 4 | ServiceOffering: string; 5 | ServiceLine: string; 6 | Service: string; 7 | ComponentName: string; 8 | ComponentId: string; 9 | [key: string]: string; 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/IUsageClient.ts: -------------------------------------------------------------------------------- 1 | import { UsageTelemetryConfig } from './UsageTelemetry'; 2 | 3 | export interface IUsageClient { 4 | getUsageUserId(): Promise; 5 | 6 | getUsageConfig(): UsageTelemetryConfig; 7 | } 8 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/IUser.ts: -------------------------------------------------------------------------------- 1 | export interface IUser { 2 | id: string; 3 | email: string; 4 | name: string; 5 | oid: string; 6 | } 7 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/InputGroup.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const InputGroup = styled.div` 4 | margin-bottom: 24px; 5 | `; 6 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/Link/Link.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { NavLink as ReactRouterNavLink } from 'react-router-dom'; 3 | import { Link as FluentLink } from '@fluentui/react/lib/Link'; 4 | import { Context } from '@micro-frontend-react/core/lib/Context'; 5 | import { UserEvent, UsageEventName, EventType } from '../UsageTelemetry'; 6 | import { IEmployeeExperienceContext } from '../IEmployeeExperienceContext'; 7 | import { ILinkProps } from './Link.types'; 8 | import { shouldUseAnchorTag } from './Link.utils'; 9 | 10 | export function Link(props: React.PropsWithChildren): React.ReactElement { 11 | const { to, children, activeStyle, exact, title, className, target, activeClassName, disabled, refresh, role, ariaLabel, onClick, } = props; 12 | const { telemetryClient } = React.useContext(Context as React.Context); 13 | const href = to || '/'; 14 | 15 | const handleClicked = (): void => { 16 | const linkEvent: UserEvent = { 17 | feature: props.usageEvent.feature, 18 | subFeature: props.usageEvent.subFeature, 19 | subFeatureLevel2: props.usageEvent.subFeatureLevel2, 20 | featureLocation: props.usageEvent.featureLocation, 21 | eventName: UsageEventName.LinkClicked, 22 | type: EventType.User, 23 | }; 24 | telemetryClient.trackEvent(linkEvent, { 25 | properties: { to, title }, 26 | ...(props.logCustomProperties?.() || {}), 27 | }); 28 | if (onClick) { 29 | onClick(); 30 | } 31 | }; 32 | 33 | if (shouldUseAnchorTag(href, refresh)) 34 | return ( 35 | 46 | {children} 47 | 48 | ); 49 | 50 | return ( 51 | 67 | {children} 68 | 69 | ); 70 | } 71 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/Link/Link.types.ts: -------------------------------------------------------------------------------- 1 | import { UsageFeatureProps } from '../UsageTelemetry'; 2 | import * as React from 'react'; 3 | 4 | export interface ILinkProps extends React.AnchorHTMLAttributes, 5 | Omit, 'type'>, 6 | React.RefAttributes { 7 | to: string; 8 | title: string; 9 | exact?: boolean; 10 | target?: '_blank' | '_self'; 11 | // eslint-disable-next-line @typescript-eslint/ban-types 12 | activeStyle?: {}; 13 | className?: string; 14 | activeClassName?: string; 15 | disabled?: boolean; 16 | refresh?: boolean; 17 | usageEvent: UsageFeatureProps; 18 | logCustomProperties?: () => { 19 | [key: string]: unknown; 20 | }; 21 | ariaLabel: string; 22 | onClick: () => void; 23 | } -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/Link/Link.utils.ts: -------------------------------------------------------------------------------- 1 | export function shouldUseAnchorTag( 2 | to: string, 3 | forceRefresh: boolean | undefined 4 | ): boolean { 5 | const lowerCasedTo = to.toLowerCase(); 6 | 7 | if (forceRefresh) return true; 8 | if (lowerCasedTo.startsWith('http')) return true; 9 | if (lowerCasedTo.startsWith('mailto')) return true; 10 | if (lowerCasedTo.startsWith('tel')) return true; 11 | if (lowerCasedTo.startsWith('\\')) return true; 12 | 13 | return false; 14 | } 15 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/Link/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Link'; 2 | export * from './Link.utils'; 3 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/OnBuildOnce/OnBuildOnce.tsx: -------------------------------------------------------------------------------- 1 | import { PropsWithChildren } from 'react'; 2 | import * as React from 'react'; 3 | import { createRoot } from 'react-dom/client'; 4 | import { BrowserRouter } from 'react-router-dom'; 5 | import { IAuthClient } from '../IAuthClient'; 6 | import { ITelemetryClient } from '../ITelemetryClient'; 7 | import { withStore } from '../withStore'; 8 | import { Shell } from '../Shell'; 9 | import { GraphClient } from '../GraphClient'; 10 | import { HttpClient } from '../HttpClient'; 11 | import { ReducerRegistry } from '../ReducerRegistry'; 12 | import { StoreBuilder } from '../StoreBuilder'; 13 | import { v4 as guid } from 'uuid'; 14 | 15 | export type BuildOnceAppOptions = { 16 | appName: string; 17 | isProduction?: boolean; 18 | authClient: IAuthClient; 19 | telemetryClient: ITelemetryClient; 20 | }; 21 | 22 | export function OnBuildOnce(Component: React.ComponentType, options: BuildOnceAppOptions) { 23 | const { appName, authClient, telemetryClient } = options; 24 | 25 | const httpClient = new HttpClient(telemetryClient, authClient); 26 | const graphClient = new GraphClient(httpClient); 27 | const reducerRegistry = new ReducerRegistry(); 28 | const storeResult = new StoreBuilder(reducerRegistry, {}) 29 | .configureLogger(!options.isProduction) 30 | .configureSaga({ telemetryClient, authClient, httpClient, graphClient, appName }) 31 | .configurePersistor({ 32 | key: appName || Component.displayName || guid(), 33 | }) 34 | .build(); 35 | 36 | const ShellWithStore: React.ComponentType> = withStore(storeResult)(Shell); 37 | const container = document.getElementById('app'); 38 | if (!container) return; 39 | 40 | const root = createRoot(container); 41 | 42 | root.render( 43 | 44 | 45 | 46 | 47 | 48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/OnBuildOnce/index.ts: -------------------------------------------------------------------------------- 1 | export * from './OnBuildOnce'; 2 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/Persona/Persona.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Persona as FabricPersona } from '@fluentui/react/lib/Persona'; 3 | import { GraphPhotoSize } from '../IGraphClient'; 4 | import { IPersonaProps, PersonaSize } from './Persona.types'; 5 | import { useGraphPhoto } from '../useGraphPhoto'; 6 | 7 | export const Persona: React.FC = (props: IPersonaProps): React.ReactElement => { 8 | const { emailAlias } = props; 9 | const photo = useGraphPhoto(emailAlias, getPixelSize(props.size)); 10 | 11 | if (!photo) return ; 12 | 13 | return ; 14 | }; 15 | 16 | const getPixelSize = (size: PersonaSize | undefined): GraphPhotoSize => { 17 | switch (size) { 18 | case PersonaSize.size8: 19 | case PersonaSize.size24: 20 | case PersonaSize.size32: 21 | case PersonaSize.size40: 22 | case PersonaSize.size48: 23 | return 48; 24 | case PersonaSize.size56: 25 | return 64; 26 | case PersonaSize.size72: 27 | return 96; 28 | case PersonaSize.size100: 29 | case PersonaSize.size120: 30 | return 120; 31 | default: 32 | return undefined; 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/Persona/Persona.types.ts: -------------------------------------------------------------------------------- 1 | import { IPersonaProps as IFabricPersonaProps, PersonaInitialsColor } from '@fluentui/react/lib/Persona'; 2 | 3 | export interface IPersonaProps extends IFabricPersonaProps { 4 | emailAlias?: string; 5 | } 6 | 7 | export { PersonaInitialsColor }; 8 | 9 | export enum PersonaSize { 10 | /** 11 | * Deprecated in favor of standardized numeric sizing. 12 | * @deprecated Use `size8` instead. 13 | */ 14 | tiny = 0, 15 | /** 16 | * Deprecated in favor of standardized numeric sizing. 17 | * @deprecated Use `size24` instead. 18 | */ 19 | extraExtraSmall = 1, 20 | /** 21 | * Deprecated in favor of standardized numeric sizing. 22 | * @deprecated Use `size32` instead. 23 | */ 24 | extraSmall = 2, 25 | /** 26 | * Deprecated in favor of standardized numeric sizing. 27 | * @deprecated Use `size40` instead. 28 | */ 29 | small = 3, 30 | /** 31 | * Deprecated in favor of standardized numeric sizing. 32 | * @deprecated Use `size48` instead. 33 | */ 34 | regular = 4, 35 | /** 36 | * Deprecated in favor of standardized numeric sizing. 37 | * @deprecated Use `size72` instead. 38 | */ 39 | large = 5, 40 | /** 41 | * Deprecated in favor of standardized numeric sizing. 42 | * @deprecated Use `size100` instead. 43 | */ 44 | extraLarge = 6, 45 | /** 46 | * No `PersonaCoin` is rendered. 47 | */ 48 | size8 = 17, 49 | /** 50 | * No `PersonaCoin` is rendered. Deprecated to align with design specifications. 51 | * @deprecated Use `size8` instead. 52 | */ 53 | size10 = 9, 54 | /** 55 | * Renders a 16px `PersonaCoin`. 56 | * @deprecated Deprecated due to not being in the design specification. 57 | */ 58 | size16 = 8, 59 | /** 60 | * Renders a 24px `PersonaCoin`. 61 | */ 62 | size24 = 10, 63 | /** 64 | * Renders a 28px `PersonaCoin`. 65 | * @deprecated Deprecated due to not being in the design specification. 66 | */ 67 | size28 = 7, 68 | /** 69 | * Renders a 32px `PersonaCoin`. 70 | */ 71 | size32 = 11, 72 | /** 73 | * Renders a 40px `PersonaCoin`. 74 | */ 75 | size40 = 12, 76 | /** 77 | * Renders a 48px `PersonaCoin`. 78 | */ 79 | size48 = 13, 80 | /** 81 | * Renders a 56px `PersonaCoin`. 82 | */ 83 | size56 = 16, 84 | /** 85 | * Renders a 72px `PersonaCoin`. 86 | */ 87 | size72 = 14, 88 | /** 89 | * Renders a 100px `PersonaCoin`. 90 | */ 91 | size100 = 15, 92 | /** 93 | * Renders a 120px `PersonaCoin`. 94 | */ 95 | size120 = 18, 96 | } 97 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/Persona/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Persona'; 2 | export * from './Persona.types'; 3 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/ReducerRegistry.ts: -------------------------------------------------------------------------------- 1 | export * from '@micro-frontend-react/redux/lib/ReducerRegistry'; 2 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/RouteComponentProvider.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Route, RouteProps } from 'react-router-dom'; 3 | import { ComponentProvider } from '@micro-frontend-react/core/lib/ComponentProvider'; 4 | import { IComponentProviderProps } from '@micro-frontend-react/core/lib/ComponentProvider/ComponentProvider.types'; 5 | 6 | export function RouteComponentProvider(props: IComponentProviderProps & RouteProps): React.ReactElement { 7 | const { path, config, exact, ...otherProps } = props; 8 | 9 | return ( 10 | } 14 | /> 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/Shell/Shell.styled.ts: -------------------------------------------------------------------------------- 1 | import { createGlobalStyle } from 'styled-components'; 2 | 3 | export const ShellStyles = createGlobalStyle` 4 | *, *:before, *:after { 5 | box-sizing: border-box; 6 | } 7 | 8 | html { 9 | height: 100%; 10 | } 11 | 12 | body { 13 | position: relative; 14 | height: 100%; 15 | margin: 0; 16 | color: #333; 17 | background-color: #F2F2F2; 18 | overflow-x: hidden; 19 | } 20 | 21 | h1, h2, h3, h4, p, ul { 22 | margin: 0; 23 | } 24 | 25 | button { 26 | cursor: pointer; 27 | } 28 | 29 | a { 30 | cursor: pointer; 31 | text-decoration: none; 32 | } 33 | 34 | ul { 35 | list-style: none; 36 | } 37 | 38 | body > iframe { 39 | display: none; 40 | } 41 | 42 | #app { 43 | height: 100%; 44 | 45 | & > div { 46 | height: 100%; 47 | } 48 | } 49 | 50 | @keyframes placeholderBlinks { 51 | 0% { 52 | opacity: 1; 53 | } 54 | 50% { 55 | opacity: 0.7; 56 | } 57 | } 58 | `; 59 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/Shell/Shell.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Context } from '@micro-frontend-react/core/lib/Context'; 3 | import { ITelemetryContext } from '../ITelemetryContext'; 4 | import { UsageHelper, UserAttribute } from '../UsageTelemetry'; 5 | import { ShellStyles } from './Shell.styled'; 6 | import { IEmployeeExperienceContext } from '../IEmployeeExperienceContext'; 7 | 8 | export function Shell(props: React.PropsWithChildren): React.ReactElement { 9 | const { children, ...context } = props; 10 | const { appName } = context; 11 | 12 | const usageHelper = appName && appName.trim() != '' ? UsageHelper.Fork(appName) : UsageHelper; 13 | const telemetryContext: ITelemetryContext = { 14 | sourceComponent: 'Shell', 15 | sourceScript: 'main', 16 | setUsageEvent: usageHelper.MassageEvent, 17 | setUsageUser: (usageUser: UserAttribute) => { 18 | UsageHelper.SetUser(usageUser); 19 | usageHelper.SetUser(usageUser); 20 | return usageUser; 21 | }, 22 | usageUser: usageHelper.GetUser, 23 | setUsageConfig: usageHelper.SetUsageConfig, 24 | getChildContext: usageHelper.ForkTelemetryContext, 25 | }; 26 | context.telemetryClient?.setContext(telemetryContext); 27 | 28 | return ( 29 | <> 30 | 31 | {children} 32 | 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/Shell/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Shell'; 2 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/StoreBuilder.ts: -------------------------------------------------------------------------------- 1 | export * from '@micro-frontend-react/redux/lib/StoreBuilder'; 2 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/TelemetryEvents.ts: -------------------------------------------------------------------------------- 1 | // These telemetries were used while back when this library had no support for HEART metrics 2 | // Since then the event names have been replaced with UsageTelemetry event names 3 | // These are provided for backward compatibility if you've been using them in your projects 4 | export enum TelemetryEvents { 5 | // App 6 | SessionStarted = 'SessionStarted', 7 | 8 | // User 9 | UserLogInRequested = 'UserLogInRequested', 10 | UserLoginFailed = 'UserLoginFailed', 11 | UserLogOutRequested = 'UserLogOutRequested', 12 | UserLogOutFailed = 'UserLogOutFailed', 13 | AcquireTokenFailed = 'AcquireTokenFailed', 14 | 15 | // Routes 16 | PageEnter = 'PageEnter', 17 | PageLeave = 'PageLeave', 18 | 19 | // Side nav 20 | NavLinkClicked = 'NavLinkClicked', 21 | 22 | // Header 23 | HeaderAppNameLinkClicked = 'HeaderAppNameLinkClicked', 24 | HeaderSearchRequested = 'HeaderSearchRequested', 25 | HeaderPanelOpened = 'HeaderPanelOpened', 26 | HeaderPanelClosed = 'HeaderPanelClosed', 27 | 28 | InvalidComponentConfig = 'InvalidComponentConfig', 29 | 30 | // HTTP 31 | APIRequestStarted = 'APIRequestStarted', 32 | APIResponseReceived = 'APIResponseReceived', 33 | APIFailedWithoutResponse = 'APIFailedWithoutResponse', 34 | APIFailedResponseReceived = 'APIFailedResponseReceived', 35 | 36 | // Components 37 | ButtonClicked = 'ButtonClicked', 38 | LinkClicked = 'LinkClicked', 39 | FormElementClicked = 'FormElementClicked', 40 | } 41 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/TextField/TextField.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { TextField as FabricTextField } from '@fluentui/react/lib/TextField'; 3 | import { IEmployeeExperienceContext } from '../IEmployeeExperienceContext'; 4 | import { ITextFieldProps } from './TextField.types'; 5 | import { Context } from '../Context'; 6 | import { UserEvent, UsageEventName, EventType } from '../UsageTelemetry'; 7 | 8 | export function TextField(props: ITextFieldProps): React.ReactElement { 9 | const { onChange, name, label, onFocus, usageEvent } = props; 10 | const { telemetryClient, telemetryContext } = React.useContext(Context as React.Context); 11 | 12 | const handleChanged = ( 13 | e: React.FormEvent, 14 | value: string | undefined 15 | ): void => { 16 | const textEvent: UserEvent = { 17 | eventName: UsageEventName.TextChanged, 18 | type: EventType.User, 19 | businessTransactionId: value || 'null', 20 | ...usageEvent, 21 | }; 22 | const customProps = props.logCustomProperties?.() || {}; 23 | telemetryClient.trackEvent(textEvent, customProps); //TODO: Review with Won => Too many change events. Sould use with debounce 24 | 25 | onChange(name, value as string); 26 | }; 27 | 28 | const handleFocused = (e: React.FocusEvent): void => { 29 | const textEvent: UserEvent = { 30 | eventName: UsageEventName.TextboxFocused, 31 | type: EventType.User, 32 | ...usageEvent, 33 | }; 34 | telemetryClient.trackEvent(textEvent, { 35 | ...telemetryContext, 36 | type: 'TextField', 37 | label, 38 | name, 39 | ...(props.logCustomProperties?.() || {}), 40 | }); 41 | 42 | onFocus && onFocus(e); 43 | }; 44 | 45 | return ; 46 | } 47 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/TextField/TextField.types.ts: -------------------------------------------------------------------------------- 1 | import { ITextFieldProps as IFabricTextFieldProps } from '@fluentui/react/lib/TextField'; 2 | import { UsageFeatureProps } from '../UsageTelemetry'; 3 | 4 | export interface ITextFieldProps extends Pick> { 5 | name: string; 6 | onChange(name: string, value: string): void; 7 | usageEvent: UsageFeatureProps; 8 | logCustomProperties?: () => { 9 | [key: string]: unknown; 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/TextField/index.ts: -------------------------------------------------------------------------------- 1 | export * from './TextField'; 2 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/UsageClient.ts: -------------------------------------------------------------------------------- 1 | import { IHttpClient } from './IHttpClient'; 2 | import { IUsageClient } from './IUsageClient'; 3 | import { UsageTelemetryConfig } from './UsageTelemetry'; 4 | 5 | export class UsageClient implements IUsageClient { 6 | private readonly usageTelemetryConfig: UsageTelemetryConfig; 7 | private readonly httpClient: IHttpClient; 8 | 9 | private readonly localStorageKey = '__Core.UserAttribute.UsageUserId__'; 10 | private readonly sesionDurationConfig = '__Core.UserAttribute.sessionDurationConfig__'; 11 | 12 | public constructor(httpClient: IHttpClient, usageTelemetryConfig: UsageTelemetryConfig) { 13 | this.usageTelemetryConfig = usageTelemetryConfig; 14 | this.httpClient = httpClient; 15 | } 16 | 17 | public getUsageConfig = (): UsageTelemetryConfig => this.usageTelemetryConfig; 18 | 19 | public async getUsageUserId(): Promise { 20 | return new Promise(async (resolve, reject): Promise => { 21 | try { 22 | let usageUserId: string | null = ''; 23 | 24 | if (this.usageTelemetryConfig.usageApi.cache === 'localStorage') { 25 | usageUserId = localStorage.getItem(this.localStorageKey); 26 | localStorage.setItem(this.sesionDurationConfig, this.usageTelemetryConfig.sessionDurationMinutes.toString()); 27 | } 28 | const invalidUserIds = ['', 'NOTFOUND', 'NOTUPNFOUND', 'ERROR', 'UNDEFINED', 'UNINITIALIZED']; 29 | if (usageUserId && invalidUserIds.indexOf(usageUserId.trim().toUpperCase()) < 0) { 30 | resolve(usageUserId); 31 | } else { 32 | const { data: content } = await this.httpClient.request<{ 33 | UsageUserId: string; 34 | }>( 35 | { 36 | url: this.usageTelemetryConfig.usageApi.url, 37 | resource: this.usageTelemetryConfig.usageApi.resourceId, 38 | header: this.usageTelemetryConfig.usageApi.headers, 39 | }, 40 | { silentError: true } 41 | ); 42 | if (content && content.UsageUserId) { 43 | if (this.usageTelemetryConfig.usageApi.cache === 'localStorage') { 44 | localStorage.setItem(this.localStorageKey, content.UsageUserId); 45 | } 46 | resolve(content.UsageUserId); 47 | } else { 48 | reject(); 49 | } 50 | } 51 | } catch { 52 | reject(); 53 | } 54 | }); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/UsageTelemetry/Config/UsageTelemetryConfig.ts: -------------------------------------------------------------------------------- 1 | export type UsageTelemetryConfig = { 2 | usageApi: { 3 | headers: { [key: string]: string }; 4 | url: string; 5 | method: string; 6 | resourceId: string; 7 | cache?: 'localStorage'; 8 | }; 9 | enableUpnLogging: false; 10 | sessionDurationMinutes: number; 11 | }; 12 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/UsageTelemetry/Helpers/CuppSchemaMapper.ts: -------------------------------------------------------------------------------- 1 | import { CustomProperties } from '../../ITelemetryClient'; 2 | 3 | const CuppSchema: { [key: string]: string } = { 4 | type: 'UsageEventType', 5 | timeTaken: 'UsageTimeTaken', 6 | eventName: 'UsageEventName', 7 | feature: 'UsageCapabilityName', 8 | subFeature: 'UsageSubCapabilityName', 9 | subFeatureLevel2: 'UsageSubCapabilityLevel2', 10 | featureLocation: 'UsageLocation', 11 | businessTransactionId: 'UsageBusinessTransactionId', 12 | experienceResult: 'UsageExperienceResult', 13 | usageUserId: 'UsageUserId', 14 | sessionId: 'UsageSessionId', // TOOD: ras1 => UsageSessionTrackingId ? 15 | actionTrackingId: 'UsageActionTrackingId', 16 | correlationTrackingId: 'UsagecCorrelationTrackingId', 17 | pageTrackingId: 'UsagePageTrackingId', 18 | eventDate: 'UsageEventDate', 19 | flightId: 'UsageFlightId', 20 | flightName: 'UsageFlightName', 21 | moduleName: 'UsageModuleName', 22 | usageVersion: 'UsageVersion', 23 | }; 24 | 25 | export const asCuppSchema = (props: CustomProperties): CustomProperties => 26 | Object.keys(props).reduce( 27 | (acc, key: string) => ({ 28 | ...acc, 29 | ...{ 30 | [CuppSchema[key] || key]: 31 | props[key] instanceof Date ? (props[key] as Date).toISOString() : props[key] !== undefined ? props[key] : '', 32 | }, 33 | }), 34 | {} 35 | ); 36 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/UsageTelemetry/Helpers/UsageEventName.ts: -------------------------------------------------------------------------------- 1 | export enum UsageEventName { 2 | ButtonClicked = 'ButtonClicked', 3 | BackEndAPICall = 'BackEndAPICall', 4 | PageLoad = 'PageLoad', 5 | PanelOpened = 'PanelOpened', 6 | PanelClosed = 'PanelClosed', 7 | CardAdded = 'CardAdded', 8 | CardDeleted = 'CardDeleted', 9 | CardResized = 'CardResized', 10 | DatePickerDismissed = 'DatePickerDismissed', 11 | CalendarMonthViewClicked = 'CalendarMonthViewClicked', 12 | CalendarDayViewClicked = 'CalendarDayViewClicked', 13 | CalendarClearButtonClicked = 'CalendarClearButtonClicked', 14 | CalendarGoToTodayButtonClicked = 'CalendarGoToTodayButtonClicked', 15 | CalendarDateClicked = 'CalendarDateClicked', 16 | CalendarMonthClicked = 'CalendarMonthClicked', 17 | CalendarNextButtonClicked = 'CalendarNextButtonClicked', 18 | CalendarPrevButtonClicked = 'CalendarPrevButtonClicked', 19 | ViewActivated = 'ViewActivated', 20 | ViewDeactivated = 'ViewDeactivated', 21 | MarkerSelected = 'MarkerSelected', 22 | ListSelected = 'ListSelected', 23 | LinkClicked = 'LinkClicked', 24 | DatePickerClicked = 'DatePickerClicked', 25 | DatePickerDateSelected = 'DatePickerDateSelected', 26 | DatePickerFocused = 'DatePickerDateSelected', 27 | ViewPhoto = 'ViewPhoto', 28 | TabSelected = 'TabSelected', 29 | DropdownSelected = 'DropdownSelected', 30 | DropdownFocused = 'DropdownFocused', 31 | CheckBoxChanged = 'CheckBoxChanged', 32 | CheckBoxFocused = 'CheckBoxFocused', 33 | RadioButtonChanged = 'RadioButtonChanged', 34 | RadioButtonFocused = 'RadioButtonFocused', 35 | TextChanged = 'TextChanged', 36 | TextboxFocused = 'TextboxFocused', 37 | OnFocus = 'OnFocus', 38 | OnBlur = 'OnBlur', 39 | TileLoaded = 'TileLoaded', 40 | MobileRefresh = 'MobileRefresh', 41 | 42 | } 43 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/UsageTelemetry/UsageEvent.ts: -------------------------------------------------------------------------------- 1 | import { UserEvent } from './UsageEvent/UserEvent'; 2 | import { SystemEvent } from './UsageEvent/SytemEvent'; 3 | import { AwareEvent } from './UsageEvent/AwareEvent'; 4 | 5 | export type UsageEvent = UserEvent | SystemEvent | AwareEvent; 6 | 7 | export enum EventType { 8 | User = 'UserAction', 9 | System = 'SystemAction', 10 | Aware = 'AwareAction', 11 | } 12 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/UsageTelemetry/UsageEvent/AwareEvent.ts: -------------------------------------------------------------------------------- 1 | import { EventType } from '..'; 2 | 3 | export type AwareEvent = { 4 | type: EventType.Aware; 5 | timeTaken: number; 6 | usageUserId: string; 7 | }; 8 | 9 | export const pickAwareEvent = (props: Partial): AwareEvent => { 10 | return (({ timeTaken, usageUserId }): AwareEvent => ({ 11 | type: EventType.Aware, 12 | timeTaken: timeTaken || 0, 13 | usageUserId: usageUserId || 'Error', 14 | }))(props || {}); 15 | }; 16 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/UsageTelemetry/UsageEvent/SytemEvent.ts: -------------------------------------------------------------------------------- 1 | import { EventType } from '../UsageEvent'; 2 | 3 | export type SystemEvent = { 4 | eventName?: string; 5 | feature?: string; 6 | subFeature?: string; 7 | subFeatureLevel2?: string; 8 | featureLocation?: string; 9 | timeTaken: number; 10 | type: EventType.System; 11 | businessTransactionId?: string; 12 | experienceResult?: boolean; 13 | }; 14 | 15 | export const pickSystemEvent = (props: Partial): SystemEvent => { 16 | return (({ 17 | eventName, 18 | feature, 19 | subFeature, 20 | subFeatureLevel2, 21 | featureLocation, 22 | timeTaken, 23 | businessTransactionId, 24 | experienceResult, 25 | }): SystemEvent => ({ 26 | type: EventType.System, 27 | eventName, 28 | feature, 29 | subFeature, 30 | subFeatureLevel2, 31 | featureLocation, 32 | timeTaken: timeTaken || 0, 33 | experienceResult: experienceResult || true, 34 | businessTransactionId, 35 | }))(props || {}); 36 | }; 37 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/UsageTelemetry/UsageEvent/UserEvent.ts: -------------------------------------------------------------------------------- 1 | import { EventType } from '..'; 2 | 3 | export type UserEvent = { 4 | feature?: string; 5 | subFeature?: string; 6 | subFeatureLevel2?: string; 7 | featureLocation?: string; 8 | type: EventType.User; 9 | businessTransactionId?: string; 10 | eventName: string; 11 | experienceResult?: boolean; 12 | timeTaken?: number; 13 | }; 14 | 15 | export const pickUserEvent = (props: Partial): UserEvent => { 16 | return (({ 17 | businessTransactionId, 18 | eventName, 19 | experienceResult, 20 | feature, 21 | subFeature, 22 | subFeatureLevel2, 23 | featureLocation, 24 | timeTaken, 25 | }): UserEvent => ({ 26 | type: EventType.User, 27 | businessTransactionId, 28 | eventName: eventName as string, 29 | experienceResult: experienceResult || true, 30 | feature, 31 | subFeature, 32 | subFeatureLevel2, 33 | featureLocation, 34 | timeTaken: timeTaken || 0, 35 | }))(props || {}); 36 | }; 37 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/UsageTelemetry/UsageFeatureProps.ts: -------------------------------------------------------------------------------- 1 | export type UsageFeatureProps = { 2 | feature: string; 3 | subFeature: string; 4 | subFeatureLevel2?: string; 5 | featureLocation?: string; 6 | }; 7 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/UsageTelemetry/UsageLog.ts: -------------------------------------------------------------------------------- 1 | import { UsageEvent } from './UsageEvent'; 2 | import { UserAttribute } from './UserAttribute'; 3 | import { UsageTracker } from './UsageTracker'; 4 | 5 | export type UsageLog = UsageEvent & 6 | UserAttribute & 7 | UsageTracker & { feature: string }; 8 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/UsageTelemetry/UsageTracker.ts: -------------------------------------------------------------------------------- 1 | import { EventType } from './UsageEvent'; 2 | 3 | export type UsageTracker = { 4 | name?: string; 5 | actionTrackingId?: string; 6 | correlationTrackingId?: string; 7 | pageTrackingId?: string; 8 | startTime?: Date; 9 | eventDate?: Date; 10 | type: string; 11 | flightId?: string; // TODO: Move this to a different action => Flighting Action 12 | flightName?: string; // TODO: Move this to a different action => Flighting Action 13 | moduleName?: string; 14 | usageUserId?: string; 15 | usageVersion?: string; 16 | inheritedName?: string; 17 | }; 18 | 19 | export const pickUsageTracker = ( 20 | props: Partial 21 | ): UsageTracker => { 22 | return (({ 23 | name, 24 | actionTrackingId, 25 | correlationTrackingId, 26 | pageTrackingId, 27 | startTime, 28 | eventDate, 29 | type, 30 | flightId, 31 | flightName, 32 | moduleName, 33 | usageUserId, 34 | }): UsageTracker => ({ 35 | name, 36 | actionTrackingId, 37 | correlationTrackingId, 38 | pageTrackingId, 39 | startTime: startTime || new Date(), 40 | eventDate: eventDate || new Date(), 41 | type: type || EventType.User, // Default the type to user when the type is not passed in 42 | flightId, 43 | flightName, 44 | moduleName, 45 | usageUserId, 46 | }))(props || {}); 47 | }; 48 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/UsageTelemetry/UserAttribute.ts: -------------------------------------------------------------------------------- 1 | import { UsageHelper } from './Helpers/Usage.helper'; 2 | 3 | export type UserAttribute = { 4 | usageUserId: string; 5 | sessionId: string; 6 | lastActiveTime: Date | string; 7 | }; 8 | 9 | export const pickUserAttribute = ( 10 | props: Partial 11 | ): UserAttribute => { 12 | return (({ lastActiveTime, usageUserId, sessionId }): UserAttribute => ({ 13 | lastActiveTime: lastActiveTime || new Date(), 14 | usageUserId: usageUserId || 'Error', 15 | sessionId: sessionId || UsageHelper.Guid(), // TODO: guid 16 | }))(props || {}); 17 | }; 18 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/UsageTelemetry/index.ts: -------------------------------------------------------------------------------- 1 | export * from './UsageFeatureProps'; 2 | export * from './UsageEvent/SytemEvent'; 3 | export * from './UsageEvent/UserEvent'; 4 | export * from './UsageEvent/AwareEvent'; 5 | export * from './Config/UsageTelemetryConfig'; 6 | export * from './UsageTracker'; 7 | export * from './UserAttribute'; 8 | export * from './UsageEvent'; 9 | export * from './UsageLog'; 10 | export * from './Helpers/Usage.helper'; 11 | export * from './Helpers/UsageEventName'; 12 | export * from './Helpers/CuppSchemaMapper'; 13 | -------------------------------------------------------------------------------- /extensions/microsoft-employee-experience/src/UsageTelemetryHelper.ts: -------------------------------------------------------------------------------- 1 | import { 2 | UsageFeatureProps, 3 | pickUserEvent, 4 | UserEvent, 5 | UsageEventName, 6 | pickSystemEvent, 7 | SystemEvent, 8 | } from './UsageTelemetry'; 9 | 10 | // Generate a new Feature properties 11 | export function getFeature( 12 | capability: string | string[], 13 | subCapability: string | string[] | undefined = undefined, 14 | subCapabilityLevel2: string | string[] | undefined = undefined, 15 | location: string | undefined = undefined 16 | ): UsageFeatureProps { 17 | return { 18 | feature: Array.isArray(capability) ? capability.join('.') : capability, 19 | subFeature: Array.isArray(subCapability) ? subCapability.join('.') : subCapability || '', 20 | subFeatureLevel2: Array.isArray(subCapabilityLevel2) ? subCapabilityLevel2.join('.') : subCapabilityLevel2 || '', 21 | featureLocation: location || '', 22 | }; 23 | } 24 | 25 | // Use to attach additional subFeatureLevel2 to existing Feature properties 26 | export function mergeFeature( 27 | existing: UsageFeatureProps, 28 | subCapabilityLevel2: string | string[] | undefined = undefined, 29 | location: string | undefined = undefined 30 | ): UsageFeatureProps { 31 | return { 32 | ...existing, 33 | subFeatureLevel2: `${existing.subFeatureLevel2 ? `${existing.subFeatureLevel2}.` : ''}${ 34 | Array.isArray(subCapabilityLevel2) ? subCapabilityLevel2.join('.') : subCapabilityLevel2 35 | }`, 36 | featureLocation: location || existing.featureLocation, 37 | }; 38 | } 39 | 40 | // To be used with