From 298e1f7645b26662bf92a8b6fa61eab4118e140c Mon Sep 17 00:00:00 2001 From: Yash Oswal Date: Fri, 16 Feb 2024 15:15:36 +0530 Subject: [PATCH] feat(report-portal): added report portal frontend and backend plugins Signed-off-by: Yash Oswal feat(report-portal): add global page for report portal plugin (#1) - added Instances Page - added Projects Page - added Launches Page - Updated Report Portal API - Added api pagination for table - fixed tsc and lint issues in backend plugin - added Readme files for both plugins - added report portal icon componet (#2) fix(report-portal): Fixed search for projects page (#3) - fix(report-portal): Fixed search for projects page - Added request to add button on instances page - Updated readme - fix(report-portal): yarn tsc issue --------- Signed-off-by: Yash Oswal --- plugins/report-portal-backend/.eslintrc.js | 1 + plugins/report-portal-backend/OWNERS | 8 + plugins/report-portal-backend/README.md | 75 +++++ .../app-config.janus-idp.yaml | 6 + plugins/report-portal-backend/config.d.ts | 28 ++ plugins/report-portal-backend/package.json | 82 +++++ plugins/report-portal-backend/src/alpha.ts | 1 + .../src/dynamic/index.ts | 11 + plugins/report-portal-backend/src/index.ts | 8 + plugins/report-portal-backend/src/plugin.ts | 33 ++ plugins/report-portal-backend/src/run.ts | 19 ++ .../src/service/router.test.ts | 28 ++ .../src/service/router.ts | 56 ++++ .../src/service/standaloneServer.ts | 40 +++ .../report-portal-backend/src/setupTests.ts | 1 + plugins/report-portal-backend/tsconfig.json | 9 + plugins/report-portal-backend/turbo.json | 9 + plugins/report-portal/.eslintrc.js | 1 + plugins/report-portal/OWNERS | 8 + plugins/report-portal/README.md | 96 ++++++ .../report-portal/app-config.janus-idp.yaml | 13 + plugins/report-portal/config.d.ts | 24 ++ plugins/report-portal/dev/index.tsx | 42 +++ plugins/report-portal/package.json | 77 +++++ .../report-portal/src/api/ReportPortalApi.ts | 29 ++ .../src/api/ReportPortalClient.ts | 70 ++++ plugins/report-portal/src/api/index.ts | 3 + plugins/report-portal/src/api/types.ts | 82 +++++ .../components/LaunchesPage/LaunchesPage.tsx | 69 ++++ .../LaunchesPageContent.tsx | 189 +++++++++++ .../src/components/LaunchesPage/index.ts | 1 + .../components/ProjectsPage/ProjectsPage.tsx | 65 ++++ .../ProjectsPageContent.tsx | 157 +++++++++ .../src/components/ProjectsPage/index.ts | 1 + .../GlobalPageContent/GlobalPageContent.tsx | 110 ++++++ .../GlobalPageContent/index.ts | 1 + .../ReportPortalGlobalPage.tsx | 64 ++++ .../ReportPortalGlobalPage/index.ts | 1 + .../ReportPortalIcon/ReportPortalIcon.tsx | 12 + .../src/components/ReportPortalIcon/index.ts | 1 + .../ReportPortalOverviewCard.tsx | 244 ++++++++++++++ .../ReportPortalOverviewCard/index.ts | 1 + .../report-portal/src/components/Router.tsx | 26 ++ plugins/report-portal/src/hooks/index.ts | 3 + .../src/hooks/useInstanceDetails.ts | 33 ++ .../src/hooks/useLaunchDetails.ts | 25 ++ .../src/hooks/useProjectDetails.ts | 27 ++ plugins/report-portal/src/index.ts | 8 + plugins/report-portal/src/mocks/entity.ts | 22 ++ plugins/report-portal/src/mocks/index.ts | 1 + plugins/report-portal/src/plugin.test.ts | 7 + plugins/report-portal/src/plugin.ts | 47 +++ plugins/report-portal/src/routes.ts | 22 ++ plugins/report-portal/src/setupTests.ts | 1 + .../src/utils/isReportPortalAvailable.ts | 8 + plugins/report-portal/tsconfig.json | 9 + plugins/report-portal/turbo.json | 9 + yarn.lock | 317 ++++++++++++++++-- 58 files changed, 2317 insertions(+), 24 deletions(-) create mode 100644 plugins/report-portal-backend/.eslintrc.js create mode 100644 plugins/report-portal-backend/OWNERS create mode 100644 plugins/report-portal-backend/README.md create mode 100644 plugins/report-portal-backend/app-config.janus-idp.yaml create mode 100644 plugins/report-portal-backend/config.d.ts create mode 100644 plugins/report-portal-backend/package.json create mode 100644 plugins/report-portal-backend/src/alpha.ts create mode 100644 plugins/report-portal-backend/src/dynamic/index.ts create mode 100644 plugins/report-portal-backend/src/index.ts create mode 100644 plugins/report-portal-backend/src/plugin.ts create mode 100644 plugins/report-portal-backend/src/run.ts create mode 100644 plugins/report-portal-backend/src/service/router.test.ts create mode 100644 plugins/report-portal-backend/src/service/router.ts create mode 100644 plugins/report-portal-backend/src/service/standaloneServer.ts create mode 100644 plugins/report-portal-backend/src/setupTests.ts create mode 100644 plugins/report-portal-backend/tsconfig.json create mode 100644 plugins/report-portal-backend/turbo.json create mode 100644 plugins/report-portal/.eslintrc.js create mode 100644 plugins/report-portal/OWNERS create mode 100644 plugins/report-portal/README.md create mode 100644 plugins/report-portal/app-config.janus-idp.yaml create mode 100644 plugins/report-portal/config.d.ts create mode 100644 plugins/report-portal/dev/index.tsx create mode 100644 plugins/report-portal/package.json create mode 100644 plugins/report-portal/src/api/ReportPortalApi.ts create mode 100644 plugins/report-portal/src/api/ReportPortalClient.ts create mode 100644 plugins/report-portal/src/api/index.ts create mode 100644 plugins/report-portal/src/api/types.ts create mode 100644 plugins/report-portal/src/components/LaunchesPage/LaunchesPage.tsx create mode 100644 plugins/report-portal/src/components/LaunchesPage/LaunchesPageContent/LaunchesPageContent.tsx create mode 100644 plugins/report-portal/src/components/LaunchesPage/index.ts create mode 100644 plugins/report-portal/src/components/ProjectsPage/ProjectsPage.tsx create mode 100644 plugins/report-portal/src/components/ProjectsPage/ProjectsPageContent/ProjectsPageContent.tsx create mode 100644 plugins/report-portal/src/components/ProjectsPage/index.ts create mode 100644 plugins/report-portal/src/components/ReportPortalGlobalPage/GlobalPageContent/GlobalPageContent.tsx create mode 100644 plugins/report-portal/src/components/ReportPortalGlobalPage/GlobalPageContent/index.ts create mode 100644 plugins/report-portal/src/components/ReportPortalGlobalPage/ReportPortalGlobalPage.tsx create mode 100644 plugins/report-portal/src/components/ReportPortalGlobalPage/index.ts create mode 100644 plugins/report-portal/src/components/ReportPortalIcon/ReportPortalIcon.tsx create mode 100644 plugins/report-portal/src/components/ReportPortalIcon/index.ts create mode 100644 plugins/report-portal/src/components/ReportPortalOverviewCard/ReportPortalOverviewCard.tsx create mode 100644 plugins/report-portal/src/components/ReportPortalOverviewCard/index.ts create mode 100644 plugins/report-portal/src/components/Router.tsx create mode 100644 plugins/report-portal/src/hooks/index.ts create mode 100644 plugins/report-portal/src/hooks/useInstanceDetails.ts create mode 100644 plugins/report-portal/src/hooks/useLaunchDetails.ts create mode 100644 plugins/report-portal/src/hooks/useProjectDetails.ts create mode 100644 plugins/report-portal/src/index.ts create mode 100644 plugins/report-portal/src/mocks/entity.ts create mode 100644 plugins/report-portal/src/mocks/index.ts create mode 100644 plugins/report-portal/src/plugin.test.ts create mode 100644 plugins/report-portal/src/plugin.ts create mode 100644 plugins/report-portal/src/routes.ts create mode 100644 plugins/report-portal/src/setupTests.ts create mode 100644 plugins/report-portal/src/utils/isReportPortalAvailable.ts create mode 100644 plugins/report-portal/tsconfig.json create mode 100644 plugins/report-portal/turbo.json diff --git a/plugins/report-portal-backend/.eslintrc.js b/plugins/report-portal-backend/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/report-portal-backend/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/report-portal-backend/OWNERS b/plugins/report-portal-backend/OWNERS new file mode 100644 index 0000000000..ac83ec4203 --- /dev/null +++ b/plugins/report-portal-backend/OWNERS @@ -0,0 +1,8 @@ +approvers: + - yashoswalyo + - riginoommen + - deshmukhmayur +reviewers: + - yashoswalyo + - riginoommen + - deshmukhmayur diff --git a/plugins/report-portal-backend/README.md b/plugins/report-portal-backend/README.md new file mode 100644 index 0000000000..061135b819 --- /dev/null +++ b/plugins/report-portal-backend/README.md @@ -0,0 +1,75 @@ +# report-portal + +Welcome to the report-portal backend plugin! + +_This plugin was created through the Backstage CLI_ + +## Getting started + +Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn +start` in the root directory, and then navigating to [/report-portal](http://localhost:3000/report-portal). + +You can also serve the plugin in isolation by running `yarn start` in the plugin directory. +This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads. +It is only meant for local development, and the setup for it can be found inside the [/dev](/dev) directory. + +## Installation + +- Install the plugin + ```shell + yarn workspace backend add @appdev-platform/backstage-plugin-report-portal-backend + ``` +- Update the following files + + - Create `/packages/backend/src/plugins/report-portal.ts` and add following code: + + ```ts + import { createRouter } from '@appdev-platform/backstage-plugin-report-portal-backend'; + import { Router } from 'express'; + + import { PluginEnvironment } from '../types'; + + export default async function createPlugin( + env: PluginEnvironment, + ): Promise { + return await createRouter({ logger: env.logger, config: env.config }); + } + ``` + + - Add following lines to `/packages/backend/src/index.ts`: + + ```ts + import reportPortal from './plugins/report-portal'; + + async function main() { + // add the files to create backend router + const reportPortalEnv = useHotMemoize(module, () => + createEnv('report-portal'), + ); + apiRouter.use('/report-portal', await reportPortal(reportPortalEnv)); + } + ``` + +- Add below configuration to `app-config.yaml`: + + ```yaml + reportPortal: + # under integrations you can configure- + # multiple instances of report portal + integrations: + # host address of your instance + # for e.g: report-portal.mycorp.com + - host: ${REPORT_PORTAL_HOST} + + # Baser API url of your instance + # for e.g: https://report-portal.mycorp.com/api/ + baseUrl: ${REPORT_PORTAL_BASE_URL} + + # Get the API key from profile page of your instance + # for e.g: Bearer fae22be1-0000-0000-8392-de1635eed9f4 + token: ${REPORT_PORTAL_TOKEN} + + # (optional) Filter the projects by type + # Default: "INTERNAL" + filterType: 'INTERNAL' + ``` diff --git a/plugins/report-portal-backend/app-config.janus-idp.yaml b/plugins/report-portal-backend/app-config.janus-idp.yaml new file mode 100644 index 0000000000..f39e9b40e2 --- /dev/null +++ b/plugins/report-portal-backend/app-config.janus-idp.yaml @@ -0,0 +1,6 @@ +reportPortal: + integrations: + - host: ${REPORT_PORTAL_HOST} + baseUrl: ${REPORT_PORTAL_BASE_URL} + token: ${REPORT_PORTAL_TOKEN} + filterType: ${REPORT_PORTAL_FILTER_TYPE} diff --git a/plugins/report-portal-backend/config.d.ts b/plugins/report-portal-backend/config.d.ts new file mode 100644 index 0000000000..40a05a4854 --- /dev/null +++ b/plugins/report-portal-backend/config.d.ts @@ -0,0 +1,28 @@ +export interface Config { + /** + * Configuration values for Report Portal plugin + */ + reportPortal: { + integrations: Array<{ + /** + * Host of report portal url + * @visibility frontend + */ + host: string; + /** + * Base api url for report portal instance + */ + baseUrl: string; + /** + * The Api token that will be used to + * @visibility secret + */ + token: string; + /** + * Filter type to apply for current host + * @visibility frontend + */ + filterType: string; + }>; + }; +} diff --git a/plugins/report-portal-backend/package.json b/plugins/report-portal-backend/package.json new file mode 100644 index 0000000000..487efdf527 --- /dev/null +++ b/plugins/report-portal-backend/package.json @@ -0,0 +1,82 @@ +{ + "name": "@appdev-platform/backstage-plugin-report-portal-backend", + "version": "0.1.1", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "types": "dist/index.d.ts" + }, + "backstage": { + "role": "backend-plugin" + }, + "exports": { + ".": "./src/index.ts", + "./alpha": "./src/alpha.ts", + "./package.json": "./package.json" + }, + "typesVersions": { + "*": { + "alpha": [ + "src/alpha.ts" + ], + "package.json": [ + "package.json" + ] + } + }, + "scripts": { + "build": "backstage-cli package build", + "clean": "backstage-cli package clean", + "export-dynamic": "janus-cli package export-dynamic-plugin", + "lint": "backstage-cli package lint", + "postpack": "backstage-cli package postpack", + "postversion": "yarn run export-dynamic", + "prepack": "backstage-cli package prepack", + "start": "backstage-cli package start", + "test": "backstage-cli package test --passWithNoTests --coverage", + "tsc": "tsc" + }, + "dependencies": { + "@backstage/backend-common": "^0.21.0", + "@backstage/backend-plugin-api": "^0.6.6", + "@backstage/backend-plugin-manager": "npm:@janus-idp/backend-plugin-manager@0.0.2-janus.5", + "@backstage/config": "^1.1.1", + "@types/express": "^*", + "express": "^4.17.1", + "express-promise-router": "^4.1.0", + "http-proxy-middleware": "^2.0.6", + "node-fetch": "^2.6.7", + "winston": "^3.2.1", + "yn": "^4.0.0" + }, + "devDependencies": { + "@backstage/cli": "0.23.0", + "@janus-idp/cli": "1.7.1", + "@types/supertest": "2.0.12", + "msw": "1.0.0", + "supertest": "6.2.4" + }, + "files": [ + "dist", + "config.d.ts", + "dist-dynamic/*.*", + "dist-dynamic/dist/**", + "dist-dynamic/alpha/*", + "app-config.janus-idp.yaml" + ], + "configSchema": "config.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/appdev-platform/backstage-plugins", + "directory": "plugins/report-portal-backend" + }, + "keywords": [ + "backstage", + "plugin" + ], + "homepage": "https://janus-idp.io/", + "bugs": "https://github.com/appdev-platform/backstage-plugins/issues" +} diff --git a/plugins/report-portal-backend/src/alpha.ts b/plugins/report-portal-backend/src/alpha.ts new file mode 100644 index 0000000000..19b2e6c4b3 --- /dev/null +++ b/plugins/report-portal-backend/src/alpha.ts @@ -0,0 +1 @@ +export { reportPortalPlugin as default } from './plugin'; diff --git a/plugins/report-portal-backend/src/dynamic/index.ts b/plugins/report-portal-backend/src/dynamic/index.ts new file mode 100644 index 0000000000..8bd3fca15a --- /dev/null +++ b/plugins/report-portal-backend/src/dynamic/index.ts @@ -0,0 +1,11 @@ +import { BackendDynamicPluginInstaller } from '@backstage/backend-plugin-manager'; + +import { createRouter } from '../service/router'; + +export const dynamicPluginInstaller: BackendDynamicPluginInstaller = { + kind: 'legacy', + router: { + pluginID: 'report-portal', + createPlugin: createRouter, + }, +}; diff --git a/plugins/report-portal-backend/src/index.ts b/plugins/report-portal-backend/src/index.ts new file mode 100644 index 0000000000..8e2a7199c3 --- /dev/null +++ b/plugins/report-portal-backend/src/index.ts @@ -0,0 +1,8 @@ +/** + * The report-portal backend plugin. + * + * @packageDocumentation + */ + +export * from './dynamic/index'; +export * from './service/router'; diff --git a/plugins/report-portal-backend/src/plugin.ts b/plugins/report-portal-backend/src/plugin.ts new file mode 100644 index 0000000000..14e28240a4 --- /dev/null +++ b/plugins/report-portal-backend/src/plugin.ts @@ -0,0 +1,33 @@ +import { loggerToWinstonLogger } from '@backstage/backend-common'; +import { + coreServices, + createBackendPlugin, +} from '@backstage/backend-plugin-api'; + +import { createRouter } from './service/router'; + +/** + * The report-portal backend plugin. + * + * @alpha + */ +export const reportPortalPlugin = createBackendPlugin({ + pluginId: 'report-portal', + register(env) { + env.registerInit({ + deps: { + logger: coreServices.logger, + config: coreServices.rootConfig, + http: coreServices.httpRouter, + }, + async init({ config, logger, http }) { + http.use(() => + createRouter({ + config: config, + logger: loggerToWinstonLogger(logger), + }), + ); + }, + }); + }, +}); diff --git a/plugins/report-portal-backend/src/run.ts b/plugins/report-portal-backend/src/run.ts new file mode 100644 index 0000000000..e48fa22f3f --- /dev/null +++ b/plugins/report-portal-backend/src/run.ts @@ -0,0 +1,19 @@ +import { getRootLogger } from '@backstage/backend-common'; + +import yn from 'yn'; + +import { startStandaloneServer } from './service/standaloneServer'; + +const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7007; +const enableCors = yn(process.env.PLUGIN_CORS, { default: false }); +const logger = getRootLogger(); + +startStandaloneServer({ port, enableCors, logger }).catch(err => { + logger.error('Standalone server failed:', err); + process.exit(1); +}); + +process.on('SIGINT', () => { + logger.info('CTRL+C pressed; exiting.'); + process.exit(0); +}); diff --git a/plugins/report-portal-backend/src/service/router.test.ts b/plugins/report-portal-backend/src/service/router.test.ts new file mode 100644 index 0000000000..714a58bfa8 --- /dev/null +++ b/plugins/report-portal-backend/src/service/router.test.ts @@ -0,0 +1,28 @@ +import { getVoidLogger } from '@backstage/backend-common'; +import { ConfigReader } from '@backstage/config'; + +import express from 'express'; + +import { createRouter } from './router'; + +describe('createRouter', () => { + let app: express.Express; + + beforeAll(async () => { + const router = await createRouter({ + logger: getVoidLogger(), + config: new ConfigReader({ reportPortal: { integrations: [] } }), + }); + app = express().use(router); + }); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + describe('just a test', () => { + it('returns ok', async () => { + expect(app).toBeDefined(); + }); + }); +}); diff --git a/plugins/report-portal-backend/src/service/router.ts b/plugins/report-portal-backend/src/service/router.ts new file mode 100644 index 0000000000..cf7c4f9750 --- /dev/null +++ b/plugins/report-portal-backend/src/service/router.ts @@ -0,0 +1,56 @@ +import { errorHandler } from '@backstage/backend-common'; +import { Config } from '@backstage/config'; + +import express from 'express'; +import Router from 'express-promise-router'; +import { createProxyMiddleware } from 'http-proxy-middleware'; +import { Logger } from 'winston'; + +export interface RouterOptions { + logger: Logger; + config: Config; +} + +export async function createRouter( + options: RouterOptions, +): Promise { + const { config } = options; + const hostsConfig = config.getConfigArray('reportPortal.integrations'); + + const router = Router(); + router.use(express.json()); + + router.get('/*', (req, res, next) => { + const hostName = req.query.host; + if (!hostName) { + res.status(500).json({ message: 'Oops, I think you forgot something?' }); + return; + } + const reqConfig = hostsConfig + .find(instance => instance.getString('host') === hostName) + ?.get() as PluginConfig; + + const proxy = createProxyMiddleware({ + target: reqConfig.baseUrl, + changeOrigin: true, + secure: false, + headers: { + Authorization: reqConfig.token, + }, + pathRewrite: { + ['/api/report-portal']: '', + }, + }); + + proxy(req, res, next); + }); + + router.use(errorHandler()); + return router; +} + +type PluginConfig = { + host: string; + baseUrl: string; + token: string; +}; diff --git a/plugins/report-portal-backend/src/service/standaloneServer.ts b/plugins/report-portal-backend/src/service/standaloneServer.ts new file mode 100644 index 0000000000..bcccc5138c --- /dev/null +++ b/plugins/report-portal-backend/src/service/standaloneServer.ts @@ -0,0 +1,40 @@ +import { createServiceBuilder } from '@backstage/backend-common'; +import { ConfigReader } from '@backstage/config'; + +import { Logger } from 'winston'; + +import { Server } from 'http'; + +import { createRouter } from './router'; + +export interface ServerOptions { + port: number; + enableCors: boolean; + logger: Logger; +} + +export async function startStandaloneServer( + options: ServerOptions, +): Promise { + const config = new ConfigReader({}); + const logger = options.logger.child({ service: 'report-portal-backend' }); + logger.debug('Starting application server...'); + const router = await createRouter({ + logger, + config, + }); + + let service = createServiceBuilder(module) + .setPort(options.port) + .addRouter('/report-portal', router); + if (options.enableCors) { + service = service.enableCors({ origin: 'http://localhost:3000' }); + } + + return await service.start().catch(err => { + logger.error('Dev server failed:', err); + process.exit(1); + }); +} + +module.hot?.accept(); diff --git a/plugins/report-portal-backend/src/setupTests.ts b/plugins/report-portal-backend/src/setupTests.ts new file mode 100644 index 0000000000..cb0ff5c3b5 --- /dev/null +++ b/plugins/report-portal-backend/src/setupTests.ts @@ -0,0 +1 @@ +export {}; diff --git a/plugins/report-portal-backend/tsconfig.json b/plugins/report-portal-backend/tsconfig.json new file mode 100644 index 0000000000..053fe87e86 --- /dev/null +++ b/plugins/report-portal-backend/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "@backstage/cli/config/tsconfig.json", + "include": ["src", "dev", "migrations"], + "exclude": ["node_modules"], + "compilerOptions": { + "outDir": "../../dist-types/plugins/report-portal-backend", + "rootDir": "." + } +} diff --git a/plugins/report-portal-backend/turbo.json b/plugins/report-portal-backend/turbo.json new file mode 100644 index 0000000000..22f01e23b1 --- /dev/null +++ b/plugins/report-portal-backend/turbo.json @@ -0,0 +1,9 @@ +{ + "extends": ["//"], + "pipeline": { + "tsc": { + "outputs": ["../../dist-types/plugins/report-portal/**"], + "dependsOn": ["^tsc"] + } + } +} diff --git a/plugins/report-portal/.eslintrc.js b/plugins/report-portal/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/report-portal/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/report-portal/OWNERS b/plugins/report-portal/OWNERS new file mode 100644 index 0000000000..ac83ec4203 --- /dev/null +++ b/plugins/report-portal/OWNERS @@ -0,0 +1,8 @@ +approvers: + - yashoswalyo + - riginoommen + - deshmukhmayur +reviewers: + - yashoswalyo + - riginoommen + - deshmukhmayur diff --git a/plugins/report-portal/README.md b/plugins/report-portal/README.md new file mode 100644 index 0000000000..5d1cfcebc1 --- /dev/null +++ b/plugins/report-portal/README.md @@ -0,0 +1,96 @@ +# report-portal + +Welcome to the report-portal plugin! + +_This plugin was created through the Backstage CLI_ + +## Getting started + +Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn start` in the root directory, and then navigating to [/report-portal](http://localhost:3000/report-portal). + +You can also serve the plugin in isolation by running `yarn start` in the plugin directory. +This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads. +It is only meant for local development, and the setup for it can be found inside the [/dev](./dev) directory. + +## Prequisite + +- [report-portal-backend](../report-portal/) plugin + +## Installation: + +- Run the following command in your backstage project + + ```shell + yarn workspace app add @appdev-platform/backstage-plugin-report-portal + ``` + +- Now import the components + + - open `/packages/app/src/App.tsx` and add the following code + + ```js + import { ReportPortalGlobalPage } from '@appdev-platform/backstage-plugin-report-portal'; + + export const AppBase = () => { + // In add the following route + } />; + }; + ``` + + - open `/packages/app/src/components/Root/Root.tsx` and add the following code + + ```js + import AssessmentIcon from '@material-ui/icons/Assessment'; + //... + //... + export const Root = ({ children }: PropsWithChildren<{}>) => ( + + + + + + ) + ``` + + - To add a card on overview tab of entity page, open `/packages/app/src/components/catalog/EntityPage.tsx` and add the following code: + + ```js + import { ReportPortalOverviewCard } from '@appdev-platform/backstage-plugin-report-portal'; + + const overviewContent = ( + + + + + + + + ); + ``` + +- Add the below configuration to your `app-config.yaml` file + + ```yaml + reportPortal: + # Contact email for support + supportEmail: ${REPORT_PORTAL_SUPPORT_MAIL} + + # under integrations you can configure- + # multiple instances of report portal + integrations: + # host address of your instance + # for e.g: report-portal.mycorp.com + - host: ${REPORT_PORTAL_HOST} + + # Baser API url of your instance + # for e.g: https://report-portal.mycorp.com/api/ + baseUrl: ${REPORT_PORTAL_BASE_URL} + + # Get the API key from profile page of your instance + # for e.g: Bearer fae22be1-0000-0000-8392-de1635eed9f4 + token: ${REPORT_PORTAL_TOKEN} + + # (optional) Filter the projects by type + # Default: "INTERNAL" + filterType: 'INTERNAL' + ``` diff --git a/plugins/report-portal/app-config.janus-idp.yaml b/plugins/report-portal/app-config.janus-idp.yaml new file mode 100644 index 0000000000..827891b97a --- /dev/null +++ b/plugins/report-portal/app-config.janus-idp.yaml @@ -0,0 +1,13 @@ +dynamicPlugins: + frontend: + janus-idp.backstage-plugin-report-portal: + apiFactories: [] + appIcons: [] + dynamicRoutes: [] + mountPoints: [] + routeBindings: [] +reportPortal: + supportEmail: ${REPORT_PORTAL_SUPPORT_MAIL} + integrations: + - host: ${REPORT_PORTAL_HOST} + filterType: ${REPORT_PORTAL_FILTER_TYPE} diff --git a/plugins/report-portal/config.d.ts b/plugins/report-portal/config.d.ts new file mode 100644 index 0000000000..731ba78f0d --- /dev/null +++ b/plugins/report-portal/config.d.ts @@ -0,0 +1,24 @@ +export interface Config { + /** + * Configuration values for Report Portal plugin + */ + reportPortal: { + /** + * Email to connect for adding more instances + * @visibility frontend + */ + supportEmail: string; + integrations: Array<{ + /** + * Host of report portal url + * @visibility frontend + */ + host: string; + /** + * Type of projects to list + * @visibility frontend + */ + filterType: string; + }>; + }; +} diff --git a/plugins/report-portal/dev/index.tsx b/plugins/report-portal/dev/index.tsx new file mode 100644 index 0000000000..6f050f14c1 --- /dev/null +++ b/plugins/report-portal/dev/index.tsx @@ -0,0 +1,42 @@ +import React from 'react'; + +import { createDevApp } from '@backstage/dev-utils'; +import { + EntityAboutCard, + EntityHasSubcomponentsCard, + EntityLinksCard, +} from '@backstage/plugin-catalog'; +import { EntityProvider } from '@backstage/plugin-catalog-react'; + +import { Grid } from '@material-ui/core'; + +import { mockEntity } from '../src/mocks'; +import { ReportPortalOverviewCard, reportPortalPlugin } from '../src/plugin'; + +const overviewContent = ( + + + + + + + + + + + + + + +); + +createDevApp() + .registerPlugin(reportPortalPlugin) + .addPage({ + element: ( + {overviewContent} + ), + title: 'Entity Page', + path: '/catalog/default/component/example-for-report-portal', + }) + .render(); diff --git a/plugins/report-portal/package.json b/plugins/report-portal/package.json new file mode 100644 index 0000000000..21e06a546d --- /dev/null +++ b/plugins/report-portal/package.json @@ -0,0 +1,77 @@ +{ + "name": "@appdev-platform/backstage-plugin-report-portal", + "version": "0.1.2", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "private": true, + "publishConfig": { + "access": "public", + "main": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "backstage": { + "role": "frontend-plugin" + }, + "sideEffects": false, + "scripts": { + "build": "backstage-cli package build", + "clean": "backstage-cli package clean", + "export-dynamic": "janus-cli package export-dynamic-plugin", + "lint": "backstage-cli package lint", + "postpack": "backstage-cli package postpack", + "postversion": "yarn run export-dynamic", + "prepack": "backstage-cli package prepack", + "start": "backstage-cli package start", + "test": "backstage-cli package test --passWithNoTests --coverage", + "tsc": "tsc" + }, + "dependencies": { + "@backstage/catalog-model": "^1.4.4", + "@backstage/core-components": "^0.14.0", + "@backstage/core-plugin-api": "^1.9.0", + "@backstage/plugin-catalog-react": "^1.10.0", + "@backstage/theme": "^0.5.1", + "@material-ui/core": "^4.12.2", + "@material-ui/icons": "^4.11.3", + "@material-ui/lab": "^4.0.0-alpha.61", + "luxon": "^3.4.4", + "react-multi-progress": "^1.3.0", + "react-use": "^17.2.4" + }, + "peerDependencies": { + "react": "16.13.1 || ^17.0.0 || ^18.0.0", + "react-router-dom": "^6.22.0" + }, + "devDependencies": { + "@backstage/cli": "0.25.2", + "@backstage/core-app-api": "1.11.0", + "@backstage/dev-utils": "1.0.22", + "@backstage/plugin-catalog": "^1.16.1", + "@backstage/test-utils": "1.5.0", + "@janus-idp/cli": "1.7.3", + "@testing-library/jest-dom": "^6.0.0", + "@testing-library/react": "^14.0.0", + "@testing-library/user-event": "^14.0.0", + "@types/luxon": "^3.4.2", + "msw": "1.0.0" + }, + "files": [ + "dist", + "config.d.ts", + "dist-scalprum", + "app-config.janus-idp.yaml" + ], + "configSchema": "config.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/appdev-platform/backstage-plugins", + "directory": "plugins/report-portal" + }, + "keywords": [ + "backstage", + "plugin" + ], + "homepage": "https://janus-idp.io/", + "bugs": "https://github.com/appdev-platform/backstage-plugins/issues" +} diff --git a/plugins/report-portal/src/api/ReportPortalApi.ts b/plugins/report-portal/src/api/ReportPortalApi.ts new file mode 100644 index 0000000000..99fc8f7532 --- /dev/null +++ b/plugins/report-portal/src/api/ReportPortalApi.ts @@ -0,0 +1,29 @@ +import { ApiRef, createApiRef } from '@backstage/core-plugin-api'; + +import { + LaunchDetailsResponse, + ProjectDetails, + ProjectListResponse, +} from './types'; + +/** @public */ +export const reportPortalApiRef: ApiRef = createApiRef({ + id: 'plugin.report-portal', +}); + +export type ReportPortalApi = { + getReportPortalBaseUrl: (host: string) => string; + getLaunchResults: ( + projectId: string, + host: string, + filters: { [key: string]: string | number } | undefined, + ) => Promise; + getProjectDetails: ( + projectId: string, + host: string, + ) => Promise; + getInstanceDetails: ( + host: string, + filters: { [key: string]: string | number } | undefined, + ) => Promise; +}; diff --git a/plugins/report-portal/src/api/ReportPortalClient.ts b/plugins/report-portal/src/api/ReportPortalClient.ts new file mode 100644 index 0000000000..416ac07adf --- /dev/null +++ b/plugins/report-portal/src/api/ReportPortalClient.ts @@ -0,0 +1,70 @@ +import { DiscoveryApi } from '@backstage/core-plugin-api'; + +import { ReportPortalApi } from './ReportPortalApi'; +import { + LaunchDetailsResponse, + ProjectDetails, + ProjectListResponse, +} from './types'; + +export class ReportPortalClient implements ReportPortalApi { + constructor(private readonly discoveryApi: DiscoveryApi) {} + + private async getBaseApiUrl() { + return `${await this.discoveryApi.getBaseUrl('report-portal')}/v1/`; + } + + getReportPortalBaseUrl(host: string) { + return `https://${host}/`; + } + + async getLaunchResults( + projectId: string, + host: string, + filters: { [key: string]: string | number } | undefined, + ) { + const baseUrl = new URL( + `${projectId}/launch/latest`, + await this.getBaseApiUrl(), + ); + if (filters) { + Object.keys(filters).forEach(key => + baseUrl.searchParams.append(key, filters[key] as string), + ); + } + baseUrl.searchParams.append('host', host); + const response = await fetch(baseUrl); + if (response.status !== 200) { + throw new Error('Failed to fetch launch details'); + } + return (await response.json()) as LaunchDetailsResponse; + } + + async getProjectDetails(projectId: string, host: string) { + const baseUrl = new URL(`project/${projectId}`, await this.getBaseApiUrl()); + baseUrl.searchParams.append('host', host); + const response = await fetch(baseUrl); + if (response.status !== 200) { + throw new Error('Failed to fetch project details'); + } + return (await response.json()) as ProjectDetails; + } + + async getInstanceDetails( + host: string, + filters: { [key: string]: string | number } | undefined, + ) { + const baseUrl = new URL('project/list', await this.getBaseApiUrl()); + if (filters) { + Object.keys(filters).forEach(key => + baseUrl.searchParams.append(key, filters[key] as string), + ); + } + baseUrl.searchParams.append('host', host); + const response = await fetch(baseUrl); + if (response.status !== 200) { + throw new Error('Failed to get instance details'); + } + return (await response.json()) as ProjectListResponse; + } +} diff --git a/plugins/report-portal/src/api/index.ts b/plugins/report-portal/src/api/index.ts new file mode 100644 index 0000000000..d990bab3b6 --- /dev/null +++ b/plugins/report-portal/src/api/index.ts @@ -0,0 +1,3 @@ +export * from './ReportPortalClient'; +export * from './ReportPortalApi'; +export * from './types'; diff --git a/plugins/report-portal/src/api/types.ts b/plugins/report-portal/src/api/types.ts new file mode 100644 index 0000000000..70863feaee --- /dev/null +++ b/plugins/report-portal/src/api/types.ts @@ -0,0 +1,82 @@ +export type ProjectDetails = { + projectId: number; + projectName: string; + usersQuantity: number; + id: number; + launchesQuantity: number; + launchesPerUser: number; + uniqueTickets: number; + launchesPerWeek: number; + lastRun: number; + entryType: string; + configuration: { + subTypes: { + [key: string]: [ + { + id: number; + locator: string; + typeRef: string; + longName: string; + shortName: string; + color: string; + }, + ]; + }; + }; + users: [ + { + login: string; + projectRole: string; + }, + ]; + creationDate: number; +}; + +export type LaunchDetails = { + owner: string; + share: boolean; + description: string; + id: number; + uuid: string; + name: string; + number: number; + startTime: number; + endTime: number; + lastModified: number; + status: string; + statistics: { + executions: { + passed: number; + failed: number; + skipped: number; + total: number; + }; + defects: { + [key: string]: { + total: number; + [key: string]: number; + }; + }; + mode: string; + approximateDuration: number; + hasRetries: boolean; + rerun: boolean; + }; +}; + +export type PageType = { + number: number; + size: number; + totalElements: number; + totalPages: number; +}; + +export type LaunchDetailsResponse = { + content: LaunchDetails[]; + page: PageType; +}; + +export type ProjectListResponse = { + content: ProjectDetails[]; + page: PageType; +}; diff --git a/plugins/report-portal/src/components/LaunchesPage/LaunchesPage.tsx b/plugins/report-portal/src/components/LaunchesPage/LaunchesPage.tsx new file mode 100644 index 0000000000..78bf9d3cbe --- /dev/null +++ b/plugins/report-portal/src/components/LaunchesPage/LaunchesPage.tsx @@ -0,0 +1,69 @@ +import React from 'react'; + +import { + Breadcrumbs, + Content, + Header, + Link, + Page, + useQueryParamState, +} from '@backstage/core-components'; +import { useRouteRef } from '@backstage/core-plugin-api'; + +import { Button, makeStyles } from '@material-ui/core'; +import Launch from '@material-ui/icons/Launch'; + +import { projectsRouteRef, rootRouteRef } from '../../routes'; +import { LaunchesPageContent } from './LaunchesPageContent/LaunchesPageContent'; + +const useStyles = makeStyles(theme => ({ + 'prj-button': { + color: '#fff', + backdropFilter: 'blur(10px)', + marginTop: theme.spacing(4), + alignItems: 'initial', + textTransform: 'none', + fontSize: '1rem', + }, +})); + +export const LaunchesPage = (props: { themeId?: string }) => { + const rootPage = useRouteRef(rootRouteRef); + const projectsPage = useRouteRef(projectsRouteRef); + const hostName = useQueryParamState('host')[0] as string; + const projectName = useQueryParamState('project')[0] as string; + const classes = useStyles(); + + return ( + +
+ + Report Portal + + {hostName} + + {projectName} + +
{projectName}
+ + } + > + +
+ + + +
+ ); +}; diff --git a/plugins/report-portal/src/components/LaunchesPage/LaunchesPageContent/LaunchesPageContent.tsx b/plugins/report-portal/src/components/LaunchesPage/LaunchesPageContent/LaunchesPageContent.tsx new file mode 100644 index 0000000000..0fad73114d --- /dev/null +++ b/plugins/report-portal/src/components/LaunchesPage/LaunchesPageContent/LaunchesPageContent.tsx @@ -0,0 +1,189 @@ +import React, { useEffect, useState } from 'react'; + +import { ErrorPanel, Table, TableColumn } from '@backstage/core-components'; +import { useApi } from '@backstage/core-plugin-api'; + +import { IconButton, Link } from '@material-ui/core'; +import Launch from '@material-ui/icons/Launch'; +import { DateTime } from 'luxon'; + +import { + LaunchDetailsResponse, + PageType, + reportPortalApiRef, +} from '../../../api'; + +type LaunchDetails = { + id: number; + launchName: string; + number: number; + total: number; + passed: number; + failed: number; + skipped: number; + startTime: number; +}; + +export const LaunchesPageContent = (props: { + host: string; + project: string; +}) => { + const { host, project } = props; + const reportPortalApi = useApi(reportPortalApiRef); + + const [loading, setLoading] = useState(true); + const [tableData, setTableData] = useState<{ + launches: LaunchDetails[]; + page: PageType; + }>({ + launches: [], + page: { + number: 1, + size: 10, + totalElements: 0, + totalPages: 1, + }, + }); + + const [error, setError] = useState(); + useEffect(() => { + setLoading(true); + reportPortalApi + .getLaunchResults(project, host, { + 'page.size': 10, + 'page.page': 1, + 'page.sort': 'startTime,DESC', + }) + .then(res => { + responseHandler(res); + }) + .catch(err => { + setLoading(false); + setError(err); + }); + }, [host, project, reportPortalApi]); + + function handlePageChange(page: number, pageSize: number) { + setLoading(true); + reportPortalApi + .getLaunchResults(project, host, { + 'page.size': pageSize, + 'page.page': page + 1, + 'page.sort': 'startTime,DESC', + }) + .then(res => { + responseHandler(res); + }) + .catch(err => { + setError(err); + setLoading(false); + }); + } + + function responseHandler(res: LaunchDetailsResponse) { + const tempArr: LaunchDetails[] = []; + res.content.forEach(data => { + tempArr.push({ + id: data.id, + launchName: data.name, + number: data.number, + total: data.statistics.executions.total ?? '-', + passed: data.statistics.executions.passed ?? '-', + failed: data.statistics.executions.failed ?? '-', + skipped: data.statistics.executions.skipped ?? '-', + startTime: data.startTime, + }); + }); + setTableData({ launches: tempArr, page: res.page }); + setLoading(false); + } + + const columns: TableColumn[] = [ + { + id: 0, + field: 'launchName', + title: 'Launch', + render: row => ( + + {row.launchName} #{row.number} + + ), + width: '50%', + searchable: true, + }, + { + id: 1, + title: 'Total', + align: 'center', + width: '5%', + render: row => {row.total}, + }, + { + id: 2, + title: 'Passed', + align: 'center', + width: '5%', + render: row => {row.passed}, + }, + { + id: 3, + title: 'Failed', + align: 'center', + width: '5%', + render: row => {row.failed}, + }, + { + id: 4, + title: 'Skipped', + align: 'center', + width: '5%', + render: row => {row.skipped}, + }, + { + id: 4, + title: 'Start Time', + align: 'center', + width: '25%', + render: row => DateTime.fromMillis(row.startTime).toRelative(), + }, + { + id: 5, + title: 'Actions', + align: 'left', + width: '5%', + render: row => ( + + + + ), + }, + ]; + if (error) return ; + return ( + + ); +}; diff --git a/plugins/report-portal/src/components/LaunchesPage/index.ts b/plugins/report-portal/src/components/LaunchesPage/index.ts new file mode 100644 index 0000000000..b804d467f9 --- /dev/null +++ b/plugins/report-portal/src/components/LaunchesPage/index.ts @@ -0,0 +1 @@ +export { LaunchesPage } from './LaunchesPage'; diff --git a/plugins/report-portal/src/components/ProjectsPage/ProjectsPage.tsx b/plugins/report-portal/src/components/ProjectsPage/ProjectsPage.tsx new file mode 100644 index 0000000000..9f0f5dadac --- /dev/null +++ b/plugins/report-portal/src/components/ProjectsPage/ProjectsPage.tsx @@ -0,0 +1,65 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; + +import { + Breadcrumbs, + Content, + Header, + Page, + useQueryParamState, +} from '@backstage/core-components'; +import { useRouteRef } from '@backstage/core-plugin-api'; + +import { Button, makeStyles } from '@material-ui/core'; +import Launch from '@material-ui/icons/Launch'; + +import { rootRouteRef } from '../../routes'; +import { ProjectsPageContent } from './ProjectsPageContent/ProjectsPageContent'; + +const useStyles = makeStyles(theme => ({ + 'rp-button': { + color: '#fff', + backdropFilter: 'blur(10px)', + marginTop: theme.spacing(4), + alignItems: 'initial', + textTransform: 'none', + fontSize: '1rem', + }, +})); + +export const ProjectsPage = (props: { themeId?: string }) => { + const rootPage = useRouteRef(rootRouteRef); + const hostName = useQueryParamState('host')[0] as string; + + const classes = useStyles(); + + return ( + +
+ + Report Portal + {hostName} + +
{hostName}
+ + } + > + +
+ + + +
+ ); +}; diff --git a/plugins/report-portal/src/components/ProjectsPage/ProjectsPageContent/ProjectsPageContent.tsx b/plugins/report-portal/src/components/ProjectsPage/ProjectsPageContent/ProjectsPageContent.tsx new file mode 100644 index 0000000000..71e62bfc8c --- /dev/null +++ b/plugins/report-portal/src/components/ProjectsPage/ProjectsPageContent/ProjectsPageContent.tsx @@ -0,0 +1,157 @@ +import React, { useEffect, useState } from 'react'; + +import { + ErrorPanel, + Link, + Table, + TableColumn, +} from '@backstage/core-components'; +import { configApiRef, useApi, useRouteRef } from '@backstage/core-plugin-api'; + +import { IconButton } from '@material-ui/core'; +import Launch from '@material-ui/icons/Launch'; +import { Skeleton } from '@material-ui/lab'; + +import { + ProjectDetails, + ProjectListResponse, + reportPortalApiRef, +} from '../../../api'; +import { launchRouteRef } from '../../../routes'; + +const UniqueLaunches = (props: { host: string; projectId: string }) => { + const { host, projectId } = props; + const [loading, setLoading] = useState(true); + const [noOfLaunches, setNoOfLaunches] = useState(0); + + const api = useApi(reportPortalApiRef); + useEffect(() => { + api.getLaunchResults(projectId, host, {}).then(res => { + setNoOfLaunches(res.content.length); + setLoading(false); + }); + }, [api, projectId, host]); + + return loading ? ( + + ) : ( + {noOfLaunches} + ); +}; + +export const ProjectsPageContent = (props: { host: string }) => { + const { host } = props; + const launchPageRoute = useRouteRef(launchRouteRef); + + const config = useApi(configApiRef).getConfigArray( + 'reportPortal.integrations', + ); + const filterType = + config + .find(value => value.getString('host') === host) + ?.getString('filterType') ?? 'INTERNAL'; + + const [loading, setLoading] = useState(true); + const [tableData, setTableData] = useState({ + content: [], + page: { + number: 1, + size: 10, + totalElements: 0, + totalPages: 1, + }, + }); + const reportPortalApi = useApi(reportPortalApiRef); + + const [error, setError] = useState(); + + useEffect(() => { + if (loading) { + reportPortalApi + .getInstanceDetails(host, { + 'filter.eq.type': filterType, + 'page.size': tableData.page.size, + 'page.page': tableData.page.number, + }) + .then(res => { + setTableData({ ...res }); + setLoading(false); + }) + .catch(err => { + setLoading(false); + setError(err); + }); + } + }); + + const columns: TableColumn[] = [ + { + id: 0, + title: 'Project', + field: 'projectName', + render: row => ( + + {row.projectName} + + ), + width: '60%', + searchable: true, + }, + { + id: 1, + title: 'Launches', + width: '30%', + render: row => , + }, + { + id: 2, + title: 'Actions', + align: 'center', + render: row => ( + + + + ), + }, + ]; + + function handlePageChange(page: number, pageSize: number) { + setLoading(true); + reportPortalApi + .getInstanceDetails(host, { + 'filter.eq.type': filterType, + 'page.size': pageSize, + 'page.page': page + 1, + }) + .then(res => { + setTableData({ ...res }); + setLoading(false); + }); + } + + if (error) return ; + + return ( +
+ ); +}; diff --git a/plugins/report-portal/src/components/ProjectsPage/index.ts b/plugins/report-portal/src/components/ProjectsPage/index.ts new file mode 100644 index 0000000000..7b8a4b5b77 --- /dev/null +++ b/plugins/report-portal/src/components/ProjectsPage/index.ts @@ -0,0 +1 @@ +export { ProjectsPage } from './ProjectsPage'; diff --git a/plugins/report-portal/src/components/ReportPortalGlobalPage/GlobalPageContent/GlobalPageContent.tsx b/plugins/report-portal/src/components/ReportPortalGlobalPage/GlobalPageContent/GlobalPageContent.tsx new file mode 100644 index 0000000000..676f495a30 --- /dev/null +++ b/plugins/report-portal/src/components/ReportPortalGlobalPage/GlobalPageContent/GlobalPageContent.tsx @@ -0,0 +1,110 @@ +import React, { useEffect, useState } from 'react'; + +import { Link, Table, TableColumn } from '@backstage/core-components'; +import { configApiRef, useApi, useRouteRef } from '@backstage/core-plugin-api'; + +import { IconButton } from '@material-ui/core'; +import LaunchIcon from '@material-ui/icons/Launch'; +import { Skeleton } from '@material-ui/lab'; + +import { reportPortalApiRef } from '../../../api'; +import { useInstanceDetails } from '../../../hooks'; +import { projectsRouteRef } from '../../../routes'; + +type InstanceData = { + instance: string; + filterType: string; + projects: React.ReactNode; +}; + +const NoOfProjects = (props: { host: string; filter: string }) => { + const { loading, projectListData } = useInstanceDetails( + props.host, + props.filter, + ); + return loading ? ( + + ) : ( + {projectListData?.content.length} + ); +}; + +export const GlobalPageContent = () => { + const config = useApi(configApiRef); + const reportPortalApi = useApi(reportPortalApiRef); + const hostsConfig = config.getConfigArray('reportPortal.integrations'); + const [hosts, _] = useState< + { host: string; filterType: string }[] | undefined + >( + hostsConfig.map(value => ({ + host: value.getString('host'), + filterType: value.getString('filterType') ?? 'INTERNAL', + })), + ); + + const projectsPageRoute = useRouteRef(projectsRouteRef)(); + + const [instanceData, setInstanceData] = useState([]); + const columns: TableColumn[] = [ + { + id: 0, + field: 'instance', + title: 'Instances', + render: rowData => ( + + {rowData.instance} + + ), + width: '60%', + }, + { + id: 1, + field: 'projects', + title: 'Projects', + align: 'left', + render: rowData => rowData.projects, + width: '10%', + }, + { + id: 2, + field: 'portalLink', + title: 'Actions', + align: 'center', + render: rowData => ( + + + + ), + width: '30%', + }, + ]; + + useEffect(() => { + if (hosts) { + const tempArr: InstanceData[] = []; + hosts.forEach(value => { + tempArr.push({ + instance: value.host, + filterType: value.filterType, + projects: ( + + ), + }); + }); + setInstanceData(tempArr); + } + }, [hosts]); + + return ( +
+ ); +}; diff --git a/plugins/report-portal/src/components/ReportPortalGlobalPage/GlobalPageContent/index.ts b/plugins/report-portal/src/components/ReportPortalGlobalPage/GlobalPageContent/index.ts new file mode 100644 index 0000000000..c902ddcd13 --- /dev/null +++ b/plugins/report-portal/src/components/ReportPortalGlobalPage/GlobalPageContent/index.ts @@ -0,0 +1 @@ +export { GlobalPageContent } from './GlobalPageContent'; diff --git a/plugins/report-portal/src/components/ReportPortalGlobalPage/ReportPortalGlobalPage.tsx b/plugins/report-portal/src/components/ReportPortalGlobalPage/ReportPortalGlobalPage.tsx new file mode 100644 index 0000000000..e2a3d1f135 --- /dev/null +++ b/plugins/report-portal/src/components/ReportPortalGlobalPage/ReportPortalGlobalPage.tsx @@ -0,0 +1,64 @@ +import React from 'react'; + +import { Content, PageWithHeader } from '@backstage/core-components'; +import { configApiRef, useApi } from '@backstage/core-plugin-api'; + +import { Button, Grid } from '@material-ui/core'; + +import { GlobalPageContent } from './GlobalPageContent'; + +export type ReportPortalGlobalPageProps = { + title?: string; + subtitle?: string; + theme?: string; +}; + +export const ReportPortalGlobalPage = (props: ReportPortalGlobalPageProps) => { + const config = useApi(configApiRef); + const sendAddress = config.getString('reportPortal.supportEmail'); + const subject = 'Request to add a report portal instance'; + const body = `Requesting to add a new Report Portal instance to Red Hat Experience Platform. + +Hostname: +Instance Project Manager: +Instance Admin email:`; + return ( + + + + + Don't see your instance here?   + + + + + + + + + ); +}; diff --git a/plugins/report-portal/src/components/ReportPortalGlobalPage/index.ts b/plugins/report-portal/src/components/ReportPortalGlobalPage/index.ts new file mode 100644 index 0000000000..710f27aa30 --- /dev/null +++ b/plugins/report-portal/src/components/ReportPortalGlobalPage/index.ts @@ -0,0 +1 @@ +export * from './ReportPortalGlobalPage'; diff --git a/plugins/report-portal/src/components/ReportPortalIcon/ReportPortalIcon.tsx b/plugins/report-portal/src/components/ReportPortalIcon/ReportPortalIcon.tsx new file mode 100644 index 0000000000..2ea2c04dde --- /dev/null +++ b/plugins/report-portal/src/components/ReportPortalIcon/ReportPortalIcon.tsx @@ -0,0 +1,12 @@ +import React from 'react'; + +import { SvgIcon, SvgIconProps } from '@material-ui/core'; + +export const ReportPortalIcon = (props: SvgIconProps) => { + return ( + + + + + ); +}; diff --git a/plugins/report-portal/src/components/ReportPortalIcon/index.ts b/plugins/report-portal/src/components/ReportPortalIcon/index.ts new file mode 100644 index 0000000000..e7c069095e --- /dev/null +++ b/plugins/report-portal/src/components/ReportPortalIcon/index.ts @@ -0,0 +1 @@ +export { ReportPortalIcon } from './ReportPortalIcon'; diff --git a/plugins/report-portal/src/components/ReportPortalOverviewCard/ReportPortalOverviewCard.tsx b/plugins/report-portal/src/components/ReportPortalOverviewCard/ReportPortalOverviewCard.tsx new file mode 100644 index 0000000000..8ecd1ad87f --- /dev/null +++ b/plugins/report-portal/src/components/ReportPortalOverviewCard/ReportPortalOverviewCard.tsx @@ -0,0 +1,244 @@ +import React, { useEffect, useState } from 'react'; +import MultiProgress from 'react-multi-progress'; + +import { InfoCard, InfoCardVariants } from '@backstage/core-components'; +import { configApiRef, useApi } from '@backstage/core-plugin-api'; +import { useEntity } from '@backstage/plugin-catalog-react'; + +import { + Divider, + Grid, + List, + ListItem, + ListItemSecondaryAction, + ListItemText, + makeStyles, + Theme, + Typography, +} from '@material-ui/core'; +import { Skeleton } from '@material-ui/lab'; + +import { useLaunchDetails, useProjectDetails } from '../../hooks'; +import { isReportPortalAvailable } from '../../utils/isReportPortalAvailable'; + +const HeaderComponent = (props: { total: number }) => { + return ( + + Test Statistics + {props.total === -1 ? ( + + + + ) : ( + Total: {props.total} + )} + + ); +}; + +type Defect = { id: number; name: string; total: number; color: string }; + +const useStylesForDefect = makeStyles((theme: Theme) => ({ + status: { + '&::before': { + width: '0.7em', + height: '0.7em', + display: 'inline-block', + marginRight: theme.spacing(1), + borderRadius: '50%', + content: '""', + backgroundColor: (props: { backgroundColor: string }) => + props.backgroundColor, + }, + }, +})); + +const DefectStatus = (props: { color: string; children: string }) => { + const classes = useStylesForDefect({ backgroundColor: props.color }); + return ( + + ); +}; + +const useStyles = makeStyles({ + results: { + fontWeight: 'bold', + }, +}); + +export const ReportPortalOverviewCard = (props: { + variant: InfoCardVariants; +}) => { + const classes = useStyles(); + const config = useApi(configApiRef); + const hostsConfig = config.getConfigArray('reportPortal.integrations'); + + const { entity } = useEntity(); + const projectId = + entity.metadata.annotations?.['reportportal.io/project-name'] ?? ''; + const launchName = + entity.metadata.annotations?.['reportportal.io/launch-name'] ?? ''; + const hostName = + entity.metadata.annotations?.['reportportal.io/host'] ?? + hostsConfig[0].getString('host'); + + const [defects, setDefects] = useState([]); + const [filters, _] = useState<{ [key: string]: string | number } | undefined>( + { + 'filter.eq.name': launchName, + }, + ); + + const { loading, launchDetails } = useLaunchDetails( + projectId, + hostName, + filters, + ); + const { loading: projectLoading, projectDetails } = useProjectDetails( + projectId, + hostName, + ); + + useEffect(() => { + if (!loading && launchDetails && projectDetails) { + const tempArr: Defect[] = []; + Object.keys(launchDetails.statistics.defects).forEach(defect => { + tempArr.push({ + name: projectDetails.configuration.subTypes?.[defect.toUpperCase()][0] + .longName, + total: launchDetails.statistics.defects?.[defect].total, + color: + projectDetails.configuration.subTypes?.[defect.toUpperCase()][0] + .color, + id: projectDetails.configuration.subTypes?.[defect.toUpperCase()][0] + .id, + }); + }); + setDefects(tempArr); + } + }, [loading, launchDetails, projectDetails]); + + if (!isReportPortalAvailable(entity)) return null; + + return ( + + } + variant={props.variant} + divider + deepLink={{ + link: `https://${hostName}/ui/#${projectId}/launches/latest/${launchDetails?.id}`, + title: 'View on Report Portal', + }} + > + + {loading ? ( + <> + + + + + + + + + + + + + + ) : ( + <> + + + + + + Passed: {launchDetails!.statistics.executions.passed ?? 0} + + + + + Failed: {launchDetails!.statistics.executions.failed ?? 0} + + + + + Skipped: {launchDetails!.statistics.executions.skipped ?? 0} + + + + )} + + + + + {!projectLoading ? ( + + {defects.length > 0 ? ( + defects.map(defect => ( + + + {defect.name} + + } + /> + + {defect.total} + + + )) + ) : ( + + No defects found + + )} + + ) : ( + + )} + + + + ); +}; diff --git a/plugins/report-portal/src/components/ReportPortalOverviewCard/index.ts b/plugins/report-portal/src/components/ReportPortalOverviewCard/index.ts new file mode 100644 index 0000000000..e2c35565e6 --- /dev/null +++ b/plugins/report-portal/src/components/ReportPortalOverviewCard/index.ts @@ -0,0 +1 @@ +export { ReportPortalOverviewCard } from './ReportPortalOverviewCard'; diff --git a/plugins/report-portal/src/components/Router.tsx b/plugins/report-portal/src/components/Router.tsx new file mode 100644 index 0000000000..f4cd3799b1 --- /dev/null +++ b/plugins/report-portal/src/components/Router.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { Route, Routes } from 'react-router-dom'; + +import { launchRouteRef, projectsRouteRef } from '../routes'; +import { LaunchesPage } from './LaunchesPage'; +import { ProjectsPage } from './ProjectsPage'; +import { + ReportPortalGlobalPage, + ReportPortalGlobalPageProps, +} from './ReportPortalGlobalPage'; + +export const Router = (props: ReportPortalGlobalPageProps) => { + return ( + + } /> + } + /> + } + /> + + ); +}; diff --git a/plugins/report-portal/src/hooks/index.ts b/plugins/report-portal/src/hooks/index.ts new file mode 100644 index 0000000000..6fd3dbad25 --- /dev/null +++ b/plugins/report-portal/src/hooks/index.ts @@ -0,0 +1,3 @@ +export * from './useProjectDetails'; +export * from './useLaunchDetails'; +export * from './useInstanceDetails'; diff --git a/plugins/report-portal/src/hooks/useInstanceDetails.ts b/plugins/report-portal/src/hooks/useInstanceDetails.ts new file mode 100644 index 0000000000..c7fd4a0f92 --- /dev/null +++ b/plugins/report-portal/src/hooks/useInstanceDetails.ts @@ -0,0 +1,33 @@ +import React from 'react'; + +import { useApi } from '@backstage/core-plugin-api'; + +import { ProjectListResponse, reportPortalApiRef } from '../api'; + +export function useInstanceDetails(host: string, filterType: string) { + const reportPortalApi = useApi(reportPortalApiRef); + const [loading, setLoading] = React.useState(true); + const [projectListData, setProjectListData] = + React.useState({ + content: [], + page: { + number: 1, + size: 10, + totalElements: 0, + totalPages: 1, + }, + }); + + React.useEffect(() => { + setLoading(true); + reportPortalApi + .getInstanceDetails(host, { 'filter.eq.type': filterType }) + .then(res => { + setProjectListData(res); + setLoading(false); + }) + .catch(err => err); + }, [host, reportPortalApi, filterType]); + + return { loading, projectListData }; +} diff --git a/plugins/report-portal/src/hooks/useLaunchDetails.ts b/plugins/report-portal/src/hooks/useLaunchDetails.ts new file mode 100644 index 0000000000..582c6eb9e9 --- /dev/null +++ b/plugins/report-portal/src/hooks/useLaunchDetails.ts @@ -0,0 +1,25 @@ +import { useEffect, useState } from 'react'; + +import { useApi } from '@backstage/core-plugin-api'; + +import { LaunchDetails, reportPortalApiRef } from '../api'; + +export function useLaunchDetails( + projectId: string, + hostName: string, + filters: { [key: string]: string | number } | undefined, +) { + const reportPortalApi = useApi(reportPortalApiRef); + const [loading, setLoading] = useState(true); + const [launchDetails, setLaunchDetails] = useState(); + + useEffect(() => { + setLoading(true); + reportPortalApi.getLaunchResults(projectId, hostName, filters).then(res => { + setLaunchDetails(res.content[0]); + setLoading(false); + }); + }, [filters, projectId, hostName, reportPortalApi]); + + return { loading, launchDetails }; +} diff --git a/plugins/report-portal/src/hooks/useProjectDetails.ts b/plugins/report-portal/src/hooks/useProjectDetails.ts new file mode 100644 index 0000000000..c83130b67e --- /dev/null +++ b/plugins/report-portal/src/hooks/useProjectDetails.ts @@ -0,0 +1,27 @@ +import React from 'react'; + +import { useApi } from '@backstage/core-plugin-api'; + +import { ProjectDetails, reportPortalApiRef } from '../api'; + +export function useProjectDetails( + projectId: string, + host: string, +): { loading: boolean; projectDetails: ProjectDetails | undefined } { + const reportPortalApi = useApi(reportPortalApiRef); + const [projectDetails, setProjectDetails] = React.useState(); + const [loading, setLoading] = React.useState(true); + + React.useEffect(() => { + setLoading(true); + reportPortalApi + .getProjectDetails(projectId, host) + .then(resp => { + setProjectDetails(resp); + setLoading(false); + }) + .catch(err => err); + }, [projectId, reportPortalApi, host]); + + return { loading, projectDetails }; +} diff --git a/plugins/report-portal/src/index.ts b/plugins/report-portal/src/index.ts new file mode 100644 index 0000000000..af0c37009b --- /dev/null +++ b/plugins/report-portal/src/index.ts @@ -0,0 +1,8 @@ +export { + reportPortalPlugin, + ReportPortalOverviewCard, + ReportPortalGlobalPage, +} from './plugin'; + +export { isReportPortalAvailable } from './utils/isReportPortalAvailable'; +export { ReportPortalIcon } from './components/ReportPortalIcon'; diff --git a/plugins/report-portal/src/mocks/entity.ts b/plugins/report-portal/src/mocks/entity.ts new file mode 100644 index 0000000000..cb16d1aa44 --- /dev/null +++ b/plugins/report-portal/src/mocks/entity.ts @@ -0,0 +1,22 @@ +import { Entity } from '@backstage/catalog-model'; + +export const mockEntity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'example-for-report-portal', + title: 'Example App', + namespace: 'default', + annotations: { + 'reportportal.io/host': + 'reportportal-hydra.apps.ocp-c1.prod.psi.redhat.com', + 'reportportal.io/project-name': 'dxp_qe', + 'reportportal.io/launch-name': 'Demo API Tests', + }, + spec: { + owner: 'guest', + type: 'service', + lifecycle: 'production', + }, + }, +}; diff --git a/plugins/report-portal/src/mocks/index.ts b/plugins/report-portal/src/mocks/index.ts new file mode 100644 index 0000000000..0228249964 --- /dev/null +++ b/plugins/report-portal/src/mocks/index.ts @@ -0,0 +1 @@ +export { mockEntity } from './entity'; diff --git a/plugins/report-portal/src/plugin.test.ts b/plugins/report-portal/src/plugin.test.ts new file mode 100644 index 0000000000..0c53dc48cb --- /dev/null +++ b/plugins/report-portal/src/plugin.test.ts @@ -0,0 +1,7 @@ +import { reportPortalPlugin } from './plugin'; + +describe('report-portal', () => { + it('should export plugin', () => { + expect(reportPortalPlugin).toBeDefined(); + }); +}); diff --git a/plugins/report-portal/src/plugin.ts b/plugins/report-portal/src/plugin.ts new file mode 100644 index 0000000000..d894c478cd --- /dev/null +++ b/plugins/report-portal/src/plugin.ts @@ -0,0 +1,47 @@ +import { + createApiFactory, + createComponentExtension, + createPlugin, + createRoutableExtension, + discoveryApiRef, +} from '@backstage/core-plugin-api'; + +import { reportPortalApiRef, ReportPortalClient } from './api'; +import { entityRootRouteRef, rootRouteRef } from './routes'; + +export const reportPortalPlugin = createPlugin({ + id: 'report-portal', + routes: { + root: rootRouteRef, + entityRoot: entityRootRouteRef, + }, + apis: [ + createApiFactory({ + api: reportPortalApiRef, + deps: { + discovery: discoveryApiRef, + }, + factory: ({ discovery }) => new ReportPortalClient(discovery), + }), + ], +}); + +export const ReportPortalOverviewCard = reportPortalPlugin.provide( + createComponentExtension({ + name: 'ReportPortalOverviewCard', + component: { + lazy: () => + import('./components/ReportPortalOverviewCard').then( + m => m.ReportPortalOverviewCard, + ), + }, + }), +); + +export const ReportPortalGlobalPage = reportPortalPlugin.provide( + createRoutableExtension({ + name: 'ReportPortalGlobalPage', + mountPoint: rootRouteRef, + component: () => import('./components/Router').then(m => m.Router), + }), +); diff --git a/plugins/report-portal/src/routes.ts b/plugins/report-portal/src/routes.ts new file mode 100644 index 0000000000..09b830609f --- /dev/null +++ b/plugins/report-portal/src/routes.ts @@ -0,0 +1,22 @@ +import { createRouteRef, createSubRouteRef } from '@backstage/core-plugin-api'; + +export const rootRouteRef = createRouteRef({ + id: 'report-portal', +}); + +export const projectsRouteRef = createSubRouteRef({ + id: 'report-portal:projects', + path: '/instance', + parent: rootRouteRef, +}); + +export const launchRouteRef = createSubRouteRef({ + id: 'report-portal:launches', + path: '/instance/project', + parent: rootRouteRef, +}); + +export const entityRootRouteRef = createRouteRef({ + id: 'report-portal:entity-page', + params: ['namespace', 'kind', 'name'], +}); diff --git a/plugins/report-portal/src/setupTests.ts b/plugins/report-portal/src/setupTests.ts new file mode 100644 index 0000000000..7b0828bfa8 --- /dev/null +++ b/plugins/report-portal/src/setupTests.ts @@ -0,0 +1 @@ +import '@testing-library/jest-dom'; diff --git a/plugins/report-portal/src/utils/isReportPortalAvailable.ts b/plugins/report-portal/src/utils/isReportPortalAvailable.ts new file mode 100644 index 0000000000..0406287142 --- /dev/null +++ b/plugins/report-portal/src/utils/isReportPortalAvailable.ts @@ -0,0 +1,8 @@ +import { Entity } from '@backstage/catalog-model'; + +export const isReportPortalAvailable = (entity: Entity): boolean => { + return Boolean( + entity.metadata.annotations?.['reportportal.io/project-name'] && + entity.metadata.annotations?.['reportportal.io/launch-name'], + ); +}; diff --git a/plugins/report-portal/tsconfig.json b/plugins/report-portal/tsconfig.json new file mode 100644 index 0000000000..25b14614f6 --- /dev/null +++ b/plugins/report-portal/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "@backstage/cli/config/tsconfig.json", + "include": ["src", "dev"], + "exclude": ["node_modules"], + "compilerOptions": { + "outDir": "../../dist-types/plugins/report-portal", + "rootDir": "." + } +} diff --git a/plugins/report-portal/turbo.json b/plugins/report-portal/turbo.json new file mode 100644 index 0000000000..22f01e23b1 --- /dev/null +++ b/plugins/report-portal/turbo.json @@ -0,0 +1,9 @@ +{ + "extends": ["//"], + "pipeline": { + "tsc": { + "outputs": ["../../dist-types/plugins/report-portal/**"], + "dependsOn": ["^tsc"] + } + } +} diff --git a/yarn.lock b/yarn.lock index f8859fb355..d89ca7d0ee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2916,7 +2916,7 @@ zod "^3.22.4" zod-to-json-schema "^3.21.4" -"@backstage/integration-aws-node@^0.1.12": +"@backstage/integration-aws-node@^0.1.12", "@backstage/integration-aws-node@^0.1.8": version "0.1.12" resolved "https://registry.yarnpkg.com/@backstage/integration-aws-node/-/integration-aws-node-0.1.12.tgz#d2c5ac7c81cd6c2733dcfd24544ad21931ea815d" integrity sha512-bPOBM1a/v3Oo4svOKjQbjvBmaKDqCGfSLBtH2rrp1dj1Mk8Pr+hmvQYQZBHqfc0gTqddRST3gz6GGL2ZKovWUw== @@ -3652,7 +3652,7 @@ yn "^4.0.0" zod "^3.22.4" -"@backstage/plugin-permission-common@0.7.13", "@backstage/plugin-permission-common@^0.7.13": +"@backstage/plugin-permission-common@0.7.13", "@backstage/plugin-permission-common@^0.7.12", "@backstage/plugin-permission-common@^0.7.13", "@backstage/plugin-permission-common@^0.7.9": version "0.7.13" resolved "https://registry.yarnpkg.com/@backstage/plugin-permission-common/-/plugin-permission-common-0.7.13.tgz#ea8509d2a38063309b8726ee6be8b95e1f99e5b9" integrity sha512-FGC6qrQc96SuovRCWQARDKss7TRenusMX9i0k0Devx/0+h2jM0TYYtuJ52jAFSAx9Db3BRRSlj9M5AQFgjoNmg== @@ -3686,6 +3686,8 @@ resolved "https://registry.yarnpkg.com/@backstage/plugin-permission-react/-/plugin-permission-react-0.4.22.tgz#7a6d60a7ada0748ca7c23ccba64b1afc7b33045c" integrity sha512-FPGbx3jasbC/PoKTud7qYgprMop1MejmgqoV3CtWFnWlhICjxEcTTl+guK5EkYWxjIiJPRFrUjEuDqQ42Fsiqg== dependencies: + "@backstage/backend-common" "^0.21.7" + "@backstage/backend-plugin-api" "^0.6.17" "@backstage/config" "^1.2.0" "@backstage/core-plugin-api" "^1.9.2" "@backstage/plugin-permission-common" "^0.7.13" @@ -4094,7 +4096,7 @@ yn "^4.0.0" zod "^3.22.4" -"@backstage/plugin-search-common@^1.2.11": +"@backstage/plugin-search-common@^1.2.11", "@backstage/plugin-search-common@^1.2.7": version "1.2.11" resolved "https://registry.yarnpkg.com/@backstage/plugin-search-common/-/plugin-search-common-1.2.11.tgz#5563f9b7b5ff915d1fe0e0e213c9536029dac91c" integrity sha512-b2gmurxNdgY6LQ4E+BzITVUFF5jCewjlkI4/oppFTsk1IH+VfQyRDoGb8u2wuYKGCwvgVPgP3qUBEo25oGTZfg== @@ -4292,6 +4294,13 @@ react-use "^17.2.4" zen-observable "^0.10.0" +"@backstage/release-manifests@^0.0.10": + version "0.0.10" + resolved "https://registry.yarnpkg.com/@backstage/release-manifests/-/release-manifests-0.0.10.tgz#10f0c23cd1a00ca9b2ac5bf5c199592ba0abedae" + integrity sha512-MKGY1IqYGqItQyX+qbI+U0VPqgh9VqFIQreCOC8JXU1sh7v8XfR2DWZGmw1qVzVtIitNeqsNwIwIJyqtiMokow== + dependencies: + cross-fetch "^3.1.5" + "@backstage/release-manifests@^0.0.11": version "0.0.11" resolved "https://registry.yarnpkg.com/@backstage/release-manifests/-/release-manifests-0.0.11.tgz#e842816d249f6903c8121253358a3211425ac83e" @@ -5267,16 +5276,50 @@ arrify "^2.0.0" extend "^3.0.2" +"@google-cloud/projectify@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@google-cloud/projectify/-/projectify-3.0.0.tgz#302b25f55f674854dce65c2532d98919b118a408" + integrity sha512-HRkZsNmjScY6Li8/kb70wjGlDDyLkVk3KvoEo9uIoxSjYLJasGiCch9+PqRVDOCGUFvEIqyogl+BeqILL4OJHA== + "@google-cloud/projectify@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@google-cloud/projectify/-/projectify-4.0.0.tgz#d600e0433daf51b88c1fa95ac7f02e38e80a07be" integrity sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA== +"@google-cloud/promisify@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@google-cloud/promisify/-/promisify-3.0.1.tgz#8d724fb280f47d1ff99953aee0c1669b25238c2e" + integrity sha512-z1CjRjtQyBOYL+5Qr9DdYIfrdLBe746jRTYfaYU6MeXkqp7UfYs/jX16lFFVzZ7PGEJvqZNqYUEtb1mvDww4pA== + "@google-cloud/promisify@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@google-cloud/promisify/-/promisify-4.0.0.tgz#a906e533ebdd0f754dca2509933334ce58b8c8b1" integrity sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g== +"@google-cloud/storage@^6.0.0": + version "6.12.0" + resolved "https://registry.yarnpkg.com/@google-cloud/storage/-/storage-6.12.0.tgz#a5d3093cc075252dca5bd19a3cfda406ad3a9de1" + integrity sha512-78nNAY7iiZ4O/BouWMWTD/oSF2YtYgYB3GZirn0To6eBOugjXVoK+GXgUXOl+HlqbAOyHxAVXOlsj3snfbQ1dw== + dependencies: + "@google-cloud/paginator" "^3.0.7" + "@google-cloud/projectify" "^3.0.0" + "@google-cloud/promisify" "^3.0.0" + abort-controller "^3.0.0" + async-retry "^1.3.3" + compressible "^2.0.12" + duplexify "^4.0.0" + ent "^2.2.0" + extend "^3.0.2" + fast-xml-parser "^4.2.2" + gaxios "^5.0.0" + google-auth-library "^8.0.1" + mime "^3.0.0" + mime-types "^2.0.8" + p-limit "^3.0.1" + retry-request "^5.0.0" + teeny-request "^8.0.0" + uuid "^8.0.0" + "@google-cloud/storage@^7.0.0": version "7.11.0" resolved "https://registry.yarnpkg.com/@google-cloud/storage/-/storage-7.11.0.tgz#9dce7e887a7c425d7f09f9713134c286789165f4" @@ -5917,7 +5960,7 @@ "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.25" -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15": +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.13", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15": version "1.4.15" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== @@ -6349,6 +6392,28 @@ ajv-formats-draft2019 "^1.6.1" tslib "^2.4.0" +"@kubernetes/client-node@0.19.0": + version "0.19.0" + resolved "https://registry.yarnpkg.com/@kubernetes/client-node/-/client-node-0.19.0.tgz#ebd2121e5c8dc1a47ff1b2574bda1e760d0abb82" + integrity sha512-WTOjGuFQ8yeW3+qD6JrAYhpwpoQbe9R8cA/61WCyFrNawSTUgLstHu7EsZRYEs39er3jDn3wCEaczz+VOFlc2Q== + dependencies: + "@types/js-yaml" "^4.0.1" + "@types/node" "^20.1.1" + "@types/request" "^2.47.1" + "@types/ws" "^8.5.3" + byline "^5.0.0" + isomorphic-ws "^5.0.0" + js-yaml "^4.1.0" + jsonpath-plus "^7.2.0" + request "^2.88.0" + rfc4648 "^1.3.0" + stream-buffers "^3.0.2" + tar "^6.1.11" + tslib "^2.4.1" + ws "^8.11.0" + optionalDependencies: + openid-client "^5.3.0" + "@kubernetes/client-node@0.20.0", "@kubernetes/client-node@^0.20.0": version "0.20.0" resolved "https://registry.yarnpkg.com/@kubernetes/client-node/-/client-node-0.20.0.tgz#4447ae27fd6eef3d4830a5a039f3b84ffd5c5913" @@ -6789,8 +6854,6 @@ clsx "^2.1.0" csstype "^3.1.3" prop-types "^15.8.1" - react-is "^18.2.0" - react-transition-group "^4.4.5" "@mui/private-theming@^5.15.14": version "5.15.14" @@ -8774,6 +8837,13 @@ resolved "https://registry.yarnpkg.com/@react-hookz/deep-equal/-/deep-equal-1.0.4.tgz#68a71f36cbc88724b3ce6f4036183778b6e7f282" integrity sha512-N56fTrAPUDz/R423pag+n6TXWbvlBZDtTehaGFjK0InmN+V2OFWLE/WmORhmn6Ce7dlwH5+tQN1LJFw3ngTJVg== +"@react-hookz/web@^23.0.0": + version "23.1.0" + resolved "https://registry.yarnpkg.com/@react-hookz/web/-/web-23.1.0.tgz#4e9bf133c56519924b4c2988aca20d09387f5e0a" + integrity sha512-fvbURdsa1ukttbLR1ASE/XmqXP09vZ1PiCYppYeR1sNMzCrdkG0iBnjxniFSVjJ8gIw2fRs6nqMTbeBz2uAkuA== + dependencies: + "@react-hookz/deep-equal" "^1.0.4" + "@react-hookz/web@^24.0.0": version "24.0.4" resolved "https://registry.yarnpkg.com/@react-hookz/web/-/web-24.0.4.tgz#7a13d4c2cc65861b926ef6c4452fba00408c8778" @@ -8947,6 +9017,15 @@ js-yaml "^4.1.0" tosource "^2.0.0-alpha.3" +"@rollup/pluginutils@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b" + integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg== + dependencies: + "@types/estree" "0.0.39" + estree-walker "^1.0.1" + picomatch "^2.2.2" + "@rollup/pluginutils@^4.1.1", "@rollup/pluginutils@^4.2.1": version "4.2.1" resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.2.1.tgz#e6c6c3aba0744edce3fb2074922d3776c0af2a6d" @@ -13346,6 +13425,11 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== +"@types/estree@0.0.39": + version "0.0.39" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" + integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== + "@types/estree@^0.0.51": version "0.0.51" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40" @@ -13511,7 +13595,7 @@ jest-diff "^26.0.0" pretty-format "^26.0.0" -"@types/jest@^29.5.11": +"@types/jest@^29.0.0", "@types/jest@^29.5.11": version "29.5.12" resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.12.tgz#7f7dc6eb4cf246d2474ed78744b05d06ce025544" integrity sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw== @@ -13877,6 +13961,13 @@ "@types/tough-cookie" "*" form-data "^2.5.0" +"@types/resolve@1.17.1": + version "1.17.1" + resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6" + integrity sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw== + dependencies: + "@types/node" "*" + "@types/resolve@1.20.2": version "1.20.2" resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.20.2.tgz#97d26e00cd4a0423b4af620abecf3e6f442b7975" @@ -14186,6 +14277,16 @@ debug "^4.3.4" ts-api-utils "^1.0.1" +"@typescript-eslint/type-utils@6.7.5": + version "6.7.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.7.5.tgz#0a65949ec16588d8956f6d967f7d9c84ddb2d72a" + integrity sha512-Gs0qos5wqxnQrvpYv+pf3XfcRXW6jiAn9zE/K+DlmYf6FcpxeNYN0AIETaPR7rHO4K2UY+D0CIbDP9Ut0U4m1g== + dependencies: + "@typescript-eslint/typescript-estree" "6.7.5" + "@typescript-eslint/utils" "6.7.5" + debug "^4.3.4" + ts-api-utils "^1.0.1" + "@typescript-eslint/types@5.62.0": version "5.62.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" @@ -14228,6 +14329,19 @@ "@typescript-eslint/typescript-estree" "6.21.0" semver "^7.5.4" +"@typescript-eslint/utils@6.7.5": + version "6.7.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.7.5.tgz#ab847b53d6b65e029314b8247c2336843dba81ab" + integrity sha512-pfRRrH20thJbzPPlPc4j0UNGvH1PjPlhlCMq4Yx7EGjV7lvEeGX0U6MJYe8+SyFutWgSHsdbJ3BXzZccYggezA== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@types/json-schema" "^7.0.12" + "@types/semver" "^7.5.0" + "@typescript-eslint/scope-manager" "6.7.5" + "@typescript-eslint/types" "6.7.5" + "@typescript-eslint/typescript-estree" "6.7.5" + semver "^7.5.4" + "@typescript-eslint/utils@^5.10.0", "@typescript-eslint/utils@^5.62.0": version "5.62.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86" @@ -14914,7 +15028,7 @@ archiver-utils@^4.0.1: normalize-path "^3.0.0" readable-stream "^3.6.0" -archiver@^5.3.1, archiver@^5.3.2: +archiver@^5.0.2, archiver@^5.3.1, archiver@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/archiver/-/archiver-5.3.2.tgz#99991d5957e53bd0303a392979276ac4ddccf3b0" integrity sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw== @@ -15822,6 +15936,11 @@ bottleneck@^2.15.3: resolved "https://registry.yarnpkg.com/bottleneck/-/bottleneck-2.19.5.tgz#5df0b90f59fd47656ebe63c78a98419205cadd91" integrity sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw== +bowser@^1.7.3: + version "1.9.4" + resolved "https://registry.yarnpkg.com/bowser/-/bowser-1.9.4.tgz#890c58a2813a9d3243704334fa81b96a5c150c9a" + integrity sha512-9IdMmj2KjigRq6oWhmwv1W36pDuA4STQZ8q6YO9um+x07xgYNCD3Oou+WP/3L1HNz7iqythGet3/p4wvc8AAwQ== + bowser@^2.11.0: version "2.11.0" resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.11.0.tgz#5ca3c35757a7aa5771500c70a73a9f91ef420a8f" @@ -17565,6 +17684,14 @@ css-declaration-sorter@^6.3.1: resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz#28beac7c20bad7f1775be3a7129d7eae409a3a71" integrity sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g== +css-in-js-utils@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/css-in-js-utils/-/css-in-js-utils-2.0.1.tgz#3b472b398787291b47cfe3e44fecfdd9e914ba99" + integrity sha512-PJF0SpJT+WdbVVt0AOYp9C8GnuruRlL/UFW7932nLWmFLQTaWEzTBQEx7/hn4BuV+WON75iAViSUJLiU3PKbpA== + dependencies: + hyphenate-style-name "^1.0.2" + isobject "^3.0.1" + css-in-js-utils@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/css-in-js-utils/-/css-in-js-utils-3.1.0.tgz#640ae6a33646d401fc720c54fc61c42cd76ae2bb" @@ -18447,7 +18574,7 @@ delegates@^1.0.0: resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== -denque@^2.1.0: +denque@^2.0.1, denque@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1" integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw== @@ -18665,7 +18792,7 @@ docker-modem@^5.0.3: split-ca "^1.0.1" ssh2 "^1.15.0" -dockerode@^3.3.5: +dockerode@^3.3.1, dockerode@^3.3.5: version "3.3.5" resolved "https://registry.yarnpkg.com/dockerode/-/dockerode-3.3.5.tgz#7ae3f40f2bec53ae5e9a741ce655fff459745629" integrity sha512-/0YNa3ZDNeLr/tSckmD69+Gq+qVNhvKfAHNeZJBnp7EOP6RGKV8ORrJHkUn20So5wU+xxT7+1n5u8PjHbfjbSA== @@ -18975,7 +19102,7 @@ encodeurl@^1.0.2, encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== -encoding@^0.1.13: +encoding@^0.1.11, encoding@^0.1.13: version "0.1.13" resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== @@ -19286,7 +19413,7 @@ esbuild@^0.16.17: "@esbuild/win32-ia32" "0.16.17" "@esbuild/win32-x64" "0.16.17" -esbuild@^0.18.0: +esbuild@^0.18.0, esbuild@~0.18.20: version "0.18.20" resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.18.20.tgz#4709f5a34801b43b799ab7d6d82f7284a9b7a7a6" integrity sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA== @@ -19430,7 +19557,7 @@ escodegen@^2.0.0, escodegen@^2.1.0: optionalDependencies: source-map "~0.6.1" -eslint-config-prettier@^8.10.0: +eslint-config-prettier@^8.10.0, eslint-config-prettier@^8.3.0: version "8.10.0" resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz#3a06a662130807e2502fc3ff8b4143d8a0658e11" integrity sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg== @@ -19467,6 +19594,15 @@ eslint-module-utils@^2.8.0: dependencies: debug "^3.2.7" +eslint-plugin-deprecation@^1.3.2: + version "1.6.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-deprecation/-/eslint-plugin-deprecation-1.6.0.tgz#b12d0c5a9baf3bcde0752ff6337703c059a4ae23" + integrity sha512-rld+Vrneh/NXRtDB0vQifOvgUy0HJYoejaxWlVnsk/LK7iij2tCWQIFcCKG4uzQb+Ef86bDke39w1lh4wnon4Q== + dependencies: + "@typescript-eslint/utils" "^6.0.0" + tslib "^2.3.1" + tsutils "^3.21.0" + eslint-plugin-deprecation@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/eslint-plugin-deprecation/-/eslint-plugin-deprecation-2.0.0.tgz#9804707a4c19f3a53615c6babc0ced3d429d69cf" @@ -19600,7 +19736,7 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4 resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== -eslint-webpack-plugin@^3.2.0: +eslint-webpack-plugin@^3.1.1, eslint-webpack-plugin@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/eslint-webpack-plugin/-/eslint-webpack-plugin-3.2.0.tgz#1978cdb9edc461e4b0195a20da950cf57988347c" integrity sha512-avrKcGncpPbPSUHX6B3stNGzkKFto3eL+DKM4+VyMrVnhPc3vRczVlCq3uhuFOdRvDHTVXuzwk1ZKUrqDQHQ9w== @@ -19733,6 +19869,11 @@ estree-walker@^0.6.1: resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.1.tgz#53049143f40c6eb918b23671d1fe3219f3a1b362" integrity sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w== +estree-walker@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700" + integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg== + estree-walker@^2.0.1, estree-walker@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" @@ -20136,6 +20277,11 @@ fast-shallow-equal@^1.0.0: resolved "https://registry.yarnpkg.com/fast-shallow-equal/-/fast-shallow-equal-1.0.0.tgz#d4dcaf6472440dcefa6f88b98e3251e27f25628b" integrity sha512-HPtaa38cPgWvaCFmRNhlc6NG7pv6NUHqjPgVAkWGoB9mQMwYB27/K0CvOM5Czy+qpT3e8XJ6Q4aPAnzpNpzNaw== +fast-text-encoding@^1.0.0: + version "1.0.6" + resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz#0aa25f7f638222e3396d72bf936afcf1d42d6867" + integrity sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w== + fast-xml-parser@4.2.5: version "4.2.5" resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.2.5.tgz#a6747a09296a6cb34f2ae634019bf1738f3b421f" @@ -20200,6 +20346,19 @@ fbjs-css-vars@^1.0.0: resolved "https://registry.yarnpkg.com/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz#216551136ae02fe255932c3ec8775f18e2c078b8" integrity sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ== +fbjs@^0.8.12: + version "0.8.18" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.18.tgz#9835e0addb9aca2eff53295cd79ca1cfc7c9662a" + integrity sha512-EQaWFK+fEPSoibjNy8IxUtaFOMXcWsY0JaVrQoZR9zC8N2Ygf9iDITPWjUTVIax95b6I742JFLqASHfsag/vKA== + dependencies: + core-js "^1.0.0" + isomorphic-fetch "^2.1.1" + loose-envify "^1.0.0" + object-assign "^4.1.0" + promise "^7.1.1" + setimmediate "^1.0.5" + ua-parser-js "^0.7.30" + fbjs@^3.0.0, fbjs@^3.0.1: version "3.0.5" resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-3.0.5.tgz#aa0edb7d5caa6340011790bd9249dbef8a81128d" @@ -20896,6 +21055,16 @@ gauge@^5.0.0: strip-ansi "^6.0.1" wide-align "^1.1.5" +gaxios@^5.0.0, gaxios@^5.0.1: + version "5.1.3" + resolved "https://registry.yarnpkg.com/gaxios/-/gaxios-5.1.3.tgz#f7fa92da0fe197c846441e5ead2573d4979e9013" + integrity sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA== + dependencies: + extend "^3.0.2" + https-proxy-agent "^5.0.0" + is-stream "^2.0.0" + node-fetch "^2.6.9" + gaxios@^6.0.0, gaxios@^6.0.2, gaxios@^6.1.1: version "6.5.0" resolved "https://registry.yarnpkg.com/gaxios/-/gaxios-6.5.0.tgz#21bc20e24f21189ce8907079b56205ff9fd2c0d7" @@ -21095,6 +21264,17 @@ github-slugger@^1.0.0: resolved "https://registry.yarnpkg.com/github-slugger/-/github-slugger-1.5.0.tgz#17891bbc73232051474d68bd867a34625c955f7d" integrity sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw== +glamor@^2.20.40: + version "2.20.40" + resolved "https://registry.yarnpkg.com/glamor/-/glamor-2.20.40.tgz#f606660357b7cf18dface731ad1a2cfa93817f05" + integrity sha512-DNXCd+c14N9QF8aAKrfl4xakPk5FdcFwmH7sD0qnC0Pr7xoZ5W9yovhUrY/dJc3psfGGXC58vqQyRtuskyUJxA== + dependencies: + fbjs "^0.8.12" + inline-style-prefixer "^3.0.6" + object-assign "^4.1.1" + prop-types "^15.5.10" + through "^2.3.8" + glob-parent@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" @@ -21298,6 +21478,13 @@ google-gax@^4.3.3: retry-request "^7.0.0" uuid "^9.0.1" +google-p12-pem@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/google-p12-pem/-/google-p12-pem-4.0.1.tgz#82841798253c65b7dc2a4e5fe9df141db670172a" + integrity sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ== + dependencies: + node-forge "^1.3.1" + gopd@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" @@ -21406,6 +21593,15 @@ graphql@^16.0.0, graphql@^16.8.1: resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.8.1.tgz#1930a965bef1170603702acdb68aedd3f3cf6f07" integrity sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw== +gtoken@^6.1.0: + version "6.1.2" + resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-6.1.2.tgz#aeb7bdb019ff4c3ba3ac100bbe7b6e74dce0e8bc" + integrity sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ== + dependencies: + gaxios "^5.0.1" + google-p12-pem "^4.0.0" + jws "^4.0.0" + gtoken@^7.0.0: version "7.1.0" resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-7.1.0.tgz#d61b4ebd10132222817f7222b1e6064bd463fc26" @@ -21987,7 +22183,7 @@ hyperlinker@^1.0.0: resolved "https://registry.yarnpkg.com/hyperlinker/-/hyperlinker-1.0.0.tgz#23dc9e38a206b208ee49bc2d6c8ef47027df0c0e" integrity sha512-Ty8UblRWFEcfSuIaajM34LdPXIhbs1ajEX/BBPv24J+enSVaEVY63xQ6lTO9VRYS5LAoghIG0IDJ+p+IPzKUQQ== -hyphenate-style-name@^1.0.3: +hyphenate-style-name@^1.0.2, hyphenate-style-name@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz#691879af8e220aea5750e8827db4ef62a54e361d" integrity sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ== @@ -22459,7 +22655,7 @@ is-buffer@^2.0.0: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== -is-builtin-module@^3.2.1: +is-builtin-module@^3.1.0, is-builtin-module@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.2.1.tgz#f03271717d8654cfcaf07ab0463faa3571581169" integrity sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A== @@ -22823,6 +23019,11 @@ is-ssh@^1.4.0: dependencies: protocols "^2.0.1" +is-stream@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + integrity sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ== + is-stream@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" @@ -22963,6 +23164,14 @@ isomorphic-dompurify@^0.13.0: dompurify "^2.2.7" jsdom "^16.5.2" +isomorphic-fetch@^2.1.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" + integrity sha512-9c4TNAKYXM5PRyVcwUZrF3W09nQ+sO7+jydgs4ZGW9dhsLG2VOlISJABombdQqQRXCwuYG3sYV/puGf5rp0qmA== + dependencies: + node-fetch "^1.0.1" + whatwg-fetch ">=0.10.0" + isomorphic-form-data@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isomorphic-form-data/-/isomorphic-form-data-2.0.0.tgz#9f6adf1c4c61ae3aefd8f110ab60fb9b143d6cec" @@ -23571,7 +23780,7 @@ jest-worker@^29.7.0: merge-stream "^2.0.0" supports-color "^8.0.0" -jest@^29.7.0: +jest@^29.0.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== @@ -23977,11 +24186,6 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" -jsonify@^0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.1.tgz#2aa3111dae3d34a0f151c63f3a45d995d9420978" - integrity sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg== - jsonparse@^1.2.0, jsonparse@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" @@ -25120,6 +25324,11 @@ logform@^2.3.2, logform@^2.4.0: safe-stable-stringify "^2.3.1" triple-beam "^1.3.0" +long@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" + integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== + long@^5.0.0, long@^5.2.1: version "5.2.3" resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1" @@ -26533,6 +26742,20 @@ mute-stream@^1.0.0, mute-stream@~1.0.0: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-1.0.0.tgz#e31bd9fe62f0aed23520aa4324ea6671531e013e" integrity sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA== +mysql2@^2.2.5: + version "2.3.3" + resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-2.3.3.tgz#944f3deca4b16629052ff8614fbf89d5552545a0" + integrity sha512-wxJUev6LgMSgACDkb/InIFxDprRa6T95+VEoR+xPvtngtccNH2dGjEB/fVZ8yg1gWv1510c9CvXuJHi5zUm0ZA== + dependencies: + denque "^2.0.1" + generate-function "^2.3.1" + iconv-lite "^0.6.3" + long "^4.0.0" + lru-cache "^6.0.0" + named-placeholders "^1.1.2" + seq-queue "^0.0.5" + sqlstring "^2.3.2" + mysql2@^3.0.0: version "3.9.7" resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-3.9.7.tgz#843755daf65b5ef08afe545fe14b8fb62824741a" @@ -26556,7 +26779,7 @@ mz@^2.4.0, mz@^2.7.0: object-assign "^4.0.1" thenify-all "^1.0.0" -named-placeholders@^1.1.3: +named-placeholders@^1.1.2, named-placeholders@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/named-placeholders/-/named-placeholders-1.1.3.tgz#df595799a36654da55dda6152ba7a137ad1d9351" integrity sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w== @@ -26764,6 +26987,14 @@ node-fetch@2.6.7: dependencies: whatwg-url "^5.0.0" +node-fetch@^1.0.1: + version "1.7.3" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" + integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ== + dependencies: + encoding "^0.1.11" + is-stream "^1.0.1" + node-fetch@^2.0.0, node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.12, node-fetch@^2.6.7, node-fetch@^2.6.9, node-fetch@^2.7.0: version "2.7.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" @@ -30025,6 +30256,13 @@ react-moment@^1.1.3: resolved "https://registry.yarnpkg.com/react-moment/-/react-moment-1.1.3.tgz#829b21dfb279aa6db47ce4f1ac2555af17a1bcdc" integrity sha512-8EPvlUL8u6EknPp1ISF5MQ3wx2OHJVXIP/iZc4wRh3iV3XozftZERDv9ANZeAtMlhNNQHdFoqcZHFUkBSTONfA== +react-multi-progress@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/react-multi-progress/-/react-multi-progress-1.3.0.tgz#c36ee7a019357de30770b47e1bc072604439b6a5" + integrity sha512-uWwcDCBQNlccuyWUVYUBslVoGCvvFXO4GMsHZnlymvIjMMUeY+tjWdXyEtWgdVh3Qgz9mTdDMx8fHtX7bxLjyg== + dependencies: + glamor "^2.20.40" + react-redux@^7.2.0: version "7.2.9" resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.9.tgz#09488fbb9416a4efe3735b7235055442b042481d" @@ -30102,6 +30340,13 @@ react-router@6.23.0, react-router@^6.23.0: dependencies: "@remix-run/router" "1.16.0" +react-router@6.23.0, react-router@^6.17.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.23.0.tgz#2f2d7492c66a6bdf760be4c6bdf9e1d672fa154b" + integrity sha512-wPMZ8S2TuPadH0sF5irFGjkNLIcRvOSaEe7v+JER8508dyJumm6XZB1u5kztlX0RVq6AzRVndzqcUh6sFIauzA== + dependencies: + "@remix-run/router" "1.16.0" + react-side-effect@^2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-2.1.2.tgz#dc6345b9e8f9906dc2eeb68700b615e0b4fe752a" @@ -30892,6 +31137,14 @@ ret@~0.1.10: resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== +retry-request@^5.0.0: + version "5.0.2" + resolved "https://registry.yarnpkg.com/retry-request/-/retry-request-5.0.2.tgz#143d85f90c755af407fcc46b7166a4ba520e44da" + integrity sha512-wfI3pk7EE80lCIXprqh7ym48IHYdwmAAzESdbU8Q9l7pnRCk9LEhpbOTNKjz6FARLm/Bl5m+4F0ABxOkYUujSQ== + dependencies: + debug "^4.1.1" + extend "^3.0.2" + retry-request@^7.0.0: version "7.0.2" resolved "https://registry.yarnpkg.com/retry-request/-/retry-request-7.0.2.tgz#60bf48cfb424ec01b03fca6665dee91d06dd95f3" @@ -32894,6 +33147,17 @@ tdigest@^0.1.1: dependencies: bintrees "1.0.2" +teeny-request@^8.0.0: + version "8.0.3" + resolved "https://registry.yarnpkg.com/teeny-request/-/teeny-request-8.0.3.tgz#5cb9c471ef5e59f2fca8280dc3c5909595e6ca24" + integrity sha512-jJZpA5He2y52yUhA7pyAGZlgQpcB+xLjcN0eUFxr9c8hP/H7uOXbBNVo/O0C/xVfJLJs680jvkFgVJEEvk9+ww== + dependencies: + http-proxy-agent "^5.0.0" + https-proxy-agent "^5.0.0" + node-fetch "^2.6.1" + stream-events "^1.0.5" + uuid "^9.0.0" + teeny-request@^9.0.0: version "9.0.0" resolved "https://registry.yarnpkg.com/teeny-request/-/teeny-request-9.0.0.tgz#18140de2eb6595771b1b02203312dfad79a4716d" @@ -33045,7 +33309,7 @@ through2@^4.0.0: dependencies: readable-stream "3" -through@2, "through@>=2.2.7 <3", through@^2.3.6, through@~2.3: +through@2, "through@>=2.2.7 <3", through@^2.3.6, through@^2.3.8, through@~2.3: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== @@ -33780,6 +34044,11 @@ typestyle@^2.4.0: csstype "3.0.10" free-style "3.1.0" +ua-parser-js@^0.7.30: + version "0.7.37" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.37.tgz#e464e66dac2d33a7a1251d7d7a99d6157ec27832" + integrity sha512-xV8kqRKM+jhMvcHWUKthV9fNebIzrNy//2O9ZwWcfiBFR5f25XVZPLlEajk/sf3Ra15V92isyQqnIEXRDaZWEA== + ua-parser-js@^1.0.35: version "1.0.37" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.37.tgz#b5dc7b163a5c1f0c510b08446aed4da92c46373f"