diff --git a/.deps/dev.md b/.deps/dev.md index 991f1994d..3a04985f5 100644 --- a/.deps/dev.md +++ b/.deps/dev.md @@ -63,18 +63,18 @@ | [`@istanbuljs/schema@0.1.3`](git+https://github.com/istanbuljs/schema.git) | MIT | clearlydefined | | [`@jest/console@29.6.2`](https://github.com/facebook/jest.git) | MIT | clearlydefined | | [`@jest/core@29.6.2`](https://github.com/facebook/jest.git) | MIT | clearlydefined | -| [`@jest/environment@29.6.2`](https://github.com/facebook/jest.git) | MIT | clearlydefined | +| [`@jest/environment@29.7.0`](https://github.com/jestjs/jest.git) | MIT | clearlydefined | | [`@jest/expect-utils@29.6.2`](https://github.com/facebook/jest.git) | MIT | clearlydefined | | [`@jest/expect@29.6.2`](https://github.com/facebook/jest.git) | MIT | clearlydefined | -| [`@jest/fake-timers@29.6.2`](https://github.com/facebook/jest.git) | MIT | clearlydefined | +| [`@jest/fake-timers@29.7.0`](https://github.com/jestjs/jest.git) | MIT | clearlydefined | | [`@jest/globals@29.6.2`](https://github.com/facebook/jest.git) | MIT | clearlydefined | | [`@jest/reporters@29.6.2`](https://github.com/facebook/jest.git) | MIT | clearlydefined | | [`@jest/schemas@29.6.0`](https://github.com/facebook/jest.git) | MIT | clearlydefined | | [`@jest/source-map@29.6.0`](https://github.com/facebook/jest.git) | MIT | clearlydefined | | [`@jest/test-result@29.6.2`](https://github.com/facebook/jest.git) | MIT | clearlydefined | -| [`@jest/test-sequencer@29.6.2`](https://github.com/facebook/jest.git) | MIT | clearlydefined | +| [`@jest/test-sequencer@29.7.0`](https://github.com/jestjs/jest.git) | MIT | clearlydefined | | [`@jest/transform@29.6.2`](https://github.com/facebook/jest.git) | MIT | clearlydefined | -| [`@jest/types@29.6.1`](https://github.com/facebook/jest.git) | MIT | clearlydefined | +| [`@jest/types@29.6.3`](https://github.com/jestjs/jest.git) | MIT | clearlydefined | | [`@jridgewell/gen-mapping@0.3.3`](https://github.com/jridgewell/gen-mapping) | MIT | clearlydefined | | [`@jridgewell/resolve-uri@3.1.1`](https://github.com/jridgewell/resolve-uri) | MIT | clearlydefined | | [`@jridgewell/set-array@1.1.2`](https://github.com/jridgewell/set-array) | MIT | clearlydefined | @@ -138,7 +138,7 @@ | [`@sinonjs/commons@3.0.0`](git+https://github.com/sinonjs/commons.git) | BSD-3-Clause | clearlydefined | | [`@sinonjs/fake-timers@10.3.0`](https://github.com/sinonjs/fake-timers.git) | BSD-3-Clause | #9214 | | [`@testing-library/dom@7.31.2`](https://github.com/testing-library/dom-testing-library) | MIT | (clearlydefined)[https://clearlydefined.io/definitions/npm/npmjs/@testing-library/dom/7.31.2] | -| [`@testing-library/jest-dom@5.17.0`](https://github.com/testing-library/jest-dom) | MIT | clearlydefined | +| [`@testing-library/jest-dom@6.1.3`](https://github.com/testing-library/jest-dom) | MIT | clearlydefined | | [`@testing-library/react@10.4.9`](https://github.com/testing-library/react-testing-library) | MIT | clearlydefined | | [`@testing-library/user-event@12.8.3`](https://github.com/testing-library/user-event) | MIT | clearlydefined | | [`@tootallnate/once@2.0.0`](git://github.com/TooTallNate/once.git) | MIT | clearlydefined | @@ -253,11 +253,12 @@ | [`at-least-node@1.0.0`](git+https://github.com/RyanZim/at-least-node.git) | ISC | clearlydefined | | [`available-typed-arrays@1.0.5`](git+https://github.com/inspect-js/available-typed-arrays.git) | MIT | clearlydefined | | [`axios-mock-adapter@1.21.5`](git+https://github.com/ctimmerm/axios-mock-adapter.git) | MIT | clearlydefined | -| [`babel-jest@29.6.2`](https://github.com/facebook/jest.git) | MIT | clearlydefined | +| [`axios@1.4.0`](https://github.com/axios/axios.git) | MIT | clearlydefined | +| [`babel-jest@29.7.0`](https://github.com/jestjs/jest.git) | MIT | clearlydefined | | [`babel-plugin-istanbul@6.1.1`](git+https://github.com/istanbuljs/babel-plugin-istanbul.git) | BSD-3-Clause | clearlydefined | -| [`babel-plugin-jest-hoist@29.5.0`](https://github.com/facebook/jest.git) | MIT | clearlydefined | +| [`babel-plugin-jest-hoist@29.6.3`](https://github.com/jestjs/jest.git) | MIT | clearlydefined | | [`babel-preset-current-node-syntax@1.0.1`](https://github.com/nicolo-ribaudo/babel-preset-current-node-syntax.git) | MIT | clearlydefined | -| [`babel-preset-jest@29.5.0`](https://github.com/facebook/jest.git) | MIT | clearlydefined | +| [`babel-preset-jest@29.6.3`](https://github.com/jestjs/jest.git) | MIT | clearlydefined | | [`before-after-hook@2.2.3`](https://github.com/gr2m/before-after-hook.git) | Apache-2.0 | clearlydefined | | [`big-integer@1.6.51`](git@github.com:peterolson/BigInteger.js.git) | Unlicense | #2439 | | [`big.js@5.2.2`](https://github.com/MikeMcl/big.js.git) | MIT | clearlydefined | @@ -279,7 +280,7 @@ | [`callsites@3.1.0`](https://github.com/sindresorhus/callsites.git) | MIT | clearlydefined | | [`camel-case@4.1.2`](git://github.com/blakeembrey/change-case.git) | MIT | clearlydefined | | [`camelcase-keys@6.2.2`](https://github.com/sindresorhus/camelcase-keys.git) | MIT | clearlydefined | -| [`camelcase@5.3.1`](https://github.com/sindresorhus/camelcase.git) | MIT | clearlydefined | +| [`camelcase@6.3.0`](https://github.com/sindresorhus/camelcase.git) | MIT | clearlydefined | | [`caniuse-api@3.0.0`](https://github.com/nyalab/caniuse-api.git) | MIT | clearlydefined | | [`caniuse-lite@1.0.30001521`](https://github.com/browserslist/caniuse-lite.git) | CC-BY-4.0 | #1196 | | [`chalk@4.1.2`](https://github.com/chalk/chalk.git) | MIT | clearlydefined | @@ -324,6 +325,7 @@ | [`copy-webpack-plugin@11.0.0`](https://github.com/webpack-contrib/copy-webpack-plugin.git) | MIT | clearlydefined | | [`core-js-pure@3.32.0`](https://github.com/zloirock/core-js.git) | MIT | #9880 | | [`cosmiconfig@7.0.0`](git+https://github.com/davidtheclark/cosmiconfig.git) | MIT | clearlydefined | +| [`create-jest@29.7.0`](https://github.com/jestjs/jest.git) | MIT | clearlydefined | | [`cross-spawn@7.0.3`](git@github.com:moxystudio/node-cross-spawn.git) | MIT | clearlydefined | | [`crypto-random-string@2.0.0`](https://github.com/sindresorhus/crypto-random-string.git) | MIT | clearlydefined | | [`css-declaration-sorter@6.4.1`](https://github.com/Siilwyn/css-declaration-sorter.git) | ISC | #9434 | @@ -559,20 +561,20 @@ | [`jackspeak@2.3.0`](git+https://github.com/isaacs/jackspeak.git) | BlueOak-1.0.0 | transitive dependency | | [`jake@10.8.7`](git://github.com/jakejs/jake.git) | Apache-2.0 | #1316 | | [`jest-changed-files@29.5.0`](https://github.com/facebook/jest.git) | MIT | clearlydefined | -| [`jest-circus@29.6.2`](https://github.com/facebook/jest.git) | MIT | clearlydefined | +| [`jest-circus@29.7.0`](https://github.com/jestjs/jest.git) | MIT | clearlydefined | | [`jest-cli@29.6.2`](https://github.com/facebook/jest.git) | MIT | clearlydefined | -| [`jest-config@29.6.2`](https://github.com/facebook/jest.git) | MIT | clearlydefined | +| [`jest-config@29.7.0`](https://github.com/jestjs/jest.git) | MIT | clearlydefined | | [`jest-diff@29.6.2`](https://github.com/facebook/jest.git) | MIT | clearlydefined | | [`jest-docblock@29.4.3`](https://github.com/facebook/jest.git) | MIT | clearlydefined | -| [`jest-each@29.6.2`](https://github.com/facebook/jest.git) | MIT | clearlydefined | -| [`jest-environment-jsdom@29.6.2`](https://github.com/facebook/jest.git) | MIT | #9317 | +| [`jest-each@29.7.0`](https://github.com/jestjs/jest.git) | MIT | clearlydefined | +| [`jest-environment-jsdom@29.7.0`](https://github.com/jestjs/jest.git) | MIT | clearlydefined | | [`jest-environment-node@29.6.2`](https://github.com/facebook/jest.git) | MIT | clearlydefined | | [`jest-get-type@29.4.3`](https://github.com/facebook/jest.git) | MIT | clearlydefined | | [`jest-haste-map@29.6.2`](https://github.com/facebook/jest.git) | MIT | clearlydefined | | [`jest-leak-detector@29.6.2`](https://github.com/facebook/jest.git) | MIT | clearlydefined | | [`jest-matcher-utils@29.6.2`](https://github.com/facebook/jest.git) | MIT | clearlydefined | | [`jest-message-util@29.6.2`](https://github.com/facebook/jest.git) | MIT | clearlydefined | -| [`jest-mock@29.6.2`](https://github.com/facebook/jest.git) | MIT | clearlydefined | +| [`jest-mock@29.7.0`](https://github.com/jestjs/jest.git) | MIT | clearlydefined | | [`jest-pnp-resolver@1.2.3`](https://github.com/arcanis/jest-pnp-resolver.git) | MIT | clearlydefined | | [`jest-regex-util@29.4.3`](https://github.com/facebook/jest.git) | MIT | clearlydefined | | [`jest-resolve-dependencies@29.6.2`](https://github.com/facebook/jest.git) | MIT | clearlydefined | @@ -580,13 +582,14 @@ | [`jest-runner@29.6.2`](https://github.com/facebook/jest.git) | MIT | clearlydefined | | [`jest-runtime@29.6.2`](https://github.com/facebook/jest.git) | MIT | clearlydefined | | [`jest-snapshot@29.6.2`](https://github.com/facebook/jest.git) | MIT | clearlydefined | -| [`jest-util@29.6.2`](https://github.com/facebook/jest.git) | MIT | clearlydefined | +| [`jest-util@29.7.0`](https://github.com/jestjs/jest.git) | MIT | clearlydefined | | [`jest-validate@29.6.2`](https://github.com/facebook/jest.git) | MIT | clearlydefined | | [`jest-watcher@29.6.2`](https://github.com/facebook/jest.git) | MIT | clearlydefined | | [`jest-websocket-mock@2.4.1`](git+https://github.com/romgain/jest-websocket-mock.git) | MIT | clearlydefined | | [`jest-worker@29.6.2`](https://github.com/facebook/jest.git) | MIT | clearlydefined | | [`jest@29.6.2`](https://github.com/facebook/jest.git) | MIT | clearlydefined | | [`jsdom@20.0.3`](https://github.com/jsdom/jsdom.git) | MIT | #7436 | +| [`jsesc@2.5.2`](https://github.com/mathiasbynens/jsesc.git) | MIT | clearlydefined | | [`json-parse-better-errors@1.0.2`](https://github.com/zkat/json-parse-better-errors) | MIT | clearlydefined | | [`json-parse-even-better-errors@3.0.0`](https://github.com/npm/json-parse-even-better-errors.git) | MIT | clearlydefined | | [`json-stable-stringify-without-jsonify@1.0.1`](git://github.com/samn/json-stable-stringify.git) | MIT | clearlydefined | @@ -600,6 +603,7 @@ | [`kleur@3.0.3`](https://github.com/lukeed/kleur.git) | MIT | clearlydefined | | [`known-css-properties@0.27.0`](https://github.com/known-css/known-css-properties.git) | MIT | clearlydefined | | [`lerna@6.6.2`](git+https://github.com/lerna/lerna.git) | MIT | #7595 | +| [`leven@3.1.0`](https://github.com/sindresorhus/leven.git) | MIT | clearlydefined | | [`levn@0.4.1`](git://github.com/gkz/levn.git) | MIT | clearlydefined | | [`libnpmaccess@6.0.4`](https://github.com/npm/cli.git) | ISC | clearlydefined | | [`libnpmpublish@7.1.4`](https://github.com/npm/cli.git) | ISC | clearlydefined | @@ -841,7 +845,7 @@ | [`string.prototype.trimstart@1.0.6`](git://github.com/es-shims/String.prototype.trimStart.git) | MIT | #4647 | | [`strip-ansi-cjs@6.0.1`](https://github.com/chalk/strip-ansi.git) | MIT | transitive dependency | | [`strip-ansi@6.0.1`](https://github.com/chalk/strip-ansi.git) | MIT | clearlydefined | -| [`strip-bom@3.0.0`](https://github.com/sindresorhus/strip-bom.git) | MIT | clearlydefined | +| [`strip-bom@4.0.0`](https://github.com/sindresorhus/strip-bom.git) | MIT | clearlydefined | | [`strip-final-newline@2.0.0`](https://github.com/sindresorhus/strip-final-newline.git) | MIT | clearlydefined | | [`strip-indent@3.0.0`](https://github.com/sindresorhus/strip-indent.git) | MIT | clearlydefined | | [`strip-json-comments@3.1.1`](https://github.com/sindresorhus/strip-json-comments.git) | MIT | clearlydefined | @@ -875,6 +879,7 @@ | [`through2@4.0.2`](https://github.com/rvagg/through2.git) | MIT | clearlydefined | | [`through@2.3.8`](https://github.com/dominictarr/through.git) | MIT | #1036 | | [`titleize@3.0.0`](https://github.com/sindresorhus/titleize.git) | MIT | clearlydefined | +| [`to-fast-properties@2.0.0`](https://github.com/sindresorhus/to-fast-properties.git) | MIT | clearlydefined | | [`to-regex-range@5.0.1`](https://github.com/micromatch/to-regex-range.git) | MIT | clearlydefined | | [`totalist@1.1.0`](https://github.com/lukeed/totalist.git) | MIT | clearlydefined | | [`touch@3.1.0`](git://github.com/isaacs/node-touch.git) | ISC | clearlydefined | diff --git a/.deps/prod.md b/.deps/prod.md index c69c156b5..345f3409c 100644 --- a/.deps/prod.md +++ b/.deps/prod.md @@ -10,7 +10,6 @@ | [`@eclipse-che/dashboard-backend@7.75.0-next`](https://github.com/eclipse-che/che-dashboard) | EPL-2.0 | ecd.che | | [`@eclipse-che/dashboard-frontend@7.75.0-next`](git://github.com/eclipse/che-dashboard.git) | EPL-2.0 | ecd.che | | [`@eclipse-che/devfile-converter@0.0.1-ff55f9a`](git+https://github.com/che-incubator/devfile-converter.git) | EPL-2.0 | ecd.che | -| [`@eclipse-che/workspace-client@0.0.1-1672830275`](https://github.com/eclipse/che-workspace-client) | EPL-2.0 | ecd.che | | [`@fastify/accept-negotiator@1.1.0`](git+https://github.com/fastify/accept-negotiator.git) | MIT | clearlydefined | | [`@fastify/ajv-compiler@3.5.0`](git+https://github.com/fastify/ajv-compiler.git) | MIT | clearlydefined | | [`@fastify/cookie@8.3.0`](git+ssh://git@github.com/fastify/fastify-cookie.git) | MIT | clearlydefined | @@ -379,7 +378,6 @@ | [`trim-right@1.0.1`](https://github.com/sindresorhus/trim-right.git) | MIT | clearlydefined | | [`tslib@2.6.1`](https://github.com/Microsoft/tslib.git) | 0BSD | #9189 | | [`tunnel-agent@0.6.0`](https://github.com/mikeal/tunnel-agent) | Apache-2.0 | clearlydefined | -| [`tunnel@0.0.6`](https://github.com/koichik/node-tunnel.git) | MIT | clearlydefined | | [`tweetnacl@0.14.5`](https://github.com/dchest/tweetnacl-js.git) | Unlicense | #1035 | | [`type-fest@1.4.0`](https://github.com/sindresorhus/type-fest.git) | (MIT OR CC0-1.0) | clearlydefined | | [`underscore@1.13.6`](git://github.com/jashkenas/underscore.git) | MIT | clearlydefined | diff --git a/packages/common/package.json b/packages/common/package.json index 4b5bda377..98a80ca78 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -28,7 +28,7 @@ "eslint-config-prettier": "^9.0.0", "eslint-plugin-notice": "^0.9.10", "eslint-plugin-prettier": "^5.0.0", - "jest": "^29.6.2", + "jest": "^29.7.0", "prettier": "^3.0.1", "rimraf": "^5.0.1", "ts-jest": "^29.1.1", diff --git a/packages/common/src/helpers/__tests__/errors.spec.ts b/packages/common/src/helpers/__tests__/errors.spec.ts index fb3cd428d..fec4743b2 100644 --- a/packages/common/src/helpers/__tests__/errors.spec.ts +++ b/packages/common/src/helpers/__tests__/errors.spec.ts @@ -335,5 +335,26 @@ describe('Errors helper', () => { }; expect(getMessage(error)).toEqual(expectedMessage); }); + + it('should return a message if `error.response.data` includes a HTML page', () => { + const htmlPage = + '...'; + const error = { + name: 'Error', + config: {}, + request: {}, + response: { + data: htmlPage, + status: 500, + statusText: 'Internal Server Error', + headers: {}, + config: {}, + request: {}, + }, + message: '"500" returned by "/kubernetes/namespace/provision".', + }; + + expect(getMessage(error)).toEqual(error.message); + }); }); }); diff --git a/packages/common/src/helpers/errors.ts b/packages/common/src/helpers/errors.ts index c60b03f53..3b7a64589 100644 --- a/packages/common/src/helpers/errors.ts +++ b/packages/common/src/helpers/errors.ts @@ -50,7 +50,9 @@ export function getMessage(error: unknown): string { if (includesAxiosResponse(error)) { const response = error.response; if (typeof response.data === 'string') { - return response.data; + if (response.data.toLowerCase().trim().indexOf('{componentToWrap}; + return {componentToWrap}; } describe('Error boundary', () => { @@ -66,6 +67,7 @@ describe('Error boundary', () => { const showDetailsAction = screen.getByRole('button', { name: 'View stack' }); userEvent.click(showDetailsAction); + expect(mockOnError).not.toHaveBeenCalled(); expect(screen.queryByText('in BadComponent', { exact: false })).toBeTruthy(); expect(screen.queryByText('in ErrorBoundary', { exact: false })).toBeTruthy(); @@ -98,6 +100,7 @@ describe('Error boundary', () => { const errorBoundary = wrapComponent(); render(errorBoundary); + expect(mockOnError).toHaveBeenCalledWith('Loading chunk 23 failed.'); expect( screen.queryByText('The application has been likely updated on the server.', { exact: false, @@ -111,6 +114,7 @@ describe('Error boundary', () => { const errorBoundary = wrapComponent(); render(errorBoundary); + expect(mockOnError).toHaveBeenCalledWith('Loading chunk 23 failed.'); expect( screen.queryByText('Refreshing a page to get newer resources in', { exact: false }), ).toBeTruthy(); @@ -127,6 +131,7 @@ describe('Error boundary', () => { const stopCountdownAction = screen.getByRole('button', { name: 'Stop countdown' }); userEvent.click(stopCountdownAction); + expect(mockOnError).toHaveBeenCalledWith('Loading chunk 23 failed.'); expect( screen.queryByText('Refreshing a page to get newer resources in', { exact: false }), ).toBeFalsy(); @@ -148,6 +153,7 @@ describe('Error boundary', () => { jest.advanceTimersByTime(35000); + expect(mockOnError).toHaveBeenCalledWith('Loading chunk 23 failed.'); expect(window.location.reload).toHaveBeenCalled(); expect(window.location.reload).toHaveBeenCalledTimes(1); }); @@ -161,6 +167,7 @@ describe('Error boundary', () => { userEvent.click(reloadNowAction); userEvent.click(reloadNowAction); + expect(mockOnError).toHaveBeenCalledWith('Loading chunk 23 failed.'); expect(window.location.reload).toHaveBeenCalled(); expect(window.location.reload).toHaveBeenCalledTimes(3); }); @@ -185,6 +192,7 @@ describe('Error boundary', () => { window.dispatchEvent(new Event('beforeunload')); render(errorBoundary); + expect(mockOnError).toHaveBeenCalledWith('Loading chunk 23 failed.'); expect( screen.queryByText( 'Contact an administrator if refreshing continues after the next load.', @@ -225,6 +233,7 @@ describe('Error boundary', () => { window.dispatchEvent(new Event('beforeunload')); render(goodComponent); + expect(mockOnError).toHaveBeenCalledWith('Loading chunk 23 failed.'); expect(sessionStorage.getItem(STORAGE_KEY_RELOAD_NUMBER)).toBeNull(); }); }); diff --git a/packages/dashboard-frontend/src/Layout/ErrorBoundary/index.tsx b/packages/dashboard-frontend/src/Layout/ErrorBoundary/index.tsx index c56c270da..c876057c3 100644 --- a/packages/dashboard-frontend/src/Layout/ErrorBoundary/index.tsx +++ b/packages/dashboard-frontend/src/Layout/ErrorBoundary/index.tsx @@ -28,7 +28,9 @@ export const STORAGE_KEY_RELOAD_NUMBER = 'UD:ErrorBoundary:reloaded'; const RELOAD_TIMEOUT_SEC = 30; const RELOADS_FOR_EXTENDED_MESSAGE = 2; -type Props = PropsWithChildren; +type Props = PropsWithChildren & { + onError: (error?: string) => void; +}; type State = { hasError: boolean; error?: Error; @@ -74,6 +76,7 @@ export class ErrorBoundary extends React.PureComponent { }); if (this.testResourceNotFound(error)) { + this.props.onError(error.message); this.setState({ shouldReload: true, }); diff --git a/packages/dashboard-frontend/src/Layout/StoreErrorsAlert/__tests__/index.spec.tsx b/packages/dashboard-frontend/src/Layout/StoreErrorsAlert/__tests__/index.spec.tsx index 66362fb80..18192c7a8 100644 --- a/packages/dashboard-frontend/src/Layout/StoreErrorsAlert/__tests__/index.spec.tsx +++ b/packages/dashboard-frontend/src/Layout/StoreErrorsAlert/__tests__/index.spec.tsx @@ -138,31 +138,6 @@ describe('StoreErrorAlert component', () => { const workspacesSettingsAlert = screen.queryByRole('heading', { name: /expected error 7/i }); expect(workspacesSettingsAlert).toBeFalsy(); }); - - it('should show sanity check error after bootstrap', () => { - const store = new FakeStoreBuilder().build(); - const { rerender } = renderComponent(store); - - // no alerts at this point - const alertHeading = screen.queryByRole('heading', { name: /danger alert/i }); - expect(alertHeading).toBeFalsy(); - - const nextStore = new FakeStoreBuilder() - .withSanityCheck({ - error: 'expected error', - }) - .build(); - rerender( - - - - , - ); - - const nextAlertHeading = screen.queryByRole('heading', { name: /danger alert/i }); - expect(nextAlertHeading).toBeTruthy(); - expect(nextAlertHeading).toHaveTextContent('expected error'); - }); }); function renderComponent(store: Store): RenderResult { diff --git a/packages/dashboard-frontend/src/Layout/index.tsx b/packages/dashboard-frontend/src/Layout/index.tsx index ba31a3179..ce38ab714 100644 --- a/packages/dashboard-frontend/src/Layout/index.tsx +++ b/packages/dashboard-frontend/src/Layout/index.tsx @@ -30,6 +30,8 @@ import { selectBranding } from '../store/Branding/selectors'; import { ToggleBarsContext } from '../contexts/ToggleBars'; import { signOut } from '../services/helpers/login'; import { selectDashboardLogo } from '../store/ServerConfig/selectors'; +import * as SanityCheckStore from '../store/SanityCheck'; +import { selectSanityCheckError } from '../store/SanityCheck/selectors'; const IS_MANAGED_SIDEBAR = false; @@ -86,6 +88,14 @@ export class Layout extends React.PureComponent { this.hideAllBars(); } } + private testBackends(error?: string): void { + this.props.testBackends().catch(() => { + if (error) { + console.error(error); + } + console.error('Error testing backends:', this.props.sanityCheckError); + }); + } public render(): React.ReactElement { /* check for startup issues */ @@ -135,7 +145,7 @@ export class Layout extends React.PureComponent { } isManagedSidebar={IS_MANAGED_SIDEBAR} > - + this.testBackends(error)}> {this.props.children} @@ -149,9 +159,10 @@ export class Layout extends React.PureComponent { const mapStateToProps = (state: AppState) => ({ branding: selectBranding(state), dashboardLogo: selectDashboardLogo(state), + sanityCheckError: selectSanityCheckError(state), }); -const connector = connect(mapStateToProps); +const connector = connect(mapStateToProps, SanityCheckStore.actionCreators); type MappedProps = ConnectedProps; export default connector(Layout); diff --git a/packages/dashboard-frontend/src/__tests__/workspaceCreationTimeCheck.check.tsx b/packages/dashboard-frontend/src/__tests__/workspaceCreationTimeCheck.check.tsx index cd3c2e037..c5bda5f94 100644 --- a/packages/dashboard-frontend/src/__tests__/workspaceCreationTimeCheck.check.tsx +++ b/packages/dashboard-frontend/src/__tests__/workspaceCreationTimeCheck.check.tsx @@ -91,7 +91,7 @@ describe('Workspace creation time', () => { await waitFor( () => expect(mockPost).toBeCalledWith('/api/factory/resolver', expect.anything()), - { timeout: 6000 }, + { timeout: 8000 }, ); expect(mockPost).toHaveBeenCalledTimes(1); expect(mockGet).not.toHaveBeenCalled(); diff --git a/packages/dashboard-frontend/src/components/BannerAlert/WebSocket/__tests__/index.spec.tsx b/packages/dashboard-frontend/src/components/BannerAlert/WebSocket/__tests__/index.spec.tsx index c64b8e784..b4de42d7e 100644 --- a/packages/dashboard-frontend/src/components/BannerAlert/WebSocket/__tests__/index.spec.tsx +++ b/packages/dashboard-frontend/src/components/BannerAlert/WebSocket/__tests__/index.spec.tsx @@ -19,7 +19,7 @@ import { BrandingData } from '../../../../services/bootstrap/branding.constant'; import { ConnectionEvent, WebsocketClient, -} from '../../../../services/dashboard-backend-client/websocketClient'; +} from '../../../../services/backend-client/websocketClient'; import { FakeStoreBuilder } from '../../../../store/__mocks__/storeBuilder'; const failingMessage = 'WebSocket connections are failing'; diff --git a/packages/dashboard-frontend/src/components/BannerAlert/WebSocket/index.tsx b/packages/dashboard-frontend/src/components/BannerAlert/WebSocket/index.tsx index dccfb4ae8..f79c5a863 100644 --- a/packages/dashboard-frontend/src/components/BannerAlert/WebSocket/index.tsx +++ b/packages/dashboard-frontend/src/components/BannerAlert/WebSocket/index.tsx @@ -20,7 +20,7 @@ import { ConnectionEvent, ConnectionListener, WebsocketClient, -} from '../../../services/dashboard-backend-client/websocketClient'; +} from '../../../services/backend-client/websocketClient'; type Props = MappedProps; diff --git a/packages/dashboard-frontend/src/inversify.config.ts b/packages/dashboard-frontend/src/inversify.config.ts index 601d8faf4..941d68534 100644 --- a/packages/dashboard-frontend/src/inversify.config.ts +++ b/packages/dashboard-frontend/src/inversify.config.ts @@ -13,20 +13,18 @@ import 'reflect-metadata'; import { Container } from 'inversify'; import getDecorators from 'inversify-inject-decorators'; -import { CheWorkspaceClient } from './services/workspace-client/cheworkspace/cheWorkspaceClient'; import { AppAlerts } from './services/alerts/appAlerts'; import { IssuesReporterService } from './services/bootstrap/issuesReporter'; import { DevWorkspaceClient } from './services/workspace-client/devworkspace/devWorkspaceClient'; import { DevWorkspaceDefaultPluginsHandler } from './services/workspace-client/devworkspace/DevWorkspaceDefaultPluginsHandler'; import { WorkspaceStoppedDetector } from './services/bootstrap/workspaceStoppedDetector'; -import { WebsocketClient } from './services/dashboard-backend-client/websocketClient'; +import { WebsocketClient } from './services/backend-client/websocketClient'; const container = new Container(); const { lazyInject } = getDecorators(container); container.bind(IssuesReporterService).toSelf().inSingletonScope(); container.bind(WebsocketClient).toSelf().inSingletonScope(); -container.bind(CheWorkspaceClient).toSelf().inSingletonScope(); container.bind(DevWorkspaceClient).toSelf().inSingletonScope(); container.bind(AppAlerts).toSelf().inSingletonScope(); container.bind(DevWorkspaceDefaultPluginsHandler).toSelf().inSingletonScope(); diff --git a/packages/dashboard-frontend/src/pages/GetStarted/GetStartedTab/ImportFromGit/GitRepoLocationInput.tsx b/packages/dashboard-frontend/src/pages/GetStarted/GetStartedTab/ImportFromGit/GitRepoLocationInput.tsx index 4ea52209f..d6efee721 100644 --- a/packages/dashboard-frontend/src/pages/GetStarted/GetStartedTab/ImportFromGit/GitRepoLocationInput.tsx +++ b/packages/dashboard-frontend/src/pages/GetStarted/GetStartedTab/ImportFromGit/GitRepoLocationInput.tsx @@ -28,7 +28,6 @@ import { ExclamationCircleIcon } from '@patternfly/react-icons'; import { FactoryLocationAdapter } from '../../../../services/factory-location-adapter'; const ERROR_PATTERN_MISMATCH = 'The URL or SSHLocation is not valid.'; -const ERROR_FAILED_LOAD = 'Failed to load the devfile.'; type Props = { onChange: (location: string) => void; @@ -41,8 +40,6 @@ type State = { }; export class GitRepoLocationInput extends React.PureComponent { - options: JSX.Element[]; - constructor(props: Props) { super(props); @@ -52,16 +49,6 @@ export class GitRepoLocationInput extends React.PureComponent { }; } - /** - * This method is used from parent component by reference. - */ - public invalidateInput(): void { - this.setState({ - errorMessage: ERROR_FAILED_LOAD, - validated: ValidatedOptions.error, - }); - } - private handleChange(location: string): void { const isValid = FactoryLocationAdapter.isHttpLocation(location) || diff --git a/packages/dashboard-frontend/src/pages/GetStarted/GetStartedTab/ImportFromGit/index.tsx b/packages/dashboard-frontend/src/pages/GetStarted/GetStartedTab/ImportFromGit/index.tsx index 1363c6bca..0168d9a69 100644 --- a/packages/dashboard-frontend/src/pages/GetStarted/GetStartedTab/ImportFromGit/index.tsx +++ b/packages/dashboard-frontend/src/pages/GetStarted/GetStartedTab/ImportFromGit/index.tsx @@ -11,36 +11,25 @@ */ import React from 'react'; -import { connect, ConnectedProps } from 'react-redux'; import { Flex, FlexItem, FormGroup, Text, TextContent, TextVariants } from '@patternfly/react-core'; -import { AppState } from '../../../../store'; -import * as DevfileRegistriesStore from '../../../../store/DevfileRegistries'; import * as FactoryResolverStore from '../../../../store/FactoryResolver'; import { GitRepoLocationInput } from './GitRepoLocationInput'; import { FactoryLocationAdapter } from '../../../../services/factory-location-adapter'; -type Props = MappedProps & { +type Props = { onDevfileResolve: (resolverState: FactoryResolverStore.ResolverState, location: string) => void; }; type State = { isLoading: boolean; }; -export class ImportFromGit extends React.PureComponent { - private factoryResolver: FactoryResolverStore.State; - private readonly devfileLocationRef: React.RefObject; - +export default class ImportFromGit extends React.PureComponent { constructor(props: Props) { super(props); this.state = { isLoading: false, }; - this.devfileLocationRef = React.createRef(); - } - - public componentDidUpdate(): void { - this.factoryResolver = this.props.factoryResolver; } private async handleLocationChange(location: string): Promise { @@ -73,7 +62,6 @@ export class ImportFromGit extends React.PureComponent { this.handleLocationChange(location)} /> @@ -84,14 +72,3 @@ export class ImportFromGit extends React.PureComponent { ); } } - -const mapStateToProps = (state: AppState) => ({ - factoryResolver: state.factoryResolver, -}); - -const connector = connect(mapStateToProps, { - ...DevfileRegistriesStore.actionCreators, -}); - -type MappedProps = ConnectedProps; -export default connector(ImportFromGit); diff --git a/packages/dashboard-frontend/src/services/backend-client/__tests__/factoryApi.spec.tsx b/packages/dashboard-frontend/src/services/backend-client/__tests__/factoryApi.spec.tsx new file mode 100644 index 000000000..dbc828cfc --- /dev/null +++ b/packages/dashboard-frontend/src/services/backend-client/__tests__/factoryApi.spec.tsx @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2018-2023 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +import mockAxios from 'axios'; +import { getFactoryResolver, refreshFactoryOauthToken } from '../factoryApi'; +import { FactoryResolver } from '../../helpers/types'; +import devfileApi from '../../devfileApi'; + +describe('Factory API', () => { + const mockPost = mockAxios.post as jest.Mock; + + const location = 'https://github.com/eclipse-che/che-dashboard.git'; + const factoryResolver: FactoryResolver = { + v: '4.0', + source: 'devfile.yaml', + scm_info: { + clone_url: location, + scm_provider: 'github', + }, + devfile: { + schemaVersion: '2.2.1', + metadata: { + name: 'che-dashboard', + namespace: 'namespace', + }, + } as devfileApi.Devfile, + links: [], + }; + + afterEach(() => { + jest.resetAllMocks(); + }); + + describe('resolve factory', () => { + it('should call "/factory/resolver"', async () => { + mockPost.mockResolvedValueOnce({ + data: expect.anything(), + }); + await getFactoryResolver(location, {}); + + expect(mockPost).toBeCalledWith('/api/factory/resolver', { + url: 'https://github.com/eclipse-che/che-dashboard.git', + }); + }); + + it('should return a factory resolver', async () => { + mockPost.mockResolvedValueOnce({ + data: factoryResolver, + }); + + const res = await getFactoryResolver(location, {}); + + expect(res).toEqual(factoryResolver); + }); + }); + + describe('refresh factory OAuth token', () => { + it('should call "/api/factory/token/refresh?url=${url}"', async () => { + mockPost.mockResolvedValueOnce({ + data: expect.anything(), + }); + + await refreshFactoryOauthToken(location); + + expect(mockPost).toBeCalledWith( + '/api/factory/token/refresh?url=https://github.com/eclipse-che/che-dashboard.git', + ); + }); + + it('should return undefined', async () => { + mockPost.mockResolvedValueOnce(undefined); + + const res = await refreshFactoryOauthToken(location); + + expect(res).toBeUndefined(); + }); + }); +}); diff --git a/packages/dashboard-frontend/src/services/backend-client/__tests__/kubernetesNamespaceApi.spec.tsx b/packages/dashboard-frontend/src/services/backend-client/__tests__/kubernetesNamespaceApi.spec.tsx new file mode 100644 index 000000000..7b1ed92b0 --- /dev/null +++ b/packages/dashboard-frontend/src/services/backend-client/__tests__/kubernetesNamespaceApi.spec.tsx @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2018-2023 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +import mockAxios from 'axios'; +import { getKubernetesNamespace, provisionKubernetesNamespace } from '../kubernetesNamespaceApi'; + +describe('Kubernetes namespace API', () => { + const mockGet = mockAxios.get as jest.Mock; + const mockPost = mockAxios.post as jest.Mock; + + const namespace: che.KubernetesNamespace = { name: 'test-name', attributes: { phase: 'Active' } }; + + afterEach(() => { + jest.resetAllMocks(); + }); + + describe('fetch namespace', () => { + it('should call "/api/kubernetes/namespace"', async () => { + mockGet.mockResolvedValueOnce({ + data: expect.anything(), + }); + await getKubernetesNamespace(); + + expect(mockGet).toBeCalledWith('/api/kubernetes/namespace'); + expect(mockPost).not.toBeCalled(); + }); + + it('should return a list of namespaces', async () => { + mockGet.mockResolvedValueOnce({ + data: [namespace], + }); + + const res = await getKubernetesNamespace(); + + expect(res).toEqual([namespace]); + }); + }); + + describe('provision namespace', () => { + it('should call "/api/kubernetes/namespace/provision"', async () => { + mockPost.mockResolvedValueOnce({ + data: expect.anything(), + }); + await provisionKubernetesNamespace(); + + expect(mockGet).not.toBeCalled(); + expect(mockPost).toBeCalledWith('/api/kubernetes/namespace/provision'); + }); + + it('should return a list of namespaces', async () => { + mockPost.mockResolvedValueOnce({ + data: [namespace], + }); + + const res = await provisionKubernetesNamespace(); + + expect(res).toEqual([namespace]); + }); + }); +}); diff --git a/packages/dashboard-frontend/src/services/backend-client/__tests__/oAuthApi.spec.tsx b/packages/dashboard-frontend/src/services/backend-client/__tests__/oAuthApi.spec.tsx new file mode 100644 index 000000000..c425bc46d --- /dev/null +++ b/packages/dashboard-frontend/src/services/backend-client/__tests__/oAuthApi.spec.tsx @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2018-2023 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +import mockAxios from 'axios'; +import { IGitOauth } from '../../../store/GitOauthConfig/types'; +import { deleteOAuthToken, getOAuthProviders, getOAuthToken } from '../oAuthApi'; + +describe('Open Authorization API', () => { + const mockGet = mockAxios.get as jest.Mock; + const mockDelete = mockAxios.delete as jest.Mock; + + const oAuthProvider = { name: 'github', endpointUrl: 'https://github.com' } as IGitOauth; + const oAuthProviderToken = { token: 'dummy_token' }; + + afterEach(() => { + jest.resetAllMocks(); + }); + + describe('fetch OAuthProviders', () => { + it('should call "/api/oauth"', async () => { + mockGet.mockResolvedValueOnce({ + data: expect.anything(), + }); + await getOAuthProviders(); + + expect(mockDelete).not.toBeCalled(); + expect(mockGet).toBeCalledWith('/api/oauth'); + }); + + it('should return a list of providers', async () => { + mockGet.mockResolvedValueOnce({ + data: [oAuthProvider], + }); + + const res = await getOAuthProviders(); + + expect(res).toEqual([oAuthProvider]); + }); + }); + + describe('fetch OAuthToken', () => { + it('should call "/api/oauth/token?oauth_provider=github"', async () => { + mockGet.mockResolvedValueOnce({ + data: expect.anything(), + }); + + await getOAuthToken(oAuthProvider.name); + + expect(mockDelete).not.toBeCalled(); + expect(mockGet).toBeCalledWith('/api/oauth/token?oauth_provider=github'); + }); + + it('should return the OAuth token', async () => { + mockGet.mockResolvedValueOnce({ + data: oAuthProviderToken, + }); + + const res = await getOAuthToken(oAuthProvider.name); + + expect(mockDelete).not.toBeCalled(); + expect(res).toEqual(oAuthProviderToken); + }); + }); + + describe('delete OAuthToken', () => { + it('should call "/api/oauth/token?oauth_provider=github"', async () => { + mockDelete.mockResolvedValueOnce(undefined); + + await deleteOAuthToken(oAuthProvider.name); + + expect(mockGet).not.toBeCalled(); + expect(mockDelete).toBeCalledWith('/api/oauth/token?oauth_provider=github'); + }); + + it('should return undefined', async () => { + mockDelete.mockResolvedValueOnce(undefined); + + const res = await deleteOAuthToken(oAuthProvider.name); + + expect(mockGet).not.toBeCalled(); + expect(res).toBeUndefined(); + }); + }); +}); diff --git a/packages/dashboard-frontend/src/services/dashboard-backend-client/clusterConfigApi.ts b/packages/dashboard-frontend/src/services/backend-client/clusterConfigApi.ts similarity index 100% rename from packages/dashboard-frontend/src/services/dashboard-backend-client/clusterConfigApi.ts rename to packages/dashboard-frontend/src/services/backend-client/clusterConfigApi.ts diff --git a/packages/dashboard-frontend/src/services/dashboard-backend-client/clusterInfoApi.ts b/packages/dashboard-frontend/src/services/backend-client/clusterInfoApi.ts similarity index 100% rename from packages/dashboard-frontend/src/services/dashboard-backend-client/clusterInfoApi.ts rename to packages/dashboard-frontend/src/services/backend-client/clusterInfoApi.ts diff --git a/packages/dashboard-frontend/src/services/dashboard-backend-client/const.ts b/packages/dashboard-frontend/src/services/backend-client/const.ts similarity index 100% rename from packages/dashboard-frontend/src/services/dashboard-backend-client/const.ts rename to packages/dashboard-frontend/src/services/backend-client/const.ts diff --git a/packages/dashboard-frontend/src/services/dashboard-backend-client/devWorkspaceApi.ts b/packages/dashboard-frontend/src/services/backend-client/devWorkspaceApi.ts similarity index 100% rename from packages/dashboard-frontend/src/services/dashboard-backend-client/devWorkspaceApi.ts rename to packages/dashboard-frontend/src/services/backend-client/devWorkspaceApi.ts diff --git a/packages/dashboard-frontend/src/services/dashboard-backend-client/devWorkspaceTemplateApi.ts b/packages/dashboard-frontend/src/services/backend-client/devWorkspaceTemplateApi.ts similarity index 100% rename from packages/dashboard-frontend/src/services/dashboard-backend-client/devWorkspaceTemplateApi.ts rename to packages/dashboard-frontend/src/services/backend-client/devWorkspaceTemplateApi.ts diff --git a/packages/dashboard-frontend/src/services/dashboard-backend-client/devworkspaceResourcesApi.ts b/packages/dashboard-frontend/src/services/backend-client/devworkspaceResourcesApi.ts similarity index 100% rename from packages/dashboard-frontend/src/services/dashboard-backend-client/devworkspaceResourcesApi.ts rename to packages/dashboard-frontend/src/services/backend-client/devworkspaceResourcesApi.ts diff --git a/packages/dashboard-frontend/src/services/dashboard-backend-client/eventsApi.ts b/packages/dashboard-frontend/src/services/backend-client/eventsApi.ts similarity index 100% rename from packages/dashboard-frontend/src/services/dashboard-backend-client/eventsApi.ts rename to packages/dashboard-frontend/src/services/backend-client/eventsApi.ts diff --git a/packages/dashboard-frontend/src/services/dashboard-backend-client/factoryResolverApi.ts b/packages/dashboard-frontend/src/services/backend-client/factoryApi.ts similarity index 76% rename from packages/dashboard-frontend/src/services/dashboard-backend-client/factoryResolverApi.ts rename to packages/dashboard-frontend/src/services/backend-client/factoryApi.ts index bace20774..cadb9fdd5 100644 --- a/packages/dashboard-frontend/src/services/dashboard-backend-client/factoryResolverApi.ts +++ b/packages/dashboard-frontend/src/services/backend-client/factoryApi.ts @@ -14,16 +14,20 @@ import axios from 'axios'; import { cheServerPrefix } from './const'; import { FactoryResolver } from '../helpers/types'; -const factoryResolverEndpoint = '/factory/resolver'; - export async function getFactoryResolver( url: string, overrideParams: { [params: string]: string } = {}, ): Promise { const response = await axios.post( - `${cheServerPrefix}${factoryResolverEndpoint}`, + `${cheServerPrefix}/factory/resolver`, Object.assign({}, overrideParams, { url }), ); return response.data; } + +export async function refreshFactoryOauthToken(url: string): Promise { + await axios.post(`${cheServerPrefix}/factory/token/refresh?url=${url}`); + + return Promise.resolve(); +} diff --git a/packages/dashboard-frontend/src/services/dashboard-backend-client/gitConfigApi.ts b/packages/dashboard-frontend/src/services/backend-client/gitConfigApi.ts similarity index 100% rename from packages/dashboard-frontend/src/services/dashboard-backend-client/gitConfigApi.ts rename to packages/dashboard-frontend/src/services/backend-client/gitConfigApi.ts diff --git a/packages/dashboard-frontend/src/services/backend-client/kubernetesNamespaceApi.ts b/packages/dashboard-frontend/src/services/backend-client/kubernetesNamespaceApi.ts new file mode 100644 index 000000000..a80986d0d --- /dev/null +++ b/packages/dashboard-frontend/src/services/backend-client/kubernetesNamespaceApi.ts @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2018-2023 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +import axios from 'axios'; +import { cheServerPrefix } from './const'; + +export async function getKubernetesNamespace(): Promise { + const response = await axios.get(`${cheServerPrefix}/kubernetes/namespace`); + + return response.data; +} + +export async function provisionKubernetesNamespace(): Promise { + const response = await axios.post(`${cheServerPrefix}/kubernetes/namespace/provision`); + + return response.data; +} diff --git a/packages/dashboard-frontend/src/services/backend-client/oAuthApi.ts b/packages/dashboard-frontend/src/services/backend-client/oAuthApi.ts new file mode 100644 index 000000000..79991a81b --- /dev/null +++ b/packages/dashboard-frontend/src/services/backend-client/oAuthApi.ts @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018-2023 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +import axios from 'axios'; +import { cheServerPrefix } from './const'; +import { api } from '@eclipse-che/common'; +import { IGitOauth } from '../../store/GitOauthConfig/types'; + +export async function getOAuthProviders(): Promise { + const response = await axios.get(`${cheServerPrefix}/oauth`); + + return response.data; +} + +export async function getOAuthToken(provider: api.GitOauthProvider): Promise<{ token: string }> { + const response = await axios.get(`${cheServerPrefix}/oauth/token?oauth_provider=${provider}`); + + return response.data; +} + +export async function deleteOAuthToken(provider: api.GitOauthProvider): Promise { + await axios.delete(`${cheServerPrefix}/oauth/token?oauth_provider=${provider}`); + + return Promise.resolve(); +} diff --git a/packages/dashboard-frontend/src/services/dashboard-backend-client/personalAccessTokenApi.ts b/packages/dashboard-frontend/src/services/backend-client/personalAccessTokenApi.ts similarity index 100% rename from packages/dashboard-frontend/src/services/dashboard-backend-client/personalAccessTokenApi.ts rename to packages/dashboard-frontend/src/services/backend-client/personalAccessTokenApi.ts diff --git a/packages/dashboard-frontend/src/services/dashboard-backend-client/podsApi.ts b/packages/dashboard-frontend/src/services/backend-client/podsApi.ts similarity index 100% rename from packages/dashboard-frontend/src/services/dashboard-backend-client/podsApi.ts rename to packages/dashboard-frontend/src/services/backend-client/podsApi.ts diff --git a/packages/dashboard-frontend/src/services/dashboard-backend-client/serverConfigApi.ts b/packages/dashboard-frontend/src/services/backend-client/serverConfigApi.ts similarity index 100% rename from packages/dashboard-frontend/src/services/dashboard-backend-client/serverConfigApi.ts rename to packages/dashboard-frontend/src/services/backend-client/serverConfigApi.ts diff --git a/packages/dashboard-frontend/src/services/dashboard-backend-client/userProfileApi.ts b/packages/dashboard-frontend/src/services/backend-client/userProfileApi.ts similarity index 100% rename from packages/dashboard-frontend/src/services/dashboard-backend-client/userProfileApi.ts rename to packages/dashboard-frontend/src/services/backend-client/userProfileApi.ts diff --git a/packages/dashboard-frontend/src/services/dashboard-backend-client/websocketClient/__tests__/index.spec.ts b/packages/dashboard-frontend/src/services/backend-client/websocketClient/__tests__/index.spec.ts similarity index 100% rename from packages/dashboard-frontend/src/services/dashboard-backend-client/websocketClient/__tests__/index.spec.ts rename to packages/dashboard-frontend/src/services/backend-client/websocketClient/__tests__/index.spec.ts diff --git a/packages/dashboard-frontend/src/services/dashboard-backend-client/websocketClient/__tests__/messageHandler.spec.ts b/packages/dashboard-frontend/src/services/backend-client/websocketClient/__tests__/messageHandler.spec.ts similarity index 100% rename from packages/dashboard-frontend/src/services/dashboard-backend-client/websocketClient/__tests__/messageHandler.spec.ts rename to packages/dashboard-frontend/src/services/backend-client/websocketClient/__tests__/messageHandler.spec.ts diff --git a/packages/dashboard-frontend/src/services/dashboard-backend-client/websocketClient/__tests__/subscriptionsManager.spec.ts b/packages/dashboard-frontend/src/services/backend-client/websocketClient/__tests__/subscriptionsManager.spec.ts similarity index 100% rename from packages/dashboard-frontend/src/services/dashboard-backend-client/websocketClient/__tests__/subscriptionsManager.spec.ts rename to packages/dashboard-frontend/src/services/backend-client/websocketClient/__tests__/subscriptionsManager.spec.ts diff --git a/packages/dashboard-frontend/src/services/dashboard-backend-client/websocketClient/index.ts b/packages/dashboard-frontend/src/services/backend-client/websocketClient/index.ts similarity index 100% rename from packages/dashboard-frontend/src/services/dashboard-backend-client/websocketClient/index.ts rename to packages/dashboard-frontend/src/services/backend-client/websocketClient/index.ts diff --git a/packages/dashboard-frontend/src/services/dashboard-backend-client/websocketClient/messageHandler.ts b/packages/dashboard-frontend/src/services/backend-client/websocketClient/messageHandler.ts similarity index 100% rename from packages/dashboard-frontend/src/services/dashboard-backend-client/websocketClient/messageHandler.ts rename to packages/dashboard-frontend/src/services/backend-client/websocketClient/messageHandler.ts diff --git a/packages/dashboard-frontend/src/services/dashboard-backend-client/websocketClient/subscriptionsManager.ts b/packages/dashboard-frontend/src/services/backend-client/websocketClient/subscriptionsManager.ts similarity index 100% rename from packages/dashboard-frontend/src/services/dashboard-backend-client/websocketClient/subscriptionsManager.ts rename to packages/dashboard-frontend/src/services/backend-client/websocketClient/subscriptionsManager.ts diff --git a/packages/dashboard-frontend/src/services/dashboard-backend-client/yamlResolverApi.ts b/packages/dashboard-frontend/src/services/backend-client/yamlResolverApi.ts similarity index 100% rename from packages/dashboard-frontend/src/services/dashboard-backend-client/yamlResolverApi.ts rename to packages/dashboard-frontend/src/services/backend-client/yamlResolverApi.ts diff --git a/packages/dashboard-frontend/src/services/bootstrap/index.ts b/packages/dashboard-frontend/src/services/bootstrap/index.ts index 7662967cf..39b0c1579 100644 --- a/packages/dashboard-frontend/src/services/bootstrap/index.ts +++ b/packages/dashboard-frontend/src/services/bootstrap/index.ts @@ -41,10 +41,10 @@ import { Workspace } from '../workspace-adapter'; import { WorkspaceRunningError, WorkspaceStoppedDetector } from './workspaceStoppedDetector'; import { selectOpenVSXUrl } from '../../store/ServerConfig/selectors'; import { selectEmptyWorkspaceUrl } from '../../store/DevfileRegistries/selectors'; -import { WebsocketClient } from '../dashboard-backend-client/websocketClient'; +import { WebsocketClient } from '../backend-client/websocketClient'; import { selectEventsResourceVersion } from '../../store/Events/selectors'; import { selectPodsResourceVersion } from '../../store/Pods/selectors'; -import { ChannelListener } from '../dashboard-backend-client/websocketClient/messageHandler'; +import { ChannelListener } from '../backend-client/websocketClient/messageHandler'; import { selectApplications } from '../../store/ClusterInfo/selectors'; import { isAvailableEndpoint } from '../helpers/api-ping'; import { DEFAULT_REGISTRY } from '../../store/DevfileRegistries'; diff --git a/packages/dashboard-frontend/src/services/helpers/types.ts b/packages/dashboard-frontend/src/services/helpers/types.ts index 80a2b4af6..8c4cf7511 100644 --- a/packages/dashboard-frontend/src/services/helpers/types.ts +++ b/packages/dashboard-frontend/src/services/helpers/types.ts @@ -13,6 +13,7 @@ import { AlertVariant } from '@patternfly/react-core'; import * as React from 'react'; import devfileApi from '../devfileApi'; +import * as cheApi from '@eclipse-che/api'; export type ActionCallback = { title: string; @@ -35,7 +36,7 @@ export interface FactoryResolver { devfile: devfileApi.Devfile | che.WorkspaceDevfile; location?: string; scm_info?: FactoryResolverScmInfo; - links: api.che.core.rest.Link[]; + links: cheApi.che.core.rest.Link[]; } export type FactoryResolverScmInfo = { diff --git a/packages/dashboard-frontend/src/services/oauth/__tests__/index.spec.ts b/packages/dashboard-frontend/src/services/oauth/__tests__/index.spec.ts index 21aff6029..5a15c2e85 100644 --- a/packages/dashboard-frontend/src/services/oauth/__tests__/index.spec.ts +++ b/packages/dashboard-frontend/src/services/oauth/__tests__/index.spec.ts @@ -13,16 +13,10 @@ import { AxiosError } from 'axios'; import common from '@eclipse-che/common'; import OAuthService from '..'; -import { container } from '../../../inversify.config'; import { DevWorkspaceBuilder } from '../../../store/__mocks__/devWorkspaceBuilder'; -import { CheWorkspaceClient } from '../../workspace-client/cheworkspace/cheWorkspaceClient'; +import * as factoryApi from '../../backend-client/factoryApi'; -const cheWorkspaceClient = container.get(CheWorkspaceClient); - -const refreshFactoryOauthTokenSpy = jest.spyOn( - cheWorkspaceClient.restApiClient, - 'refreshFactoryOauthToken', -); +const refreshFactoryOauthTokenSpy = jest.spyOn(factoryApi, 'refreshFactoryOauthToken'); const mockOpenOAuthPage = jest.fn().mockImplementation(); OAuthService.openOAuthPage = mockOpenOAuthPage; diff --git a/packages/dashboard-frontend/src/services/oauth/index.ts b/packages/dashboard-frontend/src/services/oauth/index.ts index 9f6953d69..2518dee6f 100644 --- a/packages/dashboard-frontend/src/services/oauth/index.ts +++ b/packages/dashboard-frontend/src/services/oauth/index.ts @@ -12,11 +12,8 @@ import common, { helpers } from '@eclipse-che/common'; import { OAuthResponse } from '../../store/FactoryResolver'; -import { container } from '../../inversify.config'; -import { CheWorkspaceClient } from '../workspace-client/cheworkspace/cheWorkspaceClient'; import devfileApi from '../devfileApi'; - -const WorkspaceClient = container.get(CheWorkspaceClient); +import { refreshFactoryOauthToken } from '../backend-client/factoryApi'; export default class OAuthService { static openOAuthPage(authenticationUrl: string, redirectUrl: string): void { @@ -44,7 +41,7 @@ export default class OAuthService { } try { - await WorkspaceClient.restApiClient.refreshFactoryOauthToken(project.git.remotes.origin); + await refreshFactoryOauthToken(project.git.remotes.origin); } catch (e) { if (!common.helpers.errors.includesAxiosResponse(e)) { return; diff --git a/packages/dashboard-frontend/src/services/workspace-client/__tests__/helpers.spec.ts b/packages/dashboard-frontend/src/services/workspace-client/__tests__/helpers.spec.ts index 124c82401..12dd9fd50 100644 --- a/packages/dashboard-frontend/src/services/workspace-client/__tests__/helpers.spec.ts +++ b/packages/dashboard-frontend/src/services/workspace-client/__tests__/helpers.spec.ts @@ -10,14 +10,71 @@ * Red Hat, Inc. - initial API and implementation */ -import { getCustomEditor, isForbidden, isInternalServerError, isUnauthorized } from '../helpers'; -import { CHE_EDITOR_YAML_PATH } from '../'; +import { + getCustomEditor, + hasLoginPage, + getErrorMessage, + isForbidden, + isInternalServerError, + isUnauthorized, +} from '../helpers'; +import { CHE_EDITOR_YAML_PATH } from '../helpers'; import { dump } from 'js-yaml'; import common from '@eclipse-che/common'; import devfileApi from '../../devfileApi'; import { FakeStoreBuilder } from '../../../store/__mocks__/storeBuilder'; describe('Workspace-client helpers', () => { + describe('get an error message', () => { + it('should return the default error message', () => { + expect(getErrorMessage(undefined)).toEqual('Check the browser logs message.'); + }); + it('should return the unknown error message', () => { + expect(getErrorMessage({})).toEqual('Unexpected error type. Please report a bug.'); + }); + it('should return the error message', () => { + expect( + getErrorMessage({ + response: { + status: 401, + }, + request: { + responseURL: 'http://dummyurl.com', + }, + }), + ).toEqual( + 'HTTP Error code 401. Endpoint which throws an error http://dummyurl.com. Check the browser logs message.', + ); + }); + }); + describe('checks for HTML login page in response data', () => { + it('should return false without HTML login page', () => { + expect( + hasLoginPage({ + response: { + status: 401, + statusText: '...', + headers: {}, + config: {}, + data: '...', + }, + }), + ).toBeFalsy(); + }); + it('should return true in the case with HTML login page', () => { + expect( + hasLoginPage({ + response: { + status: 401, + statusText: '...', + headers: {}, + config: {}, + data: 'Log In', + }, + }), + ).toBeTruthy(); + }); + }); describe('checks for HTTP 401 Unauthorized response status code', () => { it('should return false in the case with HTTP 400 Bad Request', () => { expect(isUnauthorized('...HTTP Status 400 ....')).toBeFalsy(); diff --git a/packages/dashboard-frontend/src/services/workspace-client/cheworkspace/cheWorkspaceClient.ts b/packages/dashboard-frontend/src/services/workspace-client/cheworkspace/cheWorkspaceClient.ts deleted file mode 100644 index 9c783beeb..000000000 --- a/packages/dashboard-frontend/src/services/workspace-client/cheworkspace/cheWorkspaceClient.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2018-2023 Red Hat, Inc. - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Red Hat, Inc. - initial API and implementation - */ - -import { injectable } from 'inversify'; -import { default as WorkspaceClientLib, IRemoteAPI } from '@eclipse-che/workspace-client'; - -/** - * This class manages the api connection. - */ -@injectable() -export class CheWorkspaceClient { - private baseUrl: string; - private _restApiClient: IRemoteAPI; - - /** - * Default constructor that is using resource. - */ - constructor() { - this.baseUrl = '/api'; - } - - get restApiClient(): IRemoteAPI { - // Lazy initialization of restApiClient - if (!this._restApiClient) { - this.updateRestApiClient(); - } - return this._restApiClient; - } - - updateRestApiClient(): void { - const baseUrl = this.baseUrl; - this._restApiClient = WorkspaceClientLib.getRestApi({ baseUrl }); - } -} diff --git a/packages/dashboard-frontend/src/services/workspace-client/devworkspace/DevWorkspaceDefaultPluginsHandler.ts b/packages/dashboard-frontend/src/services/workspace-client/devworkspace/DevWorkspaceDefaultPluginsHandler.ts index aceaf43dd..932498c11 100644 --- a/packages/dashboard-frontend/src/services/workspace-client/devworkspace/DevWorkspaceDefaultPluginsHandler.ts +++ b/packages/dashboard-frontend/src/services/workspace-client/devworkspace/DevWorkspaceDefaultPluginsHandler.ts @@ -10,7 +10,7 @@ * Red Hat, Inc. - initial API and implementation */ -import * as DwApi from '../../dashboard-backend-client/devWorkspaceApi'; +import * as DwApi from '../../backend-client/devWorkspaceApi'; import devfileApi from '../../devfileApi'; import { api } from '@eclipse-che/common'; import { createHash } from 'crypto'; diff --git a/packages/dashboard-frontend/src/services/workspace-client/devworkspace/__tests__/devWorkspaceClient.changeWorkspaceStatus.spec.ts b/packages/dashboard-frontend/src/services/workspace-client/devworkspace/__tests__/devWorkspaceClient.changeWorkspaceStatus.spec.ts index 2e1738a3f..b49b7d8ce 100644 --- a/packages/dashboard-frontend/src/services/workspace-client/devworkspace/__tests__/devWorkspaceClient.changeWorkspaceStatus.spec.ts +++ b/packages/dashboard-frontend/src/services/workspace-client/devworkspace/__tests__/devWorkspaceClient.changeWorkspaceStatus.spec.ts @@ -13,7 +13,7 @@ import { container } from '../../../../inversify.config'; import { DevWorkspaceBuilder } from '../../../../store/__mocks__/devWorkspaceBuilder'; import { DevWorkspaceClient } from '../devWorkspaceClient'; -import * as DwApi from '../../../dashboard-backend-client/devWorkspaceApi'; +import * as DwApi from '../../../backend-client/devWorkspaceApi'; describe('DevWorkspace client, changeWorkspaceStatus', () => { let client: DevWorkspaceClient; diff --git a/packages/dashboard-frontend/src/services/workspace-client/devworkspace/__tests__/devWorkspaceClient.create.spec.ts b/packages/dashboard-frontend/src/services/workspace-client/devworkspace/__tests__/devWorkspaceClient.create.spec.ts index 557432a82..165f29e52 100644 --- a/packages/dashboard-frontend/src/services/workspace-client/devworkspace/__tests__/devWorkspaceClient.create.spec.ts +++ b/packages/dashboard-frontend/src/services/workspace-client/devworkspace/__tests__/devWorkspaceClient.create.spec.ts @@ -12,8 +12,8 @@ import { container } from '../../../../inversify.config'; import { DevWorkspaceClient } from '../devWorkspaceClient'; -import * as DwtApi from '../../../dashboard-backend-client/devWorkspaceTemplateApi'; -import * as DwApi from '../../../dashboard-backend-client/devWorkspaceApi'; +import * as DwtApi from '../../../backend-client/devWorkspaceTemplateApi'; +import * as DwApi from '../../../backend-client/devWorkspaceApi'; import devfileApi from '../../../devfileApi'; describe('DevWorkspace client, create', () => { diff --git a/packages/dashboard-frontend/src/services/workspace-client/devworkspace/__tests__/devWorkspaceClient.editorUpdate.spec.ts b/packages/dashboard-frontend/src/services/workspace-client/devworkspace/__tests__/devWorkspaceClient.editorUpdate.spec.ts index 555362c37..89879393b 100644 --- a/packages/dashboard-frontend/src/services/workspace-client/devworkspace/__tests__/devWorkspaceClient.editorUpdate.spec.ts +++ b/packages/dashboard-frontend/src/services/workspace-client/devworkspace/__tests__/devWorkspaceClient.editorUpdate.spec.ts @@ -13,10 +13,10 @@ import { container } from '../../../../inversify.config'; import { DevWorkspaceClient } from '../devWorkspaceClient'; import mockAxios from 'axios'; -import { dashboardBackendPrefix } from '../../../dashboard-backend-client/const'; +import { dashboardBackendPrefix } from '../../../backend-client/const'; import getDevWorkspaceTemplate from './__mocks__/devWorkspaceSpecTemplates'; import devfileApi from '../../../devfileApi'; -import * as DwtApi from '../../../dashboard-backend-client/devWorkspaceTemplateApi'; +import * as DwtApi from '../../../backend-client/devWorkspaceTemplateApi'; describe('DevWorkspace client editor update', () => { const namespace = 'admin-che'; diff --git a/packages/dashboard-frontend/src/services/workspace-client/devworkspace/__tests__/devWorkspaceClient.onStart.spec.ts b/packages/dashboard-frontend/src/services/workspace-client/devworkspace/__tests__/devWorkspaceClient.onStart.spec.ts index 223b1836c..54fffda36 100644 --- a/packages/dashboard-frontend/src/services/workspace-client/devworkspace/__tests__/devWorkspaceClient.onStart.spec.ts +++ b/packages/dashboard-frontend/src/services/workspace-client/devworkspace/__tests__/devWorkspaceClient.onStart.spec.ts @@ -13,7 +13,7 @@ import { container } from '../../../../inversify.config'; import { DevWorkspaceBuilder } from '../../../../store/__mocks__/devWorkspaceBuilder'; import { DevWorkspaceClient } from '../devWorkspaceClient'; -import * as DwApi from '../../../dashboard-backend-client/devWorkspaceApi'; +import * as DwApi from '../../../backend-client/devWorkspaceApi'; import mockAxios from 'axios'; /* eslint-disable @typescript-eslint/no-non-null-assertion */ diff --git a/packages/dashboard-frontend/src/services/workspace-client/devworkspace/__tests__/devWorkspaceClient.update.spec.ts b/packages/dashboard-frontend/src/services/workspace-client/devworkspace/__tests__/devWorkspaceClient.update.spec.ts index 72eb58f9f..59a3b1ea1 100644 --- a/packages/dashboard-frontend/src/services/workspace-client/devworkspace/__tests__/devWorkspaceClient.update.spec.ts +++ b/packages/dashboard-frontend/src/services/workspace-client/devworkspace/__tests__/devWorkspaceClient.update.spec.ts @@ -13,7 +13,7 @@ import { container } from '../../../../inversify.config'; import { DevWorkspaceBuilder } from '../../../../store/__mocks__/devWorkspaceBuilder'; import { DevWorkspaceClient } from '../devWorkspaceClient'; -import * as DwApi from '../../../dashboard-backend-client/devWorkspaceApi'; +import * as DwApi from '../../../backend-client/devWorkspaceApi'; describe('DevWorkspace client, update', () => { let client: DevWorkspaceClient; diff --git a/packages/dashboard-frontend/src/services/workspace-client/devworkspace/devWorkspaceClient.ts b/packages/dashboard-frontend/src/services/workspace-client/devworkspace/devWorkspaceClient.ts index 629ce1cc2..bdb67e226 100644 --- a/packages/dashboard-frontend/src/services/workspace-client/devworkspace/devWorkspaceClient.ts +++ b/packages/dashboard-frontend/src/services/workspace-client/devworkspace/devWorkspaceClient.ts @@ -18,11 +18,11 @@ import { } from '@devfile/api'; import { api } from '@eclipse-che/common'; import { WorkspacesDefaultPlugins } from 'dashboard-frontend/src/store/Plugins/devWorkspacePlugins'; -import { decorate, inject, injectable } from 'inversify'; +import { inject, injectable } from 'inversify'; import { load } from 'js-yaml'; import { cloneDeep, isEqual } from 'lodash'; -import * as DwApi from '../../dashboard-backend-client/devWorkspaceApi'; -import * as DwtApi from '../../dashboard-backend-client/devWorkspaceTemplateApi'; +import * as DwApi from '../../backend-client/devWorkspaceApi'; +import * as DwtApi from '../../backend-client/devWorkspaceTemplateApi'; import devfileApi from '../../devfileApi'; import { DEVWORKSPACE_CHE_EDITOR, @@ -38,7 +38,6 @@ import { isWebTerminal } from '../../helpers/devworkspace'; import { DevWorkspaceStatus } from '../../helpers/types'; import { fetchData } from '../../registry/fetchData'; import { WorkspaceAdapter } from '../../workspace-adapter'; -import { WorkspaceClient } from '../index'; import { devWorkspaceApiGroup, devWorkspaceSingularSubresource, @@ -73,13 +72,11 @@ export interface ICheEditorYaml { }; } -decorate(injectable(), WorkspaceClient); - /** * This class manages the connection between the frontend and the devworkspace typescript library */ @injectable() -export class DevWorkspaceClient extends WorkspaceClient { +export class DevWorkspaceClient { private readonly maxStatusAttempts: number; private readonly pluginRegistryUrlEnvName: string; private readonly pluginRegistryInternalUrlEnvName: string; @@ -93,7 +90,6 @@ export class DevWorkspaceClient extends WorkspaceClient { @inject(DevWorkspaceDefaultPluginsHandler) defaultPluginsHandler: DevWorkspaceDefaultPluginsHandler, ) { - super(); this.maxStatusAttempts = 10; this.pluginRegistryUrlEnvName = 'CHE_PLUGIN_REGISTRY_URL'; this.pluginRegistryInternalUrlEnvName = 'CHE_PLUGIN_REGISTRY_INTERNAL_URL'; diff --git a/packages/dashboard-frontend/src/services/workspace-client/helpers.ts b/packages/dashboard-frontend/src/services/workspace-client/helpers.ts index 847816c31..c64cc1df6 100644 --- a/packages/dashboard-frontend/src/services/workspace-client/helpers.ts +++ b/packages/dashboard-frontend/src/services/workspace-client/helpers.ts @@ -14,52 +14,96 @@ import common from '@eclipse-che/common'; import devfileApi, { isDevfileV2 } from '../devfileApi'; import { load, dump } from 'js-yaml'; import { ICheEditorYaml } from './devworkspace/devWorkspaceClient'; -import { CHE_EDITOR_YAML_PATH } from './'; import { AppState } from '../../store'; import { ThunkDispatch } from 'redux-thunk'; import { KnownAction } from '../../store/DevfileRegistries'; import { getEditor } from '../../store/DevfileRegistries/getEditor'; +import { includesAxiosResponse } from '@eclipse-che/common/lib/helpers/errors'; + +/** + * Returns an error message for the sanity check service + */ +export function getErrorMessage(error: unknown): string { + let errorMessage = 'Check the browser logs message.'; + + if (typeof error === 'object' && error !== null) { + const { request, response } = error as { [propName: string]: any }; + const code = response?.status ? response?.status : response?.request?.status; + const endpoint = request?.responseURL ? request?.responseURL : response?.request?.responseURL; + + if (!code || !endpoint) { + return 'Unexpected error type. Please report a bug.'; + } + + errorMessage = `HTTP Error code ${code}. Endpoint which throws an error ${endpoint}. ${errorMessage}`; + } + + if (isUnauthorized(error) || isForbidden(error)) { + errorMessage += ' User session has expired. You need to re-login to the Dashboard.'; + } + + return errorMessage; +} + +/** + * Checks for login page in the axios response data + */ +export function hasLoginPage(error: unknown): boolean { + if (includesAxiosResponse(error)) { + const response = error.response; + if (typeof response.data === 'string') { + try { + const doc = new DOMParser().parseFromString(response.data, 'text/html'); + const docText = doc.documentElement.textContent; + if (docText && docText.toLowerCase().indexOf('log in') !== -1) { + return true; + } + } catch (e) { + // no op + } + } + } + return false; +} /** * Checks for HTTP 401 Unauthorized response status code */ -export function isUnauthorized(response: unknown): boolean { - return hasStatus(response, 401); +export function isUnauthorized(error: unknown): boolean { + return hasStatus(error, 401); } /** * Checks for HTTP 403 Forbidden */ -export function isForbidden(response: unknown): boolean { - return hasStatus(response, 403); +export function isForbidden(error: unknown): boolean { + return hasStatus(error, 403); } /** * Checks for HTTP 500 Internal Server Error */ -export function isInternalServerError(response: unknown): boolean { - return hasStatus(response, 500); +export function isInternalServerError(error: unknown): boolean { + return hasStatus(error, 500); } -function hasStatus(response: unknown, _status: number): boolean { - if (typeof response === 'string') { - if (response.toLowerCase().includes(`http status ${_status}`)) { +function hasStatus(error: unknown, _status: number): boolean { + if (typeof error === 'string') { + if (error.toLowerCase().includes(`http status ${_status}`)) { return true; } - } else if (common.helpers.errors.isError(response)) { - const str = response.message.toLowerCase(); + } else if (common.helpers.errors.isError(error)) { + const str = error.message.toLowerCase(); if (str.includes(`status code ${_status}`)) { return true; } - } else if (typeof response === 'object' && response !== null) { - const { status, statusCode } = response as { [propName: string]: string | number }; - if (statusCode == _status) { - return true; - } else if (status == _status) { + } else if (typeof error === 'object' && error !== null) { + const { status, statusCode } = error as { [propName: string]: string | number }; + if (statusCode == _status || status == _status) { return true; } else { try { - const str = JSON.stringify(response).toLowerCase(); + const str = JSON.stringify(error).toLowerCase(); if (str.includes(`http status ${_status}`)) { return true; } else if (str.includes(`status code ${_status}`)) { @@ -73,6 +117,8 @@ function hasStatus(response: unknown, _status: number): boolean { return false; } +export const CHE_EDITOR_YAML_PATH = '.che/che-editor.yaml'; + /** * Look for the custom editor in .che/che-editor.yaml */ diff --git a/packages/dashboard-frontend/src/services/workspace-client/index.ts b/packages/dashboard-frontend/src/services/workspace-client/index.ts deleted file mode 100644 index b708ea009..000000000 --- a/packages/dashboard-frontend/src/services/workspace-client/index.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2018-2023 Red Hat, Inc. - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Red Hat, Inc. - initial API and implementation - */ - -import { default as WorkspaceClientLib } from '@eclipse-che/workspace-client'; -import axios, { AxiosInstance } from 'axios'; - -export const CHE_EDITOR_YAML_PATH = '.che/che-editor.yaml'; - -/** - * This class manages the common functions between the che workspace client and the devworkspace client - */ -export abstract class WorkspaceClient { - protected readonly axios: AxiosInstance; - - protected constructor() { - // change this temporary solution after adding the proper method to workspace-client https://github.com/eclipse/che/issues/18311 - this.axios = (WorkspaceClientLib as any).createAxiosInstance({ loggingEnabled: false }); - - // workspaceClientLib axios interceptor - this.axios.interceptors.response.use( - response => response, - async error => { - // any status codes that falls outside the range of 2xx - return Promise.reject(error); - }, - ); - - // dashboard-backend axios interceptor - axios.interceptors.response.use( - response => response, - async error => { - // any status codes that falls outside the range of 2xx - return Promise.reject(error); - }, - ); - } -} diff --git a/packages/dashboard-frontend/src/store/BannerAlert/index.ts b/packages/dashboard-frontend/src/store/BannerAlert/index.ts index 2bcac8a60..65c9961a2 100644 --- a/packages/dashboard-frontend/src/store/BannerAlert/index.ts +++ b/packages/dashboard-frontend/src/store/BannerAlert/index.ts @@ -71,13 +71,13 @@ export const reducer: Reducer = ( switch (action.type) { case 'ADD_BANNER': - return createObject(state, { + return createObject(state, { messages: state.messages.includes(action.message) ? state.messages : state.messages.concat([action.message]), }); case 'REMOVE_BANNER': - return createObject(state, { + return createObject(state, { messages: state.messages.includes(action.message) ? state.messages.filter(message => message !== action.message) : state.messages, diff --git a/packages/dashboard-frontend/src/store/Branding/index.ts b/packages/dashboard-frontend/src/store/Branding/index.ts index 5d0ae0201..4991d2b87 100644 --- a/packages/dashboard-frontend/src/store/Branding/index.ts +++ b/packages/dashboard-frontend/src/store/Branding/index.ts @@ -117,17 +117,17 @@ export const reducer: Reducer = ( const action = incomingAction as KnownAction; switch (action.type) { case 'REQUEST_BRANDING': - return createObject(state, { + return createObject(state, { isLoading: true, error: undefined, }); case 'RECEIVED_BRANDING': - return createObject(state, { + return createObject(state, { isLoading: false, data: action.data, }); case 'RECEIVED_BRANDING_ERROR': - return createObject(state, { + return createObject(state, { isLoading: false, error: action.error, }); diff --git a/packages/dashboard-frontend/src/store/ClusterConfig/index.ts b/packages/dashboard-frontend/src/store/ClusterConfig/index.ts index e6e10ec31..616dc2dcb 100644 --- a/packages/dashboard-frontend/src/store/ClusterConfig/index.ts +++ b/packages/dashboard-frontend/src/store/ClusterConfig/index.ts @@ -15,7 +15,7 @@ import common, { ClusterConfig } from '@eclipse-che/common'; import { AppThunk } from '..'; import { createObject } from '../helpers'; import * as BannerAlertStore from '../BannerAlert'; -import { fetchClusterConfig } from '../../services/dashboard-backend-client/clusterConfigApi'; +import { fetchClusterConfig } from '../../services/backend-client/clusterConfigApi'; import { AddBannerAction } from '../BannerAlert'; import { AUTHORIZED, SanityCheckAction } from '../sanityCheckMiddleware'; @@ -105,17 +105,17 @@ export const reducer: Reducer = ( const action = incomingAction as KnownAction; switch (action.type) { case Type.REQUEST_CLUSTER_CONFIG: - return createObject(state, { + return createObject(state, { isLoading: true, error: undefined, }); case Type.RECEIVE_CLUSTER_CONFIG: - return createObject(state, { + return createObject(state, { isLoading: false, clusterConfig: action.clusterConfig, }); case Type.RECEIVE_CLUSTER_CONFIG_ERROR: - return createObject(state, { + return createObject(state, { isLoading: false, error: action.error, }); diff --git a/packages/dashboard-frontend/src/store/ClusterInfo/index.ts b/packages/dashboard-frontend/src/store/ClusterInfo/index.ts index b1593808d..62d78ff2d 100644 --- a/packages/dashboard-frontend/src/store/ClusterInfo/index.ts +++ b/packages/dashboard-frontend/src/store/ClusterInfo/index.ts @@ -14,7 +14,7 @@ import { Action, Reducer } from 'redux'; import common, { ClusterInfo } from '@eclipse-che/common'; import { AppThunk } from '..'; import { createObject } from '../helpers'; -import { fetchClusterInfo } from '../../services/dashboard-backend-client/clusterInfoApi'; +import { fetchClusterInfo } from '../../services/backend-client/clusterInfoApi'; import { AUTHORIZED, SanityCheckAction } from '../sanityCheckMiddleware'; export interface State { @@ -97,17 +97,17 @@ export const reducer: Reducer = ( const action = incomingAction as KnownAction; switch (action.type) { case Type.REQUEST_CLUSTER_INFO: - return createObject(state, { + return createObject(state, { isLoading: true, error: undefined, }); case Type.RECEIVE_CLUSTER_INFO: - return createObject(state, { + return createObject(state, { isLoading: false, clusterInfo: action.clusterInfo, }); case Type.RECEIVE_CLUSTER_INFO_ERROR: - return createObject(state, { + return createObject(state, { isLoading: false, error: action.error, }); diff --git a/packages/dashboard-frontend/src/store/DevfileRegistries/__tests__/index.spec.ts b/packages/dashboard-frontend/src/store/DevfileRegistries/__tests__/index.spec.ts index 58fa8b3f8..ce2a9380c 100644 --- a/packages/dashboard-frontend/src/store/DevfileRegistries/__tests__/index.spec.ts +++ b/packages/dashboard-frontend/src/store/DevfileRegistries/__tests__/index.spec.ts @@ -58,6 +58,14 @@ describe('Devfile registries', () => { const actions = store.getActions(); const expectedActions: devfileRegistriesStore.KnownAction[] = [ + { + type: devfileRegistriesStore.Type.REQUEST_REGISTRY_METADATA, + check: AUTHORIZED, + }, + { + type: devfileRegistriesStore.Type.REQUEST_REGISTRY_METADATA, + check: AUTHORIZED, + }, { type: devfileRegistriesStore.Type.REQUEST_REGISTRY_METADATA, check: AUTHORIZED, @@ -116,6 +124,14 @@ describe('Devfile registries', () => { const actions = store.getActions(); const expectedActions: devfileRegistriesStore.KnownAction[] = [ + { + type: devfileRegistriesStore.Type.REQUEST_REGISTRY_METADATA, + check: AUTHORIZED, + }, + { + type: devfileRegistriesStore.Type.REQUEST_REGISTRY_METADATA, + check: AUTHORIZED, + }, { type: devfileRegistriesStore.Type.REQUEST_REGISTRY_METADATA, check: AUTHORIZED, diff --git a/packages/dashboard-frontend/src/store/DevfileRegistries/fetchAndUpdateDevfileSchema.ts b/packages/dashboard-frontend/src/store/DevfileRegistries/fetchAndUpdateDevfileSchema.ts index 9f0c4ae6b..2c8c0e78b 100644 --- a/packages/dashboard-frontend/src/store/DevfileRegistries/fetchAndUpdateDevfileSchema.ts +++ b/packages/dashboard-frontend/src/store/DevfileRegistries/fetchAndUpdateDevfileSchema.ts @@ -11,7 +11,7 @@ */ import { JSONSchema7 } from 'json-schema'; -import { getDevfileSchema } from '../../services/dashboard-backend-client/devWorkspaceApi'; +import { getDevfileSchema } from '../../services/backend-client/devWorkspaceApi'; export default async function fetchAndUpdateDevfileSchema( schemaVersion: string, diff --git a/packages/dashboard-frontend/src/store/DevfileRegistries/index.ts b/packages/dashboard-frontend/src/store/DevfileRegistries/index.ts index f8980f7b4..dc6128eca 100644 --- a/packages/dashboard-frontend/src/store/DevfileRegistries/index.ts +++ b/packages/dashboard-frontend/src/store/DevfileRegistries/index.ts @@ -19,6 +19,7 @@ import fetchAndUpdateDevfileSchema from './fetchAndUpdateDevfileSchema'; import devfileApi from '../../services/devfileApi'; import { fetchResources, loadResourcesContent } from '../../services/registry/resources'; import { AUTHORIZED, SanityCheckAction } from '../sanityCheckMiddleware'; +import { selectAsyncIsAuthorized, selectSanityCheckError } from '../SanityCheck/selectors'; export const DEFAULT_REGISTRY = '/dashboard/devfile-registry/'; @@ -170,12 +171,15 @@ export const actionCreators: ActionCreators = { */ requestRegistriesMetadata: (registryUrls: string, isExternal: boolean): AppThunk> => - async (dispatch): Promise => { - await dispatch({ type: Type.REQUEST_REGISTRY_METADATA, check: AUTHORIZED }); - + async (dispatch, getState): Promise => { const registries: string[] = registryUrls.split(' '); const promises = registries.map(async url => { try { + await dispatch({ type: Type.REQUEST_REGISTRY_METADATA, check: AUTHORIZED }); + if (!(await selectAsyncIsAuthorized(getState()))) { + const error = selectSanityCheckError(getState()); + throw new Error(error); + } const metadata: che.DevfileMetaData[] = await fetchRegistryMetadata(url, isExternal); if (!Array.isArray(metadata) || metadata.length === 0) { return; @@ -205,9 +209,13 @@ export const actionCreators: ActionCreators = { requestDevfile: (url: string): AppThunk> => - async (dispatch): Promise => { - await dispatch({ type: Type.REQUEST_DEVFILE, check: AUTHORIZED }); + async (dispatch, getState): Promise => { try { + await dispatch({ type: Type.REQUEST_DEVFILE, check: AUTHORIZED }); + if (!(await selectAsyncIsAuthorized(getState()))) { + const error = selectSanityCheckError(getState()); + throw new Error(error); + } const devfile = await fetchDevfile(url); dispatch({ type: Type.RECEIVE_DEVFILE, devfile, url }); return devfile; @@ -218,10 +226,13 @@ export const actionCreators: ActionCreators = { requestResources: (resourcesUrl: string): AppThunk> => - async (dispatch): Promise => { - await dispatch({ type: Type.REQUEST_RESOURCES, check: AUTHORIZED }); - + async (dispatch, getState): Promise => { try { + await dispatch({ type: Type.REQUEST_RESOURCES, check: AUTHORIZED }); + if (!(await selectAsyncIsAuthorized(getState()))) { + const error = selectSanityCheckError(getState()); + throw new Error(error); + } const resourcesContent = await fetchResources(resourcesUrl); const resources = loadResourcesContent(resourcesContent); @@ -259,9 +270,13 @@ export const actionCreators: ActionCreators = { requestJsonSchema: (): AppThunk => - async (dispatch): Promise => { - await dispatch({ type: Type.REQUEST_SCHEMA, check: AUTHORIZED }); + async (dispatch, getState): Promise => { try { + await dispatch({ type: Type.REQUEST_SCHEMA, check: AUTHORIZED }); + if (!(await selectAsyncIsAuthorized(getState()))) { + const error = selectSanityCheckError(getState()); + throw new Error(error); + } const schemav200 = await fetchAndUpdateDevfileSchema('2.0.0'); const schemav210 = await fetchAndUpdateDevfileSchema('2.1.0'); const schemav220 = await fetchAndUpdateDevfileSchema('2.2.0'); @@ -319,24 +334,24 @@ export const reducer: Reducer = ( const action = incomingAction as KnownAction; switch (action.type) { case Type.REQUEST_REGISTRY_METADATA: - return createObject(state, { + return createObject(state, { isLoading: true, }); case Type.REQUEST_SCHEMA: - return createObject(state, { + return createObject(state, { isLoading: true, schema: {}, }); case Type.REQUEST_DEVFILE: - return createObject(state, { + return createObject(state, { isLoading: true, }); case Type.REQUEST_RESOURCES: - return createObject(state, { + return createObject(state, { isLoading: true, }); case Type.RECEIVE_REGISTRY_METADATA: - return createObject(state, { + return createObject(state, { isLoading: false, registries: createObject(state.registries, { [action.url]: { @@ -345,7 +360,7 @@ export const reducer: Reducer = ( }), }); case Type.RECEIVE_REGISTRY_ERROR: - return createObject(state, { + return createObject(state, { isLoading: false, registries: { [action.url]: { @@ -354,7 +369,7 @@ export const reducer: Reducer = ( }, }); case Type.RECEIVE_DEVFILE: - return createObject(state, { + return createObject(state, { isLoading: false, devfiles: createObject(state.devfiles, { [action.url]: { @@ -363,7 +378,7 @@ export const reducer: Reducer = ( }), }); case Type.RECEIVE_RESOURCES: - return createObject(state, { + return createObject(state, { isLoading: false, devWorkspaceResources: createObject(state.devWorkspaceResources, { [action.url]: { @@ -372,7 +387,7 @@ export const reducer: Reducer = ( }), }); case Type.RECEIVE_RESOURCES_ERROR: - return createObject(state, { + return createObject(state, { isLoading: false, devWorkspaceResources: { [action.url]: { @@ -381,26 +396,26 @@ export const reducer: Reducer = ( }, }); case Type.RECEIVE_SCHEMA: - return createObject(state, { + return createObject(state, { isLoading: false, schema: { schema: action.schema, }, }); case Type.RECEIVE_SCHEMA_ERROR: - return createObject(state, { + return createObject(state, { isLoading: false, schema: { error: action.error, }, }); case Type.SET_FILTER: { - return createObject(state, { + return createObject(state, { filter: action.value, }); } case Type.CLEAR_FILTER: { - return createObject(state, { + return createObject(state, { filter: '', }); } diff --git a/packages/dashboard-frontend/src/store/DockerConfig/__tests__/index.spec.ts b/packages/dashboard-frontend/src/store/DockerConfig/__tests__/index.spec.ts index cb00ccd91..0ead30ea3 100644 --- a/packages/dashboard-frontend/src/store/DockerConfig/__tests__/index.spec.ts +++ b/packages/dashboard-frontend/src/store/DockerConfig/__tests__/index.spec.ts @@ -18,6 +18,7 @@ import * as dwDockerConfigStore from '..'; import { AppState } from '../..'; import { AnyAction } from 'redux'; import { AUTHORIZED } from '../../sanityCheckMiddleware'; +import { Type } from '..'; // mute the outputs console.error = jest.fn(); @@ -48,11 +49,11 @@ describe('dwDockerConfig store', () => { const expectedActions: dwDockerConfigStore.KnownAction[] = [ { - type: 'REQUEST_DEVWORKSPACE_CREDENTIALS', + type: Type.REQUEST_DEVWORKSPACE_CREDENTIALS, check: AUTHORIZED, }, { - type: 'SET_DEVWORKSPACE_CREDENTIALS', + type: Type.SET_DEVWORKSPACE_CREDENTIALS, registries: [ { password: 'XXXXXXXXXXXXXXX', @@ -93,11 +94,11 @@ describe('dwDockerConfig store', () => { const expectedActions: dwDockerConfigStore.KnownAction[] = [ { - type: 'REQUEST_DEVWORKSPACE_CREDENTIALS', + type: Type.REQUEST_DEVWORKSPACE_CREDENTIALS, check: AUTHORIZED, }, { - type: 'SET_DEVWORKSPACE_CREDENTIALS', + type: Type.SET_DEVWORKSPACE_CREDENTIALS, registries: [ { password: 'YYYYYYYYYYYY', @@ -115,7 +116,7 @@ describe('dwDockerConfig store', () => { describe('reducers', () => { it('should return initial state', () => { const incomingAction: dwDockerConfigStore.RequestCredentialsAction = { - type: 'REQUEST_DEVWORKSPACE_CREDENTIALS', + type: Type.REQUEST_DEVWORKSPACE_CREDENTIALS, check: AUTHORIZED, }; const initialState = dwDockerConfigStore.reducer(undefined, incomingAction); @@ -180,7 +181,7 @@ describe('dwDockerConfig store', () => { error: undefined, }; const incomingAction: dwDockerConfigStore.RequestCredentialsAction = { - type: 'REQUEST_DEVWORKSPACE_CREDENTIALS', + type: Type.REQUEST_DEVWORKSPACE_CREDENTIALS, check: AUTHORIZED, }; @@ -216,7 +217,7 @@ describe('dwDockerConfig store', () => { error: undefined, }; const incomingAction: dwDockerConfigStore.SetCredentialsAction = { - type: 'SET_DEVWORKSPACE_CREDENTIALS', + type: Type.SET_DEVWORKSPACE_CREDENTIALS, registries: [], resourceVersion: '345', }; @@ -241,7 +242,7 @@ describe('dwDockerConfig store', () => { error: undefined, }; const incomingAction: dwDockerConfigStore.ReceiveErrorAction = { - type: 'RECEIVE_DEVWORKSPACE_CREDENTIALS_ERROR', + type: Type.RECEIVE_DEVWORKSPACE_CREDENTIALS_ERROR, error: 'unexpected error', }; diff --git a/packages/dashboard-frontend/src/store/DockerConfig/dockerConfigState.ts b/packages/dashboard-frontend/src/store/DockerConfig/dockerConfigState.ts deleted file mode 100644 index 4e9229efe..000000000 --- a/packages/dashboard-frontend/src/store/DockerConfig/dockerConfigState.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (c) 2018-2023 Red Hat, Inc. - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Red Hat, Inc. - initial API and implementation - */ - -import { RegistryEntry } from './types'; - -export interface State { - isLoading: boolean; - registries: RegistryEntry[]; - resourceVersion?: string; - error: string | undefined; -} diff --git a/packages/dashboard-frontend/src/store/DockerConfig/index.ts b/packages/dashboard-frontend/src/store/DockerConfig/index.ts index 453e04556..c1f4ff8ec 100644 --- a/packages/dashboard-frontend/src/store/DockerConfig/index.ts +++ b/packages/dashboard-frontend/src/store/DockerConfig/index.ts @@ -14,25 +14,37 @@ import { Action, Reducer } from 'redux'; import { api, helpers } from '@eclipse-che/common'; import { AppThunk } from '..'; import { createObject } from '../helpers'; -import * as DwApi from '../../services/dashboard-backend-client/devWorkspaceApi'; +import * as DwApi from '../../services/backend-client/devWorkspaceApi'; import { RegistryEntry } from './types'; -import { State } from './dockerConfigState'; import { AUTHORIZED, SanityCheckAction } from '../sanityCheckMiddleware'; import { selectDefaultNamespace } from '../InfrastructureNamespaces/selectors'; -export * from './dockerConfigState'; +import { selectAsyncIsAuthorized, selectSanityCheckError } from '../SanityCheck/selectors'; + +export interface State { + isLoading: boolean; + registries: RegistryEntry[]; + resourceVersion?: string; + error: string | undefined; +} + +export enum Type { + REQUEST_DEVWORKSPACE_CREDENTIALS = 'REQUEST_DEVWORKSPACE_CREDENTIALS', + SET_DEVWORKSPACE_CREDENTIALS = 'SET_DEVWORKSPACE_CREDENTIALS', + RECEIVE_DEVWORKSPACE_CREDENTIALS_ERROR = 'RECEIVE_DEVWORKSPACE_CREDENTIALS_ERROR', +} export interface RequestCredentialsAction extends Action, SanityCheckAction { - type: 'REQUEST_DEVWORKSPACE_CREDENTIALS'; + type: Type.REQUEST_DEVWORKSPACE_CREDENTIALS; } export interface SetCredentialsAction extends Action { - type: 'SET_DEVWORKSPACE_CREDENTIALS'; + type: Type.SET_DEVWORKSPACE_CREDENTIALS; registries: RegistryEntry[]; resourceVersion: string | undefined; } export interface ReceiveErrorAction extends Action { - type: 'RECEIVE_DEVWORKSPACE_CREDENTIALS_ERROR'; + type: Type.RECEIVE_DEVWORKSPACE_CREDENTIALS_ERROR; error: string; } @@ -47,20 +59,23 @@ export const actionCreators: ActionCreators = { requestCredentials: (): AppThunk> => async (dispatch, getState): Promise => { - const state = getState(); - const namespace = selectDefaultNamespace(state).name; - await dispatch({ type: 'REQUEST_DEVWORKSPACE_CREDENTIALS', check: AUTHORIZED }); + const namespace = selectDefaultNamespace(getState()).name; try { + await dispatch({ type: Type.REQUEST_DEVWORKSPACE_CREDENTIALS, check: AUTHORIZED }); + if (!(await selectAsyncIsAuthorized(getState()))) { + const error = selectSanityCheckError(getState()); + throw new Error(error); + } const { registries, resourceVersion } = await getDockerConfig(namespace); dispatch({ - type: 'SET_DEVWORKSPACE_CREDENTIALS', + type: Type.SET_DEVWORKSPACE_CREDENTIALS, registries, resourceVersion, }); } catch (e) { const errorMessage = helpers.errors.getMessage(e); dispatch({ - type: 'RECEIVE_DEVWORKSPACE_CREDENTIALS_ERROR', + type: Type.RECEIVE_DEVWORKSPACE_CREDENTIALS_ERROR, error: errorMessage, }); throw e; @@ -70,24 +85,28 @@ export const actionCreators: ActionCreators = { updateCredentials: (registries: RegistryEntry[]): AppThunk> => async (dispatch, getState): Promise => { - await dispatch({ type: 'REQUEST_DEVWORKSPACE_CREDENTIALS', check: AUTHORIZED }); const state = getState(); const namespace = selectDefaultNamespace(state).name; try { + await dispatch({ type: Type.REQUEST_DEVWORKSPACE_CREDENTIALS, check: AUTHORIZED }); + if (!(await selectAsyncIsAuthorized(getState()))) { + const error = selectSanityCheckError(getState()); + throw new Error(error); + } const { resourceVersion } = await putDockerConfig( namespace, registries, state.dockerConfig?.resourceVersion, ); dispatch({ - type: 'SET_DEVWORKSPACE_CREDENTIALS', + type: Type.SET_DEVWORKSPACE_CREDENTIALS, registries, resourceVersion, }); } catch (e) { const errorMessage = helpers.errors.getMessage(e); dispatch({ - type: 'RECEIVE_DEVWORKSPACE_CREDENTIALS_ERROR', + type: Type.RECEIVE_DEVWORKSPACE_CREDENTIALS_ERROR, error: errorMessage, }); throw e; @@ -165,19 +184,19 @@ export const reducer: Reducer = ( const action = incomingAction as KnownAction; switch (action.type) { - case 'REQUEST_DEVWORKSPACE_CREDENTIALS': - return createObject(state, { + case Type.REQUEST_DEVWORKSPACE_CREDENTIALS: + return createObject(state, { isLoading: true, error: undefined, }); - case 'SET_DEVWORKSPACE_CREDENTIALS': - return createObject(state, { + case Type.SET_DEVWORKSPACE_CREDENTIALS: + return createObject(state, { isLoading: false, registries: action.registries, resourceVersion: action.resourceVersion, }); - case 'RECEIVE_DEVWORKSPACE_CREDENTIALS_ERROR': - return createObject(state, { + case Type.RECEIVE_DEVWORKSPACE_CREDENTIALS_ERROR: + return createObject(state, { isLoading: false, error: action.error, }); diff --git a/packages/dashboard-frontend/src/store/DockerConfig/selectors.ts b/packages/dashboard-frontend/src/store/DockerConfig/selectors.ts index 2464bf009..824d8103a 100644 --- a/packages/dashboard-frontend/src/store/DockerConfig/selectors.ts +++ b/packages/dashboard-frontend/src/store/DockerConfig/selectors.ts @@ -12,7 +12,7 @@ import { createSelector } from 'reselect'; import { AppState } from '..'; -import { State } from './dockerConfigState'; +import { State } from './index'; const selectState = (state: AppState) => state.dockerConfig; diff --git a/packages/dashboard-frontend/src/store/Events/__tests__/actions.spec.ts b/packages/dashboard-frontend/src/store/Events/__tests__/actions.spec.ts index 6679b20ab..01b8e3684 100644 --- a/packages/dashboard-frontend/src/store/Events/__tests__/actions.spec.ts +++ b/packages/dashboard-frontend/src/store/Events/__tests__/actions.spec.ts @@ -18,7 +18,7 @@ import { ThunkDispatch } from 'redux-thunk'; import * as testStore from '..'; import { AppState } from '../..'; import { container } from '../../../inversify.config'; -import { WebsocketClient } from '../../../services/dashboard-backend-client/websocketClient'; +import { WebsocketClient } from '../../../services/backend-client/websocketClient'; import { AUTHORIZED } from '../../sanityCheckMiddleware'; import { FakeStoreBuilder } from '../../__mocks__/storeBuilder'; import { event1, event2 } from './stubs'; diff --git a/packages/dashboard-frontend/src/store/Events/index.ts b/packages/dashboard-frontend/src/store/Events/index.ts index cb0a5b8c5..a1d6c1297 100644 --- a/packages/dashboard-frontend/src/store/Events/index.ts +++ b/packages/dashboard-frontend/src/store/Events/index.ts @@ -15,8 +15,8 @@ import { CoreV1Event } from '@kubernetes/client-node'; import { Action, Reducer } from 'redux'; import { AppThunk } from '..'; import { container } from '../../inversify.config'; -import { fetchEvents } from '../../services/dashboard-backend-client/eventsApi'; -import { WebsocketClient } from '../../services/dashboard-backend-client/websocketClient'; +import { fetchEvents } from '../../services/backend-client/eventsApi'; +import { WebsocketClient } from '../../services/backend-client/websocketClient'; import { getNewerResourceVersion } from '../../services/helpers/resourceVersion'; import { createObject } from '../helpers'; import { selectDefaultNamespace } from '../InfrastructureNamespaces/selectors'; @@ -188,18 +188,18 @@ export const reducer: Reducer = ( const action = incomingAction as KnownAction; switch (action.type) { case Type.REQUEST_EVENTS: - return createObject(state, { + return createObject(state, { isLoading: true, error: undefined, }); case Type.RECEIVE_EVENTS: - return createObject(state, { + return createObject(state, { isLoading: false, events: state.events.concat(action.events), resourceVersion: getNewerResourceVersion(action.resourceVersion, state.resourceVersion), }); case Type.MODIFY_EVENT: - return createObject(state, { + return createObject(state, { events: state.events.map(event => { if (event.metadata.uid === action.event.metadata.uid) { return action.event; @@ -212,7 +212,7 @@ export const reducer: Reducer = ( ), }); case Type.DELETE_EVENT: - return createObject(state, { + return createObject(state, { events: state.events.filter(event => event.metadata.uid !== action.event.metadata.uid), resourceVersion: getNewerResourceVersion( action.event.metadata.resourceVersion, @@ -220,7 +220,7 @@ export const reducer: Reducer = ( ), }); case Type.RECEIVE_ERROR: - return createObject(state, { + return createObject(state, { isLoading: false, error: action.error, }); diff --git a/packages/dashboard-frontend/src/store/FactoryResolver/__tests__/index.spec.ts b/packages/dashboard-frontend/src/store/FactoryResolver/__tests__/index.spec.ts index 3e60c7fc4..c2a3126a0 100644 --- a/packages/dashboard-frontend/src/store/FactoryResolver/__tests__/index.spec.ts +++ b/packages/dashboard-frontend/src/store/FactoryResolver/__tests__/index.spec.ts @@ -16,7 +16,7 @@ import common from '@eclipse-che/common'; import { AppState } from '../..'; import { FakeStoreBuilder } from '../../__mocks__/storeBuilder'; import devfileApi from '../../../services/devfileApi'; -import * as factoryResolver from '../../../services/dashboard-backend-client/factoryResolverApi'; +import * as factoryResolver from '../../../services/backend-client/factoryApi'; import * as factoryResolverStore from '..'; import { AxiosError } from 'axios'; import normalizeDevfileV1 from '../normalizeDevfileV1'; @@ -26,7 +26,7 @@ import { convertDevfileV1toDevfileV2, } from '../../../services/devfile/converters'; import { AUTHORIZED } from '../../sanityCheckMiddleware'; -import * as yamlResolver from '../../../services/dashboard-backend-client/yamlResolverApi'; +import * as yamlResolver from '../../../services/backend-client/yamlResolverApi'; jest.mock('../normalizeDevfileV1.ts'); (normalizeDevfileV1 as jest.Mock).mockImplementation(devfile => { diff --git a/packages/dashboard-frontend/src/store/FactoryResolver/index.ts b/packages/dashboard-frontend/src/store/FactoryResolver/index.ts index f390dab1e..aa52b9eeb 100644 --- a/packages/dashboard-frontend/src/store/FactoryResolver/index.ts +++ b/packages/dashboard-frontend/src/store/FactoryResolver/index.ts @@ -22,13 +22,15 @@ import { convertDevfileV1toDevfileV2 } from '../../services/devfile/converters'; import normalizeDevfileV2 from './normalizeDevfileV2'; import normalizeDevfileV1 from './normalizeDevfileV1'; import { selectDefaultNamespace } from '../InfrastructureNamespaces/selectors'; -import { getYamlResolver } from '../../services/dashboard-backend-client/yamlResolverApi'; +import { getYamlResolver } from '../../services/backend-client/yamlResolverApi'; import { DEFAULT_REGISTRY } from '../DevfileRegistries'; import { isOAuthResponse } from '../../services/oauth'; import { AUTHORIZED, SanityCheckAction } from '../sanityCheckMiddleware'; -import { CHE_EDITOR_YAML_PATH } from '../../services/workspace-client'; +import { CHE_EDITOR_YAML_PATH } from '../../services/workspace-client/helpers'; import { FactoryParams } from '../../services/helpers/factoryFlow/buildFactoryParams'; -import { getFactoryResolver } from '../../services/dashboard-backend-client/factoryResolverApi'; +import { getFactoryResolver } from '../../services/backend-client/factoryApi'; +import { selectAsyncIsAuthorized, selectSanityCheckError } from '../SanityCheck/selectors'; +import * as cheApi from '@eclipse-che/api'; export type OAuthResponse = { attributes: { @@ -87,7 +89,7 @@ export type ActionCreators = { }; export async function grabLink( - links: api.che.core.rest.Link, + links: cheApi.che.core.rest.Link[], filename: string, ): Promise { // handle servers not yet providing links @@ -95,8 +97,8 @@ export async function grabLink( return undefined; } // grab the one matching - const foundLink = links.find(link => link.href.includes(`file=${filename}`)); - if (!foundLink) { + const foundLink = links.find(link => link.href?.includes(`file=${filename}`)); + if (!foundLink || !foundLink.href) { return undefined; } @@ -129,7 +131,6 @@ export const actionCreators: ActionCreators = { factoryParams: Partial = {}, ): AppThunk> => async (dispatch, getState): Promise => { - await dispatch({ type: 'REQUEST_FACTORY_RESOLVER', check: AUTHORIZED }); const state = getState(); const namespace = selectDefaultNamespace(state).name; const optionalFilesContent = {}; @@ -157,6 +158,11 @@ export const actionCreators: ActionCreators = { }; try { + await dispatch({ type: 'REQUEST_FACTORY_RESOLVER', check: AUTHORIZED }); + if (!(await selectAsyncIsAuthorized(getState()))) { + const error = selectSanityCheckError(getState()); + throw new Error(error); + } let data: FactoryResolver; if (isDevfileRegistryLocation(location)) { data = await getYamlResolver(namespace, location); @@ -245,18 +251,18 @@ export const reducer: Reducer = ( const action = incomingAction as KnownAction; switch (action.type) { case 'REQUEST_FACTORY_RESOLVER': - return createObject(state, { + return createObject(state, { isLoading: true, error: undefined, }); case 'RECEIVE_FACTORY_RESOLVER': - return createObject(state, { + return createObject(state, { isLoading: false, resolver: action.resolver, converted: action.converted, }); case 'RECEIVE_FACTORY_RESOLVER_ERROR': - return createObject(state, { + return createObject(state, { isLoading: false, error: action.error, }); diff --git a/packages/dashboard-frontend/src/store/GitConfig/__tests__/index.spec.ts b/packages/dashboard-frontend/src/store/GitConfig/__tests__/index.spec.ts index a7c5020dd..3c3355181 100644 --- a/packages/dashboard-frontend/src/store/GitConfig/__tests__/index.spec.ts +++ b/packages/dashboard-frontend/src/store/GitConfig/__tests__/index.spec.ts @@ -20,7 +20,7 @@ import { AUTHORIZED } from '../../sanityCheckMiddleware'; const mockFetchGitConfig = jest.fn().mockResolvedValue({ gitconfig: {} } as api.IGitConfig); const mockPatchGitConfig = jest.fn().mockResolvedValue({ gitconfig: {} } as api.IGitConfig); -jest.mock('../../../services/dashboard-backend-client/gitConfigApi', () => { +jest.mock('../../../services/backend-client/gitConfigApi', () => { return { fetchGitConfig: (...args: unknown[]) => mockFetchGitConfig(...args), patchGitConfig: (...args: unknown[]) => mockPatchGitConfig(...args), diff --git a/packages/dashboard-frontend/src/store/GitConfig/index.ts b/packages/dashboard-frontend/src/store/GitConfig/index.ts index 77576d279..479a469e6 100644 --- a/packages/dashboard-frontend/src/store/GitConfig/index.ts +++ b/packages/dashboard-frontend/src/store/GitConfig/index.ts @@ -12,13 +12,11 @@ import common, { api, helpers } from '@eclipse-che/common'; import { AppThunk } from '..'; -import { - fetchGitConfig, - patchGitConfig, -} from '../../services/dashboard-backend-client/gitConfigApi'; +import { fetchGitConfig, patchGitConfig } from '../../services/backend-client/gitConfigApi'; import { selectDefaultNamespace } from '../InfrastructureNamespaces/selectors'; import { AUTHORIZED } from '../sanityCheckMiddleware'; import { GitConfigUser, KnownAction, Type } from './types'; +import { selectAsyncIsAuthorized, selectSanityCheckError } from '../SanityCheck/selectors'; export * from './reducer'; export * from './types'; @@ -31,11 +29,14 @@ export const actionCreators: ActionCreators = { requestGitConfig: (): AppThunk> => async (dispatch, getState): Promise => { - await dispatch({ type: Type.REQUEST_GITCONFIG, check: AUTHORIZED }); - const state = getState(); const namespace = selectDefaultNamespace(state).name; try { + await dispatch({ type: Type.REQUEST_GITCONFIG, check: AUTHORIZED }); + if (!(await selectAsyncIsAuthorized(getState()))) { + const error = selectSanityCheckError(getState()); + throw new Error(error); + } const config = await fetchGitConfig(namespace); dispatch({ type: Type.RECEIVE_GITCONFIG, @@ -62,16 +63,20 @@ export const actionCreators: ActionCreators = { updateGitConfig: (changedGitConfig: GitConfigUser): AppThunk> => async (dispatch, getState): Promise => { - await dispatch({ type: Type.REQUEST_GITCONFIG, check: AUTHORIZED }); - - const namespace = selectDefaultNamespace(getState()).name; - const { gitConfig } = getState(); + const state = getState(); + const namespace = selectDefaultNamespace(state).name; + const { gitConfig } = state; const gitconfig = Object.assign(gitConfig.config || {}, { gitconfig: { user: changedGitConfig, }, } as api.IGitConfig); try { + await dispatch({ type: Type.REQUEST_GITCONFIG, check: AUTHORIZED }); + if (!(await selectAsyncIsAuthorized(getState()))) { + const error = selectSanityCheckError(getState()); + throw new Error(error); + } const updated = await patchGitConfig(namespace, gitconfig); dispatch({ type: Type.RECEIVE_GITCONFIG, diff --git a/packages/dashboard-frontend/src/store/GitOauthConfig/index.ts b/packages/dashboard-frontend/src/store/GitOauthConfig/index.ts index 500add196..a7d94dc49 100644 --- a/packages/dashboard-frontend/src/store/GitOauthConfig/index.ts +++ b/packages/dashboard-frontend/src/store/GitOauthConfig/index.ts @@ -15,9 +15,13 @@ import common, { api } from '@eclipse-che/common'; import { AppThunk } from '..'; import { createObject } from '../helpers'; import { AUTHORIZED } from '../sanityCheckMiddleware'; -import { container } from '../../inversify.config'; -import { CheWorkspaceClient } from '../../services/workspace-client/cheworkspace/cheWorkspaceClient'; import { IGitOauth } from './types'; +import { + deleteOAuthToken, + getOAuthProviders, + getOAuthToken, +} from '../../services/backend-client/oAuthApi'; +import { selectAsyncIsAuthorized, selectSanityCheckError } from '../SanityCheck/selectors'; export interface State { isLoading: boolean; @@ -25,8 +29,6 @@ export interface State { error: string | undefined; } -const cheWorkspaceClient = container.get(CheWorkspaceClient); - export enum Type { REQUEST_GIT_OAUTH_CONFIG = 'REQUEST_GIT_OAUTH_CONFIG', DELETE_OAUTH = 'DELETE_OAUTH', @@ -67,21 +69,31 @@ export type ActionCreators = { export const actionCreators: ActionCreators = { requestGitOauthConfig: (): AppThunk> => - async (dispatch): Promise => { - await dispatch({ + async (dispatch, getState): Promise => { + dispatch({ type: Type.REQUEST_GIT_OAUTH_CONFIG, check: AUTHORIZED, }); + if (!(await selectAsyncIsAuthorized(getState()))) { + const error = selectSanityCheckError(getState()); + dispatch({ + type: Type.RECEIVE_GIT_OAUTH_CONFIG_ERROR, + error, + }); + throw new Error(error); + } + const gitOauth: IGitOauth[] = []; try { - const oAuthProviders = await cheWorkspaceClient.restApiClient.getOAuthProviders(); + const oAuthProviders = await getOAuthProviders(); const promises: Promise[] = []; - for (const { name, endpointUrl } of oAuthProviders) { + for (const { name, endpointUrl, links } of oAuthProviders) { promises.push( - cheWorkspaceClient.restApiClient.getOAuthToken(name).then(() => { + getOAuthToken(name).then(() => { gitOauth.push({ name: name as api.GitOauthProvider, endpointUrl, + links, }); }), ); @@ -104,13 +116,22 @@ export const actionCreators: ActionCreators = { revokeOauth: (oauthProvider: api.GitOauthProvider): AppThunk> => - async (dispatch): Promise => { - await dispatch({ + async (dispatch, getState): Promise => { + dispatch({ type: Type.REQUEST_GIT_OAUTH_CONFIG, check: AUTHORIZED, }); + if (!(await selectAsyncIsAuthorized(getState()))) { + const error = selectSanityCheckError(getState()); + dispatch({ + type: Type.RECEIVE_GIT_OAUTH_CONFIG_ERROR, + error, + }); + throw new Error(error); + } + try { - await cheWorkspaceClient.restApiClient.deleteOAuthToken(oauthProvider); + await deleteOAuthToken(oauthProvider); dispatch({ type: Type.DELETE_OAUTH, provider: oauthProvider, @@ -143,22 +164,22 @@ export const reducer: Reducer = ( const action = incomingAction as KnownAction; switch (action.type) { case Type.REQUEST_GIT_OAUTH_CONFIG: - return createObject(state, { + return createObject(state, { isLoading: true, error: undefined, }); case Type.RECEIVE_GIT_OAUTH_CONFIG: - return createObject(state, { + return createObject(state, { isLoading: false, gitOauth: action.gitOauth, }); case Type.DELETE_OAUTH: - return createObject(state, { + return createObject(state, { isLoading: false, gitOauth: state.gitOauth.filter(v => v.name !== action.provider), }); case Type.RECEIVE_GIT_OAUTH_CONFIG_ERROR: - return createObject(state, { + return createObject(state, { isLoading: false, error: action.error, }); diff --git a/packages/dashboard-frontend/src/store/GitOauthConfig/types.ts b/packages/dashboard-frontend/src/store/GitOauthConfig/types.ts index ef436a426..c470f1961 100644 --- a/packages/dashboard-frontend/src/store/GitOauthConfig/types.ts +++ b/packages/dashboard-frontend/src/store/GitOauthConfig/types.ts @@ -10,9 +10,11 @@ * Red Hat, Inc. - initial API and implementation */ -import { api } from '@eclipse-che/common'; +import { api as commonApi } from '@eclipse-che/common'; +import * as cheApi from '@eclipse-che/api'; export interface IGitOauth { - name: api.GitOauthProvider; + name: commonApi.GitOauthProvider; endpointUrl: string; + links?: cheApi.che.core.rest.Link[]; } diff --git a/packages/dashboard-frontend/src/store/InfrastructureNamespaces/index.ts b/packages/dashboard-frontend/src/store/InfrastructureNamespaces/index.ts index 9aadd8431..94a6a2e5c 100644 --- a/packages/dashboard-frontend/src/store/InfrastructureNamespaces/index.ts +++ b/packages/dashboard-frontend/src/store/InfrastructureNamespaces/index.ts @@ -12,13 +12,11 @@ import { Action, Reducer } from 'redux'; import common from '@eclipse-che/common'; -import { container } from '../../inversify.config'; -import { CheWorkspaceClient } from '../../services/workspace-client/cheworkspace/cheWorkspaceClient'; import { AppThunk } from '..'; import { createObject } from '../helpers'; import { AUTHORIZED, SanityCheckAction } from '../sanityCheckMiddleware'; - -const WorkspaceClient = container.get(CheWorkspaceClient); +import { getKubernetesNamespace } from '../../services/backend-client/kubernetesNamespaceApi'; +import { selectAsyncIsAuthorized, selectSanityCheckError } from '../SanityCheck/selectors'; export interface State { isLoading: boolean; @@ -49,14 +47,14 @@ export type ActionCreators = { export const actionCreators: ActionCreators = { requestNamespaces: (): AppThunk>> => - async (dispatch): Promise> => { - await dispatch({ type: 'REQUEST_NAMESPACES', check: AUTHORIZED }); - + async (dispatch, getState): Promise> => { try { - const namespaces = - await WorkspaceClient.restApiClient.getKubernetesNamespace< - Array - >(); + await dispatch({ type: 'REQUEST_NAMESPACES', check: AUTHORIZED }); + if (!(await selectAsyncIsAuthorized(getState()))) { + const error = selectSanityCheckError(getState()); + throw new Error(error); + } + const namespaces = await getKubernetesNamespace(); dispatch({ type: 'RECEIVE_NAMESPACES', namespaces, @@ -91,17 +89,17 @@ export const reducer: Reducer = ( const action = incomingAction as KnownAction; switch (action.type) { case 'REQUEST_NAMESPACES': - return createObject(state, { + return createObject(state, { isLoading: true, error: undefined, }); case 'RECEIVE_NAMESPACES': - return createObject(state, { + return createObject(state, { isLoading: false, namespaces: action.namespaces, }); case 'RECEIVE_NAMESPACES_ERROR': - return createObject(state, { + return createObject(state, { isLoading: false, error: action.error, }); diff --git a/packages/dashboard-frontend/src/store/PersonalAccessToken/__tests__/actions.spec.ts b/packages/dashboard-frontend/src/store/PersonalAccessToken/__tests__/actions.spec.ts index 80a300b39..ddbdada66 100644 --- a/packages/dashboard-frontend/src/store/PersonalAccessToken/__tests__/actions.spec.ts +++ b/packages/dashboard-frontend/src/store/PersonalAccessToken/__tests__/actions.spec.ts @@ -17,21 +17,23 @@ import { AppState } from '../..'; import { AUTHORIZED } from '../../sanityCheckMiddleware'; import { FakeStoreBuilder } from '../../__mocks__/storeBuilder'; import { token1, token2 } from './stub'; -import * as PersonalAccessTokenApi from '../../../services/dashboard-backend-client/personalAccessTokenApi'; -import { container } from '../../../inversify.config'; -import { CheWorkspaceClient } from '../../../services/workspace-client/cheworkspace/cheWorkspaceClient'; +import * as PersonalAccessTokenApi from '../../../services/backend-client/personalAccessTokenApi'; +import * as KubernetesNamespaceApi from '../../../services/backend-client/kubernetesNamespaceApi'; -const cheWorkspaceClient = container.get(CheWorkspaceClient); -jest - .spyOn(cheWorkspaceClient.restApiClient, 'provisionKubernetesNamespace') - .mockImplementation(() => Promise.resolve({} as che.KubernetesNamespace)); +jest.mock( + '../../../services/backend-client/kubernetesNamespaceApi', + () => + ({ + provisionKubernetesNamespace: () => Promise.resolve({} as che.KubernetesNamespace), + }) as typeof KubernetesNamespaceApi, +); const mockFetchTokens = jest.fn(); const mockAddToken = jest.fn(); const mockUpdateToken = jest.fn(); const mockRemoveToken = jest.fn(); jest.mock( - '../../../services/dashboard-backend-client/personalAccessTokenApi', + '../../../services/backend-client/personalAccessTokenApi', () => ({ fetchTokens: (...args) => mockFetchTokens(...args), diff --git a/packages/dashboard-frontend/src/store/PersonalAccessToken/index.ts b/packages/dashboard-frontend/src/store/PersonalAccessToken/index.ts index 52773b06d..81519fd11 100644 --- a/packages/dashboard-frontend/src/store/PersonalAccessToken/index.ts +++ b/packages/dashboard-frontend/src/store/PersonalAccessToken/index.ts @@ -13,23 +13,21 @@ import { api, helpers } from '@eclipse-che/common'; import { Action, Reducer } from 'redux'; import { AppThunk } from '..'; -import { container } from '../../inversify.config'; import { addToken, fetchTokens, removeToken, updateToken, -} from '../../services/dashboard-backend-client/personalAccessTokenApi'; -import { CheWorkspaceClient } from '../../services/workspace-client/cheworkspace/cheWorkspaceClient'; +} from '../../services/backend-client/personalAccessTokenApi'; import { createObject } from '../helpers'; import { selectDefaultNamespace } from '../InfrastructureNamespaces/selectors'; import { AUTHORIZED, SanityCheckAction } from '../sanityCheckMiddleware'; import { State } from './state'; +import { provisionKubernetesNamespace } from '../../services/backend-client/kubernetesNamespaceApi'; +import { selectAsyncIsAuthorized, selectSanityCheckError } from '../SanityCheck/selectors'; export * from './state'; -const WorkspaceClient = container.get(CheWorkspaceClient); - export enum Type { RECEIVE_ERROR = 'RECEIVE_ERROR', RECEIVE_TOKENS = 'RECEIVE_TOKENS', @@ -87,14 +85,21 @@ export const actionCreators: ActionCreators = { requestTokens: (): AppThunk> => async (dispatch, getState): Promise => { - const state = getState(); - const namespace = selectDefaultNamespace(state).name; - - await dispatch({ + dispatch({ type: Type.REQUEST_TOKENS, check: AUTHORIZED, }); + if (!(await selectAsyncIsAuthorized(getState()))) { + const error = selectSanityCheckError(getState()); + dispatch({ + type: Type.RECEIVE_ERROR, + error, + }); + throw new Error(error); + } + const state = getState(); + const namespace = selectDefaultNamespace(state).name; try { const tokens = await fetchTokens(namespace); dispatch({ @@ -114,14 +119,21 @@ export const actionCreators: ActionCreators = { addToken: (token: api.PersonalAccessToken): AppThunk> => async (dispatch, getState): Promise => { - const state = getState(); - const namespace = selectDefaultNamespace(state).name; - - await dispatch({ + dispatch({ type: Type.REQUEST_TOKENS, check: AUTHORIZED, }); + if (!(await selectAsyncIsAuthorized(getState()))) { + const error = selectSanityCheckError(getState()); + dispatch({ + type: Type.RECEIVE_ERROR, + error, + }); + throw new Error(error); + } + const state = getState(); + const namespace = selectDefaultNamespace(state).name; let newToken: api.PersonalAccessToken; try { newToken = await addToken(namespace, token); @@ -135,8 +147,7 @@ export const actionCreators: ActionCreators = { } /* request namespace provision as it triggers tokens validation */ - - await WorkspaceClient.restApiClient.provisionKubernetesNamespace(); + await provisionKubernetesNamespace(); /* check if the new token is available */ @@ -161,14 +172,21 @@ export const actionCreators: ActionCreators = { updateToken: (token: api.PersonalAccessToken): AppThunk> => async (dispatch, getState): Promise => { - const state = getState(); - const namespace = selectDefaultNamespace(state).name; - - await dispatch({ + dispatch({ type: Type.REQUEST_TOKENS, check: AUTHORIZED, }); + if (!(await selectAsyncIsAuthorized(getState()))) { + const error = selectSanityCheckError(getState()); + dispatch({ + type: Type.RECEIVE_ERROR, + error, + }); + throw new Error(error); + } + const state = getState(); + const namespace = selectDefaultNamespace(state).name; try { const newToken = await updateToken(namespace, token); dispatch({ @@ -188,14 +206,21 @@ export const actionCreators: ActionCreators = { removeToken: (token: api.PersonalAccessToken): AppThunk> => async (dispatch, getState): Promise => { - const state = getState(); - const namespace = selectDefaultNamespace(state).name; - - await dispatch({ + dispatch({ type: Type.REQUEST_TOKENS, check: AUTHORIZED, }); + if (!(await selectAsyncIsAuthorized(getState()))) { + const error = selectSanityCheckError(getState()); + dispatch({ + type: Type.RECEIVE_ERROR, + error, + }); + throw new Error(error); + } + const state = getState(); + const namespace = selectDefaultNamespace(state).name; try { await removeToken(namespace, token); dispatch({ @@ -229,34 +254,34 @@ export const reducer: Reducer = ( const action = incomingAction as KnownAction; switch (action.type) { case Type.REQUEST_TOKENS: - return createObject(state, { + return createObject(state, { isLoading: true, error: undefined, }); case Type.RECEIVE_TOKENS: - return createObject(state, { + return createObject(state, { isLoading: false, tokens: action.tokens, }); case Type.ADD_TOKEN: - return createObject(state, { + return createObject(state, { isLoading: false, tokens: [...state.tokens, action.token], }); case Type.UPDATE_TOKEN: - return createObject(state, { + return createObject(state, { isLoading: false, tokens: state.tokens.map(token => token.tokenName === action.token.tokenName ? action.token : token, ), }); case Type.REMOVE_TOKEN: - return createObject(state, { + return createObject(state, { isLoading: false, tokens: state.tokens.filter(token => token.tokenName !== action.token.tokenName), }); case Type.RECEIVE_ERROR: - return createObject(state, { + return createObject(state, { isLoading: false, error: action.error, }); diff --git a/packages/dashboard-frontend/src/store/Plugins/chePlugins/index.ts b/packages/dashboard-frontend/src/store/Plugins/chePlugins/index.ts index 05148122f..4b9a0783b 100644 --- a/packages/dashboard-frontend/src/store/Plugins/chePlugins/index.ts +++ b/packages/dashboard-frontend/src/store/Plugins/chePlugins/index.ts @@ -16,6 +16,7 @@ import common from '@eclipse-che/common'; import { AppThunk } from '../..'; import { createObject } from '../../helpers'; import { AUTHORIZED, SanityCheckAction } from '../../sanityCheckMiddleware'; +import { selectAsyncIsAuthorized, selectSanityCheckError } from '../../SanityCheck/selectors'; // create new instance of `axios` to avoid adding an authorization header const axiosInstance = axios.create(); @@ -49,10 +50,13 @@ export type ActionCreators = { export const actionCreators: ActionCreators = { requestPlugins: (registryUrl: string): AppThunk> => - async (dispatch): Promise => { - await dispatch({ type: 'REQUEST_PLUGINS', check: AUTHORIZED }); - + async (dispatch, getState): Promise => { try { + await dispatch({ type: 'REQUEST_PLUGINS', check: AUTHORIZED }); + if (!(await selectAsyncIsAuthorized(getState()))) { + const error = selectSanityCheckError(getState()); + throw new Error(error); + } const response = await axiosInstance.request({ method: 'GET', url: `${registryUrl}/plugins/`, @@ -93,17 +97,17 @@ export const reducer: Reducer = ( const action = incomingAction as KnownAction; switch (action.type) { case 'REQUEST_PLUGINS': - return createObject(state, { + return createObject(state, { isLoading: true, error: undefined, }); case 'RECEIVE_PLUGINS': - return createObject(state, { + return createObject(state, { isLoading: false, plugins: action.plugins, }); case 'RECEIVE_PLUGINS_ERROR': - return createObject(state, { + return createObject(state, { isLoading: false, error: action.error, }); diff --git a/packages/dashboard-frontend/src/store/Plugins/devWorkspacePlugins/index.ts b/packages/dashboard-frontend/src/store/Plugins/devWorkspacePlugins/index.ts index 6b65d1915..59db6b12d 100644 --- a/packages/dashboard-frontend/src/store/Plugins/devWorkspacePlugins/index.ts +++ b/packages/dashboard-frontend/src/store/Plugins/devWorkspacePlugins/index.ts @@ -127,7 +127,7 @@ export const actionCreators: ActionCreators = { requestDwDevfile: (url: string): AppThunk> => async (dispatch): Promise => { - await dispatch({ + dispatch({ type: 'REQUEST_DW_PLUGIN', check: AUTHORIZED, url, @@ -177,7 +177,7 @@ export const actionCreators: ActionCreators = { } try { - await dispatch({ + dispatch({ type: 'REQUEST_DW_EDITOR', check: AUTHORIZED, url: editorUrl, @@ -208,7 +208,7 @@ export const actionCreators: ActionCreators = { async (dispatch, getState): Promise => { const config = getState().dwServerConfig.config; const defaultEditor = config.defaults.editor; - await dispatch({ + dispatch({ type: 'REQUEST_DW_DEFAULT_EDITOR', check: AUTHORIZED, }); @@ -223,7 +223,7 @@ export const actionCreators: ActionCreators = { throw errorMessage; } - const defaultEditorUrl = defaultEditor.startsWith('https://') + const defaultEditorUrl = (defaultEditor as string).startsWith('https://') ? defaultEditor : `${config.pluginRegistryURL}/plugins/${defaultEditor}/devfile.yaml`; @@ -240,7 +240,7 @@ export const actionCreators: ActionCreators = { requestDwDefaultPlugins: (): AppThunk> => async (dispatch, getState): Promise => { - await dispatch({ + dispatch({ type: 'REQUEST_DW_DEFAULT_PLUGINS', check: AUTHORIZED, }); @@ -280,7 +280,7 @@ export const reducer: Reducer = ( const action = incomingAction as KnownAction; switch (action.type) { case 'REQUEST_DW_PLUGIN': - return createObject(state, { + return createObject(state, { isLoading: true, plugins: { [action.url]: { @@ -291,7 +291,7 @@ export const reducer: Reducer = ( }, }); case 'REQUEST_DW_EDITOR': - return createObject(state, { + return createObject(state, { isLoading: true, editors: createObject(state.editors, { [action.editorName]: { @@ -301,13 +301,13 @@ export const reducer: Reducer = ( }), }); case 'REQUEST_DW_DEFAULT_EDITOR': - return createObject(state, { + return createObject(state, { isLoading: true, defaultEditorName: undefined, defaultEditorError: undefined, }); case 'RECEIVE_DW_PLUGIN': - return createObject(state, { + return createObject(state, { isLoading: false, plugins: { [action.url]: { @@ -317,7 +317,7 @@ export const reducer: Reducer = ( }, }); case 'RECEIVE_DW_EDITOR': - return createObject(state, { + return createObject(state, { isLoading: false, editors: createObject(state.editors, { [action.editorName]: { @@ -327,7 +327,7 @@ export const reducer: Reducer = ( }), }); case 'RECEIVE_DW_EDITOR_ERROR': - return createObject(state, { + return createObject(state, { isLoading: false, editors: { [action.editorName]: { @@ -338,7 +338,7 @@ export const reducer: Reducer = ( }); case 'RECEIVE_DW_PLUGIN_ERROR': - return createObject(state, { + return createObject(state, { isLoading: false, plugins: { [action.url]: { @@ -350,21 +350,21 @@ export const reducer: Reducer = ( }, }); case 'RECEIVE_DW_DEFAULT_EDITOR_ERROR': - return createObject(state, { + return createObject(state, { isLoading: false, defaultEditorError: action.error, }); case 'RECEIVE_DW_DEFAULT_EDITOR': - return createObject(state, { + return createObject(state, { isLoading: false, defaultEditorName: action.defaultEditorName, }); case 'REQUEST_DW_DEFAULT_PLUGINS': - return createObject(state, { + return createObject(state, { isLoading: true, }); case 'RECEIVE_DW_DEFAULT_PLUGINS': - return createObject(state, { + return createObject(state, { isLoading: false, defaultPlugins: action.defaultPlugins, }); diff --git a/packages/dashboard-frontend/src/store/Pods/Logs/__tests__/actions.spec.ts b/packages/dashboard-frontend/src/store/Pods/Logs/__tests__/actions.spec.ts index 17ca36e37..6ea27c969 100644 --- a/packages/dashboard-frontend/src/store/Pods/Logs/__tests__/actions.spec.ts +++ b/packages/dashboard-frontend/src/store/Pods/Logs/__tests__/actions.spec.ts @@ -19,7 +19,7 @@ import { ThunkDispatch } from 'redux-thunk'; import * as testStore from '..'; import { AppState } from '../../..'; import { container } from '../../../../inversify.config'; -import { WebsocketClient } from '../../../../services/dashboard-backend-client/websocketClient'; +import { WebsocketClient } from '../../../../services/backend-client/websocketClient'; import { FakeStoreBuilder } from '../../../__mocks__/storeBuilder'; describe('Pod logs store, actions', () => { diff --git a/packages/dashboard-frontend/src/store/Pods/Logs/index.ts b/packages/dashboard-frontend/src/store/Pods/Logs/index.ts index 490e5a365..d43e5f26f 100644 --- a/packages/dashboard-frontend/src/store/Pods/Logs/index.ts +++ b/packages/dashboard-frontend/src/store/Pods/Logs/index.ts @@ -15,8 +15,8 @@ import { V1Pod } from '@kubernetes/client-node'; import { Action, Reducer } from 'redux'; import { AppThunk } from '../..'; import { container } from '../../../inversify.config'; -import { WebsocketClient } from '../../../services/dashboard-backend-client/websocketClient'; -import { ChannelListener } from '../../../services/dashboard-backend-client/websocketClient/messageHandler'; +import { WebsocketClient } from '../../../services/backend-client/websocketClient'; +import { ChannelListener } from '../../../services/backend-client/websocketClient/messageHandler'; import { createObject } from '../../helpers'; import { selectDefaultNamespace } from '../../InfrastructureNamespaces/selectors'; import { selectAllPods } from '../selectors'; @@ -193,11 +193,11 @@ export const reducer: Reducer = ( const _containers = _pod?.containers; const _containerLogs = _containers?.[action.containerName]; const _logs = action.failure === _containerLogs?.failure ? _containerLogs.logs : ''; - return createObject(state, { + return createObject(state, { logs: createObject(state.logs, { - [action.podName]: createObject(_pod, { + [action.podName]: createObject(_pod, { error: undefined, - containers: createObject(_containers, { + containers: createObject(_containers, { [action.containerName]: { logs: _logs + action.logs, failure: action.failure, @@ -208,7 +208,7 @@ export const reducer: Reducer = ( }); } case Type.DELETE_LOGS: - return createObject(state, { + return createObject(state, { logs: createObject(state.logs, { [action.podName]: undefined, }), diff --git a/packages/dashboard-frontend/src/store/Pods/__tests__/actions.spec.ts b/packages/dashboard-frontend/src/store/Pods/__tests__/actions.spec.ts index b5299a2d0..7b989aa07 100644 --- a/packages/dashboard-frontend/src/store/Pods/__tests__/actions.spec.ts +++ b/packages/dashboard-frontend/src/store/Pods/__tests__/actions.spec.ts @@ -18,7 +18,7 @@ import { ThunkDispatch } from 'redux-thunk'; import * as testStore from '..'; import { AppState } from '../..'; import { container } from '../../../inversify.config'; -import { WebsocketClient } from '../../../services/dashboard-backend-client/websocketClient'; +import { WebsocketClient } from '../../../services/backend-client/websocketClient'; import { AUTHORIZED } from '../../sanityCheckMiddleware'; import { FakeStoreBuilder } from '../../__mocks__/storeBuilder'; import { pod1, pod2 } from './stub'; diff --git a/packages/dashboard-frontend/src/store/Pods/index.ts b/packages/dashboard-frontend/src/store/Pods/index.ts index d4d0d09e4..624ae8300 100644 --- a/packages/dashboard-frontend/src/store/Pods/index.ts +++ b/packages/dashboard-frontend/src/store/Pods/index.ts @@ -15,8 +15,8 @@ import { V1Pod } from '@kubernetes/client-node'; import { Action, Reducer } from 'redux'; import { AppThunk } from '..'; import { container } from '../../inversify.config'; -import { fetchPods } from '../../services/dashboard-backend-client/podsApi'; -import { WebsocketClient } from '../../services/dashboard-backend-client/websocketClient'; +import { fetchPods } from '../../services/backend-client/podsApi'; +import { WebsocketClient } from '../../services/backend-client/websocketClient'; import { getNewerResourceVersion } from '../../services/helpers/resourceVersion'; import { createObject } from '../helpers'; import { selectDefaultNamespace } from '../InfrastructureNamespaces/selectors'; @@ -198,23 +198,23 @@ export const reducer: Reducer = ( const action = incomingAction as KnownAction; switch (action.type) { case Type.REQUEST_PODS: - return createObject(state, { + return createObject(state, { isLoading: true, error: undefined, }); case Type.RECEIVE_PODS: - return createObject(state, { + return createObject(state, { isLoading: false, pods: action.pods, resourceVersion: getNewerResourceVersion(action.resourceVersion, state.resourceVersion), }); case Type.RECEIVE_ERROR: - return createObject(state, { + return createObject(state, { isLoading: false, error: action.error, }); case Type.RECEIVE_POD: - return createObject(state, { + return createObject(state, { pods: state.pods.concat([action.pod]), resourceVersion: getNewerResourceVersion( action.pod.metadata?.resourceVersion, @@ -222,7 +222,7 @@ export const reducer: Reducer = ( ), }); case Type.MODIFY_POD: - return createObject(state, { + return createObject(state, { pods: state.pods.map(pod => (isSamePod(pod, action.pod) ? action.pod : pod)), resourceVersion: getNewerResourceVersion( action.pod.metadata?.resourceVersion, @@ -230,7 +230,7 @@ export const reducer: Reducer = ( ), }); case Type.DELETE_POD: - return createObject(state, { + return createObject(state, { pods: state.pods.filter(pod => isSamePod(pod, action.pod) === false), resourceVersion: getNewerResourceVersion( action.pod.metadata?.resourceVersion, diff --git a/packages/dashboard-frontend/src/store/SanityCheck/index.ts b/packages/dashboard-frontend/src/store/SanityCheck/index.ts index b43807b09..83d81b001 100644 --- a/packages/dashboard-frontend/src/store/SanityCheck/index.ts +++ b/packages/dashboard-frontend/src/store/SanityCheck/index.ts @@ -13,14 +13,16 @@ import { Action, Reducer } from 'redux'; import { AppThunk } from '..'; import { helpers } from '@eclipse-che/common'; -import { container } from '../../inversify.config'; import { getDefer } from '../../services/helpers/deferred'; import { delay } from '../../services/helpers/delay'; -import { CheWorkspaceClient } from '../../services/workspace-client/cheworkspace/cheWorkspaceClient'; -import { isForbidden, isUnauthorized } from '../../services/workspace-client/helpers'; +import { + getErrorMessage, + hasLoginPage, + isForbidden, + isUnauthorized, +} from '../../services/workspace-client/helpers'; import { createObject } from '../helpers'; - -const WorkspaceClient = container.get(CheWorkspaceClient); +import { provisionKubernetesNamespace } from '../../services/backend-client/kubernetesNamespaceApi'; const secToStale = 5; const timeToStale = secToStale * 1000; @@ -93,7 +95,7 @@ export const actionCreators: ActionCreators = { for (let attempt = 1; attempt <= maxAttemptsNumber; attempt++) { try { - await WorkspaceClient.restApiClient.provisionKubernetesNamespace(); + await provisionKubernetesNamespace(); deferred.resolve(true); dispatch({ @@ -105,20 +107,19 @@ export const actionCreators: ActionCreators = { if (attempt === maxAttemptsNumber) { throw e; } - delay(1000); + await delay(1000); } } } catch (e) { - let errorMessage = - 'Backend is not available. Try to refresh the page or re-login to the Dashboard.'; - if (isUnauthorized(e) || isForbidden(e)) { - errorMessage = 'User session has expired. You need to re-login to the Dashboard.'; + if (isUnauthorized(e) || (isForbidden(e) && hasLoginPage(e))) { + window.location.reload(); } - deferred.resolve(false); + const errorMessage = getErrorMessage(e); dispatch({ type: Type.RECEIVED_BACKEND_CHECK_ERROR, error: errorMessage, }); + deferred.resolve(false); console.error(helpers.errors.getMessage(e)); if ( helpers.errors.includesAxiosResponse(e) && @@ -148,15 +149,15 @@ export const reducer: Reducer = ( const action = incomingAction as KnownAction; switch (action.type) { case Type.REQUEST_BACKEND_CHECK: - return createObject(state, { + return createObject(state, { error: undefined, authorized: action.authorized, lastFetched: action.lastFetched, }); case Type.RECEIVED_BACKEND_CHECK: - return createObject(state, {}); + return createObject(state, {}); case Type.RECEIVED_BACKEND_CHECK_ERROR: - return createObject(state, { + return createObject(state, { authorized: state.authorized, lastFetched: state.lastFetched, error: action.error, diff --git a/packages/dashboard-frontend/src/store/SanityCheck/selectors.ts b/packages/dashboard-frontend/src/store/SanityCheck/selectors.ts index 78aee4006..7fd58f906 100644 --- a/packages/dashboard-frontend/src/store/SanityCheck/selectors.ts +++ b/packages/dashboard-frontend/src/store/SanityCheck/selectors.ts @@ -26,4 +26,4 @@ export const selectAsyncIsAuthorized = createSelector( }, ); -export const selectSanityCheckError = createSelector(selectState, state => state.error); +export const selectSanityCheckError = createSelector(selectState, state => state.error || ''); diff --git a/packages/dashboard-frontend/src/store/ServerConfig/index.ts b/packages/dashboard-frontend/src/store/ServerConfig/index.ts index a81b545bd..1041215e1 100644 --- a/packages/dashboard-frontend/src/store/ServerConfig/index.ts +++ b/packages/dashboard-frontend/src/store/ServerConfig/index.ts @@ -14,7 +14,7 @@ import { Action, Reducer } from 'redux'; import common, { api } from '@eclipse-che/common'; import { AppThunk } from '../'; import { createObject } from '../helpers'; -import * as ServerConfigApi from '../../services/dashboard-backend-client/serverConfigApi'; +import * as ServerConfigApi from '../../services/backend-client/serverConfigApi'; import { AUTHORIZED, SanityCheckAction } from '../sanityCheckMiddleware'; export interface State { @@ -112,17 +112,17 @@ export const reducer: Reducer = ( const action = incomingAction as KnownAction; switch (action.type) { case 'REQUEST_DW_SERVER_CONFIG': - return createObject(state, { + return createObject(state, { isLoading: true, }); case 'RECEIVE_DW_SERVER_CONFIG': - return createObject(state, { + return createObject(state, { isLoading: false, config: action.config, error: undefined, }); case 'RECEIVE_DW_SERVER_CONFIG_ERROR': - return createObject(state, { + return createObject(state, { isLoading: false, error: action.error, }); diff --git a/packages/dashboard-frontend/src/store/User/Id/index.ts b/packages/dashboard-frontend/src/store/User/Id/index.ts index 47391d31a..7a697e6e7 100644 --- a/packages/dashboard-frontend/src/store/User/Id/index.ts +++ b/packages/dashboard-frontend/src/store/User/Id/index.ts @@ -16,6 +16,7 @@ import { fetchCheUserId } from '../../../services/che-user-id'; import { createObject } from '../../helpers'; import { AppThunk } from '../../index'; import { AUTHORIZED, SanityCheckAction } from '../../sanityCheckMiddleware'; +import { selectAsyncIsAuthorized, selectSanityCheckError } from '../../SanityCheck/selectors'; export interface State { cheUserId: string; @@ -52,10 +53,13 @@ export type ActionCreators = { export const actionCreators: ActionCreators = { requestCheUserId: (): AppThunk> => - async (dispatch): Promise => { - await dispatch({ type: Type.REQUEST_CHE_USER_ID, check: AUTHORIZED }); - + async (dispatch, getState): Promise => { try { + await dispatch({ type: Type.REQUEST_CHE_USER_ID, check: AUTHORIZED }); + if (!(await selectAsyncIsAuthorized(getState()))) { + const error = selectSanityCheckError(getState()); + throw new Error(error); + } const cheUserId = await fetchCheUserId(); dispatch({ type: Type.RECEIVE_CHE_USER_ID, @@ -88,17 +92,17 @@ export const reducer: Reducer = ( const action = incomingAction as KnownAction; switch (action.type) { case Type.REQUEST_CHE_USER_ID: - return createObject(state, { + return createObject(state, { isLoading: true, error: undefined, }); case Type.RECEIVE_CHE_USER_ID: - return createObject(state, { + return createObject(state, { isLoading: false, cheUserId: action.cheUserId, }); case Type.RECEIVE_CHE_USER_ID_ERROR: - return createObject(state, { + return createObject(state, { isLoading: false, error: action.error, }); diff --git a/packages/dashboard-frontend/src/store/User/Profile/index.ts b/packages/dashboard-frontend/src/store/User/Profile/index.ts index af906e376..f4ff0e890 100644 --- a/packages/dashboard-frontend/src/store/User/Profile/index.ts +++ b/packages/dashboard-frontend/src/store/User/Profile/index.ts @@ -14,10 +14,11 @@ import common, { api } from '@eclipse-che/common'; import { Action, Reducer } from 'redux'; -import { fetchUserProfile } from '../../../services/dashboard-backend-client/userProfileApi'; +import { fetchUserProfile } from '../../../services/backend-client/userProfileApi'; import { createObject } from '../../helpers'; import { AppThunk } from '../../index'; import { AUTHORIZED, SanityCheckAction } from '../../sanityCheckMiddleware'; +import { selectAsyncIsAuthorized, selectSanityCheckError } from '../../SanityCheck/selectors'; export interface State { userProfile: api.IUserProfile; @@ -57,10 +58,13 @@ export type ActionCreators = { export const actionCreators: ActionCreators = { requestUserProfile: (namespace: string): AppThunk> => - async (dispatch): Promise => { - await dispatch({ type: Type.REQUEST_USER_PROFILE, check: AUTHORIZED }); - + async (dispatch, getState): Promise => { try { + await dispatch({ type: Type.REQUEST_USER_PROFILE, check: AUTHORIZED }); + if (!(await selectAsyncIsAuthorized(getState()))) { + const error = selectSanityCheckError(getState()); + throw new Error(error); + } const userProfile = await fetchUserProfile(namespace); dispatch({ type: Type.RECEIVE_USER_PROFILE, @@ -99,17 +103,17 @@ export const reducer: Reducer = ( const action = incomingAction as KnownAction; switch (action.type) { case Type.REQUEST_USER_PROFILE: - return createObject(state, { + return createObject(state, { isLoading: true, error: undefined, }); case Type.RECEIVE_USER_PROFILE: - return createObject(state, { + return createObject(state, { isLoading: false, userProfile: action.userProfile, }); case Type.RECEIVE_USER_PROFILE_ERROR: - return createObject(state, { + return createObject(state, { isLoading: false, error: action.error, }); diff --git a/packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/__tests__/actions.spec.ts b/packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/__tests__/actions.spec.ts index 86acb05e8..5fbf4ebb4 100644 --- a/packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/__tests__/actions.spec.ts +++ b/packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/__tests__/actions.spec.ts @@ -22,8 +22,8 @@ import * as testStore from '..'; import { AppState } from '../../..'; import { container } from '../../../../inversify.config'; import { FactoryParams } from '../../../../services/helpers/factoryFlow/buildFactoryParams'; -import { fetchServerConfig } from '../../../../services/dashboard-backend-client/serverConfigApi'; -import { WebsocketClient } from '../../../../services/dashboard-backend-client/websocketClient'; +import { fetchServerConfig } from '../../../../services/backend-client/serverConfigApi'; +import { WebsocketClient } from '../../../../services/backend-client/websocketClient'; import devfileApi from '../../../../services/devfileApi'; import { DevWorkspaceClient } from '../../../../services/workspace-client/devworkspace/devWorkspaceClient'; import { AUTHORIZED } from '../../../sanityCheckMiddleware'; @@ -33,13 +33,13 @@ import { FakeStoreBuilder } from '../../../__mocks__/storeBuilder'; import { checkRunningWorkspacesLimit } from '../checkRunningWorkspacesLimit'; import { DEVWORKSPACE_STORAGE_TYPE_ATTR } from '../../../../services/devfileApi/devWorkspace/spec/template'; -jest.mock('../../../../services/dashboard-backend-client/serverConfigApi'); +jest.mock('../../../../services/backend-client/serverConfigApi'); jest.mock('../../../../services/helpers/delay', () => ({ delay: jest.fn().mockResolvedValue(undefined), })); jest.mock('../checkRunningWorkspacesLimit.ts'); -jest.mock('../../../../services/dashboard-backend-client/devworkspaceResourcesApi', () => ({ +jest.mock('../../../../services/backend-client/devworkspaceResourcesApi', () => ({ fetchResources: () => ` apiVersion: workspace.devfile.io/v1alpha2 kind: DevWorkspaceTemplate @@ -95,7 +95,7 @@ spec: })); const mockPatchTemplate = jest.fn(); -jest.mock('../../../../services/dashboard-backend-client/devWorkspaceTemplateApi', () => ({ +jest.mock('../../../../services/backend-client/devWorkspaceTemplateApi', () => ({ getTemplates: () => [ { apiVersion: 'workspace.devfile.io/v1alpha2', @@ -111,7 +111,7 @@ jest.mock('../../../../services/dashboard-backend-client/devWorkspaceTemplateApi mockPatchTemplate(templateNamespace, templateName, targetTemplatePatch), })); const mockPatchWorkspace = jest.fn(); -jest.mock('../../../../services/dashboard-backend-client/devWorkspaceApi', () => ({ +jest.mock('../../../../services/backend-client/devWorkspaceApi', () => ({ patchWorkspace: (namespace, workspaceName, patch) => mockPatchWorkspace(namespace, workspaceName, patch), })); @@ -635,6 +635,10 @@ describe('DevWorkspace store, actions', () => { const actions = store.getActions(); const expectedActions: Array = [ + { + type: testStore.Type.REQUEST_DEVWORKSPACE, + check: AUTHORIZED, + }, { message: 'Cleaning up resources for deletion', type: testStore.Type.TERMINATE_DEVWORKSPACE, @@ -659,6 +663,10 @@ describe('DevWorkspace store, actions', () => { const actions = store.getActions(); const expectedActions: Array = [ + { + type: testStore.Type.REQUEST_DEVWORKSPACE, + check: AUTHORIZED, + }, { type: testStore.Type.RECEIVE_DEVWORKSPACE_ERROR, error: `Failed to delete the workspace ${devWorkspace.metadata.name}, reason: Something unexpected happened.`, diff --git a/packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/index.ts b/packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/index.ts index cbe99da2f..d291052b3 100644 --- a/packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/index.ts +++ b/packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/index.ts @@ -16,12 +16,9 @@ import { Action, Reducer } from 'redux'; import { AppThunk } from '../..'; import { container } from '../../../inversify.config'; import { FactoryParams } from '../../../services/helpers/factoryFlow/buildFactoryParams'; -import { - injectKubeConfig, - podmanLogin, -} from '../../../services/dashboard-backend-client/devWorkspaceApi'; -import { fetchResources } from '../../../services/dashboard-backend-client/devworkspaceResourcesApi'; -import { WebsocketClient } from '../../../services/dashboard-backend-client/websocketClient'; +import { injectKubeConfig, podmanLogin } from '../../../services/backend-client/devWorkspaceApi'; +import { fetchResources } from '../../../services/backend-client/devworkspaceResourcesApi'; +import { WebsocketClient } from '../../../services/backend-client/websocketClient'; import devfileApi, { isDevWorkspace } from '../../../services/devfileApi'; import { devWorkspaceKind } from '../../../services/devfileApi/devWorkspace'; import { @@ -56,11 +53,12 @@ import { } from '../../ServerConfig/selectors'; import { checkRunningWorkspacesLimit } from './checkRunningWorkspacesLimit'; import { selectDevWorkspacesResourceVersion } from './selectors'; -import * as DwtApi from '../../../services/dashboard-backend-client/devWorkspaceTemplateApi'; +import * as DwtApi from '../../../services/backend-client/devWorkspaceTemplateApi'; import { selectDefaultDevfile } from '../../DevfileRegistries/selectors'; -import * as DwApi from '../../../services/dashboard-backend-client/devWorkspaceApi'; +import * as DwApi from '../../../services/backend-client/devWorkspaceApi'; import { selectDefaultEditor } from '../../Plugins/devWorkspacePlugins/selectors'; import { DEVWORKSPACE_STORAGE_TYPE_ATTR } from '../../../services/devfileApi/devWorkspace/spec/template'; +import { selectAsyncIsAuthorized, selectSanityCheckError } from '../../SanityCheck/selectors'; export const onStatusChangeCallbacks = new Map void>(); @@ -196,9 +194,12 @@ export const actionCreators: ActionCreators = { requestWorkspaces: (): AppThunk> => async (dispatch, getState): Promise => { - await dispatch({ type: Type.REQUEST_DEVWORKSPACE, check: AUTHORIZED }); - try { + await dispatch({ type: Type.REQUEST_DEVWORKSPACE, check: AUTHORIZED }); + if (!(await selectAsyncIsAuthorized(getState()))) { + const error = selectSanityCheckError(getState()); + throw new Error(error); + } const defaultKubernetesNamespace = selectDefaultNamespace(getState()); const defaultNamespace = defaultKubernetesNamespace.name; const { workspaces, resourceVersion } = defaultNamespace @@ -242,10 +243,13 @@ export const actionCreators: ActionCreators = { requestWorkspace: (workspace: devfileApi.DevWorkspace): AppThunk> => - async (dispatch): Promise => { - await dispatch({ type: Type.REQUEST_DEVWORKSPACE, check: AUTHORIZED }); - + async (dispatch, getState): Promise => { try { + await dispatch({ type: Type.REQUEST_DEVWORKSPACE, check: AUTHORIZED }); + if (!(await selectAsyncIsAuthorized(getState()))) { + const error = selectSanityCheckError(getState()); + throw new Error(error); + } const namespace = workspace.metadata.namespace; const name = workspace.metadata.name; const update = await getDevWorkspaceClient().getWorkspaceByName(namespace, name); @@ -290,8 +294,12 @@ export const actionCreators: ActionCreators = { return; } await OAuthService.refreshTokenIfNeeded(workspace); - await dispatch({ type: Type.REQUEST_DEVWORKSPACE, check: AUTHORIZED }); try { + await dispatch({ type: Type.REQUEST_DEVWORKSPACE, check: AUTHORIZED }); + if (!(await selectAsyncIsAuthorized(getState()))) { + const error = selectSanityCheckError(getState()); + throw new Error(error); + } checkRunningWorkspacesLimit(getState()); if (workspace.metadata.annotations?.[DEVWORKSPACE_NEXT_START_ANNOTATION]) { @@ -435,8 +443,13 @@ export const actionCreators: ActionCreators = { terminateWorkspace: (workspace: devfileApi.DevWorkspace): AppThunk> => - async (dispatch): Promise => { + async (dispatch, getState): Promise => { try { + await dispatch({ type: Type.REQUEST_DEVWORKSPACE, check: AUTHORIZED }); + if (!(await selectAsyncIsAuthorized(getState()))) { + const error = selectSanityCheckError(getState()); + throw new Error(error); + } const namespace = workspace.metadata.namespace; const name = workspace.metadata.name; await getDevWorkspaceClient().delete(namespace, name); @@ -461,10 +474,13 @@ export const actionCreators: ActionCreators = { updateWorkspaceAnnotation: (workspace: devfileApi.DevWorkspace): AppThunk> => - async (dispatch): Promise => { - await dispatch({ type: Type.REQUEST_DEVWORKSPACE, check: AUTHORIZED }); - + async (dispatch, getState): Promise => { try { + await dispatch({ type: Type.REQUEST_DEVWORKSPACE, check: AUTHORIZED }); + if (!(await selectAsyncIsAuthorized(getState()))) { + const error = selectSanityCheckError(getState()); + throw new Error(error); + } const updated = await getDevWorkspaceClient().updateAnnotation(workspace); dispatch({ type: Type.UPDATE_DEVWORKSPACE, @@ -484,10 +500,13 @@ export const actionCreators: ActionCreators = { updateWorkspace: (workspace: devfileApi.DevWorkspace): AppThunk> => - async (dispatch): Promise => { - await dispatch({ type: Type.REQUEST_DEVWORKSPACE, check: AUTHORIZED }); - + async (dispatch, getState): Promise => { try { + await dispatch({ type: Type.REQUEST_DEVWORKSPACE, check: AUTHORIZED }); + if (!(await selectAsyncIsAuthorized(getState()))) { + const error = selectSanityCheckError(getState()); + throw new Error(error); + } const updated = await getDevWorkspaceClient().update(workspace); dispatch({ type: Type.UPDATE_DEVWORKSPACE, @@ -513,15 +532,18 @@ export const actionCreators: ActionCreators = { ): AppThunk> => async (dispatch, getState): Promise => { const state = getState(); - - await dispatch({ type: Type.REQUEST_DEVWORKSPACE, check: AUTHORIZED }); - const defaultKubernetesNamespace = selectDefaultNamespace(state); const openVSXUrl = selectOpenVSXUrl(state); const pluginRegistryUrl = selectPluginRegistryUrl(state); const pluginRegistryInternalUrl = selectPluginRegistryInternalUrl(state); const defaultNamespace = defaultKubernetesNamespace.name; + try { + await dispatch({ type: Type.REQUEST_DEVWORKSPACE, check: AUTHORIZED }); + if (!(await selectAsyncIsAuthorized(getState()))) { + const error = selectSanityCheckError(getState()); + throw new Error(error); + } /* create a new DevWorkspace */ const createResp = await getDevWorkspaceClient().createDevWorkspace( defaultNamespace, @@ -592,9 +614,6 @@ export const actionCreators: ActionCreators = { (workspace: devfileApi.DevWorkspace): AppThunk> => async (dispatch, getState): Promise => { const state = getState(); - - await dispatch({ type: Type.REQUEST_DEVWORKSPACE, check: AUTHORIZED }); - const defaultsDevfile = selectDefaultDevfile(state); if (!defaultsDevfile) { throw new Error('Cannot define default devfile'); @@ -616,6 +635,11 @@ export const actionCreators: ActionCreators = { let devWorkspaceTemplateResource: devfileApi.DevWorkspaceTemplate; try { + await dispatch({ type: Type.REQUEST_DEVWORKSPACE, check: AUTHORIZED }); + if (!(await selectAsyncIsAuthorized(getState()))) { + const error = selectSanityCheckError(getState()); + throw new Error(error); + } const response = await getEditor(defaultsEditor, dispatch, getState, pluginRegistryUrl); if (response.content) { editorContent = response.content; @@ -796,9 +820,6 @@ export const actionCreators: ActionCreators = { ): AppThunk> => async (dispatch, getState): Promise => { const state = getState(); - - await dispatch({ type: Type.REQUEST_DEVWORKSPACE, check: AUTHORIZED }); - const pluginRegistryUrl = state.dwServerConfig.config.pluginRegistryURL; let devWorkspaceResource: devfileApi.DevWorkspace; let devWorkspaceTemplateResource: devfileApi.DevWorkspaceTemplate; @@ -846,6 +867,11 @@ export const actionCreators: ActionCreators = { } try { + await dispatch({ type: Type.REQUEST_DEVWORKSPACE, check: AUTHORIZED }); + if (!(await selectAsyncIsAuthorized(getState()))) { + const error = selectSanityCheckError(getState()); + throw new Error(error); + } const resourcesContent = await fetchResources({ pluginRegistryUrl, devfileContent: dump(devfile), @@ -1019,23 +1045,23 @@ export const reducer: Reducer = ( const action = incomingAction as KnownAction; switch (action.type) { case Type.REQUEST_DEVWORKSPACE: - return createObject(state, { + return createObject(state, { isLoading: true, error: undefined, }); case Type.RECEIVE_DEVWORKSPACE: - return createObject(state, { + return createObject(state, { isLoading: false, workspaces: action.workspaces, resourceVersion: getNewerResourceVersion(action.resourceVersion, state.resourceVersion), }); case Type.RECEIVE_DEVWORKSPACE_ERROR: - return createObject(state, { + return createObject(state, { isLoading: false, error: action.error, }); case Type.UPDATE_DEVWORKSPACE: - return createObject(state, { + return createObject(state, { isLoading: false, workspaces: state.workspaces.map(workspace => WorkspaceAdapter.getUID(workspace) === WorkspaceAdapter.getUID(action.workspace) @@ -1048,7 +1074,7 @@ export const reducer: Reducer = ( ), }); case Type.ADD_DEVWORKSPACE: - return createObject(state, { + return createObject(state, { isLoading: false, workspaces: state.workspaces .filter( @@ -1062,7 +1088,7 @@ export const reducer: Reducer = ( ), }); case Type.TERMINATE_DEVWORKSPACE: - return createObject(state, { + return createObject(state, { isLoading: false, workspaces: state.workspaces.map(workspace => { if (WorkspaceAdapter.getUID(workspace) === action.workspaceUID) { @@ -1078,7 +1104,7 @@ export const reducer: Reducer = ( }), }); case Type.DELETE_DEVWORKSPACE: - return createObject(state, { + return createObject(state, { isLoading: false, workspaces: state.workspaces.filter( workspace => @@ -1090,7 +1116,7 @@ export const reducer: Reducer = ( ), }); case Type.UPDATE_STARTED_WORKSPACES: - return createObject(state, { + return createObject(state, { startedWorkspaces: action.workspaces.reduce((acc, workspace) => { if (workspace.spec.started === false) { delete acc[WorkspaceAdapter.getUID(workspace)]; @@ -1113,7 +1139,7 @@ export const reducer: Reducer = ( }, state.startedWorkspaces), }); case Type.UPDATE_WARNING: - return createObject(state, { + return createObject(state, { warnings: { [WorkspaceAdapter.getUID(action.workspace)]: action.warning, }, diff --git a/packages/dashboard-frontend/src/store/Workspaces/index.ts b/packages/dashboard-frontend/src/store/Workspaces/index.ts index e5b8736a5..ece5fc2b1 100644 --- a/packages/dashboard-frontend/src/store/Workspaces/index.ts +++ b/packages/dashboard-frontend/src/store/Workspaces/index.ts @@ -300,7 +300,7 @@ export const reducer: Reducer = (state: State | undefined, action: KnownA switch (action.type) { case 'REQUEST_WORKSPACES': - return createObject(state, { + return createObject(state, { isLoading: true, }); case 'RECEIVE_ERROR': @@ -308,25 +308,25 @@ export const reducer: Reducer = (state: State | undefined, action: KnownA case 'ADD_WORKSPACE': case 'DELETE_WORKSPACE': case 'RECEIVE_WORKSPACES': - return createObject(state, { + return createObject(state, { isLoading: false, }); case 'SET_WORKSPACE_NAME': - return createObject(state, { + return createObject(state, { namespace: action.namespace, workspaceName: action.workspaceName, }); case 'CLEAR_WORKSPACE_NAME': - return createObject(state, { + return createObject(state, { namespace: '', workspaceName: '', }); case 'SET_WORKSPACE_UID': - return createObject(state, { + return createObject(state, { workspaceUID: action.workspaceUID, }); case 'CLEAR_WORKSPACE_UID': - return createObject(state, { + return createObject(state, { workspaceUID: '', }); default: diff --git a/yarn.lock b/yarn.lock index 0a98245fb..efcf7f011 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7,7 +7,7 @@ resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== -"@adobe/css-tools@^4.0.1": +"@adobe/css-tools@^4.3.0": version "4.3.1" resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.3.1.tgz#abfccb8ca78075a2b6187345c26243c1a0842f28" integrity sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg== @@ -376,7 +376,7 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== -"@eclipse-che/api@^7.0.0-beta-4.0", "@eclipse-che/api@^7.18.1", "@eclipse-che/api@^7.39.2": +"@eclipse-che/api@^7.18.1", "@eclipse-che/api@^7.39.2": version "7.72.0" resolved "https://registry.yarnpkg.com/@eclipse-che/api/-/api-7.72.0.tgz#f16a19f2628c307783203aa5c205b6098b7e57df" integrity sha512-baah1TSYAmCOuiFCHssb7mBoO5BrTAAz8tLV8Y1nqXvDIYMXXyHOnbBpl8/rVeplHGEZIDpFyFN1OGLoy6mcJA== @@ -406,16 +406,6 @@ jsonc-parser "^3.0.0" reflect-metadata "^0.1.13" -"@eclipse-che/workspace-client@0.0.1-1672830275": - version "0.0.1-1672830275" - resolved "https://registry.yarnpkg.com/@eclipse-che/workspace-client/-/workspace-client-0.0.1-1672830275.tgz#7724fbe74fa8ee86a23f888e23d7031cb608787e" - integrity sha512-QgbLxTns7m/efbWZbFRqd9UxH4ELN5Np3JzYWzqN5i/R3cra/xuV9KwDedctarZ6iwnW9ptZFsfDcU5zedZKKQ== - dependencies: - "@eclipse-che/api" "^7.0.0-beta-4.0" - axios "^0.21.4" - qs "^6.9.4" - tunnel "0.0.6" - "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.4.0" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" @@ -697,6 +687,18 @@ jest-util "^29.6.2" slash "^3.0.0" +"@jest/console@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.7.0.tgz#cd4822dbdb84529265c5a2bdb529a3c9cc950ffc" + integrity sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + "@jest/core@^29.6.2": version "29.6.2" resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.6.2.tgz#6f2d1dbe8aa0265fcd4fb8082ae1952f148209c8" @@ -731,6 +733,40 @@ slash "^3.0.0" strip-ansi "^6.0.0" +"@jest/core@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.7.0.tgz#b6cccc239f30ff36609658c5a5e2291757ce448f" + integrity sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg== + dependencies: + "@jest/console" "^29.7.0" + "@jest/reporters" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + ci-info "^3.2.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-changed-files "^29.7.0" + jest-config "^29.7.0" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-resolve-dependencies "^29.7.0" + jest-runner "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + jest-watcher "^29.7.0" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + "@jest/environment@^29.6.2": version "29.6.2" resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.6.2.tgz#794c0f769d85e7553439d107d3f43186dc6874a9" @@ -741,6 +777,16 @@ "@types/node" "*" jest-mock "^29.6.2" +"@jest/environment@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" + integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== + dependencies: + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + "@jest/expect-utils@^29.6.2": version "29.6.2" resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.6.2.tgz#1b97f290d0185d264dd9fdec7567a14a38a90534" @@ -748,6 +794,13 @@ dependencies: jest-get-type "^29.4.3" +"@jest/expect-utils@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" + integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== + dependencies: + jest-get-type "^29.6.3" + "@jest/expect@^29.6.2": version "29.6.2" resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.6.2.tgz#5a2ad58bb345165d9ce0a1845bbf873c480a4b28" @@ -756,6 +809,14 @@ expect "^29.6.2" jest-snapshot "^29.6.2" +"@jest/expect@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.7.0.tgz#76a3edb0cb753b70dfbfe23283510d3d45432bf2" + integrity sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ== + dependencies: + expect "^29.7.0" + jest-snapshot "^29.7.0" + "@jest/fake-timers@^29.6.2": version "29.6.2" resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.6.2.tgz#fe9d43c5e4b1b901168fe6f46f861b3e652a2df4" @@ -768,6 +829,18 @@ jest-mock "^29.6.2" jest-util "^29.6.2" +"@jest/fake-timers@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565" + integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== + dependencies: + "@jest/types" "^29.6.3" + "@sinonjs/fake-timers" "^10.0.2" + "@types/node" "*" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-util "^29.7.0" + "@jest/globals@^29.6.2": version "29.6.2" resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.6.2.tgz#74af81b9249122cc46f1eb25793617eec69bf21a" @@ -778,6 +851,16 @@ "@jest/types" "^29.6.1" jest-mock "^29.6.2" +"@jest/globals@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.7.0.tgz#8d9290f9ec47ff772607fa864ca1d5a2efae1d4d" + integrity sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/types" "^29.6.3" + jest-mock "^29.7.0" + "@jest/reporters@^29.6.2": version "29.6.2" resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.6.2.tgz#524afe1d76da33d31309c2c4a2c8062d0c48780a" @@ -808,6 +891,36 @@ strip-ansi "^6.0.0" v8-to-istanbul "^9.0.1" +"@jest/reporters@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.7.0.tgz#04b262ecb3b8faa83b0b3d321623972393e8f4c7" + integrity sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + "@types/node" "*" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^6.0.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.1.3" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + jest-worker "^29.7.0" + slash "^3.0.0" + string-length "^4.0.1" + strip-ansi "^6.0.0" + v8-to-istanbul "^9.0.1" + "@jest/schemas@^29.4.3", "@jest/schemas@^29.6.0": version "29.6.0" resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.0.tgz#0f4cb2c8e3dca80c135507ba5635a4fd755b0040" @@ -815,6 +928,13 @@ dependencies: "@sinclair/typebox" "^0.27.8" +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + "@jest/source-map@^29.6.0": version "29.6.0" resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.0.tgz#bd34a05b5737cb1a99d43e1957020ac8e5b9ddb1" @@ -824,6 +944,15 @@ callsites "^3.0.0" graceful-fs "^4.2.9" +"@jest/source-map@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.3.tgz#d90ba772095cf37a34a5eb9413f1b562a08554c4" + integrity sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw== + dependencies: + "@jridgewell/trace-mapping" "^0.3.18" + callsites "^3.0.0" + graceful-fs "^4.2.9" + "@jest/test-result@^29.6.2": version "29.6.2" resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.6.2.tgz#fdd11583cd1608e4db3114e8f0cce277bf7a32ed" @@ -834,6 +963,16 @@ "@types/istanbul-lib-coverage" "^2.0.0" collect-v8-coverage "^1.0.0" +"@jest/test-result@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.7.0.tgz#8db9a80aa1a097bb2262572686734baed9b1657c" + integrity sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA== + dependencies: + "@jest/console" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + "@jest/test-sequencer@^29.6.2": version "29.6.2" resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.6.2.tgz#585eff07a68dd75225a7eacf319780cb9f6b9bf4" @@ -844,6 +983,16 @@ jest-haste-map "^29.6.2" slash "^3.0.0" +"@jest/test-sequencer@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz#6cef977ce1d39834a3aea887a1726628a6f072ce" + integrity sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw== + dependencies: + "@jest/test-result" "^29.7.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + slash "^3.0.0" + "@jest/transform@^29.6.2": version "29.6.2" resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.6.2.tgz#522901ebbb211af08835bc3bcdf765ab778094e3" @@ -865,6 +1014,27 @@ slash "^3.0.0" write-file-atomic "^4.0.2" +"@jest/transform@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c" + integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== + dependencies: + "@babel/core" "^7.11.6" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^2.0.0" + fast-json-stable-stringify "^2.1.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + write-file-atomic "^4.0.2" + "@jest/types@^26.6.2": version "26.6.2" resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e" @@ -888,6 +1058,18 @@ "@types/yargs" "^17.0.8" chalk "^4.0.0" +"@jest/types@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" + integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== + dependencies: + "@jest/schemas" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + "@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": version "0.3.3" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" @@ -1603,14 +1785,13 @@ lz-string "^1.4.4" pretty-format "^26.6.2" -"@testing-library/jest-dom@^5.17.0": - version "5.17.0" - resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.17.0.tgz#5e97c8f9a15ccf4656da00fecab505728de81e0c" - integrity sha512-ynmNeT7asXyH3aSVv4vvX4Rb+0qjOhdNHnO/3vuZNqPmhDpV/+rCSGwQ7bLcmU2cJ4dvoheIO85LQj0IbJHEtg== +"@testing-library/jest-dom@^6.1.3": + version "6.1.3" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.1.3.tgz#443118c9e4043f96396f120de2c7122504a079c5" + integrity sha512-YzpjRHoCBWPzpPNtg6gnhasqtE/5O4qz8WCwDEaxtfnPO6gkaLrnuXusrGSPyhIGPezr1HM7ZH0CFaUTY9PJEQ== dependencies: - "@adobe/css-tools" "^4.0.1" + "@adobe/css-tools" "^4.3.0" "@babel/runtime" "^7.9.2" - "@types/testing-library__jest-dom" "^5.9.1" aria-query "^5.0.0" chalk "^3.0.0" css.escape "^1.5.1" @@ -1953,7 +2134,7 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== -"@types/testing-library__jest-dom@^5.14.9", "@types/testing-library__jest-dom@^5.9.1": +"@types/testing-library__jest-dom@^5.14.9": version "5.14.9" resolved "https://registry.yarnpkg.com/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.9.tgz#0fb1e6a0278d87b6737db55af5967570b67cb466" integrity sha512-FSYhIjFlfOpGSRyVoMBMuS3ws5ehFQODymf3vlI7U1K8c7PHwWwFY7VREfmsuzHSOnoKs/9/Y983ayOs7eRzqw== @@ -2796,6 +2977,19 @@ babel-jest@^29.6.2: graceful-fs "^4.2.9" slash "^3.0.0" +babel-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" + integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== + dependencies: + "@jest/transform" "^29.7.0" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^29.6.3" + chalk "^4.0.0" + graceful-fs "^4.2.9" + slash "^3.0.0" + babel-messages@^6.23.0: version "6.23.0" resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e" @@ -2824,6 +3018,16 @@ babel-plugin-jest-hoist@^29.5.0: "@types/babel__core" "^7.1.14" "@types/babel__traverse" "^7.0.6" +babel-plugin-jest-hoist@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626" + integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.1.14" + "@types/babel__traverse" "^7.0.6" + babel-plugin-transform-es2015-block-scoping@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz#d70f5299c1308d05c12f463813b0a09e73b1895f" @@ -2861,6 +3065,14 @@ babel-preset-jest@^29.5.0: babel-plugin-jest-hoist "^29.5.0" babel-preset-current-node-syntax "^1.0.0" +babel-preset-jest@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c" + integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== + dependencies: + babel-plugin-jest-hoist "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + babel-register@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071" @@ -3821,6 +4033,19 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: safe-buffer "^5.0.1" sha.js "^2.4.8" +create-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" + integrity sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-config "^29.7.0" + jest-util "^29.7.0" + prompts "^2.0.1" + cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -4239,6 +4464,11 @@ diff-sequences@^29.4.3: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.4.3.tgz#9314bc1fabe09267ffeca9cbafc457d8499a13f2" integrity sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA== +diff-sequences@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== + diffie-hellman@^5.0.0: version "5.0.3" resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" @@ -4890,6 +5120,17 @@ expect@^29.0.0, expect@^29.6.2: jest-message-util "^29.6.2" jest-util "^29.6.2" +expect@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" + integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== + dependencies: + "@jest/expect-utils" "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + exponential-backoff@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.1.tgz#64ac7526fe341ab18a39016cd22c787d01e00bf6" @@ -6570,6 +6811,17 @@ istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: istanbul-lib-coverage "^3.2.0" semver "^6.3.0" +istanbul-lib-instrument@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.0.tgz#7a8af094cbfff1d5bb280f62ce043695ae8dd5b8" + integrity sha512-x58orMzEVfzPUKqlbLd1hXCnySCxKdDKa6Rjg97CwuLLRI4g3FHTdnExu1OqffVFay6zeMW+T6/DowFLndWnIw== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^7.5.4" + istanbul-lib-report@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" @@ -6634,6 +6886,15 @@ jest-changed-files@^29.5.0: execa "^5.0.0" p-limit "^3.1.0" +jest-changed-files@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a" + integrity sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w== + dependencies: + execa "^5.0.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + jest-circus@^29.6.2: version "29.6.2" resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.6.2.tgz#1e6ffca60151ac66cad63fce34f443f6b5bb4258" @@ -6660,6 +6921,32 @@ jest-circus@^29.6.2: slash "^3.0.0" stack-utils "^2.0.3" +jest-circus@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.7.0.tgz#b6817a45fcc835d8b16d5962d0c026473ee3668a" + integrity sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^1.0.0" + is-generator-fn "^2.0.0" + jest-each "^29.7.0" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + pretty-format "^29.7.0" + pure-rand "^6.0.0" + slash "^3.0.0" + stack-utils "^2.0.3" + jest-cli@^29.6.2: version "29.6.2" resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.6.2.tgz#edb381763398d1a292cd1b636a98bfa5644b8fda" @@ -6678,6 +6965,23 @@ jest-cli@^29.6.2: prompts "^2.0.1" yargs "^17.3.1" +jest-cli@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.7.0.tgz#5592c940798e0cae677eec169264f2d839a37995" + integrity sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg== + dependencies: + "@jest/core" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + chalk "^4.0.0" + create-jest "^29.7.0" + exit "^0.1.2" + import-local "^3.0.2" + jest-config "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + yargs "^17.3.1" + jest-config@^29.6.2: version "29.6.2" resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.6.2.tgz#c68723f06b31ca5e63030686e604727d406cd7c3" @@ -6706,6 +7010,34 @@ jest-config@^29.6.2: slash "^3.0.0" strip-json-comments "^3.1.1" +jest-config@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.7.0.tgz#bcbda8806dbcc01b1e316a46bb74085a84b0245f" + integrity sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ== + dependencies: + "@babel/core" "^7.11.6" + "@jest/test-sequencer" "^29.7.0" + "@jest/types" "^29.6.3" + babel-jest "^29.7.0" + chalk "^4.0.0" + ci-info "^3.2.0" + deepmerge "^4.2.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-circus "^29.7.0" + jest-environment-node "^29.7.0" + jest-get-type "^29.6.3" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-runner "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + micromatch "^4.0.4" + parse-json "^5.2.0" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-json-comments "^3.1.1" + jest-diff@^29.2.0, jest-diff@^29.6.2: version "29.6.2" resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.6.2.tgz#c36001e5543e82a0805051d3ceac32e6825c1c46" @@ -6716,6 +7048,16 @@ jest-diff@^29.2.0, jest-diff@^29.6.2: jest-get-type "^29.4.3" pretty-format "^29.6.2" +jest-diff@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" + integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.6.3" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + jest-docblock@^29.4.3: version "29.4.3" resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.4.3.tgz#90505aa89514a1c7dceeac1123df79e414636ea8" @@ -6723,6 +7065,13 @@ jest-docblock@^29.4.3: dependencies: detect-newline "^3.0.0" +jest-docblock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.7.0.tgz#8fddb6adc3cdc955c93e2a87f61cfd350d5d119a" + integrity sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g== + dependencies: + detect-newline "^3.0.0" + jest-each@^29.6.2: version "29.6.2" resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.6.2.tgz#c9e4b340bcbe838c73adf46b76817b15712d02ce" @@ -6734,18 +7083,29 @@ jest-each@^29.6.2: jest-util "^29.6.2" pretty-format "^29.6.2" -jest-environment-jsdom@^29.6.2: - version "29.6.2" - resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-29.6.2.tgz#4fc68836a7774a771819a2f980cb47af3b1629da" - integrity sha512-7oa/+266AAEgkzae8i1awNEfTfjwawWKLpiw2XesZmaoVVj9u9t8JOYx18cG29rbPNtkUlZ8V4b5Jb36y/VxoQ== +jest-each@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.7.0.tgz#162a9b3f2328bdd991beaabffbb74745e56577d1" + integrity sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ== dependencies: - "@jest/environment" "^29.6.2" - "@jest/fake-timers" "^29.6.2" - "@jest/types" "^29.6.1" + "@jest/types" "^29.6.3" + chalk "^4.0.0" + jest-get-type "^29.6.3" + jest-util "^29.7.0" + pretty-format "^29.7.0" + +jest-environment-jsdom@^29.6.4: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz#d206fa3551933c3fd519e5dfdb58a0f5139a837f" + integrity sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" "@types/jsdom" "^20.0.0" "@types/node" "*" - jest-mock "^29.6.2" - jest-util "^29.6.2" + jest-mock "^29.7.0" + jest-util "^29.7.0" jsdom "^20.0.0" jest-environment-node@^29.6.2: @@ -6760,11 +7120,28 @@ jest-environment-node@^29.6.2: jest-mock "^29.6.2" jest-util "^29.6.2" +jest-environment-node@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376" + integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + jest-util "^29.7.0" + jest-get-type@^29.4.3: version "29.4.3" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.4.3.tgz#1ab7a5207c995161100b5187159ca82dd48b3dd5" integrity sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg== +jest-get-type@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" + integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== + jest-haste-map@^29.6.2: version "29.6.2" resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.6.2.tgz#298c25ea5255cfad8b723179d4295cf3a50a70d1" @@ -6784,6 +7161,25 @@ jest-haste-map@^29.6.2: optionalDependencies: fsevents "^2.3.2" +jest-haste-map@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104" + integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA== + dependencies: + "@jest/types" "^29.6.3" + "@types/graceful-fs" "^4.1.3" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + jest-worker "^29.7.0" + micromatch "^4.0.4" + walker "^1.0.8" + optionalDependencies: + fsevents "^2.3.2" + jest-leak-detector@^29.6.2: version "29.6.2" resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.6.2.tgz#e2b307fee78cab091c37858a98c7e1d73cdf5b38" @@ -6792,6 +7188,14 @@ jest-leak-detector@^29.6.2: jest-get-type "^29.4.3" pretty-format "^29.6.2" +jest-leak-detector@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz#5b7ec0dadfdfec0ca383dc9aa016d36b5ea4c728" + integrity sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw== + dependencies: + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + jest-matcher-utils@^29.6.2: version "29.6.2" resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.6.2.tgz#39de0be2baca7a64eacb27291f0bd834fea3a535" @@ -6802,6 +7206,16 @@ jest-matcher-utils@^29.6.2: jest-get-type "^29.4.3" pretty-format "^29.6.2" +jest-matcher-utils@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" + integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== + dependencies: + chalk "^4.0.0" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + jest-message-util@^29.6.2: version "29.6.2" resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.6.2.tgz#af7adc2209c552f3f5ae31e77cf0a261f23dc2bb" @@ -6817,6 +7231,21 @@ jest-message-util@^29.6.2: slash "^3.0.0" stack-utils "^2.0.3" +jest-message-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" + integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.6.3" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + stack-utils "^2.0.3" + jest-mock@^29.6.2: version "29.6.2" resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.6.2.tgz#ef9c9b4d38c34a2ad61010a021866dad41ce5e00" @@ -6826,6 +7255,15 @@ jest-mock@^29.6.2: "@types/node" "*" jest-util "^29.6.2" +jest-mock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" + integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-util "^29.7.0" + jest-pnp-resolver@^1.2.2: version "1.2.3" resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" @@ -6836,6 +7274,11 @@ jest-regex-util@^29.4.3: resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.4.3.tgz#a42616141e0cae052cfa32c169945d00c0aa0bb8" integrity sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg== +jest-regex-util@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" + integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== + jest-resolve-dependencies@^29.6.2: version "29.6.2" resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.6.2.tgz#36435269b6672c256bcc85fb384872c134cc4cf2" @@ -6844,6 +7287,14 @@ jest-resolve-dependencies@^29.6.2: jest-regex-util "^29.4.3" jest-snapshot "^29.6.2" +jest-resolve-dependencies@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz#1b04f2c095f37fc776ff40803dc92921b1e88428" + integrity sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA== + dependencies: + jest-regex-util "^29.6.3" + jest-snapshot "^29.7.0" + jest-resolve@^29.6.2: version "29.6.2" resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.6.2.tgz#f18405fe4b50159b7b6d85e81f6a524d22afb838" @@ -6859,6 +7310,21 @@ jest-resolve@^29.6.2: resolve.exports "^2.0.0" slash "^3.0.0" +jest-resolve@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.7.0.tgz#64d6a8992dd26f635ab0c01e5eef4399c6bcbc30" + integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== + dependencies: + chalk "^4.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-pnp-resolver "^1.2.2" + jest-util "^29.7.0" + jest-validate "^29.7.0" + resolve "^1.20.0" + resolve.exports "^2.0.0" + slash "^3.0.0" + jest-runner@^29.6.2: version "29.6.2" resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.6.2.tgz#89e8e32a8fef24781a7c4c49cd1cb6358ac7fc01" @@ -6886,6 +7352,33 @@ jest-runner@^29.6.2: p-limit "^3.1.0" source-map-support "0.5.13" +jest-runner@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.7.0.tgz#809af072d408a53dcfd2e849a4c976d3132f718e" + integrity sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ== + dependencies: + "@jest/console" "^29.7.0" + "@jest/environment" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.13.1" + graceful-fs "^4.2.9" + jest-docblock "^29.7.0" + jest-environment-node "^29.7.0" + jest-haste-map "^29.7.0" + jest-leak-detector "^29.7.0" + jest-message-util "^29.7.0" + jest-resolve "^29.7.0" + jest-runtime "^29.7.0" + jest-util "^29.7.0" + jest-watcher "^29.7.0" + jest-worker "^29.7.0" + p-limit "^3.1.0" + source-map-support "0.5.13" + jest-runtime@^29.6.2: version "29.6.2" resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.6.2.tgz#692f25e387f982e89ab83270e684a9786248e545" @@ -6914,6 +7407,34 @@ jest-runtime@^29.6.2: slash "^3.0.0" strip-bom "^4.0.0" +jest-runtime@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.7.0.tgz#efecb3141cf7d3767a3a0cc8f7c9990587d3d817" + integrity sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/globals" "^29.7.0" + "@jest/source-map" "^29.6.3" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + cjs-module-lexer "^1.0.0" + collect-v8-coverage "^1.0.0" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + strip-bom "^4.0.0" + jest-snapshot@^29.6.2: version "29.6.2" resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.6.2.tgz#9b431b561a83f2bdfe041e1cab8a6becdb01af9c" @@ -6940,6 +7461,32 @@ jest-snapshot@^29.6.2: pretty-format "^29.6.2" semver "^7.5.3" +jest-snapshot@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.7.0.tgz#c2c574c3f51865da1bb329036778a69bf88a6be5" + integrity sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw== + dependencies: + "@babel/core" "^7.11.6" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-jsx" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/types" "^7.3.3" + "@jest/expect-utils" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^29.7.0" + graceful-fs "^4.2.9" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + natural-compare "^1.4.0" + pretty-format "^29.7.0" + semver "^7.5.3" + jest-util@^29.0.0, jest-util@^29.6.2: version "29.6.2" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.6.2.tgz#8a052df8fff2eebe446769fd88814521a517664d" @@ -6952,6 +7499,18 @@ jest-util@^29.0.0, jest-util@^29.6.2: graceful-fs "^4.2.9" picomatch "^2.2.3" +jest-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" + integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + jest-validate@^29.6.2: version "29.6.2" resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.6.2.tgz#25d972af35b2415b83b1373baf1a47bb266c1082" @@ -6964,6 +7523,18 @@ jest-validate@^29.6.2: leven "^3.1.0" pretty-format "^29.6.2" +jest-validate@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.7.0.tgz#7bf705511c64da591d46b15fce41400d52147d9c" + integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw== + dependencies: + "@jest/types" "^29.6.3" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^29.6.3" + leven "^3.1.0" + pretty-format "^29.7.0" + jest-watcher@^29.6.2: version "29.6.2" resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.6.2.tgz#77c224674f0620d9f6643c4cfca186d8893ca088" @@ -6978,6 +7549,20 @@ jest-watcher@^29.6.2: jest-util "^29.6.2" string-length "^4.0.1" +jest-watcher@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.7.0.tgz#7810d30d619c3a62093223ce6bb359ca1b28a2f2" + integrity sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g== + dependencies: + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + emittery "^0.13.1" + jest-util "^29.7.0" + string-length "^4.0.1" + jest-websocket-mock@^2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/jest-websocket-mock/-/jest-websocket-mock-2.4.1.tgz#e8ed6001cbeff7739b9ee4a74f5937ec1c339408" @@ -7005,6 +7590,16 @@ jest-worker@^29.4.3, jest-worker@^29.5.0, jest-worker@^29.6.2: merge-stream "^2.0.0" supports-color "^8.0.0" +jest-worker@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" + integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== + dependencies: + "@types/node" "*" + jest-util "^29.7.0" + merge-stream "^2.0.0" + supports-color "^8.0.0" + jest@^29.6.2: version "29.6.2" resolved "https://registry.yarnpkg.com/jest/-/jest-29.6.2.tgz#3bd55b9fd46a161b2edbdf5f1d1bd0d1eab76c42" @@ -7015,6 +7610,16 @@ jest@^29.6.2: import-local "^3.0.2" jest-cli "^29.6.2" +jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-29.7.0.tgz#994676fc24177f088f1c5e3737f5697204ff2613" + integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== + dependencies: + "@jest/core" "^29.7.0" + "@jest/types" "^29.6.3" + import-local "^3.0.2" + jest-cli "^29.7.0" + joi@^17.6.4: version "17.9.2" resolved "https://registry.yarnpkg.com/joi/-/joi-17.9.2.tgz#8b2e4724188369f55451aebd1d0b1d9482470690" @@ -9359,6 +9964,15 @@ pretty-format@^29.0.0, pretty-format@^29.6.2: ansi-styles "^5.0.0" react-is "^18.0.0" +pretty-format@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== + dependencies: + "@jest/schemas" "^29.6.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + private@^0.1.8, private@~0.1.5: version "0.1.8" resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" @@ -9512,7 +10126,7 @@ q@^1.5.1: resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" integrity sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw== -qs@^6.11.2, qs@^6.9.4: +qs@^6.11.2: version "6.11.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.2.tgz#64bea51f12c1f5da1bc01496f48ffcff7c69d7d9" integrity sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA== @@ -11328,11 +11942,6 @@ tunnel-agent@^0.6.0: dependencies: safe-buffer "^5.0.1" -tunnel@0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c" - integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg== - tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"