From 419725ca739c41cdfa587f0e833b75b1d88918c7 Mon Sep 17 00:00:00 2001 From: Charles Wahome Date: Fri, 15 Mar 2024 13:47:05 +0300 Subject: [PATCH 01/45] Chore: update to 9.7.0 (#3044) * Bump version to 8.1.0 * return playwright test branch condition * Fix: Missing return statement (#2507) * Bump version to 8.2.0 * Chore: Release 9.0.0 (#2619) * Chore: Update to 8.2.0 (#2520) * Fix: Add retry handler for revoking permissions (#2515) * Fix: Modify permissions tab resize (#2523) * Feature: Surface documentation links (#2219) * Task: Add support for persisting collections (#2525) * Chore: popups manager (#2514) * Task: support multiple collections (#2549) * Task: Show feedback - add to collections (#2555) * Chore: change "remove" aria label (#2558) * Chore: append unique paths to collection (#2560) * Fix: Collections reducer failing test logic (#2573) * Task: Reorder revoking permission event messages (#2526) * Fix: Persist sample body (#2550) * Fix: collection status for nodes with IDs (#2575) * add tests * get url for id nodes * Chore: Update failing snapshots (#2571) * Chore: Uppercase methods (#2578) * Task: Dependabot upgrades for May (#2557) * Task: Add support for uploading postman collections (#2579) * Fix: Remove use of external label (#2582) * Chore: utilise resource paths (#2584) * Chore: ensure unique paths (#2586) * Chore: Dependabot upgrades - June (#2612) * Chore: Clear network error message (#2615) * Task: Add new permissions scopeType (#2585) * Bump version to 9.0.0 --------- Co-authored-by: EvansA Co-authored-by: Charles Wahome * Fix: Show documentation URL for supported Resource Explorer items (#2625) * Fix: Ensure collections are initiated in store (#2627) * Chore: Release 9.1.0 (#2694) * Create microsoft-graph-explorer-v4-branch-protection.yml (#2617) * Fix: Update dependencies with high security risk (#2631) * Fix: Get documentation on resource render (#2634) * Feature: Add flighting support (#2621) * FabricBot: Onboarding to GitOps.ResourceManagement because of FabricBot decommissioning (#2669) * Fix: Permissions scrollbar (#2576) * Fix: Bundle Monaco-editor package (#2677) * Fix: July Dependabot upgrades (#2679) * Fix: Update resource management bot (#2682) * Fix: Restore resources search box functionality (#2686) * Bump version to 9.1.0 * Bump version to 9.2.0 * Bump version to 9.3.0 * Fix: Remove component chunking (#2871) Temporarily removed until we have it working properly * Chore: Hotfix release 9.3.2 (#2898) * Hotfix: Upgrade msal-browser version (#2900) * Bump version to 9.4.0 * add CI:false * Chore: Release 9.5.0 (#2994) * Chore: Update to 9.4.0 (#2959) * Fix: Remove lazy loading on feedback button (#2963) * Fix: Ignore aria rule on Sample Queries component (#2962) * Chore: change workflow permissions (#2966) * Bump version to 9.5.0 --------- Co-authored-by: Charles Wahome Co-authored-by: github-actions * Bump version to 9.6.0 * reset permissions to initial state on error * fix failing tests * Bump version to 9.7.0 --------- Co-authored-by: github-actions Co-authored-by: Elinor Co-authored-by: EvansA Co-authored-by: Elinor --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index bdb86d580..e4496b88f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "graph-explorer-v2", - "version": "9.6.0", + "version": "9.7.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "graph-explorer-v2", - "version": "9.6.0", + "version": "9.7.0", "dependencies": { "@augloop/types-core": "file:packages/types-core-2.16.189.tgz", "@axe-core/webdriverjs": "4.8.4", diff --git a/package.json b/package.json index 7bf96399b..8d3d7eafc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "graph-explorer-v2", - "version": "9.6.0", + "version": "9.7.0", "private": true, "dependencies": { "@augloop/types-core": "file:packages/types-core-2.16.189.tgz", From 464ae59c61e6d15c4c1e617c6f94a0b4d610ddb4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Mar 2024 18:45:40 +0300 Subject: [PATCH 02/45] Chore(deps): Bump follow-redirects from 1.15.0 to 1.15.6 (#3058) --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index e4496b88f..2d2e6f514 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9801,9 +9801,9 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==" }, "node_modules/follow-redirects": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.0.tgz", - "integrity": "sha512-aExlJShTV4qOUOL7yF1U5tvLCB0xQuudbf6toyYA0E/acBNw71mvjFTnLaRp50aQaYocMR0a/RMMBIHeZnGyjQ==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "funding": [ { "type": "individual", From 5e9c4e2e87bc04a4010b7440fb9d9631feed15c9 Mon Sep 17 00:00:00 2001 From: Elinor Date: Thu, 21 Mar 2024 11:26:39 +0300 Subject: [PATCH 03/45] Fix: Resource path causing app to crash (#3053) --- src/app/utils/query-parameter-sanitization.ts | 6 ++++++ src/app/utils/query-url-sanitization.ts | 2 ++ 2 files changed, 8 insertions(+) diff --git a/src/app/utils/query-parameter-sanitization.ts b/src/app/utils/query-parameter-sanitization.ts index e9ff67407..514b124e8 100644 --- a/src/app/utils/query-parameter-sanitization.ts +++ b/src/app/utils/query-parameter-sanitization.ts @@ -16,6 +16,7 @@ const LAMBDA_OPERATORS = ['/any', '/all']; // REGEXES const ALL_ALPHA_REGEX = /^[a-z]+$/i; +const ONE_NUMERIC_REGEX = /^(?=[a-zA-Z]*\d[a-zA-Z]*$)[a-zA-Z\d]*$/; const POSITIVE_INTEGER_REGEX = /^[1-9]\d*$/; // Matches media type formats // Examples: https://www.iana.org/assignments/media-types/media-types.xhtml @@ -62,6 +63,10 @@ function isAllAlpha(str: string): boolean { return ALL_ALPHA_REGEX.test(str); } +function isAlphaNumeric(str: string): boolean { + return ONE_NUMERIC_REGEX.test(str); +} + function isPlaceHolderSegment(segment: string) { return segment.startsWith('{') && segment.endsWith('}') } @@ -483,6 +488,7 @@ function sanitizeFilterQueryOptionValue(queryParameterValue: string): string { export { isPropertyName, isAllAlpha, + isAlphaNumeric, isPlaceHolderSegment, sanitizeQueryParameter } diff --git a/src/app/utils/query-url-sanitization.ts b/src/app/utils/query-url-sanitization.ts index c07246162..1d74b56de 100644 --- a/src/app/utils/query-url-sanitization.ts +++ b/src/app/utils/query-url-sanitization.ts @@ -2,6 +2,7 @@ import { IQuery } from '../../types/query-runner'; import { isAllAlpha, + isAlphaNumeric, isPlaceHolderSegment, sanitizeQueryParameter } from './query-parameter-sanitization'; @@ -105,6 +106,7 @@ function sanitizePathSegment(previousSegment: string, segment: string): string { if ( isAllAlpha(segment) || + isAlphaNumeric(segment) || isDeprecation(segment) || SANITIZED_ITEM_PATH_REGEX.test(segment) || segmentsToIgnore.includes(segment.toLowerCase()) || From 151f34a7b627607f59dc434378b0997887527ab6 Mon Sep 17 00:00:00 2001 From: Charles Wahome Date: Mon, 25 Mar 2024 09:13:43 +0300 Subject: [PATCH 04/45] Fix: [autocomplete] show dollar sign paths (#3052) --- .../query-input/auto-complete/AutoComplete.tsx | 4 ++-- src/modules/suggestions/suggestions.ts | 10 +--------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/app/views/query-runner/query-input/auto-complete/AutoComplete.tsx b/src/app/views/query-runner/query-input/auto-complete/AutoComplete.tsx index 7dec3b35d..23db381a5 100644 --- a/src/app/views/query-runner/query-input/auto-complete/AutoComplete.tsx +++ b/src/app/views/query-runner/query-input/auto-complete/AutoComplete.tsx @@ -241,9 +241,9 @@ const AutoComplete = (props: IAutoCompleteProps) => { const appendSuggestionToUrl = (selected: string) => { if (!selected) { return; } - + const { context } = getLastDelimiterInUrl(queryUrl); let query = selected; - if (selected.startsWith(delimiters.DOLLAR.symbol)) { + if (selected.startsWith(delimiters.DOLLAR.symbol) && context === 'parameters') { selected += delimiters.EQUALS.symbol; query = ''; } diff --git a/src/modules/suggestions/suggestions.ts b/src/modules/suggestions/suggestions.ts index a31d8aef9..33abb2132 100644 --- a/src/modules/suggestions/suggestions.ts +++ b/src/modules/suggestions/suggestions.ts @@ -52,20 +52,12 @@ class Suggestions implements ISuggestions { } private createOpenApiResponse(versionedResources: IResource[], url: string): IParsedOpenApiResponse { - const paths: string[] = []; - - versionedResources.forEach((resource: IResource) => { - if (!resource.segment.contains('$')) { - paths.push(resource.segment); - } - }); - const response: IParsedOpenApiResponse = { createdAt: '', parameters: [{ verb: 'get', values: [], - links: paths + links: versionedResources.map((resource: IResource) => resource.segment) }], url }; From 53c97bb2a5a6f0feb1b77efb53f841b61efef004 Mon Sep 17 00:00:00 2001 From: Elinor Date: Tue, 26 Mar 2024 15:25:19 +0300 Subject: [PATCH 05/45] Fix: Canary urls crashing (#3062) --- src/app/utils/query-url-sanitization.ts | 3 +-- src/app/utils/resources/resources-filter.ts | 2 +- .../query-input/auto-complete/suffix/documentation.ts | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/app/utils/query-url-sanitization.ts b/src/app/utils/query-url-sanitization.ts index 1d74b56de..8e07d5b72 100644 --- a/src/app/utils/query-url-sanitization.ts +++ b/src/app/utils/query-url-sanitization.ts @@ -102,14 +102,13 @@ function sanitizedQueryUrl(url: string): string { * @param segment */ function sanitizePathSegment(previousSegment: string, segment: string): string { - const segmentsToIgnore = ['$value', '$count', '$ref', '$batch']; if ( isAllAlpha(segment) || isAlphaNumeric(segment) || isDeprecation(segment) || SANITIZED_ITEM_PATH_REGEX.test(segment) || - segmentsToIgnore.includes(segment.toLowerCase()) || + segment.startsWith('$') || ENTITY_NAME_REGEX.test(segment) ) { return segment; diff --git a/src/app/utils/resources/resources-filter.ts b/src/app/utils/resources/resources-filter.ts index 03e182c2a..a30f0f18e 100644 --- a/src/app/utils/resources/resources-filter.ts +++ b/src/app/utils/resources/resources-filter.ts @@ -24,7 +24,7 @@ function getMatchingResourceForUrl(url: string, resources: IResource[]): IResour let matching = [...resources]; let node; for (const path of parts) { - if (hasPlaceHolders(path)) { + if (hasPlaceHolders(path) && path !== '{undefined-id}') { node = matching.find(k => hasPlaceHolders(k.segment)); matching = node?.children || []; } else { diff --git a/src/app/views/query-runner/query-input/auto-complete/suffix/documentation.ts b/src/app/views/query-runner/query-input/auto-complete/suffix/documentation.ts index 605d8d97f..162381e76 100644 --- a/src/app/views/query-runner/query-input/auto-complete/suffix/documentation.ts +++ b/src/app/views/query-runner/query-input/auto-complete/suffix/documentation.ts @@ -69,7 +69,7 @@ class DocumentationService implements IDocumentationService { if (matchingResource && matchingResource.labels.length > 0) { const currentLabel = matchingResource.labels.filter(k => k.name === this.queryVersion)[0]; - const method = currentLabel.methods[0]; + const method = currentLabel?.methods[0]; if (typeof method === 'string') { return null; } From d88a4f8b4d101cb7e53d98f6881d12bfe258fe33 Mon Sep 17 00:00:00 2001 From: Elinor Date: Wed, 27 Mar 2024 12:12:42 +0300 Subject: [PATCH 06/45] Chore: Update to 9.8.0 (#3067) --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2d2e6f514..2ef8f4e02 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "graph-explorer-v2", - "version": "9.7.0", + "version": "9.8.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "graph-explorer-v2", - "version": "9.7.0", + "version": "9.8.0", "dependencies": { "@augloop/types-core": "file:packages/types-core-2.16.189.tgz", "@axe-core/webdriverjs": "4.8.4", diff --git a/package.json b/package.json index 8d3d7eafc..0b2a64d67 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "graph-explorer-v2", - "version": "9.7.0", + "version": "9.8.0", "private": true, "dependencies": { "@augloop/types-core": "file:packages/types-core-2.16.189.tgz", From f3e73d6ccebeae7f05fec9c02d12b9c731a915f9 Mon Sep 17 00:00:00 2001 From: Elinor Date: Wed, 24 Apr 2024 13:40:43 +0300 Subject: [PATCH 07/45] Fix: Dependabot upgrades - March (#3061) --- package-lock.json | 668 ++++++++++++++---- package.json | 32 +- .../authentication/AuthenticationWrapper.ts | 6 +- 3 files changed, 538 insertions(+), 168 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2ef8f4e02..3286864e9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "9.8.0", "dependencies": { "@augloop/types-core": "file:packages/types-core-2.16.189.tgz", - "@axe-core/webdriverjs": "4.8.4", + "@axe-core/webdriverjs": "4.8.5", "@azure/msal-browser": "3.10.0", "@babel/core": "7.23.3", "@babel/runtime": "7.23.8", @@ -29,28 +29,28 @@ "babel-loader": "9.1.3", "babel-preset-react-app": "10.0.1", "bfj": "8.0.0", - "bootstrap": "5.3.2", + "bootstrap": "5.3.3", "case-sensitive-paths-webpack-plugin": "2.4.0", "css-loader": "6.10.0", - "dotenv": "16.4.2", - "dotenv-expand": "10.0.0", + "dotenv": "16.4.5", + "dotenv-expand": "11.0.6", "eslint-config-react-app": "7.0.1", "eslint-plugin-react": "7.33.2", "eslint-webpack-plugin": "4.0.1", - "express": "4.18.2", + "express": "4.19.2", "expvariantassignmentsdk": "file:packages/expvariantassignmentsdk-1.0.0.tgz", "file-loader": "6.2.0", "fork-ts-checker-webpack-plugin": "9.0.2", - "fs-extra": "11.1.1", + "fs-extra": "11.2.0", "guid-typescript": "1.0.9", "isomorphic-fetch": "3.0.0", "localforage": "1.10.0", - "mini-css-extract-plugin": "2.8.0", + "mini-css-extract-plugin": "2.8.1", "monaco-editor": "0.30.1", "monaco-editor-webpack-plugin": "6.0.0", "office-ui-fabric-core": "11.1.0", "postcss-flexbugs-fixes": "5.0.2", - "postcss-loader": "7.3.3", + "postcss-loader": "8.1.1", "postcss-preset-env": "9.3.0", "re-resizable": "6.9.11", "react": "18.2.0", @@ -60,20 +60,20 @@ "redux": "4.2.1", "redux-thunk": "2.4.2", "resolve": "1.22.8", - "sass": "1.69.7", - "sass-loader": "13.3.2", - "style-loader": "3.3.3", + "sass": "1.72.0", + "sass-loader": "14.1.1", + "style-loader": "3.3.4", "typescript": "5.3.3", - "url": "0.11.1", + "url": "0.11.3", "url-loader": "4.1.1", - "webpack": "5.90.1", + "webpack": "5.90.3", "webpack-dev-server": "4.15.1", "webpack-manifest-plugin": "5.0.0", "workbox-webpack-plugin": "7.0.0" }, "devDependencies": { "@axe-core/playwright": "4.7.3", - "@playwright/test": "1.40.1", + "@playwright/test": "1.42.0", "@types/chromedriver": "81.0.1", "@types/isomorphic-fetch": "0.0.39", "@types/jest": "29.5.12", @@ -88,7 +88,7 @@ "@typescript-eslint/parser": "6.17.0", "acorn": "8.11.3", "babel-jest": "29.7.0", - "chromedriver": "119.0.1", + "chromedriver": "122.0.4", "eslint": "8.56.0", "html-webpack-plugin": "5.6.0", "jest": "29.7.0", @@ -101,7 +101,7 @@ "react-dev-utils": "12.0.1", "redux-logger": "3.0.6", "redux-mock-store": "1.5.4", - "selenium-webdriver": "4.17.0", + "selenium-webdriver": "4.18.1", "start-server-and-test": "2.0.3", "ts-jest": "29.1.2" } @@ -161,11 +161,11 @@ } }, "node_modules/@axe-core/webdriverjs": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/@axe-core/webdriverjs/-/webdriverjs-4.8.4.tgz", - "integrity": "sha512-SdQN2YSQeV2BxW8kR9q7x+IjecTLI+tNsLN2RrqatYUUsWWE2PmrLjG/whgao1xtq3Zf9Te8VmLtp2RI7L/2/A==", + "version": "4.8.5", + "resolved": "https://registry.npmjs.org/@axe-core/webdriverjs/-/webdriverjs-4.8.5.tgz", + "integrity": "sha512-cktRjzb3vl/nuyWSmULuw88O21op1LhxlStGu81zjWZssq4gVxBJSqZCBtCVt4w2ekSMxv/vjSXcx+ASnTssmw==", "dependencies": { - "axe-core": "~4.8.3" + "axe-core": "~4.8.4" }, "peerDependencies": { "selenium-webdriver": ">3.0.0-beta || >=2.53.1 || >4.0.0-alpha" @@ -4305,12 +4305,12 @@ } }, "node_modules/@playwright/test": { - "version": "1.40.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.40.1.tgz", - "integrity": "sha512-EaaawMTOeEItCRvfmkI9v6rBkF1svM8wjl/YPRrg2N2Wmp+4qJYkWtJsbew1szfKKDm6fPLy4YAanBhIlf9dWw==", + "version": "1.42.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.42.0.tgz", + "integrity": "sha512-2k1HzC28Fs+HiwbJOQDUwrWMttqSLUVdjCqitBOjdCD0svWOMQUVqrXX6iFD7POps6xXAojsX/dGBpKnjZctLA==", "dev": true, "dependencies": { - "playwright": "1.40.1" + "playwright": "1.42.0" }, "bin": { "playwright": "cli.js" @@ -4486,6 +4486,12 @@ "node": ">= 10" } }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "dev": true + }, "node_modules/@types/atob-lite": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@types/atob-lite/-/atob-lite-2.0.0.tgz", @@ -6128,6 +6134,18 @@ "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/ast-types-flow": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", @@ -6221,12 +6239,12 @@ } }, "node_modules/axios": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.1.tgz", - "integrity": "sha512-vfBmhDpKafglh0EldBEbVuoe7DyAavGSLWhuSm5ZSEKQnHhBf0xAAwybbNH1IkrJNGnS/VG4I5yxig1pCEXE4g==", + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz", + "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==", "dev": true, "dependencies": { - "follow-redirects": "^1.15.0", + "follow-redirects": "^1.15.4", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } @@ -6512,6 +6530,15 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/basic-ftp": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", @@ -6562,12 +6589,12 @@ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, "node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "dependencies": { "bytes": "3.1.2", - "content-type": "~1.0.4", + "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", @@ -6575,7 +6602,7 @@ "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.11.0", - "raw-body": "2.5.1", + "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" }, @@ -6620,9 +6647,9 @@ "dev": true }, "node_modules/bootstrap": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.2.tgz", - "integrity": "sha512-D32nmNWiQHo94BKHLmOrdjlL05q1c8oxbtBphQFb9Z5to6eGRDCm0QgeaZ4zFBHzfg2++rqa2JkqCcxDy0sH0g==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz", + "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==", "funding": [ { "type": "github", @@ -6748,13 +6775,18 @@ } }, "node_modules/call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6893,17 +6925,17 @@ } }, "node_modules/chromedriver": { - "version": "119.0.1", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-119.0.1.tgz", - "integrity": "sha512-lpCFFLaXPpvElTaUOWKdP74pFb/sJhWtWqMjn7Ju1YriWn8dT5JBk84BGXMPvZQs70WfCYWecxdMmwfIu1Mupg==", + "version": "122.0.4", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-122.0.4.tgz", + "integrity": "sha512-MxkaWaxCqefHyh9UorGzl1F6ZNBgC7pqgT0piAysLZdw20ojSgJ62ljG8SFbhDJqBTegKbmuioa6MQ1m4Czdsg==", "dev": true, "hasInstallScript": true, "dependencies": { "@testim/chrome-version": "^1.1.4", - "axios": "^1.6.0", + "axios": "^1.6.7", "compare-versions": "^6.1.0", "extract-zip": "^2.0.1", - "https-proxy-agent": "^5.0.1", + "proxy-agent": "^6.4.0", "proxy-from-env": "^1.1.0", "tcp-port-used": "^1.0.2" }, @@ -7119,9 +7151,9 @@ ] }, "node_modules/content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "engines": { "node": ">= 0.6" } @@ -7132,9 +7164,9 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "engines": { "node": ">= 0.6" } @@ -7532,6 +7564,15 @@ "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==" }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, "node_modules/data-urls": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", @@ -7652,16 +7693,19 @@ } }, "node_modules/define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dependencies": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/define-lazy-prop": { @@ -7688,6 +7732,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dev": true, + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -7912,9 +7970,9 @@ } }, "node_modules/dotenv": { - "version": "16.4.2", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.2.tgz", - "integrity": "sha512-rZSSFxke7d9nYQ5NeMIwp5PP+f8wXgKNljpOb7KtH6SKW1cEqcXAz9VSJYVLKe7Jhup/gUYOkaeSVyK8GJ+nBg==", + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", "engines": { "node": ">=12" }, @@ -7923,11 +7981,17 @@ } }, "node_modules/dotenv-expand": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz", - "integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==", + "version": "11.0.6", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.6.tgz", + "integrity": "sha512-8NHi73otpWsZGBSZwwknTXS5pqMOrk9+Ssrna8xCaxkzEpU9OTf9R5ArQGVw03//Zmk9MOwLPng9WwndvpAJ5g==", + "dependencies": { + "dotenv": "^16.4.4" + }, "engines": { "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" } }, "node_modules/duplexer": { @@ -8023,6 +8087,14 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "engines": { + "node": ">=6" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -8083,6 +8155,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-iterator-helpers": { "version": "1.0.15", "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.15.tgz", @@ -9301,16 +9392,16 @@ } }, "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.1", + "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -9801,9 +9892,9 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==" }, "node_modules/follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", + "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", "funding": [ { "type": "individual", @@ -10066,9 +10157,9 @@ "dev": true }, "node_modules/fs-extra": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz", - "integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -10152,15 +10243,19 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dependencies": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", "hasown": "^2.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -10205,6 +10300,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-uri": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.3.tgz", + "integrity": "sha512-BzUrJBS9EcUb4cFol8r4W3v1cPsSyajLSthNkz5BxbpDcHN5tIrM10E2eNvfnvBn3DaT3DUgx0OpsBKkaOpanw==", + "dev": true, + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4", + "fs-extra": "11.2.0" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", @@ -10399,11 +10509,11 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dependencies": { - "get-intrinsic": "^1.1.1" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -10855,6 +10965,25 @@ "node": ">= 0.4" } }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dev": true, + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ip-address/node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true + }, "node_modules/ip-regex": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", @@ -13356,9 +13485,9 @@ } }, "node_modules/jiti": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.18.2.tgz", - "integrity": "sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", + "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", "bin": { "jiti": "bin/jiti.js" } @@ -13394,6 +13523,12 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "dev": true + }, "node_modules/jsdom": { "version": "20.0.3", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", @@ -13868,7 +14003,7 @@ "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "engines": { "node": ">= 0.6" } @@ -13961,9 +14096,9 @@ } }, "node_modules/mini-css-extract-plugin": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.8.0.tgz", - "integrity": "sha512-CxmUYPFcTgET1zImteG/LZOy/4T5rTojesQXkSNBiquhydn78tfbCE9sjIjnJ/UcjNjOC1bphTCCW5rrS7cXAg==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.8.1.tgz", + "integrity": "sha512-/1HDlyFRxWIZPI1ZpgqlZ8jMw/1Dp/dl3P0L1jtZ+zVcHqwPhGwaJwKL00WVgfnBy6PWCde9W65or7IIETImuA==", "dependencies": { "schema-utils": "^4.0.0", "tapable": "^2.2.1" @@ -14092,6 +14227,15 @@ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/no-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", @@ -14404,6 +14548,76 @@ "node": ">=6" } }, + "node_modules/pac-proxy-agent": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.0.1.tgz", + "integrity": "sha512-ASV8yU4LLKBAjqIPMbrgtaKIvxQri/yh2OpI+S6hVa9JRkUI3Y3NPFbfngDtY7oFtSMD3w31Xns89mDa3Feo5A==", + "dev": true, + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.2", + "pac-resolver": "^7.0.0", + "socks-proxy-agent": "^8.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-proxy-agent/node_modules/agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-proxy-agent/node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-proxy-agent/node_modules/https-proxy-agent": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "dev": true, + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -14623,12 +14837,12 @@ } }, "node_modules/playwright": { - "version": "1.40.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.40.1.tgz", - "integrity": "sha512-2eHI7IioIpQ0bS1Ovg/HszsN/XKNwEG1kbzSDDmADpclKc7CyqkHw7Mg2JCz/bbCxg25QUPcjksoMW7JcIFQmw==", + "version": "1.42.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.42.0.tgz", + "integrity": "sha512-Ko7YRUgj5xBHbntrgt4EIw/nE//XBHOKVKnBjO1KuZkmkhlbgyggTe5s9hjqQ1LpN+Xg+kHsQyt5Pa0Bw5XpvQ==", "dev": true, "dependencies": { - "playwright-core": "1.40.1" + "playwright-core": "1.42.0" }, "bin": { "playwright": "cli.js" @@ -14641,9 +14855,9 @@ } }, "node_modules/playwright-core": { - "version": "1.40.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.40.1.tgz", - "integrity": "sha512-+hkOycxPiV534c4HhpfX6yrlawqVUzITRKwHAmYfmsVreltEl6fAZJ3DPfLMOODw0H3s1Itd6MDCWmP1fl/QvQ==", + "version": "1.42.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.42.0.tgz", + "integrity": "sha512-0HD9y8qEVlcbsAjdpBaFjmaTHf+1FeIddy8VJLeiqwhcNqGCBe4Wp2e8knpqiYbzxtxarxiXyNDw2cG8sCaNMQ==", "dev": true, "bin": { "playwright-core": "cli.js" @@ -15045,24 +15259,33 @@ } }, "node_modules/postcss-loader": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.3.tgz", - "integrity": "sha512-YgO/yhtevGO/vJePCQmTxiaEwER94LABZN0ZMT4A0vsak9TpO+RvKRs7EmJ8peIlB9xfXCsS7M8LjqncsUZ5HA==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.1.1.tgz", + "integrity": "sha512-0IeqyAsG6tYiDRCYKQJLAmgQr47DX6N7sFSWvQxt6AcupX8DIdmykuk/o/tx0Lze3ErGHJEp5OSRxrelC6+NdQ==", "dependencies": { - "cosmiconfig": "^8.2.0", - "jiti": "^1.18.2", - "semver": "^7.3.8" + "cosmiconfig": "^9.0.0", + "jiti": "^1.20.0", + "semver": "^7.5.4" }, "engines": { - "node": ">= 14.15.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { + "@rspack/core": "0.x || 1.x", "postcss": "^7.0.0 || ^8.0.1", "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } } }, "node_modules/postcss-loader/node_modules/argparse": { @@ -15071,20 +15294,28 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "node_modules/postcss-loader/node_modules/cosmiconfig": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.2.0.tgz", - "integrity": "sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dependencies": { - "import-fresh": "^3.2.1", + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", - "parse-json": "^5.0.0", - "path-type": "^4.0.0" + "parse-json": "^5.2.0" }, "engines": { "node": ">=14" }, "funding": { "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/postcss-loader/node_modules/js-yaml": { @@ -15544,6 +15775,72 @@ "node": ">= 0.10" } }, + "node_modules/proxy-agent": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.1", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/https-proxy-agent": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -15669,9 +15966,9 @@ } }, "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -16556,9 +16853,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/sass": { - "version": "1.69.7", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.7.tgz", - "integrity": "sha512-rzj2soDeZ8wtE2egyLXgOOHQvaC2iosZrkF6v3EUG+tBwEvhqUCzm0VP3k9gHF9LXbSrRhT5SksoI56Iw8NPnQ==", + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.72.0.tgz", + "integrity": "sha512-Gpczt3WA56Ly0Mn8Sl21Vj94s1axi9hDIzDFn9Ph9x3C3p4nNyvsqJoQyVXKou6cBlfFWEgRW4rT8Tb4i3XnVA==", "dependencies": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -16572,28 +16869,28 @@ } }, "node_modules/sass-loader": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.3.2.tgz", - "integrity": "sha512-CQbKl57kdEv+KDLquhC+gE3pXt74LEAzm+tzywcA0/aHZuub8wTErbjAoNI57rPUWRYRNC5WUnNl8eGJNbDdwg==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-14.1.1.tgz", + "integrity": "sha512-QX8AasDg75monlybel38BZ49JP5Z+uSKfKwF2rO7S74BywaRmGQMUBw9dtkS+ekyM/QnP+NOrRYq8ABMZ9G8jw==", "dependencies": { "neo-async": "^2.6.2" }, "engines": { - "node": ">= 14.15.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "fibers": ">= 3.1.0", + "@rspack/core": "0.x || 1.x", "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", "sass": "^1.3.0", "sass-embedded": "*", "webpack": "^5.0.0" }, "peerDependenciesMeta": { - "fibers": { + "@rspack/core": { "optional": true }, "node-sass": { @@ -16604,6 +16901,9 @@ }, "sass-embedded": { "optional": true + }, + "webpack": { + "optional": true } } }, @@ -16643,9 +16943,9 @@ "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==" }, "node_modules/selenium-webdriver": { - "version": "4.17.0", - "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.17.0.tgz", - "integrity": "sha512-e2E+2XBlGepzwgFbyQfSwo9Cbj6G5fFfs9MzAS00nC99EewmcS2rwn2MwtgfP7I5p1e7DYv4HQJXtWedsu6DvA==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.18.1.tgz", + "integrity": "sha512-uP4OJ5wR4+VjdTi5oi/k8oieV2fIhVdVuaOPrklKghgS59w7Zz3nGa5gcG73VcU9EBRv5IZEBRhPr7qFJAj5mQ==", "dependencies": { "jszip": "^3.10.1", "tmp": "^0.2.1", @@ -16830,14 +17130,16 @@ } }, "node_modules/set-function-length": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", - "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "dependencies": { - "define-data-property": "^1.1.1", - "get-intrinsic": "^1.2.1", + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -16897,13 +17199,17 @@ "dev": true }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -16928,6 +17234,16 @@ "node": ">=8" } }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, "node_modules/sockjs": { "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", @@ -16938,6 +17254,46 @@ "websocket-driver": "^0.7.4" } }, + "node_modules/socks": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.1.tgz", + "integrity": "sha512-B6w7tkwNid7ToxjZ08rQMT8M9BJAf8DKx8Ft4NivzH0zBUfd6jldGcisJn/RLgxcX3FPNDdNQCUEMMT79b+oCQ==", + "dev": true, + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.2.tgz", + "integrity": "sha512-8zuqoLv1aP/66PHF5TqwJ7Czm3Yv32urJQHrVyhD7mmA6d61Zv8cIXQYPTWwmg6qlupnPvs/QKDmfa4P/qct2g==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "socks": "^2.7.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/socks-proxy-agent/node_modules/agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/source-list-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", @@ -17293,9 +17649,9 @@ "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" }, "node_modules/style-loader": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.3.tgz", - "integrity": "sha512-53BiGLXAcll9maCYtZi2RCQZKa8NQQai5C4horqKyRmHj9H7QmcUyucrH+4KW/gBQbXM2AsB0axoEcFZPlfPcw==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.4.tgz", + "integrity": "sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==", "engines": { "node": ">= 12.13.0" }, @@ -17980,12 +18336,12 @@ } }, "node_modules/url": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.1.tgz", - "integrity": "sha512-rWS3H04/+mzzJkv0eZ7vEDGiQbgquI1fGfOad6zKvgYQi1SzMmhl7c/DdRGxhaWrVH6z0qWITo8rpnxK/RfEhA==", + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.3.tgz", + "integrity": "sha512-6hxOLGfZASQK/cijlZnZJTq8OXAkt/3YGfQX45vvMYXpZoo8NdWZcY73K108Jf759lS1Bv/8wXnHDTSz17dSRw==", "dependencies": { "punycode": "^1.4.1", - "qs": "^6.11.0" + "qs": "^6.11.2" } }, "node_modules/url-loader": { @@ -18074,6 +18430,20 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==" }, + "node_modules/url/node_modules/qs": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.0.tgz", + "integrity": "sha512-trVZiI6RMOkO476zLGaBIzszOdFPnCCXHPG9kn0yuS1uz6xdVxPfZdB3vUig9pxPFDM9BRAgz/YUIVQ1/vuiUg==", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/use-sync-external-store": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", @@ -18197,9 +18567,9 @@ "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" }, "node_modules/webpack": { - "version": "5.90.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.1.tgz", - "integrity": "sha512-SstPdlAC5IvgFnhiRok8hqJo/+ArAbNv7rhU4fnWGHNVfN59HSQFaxZDSAL3IFG2YmqxuRs+IU33milSxbPlog==", + "version": "5.90.3", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.3.tgz", + "integrity": "sha512-h6uDYlWCctQRuXBs1oYpVe6sFcWedl0dpcVaTf/YF67J9bKvwJajFulMVSYKHrksMB3I/pIagRzDxwxkebuzKA==", "dependencies": { "@types/eslint-scope": "^3.7.3", "@types/estree": "^1.0.5", @@ -18243,9 +18613,9 @@ } }, "node_modules/webpack-dev-middleware": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", - "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", + "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", "dependencies": { "colorette": "^2.0.10", "memfs": "^3.4.3", diff --git a/package.json b/package.json index 0b2a64d67..671207e47 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "private": true, "dependencies": { "@augloop/types-core": "file:packages/types-core-2.16.189.tgz", - "@axe-core/webdriverjs": "4.8.4", + "@axe-core/webdriverjs": "4.8.5", "@azure/msal-browser": "3.10.0", "@babel/core": "7.23.3", "@babel/runtime": "7.23.8", @@ -24,28 +24,28 @@ "babel-loader": "9.1.3", "babel-preset-react-app": "10.0.1", "bfj": "8.0.0", - "bootstrap": "5.3.2", + "bootstrap": "5.3.3", "case-sensitive-paths-webpack-plugin": "2.4.0", "css-loader": "6.10.0", - "dotenv": "16.4.2", - "dotenv-expand": "10.0.0", + "dotenv": "16.4.5", + "dotenv-expand": "11.0.6", "eslint-config-react-app": "7.0.1", "eslint-plugin-react": "7.33.2", "eslint-webpack-plugin": "4.0.1", - "express": "4.18.2", + "express": "4.19.2", "expvariantassignmentsdk": "file:packages/expvariantassignmentsdk-1.0.0.tgz", "file-loader": "6.2.0", "fork-ts-checker-webpack-plugin": "9.0.2", - "fs-extra": "11.1.1", + "fs-extra": "11.2.0", "guid-typescript": "1.0.9", "isomorphic-fetch": "3.0.0", "localforage": "1.10.0", - "mini-css-extract-plugin": "2.8.0", + "mini-css-extract-plugin": "2.8.1", "monaco-editor": "0.30.1", "monaco-editor-webpack-plugin": "6.0.0", "office-ui-fabric-core": "11.1.0", "postcss-flexbugs-fixes": "5.0.2", - "postcss-loader": "7.3.3", + "postcss-loader": "8.1.1", "postcss-preset-env": "9.3.0", "re-resizable": "6.9.11", "react": "18.2.0", @@ -55,13 +55,13 @@ "redux": "4.2.1", "redux-thunk": "2.4.2", "resolve": "1.22.8", - "sass": "1.69.7", - "sass-loader": "13.3.2", - "style-loader": "3.3.3", + "sass": "1.72.0", + "sass-loader": "14.1.1", + "style-loader": "3.3.4", "typescript": "5.3.3", - "url": "0.11.1", + "url": "0.11.3", "url-loader": "4.1.1", - "webpack": "5.90.1", + "webpack": "5.90.3", "webpack-dev-server": "4.15.1", "webpack-manifest-plugin": "5.0.0", "workbox-webpack-plugin": "7.0.0" @@ -91,7 +91,7 @@ ], "devDependencies": { "@axe-core/playwright": "4.7.3", - "@playwright/test": "1.40.1", + "@playwright/test": "1.42.0", "@types/chromedriver": "81.0.1", "@types/isomorphic-fetch": "0.0.39", "@types/jest": "29.5.12", @@ -106,7 +106,7 @@ "@typescript-eslint/parser": "6.17.0", "acorn": "8.11.3", "babel-jest": "29.7.0", - "chromedriver": "119.0.1", + "chromedriver": "122.0.4", "eslint": "8.56.0", "html-webpack-plugin": "5.6.0", "jest": "29.7.0", @@ -119,7 +119,7 @@ "react-dev-utils": "12.0.1", "redux-logger": "3.0.6", "redux-mock-store": "1.5.4", - "selenium-webdriver": "4.17.0", + "selenium-webdriver": "4.18.1", "start-server-and-test": "2.0.3", "ts-jest": "29.1.2" }, diff --git a/src/modules/authentication/AuthenticationWrapper.ts b/src/modules/authentication/AuthenticationWrapper.ts index 5c74800f7..9d44ebba9 100644 --- a/src/modules/authentication/AuthenticationWrapper.ts +++ b/src/modules/authentication/AuthenticationWrapper.ts @@ -50,7 +50,7 @@ export class AuthenticationWrapper implements IAuthenticationWrapper { } public async logIn(sessionId = '', sampleQuery?: IQuery): Promise { - if(sampleQuery){ + if (sampleQuery) { this.sampleQuery = sampleQuery; this.performingStepUpAuth = true; } @@ -58,7 +58,7 @@ export class AuthenticationWrapper implements IAuthenticationWrapper { // eslint-disable-next-line no-useless-catch try { const authResult = await this.getAuthResult([], sessionId); - if(this.performingStepUpAuth && authResult){ + if (this.performingStepUpAuth && authResult) { this.claimsAvailable = true; } return authResult; @@ -180,7 +180,7 @@ export class AuthenticationWrapper implements IAuthenticationWrapper { private getClaims(): string | undefined { const account = this.getAccount(); - if(account && (this.sampleQuery.sampleUrl !== '')){ + if (account && (this.sampleQuery.sampleUrl !== '')) { const claimsChallenge = new ClaimsChallenge(this.sampleQuery, account); const storedClaims = claimsChallenge.getClaimsFromStorage(); return storedClaims ? window.atob(storedClaims) : undefined; From a84d7a4df9070a88d112e069d010de66bca8839c Mon Sep 17 00:00:00 2001 From: Charles Wahome Date: Mon, 29 Apr 2024 09:50:20 +0300 Subject: [PATCH 08/45] Feature: support text csv (#3123) * add validation for when app is loading * add text/csv to supported responses --- src/app/services/actions/query-action-creator-util.ts | 1 + .../services/context/validation-context/ValidationProvider.tsx | 3 +++ src/types/enums.ts | 1 + 3 files changed, 5 insertions(+) diff --git a/src/app/services/actions/query-action-creator-util.ts b/src/app/services/actions/query-action-creator-util.ts index b402fa659..6b60a2d15 100644 --- a/src/app/services/actions/query-action-creator-util.ts +++ b/src/app/services/actions/query-action-creator-util.ts @@ -236,6 +236,7 @@ export function parseResponse( return response.json(); case ContentType.XML: case ContentType.HTML: + case ContentType.TextCsv: case ContentType.TextPlain: return response.text(); diff --git a/src/app/services/context/validation-context/ValidationProvider.tsx b/src/app/services/context/validation-context/ValidationProvider.tsx index 1135f8d9d..a7e568bb0 100644 --- a/src/app/services/context/validation-context/ValidationProvider.tsx +++ b/src/app/services/context/validation-context/ValidationProvider.tsx @@ -34,6 +34,9 @@ export const ValidationProvider = ({ children }: ValidationProviderProps) => { }, [resources]) useEffect(() => { + if (!queryVersion || !query || Object.keys(resources.data).length === 0) { + return; + } if (version !== queryVersion && GRAPH_API_VERSIONS.includes(queryVersion) && resources.data[queryVersion].children!.length > 0) { setVersionedResources(resources.data[queryVersion].children!); diff --git a/src/types/enums.ts b/src/types/enums.ts index 0c3da176b..2b6d03a18 100644 --- a/src/types/enums.ts +++ b/src/types/enums.ts @@ -15,6 +15,7 @@ export enum ContentType { TextPlain = 'text/plain', HTML = 'text/html', BinaryResponse = 'application/octet-stream', + TextCsv = 'text/csv', } export enum AppTheme { From 38c3849ef352d03735cb92c7a7afba8f38da5fa2 Mon Sep 17 00:00:00 2001 From: Charles Wahome Date: Tue, 30 Apr 2024 16:00:39 +0300 Subject: [PATCH 09/45] Fix: malformed json error (#3107) * create json validator function * make message more readable * show response alongside warning --- .../actions/query-action-creator-util.ts | 10 ++- .../services/actions/query-action-creators.ts | 4 +- .../query-response/response/Response.tsx | 15 ++-- .../response/ResponseMessages.tsx | 78 ++++++++++++------- 4 files changed, 67 insertions(+), 40 deletions(-) diff --git a/src/app/services/actions/query-action-creator-util.ts b/src/app/services/actions/query-action-creator-util.ts index 6b60a2d15..fe7cf328a 100644 --- a/src/app/services/actions/query-action-creator-util.ts +++ b/src/app/services/actions/query-action-creator-util.ts @@ -221,6 +221,14 @@ export async function generateResponseDownloadUrl( } } +async function tryParseJson(textValue: string) { + try { + return JSON.parse(textValue); + } catch (error) { + return textValue; + } +} + export function parseResponse( response: any, respHeaders: any = {} @@ -233,7 +241,7 @@ export function parseResponse( const contentType = getContentType(response.headers); switch (contentType) { case ContentType.Json: - return response.json(); + return response.text().then(tryParseJson); case ContentType.XML: case ContentType.HTML: case ContentType.TextCsv: diff --git a/src/app/services/actions/query-action-creators.ts b/src/app/services/actions/query-action-creators.ts index 1701be8df..d0bc91a38 100644 --- a/src/app/services/actions/query-action-creators.ts +++ b/src/app/services/actions/query-action-creators.ts @@ -145,8 +145,8 @@ export function runQuery(query: IQuery) { }; if (error instanceof ClientError) { - status.status = 0; - status.statusText = `${error.name}: ${error.message}`; + status.status = error.message; + status.statusText = error.name; } if (queryResultsInCorsError(query.sampleUrl)) { diff --git a/src/app/views/query-response/response/Response.tsx b/src/app/views/query-response/response/Response.tsx index eed57fb7f..4c8fa7896 100644 --- a/src/app/views/query-response/response/Response.tsx +++ b/src/app/views/query-response/response/Response.tsx @@ -1,18 +1,18 @@ -import { useDispatch } from 'react-redux'; -import { AppDispatch, useAppSelector } from '../../../../store'; +import { useAppSelector } from '../../../../store'; import { getContentType } from '../../../services/actions/query-action-creator-util'; -import { convertVhToPx, getResponseEditorHeight, - getResponseHeight } from '../../common/dimensions/dimensions-adjustment'; +import { + convertVhToPx, getResponseEditorHeight, + getResponseHeight +} from '../../common/dimensions/dimensions-adjustment'; import ResponseDisplay from './ResponseDisplay'; import { ResponseMessages } from './ResponseMessages'; const Response = () => { - const { dimensions: { response }, graphResponse, responseAreaExpanded, sampleQuery, authToken, graphExplorerMode } = + const { dimensions: { response }, graphResponse, responseAreaExpanded} = useAppSelector((state) => state); const { body, headers } = graphResponse; - const dispatch: AppDispatch = useDispatch(); const defaultHeight = convertVhToPx(getResponseHeight(response.height, responseAreaExpanded), 220); const monacoHeight = getResponseEditorHeight(150); @@ -20,9 +20,10 @@ const Response = () => { const contentDownloadUrl = body?.contentDownloadUrl; const throwsCorsError = body?.throwsCorsError; const contentType = getContentType(headers); + return (
- {ResponseMessages(graphResponse, sampleQuery, authToken, graphExplorerMode, dispatch)} + {!contentDownloadUrl && !throwsCorsError && headers && { - if (responseBody[`@odata.${link}`]) { - data = { - link: responseBody[`@odata.${link}`], - name: link - }; - } - }); - } - return data; +function getOdataLinkFromResponseBody(responseBody: any): ODataLink | null { + const odataLinks = ['nextLink', 'deltaLink']; + let data = null; + if (responseBody) { + odataLinks.forEach(link => { + if (responseBody[`@odata.${link}`]) { + data = { + link: responseBody[`@odata.${link}`], + name: link + }; + } + }); } + return data; +} + +export const ResponseMessages = () => { + const dispatch: AppDispatch = useDispatch(); + const messageBars = []; + + const { graphResponse: { body, headers }, sampleQuery, authToken, graphExplorerMode + } = useAppSelector((state) => state); const [displayMessage, setDisplayMessage] = useState(true); + const tokenPresent = !!authToken.token; - const { body } = graphResponse; + const contentType = getContentType(headers); const odataLink = getOdataLinkFromResponseBody(body); const setQuery = () => { @@ -51,7 +53,7 @@ export function ResponseMessages( // Display link to step to next result if (odataLink) { - return ( + messageBars.push( {translateMessage('This response contains an @odata property.')}: @odata.{odataLink.name} setQuery()} underline> @@ -63,7 +65,7 @@ export function ResponseMessages( // Display link to download file response if (body?.contentDownloadUrl) { - return ( + messageBars.push(
{translateMessage('This response contains unviewable content')} @@ -77,7 +79,7 @@ export function ResponseMessages( // Show CORS compliance message if (body?.throwsCorsError) { - return ( + messageBars.push(
{translateMessage('Response content not available due to CORS policy')} @@ -90,7 +92,7 @@ export function ResponseMessages( } if (body && !tokenPresent && displayMessage && graphExplorerMode === Mode.Complete) { - return ( + messageBars.push(
); } -} + + if (contentType === 'application/json' && typeof body === 'string') { + messageBars.push( +
+ setDisplayMessage(false)} + dismissButtonAriaLabel={translateMessage('Close')} + > + {translateMessage('Malformed JSON body')} + +
+ ); + } + + return messageBars; +} \ No newline at end of file From d2fb7f5be9eef22bf151e020164c504a5986affd Mon Sep 17 00:00:00 2001 From: Charles Wahome Date: Wed, 15 May 2024 11:45:21 +0300 Subject: [PATCH 10/45] Chore: Update to 10.0.0 (#3147) * Bump version to 10.0.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3286864e9..9b69e05ca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "graph-explorer-v2", - "version": "9.8.0", + "version": "10.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "graph-explorer-v2", - "version": "9.8.0", + "version": "10.0.0", "dependencies": { "@augloop/types-core": "file:packages/types-core-2.16.189.tgz", "@axe-core/webdriverjs": "4.8.5", diff --git a/package.json b/package.json index 671207e47..29bbf1cd4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "graph-explorer-v2", - "version": "9.8.0", + "version": "10.0.0", "private": true, "dependencies": { "@augloop/types-core": "file:packages/types-core-2.16.189.tgz", From 5d4e8745ecd84cbf2c548e27539bb0a2d1405dac Mon Sep 17 00:00:00 2001 From: Charles Wahome Date: Wed, 15 May 2024 13:28:05 +0300 Subject: [PATCH 11/45] Chore: app migration with feature flags (#3144) --- .github/workflows/linter.yml | 1 + azure-pipelines.yml | 2 ++ src/app/services/variant-constants.ts | 3 +- src/app/services/variant-service.ts | 34 +++++++++---------- .../authentication/AuthenticationWrapper.ts | 29 +++++++++++++--- src/modules/authentication/msal-app.ts | 31 +++++++++++++++-- src/telemetry/event-types.ts | 1 + 7 files changed, 75 insertions(+), 26 deletions(-) diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 9d916999d..2268744ee 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -185,6 +185,7 @@ jobs: REACT_APP_NOMINATION_PERIOD: ${{secrets.REACT_APP_NOMINATION_PERIOD}} REACT_APP_COOLDOWN_PERIOD: ${{secrets.REACT_APP_COOLDOWN_PERIOD}} REACT_APP_USAGE_TIME: ${{secrets.REACT_APP_USAGE_TIME}} + REACT_APP_MIGRATION_PARAMETER: ${{secrets.REACT_APP_MIGRATION_PARAMETER}} CI: false id: builddeploy uses: Azure/static-web-apps-deploy@v0.0.1-preview diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 76089c13c..c56392968 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -190,6 +190,7 @@ extends: REACT_APP_NOMINATION_PERIOD: $(REACT_APP_NOMINATION_PERIOD) REACT_APP_COOLDOWN_PERIOD: $(REACT_APP_COOLDOWN_PERIOD) REACT_APP_USAGE_TIME: $(REACT_APP_USAGE_TIME) + REACT_APP_MIGRATION_PARAMETER: $(REACT_APP_MIGRATION_PARAMETER) displayName: "Build static assets for staging" - task: PowerShell@2 @@ -215,6 +216,7 @@ extends: REACT_APP_NOMINATION_PERIOD: $(REACT_APP_NOMINATION_PERIOD) REACT_APP_COOLDOWN_PERIOD: $(REACT_APP_COOLDOWN_PERIOD) REACT_APP_USAGE_TIME: $(REACT_APP_USAGE_TIME) + REACT_APP_MIGRATION_PARAMETER: $(REACT_APP_MIGRATION_PARAMETER) displayName: "Build static assets for prod" - task: PowerShell@2 diff --git a/src/app/services/variant-constants.ts b/src/app/services/variant-constants.ts index 1300b0cbc..7da98a710 100644 --- a/src/app/services/variant-constants.ts +++ b/src/app/services/variant-constants.ts @@ -1 +1,2 @@ -export const ALWAYSSHOWBUTTONS = 'alwaysShowButtons'; \ No newline at end of file +export const ALWAYSSHOWBUTTONS = 'alwaysShowButtons'; +export const SAFEROLLOUTACTIVE = 'safeRolloutActive'; \ No newline at end of file diff --git a/src/app/services/variant-service.ts b/src/app/services/variant-service.ts index 4adf0de47..c3f873db4 100644 --- a/src/app/services/variant-service.ts +++ b/src/app/services/variant-service.ts @@ -1,12 +1,12 @@ /* eslint-disable max-len */ -import { VariantAssignmentRequest } from 'expvariantassignmentsdk/src/interfaces/VariantAssignmentRequest'; -import {VariantAssignmentServiceClient} from 'expvariantassignmentsdk/src/contracts/VariantAssignmentServiceClient'; +import { SeverityLevel } from '@microsoft/applicationinsights-web'; import { VariantAssignmentClientSettings } from 'expvariantassignmentsdk/src/contracts/VariantAssignmentClientSettings'; +import { VariantAssignmentServiceClient } from 'expvariantassignmentsdk/src/contracts/VariantAssignmentServiceClient'; +import { VariantAssignmentRequest } from 'expvariantassignmentsdk/src/interfaces/VariantAssignmentRequest'; + import { errorTypes, telemetry } from '../../telemetry'; import { readFromLocalStorage, saveToLocalStorage } from '../utils/local-storage'; import { EXP_URL } from './graph-constants'; -import { SeverityLevel } from '@microsoft/applicationinsights-web'; - interface TasResponse { Id: string; @@ -15,6 +15,7 @@ interface TasResponse { interface Parameters { [key: string]: string | boolean | number; } + class VariantService { private endpoint = EXP_URL; @@ -25,21 +26,18 @@ class VariantService { const settings: VariantAssignmentClientSettings = { endpoint: this.endpoint }; this.createUser(); const request: VariantAssignmentRequest = - { - parameters: this.getParameters() - }; + { + parameters: this.getParameters() + }; const client = new VariantAssignmentServiceClient(settings); - const response = await client.getVariantAssignments(request); - Promise.resolve(response).then((r) => { - if (r){ - this.expResponse = r.featureVariables as TasResponse[] | null; - this.assignmentContext = r.assignmentContext; - } - }) - .catch((error) => { - telemetry.trackException(new Error(errorTypes.UNHANDLED_ERROR), SeverityLevel.Error, error); - }); + try { + const response = await client.getVariantAssignments(request); + this.expResponse = response.featureVariables as TasResponse[] | null; + this.assignmentContext = response.assignmentContext; + } catch (error) { + telemetry.trackException(new Error(errorTypes.UNHANDLED_ERROR), SeverityLevel.Error, error as object); + } } public createUser() { @@ -51,7 +49,7 @@ class VariantService { return this.assignmentContext; } - public async getFeatureVariables(namespace: string, flagname: string) { + public getFeatureVariables(namespace: string, flagname: string) { const defaultConfig = this.expResponse?.find(c => c.Id === namespace); return defaultConfig?.Parameters[flagname]; } diff --git a/src/modules/authentication/AuthenticationWrapper.ts b/src/modules/authentication/AuthenticationWrapper.ts index 9d44ebba9..944a9e43d 100644 --- a/src/modules/authentication/AuthenticationWrapper.ts +++ b/src/modules/authentication/AuthenticationWrapper.ts @@ -11,13 +11,15 @@ import { DEFAULT_USER_SCOPES, HOME_ACCOUNT_KEY } from '../../app/services/graph-constants'; -import { signInAuthError } from './authentication-error-hints'; +import { SAFEROLLOUTACTIVE } from '../../app/services/variant-constants'; +import variantService from '../../app/services/variant-service'; import { geLocale } from '../../appLocale'; +import { IQuery } from '../../types/query-runner'; +import { ClaimsChallenge } from './ClaimsChallenge'; import { getCurrentUri } from './authUtils'; +import { signInAuthError } from './authentication-error-hints'; import IAuthenticationWrapper from './interfaces/IAuthenticationWrapper'; import { msalApplication } from './msal-app'; -import { IQuery } from '../../types/query-runner'; -import { ClaimsChallenge } from './ClaimsChallenge'; const defaultScopes = DEFAULT_USER_SCOPES.split(' '); @@ -73,7 +75,7 @@ export class AuthenticationWrapper implements IAuthenticationWrapper { authority: this.getAuthority(), prompt: 'select_account', redirectUri: getCurrentUri(), - extraQueryParameters: { mkt: geLocale } + extraQueryParameters: getExtraQueryParameters() }; try { const result = await msalApplication.loginPopup(popUpRequest); @@ -205,7 +207,7 @@ export class AuthenticationWrapper implements IAuthenticationWrapper { authority: this.getAuthority(), prompt: 'select_account', redirectUri: getCurrentUri(), - extraQueryParameters: { mkt: geLocale }, + extraQueryParameters: getExtraQueryParameters(), claims: this.getClaims() }; @@ -297,3 +299,20 @@ export class AuthenticationWrapper implements IAuthenticationWrapper { window.sessionStorage.clear(); } } + +function getExtraQueryParameters(): { [key: string]: string } { + const params: { [key: string]: string } = { + mkt: geLocale + }; + getSafeRolloutParameter(params); + return params; +} + +function getSafeRolloutParameter(params: { [key: string]: string; }) { + const safeRolloutActive = variantService.getFeatureVariables('default', SAFEROLLOUTACTIVE); + const migrationParam = process.env.REACT_APP_MIGRATION_PARAMETER; + if (safeRolloutActive && migrationParam) { + params.safe_rollout = migrationParam; + } +} + diff --git a/src/modules/authentication/msal-app.ts b/src/modules/authentication/msal-app.ts index 70a31ba26..460ea0900 100644 --- a/src/modules/authentication/msal-app.ts +++ b/src/modules/authentication/msal-app.ts @@ -1,4 +1,5 @@ -import { Configuration, PublicClientApplication } from '@azure/msal-browser'; +import { Configuration, LogLevel, PublicClientApplication } from '@azure/msal-browser'; +import { eventTypes, telemetry } from '../../telemetry'; function getClientIdFromWindow() { return (window as any).ClientId; @@ -19,10 +20,36 @@ export const configuration: Configuration = { cacheLocation: 'localStorage', storeAuthStateInCookie: true, claimsBasedCachingEnabled: true + }, + system: { + loggerOptions: { + logLevel: LogLevel.Verbose, + loggerCallback: (level, message, containsPii) => { + if (containsPii) { + return; + } + telemetry.trackEvent(eventTypes.AUTH_REQUEST_EVENT, { message, level }); + switch (level) { + case LogLevel.Error: + console.error('[MSAL]', message); + return; + case LogLevel.Info: + console.info('[MSAL]', message); + return; + case LogLevel.Verbose: + console.debug('[MSAL]', message); + return; + case LogLevel.Warning: + console.warn('[MSAL]', message); + return; + } + }, + piiLoggingEnabled: false + } } }; const msalApplication = new PublicClientApplication(configuration); msalApplication.initialize(); -export{ msalApplication }; +export { msalApplication }; diff --git a/src/telemetry/event-types.ts b/src/telemetry/event-types.ts index 78e363ee7..6ca0862e1 100644 --- a/src/telemetry/event-types.ts +++ b/src/telemetry/event-types.ts @@ -7,3 +7,4 @@ export const LISTITEM_CLICK_EVENT = 'LISTITEM_CLICK_EVENT'; export const DROPDOWN_CHANGE_EVENT = 'DROPDOWN_CHANGE_EVENT'; export const WINDOW_OPEN_EVENT = 'WINDOW_OPEN_EVENT'; export const KEYBOARD_COPY_EVENT = 'KEYBOARD_COPY_EVENT'; +export const AUTH_REQUEST_EVENT = 'AUTH_REQUEST_EVENT'; From 7695bcaff4a975505213b6a6129e3b37d96f796b Mon Sep 17 00:00:00 2001 From: Charles Wahome Date: Mon, 27 May 2024 12:20:23 +0300 Subject: [PATCH 12/45] Chore: Update 10.1.0 (#3149) * Bump version to 10.1.0 --- package-lock.json | 64 +++++++++++++++++++++++------------------------ package.json | 2 +- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9b69e05ca..b7f0e5624 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "graph-explorer-v2", - "version": "10.0.0", + "version": "10.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "graph-explorer-v2", - "version": "10.0.0", + "version": "10.1.0", "dependencies": { "@augloop/types-core": "file:packages/types-core-2.16.189.tgz", "@axe-core/webdriverjs": "4.8.5", @@ -6239,12 +6239,12 @@ } }, "node_modules/axios": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz", - "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==", + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", + "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", "dev": true, "dependencies": { - "follow-redirects": "^1.15.4", + "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } @@ -9892,9 +9892,9 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==" }, "node_modules/follow-redirects": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", - "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "funding": [ { "type": "individual", @@ -10309,7 +10309,7 @@ "basic-ftp": "^5.0.2", "data-uri-to-buffer": "^6.0.2", "debug": "^4.3.4", - "fs-extra": "11.2.0" + "fs-extra": "^11.2.0" }, "engines": { "node": ">= 14" @@ -14568,9 +14568,9 @@ } }, "node_modules/pac-proxy-agent/node_modules/agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, "dependencies": { "debug": "^4.3.4" @@ -15795,9 +15795,9 @@ } }, "node_modules/proxy-agent/node_modules/agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, "dependencies": { "debug": "^4.3.4" @@ -17255,9 +17255,9 @@ } }, "node_modules/socks": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.1.tgz", - "integrity": "sha512-B6w7tkwNid7ToxjZ08rQMT8M9BJAf8DKx8Ft4NivzH0zBUfd6jldGcisJn/RLgxcX3FPNDdNQCUEMMT79b+oCQ==", + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", "dev": true, "dependencies": { "ip-address": "^9.0.5", @@ -17269,12 +17269,12 @@ } }, "node_modules/socks-proxy-agent": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.2.tgz", - "integrity": "sha512-8zuqoLv1aP/66PHF5TqwJ7Czm3Yv32urJQHrVyhD7mmA6d61Zv8cIXQYPTWwmg6qlupnPvs/QKDmfa4P/qct2g==", + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.3.tgz", + "integrity": "sha512-VNegTZKhuGq5vSD6XNKlbqWhyt/40CgoEw8XxD6dhnm8Jq9IEa3nIa4HwnM8XOqU0CdB0BwWVXusqiFXfHB3+A==", "dev": true, "dependencies": { - "agent-base": "^7.0.2", + "agent-base": "^7.1.1", "debug": "^4.3.4", "socks": "^2.7.1" }, @@ -17283,9 +17283,9 @@ } }, "node_modules/socks-proxy-agent/node_modules/agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, "dependencies": { "debug": "^4.3.4" @@ -18431,9 +18431,9 @@ "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==" }, "node_modules/url/node_modules/qs": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.0.tgz", - "integrity": "sha512-trVZiI6RMOkO476zLGaBIzszOdFPnCCXHPG9kn0yuS1uz6xdVxPfZdB3vUig9pxPFDM9BRAgz/YUIVQ1/vuiUg==", + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.1.tgz", + "integrity": "sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==", "dependencies": { "side-channel": "^1.0.6" }, @@ -18613,9 +18613,9 @@ } }, "node_modules/webpack-dev-middleware": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", - "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", + "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", "dependencies": { "colorette": "^2.0.10", "memfs": "^3.4.3", diff --git a/package.json b/package.json index 29bbf1cd4..65bd1a733 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "graph-explorer-v2", - "version": "10.0.0", + "version": "10.1.0", "private": true, "dependencies": { "@augloop/types-core": "file:packages/types-core-2.16.189.tgz", From 499a1073666f7148042eaed26295d8656bdc7f45 Mon Sep 17 00:00:00 2001 From: Musale Martin Date: Fri, 7 Jun 2024 12:44:02 +0300 Subject: [PATCH 13/45] refactor: use strong types in authentication module (#3164) Signed-off-by: Musale Martin --- .../authentication/AuthenticationWrapper.ts | 48 +++++++++---------- src/modules/authentication/msal-app.ts | 5 +- src/types/index.d.ts | 8 ++++ 3 files changed, 34 insertions(+), 27 deletions(-) create mode 100644 src/types/index.d.ts diff --git a/src/modules/authentication/AuthenticationWrapper.ts b/src/modules/authentication/AuthenticationWrapper.ts index 944a9e43d..77f83ef54 100644 --- a/src/modules/authentication/AuthenticationWrapper.ts +++ b/src/modules/authentication/AuthenticationWrapper.ts @@ -1,6 +1,7 @@ import { AccountInfo, AuthenticationResult, + BrowserAuthError, InteractionRequiredAuthError, PopupRequest, SilentRequest @@ -45,8 +46,8 @@ export class AuthenticationWrapper implements IAuthenticationWrapper { public getSessionId(): string | null { const account = this.getAccount(); if (account) { - const idTokenClaims: any = account?.idTokenClaims; - return idTokenClaims?.sid; + const idTokenClaims = account?.idTokenClaims; + return idTokenClaims?.sid ?? null; } return null; } @@ -81,10 +82,12 @@ export class AuthenticationWrapper implements IAuthenticationWrapper { const result = await msalApplication.loginPopup(popUpRequest); this.storeHomeAccountId(result.account!); return result; - } catch (error: any) { - const { errorCode } = error; - if (errorCode === 'interaction_in_progress') { - this.eraseInteractionInProgressCookie(); + } catch (error: unknown) { + if (error instanceof BrowserAuthError){ + const { errorCode } = error; + if (errorCode === 'interaction_in_progress') { + this.eraseInteractionInProgressCookie(); + } } throw error; } @@ -166,17 +169,14 @@ export class AuthenticationWrapper implements IAuthenticationWrapper { const result = await msalApplication.acquireTokenSilent(silentRequest); this.storeHomeAccountId(result.account!); return result; - } catch (error: any) { + } catch (error: unknown) { if (error instanceof InteractionRequiredAuthError || !this.getAccount()) { return this.loginWithInteraction(scopes.length > 0 ? scopes : defaultScopes, sessionId); - - } else if (signInAuthError(error)) { - this.deleteHomeAccountId(); - throw error; } - else { - throw error; + if (typeof error === 'string' && signInAuthError(error as string)) { + this.deleteHomeAccountId(); } + throw error; } } @@ -225,22 +225,20 @@ export class AuthenticationWrapper implements IAuthenticationWrapper { const result = await msalApplication.loginPopup(popUpRequest); this.storeHomeAccountId(result.account!); return result; - } catch (error: any) { - const { errorCode } = error; - if (signInAuthError(errorCode) && !this.consentingToNewScopes && (errorCode && errorCode !== 'user_cancelled')) { - this.clearSession(); - if (errorCode === 'interaction_in_progress') { - this.eraseInteractionInProgressCookie(); + } catch (error: unknown) { + if(error instanceof BrowserAuthError){ + const { errorCode } = error; + const valid = !this.consentingToNewScopes && errorCode !== 'user_cancelled'; + if (signInAuthError(errorCode) && valid) { + this.clearSession(); + if (errorCode === 'interaction_in_progress') { + this.eraseInteractionInProgressCookie(); + } } - throw error; - } - else { - throw error; } - + throw error; } } - private storeHomeAccountId(account: AccountInfo): void { localStorage.setItem(HOME_ACCOUNT_KEY, account.homeAccountId); } diff --git a/src/modules/authentication/msal-app.ts b/src/modules/authentication/msal-app.ts index 460ea0900..046f5bc5c 100644 --- a/src/modules/authentication/msal-app.ts +++ b/src/modules/authentication/msal-app.ts @@ -1,12 +1,13 @@ +/* eslint-disable no-console */ import { Configuration, LogLevel, PublicClientApplication } from '@azure/msal-browser'; import { eventTypes, telemetry } from '../../telemetry'; function getClientIdFromWindow() { - return (window as any).ClientId; + return window?.ClientId ?? ''; } function getClientIdFromEnv() { - return process.env.REACT_APP_CLIENT_ID; + return process.env?.REACT_APP_CLIENT_ID ?? ''; } const windowHasClientId = getClientIdFromWindow(); diff --git a/src/types/index.d.ts b/src/types/index.d.ts new file mode 100644 index 000000000..581189bba --- /dev/null +++ b/src/types/index.d.ts @@ -0,0 +1,8 @@ +export {}; +declare global { + interface Window { + ClientId: string | undefined + } +} + +window.ClientId = window.ClientId || undefined; From 42599d78302afe4fab83286b668d9a2508dc01a2 Mon Sep 17 00:00:00 2001 From: Charles Wahome Date: Fri, 7 Jun 2024 12:51:50 +0300 Subject: [PATCH 14/45] Chore: Dependabot upgrades - April (#3124) --- config/webpackDevServer.config.js | 10 +- package-lock.json | 2692 ++++++++++------- package.json | 38 +- .../context/popups-context/PopupsContext.tsx | 11 +- 4 files changed, 1697 insertions(+), 1054 deletions(-) diff --git a/config/webpackDevServer.config.js b/config/webpackDevServer.config.js index 12e45ceda..202e588d1 100644 --- a/config/webpackDevServer.config.js +++ b/config/webpackDevServer.config.js @@ -89,7 +89,6 @@ module.exports = function (proxy, allowedHost) { // src/node_modules is not ignored to support absolute imports // https://github.com/facebook/create-react-app/issues/1065 // Enable HTTPS if the HTTPS environment variable is set to 'true' - https: protocol === 'https', host, historyApiFallback: { // Paths with dots should still use the history fallback. @@ -97,23 +96,24 @@ module.exports = function (proxy, allowedHost) { disableDotRule: true }, proxy, - onBeforeSetupMiddleware(devServer) { + setupMiddlewares: (middlewares, devServer) => { if (fs.existsSync(paths.proxySetup)) { // This registers user provided middleware for proxy reasons require(paths.proxySetup)(devServer.app); } // This lets us fetch source contents from webpack for the error overlay - devServer.app.use(evalSourceMapMiddleware(devServer)); + middlewares.push(evalSourceMapMiddleware(devServer)); // This lets us open files from the runtime error overlay. - devServer.app.use(errorOverlayMiddleware()); + middlewares.push(errorOverlayMiddleware()); // This service worker file is effectively a 'no-op' that will reset any // previous service worker registered for the same host:port combination. // We do this in development to avoid hitting the production cache if // it used the same host and port. // https://github.com/facebook/create-react-app/issues/2272#issuecomment-302832432 - devServer.app.use(noopServiceWorkerMiddleware('/')); + middlewares.push(noopServiceWorkerMiddleware('/')); + return middlewares; } }; }; diff --git a/package-lock.json b/package-lock.json index b7f0e5624..bee176661 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,17 +10,17 @@ "dependencies": { "@augloop/types-core": "file:packages/types-core-2.16.189.tgz", "@axe-core/webdriverjs": "4.8.5", - "@azure/msal-browser": "3.10.0", - "@babel/core": "7.23.3", - "@babel/runtime": "7.23.8", - "@fluentui/react": "8.112.5", - "@fluentui/react-icons-mdl2": "1.3.52", - "@microsoft/applicationinsights-react-js": "17.0.3", - "@microsoft/applicationinsights-web": "3.0.5", + "@azure/msal-browser": "3.14.0", + "@babel/core": "7.24.6", + "@babel/runtime": "7.24.5", + "@fluentui/react": "8.117.7", + "@fluentui/react-icons-mdl2": "1.3.63", + "@microsoft/applicationinsights-react-js": "17.2.0", + "@microsoft/applicationinsights-web": "3.2.1", "@microsoft/microsoft-graph-client": "3.0.7", "@monaco-editor/react": "4.6.0", "@ms-ofb/officebrowserfeedbacknpm": "file:packages/officebrowserfeedbacknpm-1.6.6.tgz", - "adaptive-expressions": "4.21.4", + "adaptive-expressions": "4.22.2", "adaptivecards": "3.0.2", "adaptivecards-templating": "2.3.1", "ajv": "8.12.0", @@ -36,7 +36,7 @@ "dotenv-expand": "11.0.6", "eslint-config-react-app": "7.0.1", "eslint-plugin-react": "7.33.2", - "eslint-webpack-plugin": "4.0.1", + "eslint-webpack-plugin": "4.1.0", "express": "4.19.2", "expvariantassignmentsdk": "file:packages/expvariantassignmentsdk-1.0.0.tgz", "file-loader": "6.2.0", @@ -45,13 +45,13 @@ "guid-typescript": "1.0.9", "isomorphic-fetch": "3.0.0", "localforage": "1.10.0", - "mini-css-extract-plugin": "2.8.1", + "mini-css-extract-plugin": "2.9.0", "monaco-editor": "0.30.1", "monaco-editor-webpack-plugin": "6.0.0", "office-ui-fabric-core": "11.1.0", "postcss-flexbugs-fixes": "5.0.2", "postcss-loader": "8.1.1", - "postcss-preset-env": "9.3.0", + "postcss-preset-env": "9.5.13", "re-resizable": "6.9.11", "react": "18.2.0", "react-app-polyfill": "3.0.0", @@ -61,19 +61,19 @@ "redux-thunk": "2.4.2", "resolve": "1.22.8", "sass": "1.72.0", - "sass-loader": "14.1.1", - "style-loader": "3.3.4", - "typescript": "5.3.3", + "sass-loader": "14.2.1", + "style-loader": "4.0.0", + "typescript": "5.4.5", "url": "0.11.3", "url-loader": "4.1.1", - "webpack": "5.90.3", - "webpack-dev-server": "4.15.1", + "webpack": "5.91.0", + "webpack-dev-server": "5.0.4", "webpack-manifest-plugin": "5.0.0", "workbox-webpack-plugin": "7.0.0" }, "devDependencies": { "@axe-core/playwright": "4.7.3", - "@playwright/test": "1.42.0", + "@playwright/test": "1.43.1", "@types/chromedriver": "81.0.1", "@types/isomorphic-fetch": "0.0.39", "@types/jest": "29.5.12", @@ -84,11 +84,11 @@ "@types/redux-logger": "3.0.11", "@types/redux-mock-store": "1.0.3", "@types/selenium-webdriver": "4.1.21", - "@typescript-eslint/eslint-plugin": "6.20.0", + "@typescript-eslint/eslint-plugin": "7.0.0", "@typescript-eslint/parser": "6.17.0", "acorn": "8.11.3", "babel-jest": "29.7.0", - "chromedriver": "122.0.4", + "chromedriver": "124.0.1", "eslint": "8.56.0", "html-webpack-plugin": "5.6.0", "jest": "29.7.0", @@ -172,59 +172,59 @@ } }, "node_modules/@azure/msal-browser": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-3.10.0.tgz", - "integrity": "sha512-mnmi8dCXVNZI+AGRq0jKQ3YiodlIC4W9npr6FCB9WN6NQT+6rq+cIlxgUb//BjLyzKsnYo+i4LROGeMyU+6v1A==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-3.14.0.tgz", + "integrity": "sha512-Un85LhOoecJ3HDTS3Uv3UWnXC9/43ZSO+Kc+anSqpZvcEt58SiO/3DuVCAe1A3I5UIBYJNMgTmZPGXQ0MVYrwA==", "dependencies": { - "@azure/msal-common": "14.7.1" + "@azure/msal-common": "14.10.0" }, "engines": { "node": ">=0.8.0" } }, "node_modules/@azure/msal-common": { - "version": "14.7.1", - "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-14.7.1.tgz", - "integrity": "sha512-v96btzjM7KrAu4NSEdOkhQSTGOuNUIIsUdB8wlyB9cdgl5KqEKnTonHUZ8+khvZ6Ap542FCErbnTyDWl8lZ2rA==", + "version": "14.10.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-14.10.0.tgz", + "integrity": "sha512-Zk6DPDz7e1wPgLoLgAp0349Yay9RvcjPM5We/ehuenDNsz/t9QEFI7tRoHpp/e47I4p20XE3FiDlhKwAo3utDA==", "engines": { "node": ">=0.8.0" } }, "node_modules/@babel/code-frame": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", - "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.6.tgz", + "integrity": "sha512-ZJhac6FkEd1yhG2AHOmfcXG4ceoLltoCVJjN5XsWN9BifBQr+cHJbWi0h68HZuSORq+3WtJ2z0hwF2NG1b5kcA==", "dependencies": { - "@babel/highlight": "^7.22.13", - "chalk": "^2.4.2" + "@babel/highlight": "^7.24.6", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.20.tgz", - "integrity": "sha512-BQYjKbpXjoXwFW5jGqiizJQQT/aC7pFm9Ok1OWssonuguICi264lbgMzRp2ZMmRSlfkX6DsWDDcsrctK8Rwfiw==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.6.tgz", + "integrity": "sha512-aC2DGhBq5eEdyXWqrDInSqQjO0k8xtPRf5YylULqx8MCd6jBtzqfta/3ETMRpuKIc5hyswfO80ObyA1MvkCcUQ==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.3.tgz", - "integrity": "sha512-Jg+msLuNuCJDyBvFv5+OKOUjWMZgd85bKjbICd3zWrKAo+bJ49HJufi7CQE0q0uR8NGyO6xkCACScNqyjHSZew==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.6.tgz", + "integrity": "sha512-qAHSfAdVyFmIvl0VHELib8xar7ONuSHrE2hLnsaWkYNTI68dmi1x8GYDhJjMI/e7XWal9QBlZkwbOnkcw7Z8gQ==", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.3", - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.23.2", - "@babel/parser": "^7.23.3", - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.3", - "@babel/types": "^7.23.3", + "@babel/code-frame": "^7.24.6", + "@babel/generator": "^7.24.6", + "@babel/helper-compilation-targets": "^7.24.6", + "@babel/helper-module-transforms": "^7.24.6", + "@babel/helpers": "^7.24.6", + "@babel/parser": "^7.24.6", + "@babel/template": "^7.24.6", + "@babel/traverse": "^7.24.6", + "@babel/types": "^7.24.6", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -273,13 +273,13 @@ } }, "node_modules/@babel/generator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.3.tgz", - "integrity": "sha512-keeZWAV4LU3tW0qRi19HRpabC/ilM0HRBBzf9/k8FFiG4KVpiv0FIy4hHfLfFQZNhziCTPTmd59zoyv6DNISzg==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.6.tgz", + "integrity": "sha512-S7m4eNa6YAPJRHmKsLHIDJhNAGNKoWNiWefz1MBbpnt8g9lvMDl1hir4P9bo/57bQEmuwEhnRU/AMWsD0G/Fbg==", "dependencies": { - "@babel/types": "^7.23.3", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", + "@babel/types": "^7.24.6", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" }, "engines": { @@ -310,13 +310,13 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", - "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.6.tgz", + "integrity": "sha512-VZQ57UsDGlX/5fFA7GkVPplZhHsVc+vuErWgdOiysI9Ksnw0Pbbd6pnPiR/mmJyKHgyIW0c7KT32gmhiF+cirg==", "dependencies": { - "@babel/compat-data": "^7.22.9", - "@babel/helper-validator-option": "^7.22.15", - "browserslist": "^4.21.9", + "@babel/compat-data": "^7.24.6", + "@babel/helper-validator-option": "^7.24.6", + "browserslist": "^4.22.2", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -394,9 +394,9 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.6.tgz", + "integrity": "sha512-Y50Cg3k0LKLMjxdPjIl40SdJgMB85iXn27Vk/qbHZCFx/o5XO3PSnpi675h1KEmmDb6OFArfd5SCQEQ5Q4H88g==", "engines": { "node": ">=6.9.0" } @@ -413,23 +413,23 @@ } }, "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.6.tgz", + "integrity": "sha512-xpeLqeeRkbxhnYimfr2PC+iA0Q7ljX/d1eZ9/inYbmfG2jpl8Lu3DyXvpOAnrS5kxkfOWJjioIMQsaMBXFI05w==", "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" + "@babel/template": "^7.24.6", + "@babel/types": "^7.24.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.6.tgz", + "integrity": "sha512-SF/EMrC3OD7dSta1bLJIlrsVxwtd0UpjRJqLno6125epQMJ/kyFmpTT4pbvPbdQHzCHg+biQ7Syo8lnDtbR+uA==", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.6" }, "engines": { "node": ">=6.9.0" @@ -447,26 +447,26 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", - "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.6.tgz", + "integrity": "sha512-a26dmxFJBF62rRO9mmpgrfTLsAuyHk4e1hKTUkD/fcMfynt8gvEKwQPQDVxWhca8dHoDck+55DFt42zV0QMw5g==", "dependencies": { - "@babel/types": "^7.22.15" + "@babel/types": "^7.24.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", - "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.6.tgz", + "integrity": "sha512-Y/YMPm83mV2HJTbX1Qh2sjgjqcacvOlhbzdCCsSlblOKjSYmQqEbO6rUniWQyRo9ncyfjT8hnUjlG06RXDEmcA==", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.20" + "@babel/helper-environment-visitor": "^7.24.6", + "@babel/helper-module-imports": "^7.24.6", + "@babel/helper-simple-access": "^7.24.6", + "@babel/helper-split-export-declaration": "^7.24.6", + "@babel/helper-validator-identifier": "^7.24.6" }, "engines": { "node": ">=6.9.0" @@ -523,11 +523,11 @@ } }, "node_modules/@babel/helper-simple-access": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.6.tgz", + "integrity": "sha512-nZzcMMD4ZhmB35MOOzQuiGO5RzL6tJbsT37Zx8M5L/i9KSrukGXWTjLe1knIbb/RmxoJE9GON9soq0c0VEMM5g==", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.6" }, "engines": { "node": ">=6.9.0" @@ -545,36 +545,36 @@ } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.6.tgz", + "integrity": "sha512-CvLSkwXGWnYlF9+J3iZUvwgAxKiYzK3BWuo+mLzD/MDGOZDj7Gq8+hqaOkMxmJwmlv0iu86uH5fdADd9Hxkymw==", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.6.tgz", + "integrity": "sha512-WdJjwMEkmBicq5T9fm/cHND3+UlFa2Yj8ALLgmoSQAJZysYbBjw+azChSGPN4DSPLXOcooGRvDwZWMcF/mLO2Q==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.6.tgz", + "integrity": "sha512-4yA7s865JHaqUdRbnaxarZREuPTHrjpDT+pXoAZ1yhyo6uFnIEpS8VMu16siFOHDpZNKYv5BObhsB//ycbICyw==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz", - "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.6.tgz", + "integrity": "sha512-Jktc8KkF3zIkePb48QO+IapbXlSapOW9S+ogZZkcO6bABgYAxtZcjZ/O005111YLf+j4M84uEgwYoidDkXbCkQ==", "engines": { "node": ">=6.9.0" } @@ -594,35 +594,35 @@ } }, "node_modules/@babel/helpers": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz", - "integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.6.tgz", + "integrity": "sha512-V2PI+NqnyFu1i0GyTd/O/cTpxzQCYioSkUIRmgo7gFEHKKCg5w46+r/A6WeUR1+P3TeQ49dspGPNd/E3n9AnnA==", "dependencies": { - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.2", - "@babel/types": "^7.23.0" + "@babel/template": "^7.24.6", + "@babel/types": "^7.24.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", - "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.6.tgz", + "integrity": "sha512-2YnuOp4HAk2BsBrJJvYCbItHx0zWscI1C3zgWkz+wDyD9I7GIVrfnLyrR4Y1VR+7p+chAEcrgRQYZAGIKMV7vQ==", "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-validator-identifier": "^7.24.6", "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.3.tgz", - "integrity": "sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.6.tgz", + "integrity": "sha512-eNZXdfU35nJC2h24RznROuOpO94h6x8sg9ju0tT9biNtLZ2vuP8SduLqqV+/8+cebSLV9SJEAN5Z3zQbJG/M+Q==", "bin": { "parser": "bin/babel-parser.js" }, @@ -1894,9 +1894,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.23.8", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.8.tgz", - "integrity": "sha512-Y7KbAP984rn1VGMbGqKmBLio9V7y5Je9GvU4rQPCPinCyNfUcToxIXl06d59URp/F3LwinvODxab5N/G6qggkw==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.5.tgz", + "integrity": "sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -1922,32 +1922,32 @@ "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" }, "node_modules/@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.6.tgz", + "integrity": "sha512-3vgazJlLwNXi9jhrR1ef8qiB65L1RK90+lEQwv4OxveHnqC3BfmnHdgySwRLzf6akhlOYenT+b7AfWq+a//AHw==", "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" + "@babel/code-frame": "^7.24.6", + "@babel/parser": "^7.24.6", + "@babel/types": "^7.24.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.3.tgz", - "integrity": "sha512-+K0yF1/9yR0oHdE0StHuEj3uTPzwwbrLGfNOndVJVV2TqA5+j3oljJUb4nmB954FLGjNem976+B+eDuLIjesiQ==", - "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.3", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.3", - "@babel/types": "^7.23.3", - "debug": "^4.1.0", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.6.tgz", + "integrity": "sha512-OsNjaJwT9Zn8ozxcfoBc+RaHdj3gFmCmYoQLUII1o6ZrUwku0BMg80FoOTPx+Gi6XhcQxAYE4xyjPTo4SxEQqw==", + "dependencies": { + "@babel/code-frame": "^7.24.6", + "@babel/generator": "^7.24.6", + "@babel/helper-environment-visitor": "^7.24.6", + "@babel/helper-function-name": "^7.24.6", + "@babel/helper-hoist-variables": "^7.24.6", + "@babel/helper-split-export-declaration": "^7.24.6", + "@babel/parser": "^7.24.6", + "@babel/types": "^7.24.6", + "debug": "^4.3.1", "globals": "^11.1.0" }, "engines": { @@ -1955,12 +1955,12 @@ } }, "node_modules/@babel/types": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.3.tgz", - "integrity": "sha512-OZnvoH2l8PK5eUvEcUyCt/sXgr/h+UWpVuBbOljwcrAgUl6lpchoQ++PHGyQy1AtYnVA6CEq3y5xeEI10brpXw==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.6.tgz", + "integrity": "sha512-WaMsgi6Q8zMgMth93GvWPXkhAIEobfsIkLTacoVZoK1J0CevIPGYY2Vo5YvJGqyHqXM6P4ppOYGsIRU8MM9pFQ==", "dependencies": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-string-parser": "^7.24.6", + "@babel/helper-validator-identifier": "^7.24.6", "to-fast-properties": "^2.0.0" }, "engines": { @@ -1974,9 +1974,9 @@ "dev": true }, "node_modules/@csstools/cascade-layer-name-parser": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-1.0.5.tgz", - "integrity": "sha512-v/5ODKNBMfBl0us/WQjlfsvSlYxfZLhNMVIsuCPib2ulTwGKYbKJbwqw671+qH9Y4wvWVnu7LBChvml/wBKjFg==", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-1.0.11.tgz", + "integrity": "sha512-yhsonEAhaWRQvHFYhSzOUobH2Ev++fMci+ppFRagw0qVSPlcPV4FnNmlwpM/b2BM10ZeMRkVV4So6YRswD0O0w==", "funding": [ { "type": "github", @@ -1991,14 +1991,14 @@ "node": "^14 || ^16 || >=18" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^2.3.2", - "@csstools/css-tokenizer": "^2.2.1" + "@csstools/css-parser-algorithms": "^2.6.3", + "@csstools/css-tokenizer": "^2.3.1" } }, "node_modules/@csstools/color-helpers": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-3.0.2.tgz", - "integrity": "sha512-NMVs/l7Y9eIKL5XjbCHEgGcG8LOUT2qVcRjX6EzkCdlvftHVKr2tHIPzHavfrULRZ5Q2gxrJ9f44dAlj6fX97Q==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-4.2.0.tgz", + "integrity": "sha512-hJJrSBzbfGxUsaR6X4Bzd/FLx0F1ulKnR5ljY9AiXCtsR+H+zSWQDFWlKES1BRaVZTDHLpIIHS9K2o0h+JLlrg==", "funding": [ { "type": "github", @@ -2014,9 +2014,9 @@ } }, "node_modules/@csstools/css-calc": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-1.1.4.tgz", - "integrity": "sha512-ZV1TSmToiNcQL1P3hfzlzZzA02mmVkVmXGaUDUqpYUG84PmLhVSZpKX+KfxAuOcK7de04UXSQPBrAvaya6iiGg==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-1.2.2.tgz", + "integrity": "sha512-0owrl7AruDRKAxoSIW8XzJdz7GnuW3AOj4rYLfmXsoKIX2ZZzttzGXoiC8n8V08X7wIBlEWWVB4C8fAN18+I6Q==", "funding": [ { "type": "github", @@ -2031,14 +2031,14 @@ "node": "^14 || ^16 || >=18" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^2.3.2", - "@csstools/css-tokenizer": "^2.2.1" + "@csstools/css-parser-algorithms": "^2.6.3", + "@csstools/css-tokenizer": "^2.3.1" } }, "node_modules/@csstools/css-color-parser": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-1.4.0.tgz", - "integrity": "sha512-SlGd8E6ron24JYQPQAIzu5tvmWi1H4sDKTdA7UDnwF45oJv7AVESbOlOO1YjfBhrQFuvLWUgKiOY9DwGoAxwTA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-2.0.2.tgz", + "integrity": "sha512-Agx2YmxTcZ7TfB7KNZQ+iekaxbWSdblvtA35aTwE3KfuYyjOlCg3P4KGGdQF/cjm1pHWVSBo5duF/BRfZ8s07A==", "funding": [ { "type": "github", @@ -2050,21 +2050,21 @@ } ], "dependencies": { - "@csstools/color-helpers": "^3.0.2", - "@csstools/css-calc": "^1.1.4" + "@csstools/color-helpers": "^4.2.0", + "@csstools/css-calc": "^1.2.2" }, "engines": { "node": "^14 || ^16 || >=18" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^2.3.2", - "@csstools/css-tokenizer": "^2.2.1" + "@csstools/css-parser-algorithms": "^2.6.3", + "@csstools/css-tokenizer": "^2.3.1" } }, "node_modules/@csstools/css-parser-algorithms": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.3.2.tgz", - "integrity": "sha512-sLYGdAdEY2x7TSw9FtmdaTrh2wFtRJO5VMbBrA8tEqEod7GEggFmxTSK9XqExib3yMuYNcvcTdCZIP6ukdjAIA==", + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.6.3.tgz", + "integrity": "sha512-xI/tL2zxzEbESvnSxwFgwvy5HS00oCXxL4MLs6HUiDcYfwowsoQaABKxUElp1ARITrINzBnsECOc1q0eg2GOrA==", "funding": [ { "type": "github", @@ -2079,13 +2079,13 @@ "node": "^14 || ^16 || >=18" }, "peerDependencies": { - "@csstools/css-tokenizer": "^2.2.1" + "@csstools/css-tokenizer": "^2.3.1" } }, "node_modules/@csstools/css-tokenizer": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.2.1.tgz", - "integrity": "sha512-Zmsf2f/CaEPWEVgw29odOj+WEVoiJy9s9NOv5GgNY9mZ1CZ7394By6wONrONrTsnNDv6F9hR02nvFihrGVGHBg==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.3.1.tgz", + "integrity": "sha512-iMNHTyxLbBlWIfGtabT157LH9DUx9X8+Y3oymFEuMj8HNc+rpE3dPFGFgHjpKfjeFDjLjYIAIhXPGvS2lKxL9g==", "funding": [ { "type": "github", @@ -2101,9 +2101,9 @@ } }, "node_modules/@csstools/media-query-list-parser": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.5.tgz", - "integrity": "sha512-IxVBdYzR8pYe89JiyXQuYk4aVVoCPhMJkz6ElRwlVysjwURTsTk/bmY/z4FfeRE+CRBMlykPwXEVUg8lThv7AQ==", + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.11.tgz", + "integrity": "sha512-uox5MVhvNHqitPP+SynrB1o8oPxPMt2JLgp5ghJOWf54WGQ5OKu47efne49r1SWqs3wRP8xSWjnO9MBKxhB1dA==", "funding": [ { "type": "github", @@ -2118,14 +2118,14 @@ "node": "^14 || ^16 || >=18" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^2.3.2", - "@csstools/css-tokenizer": "^2.2.1" + "@csstools/css-parser-algorithms": "^2.6.3", + "@csstools/css-tokenizer": "^2.3.1" } }, "node_modules/@csstools/postcss-cascade-layers": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-4.0.1.tgz", - "integrity": "sha512-UYFuFL9GgVnftg9v7tBvVEBRLaBeAD66euD+yYy5fYCUld9ZIWTJNCE30hm6STMEdt6FL5xzeVw1lAZ1tpvUEg==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-4.0.6.tgz", + "integrity": "sha512-Xt00qGAQyqAODFiFEJNkTpSUz5VfYqnDLECdlA/Vv17nl/OIV5QfTRHGAXrBGG5YcJyHpJ+GF9gF/RZvOQz4oA==", "funding": [ { "type": "github", @@ -2137,7 +2137,7 @@ } ], "dependencies": { - "@csstools/selector-specificity": "^3.0.0", + "@csstools/selector-specificity": "^3.1.1", "postcss-selector-parser": "^6.0.13" }, "engines": { @@ -2148,9 +2148,9 @@ } }, "node_modules/@csstools/postcss-color-function": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-3.0.7.tgz", - "integrity": "sha512-/PIB20G1TPCXmQlaJLWIYzTZRZpj6csT4ijgnshIj/kcmniIRroAfDa0xSWnfuO1eNo0NptIaPU7jzUukWn55Q==", + "version": "3.0.16", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-3.0.16.tgz", + "integrity": "sha512-KtmXfckANSKsLBoTQCzggvKft1cmmmDKYjFO4yVlB23nWUgGInVBTE9T5JLmH29NNdTWSEPLWPUxoQ6XiIEn2Q==", "funding": [ { "type": "github", @@ -2162,10 +2162,11 @@ } ], "dependencies": { - "@csstools/css-color-parser": "^1.4.0", - "@csstools/css-parser-algorithms": "^2.3.2", - "@csstools/css-tokenizer": "^2.2.1", - "@csstools/postcss-progressive-custom-properties": "^3.0.2" + "@csstools/css-color-parser": "^2.0.2", + "@csstools/css-parser-algorithms": "^2.6.3", + "@csstools/css-tokenizer": "^2.3.1", + "@csstools/postcss-progressive-custom-properties": "^3.2.0", + "@csstools/utilities": "^1.0.0" }, "engines": { "node": "^14 || ^16 || >=18" @@ -2175,9 +2176,9 @@ } }, "node_modules/@csstools/postcss-color-mix-function": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-2.0.7.tgz", - "integrity": "sha512-57/g8aGo5eKFjEeJMiRKh8Qq43K2rCyk5ZZTvJ34TNl4zUtYU5DvLkIkOnhCtL8/a4z9oMA42aOnFPddRrScUQ==", + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-2.0.16.tgz", + "integrity": "sha512-BJnD1M5Pdypl1cJuwGuzVC52PqgzaObsDLu34jgf+QU7daVFqz432PvpqvXTmfTSNt4OckOT1QIzWexEFlDNXw==", "funding": [ { "type": "github", @@ -2189,10 +2190,11 @@ } ], "dependencies": { - "@csstools/css-color-parser": "^1.4.0", - "@csstools/css-parser-algorithms": "^2.3.2", - "@csstools/css-tokenizer": "^2.2.1", - "@csstools/postcss-progressive-custom-properties": "^3.0.2" + "@csstools/css-color-parser": "^2.0.2", + "@csstools/css-parser-algorithms": "^2.6.3", + "@csstools/css-tokenizer": "^2.3.1", + "@csstools/postcss-progressive-custom-properties": "^3.2.0", + "@csstools/utilities": "^1.0.0" }, "engines": { "node": "^14 || ^16 || >=18" @@ -2202,9 +2204,9 @@ } }, "node_modules/@csstools/postcss-exponential-functions": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-exponential-functions/-/postcss-exponential-functions-1.0.1.tgz", - "integrity": "sha512-ZLK2iSK4DUxeypGce2PnQSdYugUqDTwxnhNiq1o6OyKMNYgYs4eKbvEhFG8JKr1sJWbeqBi5jRr0017l2EWVvg==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@csstools/postcss-exponential-functions/-/postcss-exponential-functions-1.0.7.tgz", + "integrity": "sha512-9usBPQX74OhiF/VuaVrp44UAPzqbKNyoaxEa6tbEXiFp+OAm3yB/TLRKyPUWg5tvvHGCduGJVdJJB3w8c8NBtA==", "funding": [ { "type": "github", @@ -2216,9 +2218,9 @@ } ], "dependencies": { - "@csstools/css-calc": "^1.1.4", - "@csstools/css-parser-algorithms": "^2.3.2", - "@csstools/css-tokenizer": "^2.2.1" + "@csstools/css-calc": "^1.2.2", + "@csstools/css-parser-algorithms": "^2.6.3", + "@csstools/css-tokenizer": "^2.3.1" }, "engines": { "node": "^14 || ^16 || >=18" @@ -2228,9 +2230,9 @@ } }, "node_modules/@csstools/postcss-font-format-keywords": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-3.0.0.tgz", - "integrity": "sha512-ntkGj+1uDa/u6lpjPxnkPcjJn7ChO/Kcy08YxctOZI7vwtrdYvFhmE476dq8bj1yna306+jQ9gzXIG/SWfOaRg==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-3.0.2.tgz", + "integrity": "sha512-E0xz2sjm4AMCkXLCFvI/lyl4XO6aN1NCSMMVEOngFDJ+k2rDwfr6NDjWljk1li42jiLNChVX+YFnmfGCigZKXw==", "funding": [ { "type": "github", @@ -2242,6 +2244,7 @@ } ], "dependencies": { + "@csstools/utilities": "^1.0.0", "postcss-value-parser": "^4.2.0" }, "engines": { @@ -2252,9 +2255,9 @@ } }, "node_modules/@csstools/postcss-gamut-mapping": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-1.0.0.tgz", - "integrity": "sha512-6UQyK8l9YaG5Ao5rBDcCnKHrLsHiQ1E0zeFQuqDJqEtinVzAPb/MwSw3TenZXL1Rnd7th3tb+4CBFHBXdW5tbQ==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-1.0.9.tgz", + "integrity": "sha512-JmOeiBJj1RJriAkr+aLBaiYUpEqdNOIo3ERQ5a4uNzy18upzrQ6tz7m2Vt1GQpJ62zQj7rC5PjAhCoZCoyE31g==", "funding": [ { "type": "github", @@ -2266,9 +2269,9 @@ } ], "dependencies": { - "@csstools/css-color-parser": "^1.4.0", - "@csstools/css-parser-algorithms": "2.3.2", - "@csstools/css-tokenizer": "^2.2.1" + "@csstools/css-color-parser": "^2.0.2", + "@csstools/css-parser-algorithms": "^2.6.3", + "@csstools/css-tokenizer": "^2.3.1" }, "engines": { "node": "^14 || ^16 || >=18" @@ -2278,9 +2281,9 @@ } }, "node_modules/@csstools/postcss-gradients-interpolation-method": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-4.0.7.tgz", - "integrity": "sha512-GT1CzE/Tyr/ei4j5BwKESkHAgg+Gzys/0mAY7W+UiR+XrcYk5hDbOrE/YJIx1rflfO/7La1bDoZtA0YnLl4qNA==", + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-4.0.17.tgz", + "integrity": "sha512-qSNIqzLPKd2SadfWwHZv42lDRyYlLaM+Vx5rRIsnYCZbQxzFfe1XAwssrcCsHgba5bA6bi5oDoFCx0W+PRCpfw==", "funding": [ { "type": "github", @@ -2292,10 +2295,11 @@ } ], "dependencies": { - "@csstools/css-color-parser": "^1.4.0", - "@csstools/css-parser-algorithms": "^2.3.2", - "@csstools/css-tokenizer": "^2.2.1", - "@csstools/postcss-progressive-custom-properties": "^3.0.2" + "@csstools/css-color-parser": "^2.0.2", + "@csstools/css-parser-algorithms": "^2.6.3", + "@csstools/css-tokenizer": "^2.3.1", + "@csstools/postcss-progressive-custom-properties": "^3.2.0", + "@csstools/utilities": "^1.0.0" }, "engines": { "node": "^14 || ^16 || >=18" @@ -2305,9 +2309,9 @@ } }, "node_modules/@csstools/postcss-hwb-function": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-3.0.6.tgz", - "integrity": "sha512-uQgWt2Ho2yy2S6qthWY7mD5v57NKxi6dD1NB8nAybU5bJSsm+hLXRGm3/zbOH4xNrqO3Cl60DFSNlSrUME3Xjg==", + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-3.0.15.tgz", + "integrity": "sha512-l34fRiZ7o5+pULv7OplXniBTU4TuKYNNOv0abuvUanddWGSy3+YHlMKUSgcVFo0d1DorxPAhJSTCrugl+4OmMQ==", "funding": [ { "type": "github", @@ -2319,9 +2323,11 @@ } ], "dependencies": { - "@csstools/css-color-parser": "^1.4.0", - "@csstools/css-parser-algorithms": "^2.3.2", - "@csstools/css-tokenizer": "^2.2.1" + "@csstools/css-color-parser": "^2.0.2", + "@csstools/css-parser-algorithms": "^2.6.3", + "@csstools/css-tokenizer": "^2.3.1", + "@csstools/postcss-progressive-custom-properties": "^3.2.0", + "@csstools/utilities": "^1.0.0" }, "engines": { "node": "^14 || ^16 || >=18" @@ -2331,9 +2337,9 @@ } }, "node_modules/@csstools/postcss-ic-unit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-3.0.2.tgz", - "integrity": "sha512-n28Er7W9qc48zNjJnvTKuVHY26/+6YlA9WzJRksIHiAWOMxSH5IksXkw7FpkIOd+jLi59BMrX/BWrZMgjkLBHg==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-3.0.6.tgz", + "integrity": "sha512-fHaU9C/sZPauXMrzPitZ/xbACbvxbkPpHoUgB9Kw5evtsBWdVkVrajOyiT9qX7/c+G1yjApoQjP1fQatldsy9w==", "funding": [ { "type": "github", @@ -2345,7 +2351,8 @@ } ], "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^3.0.2", + "@csstools/postcss-progressive-custom-properties": "^3.2.0", + "@csstools/utilities": "^1.0.0", "postcss-value-parser": "^4.2.0" }, "engines": { @@ -2356,9 +2363,9 @@ } }, "node_modules/@csstools/postcss-initial": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-initial/-/postcss-initial-1.0.0.tgz", - "integrity": "sha512-1l7iHHjIl5qmVeGItugr4ZOlCREDP71mNKqoEyxlosIoiu3Os1nPWMHpuCvDLCLiWI/ONTOg3nzJh7gwHOrqUA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-initial/-/postcss-initial-1.0.1.tgz", + "integrity": "sha512-wtb+IbUIrIf8CrN6MLQuFR7nlU5C7PwuebfeEXfjthUha1+XZj2RVi+5k/lukToA24sZkYAiSJfHM8uG/UZIdg==", "funding": [ { "type": "github", @@ -2377,9 +2384,9 @@ } }, "node_modules/@csstools/postcss-is-pseudo-class": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-4.0.3.tgz", - "integrity": "sha512-/dt5M9Ty/x3Yiq0Nm/5PJJzwkVFchJgdjKVnryBPtoMCb9ohb/nDIJOwr/Wr3hK3FDs1EA1GE6PyRYsUmQPS8Q==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-4.0.8.tgz", + "integrity": "sha512-0aj591yGlq5Qac+plaWCbn5cpjs5Sh0daovYUKJUOMjIp70prGH/XPLp7QjxtbFXz3CTvb0H9a35dpEuIuUi3Q==", "funding": [ { "type": "github", @@ -2391,7 +2398,7 @@ } ], "dependencies": { - "@csstools/selector-specificity": "^3.0.0", + "@csstools/selector-specificity": "^3.1.1", "postcss-selector-parser": "^6.0.13" }, "engines": { @@ -2401,10 +2408,37 @@ "postcss": "^8.4" } }, + "node_modules/@csstools/postcss-light-dark-function": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@csstools/postcss-light-dark-function/-/postcss-light-dark-function-1.0.5.tgz", + "integrity": "sha512-kKM9dtEaVmSTb3scL2pgef62KyWv6SK19JiAnCCuiDhlRE6PADKzaPPBXmP3qj4IEgIH+cQhdEosB0eroU6Fnw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/css-parser-algorithms": "^2.6.3", + "@csstools/css-tokenizer": "^2.3.1", + "@csstools/postcss-progressive-custom-properties": "^3.2.0", + "@csstools/utilities": "^1.0.0" + }, + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, "node_modules/@csstools/postcss-logical-float-and-clear": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-float-and-clear/-/postcss-logical-float-and-clear-2.0.0.tgz", - "integrity": "sha512-Wki4vxsF6icRvRz8eF9bPpAvwaAt0RHwhVOyzfoFg52XiIMjb6jcbHkGxwpJXP4DVrnFEwpwmrz5aTRqOW82kg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-float-and-clear/-/postcss-logical-float-and-clear-2.0.1.tgz", + "integrity": "sha512-SsrWUNaXKr+e/Uo4R/uIsqJYt3DaggIh/jyZdhy/q8fECoJSKsSMr7nObSLdvoULB69Zb6Bs+sefEIoMG/YfOA==", "funding": [ { "type": "github", @@ -2423,9 +2457,9 @@ } }, "node_modules/@csstools/postcss-logical-overflow": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-overflow/-/postcss-logical-overflow-1.0.0.tgz", - "integrity": "sha512-cIrZ8f7bGGvr+W53nEuMspcwaeaI2YTmz6LZ4yiAO5z14/PQgOOv+Pn+qjvPOPoadeY2BmpaoTzZKvdAQuM17w==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-overflow/-/postcss-logical-overflow-1.0.1.tgz", + "integrity": "sha512-Kl4lAbMg0iyztEzDhZuQw8Sj9r2uqFDcU1IPl+AAt2nue8K/f1i7ElvKtXkjhIAmKiy5h2EY8Gt/Cqg0pYFDCw==", "funding": [ { "type": "github", @@ -2444,9 +2478,9 @@ } }, "node_modules/@csstools/postcss-logical-overscroll-behavior": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-overscroll-behavior/-/postcss-logical-overscroll-behavior-1.0.0.tgz", - "integrity": "sha512-e89S2LWjnxf0SB2wNUAbqDyFb/Fow/tlOe1XqOLbNx4rf3LrQokM9qldVx7sarnddml3ORE5LDUmlKpPOOeJTA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-overscroll-behavior/-/postcss-logical-overscroll-behavior-1.0.1.tgz", + "integrity": "sha512-+kHamNxAnX8ojPCtV8WPcUP3XcqMFBSDuBuvT6MHgq7oX4IQxLIXKx64t7g9LiuJzE7vd06Q9qUYR6bh4YnGpQ==", "funding": [ { "type": "github", @@ -2465,9 +2499,9 @@ } }, "node_modules/@csstools/postcss-logical-resize": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-resize/-/postcss-logical-resize-2.0.0.tgz", - "integrity": "sha512-lCQ1aX8c5+WI4t5EoYf3alTzJNNocMqTb+u1J9CINdDhFh1fjovqK+0aHalUHsNstZmzFPNzIkU4Mb3eM9U8SA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-resize/-/postcss-logical-resize-2.0.1.tgz", + "integrity": "sha512-W5Gtwz7oIuFcKa5SmBjQ2uxr8ZoL7M2bkoIf0T1WeNqljMkBrfw1DDA8/J83k57NQ1kcweJEjkJ04pUkmyee3A==", "funding": [ { "type": "github", @@ -2489,9 +2523,9 @@ } }, "node_modules/@csstools/postcss-logical-viewport-units": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-viewport-units/-/postcss-logical-viewport-units-2.0.3.tgz", - "integrity": "sha512-xeVxqND5rlQyqLGdH7rX34sIm/JbbQKxpKQP8oD1YQqUHHCLQR9NUS57WqJKajxKN6AcNAMWJhb5LUH5RfPcyA==", + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-viewport-units/-/postcss-logical-viewport-units-2.0.9.tgz", + "integrity": "sha512-iBBJuExgHwedFH9AqNOHWzZFgYnt17zhu1qWjmSihu1P5pw0lIG9q5t3uIgJJFDNmYoOGfBKan66z9u1QH8yBQ==", "funding": [ { "type": "github", @@ -2503,7 +2537,8 @@ } ], "dependencies": { - "@csstools/css-tokenizer": "^2.2.1" + "@csstools/css-tokenizer": "^2.3.1", + "@csstools/utilities": "^1.0.0" }, "engines": { "node": "^14 || ^16 || >=18" @@ -2513,9 +2548,9 @@ } }, "node_modules/@csstools/postcss-media-minmax": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-media-minmax/-/postcss-media-minmax-1.1.0.tgz", - "integrity": "sha512-t5Li/DPC5QmW/6VFLfUvsw/4dNYYseWR0tOXDeJg/9EKUodBgNawz5tuk5vYKtNvoj+Q08odMuXcpS5YJj0AFA==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@csstools/postcss-media-minmax/-/postcss-media-minmax-1.1.6.tgz", + "integrity": "sha512-bc0frf2Lod53j6wEHVsaVElfvCf6uhc96v99M/wUfer4MmNYfO3YLx1kFuB8xXvb0AXiWx4fohCJqemHV3bfRg==", "funding": [ { "type": "github", @@ -2527,10 +2562,10 @@ } ], "dependencies": { - "@csstools/css-calc": "^1.1.4", - "@csstools/css-parser-algorithms": "^2.3.2", - "@csstools/css-tokenizer": "^2.2.1", - "@csstools/media-query-list-parser": "^2.1.5" + "@csstools/css-calc": "^1.2.2", + "@csstools/css-parser-algorithms": "^2.6.3", + "@csstools/css-tokenizer": "^2.3.1", + "@csstools/media-query-list-parser": "^2.1.11" }, "engines": { "node": "^14 || ^16 || >=18" @@ -2540,9 +2575,9 @@ } }, "node_modules/@csstools/postcss-media-queries-aspect-ratio-number-values": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-2.0.3.tgz", - "integrity": "sha512-IPL8AvnwMYW+cWtp+j8cW3MFN0RyXNT4hLOvs6Rf2N+NcbvXhSyKxZuE3W9Cv4KjaNoNoGx1d0UhT6tktq6tUw==", + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-2.0.9.tgz", + "integrity": "sha512-PR0s3tFSxPoKoPLoKuiZuYhwQC5bQxq/gFfywX2u/kh8rMzesARPZYKxE71I3jHWi6KDHGZl9Xb5xcFPwtvLiQ==", "funding": [ { "type": "github", @@ -2554,9 +2589,9 @@ } ], "dependencies": { - "@csstools/css-parser-algorithms": "^2.3.2", - "@csstools/css-tokenizer": "^2.2.1", - "@csstools/media-query-list-parser": "^2.1.5" + "@csstools/css-parser-algorithms": "^2.6.3", + "@csstools/css-tokenizer": "^2.3.1", + "@csstools/media-query-list-parser": "^2.1.11" }, "engines": { "node": "^14 || ^16 || >=18" @@ -2566,9 +2601,9 @@ } }, "node_modules/@csstools/postcss-nested-calc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-3.0.0.tgz", - "integrity": "sha512-HsB66aDWAouOwD/GcfDTS0a7wCuVWaTpXcjl5VKP0XvFxDiU+r0T8FG7xgb6ovZNZ+qzvGIwRM+CLHhDgXrYgQ==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-3.0.2.tgz", + "integrity": "sha512-ySUmPyawiHSmBW/VI44+IObcKH0v88LqFe0d09Sb3w4B1qjkaROc6d5IA3ll9kjD46IIX/dbO5bwFN/swyoyZA==", "funding": [ { "type": "github", @@ -2580,6 +2615,7 @@ } ], "dependencies": { + "@csstools/utilities": "^1.0.0", "postcss-value-parser": "^4.2.0" }, "engines": { @@ -2590,9 +2626,9 @@ } }, "node_modules/@csstools/postcss-normalize-display-values": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-3.0.1.tgz", - "integrity": "sha512-nUvRxI+ALJwkxZdPU4EDyuM380vP91sAGvI3jAOHs/sr3jfcCOzLkY6xKI1Mr526kZ3RivmMoYM/xq+XFyE/bw==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-3.0.2.tgz", + "integrity": "sha512-fCapyyT/dUdyPtrelQSIV+d5HqtTgnNP/BEG9IuhgXHt93Wc4CfC1bQ55GzKAjWrZbgakMQ7MLfCXEf3rlZJOw==", "funding": [ { "type": "github", @@ -2614,9 +2650,9 @@ } }, "node_modules/@csstools/postcss-oklab-function": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-3.0.7.tgz", - "integrity": "sha512-vBFTQD3CARB3u/XIGO44wWbcO7xG/4GsYqJlcPuUGRSK8mtxes6n4vvNFlIByyAZy2k4d4RY63nyvTbMpeNTaQ==", + "version": "3.0.16", + "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-3.0.16.tgz", + "integrity": "sha512-zm8nND+EraZrmbO4mgcT8FrJrAQUfWNfMmbV5uTCpWtAcO5ycX3E3bO8T1TjczKYRxC5QMM/91n9YExYCF4Mvw==", "funding": [ { "type": "github", @@ -2628,10 +2664,11 @@ } ], "dependencies": { - "@csstools/css-color-parser": "^1.4.0", - "@csstools/css-parser-algorithms": "^2.3.2", - "@csstools/css-tokenizer": "^2.2.1", - "@csstools/postcss-progressive-custom-properties": "^3.0.2" + "@csstools/css-color-parser": "^2.0.2", + "@csstools/css-parser-algorithms": "^2.6.3", + "@csstools/css-tokenizer": "^2.3.1", + "@csstools/postcss-progressive-custom-properties": "^3.2.0", + "@csstools/utilities": "^1.0.0" }, "engines": { "node": "^14 || ^16 || >=18" @@ -2641,9 +2678,9 @@ } }, "node_modules/@csstools/postcss-progressive-custom-properties": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-3.0.2.tgz", - "integrity": "sha512-YEvTozk1SxnV/PGL5DllBVDuLQ+jiQhyCSQiZJ6CwBMU5JQ9hFde3i1qqzZHuclZfptjrU0JjlX4ePsOhxNzHw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-3.2.0.tgz", + "integrity": "sha512-BZlirVxCRgKlE7yVme+Xvif72eTn1MYXj8oZ4Knb+jwaH4u3AN1DjbhM7j86RP5vvuAOexJ4JwfifYYKWMN/QQ==", "funding": [ { "type": "github", @@ -2665,9 +2702,9 @@ } }, "node_modules/@csstools/postcss-relative-color-syntax": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-2.0.7.tgz", - "integrity": "sha512-2AiFbJSVF4EyymLxme4JzSrbXykHolx8DdZECHjYKMhoulhKLltx5ccYgtrK3BmXGd3v3nJrWFCc8JM8bjuiOg==", + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-2.0.16.tgz", + "integrity": "sha512-TSM8fVqJkT8JZDranZPnkpxjU/Q1sNR192lXMND+EcKOUjYa6uYpGSfHgjnWjCRiBSciettS+sL7y9wmnas7qQ==", "funding": [ { "type": "github", @@ -2679,10 +2716,11 @@ } ], "dependencies": { - "@csstools/css-color-parser": "^1.4.0", - "@csstools/css-parser-algorithms": "^2.3.2", - "@csstools/css-tokenizer": "^2.2.1", - "@csstools/postcss-progressive-custom-properties": "^3.0.2" + "@csstools/css-color-parser": "^2.0.2", + "@csstools/css-parser-algorithms": "^2.6.3", + "@csstools/css-tokenizer": "^2.3.1", + "@csstools/postcss-progressive-custom-properties": "^3.2.0", + "@csstools/utilities": "^1.0.0" }, "engines": { "node": "^14 || ^16 || >=18" @@ -2692,9 +2730,9 @@ } }, "node_modules/@csstools/postcss-scope-pseudo-class": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-scope-pseudo-class/-/postcss-scope-pseudo-class-3.0.0.tgz", - "integrity": "sha512-GFNVsD97OuEcfHmcT0/DAZWAvTM/FFBDQndIOLawNc1Wq8YqpZwBdHa063Lq+Irk7azygTT+Iinyg3Lt76p7rg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-scope-pseudo-class/-/postcss-scope-pseudo-class-3.0.1.tgz", + "integrity": "sha512-3ZFonK2gfgqg29gUJ2w7xVw2wFJ1eNWVDONjbzGkm73gJHVCYK5fnCqlLr+N+KbEfv2XbWAO0AaOJCFB6Fer6A==", "funding": [ { "type": "github", @@ -2716,9 +2754,9 @@ } }, "node_modules/@csstools/postcss-stepped-value-functions": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-3.0.2.tgz", - "integrity": "sha512-I3wX44MZVv+tDuWfrd3BTvRB/YRIM2F5v1MBtTI89sxpFn47mNpTwpPYUOGPVCgKlRDfZSlxIUYhUQmqRQZZFQ==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-3.0.8.tgz", + "integrity": "sha512-X76+thsvsmH/SkqVbN+vjeFKe1ABGLRx8/Wl68QTb/zvJWdzgx5S/nbszZP5O3nTRc5eI8NxIOrQUiy30fR+0g==", "funding": [ { "type": "github", @@ -2730,9 +2768,9 @@ } ], "dependencies": { - "@csstools/css-calc": "^1.1.4", - "@csstools/css-parser-algorithms": "^2.3.2", - "@csstools/css-tokenizer": "^2.2.1" + "@csstools/css-calc": "^1.2.2", + "@csstools/css-parser-algorithms": "^2.6.3", + "@csstools/css-tokenizer": "^2.3.1" }, "engines": { "node": "^14 || ^16 || >=18" @@ -2742,9 +2780,9 @@ } }, "node_modules/@csstools/postcss-text-decoration-shorthand": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-3.0.3.tgz", - "integrity": "sha512-d5J9m49HhqXRcw1S6vTZuviHi/iknUKGjBpChiNK1ARg9sSa3b8m5lsWz5Izs8ISORZdv2bZRwbw5Z2R6gQ9kQ==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-3.0.6.tgz", + "integrity": "sha512-Q8HEu4AEiwNVZBD6+DpQ8M9SajpMow4+WtmndWIAv8qxDtDYL4JK1xXWkhOGk28PrcJawOvkrEZ8Ri59UN1TJw==", "funding": [ { "type": "github", @@ -2756,7 +2794,7 @@ } ], "dependencies": { - "@csstools/color-helpers": "^3.0.2", + "@csstools/color-helpers": "^4.2.0", "postcss-value-parser": "^4.2.0" }, "engines": { @@ -2767,9 +2805,9 @@ } }, "node_modules/@csstools/postcss-trigonometric-functions": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-3.0.2.tgz", - "integrity": "sha512-AwzNhF4QOKaLOKvMljwwFkeYXwufhRO15G+kKohHkyoNOL75xWkN+W2Y9ik9tSeAyDv+cYNlYaF+o/a79WjVjg==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-3.0.8.tgz", + "integrity": "sha512-zEzyGriPqoIYFgHJqWNy8bmoxjM4+ONyTap1ZzQK/Lll/VsCYvx0IckB33W/u89uLSVeeB8xC7uTrkoQ7ogKyQ==", "funding": [ { "type": "github", @@ -2781,9 +2819,9 @@ } ], "dependencies": { - "@csstools/css-calc": "^1.1.4", - "@csstools/css-parser-algorithms": "^2.3.2", - "@csstools/css-tokenizer": "^2.2.1" + "@csstools/css-calc": "^1.2.2", + "@csstools/css-parser-algorithms": "^2.6.3", + "@csstools/css-tokenizer": "^2.3.1" }, "engines": { "node": "^14 || ^16 || >=18" @@ -2793,9 +2831,9 @@ } }, "node_modules/@csstools/postcss-unset-value": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-3.0.0.tgz", - "integrity": "sha512-P0JD1WHh3avVyKKRKjd0dZIjCEeaBer8t1BbwGMUDtSZaLhXlLNBqZ8KkqHzYWXOJgHleXAny2/sx8LYl6qhEA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-3.0.1.tgz", + "integrity": "sha512-dbDnZ2ja2U8mbPP0Hvmt2RMEGBiF1H7oY6HYSpjteXJGihYwgxgTr6KRbbJ/V6c+4wd51M+9980qG4gKVn5ttg==", "funding": [ { "type": "github", @@ -2813,10 +2851,31 @@ "postcss": "^8.4" } }, + "node_modules/@csstools/selector-resolve-nested": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-1.1.0.tgz", + "integrity": "sha512-uWvSaeRcHyeNenKg8tp17EVDRkpflmdyvbE0DHo6D/GdBb6PDnCYYU6gRpXhtICMGMcahQmj2zGxwFM/WC8hCg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^6.0.13" + } + }, "node_modules/@csstools/selector-specificity": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-3.0.0.tgz", - "integrity": "sha512-hBI9tfBtuPIi885ZsZ32IMEU/5nlZH/KOVYJCOh7gyMxaVLGmLedYqFN6Ui1LXkI8JlC8IsuC0rF0btcRZKd5g==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-3.1.1.tgz", + "integrity": "sha512-a7cxGcJ2wIlMFLlh8z2ONm+715QkPHiyJcxwQlKOz/03GPw1COpfhcmC9wm4xlZfp//jWHNNMwzjtqHXVWU9KA==", "funding": [ { "type": "github", @@ -2834,6 +2893,27 @@ "postcss-selector-parser": "^6.0.13" } }, + "node_modules/@csstools/utilities": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@csstools/utilities/-/utilities-1.0.0.tgz", + "integrity": "sha512-tAgvZQe/t2mlvpNosA4+CkMiZ2azISW5WPAcdSalZlEjQvUfghHxfQcrCiK/7/CrfAWVxyM88kGFYO82heIGDg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -2959,43 +3039,43 @@ } }, "node_modules/@fluentui/date-time-utilities": { - "version": "8.5.14", - "resolved": "https://registry.npmjs.org/@fluentui/date-time-utilities/-/date-time-utilities-8.5.14.tgz", - "integrity": "sha512-Kc64ZBj0WiaSW/Bsh4fMy9oM2FIk1TgIqBV6+OgOtdKx9cXwLdmgGk8zuQTcuRnwv5WCk2M6wvW1M+eK3sNRGA==", + "version": "8.6.7", + "resolved": "https://registry.npmjs.org/@fluentui/date-time-utilities/-/date-time-utilities-8.6.7.tgz", + "integrity": "sha512-FzM/UYne+o7zFJ1PBQc/TKW+ZY2gfG12AjP/CBF9RM9ExxGUsX3/BGYkNzw0K5KNuqSx7LFqfnhTN9fElTrTlQ==", "dependencies": { - "@fluentui/set-version": "^8.2.12", + "@fluentui/set-version": "^8.2.21", "tslib": "^2.1.0" } }, "node_modules/@fluentui/dom-utilities": { - "version": "2.2.12", - "resolved": "https://registry.npmjs.org/@fluentui/dom-utilities/-/dom-utilities-2.2.12.tgz", - "integrity": "sha512-safCKQPJTnshYG13/U2Zx1KWhOhU4vl5RAKqW7HEBfLOHds/fAR+EzTvKgO6OgxJq59JAKJvpH2QujkLXZZQ3A==", + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@fluentui/dom-utilities/-/dom-utilities-2.3.5.tgz", + "integrity": "sha512-27DnvFFsA+1a23UGpb0E2WHvHHLNPaViJMOm4tYEGyCKV26DlBN7N5QggCd0m5fg+A+dXKWb03e0aikBquF5rQ==", "dependencies": { - "@fluentui/set-version": "^8.2.12", + "@fluentui/set-version": "^8.2.21", "tslib": "^2.1.0" } }, "node_modules/@fluentui/font-icons-mdl2": { - "version": "8.5.26", - "resolved": "https://registry.npmjs.org/@fluentui/font-icons-mdl2/-/font-icons-mdl2-8.5.26.tgz", - "integrity": "sha512-0fFUHUnUkPuYmuB/WLBfIjZ17Ne7nE2uQQDRQ/fzB7RUW8VnBbR7WbCYJjuF785nhEXLAfwq9xawTShvbMdCPg==", + "version": "8.5.42", + "resolved": "https://registry.npmjs.org/@fluentui/font-icons-mdl2/-/font-icons-mdl2-8.5.42.tgz", + "integrity": "sha512-LX51MFKcEAg69NqIAEbtlexDNtKyfiAr+BiCkLF4+XuevE+GAocQqaHh7jInws/NOuDvyK2Vz9Z2J5WTG2Mufw==", "dependencies": { - "@fluentui/set-version": "^8.2.12", - "@fluentui/style-utilities": "^8.9.19", - "@fluentui/utilities": "^8.13.20", + "@fluentui/set-version": "^8.2.21", + "@fluentui/style-utilities": "^8.10.13", + "@fluentui/utilities": "^8.15.8", "tslib": "^2.1.0" } }, "node_modules/@fluentui/foundation-legacy": { - "version": "8.2.46", - "resolved": "https://registry.npmjs.org/@fluentui/foundation-legacy/-/foundation-legacy-8.2.46.tgz", - "integrity": "sha512-qID0vHDPDK7/qAuHWsQEHyWfMz9ELM0axxlwyxZUHRi6VJRTNFRBEFI4DxlCXxEdAIhBKqLZMurhq8cmyjlCoQ==", - "dependencies": { - "@fluentui/merge-styles": "^8.5.13", - "@fluentui/set-version": "^8.2.12", - "@fluentui/style-utilities": "^8.9.19", - "@fluentui/utilities": "^8.13.20", + "version": "8.4.8", + "resolved": "https://registry.npmjs.org/@fluentui/foundation-legacy/-/foundation-legacy-8.4.8.tgz", + "integrity": "sha512-v0QPnlclvTlzC3DElPNGzQ3ybPxSqSLKTPAdzVsLpeM72IbnoomPPJIJTXKOBRdswxQhx7I3OFLHr69ciPwvbA==", + "dependencies": { + "@fluentui/merge-styles": "^8.6.8", + "@fluentui/set-version": "^8.2.21", + "@fluentui/style-utilities": "^8.10.13", + "@fluentui/utilities": "^8.15.8", "tslib": "^2.1.0" }, "peerDependencies": { @@ -3004,39 +3084,39 @@ } }, "node_modules/@fluentui/keyboard-key": { - "version": "0.4.12", - "resolved": "https://registry.npmjs.org/@fluentui/keyboard-key/-/keyboard-key-0.4.12.tgz", - "integrity": "sha512-9nPglM58ThbOEQ88KijdYl64hiTAQQ0o60HRc0vboibmr41mJ322FoBz5Q5S5QLIEbBZajrAkrDMs3PKW4CCSw==", + "version": "0.4.21", + "resolved": "https://registry.npmjs.org/@fluentui/keyboard-key/-/keyboard-key-0.4.21.tgz", + "integrity": "sha512-V77gXinXpX2FosoJExSvDAcqLUtpDuefDKQ+gmIGRrCoXOWBaG+fGn1bM1fQlo66+fsaTY7hafnBLt/QVmGv4g==", "dependencies": { "tslib": "^2.1.0" } }, "node_modules/@fluentui/merge-styles": { - "version": "8.5.13", - "resolved": "https://registry.npmjs.org/@fluentui/merge-styles/-/merge-styles-8.5.13.tgz", - "integrity": "sha512-ocgwNlQcQwn5mNlZKFazrFVbYDEQ6BptoW4GyEv6U5TEHE8HKKYuPRf340NXCRGiacSpz3vLkyDjp+L431qUXg==", + "version": "8.6.8", + "resolved": "https://registry.npmjs.org/@fluentui/merge-styles/-/merge-styles-8.6.8.tgz", + "integrity": "sha512-QdUX5ZhhnhO25k9wY9jXY3O6vhL/YfKfV1bac0kDKVkEjYszIGq8joKhrVeIUsNvaQer4zl4MMvlLOA5vcL78g==", "dependencies": { - "@fluentui/set-version": "^8.2.12", + "@fluentui/set-version": "^8.2.21", "tslib": "^2.1.0" } }, "node_modules/@fluentui/react": { - "version": "8.112.5", - "resolved": "https://registry.npmjs.org/@fluentui/react/-/react-8.112.5.tgz", - "integrity": "sha512-qeTsTS5z0hwGqatUAHZCybkHQszZEEQ0It8C6n+hfy+c1A3MJt3DmXALMY5HymqErUltcE3w7YjhEPqfP+yxag==", - "dependencies": { - "@fluentui/date-time-utilities": "^8.5.14", - "@fluentui/font-icons-mdl2": "^8.5.26", - "@fluentui/foundation-legacy": "^8.2.46", - "@fluentui/merge-styles": "^8.5.13", - "@fluentui/react-focus": "^8.8.33", - "@fluentui/react-hooks": "^8.6.32", - "@fluentui/react-portal-compat-context": "^9.0.9", - "@fluentui/react-window-provider": "^2.2.16", - "@fluentui/set-version": "^8.2.12", - "@fluentui/style-utilities": "^8.9.19", - "@fluentui/theme": "^2.6.37", - "@fluentui/utilities": "^8.13.20", + "version": "8.117.7", + "resolved": "https://registry.npmjs.org/@fluentui/react/-/react-8.117.7.tgz", + "integrity": "sha512-yHrncVrWoDoIQR80RAKMSG0THZwMa0VGDCzv27oRp6RFdB44XnFS4xtukNlkdB42IC+dCu8o6Anj7NP6DgX+MQ==", + "dependencies": { + "@fluentui/date-time-utilities": "^8.6.3", + "@fluentui/font-icons-mdl2": "^8.5.38", + "@fluentui/foundation-legacy": "^8.4.4", + "@fluentui/merge-styles": "^8.6.4", + "@fluentui/react-focus": "^8.9.1", + "@fluentui/react-hooks": "^8.8.1", + "@fluentui/react-portal-compat-context": "^9.0.11", + "@fluentui/react-window-provider": "^2.2.21", + "@fluentui/set-version": "^8.2.17", + "@fluentui/style-utilities": "^8.10.9", + "@fluentui/theme": "^2.6.47", + "@fluentui/utilities": "^8.15.4", "@microsoft/load-themed-styles": "^1.10.26", "tslib": "^2.1.0" }, @@ -3048,15 +3128,15 @@ } }, "node_modules/@fluentui/react-focus": { - "version": "8.8.33", - "resolved": "https://registry.npmjs.org/@fluentui/react-focus/-/react-focus-8.8.33.tgz", - "integrity": "sha512-6+5LWCluSzVr8rK1dUNQ4HP/Prz7OWUScrNi7C+PLZxbt4nnA5M+lDpwRZM1ZyhVhsEjH7p25tagp+EGYz+xKA==", - "dependencies": { - "@fluentui/keyboard-key": "^0.4.12", - "@fluentui/merge-styles": "^8.5.13", - "@fluentui/set-version": "^8.2.12", - "@fluentui/style-utilities": "^8.9.19", - "@fluentui/utilities": "^8.13.20", + "version": "8.9.5", + "resolved": "https://registry.npmjs.org/@fluentui/react-focus/-/react-focus-8.9.5.tgz", + "integrity": "sha512-n0vyp3I9mfP7eZqVVdM8HMLxjKWtNHM8DrbNLQC+WjuatnQR7No20yLi2vg1y16EiUhDKG/Mi9H0ohJvsYz15w==", + "dependencies": { + "@fluentui/keyboard-key": "^0.4.21", + "@fluentui/merge-styles": "^8.6.8", + "@fluentui/set-version": "^8.2.21", + "@fluentui/style-utilities": "^8.10.13", + "@fluentui/utilities": "^8.15.8", "tslib": "^2.1.0" }, "peerDependencies": { @@ -3065,13 +3145,13 @@ } }, "node_modules/@fluentui/react-hooks": { - "version": "8.6.32", - "resolved": "https://registry.npmjs.org/@fluentui/react-hooks/-/react-hooks-8.6.32.tgz", - "integrity": "sha512-0wPdNhxuBrHMcsnWwGsWMCHlMRqgW4vX+9+yFFCycUI6Ryoi/y07y6oNGwYkNrFkqarBsp0U82SN9qUGCXnJcQ==", + "version": "8.8.5", + "resolved": "https://registry.npmjs.org/@fluentui/react-hooks/-/react-hooks-8.8.5.tgz", + "integrity": "sha512-YqEa5/NEbGoAd1hokzUD/Zl3gnZJWwpg4KyqEJKMZgnWkBLaHgePtaH1HBORr/beKDC0ITPw6hqLUASl6QKTEg==", "dependencies": { - "@fluentui/react-window-provider": "^2.2.16", - "@fluentui/set-version": "^8.2.12", - "@fluentui/utilities": "^8.13.20", + "@fluentui/react-window-provider": "^2.2.25", + "@fluentui/set-version": "^8.2.21", + "@fluentui/utilities": "^8.15.8", "tslib": "^2.1.0" }, "peerDependencies": { @@ -3079,41 +3159,41 @@ "react": ">=16.8.0 <19.0.0" } }, - "node_modules/@fluentui/react-icon-provider": { - "version": "1.3.48", - "resolved": "https://registry.npmjs.org/@fluentui/react-icon-provider/-/react-icon-provider-1.3.48.tgz", - "integrity": "sha512-LMEOE+5SUZQAYVHrcX4Ejm7hAsR6JzmCBnwgMtdt2UpdX8ij57SkMu5ckVf7KiT5zyUYg8XuSV5vz/mRBYGWrA==", + "node_modules/@fluentui/react-icons-mdl2": { + "version": "1.3.63", + "resolved": "https://registry.npmjs.org/@fluentui/react-icons-mdl2/-/react-icons-mdl2-1.3.63.tgz", + "integrity": "sha512-vI9EfH+gxwequ2q+I7akp2TTmMnsjrAWYizml7Mvx/97l1X5uNKmUkZVgUWEvFic5O50UrWOQ81GjhyRRhHR+Q==", "dependencies": { - "@fluentui/set-version": "^8.2.12", - "@fluentui/style-utilities": "^8.9.20", + "@fluentui/react-icon-provider": "^1.3.59", + "@fluentui/set-version": "^8.2.17", + "@fluentui/utilities": "^8.15.4", + "@microsoft/load-themed-styles": "^1.10.26", "tslib": "^2.1.0" }, "peerDependencies": { - "@types/react": ">=16.8.0 <19.0.0", - "@types/react-dom": ">=16.8.0 <19.0.0", - "react": ">=16.8.0 <19.0.0", - "react-dom": ">=16.8.0 <19.0.0" + "react": ">=16.8.0 <19.0.0" } }, - "node_modules/@fluentui/react-icons-mdl2": { - "version": "1.3.52", - "resolved": "https://registry.npmjs.org/@fluentui/react-icons-mdl2/-/react-icons-mdl2-1.3.52.tgz", - "integrity": "sha512-rjcLTe5bhxxBPHRCjjCyP3eRJ/HXUsGkdP2X6ZSxJ2FI91U6WsEXvpcdaORvllU3RKLa6KESwVtx+88RP2cOsg==", + "node_modules/@fluentui/react-icons-mdl2/node_modules/@fluentui/react-icon-provider": { + "version": "1.3.63", + "resolved": "https://registry.npmjs.org/@fluentui/react-icon-provider/-/react-icon-provider-1.3.63.tgz", + "integrity": "sha512-g/cJZWeo42a3fRJH7qkxwOJg3830ccQorPztZX04nERzuvovY1ynZWHjdFlrx9i8lO8kxhzcOoMczQlrylJ3+Q==", "dependencies": { - "@fluentui/react-icon-provider": "^1.3.48", - "@fluentui/set-version": "^8.2.12", - "@fluentui/utilities": "^8.13.21", - "@microsoft/load-themed-styles": "^1.10.26", + "@fluentui/set-version": "^8.2.21", + "@fluentui/style-utilities": "^8.10.13", "tslib": "^2.1.0" }, "peerDependencies": { - "react": ">=16.8.0 <19.0.0" + "@types/react": ">=16.8.0 <19.0.0", + "@types/react-dom": ">=16.8.0 <19.0.0", + "react": ">=16.8.0 <19.0.0", + "react-dom": ">=16.8.0 <19.0.0" } }, "node_modules/@fluentui/react-portal-compat-context": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/@fluentui/react-portal-compat-context/-/react-portal-compat-context-9.0.9.tgz", - "integrity": "sha512-Qt4zBJjBf3QihWqDNfZ4D9ha0QdcUvw4zIErp6IkT4uFIkV2VSgEjIKXm0h2iDEZZQtzbGlFG+9hPPhH13HaPQ==", + "version": "9.0.11", + "resolved": "https://registry.npmjs.org/@fluentui/react-portal-compat-context/-/react-portal-compat-context-9.0.11.tgz", + "integrity": "sha512-ubvW/ej0O+Pago9GH3mPaxzUgsNnBoqvghNamWjyKvZIViyaXUG6+sgcAl721R+qGAFac+A20akI5qDJz/xtdg==", "dependencies": { "@swc/helpers": "^0.5.1" }, @@ -3123,11 +3203,11 @@ } }, "node_modules/@fluentui/react-window-provider": { - "version": "2.2.16", - "resolved": "https://registry.npmjs.org/@fluentui/react-window-provider/-/react-window-provider-2.2.16.tgz", - "integrity": "sha512-4gkUMSAUjo3cgCGt+0VvTbMy9qbF6zo/cmmfYtfqbSFtXz16lKixSCMIf66gXdKjovqRGVFC/XibqfrXM2QLuw==", + "version": "2.2.25", + "resolved": "https://registry.npmjs.org/@fluentui/react-window-provider/-/react-window-provider-2.2.25.tgz", + "integrity": "sha512-5VSFrnVjBD1rksI+1oPkX7s0X1pxGrgKdM0/ORZOSpqZ4Yy6t5Ibe5Zoq3YdeMoYQJdey+nnyP0/s0RLkOUCZQ==", "dependencies": { - "@fluentui/set-version": "^8.2.12", + "@fluentui/set-version": "^8.2.21", "tslib": "^2.1.0" }, "peerDependencies": { @@ -3136,34 +3216,34 @@ } }, "node_modules/@fluentui/set-version": { - "version": "8.2.12", - "resolved": "https://registry.npmjs.org/@fluentui/set-version/-/set-version-8.2.12.tgz", - "integrity": "sha512-I4uXIg9xkL2Heotf1+7CyGcHQskdtMSH0B5mSV0TL3w7WI2qpnzrpKuP2Kq6DHZN6Xrsg4ORFNJSjLxq/s9cUQ==", + "version": "8.2.21", + "resolved": "https://registry.npmjs.org/@fluentui/set-version/-/set-version-8.2.21.tgz", + "integrity": "sha512-WPHAFgPbskK0YVduc7cxm+2U+5dhd5WkzznLcqz1OeS81vAgJXWOKK4UIfCDEcqkDuDubMKDrVyHnYJYAJufJQ==", "dependencies": { "tslib": "^2.1.0" } }, "node_modules/@fluentui/style-utilities": { - "version": "8.9.20", - "resolved": "https://registry.npmjs.org/@fluentui/style-utilities/-/style-utilities-8.9.20.tgz", - "integrity": "sha512-oj0ghn21DnqCardlcEp+zob3IEAfA/Z7ZjzuYqlHuPUItwRqGmpr1wErssRC4R1kHsH6gq9ALxVgMa4/FvdzGg==", - "dependencies": { - "@fluentui/merge-styles": "^8.5.13", - "@fluentui/set-version": "^8.2.12", - "@fluentui/theme": "^2.6.38", - "@fluentui/utilities": "^8.13.21", + "version": "8.10.13", + "resolved": "https://registry.npmjs.org/@fluentui/style-utilities/-/style-utilities-8.10.13.tgz", + "integrity": "sha512-XdwfLJKir0+HC95EdQz78D0W9mBVV694nelkSLs12wGzPkruibFfESpbOgbHrPKnnBhhiQrjzGrYvuz2+WNvkQ==", + "dependencies": { + "@fluentui/merge-styles": "^8.6.8", + "@fluentui/set-version": "^8.2.21", + "@fluentui/theme": "^2.6.51", + "@fluentui/utilities": "^8.15.8", "@microsoft/load-themed-styles": "^1.10.26", "tslib": "^2.1.0" } }, "node_modules/@fluentui/theme": { - "version": "2.6.38", - "resolved": "https://registry.npmjs.org/@fluentui/theme/-/theme-2.6.38.tgz", - "integrity": "sha512-LObK/mZOQFb3aTcDlKBSLpPV0BOp5BOuNqg0Wps51b1RlisI6oS3STmw3BkcAe6jOi/p4cgLpwHMkYHh2o8PmQ==", + "version": "2.6.51", + "resolved": "https://registry.npmjs.org/@fluentui/theme/-/theme-2.6.51.tgz", + "integrity": "sha512-N0zPdE+xNGZSuiVhsdJcWpSWESk2piaQIA+kaAfY/B71Y7cWfZyzZQEITrR4IBw8EYFgPNYf7Kixo0+46ivOwQ==", "dependencies": { - "@fluentui/merge-styles": "^8.5.13", - "@fluentui/set-version": "^8.2.12", - "@fluentui/utilities": "^8.13.21", + "@fluentui/merge-styles": "^8.6.8", + "@fluentui/set-version": "^8.2.21", + "@fluentui/utilities": "^8.15.8", "tslib": "^2.1.0" }, "peerDependencies": { @@ -3172,13 +3252,14 @@ } }, "node_modules/@fluentui/utilities": { - "version": "8.13.21", - "resolved": "https://registry.npmjs.org/@fluentui/utilities/-/utilities-8.13.21.tgz", - "integrity": "sha512-YPWsRAL1jgbPxf+wAY8p6LjIG4em0NReqgU8ZCFnQx9wpQbe/ZRjQcaU06pD1tYtRGvyCutwhnWDaQHDw843Xg==", - "dependencies": { - "@fluentui/dom-utilities": "^2.2.12", - "@fluentui/merge-styles": "^8.5.13", - "@fluentui/set-version": "^8.2.12", + "version": "8.15.8", + "resolved": "https://registry.npmjs.org/@fluentui/utilities/-/utilities-8.15.8.tgz", + "integrity": "sha512-FVURRfFXOi5WuyB6c58F+A6aHllbV2oa/FdjP8pBn/MX9p+Pwy54wXNXNagYvLJ4GmylFk73XssAoUE5zy3QpQ==", + "dependencies": { + "@fluentui/dom-utilities": "^2.3.5", + "@fluentui/merge-styles": "^8.6.8", + "@fluentui/react-window-provider": "^2.2.25", + "@fluentui/set-version": "^8.2.21", "tslib": "^2.1.0" }, "peerDependencies": { @@ -3231,6 +3312,90 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==" }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -3932,13 +4097,13 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dependencies": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" @@ -3953,9 +4118,9 @@ } }, "node_modules/@jridgewell/set-array": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.1.tgz", - "integrity": "sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "engines": { "node": ">=6.0.0" } @@ -3975,135 +4140,186 @@ "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.22", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", - "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@jsonjoy.com/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.0.4.tgz", + "integrity": "sha512-aOcSN4MeAtFROysrbqG137b7gaDDSmVrl5mpo6sT/w+kcXpWnzhMjmY/Fh/sDx26NBxyIE7MB1seqLeCAzy9Sg==", + "dependencies": { + "@jsonjoy.com/base64": "^1.1.1", + "@jsonjoy.com/util": "^1.1.2", + "hyperdyperid": "^1.2.0", + "thingies": "^1.20.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.1.3.tgz", + "integrity": "sha512-g//kkF4kOwUjemValCtOc/xiYzmwMRmWq3Bn+YnzOzuZLHq2PpMOxxIayN3cKbo7Ko2Np65t6D9H81IvXbXhqg==", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, "node_modules/@leichtgewicht/ip-codec": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", - "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==" + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==" }, "node_modules/@microsoft/applicationinsights-analytics-js": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-analytics-js/-/applicationinsights-analytics-js-3.0.5.tgz", - "integrity": "sha512-SUcBfC0zPT1Pm0PyWTbqZPYNYap6C4Rnub/umrpk62UvBz/X78o3gxng2zm5QMGMf9MJtzwb63K8pl+lYpknTQ==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-analytics-js/-/applicationinsights-analytics-js-3.2.1.tgz", + "integrity": "sha512-BDFnV0WwLcUIqgPmC3Sl8Z35ybf4898WSbrfCC80BNbxi2zztyJSLlxWXKSw9j+DCkKZMYIJIsWnpKQfEtqA7g==", "dependencies": { - "@microsoft/applicationinsights-common": "3.0.5", - "@microsoft/applicationinsights-core-js": "3.0.5", + "@microsoft/applicationinsights-common": "3.2.1", + "@microsoft/applicationinsights-core-js": "3.2.1", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-utils": ">= 0.11.1 < 2.x" }, "peerDependencies": { "tslib": "*" } }, "node_modules/@microsoft/applicationinsights-cfgsync-js": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-cfgsync-js/-/applicationinsights-cfgsync-js-3.0.5.tgz", - "integrity": "sha512-YQUWlw7vGj6JKmWLfSRtiiHQ0A3rIUmCO2K3i+ufGmH6oPPS5jrnQ8F0R9tJd82W0RxqeMmROW6KGcF7zyY1MQ==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-cfgsync-js/-/applicationinsights-cfgsync-js-3.2.1.tgz", + "integrity": "sha512-a0gf3czbRycOXwIRO/dYqmTThYkGRS26TVETRpSnW12IcAuLE82FJfWHlHRmSNX9xY/F+wWc0N7BqHcWjFIbeQ==", "dependencies": { - "@microsoft/applicationinsights-common": "3.0.5", - "@microsoft/applicationinsights-core-js": "3.0.5", + "@microsoft/applicationinsights-common": "3.2.1", + "@microsoft/applicationinsights-core-js": "3.2.1", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.1 < 2.x", + "@nevware21/ts-utils": ">= 0.11.1 < 2.x" }, "peerDependencies": { "tslib": "*" } }, "node_modules/@microsoft/applicationinsights-channel-js": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-3.0.5.tgz", - "integrity": "sha512-KfTYY0uZmrQgrz8ErBh1q08eiYfzjUIVzJZHETgEkqv3l2RTndQgpmywDbVNf9wVTB7Mp89ZrFeCciVJFf5geg==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-3.2.1.tgz", + "integrity": "sha512-+aWBBbIW4/Tf4sLGZmWhd5chktBpKQpnCbkuoTHGe+AWO8Q8fsDa4w2Y89OGuEg9OJ3kr2VKTUU7LgILKFz/cg==", "dependencies": { - "@microsoft/applicationinsights-common": "3.0.5", - "@microsoft/applicationinsights-core-js": "3.0.5", + "@microsoft/applicationinsights-common": "3.2.1", + "@microsoft/applicationinsights-core-js": "3.2.1", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.1 < 2.x", + "@nevware21/ts-utils": ">= 0.11.1 < 2.x" }, "peerDependencies": { "tslib": "*" } }, "node_modules/@microsoft/applicationinsights-common": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-3.0.5.tgz", - "integrity": "sha512-ahph1fMqyLcZ1twzDKMzpHRgR9zEIyqNhMQxDgQ45ieVD641bZiYVwSlbntSXhGCtr5G5HE02zlEzwSxbx95ng==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-3.2.1.tgz", + "integrity": "sha512-vRYQ1SIZJEz1eFbs2AQiLtev5L+zmjZ1Jkj3BWfIxJLd6n0cVR4NZETBSyMuk11KH7MIOrDLvh1CzjBIJIpDAg==", "dependencies": { - "@microsoft/applicationinsights-core-js": "3.0.5", + "@microsoft/applicationinsights-core-js": "3.2.1", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-utils": ">= 0.11.1 < 2.x" }, "peerDependencies": { "tslib": "*" } }, "node_modules/@microsoft/applicationinsights-core-js": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.0.5.tgz", - "integrity": "sha512-/x+tkxsVALNWSvwGMyaLwFPdD3p156Pef9WHftXrzrKkJ+685nhrwm9MqHIyEHHpSW09ElOdpJ3rfFVqpKRQyQ==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.2.1.tgz", + "integrity": "sha512-euxkDrF5BroAY7wgviaTVZdMvRAENQtUW4pDTsIjJK26shi1m5fPCc5l+vMn7kO2wQEaEgAOVw+/kSQgXDHN+Q==", "dependencies": { "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.1 < 2.x", + "@nevware21/ts-utils": ">= 0.11.1 < 2.x" }, "peerDependencies": { "tslib": "*" } }, "node_modules/@microsoft/applicationinsights-dependencies-js": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-dependencies-js/-/applicationinsights-dependencies-js-3.0.5.tgz", - "integrity": "sha512-awF9Qx6y9mP27Bc/W5NiMMBX12ujKfaDqAw/k0YISyvhHU66lxoaI/1Lnby5hRHYYil+jMtH6FHsstnB+DRS3Q==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-dependencies-js/-/applicationinsights-dependencies-js-3.2.1.tgz", + "integrity": "sha512-NZ7OY/7KYmJ6/TFsDZojsr9mA4uNv4ZTrNNXfWqtPxx0ClYalSm6Xyjna0N1d1wktrfecHe8K/ZrWJgxI2bfRw==", "dependencies": { - "@microsoft/applicationinsights-common": "3.0.5", - "@microsoft/applicationinsights-core-js": "3.0.5", + "@microsoft/applicationinsights-common": "3.2.1", + "@microsoft/applicationinsights-core-js": "3.2.1", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.1 < 2.x", + "@nevware21/ts-utils": ">= 0.11.1 < 2.x" }, "peerDependencies": { "tslib": "*" } }, "node_modules/@microsoft/applicationinsights-properties-js": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-properties-js/-/applicationinsights-properties-js-3.0.5.tgz", - "integrity": "sha512-e0Lo408vN5FjaW/6SIIWbeC5H4qTX1sd1qRKVpe2rnZICDNUk6Rane8tJ47yBsO2eBIn8vXPx8JKRK5249yYNg==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-properties-js/-/applicationinsights-properties-js-3.2.1.tgz", + "integrity": "sha512-GO96t7tQ1eEHPENVXjOlf91h/Iz8p4I0XayALTSshHMc+eDfsi1a/b2JYrfNDC4fanPU44Kmx3QZkXrBb1ri5A==", "dependencies": { - "@microsoft/applicationinsights-common": "3.0.5", - "@microsoft/applicationinsights-core-js": "3.0.5", + "@microsoft/applicationinsights-common": "3.2.1", + "@microsoft/applicationinsights-core-js": "3.2.1", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-utils": ">= 0.11.1 < 2.x" }, "peerDependencies": { "tslib": "*" } }, "node_modules/@microsoft/applicationinsights-react-js": { - "version": "17.0.3", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-react-js/-/applicationinsights-react-js-17.0.3.tgz", - "integrity": "sha512-M3N6MDuPN0OBWbtJ4uk+DSn+gwnN10eUSUAnYSJxBsaObx0EztgmcEXfkA9cbrHDBiWLkjHF9dFLYTirKVefBw==", + "version": "17.2.0", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-react-js/-/applicationinsights-react-js-17.2.0.tgz", + "integrity": "sha512-6rpdU6IGowr5grTdZ2bdNoWqg/G5bfXU4jicS8kssQD+Rpjy4zsbVsuz/BJfTys+0n7T0XtCuaGrZvl9+EvbbA==", "dependencies": { - "@microsoft/applicationinsights-common": "^3.0.7", - "@microsoft/applicationinsights-core-js": "^3.0.7", + "@microsoft/applicationinsights-common": "^3.2.0", + "@microsoft/applicationinsights-core-js": "^3.2.0", "@microsoft/applicationinsights-shims": "^3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-utils": ">= 0.10.5 < 2.x" }, "peerDependencies": { "history": ">= 4.10.1", @@ -4111,34 +4327,6 @@ "tslib": "*" } }, - "node_modules/@microsoft/applicationinsights-react-js/node_modules/@microsoft/applicationinsights-common": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-3.0.7.tgz", - "integrity": "sha512-boumvLA7LZu0NmwT9ThpTAI64BNYUlOkFNcjUbYeKNEaE6CBPGX/z25XXlYu+j4hHldDaCn9zC1LuN7AuoMJSA==", - "dependencies": { - "@microsoft/applicationinsights-core-js": "3.0.7", - "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" - }, - "peerDependencies": { - "tslib": "*" - } - }, - "node_modules/@microsoft/applicationinsights-react-js/node_modules/@microsoft/applicationinsights-core-js": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.0.7.tgz", - "integrity": "sha512-sVnnVW4fWXzZdtUTVjuwH3xGa1cj+tW7r72voMZzyuNOZ41fBOCK9AqoV0nKP5VCgNjySwn6Rpbw82I4TKKosQ==", - "dependencies": { - "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" - }, - "peerDependencies": { - "tslib": "*" - } - }, "node_modules/@microsoft/applicationinsights-shims": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-shims/-/applicationinsights-shims-3.0.1.tgz", @@ -4148,32 +4336,32 @@ } }, "node_modules/@microsoft/applicationinsights-web": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web/-/applicationinsights-web-3.0.5.tgz", - "integrity": "sha512-+jyOGj+pMxTxLBv9pU5zhOqSaYfn7CTN86y9rzZu8CoE3eibNIZFvliICTpLX++1/lYvJgUd1OP8ZGp+xb4JYQ==", - "dependencies": { - "@microsoft/applicationinsights-analytics-js": "3.0.5", - "@microsoft/applicationinsights-cfgsync-js": "3.0.5", - "@microsoft/applicationinsights-channel-js": "3.0.5", - "@microsoft/applicationinsights-common": "3.0.5", - "@microsoft/applicationinsights-core-js": "3.0.5", - "@microsoft/applicationinsights-dependencies-js": "3.0.5", - "@microsoft/applicationinsights-properties-js": "3.0.5", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web/-/applicationinsights-web-3.2.1.tgz", + "integrity": "sha512-+U0wsifFEmvdT2hckmsoNcMx/SQrAA8qBBM7y0zefT03wHbFh0jCL/kd2MFOyKZ39v9C4uIO5L8Ftu4hBpnYWA==", + "dependencies": { + "@microsoft/applicationinsights-analytics-js": "3.2.1", + "@microsoft/applicationinsights-cfgsync-js": "3.2.1", + "@microsoft/applicationinsights-channel-js": "3.2.1", + "@microsoft/applicationinsights-common": "3.2.1", + "@microsoft/applicationinsights-core-js": "3.2.1", + "@microsoft/applicationinsights-dependencies-js": "3.2.1", + "@microsoft/applicationinsights-properties-js": "3.2.1", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.1 < 2.x", + "@nevware21/ts-utils": ">= 0.11.1 < 2.x" }, "peerDependencies": { "tslib": "*" } }, "node_modules/@microsoft/dynamicproto-js": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.2.tgz", - "integrity": "sha512-MB8trWaFREpmb037k/d0bB7T2BP7Ai24w1e1tbz3ASLB0/lwphsq3Nq8S9I5AsI5vs4zAQT+SB5nC5/dLYTiOg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.3.tgz", + "integrity": "sha512-JTWTU80rMy3mdxOjjpaiDQsTLZ6YSGGqsjURsY6AUQtIj0udlF/jYmhdLZu8693ZIC0T1IwYnFa0+QeiMnziBA==", "dependencies": { - "@nevware21/ts-utils": ">= 0.9.4 < 2.x" + "@nevware21/ts-utils": ">= 0.10.4 < 2.x" } }, "node_modules/@microsoft/load-themed-styles": { @@ -4254,23 +4442,31 @@ "whatwg-fetch": "^3.0.0" } }, + "node_modules/@ms-ofb/officebrowserfeedbacknpm/node_modules/@ms-ofb/officefloodgatecore": { + "version": "0.0.0", + "extraneous": true, + "inBundle": true, + "dependencies": { + "es6-promise": "4.2.8" + } + }, "node_modules/@ms-ofb/officebrowserfeedbacknpm/node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@nevware21/ts-async": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@nevware21/ts-async/-/ts-async-0.3.0.tgz", - "integrity": "sha512-ZUcgUH12LN/F6nzN0cYd0F/rJaMLmXr0EHVTyYfaYmK55bdwE4338uue4UiVoRqHVqNW4KDUrJc49iGogHKeWA==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@nevware21/ts-async/-/ts-async-0.5.1.tgz", + "integrity": "sha512-O2kN8n2HpDWJ7Oji+oTMnhITrCndmrNvrHbGDwAIBydx+FWvLE/vrw4QwnRRMvSCa2AJrcP59Ryklxv30KfkWQ==", "dependencies": { - "@nevware21/ts-utils": ">= 0.10.0 < 2.x" + "@nevware21/ts-utils": ">= 0.11.2 < 2.x" } }, "node_modules/@nevware21/ts-utils": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.10.1.tgz", - "integrity": "sha512-pMny25NnF2/MJwdqC3Iyjm2pGIXNxni4AROpcqDeWa+td9JMUY4bUS9uU9XW+BoBRqTLUL+WURF9SOd/6OQzRg==" + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.11.2.tgz", + "integrity": "sha512-80W8BkS09kkGuUHJX50Fqq+QqAslxUaOQytH+3JhRacXs1EpEt2JOOkYKytqFZAYir3SeH9fahniEaDzIBxlUw==" }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", @@ -4304,13 +4500,22 @@ "node": ">= 8" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@playwright/test": { - "version": "1.42.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.42.0.tgz", - "integrity": "sha512-2k1HzC28Fs+HiwbJOQDUwrWMttqSLUVdjCqitBOjdCD0svWOMQUVqrXX6iFD7POps6xXAojsX/dGBpKnjZctLA==", + "version": "1.43.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.43.1.tgz", + "integrity": "sha512-HgtQzFgNEEo4TE22K/X7sYTYNqEMMTZmFS8kTq6m8hXj+m1D8TgwgIbumHddJa9h4yl4GkKb8/bgAl2+g7eDgA==", "dev": true, "dependencies": { - "playwright": "1.42.0" + "playwright": "1.43.1" }, "bin": { "playwright": "cli.js" @@ -4464,9 +4669,9 @@ } }, "node_modules/@swc/helpers": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.2.tgz", - "integrity": "sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==", + "version": "0.5.11", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.11.tgz", + "integrity": "sha512-YNlnKRWF2sVojTpIyzwou9XoTNbzbzONwRhOoniEioF1AtaitTvVZblaQRrAzChWQ1bLYyYSWzM18y4WwgzJ+A==", "dependencies": { "tslib": "^2.4.0" } @@ -4548,9 +4753,9 @@ } }, "node_modules/@types/bonjour": { - "version": "3.5.10", - "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.10.tgz", - "integrity": "sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", "dependencies": { "@types/node": "*" } @@ -4578,18 +4783,18 @@ } }, "node_modules/@types/connect-history-api-fallback": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz", - "integrity": "sha512-4x5FkPpLipqwthjPsF7ZRbOv3uoLUFkTA9G9v583qi4pACvq0uTELrB8OLUzPWUI4IJIyvM85vzkV1nyiI2Lig==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", "dependencies": { "@types/express-serve-static-core": "*", "@types/node": "*" } }, "node_modules/@types/eslint": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.37.0.tgz", - "integrity": "sha512-Piet7dG2JBuDIfohBngQ3rCt7MgO9xCO4xIMKxBThCq5PNRB91IjlJ10eJVwfoNtvTErmxLzwBZ7rHZtbOMmFQ==", + "version": "8.56.10", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", + "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==", "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -4610,9 +4815,9 @@ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" }, "node_modules/@types/express": { - "version": "4.17.17", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", - "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", @@ -4787,6 +4992,14 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.33.tgz", "integrity": "sha512-miWq2m2FiQZmaHfdZNcbpp9PuXg34W5JZ5CrJ/BaS70VuhoJENBEQybeiYSaPBRNq6KQGnjfEnc/F3PN++D+XQ==" }, + "node_modules/@types/node-forge": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", + "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", @@ -4864,9 +5077,9 @@ } }, "node_modules/@types/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", + "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==" }, "node_modules/@types/scheduler": { "version": "0.16.2", @@ -4897,27 +5110,27 @@ } }, "node_modules/@types/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==", + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", "dependencies": { "@types/express": "*" } }, "node_modules/@types/serve-static": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.2.tgz", - "integrity": "sha512-J2LqtvFYCzaj8pVYKw8klQXrLLk7TBZmQ4ShlcdkELFKGwGMfevMLneMMRkMgZxotOD9wg497LpC7O8PcvAmfw==", + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", "dependencies": { "@types/http-errors": "*", - "@types/mime": "*", - "@types/node": "*" + "@types/node": "*", + "@types/send": "*" } }, "node_modules/@types/sockjs": { - "version": "0.3.33", - "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", - "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", "dependencies": { "@types/node": "*" } @@ -4945,10 +5158,9 @@ "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" }, "node_modules/@types/ws": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", - "integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==", - "dev": true, + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", + "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", "dependencies": { "@types/node": "*" } @@ -4982,16 +5194,16 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.20.0.tgz", - "integrity": "sha512-fTwGQUnjhoYHeSF6m5pWNkzmDDdsKELYrOBxhjMrofPqCkoC2k3B2wvGHFxa1CTIqkEn88nlW1HVMztjo2K8Hg==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.0.0.tgz", + "integrity": "sha512-M72SJ0DkcQVmmsbqlzc6EJgb/3Oz2Wdm6AyESB4YkGgCxP8u5jt5jn4/OBMPK3HLOxcttZq5xbBBU7e2By4SZQ==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.20.0", - "@typescript-eslint/type-utils": "6.20.0", - "@typescript-eslint/utils": "6.20.0", - "@typescript-eslint/visitor-keys": "6.20.0", + "@typescript-eslint/scope-manager": "7.0.0", + "@typescript-eslint/type-utils": "7.0.0", + "@typescript-eslint/utils": "7.0.0", + "@typescript-eslint/visitor-keys": "7.0.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -5008,7 +5220,7 @@ }, "peerDependencies": { "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -5017,9 +5229,9 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.20.0.tgz", - "integrity": "sha512-MM9mfZMAhiN4cOEcUOEx+0HmuaW3WBfukBZPCfwSqFnQy0grXYtngKCqpQN339X3RrwtzspWJrpbrupKYUSBXQ==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.0.0.tgz", + "integrity": "sha512-9ZIJDqagK1TTs4W9IyeB2sH/s1fFhN9958ycW8NRTg1vXGzzH5PQNzq6KbsbVGMT+oyyfa17DfchHDidcmf5cg==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -5030,13 +5242,13 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/typescript-estree": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.20.0.tgz", - "integrity": "sha512-RnRya9q5m6YYSpBN7IzKu9FmLcYtErkDkc8/dKv81I9QiLLtVBHrjz+Ev/crAqgMNW2FCsoZF4g2QUylMnJz+g==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.0.0.tgz", + "integrity": "sha512-JzsOzhJJm74aQ3c9um/aDryHgSHfaX8SHFIu9x4Gpik/+qxLvxUylhTsO9abcNu39JIdhY2LgYrFxTii3IajLA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.20.0", - "@typescript-eslint/visitor-keys": "6.20.0", + "@typescript-eslint/types": "7.0.0", + "@typescript-eslint/visitor-keys": "7.0.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -5058,17 +5270,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.20.0.tgz", - "integrity": "sha512-/EKuw+kRu2vAqCoDwDCBtDRU6CTKbUmwwI7SH7AashZ+W+7o8eiyy6V2cdOqN49KsTcASWsC5QeghYuRDTyOOg==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.0.0.tgz", + "integrity": "sha512-kuPZcPAdGcDBAyqDn/JVeJVhySvpkxzfXjJq1X1BFSTYo1TTuo4iyb937u457q4K0In84p6u2VHQGaFnv7VYqg==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.20.0", - "@typescript-eslint/types": "6.20.0", - "@typescript-eslint/typescript-estree": "6.20.0", + "@typescript-eslint/scope-manager": "7.0.0", + "@typescript-eslint/types": "7.0.0", + "@typescript-eslint/typescript-estree": "7.0.0", "semver": "^7.5.4" }, "engines": { @@ -5079,16 +5291,16 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.20.0.tgz", - "integrity": "sha512-E8Cp98kRe4gKHjJD4NExXKz/zOJ1A2hhZc+IMVD6i7w4yjIvh6VyuRI0gRtxAsXtoC35uGMaQ9rjI2zJaXDEAw==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.0.0.tgz", + "integrity": "sha512-JZP0uw59PRHp7sHQl3aF/lFgwOW2rgNVnXUksj1d932PMita9wFBd3621vHQRDvHwPsSY9FMAAHVc8gTvLYY4w==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.20.0", + "@typescript-eslint/types": "7.0.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -5293,13 +5505,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.20.0.tgz", - "integrity": "sha512-p4rvHQRDTI1tGGMDFQm+GtxP1ZHyAh64WANVoyEcNMpaTFn3ox/3CcgtIlELnRfKzSs/DwYlDccJEtr3O6qBvA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.0.0.tgz", + "integrity": "sha512-IxTStwhNDPO07CCrYuAqjuJ3Xf5MrMaNgbAZPxFXAUpAtwqFxiuItxUaVtP/SJQeCdJjwDGh9/lMOluAndkKeg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.20.0", - "@typescript-eslint/visitor-keys": "6.20.0" + "@typescript-eslint/types": "7.0.0", + "@typescript-eslint/visitor-keys": "7.0.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -5310,9 +5522,9 @@ } }, "node_modules/@typescript-eslint/scope-manager/node_modules/@typescript-eslint/types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.20.0.tgz", - "integrity": "sha512-MM9mfZMAhiN4cOEcUOEx+0HmuaW3WBfukBZPCfwSqFnQy0grXYtngKCqpQN339X3RrwtzspWJrpbrupKYUSBXQ==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.0.0.tgz", + "integrity": "sha512-9ZIJDqagK1TTs4W9IyeB2sH/s1fFhN9958ycW8NRTg1vXGzzH5PQNzq6KbsbVGMT+oyyfa17DfchHDidcmf5cg==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -5323,12 +5535,12 @@ } }, "node_modules/@typescript-eslint/scope-manager/node_modules/@typescript-eslint/visitor-keys": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.20.0.tgz", - "integrity": "sha512-E8Cp98kRe4gKHjJD4NExXKz/zOJ1A2hhZc+IMVD6i7w4yjIvh6VyuRI0gRtxAsXtoC35uGMaQ9rjI2zJaXDEAw==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.0.0.tgz", + "integrity": "sha512-JZP0uw59PRHp7sHQl3aF/lFgwOW2rgNVnXUksj1d932PMita9wFBd3621vHQRDvHwPsSY9FMAAHVc8gTvLYY4w==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.20.0", + "@typescript-eslint/types": "7.0.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -5352,13 +5564,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.20.0.tgz", - "integrity": "sha512-qnSobiJQb1F5JjN0YDRPHruQTrX7ICsmltXhkV536mp4idGAYrIyr47zF/JmkJtEcAVnIz4gUYJ7gOZa6SmN4g==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.0.0.tgz", + "integrity": "sha512-FIM8HPxj1P2G7qfrpiXvbHeHypgo2mFpFGoh5I73ZlqmJOsloSa1x0ZyXCer43++P1doxCgNqIOLqmZR6SOT8g==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.20.0", - "@typescript-eslint/utils": "6.20.0", + "@typescript-eslint/typescript-estree": "7.0.0", + "@typescript-eslint/utils": "7.0.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -5370,7 +5582,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -5379,9 +5591,9 @@ } }, "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.20.0.tgz", - "integrity": "sha512-MM9mfZMAhiN4cOEcUOEx+0HmuaW3WBfukBZPCfwSqFnQy0grXYtngKCqpQN339X3RrwtzspWJrpbrupKYUSBXQ==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.0.0.tgz", + "integrity": "sha512-9ZIJDqagK1TTs4W9IyeB2sH/s1fFhN9958ycW8NRTg1vXGzzH5PQNzq6KbsbVGMT+oyyfa17DfchHDidcmf5cg==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -5392,13 +5604,13 @@ } }, "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.20.0.tgz", - "integrity": "sha512-RnRya9q5m6YYSpBN7IzKu9FmLcYtErkDkc8/dKv81I9QiLLtVBHrjz+Ev/crAqgMNW2FCsoZF4g2QUylMnJz+g==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.0.0.tgz", + "integrity": "sha512-JzsOzhJJm74aQ3c9um/aDryHgSHfaX8SHFIu9x4Gpik/+qxLvxUylhTsO9abcNu39JIdhY2LgYrFxTii3IajLA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.20.0", - "@typescript-eslint/visitor-keys": "6.20.0", + "@typescript-eslint/types": "7.0.0", + "@typescript-eslint/visitor-keys": "7.0.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -5420,17 +5632,17 @@ } }, "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/utils": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.20.0.tgz", - "integrity": "sha512-/EKuw+kRu2vAqCoDwDCBtDRU6CTKbUmwwI7SH7AashZ+W+7o8eiyy6V2cdOqN49KsTcASWsC5QeghYuRDTyOOg==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.0.0.tgz", + "integrity": "sha512-kuPZcPAdGcDBAyqDn/JVeJVhySvpkxzfXjJq1X1BFSTYo1TTuo4iyb937u457q4K0In84p6u2VHQGaFnv7VYqg==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.20.0", - "@typescript-eslint/types": "6.20.0", - "@typescript-eslint/typescript-estree": "6.20.0", + "@typescript-eslint/scope-manager": "7.0.0", + "@typescript-eslint/types": "7.0.0", + "@typescript-eslint/typescript-estree": "7.0.0", "semver": "^7.5.4" }, "engines": { @@ -5441,16 +5653,16 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" } }, "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.20.0.tgz", - "integrity": "sha512-E8Cp98kRe4gKHjJD4NExXKz/zOJ1A2hhZc+IMVD6i7w4yjIvh6VyuRI0gRtxAsXtoC35uGMaQ9rjI2zJaXDEAw==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.0.0.tgz", + "integrity": "sha512-JZP0uw59PRHp7sHQl3aF/lFgwOW2rgNVnXUksj1d932PMita9wFBd3621vHQRDvHwPsSY9FMAAHVc8gTvLYY4w==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.20.0", + "@typescript-eslint/types": "7.0.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -5609,9 +5821,9 @@ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" }, "node_modules/@webassemblyjs/ast": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", - "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", + "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", "dependencies": { "@webassemblyjs/helper-numbers": "1.11.6", "@webassemblyjs/helper-wasm-bytecode": "1.11.6" @@ -5628,9 +5840,9 @@ "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==" }, "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", - "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==" + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", + "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==" }, "node_modules/@webassemblyjs/helper-numbers": { "version": "1.11.6", @@ -5648,14 +5860,14 @@ "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==" }, "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", - "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", + "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6" + "@webassemblyjs/wasm-gen": "1.12.1" } }, "node_modules/@webassemblyjs/ieee754": { @@ -5680,26 +5892,26 @@ "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==" }, "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", - "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", + "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-opt": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6", - "@webassemblyjs/wast-printer": "1.11.6" + "@webassemblyjs/helper-wasm-section": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-opt": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1", + "@webassemblyjs/wast-printer": "1.12.1" } }, "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", - "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", + "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", "dependencies": { - "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/ast": "1.12.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", "@webassemblyjs/ieee754": "1.11.6", "@webassemblyjs/leb128": "1.11.6", @@ -5707,22 +5919,22 @@ } }, "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", - "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", + "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1" } }, "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", - "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", + "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", "dependencies": { - "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/ast": "1.12.1", "@webassemblyjs/helper-api-error": "1.11.6", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", "@webassemblyjs/ieee754": "1.11.6", @@ -5731,11 +5943,11 @@ } }, "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", - "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", + "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", "dependencies": { - "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/ast": "1.12.1", "@xtuc/long": "4.2.2" } }, @@ -5822,9 +6034,9 @@ } }, "node_modules/adaptive-expressions": { - "version": "4.21.4", - "resolved": "https://registry.npmjs.org/adaptive-expressions/-/adaptive-expressions-4.21.4.tgz", - "integrity": "sha512-SOhILVrjPXPjdFHNNLUsWID9KQCYw7uyalN7EIiQplW99w8KuNc0SE0uonRD5X2lwOXj3s2QZFhrzxAKIeC2WQ==", + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/adaptive-expressions/-/adaptive-expressions-4.22.2.tgz", + "integrity": "sha512-isI6hyqzsUb/hTXuYQSM8ATbTnFhyX3KZzzoMYbrBYcGd8KYT3Fke3WG3Ayz9AaWzXe5R1YAJ9laYYxEJ3Yg/w==", "dependencies": { "@microsoft/recognizers-text-data-types-timex-expression": "1.3.0", "@types/atob-lite": "^2.0.0", @@ -6184,9 +6396,9 @@ "integrity": "sha512-LEeSAWeh2Gfa2FtlQE1shxQ8zi5F9GHarrGKz08TMdODD5T4eH6BMsvtnhbWZ+XQn+Gb6om/917ucvRu7l7ukw==" }, "node_modules/autoprefixer": { - "version": "10.4.16", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz", - "integrity": "sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==", + "version": "10.4.19", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", + "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", "funding": [ { "type": "opencollective", @@ -6202,9 +6414,9 @@ } ], "dependencies": { - "browserslist": "^4.21.10", - "caniuse-lite": "^1.0.30001538", - "fraction.js": "^4.3.6", + "browserslist": "^4.23.0", + "caniuse-lite": "^1.0.30001599", + "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.0.0", "postcss-value-parser": "^4.2.0" @@ -6625,21 +6837,14 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/bonjour-service": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.1.1.tgz", - "integrity": "sha512-Z/5lQRMOG9k7W+FkeGTNjh7htqn/2LMnfOvBZ8pynNZCM9MwkQkI3zeI4oz09uWdcgmgHugVvBqxGg4VQJ5PCg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.2.1.tgz", + "integrity": "sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw==", "dependencies": { - "array-flatten": "^2.1.2", - "dns-equal": "^1.0.0", "fast-deep-equal": "^3.1.3", "multicast-dns": "^7.2.5" } }, - "node_modules/bonjour-service/node_modules/array-flatten": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", - "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==" - }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -6685,9 +6890,9 @@ } }, "node_modules/browserslist": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", - "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", "funding": [ { "type": "opencollective", @@ -6703,9 +6908,9 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001541", - "electron-to-chromium": "^1.4.535", - "node-releases": "^2.0.13", + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", + "node-releases": "^2.0.14", "update-browserslist-db": "^1.0.13" }, "bin": { @@ -6766,6 +6971,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -6820,9 +7039,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001541", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001541.tgz", - "integrity": "sha512-bLOsqxDgTqUBkzxbNlSBt8annkDpQB9NdzdTbO2ooJ+eC/IQcvDspDc058g84ejCelF7vHUx57KIOjEecOHXaw==", + "version": "1.0.30001620", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001620.tgz", + "integrity": "sha512-WJvYsOjd1/BYUY6SNGUosK9DUidBPDTnOARHp3fSmFO1ekdxaY6nKRttEVrfMmYi80ctS0kz1wiWmm14fVc3ew==", "funding": [ { "type": "opencollective", @@ -6891,15 +7110,9 @@ "integrity": "sha512-+67P1GkJRaxQD6PKK0Et9DhwQB+vGg3PM5+aavopCpZT1lj9jeqfvpgTLAWErNj8qApkkmXlu/Ug74kmhagkXg==" }, "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -6912,6 +7125,9 @@ "engines": { "node": ">= 8.10.0" }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, "optionalDependencies": { "fsevents": "~2.3.2" } @@ -6925,9 +7141,9 @@ } }, "node_modules/chromedriver": { - "version": "122.0.4", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-122.0.4.tgz", - "integrity": "sha512-MxkaWaxCqefHyh9UorGzl1F6ZNBgC7pqgT0piAysLZdw20ojSgJ62ljG8SFbhDJqBTegKbmuioa6MQ1m4Czdsg==", + "version": "124.0.1", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-124.0.1.tgz", + "integrity": "sha512-hxd1tpAUhgMFBZd1h3W7KyMckxofOYCuKAMtcvBDAU0YKKorZcWuq6zP06+Ph0Z1ynPjtgAj0hP9VphCwesjZw==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -7361,9 +7577,9 @@ } }, "node_modules/css-blank-pseudo": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-6.0.0.tgz", - "integrity": "sha512-VbfLlOWO7sBHBTn6pwDQzc07Z0SDydgDBfNfCE0nvrehdBNv9RKsuupIRa/qal0+fBZhAALyQDPMKz5lnvcchw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-6.0.2.tgz", + "integrity": "sha512-J/6m+lsqpKPqWHOifAFtKFeGLOzw3jR92rxQcwRUfA/eTuZzKfKlxOmYDx2+tqOPQAueNvBiY8WhAeHu5qNmTg==", "funding": [ { "type": "github", @@ -7385,9 +7601,9 @@ } }, "node_modules/css-has-pseudo": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-6.0.0.tgz", - "integrity": "sha512-X+r+JBuoO37FBOWVNhVJhxtSBUFHgHbrcc0CjFT28JEdOw1qaDwABv/uunyodUuSy2hMPe9j/HjssxSlvUmKjg==", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-6.0.5.tgz", + "integrity": "sha512-ZTv6RlvJJZKp32jPYnAJVhowDCrRrHUTAxsYSuUPBEDJjzws6neMnzkRblxtgmv1RgcV5dhH2gn7E3wA9Wt6lw==", "funding": [ { "type": "github", @@ -7399,7 +7615,7 @@ } ], "dependencies": { - "@csstools/selector-specificity": "^3.0.0", + "@csstools/selector-specificity": "^3.1.1", "postcss-selector-parser": "^6.0.13", "postcss-value-parser": "^4.2.0" }, @@ -7445,9 +7661,9 @@ } }, "node_modules/css-prefers-color-scheme": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-9.0.0.tgz", - "integrity": "sha512-03QGAk/FXIRseDdLb7XAiu6gidQ0Nd8945xuM7VFVPpc6goJsG9uIO8xQjTxwbPdPIIV4o4AJoOJyt8gwDl67g==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-9.0.1.tgz", + "integrity": "sha512-iFit06ochwCKPRiWagbTa1OAWCvWWVdEnIFd8BaRrgO8YrrNh4RAWUQTFcYX5tdFZgFl1DJ3iiULchZyEbnF4g==", "funding": [ { "type": "github", @@ -7494,9 +7710,9 @@ } }, "node_modules/cssdb": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-7.9.0.tgz", - "integrity": "sha512-WPMT9seTQq6fPAa1yN4zjgZZeoTriSN2LqW9C+otjar12DQIWA4LuSfFrvFJiKp4oD0xIk1vumDLw8K9ur4NBw==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-8.0.1.tgz", + "integrity": "sha512-diegY/vnOYmPXY0bOBj5jeHaiK8MMpjgPuipirY8pF9AthtqEXgqVdKF5tnb6RTc/ZdhQqG0TBnInQ5CbbUW7Q==", "funding": [ { "type": "opencollective", @@ -7681,6 +7897,32 @@ "node": ">=0.10.0" } }, + "node_modules/default-browser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", + "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", + "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/default-gateway": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", @@ -7712,6 +7954,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, "engines": { "node": ">=8" } @@ -7838,15 +8081,10 @@ "node": ">=8" } }, - "node_modules/dns-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", - "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=" - }, "node_modules/dns-packet": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.0.tgz", - "integrity": "sha512-rza3UH1LwdHh9qyPXp8lkwpjSNk/AMD3dPytUoRoqnypDUhY0xvbdmVhWOfxO68frEfV9BU8V12Ez7ZsHGZpCQ==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", "dependencies": { "@leichtgewicht/ip-codec": "^2.0.1" }, @@ -8000,6 +8238,11 @@ "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", "dev": true }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -8020,9 +8263,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.573", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.573.tgz", - "integrity": "sha512-tzxxvKDTO3V5vzN2F+3v9jrK9gEbCdf1YYJUx/zVq1cyzyh+x1ddeYNNWh0ZS2ETNCVK3+Pns1LHIBq4w20X2Q==" + "version": "1.4.772", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.772.tgz", + "integrity": "sha512-jFfEbxR/abTTJA3ci+2ok1NTuOBBtB4jH+UT6PUmRN+DY3WSD4FFRsgoVQ+QNIJ0T7wrXwzsWCI2WKC46b++2A==" }, "node_modules/emittery": { "version": "0.13.1", @@ -8067,9 +8310,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", - "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", + "version": "5.16.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.1.tgz", + "integrity": "sha512-4U5pNsuDl0EhuZpq46M5xPslstkviJuhrdobaRDBk2Jy2KO37FDAJl4lb2KlNabxT0m4MTK2UHNrsAcphE8nyw==", "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -8848,15 +9091,15 @@ } }, "node_modules/eslint-webpack-plugin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/eslint-webpack-plugin/-/eslint-webpack-plugin-4.0.1.tgz", - "integrity": "sha512-fUFcXpui/FftGx3NzvWgLZXlLbu+m74sUxGEgxgoxYcUtkIQbS6SdNNZkS99m5ycb23TfoNYrDpp1k/CK5j6Hw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-webpack-plugin/-/eslint-webpack-plugin-4.1.0.tgz", + "integrity": "sha512-C3wAG2jyockIhN0YRLuKieKj2nx/gnE/VHmoHemD5ifnAtY6ZU+jNPfzPoX4Zd6RIbUyWTiZUh/ofUlBhoAX7w==", "dependencies": { - "@types/eslint": "^8.37.0", - "jest-worker": "^29.5.0", + "@types/eslint": "^8.56.5", + "jest-worker": "^29.7.0", "micromatch": "^4.0.5", "normalize-path": "^3.0.0", - "schema-utils": "^4.0.0" + "schema-utils": "^4.2.0" }, "engines": { "node": ">= 14.15.0" @@ -8879,12 +9122,12 @@ } }, "node_modules/eslint-webpack-plugin/node_modules/jest-worker": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.5.0.tgz", - "integrity": "sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", "dependencies": { "@types/node": "*", - "jest-util": "^29.5.0", + "jest-util": "^29.7.0", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" }, @@ -9918,6 +10161,32 @@ "is-callable": "^1.1.3" } }, + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/fork-ts-checker-webpack-plugin": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-9.0.2.tgz", @@ -10131,9 +10400,9 @@ } }, "node_modules/fraction.js": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.6.tgz", - "integrity": "sha512-n2aZ9tNfYDwaHhvFTkhFErqOMIb8uyzSQ+vGJBjZyanAKZVbGUQ1sngfk9FdkBw7G26O7AgNjLcecLffD1c7eg==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", "engines": { "node": "*" }, @@ -10170,9 +10439,9 @@ } }, "node_modules/fs-monkey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz", - "integrity": "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==" + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.6.tgz", + "integrity": "sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==" }, "node_modules/fs.realpath": { "version": "1.0.0", @@ -10441,9 +10710,9 @@ } }, "node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "node_modules/graphemer": { "version": "1.4.0", @@ -10835,6 +11104,14 @@ "node": ">=10.17.0" } }, + "node_modules/hyperdyperid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", + "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", + "engines": { + "node": ">=10.18" + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -11110,6 +11387,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, "bin": { "is-docker": "cli.js" }, @@ -11143,7 +11421,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "engines": { "node": ">=8" } @@ -11182,6 +11459,37 @@ "node": ">=0.10.0" } }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container/node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-map": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", @@ -11206,6 +11514,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-network-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.1.0.tgz", + "integrity": "sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g==", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -11406,6 +11725,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, "dependencies": { "is-docker": "^2.0.0" }, @@ -11554,6 +11874,23 @@ "set-function-name": "^2.0.1" } }, + "node_modules/jackspeak": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.1.2.tgz", + "integrity": "sha512-kWmLKn2tRtfYMF/BakihVVRzBKOxz4gJMiL2Rj91WnAB5TPZumSH99R/Yf1qE1u4uRimvCSJfm6hnxohXeEXjQ==", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jake": { "version": "10.8.7", "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz", @@ -13800,12 +14137,12 @@ } }, "node_modules/launch-editor": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.6.0.tgz", - "integrity": "sha512-JpDCcQnyAAzZZaZ7vEiSqL690w7dAEyLao+KC96zBplnYbJS7TYNjvM3M7y3dGz+v7aIsJk3hllWuc0kWAjyRQ==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.6.1.tgz", + "integrity": "sha512-eB/uXmFVpY4zezmGp5XtU21kwo7GBbKB+EQ+UZeWtGb9yAM5xt/Evk+lYH3eRNAtId+ej4u7TYPFZ07w4s7rRw==", "dependencies": { "picocolors": "^1.0.0", - "shell-quote": "^1.7.3" + "shell-quote": "^1.8.1" } }, "node_modules/lazy-ass": { @@ -14009,11 +14346,11 @@ } }, "node_modules/memfs": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.1.tgz", - "integrity": "sha512-1c9VPVvW5P7I85c35zAdEr1TD5+F11IToIHIlrVIcflfnzPkJa0ZoYEoEdYDP8KgPFoSZ/opDrUsAoZWym3mtw==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", + "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", "dependencies": { - "fs-monkey": "1.0.3" + "fs-monkey": "^1.0.4" }, "engines": { "node": ">= 4.0.0" @@ -14096,9 +14433,9 @@ } }, "node_modules/mini-css-extract-plugin": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.8.1.tgz", - "integrity": "sha512-/1HDlyFRxWIZPI1ZpgqlZ8jMw/1Dp/dl3P0L1jtZ+zVcHqwPhGwaJwKL00WVgfnBy6PWCde9W65or7IIETImuA==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.0.tgz", + "integrity": "sha512-Zs1YsZVfemekSZG+44vBsYTLQORkPMwnlv+aehcxK/NLKC+EGhDB39/YePYYqx/sTk6NnYpuqikhSn7+JIevTA==", "dependencies": { "schema-utils": "^4.0.0", "tapable": "^2.2.1" @@ -14138,6 +14475,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/monaco-editor": { "version": "0.30.1", "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.30.1.tgz", @@ -14299,9 +14644,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==" + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" }, "node_modules/normalize-path": { "version": "3.0.0", @@ -14472,6 +14817,7 @@ "version": "8.4.0", "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==", + "dev": true, "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", @@ -14528,15 +14874,19 @@ } }, "node_modules/p-retry": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", - "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.0.tgz", + "integrity": "sha512-JA6nkq6hKyWLLasXQXUrO4z8BUZGUt/LjlJxx8Gb2+2ntodU/SS63YZ8b0LUTbQ8ZB9iwOfhEPhg4ykKnn2KsA==", "dependencies": { - "@types/retry": "0.12.0", + "@types/retry": "0.12.2", + "is-network-error": "^1.0.0", "retry": "^0.13.1" }, "engines": { - "node": ">=8" + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-try": { @@ -14708,6 +15058,29 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", + "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", + "engines": { + "node": "14 || >=16.14" + } + }, "node_modules/path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", @@ -14837,12 +15210,12 @@ } }, "node_modules/playwright": { - "version": "1.42.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.42.0.tgz", - "integrity": "sha512-Ko7YRUgj5xBHbntrgt4EIw/nE//XBHOKVKnBjO1KuZkmkhlbgyggTe5s9hjqQ1LpN+Xg+kHsQyt5Pa0Bw5XpvQ==", + "version": "1.43.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.43.1.tgz", + "integrity": "sha512-V7SoH0ai2kNt1Md9E3Gwas5B9m8KR2GVvwZnAI6Pg0m3sh7UvgiYhRrhsziCmqMJNouPckiOhk8T+9bSAK0VIA==", "dev": true, "dependencies": { - "playwright-core": "1.42.0" + "playwright-core": "1.43.1" }, "bin": { "playwright": "cli.js" @@ -14855,9 +15228,9 @@ } }, "node_modules/playwright-core": { - "version": "1.42.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.42.0.tgz", - "integrity": "sha512-0HD9y8qEVlcbsAjdpBaFjmaTHf+1FeIddy8VJLeiqwhcNqGCBe4Wp2e8knpqiYbzxtxarxiXyNDw2cG8sCaNMQ==", + "version": "1.43.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.43.1.tgz", + "integrity": "sha512-EI36Mto2Vrx6VF7rm708qSnesVQKbxEWvPrfA1IPY6HgczBplDx7ENtx+K2n4kJ41sLLkuGfmb0ZLSSXlDhqPg==", "dev": true, "bin": { "playwright-core": "cli.js" @@ -14894,19 +15267,25 @@ } }, "node_modules/postcss-attribute-case-insensitive": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-6.0.2.tgz", - "integrity": "sha512-IRuCwwAAQbgaLhxQdQcIIK0dCVXg3XDUnzgKD8iwdiYdwU4rMWRWyl/W9/0nA4ihVpq5pyALiHB2veBJ0292pw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-6.0.3.tgz", + "integrity": "sha512-KHkmCILThWBRtg+Jn1owTnHPnFit4OkqS+eKiGEOPIGke54DCeYGJ6r0Fx/HjfE9M9kznApCLcU0DvnPchazMQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "dependencies": { - "postcss-selector-parser": "^6.0.10" + "postcss-selector-parser": "^6.0.13" }, "engines": { "node": "^14 || ^16 || >=18" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, "peerDependencies": { "postcss": "^8.4" } @@ -14926,9 +15305,9 @@ } }, "node_modules/postcss-color-functional-notation": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-6.0.2.tgz", - "integrity": "sha512-FsjSmlSufuiFBsIqQ++VxFmvX7zKndZpBkHmfXr4wqhvzM92FTEkAh703iqWTl1U3faTgqioIqCbfqdWiFVwtw==", + "version": "6.0.11", + "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-6.0.11.tgz", + "integrity": "sha512-gJ+hAtAsgBF4w7eh28Pg7EA60lx7vE5xO/B/yZawaI6FYHky+5avA9YSe73nJHnAMEVFpCMeJc6Wts5g+niksg==", "funding": [ { "type": "github", @@ -14940,8 +15319,11 @@ } ], "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^3.0.2", - "postcss-value-parser": "^4.2.0" + "@csstools/css-color-parser": "^2.0.2", + "@csstools/css-parser-algorithms": "^2.6.3", + "@csstools/css-tokenizer": "^2.3.1", + "@csstools/postcss-progressive-custom-properties": "^3.2.0", + "@csstools/utilities": "^1.0.0" }, "engines": { "node": "^14 || ^16 || >=18" @@ -14951,27 +15333,34 @@ } }, "node_modules/postcss-color-hex-alpha": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-9.0.2.tgz", - "integrity": "sha512-SfPjgr//VQ/DOCf80STIAsdAs7sbIbxATvVmd+Ec7JvR8onz9pjawhq3BJM3Pie40EE3TyB0P6hft16D33Nlyg==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-9.0.4.tgz", + "integrity": "sha512-XQZm4q4fNFqVCYMGPiBjcqDhuG7Ey2xrl99AnDJMyr5eDASsAGalndVgHZF8i97VFNy1GQeZc4q2ydagGmhelQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "dependencies": { + "@csstools/utilities": "^1.0.0", "postcss-value-parser": "^4.2.0" }, "engines": { "node": "^14 || ^16 || >=18" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, "peerDependencies": { "postcss": "^8.4" } }, "node_modules/postcss-color-rebeccapurple": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-9.0.1.tgz", - "integrity": "sha512-ds4cq5BjRieizVb2PnvbJ0omg9VCo2/KzluvoFZbxuGpsGJ5BQSD93CHBooinEtangCM5YqUOerGDl4xGmOb6Q==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-9.0.3.tgz", + "integrity": "sha512-ruBqzEFDYHrcVq3FnW3XHgwRqVMrtEPLBtD7K2YmsLKVc2jbkxzzNEctJKsPCpDZ+LeMHLKRDoSShVefGc+CkQ==", "funding": [ { "type": "github", @@ -14983,6 +15372,7 @@ } ], "dependencies": { + "@csstools/utilities": "^1.0.0", "postcss-value-parser": "^4.2.0" }, "engines": { @@ -14993,9 +15383,9 @@ } }, "node_modules/postcss-custom-media": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-10.0.2.tgz", - "integrity": "sha512-zcEFNRmDm2fZvTPdI1pIW3W//UruMcLosmMiCdpQnrCsTRzWlKQPYMa1ud9auL0BmrryKK1+JjIGn19K0UjO/w==", + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-10.0.6.tgz", + "integrity": "sha512-BjihQoIO4Wjqv9fQNExSJIim8UAmkhLxuJnhJsLTRFSba1y1MhxkJK5awsM//6JJ+/Tu5QUxf624RQAvKHv6SA==", "funding": [ { "type": "github", @@ -15007,10 +15397,10 @@ } ], "dependencies": { - "@csstools/cascade-layer-name-parser": "^1.0.5", - "@csstools/css-parser-algorithms": "^2.3.2", - "@csstools/css-tokenizer": "^2.2.1", - "@csstools/media-query-list-parser": "^2.1.5" + "@csstools/cascade-layer-name-parser": "^1.0.11", + "@csstools/css-parser-algorithms": "^2.6.3", + "@csstools/css-tokenizer": "^2.3.1", + "@csstools/media-query-list-parser": "^2.1.11" }, "engines": { "node": "^14 || ^16 || >=18" @@ -15020,9 +15410,9 @@ } }, "node_modules/postcss-custom-properties": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-13.3.2.tgz", - "integrity": "sha512-2Coszybpo8lpLY24vy2CYv9AasiZ39/bs8Imv0pWMq55Gl8NWzfc24OAo3zIX7rc6uUJAqESnVOMZ6V6lpMjJA==", + "version": "13.3.10", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-13.3.10.tgz", + "integrity": "sha512-ejaalIpl7p0k0L5ngIZ86AZGmp3m1KdeOCbSQTK4gQcB1ncaoPTHorw206+tsZRIhIDYvh5ZButEje6740YDXw==", "funding": [ { "type": "github", @@ -15034,9 +15424,10 @@ } ], "dependencies": { - "@csstools/cascade-layer-name-parser": "^1.0.5", - "@csstools/css-parser-algorithms": "^2.3.2", - "@csstools/css-tokenizer": "^2.2.1", + "@csstools/cascade-layer-name-parser": "^1.0.11", + "@csstools/css-parser-algorithms": "^2.6.3", + "@csstools/css-tokenizer": "^2.3.1", + "@csstools/utilities": "^1.0.0", "postcss-value-parser": "^4.2.0" }, "engines": { @@ -15047,9 +15438,9 @@ } }, "node_modules/postcss-custom-selectors": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-7.1.6.tgz", - "integrity": "sha512-svsjWRaxqL3vAzv71dV0/65P24/FB8TbPX+lWyyf9SZ7aZm4S4NhCn7N3Bg+Z5sZunG3FS8xQ80LrCU9hb37cw==", + "version": "7.1.10", + "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-7.1.10.tgz", + "integrity": "sha512-bV/6+IExyT2J4kMzX6c+ZMlN1xDfjcC4ePr1ywKezcTgwgUn11qQN3jdzFBpo8Dk1K7vO/OYOwMb5AtJP4JZcg==", "funding": [ { "type": "github", @@ -15061,9 +15452,9 @@ } ], "dependencies": { - "@csstools/cascade-layer-name-parser": "^1.0.5", - "@csstools/css-parser-algorithms": "^2.3.2", - "@csstools/css-tokenizer": "^2.2.1", + "@csstools/cascade-layer-name-parser": "^1.0.11", + "@csstools/css-parser-algorithms": "^2.6.3", + "@csstools/css-tokenizer": "^2.3.1", "postcss-selector-parser": "^6.0.13" }, "engines": { @@ -15074,9 +15465,9 @@ } }, "node_modules/postcss-dir-pseudo-class": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-8.0.0.tgz", - "integrity": "sha512-Oy5BBi0dWPwij/IA+yDYj+/OBMQ9EPqAzTHeSNUYrUWdll/PRJmcbiUj0MNcsBi681I1gcSTLvMERPaXzdbvJg==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-8.0.1.tgz", + "integrity": "sha512-uULohfWBBVoFiZXgsQA24JV6FdKIidQ+ZqxOouhWwdE+qJlALbkS5ScB43ZTjPK+xUZZhlaO/NjfCt5h4IKUfw==", "funding": [ { "type": "github", @@ -15098,9 +15489,9 @@ } }, "node_modules/postcss-double-position-gradients": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-5.0.2.tgz", - "integrity": "sha512-KTbvdOOy8z8zb0BTkEg4/1vqlRlApdvjw8/pFoehgQl0WVO+fezDGlvo0B8xRA+XccA7ohkQCULKNsiNOx70Cw==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-5.0.6.tgz", + "integrity": "sha512-QJ+089FKMaqDxOhhIHsJrh4IP7h4PIHNC5jZP5PMmnfUScNu8Hji2lskqpFWCvu+5sj+2EJFyzKd13sLEWOZmQ==", "funding": [ { "type": "github", @@ -15112,7 +15503,8 @@ } ], "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^3.0.2", + "@csstools/postcss-progressive-custom-properties": "^3.2.0", + "@csstools/utilities": "^1.0.0", "postcss-value-parser": "^4.2.0" }, "engines": { @@ -15131,9 +15523,9 @@ } }, "node_modules/postcss-focus-visible": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-9.0.0.tgz", - "integrity": "sha512-zA4TbVaIaT8npZBEROhZmlc+GBKE8AELPHXE7i4TmIUEQhw/P/mSJfY9t6tBzpQ1rABeGtEOHYrW4SboQeONMQ==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-9.0.1.tgz", + "integrity": "sha512-N2VQ5uPz3Z9ZcqI5tmeholn4d+1H14fKXszpjogZIrFbhaq0zNAtq8sAnw6VLiqGbL8YBzsnu7K9bBkTqaRimQ==", "funding": [ { "type": "github", @@ -15155,9 +15547,9 @@ } }, "node_modules/postcss-focus-within": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-8.0.0.tgz", - "integrity": "sha512-E7+J9nuQzZaA37D/MUZMX1K817RZGDab8qw6pFwzAkDd/QtlWJ9/WTKmzewNiuxzeq6WWY7ATiRePVoDKp+DnA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-8.0.1.tgz", + "integrity": "sha512-NFU3xcY/xwNaapVb+1uJ4n23XImoC86JNwkY/uduytSl2s9Ekc2EpzmRR63+ExitnW3Mab3Fba/wRPCT5oDILA==", "funding": [ { "type": "github", @@ -15187,9 +15579,9 @@ } }, "node_modules/postcss-gap-properties": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-5.0.0.tgz", - "integrity": "sha512-YjsEEL6890P7MCv6fch6Am1yq0EhQCJMXyT4LBohiu87+4/WqR7y5W3RIv53WdA901hhytgRvjlrAhibhW4qsA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-5.0.1.tgz", + "integrity": "sha512-k2z9Cnngc24c0KF4MtMuDdToROYqGMMUQGcE6V0odwjHyOHtaDBlLeRBV70y9/vF7KIbShrTRZ70JjsI1BZyWw==", "funding": [ { "type": "github", @@ -15208,9 +15600,9 @@ } }, "node_modules/postcss-image-set-function": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-6.0.1.tgz", - "integrity": "sha512-VlZncC9hhZ5tg0JllY4g6Z28BeoPO8DIkelioEEkXL0AA0IORlqYpTi2L8TUnl4YQrlwvBgxVy+mdZJw5R/cIQ==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-6.0.3.tgz", + "integrity": "sha512-i2bXrBYzfbRzFnm+pVuxVePSTCRiNmlfssGI4H0tJQvDue+yywXwUxe68VyzXs7cGtMaH6MCLY6IbCShrSroCw==", "funding": [ { "type": "github", @@ -15222,6 +15614,7 @@ } ], "dependencies": { + "@csstools/utilities": "^1.0.0", "postcss-value-parser": "^4.2.0" }, "engines": { @@ -15232,9 +15625,9 @@ } }, "node_modules/postcss-lab-function": { - "version": "6.0.7", - "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-6.0.7.tgz", - "integrity": "sha512-4d1lhDVPukHFqkMv4G5vVcK+tgY52vwb5uR1SWKOaO5389r2q8fMxBWuXSW+YtbCOEGP0/X9KERi9E9le2pJuw==", + "version": "6.0.16", + "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-6.0.16.tgz", + "integrity": "sha512-QWv0VxfjgIl8jBR/wuQcm/o31jn4P/LwzYuVKzNQoO5t7HPcU0d3RfWUiDrHN3frmSv+YYZppr3P81tKFTDyqg==", "funding": [ { "type": "github", @@ -15246,10 +15639,11 @@ } ], "dependencies": { - "@csstools/css-color-parser": "^1.4.0", - "@csstools/css-parser-algorithms": "^2.3.2", - "@csstools/css-tokenizer": "^2.2.1", - "@csstools/postcss-progressive-custom-properties": "^3.0.2" + "@csstools/css-color-parser": "^2.0.2", + "@csstools/css-parser-algorithms": "^2.6.3", + "@csstools/css-tokenizer": "^2.3.1", + "@csstools/postcss-progressive-custom-properties": "^3.2.0", + "@csstools/utilities": "^1.0.0" }, "engines": { "node": "^14 || ^16 || >=18" @@ -15330,9 +15724,9 @@ } }, "node_modules/postcss-logical": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-7.0.0.tgz", - "integrity": "sha512-zYf3vHkoW82f5UZTEXChTJvH49Yl9X37axTZsJGxrCG2kOUwtaAoz9E7tqYg0lsIoJLybaL8fk/2mOi81zVIUw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-7.0.1.tgz", + "integrity": "sha512-8GwUQZE0ri0K0HJHkDv87XOLC8DE0msc+HoWLeKdtjDZEwpZ5xuK3QdV6FhmHSQW40LPkg43QzvATRAI3LsRkg==", "funding": [ { "type": "github", @@ -15409,9 +15803,9 @@ } }, "node_modules/postcss-nesting": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-12.0.1.tgz", - "integrity": "sha512-6LCqCWP9pqwXw/njMvNK0hGY44Fxc4B2EsGbn6xDcxbNRzP8GYoxT7yabVVMLrX3quqOJ9hg2jYMsnkedOf8pA==", + "version": "12.1.4", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-12.1.4.tgz", + "integrity": "sha512-CcHOq94K137E+U4Ommu7pexcpp0Tjm24zl4UcqWs1oSLAr5cLI+jLrqQ5h/bdjhMX6cMbzunyustVNnvrzF8Zg==", "funding": [ { "type": "github", @@ -15423,7 +15817,8 @@ } ], "dependencies": { - "@csstools/selector-specificity": "^3.0.0", + "@csstools/selector-resolve-nested": "^1.1.0", + "@csstools/selector-specificity": "^3.1.1", "postcss-selector-parser": "^6.0.13" }, "engines": { @@ -15455,9 +15850,9 @@ } }, "node_modules/postcss-overflow-shorthand": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-5.0.0.tgz", - "integrity": "sha512-2rlxDyeSics/hC2FuMdPnWiP9WUPZ5x7FTuArXLFVpaSQ2woPSfZS4RD59HuEokbZhs/wPUQJ1E3MT6zVv94MQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-5.0.1.tgz", + "integrity": "sha512-XzjBYKLd1t6vHsaokMV9URBt2EwC9a7nDhpQpjoPk2HRTSQfokPfyAS/Q7AOrzUu6q+vp/GnrDBGuj/FCaRqrQ==", "funding": [ { "type": "github", @@ -15487,9 +15882,9 @@ } }, "node_modules/postcss-place": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-9.0.0.tgz", - "integrity": "sha512-qLEPD9VPH5opDVemwmRaujODF9nExn24VOC3ghgVLEvfYN7VZLwJHes0q/C9YR5hI2UC3VgBE8Wkdp1TxCXhtg==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-9.0.1.tgz", + "integrity": "sha512-JfL+paQOgRQRMoYFc2f73pGuG/Aw3tt4vYMR6UA3cWVMxivviPTnMFnFTczUJOA4K2Zga6xgQVE+PcLs64WC8Q==", "funding": [ { "type": "github", @@ -15511,9 +15906,9 @@ } }, "node_modules/postcss-preset-env": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-9.3.0.tgz", - "integrity": "sha512-ycw6doPrqV6QxDCtgiyGDef61bEfiSc59HGM4gOw/wxQxmKnhuEery61oOC/5ViENz/ycpRsuhTexs1kUBTvVw==", + "version": "9.5.13", + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-9.5.13.tgz", + "integrity": "sha512-YQMwWu6MAc4Envrjf/mW2BTrb5J8WkrJ4dV2VostZVDhrmEPpYREOyhmvtlFLDxK1/AmTDY8aXjZViMC1qKu/w==", "funding": [ { "type": "github", @@ -15525,66 +15920,66 @@ } ], "dependencies": { - "@csstools/postcss-cascade-layers": "^4.0.1", - "@csstools/postcss-color-function": "^3.0.7", - "@csstools/postcss-color-mix-function": "^2.0.7", - "@csstools/postcss-exponential-functions": "^1.0.1", - "@csstools/postcss-font-format-keywords": "^3.0.0", - "@csstools/postcss-gamut-mapping": "^1.0.0", - "@csstools/postcss-gradients-interpolation-method": "^4.0.7", - "@csstools/postcss-hwb-function": "^3.0.6", - "@csstools/postcss-ic-unit": "^3.0.2", - "@csstools/postcss-initial": "^1.0.0", - "@csstools/postcss-is-pseudo-class": "^4.0.3", - "@csstools/postcss-logical-float-and-clear": "^2.0.0", - "@csstools/postcss-logical-overflow": "^1.0.0", - "@csstools/postcss-logical-overscroll-behavior": "^1.0.0", - "@csstools/postcss-logical-resize": "^2.0.0", - "@csstools/postcss-logical-viewport-units": "^2.0.3", - "@csstools/postcss-media-minmax": "^1.1.0", - "@csstools/postcss-media-queries-aspect-ratio-number-values": "^2.0.3", - "@csstools/postcss-nested-calc": "^3.0.0", - "@csstools/postcss-normalize-display-values": "^3.0.1", - "@csstools/postcss-oklab-function": "^3.0.7", - "@csstools/postcss-progressive-custom-properties": "^3.0.2", - "@csstools/postcss-relative-color-syntax": "^2.0.7", - "@csstools/postcss-scope-pseudo-class": "^3.0.0", - "@csstools/postcss-stepped-value-functions": "^3.0.2", - "@csstools/postcss-text-decoration-shorthand": "^3.0.3", - "@csstools/postcss-trigonometric-functions": "^3.0.2", - "@csstools/postcss-unset-value": "^3.0.0", - "autoprefixer": "^10.4.16", - "browserslist": "^4.22.1", - "css-blank-pseudo": "^6.0.0", - "css-has-pseudo": "^6.0.0", - "css-prefers-color-scheme": "^9.0.0", - "cssdb": "^7.9.0", - "postcss-attribute-case-insensitive": "^6.0.2", + "@csstools/postcss-cascade-layers": "^4.0.6", + "@csstools/postcss-color-function": "^3.0.16", + "@csstools/postcss-color-mix-function": "^2.0.16", + "@csstools/postcss-exponential-functions": "^1.0.7", + "@csstools/postcss-font-format-keywords": "^3.0.2", + "@csstools/postcss-gamut-mapping": "^1.0.9", + "@csstools/postcss-gradients-interpolation-method": "^4.0.17", + "@csstools/postcss-hwb-function": "^3.0.15", + "@csstools/postcss-ic-unit": "^3.0.6", + "@csstools/postcss-initial": "^1.0.1", + "@csstools/postcss-is-pseudo-class": "^4.0.8", + "@csstools/postcss-light-dark-function": "^1.0.5", + "@csstools/postcss-logical-float-and-clear": "^2.0.1", + "@csstools/postcss-logical-overflow": "^1.0.1", + "@csstools/postcss-logical-overscroll-behavior": "^1.0.1", + "@csstools/postcss-logical-resize": "^2.0.1", + "@csstools/postcss-logical-viewport-units": "^2.0.9", + "@csstools/postcss-media-minmax": "^1.1.6", + "@csstools/postcss-media-queries-aspect-ratio-number-values": "^2.0.9", + "@csstools/postcss-nested-calc": "^3.0.2", + "@csstools/postcss-normalize-display-values": "^3.0.2", + "@csstools/postcss-oklab-function": "^3.0.16", + "@csstools/postcss-progressive-custom-properties": "^3.2.0", + "@csstools/postcss-relative-color-syntax": "^2.0.16", + "@csstools/postcss-scope-pseudo-class": "^3.0.1", + "@csstools/postcss-stepped-value-functions": "^3.0.8", + "@csstools/postcss-text-decoration-shorthand": "^3.0.6", + "@csstools/postcss-trigonometric-functions": "^3.0.8", + "@csstools/postcss-unset-value": "^3.0.1", + "autoprefixer": "^10.4.19", + "browserslist": "^4.22.3", + "css-blank-pseudo": "^6.0.2", + "css-has-pseudo": "^6.0.5", + "css-prefers-color-scheme": "^9.0.1", + "cssdb": "^8.0.0", + "postcss-attribute-case-insensitive": "^6.0.3", "postcss-clamp": "^4.1.0", - "postcss-color-functional-notation": "^6.0.2", - "postcss-color-hex-alpha": "^9.0.2", - "postcss-color-rebeccapurple": "^9.0.1", - "postcss-custom-media": "^10.0.2", - "postcss-custom-properties": "^13.3.2", - "postcss-custom-selectors": "^7.1.6", - "postcss-dir-pseudo-class": "^8.0.0", - "postcss-double-position-gradients": "^5.0.2", - "postcss-focus-visible": "^9.0.0", - "postcss-focus-within": "^8.0.0", + "postcss-color-functional-notation": "^6.0.11", + "postcss-color-hex-alpha": "^9.0.4", + "postcss-color-rebeccapurple": "^9.0.3", + "postcss-custom-media": "^10.0.6", + "postcss-custom-properties": "^13.3.10", + "postcss-custom-selectors": "^7.1.10", + "postcss-dir-pseudo-class": "^8.0.1", + "postcss-double-position-gradients": "^5.0.6", + "postcss-focus-visible": "^9.0.1", + "postcss-focus-within": "^8.0.1", "postcss-font-variant": "^5.0.0", - "postcss-gap-properties": "^5.0.0", - "postcss-image-set-function": "^6.0.1", - "postcss-lab-function": "^6.0.7", - "postcss-logical": "^7.0.0", - "postcss-nesting": "^12.0.1", + "postcss-gap-properties": "^5.0.1", + "postcss-image-set-function": "^6.0.3", + "postcss-lab-function": "^6.0.16", + "postcss-logical": "^7.0.1", + "postcss-nesting": "^12.1.4", "postcss-opacity-percentage": "^2.0.0", - "postcss-overflow-shorthand": "^5.0.0", + "postcss-overflow-shorthand": "^5.0.1", "postcss-page-break": "^3.0.4", - "postcss-place": "^9.0.0", - "postcss-pseudo-class-any-link": "^9.0.0", + "postcss-place": "^9.0.1", + "postcss-pseudo-class-any-link": "^9.0.2", "postcss-replace-overflow-wrap": "^4.0.0", - "postcss-selector-not": "^7.0.1", - "postcss-value-parser": "^4.2.0" + "postcss-selector-not": "^7.0.2" }, "engines": { "node": "^14 || ^16 || >=18" @@ -15594,9 +15989,9 @@ } }, "node_modules/postcss-pseudo-class-any-link": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-9.0.0.tgz", - "integrity": "sha512-QNCYIL98VKFKY6HGDEJpF6+K/sg9bxcUYnOmNHJxZS5wsFDFaVoPeG68WAuhsqwbIBSo/b9fjEnTwY2mTSD+uA==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-9.0.2.tgz", + "integrity": "sha512-HFSsxIqQ9nA27ahyfH37cRWGk3SYyQLpk0LiWw/UGMV4VKT5YG2ONee4Pz/oFesnK0dn2AjcyequDbIjKJgB0g==", "funding": [ { "type": "github", @@ -15626,19 +16021,25 @@ } }, "node_modules/postcss-selector-not": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-7.0.1.tgz", - "integrity": "sha512-1zT5C27b/zeJhchN7fP0kBr16Cc61mu7Si9uWWLoA3Px/D9tIJPKchJCkUH3tPO5D0pCFmGeApAv8XpXBQJ8SQ==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-7.0.2.tgz", + "integrity": "sha512-/SSxf/90Obye49VZIfc0ls4H0P6i6V1iHv0pzZH8SdgvZOPFkF37ef1r5cyWcMflJSFJ5bfuoluTnFnBBFiuSA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "dependencies": { - "postcss-selector-parser": "^6.0.10" + "postcss-selector-parser": "^6.0.13" }, "engines": { "node": "^14 || ^16 || >=18" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, "peerDependencies": { "postcss": "^8.4" } @@ -16776,6 +17177,17 @@ "node": ">=8" } }, + "node_modules/run-applescript": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", + "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -16869,9 +17281,9 @@ } }, "node_modules/sass-loader": { - "version": "14.1.1", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-14.1.1.tgz", - "integrity": "sha512-QX8AasDg75monlybel38BZ49JP5Z+uSKfKwF2rO7S74BywaRmGQMUBw9dtkS+ekyM/QnP+NOrRYq8ABMZ9G8jw==", + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-14.2.1.tgz", + "integrity": "sha512-G0VcnMYU18a4N7VoNDegg2OuMjYtxnqzQWARVWCIVSZwJeiL9kg8QMsuIZOplsJgTzZLF6jGxI3AClj8I9nRdQ==", "dependencies": { "neo-async": "^2.6.2" }, @@ -16920,14 +17332,14 @@ } }, "node_modules/schema-utils": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", - "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", "dependencies": { "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", + "ajv": "^8.9.0", "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" + "ajv-keywords": "^5.1.0" }, "engines": { "node": ">= 12.13.0" @@ -16956,10 +17368,11 @@ } }, "node_modules/selfsigned": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.1.1.tgz", - "integrity": "sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", "dependencies": { + "@types/node-forge": "^1.3.0", "node-forge": "^1" }, "engines": { @@ -17188,9 +17601,12 @@ } }, "node_modules/shell-quote": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz", - "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==" + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/shellwords": { "version": "0.1.1", @@ -17294,6 +17710,24 @@ "node": ">= 14" } }, + "node_modules/sonic-forest": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sonic-forest/-/sonic-forest-1.0.3.tgz", + "integrity": "sha512-dtwajos6IWMEWXdEbW1IkEkyL2gztCAgDplRIX+OT5aRKnEd5e7r7YCxRgXZdhRP1FBdOBf8axeTPhzDv8T4wQ==", + "dependencies": { + "tree-dump": "^1.0.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, "node_modules/source-list-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", @@ -17508,7 +17942,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -17518,11 +17951,29 @@ "node": ">=8" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, "node_modules/string-width/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/string.prototype.matchall": { "version": "4.0.8", @@ -17608,6 +18059,18 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -17649,18 +18112,18 @@ "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" }, "node_modules/style-loader": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.4.tgz", - "integrity": "sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-4.0.0.tgz", + "integrity": "sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA==", "engines": { - "node": ">= 12.13.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "webpack": "^5.0.0" + "webpack": "^5.27.0" } }, "node_modules/supports-color": { @@ -17900,6 +18363,17 @@ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=" }, + "node_modules/thingies": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz", + "integrity": "sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==", + "engines": { + "node": ">=10.18" + }, + "peerDependencies": { + "tslib": "^2" + } + }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -17984,6 +18458,21 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" }, + "node_modules/tree-dump": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.1.tgz", + "integrity": "sha512-WCkcRBVPSlHHq1dc/px9iOfqklvzCbdRwvlNfxGZsrHqf6aZttfPrd7DJTt6oR10dwUfpFFQeVTkPbBIZxX/YA==", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, "node_modules/tryer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", @@ -18196,9 +18685,9 @@ } }, "node_modules/typescript": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", - "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -18542,9 +19031,9 @@ } }, "node_modules/watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz", + "integrity": "sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==", "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -18567,25 +19056,25 @@ "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" }, "node_modules/webpack": { - "version": "5.90.3", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.3.tgz", - "integrity": "sha512-h6uDYlWCctQRuXBs1oYpVe6sFcWedl0dpcVaTf/YF67J9bKvwJajFulMVSYKHrksMB3I/pIagRzDxwxkebuzKA==", + "version": "5.91.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.91.0.tgz", + "integrity": "sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==", "dependencies": { "@types/eslint-scope": "^3.7.3", "@types/estree": "^1.0.5", - "@webassemblyjs/ast": "^1.11.5", - "@webassemblyjs/wasm-edit": "^1.11.5", - "@webassemblyjs/wasm-parser": "^1.11.5", + "@webassemblyjs/ast": "^1.12.1", + "@webassemblyjs/wasm-edit": "^1.12.1", + "@webassemblyjs/wasm-parser": "^1.12.1", "acorn": "^8.7.1", "acorn-import-assertions": "^1.9.0", "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.15.0", + "enhanced-resolve": "^5.16.0", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", + "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", @@ -18593,7 +19082,7 @@ "schema-utils": "^3.2.0", "tapable": "^2.1.1", "terser-webpack-plugin": "^5.3.10", - "watchpack": "^2.4.0", + "watchpack": "^2.4.1", "webpack-sources": "^3.2.3" }, "bin": { @@ -18613,91 +19102,99 @@ } }, "node_modules/webpack-dev-middleware": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", - "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.2.1.tgz", + "integrity": "sha512-hRLz+jPQXo999Nx9fXVdKlg/aehsw1ajA9skAneGmT03xwmyuhvF93p6HUKKbWhXdcERtGTzUCtIQr+2IQegrA==", "dependencies": { "colorette": "^2.0.10", - "memfs": "^3.4.3", + "memfs": "^4.6.0", "mime-types": "^2.1.31", + "on-finished": "^2.4.1", "range-parser": "^1.2.1", "schema-utils": "^4.0.0" }, "engines": { - "node": ">= 12.13.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + } } }, - "node_modules/webpack-dev-middleware/node_modules/fs-monkey": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.4.tgz", - "integrity": "sha512-INM/fWAxMICjttnD0DX1rBvinKskj5G1w+oy/pnm9u/tSlnBrzFonJMcalKJ30P8RRsPzKcCG7Q8l0jx5Fh9YQ==" - }, "node_modules/webpack-dev-middleware/node_modules/memfs": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", - "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.9.2.tgz", + "integrity": "sha512-f16coDZlTG1jskq3mxarwB+fGRrd0uXWt+o1WIhRfOwbXQZqUDsTVxQBFK9JjRQHblg8eAG2JSbprDXKjc7ijQ==", "dependencies": { - "fs-monkey": "^1.0.4" + "@jsonjoy.com/json-pack": "^1.0.3", + "@jsonjoy.com/util": "^1.1.2", + "sonic-forest": "^1.0.0", + "tslib": "^2.0.0" }, "engines": { "node": ">= 4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" } }, "node_modules/webpack-dev-server": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz", - "integrity": "sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==", - "dependencies": { - "@types/bonjour": "^3.5.9", - "@types/connect-history-api-fallback": "^1.3.5", - "@types/express": "^4.17.13", - "@types/serve-index": "^1.9.1", - "@types/serve-static": "^1.13.10", - "@types/sockjs": "^0.3.33", - "@types/ws": "^8.5.5", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.0.4.tgz", + "integrity": "sha512-dljXhUgx3HqKP2d8J/fUMvhxGhzjeNVarDLcbO/EWMSgRizDkxHQDZQaLFL5VJY9tRBj2Gz+rvCEYYvhbqPHNA==", + "dependencies": { + "@types/bonjour": "^3.5.13", + "@types/connect-history-api-fallback": "^1.5.4", + "@types/express": "^4.17.21", + "@types/serve-index": "^1.9.4", + "@types/serve-static": "^1.15.5", + "@types/sockjs": "^0.3.36", + "@types/ws": "^8.5.10", "ansi-html-community": "^0.0.8", - "bonjour-service": "^1.0.11", - "chokidar": "^3.5.3", + "bonjour-service": "^1.2.1", + "chokidar": "^3.6.0", "colorette": "^2.0.10", "compression": "^1.7.4", "connect-history-api-fallback": "^2.0.0", "default-gateway": "^6.0.3", "express": "^4.17.3", "graceful-fs": "^4.2.6", - "html-entities": "^2.3.2", + "html-entities": "^2.4.0", "http-proxy-middleware": "^2.0.3", - "ipaddr.js": "^2.0.1", - "launch-editor": "^2.6.0", - "open": "^8.0.9", - "p-retry": "^4.5.0", - "rimraf": "^3.0.2", - "schema-utils": "^4.0.0", - "selfsigned": "^2.1.1", + "ipaddr.js": "^2.1.0", + "launch-editor": "^2.6.1", + "open": "^10.0.3", + "p-retry": "^6.2.0", + "rimraf": "^5.0.5", + "schema-utils": "^4.2.0", + "selfsigned": "^2.4.1", "serve-index": "^1.9.1", "sockjs": "^0.3.24", "spdy": "^4.0.2", - "webpack-dev-middleware": "^5.3.1", - "ws": "^8.13.0" + "webpack-dev-middleware": "^7.1.0", + "ws": "^8.16.0" }, "bin": { "webpack-dev-server": "bin/webpack-dev-server.js" }, "engines": { - "node": ">= 12.13.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "webpack": "^4.37.0 || ^5.0.0" + "webpack": "^5.0.0" }, "peerDependenciesMeta": { "webpack": { @@ -18708,12 +19205,44 @@ } } }, - "node_modules/webpack-dev-server/node_modules/@types/ws": { - "version": "8.5.5", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.5.tgz", - "integrity": "sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==", + "node_modules/webpack-dev-server/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dependencies": { - "@types/node": "*" + "balanced-match": "^1.0.0" + } + }, + "node_modules/webpack-dev-server/node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-dev-server/node_modules/glob": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz", + "integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/webpack-dev-server/node_modules/ipaddr.js": { @@ -18724,6 +19253,68 @@ "node": ">= 10" } }, + "node_modules/webpack-dev-server/node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-dev-server/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/webpack-dev-server/node_modules/open": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", + "integrity": "sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-dev-server/node_modules/rimraf": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.7.tgz", + "integrity": "sha512-nV6YcJo5wbLW77m+8KjH8aB/7/rxQy9SZ0HY5shnwULfS+9nmTtVXAJET5NdZmCzA4fPI/Hm1wo/Po/4mopOdg==", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/webpack-manifest-plugin": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/webpack-manifest-plugin/-/webpack-manifest-plugin-5.0.0.tgz", @@ -19238,6 +19829,53 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, "node_modules/wrap-ansi/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", diff --git a/package.json b/package.json index 65bd1a733..f880d3c85 100644 --- a/package.json +++ b/package.json @@ -5,17 +5,17 @@ "dependencies": { "@augloop/types-core": "file:packages/types-core-2.16.189.tgz", "@axe-core/webdriverjs": "4.8.5", - "@azure/msal-browser": "3.10.0", - "@babel/core": "7.23.3", - "@babel/runtime": "7.23.8", - "@fluentui/react": "8.112.5", - "@fluentui/react-icons-mdl2": "1.3.52", - "@microsoft/applicationinsights-react-js": "17.0.3", - "@microsoft/applicationinsights-web": "3.0.5", + "@azure/msal-browser": "3.14.0", + "@babel/core": "7.24.6", + "@babel/runtime": "7.24.5", + "@fluentui/react": "8.117.7", + "@fluentui/react-icons-mdl2": "1.3.63", + "@microsoft/applicationinsights-react-js": "17.2.0", + "@microsoft/applicationinsights-web": "3.2.1", "@microsoft/microsoft-graph-client": "3.0.7", "@monaco-editor/react": "4.6.0", "@ms-ofb/officebrowserfeedbacknpm": "file:packages/officebrowserfeedbacknpm-1.6.6.tgz", - "adaptive-expressions": "4.21.4", + "adaptive-expressions": "4.22.2", "adaptivecards": "3.0.2", "adaptivecards-templating": "2.3.1", "ajv": "8.12.0", @@ -31,7 +31,7 @@ "dotenv-expand": "11.0.6", "eslint-config-react-app": "7.0.1", "eslint-plugin-react": "7.33.2", - "eslint-webpack-plugin": "4.0.1", + "eslint-webpack-plugin": "4.1.0", "express": "4.19.2", "expvariantassignmentsdk": "file:packages/expvariantassignmentsdk-1.0.0.tgz", "file-loader": "6.2.0", @@ -40,13 +40,13 @@ "guid-typescript": "1.0.9", "isomorphic-fetch": "3.0.0", "localforage": "1.10.0", - "mini-css-extract-plugin": "2.8.1", + "mini-css-extract-plugin": "2.9.0", "monaco-editor": "0.30.1", "monaco-editor-webpack-plugin": "6.0.0", "office-ui-fabric-core": "11.1.0", "postcss-flexbugs-fixes": "5.0.2", "postcss-loader": "8.1.1", - "postcss-preset-env": "9.3.0", + "postcss-preset-env": "9.5.13", "re-resizable": "6.9.11", "react": "18.2.0", "react-app-polyfill": "3.0.0", @@ -56,13 +56,13 @@ "redux-thunk": "2.4.2", "resolve": "1.22.8", "sass": "1.72.0", - "sass-loader": "14.1.1", - "style-loader": "3.3.4", - "typescript": "5.3.3", + "sass-loader": "14.2.1", + "style-loader": "4.0.0", + "typescript": "5.4.5", "url": "0.11.3", "url-loader": "4.1.1", - "webpack": "5.90.3", - "webpack-dev-server": "4.15.1", + "webpack": "5.91.0", + "webpack-dev-server": "5.0.4", "webpack-manifest-plugin": "5.0.0", "workbox-webpack-plugin": "7.0.0" }, @@ -91,7 +91,7 @@ ], "devDependencies": { "@axe-core/playwright": "4.7.3", - "@playwright/test": "1.42.0", + "@playwright/test": "1.43.1", "@types/chromedriver": "81.0.1", "@types/isomorphic-fetch": "0.0.39", "@types/jest": "29.5.12", @@ -102,11 +102,11 @@ "@types/redux-logger": "3.0.11", "@types/redux-mock-store": "1.0.3", "@types/selenium-webdriver": "4.1.21", - "@typescript-eslint/eslint-plugin": "6.20.0", + "@typescript-eslint/eslint-plugin": "7.0.0", "@typescript-eslint/parser": "6.17.0", "acorn": "8.11.3", "babel-jest": "29.7.0", - "chromedriver": "122.0.4", + "chromedriver": "124.0.1", "eslint": "8.56.0", "html-webpack-plugin": "5.6.0", "jest": "29.7.0", diff --git a/src/app/services/context/popups-context/PopupsContext.tsx b/src/app/services/context/popups-context/PopupsContext.tsx index ae97ac520..f9c74f20d 100644 --- a/src/app/services/context/popups-context/PopupsContext.tsx +++ b/src/app/services/context/popups-context/PopupsContext.tsx @@ -1,12 +1,17 @@ import { createContext, useContext, useReducer } from 'react'; -import { PopupState, initialState } from '.'; + +import { PopupState } from '.'; import { reducedPopups } from './reducedPopups'; -const PopupStateContext = createContext(initialState); +const PopupStateContext = createContext({ + popups: [] +}); const PopupDispatchContext = createContext({}); export function PopupsProvider({ children }: any) { - const [state, dispatch] = useReducer(reducedPopups, initialState); + const [state, dispatch] = useReducer(reducedPopups, { + popups: [] + }); return ( From 7c4ee1db4b375386ce45d88cf1850feaad3357b7 Mon Sep 17 00:00:00 2001 From: Musale Martin Date: Fri, 7 Jun 2024 13:04:30 +0300 Subject: [PATCH 15/45] fix: remove character escape in json response formatting (#3163) Signed-off-by: Musale Martin --- src/app/views/common/monaco/util/format-json.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/views/common/monaco/util/format-json.ts b/src/app/views/common/monaco/util/format-json.ts index f34ac2193..27f43c824 100644 --- a/src/app/views/common/monaco/util/format-json.ts +++ b/src/app/views/common/monaco/util/format-json.ts @@ -15,7 +15,7 @@ export function formatJsonStringForAllBrowsers( * 3. format the string (works for all browsers) */ try { - body = JSON.stringify(body).replace(/(?:\\[rnt]|[\r\n\t]+)+/g, ''); + body = JSON.stringify(body); body = JSON.parse(body); } catch (error) { telemetry.trackException( From b2dcd1939fef7d5d6bc37a623b37408734ddd6b3 Mon Sep 17 00:00:00 2001 From: Charles Wahome Date: Tue, 18 Jun 2024 15:24:35 +0300 Subject: [PATCH 16/45] Fix: add parameters to token request (#3218) --- .../authentication/AuthenticationWrapper.ts | 6 ++++-- src/modules/authentication/msal-app.ts | 14 -------------- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/src/modules/authentication/AuthenticationWrapper.ts b/src/modules/authentication/AuthenticationWrapper.ts index 77f83ef54..a6b338f4f 100644 --- a/src/modules/authentication/AuthenticationWrapper.ts +++ b/src/modules/authentication/AuthenticationWrapper.ts @@ -76,7 +76,8 @@ export class AuthenticationWrapper implements IAuthenticationWrapper { authority: this.getAuthority(), prompt: 'select_account', redirectUri: getCurrentUri(), - extraQueryParameters: getExtraQueryParameters() + extraQueryParameters: getExtraQueryParameters(), + tokenQueryParameters: getExtraQueryParameters() }; try { const result = await msalApplication.loginPopup(popUpRequest); @@ -207,8 +208,9 @@ export class AuthenticationWrapper implements IAuthenticationWrapper { authority: this.getAuthority(), prompt: 'select_account', redirectUri: getCurrentUri(), + claims: this.getClaims(), extraQueryParameters: getExtraQueryParameters(), - claims: this.getClaims() + tokenQueryParameters: getExtraQueryParameters() }; if (this.consentingToNewScopes || this.performingStepUpAuth) { diff --git a/src/modules/authentication/msal-app.ts b/src/modules/authentication/msal-app.ts index 046f5bc5c..3cce67d30 100644 --- a/src/modules/authentication/msal-app.ts +++ b/src/modules/authentication/msal-app.ts @@ -30,20 +30,6 @@ export const configuration: Configuration = { return; } telemetry.trackEvent(eventTypes.AUTH_REQUEST_EVENT, { message, level }); - switch (level) { - case LogLevel.Error: - console.error('[MSAL]', message); - return; - case LogLevel.Info: - console.info('[MSAL]', message); - return; - case LogLevel.Verbose: - console.debug('[MSAL]', message); - return; - case LogLevel.Warning: - console.warn('[MSAL]', message); - return; - } }, piiLoggingEnabled: false } From 552a2cb9ef170536b0dfd9e4938c9b80d457b6be Mon Sep 17 00:00:00 2001 From: Charles Wahome Date: Wed, 19 Jun 2024 18:53:52 +0300 Subject: [PATCH 17/45] Chore: Update to 10.2.0 (#3220) --- package-lock.json | 4 ++-- package.json | 2 +- src/modules/authentication/msal-app.ts | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index bee176661..c0ab750b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "graph-explorer-v2", - "version": "10.1.0", + "version": "10.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "graph-explorer-v2", - "version": "10.1.0", + "version": "10.2.0", "dependencies": { "@augloop/types-core": "file:packages/types-core-2.16.189.tgz", "@axe-core/webdriverjs": "4.8.5", diff --git a/package.json b/package.json index f880d3c85..c6c673787 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "graph-explorer-v2", - "version": "10.1.0", + "version": "10.2.0", "private": true, "dependencies": { "@augloop/types-core": "file:packages/types-core-2.16.189.tgz", diff --git a/src/modules/authentication/msal-app.ts b/src/modules/authentication/msal-app.ts index 3cce67d30..9d5ad694d 100644 --- a/src/modules/authentication/msal-app.ts +++ b/src/modules/authentication/msal-app.ts @@ -1,4 +1,3 @@ -/* eslint-disable no-console */ import { Configuration, LogLevel, PublicClientApplication } from '@azure/msal-browser'; import { eventTypes, telemetry } from '../../telemetry'; From 072f4e6522cddc45a363171d924ac696f4e01d50 Mon Sep 17 00:00:00 2001 From: Elinor Date: Wed, 19 Jun 2024 20:15:04 +0300 Subject: [PATCH 18/45] Fix: Monaco 404 errors (#3222) --- config/webpack.config.js | 4 ++++ src/index.tsx | 37 +++++++++++++++++++++++++++++++++++++ src/types/index.d.ts | 3 +++ 3 files changed, 44 insertions(+) diff --git a/config/webpack.config.js b/config/webpack.config.js index 1fd99baf5..dd16df345 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -388,6 +388,10 @@ module.exports = function (webpackEnv) { 'sass-loader' ) }, + { + test: /\.css$/, + use: ['style-loader', 'css-loader'] + }, { test: /\.ttf$/, type: 'asset/resource' diff --git a/src/index.tsx b/src/index.tsx index 7848f55f3..ce3a4acbc 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -160,6 +160,43 @@ function loadResources() { } loadResources(); +/** + * Set's up Monaco Editor's Workers. + */ +enum Workers { + Json = 'json', + Editor = 'editor', + Typescript='ts', + Css='css', + Html='html' +} + +window.MonacoEnvironment = { + getWorkerUrl(moduleId: any, label: string) { + switch (label) { + case 'json': + return getWorkerFor(Workers.Json); + case 'css': + return getWorkerFor(Workers.Css); + case 'html': + return getWorkerFor(Workers.Html); + case 'typescript': + return getWorkerFor(Workers.Typescript); + default: + return getWorkerFor(Workers.Editor); + } + } +}; + +function getWorkerFor(worker: string): string { + // tslint:disable-next-line:max-line-length + const WORKER_PATH = + 'https://graphstagingblobstorage.blob.core.windows.net/staging/vendor/bower_components/explorer-v2/build'; + + return `data:text/javascript;charset=utf-8,${encodeURIComponent(` + importScripts('${WORKER_PATH}/${worker}.worker.js');`)}`; +} + variantService.initialize(); const telemetryProvider: ITelemetry = telemetry; telemetryProvider.initialize(); diff --git a/src/types/index.d.ts b/src/types/index.d.ts index 581189bba..237180bcf 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -1,7 +1,10 @@ +import { Environment } from 'monaco-editor/esm/vs/editor/editor.api'; + export {}; declare global { interface Window { ClientId: string | undefined + MonacoEnvironment: Environment; } } From 6e4266cf2bdd803706d9caffb42838e6e2650772 Mon Sep 17 00:00:00 2001 From: Elinor Date: Wed, 3 Jul 2024 11:53:27 +0300 Subject: [PATCH 19/45] Chore: Update to 10.2.1 (#3224) --- package-lock.json | 4 ++-- package.json | 2 +- src/index.tsx | 37 ------------------------------------- src/types/index.d.ts | 3 --- 4 files changed, 3 insertions(+), 43 deletions(-) diff --git a/package-lock.json b/package-lock.json index c0ab750b3..51fb122a5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "graph-explorer-v2", - "version": "10.2.0", + "version": "10.2.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "graph-explorer-v2", - "version": "10.2.0", + "version": "10.2.1", "dependencies": { "@augloop/types-core": "file:packages/types-core-2.16.189.tgz", "@axe-core/webdriverjs": "4.8.5", diff --git a/package.json b/package.json index c6c673787..0069eb492 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "graph-explorer-v2", - "version": "10.2.0", + "version": "10.2.1", "private": true, "dependencies": { "@augloop/types-core": "file:packages/types-core-2.16.189.tgz", diff --git a/src/index.tsx b/src/index.tsx index ce3a4acbc..7848f55f3 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -160,43 +160,6 @@ function loadResources() { } loadResources(); -/** - * Set's up Monaco Editor's Workers. - */ -enum Workers { - Json = 'json', - Editor = 'editor', - Typescript='ts', - Css='css', - Html='html' -} - -window.MonacoEnvironment = { - getWorkerUrl(moduleId: any, label: string) { - switch (label) { - case 'json': - return getWorkerFor(Workers.Json); - case 'css': - return getWorkerFor(Workers.Css); - case 'html': - return getWorkerFor(Workers.Html); - case 'typescript': - return getWorkerFor(Workers.Typescript); - default: - return getWorkerFor(Workers.Editor); - } - } -}; - -function getWorkerFor(worker: string): string { - // tslint:disable-next-line:max-line-length - const WORKER_PATH = - 'https://graphstagingblobstorage.blob.core.windows.net/staging/vendor/bower_components/explorer-v2/build'; - - return `data:text/javascript;charset=utf-8,${encodeURIComponent(` - importScripts('${WORKER_PATH}/${worker}.worker.js');`)}`; -} - variantService.initialize(); const telemetryProvider: ITelemetry = telemetry; telemetryProvider.initialize(); diff --git a/src/types/index.d.ts b/src/types/index.d.ts index 237180bcf..581189bba 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -1,10 +1,7 @@ -import { Environment } from 'monaco-editor/esm/vs/editor/editor.api'; - export {}; declare global { interface Window { ClientId: string | undefined - MonacoEnvironment: Environment; } } From d46a9f3b2d722b0777637a29e0c6945c46e2bd9b Mon Sep 17 00:00:00 2001 From: Musale Martin Date: Tue, 9 Jul 2024 10:54:28 +0300 Subject: [PATCH 20/45] Fix: move getting resource version children to a function (#3244) --- .../auto-complete/suffix/SuffixRenderer.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/app/views/query-runner/query-input/auto-complete/suffix/SuffixRenderer.tsx b/src/app/views/query-runner/query-input/auto-complete/suffix/SuffixRenderer.tsx index 05972fa91..c65755482 100644 --- a/src/app/views/query-runner/query-input/auto-complete/suffix/SuffixRenderer.tsx +++ b/src/app/views/query-runner/query-input/auto-complete/suffix/SuffixRenderer.tsx @@ -23,11 +23,15 @@ const SuffixRenderer = () => { const getDocumentationLink = (): string | null => { const { queries } = samples; - + const getChildren = ()=> { + if (resources.data && Object.keys(resources.data).length > 0 && sampleQuery.selectedVersion in resources.data){ + return resources.data[sampleQuery.selectedVersion].children ?? []; + } + return []; + } const resourceDocumentationUrl = new DocumentationService({ sampleQuery, - source: Object.keys(resources.data).length > 0 ? - resources.data[sampleQuery.selectedVersion].children! : [] + source: getChildren() }).getDocumentationLink(); const sampleDocumentationUrl = new DocumentationService({ From a7e142b19edc0fc13de10c90f337495a6f05efcc Mon Sep 17 00:00:00 2001 From: Charles Wahome Date: Mon, 15 Jul 2024 15:11:13 +0300 Subject: [PATCH 21/45] Fix: monaco errors (#3251) --- src/index.tsx | 37 +++++++++++++++++++++++++++++++++++++ src/types/index.d.ts | 3 +++ 2 files changed, 40 insertions(+) diff --git a/src/index.tsx b/src/index.tsx index 7848f55f3..ce3a4acbc 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -160,6 +160,43 @@ function loadResources() { } loadResources(); +/** + * Set's up Monaco Editor's Workers. + */ +enum Workers { + Json = 'json', + Editor = 'editor', + Typescript='ts', + Css='css', + Html='html' +} + +window.MonacoEnvironment = { + getWorkerUrl(moduleId: any, label: string) { + switch (label) { + case 'json': + return getWorkerFor(Workers.Json); + case 'css': + return getWorkerFor(Workers.Css); + case 'html': + return getWorkerFor(Workers.Html); + case 'typescript': + return getWorkerFor(Workers.Typescript); + default: + return getWorkerFor(Workers.Editor); + } + } +}; + +function getWorkerFor(worker: string): string { + // tslint:disable-next-line:max-line-length + const WORKER_PATH = + 'https://graphstagingblobstorage.blob.core.windows.net/staging/vendor/bower_components/explorer-v2/build'; + + return `data:text/javascript;charset=utf-8,${encodeURIComponent(` + importScripts('${WORKER_PATH}/${worker}.worker.js');`)}`; +} + variantService.initialize(); const telemetryProvider: ITelemetry = telemetry; telemetryProvider.initialize(); diff --git a/src/types/index.d.ts b/src/types/index.d.ts index 581189bba..237180bcf 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -1,7 +1,10 @@ +import { Environment } from 'monaco-editor/esm/vs/editor/editor.api'; + export {}; declare global { interface Window { ClientId: string | undefined + MonacoEnvironment: Environment; } } From 5800f7177f382c66ca05c749c9278b8ca6ddda60 Mon Sep 17 00:00:00 2001 From: Charles Wahome Date: Thu, 25 Jul 2024 17:17:25 +0300 Subject: [PATCH 22/45] Chore: Update 10.3.0 (#3254) --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 51fb122a5..7f26ccca6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "graph-explorer-v2", - "version": "10.2.1", + "version": "10.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "graph-explorer-v2", - "version": "10.2.1", + "version": "10.3.0", "dependencies": { "@augloop/types-core": "file:packages/types-core-2.16.189.tgz", "@axe-core/webdriverjs": "4.8.5", diff --git a/package.json b/package.json index 0069eb492..d3f56d0c4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "graph-explorer-v2", - "version": "10.2.1", + "version": "10.3.0", "private": true, "dependencies": { "@augloop/types-core": "file:packages/types-core-2.16.189.tgz", From 676e1137e885f2353ba7b109e38ae2e6249acc17 Mon Sep 17 00:00:00 2001 From: Nickii Miaro Date: Wed, 7 Aug 2024 14:53:03 +0300 Subject: [PATCH 23/45] Chore: onboard repository to release repository (#3097) --- .github/workflows/linter.yml | 1 + azure-pipelines.yml | 30 +++++++++++++++++-- src/app/services/graph-constants.ts | 1 - src/app/services/reducers/devxApi-reducers.ts | 3 +- src/telemetry/filters.ts | 4 +-- 5 files changed, 32 insertions(+), 7 deletions(-) diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 2268744ee..68b7a33bb 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -185,6 +185,7 @@ jobs: REACT_APP_NOMINATION_PERIOD: ${{secrets.REACT_APP_NOMINATION_PERIOD}} REACT_APP_COOLDOWN_PERIOD: ${{secrets.REACT_APP_COOLDOWN_PERIOD}} REACT_APP_USAGE_TIME: ${{secrets.REACT_APP_USAGE_TIME}} + REACT_APP_DEVX_API_URL: ${{secrets.REACT_APP_DEVX_API_URL}} REACT_APP_MIGRATION_PARAMETER: ${{secrets.REACT_APP_MIGRATION_PARAMETER}} CI: false id: builddeploy diff --git a/azure-pipelines.yml b/azure-pipelines.yml index c56392968..683b17e86 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -63,6 +63,9 @@ resources: type: git name: 1ESPipelineTemplates/1ESPipelineTemplates ref: refs/tags/release + - repository: ReleasePipelines + type: git + name: "Graph Developer Experiences/release-pipelines" extends: template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates @@ -71,6 +74,12 @@ extends: name: Azure-Pipelines-1ESPT-ExDShared image: windows-latest os: windows + + sdl: + sourceRepositoriesToScan: + exclude: + - repository: ReleasePipelines + customBuildTags: - ES365AIMigrationTooling @@ -169,7 +178,8 @@ extends: - job: Three displayName: "Publish artifacts" dependsOn: One - condition: and(succeeded(), or(eq(variables['isMaster'], 'true'), eq(variables['isDev'], 'true'))) + # Test deployment + # condition: and(succeeded(), or(eq(variables['isMaster'], 'true'), eq(variables['isDev'], 'true'))) steps: - task: NodeTool@0 inputs: @@ -190,6 +200,7 @@ extends: REACT_APP_NOMINATION_PERIOD: $(REACT_APP_NOMINATION_PERIOD) REACT_APP_COOLDOWN_PERIOD: $(REACT_APP_COOLDOWN_PERIOD) REACT_APP_USAGE_TIME: $(REACT_APP_USAGE_TIME) + REACT_APP_DEVX_API_URL: $(REACT_APP_DEVX_API_URL) REACT_APP_MIGRATION_PARAMETER: $(REACT_APP_MIGRATION_PARAMETER) displayName: "Build static assets for staging" @@ -216,6 +227,7 @@ extends: REACT_APP_NOMINATION_PERIOD: $(REACT_APP_NOMINATION_PERIOD) REACT_APP_COOLDOWN_PERIOD: $(REACT_APP_COOLDOWN_PERIOD) REACT_APP_USAGE_TIME: $(REACT_APP_USAGE_TIME) + REACT_APP_DEVX_API_URL: $(REACT_APP_DEVX_API_URL) REACT_APP_MIGRATION_PARAMETER: $(REACT_APP_MIGRATION_PARAMETER) displayName: "Build static assets for prod" @@ -252,6 +264,14 @@ extends: contents: node_modules displayName: "Delete node_modules" + - task: ArchiveFiles@2 + inputs: + rootFolderOrFile: '$(Build.ArtifactStagingDirectory)/build' + includeRootFolder: false + archiveType: 'zip' + archiveFile: '$(Build.ArtifactStagingDirectory)/build/graph-explorer.zip' + replaceExistingArchive: true + templateContext: outputs: - output: pipelineArtifact @@ -261,4 +281,10 @@ extends: - output: pipelineArtifact displayName: 'Publish Artifact: drop' targetPath: "$(build.ArtifactStagingDirectory)/build" - artifactName: drop \ No newline at end of file + artifactName: drop + + - template: pipelines/templates/checkout-and-copy-1es.yml@ReleasePipelines + parameters: + directory: 'microsoft-graph-explorer-v4' + repoName: ReleasePipelines + dependsOn: ['Three'] diff --git a/src/app/services/graph-constants.ts b/src/app/services/graph-constants.ts index c1ff4ad76..bdc531522 100644 --- a/src/app/services/graph-constants.ts +++ b/src/app/services/graph-constants.ts @@ -5,7 +5,6 @@ export const BETA_USER_INFO_URL = `${GRAPH_URL}/beta/me/profile`; export const USER_PICTURE_URL = `${GRAPH_URL}/beta/me/photo/$value`; export const AUTH_URL = 'https://login.microsoftonline.com'; export const DEFAULT_USER_SCOPES = 'openid profile User.Read'; -export const DEVX_API_URL = 'https://graphexplorerapi.azurewebsites.net'; export const GRAPH_API_SANDBOX_URL = 'https://proxy.apisandbox.msdn.microsoft.com/svc'; export const GRAPH_API_SANDBOX_ENDPOINT_URL = diff --git a/src/app/services/reducers/devxApi-reducers.ts b/src/app/services/reducers/devxApi-reducers.ts index 227ad1a3c..f58be799c 100644 --- a/src/app/services/reducers/devxApi-reducers.ts +++ b/src/app/services/reducers/devxApi-reducers.ts @@ -1,10 +1,9 @@ import { AppAction } from '../../../types/action'; import { IDevxAPI } from '../../../types/devx-api'; -import { DEVX_API_URL } from '../graph-constants'; import { SET_DEVX_API_URL_SUCCESS } from '../redux-constants'; const initialState: IDevxAPI = { - baseUrl: DEVX_API_URL, + baseUrl: process.env.REACT_APP_DEVX_API_URL || '', parameters: '' }; export function devxApi(state: IDevxAPI = initialState, action: AppAction): any { diff --git a/src/telemetry/filters.ts b/src/telemetry/filters.ts index d7893c4a1..1b4670e39 100644 --- a/src/telemetry/filters.ts +++ b/src/telemetry/filters.ts @@ -1,6 +1,5 @@ import { ITelemetryItem } from '@microsoft/applicationinsights-web'; import { - DEVX_API_URL, GRAPH_API_SANDBOX_URL, GRAPH_TOOOLKIT_EXAMPLE_URL, GRAPH_URL, @@ -32,10 +31,11 @@ export function filterRemoteDependencyData(envelope: ITelemetryItem): boolean { const urlObject = new URL(baseData.target || ''); const graphProxyUrl = store.getState()?.proxyUrl; + const devxApiUrl = process.env.REACT_APP_DEVX_API_URL || ''; const targetsToInclude = [ GRAPH_URL, - DEVX_API_URL, + new URL(devxApiUrl).origin, GRAPH_API_SANDBOX_URL, new URL(graphProxyUrl).origin, new URL(GRAPH_TOOOLKIT_EXAMPLE_URL).origin, From b366636d035c7bb9d5ed895d0468d6a15938510d Mon Sep 17 00:00:00 2001 From: Charles Wahome Date: Wed, 7 Aug 2024 16:57:39 +0300 Subject: [PATCH 24/45] Task: use redux toolkit (#3186) --- azure-pipelines.yml | 3 +- package-lock.json | 151 +++++- package.json | 5 +- src/app/middleware/localStorageMiddleware.ts | 33 +- src/app/middleware/telemetryMiddleware.ts | 112 +++-- .../adaptive-cards-action-creator.spec.ts | 214 --------- .../actions/adaptive-cards-action-creator.ts | 78 ---- .../actions/auth-action-creators.spec.ts | 61 ++- .../services/actions/auth-action-creators.ts | 57 --- .../autocomplete-action-creators.spec.ts | 89 ++-- .../actions/autocomplete-action-creators.ts | 47 -- .../collections-action-creators.spec.ts | 37 +- .../actions/collections-action-creators.ts | 26 -- .../actions/devxApi-action-creators.spec.ts | 4 +- .../actions/devxApi-action-creators.ts | 9 - .../actions/dimensions-action-creator.spec.ts | 4 +- .../actions/dimensions-action-creator.ts | 10 - .../explorer-mode-action-creator.spec.ts | 8 +- .../actions/explorer-mode-action-creator.ts | 10 - .../services/actions/mockThunkMiddleware.ts | 8 + .../permissions-action-creator.spec.ts | 303 +++++------- .../actions/permissions-action-creator.ts | 439 ------------------ .../actions/profile-action-creators.spec.ts | 38 +- ...-action-creators.ts => profile-actions.ts} | 41 +- .../actions/proxy-action-creator.spec.ts | 48 +- .../services/actions/proxy-action-creator.ts | 25 - .../actions/query-action-creator-util.ts | 33 +- .../actions/query-action-creators.spec.ts | 67 +-- .../services/actions/query-action-creators.ts | 225 --------- .../query-input-action-creators.spec.ts | 8 +- .../actions/query-input-action-creators.ts | 10 - .../actions/query-loading-action-creators.ts | 9 - .../query-status-action-creator.spec.ts | 34 +- .../actions/query-status-action-creator.ts | 27 -- .../request-history-action-creators.spec.ts | 82 ++-- .../request-history-action-creators.ts | 68 --- .../resource-explorer-action-creators.spec.ts | 141 +++--- .../resource-explorer-action-creators.ts | 82 ---- .../response-expanded-action-creator.spec.ts | 13 +- .../response-expanded-action-creator.ts | 9 - .../services/actions/revoke-scopes.action.ts | 146 ++++++ .../actions/samples-action-creators.spec.ts | 68 ++- .../actions/samples-action-creators.ts | 65 --- .../actions/snippet-action-creator.spec.ts | 83 +--- .../actions/snippet-action-creator.ts | 121 ----- .../terms-of-use-action-creator.spec.ts | 18 +- .../actions/terms-of-use-action-creator.ts | 11 - .../actions/theme-action-creator.spec.ts | 26 +- .../services/actions/theme-action-creator.ts | 17 - .../toggle-sidebar-action-creator.spec.ts | 8 +- .../actions/toggle-sidebar-action-creator.ts | 9 - .../reducers/adaptive-cards-reducer.spec.ts | 81 ---- .../reducers/adaptive-cards-reducer.ts | 36 -- .../services/reducers/auth-reducers.spec.ts | 49 -- src/app/services/reducers/auth-reducers.ts | 45 -- .../reducers/autocomplete-reducer.spec.ts | 70 --- .../services/reducers/autocomplete-reducer.ts | 34 -- .../reducers/collections-reducer.spec.ts | 123 ----- .../services/reducers/collections-reducer.ts | 46 -- .../reducers/collections-reducer.util.ts | 2 +- src/app/services/reducers/devxApi-reducers.ts | 18 - .../services/reducers/dimensions-reducers.ts | 31 -- .../reducers/graph-explorer-mode-reducer.ts | 12 - .../reducers/graph-explorer-mode.spec.ts | 13 - src/app/services/reducers/index.ts | 63 +-- .../reducers/permissions-reducer.spec.ts | 123 ----- .../services/reducers/permissions-reducer.ts | 99 ---- .../services/reducers/profile-reducer.spec.ts | 42 -- src/app/services/reducers/profile-reducer.ts | 13 - .../services/reducers/proxy-url-reducer.ts | 12 - .../reducers/query-input-reducers.spec.ts | 34 -- .../services/reducers/query-input-reducers.ts | 11 - .../reducers/query-loading-reducers.spec.ts | 105 ----- .../reducers/query-loading-reducers.ts | 33 -- .../reducers/query-runner-reducers.spec.ts | 79 ---- .../reducers/query-runner-reducers.ts | 34 -- .../reducers/query-runner-status-reducers.ts | 23 - .../reducers/request-history-reducers.spec.ts | 86 ---- .../reducers/request-history-reducers.ts | 37 -- .../reducers/resources-reducer.spec.ts | 166 ------- .../services/reducers/resources-reducer.ts | 37 -- .../reducers/response-expanded-reducer.ts | 12 - .../reducers/samples-reducers.spec.ts | 85 ---- src/app/services/reducers/samples-reducers.ts | 33 -- .../services/reducers/snippet-reducer.spec.ts | 48 -- src/app/services/reducers/snippet-reducer.ts | 46 -- .../reducers/terms-of-use-reducer.spec.ts | 18 - .../services/reducers/terms-of-use-reducer.ts | 11 - src/app/services/reducers/theme-reducer.ts | 11 - .../reducers/toggle-sidebar-reducer.spec.ts | 109 ----- .../reducers/toggle-sidebar-reducer.ts | 29 -- src/app/services/redux-constants.ts | 113 +++-- src/app/services/slices/auth.slice.ts | 144 ++++++ src/app/services/slices/autocomplete.slice.ts | 63 +++ src/app/services/slices/collections.slice.ts | 38 ++ src/app/services/slices/devxapi.slice.ts | 18 + src/app/services/slices/dimensions.slice.ts | 32 ++ .../services/slices/explorer-mode.slice.ts | 13 + .../services/slices/graph-response.slice.ts | 261 +++++++++++ src/app/services/slices/history.slice.ts | 33 ++ src/app/services/slices/index.ts | 1 + .../slices/permission-grants.slice.ts | 122 +++++ src/app/services/slices/profile.slice.ts | 57 +++ src/app/services/slices/proxy.slice.ts | 40 ++ src/app/services/slices/query-status.slice.ts | 28 ++ src/app/services/slices/resources.slice.ts | 87 ++++ .../slices/response-area-expanded.slice.ts | 12 + src/app/services/slices/sample-query.slice.ts | 23 + src/app/services/slices/samples.slice.ts | 76 +++ src/app/services/slices/scopes.slice.ts | 105 +++++ .../slices/sidebar-properties.slice.ts | 50 ++ src/app/services/slices/snippet.slice.ts | 104 +++++ src/app/services/slices/terms-of-use.slice.ts | 12 + src/app/services/slices/theme.slice.ts | 12 + src/app/utils/error-utils/ScopesError.ts | 25 + src/app/utils/getPermissionsScopeType.ts | 9 + src/app/utils/query-url-sanitization.ts | 5 +- src/app/utils/snippet.utils.ts | 30 ++ src/app/utils/status-message.ts | 4 +- src/app/views/App.tsx | 39 +- src/app/views/app-sections/StatusMessages.tsx | 14 +- .../views/app-sections/TermsOfUseMessage.tsx | 7 +- .../views/authentication/Authentication.tsx | 23 +- .../views/authentication/profile/Profile.tsx | 26 +- src/app/views/main-header/FeedbackButton.tsx | 4 +- src/app/views/main-header/Help.tsx | 8 +- src/app/views/main-header/MainHeader.tsx | 8 +- .../views/main-header/settings/Settings.tsx | 2 +- .../main-header/settings/ThemeChooser.tsx | 17 +- .../views/query-response/QueryResponse.tsx | 7 +- .../adaptive-cards/AdaptiveCard.tsx | 64 ++- .../adaptive-cards/adaptive-cards.util.ts | 48 ++ .../headers/ResponseHeaders.tsx | 2 +- .../pivot-items/pivot-items.tsx | 8 +- .../query-response/response/Response.tsx | 3 +- .../response/ResponseMessages.tsx | 21 +- .../query-response/snippets/Snippets.tsx | 7 +- .../snippets/snippets-helper.tsx | 12 +- src/app/views/query-runner/QueryRunner.tsx | 39 +- .../query-runner/query-input/QueryInput.tsx | 14 +- .../auto-complete/AutoComplete.tsx | 11 +- .../auto-complete/auto-complete.util.ts | 1 + .../views/query-runner/request/Request.tsx | 29 +- .../views/query-runner/request/auth/Auth.tsx | 7 +- .../request/feedback/FeedbackForm.tsx | 15 +- .../request/headers/RequestHeaders.tsx | 13 +- .../request/permissions/ConsentType.tsx | 5 +- .../request/permissions/PermissionItem.tsx | 33 +- .../request/permissions/Permissions.Full.tsx | 51 +- .../request/permissions/Permissions.Query.tsx | 28 +- .../query-runner/request/permissions/util.ts | 28 +- src/app/views/sidebar/history/History.tsx | 76 +-- .../views/sidebar/history/har-utils.spec.ts | 2 +- src/app/views/sidebar/history/har-utils.ts | 17 +- .../resource-explorer/ResourceExplorer.tsx | 15 +- .../resource-explorer/ResourceLink.tsx | 2 +- .../collection/PreviewCollection.tsx | 7 +- .../collection/UploadCollection.tsx | 20 +- .../collection/postman.util.ts | 3 +- .../command-options/CommandOptions.tsx | 7 +- .../resource-explorer.utils.ts | 2 +- .../sidebar/sample-queries/SampleQueries.tsx | 19 +- src/index.tsx | 54 ++- src/modules/authentication/msal-app.ts | 2 - src/modules/cache/history-utils.spec.ts | 6 +- .../suggestions/utilities/delimiters.ts | 3 +- src/store/definition.d.ts | 1 - src/store/index.ts | 90 +++- src/types/action.ts | 2 +- src/types/adaptivecard.ts | 7 - src/types/authentication.ts | 9 +- src/types/history.ts | 10 +- src/types/permissions.ts | 12 +- src/types/profile.ts | 4 +- src/types/query-response.ts | 7 +- src/types/root.ts | 45 -- src/types/status.ts | 2 +- 177 files changed, 2867 insertions(+), 4867 deletions(-) delete mode 100644 src/app/services/actions/adaptive-cards-action-creator.spec.ts delete mode 100644 src/app/services/actions/adaptive-cards-action-creator.ts delete mode 100644 src/app/services/actions/auth-action-creators.ts delete mode 100644 src/app/services/actions/autocomplete-action-creators.ts delete mode 100644 src/app/services/actions/collections-action-creators.ts delete mode 100644 src/app/services/actions/devxApi-action-creators.ts delete mode 100644 src/app/services/actions/dimensions-action-creator.ts delete mode 100644 src/app/services/actions/explorer-mode-action-creator.ts create mode 100644 src/app/services/actions/mockThunkMiddleware.ts delete mode 100644 src/app/services/actions/permissions-action-creator.ts rename src/app/services/actions/{profile-action-creators.ts => profile-actions.ts} (74%) delete mode 100644 src/app/services/actions/proxy-action-creator.ts delete mode 100644 src/app/services/actions/query-action-creators.ts delete mode 100644 src/app/services/actions/query-input-action-creators.ts delete mode 100644 src/app/services/actions/query-loading-action-creators.ts delete mode 100644 src/app/services/actions/query-status-action-creator.ts delete mode 100644 src/app/services/actions/request-history-action-creators.ts delete mode 100644 src/app/services/actions/resource-explorer-action-creators.ts delete mode 100644 src/app/services/actions/response-expanded-action-creator.ts create mode 100644 src/app/services/actions/revoke-scopes.action.ts delete mode 100644 src/app/services/actions/samples-action-creators.ts delete mode 100644 src/app/services/actions/snippet-action-creator.ts delete mode 100644 src/app/services/actions/terms-of-use-action-creator.ts delete mode 100644 src/app/services/actions/theme-action-creator.ts delete mode 100644 src/app/services/actions/toggle-sidebar-action-creator.ts delete mode 100644 src/app/services/reducers/adaptive-cards-reducer.spec.ts delete mode 100644 src/app/services/reducers/adaptive-cards-reducer.ts delete mode 100644 src/app/services/reducers/auth-reducers.spec.ts delete mode 100644 src/app/services/reducers/auth-reducers.ts delete mode 100644 src/app/services/reducers/autocomplete-reducer.spec.ts delete mode 100644 src/app/services/reducers/autocomplete-reducer.ts delete mode 100644 src/app/services/reducers/collections-reducer.spec.ts delete mode 100644 src/app/services/reducers/collections-reducer.ts delete mode 100644 src/app/services/reducers/devxApi-reducers.ts delete mode 100644 src/app/services/reducers/dimensions-reducers.ts delete mode 100644 src/app/services/reducers/graph-explorer-mode-reducer.ts delete mode 100644 src/app/services/reducers/graph-explorer-mode.spec.ts delete mode 100644 src/app/services/reducers/permissions-reducer.spec.ts delete mode 100644 src/app/services/reducers/permissions-reducer.ts delete mode 100644 src/app/services/reducers/profile-reducer.spec.ts delete mode 100644 src/app/services/reducers/profile-reducer.ts delete mode 100644 src/app/services/reducers/proxy-url-reducer.ts delete mode 100644 src/app/services/reducers/query-input-reducers.spec.ts delete mode 100644 src/app/services/reducers/query-input-reducers.ts delete mode 100644 src/app/services/reducers/query-loading-reducers.spec.ts delete mode 100644 src/app/services/reducers/query-loading-reducers.ts delete mode 100644 src/app/services/reducers/query-runner-reducers.spec.ts delete mode 100644 src/app/services/reducers/query-runner-reducers.ts delete mode 100644 src/app/services/reducers/query-runner-status-reducers.ts delete mode 100644 src/app/services/reducers/request-history-reducers.spec.ts delete mode 100644 src/app/services/reducers/request-history-reducers.ts delete mode 100644 src/app/services/reducers/resources-reducer.spec.ts delete mode 100644 src/app/services/reducers/resources-reducer.ts delete mode 100644 src/app/services/reducers/response-expanded-reducer.ts delete mode 100644 src/app/services/reducers/samples-reducers.spec.ts delete mode 100644 src/app/services/reducers/samples-reducers.ts delete mode 100644 src/app/services/reducers/snippet-reducer.spec.ts delete mode 100644 src/app/services/reducers/snippet-reducer.ts delete mode 100644 src/app/services/reducers/terms-of-use-reducer.spec.ts delete mode 100644 src/app/services/reducers/terms-of-use-reducer.ts delete mode 100644 src/app/services/reducers/theme-reducer.ts delete mode 100644 src/app/services/reducers/toggle-sidebar-reducer.spec.ts delete mode 100644 src/app/services/reducers/toggle-sidebar-reducer.ts create mode 100644 src/app/services/slices/auth.slice.ts create mode 100644 src/app/services/slices/autocomplete.slice.ts create mode 100644 src/app/services/slices/collections.slice.ts create mode 100644 src/app/services/slices/devxapi.slice.ts create mode 100644 src/app/services/slices/dimensions.slice.ts create mode 100644 src/app/services/slices/explorer-mode.slice.ts create mode 100644 src/app/services/slices/graph-response.slice.ts create mode 100644 src/app/services/slices/history.slice.ts create mode 100644 src/app/services/slices/index.ts create mode 100644 src/app/services/slices/permission-grants.slice.ts create mode 100644 src/app/services/slices/profile.slice.ts create mode 100644 src/app/services/slices/proxy.slice.ts create mode 100644 src/app/services/slices/query-status.slice.ts create mode 100644 src/app/services/slices/resources.slice.ts create mode 100644 src/app/services/slices/response-area-expanded.slice.ts create mode 100644 src/app/services/slices/sample-query.slice.ts create mode 100644 src/app/services/slices/samples.slice.ts create mode 100644 src/app/services/slices/scopes.slice.ts create mode 100644 src/app/services/slices/sidebar-properties.slice.ts create mode 100644 src/app/services/slices/snippet.slice.ts create mode 100644 src/app/services/slices/terms-of-use.slice.ts create mode 100644 src/app/services/slices/theme.slice.ts create mode 100644 src/app/utils/error-utils/ScopesError.ts create mode 100644 src/app/utils/getPermissionsScopeType.ts create mode 100644 src/app/utils/snippet.utils.ts create mode 100644 src/app/views/query-response/adaptive-cards/adaptive-cards.util.ts delete mode 100644 src/store/definition.d.ts diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 683b17e86..1124b5902 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -178,8 +178,7 @@ extends: - job: Three displayName: "Publish artifacts" dependsOn: One - # Test deployment - # condition: and(succeeded(), or(eq(variables['isMaster'], 'true'), eq(variables['isDev'], 'true'))) + condition: and(succeeded(), or(eq(variables['isMaster'], 'true'), eq(variables['isDev'], 'true'))) steps: - task: NodeTool@0 inputs: diff --git a/package-lock.json b/package-lock.json index 7f26ccca6..41e96fdab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@microsoft/microsoft-graph-client": "3.0.7", "@monaco-editor/react": "4.6.0", "@ms-ofb/officebrowserfeedbacknpm": "file:packages/officebrowserfeedbacknpm-1.6.6.tgz", + "@reduxjs/toolkit": "2.2.5", "adaptive-expressions": "4.22.2", "adaptivecards": "3.0.2", "adaptivecards-templating": "2.3.1", @@ -57,8 +58,6 @@ "react-app-polyfill": "3.0.0", "react-dom": "18.2.0", "react-redux": "8.1.3", - "redux": "4.2.1", - "redux-thunk": "2.4.2", "resolve": "1.22.8", "sass": "1.72.0", "sass-loader": "14.2.1", @@ -78,6 +77,7 @@ "@types/isomorphic-fetch": "0.0.39", "@types/jest": "29.5.12", "@types/lodash.debounce": "4.0.9", + "@types/markdown-it": "^14.1.1", "@types/react": "18.2.55", "@types/react-dom": "18.2.19", "@types/react-redux": "7.1.30", @@ -97,6 +97,7 @@ "jest-fetch-mock": "3.0.3", "jest-sonar-reporter": "2.0.0", "jest-watch-typeahead": "2.2.2", + "markdown-it": "^14.1.0", "node-notifier": "10.0.1", "react-dev-utils": "12.0.1", "redux-logger": "3.0.6", @@ -4534,6 +4535,51 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@reduxjs/toolkit": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.2.5.tgz", + "integrity": "sha512-aeFA/s5NCG7NoJe/MhmwREJxRkDs0ZaSqt0MxhWUrwCf1UQXpwR87RROJEql0uAkLI6U7snBOYOcKw83ew3FPg==", + "dependencies": { + "immer": "^10.0.3", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, + "node_modules/@reduxjs/toolkit/node_modules/immer": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", + "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/@reduxjs/toolkit/node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" + }, + "node_modules/@reduxjs/toolkit/node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "peerDependencies": { + "redux": "^5.0.0" + } + }, "node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -4955,6 +5001,12 @@ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=" }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "dev": true + }, "node_modules/@types/lodash": { "version": "4.14.182", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz", @@ -4982,6 +5034,22 @@ "resolved": "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw==" }, + "node_modules/@types/markdown-it": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.1.tgz", + "integrity": "sha512-4NpsnpYl2Gt1ljyBGrKMxFYAYvpqbnnkgP/i/g+NLpjEUa3obn1XJCur9YbEXKDAkaXqsR1LbDnGEJ0MmKFxfg==", + "dev": true, + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "dev": true + }, "node_modules/@types/mime": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", @@ -14187,6 +14255,15 @@ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dev": true, + "dependencies": { + "uc.micro": "^2.0.0" + } + }, "node_modules/loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", @@ -14337,6 +14414,47 @@ "integrity": "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==", "dev": true }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/markdown-it/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/markdown-it/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -16287,6 +16405,15 @@ "node": ">=6" } }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/pure-rand": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.4.tgz", @@ -16845,6 +16972,7 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "devOptional": true, "dependencies": { "@babel/runtime": "^7.9.2" } @@ -16867,14 +16995,6 @@ "lodash.isplainobject": "^4.0.6" } }, - "node_modules/redux-thunk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz", - "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==", - "peerDependencies": { - "redux": "^4" - } - }, "node_modules/reflect.getprototypeof": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz", @@ -17023,6 +17143,11 @@ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" }, + "node_modules/reselect": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.0.tgz", + "integrity": "sha512-aw7jcGLDpSgNDyWBQLv2cedml85qd95/iszJjN988zX1t7AVRJi19d9kto5+W7oCfQ94gyo40dVbT6g2k4/kXg==" + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -18696,6 +18821,12 @@ "node": ">=14.17" } }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true + }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", diff --git a/package.json b/package.json index d3f56d0c4..b03c665a1 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@microsoft/microsoft-graph-client": "3.0.7", "@monaco-editor/react": "4.6.0", "@ms-ofb/officebrowserfeedbacknpm": "file:packages/officebrowserfeedbacknpm-1.6.6.tgz", + "@reduxjs/toolkit": "2.2.5", "adaptive-expressions": "4.22.2", "adaptivecards": "3.0.2", "adaptivecards-templating": "2.3.1", @@ -52,8 +53,6 @@ "react-app-polyfill": "3.0.0", "react-dom": "18.2.0", "react-redux": "8.1.3", - "redux": "4.2.1", - "redux-thunk": "2.4.2", "resolve": "1.22.8", "sass": "1.72.0", "sass-loader": "14.2.1", @@ -96,6 +95,7 @@ "@types/isomorphic-fetch": "0.0.39", "@types/jest": "29.5.12", "@types/lodash.debounce": "4.0.9", + "@types/markdown-it": "^14.1.1", "@types/react": "18.2.55", "@types/react-dom": "18.2.19", "@types/react-redux": "7.1.30", @@ -115,6 +115,7 @@ "jest-fetch-mock": "3.0.3", "jest-sonar-reporter": "2.0.0", "jest-watch-typeahead": "2.2.2", + "markdown-it": "^14.1.0", "node-notifier": "10.0.1", "react-dev-utils": "12.0.1", "redux-logger": "3.0.6", diff --git a/src/app/middleware/localStorageMiddleware.ts b/src/app/middleware/localStorageMiddleware.ts index 9bbfba6c1..a12673fe1 100644 --- a/src/app/middleware/localStorageMiddleware.ts +++ b/src/app/middleware/localStorageMiddleware.ts @@ -1,37 +1,37 @@ +import { Dispatch, Middleware, UnknownAction } from '@reduxjs/toolkit'; import { collectionsCache } from '../../modules/cache/collections.cache'; -import { resourcesCache } from '../../modules/cache/resources.cache'; import { samplesCache } from '../../modules/cache/samples.cache'; import { AppAction } from '../../types/action'; -import { ResourcePath } from '../../types/resources'; -import { addResourcePaths } from '../services/actions/collections-action-creators'; +import { Collection, ResourcePath } from '../../types/resources'; import { CURRENT_THEME } from '../services/graph-constants'; import { getUniquePaths } from '../services/reducers/collections-reducer.util'; import { - CHANGE_THEME_SUCCESS, COLLECTION_CREATE_SUCCESS, FETCH_RESOURCES_ERROR, FETCH_RESOURCES_SUCCESS, + CHANGE_THEME_SUCCESS, COLLECTION_CREATE_SUCCESS, RESOURCEPATHS_ADD_SUCCESS, RESOURCEPATHS_DELETE_SUCCESS, SAMPLES_FETCH_SUCCESS } from '../services/redux-constants'; import { saveToLocalStorage } from '../utils/local-storage'; -const localStorageMiddleware = (store: any) => (next: any) => async (action: AppAction) => { +const localStorageMiddleware: Middleware<{}, any, Dispatch> = () => (next) => async (value) => { + const action = value as AppAction; switch (action.type) { case CHANGE_THEME_SUCCESS: - saveToLocalStorage(CURRENT_THEME,action.response); + saveToLocalStorage(CURRENT_THEME, action.payload); break; case SAMPLES_FETCH_SUCCESS: - samplesCache.saveSamples(action.response); + samplesCache.saveSamples(action.payload); break; case RESOURCEPATHS_ADD_SUCCESS: { const collections = await collectionsCache.read(); const item = collections.find(k => k.isDefault)!; - item.paths = getUniquePaths(item.paths, action.response); + item.paths = getUniquePaths(item.paths, action.payload as ResourcePath[]); await collectionsCache.update(item.id, item); break; } case RESOURCEPATHS_DELETE_SUCCESS: { - const paths = action.response; + const paths = action.payload as ResourcePath[]; const collections = await collectionsCache.read(); const collection = collections.find(k => k.isDefault)!; paths.forEach((path: ResourcePath) => { @@ -45,17 +45,7 @@ const localStorageMiddleware = (store: any) => (next: any) => async (action: App } case COLLECTION_CREATE_SUCCESS: { - await collectionsCache.create(action.response); - break; - } - - case FETCH_RESOURCES_SUCCESS: - case FETCH_RESOURCES_ERROR: { - resourcesCache.readCollection().then((data: ResourcePath[]) => { - if (data && data.length > 0) { - store.dispatch(addResourcePaths(data)); - } - }); + await collectionsCache.create(action.payload as Collection); break; } @@ -63,6 +53,7 @@ const localStorageMiddleware = (store: any) => (next: any) => async (action: App break; } return next(action); -}; +} + export default localStorageMiddleware; diff --git a/src/app/middleware/telemetryMiddleware.ts b/src/app/middleware/telemetryMiddleware.ts index a37f69c06..45267821f 100644 --- a/src/app/middleware/telemetryMiddleware.ts +++ b/src/app/middleware/telemetryMiddleware.ts @@ -1,4 +1,7 @@ import { SeverityLevel } from '@microsoft/applicationinsights-web'; +import { Dispatch, Middleware, UnknownAction } from '@reduxjs/toolkit'; + +import { ApplicationState } from '../../store'; import { componentNames, errorTypes, @@ -7,9 +10,7 @@ import { } from '../../telemetry'; import { AppAction } from '../../types/action'; import { IQuery } from '../../types/query-runner'; -import { ApplicationState } from '../../types/root'; import { - FETCH_ADAPTIVE_CARD_ERROR, FETCH_SCOPES_ERROR, GET_SNIPPET_ERROR, RESOURCEPATHS_ADD_SUCCESS, @@ -18,65 +19,56 @@ import { } from '../services/redux-constants'; import { sanitizeQueryUrl } from '../utils/query-url-sanitization'; -const telemetryMiddleware = - (store: any) => (next: any) => async (action: AppAction) => { - const state: ApplicationState = store.getState(); - switch (action.type) { - case GET_SNIPPET_ERROR: { - trackException( - componentNames.GET_SNIPPET_ACTION, - state.sampleQuery, - action.response.error, - { - Language: action.response.language - } - ); - break; - } - case FETCH_SCOPES_ERROR: { - trackException( - componentNames.FETCH_PERMISSIONS_ACTION, - state.sampleQuery, - action.response.error, - {} - ); - break; - } - case SAMPLES_FETCH_ERROR: { - trackException( - componentNames.FETCH_SAMPLES_ACTION, - state.sampleQuery, - action.response, - {} - ); - break; - } - case FETCH_ADAPTIVE_CARD_ERROR: { - trackException( - componentNames.GET_ADAPTIVE_CARD_ACTION, - state.sampleQuery, - action.response, - {} - ); - break; - } - case RESOURCEPATHS_ADD_SUCCESS: { - telemetry.trackEvent(eventTypes.LISTITEM_CLICK_EVENT, { - ComponentName: componentNames.ADD_RESOURCE_TO_COLLECTION_LIST_ITEM, - ResourcePath: action.response[0].url - }); - break; - } - case RESOURCEPATHS_DELETE_SUCCESS: { - telemetry.trackEvent(eventTypes.LISTITEM_CLICK_EVENT, { - ComponentName: componentNames.REMOVE_RESOURCE_FROM_COLLECTION_BUTTON, - ResourceCount: action.response.length - }); - break; - } +const telemetryMiddleware: Middleware<{}, any, Dispatch> = (store) => (next) => async (value) => { + const state: ApplicationState = store.getState(); + const action = value as AppAction; + switch (action.type) { + case GET_SNIPPET_ERROR: { + trackException( + componentNames.GET_SNIPPET_ACTION, + state.sampleQuery, + action.payload.error, + { + Language: action.payload.language + } + ); + break; + } + case FETCH_SCOPES_ERROR: { + trackException( + componentNames.FETCH_PERMISSIONS_ACTION, + state.sampleQuery, + action.payload.error, + {} + ); + break; + } + case SAMPLES_FETCH_ERROR: { + trackException( + componentNames.FETCH_SAMPLES_ACTION, + state.sampleQuery, + action.payload, + {} + ); + break; } - return next(action); - }; + case RESOURCEPATHS_ADD_SUCCESS: { + telemetry.trackEvent(eventTypes.LISTITEM_CLICK_EVENT, { + ComponentName: componentNames.ADD_RESOURCE_TO_COLLECTION_LIST_ITEM, + ResourcePath: action.payload[0].url + }); + break; + } + case RESOURCEPATHS_DELETE_SUCCESS: { + telemetry.trackEvent(eventTypes.LISTITEM_CLICK_EVENT, { + ComponentName: componentNames.REMOVE_RESOURCE_FROM_COLLECTION_BUTTON, + ResourceCount: action.payload.length + }); + break; + } + } + return next(action); +}; function trackException( componentName: string, diff --git a/src/app/services/actions/adaptive-cards-action-creator.spec.ts b/src/app/services/actions/adaptive-cards-action-creator.spec.ts deleted file mode 100644 index 0f530ea01..000000000 --- a/src/app/services/actions/adaptive-cards-action-creator.spec.ts +++ /dev/null @@ -1,214 +0,0 @@ -import { - getAdaptiveCard, - getAdaptiveCardError, - getAdaptiveCardPending, - getAdaptiveCardSuccess -} from '../../../app/services/actions/adaptive-cards-action-creator'; -import { - FETCH_ADAPTIVE_CARD_ERROR, - FETCH_ADAPTIVE_CARD_PENDING, - FETCH_ADAPTIVE_CARD_SUCCESS -} from '../../../app/services/redux-constants'; -import { IQuery } from '../../../types/query-runner'; -import configureMockStore from 'redux-mock-store'; -import thunk from 'redux-thunk'; -import { AppAction } from '../../../types/action'; -const middleware = [thunk]; -const mockStore = configureMockStore(middleware); - -describe('Graph Explorer Adaptive Cards Action Creators\'', () => { - beforeEach(() => { - // eslint-disable-next-line no-undef - fetchMock.resetMocks(); - }); - - it('should dispatch ADAPTIVE_FETCH_SUCCESS when getAdaptiveCardSuccess is called', () => { - - const result = { sample: 'response' }; - const expectedAction: AppAction = { - type: FETCH_ADAPTIVE_CARD_SUCCESS, - response: result - }; - - const action = getAdaptiveCardSuccess(result); - expect(action).toEqual(expectedAction); - - }); - - it('should dispatch ADAPTIVE_FETCH_PENDING when getAdaptiveCardPending is called', () => { - - const expectedAction: AppAction = { - type: FETCH_ADAPTIVE_CARD_PENDING, - response: '' - }; - - const action = getAdaptiveCardPending(); - expect(action).toEqual(expectedAction); - - }); - - it('should dispatch ADAPTIVE_FETCH_ERROR when getAdaptiveCardError is called', () => { - - const error = 'sample error'; - const expectedAction: AppAction = { - type: FETCH_ADAPTIVE_CARD_ERROR, - response: error - }; - - const action = getAdaptiveCardError(error); - expect(action).toEqual(expectedAction); - - }); - - it('should dispatch FETCH_ADAPTIVE_CARD_SUCCESS when no payload is supplied to getAdaptiveCard()', () => { - const result = { sample: 'response' }; - const expectedAction: AppAction = { - type: FETCH_ADAPTIVE_CARD_SUCCESS, - response: {} - }; - - // eslint-disable-next-line no-undef - fetchMock.mockResponse(JSON.stringify(result)); - - const store = mockStore({}); - const sampleQuery: IQuery = { - selectedVerb: 'GET', - selectedVersion: 'v1', - sampleUrl: 'https://graph.microsoft.com/v1.0/me/events', - sampleBody: '', - sampleHeaders: [] - } - - // @ts-ignore - return store.dispatch(getAdaptiveCard('', sampleQuery)) - // @ts-ignore - .then(() => { - expect(store.getActions()).toEqual([expectedAction]); - }); - }); - - it('should dispatch FETCH_ADAPTIVE_CARD_SUCCESS when getAdaptiveCards() is called with payload', () => { - const result = { sample: 'response' }; - const expectedAction: AppAction[] = [ - { - type: FETCH_ADAPTIVE_CARD_SUCCESS, - response: { - 'Given name': 'Megan', - 'Surname': 'Bowen', - 'Job title': 'Auditor', - 'Office location': '12/1110', - 'Email': 'MeganBowen@M365x214355.onmicrosoft.com', - 'Business phones': '+1 412 555 0109' - } - }, - { - type: FETCH_ADAPTIVE_CARD_PENDING, - response: '' - } - ]; - - const payload = { - businessPhones: ['+1 412 555 0109'], - displayName: 'Megan Bowen', - givenName: 'Megan', - jobTitle: 'Auditor', - mail: 'MeganB@M365x214355.onmicrosoft.com', - mobilePhone: null, - officeLocation: '12/1110', - preferredLanguage: 'en-US', - surname: 'Bowen', - userPrincipalName: 'MeganB@M365x214355.onmicrosoft.com', - id: '48d31887-5fad-4d73-a9f5-3c356e68a038' - } - - // eslint-disable-next-line no-undef - fetchMock.mockResponse(JSON.stringify(result)); - - const store = mockStore({}); - const sampleQuery: IQuery = { - selectedVerb: 'GET', - selectedVersion: 'v1', - sampleUrl: 'https://graph.microsoft.com/v1.0/me/', - sampleBody: '', - sampleHeaders: [] - } - - // @ts-ignore - return store.dispatch(getAdaptiveCard(payload, sampleQuery)) - // @ts-ignore - .then(() => { - expect(store.getActions()[0].type).toEqual(expectedAction[1].type); - expect(store.getActions()[1].type).toEqual(expectedAction[0].type); - }); - }) - - it('should return no template available if a sample query has no adaptive card', () => { - const result = { sample: 'response' }; - const expectedAction: AppAction = { - type: FETCH_ADAPTIVE_CARD_ERROR, - response: 'No template available' - }; - - const payload = { - businessPhones: ['+1 412 555 0109'], - displayName: 'Megan Bowen', - givenName: 'Megan', - jobTitle: 'Auditor', - mail: 'MeganB@M365x214355.onmicrosoft.com', - mobilePhone: null, - officeLocation: '12/1110', - preferredLanguage: 'en-US', - surname: 'Bowen', - userPrincipalName: 'MeganB@M365x214355.onmicrosoft.com', - id: '48d31887-5fad-4d73-a9f5-3c356e68a038' - } - - // eslint-disable-next-line no-undef - fetchMock.mockResponse(JSON.stringify(result)); - - const store = mockStore({}); - const sampleQuery: IQuery = { - selectedVerb: 'GET', - selectedVersion: 'v1', - sampleUrl: 'https://graph.microsoft.com/v1.0/me/events', - sampleBody: '', - sampleHeaders: [] - } - - // @ts-ignore - return store.dispatch(getAdaptiveCard(payload, sampleQuery)) - // @ts-ignore - .then(() => { - expect(store.getActions()).toEqual([expectedAction]); - }); - }); - - it('should return invalid payload for card if the payload received is an empty object', () => { - const result = { sample: 'response' }; - const expectedAction: AppAction = { - type: FETCH_ADAPTIVE_CARD_ERROR, - response: 'Invalid payload for card' - }; - - const payload = {}; - - // eslint-disable-next-line no-undef - fetchMock.mockResponse(JSON.stringify(result)); - - const store = mockStore({}); - const sampleQuery: IQuery = { - selectedVerb: 'GET', - selectedVersion: 'v1', - sampleUrl: 'https://graph.microsoft.com/v1.0/me/events', - sampleBody: '', - sampleHeaders: [] - } - - // @ts-ignore - return store.dispatch(getAdaptiveCard(payload, sampleQuery)) - // @ts-ignore - .then(() => { - expect(store.getActions()).toEqual([expectedAction]); - }); - }); -}); diff --git a/src/app/services/actions/adaptive-cards-action-creator.ts b/src/app/services/actions/adaptive-cards-action-creator.ts deleted file mode 100644 index 72992009d..000000000 --- a/src/app/services/actions/adaptive-cards-action-creator.ts +++ /dev/null @@ -1,78 +0,0 @@ -import * as AdaptiveCardsTemplateAPI from 'adaptivecards-templating'; -import { AppDispatch } from '../../../store'; -import { AppAction } from '../../../types/action'; -import { IAdaptiveCardContent } from '../../../types/adaptivecard'; -import { IQuery } from '../../../types/query-runner'; -import { lookupTemplate } from '../../utils/adaptive-cards-lookup'; -import { - FETCH_ADAPTIVE_CARD_ERROR, - FETCH_ADAPTIVE_CARD_PENDING, - FETCH_ADAPTIVE_CARD_SUCCESS -} from '../redux-constants'; - -export function getAdaptiveCardSuccess(result: object): AppAction { - return { - type: FETCH_ADAPTIVE_CARD_SUCCESS, - response: result - }; -} - -export function getAdaptiveCardError(error: string): AppAction { - return { - type: FETCH_ADAPTIVE_CARD_ERROR, - response: error - }; -} - -export function getAdaptiveCardPending(): AppAction { - return { - type: FETCH_ADAPTIVE_CARD_PENDING, - response: '' - }; -} - -export function getAdaptiveCard(payload: string, sampleQuery: IQuery) { - return async (dispatch: AppDispatch): Promise => { - if (!payload) { - // no payload so return empty result - return dispatch(getAdaptiveCardSuccess({})); - } - - if (Object.keys(payload).length === 0) { - // check if the payload is something else that we cannot use - return dispatch(getAdaptiveCardError('Invalid payload for card')); - } - - const templateFileName = lookupTemplate(sampleQuery); - if (!templateFileName) { - // we dont support this card yet - return dispatch(getAdaptiveCardError('No template available')); - } - - dispatch(getAdaptiveCardPending()); - try { - const card = createCardFromTemplate(templateFileName, payload); - const adaptiveCardContent: IAdaptiveCardContent = { - card, - template: templateFileName - }; - return dispatch(getAdaptiveCardSuccess(adaptiveCardContent)); - } catch (error: any) { - // something wrong happened - return dispatch(getAdaptiveCardError(error)); - } - }; -} - -function createCardFromTemplate(templatePayload: any, payload: string): AdaptiveCardsTemplateAPI.Template { - const template = new AdaptiveCardsTemplateAPI.Template(templatePayload); - const context: AdaptiveCardsTemplateAPI.IEvaluationContext = { - $root: payload - }; - AdaptiveCardsTemplateAPI.GlobalSettings.getUndefinedFieldValueSubstitutionString = ( - // eslint-disable-next-line no-unused-vars - _path: string - ) => ' '; - return template.expand(context); -} - diff --git a/src/app/services/actions/auth-action-creators.spec.ts b/src/app/services/actions/auth-action-creators.spec.ts index 97b24b7d3..50e5e3710 100644 --- a/src/app/services/actions/auth-action-creators.spec.ts +++ b/src/app/services/actions/auth-action-creators.spec.ts @@ -1,20 +1,21 @@ +import { PayloadAction } from '@reduxjs/toolkit'; +import configureMockStore from 'redux-mock-store'; import { AUTHENTICATION_PENDING, GET_AUTH_TOKEN_SUCCESS, GET_CONSENTED_SCOPES_SUCCESS, LOGOUT_SUCCESS } from '../../../app/services/redux-constants'; import { - getAuthTokenSuccess, getConsentedScopesSuccess, signOutSuccess, + getAuthTokenSuccess, getConsentedScopesSuccess, setAuthenticationPending, - storeScopes, signIn, signOut -} from '../../../app/services/actions/auth-action-creators'; -import { AppAction } from '../../../types/action'; -import configureMockStore from 'redux-mock-store'; -import thunk from 'redux-thunk'; -import { HOME_ACCOUNT_KEY } from '../graph-constants'; + signIn, signOut, + signOutSuccess, + storeScopes +} from '../../../app/services/slices/auth.slice'; import { msalApplication } from '../../../modules/authentication/msal-app'; +import { HOME_ACCOUNT_KEY } from '../graph-constants'; +import { mockThunkMiddleware } from './mockThunkMiddleware'; -const middlewares = [thunk]; -const mockStore = configureMockStore(middlewares); +const mockStore = configureMockStore([mockThunkMiddleware]); window.open = jest.fn(); jest.spyOn(window.sessionStorage.__proto__, 'clear'); @@ -29,14 +30,13 @@ msalApplication.logoutPopup = jest.fn(); describe('Auth Action Creators', () => { it('should dispatch AUTHENTICATION_PENDING when setAuthenticationPending() is called', () => { // Arrange - const response: boolean = true; - const expectedAction: AppAction = { + const expectedAction: PayloadAction = { type: AUTHENTICATION_PENDING, - response + payload: undefined } // Act - const action = setAuthenticationPending(response); + const action = setAuthenticationPending(); // Assert expect(action).toEqual(expectedAction); @@ -44,14 +44,13 @@ describe('Auth Action Creators', () => { it('should dispatch GET_AUTH_TOKEN_SUCCESS when getAuthTokenSuccess() is called', () => { // Arrange - const response: boolean = true; - const expectedAction: AppAction = { + const expectedAction: PayloadAction = { type: GET_AUTH_TOKEN_SUCCESS, - response + payload: undefined } // Act - const action = getAuthTokenSuccess(response); + const action = getAuthTokenSuccess(); // Assert expect(action).toEqual(expectedAction); @@ -60,9 +59,9 @@ describe('Auth Action Creators', () => { it('should dispatch GET_CONSENTED_SCOPES_SUCCESS when getConsentedScopesSuccess() is called', () => { // Arrange const response: string[] = ['mail.read', 'profile.read']; - const expectedAction: AppAction = { + const expectedAction: PayloadAction = { type: GET_CONSENTED_SCOPES_SUCCESS, - response + payload: response } // Act @@ -74,13 +73,12 @@ describe('Auth Action Creators', () => { it('should dispatch LOGOUT_SUCCESS when signOutSuccess() is called', () => { // Arrange - const response: boolean = true; - const expectedAction: AppAction = { + const expectedAction: PayloadAction = { type: LOGOUT_SUCCESS, - response + payload: undefined } // Act - const action = signOutSuccess(response); + const action = signOutSuccess(); // Assert expect(action).toEqual(expectedAction); @@ -89,9 +87,9 @@ describe('Auth Action Creators', () => { it('should dispatch GET_CONSENTED_SCOPES_SUCCESS when storeScopes() is called', () => { // Arrange const response: string[] = ['mail.read', 'profile.read']; - const expectedAction: AppAction = { + const expectedAction: PayloadAction = { type: GET_CONSENTED_SCOPES_SUCCESS, - response + payload: response } // Act @@ -107,10 +105,9 @@ describe('Auth Action Creators', () => { it('should dispatch GET_AUTH_TOKEN_SUCCESS when signIn() is called', () => { // Arrange - const response: boolean = true; - const expectedAction: AppAction = { + const expectedAction: PayloadAction = { type: GET_AUTH_TOKEN_SUCCESS, - response + payload: undefined } // Act @@ -126,9 +123,9 @@ describe('Auth Action Creators', () => { it('should dispatch LOGOUT_SUCCESS when signOutSuccess() is called', () => { // Arrange const response: boolean = true; - const expectedAction: AppAction = { + const expectedAction: PayloadAction = { type: LOGOUT_SUCCESS, - response + payload: response } // Act @@ -152,11 +149,11 @@ describe('Auth Action Creators', () => { const expectedActions = [ { type: AUTHENTICATION_PENDING, - response: true + payload: undefined }, { type: LOGOUT_SUCCESS, - response: true + payload: undefined } ]; // Act and assert diff --git a/src/app/services/actions/auth-action-creators.ts b/src/app/services/actions/auth-action-creators.ts deleted file mode 100644 index e49455089..000000000 --- a/src/app/services/actions/auth-action-creators.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { authenticationWrapper } from '../../../modules/authentication'; -import { AppDispatch } from '../../../store'; -import { AppAction } from '../../../types/action'; -import { Mode } from '../../../types/enums'; -import { - AUTHENTICATION_PENDING, GET_AUTH_TOKEN_SUCCESS, GET_CONSENTED_SCOPES_SUCCESS, - LOGOUT_SUCCESS -} from '../redux-constants'; - -export function getAuthTokenSuccess(response: boolean): AppAction { - return { - type: GET_AUTH_TOKEN_SUCCESS, - response - }; -} - -export function getConsentedScopesSuccess(response: string[]): AppAction { - return { - type: GET_CONSENTED_SCOPES_SUCCESS, - response - }; -} - -export function signOutSuccess(response: boolean): AppAction { - return { - type: LOGOUT_SUCCESS, - response - }; -} - -export function setAuthenticationPending(response: boolean): AppAction { - return { - type: AUTHENTICATION_PENDING, - response - }; -} - -export function signOut() { - return (dispatch: AppDispatch, getState: Function) => { - const { graphExplorerMode } = getState(); - dispatch(setAuthenticationPending(true)); - if (graphExplorerMode === Mode.Complete) { - authenticationWrapper.logOut(); - } else { - authenticationWrapper.logOutPopUp(); - } - dispatch(signOutSuccess(true)); - }; -} - -export function signIn() { - return (dispatch: AppDispatch) => dispatch(getAuthTokenSuccess(true)); -} - -export function storeScopes(consentedScopes: string[]) { - return (dispatch: AppDispatch) => dispatch(getConsentedScopesSuccess(consentedScopes)); -} diff --git a/src/app/services/actions/autocomplete-action-creators.spec.ts b/src/app/services/actions/autocomplete-action-creators.spec.ts index 8b3548130..e9b932faf 100644 --- a/src/app/services/actions/autocomplete-action-creators.spec.ts +++ b/src/app/services/actions/autocomplete-action-creators.spec.ts @@ -1,13 +1,16 @@ -import thunk from 'redux-thunk'; + +import { AnyAction } from '@reduxjs/toolkit'; import configureMockStore from 'redux-mock-store'; -import { store } from '../../../../src/store/index'; -import { ApplicationState } from '../../../types/root'; -import { Mode } from '../../../types/enums'; -import { fetchAutoCompleteOptions } from '../../../app/services/actions/autocomplete-action-creators'; + +import { ApplicationState, store } from '../../../../src/store/index'; +import { fetchAutoCompleteOptions } from '../../../app/services/slices/autocomplete.slice'; import { suggestions } from '../../../modules/suggestions/suggestions'; +import { Mode } from '../../../types/enums'; +import { AUTOCOMPLETE_FETCH_ERROR, AUTOCOMPLETE_FETCH_PENDING, AUTOCOMPLETE_FETCH_SUCCESS } from '../redux-constants'; +import { mockThunkMiddleware } from './mockThunkMiddleware'; -const middleware = [thunk]; -const mockStore = configureMockStore(middleware); + +const mockStore = configureMockStore([mockThunkMiddleware]); jest.mock('../../../../src/store/index'); window.fetch = jest.fn(); @@ -17,25 +20,24 @@ const mockState: ApplicationState = { baseUrl: 'https://graph.microsoft.com/v1.0/me', parameters: '$count=true' }, - profile: null, + profile: { + user: undefined, + error: undefined, + status: 'unset' + }, sampleQuery: { sampleUrl: 'http://localhost:8080/api/v1/samples/1', selectedVerb: 'GET', selectedVersion: 'v1', sampleHeaders: [] }, - authToken: { token: false, pending: false }, - consentedScopes: [], - isLoadingData: false, + auth: { + authToken: { token: false, pending: false }, + consentedScopes: [] + }, queryRunnerStatus: null, termsOfUse: true, theme: 'dark', - adaptiveCard: { - pending: false, - data: { - template: 'Template' - } - }, graphExplorerMode: Mode.Complete, sidebarProperties: { showSidebar: true, @@ -56,8 +58,11 @@ const mockState: ApplicationState = { }, history: [], graphResponse: { - body: undefined, - headers: undefined + isLoadingData: false, + response: { + body: undefined, + headers: undefined + } }, snippets: { pending: false, @@ -92,13 +97,27 @@ const mockState: ApplicationState = { pending: false, data: {}, error: null - } + }, + permissionGrants: { + pending: false, + permissions: [], + error: null + }, + collections: [], + proxyUrl: '' } store.getState = () => ({ ...mockState, proxyUrl: '', - collections: [] + collections: [], + graphExplorerMode: Mode.Complete, + queryRunnerStatus: null, + samples: { + queries: [], + pending: false, + error: null + } }) describe('fetchAutoCompleteOptions', () => { @@ -114,37 +133,43 @@ describe('fetchAutoCompleteOptions', () => { const store_ = mockStore(store.getState()); // Call the function by dispatching the returned async function - await store_.dispatch(fetchAutoCompleteOptions(url, version)); + await store_.dispatch(fetchAutoCompleteOptions({ url, version }) as unknown as AnyAction); // Assertions const expectedActions = [ - { type: 'AUTOCOMPLETE_FETCH_PENDING', response: null }, + { type: AUTOCOMPLETE_FETCH_PENDING, payload: undefined }, { - type: 'AUTOCOMPLETE_FETCH_SUCCESS', - response: [ 'option1', 'option2', 'option3' ] + type: AUTOCOMPLETE_FETCH_SUCCESS, + payload: ['option1', 'option2', 'option3'] } ]; - expect(store_.getActions()).toEqual(expectedActions); + expect(store_.getActions().map(action => { + const { meta, ...rest } = action; + return rest; + })).toEqual(expectedActions); }); it('dispatches fetchAutocompleteError when suggestions retrieval fails', async () => { const url = '/some/url'; const version = '1.0'; - // Mock a response with null - suggestions.getSuggestions = jest.fn().mockResolvedValue(null); + // Mock a response with an error + suggestions.getSuggestions = jest.fn().mockRejectedValue(new Error()); // Create a mock store const store_ = mockStore(store.getState()); // Call the function by dispatching the returned async function - await store_.dispatch(fetchAutoCompleteOptions(url, version)); + await store_.dispatch(fetchAutoCompleteOptions({ url, version }) as unknown as AnyAction); // Assertions const expectedActions = [ - { type: 'AUTOCOMPLETE_FETCH_PENDING', response: null }, - { type: 'AUTOCOMPLETE_FETCH_ERROR', response: {} } + { type: AUTOCOMPLETE_FETCH_PENDING, payload: undefined }, + { type: AUTOCOMPLETE_FETCH_ERROR, payload: new Error() } ]; - expect(store_.getActions()).toEqual(expectedActions); + expect(store_.getActions().map(action => { + const { meta, error, ...rest } = action; + return rest; + })).toEqual(expectedActions); }); }); diff --git a/src/app/services/actions/autocomplete-action-creators.ts b/src/app/services/actions/autocomplete-action-creators.ts deleted file mode 100644 index f9a42ff43..000000000 --- a/src/app/services/actions/autocomplete-action-creators.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { SignContext, suggestions } from '../../../modules/suggestions'; -import { AppAction } from '../../../types/action'; -import { - AUTOCOMPLETE_FETCH_ERROR, - AUTOCOMPLETE_FETCH_PENDING, - AUTOCOMPLETE_FETCH_SUCCESS -} from '../redux-constants'; - -export function fetchAutocompleteSuccess(response: object): AppAction { - return { - type: AUTOCOMPLETE_FETCH_SUCCESS, - response - }; -} - -export function fetchAutocompleteError(response: object): AppAction { - return { - type: AUTOCOMPLETE_FETCH_ERROR, - response - }; -} - -export function fetchAutocompletePending(): AppAction { - return { - type: AUTOCOMPLETE_FETCH_PENDING, - response: null - }; -} - -export function fetchAutoCompleteOptions(url: string, version: string, context: SignContext = 'paths') { - return async (dispatch: Function, getState: Function) => { - const devxApiUrl = getState().devxApi.baseUrl; - const resources = Object.keys(getState().resources.data).length > 0 ? getState().resources.data[version] : []; - dispatch(fetchAutocompletePending()); - const autoOptions = await suggestions.getSuggestions( - url, - devxApiUrl, - version, - context, - resources - ); - if (autoOptions) { - return dispatch(fetchAutocompleteSuccess(autoOptions)); - } - return dispatch(fetchAutocompleteError({})); - }; -} diff --git a/src/app/services/actions/collections-action-creators.spec.ts b/src/app/services/actions/collections-action-creators.spec.ts index 4251fb5c8..9cead35c6 100644 --- a/src/app/services/actions/collections-action-creators.spec.ts +++ b/src/app/services/actions/collections-action-creators.spec.ts @@ -1,43 +1,26 @@ import configureMockStore from 'redux-mock-store'; -import thunk from 'redux-thunk'; import { RESOURCEPATHS_ADD_SUCCESS, RESOURCEPATHS_DELETE_SUCCESS } from '../redux-constants'; -import { addResourcePaths, removeResourcePaths } from './collections-action-creators'; +import { addResourcePaths, removeResourcePaths } from '../slices/collections.slice'; +import { ResourceLinkType, ResourcePath } from '../../../types/resources'; -const middlewares = [thunk]; -const mockStore = configureMockStore(middlewares); +const mockStore = configureMockStore(); -const paths = [ +const paths: ResourcePath[] = [ { key: '5-{serviceHealth-id}-issues', url: '/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}/issues', name: 'issues (1)', - labels: [ - { name: 'v1.0', methods: ['GET', 'POST'] }, - { name: 'beta', methods: ['GET', 'POST'] } - ], - isExpanded: true, - parent: '{serviceHealth-id}', - level: 5, - paths: ['/', 'admin', 'serviceAnnouncement', 'healthOverviews', '{serviceHealth-id}'], - type: 'path', - links: [] + type: ResourceLinkType.PATH, + paths: ['/', 'admin', 'serviceAnnouncement', 'healthOverviews', '{serviceHealth-id}'] }, { key: '6-issues-{serviceHealthIssue-id}', - url: '/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}/issues/{serviceHealthIssue-id}', + url: '/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}/issues/{serviceHealthIssues}', name: '{serviceHealthIssue-id} (1)', - labels: [ - { name: 'v1.0', methods: ['GET', 'PATCH', 'DELETE'] }, - { name: 'beta', methods: ['GET', 'PATCH', 'DELETE'] } - ], - isExpanded: true, - parent: 'issues', - level: 6, paths: ['/', 'admin', 'serviceAnnouncement', 'healthOverviews', '{serviceHealth-id}', 'issues'], - type: 'path', - links: [] + type: ResourceLinkType.PATH } ]; @@ -51,7 +34,7 @@ describe('Collections actions', () => { const expectedActions = [ { type: RESOURCEPATHS_ADD_SUCCESS, - response: paths + payload: paths } ]; @@ -70,7 +53,7 @@ describe('Collections actions', () => { const expectedActions = [ { type: RESOURCEPATHS_DELETE_SUCCESS, - response: paths + payload: paths } ]; diff --git a/src/app/services/actions/collections-action-creators.ts b/src/app/services/actions/collections-action-creators.ts deleted file mode 100644 index a8b97ece2..000000000 --- a/src/app/services/actions/collections-action-creators.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { AppAction } from '../../../types/action'; -import { - COLLECTION_CREATE_SUCCESS, - RESOURCEPATHS_ADD_SUCCESS, RESOURCEPATHS_DELETE_SUCCESS -} from '../redux-constants'; - -export function addResourcePaths(response: object): AppAction { - return { - type: RESOURCEPATHS_ADD_SUCCESS, - response - }; -} - -export function createCollection(response: object): AppAction { - return { - type: COLLECTION_CREATE_SUCCESS, - response - }; -} - -export function removeResourcePaths(response: object): AppAction { - return { - type: RESOURCEPATHS_DELETE_SUCCESS, - response - }; -} diff --git a/src/app/services/actions/devxApi-action-creators.spec.ts b/src/app/services/actions/devxApi-action-creators.spec.ts index 647bf74e6..4fbbab379 100644 --- a/src/app/services/actions/devxApi-action-creators.spec.ts +++ b/src/app/services/actions/devxApi-action-creators.spec.ts @@ -1,4 +1,4 @@ -import { setDevxApiUrl } from '../../../app/services/actions/devxApi-action-creators'; +import { setDevxApiUrl } from '../../../app/services/slices/devxapi.slice'; import { SET_DEVX_API_URL_SUCCESS } from '../../../app/services/redux-constants'; import { IDevxAPI } from '../../../types/devx-api'; @@ -15,7 +15,7 @@ describe('Devx api url', () => { const expectedActions = { type: SET_DEVX_API_URL_SUCCESS, - response: devxApi + payload: devxApi }; // Act diff --git a/src/app/services/actions/devxApi-action-creators.ts b/src/app/services/actions/devxApi-action-creators.ts deleted file mode 100644 index c7af51a03..000000000 --- a/src/app/services/actions/devxApi-action-creators.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { AppAction } from '../../../types/action'; -import { SET_DEVX_API_URL_SUCCESS } from '../redux-constants'; - -export function setDevxApiUrl(response: object): AppAction { - return { - type: SET_DEVX_API_URL_SUCCESS, - response - }; -} diff --git a/src/app/services/actions/dimensions-action-creator.spec.ts b/src/app/services/actions/dimensions-action-creator.spec.ts index 88beee019..b5f4f348f 100644 --- a/src/app/services/actions/dimensions-action-creator.spec.ts +++ b/src/app/services/actions/dimensions-action-creator.spec.ts @@ -1,4 +1,4 @@ -import { setDimensions } from '../../../app/services/actions/dimensions-action-creator'; +import { setDimensions } from '../../../app/services/slices/dimensions.slice'; import { RESIZE_SUCCESS } from '../../../app/services/redux-constants'; import { IDimensions } from '../../../types/dimensions'; @@ -26,7 +26,7 @@ describe('Dimensions setting on GE', () => { const expectedActions = { type: RESIZE_SUCCESS, - response: dimensions + payload: dimensions } // Act diff --git a/src/app/services/actions/dimensions-action-creator.ts b/src/app/services/actions/dimensions-action-creator.ts deleted file mode 100644 index 7b489ab7c..000000000 --- a/src/app/services/actions/dimensions-action-creator.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { AppAction } from '../../../types/action'; -import { IDimensions } from '../../../types/dimensions'; -import { RESIZE_SUCCESS } from '../redux-constants'; - -export function setDimensions(response: IDimensions): AppAction { - return { - type: RESIZE_SUCCESS, - response - }; -} diff --git a/src/app/services/actions/explorer-mode-action-creator.spec.ts b/src/app/services/actions/explorer-mode-action-creator.spec.ts index e22dfd849..91c115854 100644 --- a/src/app/services/actions/explorer-mode-action-creator.spec.ts +++ b/src/app/services/actions/explorer-mode-action-creator.spec.ts @@ -1,19 +1,17 @@ import configureMockStore from 'redux-mock-store'; -import thunk from 'redux-thunk'; -import { setGraphExplorerMode } from './explorer-mode-action-creator'; +import { setGraphExplorerMode } from '../slices/explorer-mode.slice'; import { SET_GRAPH_EXPLORER_MODE_SUCCESS } from '../redux-constants'; import { Mode } from '../../../types/enums'; -const middlewares = [thunk]; -const mockStore = configureMockStore(middlewares); +const mockStore = configureMockStore(); describe('Graph Explorer Mode Action Creators', () => { it('should dispatch SET_GRAPH_EXPLORER_MODE_SUCCESS when setGraphExplorerMode() is called', () => { const expectedActions = [ { type: SET_GRAPH_EXPLORER_MODE_SUCCESS, - response: Mode.TryIt + payload: Mode.TryIt } ]; diff --git a/src/app/services/actions/explorer-mode-action-creator.ts b/src/app/services/actions/explorer-mode-action-creator.ts deleted file mode 100644 index 5e01f8fc4..000000000 --- a/src/app/services/actions/explorer-mode-action-creator.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { AppAction } from '../../../types/action'; -import { Mode } from '../../../types/enums'; -import { SET_GRAPH_EXPLORER_MODE_SUCCESS } from '../redux-constants'; - -export function setGraphExplorerMode(mode: Mode): AppAction { - return { - type: SET_GRAPH_EXPLORER_MODE_SUCCESS, - response: mode - }; -} diff --git a/src/app/services/actions/mockThunkMiddleware.ts b/src/app/services/actions/mockThunkMiddleware.ts new file mode 100644 index 000000000..0beb8fcfc --- /dev/null +++ b/src/app/services/actions/mockThunkMiddleware.ts @@ -0,0 +1,8 @@ +// This is a simple custom middleware that allows us to dispatch functions (like redux-thunk) +export const mockThunkMiddleware = (store: any) => (next: any) => (action: any) => { + if (typeof action === 'function') { + return action(store.dispatch, store.getState); + } + + return next(action); +}; diff --git a/src/app/services/actions/permissions-action-creator.spec.ts b/src/app/services/actions/permissions-action-creator.spec.ts index ca65282bd..999336a6b 100644 --- a/src/app/services/actions/permissions-action-creator.spec.ts +++ b/src/app/services/actions/permissions-action-creator.spec.ts @@ -1,31 +1,29 @@ +import configureMockStore from 'redux-mock-store'; + +import { AnyAction } from '@reduxjs/toolkit'; import { - FETCH_SCOPES_ERROR, + FETCH_FULL_SCOPES_PENDING, FETCH_FULL_SCOPES_SUCCESS, - FETCH_URL_SCOPES_PENDING + GET_CONSENTED_SCOPES_PENDING, + QUERY_GRAPH_STATUS, + REVOKE_SCOPES_PENDING } from '../../../app/services/redux-constants'; - -import { - fetchFullScopesSuccess, fetchScopesError, getPermissionsScopeType, fetchScopes, - consentToScopes, - fetchUrlScopesPending, - fetchFullScopesPending, - revokeScopes -} from './permissions-action-creator'; -import { IPermissionsResponse } from '../../../types/permissions'; -import { store } from '../../../store/index'; -import { ApplicationState } from '../../../types/root'; -import { Mode } from '../../../types/enums'; -import configureMockStore from 'redux-mock-store'; import { authenticationWrapper } from '../../../modules/authentication'; -import thunk from 'redux-thunk'; +import { ApplicationState, store } from '../../../store/index'; +import { Mode } from '../../../types/enums'; +import { getPermissionsScopeType } from '../../utils/getPermissionsScopeType'; +import { translateMessage } from '../../utils/translate-messages'; import { ACCOUNT_TYPE } from '../graph-constants'; +import { consentToScopes } from '../slices/auth.slice'; +import { fetchScopes } from '../slices/scopes.slice'; +import { mockThunkMiddleware } from './mockThunkMiddleware'; import { RevokePermissionsUtil } from './permissions-action-creator.util'; -import { translateMessage } from '../../utils/translate-messages'; -const middleware = [thunk]; -let mockStore = configureMockStore(middleware); +import { revokeScopes } from './revoke-scopes.action'; + +let mockStore = configureMockStore([mockThunkMiddleware]); beforeEach(() => { - const mockStore_ = configureMockStore(middleware); + const mockStore_ = configureMockStore([mockThunkMiddleware]); mockStore = mockStore_ }) window.open = jest.fn(); @@ -36,13 +34,17 @@ const mockState: ApplicationState = { parameters: '$count=true' }, profile: { - id: '123', - displayName: 'test', - emailAddress: 'johndoe@ms.com', - profileImageUrl: 'https://graph.microsoft.com/v1.0/me/photo/$value', - ageGroup: 0, - tenant: 'binaryDomain', - profileType: ACCOUNT_TYPE.MSA + user: { + id: '123', + displayName: 'test', + emailAddress: 'johndoe@ms.com', + profileImageUrl: 'https://graph.microsoft.com/v1.0/me/photo/$value', + ageGroup: 0, + tenant: 'binaryDomain', + profileType: ACCOUNT_TYPE.MSA + }, + status: 'success', + error: undefined }, sampleQuery: { sampleUrl: 'http://localhost:8080/api/v1/samples/1', @@ -50,18 +52,13 @@ const mockState: ApplicationState = { selectedVersion: 'v1', sampleHeaders: [] }, - authToken: { token: false, pending: false }, - consentedScopes: ['profile.read User.Read Files.Read'], - isLoadingData: false, + auth: { + authToken: { token: false, pending: false }, + consentedScopes: ['profile.read User.Read Files.Read'] + }, queryRunnerStatus: null, termsOfUse: true, theme: 'dark', - adaptiveCard: { - pending: false, - data: { - template: 'Template' - } - }, graphExplorerMode: Mode.Complete, sidebarProperties: { showSidebar: true, @@ -82,8 +79,11 @@ const mockState: ApplicationState = { }, history: [], graphResponse: { - body: undefined, - headers: undefined + isLoadingData: false, + response: { + body: undefined, + headers: undefined + } }, snippets: { pending: false, @@ -118,7 +118,14 @@ const mockState: ApplicationState = { pending: false, data: {}, error: null - } + }, + permissionGrants: { + pending: false, + permissions: [], + error: null + }, + collections: [], + proxyUrl: '' } const currentState = store.getState(); store.getState = () => { @@ -129,66 +136,6 @@ store.getState = () => { } describe('Permissions action creators', () => { - it('should dispatch FETCH_SCOPES_SUCCESS when fetchFullScopesSuccess() is called', () => { - // Arrange - const response: IPermissionsResponse = { - scopes: { - fullPermissions: [], - specificPermissions: [] - } - } - - const expectedAction = { - type: FETCH_FULL_SCOPES_SUCCESS, - response - } - - // Act - const action = fetchFullScopesSuccess(response); - - // Assert - expect(action).toEqual(expectedAction); - }); - - it('should dispatch FETCH_SCOPES_ERROR when fetchScopesError() is called', () => { - // Arrange - const response = { - error: {} - } - - const expectedAction = { - type: FETCH_SCOPES_ERROR, - response - } - - // Act - const action = fetchScopesError(response); - - // Assert - expect(action).toEqual(expectedAction); - }); - - // eslint-disable-next-line max-len - it('should dispatch FETCH_FULL_SCOPES_PENDING or FETCH_URL_SCOPES_PENDING depending on type passed to fetchScopesPending', () => { - // Arrange - const expectedFullScopesAction = { - type: 'FETCH_SCOPES_PENDING', - response: 'full' - } - - const expectedUrlScopesAction = { - type: FETCH_URL_SCOPES_PENDING, - response: 'url' - } - - // Act - const fullScopesAction = fetchFullScopesPending(); - const urlScopesAction = fetchUrlScopesPending(); - - // Assert - expect(fullScopesAction).toEqual(expectedFullScopesAction); - expect(urlScopesAction).toEqual(expectedUrlScopesAction) - }); it('should return a valid scope type when getPermissionsScopeType() is called with a user profile or null', () => { // Arrange @@ -202,17 +149,17 @@ describe('Permissions action creators', () => { }); - it('should fetch scopes', () => { + it('should fetch scopes', async () => { // Arrange const expectedResult = {} - const expectedAction: any = [ + const expectedAction = [ { - type: 'FETCH_SCOPES_PENDING', - response: 'full' + type: FETCH_FULL_SCOPES_PENDING, + payload: undefined }, { - type: 'FULL_SCOPES_FETCH_SUCCESS', - response: { + type: FETCH_FULL_SCOPES_SUCCESS, + payload: { scopes: { fullPermissions: {} } @@ -220,7 +167,7 @@ describe('Permissions action creators', () => { } ]; - const store_ = mockStore(mockState); + const store_ = mockStore(store.getState()); const mockFetch = jest.fn().mockImplementation(() => { return Promise.resolve({ @@ -231,23 +178,26 @@ describe('Permissions action creators', () => { window.fetch = mockFetch; - // Act and Assert - // @ts-ignore - return store_.dispatch(fetchScopes()) - // @ts-ignore - .then(() => { - expect(store_.getActions()).toEqual(expectedAction); - }); + // Act + await store_.dispatch(fetchScopes('full') as unknown as AnyAction); + + // Assert + expect(store_.getActions().map(action => { + const { meta, error, ...rest } = action; + return rest; + })).toEqual(expectedAction); + }); it('should consent to scopes', () => { + const scopes = ['profile.Read User.Read']; // Arrange jest.spyOn(authenticationWrapper, 'consentToScopes').mockResolvedValue({ accessToken: 'jkkkkkkkkkkkkkkkkkkkksdss', authority: 'string', uniqueId: 'string', tenantId: 'string', - scopes: ['profile.Read User.Read'], + scopes, account: { homeAccountId: 'string', environment: 'string', @@ -262,33 +212,21 @@ describe('Permissions action creators', () => { tokenType: 'AAD', correlationId: 'string' }) - const expectedAction: any = [ - { type: 'GET_AUTH_TOKEN_SUCCESS', response: true }, - { - type: 'GET_CONSENTED_SCOPES_SUCCESS', - response: ['profile.Read User.Read'] - }, - { - type: 'QUERY_GRAPH_STATUS', - response: { - statusText: translateMessage('Success'), - status: translateMessage('Scope consent successful'), - ok: true, - messageType: 4 - } - }, - { type: 'GET_ALL_PRINCIPAL_GRANTS_PENDING', response: true } + const expectedActions = [ + { type: GET_CONSENTED_SCOPES_PENDING } ]; const store_ = mockStore(mockState); // Act and Assert - // @ts-ignore - return store_.dispatch(consentToScopes()) - // @ts-ignore - .then(() => { - expect(store_.getActions()).toEqual(expectedAction); - }); + store_.dispatch(consentToScopes(scopes) as unknown as AnyAction); + + + expect(store_.getActions().map(action => { + const { meta, ...rest } = action; + return rest; + })).toEqual(expectedActions); + }); describe('Revoke scopes', () => { @@ -353,37 +291,26 @@ describe('Permissions action creators', () => { }) const expectedActions = [ - { type: 'REVOKE_SCOPES_PENDING', response: null }, - { type: 'REVOKE_SCOPES_ERROR', response: null }, + { type: REVOKE_SCOPES_PENDING }, { - type: 'QUERY_GRAPH_STATUS', - response: { + type: QUERY_GRAPH_STATUS, + payload: { statusText: translateMessage('Revoking'), status: translateMessage('Please wait while we revoke this permission'), ok: false, messageType: 0 } - }, - { type: 'REVOKE_SCOPES_ERROR', response: null }, - { - type: 'QUERY_GRAPH_STATUS', - response: { - statusText: translateMessage('Default scope'), - status: translateMessage('Cannot delete default scope'), - ok: false, - messageType: 1 - } } ] + // Act + store_.dispatch(revokeScopes('User.Read') as unknown as AnyAction); - // Act and Assert - // @ts-ignore - return store_.dispatch(revokeScopes('User.Read')) - // @ts-ignore - .then(() => { - expect(store_.getActions()).toEqual(expectedActions); - }); + // Assert + expect(store_.getActions().map(action => { + const { meta, ...rest } = action; + return rest; + })).toEqual(expectedActions); }); it('should return 401 when user does not have required permissions', async () => { @@ -420,40 +347,28 @@ describe('Permissions action creators', () => { }); const expectedActions = [ - { type: 'REVOKE_SCOPES_PENDING', response: null }, - { type: 'REVOKE_SCOPES_ERROR', response: null }, + { type: REVOKE_SCOPES_PENDING }, { - type: 'QUERY_GRAPH_STATUS', - response: { + type: QUERY_GRAPH_STATUS, + payload: { statusText: translateMessage('Revoking '), status: translateMessage('Please wait while we revoke this permission'), ok: false, messageType: 0 } - }, - { type: 'REVOKE_SCOPES_ERROR', response: null }, - { - type: 'QUERY_GRAPH_STATUS', - response: { - statusText: translateMessage('Unable to dissent'), - status: translateMessage('Unable to dissent. You require the following permissions to revoke'), - ok: false, - messageType: 1 - } } ] - // Act and Assert - // @ts-ignore - return store_.dispatch(revokeScopes('Access.Read')) - // @ts-ignore - .then(() => { - expect(store_.getActions()).toEqual(expectedActions); - }); + // Act + store_.dispatch(revokeScopes('Access.Read') as unknown as AnyAction); + // Assert + expect(store_.getActions().map(action => { + const { meta, ...rest } = action; + return rest; + })).toEqual(expectedActions); }); - //revisit it('should raise error when user attempts to dissent to an admin granted permission', async () => { // Arrange const store_ = mockStore(mockState); @@ -491,34 +406,26 @@ describe('Permissions action creators', () => { jest.spyOn(RevokePermissionsUtil, 'isSignedInUserTenantAdmin').mockResolvedValue(false); const expectedActions = [ - { type: 'REVOKE_SCOPES_PENDING', response: null }, - { type: 'REVOKE_SCOPES_ERROR', response: null }, + { type: REVOKE_SCOPES_PENDING, payload: undefined }, { - type: 'QUERY_GRAPH_STATUS', - response: { + type: QUERY_GRAPH_STATUS, + payload: { statusText: translateMessage('Revoking'), status: translateMessage('Please wait while we revoke this permission'), ok: false, messageType: 0 } - }, - { type: 'REVOKE_SCOPES_ERROR', response: null }, - { - type: 'QUERY_GRAPH_STATUS', - response: { - statusText: translateMessage('Revoking admin granted scopes'), - // eslint-disable-next-line max-len - status: translateMessage('You are unconsenting to an admin pre-consented permission'), - ok: false, - messageType: 1 - } } ] + // Act + store_.dispatch(revokeScopes('Access.Read') as unknown as AnyAction); + + // Assert + expect(store_.getActions().map(action => { + const { meta, ...rest } = action; + return rest; + })).toEqual(expectedActions); - // Act and Assert - // @ts-ignore - await store_.dispatch(revokeScopes('Access.Read')); - expect(store_.getActions()).toEqual(expectedActions); }); }) }) \ No newline at end of file diff --git a/src/app/services/actions/permissions-action-creator.ts b/src/app/services/actions/permissions-action-creator.ts deleted file mode 100644 index df9d2463b..000000000 --- a/src/app/services/actions/permissions-action-creator.ts +++ /dev/null @@ -1,439 +0,0 @@ -import { MessageBarType } from '@fluentui/react'; - -import { authenticationWrapper } from '../../../modules/authentication'; -import { AppAction } from '../../../types/action'; -import { IUser } from '../../../types/profile'; -import { IRequestOptions } from '../../../types/request'; -import { ApplicationState } from '../../../types/root'; -import { sanitizeQueryUrl } from '../../utils/query-url-sanitization'; -import { parseSampleUrl } from '../../utils/sample-url-generation'; -import { translateMessage } from '../../utils/translate-messages'; -import { getConsentAuthErrorHint } from '../../../modules/authentication/authentication-error-hints'; -import { ACCOUNT_TYPE, DEFAULT_USER_SCOPES, PERMS_SCOPE, - REVOKING_PERMISSIONS_REQUIRED_SCOPES } from '../graph-constants'; -import { - FETCH_SCOPES_ERROR, - FETCH_FULL_SCOPES_PENDING, - FETCH_URL_SCOPES_PENDING, - FETCH_FULL_SCOPES_SUCCESS, - FETCH_URL_SCOPES_SUCCESS, - GET_ALL_PRINCIPAL_GRANTS_SUCCESS, GET_ALL_PRINCIPAL_GRANTS_ERROR, REVOKE_SCOPES_PENDING, - REVOKE_SCOPES_SUCCESS, REVOKE_SCOPES_ERROR, GET_ALL_PRINCIPAL_GRANTS_PENDING -} from '../redux-constants'; -import { - getAuthTokenSuccess, - getConsentedScopesSuccess -} from './auth-action-creators'; -import { getProfileInfo } from './profile-action-creators'; -import { setQueryResponseStatus } from './query-status-action-creator'; -import { RevokePermissionsUtil, REVOKE_STATUS } from './permissions-action-creator.util'; -import { componentNames, eventTypes, telemetry } from '../../../telemetry'; -import { RevokeScopesError } from '../../utils/error-utils/RevokeScopesError'; -import { IOAuthGrantPayload, IPermissionGrant } from '../../../types/permissions'; - -export function fetchFullScopesSuccess(response: object): AppAction { - return { - type: FETCH_FULL_SCOPES_SUCCESS, - response - }; -} - -export function fetchUrlScopesSuccess(response: Object): AppAction { - return { - type: FETCH_URL_SCOPES_SUCCESS, - response - } -} - -export function fetchFullScopesPending(): AppAction { - return { - type: FETCH_FULL_SCOPES_PENDING, - response: 'full' - }; -} - -export function fetchUrlScopesPending(): AppAction { - return { - type: FETCH_URL_SCOPES_PENDING, - response: 'url' - }; -} - -export function fetchScopesError(response: object): AppAction { - return { - type: FETCH_SCOPES_ERROR, - response - }; -} - -export function getAllPrincipalGrantsPending(response: boolean){ - return { - type: GET_ALL_PRINCIPAL_GRANTS_PENDING, - response - }; -} - -export function getAllPrincipalGrantsSuccess(response: object): AppAction { - return { - type: GET_ALL_PRINCIPAL_GRANTS_SUCCESS, - response - }; -} - -export function getAllPrincipalGrantsError(response: object): AppAction { - return { - type: GET_ALL_PRINCIPAL_GRANTS_ERROR, - response - }; - -} - -export function revokeScopesPending(): AppAction { - return { - type: REVOKE_SCOPES_PENDING, - response: null - } -} - -export function revokeScopesSuccess(): AppAction { - return { - type: REVOKE_SCOPES_SUCCESS, - response: null - } -} - -export function revokeScopesError(): AppAction { - return { - type: REVOKE_SCOPES_ERROR, - response: null - } -} - -type ScopesFetchType = 'full' | 'query'; - -export function fetchScopes(scopesFetchType: ScopesFetchType = 'full') { - return async (dispatch: Function, getState: Function) => { - try { - const { devxApi, profile, sampleQuery: query }: ApplicationState = getState(); - const scopeType = getPermissionsScopeType(profile); - let permissionsUrl = `${devxApi.baseUrl}/permissions?scopeType=${scopeType}`; - - if (scopesFetchType === 'query') { - const signature = sanitizeQueryUrl(query.sampleUrl); - const { requestUrl, sampleUrl } = parseSampleUrl(signature); - - if (!sampleUrl) { - throw new Error('url is invalid'); - } - - // eslint-disable-next-line max-len - permissionsUrl = `${permissionsUrl}&requesturl=/${requestUrl}&method=${query.selectedVerb}`; - } - - if (devxApi.parameters) { - permissionsUrl = `${permissionsUrl}&${devxApi.parameters}`; - } - - const headers = { - 'Content-Type': 'application/json' - }; - - const options: IRequestOptions = { headers }; - if (scopesFetchType === 'full') { - dispatch(fetchFullScopesPending()); - } else { - dispatch(fetchUrlScopesPending()); - } - - const response = await fetch(permissionsUrl, options); - if (response.ok) { - const scopes = await response.json(); - - return scopesFetchType === 'full' ? dispatch(fetchFullScopesSuccess({ - scopes: { fullPermissions: scopes } - })) : - dispatch(fetchUrlScopesSuccess({ - scopes: { specificPermissions: scopes } - })); - } - - throw response; - } catch (error) { - return dispatch( - fetchScopesError({ - error - }) - ); - } - }; -} - -export function getPermissionsScopeType(profile: IUser | null | undefined) { - if (profile?.profileType === ACCOUNT_TYPE.MSA) { - return PERMS_SCOPE.PERSONAL; - } - return PERMS_SCOPE.WORK; -} - -export function consentToScopes(scopes: string[]) { - return async (dispatch: Function, getState: Function) => { - try { - const { profile, consentedScopes }: ApplicationState = getState(); - const authResponse = await authenticationWrapper.consentToScopes(scopes); - if (authResponse && authResponse.accessToken) { - dispatch(getAuthTokenSuccess(true)); - const validatedScopes = validateConsentedScopes(scopes, consentedScopes, authResponse.scopes); - dispatch(getConsentedScopesSuccess(validatedScopes)); - if ( - authResponse.account && - authResponse.account.localAccountId !== profile?.id - ) { - dispatch(getProfileInfo()); - } - dispatch( - setQueryResponseStatus({ - statusText: translateMessage('Success'), - status: translateMessage('Scope consent successful'), - ok: true, - messageType: MessageBarType.success - })) - dispatch(fetchAllPrincipalGrants()); - } - } catch (error: any) { - const { errorCode } = error; - dispatch( - setQueryResponseStatus({ - statusText: translateMessage('Scope consent failed'), - status: errorCode, - ok: false, - messageType: MessageBarType.error, - hint: getConsentAuthErrorHint(errorCode) - }) - ); - } - }; -} - -const validateConsentedScopes = (scopeToBeConsented: string[], consentedScopes: string[], - consentedResponse: string[]) => { - if(!consentedScopes || !consentedResponse || !scopeToBeConsented) { - return consentedResponse; - } - const expectedScopes = [...consentedScopes, ...scopeToBeConsented]; - if (expectedScopes.length === consentedResponse.length) { - return consentedResponse; - } - return expectedScopes; -} - -interface IPermissionUpdate { - permissionBeingRevokedIsAllPrincipal: boolean; - userIsTenantAdmin: boolean; - revokePermissionUtil: RevokePermissionsUtil; - grantsPayload: IOAuthGrantPayload; - profile: IUser; - permissionToRevoke: string; - newScopesArray: string[]; - retryCount: number; - retryDelay: number; - dispatch: Function; -} -export function revokeScopes(permissionToRevoke: string) { - return async (dispatch: Function, getState: Function) => { - const { consentedScopes, profile } = getState(); - const requiredPermissions = REVOKING_PERMISSIONS_REQUIRED_SCOPES.split(' '); - const defaultUserScopes = DEFAULT_USER_SCOPES.split(' '); - dispatch(revokeScopesPending()); - dispatchScopesStatus(dispatch, 'Please wait while we revoke this permission', 'Revoking ', 0); - const revokePermissionUtil = await RevokePermissionsUtil.initialize(profile.id); - - if (!consentedScopes || consentedScopes.length === 0) { - dispatch(revokeScopesError()); - trackRevokeConsentEvent(REVOKE_STATUS.preliminaryChecksFail, permissionToRevoke); - return; - } - - const newScopesArray: string[] = consentedScopes.filter((scope: string) => scope !== permissionToRevoke); - - try { - const { userIsTenantAdmin, permissionBeingRevokedIsAllPrincipal, grantsPayload } = await revokePermissionUtil. - getUserPermissionChecks({ consentedScopes, requiredPermissions, defaultUserScopes, permissionToRevoke }); - const retryCount = 0; - const retryDelay = 100; - const permissionsUpdateObject: IPermissionUpdate = { - permissionBeingRevokedIsAllPrincipal, userIsTenantAdmin, revokePermissionUtil, grantsPayload, - profile, permissionToRevoke, newScopesArray, retryCount, dispatch, retryDelay } - - const updatedScopes = await updatePermissions(permissionsUpdateObject) - - if (updatedScopes) { - dispatchScopesStatus(dispatch, 'Permission revoked', 'Success', 4); - dispatch(getConsentedScopesSuccess(updatedScopes)); - dispatch(revokeScopesSuccess()); - trackRevokeConsentEvent(REVOKE_STATUS.success, permissionToRevoke); - } - else{ - throw new RevokeScopesError({ - errorText: 'Scopes not updated', statusText: 'An error occurred when unconsenting', - status: '500', messageType: 1 - }) - } - } - catch (errorMessage: any) { - if (errorMessage instanceof RevokeScopesError || errorMessage instanceof Function) { - const { errorText, statusText, status, messageType } = errorMessage - dispatchScopesStatus(dispatch, statusText, status, messageType); - const permissionObject = { - permissionToRevoke, - statusCode: statusText, - status: errorText - } - trackRevokeConsentEvent(REVOKE_STATUS.failure, permissionObject); - } - else { - const { code, message } = errorMessage; - trackRevokeConsentEvent(REVOKE_STATUS.failure, 'Failed to revoke consent'); - dispatchScopesStatus(dispatch, message ? message : 'Failed to revoke consent', code ? code : 'Failed', 1); - } - } - } -} - -async function updatePermissions(permissionsUpdateObject: IPermissionUpdate): -Promise { - const { - permissionBeingRevokedIsAllPrincipal, userIsTenantAdmin, revokePermissionUtil, grantsPayload, - profile, permissionToRevoke, newScopesArray, retryCount, dispatch, retryDelay } = permissionsUpdateObject; - let isRevokeSuccessful; - const maxRetryCount = 7; - const newScopesString = newScopesArray.join(' '); - - if (permissionBeingRevokedIsAllPrincipal && userIsTenantAdmin) { - isRevokeSuccessful = await revokePermissionUtil.getUpdatedAllPrincipalPermissionGrant(grantsPayload, - permissionToRevoke); - } else { - isRevokeSuccessful = await revokePermissionUtil.updateSinglePrincipalPermissionGrant(grantsPayload, profile, - newScopesString); - } - - if (isRevokeSuccessful) { - return newScopesString.split(' '); - } - else if((retryCount < maxRetryCount) && !isRevokeSuccessful) { - await new Promise(resolve => setTimeout(resolve, retryDelay * 2)); - dispatchScopesStatus(dispatch, 'We are retrying the revoking operation', 'Retrying', 5); - - permissionsUpdateObject.retryCount += 1; - return updatePermissions(permissionsUpdateObject); - } - else{ - return null; - } - -} - -const dispatchScopesStatus = (dispatch: Function, statusText: string, status: string, messageType: number) => { - dispatch(revokeScopesError()); - dispatch( - setQueryResponseStatus({ - statusText: translateMessage(status), - status: translateMessage(statusText), - ok: false, - messageType - }) - ) -} - -const trackRevokeConsentEvent = (status: string, permissionObject: any) => { - telemetry.trackEvent(eventTypes.BUTTON_CLICK_EVENT, { - componentName: componentNames.REVOKE_PERMISSION_CONSENT_BUTTON, - permissionObject, - status - }); -} - -export function fetchAllPrincipalGrants() { - return async (dispatch: Function, getState: Function) => { - dispatch(getAllPrincipalGrantsPending(true)); - try { - const { profile, consentedScopes, scopes } = getState(); - const tenantWideGrant: IOAuthGrantPayload = scopes.data.tenantWidePermissionsGrant; - const revokePermissionUtil = await RevokePermissionsUtil.initialize(profile.id); - if (revokePermissionUtil && revokePermissionUtil.getGrantsPayload() !== null){ - const servicePrincipalAppId = revokePermissionUtil.getServicePrincipalAppId(); - dispatch(getAllPrincipalGrantsPending(true)); - const requestCounter = 0; - - await checkScopesConsentType(servicePrincipalAppId, tenantWideGrant, revokePermissionUtil, - consentedScopes, profile, requestCounter, dispatch); - } - else{ - dispatch(getAllPrincipalGrantsPending(false)); - dispatchScopesStatus(dispatch, 'Permissions', 'You require the following permissions to read', 0) - } - } catch (error: any) { - dispatch(getAllPrincipalGrantsPending(false)); - dispatch(getAllPrincipalGrantsError(error)); - } - } -} - -const dispatchGrantsStatus = (dispatch: Function, tenantGrantValue: IPermissionGrant[]): void => { - dispatch(getAllPrincipalGrantsPending(false)); - dispatch(getAllPrincipalGrantsSuccess(tenantGrantValue)); -} - -const allScopesHaveConsentType = (consentedScopes: string[], tenantWideGrant: IOAuthGrantPayload, id: string) => { - const allPrincipalGrants: string[] = getAllPrincipalGrant(tenantWideGrant.value); - const singlePrincipalGrants: string[] = getSinglePrincipalGrant(tenantWideGrant.value, id); - const combinedPermissions = [...allPrincipalGrants, ...singlePrincipalGrants]; - return consentedScopes.every(scope => combinedPermissions.includes(scope)); -} - -export const getAllPrincipalGrant = (tenantWideGrant: IPermissionGrant[]): string[] => { - if(tenantWideGrant){ - const allGrants = tenantWideGrant; - if(allGrants){ - const principalGrant = allGrants.find(grant => grant.consentType === 'AllPrincipals'); - if(principalGrant){ - return principalGrant.scope.split(' '); - } - } - } - return []; -} - -export const getSinglePrincipalGrant = (tenantWideGrant: IPermissionGrant[], principalId: string): string[] => { - if(tenantWideGrant && principalId){ - const allGrants = tenantWideGrant; - const singlePrincipalGrant = allGrants.find(grant => grant.principalId === principalId); - if(singlePrincipalGrant){ - return singlePrincipalGrant.scope.split(' '); - } - } - return []; -} -async function checkScopesConsentType(servicePrincipalAppId: string, tenantWideGrant: IOAuthGrantPayload, - revokePermissionUtil: RevokePermissionsUtil, consentedScopes: string[], profile: IUser, - requestCounter: number, dispatch: Function) { - if (servicePrincipalAppId) { - tenantWideGrant = revokePermissionUtil.getGrantsPayload(); - if (tenantWideGrant) { - if (!allScopesHaveConsentType(consentedScopes, tenantWideGrant, profile.id)) { - while (requestCounter < 10 && profile && profile.id && - !allScopesHaveConsentType(consentedScopes, tenantWideGrant, profile.id)) { - requestCounter += 1; - await new Promise((resolve) => setTimeout(resolve, 400 * requestCounter)); - revokePermissionUtil = await RevokePermissionsUtil.initialize(profile.id); - dispatch(getAllPrincipalGrantsPending(true)); - tenantWideGrant = revokePermissionUtil.getGrantsPayload(); - } - dispatchGrantsStatus(dispatch, tenantWideGrant.value); - } - else { - dispatchGrantsStatus(dispatch, tenantWideGrant.value); - } - } - } -} - diff --git a/src/app/services/actions/profile-action-creators.spec.ts b/src/app/services/actions/profile-action-creators.spec.ts index 233407944..d59e3d052 100644 --- a/src/app/services/actions/profile-action-creators.spec.ts +++ b/src/app/services/actions/profile-action-creators.spec.ts @@ -1,29 +1,16 @@ import configureMockStore from 'redux-mock-store'; -import thunk from 'redux-thunk'; -import { AppAction } from '../../../types/action'; -import { getProfileInfo, profileRequestError, profileRequestSuccess } from './profile-action-creators'; -import { PROFILE_REQUEST_ERROR, PROFILE_REQUEST_SUCCESS } from '../redux-constants'; -const middlewares = [thunk]; -const mockStore = configureMockStore(middlewares); +import { PROFILE_REQUEST_ERROR } from '../redux-constants'; +import { mockThunkMiddleware } from './mockThunkMiddleware'; +import { getProfileInfo } from '../slices/profile.slice'; + +const mockStore = configureMockStore([mockThunkMiddleware]); describe('Profile action creators', () => { beforeEach(() => { fetchMock.resetMocks(); }); - it('should dispatch PROFILE_REQUEST_SUCCESS when profileRequestSuccess() is called', () => { - - const response = fetchMock.mockResponseOnce(JSON.stringify({ ok: false })); - const expectedAction: AppAction = { - type: PROFILE_REQUEST_SUCCESS, - response - }; - - const action = profileRequestSuccess(response); - expect(action).toEqual(expectedAction); - }); - it('should dispatch PROFILE_REQUEST_ERROR when getProfileInfo() request fails', () => { fetchMock.mockResponseOnce(JSON.stringify({ ok: false })); const store = mockStore({}); @@ -35,19 +22,4 @@ describe('Profile action creators', () => { }) .catch((e: Error) => { throw e }) }); - - it('should dispatch PROFILE_REQUEST_ERROR when profileRequestError() is called', () => { - // Arrange - const response = {}; - const expectedAction: AppAction = { - type: PROFILE_REQUEST_ERROR, - response - } - - // Act - const action = profileRequestError(response); - - // Assert - expect(action).toEqual(expectedAction); - }); }); \ No newline at end of file diff --git a/src/app/services/actions/profile-action-creators.ts b/src/app/services/actions/profile-actions.ts similarity index 74% rename from src/app/services/actions/profile-action-creators.ts rename to src/app/services/actions/profile-actions.ts index 531486910..573f6730e 100644 --- a/src/app/services/actions/profile-action-creators.ts +++ b/src/app/services/actions/profile-actions.ts @@ -1,7 +1,5 @@ import { AgeGroup } from '@ms-ofb/officebrowserfeedbacknpm/scripts/app/Configuration/IInitOptions'; -import { AppDispatch } from '../../../store'; -import { AppAction } from '../../../types/action'; import { IUser } from '../../../types/profile'; import { IQuery } from '../../../types/query-runner'; import { translateMessage } from '../../utils/translate-messages'; @@ -9,7 +7,6 @@ import { ACCOUNT_TYPE, BETA_USER_INFO_URL, DEFAULT_USER_SCOPES, USER_INFO_URL, USER_ORGANIZATION_URL, USER_PICTURE_URL } from '../graph-constants'; -import { PROFILE_REQUEST_ERROR, PROFILE_REQUEST_SUCCESS } from '../redux-constants'; import { makeGraphRequest, parseResponse } from './query-action-creator-util'; interface IBetaProfile { @@ -22,20 +19,6 @@ interface IProfileResponse { response: any; } -export function profileRequestSuccess(response: object): AppAction { - return { - type: PROFILE_REQUEST_SUCCESS, - response - }; -} - -export function profileRequestError(response: object): AppAction { - return { - type: PROFILE_REQUEST_ERROR, - response - }; -} - const query: IQuery = { selectedVerb: 'GET', sampleHeaders: [ @@ -48,22 +31,6 @@ const query: IQuery = { sampleUrl: '' }; -export function getProfileInfo() { - return async (dispatch: AppDispatch) => { - try { - const profile: IUser = await getProfileInformation(); - const { profileType, ageGroup } = await getBetaProfile(); - profile.profileType = profileType; - profile.ageGroup = ageGroup; - profile.profileImageUrl = await getProfileImage(); - profile.tenant = await getTenantInfo(profileType); - dispatch(profileRequestSuccess(profile)); - } catch (error) { - dispatch(profileRequestError({ error })); - } - }; -} - export async function getProfileInformation(): Promise { const profile: IUser = { id: '', @@ -81,8 +48,8 @@ export async function getProfileInformation(): Promise { profile.displayName = userInfo.displayName; profile.emailAddress = userInfo.mail || userInfo.userPrincipalName; return profile; - } catch (error: any) { - throw new Error(translateMessage('Failed to get profile information') + '- ' + error.toString()); + } catch (error: unknown) { + throw new Error(translateMessage('Failed to get profile information') + '- ' + error); } } @@ -136,7 +103,7 @@ export async function getProfileImage(): Promise { export async function getProfileResponse(): Promise { const scopes = DEFAULT_USER_SCOPES.split(' '); - const respHeaders: any = {}; + const respHeaders: Record = {}; const response = await makeGraphRequest(scopes)(query); const userInfo = await parseResponse(response, respHeaders); @@ -146,7 +113,7 @@ export async function getProfileResponse(): Promise { }; } -export async function getTenantInfo(profileType: ACCOUNT_TYPE) { +export async function getTenantInfo(profileType: ACCOUNT_TYPE): Promise { if (profileType === ACCOUNT_TYPE.MSA) { return 'Personal'; } diff --git a/src/app/services/actions/proxy-action-creator.spec.ts b/src/app/services/actions/proxy-action-creator.spec.ts index 9853f9f8e..4ceafc668 100644 --- a/src/app/services/actions/proxy-action-creator.spec.ts +++ b/src/app/services/actions/proxy-action-creator.spec.ts @@ -1,48 +1,46 @@ -import { SET_GRAPH_PROXY_URL } from '../../../app/services/redux-constants'; -import { setGraphProxyUrl, getGraphProxyUrl } from '../../../app/services/actions/proxy-action-creator'; +import { AnyAction } from '@reduxjs/toolkit'; import configureMockStore from 'redux-mock-store'; -import thunk from 'redux-thunk'; -import { AppAction } from '../../../types/action'; +import { getGraphProxyUrl, setGraphProxyUrl } from '../../../app/services/slices/proxy.slice'; +import { GRAPH_API_SANDBOX_URL } from '../graph-constants'; +import { GET_GRAPH_PROXY_URL_ERROR, GET_GRAPH_PROXY_URL_PENDING, SET_GRAPH_PROXY_URL } from '../redux-constants'; +import { mockThunkMiddleware } from './mockThunkMiddleware'; -const middleware = [thunk]; -const mockStore = configureMockStore(middleware); +const mockStore = configureMockStore([mockThunkMiddleware]); describe('Tests Proxy-Action-Creators', () => { beforeEach(() => { fetchMock.resetMocks(); }); + it('should dispatch SET_GRAPH_PROXY_URL when setGraphProxyUrl is called', () => { // Arrange - const response: string = 'https://proxy.apisandbox.msdn.microsoft.com/svc'; - const expectedAction: AppAction = { + const payload: string = 'https://proxy.apisandbox.msdn.microsoft.com/svc'; + const expectedAction = { type: SET_GRAPH_PROXY_URL, - response + payload } // Act - const action = setGraphProxyUrl(response); + const action = setGraphProxyUrl(payload); // Assert expect(action).toEqual(expectedAction); }) - it('should dispatch SET_GRAPH_PROXY_URL when getGraphProxyUrl() is called', () => { + it('should dispatch GET_GRAPH_PROXY_URL when getGraphProxyUrl() is called', async () => { // Arrange - fetchMock.mockResponseOnce(JSON.stringify({ ok: false })); - const expectedAction: AppAction = { - type: SET_GRAPH_PROXY_URL, - response: { - ok: false - } - } + fetchMock.mockResponseOnce(GRAPH_API_SANDBOX_URL); + const store_ = mockStore({}); + await store_.dispatch(getGraphProxyUrl() as unknown as AnyAction); - const store = mockStore({}); + const expectedActions = [ + { type: GET_GRAPH_PROXY_URL_PENDING, payload: undefined }, + { type: GET_GRAPH_PROXY_URL_ERROR, payload: GRAPH_API_SANDBOX_URL } + ]; + expect(store_.getActions().map(action => { + const { meta, error, ...rest } = action; + return rest; + })).toEqual(expectedActions); - // Act and Assert - // @ts-ignore - store.dispatch(getGraphProxyUrl()).then(() => { - expect(store.getActions()).toEqual([expectedAction]); - }) - .catch((e: Error) => { throw e }); }) }) \ No newline at end of file diff --git a/src/app/services/actions/proxy-action-creator.ts b/src/app/services/actions/proxy-action-creator.ts deleted file mode 100644 index f86f640e1..000000000 --- a/src/app/services/actions/proxy-action-creator.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { AppAction } from '../../../types/action'; -import { GRAPH_API_SANDBOX_ENDPOINT_URL, GRAPH_API_SANDBOX_URL } from '../graph-constants'; -import { SET_GRAPH_PROXY_URL } from '../redux-constants'; - -export function getGraphProxyUrl() { - return async (dispatch: Function) => { - try { - const response = await fetch(GRAPH_API_SANDBOX_ENDPOINT_URL); - if (!response.ok) { - throw response; - } - const res = await response.json(); - return dispatch(setGraphProxyUrl(res)); - } catch (error) { - return dispatch(setGraphProxyUrl(GRAPH_API_SANDBOX_URL)); - } - }; -} - -export function setGraphProxyUrl(response: string): AppAction { - return { - type: SET_GRAPH_PROXY_URL, - response - }; -} \ No newline at end of file diff --git a/src/app/services/actions/query-action-creator-util.ts b/src/app/services/actions/query-action-creator-util.ts index fe7cf328a..0f9e8c27f 100644 --- a/src/app/services/actions/query-action-creator-util.ts +++ b/src/app/services/actions/query-action-creator-util.ts @@ -9,7 +9,7 @@ import { } from '@microsoft/microsoft-graph-client/authProviders/authCodeMsalBrowser'; import { authenticationWrapper } from '../../../modules/authentication'; -import { AppAction } from '../../../types/action'; +import { ApplicationState } from '../../../store'; import { ContentType } from '../../../types/enums'; import { IQuery } from '../../../types/query-runner'; import { IRequestOptions } from '../../../types/request'; @@ -19,24 +19,13 @@ import { encodeHashCharacters } from '../../utils/query-url-sanitization'; import { translateMessage } from '../../utils/translate-messages'; import { authProvider, GraphClient } from '../graph-client'; import { DEFAULT_USER_SCOPES, GRAPH_URL } from '../graph-constants'; -import { QUERY_GRAPH_SUCCESS } from '../redux-constants'; -import { queryRunningStatus } from './query-loading-action-creators'; - -export function queryResponse(response: object): AppAction { - return { - type: QUERY_GRAPH_SUCCESS, - response - }; -} export async function anonymousRequest( - dispatch: Function, query: IQuery, getState: Function ) { - const { proxyUrl, queryRunnerStatus } = getState(); - const { graphUrl, options } = createAnonymousRequest(query, proxyUrl, queryRunnerStatus); - dispatch(queryRunningStatus(true)); + const { proxyUrl, queryRunnerStatus } = getState() as ApplicationState; + const { graphUrl, options } = createAnonymousRequest(query, proxyUrl, queryRunnerStatus!); return fetch(graphUrl, options) .catch(() => { throw new ClientError({ error: translateMessage('Could not connect to the sandbox') }); @@ -78,11 +67,9 @@ export function createAnonymousRequest(query: IQuery, proxyUrl: string, queryRun } export function authenticatedRequest( - dispatch: Function, query: IQuery, scopes: string[] = DEFAULT_USER_SCOPES.split(' ') ) { - dispatch(queryRunningStatus(true)); return makeGraphRequest(scopes)(query); } @@ -90,7 +77,7 @@ function createAuthenticatedRequest( scopes: string[], query: IQuery ): GraphRequest { - const sampleHeaders: any = {}; + const sampleHeaders: Record = {}; sampleHeaders.SdkVersion = 'GraphExplorer/4.0'; sampleHeaders.prefer = 'ms-graph-dev-mode'; @@ -121,7 +108,7 @@ export function makeGraphRequest(scopes: string[]) { return async (query: IQuery) => { let response; - const graphRequest = createAuthenticatedRequest(scopes, query); + const graphRequest: GraphRequest = createAuthenticatedRequest(scopes, query); switch (query.selectedVerb) { case 'GET': @@ -230,11 +217,11 @@ async function tryParseJson(textValue: string) { } export function parseResponse( - response: any, - respHeaders: any = {} + response: Response, + respHeaders: { [key: string]: string } = {} ): Promise { if (response && response.headers) { - response.headers.forEach((val: any, key: any) => { + response.headers.forEach((val: string, key: string) => { respHeaders[key] = val; }); @@ -249,10 +236,10 @@ export function parseResponse( return response.text(); default: - return response; + return Promise.resolve(response); } } - return response; + return Promise.resolve(response); } /** diff --git a/src/app/services/actions/query-action-creators.spec.ts b/src/app/services/actions/query-action-creators.spec.ts index 7f1cca254..1641476cd 100644 --- a/src/app/services/actions/query-action-creators.spec.ts +++ b/src/app/services/actions/query-action-creators.spec.ts @@ -1,19 +1,20 @@ /* eslint-disable max-len */ import configureMockStore from 'redux-mock-store'; -import thunk from 'redux-thunk'; -import { runQuery } from './query-action-creators'; -import { QUERY_GRAPH_SUCCESS } from '../redux-constants'; +import { ADD_HISTORY_ITEM_SUCCESS, QUERY_GRAPH_RUNNING, QUERY_GRAPH_STATUS, QUERY_GRAPH_SUCCESS } from '../redux-constants'; +import { runQuery } from '../slices/graph-response.slice'; +import { mockThunkMiddleware } from './mockThunkMiddleware'; +import { AnyAction } from '@reduxjs/toolkit'; +import { IQuery } from '../../../types/query-runner'; -const middlewares = [thunk]; -const mockStore = configureMockStore(middlewares); +const mockStore = configureMockStore([mockThunkMiddleware]); describe('Query action creators', () => { beforeEach(() => { fetchMock.resetMocks(); }); - it('should dispatch QUERY_GRAPH_SUCCESS when runQuery() is called', () => { + it.skip('should dispatch QUERY_GRAPH_SUCCESS when runQuery() is called', () => { const createdAt = new Date().toISOString(); const sampleUrl = 'https://graph.microsoft.com/v1.0/me/'; @@ -25,11 +26,10 @@ describe('Query action creators', () => { const expectedActions = [ { - type: 'QUERY_GRAPH_RUNNING', - response: true + type: QUERY_GRAPH_RUNNING }, { - response: + payload: { body: undefined, createdAt, @@ -49,26 +49,31 @@ describe('Query action creators', () => { statusText: 'OK', url: sampleUrl }, - type: 'ADD_HISTORY_ITEM_SUCCESS' + type: ADD_HISTORY_ITEM_SUCCESS }, { type: QUERY_GRAPH_SUCCESS, - response: { + payload: { body: { displayName: 'Megan Bowen', ok: true }, headers: { 'content-type': 'application-json' } } } ]; - const store = mockStore({ graphResponse: '' }); - const query = { sampleUrl }; + const query: IQuery = { + sampleUrl, + sampleHeaders: [], + sampleBody: '', + selectedVerb: 'GET', + selectedVersion: 'v1.0' + } + const store_ = mockStore({ graphResponse: '' }); + store_.dispatch(runQuery(query) as unknown as AnyAction); + expect(store_.getActions().map(action => { + const { meta, ...rest } = action; + return rest; + })).toEqual(expectedActions); - // @ts-ignore - return store.dispatch(runQuery(query)) - .then(() => { - expect(store.getActions()[0]).toEqual(expectedActions[0]); - }) - .catch((e: Error) => { throw e }); }); it('should dispatch QUERY_GRAPH_SUCCESS, ADD_HISTORY_ITEM_SUCCESS and QUERY_GRAPH_STATUS when runQuery is called', () => { @@ -77,7 +82,7 @@ describe('Query action creators', () => { fetchMock.mockResponseOnce(JSON.stringify({ ok: false })); }, 1000); - const expectedActions = ['QUERY_GRAPH_SUCCESS', 'ADD_HISTORY_ITEM_SUCCESS', 'QUERY_GRAPH_STATUS']; + const expectedActions = [QUERY_GRAPH_SUCCESS, ADD_HISTORY_ITEM_SUCCESS, QUERY_GRAPH_STATUS]; const getDispatchedTypes = (actions: any) => { const types_: string[] = []; @@ -109,11 +114,18 @@ describe('Query action creators', () => { .catch((e: Error) => { throw e }); }); - it('should dispatch query status when a 401 is received', () => { + it.skip('should dispatch query status when a 401 is received', () => { const sampleUrl = 'https://graph.microsoft.com/v1.0/me'; + const query: IQuery = { + sampleUrl, + sampleHeaders: [], + sampleBody: '', + selectedVerb: 'GET', + selectedVersion: 'v1.0' + } - const store = mockStore({ graphResponse: '' }); - const query = { sampleUrl } + const store_ = mockStore({ graphResponse: '' }); + store_.dispatch(runQuery(query) as unknown as AnyAction); const mockFetch = jest.fn().mockImplementation(() => { return Promise.resolve({ ok: false, @@ -125,11 +137,10 @@ describe('Query action creators', () => { window.fetch = mockFetch; - // @ts-ignore - return store.dispatch(runQuery(query)) - .then((response) => { - expect(response.type).toBe('QUERY_GRAPH_STATUS'); - expect(response.response.ok).toBe(false); + store_.dispatch(runQuery(query) as unknown as AnyAction) + .then((response: { type: any; payload: { ok: boolean; }; }) => { + expect(response.type).toBe(QUERY_GRAPH_STATUS); + expect(response.payload.ok).toBe(false); }) .catch((e: Error) => { throw e }); diff --git a/src/app/services/actions/query-action-creators.ts b/src/app/services/actions/query-action-creators.ts deleted file mode 100644 index d0bc91a38..000000000 --- a/src/app/services/actions/query-action-creators.ts +++ /dev/null @@ -1,225 +0,0 @@ -import { MessageBarType } from '@fluentui/react'; - -import { ContentType } from '../../../types/enums'; -import { IHistoryItem } from '../../../types/history'; -import { IQuery } from '../../../types/query-runner'; -import { IStatus } from '../../../types/status'; -import { ClientError } from '../../utils/error-utils/ClientError'; -import { setStatusMessage } from '../../utils/status-message'; -import { historyCache } from '../../../modules/cache/history-utils'; -import { - anonymousRequest, - authenticatedRequest, - generateResponseDownloadUrl, - isFileResponse, - isImageResponse, - parseResponse, - queryResponse, - queryResultsInCorsError -} from './query-action-creator-util'; -import { setQueryResponseStatus } from './query-status-action-creator'; -import { addHistoryItem } from './request-history-action-creators'; -import { authenticationWrapper } from '../../../modules/authentication'; -import { BrowserAuthError } from '@azure/msal-browser'; -import { ClaimsChallenge } from '../../../modules/authentication/ClaimsChallenge'; -import { translateMessage } from '../../utils/translate-messages'; - -const MAX_NUMBER_OF_RETRIES = 3; -let CURRENT_RETRIES = 0; -export function runQuery(query: IQuery) { - return (dispatch: Function, getState: Function) => { - const tokenPresent = !!getState()?.authToken?.token; - const respHeaders: any = {}; - const createdAt = new Date().toISOString(); - - if (tokenPresent) { - return authenticatedRequest(dispatch, query) - .then(async (response: Response) => { - await processResponse(response, respHeaders, dispatch, createdAt); - }) - .catch(async (error: any) => { - return handleError(dispatch, error); - }); - } - - return anonymousRequest(dispatch, query, getState) - .then(async (response: Response) => { - await processResponse(response, respHeaders, dispatch, createdAt); - }) - .catch(async (error: any) => { - return handleError(dispatch, error); - }); - }; - - async function processResponse( - response: Response, - respHeaders: any, - dispatch: Function, - createdAt: any - ) { - let result = await parseResponse(response, respHeaders); - const duration = new Date().getTime() - new Date(createdAt).getTime(); - createHistory( - response, - respHeaders, - query, - createdAt, - dispatch, - result, - duration - ); - - const status: IStatus = { - messageType: MessageBarType.error, - ok: false, - duration, - status: response.status || 400, - statusText: '' - }; - - if (response) { - status.status = response.status; - status.statusText = - response.statusText === '' - ? setStatusMessage(response.status) - : response.statusText; - } - - if (response && response.ok) { - CURRENT_RETRIES = 0; - status.ok = true; - status.messageType = MessageBarType.success; - - if (isFileResponse(respHeaders)) { - const contentDownloadUrl = await generateResponseDownloadUrl( - response, - respHeaders - ); - if (contentDownloadUrl) { - result = { - contentDownloadUrl - }; - } - } - } - - if(response && response.status === 401 && (CURRENT_RETRIES < MAX_NUMBER_OF_RETRIES)) { - const successful = await runReAuthenticatedRequest(response); - if(successful){ - dispatch(runQuery(query)); - return; - } - } - - dispatch(setQueryResponseStatus(status)); - - return dispatch( - queryResponse({ - body: result, - headers: respHeaders - }) - ); - } - - async function runReAuthenticatedRequest(response: Response): Promise{ - if (response.headers.get('www-authenticate')) { - const account = authenticationWrapper.getAccount(); - if (!account) { return false; } - new ClaimsChallenge(query, account).handle(response.headers); - const authResult = await authenticationWrapper.logIn('', query); - if (authResult.accessToken) { - CURRENT_RETRIES += 1; - return true; - } - } - return false; - } - - function handleError(dispatch: Function, error: any) { - let body = error; - const status: IStatus = { - messageType: MessageBarType.error, - ok: false, - status: 400, - statusText: 'Bad Request' - }; - - if (error instanceof ClientError) { - status.status = error.message; - status.statusText = error.name; - } - - if (queryResultsInCorsError(query.sampleUrl)) { - status.status = 0; - status.statusText = 'CORS error'; - body = { - throwsCorsError: true - }; - } - - if (error && error instanceof BrowserAuthError) { - if (error.errorCode === 'user_cancelled'){ - status.hint = `${translateMessage('user_cancelled')}`; - } - else{ - status.statusText = `${error.name}: ${error.message}`; - } - } - - dispatch( - queryResponse({ - body, - headers: null - }) - ); - - return dispatch(setQueryResponseStatus(status)); - } -} - -async function createHistory( - response: Response, - respHeaders: any, - query: IQuery, - createdAt: any, - dispatch: Function, - result: any, - duration: number -) { - const status = response.status; - const statusText = response.statusText === '' ? setStatusMessage(status) : response.statusText; - const responseHeaders = { ...respHeaders }; - const contentType = respHeaders['content-type']; - - if (isImageResponse(contentType)) { - result = { - message: 'Run the query to view the image' - }; - responseHeaders['content-type'] = ContentType.Json; - } - - if (isFileResponse(respHeaders)) { - result = { - message: 'Run the query to generate file download URL' - }; - } - - const historyItem: IHistoryItem = { - index: -1, - url: query.sampleUrl, - method: query.selectedVerb, - headers: query.sampleHeaders, - body: query.sampleBody, - responseHeaders, - createdAt, - status, - statusText, - duration, - result - }; - - historyCache.writeHistoryData(historyItem); - - dispatch(addHistoryItem(historyItem)); - return result; -} diff --git a/src/app/services/actions/query-input-action-creators.spec.ts b/src/app/services/actions/query-input-action-creators.spec.ts index de834859d..17a54aa87 100644 --- a/src/app/services/actions/query-input-action-creators.spec.ts +++ b/src/app/services/actions/query-input-action-creators.spec.ts @@ -1,11 +1,9 @@ import configureMockStore from 'redux-mock-store'; -import thunk from 'redux-thunk'; -import { setSampleQuery } from './query-input-action-creators'; +import { setSampleQuery } from '../slices/sample-query.slice'; import { SET_SAMPLE_QUERY_SUCCESS } from '../redux-constants'; -const middlewares = [thunk]; -const mockStore = configureMockStore(middlewares); +const mockStore = configureMockStore(); describe('Query input action creators should', () => { beforeEach(() => { @@ -16,7 +14,7 @@ describe('Query input action creators should', () => { const expectedActions = [ { type: SET_SAMPLE_QUERY_SUCCESS, - response: { + payload: { selectedVerb: 'GET', sampleUrl: 'https://graph.microsoft.com/v1.0/me/' } diff --git a/src/app/services/actions/query-input-action-creators.ts b/src/app/services/actions/query-input-action-creators.ts deleted file mode 100644 index 50e6b4ef4..000000000 --- a/src/app/services/actions/query-input-action-creators.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { AppAction } from '../../../types/action'; -import { IQuery } from '../../../types/query-runner'; -import { SET_SAMPLE_QUERY_SUCCESS } from '../redux-constants'; - -export function setSampleQuery(response: IQuery): AppAction { - return { - type: SET_SAMPLE_QUERY_SUCCESS, - response - }; -} diff --git a/src/app/services/actions/query-loading-action-creators.ts b/src/app/services/actions/query-loading-action-creators.ts deleted file mode 100644 index 25b42aa6a..000000000 --- a/src/app/services/actions/query-loading-action-creators.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { AppAction } from '../../../types/action'; -import { QUERY_GRAPH_RUNNING } from '../redux-constants'; - -export function queryRunningStatus(response: boolean): AppAction { - return { - type: QUERY_GRAPH_RUNNING, - response - }; -} \ No newline at end of file diff --git a/src/app/services/actions/query-status-action-creator.spec.ts b/src/app/services/actions/query-status-action-creator.spec.ts index 5db8aedcb..2fc399194 100644 --- a/src/app/services/actions/query-status-action-creator.spec.ts +++ b/src/app/services/actions/query-status-action-creator.spec.ts @@ -1,11 +1,14 @@ -import { CLEAR_RESPONSE, QUERY_GRAPH_STATUS } from '../../../app/services/redux-constants'; -import { clearResponse, setQueryResponseStatus } from '../../../app/services/actions/query-status-action-creator'; -import { AppAction } from '../../../types/action'; +import configureMockStore from 'redux-mock-store'; + +import { QUERY_GRAPH_STATUS } from '../../../app/services/redux-constants'; +import { setQueryResponseStatus } from '../../../app/services/slices/query-status.slice'; + +const mockStore = configureMockStore([]); describe('Query Action Creators', () => { it('should dispatch QUERY_GRAPH_STATUS when setQueryResponseStatus() is called', () => { // Arrange - const response = { + const payload = { ok: false, statusText: 'Something worked!', status: 200, @@ -13,29 +16,16 @@ describe('Query Action Creators', () => { hint: 'Something worked!' } - const expectedAction: AppAction = { + const expectedAction = { type: QUERY_GRAPH_STATUS, - response - } - - // Act - const action = setQueryResponseStatus(response); - - // Assert - expect(action).toEqual(expectedAction); - }); - - it('should dispatch CLEAR_RESPONSE action when clearResponse() is called', () => { - // Assert - const expectedAction: AppAction = { - type: CLEAR_RESPONSE, - response: null + payload } // Act - const action = clearResponse(); + const store = mockStore({ queryRunnerStatus: null, auth: {} }); + store.dispatch(setQueryResponseStatus(payload)); // Assert - expect(action).toEqual(expectedAction); + expect(store.getActions()).toEqual([expectedAction]); }); }); diff --git a/src/app/services/actions/query-status-action-creator.ts b/src/app/services/actions/query-status-action-creator.ts deleted file mode 100644 index c0a128722..000000000 --- a/src/app/services/actions/query-status-action-creator.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Dispatch } from 'redux'; -import { AppAction } from '../../../types/action'; -import { CLEAR_QUERY_STATUS, CLEAR_RESPONSE, QUERY_GRAPH_STATUS } from '../redux-constants'; - -export function setQueryResponseStatus(response: object): AppAction { - return { - type: QUERY_GRAPH_STATUS, - response - }; -} - -export function clearResponse(): AppAction { - return { - type: CLEAR_RESPONSE, - response: null - }; -} - - -export function clearQueryStatus() { - return (dispatch: Dispatch) => { - dispatch({ - type: CLEAR_QUERY_STATUS - }); - }; -} - diff --git a/src/app/services/actions/request-history-action-creators.spec.ts b/src/app/services/actions/request-history-action-creators.spec.ts index 4b3684dcc..5ab0c4781 100644 --- a/src/app/services/actions/request-history-action-creators.spec.ts +++ b/src/app/services/actions/request-history-action-creators.spec.ts @@ -1,22 +1,18 @@ import configureMockStore from 'redux-mock-store'; -import thunk from 'redux-thunk'; + +import { PayloadAction } from '@reduxjs/toolkit'; +import { IHistoryItem } from '../../../types/history'; import { - addHistoryItem, viewHistoryItem, removeHistoryItem, - bulkRemoveHistoryItems, - bulkAddHistoryItems -} from './request-history-action-creators'; -import { - ADD_HISTORY_ITEM_SUCCESS, VIEW_HISTORY_ITEM_SUCCESS, - REMOVE_HISTORY_ITEM_SUCCESS, + ADD_HISTORY_ITEM_SUCCESS, + BULK_ADD_HISTORY_ITEMS_SUCCESS, REMOVE_ALL_HISTORY_ITEMS_SUCCESS, - BULK_ADD_HISTORY_ITEMS_SUCCESS + REMOVE_HISTORY_ITEM_SUCCESS } from '../redux-constants'; -import { IHistoryItem } from '../../../types/history'; -import { AppAction } from '../../../types/action'; -import { IGraphResponse } from '../../../types/query-response'; +import { addHistoryItem, bulkAddHistoryItems, removeAllHistoryItems, removeHistoryItem } from '../slices/history.slice'; +import { mockThunkMiddleware } from './mockThunkMiddleware'; -const middlewares = [thunk]; +const middlewares = [mockThunkMiddleware]; const mockStore = configureMockStore(middlewares); describe('Request History Action Creators', () => { @@ -25,7 +21,7 @@ describe('Request History Action Creators', () => { const expectedActions = [ { type: ADD_HISTORY_ITEM_SUCCESS, - response: historyItem + payload: historyItem } ]; @@ -36,33 +32,12 @@ describe('Request History Action Creators', () => { expect(store.getActions()).toEqual(expectedActions); }); - it('should dispatch VIEW_HISTORY_ITEM_SUCCESS when viewHistoryItem() is called with a valid history item', () => { - // Assert - const response: IGraphResponse = { - body: undefined, - headers: undefined - } - - const expectedAction: AppAction = { - type: VIEW_HISTORY_ITEM_SUCCESS, - response - } - - // Act - const store = mockStore({ history: [] }); - - // Assert - // @ts-ignore - store.dispatch(viewHistoryItem(response)); - expect(store.getActions()).toEqual([expectedAction]); - }); - it('should dispatch REMOVE_HISTORY_ITEM_SUCCESS when a history item is removed', () => { // Arrange const historyItem: IHistoryItem = { index: 0, statusText: 'Something worked!', - responseHeaders: [], + responseHeaders: {}, result: {}, url: 'https://graph.microsoft.com/v1.0/me', method: 'GET', @@ -72,19 +47,16 @@ describe('Request History Action Creators', () => { duration: 200 } - const expectedAction: AppAction = { + const expectedAction: PayloadAction = { type: REMOVE_HISTORY_ITEM_SUCCESS, - response: historyItem + payload: historyItem } const store = mockStore([historyItem]); // Act and Assert - // @ts-ignore store.dispatch(removeHistoryItem(historyItem)) - .then(() => { - expect(store.getActions()).toEqual([expectedAction]); - }) + expect(store.getActions()).toEqual([expectedAction]); }); @@ -117,28 +89,30 @@ describe('Request History Action Creators', () => { } ] - const expectedAction: AppAction = { + const listOfKeys: string[] = []; + historyItems.forEach(historyItem => { + listOfKeys.push(historyItem.createdAt); + }); + + const expectedAction: PayloadAction = { type: REMOVE_ALL_HISTORY_ITEMS_SUCCESS, - response: ['12345', '12345'] + payload: ['12345', '12345'] } const store = mockStore(historyItems); // Act and Assert - // @ts-ignore - store.dispatch(bulkRemoveHistoryItems(historyItems)) - .then(() => { - expect(store.getActions()).toEqual([expectedAction]); - }) + store.dispatch(removeAllHistoryItems(listOfKeys)) + expect(store.getActions()).toEqual([expectedAction]); }); it('should dispatch BULK_ADD_HISTORY_ITEMS_SUCCESS when bulkAddHistoryItems() is called', () => { // Arrange - const historyItems = [ + const historyItems: IHistoryItem[] = [ { index: 0, statusText: 'OK', - responseHeaders: [], + responseHeaders: {}, result: {}, url: 'https://graph.microsoft.com/v1.0/me', method: 'GET', @@ -150,7 +124,7 @@ describe('Request History Action Creators', () => { { index: 1, statusText: 'OK', - responseHeaders: [], + responseHeaders: {}, result: {}, url: 'https://graph.microsoft.com/v1.0/me/events', method: 'GET', @@ -161,9 +135,9 @@ describe('Request History Action Creators', () => { } ] - const expectedAction: AppAction = { + const expectedAction: PayloadAction = { type: BULK_ADD_HISTORY_ITEMS_SUCCESS, - response: historyItems + payload: historyItems } const store = mockStore([]); diff --git a/src/app/services/actions/request-history-action-creators.ts b/src/app/services/actions/request-history-action-creators.ts deleted file mode 100644 index 380baab01..000000000 --- a/src/app/services/actions/request-history-action-creators.ts +++ /dev/null @@ -1,68 +0,0 @@ - -import { AppAction } from '../../../types/action'; -import { IHistoryItem } from '../../../types/history'; -import { historyCache } from '../../../modules/cache/history-utils'; -import { - ADD_HISTORY_ITEM_SUCCESS, - REMOVE_ALL_HISTORY_ITEMS_SUCCESS, - REMOVE_HISTORY_ITEM_SUCCESS, - VIEW_HISTORY_ITEM_SUCCESS, - BULK_ADD_HISTORY_ITEMS_SUCCESS -} from '../redux-constants'; - -export function addHistoryItem(historyItem: IHistoryItem): AppAction { - return { - type: ADD_HISTORY_ITEM_SUCCESS, - response: historyItem - }; -} - -export function bulkAddHistoryItems(historyItems: IHistoryItem[]): AppAction { - return { - type: BULK_ADD_HISTORY_ITEMS_SUCCESS, - response: historyItems - }; -} - -export function viewHistoryItem(historyItem: IHistoryItem): AppAction { - return { - type: VIEW_HISTORY_ITEM_SUCCESS, - response: { - body: historyItem.result, - headers: historyItem.headers - } - }; -} - -export function removeHistoryItem(historyItem: IHistoryItem) { - - delete historyItem.category; - return async (dispatch: Function) => { - return historyCache.removeHistoryData(historyItem) - .then(() => { - dispatch({ - type: REMOVE_HISTORY_ITEM_SUCCESS, - response: historyItem - }); - }); - }; -} - -export function bulkRemoveHistoryItems(historyItems: IHistoryItem[]) { - - const listOfKeys: any = []; - historyItems.forEach(historyItem => { - listOfKeys.push(historyItem.createdAt); - }); - - return async (dispatch: Function) => { - return historyCache.bulkRemoveHistoryData(listOfKeys) - .then(() => { - dispatch({ - type: REMOVE_ALL_HISTORY_ITEMS_SUCCESS, - response: listOfKeys - }); - }); - }; -} - diff --git a/src/app/services/actions/resource-explorer-action-creators.spec.ts b/src/app/services/actions/resource-explorer-action-creators.spec.ts index 839957011..edba19e18 100644 --- a/src/app/services/actions/resource-explorer-action-creators.spec.ts +++ b/src/app/services/actions/resource-explorer-action-creators.spec.ts @@ -1,44 +1,39 @@ import configureMockStore from 'redux-mock-store'; -import thunk from 'redux-thunk'; -import { - fetchResources, fetchResourcesError, - fetchResourcesPending, fetchResourcesSuccess -} from '../../../app/services/actions/resource-explorer-action-creators'; + +import { AnyAction } from '@reduxjs/toolkit'; import { - FETCH_RESOURCES_ERROR, FETCH_RESOURCES_PENDING, FETCH_RESOURCES_SUCCESS } from '../../../app/services/redux-constants'; -import { AppAction } from '../../../types/action'; +import { ApplicationState } from '../../../store'; import { Mode } from '../../../types/enums'; -import { ApplicationState } from '../../../types/root'; +import { fetchResources } from '../slices/resources.slice'; +import { mockThunkMiddleware } from './mockThunkMiddleware'; -const middlewares = [thunk]; -const mockStore = configureMockStore(middlewares); +const mockStore = configureMockStore([mockThunkMiddleware]); const mockState: ApplicationState = { devxApi: { baseUrl: 'https://graph.microsoft.com/v1.0/me', parameters: '$count=true' }, - profile: null, + profile: { + user: undefined, + error: undefined, + status: 'unset' + }, sampleQuery: { sampleUrl: 'http://localhost:8080/api/v1/samples/1', selectedVerb: 'GET', selectedVersion: 'v1', sampleHeaders: [] }, - authToken: { token: false, pending: false }, - consentedScopes: [], - isLoadingData: false, + auth: { + authToken: { token: false, pending: false }, + consentedScopes: [] + }, queryRunnerStatus: null, termsOfUse: true, theme: 'dark', - adaptiveCard: { - pending: false, - data: { - template: 'Template' - } - }, graphExplorerMode: Mode.Complete, sidebarProperties: { showSidebar: true, @@ -49,6 +44,11 @@ const mockState: ApplicationState = { pending: false, error: null }, + permissionGrants: { + permissions: [], + pending: false, + error: null + }, scopes: { pending: { isSpecificPermissions: false, isFullPermissions: false }, data: { @@ -59,8 +59,11 @@ const mockState: ApplicationState = { }, history: [], graphResponse: { - body: undefined, - headers: undefined + isLoadingData: false, + response: { + body: undefined, + headers: undefined + } }, snippets: { pending: false, @@ -95,7 +98,9 @@ const mockState: ApplicationState = { pending: false, data: {}, error: null - } + }, + collections: [], + proxyUrl: '' } const paths = [ @@ -135,66 +140,32 @@ describe('Resource Explorer actions', () => { fetchMock.resetMocks(); }); - it('should dispatch FETCH_RESOURCES_SUCCESS when fetchResourcesSuccess() is called', () => { - - const response = fetchMock.mockResponseOnce(JSON.stringify({ ok: true })); - const expectedAction: AppAction = { - type: FETCH_RESOURCES_SUCCESS, - response - }; - - const action = fetchResourcesSuccess(response); - expect(action.type).toEqual(expectedAction.type); - }); - - it('should dispatch FETCH_RESOURCES_ERROR when fetchResourcesError() is called', () => { - // Arrange - const response = {}; - const expectedAction: AppAction = { - type: FETCH_RESOURCES_ERROR, - response - } - - // Act - const action = fetchResourcesError(response); - - // Assert - expect(action.type).toEqual(expectedAction.type); - }) - - it('should dispatch FETCH_RESOURCES_PENDING when fetchResourcesPending() is called', () => { - // Arrange - const expectedAction: AppAction = { - type: FETCH_RESOURCES_PENDING, - response: null - } - - // Act - const action = fetchResourcesPending(); - - // Assert - expect(action.type).toEqual(expectedAction.type); - }); - - it.skip('should dispatch FETCH_RESOURCES_PENDING and FETCH_RESOURCES_SUCCESS when fetchResources() is called', () => { - // Arrange - const expectedAction: AppAction[] = [ - { type: FETCH_RESOURCES_PENDING, response: null }, - { - type: FETCH_RESOURCES_SUCCESS, - response: { paths, ok: true } - } - ] - - const store = mockStore(mockState); - fetchMock.mockResponseOnce(JSON.stringify({ paths, ok: true })); - - // Act and Assert - // @ts-ignore - store.dispatch(fetchResources()) - .then(() => { - expect(store.getActions()).toEqual(expectedAction); - }) - .catch((e: Error) => { throw e }) - }); + it('should dispatch FETCH_RESOURCES_PENDING and FETCH_RESOURCES_SUCCESS when fetchResources() is called', + async () => { + const expectedResults = paths; + const expectedActions = [ + { type: FETCH_RESOURCES_PENDING, payload: undefined }, + { + type: FETCH_RESOURCES_SUCCESS, + payload: { 'v1.0': paths, 'beta': paths } + } + ] + + const store_ = mockStore(mockState); + const mockFetch = jest.fn().mockImplementation(() => { + return Promise.resolve({ + ok: true, + json: () => Promise.resolve(expectedResults) + }) + }); + + window.fetch = mockFetch; + + await store_.dispatch(fetchResources() as unknown as AnyAction); + + expect(store_.getActions().map(action => { + const { meta, ...rest } = action; + return rest; + })).toEqual(expectedActions); + }); }); diff --git a/src/app/services/actions/resource-explorer-action-creators.ts b/src/app/services/actions/resource-explorer-action-creators.ts deleted file mode 100644 index 97c97cf5f..000000000 --- a/src/app/services/actions/resource-explorer-action-creators.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { resourcesCache } from '../../../modules/cache/resources.cache'; -import { AppAction } from '../../../types/action'; -import { IRequestOptions } from '../../../types/request'; -import { IResource } from '../../../types/resources'; -import { ApplicationState } from '../../../types/root'; -import { - FETCH_RESOURCES_ERROR, - FETCH_RESOURCES_PENDING, - FETCH_RESOURCES_SUCCESS -} from '../redux-constants'; - -export function fetchResourcesSuccess(response: object): AppAction { - return { - type: FETCH_RESOURCES_SUCCESS, - response - }; -} - -export function fetchResourcesPending(): AppAction { - return { - type: FETCH_RESOURCES_PENDING, - response: null - }; -} - -export function fetchResourcesError(response: object): AppAction { - return { - type: FETCH_RESOURCES_ERROR, - response - }; -} - -export function fetchResources() { - return async (dispatch: Function, getState: Function) => { - const { devxApi }: ApplicationState = getState(); - const resourcesUrl = `${devxApi.baseUrl}/openapi/tree`; - const v1Url = resourcesUrl + '?graphVersions=v1.0'; - const betaUrl = resourcesUrl + '?graphVersions=beta'; - - const headers = { - 'Content-Type': 'application/json' - }; - - const options: IRequestOptions = { headers }; - - dispatch(fetchResourcesPending()); - - try { - const v1CachedResources = await resourcesCache.readResources('v1.0'); - const betaCachedResources = await resourcesCache.readResources('beta'); - if (v1CachedResources && betaCachedResources) { - return dispatch(fetchResourcesSuccess({ - 'v1.0': v1CachedResources, - 'beta': betaCachedResources - })); - } else { - const [v1Response, betaResponse] = await Promise.all([ - fetch(v1Url, options), - fetch(betaUrl, options) - ]); - - if (v1Response.ok && betaResponse.ok) { - const [v1Data, betaData] = await Promise.all([ - v1Response.json(), betaResponse.json() - ]); - - resourcesCache.saveResources(v1Data as IResource, 'v1.0'); - resourcesCache.saveResources(betaData as IResource, 'beta'); - - return dispatch(fetchResourcesSuccess({ - 'v1.0': v1Data, - 'beta': betaData - })); - } else { - throw new Error('Failed to fetch resources'); - } - } - } catch (error) { - return dispatch(fetchResourcesError({ error })); - } - }; -} \ No newline at end of file diff --git a/src/app/services/actions/response-expanded-action-creator.spec.ts b/src/app/services/actions/response-expanded-action-creator.spec.ts index 91c1398ef..5ecb0ae7a 100644 --- a/src/app/services/actions/response-expanded-action-creator.spec.ts +++ b/src/app/services/actions/response-expanded-action-creator.spec.ts @@ -1,19 +1,20 @@ +import { PayloadAction } from '@reduxjs/toolkit'; + import { RESPONSE_EXPANDED } from '../../../app/services/redux-constants'; -import { expandResponseArea } from '../../../app/services/actions/response-expanded-action-creator'; -import { AppAction } from '../../../types/action'; +import { expandResponseArea } from '../slices/response-area-expanded.slice'; describe('Response Area Expansion', () => { it('should dispatch RESPONSE_EXPANDED when expandResponseArea() is called', () => { //Arrange - const response: boolean = true; + const payload: boolean = true; - const expectedAction: AppAction = { + const expectedAction: PayloadAction = { type: RESPONSE_EXPANDED, - response + payload } // Act - const action = expandResponseArea(response); + const action = expandResponseArea(payload); // Assert expect(action).toEqual(expectedAction); diff --git a/src/app/services/actions/response-expanded-action-creator.ts b/src/app/services/actions/response-expanded-action-creator.ts deleted file mode 100644 index 3d5da150a..000000000 --- a/src/app/services/actions/response-expanded-action-creator.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { AppAction } from '../../../types/action'; -import { RESPONSE_EXPANDED } from '../redux-constants'; - -export function expandResponseArea(expanded: boolean): AppAction { - return { - type: RESPONSE_EXPANDED, - response: expanded - }; -} diff --git a/src/app/services/actions/revoke-scopes.action.ts b/src/app/services/actions/revoke-scopes.action.ts new file mode 100644 index 000000000..3e484e066 --- /dev/null +++ b/src/app/services/actions/revoke-scopes.action.ts @@ -0,0 +1,146 @@ +import { createAsyncThunk } from '@reduxjs/toolkit'; +import { ApplicationState } from '../../../store'; +import { componentNames, eventTypes, telemetry } from '../../../telemetry'; +import { IOAuthGrantPayload } from '../../../types/permissions'; +import { IUser } from '../../../types/profile'; +import { RevokeScopesError } from '../../utils/error-utils/RevokeScopesError'; +import { translateMessage } from '../../utils/translate-messages'; +import { DEFAULT_USER_SCOPES, REVOKING_PERMISSIONS_REQUIRED_SCOPES } from '../graph-constants'; +import { getConsentedScopesSuccess } from '../slices/auth.slice'; +import { setQueryResponseStatus } from '../slices/query-status.slice'; +import { REVOKE_STATUS, RevokePermissionsUtil } from './permissions-action-creator.util'; + +interface IPermissionUpdate { + permissionBeingRevokedIsAllPrincipal: boolean; + userIsTenantAdmin: boolean; + revokePermissionUtil: RevokePermissionsUtil; + grantsPayload: IOAuthGrantPayload; + profile: IUser; + permissionToRevoke: string; + newScopesArray: string[]; + retryCount: number; + retryDelay: number; + dispatch: Function; +} + +export const revokeScopes = createAsyncThunk( + 'auth/revokeScopes', + async (permissionToRevoke: string, { dispatch, getState, rejectWithValue }) => { + const { auth: { consentedScopes }, profile } = getState() as ApplicationState; + const requiredPermissions = REVOKING_PERMISSIONS_REQUIRED_SCOPES.split(' '); + const defaultUserScopes = DEFAULT_USER_SCOPES.split(' '); + + dispatchScopesStatus(dispatch, 'Please wait while we revoke this permission', 'Revoking ', 0); + const revokePermissionUtil = await RevokePermissionsUtil.initialize(profile.user!.id!); + + if (!consentedScopes || consentedScopes.length === 0) { + trackRevokeConsentEvent(REVOKE_STATUS.preliminaryChecksFail, permissionToRevoke); + return rejectWithValue('No consented scopes found'); + } + + const newScopesArray: string[] = consentedScopes.filter((scope: string) => scope !== permissionToRevoke); + + try { + const { userIsTenantAdmin, permissionBeingRevokedIsAllPrincipal, grantsPayload } = await revokePermissionUtil + .getUserPermissionChecks({ consentedScopes, requiredPermissions, defaultUserScopes, permissionToRevoke }); + + const retryCount = 0; + const retryDelay = 100; + const permissionsUpdateObject: IPermissionUpdate = { + permissionBeingRevokedIsAllPrincipal, + userIsTenantAdmin, + revokePermissionUtil, + grantsPayload, + profile: profile.user!, + permissionToRevoke, + newScopesArray, + retryCount, + dispatch, + retryDelay + }; + + const updatedScopes = await updatePermissions(permissionsUpdateObject); + + if (updatedScopes) { + dispatchScopesStatus(dispatch, 'Permission revoked', 'Success', 4); + dispatch(getConsentedScopesSuccess(updatedScopes)); + trackRevokeConsentEvent(REVOKE_STATUS.success, permissionToRevoke); + return updatedScopes; + } else { + throw new RevokeScopesError({ + errorText: 'Scopes not updated', + statusText: 'An error occurred when unconsenting', + status: '500', + messageType: 1 + }); + } + } catch (errorMessage: any) { + if (errorMessage instanceof RevokeScopesError) { + const { errorText, statusText, status, messageType } = errorMessage; + dispatchScopesStatus(dispatch, statusText, status, messageType); + const permissionObject = { + permissionToRevoke, + statusCode: statusText, + status: errorText + }; + trackRevokeConsentEvent(REVOKE_STATUS.failure, permissionObject); + return rejectWithValue(errorMessage); + } else { + const { code, message } = errorMessage; + trackRevokeConsentEvent(REVOKE_STATUS.failure, 'Failed to revoke consent'); + dispatchScopesStatus(dispatch, message ? message : 'Failed to revoke consent', code ? code : 'Failed', 1); + return rejectWithValue(errorMessage); + } + } + } +); + +const dispatchScopesStatus = (dispatch: Function, statusText: string, status: string, messageType: number) => { + dispatch( + setQueryResponseStatus({ + statusText: translateMessage(status), + status: translateMessage(statusText), + ok: false, + messageType + }) + ) +} + +const trackRevokeConsentEvent = (status: string, permissionObject: any) => { + telemetry.trackEvent(eventTypes.BUTTON_CLICK_EVENT, { + componentName: componentNames.REVOKE_PERMISSION_CONSENT_BUTTON, + permissionObject, + status + }); +} + +async function updatePermissions(permissionsUpdateObject: IPermissionUpdate): Promise { + const { + permissionBeingRevokedIsAllPrincipal, userIsTenantAdmin, revokePermissionUtil, grantsPayload, + profile, permissionToRevoke, newScopesArray, retryCount, dispatch, retryDelay } = permissionsUpdateObject; + let isRevokeSuccessful; + const maxRetryCount = 7; + const newScopesString = newScopesArray.join(' '); + + if (permissionBeingRevokedIsAllPrincipal && userIsTenantAdmin) { + isRevokeSuccessful = await revokePermissionUtil.getUpdatedAllPrincipalPermissionGrant(grantsPayload, + permissionToRevoke); + } else { + isRevokeSuccessful = await revokePermissionUtil.updateSinglePrincipalPermissionGrant(grantsPayload, profile, + newScopesString); + } + + if (isRevokeSuccessful) { + return newScopesString.split(' '); + } + else if ((retryCount < maxRetryCount) && !isRevokeSuccessful) { + await new Promise(resolve => setTimeout(resolve, retryDelay * 2)); + dispatchScopesStatus(dispatch, 'We are retrying the revoking operation', 'Retrying', 5); + + permissionsUpdateObject.retryCount += 1; + return updatePermissions(permissionsUpdateObject); + } + else { + return null; + } +} \ No newline at end of file diff --git a/src/app/services/actions/samples-action-creators.spec.ts b/src/app/services/actions/samples-action-creators.spec.ts index f1ba15c13..e6a2ed2d8 100644 --- a/src/app/services/actions/samples-action-creators.spec.ts +++ b/src/app/services/actions/samples-action-creators.spec.ts @@ -1,48 +1,38 @@ +import { AnyAction, PayloadAction } from '@reduxjs/toolkit'; +import configureMockStore from 'redux-mock-store'; +import { ISampleQuery } from '../../../types/query-runner'; import { - fetchSamplesSuccess, fetchSamplesError, - fetchSamplesPending -} from './samples-action-creators'; -import { - SAMPLES_FETCH_SUCCESS, SAMPLES_FETCH_PENDING, SAMPLES_FETCH_ERROR + SAMPLES_FETCH_PENDING, + SAMPLES_FETCH_SUCCESS } from '../redux-constants'; -import { AppAction } from '../../../types/action'; +import { fetchSamples } from '../slices/samples.slice'; +import { mockThunkMiddleware } from './mockThunkMiddleware'; +import { queries } from '../../views/sidebar/sample-queries/queries'; -describe('Samples action creators', () => { - beforeEach(() => { - fetchMock.resetMocks(); - }); - it('should dispatch SAMPLES_FETCH_SUCCESS when fetchSamplesSuccess() is called', () => { +const mockStore = configureMockStore([mockThunkMiddleware]); - const response = fetchMock.mockResponseOnce(JSON.stringify({ ok: true })); - const expectedAction: AppAction = { - type: SAMPLES_FETCH_SUCCESS, - response - }; - - const action = fetchSamplesSuccess(response); - expect(action).toEqual(expectedAction); - }); - - it('should dispatch SAMPLES_FETCH_PENDING when fetchSamplesPending() is called', () => { - const expectedAction: AppAction = { - type: SAMPLES_FETCH_PENDING, - response: null - }; - - const action = fetchSamplesPending(); - expect(action).toEqual(expectedAction); - }) +describe('Samples action creators', () => { - it('should dispatch SAMPLES_FETCH_ERROR when fetchSamplesError() is called', () => { - const response = new Error('error'); - const expectedAction: AppAction = { - type: SAMPLES_FETCH_ERROR, - response - }; + it('should dispatch SAMPLES_FETCH_PENDING when fetchSamples() is called', () => { + // Arrange + const expectedActions = [ + { + type: SAMPLES_FETCH_PENDING, + payload: undefined + } + ]; + const store_ = mockStore({}); + fetchMock.mockResponseOnce(JSON.stringify({ queries })); + + // Act + store_.dispatch(fetchSamples() as unknown as AnyAction); + + // Assert + expect(store_.getActions().map(action => { + const { meta, ...rest } = action; + return rest; + })).toEqual(expectedActions); - const action = fetchSamplesError(response); - expect(action).toEqual(expectedAction); }); - }); diff --git a/src/app/services/actions/samples-action-creators.ts b/src/app/services/actions/samples-action-creators.ts deleted file mode 100644 index 1a2d0e68d..000000000 --- a/src/app/services/actions/samples-action-creators.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { samplesCache } from '../../../modules/cache/samples.cache'; -import { AppDispatch } from '../../../store'; -import { AppAction } from '../../../types/action'; -import { IRequestOptions } from '../../../types/request'; -import { queries } from '../../views/sidebar/sample-queries/queries'; -import { - SAMPLES_FETCH_ERROR, - SAMPLES_FETCH_PENDING, - SAMPLES_FETCH_SUCCESS -} from '../redux-constants'; - -export function fetchSamplesSuccess(response: object): AppAction { - return { - type: SAMPLES_FETCH_SUCCESS, - response - }; -} - -export function fetchSamplesError(response: object): AppAction { - return { - type: SAMPLES_FETCH_ERROR, - response - }; -} - -export function fetchSamplesPending(): AppAction { - return { - type: SAMPLES_FETCH_PENDING, - response: null - }; -} - -export function fetchSamples() { - return async (dispatch: AppDispatch, getState: Function) => { - const { devxApi } = getState(); - let samplesUrl = `${devxApi.baseUrl}/samples`; - - samplesUrl = devxApi.parameters - ? `${samplesUrl}?${devxApi.parameters}` - : `${samplesUrl}`; - - const headers = { - 'Content-Type': 'application/json' - }; - - const options: IRequestOptions = { headers }; - - dispatch(fetchSamplesPending()); - - try { - const response = await fetch(samplesUrl, options); - if (!response.ok) { - throw response; - } - const res = await response.json(); - return dispatch(fetchSamplesSuccess(res.sampleQueries)); - } catch (error) { - let cachedSamples = await samplesCache.readSamples(); - if (cachedSamples.length === 0) { - cachedSamples = queries; - } - return dispatch(fetchSamplesError(cachedSamples)); - } - }; -} diff --git a/src/app/services/actions/snippet-action-creator.spec.ts b/src/app/services/actions/snippet-action-creator.spec.ts index ccaf0511c..7a09444ad 100644 --- a/src/app/services/actions/snippet-action-creator.spec.ts +++ b/src/app/services/actions/snippet-action-creator.spec.ts @@ -1,79 +1,30 @@ import configureMockStore from 'redux-mock-store'; -import thunk from 'redux-thunk'; +import { AnyAction } from '@reduxjs/toolkit'; -import { - getSnippetSuccess, getSnippetError, - getSnippetPending, - getSnippet, - constructHeaderString -} from './snippet-action-creator'; -import { GET_SNIPPET_SUCCESS, GET_SNIPPET_ERROR, GET_SNIPPET_PENDING } from '../redux-constants'; import { Header, IQuery } from '../../../types/query-runner'; -import { AppAction } from '../../../types/action'; +import { constructHeaderString } from '../../utils/snippet.utils'; +import { GET_SNIPPET_PENDING, GET_SNIPPET_SUCCESS } from '../redux-constants'; +import { getSnippet } from '../slices/snippet.slice'; +import { mockThunkMiddleware } from './mockThunkMiddleware'; -const middlewares = [thunk]; -const mockStore = configureMockStore(middlewares); +const mockStore = configureMockStore([mockThunkMiddleware]); describe('Snippet actions creators', () => { - it('should dispatch GET_SNIPPET_SUCCESS when getSnippetSuccess() is called', () => { - const snippet = 'GraphServiceClient graphClient = new GraphServiceClient( authProvider );'; - - const expectedAction: AppAction[] = [{ - type: GET_SNIPPET_SUCCESS, - response: { - csharp: snippet - } - }]; - - const store = mockStore({ snippets: {} }); - - // @ts-ignore - store.dispatch(getSnippetSuccess({ - csharp: snippet - })); - - expect(store.getActions()).toEqual(expectedAction); - }); - - it('should dispatch GET_SNIPPET_PENDING when getSnippetPending() is called', () => { - const expectedAction: AppAction = { - type: GET_SNIPPET_PENDING, - response: null - }; - - const action = getSnippetPending(); - - expect(action).toEqual(expectedAction); - }) - - it('should dispatch GET_SNIPPET_ERROR when getSnippetError() is called', () => { - const response = {}; - const expectedAction: AppAction = { - type: GET_SNIPPET_ERROR, - response - }; - - const action = getSnippetError(response); - - expect(action).toEqual(expectedAction); - }) - - it('should dispatch GET_SNIPPET_ERROR when getSnippet() api call errors out', () => { + it('should dispatch GET_SNIPPET_ERROR when getSnippet() api call errors out', async () => { // Arrange const expectedActions = [ { - type: 'GET_SNIPPET_PENDING', - response: null + type: GET_SNIPPET_PENDING }, { type: GET_SNIPPET_SUCCESS, - response: { + payload: { CSharp: '{"ok":true}' } } ] - const store = mockStore({ + const store_ = mockStore({ devxApi: { baseUrl: 'https://graphexplorerapi.azurewebsites.net', parameters: '' @@ -88,14 +39,14 @@ describe('Snippet actions creators', () => { }); fetchMock.mockResponseOnce(JSON.stringify({ ok: true })); - // Act and Assert - // @ts-ignore - store.dispatch(getSnippet('CSharp')) - .then(() => { - expect(store.getActions()).toEqual(expectedActions); - }) - .catch((e: Error) => { throw e }); + // Act + await store_.dispatch(getSnippet('CSharp') as unknown as AnyAction); + // Assert + expect(store_.getActions().map(action => { + const { meta, error, ...rest } = action; + return rest; + })).toEqual(expectedActions); }); it('should construct headers string to be sent with the request for obtaining code snippets', () => { diff --git a/src/app/services/actions/snippet-action-creator.ts b/src/app/services/actions/snippet-action-creator.ts deleted file mode 100644 index f2968fcba..000000000 --- a/src/app/services/actions/snippet-action-creator.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { AppAction } from '../../../types/action'; -import { Header, IQuery } from '../../../types/query-runner'; -import { IRequestOptions } from '../../../types/request'; -import { parseSampleUrl } from '../../utils/sample-url-generation'; -import { - GET_SNIPPET_ERROR, - GET_SNIPPET_PENDING, - GET_SNIPPET_SUCCESS, - SET_SNIPPET_TAB_SUCCESS -} from '../redux-constants'; - -export function getSnippetSuccess(response: string): AppAction { - return { - type: GET_SNIPPET_SUCCESS, - response - }; -} - -export function getSnippetError(response: object): AppAction { - return { - type: GET_SNIPPET_ERROR, - response - }; -} - -export function getSnippetPending(): AppAction { - return { - type: GET_SNIPPET_PENDING, - response: null - }; -} - -export function setSnippetTabSuccess(response: string): AppAction { - return { - type: SET_SNIPPET_TAB_SUCCESS, - response - } -} - -export function getSnippet(language: string) { - return async (dispatch: Function, getState: Function) => { - const { devxApi, sampleQuery } = getState(); - - try { - let snippetsUrl = `${devxApi.baseUrl}/api/graphexplorersnippets`; - - const { requestUrl, sampleUrl, queryVersion, search } = parseSampleUrl( - sampleQuery.sampleUrl - ); - if (!sampleUrl) { - throw new Error('url is invalid'); - } - if (language !== 'csharp') { - snippetsUrl += `?lang=${language}`; - } - const openApiSnippets: string[] = ['go', 'powershell', 'python', 'cli', 'php']; - if (openApiSnippets.includes(language)) { - snippetsUrl += '&generation=openapi'; - } - - dispatch(getSnippetPending()); - - const method = 'POST'; - const headers = { - 'Content-Type': 'application/http' - }; - - const requestBody = - sampleQuery.sampleBody && - Object.keys(sampleQuery.sampleBody).length !== 0 - ? JSON.stringify(sampleQuery.sampleBody) - : ''; - - const httpVersion = 'HTTP/1.1'; - const host = 'Host: graph.microsoft.com'; - const sampleHeaders = constructHeaderString(sampleQuery); - - // eslint-disable-next-line max-len - let body = `${sampleQuery.selectedVerb} /${queryVersion}/${requestUrl + search} ${httpVersion}\r\n${host}\r\n${sampleHeaders}\r\n\r\n`; - if (sampleQuery.selectedVerb !== 'GET') { - body += `${requestBody}`; - } - - const options: IRequestOptions = { method, headers, body }; - const obj: any = {}; - - const response = await fetch(snippetsUrl, options); - if (response.ok) { - const result = await response.text(); - obj[language] = result; - return dispatch(getSnippetSuccess(obj)); - } - throw response; - } catch (error) { - return dispatch(getSnippetError({ error, language })); - } - }; -} - -export function constructHeaderString(sampleQuery: IQuery): string { - const { sampleHeaders, selectedVerb } = sampleQuery; - let headersString = ''; - - const isContentTypeInHeaders: boolean = !!(sampleHeaders.find(header => - header.name.toLocaleLowerCase() === 'content-type')); - - if (sampleHeaders && sampleHeaders.length > 0) { - headersString = getHeaderStringProperties(sampleHeaders); - } - - headersString += !isContentTypeInHeaders && selectedVerb !== 'GET' ? 'Content-Type: application/json\r\n' : ''; - return headersString; -} - -function getHeaderStringProperties(sampleHeaders: Header[]): string { - let constructedHeader = '' - sampleHeaders.forEach((header: Header) => { - constructedHeader += `${header.name}: ${header.value}\r\n`; - }); - return constructedHeader; -} \ No newline at end of file diff --git a/src/app/services/actions/terms-of-use-action-creator.spec.ts b/src/app/services/actions/terms-of-use-action-creator.spec.ts index b456530ec..766468d9b 100644 --- a/src/app/services/actions/terms-of-use-action-creator.spec.ts +++ b/src/app/services/actions/terms-of-use-action-creator.spec.ts @@ -1,28 +1,24 @@ import configureMockStore from 'redux-mock-store'; -import thunk from 'redux-thunk'; -import { clearTermsOfUse } from '../../../app/services/actions/terms-of-use-action-creator'; +import { PayloadAction } from '@reduxjs/toolkit'; import { CLEAR_TERMS_OF_USE } from '../../../app/services/redux-constants'; -import { AppAction } from '../../../types/action'; +import { clearTermsOfUse } from '../slices/terms-of-use.slice'; +import { mockThunkMiddleware } from './mockThunkMiddleware'; -const middlewares = [thunk]; -const mockStore = configureMockStore(middlewares); +const mockStore = configureMockStore([mockThunkMiddleware]); describe('Terms of Use Action Creators', () => { it('should set terms of use flag to false', () => { - const expectedAction: AppAction[] = [ + const expectedAction: PayloadAction[] = [ { type: CLEAR_TERMS_OF_USE, - response: null + payload: undefined } ]; const store = mockStore({ termsOfUse: {} }); - // @ts-ignore - store.dispatch(clearTermsOfUse({ - termsOfUse: false - })); + store.dispatch(clearTermsOfUse()); expect(store.getActions()).toEqual(expectedAction); }); }); \ No newline at end of file diff --git a/src/app/services/actions/terms-of-use-action-creator.ts b/src/app/services/actions/terms-of-use-action-creator.ts deleted file mode 100644 index 757d33905..000000000 --- a/src/app/services/actions/terms-of-use-action-creator.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Dispatch } from 'redux'; -import { CLEAR_TERMS_OF_USE } from '../redux-constants'; - -export function clearTermsOfUse() { - return (dispatch: Dispatch) => { - dispatch({ - type: CLEAR_TERMS_OF_USE, - response: null - }); - }; -} \ No newline at end of file diff --git a/src/app/services/actions/theme-action-creator.spec.ts b/src/app/services/actions/theme-action-creator.spec.ts index b527ee078..8abcb5a20 100644 --- a/src/app/services/actions/theme-action-creator.spec.ts +++ b/src/app/services/actions/theme-action-creator.spec.ts @@ -1,33 +1,15 @@ -import { changeThemeSuccess, changeTheme } from '../../../app/services/actions/theme-action-creator'; -import { CHANGE_THEME_SUCCESS } from '../../../app/services/redux-constants'; import configureMockStore from 'redux-mock-store'; -import thunk from 'redux-thunk'; +import { changeTheme } from '../slices/theme.slice'; -const middlewares = [thunk]; -const mockStore = configureMockStore(middlewares); +const mockStore = configureMockStore(); describe('Change theme action creator', () => { it('Changes theme to dark', () => { const expectedActions = [ { - type: CHANGE_THEME_SUCCESS, - response: 'dark' - } - ]; - - const store = mockStore({ theme: '' }); - - // @ts-ignore - store.dispatch(changeThemeSuccess('dark')); - expect(store.getActions()).toEqual(expectedActions); - }) - - it('dispatches an action that changes the theme ', () => { - const expectedActions = [ - { - type: CHANGE_THEME_SUCCESS, - response: 'dark' + type: 'theme/changeTheme', + payload: 'dark' } ]; diff --git a/src/app/services/actions/theme-action-creator.ts b/src/app/services/actions/theme-action-creator.ts deleted file mode 100644 index 243e277bb..000000000 --- a/src/app/services/actions/theme-action-creator.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Dispatch } from 'redux'; -import { AppThunk } from '../../../store'; -import { AppAction } from '../../../types/action'; -import { CHANGE_THEME_SUCCESS } from '../redux-constants'; - -export function changeThemeSuccess(response: string): AppAction { - return { - type: CHANGE_THEME_SUCCESS, - response - }; -} - -export function changeTheme(theme: string): AppThunk { - return (dispatch: Dispatch): any => { - return dispatch(changeThemeSuccess(theme)); - }; -} diff --git a/src/app/services/actions/toggle-sidebar-action-creator.spec.ts b/src/app/services/actions/toggle-sidebar-action-creator.spec.ts index 3ce434a1c..13364593c 100644 --- a/src/app/services/actions/toggle-sidebar-action-creator.spec.ts +++ b/src/app/services/actions/toggle-sidebar-action-creator.spec.ts @@ -1,18 +1,16 @@ import configureMockStore from 'redux-mock-store'; -import thunk from 'redux-thunk'; -import { toggleSidebar } from './toggle-sidebar-action-creator'; import { TOGGLE_SIDEBAR_SUCCESS } from '../redux-constants'; +import { toggleSidebar } from '../slices/sidebar-properties.slice'; -const middlewares = [thunk]; -const mockStore = configureMockStore(middlewares); +const mockStore = configureMockStore(); describe('Toggle Sidebar Action Creators', () => { it('should dispatch TOGGLE_SIDEBAR_SUCCESS and set sidebar toggle to visible when toggleSidebar() is called', () => { const expectedActions = [ { type: TOGGLE_SIDEBAR_SUCCESS, - response: { + payload: { mobileScreen: true, showSidebar: false } diff --git a/src/app/services/actions/toggle-sidebar-action-creator.ts b/src/app/services/actions/toggle-sidebar-action-creator.ts deleted file mode 100644 index ab7e1241c..000000000 --- a/src/app/services/actions/toggle-sidebar-action-creator.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { AppAction } from '../../../types/action'; -import { TOGGLE_SIDEBAR_SUCCESS } from '../redux-constants'; - -export function toggleSidebar(response: object): AppAction { - return { - type: TOGGLE_SIDEBAR_SUCCESS, - response - }; -} diff --git a/src/app/services/reducers/adaptive-cards-reducer.spec.ts b/src/app/services/reducers/adaptive-cards-reducer.spec.ts deleted file mode 100644 index 535795c9e..000000000 --- a/src/app/services/reducers/adaptive-cards-reducer.spec.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { adaptiveCard } from '../../../app/services/reducers/adaptive-cards-reducer'; -import { IAdaptiveCardResponse } from '../../../types/adaptivecard'; - -describe('Graph Explorer Adaptive Cards Reducer', () => { - it('should return initial state', () => { - const initialState: IAdaptiveCardResponse = { - pending: false, - error: undefined, - data: undefined - }; - - const dummyAction = { type: 'Dummy', response: { dummy: 'Dummy' } }; - const newState = adaptiveCard(initialState, dummyAction); - - // expect the initial state if we have an undefined action - expect(newState).toEqual(initialState); - - }); - - it('should handle FETCH_ADAPTIVE_CARD_ERROR', () => { - const initialState: IAdaptiveCardResponse = { - pending: false, - error: undefined, - data: undefined - }; - - const errorAction = { type: 'FETCH_ADAPTIVE_CARD_ERROR', response: {} }; - const newState = adaptiveCard(initialState, errorAction); - - const expectedState = { - pending: false, - data: null, - error: {} - }; - - // expect null data as there is an error - expect(expectedState).toEqual(newState); - - }); - - it('should handle FETCH_ADAPTIVE_CARD_PENDING', () => { - const initialState: IAdaptiveCardResponse = { - pending: false, - error: undefined, - data: undefined - }; - - const errorAction = { type: 'FETCH_ADAPTIVE_CARD_PENDING', response: {} }; - const newState = adaptiveCard(initialState, errorAction); - - const expectedState = { - pending: true, - data: null - }; - - // expect that pending set to true - expect(expectedState).toEqual(newState); - - }); - - it('should handle FETCH_ADAPTIVE_CARD_SUCCESS', () => { - const initialState: IAdaptiveCardResponse = { - pending: false, - error: undefined, - data: undefined - }; - - const errorAction = { type: 'FETCH_ADAPTIVE_CARD_SUCCESS', response: 'Sample adaptive card data' }; - const newState = adaptiveCard(initialState, errorAction); - - const expectedState = { - pending: false, - data: 'Sample adaptive card data' - }; - - // expect that pending is false with data provided - expect(expectedState).toEqual(newState); - - }); - -}); \ No newline at end of file diff --git a/src/app/services/reducers/adaptive-cards-reducer.ts b/src/app/services/reducers/adaptive-cards-reducer.ts deleted file mode 100644 index 5a7718a2a..000000000 --- a/src/app/services/reducers/adaptive-cards-reducer.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { AppAction } from '../../../types/action'; -import { IAdaptiveCardResponse } from '../../../types/adaptivecard'; -import { - FETCH_ADAPTIVE_CARD_ERROR, - FETCH_ADAPTIVE_CARD_PENDING, - FETCH_ADAPTIVE_CARD_SUCCESS -} from '../redux-constants'; - -const initialState: IAdaptiveCardResponse = { - pending: false, - data: undefined, - error: '' -}; - -export function adaptiveCard(state = initialState, action: AppAction): any { - switch (action.type) { - case FETCH_ADAPTIVE_CARD_SUCCESS: - return { - pending: false, - data: action.response - }; - case FETCH_ADAPTIVE_CARD_PENDING: - return { - pending: true, - data: null - }; - case FETCH_ADAPTIVE_CARD_ERROR: - return { - pending: false, - data: null, - error: action.response - }; - default: - return state; - } -} \ No newline at end of file diff --git a/src/app/services/reducers/auth-reducers.spec.ts b/src/app/services/reducers/auth-reducers.spec.ts deleted file mode 100644 index 353be18cc..000000000 --- a/src/app/services/reducers/auth-reducers.spec.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { authToken, consentedScopes } from './auth-reducers'; -import { GET_AUTH_TOKEN_SUCCESS, GET_CONSENTED_SCOPES_SUCCESS } from '../redux-constants'; - -describe('Auth Reducer', () => { - it('should return initial state', () => { - const initialState = { token: false, pending: false }; - const dummyAction = { type: 'Dummy', response: { displayName: 'Megan Bowen' } }; - const newState = authToken(initialState, dummyAction); - - expect(newState).toEqual(initialState); - }); - - it('should handle GET_AUTH_TOKEN_SUCCESS', () => { - const initialState = { token: false, pending: false }; - - const queryAction = { type: GET_AUTH_TOKEN_SUCCESS, response: true }; - const newState = authToken(initialState, queryAction); - - expect(newState).toEqual({ token: true, pending: false }); - }); - - it('should handle LOGOUT_SUCCESS', () => { - const initialState = { token: true, pending: false }; - - const queryAction = { type: 'LOGOUT_SUCCESS', response: false }; - const newState = authToken(initialState, queryAction); - - expect(newState).toEqual({ token: false, pending: false }); - }); - - it('should handle AUTHENTICATION_PENDING', () => { - const initialState = { token: false, pending: false }; - - const queryAction = { type: 'AUTHENTICATION_PENDING', response: true }; - const newState = authToken(initialState, queryAction); - - expect(newState).toEqual({ token: true, pending: true }); - }); - - it('should handle GET_CONSENTED_SCOPES_SUCCESS', () => { - const initialState = ['profile.read', 'profile.write', 'email.read']; - const action_ = { - type: GET_CONSENTED_SCOPES_SUCCESS, - response: ['profile.read', 'profile.write', 'email.read', 'email.write'] - } - const newState = consentedScopes(initialState, action_); - expect(newState).toEqual(['profile.read', 'profile.write', 'email.read', 'email.write']); - }) -}); diff --git a/src/app/services/reducers/auth-reducers.ts b/src/app/services/reducers/auth-reducers.ts deleted file mode 100644 index 916526896..000000000 --- a/src/app/services/reducers/auth-reducers.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { AppAction } from '../../../types/action'; -import { IAuthenticateResult } from '../../../types/authentication'; -import { - AUTHENTICATION_PENDING, GET_AUTH_TOKEN_SUCCESS, - GET_CONSENTED_SCOPES_SUCCESS, LOGOUT_SUCCESS -} from '../redux-constants'; - - -const initialState: IAuthenticateResult = { - pending: false, - token: false -} - -export function authToken(state = initialState, action: AppAction): IAuthenticateResult { - switch (action.type) { - case GET_AUTH_TOKEN_SUCCESS: - return { - token: true, - pending: false - }; - case LOGOUT_SUCCESS: - return { - token: false, - pending: false - }; - case AUTHENTICATION_PENDING: - return { - token: true, - pending: true - }; - default: - return state; - } -} - -export function consentedScopes(state: string[] = [], action: AppAction): any { - switch (action.type) { - case GET_CONSENTED_SCOPES_SUCCESS: - return action.response; - case LOGOUT_SUCCESS: - return []; - default: - return state; - } -} diff --git a/src/app/services/reducers/autocomplete-reducer.spec.ts b/src/app/services/reducers/autocomplete-reducer.spec.ts deleted file mode 100644 index 62746b0ca..000000000 --- a/src/app/services/reducers/autocomplete-reducer.spec.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { autoComplete } from '../../../app/services/reducers/autocomplete-reducer'; -import { - AUTOCOMPLETE_FETCH_ERROR, AUTOCOMPLETE_FETCH_PENDING, - AUTOCOMPLETE_FETCH_SUCCESS -} from '../../../app/services/redux-constants'; - -const initialState = { - pending: false, - data: null, - error: null -} - -describe('Autocomplete reducer', () => { - it('should handle AUTOCOMPLETE_FETCH_PENDING', () => { - const action = { - type: AUTOCOMPLETE_FETCH_PENDING, - response: null - } - const expectedState = { - pending: true, - data: null, - error: null - } - expect(autoComplete(initialState, action)).toEqual(expectedState) - }); - - it('should handle AUTOCOMPLETE_FETCH_SUCCESS', () => { - const action = { - type: AUTOCOMPLETE_FETCH_SUCCESS, - response: { - data: 'test' - } - } - const expectedState = { - pending: false, - data: { - data: 'test' - }, - error: null - }; - expect(autoComplete(initialState, action)).toEqual(expectedState) - - }); - - it('should handle AUTOCOMPLETE_FETCH_ERROR', () => { - const action = { - type: AUTOCOMPLETE_FETCH_ERROR, - response: 'test' - } - const expectedState = { - pending: false, - data: null, - error: 'test' - } - expect(autoComplete(initialState, action)).toEqual(expectedState) - }); - - it('should return unaltered state', () => { - const action = { - type: '', - response: null - } - const expectedState = { - pending: false, - data: null, - error: null - } - expect(autoComplete(initialState, action)).toEqual(expectedState) - }) -}) \ No newline at end of file diff --git a/src/app/services/reducers/autocomplete-reducer.ts b/src/app/services/reducers/autocomplete-reducer.ts deleted file mode 100644 index 33e102102..000000000 --- a/src/app/services/reducers/autocomplete-reducer.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { AppAction } from '../../../types/action'; -import { IAutocompleteResponse } from '../../../types/auto-complete'; -import { AUTOCOMPLETE_FETCH_ERROR, AUTOCOMPLETE_FETCH_PENDING, AUTOCOMPLETE_FETCH_SUCCESS } from '../redux-constants'; - -const initialState: IAutocompleteResponse = { - pending: false, - data: null, - error: null -}; - -export function autoComplete(state = initialState, action: AppAction): IAutocompleteResponse { - switch (action.type) { - case AUTOCOMPLETE_FETCH_PENDING: - return { - error: null, - data: null, - pending: true - }; - case AUTOCOMPLETE_FETCH_SUCCESS: - return { - pending: false, - data: action.response, - error: null - }; - case AUTOCOMPLETE_FETCH_ERROR: - return { - pending: false, - data: null, - error: action.response - }; - default: - return state; - } -} diff --git a/src/app/services/reducers/collections-reducer.spec.ts b/src/app/services/reducers/collections-reducer.spec.ts deleted file mode 100644 index b845bfbd2..000000000 --- a/src/app/services/reducers/collections-reducer.spec.ts +++ /dev/null @@ -1,123 +0,0 @@ -import configureMockStore from 'redux-mock-store'; -import thunk from 'redux-thunk'; - -import { Collection, IResourceLink, ResourceLinkType, ResourcePath } from '../../../types/resources'; -import { addResourcePaths, removeResourcePaths } from '../actions/collections-action-creators'; -import { RESOURCEPATHS_ADD_SUCCESS, RESOURCEPATHS_DELETE_SUCCESS } from '../redux-constants'; -import { collections } from './collections-reducer'; - -const middlewares = [thunk]; -const mockStore = configureMockStore(middlewares); - -const initialState: Collection[] = [{ - id: '1', - name: 'Test Collection', - paths: [], - isDefault: true -}]; - -const paths: ResourcePath[] = [{ - key: '5-issues', - url: '/issues', - name: 'issues (1)', - version: 'v1.0', - method: 'GET', - paths: ['/'], - type: ResourceLinkType.PATH -}]; - -const resourceLinks: IResourceLink[] = [ - { - labels: [ - { - name: 'v1.0', methods: [{ - name: 'GET', - documentationUrl: null - }, { - name: 'POST', - documentationUrl: null - }] - } - ], - key: '5-issues', - url: '/issues', - name: 'issues (1)', - icon: 'LightningBolt', - isExpanded: true, - level: 7, - parent: '/', - paths: ['/'], - type: ResourceLinkType.PATH, - links: [] - } -]; - -describe('Collections Reducer', () => { - it('should return initial state', () => { - const dummyAction = { type: 'Dummy', response: { dummy: 'Dummy' } }; - const newState = collections(initialState, dummyAction); - expect(newState).toEqual(initialState); - }); - - it('should handle RESOURCEPATHS_ADD_SUCCESS', () => { - const expectedActions = [{ response: paths, type: RESOURCEPATHS_ADD_SUCCESS }]; - const store = mockStore({ resources: {} }); - store.dispatch(addResourcePaths(paths)); - expect(store.getActions()).toEqual(expectedActions); - }); - - it('should handle RESOURCEPATHS_DELETE_SUCCESS', () => { - const expectedActions = [{ response: paths, type: RESOURCEPATHS_DELETE_SUCCESS }]; - const store = mockStore({ - resources: { - paths - } - }); - store.dispatch(removeResourcePaths(paths)); - expect(store.getActions()).toEqual(expectedActions); - }); - - it('should handle RESOURCEPATHS_ADD_SUCCESS and return new state with the paths', () => { - const newState = [...initialState]; - newState[0].paths = []; - const action_ = { - type: RESOURCEPATHS_ADD_SUCCESS, - response: paths - } - const state_ = collections(newState, action_); - expect(state_[0].paths.length).toEqual(resourceLinks.length); - }); - - it('should handle RESOURCEPATHS_DELETE_SUCCESS and return new state with no resource paths', () => { - const newState = [...initialState]; - newState[0].paths = resourceLinks; - const action_ = { - type: RESOURCEPATHS_DELETE_SUCCESS, - response: paths - } - const state_ = collections(newState, action_); - expect(state_[0].paths).toEqual([]); - }); - - it('should return unchanged state if no relevant action is passed', () => { - const newState = [...initialState]; - const action_ = { - type: 'Dummy', - response: { dummy: 'Dummy' } - } - const state_ = collections(newState, action_); - expect(state_).toEqual(newState); - }); - - it('should handle RESOURCEPATHS_ADD_SUCCESS and return unique paths', () => { - const newState = [...initialState]; - newState[0].paths = paths; - const action_ = { - type: RESOURCEPATHS_ADD_SUCCESS, - response: [paths[0]] - } - const state_ = collections(newState, action_); - expect(state_[0].paths.length).toEqual(paths.length); - }); - -}); diff --git a/src/app/services/reducers/collections-reducer.ts b/src/app/services/reducers/collections-reducer.ts deleted file mode 100644 index ef13177bf..000000000 --- a/src/app/services/reducers/collections-reducer.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { AppAction } from '../../../types/action'; -import { Collection, ResourcePath } from '../../../types/resources'; -import { - COLLECTION_CREATE_SUCCESS, - RESOURCEPATHS_ADD_SUCCESS, RESOURCEPATHS_DELETE_SUCCESS -} from '../redux-constants'; -import { getUniquePaths } from './collections-reducer.util'; - -const initialState: Collection[] = []; - -export function collections(state: Collection[] = initialState, action: AppAction): Collection[] { - switch (action.type) { - - case COLLECTION_CREATE_SUCCESS: - const items = [...state]; - items.push(action.response); - return items; - - case RESOURCEPATHS_ADD_SUCCESS: - const index = state.findIndex(k => k.isDefault); - if (index > -1) { - const paths: ResourcePath[] = getUniquePaths(state[index].paths, action.response); - const context = [...state]; - context[index].paths = paths; - return context; - } - return state - - case RESOURCEPATHS_DELETE_SUCCESS: - const indexOfDefaultCollection = state.findIndex(k => k.isDefault); - if (indexOfDefaultCollection > -1) { - const list: ResourcePath[] = [...state[indexOfDefaultCollection].paths]; - action.response.forEach((path: ResourcePath) => { - const pathIndex = list.findIndex(k => k.key === path.key); - list.splice(pathIndex, 1); - }); - const newState = [...state]; - newState[indexOfDefaultCollection].paths = list; - return newState; - } - return state - - default: - return state; - } -} diff --git a/src/app/services/reducers/collections-reducer.util.ts b/src/app/services/reducers/collections-reducer.util.ts index 2d18db546..a3c0f8e61 100644 --- a/src/app/services/reducers/collections-reducer.util.ts +++ b/src/app/services/reducers/collections-reducer.util.ts @@ -2,7 +2,7 @@ import { ResourcePath } from '../../../types/resources'; const getUniquePaths = (paths: ResourcePath[], items: ResourcePath[]): ResourcePath[] => { const content: ResourcePath[] = [...paths]; - items.forEach((element: any) => { + items.forEach((element: ResourcePath) => { const exists = !!content.find(k => k.key === element.key); if (!exists) { content.push(element); diff --git a/src/app/services/reducers/devxApi-reducers.ts b/src/app/services/reducers/devxApi-reducers.ts deleted file mode 100644 index f58be799c..000000000 --- a/src/app/services/reducers/devxApi-reducers.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { AppAction } from '../../../types/action'; -import { IDevxAPI } from '../../../types/devx-api'; -import { SET_DEVX_API_URL_SUCCESS } from '../redux-constants'; - -const initialState: IDevxAPI = { - baseUrl: process.env.REACT_APP_DEVX_API_URL || '', - parameters: '' -}; -export function devxApi(state: IDevxAPI = initialState, action: AppAction): any { - switch (action.type) { - - case SET_DEVX_API_URL_SUCCESS: - return action.response; - - default: - return state; - } -} diff --git a/src/app/services/reducers/dimensions-reducers.ts b/src/app/services/reducers/dimensions-reducers.ts deleted file mode 100644 index 7345999ae..000000000 --- a/src/app/services/reducers/dimensions-reducers.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { AppAction } from '../../../types/action'; -import { IDimensions } from '../../../types/dimensions'; -import { RESIZE_SUCCESS } from '../redux-constants'; - -const initialState: IDimensions = { - request: { - width: '100%', - height: '38vh' - }, - response: { - width: '100%', - height: '50vh' - }, - sidebar: { - width: '28%', - height: '' - }, - content: { - width: '72%', - height: '100%' - } -}; - -export function dimensions(state = initialState, action: AppAction): any { - switch (action.type) { - case RESIZE_SUCCESS: - return action.response; - default: - return state; - } -} \ No newline at end of file diff --git a/src/app/services/reducers/graph-explorer-mode-reducer.ts b/src/app/services/reducers/graph-explorer-mode-reducer.ts deleted file mode 100644 index f9c892a9e..000000000 --- a/src/app/services/reducers/graph-explorer-mode-reducer.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { AppAction } from '../../../types/action'; -import { Mode } from '../../../types/enums'; -import { SET_GRAPH_EXPLORER_MODE_SUCCESS } from '../redux-constants'; - -export function graphExplorerMode(state = Mode.Complete, action: AppAction): any { - switch (action.type) { - case SET_GRAPH_EXPLORER_MODE_SUCCESS: - return action.response; - default: - return state; - } -} diff --git a/src/app/services/reducers/graph-explorer-mode.spec.ts b/src/app/services/reducers/graph-explorer-mode.spec.ts deleted file mode 100644 index 7fcf29982..000000000 --- a/src/app/services/reducers/graph-explorer-mode.spec.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { graphExplorerMode } from '../../../app/services/reducers/graph-explorer-mode-reducer'; -import { SET_GRAPH_EXPLORER_MODE_SUCCESS } from '../../../app/services/redux-constants'; -import { Mode } from '../../../types/enums'; - -describe('Graph Explorer Mode Reducer', () => { - it('should change graph explorer Mode', () => { - const initialState = undefined; - const dummyAction = { type: SET_GRAPH_EXPLORER_MODE_SUCCESS, response: Mode.TryIt }; - const newState = graphExplorerMode(initialState, dummyAction); - - expect(newState).toEqual(Mode.TryIt); - }); -}); diff --git a/src/app/services/reducers/index.ts b/src/app/services/reducers/index.ts index 6c412a761..02beeb2b2 100644 --- a/src/app/services/reducers/index.ts +++ b/src/app/services/reducers/index.ts @@ -1,50 +1,51 @@ -import { combineReducers } from 'redux'; -import { adaptiveCard } from './adaptive-cards-reducer'; -import { authToken, consentedScopes } from './auth-reducers'; -import { autoComplete } from './autocomplete-reducer'; -import { collections } from './collections-reducer'; -import { devxApi } from './devxApi-reducers'; -import { dimensions } from './dimensions-reducers'; -import { graphExplorerMode } from './graph-explorer-mode-reducer'; -import { scopes } from './permissions-reducer'; -import { profile } from './profile-reducer'; -import { proxyUrl } from './proxy-url-reducer'; -import { sampleQuery } from './query-input-reducers'; -import { isLoadingData } from './query-loading-reducers'; -import { graphResponse } from './query-runner-reducers'; -import { queryRunnerStatus } from './query-runner-status-reducers'; -import { history } from './request-history-reducers'; -import { resources } from './resources-reducer'; -import { responseAreaExpanded } from './response-expanded-reducer'; -import { samples } from './samples-reducers'; -import { snippets } from './snippet-reducer'; -import { termsOfUse } from './terms-of-use-reducer'; -import { theme } from './theme-reducer'; -import { sidebarProperties } from './toggle-sidebar-reducer'; +import auth from '../slices/auth.slice'; +import autoComplete from '../slices/autocomplete.slice'; +import collections from '../slices/collections.slice'; +import devxApi from '../slices/devxapi.slice'; +import dimensions from '../slices/dimensions.slice'; +import graphExplorerMode from '../slices/explorer-mode.slice'; +import graphResponse from '../slices/graph-response.slice'; +import history from '../slices/history.slice'; +import permissionGrants from '../slices/permission-grants.slice'; +import profile from '../slices/profile.slice'; +import proxyUrl from '../slices/proxy.slice'; +import queryRunnerStatus from '../slices/query-status.slice'; +import resources from '../slices/resources.slice'; +import responseAreaExpanded from '../slices/response-area-expanded.slice'; +import sampleQuery from '../slices/sample-query.slice'; +import samplesReducer from '../slices/samples.slice'; +import scopes from '../slices/scopes.slice'; +import snippets from '../slices/snippet.slice'; +import themeChange from '../slices/theme.slice'; +import termsOfUse from '../slices/terms-of-use.slice'; +import sidebarProperties from '../slices/sidebar-properties.slice'; -export default combineReducers({ - adaptiveCard, - authToken, +const reducers = { + auth, autoComplete, collections, - consentedScopes, devxApi, dimensions, graphExplorerMode, graphResponse, history, - isLoadingData, + permissionGrants, profile, proxyUrl, queryRunnerStatus, resources, responseAreaExpanded, sampleQuery, - samples, + samples: samplesReducer, scopes, sidebarProperties, snippets, termsOfUse, - theme -}); + theme: themeChange +}; + +export { + reducers +}; + diff --git a/src/app/services/reducers/permissions-reducer.spec.ts b/src/app/services/reducers/permissions-reducer.spec.ts deleted file mode 100644 index 4f8cf90b2..000000000 --- a/src/app/services/reducers/permissions-reducer.spec.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { scopes } from '../../../app/services/reducers/permissions-reducer'; -import { - FETCH_SCOPES_ERROR, FETCH_FULL_SCOPES_PENDING, - FETCH_FULL_SCOPES_SUCCESS, - FETCH_URL_SCOPES_SUCCESS, - FETCH_URL_SCOPES_PENDING -} from '../../../app/services/redux-constants'; - -const initialState = { - pending: { isSpecificPermissions: false, isFullPermissions: false }, - data: { - fullPermissions: [], - specificPermissions: [] - }, - error: null -}; - -describe('Permissions reducer', () => { - it('should handle FETCH_FULL_SCOPES_SUCCESS', () => { - const action = { - type: FETCH_FULL_SCOPES_SUCCESS, - response: { - scopes: { - specificPermissions: [], - fullPermissions: ['profile.read', 'profile.write', 'email.read', 'email.write'] - } - } - } - - const expectedState = { - pending: { isSpecificPermissions: false, isFullPermissions: false }, - data: { - fullPermissions: ['profile.read', 'profile.write', 'email.read', 'email.write'], - specificPermissions: [] - }, - error: null - } - - const newState = scopes(initialState, action); - expect(newState).toEqual(expectedState); - }); - - it('should handle FETCH_URL_SCOPES_SUCCESS', () => { - const action = { - type: FETCH_URL_SCOPES_SUCCESS, - response: { - scopes: { - specificPermissions: ['profile.read', 'profile.write', 'email.read', 'email.write'], - fullPermissions: [] - } - } - } - - const expectedState = { - pending: { isSpecificPermissions: false, isFullPermissions: false }, - data: { - fullPermissions: [], - specificPermissions: ['profile.read', 'profile.write', 'email.read', 'email.write'] - }, - error: null - } - - const newState = scopes(initialState, action); - expect(newState).toEqual(expectedState); - }) - - it('should handle FETCH_SCOPES_ERROR', () => { - const action = { - type: FETCH_SCOPES_ERROR, - response: 'error' - } - const expectedState = { - pending: { isSpecificPermissions: false, isFullPermissions: false }, - data: { - specificPermissions: [], - fullPermissions: [], - tenantWidePermissionsGrant: [] - }, - error: 'error' - } - - const newState = scopes(initialState, action); - expect(newState).toEqual(expectedState); - }); - - it('should handle FETCH_FULL_SCOPES_PENDING', () => { - const action = { - type: FETCH_FULL_SCOPES_PENDING, - response: '' - } - - const expectedState = { - pending: { isSpecificPermissions: false, isFullPermissions: true }, - data: { - fullPermissions: [], - specificPermissions: [] - }, - error: null - } - - const newState = scopes(initialState, action); - expect(newState).toEqual(expectedState); - }); - - it('should handle FETCH_URL_SCOPES_PENDING', () => { - const action = { - type: FETCH_URL_SCOPES_PENDING, - response: '' - } - - const expectedState = { - pending: { isSpecificPermissions: true, isFullPermissions: false }, - data: { - fullPermissions: [], - specificPermissions: [] - }, - error: null - } - - const newState = scopes(initialState, action); - expect(newState).toEqual(expectedState); - }) -}); \ No newline at end of file diff --git a/src/app/services/reducers/permissions-reducer.ts b/src/app/services/reducers/permissions-reducer.ts deleted file mode 100644 index 80c6fc88a..000000000 --- a/src/app/services/reducers/permissions-reducer.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { AppAction } from '../../../types/action'; -import { IPermissionsResponse, IScopes } from '../../../types/permissions'; -import { - FETCH_SCOPES_ERROR, FETCH_URL_SCOPES_PENDING, FETCH_FULL_SCOPES_SUCCESS, - FETCH_URL_SCOPES_SUCCESS, FETCH_FULL_SCOPES_PENDING, GET_ALL_PRINCIPAL_GRANTS_SUCCESS, - REVOKE_SCOPES_PENDING, REVOKE_SCOPES_ERROR, REVOKE_SCOPES_SUCCESS, GET_ALL_PRINCIPAL_GRANTS_PENDING, - GET_ALL_PRINCIPAL_GRANTS_ERROR -} from '../redux-constants'; - -const initialState: IScopes = { - pending: { - isSpecificPermissions: false, - isFullPermissions: false, - isTenantWidePermissionsGrant: false, - isRevokePermissions: false - }, - data: { - specificPermissions: [], - fullPermissions: [], - tenantWidePermissionsGrant: [] - }, - error: null -}; - -export function scopes(state: IScopes = initialState, action: AppAction): any { - let response: IPermissionsResponse; - switch (action.type) { - case FETCH_FULL_SCOPES_SUCCESS: - response = { ...action.response as IPermissionsResponse }; - return { - pending: { ...state.pending, isFullPermissions: false }, - data: { ...state.data, fullPermissions: response.scopes.fullPermissions }, - error: null - }; - case FETCH_URL_SCOPES_SUCCESS: - response = { ...action.response as IPermissionsResponse }; - return { - pending: { ...state.pending, isSpecificPermissions: false }, - data: { ...state.data, specificPermissions: response.scopes.specificPermissions }, - error: null - } - case FETCH_SCOPES_ERROR: - return { - pending: { isFullPermissions: false, isSpecificPermissions: false }, - error: action.response, - data: initialState.data - }; - case FETCH_URL_SCOPES_PENDING: - return { - pending: { ...state.pending, isSpecificPermissions: true }, - data: state.data, - error: null - }; - case FETCH_FULL_SCOPES_PENDING: - return { - pending: { ...state.pending, isFullPermissions: true }, - data: state.data, - error: null - }; - case GET_ALL_PRINCIPAL_GRANTS_PENDING: - return { - pending: { ...state.pending, isTenantWidePermissionsGrant: action.response }, - data: state.data, - error: null - } - case GET_ALL_PRINCIPAL_GRANTS_SUCCESS: - return { - pending: state.pending, - data: { ...state.data, tenantWidePermissionsGrant: action.response }, - error: null - } - case GET_ALL_PRINCIPAL_GRANTS_ERROR: - return { - pending: state.pending, - data: state.data, - error: action.response - } - case REVOKE_SCOPES_PENDING: - return { - pending: { ...state.pending, isRevokePermissions: true }, - data: state.data, - error: null - } - case REVOKE_SCOPES_ERROR: - return { - pending: { ...state.pending, isRevokePermissions: false }, - data: state.data, - error: 'error' - } - case REVOKE_SCOPES_SUCCESS: - return { - pending: { ...state.pending, isRevokePermissions: false }, - data: state.data, - error: null - } - default: - return state; - } -} diff --git a/src/app/services/reducers/profile-reducer.spec.ts b/src/app/services/reducers/profile-reducer.spec.ts deleted file mode 100644 index 5ca2da69c..000000000 --- a/src/app/services/reducers/profile-reducer.spec.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { profile } from '../../../app/services/reducers/profile-reducer'; -import { LOGOUT_SUCCESS, PROFILE_REQUEST_SUCCESS } from '../../../app/services/redux-constants'; - -const initialState = null; - -describe('Profile reducer', () => { - it('should handle LOGOUT_SUCCESS', () => { - const action = { - type: LOGOUT_SUCCESS, - response: null - } - - const expectedState = null; - - const newState = profile(initialState, action); - expect(newState).toEqual(expectedState); - }); - - it('should handle PROFILE_REQUEST_SUCCESS', () => { - const action = { - type: PROFILE_REQUEST_SUCCESS, - response: { - name: 'John Doe', - email: '', - phone: '', - address: '', - city: '' - } - } - - const expectedState = { - name: 'John Doe', - email: '', - phone: '', - address: '', - city: '' - }; - - const newState = profile(initialState, action); - expect(newState).toEqual(expectedState); - }) -}) \ No newline at end of file diff --git a/src/app/services/reducers/profile-reducer.ts b/src/app/services/reducers/profile-reducer.ts deleted file mode 100644 index b9976cd69..000000000 --- a/src/app/services/reducers/profile-reducer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { AppAction } from '../../../types/action'; -import { LOGOUT_SUCCESS, PROFILE_REQUEST_SUCCESS } from '../redux-constants'; - -export function profile(state = null, action: AppAction): any { - switch (action.type) { - case LOGOUT_SUCCESS: - return null; - case PROFILE_REQUEST_SUCCESS: - return action.response; - default: - return state; - } -} diff --git a/src/app/services/reducers/proxy-url-reducer.ts b/src/app/services/reducers/proxy-url-reducer.ts deleted file mode 100644 index 59529dc7b..000000000 --- a/src/app/services/reducers/proxy-url-reducer.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { AppAction } from '../../../types/action'; -import { GRAPH_API_SANDBOX_URL } from '../graph-constants'; -import { SET_GRAPH_PROXY_URL } from '../redux-constants'; - -export function proxyUrl(state = GRAPH_API_SANDBOX_URL, action: AppAction): any { - switch (action.type) { - case SET_GRAPH_PROXY_URL: - return action.response; - default: - return state; - } -} diff --git a/src/app/services/reducers/query-input-reducers.spec.ts b/src/app/services/reducers/query-input-reducers.spec.ts deleted file mode 100644 index ee6a9172f..000000000 --- a/src/app/services/reducers/query-input-reducers.spec.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { sampleQuery } from './query-input-reducers'; -import { SET_SAMPLE_QUERY_SUCCESS } from '../redux-constants'; - -describe('Query INput Reducer', () => { - it('should return initial state', () => { - const initialState = {}; - const dummyAction = { - type: 'Dummy', response: { - selectedVerb: 'GET', - sampleUrl: 'https://graph.microsoft.com/v1.0/me/' - } - }; - const newState = sampleQuery(initialState, dummyAction); - - expect(newState).toEqual(initialState); - }); - - it('should handle SET_SAMPLE_QUERY_SUCCESS', () => { - const initialState = {}; - - const query = { - selectedVerb: 'GET', - sampleUrl: 'https://graph.microsoft.com/v1.0/me/' - }; - - const action = { - type: SET_SAMPLE_QUERY_SUCCESS, response: query - }; - - const newState = sampleQuery(initialState, action); - - expect(newState).toEqual(query); - }); -}); diff --git a/src/app/services/reducers/query-input-reducers.ts b/src/app/services/reducers/query-input-reducers.ts deleted file mode 100644 index 29b2c263d..000000000 --- a/src/app/services/reducers/query-input-reducers.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { AppAction } from '../../../types/action'; -import { SET_SAMPLE_QUERY_SUCCESS } from '../redux-constants'; - -export function sampleQuery(state = {}, action: AppAction): any { - switch (action.type) { - case SET_SAMPLE_QUERY_SUCCESS: - return action.response; - default: - return state; - } -} diff --git a/src/app/services/reducers/query-loading-reducers.spec.ts b/src/app/services/reducers/query-loading-reducers.spec.ts deleted file mode 100644 index f82a9eab2..000000000 --- a/src/app/services/reducers/query-loading-reducers.spec.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { isLoadingData } from './query-loading-reducers'; -import { - FETCH_SCOPES_ERROR, - GET_CONSENT_ERROR, - PROFILE_REQUEST_ERROR, - PROFILE_REQUEST_SUCCESS, - QUERY_GRAPH_RUNNING, - QUERY_GRAPH_STATUS, - QUERY_GRAPH_SUCCESS -} from '../redux-constants'; - -describe('Query loading reducer', () => { - it('should return false in case of get_consent error', () => { - const initialState = { - pending: false, - data: {}, - error: null - }; - const dummyAction = { - type: GET_CONSENT_ERROR, - response: null - }; - - const newState = isLoadingData(initialState, dummyAction); - expect(newState).toEqual(false); - }); - - it('should return false in case of QUERY_GRAPH_SUCCESS', () => { - const initialState = {}; - const dummyAction = { - type: QUERY_GRAPH_SUCCESS, - response: null - }; - - const newState = isLoadingData(initialState, dummyAction); - expect(newState).toEqual(false); - }); - - it('should return false in case of FETCH_SCOPES_ERROR', () => { - const initialState = {}; - const dummyAction = { - type: FETCH_SCOPES_ERROR, - response: null - } - - const newState = isLoadingData(initialState, dummyAction); - expect(newState).toEqual(false); - }); - - it('should retur false in case of PROFILE_REQUEST_SUCCESS', () => { - const initialState = {}; - const dummyAction = { - type: PROFILE_REQUEST_SUCCESS, - response: null - } - - const newState = isLoadingData(initialState, dummyAction); - expect(newState).toEqual(false); - }); - - it('should return false in case of PROFILE_REQUEST_SUCCESS', () => { - const initialState = {}; - const dummyAction = { - type: PROFILE_REQUEST_SUCCESS, - response: null - } - - const newState = isLoadingData(initialState, dummyAction); - expect(newState).toEqual(false); - }); - - it('should return unaltered state by default', () => { - const initialState = { - pending: false, - data: {}, - error: null - }; - const dummyAction = { - type: '', - response: null - } - - const newState = isLoadingData(initialState, dummyAction); - expect(newState).toEqual(initialState); - }); - - it('should return a response in case of QUERY_GRAPH_RUNNING', () => { - const initialState = { - pending: false, - data: {}, - error: null - }; - const dummyAction = { - type: QUERY_GRAPH_RUNNING, - response: { - pending: true, - data: {}, - error: null - } - } - - const newState = isLoadingData(initialState, dummyAction); - expect(newState).toEqual(dummyAction.response); - }); -}) \ No newline at end of file diff --git a/src/app/services/reducers/query-loading-reducers.ts b/src/app/services/reducers/query-loading-reducers.ts deleted file mode 100644 index 9c7c93fe4..000000000 --- a/src/app/services/reducers/query-loading-reducers.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { AppAction } from '../../../types/action'; -import { - FETCH_SCOPES_ERROR, - GET_CONSENT_ERROR, - PROFILE_REQUEST_ERROR, - PROFILE_REQUEST_SUCCESS, - QUERY_GRAPH_RUNNING, - QUERY_GRAPH_STATUS, - QUERY_GRAPH_SUCCESS -} from '../redux-constants'; - -export function isLoadingData(state = {}, action: AppAction): any { - switch (action.type) { - case GET_CONSENT_ERROR: - return false; - case QUERY_GRAPH_RUNNING: - if (action.response) { - return action.response; - } - case QUERY_GRAPH_SUCCESS: - return false; - case QUERY_GRAPH_STATUS: - return false; - case FETCH_SCOPES_ERROR: - return false; - case PROFILE_REQUEST_ERROR: - return false; - case PROFILE_REQUEST_SUCCESS: - return false; - default: - return state; - } -} diff --git a/src/app/services/reducers/query-runner-reducers.spec.ts b/src/app/services/reducers/query-runner-reducers.spec.ts deleted file mode 100644 index a502ba32d..000000000 --- a/src/app/services/reducers/query-runner-reducers.spec.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { graphResponse } from './query-runner-reducers'; -import { queryRunnerStatus } from './query-runner-status-reducers'; -import { - CLEAR_QUERY_STATUS, QUERY_GRAPH_RUNNING, QUERY_GRAPH_STATUS, - QUERY_GRAPH_SUCCESS, VIEW_HISTORY_ITEM_SUCCESS -} from '../redux-constants'; -import { IGraphResponse } from '../../../types/query-response'; - -describe('Query Runner Reducer', () => { - it('should return initial state', () => { - const initialState: IGraphResponse = { body: undefined, headers: undefined }; - const dummyAction = { type: 'Dummy', response: { displayName: 'Megan Bowen' } }; - const newState = graphResponse(initialState, dummyAction); - - expect(newState).toEqual(initialState); - }); - - it('should handle QUERY_GRAPH_SUCCESS', () => { - const initialState: IGraphResponse = { body: undefined, headers: undefined }; - const mockResponse = { - body: { - displayName: 'Megan Bowen' - }, - headers: { - 'content-type': 'application-json' - } - }; - const queryAction = { type: QUERY_GRAPH_SUCCESS, response: mockResponse }; - const newState = graphResponse(initialState, queryAction); - - expect(newState).toEqual(mockResponse); - }); - - it('should handle QUERY_GRAPH_STATUS', () => { - const initialState: IGraphResponse = { body: undefined, headers: undefined }; - const mockResponse = { - status: 400 - }; - const queryAction = { type: QUERY_GRAPH_STATUS, response: mockResponse }; - const newState = queryRunnerStatus(initialState, queryAction); - - expect(newState).toEqual(mockResponse); - }); - - it('should handle CLEAR_QUERY_STATUS', () => { - const initialState: IGraphResponse = { body: undefined, headers: undefined }; - - const action = { type: CLEAR_QUERY_STATUS, response: '' }; - const newState = queryRunnerStatus(initialState, action); - - expect(newState).toEqual(null); - }); - - it('should handle VIEW_HISTORY_ITEM_UCCESS', () => { - const initialState: IGraphResponse = { body: undefined, headers: undefined }; - const mockResponse = { - body: { - displayName: 'Megan Bowen' - }, - headers: { - 'content-type': 'application-json' - } - }; - - const action = { type: VIEW_HISTORY_ITEM_SUCCESS, response: mockResponse }; - - const newState = queryRunnerStatus(initialState, action); - expect(newState).toEqual(null); - }); - - it('should handle QUERY_GRAPH_RUNNING', () => { - const initialState: IGraphResponse = { body: undefined, headers: undefined }; - const expectedState = initialState; - - const action = { type: QUERY_GRAPH_RUNNING, response: '' }; - const newState = graphResponse(initialState, action); - expect(newState).toEqual(expectedState); - }) -}); diff --git a/src/app/services/reducers/query-runner-reducers.ts b/src/app/services/reducers/query-runner-reducers.ts deleted file mode 100644 index 8133ad0dd..000000000 --- a/src/app/services/reducers/query-runner-reducers.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { AppAction } from '../../../types/action'; -import { IGraphResponse } from '../../../types/query-response'; -import { - CLEAR_RESPONSE, - LOGOUT_SUCCESS, - QUERY_GRAPH_RUNNING, - QUERY_GRAPH_SUCCESS, - VIEW_HISTORY_ITEM_SUCCESS -} from '../redux-constants'; - -const initialState: IGraphResponse = { - body: undefined, - headers: undefined -}; - -export function graphResponse( - state: IGraphResponse = initialState, - action: AppAction -): any { - switch (action.type) { - case QUERY_GRAPH_SUCCESS: - return action.response; - case VIEW_HISTORY_ITEM_SUCCESS: - return action.response; - case QUERY_GRAPH_RUNNING: - return initialState; - case CLEAR_RESPONSE: - return initialState; - case LOGOUT_SUCCESS: - return initialState; - default: - return state; - } -} diff --git a/src/app/services/reducers/query-runner-status-reducers.ts b/src/app/services/reducers/query-runner-status-reducers.ts deleted file mode 100644 index b7489ea02..000000000 --- a/src/app/services/reducers/query-runner-status-reducers.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { AppAction } from '../../../types/action'; -import { CLEAR_QUERY_STATUS, - GET_CONSENT_ERROR, LOGOUT_SUCCESS, QUERY_GRAPH_RUNNING, QUERY_GRAPH_STATUS, - VIEW_HISTORY_ITEM_SUCCESS } from '../redux-constants'; - -export function queryRunnerStatus(state = {}, action: AppAction): any { - switch (action.type) { - case QUERY_GRAPH_STATUS: - return action.response; - case GET_CONSENT_ERROR: - return action.response; - case QUERY_GRAPH_RUNNING: - return null; - case CLEAR_QUERY_STATUS: - return null; - case VIEW_HISTORY_ITEM_SUCCESS: - return null; - case LOGOUT_SUCCESS: - return null; - default: - return state; - } -} diff --git a/src/app/services/reducers/request-history-reducers.spec.ts b/src/app/services/reducers/request-history-reducers.spec.ts deleted file mode 100644 index b76fecf37..000000000 --- a/src/app/services/reducers/request-history-reducers.spec.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { history } from './request-history-reducers'; -import { - ADD_HISTORY_ITEM_SUCCESS, BULK_ADD_HISTORY_ITEMS_SUCCESS, REMOVE_ALL_HISTORY_ITEMS_SUCCESS, - REMOVE_HISTORY_ITEM_SUCCESS -} from '../redux-constants'; - - -describe('Request History Reducer', () => { - it('should return initial state', () => { - const initialState: any = []; - const dummyHistoryItem: any = [{ name: 'Key', value: 'Value' }]; - const newState = history(initialState, dummyHistoryItem); - - expect(newState).toEqual(initialState); - }); - - it('should handle ADD_HISTORY_ITEM_SUCCESS', () => { - const initialState: any = []; - const dummy = { query: 'query', createdAt: new Date().toISOString() }; - const queryAction = { - type: ADD_HISTORY_ITEM_SUCCESS, - response: dummy - }; - - const newState = history(initialState, queryAction); - - expect(newState).toEqual([dummy]); - }); - - it('should handle REMOVE_HISTORY_ITEM_SUCCESS', () => { - const initialState = [ - 1, 2 - ] - - const expectedState = [ - 1 - ] - - const action = { - type: REMOVE_HISTORY_ITEM_SUCCESS, - response: 2 - } - - const newState = history(initialState, action); - expect(newState).toEqual(expectedState); - }); - - it('should handle REMOVE_ALL_HISTORY_ITEMS_SUCCESS', () => { - const initialState = [ - { - index: 0 - } - ] - - const expectedState: any = [ - { - index: 0 - } - ] - - const action = { - type: REMOVE_ALL_HISTORY_ITEMS_SUCCESS, - response: initialState - } - - const newState = history(initialState, action); - expect(newState).toEqual(expectedState); - }); - - it('should handle BULK_ADD_HISTORY_ITEMS_SUCCESS', () => { - const initialState: any = []; - const dummy = [ - { query: 'query', createdAt: new Date().toISOString() }, - { query: 'query2', createdAt: new Date().toISOString() } - ]; - const queryAction = { - type: BULK_ADD_HISTORY_ITEMS_SUCCESS, - response: dummy - }; - - const newState = history(initialState, queryAction); - - expect(newState).toEqual(dummy); - }) - -}); diff --git a/src/app/services/reducers/request-history-reducers.ts b/src/app/services/reducers/request-history-reducers.ts deleted file mode 100644 index 28e60f209..000000000 --- a/src/app/services/reducers/request-history-reducers.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { AppAction } from '../../../types/action'; -import { IHistoryItem } from '../../../types/history'; -import { - ADD_HISTORY_ITEM_SUCCESS, - BULK_ADD_HISTORY_ITEMS_SUCCESS, - REMOVE_ALL_HISTORY_ITEMS_SUCCESS, - REMOVE_HISTORY_ITEM_SUCCESS -} from '../redux-constants'; - -export function history(state: any[] = [], action: AppAction): any { - let historyItems: any[]; - switch (action.type) { - case ADD_HISTORY_ITEM_SUCCESS: - historyItems = [...state, action.response]; - historyItems = historyItems.reduce((current, compare) => { - return current.findIndex((historyItem: IHistoryItem) => { - return historyItem.createdAt === compare.createdAt; - }) < 0 ? [...current, compare] : current; - }, []); - - return historyItems; - - case BULK_ADD_HISTORY_ITEMS_SUCCESS: - historyItems = [...state, ...action.response]; - return historyItems; - - case REMOVE_HISTORY_ITEM_SUCCESS: - return state.filter(historyItem => historyItem !== action.response); - - case REMOVE_ALL_HISTORY_ITEMS_SUCCESS: - const historyItemsToDelete: any = action.response; - return state.filter((historyItem: IHistoryItem) => !historyItemsToDelete.includes(historyItem.createdAt)); - - default: - return state; - } -} diff --git a/src/app/services/reducers/resources-reducer.spec.ts b/src/app/services/reducers/resources-reducer.spec.ts deleted file mode 100644 index 4a3fad5a0..000000000 --- a/src/app/services/reducers/resources-reducer.spec.ts +++ /dev/null @@ -1,166 +0,0 @@ -import configureMockStore from 'redux-mock-store'; -import thunk from 'redux-thunk'; - -import { resources } from '../../../app/services/reducers/resources-reducer'; -import { - FETCH_RESOURCES_ERROR, FETCH_RESOURCES_PENDING, - FETCH_RESOURCES_SUCCESS -} from '../../../app/services/redux-constants'; -import { IResource, IResourceLink, IResources, ResourceLinkType } from '../../../types/resources'; - -const res = { - 'segment': '/', - 'labels': [ - { - 'name': 'v1.0', - 'methods': [ - 'GET' - ] - }, - { - 'name': 'beta', - 'methods': [ - 'GET' - ] - } - ], - 'children': [ - { - 'segment': 'accessReviewDecisions', - 'labels': [ - { - 'name': 'beta', - 'methods': [ - 'GET', - 'POST' - ] - } - ], - 'children': [ - { - 'segment': '{accessReviewDecision-id}', - 'labels': [ - { - 'name': 'beta', - 'methods': [ - 'GET', - 'PATCH', - 'DELETE' - ] - } - ] - } - ] - } - ] -}; - -const resource = JSON.parse(JSON.stringify(res)) as IResource - -const initialState: IResources = { - pending: false, - data: {}, - error: null -}; - -const paths = [{ - key: '5-issues', - url: '/issues', - name: 'issues (1)', - labels: [ - { - name: 'v1.0', methods: [{ - name: 'GET', - documentationUrl: null - }, { - name: 'POST', - documentationUrl: null - }] - }, - { - name: 'beta', methods: [{ - name: 'GET', - documentationUrl: null - }, { - name: 'POST', - documentationUrl: null - }] - } - ], - version: 'v1.0', - methods: [{ - name: 'GET', - documentationUrl: null - }, { - name: 'POST', - documentationUrl: null - }], - isExpanded: true, - parent: '/', - level: 1, - paths: ['/'], - type: 'path', - links: [] -}]; - -const resourceLinks: IResourceLink[] = [ - { - labels: [ - { - name: 'v1.0', methods: [{ - name: 'GET', - documentationUrl: null - }, { - name: 'POST', - documentationUrl: null - }] - } - ], - key: '5-issues', - url: '/issues', - name: 'issues (1)', - icon: 'LightningBolt', - isExpanded: true, - level: 7, - parent: '/', - paths: ['/'], - type: ResourceLinkType.PATH, - links: [] - } -]; - -describe('Resources Reducer', () => { - it('should return initial state', () => { - const dummyAction = { type: 'Dummy', response: { dummy: 'Dummy' } }; - const newState = resources(initialState, dummyAction); - expect(newState).toEqual(initialState); - }); - - it('should handle FETCH_RESOURCES_SUCCESS', () => { - const newState = { ...initialState }; - newState.data['v1.0'] = resource; - const resourceAction = { type: FETCH_RESOURCES_SUCCESS, response: { 'v1.0': resource } }; - const state = resources(initialState, resourceAction); - expect(state).toEqual(newState); - }); - - it('should handle FETCH_RESOURCES_ERROR', () => { - - const mockResponse = new Error('400'); - - const newState = { ...initialState, error: mockResponse, data: {} }; - const resourceAction = { type: FETCH_RESOURCES_ERROR, response: mockResponse }; - - const state = resources(initialState, resourceAction); - expect(state).toEqual(newState); - }); - - it('should handle FETCH_RESOURCES_PENDING', () => { - const isRunning = true; - const newState = { ...initialState, pending: isRunning, data: {} }; - const queryAction = { type: FETCH_RESOURCES_PENDING, response: null }; - const state = resources(initialState, queryAction); - expect(state).toEqual(newState); - }); - -}); diff --git a/src/app/services/reducers/resources-reducer.ts b/src/app/services/reducers/resources-reducer.ts deleted file mode 100644 index ccfeac2ea..000000000 --- a/src/app/services/reducers/resources-reducer.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { AppAction } from '../../../types/action'; -import { IResource, IResources } from '../../../types/resources'; -import { - FETCH_RESOURCES_ERROR, FETCH_RESOURCES_PENDING, - FETCH_RESOURCES_SUCCESS -} from '../redux-constants'; - -const initialState: IResources = { - pending: false, - data: {}, - error: null -}; - -export function resources(state: IResources = initialState, action: AppAction): IResources { - switch (action.type) { - case FETCH_RESOURCES_SUCCESS: - return { - pending: false, - data: action.response, - error: null - }; - case FETCH_RESOURCES_ERROR: - return { - pending: false, - error: action.response, - data: {} - }; - case FETCH_RESOURCES_PENDING: - return { - pending: true, - data: initialState.data, - error: null - }; - default: - return state; - } -} diff --git a/src/app/services/reducers/response-expanded-reducer.ts b/src/app/services/reducers/response-expanded-reducer.ts deleted file mode 100644 index 0940775e1..000000000 --- a/src/app/services/reducers/response-expanded-reducer.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { AppAction } from '../../../types/action'; -import { RESPONSE_EXPANDED } from '../redux-constants'; - -export function responseAreaExpanded(state: boolean = false, action: AppAction): any { - switch (action.type) { - case RESPONSE_EXPANDED: - return !!action.response; - - default: - return state; - } -} diff --git a/src/app/services/reducers/samples-reducers.spec.ts b/src/app/services/reducers/samples-reducers.spec.ts deleted file mode 100644 index 7c86f0030..000000000 --- a/src/app/services/reducers/samples-reducers.spec.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { samples } from './samples-reducers'; -import { - SAMPLES_FETCH_ERROR, SAMPLES_FETCH_PENDING, - SAMPLES_FETCH_SUCCESS -} from '../redux-constants'; -import { queries } from '../../views/sidebar/sample-queries/queries'; -import { ISampleQuery } from '../../../types/query-runner'; - -describe('Samples Reducer', () => { - it('should return initial state', () => { - const initialState: any = { - pending: false, - queries: [], - error: null - }; - const dummyAction = { type: 'Dummy', response: { dummy: 'Dummy' } }; - const newState = samples(initialState, dummyAction); - - expect(newState).toEqual(initialState); - }); - - it('should handle SAMPLES_FETCH_SUCCESS', () => { - const initialState: any = { - pending: false, - queries: [], - error: null - }; - - const sampleQueries: ISampleQuery[] = - [ - { - 'category': 'Getting Started', - 'method': 'GET', - 'humanName': 'my profile', - 'requestUrl': '/v1.0/me/', - 'docLink': 'https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/resources/users', - 'skipTest': false - } - ]; - - const newState = { ...initialState }; - newState.queries = sampleQueries; - - const queryAction = { type: SAMPLES_FETCH_SUCCESS, response: sampleQueries }; - const state = samples(initialState, queryAction); - - expect(state).toEqual(newState); - }); - - it('should handle SAMPLES_FETCH_ERROR', () => { - const initialState: any = { - pending: false, - queries: [], - error: null - }; - - const newState = { ...initialState }; - newState.error = 'error'; - newState.queries = queries; - - const queryAction = { type: SAMPLES_FETCH_ERROR, response: queries }; - const state = samples(initialState, queryAction); - - expect(state).toEqual(newState); - }); - - it('should handle SAMPLES_FETCH_PENDING', () => { - const initialState: any = { - pending: false, - queries: [], - error: null - }; - - const isQueryRunning = true; - - const newState = { ...initialState }; - newState.pending = isQueryRunning; - - const queryAction: any = { type: SAMPLES_FETCH_PENDING }; - const state = samples(initialState, queryAction); - - expect(state).toEqual(newState); - }); - -}); diff --git a/src/app/services/reducers/samples-reducers.ts b/src/app/services/reducers/samples-reducers.ts deleted file mode 100644 index 62e2d7121..000000000 --- a/src/app/services/reducers/samples-reducers.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { AppAction } from '../../../types/action'; -import { SAMPLES_FETCH_ERROR, SAMPLES_FETCH_PENDING, SAMPLES_FETCH_SUCCESS } from '../redux-constants'; - -const initialState = { - pending: false, - queries: [], - error: null -}; - -export function samples(state = initialState, action: AppAction): any { - switch (action.type) { - case SAMPLES_FETCH_PENDING: - return { - ...state, - pending: true - }; - case SAMPLES_FETCH_SUCCESS: - return { - ...state, - pending: false, - queries: action.response - }; - case SAMPLES_FETCH_ERROR: - return { - ...state, - pending: false, - queries: action.response, - error: 'error' - }; - default: - return state; - } -} diff --git a/src/app/services/reducers/snippet-reducer.spec.ts b/src/app/services/reducers/snippet-reducer.spec.ts deleted file mode 100644 index ee4b2bca0..000000000 --- a/src/app/services/reducers/snippet-reducer.spec.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { snippets } from './snippet-reducer'; -import { GET_SNIPPET_ERROR, GET_SNIPPET_SUCCESS } from '../redux-constants'; - -describe('Graph Explorer Snippet Reducer', () => { - it('should set csharp code snippet', () => { - const initialState = { - pending: false, - data: {}, - error: null - }; - const snippet = { csharp: 'GraphServiceClient graphClient = new GraphServiceClient( authProvider );' }; - - const response = { - data: snippet, - error: null, - pending: false - }; - const dummyAction = { - type: GET_SNIPPET_SUCCESS, - response: snippet - }; - - const newState = snippets(initialState, dummyAction); - expect(newState).toEqual(response); - }); - - it('should handle GET_SNIPPET_ERROR', () => { - const initialState = { - pending: false, - data: {}, - error: null - } - - const action = { - type: GET_SNIPPET_ERROR, - response: 'error' - } - - const expectedState = { - pending: false, - data: null, - error: 'error' - } - - const newState = snippets(initialState, action); - expect(newState).toEqual(expectedState); - }) -}); diff --git a/src/app/services/reducers/snippet-reducer.ts b/src/app/services/reducers/snippet-reducer.ts deleted file mode 100644 index badef4a90..000000000 --- a/src/app/services/reducers/snippet-reducer.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { AppAction } from '../../../types/action'; -import { - GET_SNIPPET_ERROR, GET_SNIPPET_PENDING, GET_SNIPPET_SUCCESS, - SET_SNIPPET_TAB_SUCCESS -} from '../redux-constants'; -import { ISnippet } from '../../../types/snippets'; - -const initialState: ISnippet = { - pending: false, - data: {}, - error: null, - snippetTab: 'csharp' -}; - -export function snippets(state = initialState, action: AppAction): any { - switch (action.type) { - case GET_SNIPPET_SUCCESS: - return { - ...state, - pending: false, - data: action.response as object, - error: null - }; - case GET_SNIPPET_ERROR: - return { - ...state, - pending: false, - data: null, - error: action.response as object - }; - case GET_SNIPPET_PENDING: - return { - ...state, - pending: true, - data: null, - error: null - }; - case SET_SNIPPET_TAB_SUCCESS: - return { - ...state, - snippetTab: action.response - } - default: - return state; - } -} \ No newline at end of file diff --git a/src/app/services/reducers/terms-of-use-reducer.spec.ts b/src/app/services/reducers/terms-of-use-reducer.spec.ts deleted file mode 100644 index 879a0a0dc..000000000 --- a/src/app/services/reducers/terms-of-use-reducer.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { termsOfUse } from '../../../app/services/reducers/terms-of-use-reducer'; -import { CLEAR_TERMS_OF_USE } from '../../../app/services/redux-constants'; -import { AppAction } from '../../../types/action'; - -describe('Terms of Use Action Creators', () => { - it('should return initial state', () => { - const initialState = true; - - const dummyAction: AppAction = { - type: CLEAR_TERMS_OF_USE, - response: false - }; - const newState = termsOfUse(initialState, dummyAction); - - expect(newState).toEqual(false); - - }); -}); \ No newline at end of file diff --git a/src/app/services/reducers/terms-of-use-reducer.ts b/src/app/services/reducers/terms-of-use-reducer.ts deleted file mode 100644 index 6f31399d7..000000000 --- a/src/app/services/reducers/terms-of-use-reducer.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { AppAction } from '../../../types/action'; -import { CLEAR_TERMS_OF_USE } from '../redux-constants'; - -export function termsOfUse(state = true, action: AppAction): boolean { - switch (action.type) { - case CLEAR_TERMS_OF_USE: - return false; - default: - return state; - } -} \ No newline at end of file diff --git a/src/app/services/reducers/theme-reducer.ts b/src/app/services/reducers/theme-reducer.ts deleted file mode 100644 index 795287d2c..000000000 --- a/src/app/services/reducers/theme-reducer.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { AppAction } from '../../../types/action'; -import { CHANGE_THEME_SUCCESS } from '../redux-constants'; - -export function theme(state = {}, action: AppAction): string | object { - switch (action.type) { - case CHANGE_THEME_SUCCESS: - return action.response; - default: - return state; - } -} diff --git a/src/app/services/reducers/toggle-sidebar-reducer.spec.ts b/src/app/services/reducers/toggle-sidebar-reducer.spec.ts deleted file mode 100644 index 6a376ec97..000000000 --- a/src/app/services/reducers/toggle-sidebar-reducer.spec.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { sidebarProperties } from './toggle-sidebar-reducer'; -import { - SET_SAMPLE_QUERY_SUCCESS, TOGGLE_SIDEBAR_SUCCESS, - VIEW_HISTORY_ITEM_SUCCESS -} from '../redux-constants'; - - -describe('Toggle sidebar', () => { - it('should handle TOGGLE_SIDEBAR_SUCCESS', () => { - const initialState = { - showSidebar: false, - mobileScreen: false - } - - const action = { - type: TOGGLE_SIDEBAR_SUCCESS, - response: { - showSidebar: true, - mobileScreen: false - } - } - - const expectedState = { - showSidebar: true, - mobileScreen: false - } - const newState = sidebarProperties(initialState, action); - expect(newState).toEqual(expectedState); - }); - - it('should handle SET_SAMPLE_QUERY_SUCCESS', () => { - const initialState = { - showSidebar: true, - mobileScreen: true - } - - const action = { - type: SET_SAMPLE_QUERY_SUCCESS, - response: {} - } - - const expectedState = { - showSidebar: false, - mobileScreen: true - } - const newState = sidebarProperties(initialState, action); - expect(newState.showSidebar).toEqual(expectedState.showSidebar); - }); - - it('should handle VIEW_HISTORY_ITEM_SUCCESS', () => { - const initialState = { - showSidebar: true, - mobileScreen: true - } - - const action = { - type: VIEW_HISTORY_ITEM_SUCCESS, - response: { - showSidebar: false, - mobileScreen: true - } - } - - const expectedState = { - showSidebar: false, - mobileScreen: true - } - const newState = sidebarProperties(initialState, action); - expect(newState.showSidebar).toEqual(expectedState.showSidebar); - }); - - it('should return default state', () => { - const initialState = { - showSidebar: false, - mobileScreen: false - } - - const action = { - type: 'NOT_A_VALID_ACTION', - response: '' - } - - const expectedState = { - showSidebar: false, - mobileScreen: false - } - const newState = sidebarProperties(initialState, action); - expect(newState).toEqual(expectedState); - }); - - it('should handle QUERY_GRAPH_RUNNING', () => { - const initialState = { - showSidebar: true, - mobileScreen: true - } - const action = { - type: 'QUERY_GRAPH_RUNNING', - response: '' - } - - const expectedState = { - showSidebar: false, - mobileScreen: true - } - const newState = sidebarProperties(initialState, action); - expect(newState).toEqual(expectedState); - }) - -}) \ No newline at end of file diff --git a/src/app/services/reducers/toggle-sidebar-reducer.ts b/src/app/services/reducers/toggle-sidebar-reducer.ts deleted file mode 100644 index 1dd4e9a52..000000000 --- a/src/app/services/reducers/toggle-sidebar-reducer.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { AppAction } from '../../../types/action'; -import { - QUERY_GRAPH_RUNNING, SET_SAMPLE_QUERY_SUCCESS, - TOGGLE_SIDEBAR_SUCCESS, VIEW_HISTORY_ITEM_SUCCESS -} from '../redux-constants'; -import { ISidebarProps } from '../../../types/sidebar'; - -const initialState: ISidebarProps = { - showSidebar: false, - mobileScreen: false -}; - -export function sidebarProperties(state = initialState, action: AppAction): any { - switch (action.type) { - case TOGGLE_SIDEBAR_SUCCESS: - return action.response; - case QUERY_GRAPH_RUNNING: - case SET_SAMPLE_QUERY_SUCCESS: - case VIEW_HISTORY_ITEM_SUCCESS: - if (state.mobileScreen) { - return { - ...state, - showSidebar: false - }; - } - default: - return state; - } -} diff --git a/src/app/services/redux-constants.ts b/src/app/services/redux-constants.ts index 65cb0f06b..b0c0cb624 100644 --- a/src/app/services/redux-constants.ts +++ b/src/app/services/redux-constants.ts @@ -1,63 +1,58 @@ -export const QUERY_GRAPH_SUCCESS = 'QUERY_GRAPH_SUCCESS'; -export const QUERY_GRAPH_STATUS = 'QUERY_GRAPH_STATUS'; -export const CLEAR_QUERY_STATUS = 'CLEAR_QUERY_STATUS'; -export const GET_AUTH_TOKEN_SUCCESS = 'GET_AUTH_TOKEN_SUCCESS'; -export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS'; -export const QUERY_GRAPH_RUNNING = 'QUERY_GRAPH_RUNNING'; -export const HEADER_ADD_SUCCESS = 'HEADER_ADD_SUCCESS'; -export const HEADER_REMOVE_SUCCESS = 'HEADER_REMOVE_SUCCESS'; -export const SET_GRAPH_EXPLORER_MODE_SUCCESS = 'SET_GRAPH_EXPLORER_MODE_SUCCESS'; -export const SET_SAMPLE_QUERY_SUCCESS = 'SET_SAMPLE_QUERY_SUCCESS'; -export const PROFILE_REQUEST_SUCCESS = 'PROFILE_REQUEST_SUCCESS'; -export const PROFILE_REQUEST_ERROR = 'PROFILE_REQUEST_ERROR'; -export const GET_SNIPPET_SUCCESS = 'GET_SNIPPET_SUCCESS'; -export const GET_SNIPPET_ERROR = 'GET_SNIPPET_ERROR'; -export const GET_SNIPPET_PENDING = 'GET_SNIPPET_PENDING'; -export const REMOVE_HISTORY_ITEM_SUCCESS = 'REMOVE_HISTORY_ITEM_SUCCESS'; -export const ADD_HISTORY_ITEM_SUCCESS = 'ADD_HISTORY_ITEM_SUCCESS'; +export const QUERY_GRAPH_SUCCESS = 'query/runQuery/fulfilled'; +export const QUERY_GRAPH_STATUS = 'queryRunnerStatus/setQueryResponseStatus'; +export const CLEAR_QUERY_STATUS = 'queryRunnerStatus/clearQueryStatus'; +export const GET_AUTH_TOKEN_SUCCESS = 'auth/getAuthTokenSuccess'; +export const LOGOUT_SUCCESS = 'auth/signOutSuccess'; +export const QUERY_GRAPH_RUNNING = 'query/runQuery/pending'; +export const SET_SAMPLE_QUERY_SUCCESS = 'sampleQuery/setSampleQuery'; +export const SET_GRAPH_EXPLORER_MODE_SUCCESS = 'graphExplorerMode/setGraphExplorerMode'; +export const PROFILE_REQUEST_SUCCESS = 'profile/getProfileInfo/success'; +export const PROFILE_REQUEST_ERROR = 'profile/getProfileInfo/error'; +export const GET_SNIPPET_SUCCESS = 'snippet/getSnippet/fulfilled'; +export const GET_SNIPPET_ERROR = 'snippet/getSnippet/rejected'; +export const GET_SNIPPET_PENDING = 'snippet/getSnippet/pending'; +export const REMOVE_HISTORY_ITEM_SUCCESS = 'history/removeHistoryItem'; +export const ADD_HISTORY_ITEM_SUCCESS = 'history/addHistoryItem'; export const GET_HISTORY_ITEMS_SUCCESS = 'GET_HISTORY_ITEMS_SUCCESS'; -export const VIEW_HISTORY_ITEM_SUCCESS = 'VIEW_HISTORY_ITEM_SUCCESS'; -export const CLEAR_RESPONSE = 'CLEAR_RESPONSE'; -export const SAMPLES_FETCH_SUCCESS = 'SAMPLES_FETCH_SUCCESS'; -export const SAMPLES_FETCH_ERROR = 'SAMPLES_FETCH_ERROR'; -export const SAMPLES_FETCH_PENDING = 'SAMPLES_FETCH_PENDING'; +export const SAMPLES_FETCH_SUCCESS = 'samples/fetchSamples/fulfilled'; +export const SAMPLES_FETCH_ERROR = 'samples/fetchSamples/rejected'; +export const SAMPLES_FETCH_PENDING = 'samples/fetchSamples/pending'; export const CHANGE_THEME_SUCCESS = 'CHANGE_THEME_SUCCESS'; -export const TOGGLE_SIDEBAR_SUCCESS = 'TOGGLE_SIDEBAR_SUCCESS'; -export const FETCH_ADAPTIVE_CARD_SUCCESS = 'FETCH_ADAPTIVE_CARD_SUCCESS'; -export const FETCH_ADAPTIVE_CARD_PENDING = 'FETCH_ADAPTIVE_CARD_PENDING'; -export const FETCH_ADAPTIVE_CARD_ERROR = 'FETCH_ADAPTIVE_CARD_ERROR'; -export const CLEAR_TERMS_OF_USE = 'CLEAR_TERMS_OF_USE'; -export const FETCH_FULL_SCOPES_SUCCESS = 'FULL_SCOPES_FETCH_SUCCESS'; -export const FETCH_URL_SCOPES_SUCCESS = 'FETCH_URL_SCOPES_SUCCESS'; -export const FETCH_SCOPES_ERROR = 'SCOPES_FETCH_ERROR'; -export const FETCH_FULL_SCOPES_PENDING = 'FETCH_SCOPES_PENDING'; -export const FETCH_URL_SCOPES_PENDING = 'FETCH_URL_SCOPES_PENDING'; -export const GET_CONSENT_ERROR = 'GET_CONSENT_ERROR'; -export const GET_CONSENTED_SCOPES_SUCCESS = 'GET_CONSENTED_SCOPES_SUCCESS'; -export const SET_DEVX_API_URL_SUCCESS = 'SET_DEVX_API_URL_SUCCESS'; -export const REMOVE_ALL_HISTORY_ITEMS_SUCCESS = 'REMOVE_ALL_HISTORY_ITEMS_SUCCESS'; -export const AUTOCOMPLETE_FETCH_SUCCESS = 'AUTOCOMPLETE_FETCH_SUCCESS'; -export const AUTOCOMPLETE_FETCH_ERROR = 'AUTOCOMPLETE_FETCH_ERROR'; -export const AUTOCOMPLETE_FETCH_PENDING = 'AUTOCOMPLETE_FETCH_PENDING'; -export const RESIZE_SUCCESS = 'RESIZE_SUCCESS'; -export const RESPONSE_EXPANDED = 'RESPONSE_EXPANDED'; +export const TOGGLE_SIDEBAR_SUCCESS = 'sidebarProperties/toggleSidebar'; +export const CLEAR_TERMS_OF_USE = 'termsOfUse/clearTermsOfUse'; +export const FETCH_FULL_SCOPES_SUCCESS = 'scopes/fetchScopes/fulfilled'; +export const FETCH_URL_SCOPES_SUCCESS = 'scopes/fetchScopes/fulfilled'; +export const FETCH_SCOPES_ERROR = 'scopes/fetchScopes/error'; +export const FETCH_FULL_SCOPES_PENDING = 'scopes/fetchScopes/pending'; +export const FETCH_URL_SCOPES_PENDING = 'scopes/fetchScopes/pending'; +export const GET_CONSENTED_SCOPES_SUCCESS = 'auth/getConsentedScopesSuccess'; +export const GET_CONSENTED_SCOPES_ERROR = 'auth/consentToScopes/rejected'; +export const GET_CONSENTED_SCOPES_PENDING = 'auth/consentToScopes/pending'; +export const GET_CONSENTED_SCOPES_FULFILLED = 'auth/consentToScopes/fulfilled'; +export const SET_DEVX_API_URL_SUCCESS = 'devxApi/setDevxApiUrl'; +export const REMOVE_ALL_HISTORY_ITEMS_SUCCESS = 'history/removeAllHistoryItems'; +export const AUTOCOMPLETE_FETCH_SUCCESS = 'autoComplete/fetch/fulfilled'; +export const AUTOCOMPLETE_FETCH_ERROR = 'autoComplete/fetch/rejected'; +export const AUTOCOMPLETE_FETCH_PENDING = 'autoComplete/fetch/pending'; +export const RESIZE_SUCCESS = 'dimensions/setDimensions'; +export const RESPONSE_EXPANDED = 'responseAreaExpanded/expandResponseArea'; export const PERMISSIONS_PANEL_OPEN = 'PERMISSIONS_PANEL_OPEN'; -export const AUTHENTICATION_PENDING = 'AUTHENTICATION_PENDING'; -export const SET_GRAPH_PROXY_URL = 'SET_GRAPH_PROXY_URL'; -export const FETCH_RESOURCES_SUCCESS = 'RESOURCES_FETCH_SUCCESS'; -export const FETCH_RESOURCES_ERROR = 'RESOURCES_FETCH_ERROR'; -export const FETCH_RESOURCES_PENDING = 'FETCH_RESOURCES_PENDING'; -export const GET_POLICY_SUCCESS = 'GET_POLICY_SUCCESS'; -export const GET_POLICY_ERROR = 'GET_POLICY_ERROR'; -export const GET_POLICY_PENDING = 'GET_POLICY_PENDING'; -export const RESOURCEPATHS_ADD_SUCCESS = 'RESOURCEPATHS_ADD_SUCCESS'; -export const RESOURCEPATHS_DELETE_SUCCESS = 'RESOURCEPATHS_DELETE_SUCCESS'; -export const BULK_ADD_HISTORY_ITEMS_SUCCESS = 'BULK_ADD_HISTORY_ITEMS_SUCCESS'; +export const AUTHENTICATION_PENDING = 'auth/setAuthenticationPending'; +export const FETCH_RESOURCES_SUCCESS = 'resources/fetchResources/fulfilled'; +export const FETCH_RESOURCES_ERROR = 'resources/fetchResources/rejected'; +export const FETCH_RESOURCES_PENDING = 'resources/fetchResources/pending'; +export const GET_GRAPH_PROXY_URL_PENDING = 'proxyUrl/getGraphProxyUrl/pending'; +export const GET_GRAPH_PROXY_URL_SUCCESS = 'proxyUrl/getGraphProxyUrl/fulfilled'; +export const GET_GRAPH_PROXY_URL_ERROR = 'proxyUrl/getGraphProxyUrl/rejected'; +export const SET_GRAPH_PROXY_URL = 'proxyUrl/setGraphProxyUrl'; +export const RESOURCEPATHS_ADD_SUCCESS = 'collections/addResourcePaths'; +export const RESOURCEPATHS_DELETE_SUCCESS = 'collections/removeResourcePaths'; +export const BULK_ADD_HISTORY_ITEMS_SUCCESS = 'history/bulkAddHistoryItems'; export const SET_SNIPPET_TAB_SUCCESS = 'SET_SNIPPET_TAB_SUCCESS'; -export const GET_ALL_PRINCIPAL_GRANTS_PENDING = 'GET_ALL_PRINCIPAL_GRANTS_PENDING'; -export const GET_ALL_PRINCIPAL_GRANTS_SUCCESS = 'GET_ALL_PRINCIPAL_GRANTS_SUCCESS'; -export const GET_ALL_PRINCIPAL_GRANTS_ERROR = 'GET_ALL_PRINCIPAL_GRANTS_ERROR'; -export const REVOKE_SCOPES_PENDING = 'REVOKE_SCOPES_PENDING'; -export const REVOKE_SCOPES_SUCCESS = 'REVOKE_SCOPES_SUCCESS'; -export const REVOKE_SCOPES_ERROR = 'REVOKE_SCOPES_ERROR'; -export const COLLECTION_CREATE_SUCCESS = 'COLLECTION_CREATE_SUCCESS'; +export const GET_ALL_PRINCIPAL_GRANTS_PENDING = 'permissionGrants/getAllPrincipalGrants/pending'; +export const GET_ALL_PRINCIPAL_GRANTS_SUCCESS = 'permissionGrants/getAllPrincipalGrants/fulfilled'; +export const GET_ALL_PRINCIPAL_GRANTS_ERROR = 'permissionGrants/getAllPrincipalGrants/rejected'; +export const REVOKE_SCOPES_PENDING = 'auth/revokeScopes/pending'; +export const REVOKE_SCOPES_SUCCESS = 'auth/revokeScopes/fulfilled'; +export const REVOKE_SCOPES_ERROR = 'auth/revokeScopes/rejected'; +export const COLLECTION_CREATE_SUCCESS = 'collections/createCollection'; diff --git a/src/app/services/slices/auth.slice.ts b/src/app/services/slices/auth.slice.ts new file mode 100644 index 000000000..4fa56f9d4 --- /dev/null +++ b/src/app/services/slices/auth.slice.ts @@ -0,0 +1,144 @@ +import { BrowserAuthError } from '@azure/msal-browser'; +import { MessageBarType } from '@fluentui/react'; +import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'; + +import { authenticationWrapper } from '../../../modules/authentication'; +import { getConsentAuthErrorHint } from '../../../modules/authentication/authentication-error-hints'; +import { AppDispatch, ApplicationState } from '../../../store'; +import { AuthenticateResult } from '../../../types/authentication'; +import { Mode } from '../../../types/enums'; +import { translateMessage } from '../../utils/translate-messages'; +import { revokeScopes } from '../actions/revoke-scopes.action'; +import { fetchAllPrincipalGrants } from './permission-grants.slice'; +import { getProfileInfo } from './profile.slice'; +import { setQueryResponseStatus } from './query-status.slice'; + +const initialState: AuthenticateResult = { + authToken: { + pending: false, + token: false + }, + consentedScopes: [] +} + +const authSlice = createSlice({ + name: 'auth', + initialState, + reducers: { + getAuthTokenSuccess(state) { + state.authToken.token = true; + state.authToken.pending = false; + }, + signOutSuccess(state) { + state.authToken.token = false; + state.authToken.pending = false; + state.consentedScopes = []; + }, + setAuthenticationPending(state) { + state.authToken.token = true; + state.authToken.pending = true; + }, + getConsentedScopesSuccess(state, action: PayloadAction) { + state.consentedScopes = action.payload; + } + }, + extraReducers: (builder) => { + builder.addCase(consentToScopes.pending, (state) => { + state.authToken.pending = true; + }); + builder.addCase(consentToScopes.fulfilled, (state, action) => { + state.authToken.pending = false; + state.consentedScopes = action.payload!; + }); + builder.addCase(consentToScopes.rejected, (state) => { + state.authToken.pending = false; + }); + builder.addCase(revokeScopes.pending, (state) => { + state.authToken.pending = true; + }); + builder.addCase(revokeScopes.fulfilled, (state, action) => { + state.authToken.pending = false; + state.consentedScopes = action.payload!; + }); + builder.addCase(revokeScopes.rejected, (state) => { + state.authToken.pending = false; + }); + } +}); + +export const { getAuthTokenSuccess, signOutSuccess, + setAuthenticationPending, getConsentedScopesSuccess } = authSlice.actions; + +export function signOut() { + return (dispatch: AppDispatch, getState: Function) => { + const state = getState() as ApplicationState; + const { graphExplorerMode } = state; + dispatch(setAuthenticationPending()); + if (graphExplorerMode === Mode.Complete) { + authenticationWrapper.logOut(); + } else { + authenticationWrapper.logOutPopUp(); + } + dispatch(signOutSuccess()); + }; +} + +const validateConsentedScopes = (scopeToBeConsented: string[], consentedScopes: string[], + consentedResponse: string[]) => { + if (!consentedScopes || !consentedResponse || !scopeToBeConsented) { + return consentedResponse; + } + const expectedScopes = [...consentedScopes, ...scopeToBeConsented]; + if (expectedScopes.length === consentedResponse.length) { + return consentedResponse; + } + return expectedScopes; +} + +export const consentToScopes = createAsyncThunk( + 'auth/consentToScopes', + async (scopes: string[], { dispatch, getState }) => { + try { + const { profile, auth: { consentedScopes } } = getState() as ApplicationState; + const authResponse = await authenticationWrapper.consentToScopes(scopes); + if (authResponse && authResponse.accessToken) { + dispatch(getAuthTokenSuccess()); + const validatedScopes = validateConsentedScopes(scopes, consentedScopes, authResponse.scopes); + if (authResponse.account && authResponse.account.localAccountId !== profile?.user?.id) { + dispatch(getProfileInfo()); + } + dispatch( + setQueryResponseStatus({ + statusText: translateMessage('Success'), + status: translateMessage('Scope consent successful'), + ok: true, + messageType: MessageBarType.success + }) + ); + dispatch(fetchAllPrincipalGrants()); + return validatedScopes; + } + } catch (error: unknown) { + const { errorCode } = error as BrowserAuthError; + dispatch( + setQueryResponseStatus({ + statusText: translateMessage('Scope consent failed'), + status: errorCode, + ok: false, + messageType: MessageBarType.error, + hint: getConsentAuthErrorHint(errorCode) + }) + ); + } + } +); + +export function signIn() { + return (dispatch: AppDispatch) => dispatch(getAuthTokenSuccess()); +} + +export function storeScopes(consentedScopes: string[]) { + return (dispatch: AppDispatch) => dispatch(getConsentedScopesSuccess(consentedScopes)); +} + +export default authSlice.reducer; diff --git a/src/app/services/slices/autocomplete.slice.ts b/src/app/services/slices/autocomplete.slice.ts new file mode 100644 index 000000000..082a441f4 --- /dev/null +++ b/src/app/services/slices/autocomplete.slice.ts @@ -0,0 +1,63 @@ +import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; + +import { SignContext, suggestions } from '../../../modules/suggestions'; +import { ApplicationState } from '../../../store'; +import { IAutocompleteResponse } from '../../../types/auto-complete'; +import { IParsedOpenApiResponse } from '../../../types/open-api'; + +export const fetchAutoCompleteOptions = createAsyncThunk( + 'autoComplete/fetch', + async (arg: { url: string, version: string, context?: SignContext }, { getState, rejectWithValue }) => { + const { url, version, context = 'paths' } = arg; + const state = getState() as ApplicationState; + + try { + const devxApiUrl = state.devxApi.baseUrl; + const resources = Object.keys(state.resources.data).length > 0 ? state.resources.data[version] : undefined; + const autoOptions = await suggestions.getSuggestions( + url, + devxApiUrl, + version, + context, + resources + ); + return autoOptions; + } catch (error) { + return rejectWithValue(error); + } + } +); + +const initialState: IAutocompleteResponse = { + pending: false, + data: null, + error: null +}; + +const autoCompleteSlice = createSlice({ + name: 'autoComplete', + initialState, + reducers: {}, + extraReducers: (builder) => { + builder + .addCase(fetchAutoCompleteOptions.pending, (state) => { + state.pending = true; + state.data = null; + state.error = null; + }) + .addCase(fetchAutoCompleteOptions.fulfilled, (state, action) => { + state.pending = false; + state.data = action.payload as IParsedOpenApiResponse; + state.error = null; + }) + .addCase(fetchAutoCompleteOptions.rejected, (state, action) => { + state.pending = false; + state.data = null; + if (action.payload) { + state.error = action.payload as Error; + } + }); + } +}); + +export default autoCompleteSlice.reducer; diff --git a/src/app/services/slices/collections.slice.ts b/src/app/services/slices/collections.slice.ts new file mode 100644 index 000000000..b7c5a37b6 --- /dev/null +++ b/src/app/services/slices/collections.slice.ts @@ -0,0 +1,38 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { Collection, ResourcePath } from '../../../types/resources'; + +const initialState: Collection[] = []; + +const collections = createSlice({ + name: 'collections', + initialState, + reducers: { + createCollection: (state, action: PayloadAction) => { + state.push(action.payload); + return state + }, + addResourcePaths:(state, action: PayloadAction) => { + const index = state.findIndex(collection => collection.isDefault); + if (index > -1) { + state[index].paths.push(...action.payload) + } + }, + removeResourcePaths: (state, action: PayloadAction)=>{ + const index = state.findIndex(collection => collection.isDefault); + if(index > -1) { + const defaultResourcePaths = [...state[index].paths]; + action.payload.forEach((resourcePath: ResourcePath)=>{ + const delIndex = defaultResourcePaths.findIndex(p=>p.key === resourcePath.key) + if (delIndex > -1) { + defaultResourcePaths.splice(delIndex, 1) + } + }) + state[index].paths = defaultResourcePaths; + } + } + } +}) + +export const {createCollection, addResourcePaths, removeResourcePaths} = collections.actions + +export default collections.reducer \ No newline at end of file diff --git a/src/app/services/slices/devxapi.slice.ts b/src/app/services/slices/devxapi.slice.ts new file mode 100644 index 000000000..94a1d6570 --- /dev/null +++ b/src/app/services/slices/devxapi.slice.ts @@ -0,0 +1,18 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { IDevxAPI } from '../../../types/devx-api'; + +const initialState: IDevxAPI = { + baseUrl: process.env.REACT_APP_DEVX_API_URL || '', + parameters: '' +}; + +const devxApi = createSlice({ + name: 'devxApi', + initialState, + reducers: { + setDevxApiUrl: (state, action: PayloadAction) => state = action.payload + } +}) + +export const { setDevxApiUrl } = devxApi.actions; +export default devxApi.reducer; diff --git a/src/app/services/slices/dimensions.slice.ts b/src/app/services/slices/dimensions.slice.ts new file mode 100644 index 000000000..05d9358e6 --- /dev/null +++ b/src/app/services/slices/dimensions.slice.ts @@ -0,0 +1,32 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { IDimensions } from '../../../types/dimensions'; + +const initialState: IDimensions = { + request: { + width: '100%', + height: '38vh' + }, + response: { + width: '100%', + height: '50vh' + }, + sidebar: { + width: '28%', + height: '' + }, + content: { + width: '72%', + height: '100%' + } +}; + +const dimensions = createSlice({ + name: 'dimensions', + initialState, + reducers: { + setDimensions: (state, action: PayloadAction) => state = action.payload + } +}) + +export const { setDimensions } = dimensions.actions; +export default dimensions.reducer; \ No newline at end of file diff --git a/src/app/services/slices/explorer-mode.slice.ts b/src/app/services/slices/explorer-mode.slice.ts new file mode 100644 index 000000000..3506af4e1 --- /dev/null +++ b/src/app/services/slices/explorer-mode.slice.ts @@ -0,0 +1,13 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { Mode } from '../../../types/enums'; + +const graphExplorerMode = createSlice({ + name: 'graphExplorerMode', + initialState: Mode.Complete, + reducers: { + setGraphExplorerMode: (_, action: PayloadAction) => action.payload + } +}) + +export const { setGraphExplorerMode } = graphExplorerMode.actions +export default graphExplorerMode.reducer; \ No newline at end of file diff --git a/src/app/services/slices/graph-response.slice.ts b/src/app/services/slices/graph-response.slice.ts new file mode 100644 index 000000000..5c81b1caa --- /dev/null +++ b/src/app/services/slices/graph-response.slice.ts @@ -0,0 +1,261 @@ +import { BrowserAuthError } from '@azure/msal-browser'; +import { MessageBarType } from '@fluentui/react'; +import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'; + +import { authenticationWrapper } from '../../../modules/authentication'; +import { ClaimsChallenge } from '../../../modules/authentication/ClaimsChallenge'; +import { historyCache } from '../../../modules/cache/history-utils'; +import { ApplicationState } from '../../../store'; +import { ContentType } from '../../../types/enums'; +import { IHistoryItem } from '../../../types/history'; +import { IGraphResponse } from '../../../types/query-response'; +import { IQuery } from '../../../types/query-runner'; +import { IStatus } from '../../../types/status'; +import { ClientError } from '../../utils/error-utils/ClientError'; +import { setStatusMessage } from '../../utils/status-message'; +import { translateMessage } from '../../utils/translate-messages'; +import { + anonymousRequest, + authenticatedRequest, + generateResponseDownloadUrl, + isFileResponse, + isImageResponse, + parseResponse, + queryResultsInCorsError +} from '../actions/query-action-creator-util'; +import { LOGOUT_SUCCESS } from '../redux-constants'; +import { addHistoryItem } from './history.slice'; +import { setQueryResponseStatus } from './query-status.slice'; + +const MAX_NUMBER_OF_RETRIES = 3; +let CURRENT_RETRIES = 0; + +interface Result { + body: any; + headers: { [key: string]: string }; +} + +const initialState: IGraphResponse = { + isLoadingData: false, + response: { + body: undefined, + headers: undefined + } +}; + +export const runQuery = createAsyncThunk( + 'query/runQuery', + async (query: IQuery, { dispatch, getState, rejectWithValue }) => { + const state = getState() as ApplicationState; + const tokenPresent = !!state?.auth?.authToken?.token; + const respHeaders: { [key: string]: string } = {}; + const createdAt = new Date().toISOString(); + + try { + const response: Response = tokenPresent + ? await authenticatedRequest(query) + : await anonymousRequest(query, getState); + + const result: Result = await processResponse(response, respHeaders, dispatch, query); + + const duration = new Date().getTime() - new Date(createdAt).getTime(); + const status = generateStatus({ duration, response }); + dispatch(setQueryResponseStatus(status)); + + const historyItem = generateHistoryItem(status, respHeaders, + query, createdAt, result, duration); + dispatch(addHistoryItem(historyItem)); + + return result; + } catch (err: unknown) { + const error = err as Error; + const { status, body } = await handleError(error, query); + dispatch(setQueryResponseStatus(status)); + if (body) { + return rejectWithValue({ body, headers: {} }); + } + } + } +); + +const querySlice = createSlice({ + name: 'query', + initialState, + reducers: { + setQueryResponse(state, action: PayloadAction) { + state.isLoadingData = false; + state.response = { + body: action.payload.body, + headers: action.payload.headers + }; + } + }, + extraReducers: (builder) => { + builder + .addCase(runQuery.pending, (state) => { + state.isLoadingData = true; + state.response = { + body: undefined, + headers: undefined + }; + }) + .addCase(runQuery.rejected, (state, action) => { + state.isLoadingData = false; + const actionPayload = action.payload as Result; + state.response = { + body: actionPayload.body!, + headers: actionPayload.headers + }; + }) + .addCase(LOGOUT_SUCCESS, (state) => { + state.isLoadingData = false; + state.response = { + body: undefined, + headers: undefined + }; + }) + .addCase(runQuery.fulfilled, (state, action) => { + state.isLoadingData = false; + if (action.payload) { + const actionPayload = action.payload as Result; + state.response = { + body: actionPayload.body, + headers: actionPayload.headers + }; + } + }); + } +}); + +export const { setQueryResponse } = querySlice.actions; +export default querySlice.reducer; + +async function processResponse(response: Response, respHeaders: { [key: string]: string }, + dispatch: Function, query: IQuery): Promise { + let result = await parseResponse(response, respHeaders); + if (response && response.ok) { + CURRENT_RETRIES = 0; + if (isFileResponse(respHeaders)) { + const contentDownloadUrl = await generateResponseDownloadUrl(response, respHeaders); + if (contentDownloadUrl) { + result = { contentDownloadUrl }; + } + } + } + + if (response && response.status === 401 && CURRENT_RETRIES < MAX_NUMBER_OF_RETRIES) { + const successful = await runReAuthenticatedRequest(response, query); + if (successful) { + dispatch(runQuery(query)); + return { body: null, headers: {} }; // returning an empty object for the original request + } + } + + return { body: result, headers: respHeaders }; +} + +const generateStatus = ({ duration, response }: { duration: number; response: Response }): IStatus => { + const status: IStatus = { + messageType: MessageBarType.error, + ok: false, + duration, + status: response.status || 400, + statusText: '' + }; + + if (response) { + status.status = response.status; + status.statusText = response.statusText === '' ? setStatusMessage(response.status) : response.statusText; + } + + if (response && response.ok) { + CURRENT_RETRIES = 0; + status.ok = true; + status.messageType = MessageBarType.success; + } + return status; +} + +async function runReAuthenticatedRequest(response: Response, query: IQuery): Promise { + if (response.headers.get('www-authenticate')) { + const account = authenticationWrapper.getAccount(); + if (!account) { return false; } + new ClaimsChallenge(query, account).handle(response.headers); + const authResult = await authenticationWrapper.logIn('', query); + if (authResult.accessToken) { + CURRENT_RETRIES += 1; + return true; + } + } + return false; +} + +function generateHistoryItem( + status: IStatus, + respHeaders: { [key: string]: string }, + query: IQuery, + createdAt: string, + result: Result, + duration: number +): IHistoryItem { + let response = { ...result }; + const responseHeaders = { ...respHeaders }; + const contentType = respHeaders['content-type']; + + if (isImageResponse(contentType)) { + response = { ...response, body: 'Run the query to view the image' }; + responseHeaders['content-type'] = ContentType.Json; + } + + if (isFileResponse(respHeaders)) { + response = { ...response, body: 'Run the query to generate file download URL' }; + } + + const historyItem: IHistoryItem = { + index: -1, + url: query.sampleUrl, + method: query.selectedVerb, + headers: query.sampleHeaders, + body: query.sampleBody, + responseHeaders, + createdAt, + status: status.status as number, + statusText: status.statusText, + duration, + result: response.body + }; + + historyCache.writeHistoryData(historyItem); + return historyItem; +} + +async function handleError(error: Error, query: IQuery) { + let body = null; + const status: IStatus = { + messageType: MessageBarType.error, + ok: false, + status: 400, + statusText: 'Bad Request' + }; + + if (error instanceof ClientError) { + status.status = error.message; + status.statusText = error.name; + } + + if (queryResultsInCorsError(query.sampleUrl)) { + status.status = 0; + status.statusText = 'CORS error'; + body = { throwsCorsError: true }; + } + + if (error instanceof BrowserAuthError) { + if (error.errorCode === 'user_cancelled') { + status.hint = translateMessage('user_cancelled'); + } else { + status.statusText = `${error.name}: ${error.message}`; + } + } + + return { status, body }; +} diff --git a/src/app/services/slices/history.slice.ts b/src/app/services/slices/history.slice.ts new file mode 100644 index 000000000..f9a609293 --- /dev/null +++ b/src/app/services/slices/history.slice.ts @@ -0,0 +1,33 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; + +import { IHistoryItem } from '../../../types/history'; + +const initialState: IHistoryItem[] = []; + +const historySlice = createSlice({ + name: 'history', + initialState, + reducers: { + addHistoryItem(state, action: PayloadAction) { + state.push(action.payload); + }, + bulkAddHistoryItems(state, action: PayloadAction) { + state.push(...action.payload); + }, + removeHistoryItem(state, action: PayloadAction) { + return state.filter(item => item.createdAt !== action.payload.createdAt); + }, + removeAllHistoryItems(state, action: PayloadAction) { + return state.filter(item => !action.payload.includes(item.createdAt)); + } + } +}); + +export const { + addHistoryItem, + bulkAddHistoryItems, + removeHistoryItem, + removeAllHistoryItems +} = historySlice.actions; + +export default historySlice.reducer; diff --git a/src/app/services/slices/index.ts b/src/app/services/slices/index.ts new file mode 100644 index 000000000..1e7f89a2d --- /dev/null +++ b/src/app/services/slices/index.ts @@ -0,0 +1 @@ +export type Status = 'idle' | 'loading' | 'succeeded' | 'failed'; diff --git a/src/app/services/slices/permission-grants.slice.ts b/src/app/services/slices/permission-grants.slice.ts new file mode 100644 index 000000000..6c0623fea --- /dev/null +++ b/src/app/services/slices/permission-grants.slice.ts @@ -0,0 +1,122 @@ +import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'; + +import { ApplicationState } from '../../../store'; +import { IPermissionGrant, PermissionGrantsState } from '../../../types/permissions'; +import { IUser } from '../../../types/profile'; +import { translateMessage } from '../../utils/translate-messages'; +import { RevokePermissionsUtil } from '../actions/permissions-action-creator.util'; +import { setQueryResponseStatus } from '../slices/query-status.slice'; + +const initialState: PermissionGrantsState = { + pending: false, + error: null, + permissions: [] +}; + +export const fetchAllPrincipalGrants = createAsyncThunk( + 'permissionGrants/fetchAllPrincipalGrants', + async (_, { dispatch, getState, rejectWithValue }) => { + try { + const state = getState() as ApplicationState; + const { auth: { consentedScopes }, profile } = state; + const revokePermissionUtil = await RevokePermissionsUtil.initialize(profile.user!.id); + + if (revokePermissionUtil && revokePermissionUtil.getGrantsPayload() !== null) { + const servicePrincipalAppId = revokePermissionUtil.getServicePrincipalAppId(); + const requestCounter = 0; + + const permissions = await checkScopesConsentType(servicePrincipalAppId, revokePermissionUtil, + consentedScopes, profile.user!, requestCounter); + return permissions; + + } else { + dispatch(setQueryResponseStatus({ + statusText: translateMessage('Permissions'), + status: translateMessage('You require the following permissions to read'), + ok: false, + messageType: 0 + })); + throw new Error('Permission required'); + } + } catch (err: unknown) { + const error = err as Error; + return rejectWithValue(error.message); + } + } +); + +const permissionGrantsSlice = createSlice({ + name: 'permissionGrants', + initialState, + reducers: {}, + extraReducers: (builder) => { + builder + .addCase(fetchAllPrincipalGrants.pending, (state) => { + state.pending = true; + state.error = null; + }) + .addCase(fetchAllPrincipalGrants.fulfilled, (state, action: PayloadAction) => { + state.pending = false; + state.permissions = action.payload; + }) + .addCase(fetchAllPrincipalGrants.rejected, (state) => { + state.pending = false; + }); + } +}); + + +const allScopesHaveConsentType = (consentedScopes: string[], permissions: IPermissionGrant[], id: string) => { + const allPrincipalGrants: string[] = getAllPrincipalGrant(permissions); + const singlePrincipalGrants: string[] = getSinglePrincipalGrant(permissions, id); + const combinedPermissions = [...allPrincipalGrants, ...singlePrincipalGrants]; + return consentedScopes.every(scope => combinedPermissions.includes(scope)); +} + +export const getAllPrincipalGrant = (tenantWideGrant: IPermissionGrant[]): string[] => { + if (tenantWideGrant) { + const allGrants = tenantWideGrant; + if (allGrants) { + const principalGrant = allGrants.find(grant => grant.consentType === 'AllPrincipals'); + if (principalGrant) { + return principalGrant.scope.split(' '); + } + } + } + return []; +} + +export const getSinglePrincipalGrant = (tenantWideGrant: IPermissionGrant[], principalId: string): string[] => { + if (tenantWideGrant && principalId) { + const allGrants = tenantWideGrant; + const singlePrincipalGrant = allGrants.find(grant => grant.principalId === principalId); + if (singlePrincipalGrant) { + return singlePrincipalGrant.scope.split(' '); + } + } + return []; +} + +async function checkScopesConsentType(servicePrincipalAppId: string, revokePermissionUtil: RevokePermissionsUtil, + consentedScopes: string[], profile: IUser, requestCounter: number) { + if (servicePrincipalAppId) { + let grantsPayload = revokePermissionUtil.getGrantsPayload(); + if (grantsPayload) { + if (!allScopesHaveConsentType(consentedScopes, grantsPayload.value, profile.id)) { + while (requestCounter < 10 && profile && profile.id && + !allScopesHaveConsentType(consentedScopes, grantsPayload.value, profile.id)) { + requestCounter += 1; + await new Promise((resolve) => setTimeout(resolve, 400 * requestCounter)); + revokePermissionUtil = await RevokePermissionsUtil.initialize(profile.id); + grantsPayload = revokePermissionUtil.getGrantsPayload(); + } + return grantsPayload.value; + } else { + return grantsPayload.value; + } + } + } + return []; +} + +export default permissionGrantsSlice.reducer; \ No newline at end of file diff --git a/src/app/services/slices/profile.slice.ts b/src/app/services/slices/profile.slice.ts new file mode 100644 index 000000000..e67bab7bf --- /dev/null +++ b/src/app/services/slices/profile.slice.ts @@ -0,0 +1,57 @@ +import { createAsyncThunk, createSlice } from '@reduxjs/toolkit' +import { IProfileState, IUser } from '../../../types/profile'; +import { + getProfileInformation, + getBetaProfile, + getProfileImage, + getTenantInfo} from '../actions/profile-actions'; + +const initialState: IProfileState = { + status: 'unset', + user: undefined, + error: undefined +} + +const getProfileInfo = createAsyncThunk( + 'profile/getProfileInfo', + async (_, {rejectWithValue}) => { + try { + const user: IUser = await getProfileInformation(); + const { profileType, ageGroup } = await getBetaProfile(); + user.profileType = profileType; + user.ageGroup = ageGroup; + user.profileImageUrl = await getProfileImage(); + user.tenant = await getTenantInfo(profileType); + return user + } catch (error) { + rejectWithValue({ error }); + } + } +) + +const profile = createSlice({ + name:'profile', + initialState, + reducers:{}, + extraReducers: (builder)=>{ + builder + .addCase(getProfileInfo.pending, (state)=>{ + state.status = 'unset' + state.error = undefined + state.user = undefined + }) + .addCase(getProfileInfo.fulfilled, (state, action) => { + state.status = 'success' + state.user = action.payload + }) + .addCase(getProfileInfo.rejected, (state, action) =>{ + state.error = action.error as Error + state.status = 'error' + state.user = undefined + + }) + } +}) + +export {getProfileInfo}; +export default profile.reducer; \ No newline at end of file diff --git a/src/app/services/slices/proxy.slice.ts b/src/app/services/slices/proxy.slice.ts new file mode 100644 index 000000000..3d7091ac1 --- /dev/null +++ b/src/app/services/slices/proxy.slice.ts @@ -0,0 +1,40 @@ +import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { GRAPH_API_SANDBOX_ENDPOINT_URL, GRAPH_API_SANDBOX_URL } from '../graph-constants'; + +export const getGraphProxyUrl = createAsyncThunk( + 'proxyUrl/getGraphProxyUrl', + async (_, { rejectWithValue }) => { + try { + const response = await fetch(GRAPH_API_SANDBOX_ENDPOINT_URL); + if (!response.ok) { + throw new Error('Network response was not ok'); + } + return await response.json(); + } catch (error) { + return rejectWithValue(GRAPH_API_SANDBOX_URL); + } + } +); + +const proxyUrlSlice = createSlice({ + name: 'proxyUrl', + initialState: GRAPH_API_SANDBOX_URL, + reducers: { + setGraphProxyUrl: (_state, action: PayloadAction) => { + return action.payload; + } + }, + extraReducers: (builder) => { + builder.addCase(getGraphProxyUrl.fulfilled, (_state, action) => { + return action.payload as string; + }); + builder.addCase(getGraphProxyUrl.rejected, (_state, action) => { + if (action.payload) { + return action.payload as string; + } + }); + } +}); + +export const { setGraphProxyUrl } = proxyUrlSlice.actions; +export default proxyUrlSlice.reducer; diff --git a/src/app/services/slices/query-status.slice.ts b/src/app/services/slices/query-status.slice.ts new file mode 100644 index 000000000..5609dcb8d --- /dev/null +++ b/src/app/services/slices/query-status.slice.ts @@ -0,0 +1,28 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; + +import { IStatus } from '../../../types/status'; +import { LOGOUT_SUCCESS, QUERY_GRAPH_RUNNING } from '../redux-constants'; + +const queryRunnerStatusSlice = createSlice({ + name: 'queryRunnerStatus', + initialState: null as IStatus | null, + reducers: { + setQueryResponseStatus: (_state, action: PayloadAction) => { + return action.payload; + }, + clearQueryStatus: () => { + return null; + } + }, + extraReducers: (builder) => { + builder.addCase(QUERY_GRAPH_RUNNING, () => { + return null; + }); + builder.addCase(LOGOUT_SUCCESS, () => { + return null; + }); + } +}); + +export const { setQueryResponseStatus, clearQueryStatus } = queryRunnerStatusSlice.actions; +export default queryRunnerStatusSlice.reducer; diff --git a/src/app/services/slices/resources.slice.ts b/src/app/services/slices/resources.slice.ts new file mode 100644 index 000000000..7e2d84a6d --- /dev/null +++ b/src/app/services/slices/resources.slice.ts @@ -0,0 +1,87 @@ +import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'; + +import { resourcesCache } from '../../../modules/cache/resources.cache'; +import { ApplicationState } from '../../../store'; +import { IRequestOptions } from '../../../types/request'; +import { IResource, IResources } from '../../../types/resources'; + +const initialState: IResources = { + pending: false, + data: {}, + error: null +}; + +export const fetchResources = createAsyncThunk( + 'resources/fetchResources', async (_, { getState, rejectWithValue }) => { + const { devxApi } = getState() as ApplicationState; + const resourcesUrl = `${devxApi.baseUrl}/openapi/tree`; + const v1Url = resourcesUrl + '?graphVersions=v1.0'; + const betaUrl = resourcesUrl + '?graphVersions=beta'; + + const headers = { + 'Content-Type': 'application/json' + } + + const options: IRequestOptions = { headers }; + + try { + const v1CachedResources = await resourcesCache.readResources('v1.0'); + const betaCachedResources = await resourcesCache.readResources('beta'); + + if (v1CachedResources && betaCachedResources) { + return { + 'v1.0': v1CachedResources, + beta: betaCachedResources + }; + } else { + const [v1Response, betaResponse] = await Promise.all([ + fetch(v1Url, options), + fetch(betaUrl, options) + ]); + + if (v1Response.ok && betaResponse.ok) { + const [v1Data, betaData] = await Promise.all([ + v1Response.json(), + betaResponse.json() + ]); + + resourcesCache.saveResources(v1Data as IResource, 'v1.0'); + resourcesCache.saveResources(betaData as IResource, 'beta'); + + return { + 'v1.0': v1Data, + beta: betaData + } + } else { + throw new Error('Failed to fetch resources'); + } + } + } catch (err) { + const error = err as Error; + return rejectWithValue(error); + } + }); + +const resourcesSlice = createSlice({ + name: 'resources', + initialState, + reducers: {}, + extraReducers: (builder) => { + builder + .addCase(fetchResources.pending, (state) => { + state.pending = true; + state.error = null; + }) + .addCase(fetchResources.fulfilled, (state, action: PayloadAction<{ [version: string]: IResource }>) => { + state.pending = false; + state.data = action.payload; + state.error = null; + }) + .addCase(fetchResources.rejected, (state, action) => { + state.pending = false; + state.error = action.payload as Error; + }); + } +}); + +export default resourcesSlice.reducer; diff --git a/src/app/services/slices/response-area-expanded.slice.ts b/src/app/services/slices/response-area-expanded.slice.ts new file mode 100644 index 000000000..73cf8aa1a --- /dev/null +++ b/src/app/services/slices/response-area-expanded.slice.ts @@ -0,0 +1,12 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; + +const responseAreaExpandedSlice = createSlice({ + name: 'responseAreaExpanded', + initialState: false, + reducers: { + expandResponseArea: (_, action: PayloadAction) => action.payload + } +}) + +export const { expandResponseArea } = responseAreaExpandedSlice.actions +export default responseAreaExpandedSlice.reducer; \ No newline at end of file diff --git a/src/app/services/slices/sample-query.slice.ts b/src/app/services/slices/sample-query.slice.ts new file mode 100644 index 000000000..d7f727599 --- /dev/null +++ b/src/app/services/slices/sample-query.slice.ts @@ -0,0 +1,23 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { IQuery } from '../../../types/query-runner'; + +const initialState: IQuery = { + selectedVerb: 'GET', + sampleHeaders: [], + sampleUrl: 'https://graph.microsoft.com/v1.0/me', + sampleBody: undefined, + selectedVersion: 'v1.0' +} +const sampleQuery = createSlice({ + name: 'sampleQuery', + initialState, + reducers: { + setSampleQuery: (state, action: PayloadAction)=>{ + state = action.payload + return state + } + } +}) + +export const {setSampleQuery} = sampleQuery.actions +export default sampleQuery.reducer; diff --git a/src/app/services/slices/samples.slice.ts b/src/app/services/slices/samples.slice.ts new file mode 100644 index 000000000..be45b120e --- /dev/null +++ b/src/app/services/slices/samples.slice.ts @@ -0,0 +1,76 @@ +import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; + +import { samplesCache } from '../../../modules/cache/samples.cache'; +import { ApplicationState } from '../../../store'; +import { ISampleQuery } from '../../../types/query-runner'; +import { queries } from '../../views/sidebar/sample-queries/queries'; + +interface SamplesState { + queries: ISampleQuery[]; + pending: boolean; + error: object | null | string; +} + +const initialState: SamplesState = { + queries: [], + pending: false, + error: null +}; + +export const fetchSamples = createAsyncThunk( + 'samples/fetchSamples', + async (_, { getState, rejectWithValue }) => { + const state = getState() as ApplicationState; + const { devxApi } = state; + let samplesUrl = `${devxApi.baseUrl}/samples`; + + samplesUrl = devxApi.parameters + ? `${samplesUrl}?${devxApi.parameters}` + : `${samplesUrl}`; + + const headers = { + 'Content-Type': 'application/json' + }; + + try { + const response = await fetch(samplesUrl, { headers }); + if (!response.ok) { + throw response; + } + const res = await response.json(); + return res.sampleQueries; + } catch (error) { + let cachedSamples = await samplesCache.readSamples(); + if (cachedSamples.length === 0) { + cachedSamples = queries; + } + return rejectWithValue(cachedSamples); + } + } +); + +const samplesSlice = createSlice({ + name: 'samples', + initialState, + reducers: {}, + extraReducers: (builder) => { + builder + .addCase(fetchSamples.pending, (state) => { + state.pending = true; + state.error = null; + }) + .addCase(fetchSamples.fulfilled, (state, action) => { + state.pending = false; + state.queries = action.payload; + }) + .addCase(fetchSamples.rejected, (state, action) => { + if (action.payload) { + state.queries = action.payload; + } + state.pending = false; + state.error = 'failed'; + }); + } +}); + +export default samplesSlice.reducer; diff --git a/src/app/services/slices/scopes.slice.ts b/src/app/services/slices/scopes.slice.ts new file mode 100644 index 000000000..acac3aed6 --- /dev/null +++ b/src/app/services/slices/scopes.slice.ts @@ -0,0 +1,105 @@ +import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; + +import { IPermission, IScopes } from '../../../types/permissions'; +import { IRequestOptions } from '../../../types/request'; +import { ApplicationState } from '../../../store'; +import { ScopesError } from '../../utils/error-utils/ScopesError'; +import { getPermissionsScopeType } from '../../utils/getPermissionsScopeType'; +import { sanitizeQueryUrl } from '../../utils/query-url-sanitization'; +import { parseSampleUrl } from '../../utils/sample-url-generation'; + +type ScopesFetchType = 'full' | 'query'; + +const initialState: IScopes = { + pending: { + isSpecificPermissions: false, + isFullPermissions: false + }, + data: { + specificPermissions: [], + fullPermissions: [] + }, + error: null +} + +export const fetchScopes = createAsyncThunk( + 'scopes/fetchScopes', + async (scopesFetchType: ScopesFetchType = 'full', { getState, rejectWithValue }) => { + const state = getState() as ApplicationState; + const { devxApi, profile, sampleQuery: query } = state; + const scopeType = getPermissionsScopeType(profile.user!); + let permissionsUrl = `${devxApi.baseUrl}/permissions?scopeType=${scopeType}`; + + if (scopesFetchType === 'query') { + const signature = sanitizeQueryUrl(query.sampleUrl); + const { requestUrl, sampleUrl } = parseSampleUrl(signature); + + if (!sampleUrl) { + throw new Error('url is invalid'); + } + + permissionsUrl = `${permissionsUrl}&requesturl=/${requestUrl}&method=${query.selectedVerb}`; + } + + if (devxApi && devxApi?.parameters) { + permissionsUrl = `${permissionsUrl}&${devxApi?.parameters!}`; + } + + const headers = { + 'Content-Type': 'application/json' + }; + + const options: IRequestOptions = { headers }; + try { + const response = await fetch(permissionsUrl, options); + if (response.ok) { + const scopes = await response.json() as IPermission[]; + + if (scopesFetchType === 'full') { + return { scopes: { fullPermissions: scopes } }; + } else { + return { scopes: { specificPermissions: scopes } }; + } + } else { + throw new ScopesError({ + url: permissionsUrl, + message: scopesFetchType === 'full' ? 'Cannot get full scopes': 'Cannot get url specific scopes', + status: response.status, + messageType: 1 + }); + } + } catch (error: unknown) { + return rejectWithValue(error as ScopesError); + } + } +); + +const scopesSlice = createSlice({ + name: 'scopes', + initialState, + reducers: {}, + extraReducers: (builder) => { + builder + .addCase(fetchScopes.pending, (state, action) => { + state.pending = action.meta.arg === 'full' + ? { ...state.pending, isFullPermissions: true } + : { ...state.pending, isSpecificPermissions: true }; + state.error = null; + }) + .addCase(fetchScopes.fulfilled, (state, action) => { + if (action.meta.arg === 'full') { + state.data.fullPermissions = action.payload.scopes.fullPermissions || []; + } else { + state.data.specificPermissions = action.payload.scopes.specificPermissions || []; + } + state.pending = initialState.pending; + }) + .addCase(fetchScopes.rejected, (state, action) => { + state.pending = initialState.pending; + state.data = initialState.data; + state.error = action.payload as ScopesError; + }); + } +}); + +export default scopesSlice.reducer; \ No newline at end of file diff --git a/src/app/services/slices/sidebar-properties.slice.ts b/src/app/services/slices/sidebar-properties.slice.ts new file mode 100644 index 000000000..2fbcae67d --- /dev/null +++ b/src/app/services/slices/sidebar-properties.slice.ts @@ -0,0 +1,50 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; + +import { ISidebarProps } from '../../../types/sidebar'; +import { + QUERY_GRAPH_RUNNING, QUERY_GRAPH_SUCCESS, + SET_SAMPLE_QUERY_SUCCESS +} from '../redux-constants'; + +const initialState: ISidebarProps = { + showSidebar: false, + mobileScreen: false +}; + +const sidebarPropertiesSlice = createSlice({ + name: 'sidebarProperties', + initialState, + reducers: { + toggleSidebar: (_, action: PayloadAction) => action.payload + }, + extraReducers: (builder) => { + builder + .addCase(QUERY_GRAPH_RUNNING, (state) => { + if (state.mobileScreen) { + return { + ...state, + showSidebar: false + } + } + }) + .addCase(SET_SAMPLE_QUERY_SUCCESS, (state) => { + if (state.mobileScreen) { + return { + ...state, + showSidebar: false + } + } + }) + .addCase(QUERY_GRAPH_SUCCESS, (state) => { + if (state.mobileScreen) { + return { + ...state, + showSidebar: false + } + } + }) + } +}) + +export const { toggleSidebar } = sidebarPropertiesSlice.actions +export default sidebarPropertiesSlice.reducer; \ No newline at end of file diff --git a/src/app/services/slices/snippet.slice.ts b/src/app/services/slices/snippet.slice.ts new file mode 100644 index 000000000..2cdf50d07 --- /dev/null +++ b/src/app/services/slices/snippet.slice.ts @@ -0,0 +1,104 @@ +import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'; + +import { ApplicationState } from '../../../store'; +import { IRequestOptions } from '../../../types/request'; +import { ISnippet } from '../../../types/snippets'; +import { parseSampleUrl } from '../../utils/sample-url-generation'; +import { constructHeaderString } from '../../utils/snippet.utils'; + +const initialState: ISnippet = { + pending: false, + data: {}, + error: null, + snippetTab: 'csharp' +}; + +export const getSnippet = createAsyncThunk( + 'snippet/getSnippet', + async (language, { getState, rejectWithValue }) => { + const { devxApi, sampleQuery } = getState() as ApplicationState; + + try { + let snippetsUrl = `${devxApi.baseUrl}/api/graphexplorersnippets`; + + const { requestUrl, sampleUrl, queryVersion, search } = parseSampleUrl( + sampleQuery.sampleUrl + ); + + if (!sampleUrl) { + throw new Error('url is invalid'); + } + + if (language !== 'csharp') { + snippetsUrl += `?lang=${language}`; + } + + const openApiSnippets: string[] = ['go', 'powershell', 'python', 'cli', 'php']; + if (openApiSnippets.includes(language)) { + snippetsUrl += '&generation=openapi'; + } + + const method = 'POST'; + const headers = { + 'Content-Type': 'application/http' + }; + + const requestBody = + sampleQuery.sampleBody && Object.keys(sampleQuery.sampleBody).length !== 0 + ? JSON.stringify(sampleQuery.sampleBody) + : ''; + + const httpVersion = 'HTTP/1.1'; + const host = 'Host: graph.microsoft.com'; + const sampleHeaders = constructHeaderString(sampleQuery); + + // eslint-disable-next-line max-len + let body = `${sampleQuery.selectedVerb} /${queryVersion}/${requestUrl + search} ${httpVersion}\r\n${host}\r\n${sampleHeaders}\r\n\r\n`; + if (sampleQuery.selectedVerb !== 'GET') { + body += `${requestBody}`; + } + + const options: IRequestOptions = { method, headers, body }; + + const response = await fetch(snippetsUrl, options); + if (response.ok) { + const result = await response.text(); + return { [language]: result }; + } + throw new Error(response.statusText); + } catch (err: unknown) { + const error = err as Error; + return rejectWithValue({ error: error.message, language }); + } + }); + +const snippetSlice = createSlice({ + name: 'snippet', + initialState, + reducers: { + setSnippetTabSuccess(state, action: PayloadAction) { + state.snippetTab = action.payload; + } + }, + extraReducers: (builder) => { + builder + .addCase(getSnippet.pending, (state) => { + state.pending = true; + state.error = null; + state.data = {}; + }) + .addCase(getSnippet.fulfilled, (state, action) => { + state.pending = false; + state.data = action.payload; + state.error = null; + }) + .addCase(getSnippet.rejected, (state, action) => { + state.pending = false; + state.error = action.payload as object; + state.data = {}; + }); + } +}); + +export const { setSnippetTabSuccess } = snippetSlice.actions; +export default snippetSlice.reducer; diff --git a/src/app/services/slices/terms-of-use.slice.ts b/src/app/services/slices/terms-of-use.slice.ts new file mode 100644 index 000000000..0181c2e36 --- /dev/null +++ b/src/app/services/slices/terms-of-use.slice.ts @@ -0,0 +1,12 @@ +import { createSlice } from '@reduxjs/toolkit'; + +const termsOfUseSlice = createSlice({ + name: 'termsOfUse', + initialState: true, + reducers: { + clearTermsOfUse: () => { return false } + } +}) + +export const { clearTermsOfUse } = termsOfUseSlice.actions +export default termsOfUseSlice.reducer; \ No newline at end of file diff --git a/src/app/services/slices/theme.slice.ts b/src/app/services/slices/theme.slice.ts new file mode 100644 index 000000000..63d6bc142 --- /dev/null +++ b/src/app/services/slices/theme.slice.ts @@ -0,0 +1,12 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; + +const theme = createSlice({ + name: 'theme', + initialState: 'light', + reducers: { + changeTheme: (_, action: PayloadAction)=> action.payload + } +}) + +export const {changeTheme} = theme.actions +export default theme.reducer; \ No newline at end of file diff --git a/src/app/utils/error-utils/ScopesError.ts b/src/app/utils/error-utils/ScopesError.ts new file mode 100644 index 000000000..92b28e31a --- /dev/null +++ b/src/app/utils/error-utils/ScopesError.ts @@ -0,0 +1,25 @@ +import { ClientError } from './ClientError'; + +interface IScopesError { + url: string; + message: string; + messageType: number; + status: number; +} + +export class ScopesError extends ClientError { + message: string; + messageType: number; + status: number; + url: string; + + constructor(error: IScopesError = { url: '', message: '', messageType: 0, status: 0 }) { + super(); + Object.assign(this, error); + this.name = 'ScopesError'; + this.url = error.url; + this.message = error.message; + this.messageType = error.messageType; + this.status = error.status; + } +} \ No newline at end of file diff --git a/src/app/utils/getPermissionsScopeType.ts b/src/app/utils/getPermissionsScopeType.ts new file mode 100644 index 000000000..7874343c2 --- /dev/null +++ b/src/app/utils/getPermissionsScopeType.ts @@ -0,0 +1,9 @@ +import { IUser } from '../../types/profile'; +import { ACCOUNT_TYPE, PERMS_SCOPE } from '../services/graph-constants'; + +export function getPermissionsScopeType(profile: IUser | null | undefined) { + if (profile?.profileType === ACCOUNT_TYPE.MSA) { + return PERMS_SCOPE.PERSONAL; + } + return PERMS_SCOPE.WORK; +} diff --git a/src/app/utils/query-url-sanitization.ts b/src/app/utils/query-url-sanitization.ts index 8e07d5b72..1d46e11fe 100644 --- a/src/app/utils/query-url-sanitization.ts +++ b/src/app/utils/query-url-sanitization.ts @@ -159,5 +159,8 @@ function sanitizeQueryParameters(queryString: string): string { } export function encodeHashCharacters(query: IQuery): string { - return query.sampleUrl.replace(/#/g, '%2523'); + if (query.sampleUrl) { + return query.sampleUrl.replace(/#/g, '%2523'); + } + return ''; } diff --git a/src/app/utils/snippet.utils.ts b/src/app/utils/snippet.utils.ts new file mode 100644 index 000000000..5f0750eff --- /dev/null +++ b/src/app/utils/snippet.utils.ts @@ -0,0 +1,30 @@ +import { Header, IQuery } from '../../types/query-runner'; + +function constructHeaderString(sampleQuery: IQuery): string { + const { sampleHeaders, selectedVerb } = sampleQuery; + let headersString = ''; + + const isContentTypeInHeaders: boolean = !!sampleHeaders.find( + (header) => header.name.toLocaleLowerCase() === 'content-type' + ); + + if (sampleHeaders && sampleHeaders.length > 0) { + headersString = getHeaderStringProperties(sampleHeaders); + } + + headersString += + !isContentTypeInHeaders && selectedVerb !== 'GET' + ? 'Content-Type: application/json\r\n' + : ''; + return headersString; +} + +function getHeaderStringProperties(sampleHeaders: Header[]): string { + let constructedHeader = ''; + sampleHeaders.forEach((header: Header) => { + constructedHeader += `${header.name}: ${header.value}\r\n`; + }); + return constructedHeader; +} + +export { constructHeaderString }; \ No newline at end of file diff --git a/src/app/utils/status-message.ts b/src/app/utils/status-message.ts index 654cd7427..6eadb0058 100644 --- a/src/app/utils/status-message.ts +++ b/src/app/utils/status-message.ts @@ -9,8 +9,8 @@ export function replaceLinks(message: string): string { return message; } -export function convertArrayToObject(array: any[]): object { - const initialValue = {}; +export function convertArrayToObject(array: T[]): { [key: string]: T } { + const initialValue: { [key: string]: T } = {}; return array.reduce((obj, item, index) => { return { ...obj, diff --git a/src/app/views/App.tsx b/src/app/views/App.tsx index 605e4c142..51cbdaddf 100644 --- a/src/app/views/App.tsx +++ b/src/app/views/App.tsx @@ -1,36 +1,37 @@ import { Announced, getTheme, ITheme, styled } from '@fluentui/react'; +import { bindActionCreators, Dispatch } from '@reduxjs/toolkit'; import { Resizable } from 're-resizable'; import { Component } from 'react'; import { connect } from 'react-redux'; -import { bindActionCreators, Dispatch } from 'redux'; import { removeSpinners } from '../..'; import { authenticationWrapper } from '../../modules/authentication'; +import { ApplicationState } from '../../store'; import { componentNames, eventTypes, telemetry } from '../../telemetry'; import { loadGETheme } from '../../themes'; import { ThemeContext } from '../../themes/theme-context'; import { Mode } from '../../types/enums'; import { IInitMessage, IQuery, IThemeChangedMessage } from '../../types/query-runner'; -import { ApplicationState } from '../../types/root'; import { ISharedQueryParams } from '../../types/share-query'; import { ISidebarProps } from '../../types/sidebar'; -import * as authActionCreators from '../services/actions/auth-action-creators'; -import { setDimensions } from '../services/actions/dimensions-action-creator'; -import { runQuery } from '../services/actions/query-action-creators'; -import { setSampleQuery } from '../services/actions/query-input-action-creators'; -import { changeTheme } from '../services/actions/theme-action-creator'; -import { toggleSidebar } from '../services/actions/toggle-sidebar-action-creator'; import { PopupsProvider } from '../services/context/popups-context'; +import { ValidationProvider } from '../services/context/validation-context/ValidationProvider'; import { GRAPH_URL } from '../services/graph-constants'; +import { signIn, storeScopes } from '../services/slices/auth.slice'; +import { setDimensions } from '../services/slices/dimensions.slice'; +import { runQuery } from '../services/slices/graph-response.slice'; +import { setSampleQuery } from '../services/slices/sample-query.slice'; +import { toggleSidebar } from '../services/slices/sidebar-properties.slice'; +import { changeTheme } from '../services/slices/theme.slice'; import { parseSampleUrl } from '../utils/sample-url-generation'; import { substituteTokens } from '../utils/token-helpers'; import { translateMessage } from '../utils/translate-messages'; import { TermsOfUseMessage } from './app-sections'; -import { StatusMessages } from './common/lazy-loader/component-registry'; import { headerMessaging } from './app-sections/HeaderMessaging'; import { appStyles } from './App.styles'; import { classNames } from './classnames'; import { KeyboardCopyEvent } from './common/copy-button/KeyboardCopyEvent'; +import { StatusMessages } from './common/lazy-loader/component-registry'; import PopupsWrapper from './common/popups/PopupsWrapper'; import { createShareLink } from './common/share'; import { MainHeader } from './main-header/MainHeader'; @@ -38,7 +39,6 @@ import { QueryResponse } from './query-response'; import { QueryRunner } from './query-runner'; import { parse } from './query-runner/util/iframe-message-parser'; import { Sidebar } from './sidebar/Sidebar'; -import { ValidationProvider } from '../services/context/validation-context/ValidationProvider'; export interface IAppProps { theme?: ITheme; styles?: object; @@ -318,9 +318,17 @@ class App extends Component { const width = parseFloat(sidebarWidth.replace('%', '')); const { dimensions, actions }: any = this.props; - const dimensionsToUpdate = { ...dimensions }; - dimensionsToUpdate.content.width = `${maxWidth - width}%`; - dimensionsToUpdate.sidebar.width = `${width}%`; + const dimensionsToUpdate = { + ...dimensions, + content: { + ...dimensions.content, + width: `${maxWidth - width}%` + }, + sidebar: { + ...dimensions.sidebar, + width: `${width}%` + } + }; if (actions) { actions.setDimensions(dimensionsToUpdate); } @@ -492,7 +500,7 @@ class App extends Component { } const mapStateToProps = ({ sidebarProperties, theme, dimensions, - profile, sampleQuery, authToken, graphExplorerMode + profile, sampleQuery, auth: { authToken }, graphExplorerMode }: ApplicationState) => { const mobileScreen = !!sidebarProperties.mobileScreen; const showSidebar = !!sidebarProperties.showSidebar; @@ -517,7 +525,8 @@ const mapDispatchToProps = (dispatch: Dispatch) => { runQuery, setSampleQuery, toggleSidebar, - ...authActionCreators, + signIn, + storeScopes, changeTheme, setDimensions }, diff --git a/src/app/views/app-sections/StatusMessages.tsx b/src/app/views/app-sections/StatusMessages.tsx index 03ff6f314..7262de7e3 100644 --- a/src/app/views/app-sections/StatusMessages.tsx +++ b/src/app/views/app-sections/StatusMessages.tsx @@ -1,12 +1,12 @@ import { Link, MessageBar } from '@fluentui/react'; import { Fragment } from 'react'; -import { useDispatch } from 'react-redux'; -import { AppDispatch, useAppSelector } from '../../../store'; + +import { useAppDispatch, useAppSelector } from '../../../store'; import { IQuery } from '../../../types/query-runner'; -import { setSampleQuery } from '../../services/actions/query-input-action-creators'; -import { clearQueryStatus } from '../../services/actions/query-status-action-creator'; import { GRAPH_URL } from '../../services/graph-constants'; +import { clearQueryStatus } from '../../services/slices/query-status.slice'; +import { setSampleQuery } from '../../services/slices/sample-query.slice'; import { convertArrayToObject, extractUrl, getMatchesAndParts, matchIncludesLink, replaceLinks @@ -14,7 +14,7 @@ import { import { translateMessage } from '../../utils/translate-messages'; const StatusMessages = () => { - const dispatch: AppDispatch = useDispatch(); + const dispatch = useAppDispatch(); const { queryRunnerStatus, sampleQuery } = useAppSelector((state) => state); @@ -54,7 +54,9 @@ const StatusMessages = () => { if (queryRunnerStatus) { const { messageType, statusText, status, duration, hint } = queryRunnerStatus; - let urls: any = {}; + if (Object.keys(queryRunnerStatus).length === 0) { return null; } + + let urls: { [key: string]: string; } = {}; let message = status.toString(); const extractedUrls = extractUrl(status.toString()); if (extractedUrls) { diff --git a/src/app/views/app-sections/TermsOfUseMessage.tsx b/src/app/views/app-sections/TermsOfUseMessage.tsx index b824d7202..66e0c8446 100644 --- a/src/app/views/app-sections/TermsOfUseMessage.tsx +++ b/src/app/views/app-sections/TermsOfUseMessage.tsx @@ -1,10 +1,9 @@ import { Link, MessageBar, MessageBarType, styled } from '@fluentui/react'; -import { useDispatch } from 'react-redux'; import { geLocale } from '../../../appLocale'; -import { AppDispatch, useAppSelector } from '../../../store'; +import { useAppDispatch, useAppSelector } from '../../../store'; import { componentNames, telemetry } from '../../../telemetry'; -import { clearTermsOfUse } from '../../services/actions/terms-of-use-action-creator'; +import { clearTermsOfUse } from '../../services/slices/terms-of-use.slice'; import { translateMessage } from '../../utils/translate-messages'; import { appStyles } from '../App.styles'; @@ -13,7 +12,7 @@ const StyledTermsOfUseMessage = () => { const { termsOfUse } = useAppSelector((state) => state); - const dispatch: AppDispatch = useDispatch(); + const dispatch = useAppDispatch(); if (termsOfUse) { return { - const dispatch: AppDispatch = useDispatch(); + const dispatch: AppDispatch = useAppDispatch(); const [loginInProgress, setLoginInProgress] = useState(false); - const { authToken } = useAppSelector((state) => state); + const { auth: { authToken } } = useAppSelector((state) => state); const tokenPresent = !!authToken.token; const logoutInProgress = !!authToken.pending; @@ -33,7 +32,9 @@ const Authentication = (props: any) => { const authResponse = await authenticationWrapper.logIn(); if (authResponse) { setLoginInProgress(false); - dispatch(getAuthTokenSuccess(!!authResponse.accessToken)); + if (authResponse.accessToken) { + dispatch(getAuthTokenSuccess()); + } dispatch(getConsentedScopesSuccess(authResponse.scopes)); } } catch (error: any) { @@ -67,14 +68,16 @@ const Authentication = (props: any) => { telemetry.trackEvent(eventTypes.BUTTON_CLICK_EVENT, { ComponentName: componentNames.SIGN_IN_WITH_OTHER_ACCOUNT_BUTTON }); - try{ + try { const authResponse = await authenticationWrapper.logInWithOther(); if (authResponse) { setLoginInProgress(false); - dispatch(getAuthTokenSuccess(!!authResponse.accessToken)); + if (authResponse.accessToken) { + dispatch(getAuthTokenSuccess()); + } dispatch(getConsentedScopesSuccess(authResponse.scopes)); } - } catch(error: any) { + } catch (error: any) { setLoginInProgress(false); } } diff --git a/src/app/views/authentication/profile/Profile.tsx b/src/app/views/authentication/profile/Profile.tsx index 24399483f..2d0eae5f3 100644 --- a/src/app/views/authentication/profile/Profile.tsx +++ b/src/app/views/authentication/profile/Profile.tsx @@ -4,18 +4,17 @@ import { } from '@fluentui/react'; import { useId } from '@fluentui/react-hooks'; import { useEffect, useState } from 'react'; -import { useDispatch } from 'react-redux'; -import { AppDispatch, useAppSelector } from '../../../../store'; +import { AppDispatch, useAppDispatch, useAppSelector } from '../../../../store'; import { Mode } from '../../../../types/enums'; -import { signOut } from '../../../services/actions/auth-action-creators'; -import { getProfileInfo } from '../../../services/actions/profile-action-creators'; +import { signOut } from '../../../services/slices/auth.slice'; import { usePopups } from '../../../services/hooks'; import { translateMessage } from '../../../utils/translate-messages'; import { classNames } from '../../classnames'; import { authenticationStyles } from '../Authentication.styles'; import { profileStyles } from './Profile.styles'; -const getInitials = (name: string) => { +import { getProfileInfo } from '../../../services/slices/profile.slice'; +const getInitials = (name: string | undefined) => { let initials = ''; if (name && name !== '') { const n = name.indexOf('('); @@ -32,8 +31,9 @@ const getInitials = (name: string) => { }; const Profile = (props: any) => { - const dispatch: AppDispatch = useDispatch(); - const { profile, authToken, graphExplorerMode } = useAppSelector((state) => state); + const dispatch: AppDispatch = useAppDispatch(); + const { profile, auth: { authToken }, graphExplorerMode } = useAppSelector((state) => state); + const user = profile.user; const { show: showPermissions } = usePopups('full-permissions', 'panel'); const authenticated = authToken.token; @@ -67,10 +67,10 @@ const Profile = (props: any) => { } const persona: IPersonaSharedProps = { - imageUrl: profile.profileImageUrl, - imageInitials: getInitials(profile.displayName), - text: profile.displayName, - secondaryText: profile.emailAddress + imageUrl: user?.profileImageUrl, + imageInitials: getInitials(user?.displayName), + text: user?.displayName, + secondaryText: user?.emailAddress }; const changePanelState = () => { @@ -133,8 +133,8 @@ const Profile = (props: any) => { styles={{ root: { border: '1px solid' + theme.palette.neutralTertiary } }} > - {profile && - + {user && + } handleSignOut()}> {translateMessage('sign out')} diff --git a/src/app/views/main-header/FeedbackButton.tsx b/src/app/views/main-header/FeedbackButton.tsx index 259291171..691dd5caf 100644 --- a/src/app/views/main-header/FeedbackButton.tsx +++ b/src/app/views/main-header/FeedbackButton.tsx @@ -8,7 +8,7 @@ import { useAppSelector } from '../../../store'; export const FeedbackButton = () => { const [enableSurvey, setEnableSurvey] = useState(false); - const { profile } = useAppSelector((state) => state); + const { profile: { user } } = useAppSelector((state) => state); const currentTheme = getTheme(); const feedbackIcon : IIconProps = { iconName : 'Feedback' @@ -63,7 +63,7 @@ export const FeedbackButton = () => { return (
- {profile?.profileType !== ACCOUNT_TYPE.AAD && + {user?.profileType !== ACCOUNT_TYPE.AAD &&
{ - const { authToken } = useAppSelector((state) => state); + const { auth: { authToken } } = useAppSelector((state) => state); const authenticated = authToken.token; const [items, setItems] = useState([]); const currentTheme = getTheme(); @@ -102,19 +102,19 @@ export const Help = () => { calloutProps: { style: calloutStyles }, - styles:{container: {border: '1px solid' + currentTheme.palette.neutralTertiary}} + styles: { container: { border: '1px solid' + currentTheme.palette.neutralTertiary } } }; return (
+
{translateMessage('Help')}
} id={getId()} calloutProps={{ gapSpace: 0 }} - styles={ tooltipStyles } + styles={tooltipStyles} > = (props: MainHeaderProps) => { - const { profile, graphExplorerMode, sidebarProperties } = useAppSelector( + const { profile: { user }, graphExplorerMode, sidebarProperties } = useAppSelector( (state) => state ); @@ -86,7 +86,7 @@ export const MainHeader: React.FunctionComponent = (props: Main tokens={{ childrenGap: mobileScreen ? 0 : 10 }} > {!mobileScreen && } - {!profile && !mobileScreen && + {!user && !mobileScreen &&
= (props: Main
} - {profile && !mobileScreen && + {user && !mobileScreen &&
- +
} diff --git a/src/app/views/main-header/settings/Settings.tsx b/src/app/views/main-header/settings/Settings.tsx index 57e7654c7..dfec3b531 100644 --- a/src/app/views/main-header/settings/Settings.tsx +++ b/src/app/views/main-header/settings/Settings.tsx @@ -14,7 +14,7 @@ import { translateMessage } from '../../../utils/translate-messages'; import { mainHeaderStyles } from '../MainHeader.styles'; export const Settings: React.FunctionComponent = () => { - const { authToken } = useAppSelector((state) => state); + const { auth: { authToken } } = useAppSelector((state) => state); const authenticated = authToken.token; const [items, setItems] = useState([]); const currentTheme = getTheme(); diff --git a/src/app/views/main-header/settings/ThemeChooser.tsx b/src/app/views/main-header/settings/ThemeChooser.tsx index e1af0540c..db8727154 100644 --- a/src/app/views/main-header/settings/ThemeChooser.tsx +++ b/src/app/views/main-header/settings/ThemeChooser.tsx @@ -1,25 +1,24 @@ -import { ChoiceGroup, DefaultButton, DialogFooter } from '@fluentui/react'; +import { ChoiceGroup, DefaultButton, DialogFooter, IChoiceGroupOption } from '@fluentui/react'; -import { useDispatch } from 'react-redux'; -import { AppDispatch, useAppSelector } from '../../../../store'; +import { useAppDispatch, useAppSelector } from '../../../../store'; import { componentNames, eventTypes, telemetry } from '../../../../telemetry'; import { loadGETheme } from '../../../../themes'; import { AppTheme } from '../../../../types/enums'; -import { changeTheme } from '../../../services/actions/theme-action-creator'; import { PopupsComponent } from '../../../services/context/popups-context'; +import { changeTheme } from '../../../services/slices/theme.slice'; import { translateMessage } from '../../../utils/translate-messages'; const ThemeChooser: React.FC> = (props) => { - const dispatch: AppDispatch = useDispatch(); - const { theme: appTheme } = useAppSelector((state) => state); + const dispatch = useAppDispatch(); + const appTheme = useAppSelector(state=> state.theme); - const handleChangeTheme = (selectedTheme: any) => { - const newTheme: string = selectedTheme.key; + const handleChangeTheme = (selectedTheme: IChoiceGroupOption | undefined) => { + const newTheme: string = selectedTheme?.key ?? ''; dispatch(changeTheme(newTheme)); loadGETheme(newTheme); telemetry.trackEvent(eventTypes.BUTTON_CLICK_EVENT, { ComponentName: componentNames.SELECT_THEME_BUTTON, - SelectedTheme: selectedTheme.key.replace('-', ' ').toSentenceCase() + SelectedTheme: newTheme.replace('-', ' ').toSentenceCase() }); }; diff --git a/src/app/views/query-response/QueryResponse.tsx b/src/app/views/query-response/QueryResponse.tsx index d2a07514c..933510f8f 100644 --- a/src/app/views/query-response/QueryResponse.tsx +++ b/src/app/views/query-response/QueryResponse.tsx @@ -3,12 +3,11 @@ import { } from '@fluentui/react'; import { Resizable } from 're-resizable'; import { CSSProperties, useEffect, useState } from 'react'; -import { useDispatch } from 'react-redux'; -import { AppDispatch, useAppSelector } from '../../../store'; +import { useAppDispatch, useAppSelector } from '../../../store'; import { telemetry } from '../../../telemetry'; import { IQuery } from '../../../types/query-runner'; -import { expandResponseArea } from '../../services/actions/response-expanded-action-creator'; +import { expandResponseArea } from '../../services/slices/response-area-expanded.slice'; import { translateMessage } from '../../utils/translate-messages'; import { convertVhToPx } from '../common/dimensions/dimensions-adjustment'; import { GetPivotItems } from './pivot-items/pivot-items'; @@ -16,7 +15,7 @@ import './query-response.scss'; import { queryResponseStyles } from './queryResponse.styles'; const QueryResponse = () => { - const dispatch: AppDispatch = useDispatch(); + const dispatch = useAppDispatch(); const [showModal, setShowModal] = useState(false); const [responseHeight, setResponseHeight] = useState('610px'); const { sampleQuery, dimensions, snippets } = useAppSelector((state) => state); diff --git a/src/app/views/query-response/adaptive-cards/AdaptiveCard.tsx b/src/app/views/query-response/adaptive-cards/AdaptiveCard.tsx index 3b7fcaa7f..b635fa932 100644 --- a/src/app/views/query-response/adaptive-cards/AdaptiveCard.tsx +++ b/src/app/views/query-response/adaptive-cards/AdaptiveCard.tsx @@ -3,13 +3,12 @@ import { MessageBar, MessageBarType, Pivot, PivotItem, styled } from '@fluentui/react'; import * as AdaptiveCardsAPI from 'adaptivecards'; -import { useEffect } from 'react'; -import { useDispatch } from 'react-redux'; +import { useEffect, useState } from 'react'; -import { AppDispatch, useAppSelector } from '../../../../store'; +import { useAppSelector } from '../../../../store'; import { componentNames, telemetry } from '../../../../telemetry'; +import { IAdaptiveCardContent } from '../../../../types/adaptivecard'; import { IQuery } from '../../../../types/query-runner'; -import { getAdaptiveCard } from '../../../services/actions/adaptive-cards-action-creator'; import { translateMessage } from '../../../utils/translate-messages'; import { classNames } from '../../classnames'; import { Monaco } from '../../common'; @@ -20,15 +19,21 @@ import { } from '../../common/dimensions/dimensions-adjustment'; import { CopyButton } from '../../common/lazy-loader/component-registry'; import { queryResponseStyles } from './../queryResponse.styles'; +import { getAdaptiveCard } from './adaptive-cards.util'; +import MarkdownIt from 'markdown-it'; + +export interface AdaptiveCardResponse { + data?: IAdaptiveCardContent; + error?: string; +} const AdaptiveCard = (props: any) => { let adaptiveCard: AdaptiveCardsAPI.AdaptiveCard | null = new AdaptiveCardsAPI.AdaptiveCard(); - const dispatch: AppDispatch = useDispatch(); + const [cardContent, setCardContent] = useState(undefined); const { body, hostConfig } = props; const { dimensions: { response }, responseAreaExpanded, - sampleQuery, queryRunnerStatus: queryStatus, adaptiveCard: card, theme } = useAppSelector((state) => state); - const { data, pending } = card; + sampleQuery, queryRunnerStatus: queryStatus, theme } = useAppSelector((state) => state); const classes = classNames(props); const currentTheme: ITheme = getTheme(); @@ -38,7 +43,17 @@ const AdaptiveCard = (props: any) => { const monacoHeight = getResponseEditorHeight(190); useEffect(() => { - dispatch(getAdaptiveCard(body, sampleQuery)); + try { + const content = getAdaptiveCard(body, sampleQuery); + setCardContent({ + data: content + }) + } catch (err: unknown) { + const error = err as Error; + setCardContent({ + error: error.message + }) + } if (!adaptiveCard) { adaptiveCard = new AdaptiveCardsAPI.AdaptiveCard(); @@ -67,18 +82,8 @@ const AdaptiveCard = (props: any) => { return
; } - if (body && pending) { - return ( - - ); - } - - - if (body && !pending) { - if (!data || (queryStatus && !queryStatus.ok)) { + if (body) { + if (!cardContent?.data || (queryStatus && !queryStatus.ok)) { return (
); - } catch (err: any) { - return
{err.message}
; + } catch (err: unknown) { + const error = err as Error; + return
{error.message}
; } } } diff --git a/src/app/views/query-response/adaptive-cards/adaptive-cards.util.ts b/src/app/views/query-response/adaptive-cards/adaptive-cards.util.ts new file mode 100644 index 000000000..7266fa630 --- /dev/null +++ b/src/app/views/query-response/adaptive-cards/adaptive-cards.util.ts @@ -0,0 +1,48 @@ +import * as AdaptiveCardsTemplateAPI from 'adaptivecards-templating'; + +import { IAdaptiveCardContent } from '../../../../types/adaptivecard'; +import { IQuery } from '../../../../types/query-runner'; +import { lookupTemplate } from '../../../utils/adaptive-cards-lookup'; + +export function getAdaptiveCard(payload: string, sampleQuery: IQuery): IAdaptiveCardContent { + if (!payload) { + // no payload so return empty result + throw new Error('No payload available'); + } + + if (Object.keys(payload).length === 0) { + // check if the payload is something else that we cannot use + throw new Error('Invalid payload for card'); + } + + const templateFileName = lookupTemplate(sampleQuery); + if (!templateFileName) { + // we dont support this card yet + throw new Error('No template available'); + } + + try { + const card = createCardFromTemplate(templateFileName, payload); + const adaptiveCardContent: IAdaptiveCardContent = { + card, + template: templateFileName + }; + return adaptiveCardContent; + } catch (err: unknown) { + // something wrong happened + const error = err as Error; + throw error.message; + } +} + +function createCardFromTemplate(templatePayload: string, payload: string): AdaptiveCardsTemplateAPI.Template { + const template = new AdaptiveCardsTemplateAPI.Template(templatePayload); + const context: AdaptiveCardsTemplateAPI.IEvaluationContext = { + $root: payload + }; + AdaptiveCardsTemplateAPI.GlobalSettings.getUndefinedFieldValueSubstitutionString = ( + // eslint-disable-next-line no-unused-vars + _path: string + ) => ' '; + return template.expand(context); +} \ No newline at end of file diff --git a/src/app/views/query-response/headers/ResponseHeaders.tsx b/src/app/views/query-response/headers/ResponseHeaders.tsx index 026061af3..7a24683ff 100644 --- a/src/app/views/query-response/headers/ResponseHeaders.tsx +++ b/src/app/views/query-response/headers/ResponseHeaders.tsx @@ -11,7 +11,7 @@ import { useAppSelector } from '../../../../store'; const ResponseHeaders = () => { const { dimensions: { response }, graphResponse, responseAreaExpanded, sampleQuery } = useAppSelector((state) => state); - const { headers } = graphResponse; + const { headers } = graphResponse.response; const defaultHeight = convertVhToPx(getResponseHeight(response.height, responseAreaExpanded), 220); const monacoHeight = getResponseEditorHeight(120); diff --git a/src/app/views/query-response/pivot-items/pivot-items.tsx b/src/app/views/query-response/pivot-items/pivot-items.tsx index 1cf450d30..b5d2dbb6c 100644 --- a/src/app/views/query-response/pivot-items/pivot-items.tsx +++ b/src/app/views/query-response/pivot-items/pivot-items.tsx @@ -11,13 +11,15 @@ import { translateMessage } from '../../../utils/translate-messages'; import { darkThemeHostConfig, lightThemeHostConfig } from '../adaptive-cards/AdaptiveHostConfig'; import { queryResponseStyles } from '../queryResponse.styles'; import { Response } from '../response'; -import { AdaptiveCards, GraphToolkit, ResponseHeaders, - Snippets } from '../../common/lazy-loader/component-registry'; +import { + AdaptiveCards, GraphToolkit, ResponseHeaders, + Snippets +} from '../../common/lazy-loader/component-registry'; export const GetPivotItems = () => { const { graphExplorerMode: mode, sampleQuery, - graphResponse: { body } } = useAppSelector((state) => state); + graphResponse: { response: { body } } } = useAppSelector((state) => state); const currentTheme: ITheme = getTheme(); const dotStyle = queryResponseStyles(currentTheme).dot; diff --git a/src/app/views/query-response/response/Response.tsx b/src/app/views/query-response/response/Response.tsx index 4c8fa7896..00cacc7f3 100644 --- a/src/app/views/query-response/response/Response.tsx +++ b/src/app/views/query-response/response/Response.tsx @@ -10,9 +10,8 @@ import ResponseDisplay from './ResponseDisplay'; import { ResponseMessages } from './ResponseMessages'; const Response = () => { - const { dimensions: { response }, graphResponse, responseAreaExpanded} = + const { dimensions: { response }, graphResponse: { response: { body, headers } }, responseAreaExpanded } = useAppSelector((state) => state); - const { body, headers } = graphResponse; const defaultHeight = convertVhToPx(getResponseHeight(response.height, responseAreaExpanded), 220); const monacoHeight = getResponseEditorHeight(150); diff --git a/src/app/views/query-response/response/ResponseMessages.tsx b/src/app/views/query-response/response/ResponseMessages.tsx index 1cc4cb801..c81406fd5 100644 --- a/src/app/views/query-response/response/ResponseMessages.tsx +++ b/src/app/views/query-response/response/ResponseMessages.tsx @@ -1,14 +1,13 @@ import { Link, MessageBar, MessageBarType } from '@fluentui/react'; import { useState } from 'react'; -import { useDispatch } from 'react-redux'; -import { AppDispatch, useAppSelector } from '../../../../store'; +import { useAppDispatch, useAppSelector } from '../../../../store'; import { Mode } from '../../../../types/enums'; import { IQuery } from '../../../../types/query-runner'; import { getContentType } from '../../../services/actions/query-action-creator-util'; -import { runQuery } from '../../../services/actions/query-action-creators'; -import { setSampleQuery } from '../../../services/actions/query-input-action-creators'; import { MOZILLA_CORS_DOCUMENTATION_LINK } from '../../../services/graph-constants'; +import { runQuery } from '../../../services/slices/graph-response.slice'; +import { setSampleQuery } from '../../../services/slices/sample-query.slice'; import { translateMessage } from '../../../utils/translate-messages'; interface ODataLink { @@ -33,10 +32,10 @@ function getOdataLinkFromResponseBody(responseBody: any): ODataLink | null { } export const ResponseMessages = () => { - const dispatch: AppDispatch = useDispatch(); + const dispatch = useAppDispatch(); const messageBars = []; - const { graphResponse: { body, headers }, sampleQuery, authToken, graphExplorerMode + const { graphResponse: { response: { body, headers } }, sampleQuery, auth: { authToken }, graphExplorerMode } = useAppSelector((state) => state); const [displayMessage, setDisplayMessage] = useState(true); @@ -54,7 +53,7 @@ export const ResponseMessages = () => { // Display link to step to next result if (odataLink) { messageBars.push( - + {translateMessage('This response contains an @odata property.')}: @odata.{odataLink.name} setQuery()} underline>  {translateMessage('Click here to follow the link')} @@ -66,7 +65,7 @@ export const ResponseMessages = () => { // Display link to download file response if (body?.contentDownloadUrl) { messageBars.push( -
+
{translateMessage('This response contains unviewable content')} @@ -80,7 +79,7 @@ export const ResponseMessages = () => { // Show CORS compliance message if (body?.throwsCorsError) { messageBars.push( -
+
{translateMessage('Response content not available due to CORS policy')} @@ -93,7 +92,7 @@ export const ResponseMessages = () => { if (body && !tokenPresent && displayMessage && graphExplorerMode === Mode.Complete) { messageBars.push( -
+
{ if (contentType === 'application/json' && typeof body === 'string') { messageBars.push( -
+
setDisplayMessage(false)} diff --git a/src/app/views/query-response/snippets/Snippets.tsx b/src/app/views/query-response/snippets/Snippets.tsx index 82f2879fe..0a037d151 100644 --- a/src/app/views/query-response/snippets/Snippets.tsx +++ b/src/app/views/query-response/snippets/Snippets.tsx @@ -1,16 +1,15 @@ import { FontSizes, Label, Pivot, PivotItem } from '@fluentui/react'; -import { useDispatch } from 'react-redux'; import { useContext } from 'react'; -import { AppDispatch, useAppSelector } from '../../../../store'; +import { useAppDispatch, useAppSelector } from '../../../../store'; import { componentNames, telemetry } from '../../../../telemetry'; -import { setSnippetTabSuccess } from '../../../services/actions/snippet-action-creator'; import { ValidationContext } from '../../../services/context/validation-context/ValidationContext'; +import { setSnippetTabSuccess } from '../../../services/slices/snippet.slice'; import { translateMessage } from '../../../utils/translate-messages'; import { renderSnippets } from './snippets-helper'; function GetSnippets() { - const dispatch: AppDispatch = useDispatch(); + const dispatch = useAppDispatch(); const validation = useContext(ValidationContext); const { snippets, sampleQuery } = useAppSelector((state) => state); diff --git a/src/app/views/query-response/snippets/snippets-helper.tsx b/src/app/views/query-response/snippets/snippets-helper.tsx index bda805950..a06386000 100644 --- a/src/app/views/query-response/snippets/snippets-helper.tsx +++ b/src/app/views/query-response/snippets/snippets-helper.tsx @@ -1,16 +1,14 @@ /* eslint-disable max-len */ import { ITheme, Label, Link, PivotItem, getTheme } from '@fluentui/react'; import { useEffect, useState } from 'react'; -import { useDispatch } from 'react-redux'; -import { getSnippet } from '../../../services/actions/snippet-action-creator'; -import { Monaco } from '../../common'; -import { trackedGenericCopy } from '../../common/copy'; - -import { AppDispatch, useAppSelector } from '../../../../store'; +import { useAppDispatch, useAppSelector } from '../../../../store'; import { componentNames, telemetry } from '../../../../telemetry'; import { CODE_SNIPPETS_COPY_BUTTON } from '../../../../telemetry/component-names'; +import { getSnippet } from '../../../services/slices/snippet.slice'; import { translateMessage } from '../../../utils/translate-messages'; +import { Monaco } from '../../common'; +import { trackedGenericCopy } from '../../common/copy'; import { convertVhToPx, getResponseEditorHeight, getResponseHeight @@ -93,7 +91,7 @@ function Snippet(props: ISnippetProps) { const monacoHeight = getResponseEditorHeight(235); - const dispatch: AppDispatch = useDispatch(); + const dispatch = useAppDispatch(); useEffect(() => { setSnippetError(error?.error ? error.error : error); diff --git a/src/app/views/query-runner/QueryRunner.tsx b/src/app/views/query-runner/QueryRunner.tsx index d021fbde1..bd08783a5 100644 --- a/src/app/views/query-runner/QueryRunner.tsx +++ b/src/app/views/query-runner/QueryRunner.tsx @@ -1,14 +1,13 @@ import { IDropdownOption, MessageBarType } from '@fluentui/react'; import { useEffect, useState } from 'react'; -import { useDispatch } from 'react-redux'; -import { AppDispatch, useAppSelector } from '../../../store'; +import { useAppDispatch, useAppSelector } from '../../../store'; import { componentNames, eventTypes, telemetry } from '../../../telemetry'; import { ContentType } from '../../../types/enums'; import { IQuery } from '../../../types/query-runner'; -import { runQuery } from '../../services/actions/query-action-creators'; -import { setSampleQuery } from '../../services/actions/query-input-action-creators'; -import { setQueryResponseStatus } from '../../services/actions/query-status-action-creator'; +import { runQuery } from '../../services/slices/graph-response.slice'; +import { setQueryResponseStatus } from '../../services/slices/query-status.slice'; +import { setSampleQuery } from '../../services/slices/sample-query.slice'; import { sanitizeQueryUrl } from '../../utils/query-url-sanitization'; import { parseSampleUrl } from '../../utils/sample-url-generation'; import { translateMessage } from '../../utils/translate-messages'; @@ -17,7 +16,7 @@ import './query-runner.scss'; import Request from './request/Request'; const QueryRunner = (props: any) => { - const dispatch: AppDispatch = useDispatch(); + const dispatch = useAppDispatch(); const { sampleQuery } = useAppSelector((state) => state); const [sampleBody, setSampleBody] = useState(''); @@ -46,12 +45,13 @@ const QueryRunner = (props: any) => { }; const handleOnRunQuery = (query?: IQuery) => { - if (sampleBody && sampleQuery.selectedVerb !== 'GET') { - const headers = sampleQuery.sampleHeaders; - const contentType = headers.find(k => k.name.toLowerCase() === 'content-type'); + let sample = { ...sampleQuery }; + if (sampleBody && sample.selectedVerb !== 'GET') { + const headers = sample.sampleHeaders; + const contentType = headers.find((k: { name: string; }) => k.name.toLowerCase() === 'content-type'); if (!contentType || (contentType.value === ContentType.Json)) { try { - sampleQuery.sampleBody = JSON.parse(sampleBody); + sample.sampleBody = JSON.parse(sampleBody); } catch (error) { dispatch(setQueryResponseStatus({ ok: false, @@ -62,24 +62,27 @@ const QueryRunner = (props: any) => { return; } } else { - sampleQuery.sampleBody = sampleBody; + sample.sampleBody = sampleBody; } } if (query) { - sampleQuery.sampleUrl = query.sampleUrl; - sampleQuery.selectedVersion = query.selectedVersion; - sampleQuery.selectedVerb = query.selectedVerb; + sample = { + ...sample, + sampleUrl: query.sampleUrl, + selectedVersion: query.selectedVersion, + selectedVerb: query.selectedVerb + } } - dispatch(runQuery(sampleQuery)); - const sanitizedUrl = sanitizeQueryUrl(sampleQuery.sampleUrl); + dispatch(runQuery(sample)); + const sanitizedUrl = sanitizeQueryUrl(sample.sampleUrl); const deviceCharacteristics = telemetry.getDeviceCharacteristicsData(); telemetry.trackEvent(eventTypes.BUTTON_CLICK_EVENT, { ComponentName: componentNames.RUN_QUERY_BUTTON, - SelectedVersion: sampleQuery.selectedVersion, - QuerySignature: `${sampleQuery.selectedVerb} ${sanitizedUrl}`, + SelectedVersion: sample.selectedVersion, + QuerySignature: `${sample.selectedVerb} ${sanitizedUrl}`, ...deviceCharacteristics }); }; diff --git a/src/app/views/query-runner/query-input/QueryInput.tsx b/src/app/views/query-runner/query-input/QueryInput.tsx index a9d2aaada..5e1c05e04 100644 --- a/src/app/views/query-runner/query-input/QueryInput.tsx +++ b/src/app/views/query-runner/query-input/QueryInput.tsx @@ -1,12 +1,11 @@ import { Dropdown, IDropdownOption, IStackTokens, Stack } from '@fluentui/react'; import { useContext } from 'react'; -import { useDispatch } from 'react-redux'; -import { AppDispatch, useAppSelector } from '../../../../store'; +import { useAppDispatch, useAppSelector } from '../../../../store'; import { IQuery, IQueryInputProps, httpMethods } from '../../../../types/query-runner'; -import { setSampleQuery } from '../../../services/actions/query-input-action-creators'; import { ValidationContext } from '../../../services/context/validation-context/ValidationContext'; import { GRAPH_API_VERSIONS } from '../../../services/graph-constants'; +import { setSampleQuery } from '../../../services/slices/sample-query.slice'; import { getStyleFor } from '../../../utils/http-methods.utils'; import { parseSampleUrl } from '../../../utils/sample-url-generation'; import { translateMessage } from '../../../utils/translate-messages'; @@ -23,7 +22,7 @@ const QueryInput = (props: IQueryInputProps) => { handleOnVersionChange } = props; - const dispatch: AppDispatch = useDispatch(); + const dispatch = useAppDispatch(); const validation = useContext(ValidationContext); const urlVersions: IDropdownOption[] = []; @@ -34,8 +33,9 @@ const QueryInput = (props: IQueryInputProps) => { }) }); - const { sampleQuery, authToken, - isLoadingData: submitting, sidebarProperties } = useAppSelector((state) => state); + const { sampleQuery, auth: { authToken }, + graphResponse: { isLoadingData }, + sidebarProperties } = useAppSelector((state) => state); const authenticated = !!authToken.token; const { mobileScreen } = sidebarProperties; @@ -116,7 +116,7 @@ const QueryInput = (props: IQueryInputProps) => { disabled={showError || !sampleQuery.sampleUrl || !validation.isValid} role='button' handleOnClick={() => runQuery()} - submitting={submitting} + submitting={isLoadingData} allowDisabledFocus={true} /> diff --git a/src/app/views/query-runner/query-input/auto-complete/AutoComplete.tsx b/src/app/views/query-runner/query-input/auto-complete/AutoComplete.tsx index 23db381a5..9ddd5d9be 100644 --- a/src/app/views/query-runner/query-input/auto-complete/AutoComplete.tsx +++ b/src/app/views/query-runner/query-input/auto-complete/AutoComplete.tsx @@ -1,14 +1,13 @@ import { getTheme, ITextFieldProps, KeyCodes, mergeStyles, Text, TextField } from '@fluentui/react'; import { useContext, useEffect, useRef, useState } from 'react'; -import { useDispatch } from 'react-redux'; import { delimiters, getLastDelimiterInUrl, getSuggestions, SignContext } from '../../../../../modules/suggestions'; -import { AppDispatch, useAppSelector } from '../../../../../store'; +import { useAppDispatch, useAppSelector } from '../../../../../store'; import { componentNames, eventTypes, telemetry } from '../../../../../telemetry'; import { IAutoCompleteProps } from '../../../../../types/auto-complete'; -import { fetchAutoCompleteOptions } from '../../../../services/actions/autocomplete-action-creators'; import { ValidationContext } from '../../../../services/context/validation-context/ValidationContext'; import { GRAPH_API_VERSIONS, GRAPH_URL } from '../../../../services/graph-constants'; +import { fetchAutoCompleteOptions } from '../../../../services/slices/autocomplete.slice'; import { sanitizeQueryUrl } from '../../../../utils/query-url-sanitization'; import { parseSampleUrl } from '../../../../utils/sample-url-generation'; import { translateMessage } from '../../../../utils/translate-messages'; @@ -23,7 +22,7 @@ import { usePrevious } from './use-previous'; const AutoComplete = (props: IAutoCompleteProps) => { - const dispatch: AppDispatch = useDispatch(); + const dispatch = useAppDispatch(); const validation = useContext(ValidationContext); // eslint-disable-next-line @typescript-eslint/no-explicit-any const focusRef = useRef(null); @@ -190,11 +189,11 @@ const AutoComplete = (props: IAutoCompleteProps) => { } if (!requestUrl) { - dispatch(fetchAutoCompleteOptions('', queryVersion)); + dispatch(fetchAutoCompleteOptions({ url: '', version: queryVersion })); return; } - dispatch(fetchAutoCompleteOptions(requestUrl, queryVersion, context)); + dispatch(fetchAutoCompleteOptions({ url: requestUrl, version: queryVersion, context })); } const displayAutoCompleteSuggestions = (url: string) => { diff --git a/src/app/views/query-runner/query-input/auto-complete/auto-complete.util.ts b/src/app/views/query-runner/query-input/auto-complete/auto-complete.util.ts index 3539bb3a8..04c734d4e 100644 --- a/src/app/views/query-runner/query-input/auto-complete/auto-complete.util.ts +++ b/src/app/views/query-runner/query-input/auto-complete/auto-complete.util.ts @@ -35,6 +35,7 @@ function getFilteredSuggestions(compareString: string, suggestions: string[]) { } function getSearchText(input: string, index: number) { + if (!input || !index) { return { previous: '', searchText:'' }} const stringPosition = index + 1; const previous = input.substring(0, stringPosition); const searchText = input.replace(previous, ''); diff --git a/src/app/views/query-runner/request/Request.tsx b/src/app/views/query-runner/request/Request.tsx index 1383cf43e..04f3581ff 100644 --- a/src/app/views/query-runner/request/Request.tsx +++ b/src/app/views/query-runner/request/Request.tsx @@ -5,20 +5,19 @@ import { } from '@fluentui/react'; import { Resizable } from 're-resizable'; import { CSSProperties, useEffect, useState } from 'react'; -import { useDispatch } from 'react-redux'; -import { AppDispatch, useAppSelector } from '../../../../store'; +import { useAppDispatch, useAppSelector } from '../../../../store'; import { telemetry } from '../../../../telemetry'; import { Mode } from '../../../../types/enums'; -import { setDimensions } from '../../../services/actions/dimensions-action-creator'; +import { setDimensions } from '../../../services/slices/dimensions.slice'; import { translateMessage } from '../../../utils/translate-messages'; import { convertPxToVh, convertVhToPx } from '../../common/dimensions/dimensions-adjustment'; +import { Auth, Permissions, RequestHeaders } from '../../common/lazy-loader/component-registry'; import { RequestBody } from './body'; import './request.scss'; -import { Permissions, Auth, RequestHeaders } from '../../common/lazy-loader/component-registry'; const Request = (props: any) => { - const dispatch: AppDispatch = useDispatch(); + const dispatch = useAppDispatch(); const [selectedPivot, setSelectedPivot] = useState('request-body'); const { graphExplorerMode: mode, dimensions, sidebarProperties } = useAppSelector((state) => state); const pivot = selectedPivot.replace('.$', ''); @@ -140,13 +139,23 @@ const Request = (props: any) => { const heightInPx = requestHeight.replace('px', '').trim(); const requestHeightInVh = convertPxToVh(parseFloat(heightInPx)).toString(); const maxDeviceVerticalHeight = 90; - const dimen = { ...dimensions }; - dimen.request.height = requestHeightInVh; - const response = maxDeviceVerticalHeight - parseFloat(requestHeightInVh.replace('vh', '')); - dimen.response.height = response + 'vh'; - dispatch(setDimensions(dimen)); + + const dimensionsToUpdate = { + ...dimensions, + request: { + ...dimensions.request, + height: requestHeightInVh + }, + response: { + ...dimensions.response, + height: `${maxDeviceVerticalHeight - parseFloat(requestHeightInVh.replace('vh', ''))}vh` + } + }; + + dispatch(setDimensions(dimensionsToUpdate)); }; + // Resizable element does not update it's size when the browser window is resized. // This is a workaround to reset the height const resizeHandler = () => { diff --git a/src/app/views/query-runner/request/auth/Auth.tsx b/src/app/views/query-runner/request/auth/Auth.tsx index 6a77e1ff2..63fcc696b 100644 --- a/src/app/views/query-runner/request/auth/Auth.tsx +++ b/src/app/views/query-runner/request/auth/Auth.tsx @@ -14,7 +14,8 @@ import { convertVhToPx } from '../../../common/dimensions/dimensions-adjustment' import { authStyles } from './Auth.styles'; export function Auth(props: any) { - const { authToken, profile, dimensions: { request: { height } } } = useAppSelector((state) => state); + const { auth: { authToken }, profile, dimensions: { request: { height } } } = useAppSelector((state) => state); + const {user} = profile; const requestHeight = convertVhToPx(height, 60); const [accessToken, setAccessToken] = useState(null); const [loading, setLoading] = useState(false); @@ -45,13 +46,13 @@ export function Auth(props: any) { ; } - const tokenDetailsDisabled = profile?.profileType === ACCOUNT_TYPE.MSA; + const tokenDetailsDisabled = user?.profileType === ACCOUNT_TYPE.MSA; return (
{!loading ?
- + (undefined); const currentTheme = getTheme(); const { NODE_ENV } = process.env; - const { profile } = useAppSelector((state) => state); + const { profile: { user } } = useAppSelector((state) => state); function surveyActivated(launcher: any, surveyItem: any) { return surveyItem; @@ -80,8 +79,8 @@ export default function FeedbackForm({ activated, onDismissSurvey, onDisableSurv appId: 2256, stylesUrl: ' ', // Mandatory field environment: (NODE_ENV === 'development') ? 1 : 0, // 0 - Prod, 1 - Int - ageGroup: profile?.ageGroup, - authenticationType: getAuthType(profile?.profileType!), + ageGroup: user?.ageGroup, + authenticationType: getAuthType(user?.profileType!), locale: geLocale, onError: (error: string): string => { throw error; }, build: getVersion().toString(), @@ -103,7 +102,7 @@ export default function FeedbackForm({ activated, onDismissSurvey, onDisableSurv autoDismiss: 2, campaignDefinitions: CampaignDefinitions, showEmailAddress: true, - surveyEnabled: (profile?.profileType !== ACCOUNT_TYPE.AAD), + surveyEnabled: (user?.profileType !== ACCOUNT_TYPE.AAD), onDismiss: (campaignId: string, submitted: boolean) => { const SecondsBeforePopup = getSecondsBeforePopup(floodgateObject.floodgate.getEngine() .previousSurveyEventActivityStats); diff --git a/src/app/views/query-runner/request/headers/RequestHeaders.tsx b/src/app/views/query-runner/request/headers/RequestHeaders.tsx index ddb8f2af6..841472c6b 100644 --- a/src/app/views/query-runner/request/headers/RequestHeaders.tsx +++ b/src/app/views/query-runner/request/headers/RequestHeaders.tsx @@ -1,9 +1,8 @@ import { Announced, ITextField, PrimaryButton, styled, TextField } from '@fluentui/react'; import { createRef, useState } from 'react'; -import { useDispatch } from 'react-redux'; -import { AppDispatch, useAppSelector } from '../../../../../store'; -import * as queryInputActionCreators from '../../../../services/actions/query-input-action-creators'; +import { useAppDispatch, useAppSelector } from '../../../../../store'; +import { setSampleQuery } from '../../../../services/slices/sample-query.slice'; import { translateMessage } from '../../../../utils/translate-messages'; import { classNames } from '../../../classnames'; import { convertVhToPx } from '../../../common/dimensions/dimensions-adjustment'; @@ -26,7 +25,7 @@ const RequestHeaders = (props: any) => { const sampleQueryHeaders = sampleQuery.sampleHeaders; - const dispatch: AppDispatch = useDispatch(); + const dispatch = useAppDispatch(); const classes = classNames(props); const textfieldRef = createRef(); @@ -44,7 +43,7 @@ const RequestHeaders = (props: any) => { const query = { ...sampleQuery }; query.sampleHeaders = headers; - dispatch(queryInputActionCreators.setSampleQuery(query)); + dispatch(setSampleQuery(query)); setAnnouncedMessage(translateMessage('Request Header deleted')); onSetFocus(); //set focus to textfield after an item is deleted }; @@ -67,7 +66,7 @@ const RequestHeaders = (props: any) => { const query = { ...sampleQuery }; query.sampleHeaders = newHeaders; - dispatch(queryInputActionCreators.setSampleQuery(query)); + dispatch(setSampleQuery(query)); } }; @@ -86,7 +85,7 @@ const RequestHeaders = (props: any) => { headers = headers.filter(head => head.name !== headerToRemove.name); const query = { ...sampleQuery }; query.sampleHeaders = headers; - dispatch(queryInputActionCreators.setSampleQuery(query)); + dispatch(setSampleQuery(query)); } return ( diff --git a/src/app/views/query-runner/request/permissions/ConsentType.tsx b/src/app/views/query-runner/request/permissions/ConsentType.tsx index 9a0205243..27656436d 100644 --- a/src/app/views/query-runner/request/permissions/ConsentType.tsx +++ b/src/app/views/query-runner/request/permissions/ConsentType.tsx @@ -1,6 +1,7 @@ import { DirectionalHint, IconButton, IIconProps, Label, Spinner, TooltipHost } from '@fluentui/react'; -import { IPermission } from '../../../../../types/permissions' -import { fetchAllPrincipalGrants } from '../../../../services/actions/permissions-action-creator'; + +import { IPermission } from '../../../../../types/permissions'; +import { fetchAllPrincipalGrants } from '../../../../services/slices/permission-grants.slice'; import { translateMessage } from '../../../../utils/translate-messages'; interface IConsentType { diff --git a/src/app/views/query-runner/request/permissions/PermissionItem.tsx b/src/app/views/query-runner/request/permissions/PermissionItem.tsx index 17688f598..728ada889 100644 --- a/src/app/views/query-runner/request/permissions/PermissionItem.tsx +++ b/src/app/views/query-runner/request/permissions/PermissionItem.tsx @@ -2,21 +2,19 @@ import { DefaultButton, FontSizes, IColumn, IIconProps, IconButton, Label, PrimaryButton, TooltipHost, getId, getTheme } from '@fluentui/react'; -import { useDispatch } from 'react-redux'; -import { AppDispatch, useAppSelector } from '../../../../../store'; +import { AppDispatch, useAppDispatch, useAppSelector } from '../../../../../store'; import { IPermission, IPermissionGrant } from '../../../../../types/permissions'; -import { - consentToScopes, getAllPrincipalGrant, - getSinglePrincipalGrant, revokeScopes -} from '../../../../services/actions/permissions-action-creator'; +import { revokeScopes } from '../../../../services/actions/revoke-scopes.action'; import { REVOKING_PERMISSIONS_REQUIRED_SCOPES } from '../../../../services/graph-constants'; +import { consentToScopes } from '../../../../services/slices/auth.slice'; +import { getAllPrincipalGrant, getSinglePrincipalGrant } from '../../../../services/slices/permission-grants.slice'; import { translateMessage } from '../../../../utils/translate-messages'; import { PermissionConsentType } from './ConsentType'; import { permissionStyles } from './Permission.styles'; interface PermissionItemProps { - item: any; index: any; column: IColumn | undefined; + item: IPermission; index: number; column: IColumn | undefined; } const buttonIcon: IIconProps = { @@ -34,9 +32,9 @@ const infoIcon: IIconProps = { const PermissionItem = (props: PermissionItemProps): JSX.Element | null => { const theme = getTheme(); - const dispatch: AppDispatch = useDispatch(); + const dispatch: AppDispatch = useAppDispatch(); const hostId: string = getId('tooltipHost'); - const { scopes, consentedScopes, profile } = useAppSelector((state) => state); + const { scopes, auth: { consentedScopes }, profile: { user }, permissionGrants } = useAppSelector((state) => state); const { item, column } = props; const consented = !!item.consented; @@ -48,14 +46,14 @@ const PermissionItem = (props: PermissionItemProps): JSX.Element | null => { }; const getAllPrincipalPermissions = (tenantWidePermissionsGrant: IPermissionGrant[]): string[] => { - const allPrincipalPermissions = tenantWidePermissionsGrant.find((permission: any) => + const allPrincipalPermissions = tenantWidePermissionsGrant.find((permission: IPermissionGrant) => permission.consentType.toLowerCase() === 'AllPrincipals'.toLowerCase()); return allPrincipalPermissions ? allPrincipalPermissions.scope.split(' ') : []; } const userHasRequiredPermissions = (): boolean => { - if (scopes && scopes.data.tenantWidePermissionsGrant && scopes.data.tenantWidePermissionsGrant.length > 0) { - const allPrincipalPermissions = getAllPrincipalPermissions(scopes.data.tenantWidePermissionsGrant); + if (permissionGrants && permissionGrants.permissions && permissionGrants.permissions.length > 0) { + const allPrincipalPermissions = getAllPrincipalPermissions(permissionGrants.permissions); const principalAndAllPrincipalPermissions = [...allPrincipalPermissions, ...consentedScopes]; const requiredPermissions = REVOKING_PERMISSIONS_REQUIRED_SCOPES.split(' '); return requiredPermissions.every(scope => principalAndAllPrincipalPermissions.includes(scope)); @@ -64,12 +62,11 @@ const PermissionItem = (props: PermissionItemProps): JSX.Element | null => { } const ConsentTypeProperty = (): JSX.Element | null => { - if (scopes && consented && profile && profile.id) { - - const tenantWideGrant: IPermissionGrant[] = scopes.data.tenantWidePermissionsGrant!; + if (scopes && consented && user?.id) { + const tenantWideGrant: IPermissionGrant[] = permissionGrants.permissions!; const allPrincipalPermissions = getAllPrincipalGrant(tenantWideGrant); - const singlePrincipalPermissions: string[] = getSinglePrincipalGrant(tenantWideGrant, profile.id); - const tenantGrantFetchPending = scopes.pending.isTenantWidePermissionsGrant; + const singlePrincipalPermissions: string[] = getSinglePrincipalGrant(tenantWideGrant, user?.id); + const tenantGrantFetchPending = permissionGrants.pending; const consentTypeProperties = { item, allPrincipalPermissions, singlePrincipalPermissions, tenantGrantFetchPending, dispatch @@ -114,7 +111,7 @@ const PermissionItem = (props: PermissionItemProps): JSX.Element | null => { } if (column) { - const content = item[column.fieldName as keyof any] as string; + const content = item[column.fieldName as keyof IPermission] as string; switch (column.key) { case 'value': diff --git a/src/app/views/query-runner/request/permissions/Permissions.Full.tsx b/src/app/views/query-runner/request/permissions/Permissions.Full.tsx index 01e30446a..da0759bfd 100644 --- a/src/app/views/query-runner/request/permissions/Permissions.Full.tsx +++ b/src/app/views/query-runner/request/permissions/Permissions.Full.tsx @@ -4,15 +4,15 @@ import { IContextualMenuProps, Label, SearchBox, SelectionMode, Stack, TooltipHost } from '@fluentui/react'; -import { useEffect, useRef, useState } from 'react'; -import { useDispatch } from 'react-redux'; +import { useEffect, useState } from 'react'; -import { AppDispatch, useAppSelector } from '../../../../../store'; +import { useAppDispatch, useAppSelector } from '../../../../../store'; import { componentNames, eventTypes, telemetry } from '../../../../../telemetry'; import { SortOrder } from '../../../../../types/enums'; import { IPermission } from '../../../../../types/permissions'; -import { fetchAllPrincipalGrants, fetchScopes } from '../../../../services/actions/permissions-action-creator'; import { PopupsComponent } from '../../../../services/context/popups-context'; +import { fetchAllPrincipalGrants } from '../../../../services/slices/permission-grants.slice'; +import { fetchScopes } from '../../../../services/slices/scopes.slice'; import { dynamicSort } from '../../../../utils/dynamic-sort'; import { generateGroupsFromList } from '../../../../utils/generate-groups'; import { searchBoxStyles } from '../../../../utils/searchbox.styles'; @@ -29,20 +29,21 @@ interface PermissionListItem extends IPermission { const FullPermissions: React.FC> = (): JSX.Element => { const theme = getTheme(); - const dispatch: AppDispatch = useDispatch(); + const dispatch = useAppDispatch(); const [filter, setFilter] = useState('all-permissions'); const { panelContainer: panelStyles, tooltipStyles, detailsHeaderStyles } = permissionStyles(theme); - const { consentedScopes, scopes, authToken } = useAppSelector((state) => state); + const { scopes, auth: { consentedScopes, authToken } } = useAppSelector((state) => state); const { fullPermissions } = scopes.data; const tokenPresent = !!authToken.token; const loading = scopes.pending.isFullPermissions; const [permissions, setPermissions] = useState([]); const [searchValue, setSearchValue] = useState(''); + let listOfPermissions: IPermission[] = permissions; const getPermissions = (): void => { - dispatch(fetchScopes()); + dispatch(fetchScopes('full')); fetchPermissionGrants(); } @@ -56,12 +57,13 @@ const FullPermissions: React.FC> = (): JSX.Element => { getPermissions(); }, []); - useEffect(() => { - setConsentedStatus(tokenPresent, permissions, consentedScopes); - }, [consentedScopes]); - const sortPermissions = (permissionsToSort: IPermission[]): IPermission[] => { - return permissionsToSort ? permissionsToSort.sort(dynamicSort('value', SortOrder.ASC)) : []; + try { + return [...permissionsToSort].sort(dynamicSort('value', SortOrder.ASC)); + } catch (error) { + // ignore + } + return permissionsToSort; } const renderDetailsHeader = (properties: any, defaultRender?: any): JSX.Element => { @@ -82,8 +84,6 @@ const FullPermissions: React.FC> = (): JSX.Element => { } }, [scopes.data]); - setConsentedStatus(tokenPresent, permissions, consentedScopes); - const searchValueChanged = (value?: string): void => { setSearchValue(value!); const searchResults = searchPermissions(value); @@ -141,18 +141,19 @@ const FullPermissions: React.FC> = (): JSX.Element => { const chooseFilter = (chosenFilter: Filter) => { setFilter(chosenFilter); + const searchResults = searchPermissions(searchValue); switch (chosenFilter) { case 'all-permissions': { - setPermissions(searchPermissions(searchValue)); + setPermissions(searchResults); break; } case 'consented-permissions': { - setPermissions(searchPermissions(searchValue) + setPermissions(setConsentedStatus(tokenPresent, searchResults, consentedScopes) .filter((permission: IPermission) => permission.consented)); break; } case 'unconsented-permissions': { - setPermissions(searchPermissions(searchValue) + setPermissions(setConsentedStatus(tokenPresent, searchResults, consentedScopes) .filter((permission: IPermission) => !permission.consented)); break; } @@ -160,12 +161,13 @@ const FullPermissions: React.FC> = (): JSX.Element => { } const handleRenderItemColumn = (item?: IPermission, index?: number, column?: IColumn) => { - return ; + return ; } const columns = getColumns({ source: 'panel', tokenPresent }); + listOfPermissions = setConsentedStatus(tokenPresent, sortPermissions(permissions), consentedScopes); const permissionsList: PermissionListItem[] = []; - permissions.map((perm: IPermission) => { + listOfPermissions.map((perm: IPermission) => { const permission: PermissionListItem = { ...perm }; const permissionValue = permission.value; permission.groupName = permissionValue.split('.')[0]; @@ -199,6 +201,7 @@ const FullPermissions: React.FC> = (): JSX.Element => { }); } + return (
{loading ?
- + + + ); diff --git a/src/app/views/common/download.ts b/src/app/views/common/download.ts index 431a0643a..8d585522e 100644 --- a/src/app/views/common/download.ts +++ b/src/app/views/common/download.ts @@ -1,11 +1,10 @@ -import { telemetry, eventTypes, componentNames } from '../../../telemetry'; +import { telemetry, eventTypes } from '../../../telemetry'; -export function downloadToLocal(content: any, filename: string) { +function downloadToLocal(content: any, filename: string) { const blob = new Blob([JSON.stringify(content, null, 4)], { type: 'text/json' }); download(blob, filename); - trackDownload(filename); } function download(blob: Blob, filename: string) { @@ -17,9 +16,14 @@ function download(blob: Blob, filename: string) { document.body.removeChild(elem); } -function trackDownload(filename: string) { +function trackDownload(filename: string, componentName: string) { telemetry.trackEvent(eventTypes.BUTTON_CLICK_EVENT, { - ComponentName: componentNames.DOWNLOAD_POSTMAN_COLLECTION_BUTTON, + componentName, filename }); } + +export { + downloadToLocal, + trackDownload +}; diff --git a/src/app/views/common/lazy-loader/component-registry/index.tsx b/src/app/views/common/lazy-loader/component-registry/index.tsx index faf435c22..08218c510 100644 --- a/src/app/views/common/lazy-loader/component-registry/index.tsx +++ b/src/app/views/common/lazy-loader/component-registry/index.tsx @@ -76,5 +76,4 @@ export const ResourceExplorer = (props?: any) => { return ( ) -} - +} \ No newline at end of file diff --git a/src/app/views/common/lazy-loader/component-registry/popups.tsx b/src/app/views/common/lazy-loader/component-registry/popups.tsx index 732f62b88..c2e49a921 100644 --- a/src/app/views/common/lazy-loader/component-registry/popups.tsx +++ b/src/app/views/common/lazy-loader/component-registry/popups.tsx @@ -3,12 +3,18 @@ import { lazy } from 'react'; export const popups = new Map([ ['share-query', lazy(() => import('../../../query-runner/query-input/share-query/ShareQuery'))], ['theme-chooser', lazy(() => import('../../../main-header/settings/ThemeChooser'))], - ['preview-collection', lazy(() => import('../../../sidebar/resource-explorer/collection/PreviewCollection'))], - ['full-permissions', lazy(() => import('../../../query-runner/request/permissions/Permissions.Full'))] + ['preview-collection', lazy(() => import('../../../sidebar/resource-explorer/collection/APICollection'))], + ['full-permissions', lazy(() => import('../../../query-runner/request/permissions/Permissions.Full'))], + ['collection-permissions', lazy(() => import('../../../sidebar/resource-explorer/collection/CollectionPermissions'))], + ['edit-collection-panel', lazy(() => import('../../../sidebar/resource-explorer/collection/EditCollectionPanel'))], + ['edit-scope-panel', lazy(() => import('../../../sidebar/resource-explorer/collection/EditScopePanel'))] ]); export type PopupItem = 'share-query' | 'theme-chooser' | 'preview-collection' | - 'full-permissions'; \ No newline at end of file + 'full-permissions' | + 'collection-permissions' | + 'edit-collection-panel' | + 'edit-scope-panel' \ No newline at end of file diff --git a/src/app/views/common/popups/PanelWrapper.tsx b/src/app/views/common/popups/PanelWrapper.tsx index f5c60d07a..a7e20a1b0 100644 --- a/src/app/views/common/popups/PanelWrapper.tsx +++ b/src/app/views/common/popups/PanelWrapper.tsx @@ -1,11 +1,12 @@ -import { getTheme, IOverlayProps, Panel, PanelType, Spinner } from '@fluentui/react'; +import { getTheme, IconButton, IOverlayProps, Panel, PanelType, Spinner } from '@fluentui/react'; import { Suspense } from 'react'; import { useAppSelector } from '../../../../store'; +import { translateMessage } from '../../../utils/translate-messages'; import { WrapperProps } from './popups.types'; export function PanelWrapper(props: WrapperProps) { - const { theme: appTheme } = useAppSelector((state) => state); + const appTheme = useAppSelector((state) => state.theme); const theme = getTheme(); const { isOpen, dismissPopup, Component, popupsProps, closePopup } = props; const { title, renderFooter } = popupsProps.settings; @@ -42,22 +43,52 @@ export function PanelWrapper(props: WrapperProps) { const panelType = getPanelType(); const onRenderFooterContent = (): JSX.Element | null => { - return renderFooter? renderFooter() : null; + return renderFooter ? renderFooter() : null; } + const showBackButton = title === 'Edit Scope' || title === 'Edit Collection' || title === 'Preview Permissions'; + + const onRenderHeader = (): JSX.Element => ( +
+ dismissPopup()} + styles={{ root: { marginRight: 8 } }} + /> + + {title} + +
+ ); + return (
dismissPopup()} - hasCloseButton={true} + hasCloseButton={false} type={panelType} headerText={headerText.toString()} isFooterAtBottom={true} + isBlocking={true} + isLightDismiss={false} closeButtonAriaLabel='Close' overlayProps={panelOverlayProps} onRenderFooterContent={onRenderFooterContent} + onRenderHeader={showBackButton ? onRenderHeader: undefined} > + dismissPopup()} + />
{ }> diff --git a/src/app/views/query-runner/query-input/QueryInput.styles.ts b/src/app/views/query-runner/query-input/QueryInput.styles.ts index e505274e4..c4d16e454 100644 --- a/src/app/views/query-runner/query-input/QueryInput.styles.ts +++ b/src/app/views/query-runner/query-input/QueryInput.styles.ts @@ -1,7 +1,7 @@ import { ITheme } from '@fluentui/react'; export const queryInputStyles = (theme: ITheme) => { - const controlWidth = '94%'; + const controlWidth = '90%'; return { autoComplete: { input: { diff --git a/src/app/views/query-runner/query-input/QueryInput.tsx b/src/app/views/query-runner/query-input/QueryInput.tsx index 6adb5f982..35ec782e1 100644 --- a/src/app/views/query-runner/query-input/QueryInput.tsx +++ b/src/app/views/query-runner/query-input/QueryInput.tsx @@ -13,7 +13,6 @@ import SubmitButton from '../../../views/common/submit-button/SubmitButton'; import { shouldRunQuery } from '../../sidebar/sample-queries/sample-query-utils'; import { queryRunnerStyles } from '../QueryRunner.styles'; import { AutoComplete } from './auto-complete'; -import { ShareButton } from './share-query'; const QueryInput = (props: IQueryInputProps) => { const { @@ -25,6 +24,7 @@ const QueryInput = (props: IQueryInputProps) => { const dispatch = useAppDispatch(); const validation = useContext(ValidationContext); + const urlVersions: IDropdownOption[] = []; GRAPH_API_VERSIONS.forEach(version => { urlVersions.push({ @@ -44,7 +44,7 @@ const QueryInput = (props: IQueryInputProps) => { method: sampleQuery.selectedVerb, authenticated, url: sampleQuery.sampleUrl }); - const { queryButtonStyles, verbSelector, shareQueryButtonStyles } = queryRunnerStyles(); + const { queryButtonStyles, verbSelector } = queryRunnerStyles(); verbSelector.title = { ...verbSelector.title, background: getStyleFor(sampleQuery.selectedVerb) @@ -79,13 +79,13 @@ const QueryInput = (props: IQueryInputProps) => { }; const queryInputStackTokens: IStackTokens = { - childrenGap: 7 + childrenGap: 10 }; return ( <> - + { onChange={(event, method) => handleOnVersionChange(method)} /> - + { allowDisabledFocus={true} /> - - + + diff --git a/src/app/views/query-runner/query-input/auto-complete/suffix/SuffixRenderer.tsx b/src/app/views/query-runner/query-input/auto-complete/suffix/SuffixRenderer.tsx index 05a5cc287..00d4265ef 100644 --- a/src/app/views/query-runner/query-input/auto-complete/suffix/SuffixRenderer.tsx +++ b/src/app/views/query-runner/query-input/auto-complete/suffix/SuffixRenderer.tsx @@ -11,6 +11,7 @@ import { parseSampleUrl } from '../../../../../utils/sample-url-generation'; import { translateMessage } from '../../../../../utils/translate-messages'; import DocumentationService from './documentation'; import { styles } from './suffix.styles'; +import ShareButton from '../../share-query/ShareButton'; const SuffixRenderer = () => { const sampleQuery = useAppSelector((state)=> state.sampleQuery); @@ -91,6 +92,7 @@ const SuffixRenderer = () => { disabled={!documentationLinkAvailable} /> + ); } diff --git a/src/app/views/query-runner/query-input/share-query/ShareButton.tsx b/src/app/views/query-runner/query-input/share-query/ShareButton.tsx index 3d7989024..7a9270341 100644 --- a/src/app/views/query-runner/query-input/share-query/ShareButton.tsx +++ b/src/app/views/query-runner/query-input/share-query/ShareButton.tsx @@ -1,11 +1,10 @@ import { - DirectionalHint, - IconButton, IIconProps, TooltipHost + IconButton, IIconProps, ITooltipHostStyles, TooltipHost } from '@fluentui/react'; import { usePopups } from '../../../../services/hooks'; import { translateMessage } from '../../../../utils/translate-messages'; -import { shareQueryStyles } from './ShareQuery.styles'; +import { styles } from '../auto-complete/suffix/suffix.styles'; const ShareButton = () => { @@ -15,7 +14,7 @@ const ShareButton = () => { iconName: 'Share' } - const shareButtonStyles = shareQueryStyles().iconButton; + const shareButtonStyles: Partial = { root: { display: 'inline-block' } }; const content =
{translateMessage('Share Query')}
const calloutProps = { @@ -27,7 +26,7 @@ const ShareButton = () => { showShareQuery({ @@ -37,8 +36,7 @@ const ShareButton = () => { } })} iconProps={iconProps} - styles={shareButtonStyles} - role={'button'} + className={styles.iconButton} ariaLabel={translateMessage('Share Query')} /> diff --git a/src/app/views/sidebar/history/History.tsx b/src/app/views/sidebar/history/History.tsx index a89b03e76..379233fad 100644 --- a/src/app/views/sidebar/history/History.tsx +++ b/src/app/views/sidebar/history/History.tsx @@ -29,6 +29,8 @@ import { classNames } from '../../classnames'; import { NoResultsFound } from '../sidebar-utils/SearchResult'; import { sidebarStyles } from '../Sidebar.styles'; import { createHarEntry, exportQuery, generateHar } from './har-utils'; +import { ResourceLinkType } from '../../../../types/resources'; +import { addResourcePaths, removeResourcePaths } from '../../../services/slices/collections.slice'; const columns = [ { key: 'button', name: '', fieldName: '', minWidth: 20, maxWidth: 20 }, @@ -96,6 +98,7 @@ const History = (props: any) => { const [category, setCategory] = useState(''); const [groups, setGroups] = useState([]); const [searchStarted, setSearchStarted] = useState(false); + const {collections} = useAppSelector((state) => state.collections); const shouldGenerateGroups = useRef(true); @@ -120,6 +123,15 @@ const History = (props: any) => { return NoResultsFound('We did not find any history items'); } + const isInCollection = (item: IHistoryItem) => { + const defaultCollection = collections.find((collection) => collection.isDefault); + if (!defaultCollection) { return false; } + return defaultCollection.paths.some((path) => { + const { relativeUrl } = processUrlAndVersion(item.url); + return path.url === relativeUrl && path.method === item.method; + }); + }; + const searchValueChanged = (_event: any, value?: string): void => { shouldGenerateGroups.current = true; setSearchStarted(searchStatus => !searchStatus); @@ -146,6 +158,7 @@ const History = (props: any) => { ); }; + const renderItemColumn = (item: any, index: number | undefined, column: IColumn | undefined) => { const hostId: string = getId('tooltipHost'); const currentTheme = getTheme(); @@ -155,6 +168,7 @@ const History = (props: any) => { const viewText = translateMessage('view'); const removeText = translateMessage('Delete'); const exportQueryText = translateMessage('Export'); + const inCollection = isInCollection(item); if (column) { const queryContent = item[column.fieldName as keyof any] as string; @@ -202,6 +216,23 @@ const History = (props: any) => { }, onClick: () => onExportQuery(item) }, + ...(inCollection + ? [ + { + key: 'removeFromCollection', + text: translateMessage('Remove from Collection'), + iconProps: { iconName: 'BoxSubtractSolid' }, + onClick: () => handleRemoveFromCollection(item) + } + ] + : [ + { + key: 'addToCollection', + text: translateMessage('Add to Collection'), + iconProps: { iconName: 'BoxAdditionSolid' }, + onClick: () => handleAddToCollection(item) + } + ]), { key: 'remove', text: removeText, @@ -478,7 +509,43 @@ const History = (props: any) => { ItemIndex: query.index, QuerySignature: `${query.method} ${sanitizedUrl}` }); - } + }; + + const processUrlAndVersion = (url: string) => { + let version = 'v1.0'; + if (url.includes('graph.microsoft.com/beta')) { + version = 'beta'; + url = url.replace('https://graph.microsoft.com/beta', ''); + } else { + url = url.replace('https://graph.microsoft.com/v1.0', ''); + } + return { relativeUrl: url, version }; + }; + const formatHistoryItem = (item: IHistoryItem) => { + const { relativeUrl, version } = processUrlAndVersion(item.url); + const pathSegments = relativeUrl.split('/').filter(Boolean); + const name = pathSegments[pathSegments.length - 1] || relativeUrl; + + return { + paths: pathSegments, + name, + type: ResourceLinkType.PATH, + version, + method: item.method, + url: relativeUrl, + key: `${item.index}-${item.url}` + }; + }; + + const handleAddToCollection = (item: IHistoryItem) => { + const resourcePath = formatHistoryItem(item); + dispatch(addResourcePaths([resourcePath])); + }; + + const handleRemoveFromCollection = (item: IHistoryItem) => { + const resourcePath = formatHistoryItem(item); + dispatch(removeResourcePaths([resourcePath])); + }; return ( diff --git a/src/app/views/sidebar/resource-explorer/ResourceExplorer.tsx b/src/app/views/sidebar/resource-explorer/ResourceExplorer.tsx index 068366881..a78fd3ae9 100644 --- a/src/app/views/sidebar/resource-explorer/ResourceExplorer.tsx +++ b/src/app/views/sidebar/resource-explorer/ResourceExplorer.tsx @@ -1,9 +1,8 @@ import { - INavLink, INavLinkGroup, Label, Nav, SearchBox, Spinner, SpinnerSize, - Stack, - styled, - Toggle -} from '@fluentui/react'; + DefaultButton, + INavLink, INavLinkGroup, Label, + Nav, SearchBox, Spinner, SpinnerSize, Stack, styled, Toggle, + useTheme} from '@fluentui/react'; import debouce from 'lodash.debounce'; import { useEffect, useMemo, useState } from 'react'; @@ -20,23 +19,20 @@ import { translateMessage } from '../../../utils/translate-messages'; import { classNames } from '../../classnames'; import { NoResultsFound } from '../sidebar-utils/SearchResult'; import { sidebarStyles } from '../Sidebar.styles'; -import { UploadPostmanCollection } from './collection/UploadCollection'; -import CommandOptions from './command-options/CommandOptions'; -import { - createResourcesList, getResourcePaths, - getUrlFromLink -} from './resource-explorer.utils'; +import { createResourcesList, getResourcePaths, getUrlFromLink } from './resource-explorer.utils'; import ResourceLink from './ResourceLink'; -import { navStyles } from './resources.styles'; +import { navStyles, resourceExplorerStyles } from './resources.styles'; +import { usePopups } from '../../../services/hooks/usePopups'; const UnstyledResourceExplorer = (props: any) => { - const { resources: { data, pending }, collections } = useAppSelector( - (state) => state - ); + const { data, pending } = useAppSelector((state) => state.resources); + const { collections } = useAppSelector((state) => state.collections); const dispatch: AppDispatch = useAppDispatch(); const classes = classNames(props); - const selectedLinks = collections && collections.length > 0 ? collections.find(k => k.isDefault)!.paths : []; + const theme = useTheme(); + const styles = resourceExplorerStyles(theme); + const selectedLinks = collections && collections.length > 0 ? collections.find(k => k.isDefault)!.paths : []; const versions: { key: string, text: string }[] = [ { key: 'v1.0', text: 'v1.0' }, { key: 'beta', text: 'beta' } @@ -48,10 +44,10 @@ const UnstyledResourceExplorer = (props: any) => { ? data[version].children : []; const [searchText, setSearchText] = useState(''); - const filteredPayload = searchText ? searchResources(resourcesToUse!, searchText) : resourcesToUse!; + const filteredPayload = searchText ? searchResources(resourcesToUse, searchText) : resourcesToUse; const navigationGroup = createResourcesList(filteredPayload, version, searchText); - const [items, setItems] = useState(navigationGroup); + const { show: previewCollection } = usePopups('preview-collection', 'panel'); useEffect(() => { setItems(navigationGroup); @@ -118,6 +114,15 @@ const UnstyledResourceExplorer = (props: any) => { }); } + const openPreviewCollection = () => { + previewCollection({ + settings: { + title: translateMessage('My API collection'), + width: 'xl' + } + }) + } + if (pending) { return ( { onChange={debouncedSearch} styles={searchBoxStyles} /> -
- - - < UploadPostmanCollection /> + + + +
+ {selectedLinks.length > 0 ? `(${selectedLinks.length})` : ''} +
+
+
+
+ + + + + + - - - {selectedLinks && selectedLinks.length > 0 && <> - - - - } - - - {items[0].links.length > 0 && - } - { items[0].links.length === 0 ? NoResultsFound('No resources found', { paddingBottom: '20px' }) : (