diff --git a/backstage-plugin/plugins/open-dora/package.json b/backstage-plugin/plugins/open-dora/package.json index b1d1f87..7e3944f 100644 --- a/backstage-plugin/plugins/open-dora/package.json +++ b/backstage-plugin/plugins/open-dora/package.json @@ -49,7 +49,8 @@ "@testing-library/user-event": "^14.0.0", "@types/node": "*", "cross-fetch": "^3.1.5", - "msw": "^1.0.0" + "msw": "^2.0.6", + "undici": "^5.27.2" }, "files": [ "dist", @@ -64,6 +65,14 @@ "lines": 100, "statements": 100 } - } + }, + "testEnvironmentOptions": { + "customExportConditions": [ + "" + ] + }, + "setupFiles": [ + "./jest.polyfills.js" + ] } } diff --git a/backstage-plugin/plugins/open-dora/src/components/DashboardComponent/DashboardComponent.test.tsx b/backstage-plugin/plugins/open-dora/src/components/DashboardComponent/DashboardComponent.test.tsx index 96f5776..d54af70 100644 --- a/backstage-plugin/plugins/open-dora/src/components/DashboardComponent/DashboardComponent.test.tsx +++ b/backstage-plugin/plugins/open-dora/src/components/DashboardComponent/DashboardComponent.test.tsx @@ -1,38 +1,32 @@ +import type { EntityRelation } from '@backstage/catalog-model'; +import { ApiProvider } from '@backstage/core-app-api'; +import { EntityProvider } from '@backstage/plugin-catalog-react'; +import { + MockConfigApi, + renderInTestApp, + TestApiRegistry, +} from '@backstage/test-utils'; +import { act, fireEvent, screen } from '@testing-library/react'; +import { delay, http, HttpResponse } from 'msw'; import React from 'react'; +import { + GroupDataService, + groupDataServiceApiRef, +} from '../../services/GroupDataService'; +import { server } from '../../setupTests'; import { DashboardComponent, EntityDashboardComponent, } from './DashboardComponent'; -import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils'; -import { groupDataServiceApiRef } from '../../services/GroupDataService'; -import { ApiProvider } from '@backstage/core-app-api'; -import { fireEvent, screen, act } from '@testing-library/react'; -import { MetricData } from '../../models/MetricData'; -import { EntityProvider } from '@backstage/plugin-catalog-react'; -import type { EntityRelation } from '@backstage/catalog-model'; -async function renderComponentWithApis( - component: JSX.Element, - mockData?: jest.Mock, -) { - const groupDataApiMock = { - retrieveMetricDataPoints: - mockData ?? - jest - .fn() - .mockResolvedValueOnce({ - aggregation: 'weekly', - dataPoints: [{ key: '10/23', value: 1.0 }], - }) - .mockResolvedValueOnce({ - aggregation: 'weekly', - dataPoints: [{ key: '11/23', value: 2.0 }], - }), - }; +async function renderComponentWithApis(component: JSX.Element) { + const mockConfig = new MockConfigApi({ + 'open-dora': { apiBaseUrl: 'http://localhost:10666' }, + }); const apiRegistry = TestApiRegistry.from([ groupDataServiceApiRef, - groupDataApiMock, + new GroupDataService({ configApi: mockConfig }), ]); return await renderInTestApp( @@ -41,8 +35,8 @@ async function renderComponentWithApis( } describe('DashboardComponent', () => { - function renderDashboardComponent(mockData?: jest.Mock) { - return renderComponentWithApis(, mockData); + function renderDashboardComponent() { + return renderComponentWithApis(); } it('should show a dropdown with the aggregation choices', async () => { @@ -62,172 +56,143 @@ describe('DashboardComponent', () => { expect(queryByText('OpenDORA (by Devoteam)')).not.toBeNull(); }); - it('should show a graph for deployment frequency data', async () => { - const { queryByText } = await renderDashboardComponent( - jest - .fn() - .mockResolvedValueOnce({ - aggregation: 'weekly', - dataPoints: [ - { key: 'count_first_key', value: 1.0 }, - { key: 'count_second_key', value: 1.0 }, - { key: 'count_third_key', value: 1.0 }, - ], - }) - .mockResolvedValueOnce({ + it('should show graphs for deployment frequency data', async () => { + server.use( + http.get('http://localhost:10666/dora/api/metric', ({ request }) => { + const type = new URL(request.url).searchParams.get('type'); + return HttpResponse.json({ aggregation: 'weekly', dataPoints: [ - { key: 'average_first_key', value: 2.0 }, - { key: 'average_second_key', value: 2.0 }, - { key: 'average_third_key', value: 2.0 }, + { key: `${type}_first_key`, value: 1.0 }, + { key: `${type}_second_key`, value: 1.0 }, + { key: `${type}_third_key`, value: 1.0 }, ], - }), + }); + }), ); + const { queryByText } = await renderDashboardComponent(); expect(queryByText('Deployment Frequency')).not.toBeNull(); - expect(queryByText('count_first_key')).not.toBeNull(); - expect(queryByText('count_second_key')).not.toBeNull(); - expect(queryByText('count_third_key')).not.toBeNull(); + expect(queryByText('df_count_first_key')).not.toBeNull(); + expect(queryByText('df_count_second_key')).not.toBeNull(); + expect(queryByText('df_count_third_key')).not.toBeNull(); expect(queryByText('Deployment Frequency Average')).not.toBeNull(); - expect(queryByText('average_first_key')).not.toBeNull(); - expect(queryByText('average_second_key')).not.toBeNull(); - expect(queryByText('average_third_key')).not.toBeNull(); + expect(queryByText('df_average_first_key')).not.toBeNull(); + expect(queryByText('df_average_second_key')).not.toBeNull(); + expect(queryByText('df_average_third_key')).not.toBeNull(); }); it('should retrieve new data when the aggregation is changed', async () => { - const apiMock = jest - .fn() - .mockResolvedValueOnce({ - aggregation: 'weekly', - dataPoints: [ - { key: 'count_first_key', value: 1.0 }, - { key: 'count_second_key', value: 1.0 }, - { key: 'count_third_key', value: 1.0 }, - ], - }) - .mockResolvedValueOnce({ - aggregation: 'weekly', - dataPoints: [ - { key: 'average_first_key', value: 2.0 }, - { key: 'average_second_key', value: 2.0 }, - { key: 'average_third_key', value: 2.0 }, - ], - }) - .mockResolvedValueOnce({ - aggregation: 'monthly', - dataPoints: [ - { key: 'count_new_first_key', value: 1.0 }, - { key: 'count_new_second_key', value: 1.0 }, - { key: 'count_new_third_key', value: 1.0 }, - ], - }) - .mockResolvedValueOnce({ - aggregation: 'monthly', - dataPoints: [ - { key: 'average_new_first_key', value: 2.0 }, - { key: 'average_new_second_key', value: 2.0 }, - { key: 'average_new_third_key', value: 2.0 }, - ], - }); - const { queryByText, getByText } = await renderDashboardComponent(apiMock); - - expect(apiMock).toHaveBeenCalledTimes(2); - expect(apiMock).toHaveBeenCalledWith({ - type: 'df_count', - aggregation: 'weekly', - }); - expect(apiMock).toHaveBeenLastCalledWith({ - type: 'df_average', - aggregation: 'weekly', - }); + server.use( + http.get('http://localhost:10666/dora/api/metric', ({ request }) => { + const params = new URL(request.url).searchParams; + const type = params.get('type'); + const aggregation = params.get('aggregation'); + + return HttpResponse.json({ + aggregation: aggregation, + dataPoints: [{ key: `${aggregation}_${type}_first_key`, value: 1.0 }], + }); + }), + ); + const { queryByText, getByText } = await renderDashboardComponent(); - expect(queryByText('count_first_key')).not.toBeNull(); - expect(queryByText('count_second_key')).not.toBeNull(); - expect(queryByText('count_third_key')).not.toBeNull(); + expect(queryByText('weekly_df_count_first_key')).not.toBeNull(); + expect(queryByText('weekly_df_average_first_key')).not.toBeNull(); + + expect(queryByText('monthly_df_count_first_key')).toBeNull(); + expect(queryByText('monthly_df_average_first_key')).toBeNull(); fireEvent.mouseDown(getByText('Weekly')); await act(async () => { fireEvent.click(screen.getByText('Monthly')); }); - expect(apiMock).toHaveBeenCalledTimes(4); - expect(apiMock).toHaveBeenCalledWith({ - type: 'df_count', - aggregation: 'monthly', - }); - expect(apiMock).toHaveBeenLastCalledWith({ - type: 'df_average', - aggregation: 'monthly', - }); - - expect(queryByText('count_first_key')).toBeNull(); - expect(queryByText('count_second_key')).toBeNull(); - expect(queryByText('count_third_key')).toBeNull(); - expect(queryByText('count_new_first_key')).not.toBeNull(); - expect(queryByText('count_new_second_key')).not.toBeNull(); - expect(queryByText('count_new_third_key')).not.toBeNull(); + expect(queryByText('weekly_df_count_first_key')).toBeNull(); + expect(queryByText('weekly_df_average_first_key')).toBeNull(); - expect(queryByText('average_first_key')).toBeNull(); - expect(queryByText('average_second_key')).toBeNull(); - expect(queryByText('average_third_key')).toBeNull(); - - expect(queryByText('average_new_first_key')).not.toBeNull(); - expect(queryByText('average_new_second_key')).not.toBeNull(); - expect(queryByText('average_new_third_key')).not.toBeNull(); + expect(queryByText('monthly_df_count_first_key')).not.toBeNull(); + expect(queryByText('monthly_df_average_first_key')).not.toBeNull(); }); it('should show loading indicator when waiting on data to return', async () => { - jest.useFakeTimers(); - - const apiMock = jest - .fn() - .mockImplementationOnce(() => { - return new Promise(resolve => { - resolve({ - aggregation: 'monthly', - dataPoints: [{ key: 'count_data_key', value: 1.0 }], - }); - }); - }) - .mockImplementationOnce(() => { - return new Promise(resolve => { - setTimeout(() => { - resolve({ - aggregation: 'monthly', - dataPoints: [{ key: 'average_data_key', value: 1.0 }], - }); - }, 1000); + jest.useFakeTimers({ + legacyFakeTimers: true, + }); + + server.use( + http.get('http://localhost:10666/dora/api/metric', async () => { + await delay(10000); + + return HttpResponse.json({ + aggregation: 'weekly', + dataPoints: [{ key: `first_key`, value: 1.0 }], }); - }); - const { queryByText, queryByRole, findByRole } = - await renderDashboardComponent(apiMock); + }), + ); - expect(await findByRole('progressbar')).not.toBeNull(); - expect(queryByText('average_data_key')).toBeNull(); + const { queryByText, queryByRole, findAllByRole, queryAllByText } = + await renderDashboardComponent(); + + expect(await findAllByRole('progressbar')).toHaveLength(2); + expect(await queryByText('first_key')).toBeNull(); await act(async () => { jest.runAllTimers(); }); expect(queryByRole('progressbar')).toBeNull(); - expect(queryByText('average_data_key')).not.toBeNull(); + expect(queryAllByText('first_key')).toHaveLength(2); }); it('should show the error returned from the service', async () => { - const { queryAllByText } = await renderDashboardComponent( - jest.fn().mockRejectedValue({ status: 500, message: 'server error' }), + server.use( + http.get('http://localhost:10666/dora/api/metric', () => { + return new HttpResponse(null, { status: 401 }); + }), + ); + const { queryAllByText, getByText } = await renderDashboardComponent(); + expect(queryAllByText('Error: Unauthorized')).toHaveLength(2); + + server.use( + http.get('http://localhost:10666/dora/api/metric', () => { + return HttpResponse.error(); + }), ); - expect(queryAllByText('server error')).not.toBeNull(); - expect(queryAllByText('server error')).toHaveLength(2); + + // Trigger another request + fireEvent.mouseDown(getByText('Weekly')); + await act(async () => { + fireEvent.click(screen.getByText('Monthly')); + }); + + expect(queryAllByText('Error: Failed to fetch')).toHaveLength(2); }); }); describe('EntityDashboardComponent', () => { - function renderEntityDashboardComponent( - mockData?: jest.Mock, - relations?: EntityRelation[], - ) { + function renderEntityDashboardComponent(relations?: EntityRelation[]) { + server.use( + http.get('http://localhost:10666/dora/api/metric', ({ request }) => { + const params = new URL(request.url).searchParams; + const type = params.get('type'); + const aggregation = params.get('aggregation'); + const project = params.get('project'); + const team = params.get('team'); + + return HttpResponse.json({ + aggregation: aggregation, + dataPoints: [ + { + key: `${project}_${team}_${aggregation}_${type}_first_key`, + value: 1.0, + }, + ], + }); + }), + ); + return renderComponentWithApis( { > , - mockData, ); } it('should send component info to the service from the context', async () => { - const apiMock = jest.fn().mockResolvedValue({ - aggregation: 'weekly', - dataPoints: [{ key: 'first_key', value: 1.0 }], - }); - - await renderEntityDashboardComponent(apiMock, [ + const { queryByText } = await renderEntityDashboardComponent([ { targetRef: 'kind:namespace/owner-name', type: 'ownedBy' }, ]); - expect(apiMock).toHaveBeenCalledTimes(2); - - expect(apiMock).toHaveBeenLastCalledWith({ - type: 'df_average', - aggregation: 'weekly', - project: 'entity-name', - team: 'owner-name', - }); + expect( + queryByText('entity-name_owner-name_weekly_df_average_first_key'), + ).not.toBeNull(); }); it('should send component info without owner info', async () => { - const apiMock = jest.fn().mockResolvedValue({ - aggregation: 'weekly', - dataPoints: [{ key: 'first_key', value: 1.0 }], - }); - - await renderEntityDashboardComponent(apiMock); + const { queryByText } = await renderEntityDashboardComponent(); - expect(apiMock).toHaveBeenCalledTimes(2); - expect(apiMock).toHaveBeenCalledWith({ - type: 'df_count', - aggregation: 'weekly', - project: 'entity-name', - }); - - expect(apiMock).toHaveBeenLastCalledWith({ - type: 'df_average', - aggregation: 'weekly', - project: 'entity-name', - }); + expect( + queryByText('entity-name_null_weekly_df_average_first_key'), + ).not.toBeNull(); }); }); diff --git a/backstage-plugin/plugins/open-dora/src/jest.polyfills.js b/backstage-plugin/plugins/open-dora/src/jest.polyfills.js new file mode 100644 index 0000000..9598a44 --- /dev/null +++ b/backstage-plugin/plugins/open-dora/src/jest.polyfills.js @@ -0,0 +1,30 @@ +// jest.polyfills.js +/** + * @note The block below contains polyfills for Node.js globals + * required for Jest to function when running JSDOM tests. + * These HAVE to be require's and HAVE to be in this exact + * order, since "undici" depends on the "TextEncoder" global API. + * + * Consider migrating to a more modern test runner if + * you don't want to deal with this. + */ + +const { TextDecoder, TextEncoder } = require('node:util'); + +Object.defineProperties(globalThis, { + TextDecoder: { value: TextDecoder }, + TextEncoder: { value: TextEncoder }, +}); + +const { Blob, File } = require('node:buffer'); +const { fetch, Headers, FormData, Request, Response } = require('undici'); + +Object.defineProperties(globalThis, { + fetch: { value: fetch, writable: true }, + Blob: { value: Blob }, + File: { value: File }, + Headers: { value: Headers }, + FormData: { value: FormData }, + Request: { value: Request }, + Response: { value: Response }, +}); diff --git a/backstage-plugin/plugins/open-dora/src/mswHandlers.ts b/backstage-plugin/plugins/open-dora/src/mswHandlers.ts new file mode 100644 index 0000000..0847419 --- /dev/null +++ b/backstage-plugin/plugins/open-dora/src/mswHandlers.ts @@ -0,0 +1,10 @@ +import { http, HttpResponse } from 'msw'; + +export const handlers = [ + http.get('http://localhost:10666/dora/api/metric', () => { + return HttpResponse.json({ + aggregation: 'weekly', + dataPoints: [{ key: '10/23', value: 1.0 }], + }); + }), +]; diff --git a/backstage-plugin/plugins/open-dora/src/services/GroupDataService.ts b/backstage-plugin/plugins/open-dora/src/services/GroupDataService.ts index cdef898..d680c72 100644 --- a/backstage-plugin/plugins/open-dora/src/services/GroupDataService.ts +++ b/backstage-plugin/plugins/open-dora/src/services/GroupDataService.ts @@ -17,6 +17,7 @@ export class GroupDataService { aggregation?: string; }) { const baseUrl = this.options.configApi.getString('open-dora.apiBaseUrl'); + const url = new URL(baseUrl); url.pathname = 'dora/api/metric'; for (const [key, value] of Object.entries(params)) { @@ -27,6 +28,19 @@ export class GroupDataService { const data = await fetch(url.toString(), { method: 'GET', }); - return (await data.json()) as MetricData; + + if (!data.ok) { + throw new Error(data.statusText); + } + + const response = await data.json(); + if ( + response.aggregation === undefined || + response.dataPoints === undefined + ) { + throw new Error('Unexpected response'); + } + + return response as MetricData; } } diff --git a/backstage-plugin/plugins/open-dora/src/setupTests.ts b/backstage-plugin/plugins/open-dora/src/setupTests.ts index 48c09b5..2ac8f20 100644 --- a/backstage-plugin/plugins/open-dora/src/setupTests.ts +++ b/backstage-plugin/plugins/open-dora/src/setupTests.ts @@ -1,2 +1,10 @@ import '@testing-library/jest-dom'; import 'cross-fetch/polyfill'; +import { setupServer } from 'msw/node'; +import { handlers } from './mswHandlers'; + +export const server = setupServer(...handlers); + +beforeAll(() => server.listen()); +afterEach(() => server.resetHandlers()); +afterAll(() => server.close()); diff --git a/backstage-plugin/yarn.lock b/backstage-plugin/yarn.lock index 4416a39..6847da0 100644 --- a/backstage-plugin/yarn.lock +++ b/backstage-plugin/yarn.lock @@ -1641,6 +1641,27 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@bundled-es-modules/cookie@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@bundled-es-modules/cookie/-/cookie-2.0.0.tgz#c3b82703969a61cf6a46e959a012b2c257f6b164" + integrity sha512-Or6YHg/kamKHpxULAdSqhGqnWFneIXu1NKvvfBBzKGwpVsYuFIQ5aBPHDnnoR3ghW1nvSkALd+EF9iMtY7Vjxw== + dependencies: + cookie "^0.5.0" + +"@bundled-es-modules/js-levenshtein@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@bundled-es-modules/js-levenshtein/-/js-levenshtein-2.0.1.tgz#b02bbbd546358ab77080a430f0911cfc2b3779c4" + integrity sha512-DERMS3yfbAljKsQc0U2wcqGKUWpdFjwqWuoMugEJlqBnKO180/n+4SR/J8MRDt1AN48X1ovgoD9KrdVXcaa3Rg== + dependencies: + js-levenshtein "^1.1.6" + +"@bundled-es-modules/statuses@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@bundled-es-modules/statuses/-/statuses-1.0.1.tgz#761d10f44e51a94902c4da48675b71a76cc98872" + integrity sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg== + dependencies: + statuses "^2.0.1" + "@changesets/types@^4.0.1": version "4.1.0" resolved "https://registry.yarnpkg.com/@changesets/types/-/types-4.1.0.tgz#fb8f7ca2324fd54954824e864f9a61a82cb78fe0" @@ -2286,6 +2307,11 @@ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.37.0.tgz#cf1b5fa24217fe007f6487a26d765274925efa7d" integrity sha512-x5vzdtOOGgFVDCUs81QRB2+liax8rFg3+7hqM+QhBG0/G3F1ZsoYl97UrqgHgQ9KKT7G6c4V+aTUCgu/n22v1A== +"@fastify/busboy@^2.0.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.0.tgz#0709e9f4cb252351c609c6e6d8d6779a8d25edff" + integrity sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA== + "@floating-ui/core@^1.4.1": version "1.4.1" resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.4.1.tgz#0d633f4b76052668afb932492ac452f7ebe97f17" @@ -3450,27 +3476,22 @@ prop-types "^15.7.2" react-is "^16.8.0 || ^17.0.0" -"@mswjs/cookies@^0.2.2": - version "0.2.2" - resolved "https://registry.yarnpkg.com/@mswjs/cookies/-/cookies-0.2.2.tgz#b4e207bf6989e5d5427539c2443380a33ebb922b" - integrity sha512-mlN83YSrcFgk7Dm1Mys40DLssI1KdJji2CMKN8eOlBqsTADYzj2+jWzsANsUTFbxDMWPD5e9bfA1RGqBpS3O1g== - dependencies: - "@types/set-cookie-parser" "^2.4.0" - set-cookie-parser "^2.4.6" +"@mswjs/cookies@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@mswjs/cookies/-/cookies-1.1.0.tgz#1528eb43630caf83a1d75d5332b30e75e9bb1b5b" + integrity sha512-0ZcCVQxifZmhwNBoQIrystCb+2sWBY2Zw8lpfJBPCHGCA/HWqehITeCRVIv4VMy8MPlaHo2w2pTHFV2pFfqKPw== -"@mswjs/interceptors@^0.17.5": - version "0.17.9" - resolved "https://registry.yarnpkg.com/@mswjs/interceptors/-/interceptors-0.17.9.tgz#0096fc88fea63ee42e36836acae8f4ae33651c04" - integrity sha512-4LVGt03RobMH/7ZrbHqRxQrS9cc2uh+iNKSj8UWr8M26A2i793ju+csaB5zaqYltqJmA2jUq4VeYfKmVqvsXQg== +"@mswjs/interceptors@^0.25.11": + version "0.25.11" + resolved "https://registry.yarnpkg.com/@mswjs/interceptors/-/interceptors-0.25.11.tgz#65014cd0951cc4c6916f52d80bdfefe439a62b21" + integrity sha512-27aonWAjdeoZN4j4j6QvePOSOacQUucFRUESAU8FUXsmmagDjmyOi4/NHizxzY/NHSk/HAyqF/IMhl+9puhqNw== dependencies: - "@open-draft/until" "^1.0.3" - "@types/debug" "^4.1.7" - "@xmldom/xmldom" "^0.8.3" - debug "^4.3.3" - headers-polyfill "^3.1.0" + "@open-draft/deferred-promise" "^2.2.0" + "@open-draft/logger" "^0.3.0" + "@open-draft/until" "^2.0.0" + is-node-process "^1.2.0" outvariant "^1.2.1" - strict-event-emitter "^0.2.4" - web-encoding "^1.1.5" + strict-event-emitter "^0.5.1" "@mui/base@5.0.0-beta.14": version "5.0.0-beta.14" @@ -3997,10 +4018,23 @@ dependencies: "@octokit/openapi-types" "^16.0.0" -"@open-draft/until@^1.0.3": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@open-draft/until/-/until-1.0.3.tgz#db9cc719191a62e7d9200f6e7bab21c5b848adca" - integrity sha512-Aq58f5HiWdyDlFffbbSjAlv596h/cOnt2DO1w3DOC7OJ5EHs0hd/nycJfiu9RJbT6Yk6F1knnRRXNSpxoIVZ9Q== +"@open-draft/deferred-promise@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz#4a822d10f6f0e316be4d67b4d4f8c9a124b073bd" + integrity sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA== + +"@open-draft/logger@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@open-draft/logger/-/logger-0.3.0.tgz#2b3ab1242b360aa0adb28b85f5d7da1c133a0954" + integrity sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ== + dependencies: + is-node-process "^1.2.0" + outvariant "^1.4.0" + +"@open-draft/until@^2.0.0", "@open-draft/until@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@open-draft/until/-/until-2.1.0.tgz#0acf32f470af2ceaf47f095cdecd40d68666efda" + integrity sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg== "@pmmmwh/react-refresh-webpack-plugin@^0.5.7": version "0.5.10" @@ -4528,7 +4562,7 @@ resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.1.tgz#bfd02c1f2224567676c1545199f87c3a861d878d" integrity sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q== -"@types/debug@^4.0.0", "@types/debug@^4.1.7": +"@types/debug@^4.0.0": version "4.1.7" resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.7.tgz#7cc0ea761509124709b8b2d1090d8f6c17aadb82" integrity sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg== @@ -4764,7 +4798,7 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== -"@types/react-dom@<18.0.0", "@types/react-dom@^17": +"@types/react-dom@<18.0.0": version "17.0.20" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.20.tgz#e0c8901469d732b36d8473b40b679ad899da1b53" integrity sha512-4pzIjSxDueZZ90F52mU3aPoogkHIoSIDG+oQ+wQK7Cy2B9S+MvOqY0uEA/qawKz381qrEDkvpwyt8Bm31I8sbA== @@ -4867,13 +4901,6 @@ "@types/mime" "*" "@types/node" "*" -"@types/set-cookie-parser@^2.4.0": - version "2.4.2" - resolved "https://registry.yarnpkg.com/@types/set-cookie-parser/-/set-cookie-parser-2.4.2.tgz#b6a955219b54151bfebd4521170723df5e13caad" - integrity sha512-fBZgytwhYAUkj/jC/FAV4RQ5EerRup1YQsXQCh8rZfiHkc4UahC192oH0smGwsXol3cL3A5oETuAHeQHmhXM4w== - dependencies: - "@types/node" "*" - "@types/sockjs@^0.3.33": version "0.3.33" resolved "https://registry.yarnpkg.com/@types/sockjs/-/sockjs-0.3.33.tgz#570d3a0b99ac995360e3136fd6045113b1bd236f" @@ -4886,6 +4913,11 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== +"@types/statuses@^2.0.1": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@types/statuses/-/statuses-2.0.4.tgz#041143ba4a918e8f080f8b0ffbe3d4cb514e2315" + integrity sha512-eqNDvZsCNY49OAXB0Firg/Sc2BgoWsntsLUdybGFOhAfCD6QJ2n9HXUIHGqt5qjrxmMv4wS8WLAw43ZkKcJ8Pw== + "@types/styled-jsx@^2.2.8": version "2.2.9" resolved "https://registry.yarnpkg.com/@types/styled-jsx/-/styled-jsx-2.2.9.tgz#e50b3f868c055bcbf9bc353eca6c10fdad32a53f" @@ -5236,11 +5268,6 @@ "@webassemblyjs/ast" "1.11.1" "@xtuc/long" "4.2.2" -"@xmldom/xmldom@^0.8.3": - version "0.8.6" - resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.8.6.tgz#8a1524eb5bd5e965c1e3735476f0262469f71440" - integrity sha512-uRjjusqpoqfmRkTaNuLJ2VohVr67Q5YwDATW3VU7PfzTj6IRaihGrYI7zckGZjxQPBIp63nfvJbM+Yu5ICh0Bg== - "@xobotyi/scrollbar-width@^1.9.5": version "1.9.5" resolved "https://registry.yarnpkg.com/@xobotyi/scrollbar-width/-/scrollbar-width-1.9.5.tgz#80224a6919272f405b87913ca13b92929bdf3c4d" @@ -5269,11 +5296,6 @@ js-yaml "^3.10.0" tslib "^2.4.0" -"@zxing/text-encoding@0.9.0": - version "0.9.0" - resolved "https://registry.yarnpkg.com/@zxing/text-encoding/-/text-encoding-0.9.0.tgz#fb50ffabc6c7c66a0c96b4c03e3d9be74864b70b" - integrity sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA== - JSONStream@^1.0.4: version "1.3.5" resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" @@ -6202,14 +6224,6 @@ chalk@2.4.2, chalk@^2.0.0: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad" - integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - chalk@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" @@ -6731,16 +6745,11 @@ cookie-signature@1.0.6: resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== -cookie@0.5.0: +cookie@0.5.0, cookie@^0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== -cookie@^0.4.2: - version "0.4.2" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" - integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== - copy-to-clipboard@^3.3.1: version "3.3.3" resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz#55ac43a1db8ae639a4bd99511c148cdd1b83a1b0" @@ -8181,7 +8190,7 @@ eventemitter3@^4.0.0, eventemitter3@^4.0.4: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== -events@^3.0.0, events@^3.2.0, events@^3.3.0: +events@^3.0.0, events@^3.2.0: version "3.3.0" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== @@ -8935,11 +8944,16 @@ graphql-tag@^2.10.3: dependencies: tslib "^2.1.0" -"graphql@^15.0.0 || ^16.0.0", graphql@^16.0.0: +graphql@^16.0.0: version "16.6.0" resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.6.0.tgz#c2dcffa4649db149f6282af726c8c83f1c7c5fdb" integrity sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw== +graphql@^16.8.1: + version "16.8.1" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.8.1.tgz#1930a965bef1170603702acdb68aedd3f3cf6f07" + integrity sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw== + gzip-size@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462" @@ -9081,10 +9095,10 @@ he@^1.2.0: resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== -headers-polyfill@^3.1.0, headers-polyfill@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/headers-polyfill/-/headers-polyfill-3.1.2.tgz#9a4dcb545c5b95d9569592ef7ec0708aab763fbe" - integrity sha512-tWCK4biJ6hcLqTviLXVR9DTRfYGQMXEIUj3gwJ2rZ5wO/at3XtkI4g8mCvFdUF9l1KMBNCfmNAdnahm1cgavQA== +headers-polyfill@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/headers-polyfill/-/headers-polyfill-4.0.2.tgz#9115a76eee3ce8fbf95b6e3c6bf82d936785b44a" + integrity sha512-EWGTfnTqAO2L/j5HZgoM/3z82L7necsJ0pO9Tp0X1wil3PDLrkypTBRgVO2ExehEEvUycejZD3FuRaXpZZc3kw== highlight.js@^10.4.1, highlight.js@~10.7.0: version "10.7.3" @@ -11832,28 +11846,31 @@ ms@2.1.3, ms@^2.0.0, ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -msw@^1.0.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/msw/-/msw-1.2.1.tgz#9dd347583eeba5e5c7f33b54be5600a899dc61bd" - integrity sha512-bF7qWJQSmKn6bwGYVPXOxhexTCGD5oJSZg8yt8IBClxvo3Dx/1W0zqE1nX9BSWmzRsCKWfeGWcB/vpqV6aclpw== - dependencies: - "@mswjs/cookies" "^0.2.2" - "@mswjs/interceptors" "^0.17.5" - "@open-draft/until" "^1.0.3" +msw@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/msw/-/msw-2.0.6.tgz#635ddfc0908c481f10a5b0c696c3732bc693d615" + integrity sha512-74nN9ADQxHsVx8YGOq5yeDE4bye1mVMAYXojKl0qWraRunTK8UjAVk3wSmOOvVVIDJsyySyjDZ4oYvRuzuMO2g== + dependencies: + "@bundled-es-modules/cookie" "^2.0.0" + "@bundled-es-modules/js-levenshtein" "^2.0.1" + "@bundled-es-modules/statuses" "^1.0.1" + "@mswjs/cookies" "^1.1.0" + "@mswjs/interceptors" "^0.25.11" + "@open-draft/until" "^2.1.0" "@types/cookie" "^0.4.1" "@types/js-levenshtein" "^1.1.1" - chalk "4.1.1" + "@types/statuses" "^2.0.1" + chalk "^4.1.2" chokidar "^3.4.2" - cookie "^0.4.2" - graphql "^15.0.0 || ^16.0.0" - headers-polyfill "^3.1.2" + graphql "^16.8.1" + headers-polyfill "^4.0.1" inquirer "^8.2.0" is-node-process "^1.2.0" js-levenshtein "^1.1.6" node-fetch "^2.6.7" outvariant "^1.4.0" path-to-regexp "^6.2.0" - strict-event-emitter "^0.4.3" + strict-event-emitter "^0.5.0" type-fest "^2.19.0" yargs "^17.3.1" @@ -14359,11 +14376,6 @@ set-blocking@^2.0.0, set-blocking@~2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== -set-cookie-parser@^2.4.6: - version "2.6.0" - resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz#131921e50f62ff1a66a461d7d62d7b21d5d15a51" - integrity sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ== - set-harmonic-interval@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/set-harmonic-interval/-/set-harmonic-interval-1.0.1.tgz#e1773705539cdfb80ce1c3d99e7f298bb3995249" @@ -14722,7 +14734,7 @@ stacktrace-js@^2.0.2: stack-generator "^2.0.5" stacktrace-gps "^3.0.4" -statuses@2.0.1: +statuses@2.0.1, statuses@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== @@ -14763,17 +14775,10 @@ stream-http@^2.7.2: to-arraybuffer "^1.0.0" xtend "^4.0.0" -strict-event-emitter@^0.2.4: - version "0.2.8" - resolved "https://registry.yarnpkg.com/strict-event-emitter/-/strict-event-emitter-0.2.8.tgz#b4e768927c67273c14c13d20e19d5e6c934b47ca" - integrity sha512-KDf/ujU8Zud3YaLtMCcTI4xkZlZVIYxTLr+XIULexP+77EEVWixeXroLUXQXiVtH4XH2W7jr/3PT1v3zBuvc3A== - dependencies: - events "^3.3.0" - -strict-event-emitter@^0.4.3: - version "0.4.6" - resolved "https://registry.yarnpkg.com/strict-event-emitter/-/strict-event-emitter-0.4.6.tgz#ff347c8162b3e931e3ff5f02cfce6772c3b07eb3" - integrity sha512-12KWeb+wixJohmnwNFerbyiBrAlq5qJLwIt38etRtKtmmHyDSoGlIqFE9wx+4IwG0aDjI7GV8tc8ZccjWZZtTg== +strict-event-emitter@^0.5.0, strict-event-emitter@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz#1602ece81c51574ca39c6815e09f1a3e8550bd93" + integrity sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ== strict-uri-encode@^2.0.0: version "2.0.0" @@ -15504,6 +15509,13 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" +undici@^5.27.2: + version "5.27.2" + resolved "https://registry.yarnpkg.com/undici/-/undici-5.27.2.tgz#a270c563aea5b46cc0df2550523638c95c5d4411" + integrity sha512-iS857PdOEy/y3wlM3yRp+6SNQQ6xU0mmZcwRSriqk+et/cwWAtwmIGf6WkoDN2EK/AMdCO/dfXzIwi+rFMrjjQ== + dependencies: + "@fastify/busboy" "^2.0.0" + unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc" @@ -15889,15 +15901,6 @@ wcwidth@^1.0.0, wcwidth@^1.0.1: dependencies: defaults "^1.0.3" -web-encoding@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/web-encoding/-/web-encoding-1.1.5.tgz#fc810cf7667364a6335c939913f5051d3e0c4864" - integrity sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA== - dependencies: - util "^0.12.3" - optionalDependencies: - "@zxing/text-encoding" "0.9.0" - webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"