From 38b17fe361dadd9a98a7ca95bd5c6e39bfb3f2ce Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Oct 2020 04:45:38 +0000 Subject: [PATCH 001/758] chore(deps-dev): bump @testing-library/cypress from 6.0.1 to 7.0.1 Bumps [@testing-library/cypress](https://github.com/kentcdodds/cypress-testing-library) from 6.0.1 to 7.0.1. - [Release notes](https://github.com/kentcdodds/cypress-testing-library/releases) - [Changelog](https://github.com/testing-library/cypress-testing-library/blob/master/CHANGELOG.md) - [Commits](https://github.com/kentcdodds/cypress-testing-library/compare/v6.0.1...v7.0.1) Signed-off-by: dependabot[bot] --- packages/app/package.json | 2 +- yarn.lock | 21 ++++++--------------- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/packages/app/package.json b/packages/app/package.json index e8dfb94046..a6875ba86f 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -46,7 +46,7 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@testing-library/cypress": "^6.0.0", + "@testing-library/cypress": "^7.0.1", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/yarn.lock b/yarn.lock index a3b64f0650..d17878a727 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4467,16 +4467,15 @@ dependencies: defer-to-connect "^2.0.0" -"@testing-library/cypress@^6.0.0": - version "6.0.1" - resolved "https://registry.npmjs.org/@testing-library/cypress/-/cypress-6.0.1.tgz#c924a69617db6a403c498abcb63759b79318fc76" - integrity sha512-hcPu2OnVuSTX1yDubEe7TBRD6mP2VgyopP1WTBIrJP79INPZdgOAX+TMNH68uZ/r5b4C7100IB4hjPIEQ+/Xog== +"@testing-library/cypress@^7.0.1": + version "7.0.1" + resolved "https://registry.npmjs.org/@testing-library/cypress/-/cypress-7.0.1.tgz#2843033acaefe96cb4cf789f16e98d957383e59d" + integrity sha512-LtggqG/7Hdc1EiKdmqXQwxWOO3ET1dkZtq0S8mIe8o+xaOtaVLrdCn0dE8Bi4Aj7z3w51w6wN9STdYymnUPlnQ== dependencies: "@babel/runtime" "^7.11.2" "@testing-library/dom" "^7.22.2" - "@types/testing-library__cypress" "^5.0.6" -"@testing-library/dom@^7.11.0", "@testing-library/dom@^7.17.1", "@testing-library/dom@^7.22.2": +"@testing-library/dom@^7.17.1", "@testing-library/dom@^7.22.2": version "7.23.0" resolved "https://registry.npmjs.org/@testing-library/dom/-/dom-7.23.0.tgz#c54c0fa53705ad867bcefb52fc0c96487fbc10f6" integrity sha512-H5m090auYH+obdZmsaYLrSWC5OauWD2CvNbz88KBxQJoXgkJzbU0DpAG8BS7Evj5WqCC3nAAKrLS6vw0ljUYLg== @@ -5600,14 +5599,6 @@ dependencies: "@types/estree" "*" -"@types/testing-library__cypress@^5.0.6": - version "5.0.6" - resolved "https://registry.npmjs.org/@types/testing-library__cypress/-/testing-library__cypress-5.0.6.tgz#9015f575c1a98f05996a4fe769071134ee488c26" - integrity sha512-TUp5wfanU7zUZigKqIeQDChnHQ1MEzbYqrI5iCQMFiesWNOASWm/el1lFBh1JPqmd6GkdDdDiHYJnkqd9le2ww== - dependencies: - "@testing-library/dom" "^7.11.0" - cypress "*" - "@types/testing-library__jest-dom@^5.9.1": version "5.9.1" resolved "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.9.1.tgz#aba5ee062b7880f69c212ef769389f30752806e5" @@ -9338,7 +9329,7 @@ cyclist@^1.0.1: resolved "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= -cypress@*, cypress@^4.2.0: +cypress@^4.2.0: version "4.12.1" resolved "https://registry.npmjs.org/cypress/-/cypress-4.12.1.tgz#0ead1b9f4c0917d69d8b57f996b6e01fe693b6ec" integrity sha512-9SGIPEmqU8vuRA6xst2CMTYd9sCFCxKSzrHt0wr+w2iAQMCIIsXsQ5Gplns1sT6LDbZcmLv6uehabAOl3fhc9Q== From 17e7efdc9f47e41723e858013862b529da9cb6de Mon Sep 17 00:00:00 2001 From: Nikita Nek Dudnik Date: Thu, 8 Oct 2020 15:06:32 +0200 Subject: [PATCH 002/758] remove on close from component creation modal --- .../src/components/JobStatusModal/JobStatusModal.tsx | 10 ++-------- .../MultistepJsonForm/MultistepJsonForm.tsx | 12 ++++++++---- .../src/components/TemplatePage/TemplatePage.tsx | 2 -- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/plugins/scaffolder/src/components/JobStatusModal/JobStatusModal.tsx b/plugins/scaffolder/src/components/JobStatusModal/JobStatusModal.tsx index c531e0ec2c..149f7a47d7 100644 --- a/plugins/scaffolder/src/components/JobStatusModal/JobStatusModal.tsx +++ b/plugins/scaffolder/src/components/JobStatusModal/JobStatusModal.tsx @@ -30,18 +30,12 @@ import { entityRoute } from '@backstage/plugin-catalog'; import { generatePath } from 'react-router-dom'; type Props = { - onClose: () => void; onComplete: (job: Job) => void; jobId: string; entity: TemplateEntityV1alpha1 | null; }; -export const JobStatusModal = ({ - onClose, - jobId, - onComplete, - entity, -}: Props) => { +export const JobStatusModal = ({ jobId, onComplete, entity }: Props) => { const job = useJobPolling(jobId); const [dialogTitle, setDialogTitle] = useState('Creating component...'); @@ -54,7 +48,7 @@ export const JobStatusModal = ({ }, [job, onComplete, setDialogTitle]); return ( - + {dialogTitle} {!job ? ( diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx index f0ba720b39..9700d43c00 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx @@ -27,7 +27,7 @@ import { } from '@material-ui/core'; import { FormProps, IChangeEvent, withTheme } from '@rjsf/core'; import { Theme as MuiTheme } from '@rjsf/material-ui'; -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; const Form = withTheme(MuiTheme); type Step = { @@ -54,6 +54,7 @@ export const MultistepJsonForm = ({ onFinish, }: Props) => { const [activeStep, setActiveStep] = useState(0); + const [formDataEvent, setFormDataEvent] = useState({ formData: {} }); const handleReset = () => { setActiveStep(0); @@ -62,18 +63,21 @@ export const MultistepJsonForm = ({ const handleNext = () => setActiveStep(Math.min(activeStep + 1, steps.length)); const handleBack = () => setActiveStep(Math.max(activeStep - 1, 0)); - + useEffect(() => { + onChange(formDataEvent as IChangeEvent); + }, [formDataEvent, onChange]); return ( <> {steps.map(({ label, schema, ...formProps }) => ( {label} - +
setFormDataEvent(e)} schema={schema as FormProps['schema']} onSubmit={e => { if (e.errors.length === 0) handleNext(); diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx index e68c795bd6..19196d39ad 100644 --- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx +++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx @@ -93,7 +93,6 @@ export const TemplatePage = () => { setFormState({ ...formState, ...e.formData }); const [jobId, setJobId] = useState(null); - const handleClose = () => setJobId(null); const handleCreate = async () => { try { @@ -161,7 +160,6 @@ export const TemplatePage = () => { )} From 5539625f645704e97fabc72eadc9fcf5094cf6b4 Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Mon, 19 Oct 2020 14:15:46 +0200 Subject: [PATCH 003/758] create pagerduty plugin --- packages/app/package.json | 1 + packages/app/src/plugins.ts | 1 + plugins/pagerduty/.eslintrc.js | 3 ++ plugins/pagerduty/README.md | 13 ++++++++ plugins/pagerduty/dev/index.tsx | 19 ++++++++++++ plugins/pagerduty/package.json | 46 ++++++++++++++++++++++++++++ plugins/pagerduty/src/index.ts | 16 ++++++++++ plugins/pagerduty/src/plugin.test.ts | 22 +++++++++++++ plugins/pagerduty/src/plugin.ts | 25 +++++++++++++++ plugins/pagerduty/src/setupTests.ts | 18 +++++++++++ 10 files changed, 164 insertions(+) create mode 100644 plugins/pagerduty/.eslintrc.js create mode 100644 plugins/pagerduty/README.md create mode 100644 plugins/pagerduty/dev/index.tsx create mode 100644 plugins/pagerduty/package.json create mode 100644 plugins/pagerduty/src/index.ts create mode 100644 plugins/pagerduty/src/plugin.test.ts create mode 100644 plugins/pagerduty/src/plugin.ts create mode 100644 plugins/pagerduty/src/setupTests.ts diff --git a/packages/app/package.json b/packages/app/package.json index 65ba6140b2..487e4ad0c5 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -21,6 +21,7 @@ "@backstage/plugin-kubernetes": "^0.1.1-alpha.25", "@backstage/plugin-lighthouse": "^0.1.1-alpha.25", "@backstage/plugin-newrelic": "^0.1.1-alpha.25", + "@backstage/plugin-pagerduty": "^0.1.1-alpha.25", "@backstage/plugin-register-component": "^0.1.1-alpha.25", "@backstage/plugin-rollbar": "^0.1.1-alpha.25", "@backstage/plugin-scaffolder": "^0.1.1-alpha.25", diff --git a/packages/app/src/plugins.ts b/packages/app/src/plugins.ts index bdc3851318..3851556882 100644 --- a/packages/app/src/plugins.ts +++ b/packages/app/src/plugins.ts @@ -38,3 +38,4 @@ export { plugin as Cloudbuild } from '@backstage/plugin-cloudbuild'; export { plugin as CostInsights } from '@backstage/plugin-cost-insights'; export { plugin as GitHubInsights } from '@roadiehq/backstage-plugin-github-insights'; export { plugin as UserSettings } from '@backstage/plugin-user-settings'; +export { plugin as Pagerduty } from '@backstage/plugin-pagerduty'; diff --git a/plugins/pagerduty/.eslintrc.js b/plugins/pagerduty/.eslintrc.js new file mode 100644 index 0000000000..13573efa9c --- /dev/null +++ b/plugins/pagerduty/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint')], +}; diff --git a/plugins/pagerduty/README.md b/plugins/pagerduty/README.md new file mode 100644 index 0000000000..0335cb5a9b --- /dev/null +++ b/plugins/pagerduty/README.md @@ -0,0 +1,13 @@ +# pagerduty + +Welcome to the pagerduty 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 [/pagerduty](http://localhost:3000/pagerduty). + +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. diff --git a/plugins/pagerduty/dev/index.tsx b/plugins/pagerduty/dev/index.tsx new file mode 100644 index 0000000000..264d6f801f --- /dev/null +++ b/plugins/pagerduty/dev/index.tsx @@ -0,0 +1,19 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createDevApp } from '@backstage/dev-utils'; +import { plugin } from '../src/plugin'; + +createDevApp().registerPlugin(plugin).render(); diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json new file mode 100644 index 0000000000..33054c6163 --- /dev/null +++ b/plugins/pagerduty/package.json @@ -0,0 +1,46 @@ +{ + "name": "@backstage/plugin-pagerduty", + "version": "0.1.1-alpha.25", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "publishConfig": { + "access": "public", + "main": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "scripts": { + "build": "backstage-cli plugin:build", + "start": "backstage-cli plugin:serve", + "lint": "backstage-cli lint", + "test": "backstage-cli test", + "diff": "backstage-cli plugin:diff", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", + "clean": "backstage-cli clean" + }, + "dependencies": { + "@backstage/core": "^0.1.1-alpha.25", + "@backstage/theme": "^0.1.1-alpha.25", + "@material-ui/core": "^4.11.0", + "@material-ui/icons": "^4.9.1", + "@material-ui/lab": "4.0.0-alpha.45", + "react": "^16.13.1", + "react-dom": "^16.13.1", + "react-use": "^15.3.3" + }, + "devDependencies": { + "@backstage/cli": "^0.1.1-alpha.25", + "@backstage/dev-utils": "^0.1.1-alpha.25", + "@testing-library/jest-dom": "^5.10.1", + "@testing-library/react": "^10.4.1", + "@testing-library/user-event": "^12.0.7", + "@types/jest": "^26.0.7", + "@types/node": "^12.0.0", + "msw": "^0.20.5", + "node-fetch": "^2.6.1" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/pagerduty/src/index.ts b/plugins/pagerduty/src/index.ts new file mode 100644 index 0000000000..224e293890 --- /dev/null +++ b/plugins/pagerduty/src/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { plugin } from './plugin'; diff --git a/plugins/pagerduty/src/plugin.test.ts b/plugins/pagerduty/src/plugin.test.ts new file mode 100644 index 0000000000..8d4545ac12 --- /dev/null +++ b/plugins/pagerduty/src/plugin.test.ts @@ -0,0 +1,22 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { plugin } from './plugin'; + +describe('pagerduty', () => { + it('should export plugin', () => { + expect(plugin).toBeDefined(); + }); +}); diff --git a/plugins/pagerduty/src/plugin.ts b/plugins/pagerduty/src/plugin.ts new file mode 100644 index 0000000000..8f8523b78b --- /dev/null +++ b/plugins/pagerduty/src/plugin.ts @@ -0,0 +1,25 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createPlugin, createRouteRef } from '@backstage/core'; + +export const rootRouteRef = createRouteRef({ + path: '/pagerduty', + title: 'pagerduty', +}); + +export const plugin = createPlugin({ + id: 'pagerduty', +}); diff --git a/plugins/pagerduty/src/setupTests.ts b/plugins/pagerduty/src/setupTests.ts new file mode 100644 index 0000000000..d7857386de --- /dev/null +++ b/plugins/pagerduty/src/setupTests.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import '@testing-library/jest-dom'; + +global.fetch = require('node-fetch'); From 47e9c6d56bd8fac1cb3d06cfefeb31e5646a9235 Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Tue, 20 Oct 2020 09:55:32 +0200 Subject: [PATCH 004/758] add main functionality --- .../src/components/PagerDutyServiceCard.tsx | 294 ++++++++++++++++++ plugins/pagerduty/src/components/pd.svg | 13 + plugins/pagerduty/src/index.ts | 4 + 3 files changed, 311 insertions(+) create mode 100644 plugins/pagerduty/src/components/PagerDutyServiceCard.tsx create mode 100644 plugins/pagerduty/src/components/pd.svg diff --git a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx new file mode 100644 index 0000000000..3dadacadfb --- /dev/null +++ b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx @@ -0,0 +1,294 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { + InfoCard, + StatusError, + StatusWarning, + StatusOK, +} from '@backstage/core'; +import { Entity } from '@backstage/catalog-model'; +import { + List, + ListSubheader, + ListItem, + ListItemIcon, + ListItemSecondaryAction, + Tooltip, + ListItemText, + makeStyles, + IconButton, +} from '@material-ui/core'; +import moment from 'moment'; +import Pagerduty from './pd.svg'; +import UserIcon from '@material-ui/icons/Person'; +import EmailIcon from '@material-ui/icons/Email'; + +const useStyles = makeStyles({ + buttonContainer: {}, + denseListIcon: { + marginRight: 0, + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + }, + svgButtonImage: { + height: '1em', + }, +}); + +type IncidentListProps = { + incidents: any; +}; + +const IncidentList = ({ incidents }: IncidentListProps) => { + const classes = useStyles(); + return incidents.map((incident: any) => ( + + + +
+ {incident.status === 'triggered' ? ( + + ) : ( + + )} +
+
+
+ + Created {moment(incident.createdAt).fromNow()}, assigned to{' '} + {(incident.assignees.length && incident.assignees[0].name) || + 'nobody'} + + } + /> + + + + View in PagerDuty + + + +
+ )); +}; + +const IncidentsEmptyState = () => { + const classes = useStyles(); + return ( + + +
+ +
+
+ +
+ ); +}; + +type IncidentsProps = { + incidents: Array; +}; +export const Incidents = ({ incidents }: IncidentsProps) => ( + Incidents}> + {incidents.length ? ( + + ) : ( + + )} + +); + +type EscalationProps = { + escalation: PagerDutyUserData[]; +}; + +const EscalationUsers = ({ escalation }: EscalationProps) => { + const classes = useStyles(); + const escalations = escalation.map((user, index) => ( + + + + + + + + + + + + + + View in PagerDuty + + + + + )); + return
{escalations}
; +}; + +const EscalationUsersEmptyState = () => { + const classes = useStyles(); + return ( + + +
+ +
+
+ +
+ ); +}; + +const EscalationPolicy = ({ escalation }: EscalationProps) => ( + Escalation Policy}> + {escalation.length ? ( + + ) : ( + + )} + +); + +type CardBodyProps = { + entity: Entity; + data: PagerDutyData; +}; + +const CardBody = ({ entity, data }: CardBodyProps) => { + const { pagerDutyServices } = data; + const classes = useStyles(); + + if (!pagerDutyServices.length) { + return ( +
+ 'The specified PagerDuty integration key does not match any PagerDuty + service.' +
+ ); + } + + const { activeIncidents, escalationPolicy } = pagerDutyServices[0]; + + return ( + <> + + +
+ {/* */} +
+ + ); +}; + +type PagerDutyIncident = { + id: string; + title: string; + status: string; + createdAt: string; + homepageUrl: string; + assignees: Partial[]; +}; + +type PagerDutyUserData = { + email: string; + name: string; + homepageUrl: string; + id: string; +}; + +type PagerDutyServicesData = { + activeIncidents: PagerDutyIncident[]; + escalationPolicy: PagerDutyUserData[]; + id: string; + name: string; + homepageUrl: string; +}; + +type PagerDutyData = { + pagerDutyServices: PagerDutyServicesData[]; +}; + +type Props = { + entity: Entity; +}; + +export const PagerDutyServiceCard = ({ entity }: Props) => { + // TODO: fetch this data + const mockData: PagerDutyData = { + pagerDutyServices: [ + { + activeIncidents: [ + { + id: 'id', + title: 'something happened', + status: 'triggered', + createdAt: '2020:01:01', + homepageUrl: 'url', + assignees: [ + { + name: 'name', + }, + ], + }, + ], + escalationPolicy: [ + { + email: 'user@example.com', + name: 'Name', + homepageUrl: 'https://spotify.pagerduty.com/users/FOO', + id: 'user id', + }, + ], + id: 'id', + name: 'name', + homepageUrl: 'https://spotify.pagety.com/service-directory/BAR', + }, + ], + }; + + return ( + + + + ); +}; + +export const isPluginApplicableToEntity = (entity: Entity) => true; diff --git a/plugins/pagerduty/src/components/pd.svg b/plugins/pagerduty/src/components/pd.svg new file mode 100644 index 0000000000..237ffa2011 --- /dev/null +++ b/plugins/pagerduty/src/components/pd.svg @@ -0,0 +1,13 @@ + + + + pd_icon + Created with Sketch. + + + + + + + + diff --git a/plugins/pagerduty/src/index.ts b/plugins/pagerduty/src/index.ts index 224e293890..b24018887a 100644 --- a/plugins/pagerduty/src/index.ts +++ b/plugins/pagerduty/src/index.ts @@ -14,3 +14,7 @@ * limitations under the License. */ export { plugin } from './plugin'; +export { + isPluginApplicableToEntity, + PagerDutyServiceCard, +} from './components/PagerDutyServiceCard'; From e8854140c3f8af226f3d2a1bf8acaadb562deab2 Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Tue, 20 Oct 2020 09:56:55 +0200 Subject: [PATCH 005/758] add Pagerduty card to entity overview --- packages/app/src/components/catalog/EntityPage.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index f30e7ff34f..51467fc2bb 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -66,6 +66,10 @@ import { isPluginApplicableToEntity as isPullRequestsAvailable, PullRequestsStatsCard, } from '@roadiehq/backstage-plugin-github-pull-requests'; +import { + isPluginApplicableToEntity as isPagerDutyAvailable, + PagerDutyServiceCard, +} from '@backstage/plugin-pagerduty'; const CICDSwitcher = ({ entity }: { entity: Entity }) => { // This component is just an example of how you can implement your company's logic in entity page. @@ -131,6 +135,11 @@ const OverviewContent = ({ entity }: { entity: Entity }) => ( + {isPagerDutyAvailable(entity) && ( + + + + )} {isGitHubAvailable(entity) && ( <> From 82fbe62743b45cbab7d3bac590532f1baabdbb64 Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Tue, 20 Oct 2020 11:21:18 +0200 Subject: [PATCH 006/758] splitted up component to separate files --- .../pagerduty/src/components/Escalation.tsx | 112 ++++++++ .../pagerduty/src/components/Incidents.tsx | 120 +++++++++ .../src/components/PagerDutyServiceCard.tsx | 253 ++---------------- plugins/pagerduty/src/components/types.tsx | 27 ++ 4 files changed, 280 insertions(+), 232 deletions(-) create mode 100644 plugins/pagerduty/src/components/Escalation.tsx create mode 100644 plugins/pagerduty/src/components/Incidents.tsx create mode 100644 plugins/pagerduty/src/components/types.tsx diff --git a/plugins/pagerduty/src/components/Escalation.tsx b/plugins/pagerduty/src/components/Escalation.tsx new file mode 100644 index 0000000000..217b93a400 --- /dev/null +++ b/plugins/pagerduty/src/components/Escalation.tsx @@ -0,0 +1,112 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { + List, + ListSubheader, + ListItem, + ListItemIcon, + ListItemSecondaryAction, + Tooltip, + ListItemText, + makeStyles, + IconButton, +} from '@material-ui/core'; +import UserIcon from '@material-ui/icons/Person'; +import EmailIcon from '@material-ui/icons/Email'; +import { StatusWarning } from '@backstage/core'; +import Pagerduty from './pd.svg'; +import { PagerDutyUserData } from './types'; + +const useStyles = makeStyles({ + denseListIcon: { + marginRight: 0, + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + }, + svgButtonImage: { + height: '1em', + }, +}); + +type EscalationUserProps = { + user: PagerDutyUserData; +}; + +const EscalationUser = ({ user }: EscalationUserProps) => { + const classes = useStyles(); + return ( + + + + + + + + + + + + + + View in PagerDuty + + + + + ); +}; + +const EscalationUsersEmptyState = () => { + const classes = useStyles(); + return ( + + +
+ +
+
+ +
+ ); +}; + +type EscalationPolicyProps = { + escalation: PagerDutyUserData[]; +}; + +export const EscalationPolicy = ({ escalation }: EscalationPolicyProps) => ( + Escalation Policy}> + {escalation.length ? ( + escalation.map((user, index) => ( + + )) + ) : ( + + )} + +); diff --git a/plugins/pagerduty/src/components/Incidents.tsx b/plugins/pagerduty/src/components/Incidents.tsx new file mode 100644 index 0000000000..6f6d194a77 --- /dev/null +++ b/plugins/pagerduty/src/components/Incidents.tsx @@ -0,0 +1,120 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { + List, + ListItem, + ListItemIcon, + ListItemSecondaryAction, + Tooltip, + ListItemText, + makeStyles, + IconButton, + ListSubheader, +} from '@material-ui/core'; +import { StatusError, StatusWarning, StatusOK } from '@backstage/core'; +import Pagerduty from './pd.svg'; +import moment from 'moment'; + +const useStyles = makeStyles({ + denseListIcon: { + marginRight: 0, + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + }, + svgButtonImage: { + height: '1em', + }, +}); + +const IncidentsEmptyState = () => { + const classes = useStyles(); + return ( + + +
+ +
+
+ +
+ ); +}; + +type IncidentListProps = { + incidents: any; +}; + +const IncidentList = ({ incidents }: IncidentListProps) => { + const classes = useStyles(); + return incidents.map((incident: any) => ( + + + +
+ {incident.status === 'triggered' ? ( + + ) : ( + + )} +
+
+
+ + Created {moment(incident.createdAt).fromNow()}, assigned to{' '} + {(incident.assignees.length && incident.assignees[0].name) || + 'nobody'} + + } + /> + + + + View in PagerDuty + + + +
+ )); +}; + +type IncidentsProps = { + incidents: Array; +}; + +export const Incidents = ({ incidents }: IncidentsProps) => ( + Incidents}> + {incidents.length ? ( + + ) : ( + + )} + +); diff --git a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx index 3dadacadfb..cd51c4bcee 100644 --- a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx +++ b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx @@ -14,237 +14,17 @@ * limitations under the License. */ import React from 'react'; -import { - InfoCard, - StatusError, - StatusWarning, - StatusOK, -} from '@backstage/core'; +import { InfoCard, MissingAnnotationEmptyState } from '@backstage/core'; import { Entity } from '@backstage/catalog-model'; -import { - List, - ListSubheader, - ListItem, - ListItemIcon, - ListItemSecondaryAction, - Tooltip, - ListItemText, - makeStyles, - IconButton, -} from '@material-ui/core'; -import moment from 'moment'; -import Pagerduty from './pd.svg'; -import UserIcon from '@material-ui/icons/Person'; -import EmailIcon from '@material-ui/icons/Email'; +import { Grid, Button } from '@material-ui/core'; +import { Incidents } from './Incidents'; +import { EscalationPolicy } from './Escalation'; +import { PagerDutyData } from './types'; -const useStyles = makeStyles({ - buttonContainer: {}, - denseListIcon: { - marginRight: 0, - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - justifyContent: 'center', - }, - svgButtonImage: { - height: '1em', - }, -}); +const PAGERDUTY_INTEGRATION_KEY = 'pagerduty.com/integration-key'; -type IncidentListProps = { - incidents: any; -}; - -const IncidentList = ({ incidents }: IncidentListProps) => { - const classes = useStyles(); - return incidents.map((incident: any) => ( - - - -
- {incident.status === 'triggered' ? ( - - ) : ( - - )} -
-
-
- - Created {moment(incident.createdAt).fromNow()}, assigned to{' '} - {(incident.assignees.length && incident.assignees[0].name) || - 'nobody'} - - } - /> - - - - View in PagerDuty - - - -
- )); -}; - -const IncidentsEmptyState = () => { - const classes = useStyles(); - return ( - - -
- -
-
- -
- ); -}; - -type IncidentsProps = { - incidents: Array; -}; -export const Incidents = ({ incidents }: IncidentsProps) => ( - Incidents}> - {incidents.length ? ( - - ) : ( - - )} - -); - -type EscalationProps = { - escalation: PagerDutyUserData[]; -}; - -const EscalationUsers = ({ escalation }: EscalationProps) => { - const classes = useStyles(); - const escalations = escalation.map((user, index) => ( - - - - - - - - - - - - - - View in PagerDuty - - - - - )); - return
{escalations}
; -}; - -const EscalationUsersEmptyState = () => { - const classes = useStyles(); - return ( - - -
- -
-
- -
- ); -}; - -const EscalationPolicy = ({ escalation }: EscalationProps) => ( - Escalation Policy}> - {escalation.length ? ( - - ) : ( - - )} - -); - -type CardBodyProps = { - entity: Entity; - data: PagerDutyData; -}; - -const CardBody = ({ entity, data }: CardBodyProps) => { - const { pagerDutyServices } = data; - const classes = useStyles(); - - if (!pagerDutyServices.length) { - return ( -
- 'The specified PagerDuty integration key does not match any PagerDuty - service.' -
- ); - } - - const { activeIncidents, escalationPolicy } = pagerDutyServices[0]; - - return ( - <> - - -
- {/* */} -
- - ); -}; - -type PagerDutyIncident = { - id: string; - title: string; - status: string; - createdAt: string; - homepageUrl: string; - assignees: Partial[]; -}; - -type PagerDutyUserData = { - email: string; - name: string; - homepageUrl: string; - id: string; -}; - -type PagerDutyServicesData = { - activeIncidents: PagerDutyIncident[]; - escalationPolicy: PagerDutyUserData[]; - id: string; - name: string; - homepageUrl: string; -}; - -type PagerDutyData = { - pagerDutyServices: PagerDutyServicesData[]; -}; +export const isPluginApplicableToEntity = (entity: Entity) => + Boolean(entity.metadata.annotations?.[PAGERDUTY_INTEGRATION_KEY]); type Props = { entity: Entity; @@ -284,11 +64,20 @@ export const PagerDutyServiceCard = ({ entity }: Props) => { ], }; - return ( + const { activeIncidents, escalationPolicy } = mockData.pagerDutyServices[0]; + + return isPluginApplicableToEntity(entity) ? ( + + ) : ( - + + + + {/* */} + + ); }; - -export const isPluginApplicableToEntity = (entity: Entity) => true; diff --git a/plugins/pagerduty/src/components/types.tsx b/plugins/pagerduty/src/components/types.tsx new file mode 100644 index 0000000000..b3b94f0200 --- /dev/null +++ b/plugins/pagerduty/src/components/types.tsx @@ -0,0 +1,27 @@ +export type PagerDutyIncident = { + id: string; + title: string; + status: string; + createdAt: string; + homepageUrl: string; + assignees: Partial[]; +}; + +export type PagerDutyUserData = { + email: string; + name: string; + homepageUrl: string; + id: string; +}; + +export type PagerDutyServicesData = { + activeIncidents: PagerDutyIncident[]; + escalationPolicy: any; // PagerDutyUserData[]; + id: string; + name: string; + homepageUrl: string; +}; + +export type PagerDutyData = { + pagerDutyServices: PagerDutyServicesData[]; +}; From ac3b033665e1d5b9833e40faf269cc28db66433a Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Wed, 21 Oct 2020 10:01:10 +0200 Subject: [PATCH 007/758] Add catalog import plugin to router --- packages/app/package.json | 1 + packages/app/src/App.tsx | 2 ++ packages/app/src/plugins.ts | 1 + 3 files changed, 4 insertions(+) diff --git a/packages/app/package.json b/packages/app/package.json index dc452e0803..254bae9c46 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -37,6 +37,7 @@ "@roadiehq/backstage-plugin-github-insights": "^0.2.7", "@roadiehq/backstage-plugin-github-pull-requests": "^0.5.2", "@roadiehq/backstage-plugin-travis-ci": "^0.2.3", + "@roadiehq/backstage-plugin-catalog-import": "^0.1.0", "dayjs": "^1.9.1", "history": "^5.0.0", "prop-types": "^15.7.2", diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 81d08f58a9..883fe1e1ca 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -27,6 +27,7 @@ import * as plugins from './plugins'; import { apis } from './apis'; import { hot } from 'react-hot-loader/root'; import { providers } from './identityProviders'; +import { Router as CatalogImportRouter } from '@roadiehq/backstage-plugin-catalog-import'; import { Router as CatalogRouter } from '@backstage/plugin-catalog'; import { Router as DocsRouter } from '@backstage/plugin-techdocs'; import { Router as GraphiQLRouter } from '@backstage/plugin-graphiql'; @@ -83,6 +84,7 @@ const AppRoutes = () => ( element={} /> } /> + } /> {...deprecatedAppRoutes} ); diff --git a/packages/app/src/plugins.ts b/packages/app/src/plugins.ts index bdc3851318..64c49eceba 100644 --- a/packages/app/src/plugins.ts +++ b/packages/app/src/plugins.ts @@ -37,4 +37,5 @@ export { plugin as Kubernetes } from '@backstage/plugin-kubernetes'; export { plugin as Cloudbuild } from '@backstage/plugin-cloudbuild'; export { plugin as CostInsights } from '@backstage/plugin-cost-insights'; export { plugin as GitHubInsights } from '@roadiehq/backstage-plugin-github-insights'; +export { plugin as CatalogImport } from '@roadiehq/backstage-plugin-catalog-import'; export { plugin as UserSettings } from '@backstage/plugin-user-settings'; From 4dba9ea97ae30e1e5b83ee6dcf251c141b41519c Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Wed, 21 Oct 2020 10:04:05 +0200 Subject: [PATCH 008/758] add TriggerButton and TriggerDialog, refactor them --- .../src/components/PagerDutyServiceCard.tsx | 21 ++- .../src/components/TriggerButton.tsx | 68 +++++++++ .../src/components/TriggerDialog.tsx | 131 ++++++++++++++++++ 3 files changed, 213 insertions(+), 7 deletions(-) create mode 100644 plugins/pagerduty/src/components/TriggerButton.tsx create mode 100644 plugins/pagerduty/src/components/TriggerDialog.tsx diff --git a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx index cd51c4bcee..e60575ee9b 100644 --- a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx +++ b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx @@ -16,10 +16,11 @@ import React from 'react'; import { InfoCard, MissingAnnotationEmptyState } from '@backstage/core'; import { Entity } from '@backstage/catalog-model'; -import { Grid, Button } from '@material-ui/core'; +import { Grid } from '@material-ui/core'; import { Incidents } from './Incidents'; import { EscalationPolicy } from './Escalation'; import { PagerDutyData } from './types'; +import { TriggerButton } from './TriggerButton'; const PAGERDUTY_INTEGRATION_KEY = 'pagerduty.com/integration-key'; @@ -64,19 +65,25 @@ export const PagerDutyServiceCard = ({ entity }: Props) => { ], }; - const { activeIncidents, escalationPolicy } = mockData.pagerDutyServices[0]; + const { + activeIncidents, + escalationPolicy, + homepageUrl, + } = mockData.pagerDutyServices[0]; + + const link = { + title: 'View in PagerDuty', + link: homepageUrl, + }; return isPluginApplicableToEntity(entity) ? ( ) : ( - + - {/* */} - + ); diff --git a/plugins/pagerduty/src/components/TriggerButton.tsx b/plugins/pagerduty/src/components/TriggerButton.tsx new file mode 100644 index 0000000000..578b86d50b --- /dev/null +++ b/plugins/pagerduty/src/components/TriggerButton.tsx @@ -0,0 +1,68 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React, { useState } from 'react'; +import { Button } from '@material-ui/core'; +import { TriggerDialog } from './TriggerDialog'; +import { Entity } from '@backstage/catalog-model'; + +type Props = { + entity: Entity; +}; + +export const TriggerButton = ({ entity }: Props) => { + const [showDialog, setShowDialog] = useState(false); + + const handleDialog = () => { + setShowDialog(!showDialog); + }; + + // const onTriggerAlarm = async (description: string) => { + // try { + // //TODO: call method from pagerduty client + // alertApi.post({ + // message: `Alarm successfully triggered by ${userId}`, + // }); + // } catch (error) { + // alertApi.post({ + // message: `Failed to trigger alarm, ${error.message}`, + // severity: 'error', + // }); + // throw error; + // } + // }; + + return ( + <> + + {showDialog && ( + + )} + + ); +}; diff --git a/plugins/pagerduty/src/components/TriggerDialog.tsx b/plugins/pagerduty/src/components/TriggerDialog.tsx new file mode 100644 index 0000000000..29378cce63 --- /dev/null +++ b/plugins/pagerduty/src/components/TriggerDialog.tsx @@ -0,0 +1,131 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React, { useState, useEffect } from 'react'; +import { + Dialog, + DialogTitle, + TextField, + makeStyles, + DialogActions, + Button, + DialogContent, + FormControl, + FormHelperText, +} from '@material-ui/core'; +import { Progress, useApi, alertApiRef, identityApiRef } from '@backstage/core'; +import { useAsyncFn } from 'react-use'; + +const useStyles = makeStyles({ + warningText: { + border: '1px solid rgba(245, 155, 35, 0.5)', + backgroundColor: 'rgba(245, 155, 35, 0.2)', + padding: '0.5em 1em', + }, +}); + +type Props = { + name: string; + integrationKey: string; + onClose: () => void; +}; + +export const TriggerDialog = ({ name, integrationKey, onClose }: Props) => { + const classes = useStyles(); + const [description, setDescription] = useState(''); + const alertApi = useApi(alertApiRef); + const identityApi = useApi(identityApiRef); + const userId = identityApi.getUserId(); + + const descriptionChanged = (event: any) => { + setDescription(event.target.value); + }; + + const promiseFunc = () => + new Promise((resolve, reject) => { + setTimeout(() => { + reject('Inside test await'); + }, 1000); + }); + + const [{ value, loading, error }, triggerAlarm] = useAsyncFn(async () => { + return await promiseFunc(); + }); + + useEffect(() => { + if (value) { + alertApi.post({ + message: `Alarm successfully triggered by ${userId}`, + }); + onClose(); + } + + if (error) { + alertApi.post({ + message: `Failed to trigger alarm, ${error.message}`, + severity: 'error', + }); + } + }, [value, error]); + + return ( + + + This action will send PagerDuty alarms and notifications to on-call + people responsible for {name}. + + +

+ Note: If the issue you are seeing does not need urgent attention, + please get in touch with the responsible team using their preferred + communications channel. For most views, you can find links to support + and information channels by clicking the support button in the top + right corner of the page. If the issue is urgent, please don't + hesitate to trigger the alert. +

+

+ Please describe the problem you want to report. Be as descriptive as + possible. Your Spotify username and a reference to the current page + will automatically be sent amended to the alarm so that we can debug + the issue and reach out to you if necessary. +

+ +
+ + + + + {loading && } +
+ ); +}; From 4e93108ba4d94ea9818777604e02f9b3831aff08 Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Wed, 21 Oct 2020 14:12:52 +0200 Subject: [PATCH 009/758] trigger pager alarm --- plugins/pagerduty/package.json | 4 +- plugins/pagerduty/src/api/pagerDutyClient.ts | 73 +++++++++++++++++++ .../src/{components => assets}/pd.svg | 0 .../pagerduty/src/components/Escalation.tsx | 2 +- .../pagerduty/src/components/Incidents.tsx | 2 +- .../src/components/PagerDutyServiceCard.tsx | 4 +- .../src/components/TriggerButton.tsx | 21 +----- .../src/components/TriggerDialog.tsx | 31 ++++---- 8 files changed, 99 insertions(+), 38 deletions(-) create mode 100644 plugins/pagerduty/src/api/pagerDutyClient.ts rename plugins/pagerduty/src/{components => assets}/pd.svg (100%) diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json index 33054c6163..e5d0264be3 100644 --- a/plugins/pagerduty/package.json +++ b/plugins/pagerduty/package.json @@ -22,12 +22,14 @@ "dependencies": { "@backstage/core": "^0.1.1-alpha.25", "@backstage/theme": "^0.1.1-alpha.25", + "@backstage/catalog-model": "^0.1.1-alpha.25", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", "react": "^16.13.1", "react-dom": "^16.13.1", - "react-use": "^15.3.3" + "react-use": "^15.3.3", + "moment": "^2.27.0" }, "devDependencies": { "@backstage/cli": "^0.1.1-alpha.25", diff --git a/plugins/pagerduty/src/api/pagerDutyClient.ts b/plugins/pagerduty/src/api/pagerDutyClient.ts new file mode 100644 index 0000000000..d5db842dab --- /dev/null +++ b/plugins/pagerduty/src/api/pagerDutyClient.ts @@ -0,0 +1,73 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +type Options = { + method: string; + headers: { + 'Content-Type': string; + Accept: string; + }; + body: string; +}; + +const request = async ( + url: string, + options: Options, +): Promise => { + const response = await fetch(url, options); + + if (!response.ok) { + const payload = await response.json(); + const errors = payload.errors.map((error: string) => error).join(' '); + const message = `Request failed with ${response.status}, ${errors}`; + + throw new Error(message); + } + + return await response.json(); +}; + +export function triggerPagerDutyAlarm( + integrationKey: string, + source: string, + description: string, + userName: string, +) { + const options = { + method: 'POST', + headers: { + 'Content-Type': 'application/json; charset=UTF-8', + Accept: 'application/json, text/plain, */*', + }, + body: JSON.stringify({ + event_action: 'trigger', + routing_key: integrationKey, + client: 'Backstage', + client_url: source, + payload: { + summary: description, + source: source, + severity: 'error', + class: 'manual trigger', + custom_details: { + user: userName, + }, + }, + }), + }; + + return request('https://events.pagerduty.com/v2/enqueue', options); +} diff --git a/plugins/pagerduty/src/components/pd.svg b/plugins/pagerduty/src/assets/pd.svg similarity index 100% rename from plugins/pagerduty/src/components/pd.svg rename to plugins/pagerduty/src/assets/pd.svg diff --git a/plugins/pagerduty/src/components/Escalation.tsx b/plugins/pagerduty/src/components/Escalation.tsx index 217b93a400..74e253d516 100644 --- a/plugins/pagerduty/src/components/Escalation.tsx +++ b/plugins/pagerduty/src/components/Escalation.tsx @@ -29,7 +29,7 @@ import { import UserIcon from '@material-ui/icons/Person'; import EmailIcon from '@material-ui/icons/Email'; import { StatusWarning } from '@backstage/core'; -import Pagerduty from './pd.svg'; +import Pagerduty from '../assets/pd.svg'; import { PagerDutyUserData } from './types'; const useStyles = makeStyles({ diff --git a/plugins/pagerduty/src/components/Incidents.tsx b/plugins/pagerduty/src/components/Incidents.tsx index 6f6d194a77..98a26c66db 100644 --- a/plugins/pagerduty/src/components/Incidents.tsx +++ b/plugins/pagerduty/src/components/Incidents.tsx @@ -27,7 +27,7 @@ import { ListSubheader, } from '@material-ui/core'; import { StatusError, StatusWarning, StatusOK } from '@backstage/core'; -import Pagerduty from './pd.svg'; +import Pagerduty from '../assets/pd.svg'; import moment from 'moment'; const useStyles = makeStyles({ diff --git a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx index e60575ee9b..e0564d76ce 100644 --- a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx +++ b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx @@ -22,7 +22,7 @@ import { EscalationPolicy } from './Escalation'; import { PagerDutyData } from './types'; import { TriggerButton } from './TriggerButton'; -const PAGERDUTY_INTEGRATION_KEY = 'pagerduty.com/integration-key'; +export const PAGERDUTY_INTEGRATION_KEY = 'pagerduty.com/integration-key'; export const isPluginApplicableToEntity = (entity: Entity) => Boolean(entity.metadata.annotations?.[PAGERDUTY_INTEGRATION_KEY]); @@ -76,7 +76,7 @@ export const PagerDutyServiceCard = ({ entity }: Props) => { link: homepageUrl, }; - return isPluginApplicableToEntity(entity) ? ( + return !isPluginApplicableToEntity(entity) ? ( ) : ( diff --git a/plugins/pagerduty/src/components/TriggerButton.tsx b/plugins/pagerduty/src/components/TriggerButton.tsx index 578b86d50b..eb051e68fa 100644 --- a/plugins/pagerduty/src/components/TriggerButton.tsx +++ b/plugins/pagerduty/src/components/TriggerButton.tsx @@ -18,6 +18,7 @@ import React, { useState } from 'react'; import { Button } from '@material-ui/core'; import { TriggerDialog } from './TriggerDialog'; import { Entity } from '@backstage/catalog-model'; +import { PAGERDUTY_INTEGRATION_KEY } from './PagerDutyServiceCard'; type Props = { entity: Entity; @@ -30,21 +31,6 @@ export const TriggerButton = ({ entity }: Props) => { setShowDialog(!showDialog); }; - // const onTriggerAlarm = async (description: string) => { - // try { - // //TODO: call method from pagerduty client - // alertApi.post({ - // message: `Alarm successfully triggered by ${userId}`, - // }); - // } catch (error) { - // alertApi.post({ - // message: `Failed to trigger alarm, ${error.message}`, - // severity: 'error', - // }); - // throw error; - // } - // }; - return ( <> From fe2d8ac7152fc3a2d88df1dea66750f4dffa732a Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Thu, 22 Oct 2020 10:18:46 +0200 Subject: [PATCH 010/758] wip:fetching data from Api --- .../app/src/components/catalog/EntityPage.tsx | 10 +++---- plugins/pagerduty/src/api/pagerDutyClient.ts | 29 +++++++++++++++++-- .../src/components/PagerDutyServiceCard.tsx | 24 ++++++++++++++- 3 files changed, 54 insertions(+), 9 deletions(-) diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index 51467fc2bb..7629c86a1e 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -135,11 +135,11 @@ const OverviewContent = ({ entity }: { entity: Entity }) => ( - {isPagerDutyAvailable(entity) && ( - - - - )} + {/* {isPagerDutyAvailable(entity) && ( */} + + + + {/* )} */} {isGitHubAvailable(entity) && ( <> diff --git a/plugins/pagerduty/src/api/pagerDutyClient.ts b/plugins/pagerduty/src/api/pagerDutyClient.ts index d5db842dab..fa42c82f9e 100644 --- a/plugins/pagerduty/src/api/pagerDutyClient.ts +++ b/plugins/pagerduty/src/api/pagerDutyClient.ts @@ -14,18 +14,22 @@ * limitations under the License. */ +const API_URL = 'https://api.pagerduty.com'; +const EVENTS_API_URL = 'https://events.pagerduty.com/v2'; + type Options = { method: string; headers: { 'Content-Type': string; Accept: string; + Authorization?: string; }; - body: string; + body?: string; }; const request = async ( url: string, - options: Options, + options: any, //Options, ): Promise => { const response = await fetch(url, options); @@ -40,6 +44,25 @@ const request = async ( return await response.json(); }; +export const getServices = async (token: string, integrationKey: string) => { + const options = { + method: 'GET', + headers: { + Authorization: `Token token=${token}`, + Accept: 'application/vnd.pagerduty+json;version=2', + 'Content-Type': 'application/json', + }, + // query: { + // query: encodeURIComponent(`key:${integrationKey}`), + // }, + }; + + return request( + `${API_URL}/services/?query=key%253A238b701cc9d048f0bdd828355178eafe`, + options, + ); +}; + export function triggerPagerDutyAlarm( integrationKey: string, source: string, @@ -69,5 +92,5 @@ export function triggerPagerDutyAlarm( }), }; - return request('https://events.pagerduty.com/v2/enqueue', options); + return request(`${EVENTS_API_URL}/enqueue`, options); } diff --git a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx index e0564d76ce..917ae27090 100644 --- a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx +++ b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx @@ -14,13 +14,20 @@ * limitations under the License. */ import React from 'react'; -import { InfoCard, MissingAnnotationEmptyState } from '@backstage/core'; +import { + InfoCard, + MissingAnnotationEmptyState, + useApi, + configApiRef, +} from '@backstage/core'; import { Entity } from '@backstage/catalog-model'; import { Grid } from '@material-ui/core'; import { Incidents } from './Incidents'; import { EscalationPolicy } from './Escalation'; import { PagerDutyData } from './types'; import { TriggerButton } from './TriggerButton'; +import { useAsync } from 'react-use'; +import { getServices } from '../api/pagerDutyClient'; export const PAGERDUTY_INTEGRATION_KEY = 'pagerduty.com/integration-key'; @@ -32,6 +39,20 @@ type Props = { }; export const PagerDutyServiceCard = ({ entity }: Props) => { + const configApi = useApi(configApiRef); + const pagerDutyToken = + configApi.getOptionalString('pagerduty.api_token') ?? undefined; + + console.log({ pagerDutyToken }); + const { value, loading, error } = useAsync(async () => { + return await getServices( + pagerDutyToken!, + entity.metadata.annotations![PAGERDUTY_INTEGRATION_KEY], + ); + }); + if (value) console.log(value); + if (error) throw new Error(`Error in getting services, ${error}`); + // TODO: fetch this data const mockData: PagerDutyData = { pagerDutyServices: [ @@ -85,6 +106,7 @@ export const PagerDutyServiceCard = ({ entity }: Props) => { + {/* todo show something when we dont have token */} ); }; From 0609e726936e457c0407fa83ba8783409275f864 Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Thu, 22 Oct 2020 15:12:38 +0200 Subject: [PATCH 011/758] add pagerduty backend --- packages/backend/package.json | 1 + packages/backend/src/index.ts | 3 + packages/backend/src/plugins/pagerduty.ts | 27 ++++++++ plugins/pagerduty-backend/.eslintrc.js | 3 + plugins/pagerduty-backend/README.md | 13 ++++ plugins/pagerduty-backend/package.json | 40 ++++++++++++ plugins/pagerduty-backend/src/index.ts | 17 +++++ plugins/pagerduty-backend/src/run.ts | 33 ++++++++++ .../src/service/router.test.ts | 45 ++++++++++++++ .../pagerduty-backend/src/service/router.ts | 42 +++++++++++++ .../src/service/standaloneServer.ts | 62 +++++++++++++++++++ plugins/pagerduty-backend/src/setupTests.ts | 18 ++++++ 12 files changed, 304 insertions(+) create mode 100644 packages/backend/src/plugins/pagerduty.ts create mode 100644 plugins/pagerduty-backend/.eslintrc.js create mode 100644 plugins/pagerduty-backend/README.md create mode 100644 plugins/pagerduty-backend/package.json create mode 100644 plugins/pagerduty-backend/src/index.ts create mode 100644 plugins/pagerduty-backend/src/run.ts create mode 100644 plugins/pagerduty-backend/src/service/router.test.ts create mode 100644 plugins/pagerduty-backend/src/service/router.ts create mode 100644 plugins/pagerduty-backend/src/service/standaloneServer.ts create mode 100644 plugins/pagerduty-backend/src/setupTests.ts diff --git a/packages/backend/package.json b/packages/backend/package.json index ef55b007a7..c31629e82e 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -31,6 +31,7 @@ "@backstage/plugin-scaffolder-backend": "^0.1.1-alpha.25", "@backstage/plugin-sentry-backend": "^0.1.1-alpha.25", "@backstage/plugin-techdocs-backend": "^0.1.1-alpha.25", + "@backstage/plugin-pagerduty-backend": "^0.1.1-alpha.25", "@gitbeaker/node": "^23.5.0", "@octokit/rest": "^18.0.0", "azure-devops-node-api": "^10.1.1", diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 18fd44f089..0ca17c7d7f 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -46,6 +46,7 @@ import techdocs from './plugins/techdocs'; import graphql from './plugins/graphql'; import app from './plugins/app'; import { PluginEnvironment } from './types'; +import pagerduty from './plugins/pagerduty'; function makeCreateEnv(config: ConfigReader) { const root = getRootLogger(); @@ -79,6 +80,7 @@ async function main() { const kubernetesEnv = useHotMemoize(module, () => createEnv('kubernetes')); const graphqlEnv = useHotMemoize(module, () => createEnv('graphql')); const appEnv = useHotMemoize(module, () => createEnv('app')); + const pagerdutyEnv = useHotMemoize(module, () => createEnv('pagerduty')); const apiRouter = Router(); apiRouter.use('/catalog', await catalog(catalogEnv)); @@ -90,6 +92,7 @@ async function main() { apiRouter.use('/kubernetes', await kubernetes(kubernetesEnv)); apiRouter.use('/proxy', await proxy(proxyEnv)); apiRouter.use('/graphql', await graphql(graphqlEnv)); + apiRouter.use('/pagerduty', await pagerduty(pagerdutyEnv)); apiRouter.use(notFoundHandler()); const service = createServiceBuilder(module) diff --git a/packages/backend/src/plugins/pagerduty.ts b/packages/backend/src/plugins/pagerduty.ts new file mode 100644 index 0000000000..6dc3d8c9ba --- /dev/null +++ b/packages/backend/src/plugins/pagerduty.ts @@ -0,0 +1,27 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createRouter } from '@backstage/plugin-pagerduty-backend'; +import { PluginEnvironment } from '../types'; + +export default async function createPlugin({ + logger, + config, +}: PluginEnvironment) { + return await createRouter({ + logger, + config, + }); +} diff --git a/plugins/pagerduty-backend/.eslintrc.js b/plugins/pagerduty-backend/.eslintrc.js new file mode 100644 index 0000000000..16a033dbc6 --- /dev/null +++ b/plugins/pagerduty-backend/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint.backend')], +}; diff --git a/plugins/pagerduty-backend/README.md b/plugins/pagerduty-backend/README.md new file mode 100644 index 0000000000..099a94a85f --- /dev/null +++ b/plugins/pagerduty-backend/README.md @@ -0,0 +1,13 @@ +# pagerduty + +Welcome to the pagerduty 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 [/pagerduty](http://localhost:3000/pagerduty). + +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. diff --git a/plugins/pagerduty-backend/package.json b/plugins/pagerduty-backend/package.json new file mode 100644 index 0000000000..cad4ba268e --- /dev/null +++ b/plugins/pagerduty-backend/package.json @@ -0,0 +1,40 @@ +{ + "name": "@backstage/plugin-pagerduty-backend", + "version": "0.1.1-alpha.25", + "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" + }, + "scripts": { + "start": "backstage-cli backend:dev", + "build": "backstage-cli backend:build", + "lint": "backstage-cli lint", + "test": "backstage-cli test", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", + "clean": "backstage-cli clean" + }, + "dependencies": { + "@backstage/backend-common": "^0.1.1-alpha.25", + "@backstage/config": "^0.1.1-alpha.25", + "@types/express": "^4.17.6", + "express": "^4.17.1", + "express-promise-router": "^3.0.3", + "winston": "^3.2.1", + "node-fetch": "^2.6.1", + "yn": "^4.0.0" + }, + "devDependencies": { + "@backstage/cli": "^0.1.1-alpha.25", + "@types/supertest": "^2.0.8", + "supertest": "^4.0.2", + "msw": "^0.20.5" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/pagerduty-backend/src/index.ts b/plugins/pagerduty-backend/src/index.ts new file mode 100644 index 0000000000..7612c392a2 --- /dev/null +++ b/plugins/pagerduty-backend/src/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './service/router'; diff --git a/plugins/pagerduty-backend/src/run.ts b/plugins/pagerduty-backend/src/run.ts new file mode 100644 index 0000000000..b96989e4b8 --- /dev/null +++ b/plugins/pagerduty-backend/src/run.ts @@ -0,0 +1,33 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +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) : 7000; +const enableCors = yn(process.env.PLUGIN_CORS, { default: false }); +const logger = getRootLogger(); + +startStandaloneServer({ port, enableCors, logger }).catch(err => { + logger.error(err); + process.exit(1); +}); + +process.on('SIGINT', () => { + logger.info('CTRL+C pressed; exiting.'); + process.exit(0); +}); diff --git a/plugins/pagerduty-backend/src/service/router.test.ts b/plugins/pagerduty-backend/src/service/router.test.ts new file mode 100644 index 0000000000..0aaeafa379 --- /dev/null +++ b/plugins/pagerduty-backend/src/service/router.test.ts @@ -0,0 +1,45 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getVoidLogger } from '@backstage/backend-common'; +import express from 'express'; +import request from 'supertest'; + +import { createRouter } from './router'; + +describe('createRouter', () => { + let app: express.Express; + + beforeAll(async () => { + const router = await createRouter({ + logger: getVoidLogger(), + }); + app = express().use(router); + }); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + describe('GET /health', () => { + it('returns ok', async () => { + const response = await request(app).get('/health'); + + expect(response.status).toEqual(200); + expect(response.body).toEqual({ status: 'ok' }); + }); + }); +}); diff --git a/plugins/pagerduty-backend/src/service/router.ts b/plugins/pagerduty-backend/src/service/router.ts new file mode 100644 index 0000000000..eb7cc734c2 --- /dev/null +++ b/plugins/pagerduty-backend/src/service/router.ts @@ -0,0 +1,42 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { errorHandler } from '@backstage/backend-common'; +import { Config } from '@backstage/config'; +import express from 'express'; +import Router from 'express-promise-router'; +import { Logger } from 'winston'; + +export interface RouterOptions { + logger: Logger; + config: Config; +} + +export async function createRouter( + options: RouterOptions, +): Promise { + const { logger } = options; + + const router = Router(); + router.use(express.json()); + + router.get('/health', (_, response) => { + logger.info('PONG!'); + response.send({ status: 'ok' }); + }); + router.use(errorHandler()); + return router; +} diff --git a/plugins/pagerduty-backend/src/service/standaloneServer.ts b/plugins/pagerduty-backend/src/service/standaloneServer.ts new file mode 100644 index 0000000000..73f928bef4 --- /dev/null +++ b/plugins/pagerduty-backend/src/service/standaloneServer.ts @@ -0,0 +1,62 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createServiceBuilder } from '@backstage/backend-common'; +import { Server } from 'http'; +import { Logger } from 'winston'; +import { createRouter } from './router'; + +export interface ServerOptions { + port: number; + enableCors: boolean; + logger: Logger; +} + +export async function startStandaloneServer( + options: ServerOptions, +): Promise { + const logger = options.logger.child({ service: 'pagerduty-backend' }); + logger.debug('Starting application server...'); + const router = await createRouter({ + logger, + }); + + const service = createServiceBuilder(module) + .enableCors({ origin: 'http://localhost:3000' }) + .addRouter('/pagerduty', router); + + return await service.start().catch(err => { + logger.error(err); + process.exit(1); + }); +} + +module.hot?.accept(); diff --git a/plugins/pagerduty-backend/src/setupTests.ts b/plugins/pagerduty-backend/src/setupTests.ts new file mode 100644 index 0000000000..a5907fd52f --- /dev/null +++ b/plugins/pagerduty-backend/src/setupTests.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export {}; +global.fetch = require('node-fetch'); From 6d1f4a996d25f93a6b5a0c5f8f605f58048d3e2f Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Fri, 23 Oct 2020 13:14:08 +0200 Subject: [PATCH 012/758] fetching data from pagerduty endpoint in interval --- .../pagerduty-backend/src/service/router.ts | 95 ++++++++++++++++++- 1 file changed, 92 insertions(+), 3 deletions(-) diff --git a/plugins/pagerduty-backend/src/service/router.ts b/plugins/pagerduty-backend/src/service/router.ts index eb7cc734c2..192a22019a 100644 --- a/plugins/pagerduty-backend/src/service/router.ts +++ b/plugins/pagerduty-backend/src/service/router.ts @@ -14,11 +14,79 @@ * limitations under the License. */ -import { errorHandler } from '@backstage/backend-common'; +import { errorHandler, useHotCleanup } from '@backstage/backend-common'; import { Config } from '@backstage/config'; import express from 'express'; import Router from 'express-promise-router'; import { Logger } from 'winston'; +import fetch from 'node-fetch'; + +const router = Router(); +const API_URL = 'https://api.pagerduty.com'; +const EVENTS_API_URL = 'https://events.pagerduty.com/v2'; + +type Options = { + method: string; + headers: { + 'Content-Type': string; + Accept: string; + Authorization?: string; + }; + body?: string; +}; + +async function request( + url: string, + options: any, // Options, +): Promise { + // TODO: handle errors better + const response = await fetch(url, options); + + if (!response.ok) { + const payload = await response.json(); + const errors = payload.errors.map((error: string) => error).join(' '); + const message = `Request failed with ${response.status}, ${errors}`; + + throw new Error(message); + } + + return await response.json(); +} + +async function services(token?: string) { + // TODO: handle missing token differently + if (!token) { + return 'Missing Token'; + } + const options = { + method: 'GET', + headers: { + Authorization: `Token token=${token}`, + Accept: 'application/vnd.pagerduty+json;version=2', + 'Content-Type': 'application/json', + }, + }; + let offset = 0; + // TODO: Create type for this data + const data: any = []; + + async function fetchData() { + const result: any = await request( + `${API_URL}/services?offset=${offset}`, + options, + ); + data.push(...result.services); + + // TODO: remove offset when we are done + if (offset < 4 && result.more) { + ++offset; + await fetchData(); + } + } + + await fetchData(); + return data; +} export interface RouterOptions { logger: Logger; @@ -28,15 +96,36 @@ export interface RouterOptions { export async function createRouter( options: RouterOptions, ): Promise { - const { logger } = options; + const { logger, config } = options; + const token = config.getOptionalString('pagerduty.api_token') ?? undefined; + + let cachedData: any = []; + + let cancel = false; + const intervalId = setInterval(async () => { + if (!cancel) { + cancel = true; + const result = await services(token); + cachedData = result; + cancel = false; + } + }, 10000); + + useHotCleanup(module, () => clearInterval(intervalId)); - const router = Router(); router.use(express.json()); + router.get('/services', async (_, response) => { + logger.info('pinged'); + + response.send({ services: cachedData, size: cachedData.length }); + }); + router.get('/health', (_, response) => { logger.info('PONG!'); response.send({ status: 'ok' }); }); + router.use(errorHandler()); return router; } From c9c212e064a365f2cffcfc7eb6a10eef5699a0ae Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Fri, 23 Oct 2020 14:59:36 +0200 Subject: [PATCH 013/758] added error handling --- .../pagerduty-backend/src/service/router.ts | 98 +++++++++++++------ plugins/pagerduty/src/api/pagerDutyClient.ts | 20 ---- .../src/components/PagerDutyServiceCard.tsx | 10 +- 3 files changed, 71 insertions(+), 57 deletions(-) diff --git a/plugins/pagerduty-backend/src/service/router.ts b/plugins/pagerduty-backend/src/service/router.ts index 192a22019a..1fbab9b551 100644 --- a/plugins/pagerduty-backend/src/service/router.ts +++ b/plugins/pagerduty-backend/src/service/router.ts @@ -20,6 +20,7 @@ import express from 'express'; import Router from 'express-promise-router'; import { Logger } from 'winston'; import fetch from 'node-fetch'; +import { Response } from 'express'; const router = Router(); const API_URL = 'https://api.pagerduty.com'; @@ -38,19 +39,21 @@ type Options = { async function request( url: string, options: any, // Options, -): Promise { +): Promise { // TODO: handle errors better - const response = await fetch(url, options); + try { + const response = await fetch(url, options); - if (!response.ok) { - const payload = await response.json(); - const errors = payload.errors.map((error: string) => error).join(' '); - const message = `Request failed with ${response.status}, ${errors}`; - - throw new Error(message); + if (!response.ok) { + const payload = await response.json(); + const errors = payload.errors.map((error: string) => error).join(' '); + const message = `Request failed with ${response.status}, ${errors}`; + throw new Error(message); + } + return await response.json(); + } catch (error) { + throw new Error(error); } - - return await response.json(); } async function services(token?: string) { @@ -58,6 +61,7 @@ async function services(token?: string) { if (!token) { return 'Missing Token'; } + const options = { method: 'GET', headers: { @@ -66,25 +70,38 @@ async function services(token?: string) { 'Content-Type': 'application/json', }, }; + let offset = 0; // TODO: Create type for this data - const data: any = []; + const data: PagerDutyService[] = []; - async function fetchData() { - const result: any = await request( + async function getData() { + const result = await request( `${API_URL}/services?offset=${offset}`, options, ); - data.push(...result.services); + + data.push( + ...result.services.map(service => ({ + id: service.id, + name: service.name, + homepageUrl: service.html_url, + })), + ); // TODO: remove offset when we are done - if (offset < 4 && result.more) { + if (offset < 2 && result.more) { ++offset; - await fetchData(); + await getData(); } } - await fetchData(); + try { + await getData(); + } catch (error) { + throw new Error(error); + } + return data; } @@ -93,6 +110,19 @@ export interface RouterOptions { config: Config; } +type PagerDutyService = { + // activeIncidents: any[]; + // escalationPolicy: any[]; + id: string; + name: string; + homepageUrl: string; +}; + +type PagerDutyResponse = { + services: any[]; + more: boolean; +}; + export async function createRouter( options: RouterOptions, ): Promise { @@ -100,14 +130,21 @@ export async function createRouter( const token = config.getOptionalString('pagerduty.api_token') ?? undefined; let cachedData: any = []; + let errorMessage: string; let cancel = false; const intervalId = setInterval(async () => { if (!cancel) { cancel = true; - const result = await services(token); - cachedData = result; - cancel = false; + try { + logger.info('Fetching data from PagerDuty API'); + cachedData = await services(token); + } catch (error) { + logger.error(`Failed to fetch data, ${error.message}`); + errorMessage = error.message; + } finally { + cancel = false; + } } }, 10000); @@ -115,17 +152,18 @@ export async function createRouter( router.use(express.json()); - router.get('/services', async (_, response) => { - logger.info('pinged'); - - response.send({ services: cachedData, size: cachedData.length }); - }); - - router.get('/health', (_, response) => { - logger.info('PONG!'); - response.send({ status: 'ok' }); - }); + router.get( + '/services', + async (_, response: Response) => { + if (errorMessage) { + response.status(500).send({ error: errorMessage }); + } else { + response.send({ services: cachedData }); + } + }, + ); router.use(errorHandler()); + return router; } diff --git a/plugins/pagerduty/src/api/pagerDutyClient.ts b/plugins/pagerduty/src/api/pagerDutyClient.ts index fa42c82f9e..dc2176680d 100644 --- a/plugins/pagerduty/src/api/pagerDutyClient.ts +++ b/plugins/pagerduty/src/api/pagerDutyClient.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -const API_URL = 'https://api.pagerduty.com'; const EVENTS_API_URL = 'https://events.pagerduty.com/v2'; type Options = { @@ -44,25 +43,6 @@ const request = async ( return await response.json(); }; -export const getServices = async (token: string, integrationKey: string) => { - const options = { - method: 'GET', - headers: { - Authorization: `Token token=${token}`, - Accept: 'application/vnd.pagerduty+json;version=2', - 'Content-Type': 'application/json', - }, - // query: { - // query: encodeURIComponent(`key:${integrationKey}`), - // }, - }; - - return request( - `${API_URL}/services/?query=key%253A238b701cc9d048f0bdd828355178eafe`, - options, - ); -}; - export function triggerPagerDutyAlarm( integrationKey: string, source: string, diff --git a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx index 917ae27090..b7fde8eae3 100644 --- a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx +++ b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx @@ -27,7 +27,6 @@ import { EscalationPolicy } from './Escalation'; import { PagerDutyData } from './types'; import { TriggerButton } from './TriggerButton'; import { useAsync } from 'react-use'; -import { getServices } from '../api/pagerDutyClient'; export const PAGERDUTY_INTEGRATION_KEY = 'pagerduty.com/integration-key'; @@ -40,15 +39,12 @@ type Props = { export const PagerDutyServiceCard = ({ entity }: Props) => { const configApi = useApi(configApiRef); - const pagerDutyToken = - configApi.getOptionalString('pagerduty.api_token') ?? undefined; - console.log({ pagerDutyToken }); const { value, loading, error } = useAsync(async () => { - return await getServices( - pagerDutyToken!, - entity.metadata.annotations![PAGERDUTY_INTEGRATION_KEY], + const response = await fetch( + `${configApi.getString('backend.baseUrl')}/api/pagerduty/services`, ); + return await response.json(); }); if (value) console.log(value); if (error) throw new Error(`Error in getting services, ${error}`); From 0ef0518262668d4754d2fa6e18c5a5099edf77d4 Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Wed, 21 Oct 2020 10:01:10 +0200 Subject: [PATCH 014/758] Add catalog import plugin to router --- packages/app/package.json | 1 + packages/app/src/App.tsx | 2 ++ packages/app/src/plugins.ts | 1 + 3 files changed, 4 insertions(+) diff --git a/packages/app/package.json b/packages/app/package.json index dc452e0803..254bae9c46 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -37,6 +37,7 @@ "@roadiehq/backstage-plugin-github-insights": "^0.2.7", "@roadiehq/backstage-plugin-github-pull-requests": "^0.5.2", "@roadiehq/backstage-plugin-travis-ci": "^0.2.3", + "@roadiehq/backstage-plugin-catalog-import": "^0.1.0", "dayjs": "^1.9.1", "history": "^5.0.0", "prop-types": "^15.7.2", diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 81d08f58a9..883fe1e1ca 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -27,6 +27,7 @@ import * as plugins from './plugins'; import { apis } from './apis'; import { hot } from 'react-hot-loader/root'; import { providers } from './identityProviders'; +import { Router as CatalogImportRouter } from '@roadiehq/backstage-plugin-catalog-import'; import { Router as CatalogRouter } from '@backstage/plugin-catalog'; import { Router as DocsRouter } from '@backstage/plugin-techdocs'; import { Router as GraphiQLRouter } from '@backstage/plugin-graphiql'; @@ -83,6 +84,7 @@ const AppRoutes = () => ( element={} /> } /> + } /> {...deprecatedAppRoutes} ); diff --git a/packages/app/src/plugins.ts b/packages/app/src/plugins.ts index bdc3851318..64c49eceba 100644 --- a/packages/app/src/plugins.ts +++ b/packages/app/src/plugins.ts @@ -37,4 +37,5 @@ export { plugin as Kubernetes } from '@backstage/plugin-kubernetes'; export { plugin as Cloudbuild } from '@backstage/plugin-cloudbuild'; export { plugin as CostInsights } from '@backstage/plugin-cost-insights'; export { plugin as GitHubInsights } from '@roadiehq/backstage-plugin-github-insights'; +export { plugin as CatalogImport } from '@roadiehq/backstage-plugin-catalog-import'; export { plugin as UserSettings } from '@backstage/plugin-user-settings'; From 3a438e1833ba3a23e24ce1f06e735721f08537d9 Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Sun, 25 Oct 2020 23:38:29 +0100 Subject: [PATCH 015/758] Add config-backend endpoint to generate config --- packages/app/src/App.tsx | 2 +- packages/app/src/apis.ts | 14 - packages/backend/src/plugins/catalog.ts | 2 + packages/catalog-model/src/location/index.ts | 6 +- .../catalog-model/src/location/validation.ts | 7 + .../src/ingestion/ConfigGenerator.ts | 57 +++ .../catalog-backend/src/ingestion/types.ts | 8 + .../src/service/CatalogBuilder.ts | 5 + plugins/catalog-backend/src/service/router.ts | 20 +- yarn.lock | 471 +++++++++++++++--- 10 files changed, 516 insertions(+), 76 deletions(-) create mode 100644 plugins/catalog-backend/src/ingestion/ConfigGenerator.ts diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 883fe1e1ca..8b10366c6e 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -84,7 +84,7 @@ const AppRoutes = () => ( element={} /> } /> - } /> + } /> {...deprecatedAppRoutes} ); diff --git a/packages/app/src/apis.ts b/packages/app/src/apis.ts index c129bebfa2..567ac13c78 100644 --- a/packages/app/src/apis.ts +++ b/packages/app/src/apis.ts @@ -25,16 +25,6 @@ import { GraphQLEndpoints, } from '@backstage/plugin-graphiql'; -import { - TravisCIApi, - travisCIApiRef, -} from '@roadiehq/backstage-plugin-travis-ci'; - -import { - GithubPullRequestsClient, - githubPullRequestsApiRef, -} from '@roadiehq/backstage-plugin-github-pull-requests'; - import { costInsightsApiRef } from '@backstage/plugin-cost-insights'; import { ExampleCostInsightsClient } from './plugins/cost-insights'; @@ -59,8 +49,4 @@ export const apis = [ }), createApiFactory(costInsightsApiRef, new ExampleCostInsightsClient()), - - // TODO: move to plugins - createApiFactory(travisCIApiRef, new TravisCIApi()), - createApiFactory(githubPullRequestsApiRef, new GithubPullRequestsClient()), ]; diff --git a/packages/backend/src/plugins/catalog.ts b/packages/backend/src/plugins/catalog.ts index 3c205a3800..8d6c823dce 100644 --- a/packages/backend/src/plugins/catalog.ts +++ b/packages/backend/src/plugins/catalog.ts @@ -28,6 +28,7 @@ export default async function createPlugin(env: PluginEnvironment) { entitiesCatalog, locationsCatalog, higherOrderOperation, + configGenerator, } = await builder.build(); useHotCleanup( @@ -39,6 +40,7 @@ export default async function createPlugin(env: PluginEnvironment) { entitiesCatalog, locationsCatalog, higherOrderOperation, + configGenerator, logger: env.logger, }); } diff --git a/packages/catalog-model/src/location/index.ts b/packages/catalog-model/src/location/index.ts index ff696524ab..1b7587c1f0 100644 --- a/packages/catalog-model/src/location/index.ts +++ b/packages/catalog-model/src/location/index.ts @@ -15,5 +15,9 @@ */ export type { Location, LocationSpec } from './types'; -export { locationSchema, locationSpecSchema } from './validation'; +export { + locationSchema, + locationSpecSchema, + repoPathSchema, +} from './validation'; export { LOCATION_ANNOTATION } from './annotation'; diff --git a/packages/catalog-model/src/location/validation.ts b/packages/catalog-model/src/location/validation.ts index 27d74b9a82..28662119b0 100644 --- a/packages/catalog-model/src/location/validation.ts +++ b/packages/catalog-model/src/location/validation.ts @@ -34,3 +34,10 @@ export const locationSchema = yup }) .noUnknown() .required(); + +export const repoPathSchema = yup + .object<{ repoPath: string }>({ + repoPath: yup.string().required(), + }) + .noUnknown() + .required(); diff --git a/plugins/catalog-backend/src/ingestion/ConfigGenerator.ts b/plugins/catalog-backend/src/ingestion/ConfigGenerator.ts new file mode 100644 index 0000000000..6879cd8291 --- /dev/null +++ b/plugins/catalog-backend/src/ingestion/ConfigGenerator.ts @@ -0,0 +1,57 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { InputError } from '@backstage/backend-common'; +import { Entity, Location, LocationSpec } from '@backstage/catalog-model'; +import { v4 as uuidv4 } from 'uuid'; +import { Logger } from 'winston'; +import { EntitiesCatalog, LocationsCatalog } from '../catalog'; +import { EntityUpsertResponse } from '../catalog/types'; +import { durationText } from '../util/timing'; +import { AddLocationResult, ConfigGenerator, LocationReader } from './types'; + +/** + * Placeholder for operations that span several catalogs and/or stretches out + * in time. + * + * TODO(freben): Find a better home for these, possibly refactoring to use the + * database more directly. + */ +export class ConfigGeneratorClient implements ConfigGenerator { + logger: Logger; + + constructor(logger: Logger) { + this.logger = logger; + } + async generateConfig(repoPath: string): Promise { + const [ownerName, repoName] = [ + repoPath.split('/')[0], + repoPath.split('/')[1], + ]; + const config = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: repoName, + description: '', + annotations: { 'github.com/project-slug': repoPath }, + }, + spec: { type: 'service', owner: ownerName, lifecycle: 'experimental' }, + }; + + return [config]; + } +} diff --git a/plugins/catalog-backend/src/ingestion/types.ts b/plugins/catalog-backend/src/ingestion/types.ts index c586e2d438..2d7165c140 100644 --- a/plugins/catalog-backend/src/ingestion/types.ts +++ b/plugins/catalog-backend/src/ingestion/types.ts @@ -65,3 +65,11 @@ export type ReadLocationError = { location: LocationSpec; error: Error; }; + +// +// ConfigGenerator +// + +export type ConfigGenerator = { + generateConfig(repoPath: string): Promise; +}; diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts index 4ec2a0834d..c232887b04 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.ts @@ -64,12 +64,14 @@ import { YamlProcessor, } from '../ingestion'; import { CatalogRulesEnforcer } from '../ingestion/CatalogRules'; +import { ConfigGeneratorClient } from '../ingestion/ConfigGenerator'; import { LdapOrgReaderProcessor } from '../ingestion/processors/LdapOrgReaderProcessor'; import { jsonPlaceholderResolver, textPlaceholderResolver, yamlPlaceholderResolver, } from '../ingestion/processors/PlaceholderProcessor'; +import { ConfigGenerator } from '../ingestion/types'; export type CatalogEnvironment = { logger: Logger; @@ -249,6 +251,7 @@ export class CatalogBuilder { entitiesCatalog: EntitiesCatalog; locationsCatalog: LocationsCatalog; higherOrderOperation: HigherOrderOperation; + configGenerator: ConfigGenerator; }> { const { config, database, logger } = this.env; @@ -276,11 +279,13 @@ export class CatalogBuilder { locationReader, logger, ); + const configGenerator = new ConfigGeneratorClient(logger); return { entitiesCatalog, locationsCatalog, higherOrderOperation, + configGenerator, }; } diff --git a/plugins/catalog-backend/src/service/router.ts b/plugins/catalog-backend/src/service/router.ts index c7d05afb2d..aefcca28e2 100644 --- a/plugins/catalog-backend/src/service/router.ts +++ b/plugins/catalog-backend/src/service/router.ts @@ -15,27 +15,33 @@ */ import { errorHandler, InputError } from '@backstage/backend-common'; -import { locationSpecSchema } from '@backstage/catalog-model'; +import { locationSpecSchema, repoPathSchema } from '@backstage/catalog-model'; import type { Entity } from '@backstage/catalog-model'; import express from 'express'; import Router from 'express-promise-router'; import { Logger } from 'winston'; import { EntitiesCatalog, LocationsCatalog } from '../catalog'; import { EntityFilters } from '../database'; -import { HigherOrderOperation } from '../ingestion/types'; +import { ConfigGenerator, HigherOrderOperation } from '../ingestion/types'; import { requireRequestBody, validateRequestBody } from './util'; export interface RouterOptions { entitiesCatalog?: EntitiesCatalog; locationsCatalog?: LocationsCatalog; higherOrderOperation?: HigherOrderOperation; + configGenerator?: ConfigGenerator; logger: Logger; } export async function createRouter( options: RouterOptions, ): Promise { - const { entitiesCatalog, locationsCatalog, higherOrderOperation } = options; + const { + entitiesCatalog, + locationsCatalog, + higherOrderOperation, + configGenerator, + } = options; const router = Router(); router.use(express.json()); @@ -121,6 +127,14 @@ export async function createRouter( }); } + if (configGenerator) { + router.post('/generate-config', async (req, res) => { + const input = await validateRequestBody(req, repoPathSchema); + const output = await configGenerator.generateConfig(input.repoPath); + res.status(201).send(output); + }); + } + router.use(errorHandler()); return router; } diff --git a/yarn.lock b/yarn.lock index 82dc99d277..84334952e8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1673,6 +1673,22 @@ resolved "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46" integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA== +"@eslint/eslintrc@^0.2.0": + version "0.2.0" + resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.2.0.tgz#bc7e3c4304d4c8720968ccaee793087dfb5fe6b4" + integrity sha512-+cIGPCBdLCzqxdtwppswP+zTsH9BOIGzAeKfBIbtb4gW/giMlfMwP0HUSFfhzh20f9u8uZ8hOp62+4GPquTbwQ== + dependencies: + ajv "^6.12.4" + debug "^4.1.1" + espree "^7.3.0" + globals "^12.1.0" + ignore "^4.0.6" + import-fresh "^3.2.1" + js-yaml "^3.13.1" + lodash "^4.17.19" + minimatch "^3.0.4" + strip-json-comments "^3.1.1" + "@evocateur/libnpmaccess@^3.1.2": version "3.1.2" resolved "https://registry.npmjs.org/@evocateur/libnpmaccess/-/libnpmaccess-3.1.2.tgz#ecf7f6ce6b004e9f942b098d92200be4a4b1c845" @@ -3218,7 +3234,7 @@ resolved "https://registry.npmjs.org/@material-icons/font/-/font-1.0.3.tgz#f722e5a69a03f20ef47d015cb69420bebeeaabe5" integrity sha512-aIRd0Z9b/HJ/O24KOaP7dNsXypMnXhpWrpLVYvQB/JesVqXYSbioYND200sR+C14a0LSCp+qWnWCnSXmN1hWGw== -"@material-ui/core@^4.11.0", "@material-ui/core@^4.9.1": +"@material-ui/core@^4.11.0", "@material-ui/core@^4.9.1", "@material-ui/core@^4.9.10": version "4.11.0" resolved "https://registry.npmjs.org/@material-ui/core/-/core-4.11.0.tgz#b69b26e4553c9e53f2bfaf1053e216a0af9be15a" integrity sha512-bYo9uIub8wGhZySHqLQ833zi4ZML+XCBE1XwJ8EuUVSpTWWG57Pm+YugQToJNFsEyiKFhPh8DPD0bgupz8n01g== @@ -3254,7 +3270,7 @@ prop-types "^15.7.2" react-is "^16.8.0" -"@material-ui/lab@^4.0.0-alpha.56": +"@material-ui/lab@^4.0.0-alpha.49", "@material-ui/lab@^4.0.0-alpha.56": version "4.0.0-alpha.56" resolved "https://registry.npmjs.org/@material-ui/lab/-/lab-4.0.0-alpha.56.tgz#ff63080949b55b40625e056bbda05e130d216d34" integrity sha512-xPlkK+z/6y/24ka4gVJgwPfoCF4RCh8dXb1BNE7MtF9bXEBLN/lBxNTK8VAa0qm3V2oinA6xtUIdcRh0aeRtVw== @@ -3547,7 +3563,7 @@ dependencies: "@types/node" ">= 8" -"@open-draft/until@^1.0.3": +"@open-draft/until@^1.0.0", "@open-draft/until@^1.0.3": version "1.0.3" resolved "https://registry.npmjs.org/@open-draft/until/-/until-1.0.3.tgz#db9cc719191a62e7d9200f6e7bab21c5b848adca" integrity sha512-Aq58f5HiWdyDlFffbbSjAlv596h/cOnt2DO1w3DOC7OJ5EHs0hd/nycJfiu9RJbT6Yk6F1knnRRXNSpxoIVZ9Q== @@ -3628,6 +3644,11 @@ prop-types "^15.6.1" react-lifecycles-compat "^3.0.4" +"@rehooks/local-storage@^2.4.0": + version "2.4.0" + resolved "https://registry.npmjs.org/@rehooks/local-storage/-/local-storage-2.4.0.tgz#fb884b2b657cad5f77aa6ab60bb3532f0e0725d2" + integrity sha512-LoXDbEHsuIckVgBsFAv8SuU/M7memjyfWut9Zf36TQXqqCHBRFv8bweg9PymQCa1aWIMjNrZQflFdo55FDlXYg== + "@rjsf/core@^2.4.0": version "2.4.0" resolved "https://registry.npmjs.org/@rjsf/core/-/core-2.4.0.tgz#c50bcff0d8178948ce08123177399d8816d51929" @@ -3670,51 +3691,6 @@ react-router "^6.0.0-beta.0" react-use "^15.3.3" -"@roadiehq/backstage-plugin-github-pull-requests@^0.5.2": - version "0.5.2" - resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-github-pull-requests/-/backstage-plugin-github-pull-requests-0.5.2.tgz#455355fe5cdcc43c22b80a8ef3697378084680a1" - integrity sha512-GM+nWQZ+aehMclN70Pp35UQe3I46SQCjWiGZ6/0TfDzrueHln39L2UspUj1L/713xDDtDK8ZnTJdFn/qKx7j4A== - dependencies: - "@backstage/catalog-model" "^0.1.1-alpha.25" - "@backstage/core" "^0.1.1-alpha.25" - "@backstage/plugin-catalog" "^0.1.1-alpha.25" - "@backstage/theme" "^0.1.1-alpha.25" - "@material-ui/core" "^4.11.0" - "@material-ui/icons" "^4.9.1" - "@material-ui/lab" "^4.0.0-alpha.56" - "@octokit/rest" "^18.0.0" - "@octokit/types" "^5.0.1" - "@types/react-dom" "^16.9.8" - history "^5.0.0" - moment "^2.27.0" - react "^16.13.1" - react-dom "^16.13.1" - react-router "6.0.0-beta.0" - react-use "^15.3.3" - -"@roadiehq/backstage-plugin-travis-ci@^0.2.3": - version "0.2.3" - resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-travis-ci/-/backstage-plugin-travis-ci-0.2.3.tgz#2d1c5a7ed3eab4fdcf95243b73dee44c0cff5a57" - integrity sha512-Xi8vZFbcm+D4ykyU3I9hImAd22F1v6M1F/H9CJanRHqj5esJxnuCS2+Kf+w09bwb9DuM8/c68PuMlf3PMIsOwg== - dependencies: - "@backstage/catalog-model" "^0.1.1-alpha.22" - "@backstage/core" "^0.1.1-alpha.22" - "@backstage/core-api" "^0.1.1-alpha.22" - "@backstage/plugin-catalog" "^0.1.1-alpha.22" - "@backstage/theme" "^0.1.1-alpha.22" - "@material-ui/core" "^4.9.1" - "@material-ui/icons" "^4.9.1" - "@material-ui/lab" "4.0.0-alpha.45" - date-fns "^2.15.0" - history "^5.0.0" - moment "^2.27.0" - react "^16.13.1" - react-dom "^16.13.1" - react-lazylog "^4.5.2" - react-router "6.0.0-beta.0" - react-router-dom "6.0.0-beta.0" - react-use "^15.3.3" - "@rollup/plugin-commonjs@^13.0.0": version "13.0.0" resolved "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-13.0.0.tgz#8a1d684ba6848afe8b9e3d85649d4b2f6f7217ec" @@ -3728,6 +3704,19 @@ magic-string "^0.25.2" resolve "^1.11.0" +"@rollup/plugin-commonjs@^14.0.0": + version "14.0.0" + resolved "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-14.0.0.tgz#4285f9ec2db686a31129e5a2b415c94aa1f836f0" + integrity sha512-+PSmD9ePwTAeU106i9FRdc+Zb3XUWyW26mo5Atr2mk82hor8+nPwkztEjFo8/B1fJKfaQDg9aM2bzQkjhi7zOw== + dependencies: + "@rollup/pluginutils" "^3.0.8" + commondir "^1.0.1" + estree-walker "^1.0.1" + glob "^7.1.2" + is-reference "^1.1.2" + magic-string "^0.25.2" + resolve "^1.11.0" + "@rollup/plugin-json@^4.0.2": version "4.1.0" resolved "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-4.1.0.tgz#54e09867ae6963c593844d8bd7a9c718294496f3" @@ -3735,6 +3724,19 @@ dependencies: "@rollup/pluginutils" "^3.0.8" +"@rollup/plugin-node-resolve@^8.4.0": + version "8.4.0" + resolved "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-8.4.0.tgz#261d79a680e9dc3d86761c14462f24126ba83575" + integrity sha512-LFqKdRLn0ShtQyf6SBYO69bGE1upV6wUhBX0vFOUnLAyzx5cwp8svA0eHUnu8+YU57XOkrMtfG63QOpQx25pHQ== + dependencies: + "@rollup/pluginutils" "^3.1.0" + "@types/resolve" "1.17.1" + builtin-modules "^3.1.0" + deep-freeze "^0.0.1" + deepmerge "^4.2.2" + is-module "^1.0.0" + resolve "^1.17.0" + "@rollup/plugin-node-resolve@^9.0.0": version "9.0.0" resolved "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-9.0.0.tgz#39bd0034ce9126b39c1699695f440b4b7d2b62e6" @@ -3765,6 +3767,15 @@ estree-walker "^1.0.1" picomatch "^2.2.2" +"@rollup/pluginutils@^4.0.0": + version "4.0.0" + resolved "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.0.0.tgz#e18e9f5a3925779fc15209dd316c1bd260d195ef" + integrity sha512-b5QiJRye4JlSg29bKNEECoKbLuPXZkPEHSgEjjP1CJV1CPdDBybfYHfm6kyq8yK51h/Zsyl8OvWUrp0FUBukEQ== + dependencies: + "@types/estree" "0.0.45" + estree-walker "^2.0.1" + picomatch "^2.2.2" + "@samverschueren/stream-to-observable@^0.3.0": version "0.3.0" resolved "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz#ecdf48d532c58ea477acfcab80348424f8d0662f" @@ -4696,7 +4707,7 @@ "@babel/runtime" "^7.5.4" "@types/testing-library__react-hooks" "^3.3.0" -"@testing-library/react-hooks@^3.4.2": +"@testing-library/react-hooks@^3.4.1", "@testing-library/react-hooks@^3.4.2": version "3.4.2" resolved "https://registry.npmjs.org/@testing-library/react-hooks/-/react-hooks-3.4.2.tgz#8deb94f7684e0d896edd84a4c90e5b79a0810bc2" integrity sha512-RfPG0ckOzUIVeIqlOc1YztKgFW+ON8Y5xaSPbiBkfj9nMkkiLhLeBXT5icfPX65oJV/zCZu4z8EVnUc6GY9C5A== @@ -4936,6 +4947,11 @@ dependencies: "@types/express" "*" +"@types/cookie@^0.3.3": + version "0.3.3" + resolved "https://registry.npmjs.org/@types/cookie/-/cookie-0.3.3.tgz#85bc74ba782fb7aa3a514d11767832b0e3bc6803" + integrity sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow== + "@types/cookie@^0.4.0": version "0.4.0" resolved "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.0.tgz#14f854c0f93d326e39da6e3b6f34f7d37513d108" @@ -5027,6 +5043,11 @@ resolved "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== +"@types/estree@0.0.45": + version "0.0.45" + resolved "https://registry.npmjs.org/@types/estree/-/estree-0.0.45.tgz#e9387572998e5ecdac221950dab3e8c3b16af884" + integrity sha512-jnqIUKDUqJbDIUxm0Uj7bnlMnRm1T/eZ9N+AVMqhPgzrba2GhGG5o/jCTwmdPK709nEZsGoMzXEDUjcXHa3W0g== + "@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.5": version "4.17.9" resolved "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.9.tgz#2d7b34dcfd25ec663c25c85d76608f8b249667f1" @@ -5117,6 +5138,11 @@ resolved "https://registry.npmjs.org/@types/history/-/history-4.7.5.tgz#527d20ef68571a4af02ed74350164e7a67544860" integrity sha512-wLD/Aq2VggCJXSjxEwrMafIP51Z+13H78nXIX0ABEuIGhmB5sNGbR113MOKo+yfw+RDo1ZU3DM6yfnnRF/+ouw== +"@types/history@^4.7.7": + version "4.7.8" + resolved "https://registry.npmjs.org/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934" + integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA== + "@types/html-minifier-terser@^5.0.0": version "5.1.0" resolved "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.0.tgz#551a4589b6ee2cc9c1dff08056128aec29b94880" @@ -5226,6 +5252,14 @@ jest-diff "^25.2.1" pretty-format "^25.2.1" +"@types/jest@^25.2.2": + version "25.2.3" + resolved "https://registry.npmjs.org/@types/jest/-/jest-25.2.3.tgz#33d27e4c4716caae4eced355097a47ad363fdcaf" + integrity sha512-JXc1nK/tXHiDhV55dvfzqtmP4S3sy3T3ouV2tkViZgxY/zeUkcpQcQPGRlgF4KmWzWW5oiWYSZwtCB+2RsE4Fw== + dependencies: + jest-diff "^25.2.1" + pretty-format "^25.2.1" + "@types/jquery@^3.3.34": version "3.5.1" resolved "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.1.tgz#cebb057acf5071c40e439f30e840c57a30d406c3" @@ -5970,7 +6004,7 @@ resolved "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d" integrity sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg== -"@typescript-eslint/eslint-plugin@^v3.10.1": +"@typescript-eslint/eslint-plugin@^3.8.0", "@typescript-eslint/eslint-plugin@^v3.10.1": version "3.10.1" resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.10.1.tgz#7e061338a1383f59edc204c605899f93dc2e2c8f" integrity sha512-PQg0emRtzZFWq6PxBcdxRH3QIQiyFO3WCVpRL3fgj5oQS3CDs3AeAKfv4DxNhzn8ITdNJGJ4D3Qw8eAJf3lXeQ== @@ -6005,7 +6039,7 @@ eslint-scope "^5.0.0" eslint-utils "^2.0.0" -"@typescript-eslint/parser@^v3.10.1": +"@typescript-eslint/parser@^3.8.0", "@typescript-eslint/parser@^v3.10.1": version "3.10.1" resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-3.10.1.tgz#1883858e83e8b442627e1ac6f408925211155467" integrity sha512-Ug1RcWcrJP02hmtaXVS3axPPTTPnZjupqhgj+NnZ6BCkwSImWk/283347+x9wN+lqOdK9Eo3vsyiyDHgsmiEJw== @@ -6342,6 +6376,11 @@ acorn@^7.1.1, acorn@^7.2.0: resolved "https://registry.npmjs.org/acorn/-/acorn-7.3.1.tgz#85010754db53c3fbaf3b9ea3e083aa5c5d147ffd" integrity sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA== +acorn@^7.4.0: + version "7.4.1" + resolved "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== + address@1.1.2, address@^1.0.1: version "1.1.2" resolved "https://registry.npmjs.org/address/-/address-1.1.2.tgz#bf1116c9c758c51b7a933d296b72c221ed9428b6" @@ -9231,7 +9270,7 @@ cross-env@^7.0.0: dependencies: cross-spawn "^7.0.1" -cross-fetch@3.0.6, cross-fetch@^3.0.5, cross-fetch@^3.0.6: +cross-fetch@3.0.6, cross-fetch@^3.0.4, cross-fetch@^3.0.5, cross-fetch@^3.0.6: version "3.0.6" resolved "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.0.6.tgz#3a4040bc8941e653e0e9cf17f29ebcd177d3365c" integrity sha512-KBPUbqgFjzWlVcURG+Svp9TlhA5uliYtiNx/0r8nv0pdypeQCRJ9IaSIc3q/x3q8t3F75cHuwxVql1HFGHCNJQ== @@ -9597,7 +9636,7 @@ cyclist@^1.0.1: resolved "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= -cypress@*, cypress@^4.2.0: +cypress@*, cypress@^4.11.0, cypress@^4.2.0: version "4.12.1" resolved "https://registry.npmjs.org/cypress/-/cypress-4.12.1.tgz#0ead1b9f4c0917d69d8b57f996b6e01fe693b6ec" integrity sha512-9SGIPEmqU8vuRA6xst2CMTYd9sCFCxKSzrHt0wr+w2iAQMCIIsXsQ5Gplns1sT6LDbZcmLv6uehabAOl3fhc9Q== @@ -9930,6 +9969,11 @@ deep-extend@0.6.0, deep-extend@^0.6.0, deep-extend@~0.6.0: resolved "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== +deep-freeze@^0.0.1: + version "0.0.1" + resolved "https://registry.npmjs.org/deep-freeze/-/deep-freeze-0.0.1.tgz#3a0b0005de18672819dfd38cd31f91179c893e84" + integrity sha1-OgsABd4YZygZ39OM0x+RF5yJPoQ= + deep-is@^0.1.3, deep-is@~0.1.3: version "0.1.3" resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" @@ -10683,6 +10727,41 @@ es-abstract@^1.17.0, es-abstract@^1.17.0-next.0, es-abstract@^1.17.0-next.1, es- string.prototype.trimleft "^2.1.1" string.prototype.trimright "^2.1.1" +es-abstract@^1.17.5: + version "1.17.7" + resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz#a4de61b2f66989fc7421676c1cb9787573ace54c" + integrity sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g== + dependencies: + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.2.2" + is-regex "^1.1.1" + object-inspect "^1.8.0" + object-keys "^1.1.1" + object.assign "^4.1.1" + string.prototype.trimend "^1.0.1" + string.prototype.trimstart "^1.0.1" + +es-abstract@^1.18.0-next.0, es-abstract@^1.18.0-next.1: + version "1.18.0-next.1" + resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz#6e3a0a4bda717e5023ab3b8e90bec36108d22c68" + integrity sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA== + dependencies: + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.2.2" + is-negative-zero "^2.0.0" + is-regex "^1.1.1" + object-inspect "^1.8.0" + object-keys "^1.1.1" + object.assign "^4.1.1" + string.prototype.trimend "^1.0.1" + string.prototype.trimstart "^1.0.1" + es-array-method-boxes-properly@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz#873f3e84418de4ee19c5be752990b2e44718d09e" @@ -10773,6 +10852,11 @@ es6-weak-map@^2.0.2: es6-iterator "^2.0.3" es6-symbol "^3.1.1" +esbuild@^0.6.18: + version "0.6.34" + resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.6.34.tgz#76565a60e006f45d5f273b6e59e61ed0816551f5" + integrity sha512-InRdL/Q96pUucPqovJzvuLhquZr6jOn81FDVwFjCKz1rYKIm9OdOC+7Fs4vr6x48vKBl5LzKgtjU39BUpO636A== + esbuild@^0.7.7: version "0.7.7" resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.7.7.tgz#fd86332d1c0a047231bd6da930666028c40c14bf" @@ -10861,6 +10945,13 @@ eslint-plugin-cypress@^2.10.3: dependencies: globals "^11.12.0" +eslint-plugin-cypress@^2.11.1: + version "2.11.2" + resolved "https://registry.npmjs.org/eslint-plugin-cypress/-/eslint-plugin-cypress-2.11.2.tgz#a8f3fe7ec840f55e4cea37671f93293e6c3e76a0" + integrity sha512-1SergF1sGbVhsf7MYfOLiBhdOg6wqyeV9pXUAIDIffYTGMN3dTBQS9nFAzhLsHhO+Bn0GaVM1Ecm71XUidQ7VA== + dependencies: + globals "^11.12.0" + eslint-plugin-graphql@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/eslint-plugin-graphql/-/eslint-plugin-graphql-4.0.0.tgz#d238ff2baee4d632cfcbe787a7a70a1f50428358" @@ -10957,6 +11048,23 @@ eslint-plugin-react@^7.12.4: string.prototype.matchall "^4.0.2" xregexp "^4.3.0" +eslint-plugin-react@^7.20.5: + version "7.21.5" + resolved "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.21.5.tgz#50b21a412b9574bfe05b21db176e8b7b3b15bff3" + integrity sha512-8MaEggC2et0wSF6bUeywF7qQ46ER81irOdWS4QWxnnlAEsnzeBevk1sWh7fhpCghPpXb+8Ks7hvaft6L/xsR6g== + dependencies: + array-includes "^3.1.1" + array.prototype.flatmap "^1.2.3" + doctrine "^2.1.0" + has "^1.0.3" + jsx-ast-utils "^2.4.1 || ^3.0.0" + object.entries "^1.1.2" + object.fromentries "^2.0.2" + object.values "^1.1.1" + prop-types "^15.7.2" + resolve "^1.18.1" + string.prototype.matchall "^4.0.2" + eslint-scope@^4.0.3: version "4.0.3" resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" @@ -10973,6 +11081,14 @@ eslint-scope@^5.0.0, eslint-scope@^5.1.0: esrecurse "^4.1.0" estraverse "^4.1.1" +eslint-scope@^5.1.1: + version "5.1.1" + resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + eslint-utils@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.0.0.tgz#7be1cc70f27a72a76cd14aa698bcabed6890e1cd" @@ -10980,11 +11096,23 @@ eslint-utils@^2.0.0: dependencies: eslint-visitor-keys "^1.1.0" +eslint-utils@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" + integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== + dependencies: + eslint-visitor-keys "^1.1.0" + eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.2.0.tgz#74415ac884874495f78ec2a97349525344c981fa" integrity sha512-WFb4ihckKil6hu3Dp798xdzSfddwKKU3+nGniKF6HfeW6OLd2OUDEPP7TcHtB5+QXOKg2s6B2DaMPE1Nn/kxKQ== +eslint-visitor-keys@^1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" + integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== + eslint-visitor-keys@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8" @@ -11032,6 +11160,49 @@ eslint@^7.1.0: text-table "^0.2.0" v8-compile-cache "^2.0.3" +eslint@^7.6.0: + version "7.12.0" + resolved "https://registry.npmjs.org/eslint/-/eslint-7.12.0.tgz#7b6a85f87a9adc239e979bb721cde5ce0dc27da6" + integrity sha512-n5pEU27DRxCSlOhJ2rO57GDLcNsxO0LPpAbpFdh7xmcDmjmlGUfoyrsB3I7yYdQXO5N3gkSTiDrPSPNFiiirXA== + dependencies: + "@babel/code-frame" "^7.0.0" + "@eslint/eslintrc" "^0.2.0" + ajv "^6.10.0" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.0.1" + doctrine "^3.0.0" + enquirer "^2.3.5" + eslint-scope "^5.1.1" + eslint-utils "^2.1.0" + eslint-visitor-keys "^2.0.0" + espree "^7.3.0" + esquery "^1.2.0" + esutils "^2.0.2" + file-entry-cache "^5.0.1" + functional-red-black-tree "^1.0.1" + glob-parent "^5.0.0" + globals "^12.1.0" + ignore "^4.0.6" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + js-yaml "^3.13.1" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash "^4.17.19" + minimatch "^3.0.4" + natural-compare "^1.4.0" + optionator "^0.9.1" + progress "^2.0.0" + regexpp "^3.1.0" + semver "^7.2.1" + strip-ansi "^6.0.0" + strip-json-comments "^3.1.0" + table "^5.2.3" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + esm@^3.2.25: version "3.2.25" resolved "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10" @@ -11046,6 +11217,15 @@ espree@^7.1.0: acorn-jsx "^5.2.0" eslint-visitor-keys "^1.2.0" +espree@^7.3.0: + version "7.3.0" + resolved "https://registry.npmjs.org/espree/-/espree-7.3.0.tgz#dc30437cf67947cf576121ebd780f15eeac72348" + integrity sha512-dksIWsvKCixn1yrEXO8UosNSxaDoSYpq9reEjZSbHLpT5hpaCAKTLBwq0RHtLrIr+c0ByiYzWT8KTMRzoRCNlw== + dependencies: + acorn "^7.4.0" + acorn-jsx "^5.2.0" + eslint-visitor-keys "^1.3.0" + esprima@^4.0.0, esprima@^4.0.1, esprima@~4.0.0: version "4.0.1" resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" @@ -11065,6 +11245,13 @@ esrecurse@^4.1.0: dependencies: estraverse "^4.1.0" +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0: version "4.3.0" resolved "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" @@ -11075,6 +11262,11 @@ estraverse@^5.1.0: resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.1.0.tgz#374309d39fd935ae500e7b92e8a6b4c720e59642" integrity sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw== +estraverse@^5.2.0: + version "5.2.0" + resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" + integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== + estree-walker@^0.6.1: version "0.6.1" resolved "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz#53049143f40c6eb918b23671d1fe3219f3a1b362" @@ -11085,6 +11277,11 @@ estree-walker@^1.0.1: resolved "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700" integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg== +estree-walker@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.1.tgz#f8e030fb21cefa183b44b7ad516b747434e7a3e0" + integrity sha512-tF0hv+Yi2Ot1cwj9eYHtxC0jB9bmjacjQs6ZBTj82H8JwUywFuc+7E83NWfNMwHXZc11mjfFcVXPe9gEP4B8dg== + esutils@^2.0.2: version "2.0.3" resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" @@ -12705,7 +12902,7 @@ graphql-upload@^8.0.2: http-errors "^1.7.3" object-path "^0.11.4" -graphql@15.3.0, graphql@^15.3.0: +graphql@15.3.0, graphql@^15.0.0, graphql@^15.3.0: version "15.3.0" resolved "https://registry.npmjs.org/graphql/-/graphql-15.3.0.tgz#3ad2b0caab0d110e3be4a5a9b2aa281e362b5278" integrity sha512-GTCJtzJmkFLWRfFJuoo9RWWa/FfamUHgiFosxi/X1Ani4AVWbeyBenZTNX6dM+7WSbbFfTo/25eh0LLkwHMw2w== @@ -12881,7 +13078,7 @@ he@^1.1.0, he@^1.2.0: resolved "https://registry.npmjs.org/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== -headers-utils@^1.2.0: +headers-utils@^1.1.9, headers-utils@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/headers-utils/-/headers-utils-1.2.0.tgz#5e10d1bc9d2bccf789547afca5b991a3167241e8" integrity sha512-4/BMXcWrJErw7JpM87gF8MNEXcIMLzepYZjNRv/P9ctgupl2Ywa3u1PgHtNhSRq84bHH9Ndlkdy7bSi+bZ9I9A== @@ -13684,6 +13881,11 @@ is-callable@^1.1.4, is-callable@^1.1.5: resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab" integrity sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q== +is-callable@^1.2.2: + version "1.2.2" + resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9" + integrity sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA== + is-ci@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" @@ -13703,6 +13905,13 @@ is-color-stop@^1.0.0: rgb-regex "^1.0.1" rgba-regex "^1.0.0" +is-core-module@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.0.0.tgz#58531b70aed1db7c0e8d4eb1a0a2d1ddd64bd12d" + integrity sha512-jq1AH6C8MuteOoBPwkxHafmByhL9j5q4OaPGdbuD+ZtQJVzH+i6E3BJDQcBA09k57i2Hh2yQbEG8yObZ0jdlWw== + dependencies: + has "^1.0.3" + is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" @@ -13871,6 +14080,11 @@ is-module@^1.0.0: resolved "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE= +is-negative-zero@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz#9553b121b0fac28869da9ed459e20c7543788461" + integrity sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE= + is-npm@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz#c90dd8380696df87a7a6d823c20d0b12bbe3c84d" @@ -14389,6 +14603,14 @@ jest-esm-transformer@^1.0.0: "@babel/core" "^7.4.4" "@babel/plugin-transform-modules-commonjs" "^7.4.4" +jest-fetch-mock@^3.0.3: + version "3.0.3" + resolved "https://registry.npmjs.org/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz#31749c456ae27b8919d69824f1c2bd85fe0a1f3b" + integrity sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw== + dependencies: + cross-fetch "^3.0.4" + promise-polyfill "^8.1.3" + jest-get-type@^25.2.6: version "25.2.6" resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz#0b0a32fab8908b44d508be81681487dbabb8d877" @@ -14683,6 +14905,11 @@ jose@^2.0.2: dependencies: "@panva/asn1.js" "^1.0.0" +joycon@^2.2.5: + version "2.2.5" + resolved "https://registry.npmjs.org/joycon/-/joycon-2.2.5.tgz#8d4cf4cbb2544d7b7583c216fcdfec19f6be1615" + integrity sha512-YqvUxoOcVPnCp0VU1/56f+iKSdvIRJYPznH22BdXV3xMk75SFXhWeJkZ8C9XxUWt1b5x2X1SxuFygW1U0FmkEQ== + js-cookie@^2.2.1: version "2.2.1" resolved "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8" @@ -15088,6 +15315,14 @@ jsx-ast-utils@^2.2.1, jsx-ast-utils@^2.2.3: array-includes "^3.0.3" object.assign "^4.1.0" +"jsx-ast-utils@^2.4.1 || ^3.0.0": + version "3.1.0" + resolved "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.1.0.tgz#642f1d7b88aa6d7eb9d8f2210e166478444fa891" + integrity sha512-d4/UOjg+mxAWxCiF0c5UTSwyqbchkbqCvK87aBovhnh8GtysTjWmgC63tY0cJx/HzGgm9qnA147jVBdpOiQ2RA== + dependencies: + array-includes "^3.1.1" + object.assign "^4.1.1" + jwa@^1.4.1: version "1.4.1" resolved "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" @@ -16516,6 +16751,22 @@ ms@2.1.2, ms@^2.0.0, ms@^2.1.1: resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +msw@^0.19.5: + version "0.19.5" + resolved "https://registry.npmjs.org/msw/-/msw-0.19.5.tgz#7d2a1a852ccf1644d3db6735d69fff6777aac33f" + integrity sha512-J5eQ++gDVZoHPC8gVXtWcakLjgmPipvFj/sEnlRV/WViXuiq2CamSqO3Wbh6H8bAmj+k2vUWCfcVT1HjMdKB2Q== + dependencies: + "@open-draft/until" "^1.0.0" + "@types/cookie" "^0.3.3" + chalk "^4.0.0" + cookie "^0.4.1" + graphql "^15.0.0" + headers-utils "^1.1.9" + node-match-path "^0.4.2" + node-request-interceptor "^0.2.5" + statuses "^2.0.0" + yargs "^15.3.1" + msw@^0.20.5: version "0.20.5" resolved "https://registry.npmjs.org/msw/-/msw-0.20.5.tgz#b6141080c0d8b17c451d9ca36c28cc47b4ac487a" @@ -16829,7 +17080,7 @@ node-libs-browser@^2.2.1: util "^0.11.0" vm-browserify "^1.0.1" -node-match-path@^0.4.4: +node-match-path@^0.4.2, node-match-path@^0.4.4: version "0.4.4" resolved "https://registry.npmjs.org/node-match-path/-/node-match-path-0.4.4.tgz#516a10926093c0cc6f237d020685b593b19baebb" integrity sha512-pBq9gp7TG0r0VXuy/oeZmQsjBSnYQo7G886Ly/B3azRwZuEtHCY155dzmfoKWcDPGgyfIGD8WKVC7h3+6y7yTg== @@ -16888,6 +17139,14 @@ node-releases@^1.1.52, node-releases@^1.1.58: resolved "https://registry.npmjs.org/node-releases/-/node-releases-1.1.60.tgz#6948bdfce8286f0b5d0e5a88e8384e954dfe7084" integrity sha512-gsO4vjEdQaTusZAEebUWp2a5d7dF5DYoIpDG7WySnk7BuZDW+GPpHXoXXuYawRBr/9t5q54tirPz79kFIWg4dA== +node-request-interceptor@^0.2.5: + version "0.2.6" + resolved "https://registry.npmjs.org/node-request-interceptor/-/node-request-interceptor-0.2.6.tgz#541278d7033bb6a8befb5dd793f83428cf6446a2" + integrity sha512-aJW1tPSM7nzuZFRe+C/KSz22GJO3CVFMxHHmMGX8Z+tjP7TCIVbzeckLFVfJG68BdVgrdOOP7Ejc57ag820eyA== + dependencies: + debug "^4.1.1" + headers-utils "^1.2.0" + node-request-interceptor@^0.3.5: version "0.3.5" resolved "https://registry.npmjs.org/node-request-interceptor/-/node-request-interceptor-0.3.5.tgz#4b26159617829c9a70643012c0fdc3ae4c78ae43" @@ -17150,6 +17409,11 @@ object-inspect@^1.7.0: resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw== +object-inspect@^1.8.0: + version "1.8.0" + resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" + integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA== + object-is@^1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/object-is/-/object-is-1.0.2.tgz#6b80eb84fe451498f65007982f035a5b445edec4" @@ -17182,6 +17446,16 @@ object.assign@^4.1.0: has-symbols "^1.0.0" object-keys "^1.0.11" +object.assign@^4.1.1: + version "4.1.1" + resolved "https://registry.npmjs.org/object.assign/-/object.assign-4.1.1.tgz#303867a666cdd41936ecdedfb1f8f3e32a478cdd" + integrity sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.18.0-next.0" + has-symbols "^1.0.1" + object-keys "^1.1.1" + object.defaults@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz#3a7f868334b407dea06da16d88d5cd29e435fecf" @@ -17202,6 +17476,15 @@ object.entries@^1.1.0, object.entries@^1.1.1: function-bind "^1.1.1" has "^1.0.3" +object.entries@^1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/object.entries/-/object.entries-1.1.2.tgz#bc73f00acb6b6bb16c203434b10f9a7e797d3add" + integrity sha512-BQdB9qKmb/HyNdMNWVr7O3+z5MUIx3aiegEIJqjMBbBf0YT9RRxTJSim4mzFqtyr7PDAHigq0N9dO0m0tRakQA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + has "^1.0.3" + "object.fromentries@^2.0.0 || ^1.0.0", object.fromentries@^2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.2.tgz#4a09c9b9bb3843dd0f89acdb517a794d4f355ac9" @@ -18862,6 +19145,11 @@ promise-inflight@^1.0.1: resolved "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= +promise-polyfill@^8.1.3: + version "8.2.0" + resolved "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.2.0.tgz#367394726da7561457aba2133c9ceefbd6267da0" + integrity sha512-k/TC0mIcPVF6yHhUvwAp7cvL6I2fFV7TzF1DuGPI8mBh4QQazf36xCKEHKTZKRysEoTQoQdKyP25J8MPJp7j5g== + promise-retry@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/promise-retry/-/promise-retry-1.1.1.tgz#6739e968e3051da20ce6497fb2b50f6911df3d6d" @@ -20449,6 +20737,14 @@ resolve@1.17.0, resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.11.0 dependencies: path-parse "^1.0.6" +resolve@^1.18.1: + version "1.18.1" + resolved "https://registry.npmjs.org/resolve/-/resolve-1.18.1.tgz#018fcb2c5b207d2a6424aee361c5a266da8f4130" + integrity sha512-lDfCPaMKfOJXjy0dPayzPdF1phampNWr3qFCjAu+rw/qbQmr5jWH5xN2hwh9QKfw9E5v4hwV7A+jrCmL8yjjqA== + dependencies: + is-core-module "^2.0.0" + path-parse "^1.0.6" + responselike@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" @@ -20563,13 +20859,20 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" -rollup-plugin-dts@1.4.13: +rollup-plugin-dts@1.4.13, rollup-plugin-dts@^1.4.10: version "1.4.13" resolved "https://registry.npmjs.org/rollup-plugin-dts/-/rollup-plugin-dts-1.4.13.tgz#4f086e84f4fdcc1f49160799ebc66f6b09db292b" integrity sha512-7mxoQ6PcmCkBE5ZhrjGDL4k42XLy8BkSqpiRi1MipwiGs+7lwi4mQkp2afX+OzzLjJp/TGM8llfe8uayIUhPEw== optionalDependencies: "@babel/code-frame" "^7.10.4" +rollup-plugin-dts@1.4.8: + version "1.4.8" + resolved "https://registry.npmjs.org/rollup-plugin-dts/-/rollup-plugin-dts-1.4.8.tgz#3b0eadc285aede11c3f5ced72b56b5f7735df95c" + integrity sha512-2qHB4B3oaTyi1mDmqDzsRGKlev32v3EhMUAmc45Cn9eB22R7FsYDiigvT9XdM/oQzfd0qv5MkeZju8DP0nauiA== + optionalDependencies: + "@babel/code-frame" "^7.10.4" + rollup-plugin-esbuild@^2.0.0: version "2.3.0" resolved "https://registry.npmjs.org/rollup-plugin-esbuild/-/rollup-plugin-esbuild-2.3.0.tgz#7c107d4508af80d30966a148b306cb7d5b36b258" @@ -20577,11 +20880,25 @@ rollup-plugin-esbuild@^2.0.0: dependencies: "@rollup/pluginutils" "^3.1.0" +rollup-plugin-esbuild@^2.4.2: + version "2.5.2" + resolved "https://registry.npmjs.org/rollup-plugin-esbuild/-/rollup-plugin-esbuild-2.5.2.tgz#fd7d4a88518898012a9d07e4c3ef8b4009c244d3" + integrity sha512-E4q3ac1AlMd0m0ZRYffdiorOt2eZcxfbdPaqBLs7JLnPE8krgIAihOD6cTUc54UJjoOMA9WcY63TR+JKWLzYNw== + dependencies: + "@rollup/pluginutils" "^4.0.0" + joycon "^2.2.5" + strip-json-comments "^3.1.1" + rollup-plugin-peer-deps-external@^2.2.2: version "2.2.3" resolved "https://registry.npmjs.org/rollup-plugin-peer-deps-external/-/rollup-plugin-peer-deps-external-2.2.3.tgz#059a8aec1eefb48a475e9fcedc3b9e3deb521213" integrity sha512-W6IePXTExGXVDAlfZbNUUrx3GxUOZP248u5n4a4ID1XZMrbQ+uGeNiEfapvdzwx0qZi5DNH/hDLiPUP+pzFIxg== +rollup-plugin-peer-deps-external@^2.2.3: + version "2.2.4" + resolved "https://registry.npmjs.org/rollup-plugin-peer-deps-external/-/rollup-plugin-peer-deps-external-2.2.4.tgz#8a420bbfd6dccc30aeb68c9bf57011f2f109570d" + integrity sha512-AWdukIM1+k5JDdAqV/Cxd+nejvno2FVLVeZ74NKggm3Q5s9cbbcOgUPGdbxPi4BXu7xGaZ8HG12F+thImYu/0g== + rollup-plugin-postcss@^3.1.1: version "3.1.8" resolved "https://registry.npmjs.org/rollup-plugin-postcss/-/rollup-plugin-postcss-3.1.8.tgz#d1bcaf8eb0fcb0936e3684c22dd8628d13a82fd1" @@ -20620,6 +20937,13 @@ rollup-pluginutils@^2.8.2: dependencies: estree-walker "^0.6.1" +rollup@2.23.1: + version "2.23.1" + resolved "https://registry.npmjs.org/rollup/-/rollup-2.23.1.tgz#d458d28386dc7660c2e8a4978bea6f9494046c20" + integrity sha512-Heyl885+lyN/giQwxA8AYT2GY3U+gOlTqVLrMQYno8Z1X9lAOpfXPiKiZCyPc25e9BLJM3Zlh957dpTlO4pa8A== + optionalDependencies: + fsevents "~2.1.2" + rollup@2.23.x: version "2.23.0" resolved "https://registry.npmjs.org/rollup/-/rollup-2.23.0.tgz#b7ab1fee0c0e60132fd0553c4df1e9cdacfada9d" @@ -20635,6 +20959,13 @@ rollup@^0.63.4: "@types/estree" "0.0.39" "@types/node" "*" +rollup@^2.23.1: + version "2.32.1" + resolved "https://registry.npmjs.org/rollup/-/rollup-2.32.1.tgz#625a92c54f5b4d28ada12d618641491d4dbb548c" + integrity sha512-Op2vWTpvK7t6/Qnm1TTh7VjEZZkN8RWgf0DHbkKzQBwNf748YhXbozHVefqpPp/Fuyk/PQPAnYsBxAEtlMvpUw== + optionalDependencies: + fsevents "~2.1.2" + rsvp@^4.8.4: version "4.8.5" resolved "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" @@ -21756,6 +22087,14 @@ string.prototype.padstart@^3.0.0: define-properties "^1.1.3" es-abstract "^1.17.0-next.1" +string.prototype.trimend@^1.0.1: + version "1.0.2" + resolved "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.2.tgz#6ddd9a8796bc714b489a3ae22246a208f37bfa46" + integrity sha512-8oAG/hi14Z4nOVP0z6mdiVZ/wqjDtWSLygMigTzAb+7aPEDTleeFf+WrF+alzecxIRkckkJVn+dTlwzJXORATw== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.18.0-next.1" + string.prototype.trimleft@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz#9bdb8ac6abd6d602b17a4ed321870d2f8dcefc74" @@ -21772,6 +22111,14 @@ string.prototype.trimright@^2.1.1: define-properties "^1.1.3" function-bind "^1.1.1" +string.prototype.trimstart@^1.0.1: + version "1.0.2" + resolved "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.2.tgz#22d45da81015309cd0cdd79787e8919fc5c613e7" + integrity sha512-7F6CdBTl5zyu30BJFdzSTlSlLPwODC23Od+iLoVH8X6+3fvDPPuBVVj9iaB1GOsSTSIgVfsfm27R2FGrAPznWg== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.18.0-next.1" + string_decoder@^1.0.0, string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -21874,6 +22221,11 @@ strip-json-comments@^3.1.0: resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.0.tgz#7638d31422129ecf4457440009fba03f9f9ac180" integrity sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w== +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" @@ -24194,6 +24546,11 @@ yaml@*, yaml@^1.10.0, yaml@^1.7.2, yaml@^1.9.2: resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e" integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg== +yaml@^2.0.0-1: + version "2.0.0-1" + resolved "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz#8c3029b3ee2028306d5bcf396980623115ff8d18" + integrity sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ== + yargs-parser@18.x, yargs-parser@^18.1.2: version "18.1.3" resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" From 2ceddba979ea487052826e71147ca78a2c306907 Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Tue, 27 Oct 2020 08:31:53 +0100 Subject: [PATCH 016/758] Cleanup --- .../src/ingestion/ConfigGenerator.ts | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/plugins/catalog-backend/src/ingestion/ConfigGenerator.ts b/plugins/catalog-backend/src/ingestion/ConfigGenerator.ts index 6879cd8291..d4d586b4de 100644 --- a/plugins/catalog-backend/src/ingestion/ConfigGenerator.ts +++ b/plugins/catalog-backend/src/ingestion/ConfigGenerator.ts @@ -14,22 +14,10 @@ * limitations under the License. */ -import { InputError } from '@backstage/backend-common'; -import { Entity, Location, LocationSpec } from '@backstage/catalog-model'; -import { v4 as uuidv4 } from 'uuid'; +import { Entity } from '@backstage/catalog-model'; import { Logger } from 'winston'; -import { EntitiesCatalog, LocationsCatalog } from '../catalog'; -import { EntityUpsertResponse } from '../catalog/types'; -import { durationText } from '../util/timing'; -import { AddLocationResult, ConfigGenerator, LocationReader } from './types'; +import { ConfigGenerator } from './types'; -/** - * Placeholder for operations that span several catalogs and/or stretches out - * in time. - * - * TODO(freben): Find a better home for these, possibly refactoring to use the - * database more directly. - */ export class ConfigGeneratorClient implements ConfigGenerator { logger: Logger; From dbadbe7982a5885199f86887ab7eadfab528d437 Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Wed, 28 Oct 2020 23:45:43 +0100 Subject: [PATCH 017/758] Renames and new types --- packages/backend/src/plugins/catalog.ts | 4 +- packages/catalog-model/src/location/index.ts | 2 +- .../catalog-model/src/location/validation.ts | 6 +- ...ConfigGenerator.ts => LocationAnalyzer.ts} | 30 ++++---- .../catalog-backend/src/ingestion/types.ts | 63 +++++++++++++++- .../src/service/CatalogBuilder.ts | 10 +-- plugins/catalog-backend/src/service/router.ts | 19 +++-- yarn.lock | 75 ++++--------------- 8 files changed, 115 insertions(+), 94 deletions(-) rename plugins/catalog-backend/src/ingestion/{ConfigGenerator.ts => LocationAnalyzer.ts} (60%) diff --git a/packages/backend/src/plugins/catalog.ts b/packages/backend/src/plugins/catalog.ts index 8d6c823dce..1338fab440 100644 --- a/packages/backend/src/plugins/catalog.ts +++ b/packages/backend/src/plugins/catalog.ts @@ -28,7 +28,7 @@ export default async function createPlugin(env: PluginEnvironment) { entitiesCatalog, locationsCatalog, higherOrderOperation, - configGenerator, + locationAnalyzer, } = await builder.build(); useHotCleanup( @@ -40,7 +40,7 @@ export default async function createPlugin(env: PluginEnvironment) { entitiesCatalog, locationsCatalog, higherOrderOperation, - configGenerator, + locationAnalyzer, logger: env.logger, }); } diff --git a/packages/catalog-model/src/location/index.ts b/packages/catalog-model/src/location/index.ts index 1b7587c1f0..ce64b988a6 100644 --- a/packages/catalog-model/src/location/index.ts +++ b/packages/catalog-model/src/location/index.ts @@ -18,6 +18,6 @@ export type { Location, LocationSpec } from './types'; export { locationSchema, locationSpecSchema, - repoPathSchema, + analyzeLocationSchema, } from './validation'; export { LOCATION_ANNOTATION } from './annotation'; diff --git a/packages/catalog-model/src/location/validation.ts b/packages/catalog-model/src/location/validation.ts index 28662119b0..4d2e602862 100644 --- a/packages/catalog-model/src/location/validation.ts +++ b/packages/catalog-model/src/location/validation.ts @@ -35,9 +35,9 @@ export const locationSchema = yup .noUnknown() .required(); -export const repoPathSchema = yup - .object<{ repoPath: string }>({ - repoPath: yup.string().required(), +export const analyzeLocationSchema = yup + .object<{ location: LocationSpec }>({ + location: locationSpecSchema, }) .noUnknown() .required(); diff --git a/plugins/catalog-backend/src/ingestion/ConfigGenerator.ts b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts similarity index 60% rename from plugins/catalog-backend/src/ingestion/ConfigGenerator.ts rename to plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts index d4d586b4de..f6991b1c90 100644 --- a/plugins/catalog-backend/src/ingestion/ConfigGenerator.ts +++ b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts @@ -14,32 +14,36 @@ * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; import { Logger } from 'winston'; -import { ConfigGenerator } from './types'; +import { + AnalyzeLocationRequest, + AnalyzeLocationResponse, + LocationAnalyzer, +} from './types'; -export class ConfigGeneratorClient implements ConfigGenerator { - logger: Logger; +export class LocationAnalyzerClient implements LocationAnalyzer { + private readonly logger: Logger; constructor(logger: Logger) { this.logger = logger; } - async generateConfig(repoPath: string): Promise { - const [ownerName, repoName] = [ - repoPath.split('/')[0], - repoPath.split('/')[1], - ]; - const config = { + async generateConfig( + request: AnalyzeLocationRequest, + ): Promise { + const [ownerName, repoName] = request.location.target.split('/').slice(-2); + const entity = { apiVersion: 'backstage.io/v1alpha1', kind: 'Component', metadata: { name: repoName, - description: '', - annotations: { 'github.com/project-slug': repoPath }, + annotations: { 'github.com/project-slug': `${ownerName}/${repoName}` }, }, spec: { type: 'service', owner: ownerName, lifecycle: 'experimental' }, }; - return [config]; + return { + existingEntityFiles: [], + generateEntities: [{ entity, fields: [] }], + }; } } diff --git a/plugins/catalog-backend/src/ingestion/types.ts b/plugins/catalog-backend/src/ingestion/types.ts index 2d7165c140..19d472b3ab 100644 --- a/plugins/catalog-backend/src/ingestion/types.ts +++ b/plugins/catalog-backend/src/ingestion/types.ts @@ -20,6 +20,7 @@ import type { Location, LocationSpec, } from '@backstage/catalog-model'; +import { RecursivePartial } from './processors/ldap/util'; // // HigherOrderOperation @@ -70,6 +71,64 @@ export type ReadLocationError = { // ConfigGenerator // -export type ConfigGenerator = { - generateConfig(repoPath: string): Promise; +export type LocationAnalyzer = { + generateConfig( + location: AnalyzeLocationRequest, + ): Promise; +}; + +export type AnalyzeLocationRequest = { + location: LocationSpec; +}; + +export type AnalyzeLocationResponse = { + existingEntityFiles: AnalyzeLocationExistingEntity[]; + generateEntities: AnalyzeLocationGenerateEntity[]; +}; + +// If the folder pointed to already contained catalog info yaml files, they are +// read and emitted like this so that the frontend can inform the user that it +// located them and can make sure to register them as well if they weren't +// already +type AnalyzeLocationExistingEntity = { + location: LocationSpec; + isRegistered: boolean; + entity: Entity; +}; + +// This is some form of representation of what the analyzer could deduce. +// We should probably have a chat about how this can best be conveyed to +// the frontend. It'll probably contain a (possibly incomplete) entity, plus +// enough info for the frontend to know what form data to show to the user +// for overriding/completing the info. +type AnalyzeLocationGenerateEntity = { + // Some form of partial representation of the entity + entity: RecursivePartial; + // Lists the suggestions that the user may want to override + fields: AnalyzeLocationEntityField[]; +}; + +// This is where I get really vague. Something like this perhaps? Or it could be +// something like a json-schema that contains enough info for the frontend to +// be able to present a form and explanations +type AnalyzeLocationEntityField = { + // e.g. "spec.owner"? The frontend needs to know how to "inject" the field into the + // entity again if the user wants to change it + field: string; + + // The outcome of the analysis for this particular field + state: + | 'analysis_suggested_value' + | 'analysis_suggested_no_value' + | 'needs_user_input'; + + // If the analysis did suggest a value, this is where it would be. Not sure if we want + // to limit this to strings or if we want it to be any JsonValue + value: string | null; + + // A text to show to the user to inform about the choices made. Like, it could say + // "Found a CODEOWNERS file that covers this target, so we suggest leaving this + // field empty; which would currently make it owned by X" where X is taken from the + // codeowners file. + description: string; }; diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts index 39417dfd6f..439b85591f 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.ts @@ -52,7 +52,7 @@ import { UrlReaderProcessor, } from '../ingestion'; import { CatalogRulesEnforcer } from '../ingestion/CatalogRules'; -import { ConfigGeneratorClient } from '../ingestion/ConfigGenerator'; +import { LocationAnalyzerClient } from '../ingestion/LocationAnalyzer'; import { BuiltinKindsEntityProcessor } from '../ingestion/processors/BuiltinKindsEntityProcessor'; import { LdapOrgReaderProcessor } from '../ingestion/processors/LdapOrgReaderProcessor'; import { @@ -60,7 +60,7 @@ import { textPlaceholderResolver, yamlPlaceholderResolver, } from '../ingestion/processors/PlaceholderProcessor'; -import { ConfigGenerator } from '../ingestion/types'; +import { LocationAnalyzer } from '../ingestion/types'; export type CatalogEnvironment = { logger: Logger; @@ -204,7 +204,7 @@ export class CatalogBuilder { entitiesCatalog: EntitiesCatalog; locationsCatalog: LocationsCatalog; higherOrderOperation: HigherOrderOperation; - configGenerator: ConfigGenerator; + locationAnalyzer: LocationAnalyzer; }> { const { config, database, logger } = this.env; @@ -232,13 +232,13 @@ export class CatalogBuilder { locationReader, logger, ); - const configGenerator = new ConfigGeneratorClient(logger); + const locationAnalyzer = new LocationAnalyzerClient(logger); return { entitiesCatalog, locationsCatalog, higherOrderOperation, - configGenerator, + locationAnalyzer, }; } diff --git a/plugins/catalog-backend/src/service/router.ts b/plugins/catalog-backend/src/service/router.ts index b11572b180..8d5fb73c37 100644 --- a/plugins/catalog-backend/src/service/router.ts +++ b/plugins/catalog-backend/src/service/router.ts @@ -15,13 +15,16 @@ */ import { errorHandler } from '@backstage/backend-common'; -import { locationSpecSchema, repoPathSchema } from '@backstage/catalog-model'; +import { + locationSpecSchema, + analyzeLocationSchema, +} from '@backstage/catalog-model'; import type { Entity } from '@backstage/catalog-model'; import express from 'express'; import Router from 'express-promise-router'; import { Logger } from 'winston'; import { EntitiesCatalog, LocationsCatalog } from '../catalog'; -import { ConfigGenerator, HigherOrderOperation } from '../ingestion/types'; +import { LocationAnalyzer, HigherOrderOperation } from '../ingestion/types'; import { translateQueryToEntityFilters, translateQueryToFieldMapper, @@ -32,7 +35,7 @@ export interface RouterOptions { entitiesCatalog?: EntitiesCatalog; locationsCatalog?: LocationsCatalog; higherOrderOperation?: HigherOrderOperation; - configGenerator?: ConfigGenerator; + locationAnalyzer?: LocationAnalyzer; logger: Logger; } @@ -43,7 +46,7 @@ export async function createRouter( entitiesCatalog, locationsCatalog, higherOrderOperation, - configGenerator, + locationAnalyzer: locationAnalyzer, } = options; const router = Router(); @@ -135,10 +138,10 @@ export async function createRouter( }); } - if (configGenerator) { - router.post('/generate-config', async (req, res) => { - const input = await validateRequestBody(req, repoPathSchema); - const output = await configGenerator.generateConfig(input.repoPath); + if (locationAnalyzer) { + router.post('/analyze-location', async (req, res) => { + const input = await validateRequestBody(req, analyzeLocationSchema); + const output = await locationAnalyzer.generateConfig(input); res.status(201).send(output); }); } diff --git a/yarn.lock b/yarn.lock index 7f85f649c9..1c6125854e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3691,50 +3691,18 @@ react-router "^6.0.0-beta.0" react-use "^15.3.3" -"@roadiehq/backstage-plugin-github-pull-requests@^0.5.2": - version "0.5.2" - resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-github-pull-requests/-/backstage-plugin-github-pull-requests-0.5.2.tgz#455355fe5cdcc43c22b80a8ef3697378084680a1" - integrity sha512-GM+nWQZ+aehMclN70Pp35UQe3I46SQCjWiGZ6/0TfDzrueHln39L2UspUj1L/713xDDtDK8ZnTJdFn/qKx7j4A== +"@rollup/plugin-commonjs@^14.0.0": + version "14.0.0" + resolved "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-14.0.0.tgz#4285f9ec2db686a31129e5a2b415c94aa1f836f0" + integrity sha512-+PSmD9ePwTAeU106i9FRdc+Zb3XUWyW26mo5Atr2mk82hor8+nPwkztEjFo8/B1fJKfaQDg9aM2bzQkjhi7zOw== dependencies: - "@backstage/catalog-model" "^0.1.1-alpha.25" - "@backstage/core" "^0.1.1-alpha.25" - "@backstage/plugin-catalog" "^0.1.1-alpha.25" - "@backstage/theme" "^0.1.1-alpha.25" - "@material-ui/core" "^4.11.0" - "@material-ui/icons" "^4.9.1" - "@material-ui/lab" "^4.0.0-alpha.56" - "@octokit/rest" "^18.0.0" - "@octokit/types" "^5.0.1" - "@types/react-dom" "^16.9.8" - history "^5.0.0" - moment "^2.27.0" - react "^16.13.1" - react-dom "^16.13.1" - react-router "6.0.0-beta.0" - react-use "^15.3.3" - -"@roadiehq/backstage-plugin-travis-ci@^0.2.5": - version "0.2.5" - resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-travis-ci/-/backstage-plugin-travis-ci-0.2.5.tgz#588cd8b4a4dd4bd426c0012f8d35eae4504b55f4" - integrity sha512-4aJizmvIeunpl8D/YDvzagNzikPU51DSZFz1tN0XCwRR+buTPknV141qsKP+yxUbs6nx3Xuh+T/vNkb9lk6FlA== - dependencies: - "@backstage/catalog-model" "^0.1.1-alpha.24" - "@backstage/core" "^0.1.1-alpha.24" - "@backstage/core-api" "^0.1.1-alpha.24" - "@backstage/plugin-catalog" "^0.1.1-alpha.24" - "@backstage/theme" "^0.1.1-alpha.24" - "@material-ui/core" "^4.9.1" - "@material-ui/icons" "^4.9.1" - "@material-ui/lab" "4.0.0-alpha.45" - date-fns "^2.15.0" - history "^5.0.0" - moment "^2.27.0" - react "^16.13.1" - react-dom "^16.13.1" - react-lazylog "^4.5.2" - react-router "6.0.0-beta.0" - react-router-dom "6.0.0-beta.0" - react-use "^15.3.3" + "@rollup/pluginutils" "^3.0.8" + commondir "^1.0.1" + estree-walker "^1.0.1" + glob "^7.1.2" + is-reference "^1.1.2" + magic-string "^0.25.2" + resolve "^1.11.0" "@rollup/plugin-commonjs@^16.0.0": version "16.0.0" @@ -3749,19 +3717,6 @@ magic-string "^0.25.7" resolve "^1.17.0" -"@rollup/plugin-commonjs@^14.0.0": - version "14.0.0" - resolved "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-14.0.0.tgz#4285f9ec2db686a31129e5a2b415c94aa1f836f0" - integrity sha512-+PSmD9ePwTAeU106i9FRdc+Zb3XUWyW26mo5Atr2mk82hor8+nPwkztEjFo8/B1fJKfaQDg9aM2bzQkjhi7zOw== - dependencies: - "@rollup/pluginutils" "^3.0.8" - commondir "^1.0.1" - estree-walker "^1.0.1" - glob "^7.1.2" - is-reference "^1.1.2" - magic-string "^0.25.2" - resolve "^1.11.0" - "@rollup/plugin-json@^4.0.2": version "4.1.0" resolved "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-4.1.0.tgz#54e09867ae6963c593844d8bd7a9c718294496f3" @@ -9881,7 +9836,7 @@ dateformat@^3.0.0: resolved "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== -dayjs@^1.9.4: +dayjs@^1.9.1, dayjs@^1.9.4: version "1.9.4" resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.9.4.tgz#fcde984e227f4296f04e7b05720adad2e1071f1b" integrity sha512-ABSF3alrldf7nM9sQ2U+Ln67NRwmzlLOqG7kK03kck0mw3wlSSEKv/XhKGGxUjQcS57QeiCyNdrFgtj9nWlrng== @@ -14227,7 +14182,7 @@ is-promise@^2.1, is-promise@^2.1.0: resolved "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== -is-reference@^1.2.1: +is-reference@^1.1.2, is-reference@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz#8b2dac0b371f4bc994fdeaba9eb542d03002d0b7" integrity sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ== @@ -16125,7 +16080,7 @@ macos-release@^2.2.0: resolved "https://registry.npmjs.org/macos-release/-/macos-release-2.3.0.tgz#eb1930b036c0800adebccd5f17bc4c12de8bb71f" integrity sha512-OHhSbtcviqMPt7yfw5ef5aghS2jzFVKEFyCJndQt2YpSQ9qRVSEv2axSJI1paVThEu+FFGs584h/1YhxjVqajA== -magic-string@^0.25.7: +magic-string@^0.25.2, magic-string@^0.25.7: version "0.25.7" resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA== @@ -20782,7 +20737,7 @@ resolve@1.17.0, resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.12.0 dependencies: path-parse "^1.0.6" -resolve@^1.18.1: +resolve@^1.11.0, resolve@^1.18.1: version "1.18.1" resolved "https://registry.npmjs.org/resolve/-/resolve-1.18.1.tgz#018fcb2c5b207d2a6424aee361c5a266da8f4130" integrity sha512-lDfCPaMKfOJXjy0dPayzPdF1phampNWr3qFCjAu+rw/qbQmr5jWH5xN2hwh9QKfw9E5v4hwV7A+jrCmL8yjjqA== From babbd50ddb67d63be60bee495f5f41b00989b9b1 Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Thu, 29 Oct 2020 09:56:28 +0100 Subject: [PATCH 018/758] Create import-component frontend plugin --- .../catalog-backend/src/ingestion/index.ts | 9 +- plugins/import-component/.eslintrc.js | 3 + plugins/import-component/README.md | 7 + plugins/import-component/dev/index.tsx | 22 +++ plugins/import-component/package.json | 56 ++++++ .../src/api/CatalogImportApi.ts | 56 ++++++ .../src/api/CatalogImportClient.ts | 135 ++++++++++++++ plugins/import-component/src/api/index.ts | 34 ++++ plugins/import-component/src/api/types.ts | 38 ++++ .../ImportComponentForm.tsx | 170 ++++++++++++++++++ .../components/ImportComponentForm/index.ts | 17 ++ .../ComponentConfigDisplay.tsx | 98 ++++++++++ .../ImportComponentPage.tsx | 95 ++++++++++ .../ImportComponentPage/ImportFinished.tsx | 73 ++++++++ .../ImportComponentPage/ImportStepper.tsx | 42 +++++ .../components/ImportComponentPage/index.ts | 17 ++ .../ImportComponentPage/useGithubRepos.ts | 74 ++++++++ .../src/components/Router.tsx | 26 +++ plugins/import-component/src/index.ts | 18 ++ plugins/import-component/src/plugin.test.ts | 23 +++ plugins/import-component/src/plugin.ts | 54 ++++++ plugins/import-component/src/setupTests.ts | 17 ++ plugins/import-component/src/util/types.ts | 22 +++ .../src/util/validate.test.ts | 33 ++++ plugins/import-component/src/util/validate.ts | 21 +++ plugins/import-component/tsconfig.json | 5 + 26 files changed, 1157 insertions(+), 8 deletions(-) create mode 100644 plugins/import-component/.eslintrc.js create mode 100644 plugins/import-component/README.md create mode 100644 plugins/import-component/dev/index.tsx create mode 100644 plugins/import-component/package.json create mode 100644 plugins/import-component/src/api/CatalogImportApi.ts create mode 100644 plugins/import-component/src/api/CatalogImportClient.ts create mode 100644 plugins/import-component/src/api/index.ts create mode 100644 plugins/import-component/src/api/types.ts create mode 100644 plugins/import-component/src/components/ImportComponentForm/ImportComponentForm.tsx create mode 100644 plugins/import-component/src/components/ImportComponentForm/index.ts create mode 100644 plugins/import-component/src/components/ImportComponentPage/ComponentConfigDisplay.tsx create mode 100644 plugins/import-component/src/components/ImportComponentPage/ImportComponentPage.tsx create mode 100644 plugins/import-component/src/components/ImportComponentPage/ImportFinished.tsx create mode 100644 plugins/import-component/src/components/ImportComponentPage/ImportStepper.tsx create mode 100644 plugins/import-component/src/components/ImportComponentPage/index.ts create mode 100644 plugins/import-component/src/components/ImportComponentPage/useGithubRepos.ts create mode 100644 plugins/import-component/src/components/Router.tsx create mode 100644 plugins/import-component/src/index.ts create mode 100644 plugins/import-component/src/plugin.test.ts create mode 100644 plugins/import-component/src/plugin.ts create mode 100644 plugins/import-component/src/setupTests.ts create mode 100644 plugins/import-component/src/util/types.ts create mode 100644 plugins/import-component/src/util/validate.test.ts create mode 100644 plugins/import-component/src/util/validate.ts create mode 100644 plugins/import-component/tsconfig.json diff --git a/plugins/catalog-backend/src/ingestion/index.ts b/plugins/catalog-backend/src/ingestion/index.ts index 4ebb2edc79..fad4ae34f7 100644 --- a/plugins/catalog-backend/src/ingestion/index.ts +++ b/plugins/catalog-backend/src/ingestion/index.ts @@ -16,12 +16,5 @@ export { HigherOrderOperations } from './HigherOrderOperations'; export { LocationReaders } from './LocationReaders'; -export type { - AddLocationResult, - HigherOrderOperation, - LocationReader, - ReadLocationEntity, - ReadLocationError, - ReadLocationResult, -} from './types'; +export * from './types'; export * from './processors'; diff --git a/plugins/import-component/.eslintrc.js b/plugins/import-component/.eslintrc.js new file mode 100644 index 0000000000..13573efa9c --- /dev/null +++ b/plugins/import-component/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint')], +}; diff --git a/plugins/import-component/README.md b/plugins/import-component/README.md new file mode 100644 index 0000000000..682212d98d --- /dev/null +++ b/plugins/import-component/README.md @@ -0,0 +1,7 @@ +# Register component plugin + +Welcome to the import-component plugin! + +This plugin allows you to create a component-config YAML file for your repository. + +When installed it is accessible on [localhost:3000/import-component](localhost:3000/import-component). diff --git a/plugins/import-component/dev/index.tsx b/plugins/import-component/dev/index.tsx new file mode 100644 index 0000000000..d97643057b --- /dev/null +++ b/plugins/import-component/dev/index.tsx @@ -0,0 +1,22 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createDevApp } from '@backstage/dev-utils'; +import { plugin } from '../src/plugin'; + +createDevApp() + .registerPlugin(plugin) + .render(); diff --git a/plugins/import-component/package.json b/plugins/import-component/package.json new file mode 100644 index 0000000000..73a9aa007f --- /dev/null +++ b/plugins/import-component/package.json @@ -0,0 +1,56 @@ +{ + "name": "@backstage/plugin-import-component", + "version": "0.1.1-alpha.26", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "private": false, + "publishConfig": { + "access": "public", + "main": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "scripts": { + "build": "backstage-cli plugin:build", + "start": "backstage-cli plugin:serve", + "lint": "backstage-cli lint", + "test": "backstage-cli test", + "diff": "backstage-cli plugin:diff", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", + "clean": "backstage-cli clean" + }, + "dependencies": { + "@backstage/catalog-model": "^0.1.1-alpha.26", + "@backstage/core": "^0.1.1-alpha.26", + "@backstage/plugin-catalog": "^0.1.1-alpha.26", + "@backstage/plugin-catalog-backend": "^0.1.1-alpha.26", + "@backstage/theme": "^0.1.1-alpha.26", + "@material-ui/core": "^4.11.0", + "@material-ui/icons": "^4.9.1", + "@material-ui/lab": "4.0.0-alpha.45", + "@octokit/rest": "^18.0.6", + "react": "^16.13.1", + "react-dom": "^16.13.1", + "react-hook-form": "^6.6.0", + "react-router": "^5.2.0", + "react-router-dom": "6.0.0-beta.0", + "react-use": "^15.3.3", + "yaml": "^1.10.0" + }, + "devDependencies": { + "@backstage/cli": "^0.1.1-alpha.26", + "@backstage/dev-utils": "^0.1.1-alpha.26", + "@backstage/test-utils": "^0.1.1-alpha.26", + "@testing-library/jest-dom": "^5.10.1", + "@testing-library/react": "^10.4.1", + "@testing-library/user-event": "^12.0.7", + "@types/jest": "^26.0.7", + "@types/node": "^12.0.0", + "cross-fetch": "^3.0.6", + "msw": "^0.21.2" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/import-component/src/api/CatalogImportApi.ts b/plugins/import-component/src/api/CatalogImportApi.ts new file mode 100644 index 0000000000..58d1f87f00 --- /dev/null +++ b/plugins/import-component/src/api/CatalogImportApi.ts @@ -0,0 +1,56 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * Copyright 2020 Roadie AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createApiRef } from '@backstage/core'; +import { Entity } from '@backstage/catalog-model'; +import { RecursivePartial } from '../util/types'; + +export const catalogImportApiRef = createApiRef({ + id: 'plugin.catalogimport.service', + description: 'Used by the catalog import plugin to make requests', +}); + +export interface CatalogImportApi { + submitPRToRepo(options: { + token: string; + owner: string; + repo: string; + fileContent: string; + }): Promise<{ errorMessage: string | null; link: string }>; + createRepositoryLocation(options: { + token: string; + owner: string; + repo: string; + }): Promise<{ errorMessage: string | null }>; + generateEntityDefinitions(options: { + repo: string; + }): Promise[]>; +} diff --git a/plugins/import-component/src/api/CatalogImportClient.ts b/plugins/import-component/src/api/CatalogImportClient.ts new file mode 100644 index 0000000000..d67febe2c8 --- /dev/null +++ b/plugins/import-component/src/api/CatalogImportClient.ts @@ -0,0 +1,135 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * Copyright 2020 Roadie AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Octokit } from '@octokit/rest'; +import { Entity } from '@backstage/catalog-model'; +import { DiscoveryApi } from '@backstage/core'; +import { CatalogImportApi } from './CatalogImportApi'; +import { AnalyzeLocationResponse } from '@backstage/plugin-catalog-backend'; +import { RecursivePartial } from '../util/types'; + +export const API_BASE_URL = '/api/catalog/locations'; + +export class CatalogImportClient implements CatalogImportApi { + private readonly discoveryApi: DiscoveryApi; + + constructor(options: { discoveryApi: DiscoveryApi }) { + this.discoveryApi = options.discoveryApi; + } + + async generateEntityDefinitions({ + repo, + }: { + repo: string; + }): Promise[]> { + const response = await fetch( + `${await this.discoveryApi.getBaseUrl('catalog')}/analyze-location`, + { + headers: { + 'Content-Type': 'application/json', + }, + method: 'POST', + body: JSON.stringify({ + location: { type: 'github', target: repo }, + }), + }, + ); + const payload = (await response.json()) as AnalyzeLocationResponse; + return payload.generateEntities.map(x => x.entity); + } + + async createRepositoryLocation({ + owner, + repo, + }: { + token: string; + owner: string; + repo: string; + }): Promise<{ errorMessage: string | null }> { + await fetch(`${await this.discoveryApi.getBaseUrl('catalog')}/locations`, { + headers: { + 'Content-Type': 'application/json', + }, + method: 'POST', + body: JSON.stringify({ + type: 'github', + target: `https://github.com/${owner}/${repo}/blob/master/catalog-info.yaml`, + presence: 'optional', + }), + }); + return { errorMessage: null }; + } + + async submitPRToRepo({ + token, + owner, + repo, + fileContent, + }: { + token: string; + owner: string; + repo: string; + fileContent: string; + }): Promise<{ errorMessage: string | null; link: string }> { + const octo = new Octokit({ + auth: token, + }); + + const parentRef = await octo.git.getRef({ + owner, + repo, + ref: 'heads/master', + }); + await octo.git.createRef({ + owner, + repo, + ref: 'refs/heads/backstage-integration', + sha: parentRef.data.object.sha, + }); + await octo.repos.createOrUpdateFileContents({ + owner, + repo, + path: 'backstage.yaml', + message: 'Add backstage.yaml config file', + content: btoa(fileContent), + branch: 'backstage-integration', + }); + const pullRequestRespone = await octo.pulls.create({ + owner, + repo, + title: 'Add backstage.yaml config file', + head: 'backstage-integration', + base: 'master', + }); + + return { errorMessage: null, link: pullRequestRespone.data.html_url }; + } +} diff --git a/plugins/import-component/src/api/index.ts b/plugins/import-component/src/api/index.ts new file mode 100644 index 0000000000..9ebc110843 --- /dev/null +++ b/plugins/import-component/src/api/index.ts @@ -0,0 +1,34 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * Copyright 2020 Roadie AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './CatalogImportApi'; +export * from './CatalogImportClient'; +export * from './types'; diff --git a/plugins/import-component/src/api/types.ts b/plugins/import-component/src/api/types.ts new file mode 100644 index 0000000000..c18d0cf547 --- /dev/null +++ b/plugins/import-component/src/api/types.ts @@ -0,0 +1,38 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * Copyright 2020 Roadie AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export type GithubRepoDto = { + full_name: string; + private: boolean; + description: string; + html_url: string; + language: string; +}; diff --git a/plugins/import-component/src/components/ImportComponentForm/ImportComponentForm.tsx b/plugins/import-component/src/components/ImportComponentForm/ImportComponentForm.tsx new file mode 100644 index 0000000000..c28e5889ac --- /dev/null +++ b/plugins/import-component/src/components/ImportComponentForm/ImportComponentForm.tsx @@ -0,0 +1,170 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Entity } from '@backstage/catalog-model'; +import { errorApiRef, useApi } from '@backstage/core'; +import { BackstageTheme } from '@backstage/theme'; +import { + Button, + FormControl, + FormHelperText, + InputLabel, + MenuItem, + Select, + TextField, +} from '@material-ui/core'; +import { makeStyles } from '@material-ui/core/styles'; +import React from 'react'; +import { Controller, useForm } from 'react-hook-form'; +import { useMountedState } from 'react-use'; +import { RecursivePartial } from '../../util/types'; +import { ComponentIdValidators } from '../../util/validate'; +import { useGithubRepos } from '../ImportComponentPage/useGithubRepos'; + +const useStyles = makeStyles(theme => ({ + form: { + alignItems: 'flex-start', + display: 'flex', + flexFlow: 'column nowrap', + }, + submit: { + marginTop: theme.spacing(1), + }, + select: { + minWidth: 120, + }, +})); + +type Props = { + nextStep: () => void; + saveConfig: (configFile: { + repo: string; + config: RecursivePartial[]; + }) => void; +}; + +export const RegisterComponentForm = ({ nextStep, saveConfig }: Props) => { + const { control, register, handleSubmit, errors, formState } = useForm({ + mode: 'onChange', + }); + const classes = useStyles(); + const hasErrors = !!errors.componentLocation; + const dirty = formState?.isDirty; + + const isMounted = useMountedState(); + const errorApi = useApi(errorApiRef); + const { generateEntityDefinitions } = useGithubRepos(); + + const onSubmit = async (formData: Record) => { + const { componentLocation: target } = formData; + try { + // const typeMapping = [ + // { url: /^https:\/\/gitlab\.com\/.*/, type: 'gitlab/api' }, + // { url: /^https:\/\/bitbucket\.org\/.*/, type: 'bitbucket/api' }, + // { url: /^https:\/\/dev\.azure\.com\/.*/, type: 'azure/api' }, + // { url: /.*/, type: 'github' }, + // ]; + + // const type = + // scmType === 'AUTO' + // ? typeMapping.filter(item => item.url.test(target))[0].type + // : scmType; + + if (!isMounted()) return; + + const repo = target + .split('/') + .slice(-2) + .join('/'); + + const config = await generateEntityDefinitions(repo); + saveConfig({ + repo, + config, + }); + nextStep(); + } catch (e) { + errorApi.post(e); + } + }; + + return ( + + + + + {errors.componentLocation && ( + + {errors.componentLocation.message} + + )} + + + + Host type + ( + + )} + /> + + + + ); +}; diff --git a/plugins/import-component/src/components/ImportComponentForm/index.ts b/plugins/import-component/src/components/ImportComponentForm/index.ts new file mode 100644 index 0000000000..ed118f5412 --- /dev/null +++ b/plugins/import-component/src/components/ImportComponentForm/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './ImportComponentForm'; diff --git a/plugins/import-component/src/components/ImportComponentPage/ComponentConfigDisplay.tsx b/plugins/import-component/src/components/ImportComponentPage/ComponentConfigDisplay.tsx new file mode 100644 index 0000000000..22d3dac322 --- /dev/null +++ b/plugins/import-component/src/components/ImportComponentPage/ComponentConfigDisplay.tsx @@ -0,0 +1,98 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * Copyright 2020 RoadieHQ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React, { useCallback, useState } from 'react'; +import { Button, CircularProgress, Grid, Tooltip } from '@material-ui/core'; +import Alert from '@material-ui/lab/Alert'; +import { useGithubRepos } from './useGithubRepos'; +import { Entity } from '@backstage/catalog-model'; +import { RecursivePartial } from '../../util/types'; + +type Props = { + nextStep: () => void; + configFile: { repo: string; config: RecursivePartial[] }; + savePRLink: (PRLink: string) => void; +}; + +const ComponentConfigDisplay: React.FC = ({ + nextStep, + configFile, + savePRLink, +}) => { + const [submitting, setSubmitting] = useState(false); + const { submitPRToRepo } = useGithubRepos(); + const onNext = useCallback(async () => { + setSubmitting(true); + const result = await submitPRToRepo(configFile); + savePRLink(result.link); + setSubmitting(false); + nextStep(); + }, [submitPRToRepo, configFile, nextStep, savePRLink]); + + return ( + + + + config file has been generated. You can optionally edit it before + submitting Pull Request + + + + + + + + + + + {submitting ? ( + + + + ) : null} + + + + + + ); +}; + +export default ComponentConfigDisplay; diff --git a/plugins/import-component/src/components/ImportComponentPage/ImportComponentPage.tsx b/plugins/import-component/src/components/ImportComponentPage/ImportComponentPage.tsx new file mode 100644 index 0000000000..719d5e93f4 --- /dev/null +++ b/plugins/import-component/src/components/ImportComponentPage/ImportComponentPage.tsx @@ -0,0 +1,95 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React, { useState } from 'react'; +import { Grid } from '@material-ui/core'; +import { + InfoCard, + Page, + Content, + Header, + SupportButton, + ContentHeader, +} from '@backstage/core'; +import { RegisterComponentForm } from '../ImportComponentForm'; +import { Entity } from '@backstage/catalog-model'; +import ImportStepper from './ImportStepper'; +import ComponentConfigDisplay from './ComponentConfigDisplay'; +import { ImportFinished } from './ImportFinished'; +import { RecursivePartial } from '../../util/types'; + +export const ImportComponentPage = () => { + const [activeStep, setActiveStep] = useState(0); + const [configFile, setConfigFile] = useState<{ + repo: string; + config: RecursivePartial[]; + }>({ repo: '', config: [] }); + const [PRLink, setPRLink] = useState(''); + const nextStep = (options?: { reset: boolean }) => { + setActiveStep(step => (options?.reset ? 0 : step + 1)); + }; + + return ( + +
+ + + + Generate a component definition file and automatically submit it as + a pull request. TODO: Add more information about what this is. + + + + + + + ), + }, + { + step: 'Review generated component config files', + content: ( + + ), + }, + { + step: 'Finish', + content: ( + + ), + }, + ]} + activeStep={activeStep} + nextStep={nextStep} + /> + + + + + + ); +}; diff --git a/plugins/import-component/src/components/ImportComponentPage/ImportFinished.tsx b/plugins/import-component/src/components/ImportComponentPage/ImportFinished.tsx new file mode 100644 index 0000000000..6abe551f96 --- /dev/null +++ b/plugins/import-component/src/components/ImportComponentPage/ImportFinished.tsx @@ -0,0 +1,73 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import Button from '@material-ui/core/Button'; +import { + Grid, + Link, + Typography, + createStyles, + makeStyles, + Theme, +} from '@material-ui/core'; +import { Alert } from '@material-ui/lab'; + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + root: { + width: '100%', + }, + heading: { + fontSize: theme.typography.pxToRem(15), + fontWeight: theme.typography.fontWeightRegular, + }, + linkList: { + display: 'flex', + flexDirection: 'column', + }, + }), +); +type Props = { + nextStep: (options?: { reset: boolean }) => void; + PRLink: string; +}; + +export const ImportFinished: React.FC = ({ nextStep, PRLink }) => { + const classes = useStyles(); + return ( + + + + Pull requests have been successfully opened. You can start again to + import more repositories + + + + Pull request link: + {PRLink} + + + + + + ); +}; diff --git a/plugins/import-component/src/components/ImportComponentPage/ImportStepper.tsx b/plugins/import-component/src/components/ImportComponentPage/ImportStepper.tsx new file mode 100644 index 0000000000..03752fa7d4 --- /dev/null +++ b/plugins/import-component/src/components/ImportComponentPage/ImportStepper.tsx @@ -0,0 +1,42 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import Stepper from '@material-ui/core/Stepper'; +import Step from '@material-ui/core/Step'; +import StepLabel from '@material-ui/core/StepLabel'; +import { StepContent } from '@material-ui/core'; + +type Props = { + steps: { step: string; content: React.ReactNode }[]; + activeStep: number; + nextStep: (options?: { reset: boolean }) => void; +}; + +export default function ImportStepper({ steps, activeStep }: Props) { + return ( + + {steps.map(({ step }) => { + const stepProps: { completed?: boolean } = {}; + return ( + + {step} + {steps[activeStep].content} + + ); + })} + + ); +} diff --git a/plugins/import-component/src/components/ImportComponentPage/index.ts b/plugins/import-component/src/components/ImportComponentPage/index.ts new file mode 100644 index 0000000000..dee2816011 --- /dev/null +++ b/plugins/import-component/src/components/ImportComponentPage/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './ImportComponentPage'; diff --git a/plugins/import-component/src/components/ImportComponentPage/useGithubRepos.ts b/plugins/import-component/src/components/ImportComponentPage/useGithubRepos.ts new file mode 100644 index 0000000000..f28d5a444e --- /dev/null +++ b/plugins/import-component/src/components/ImportComponentPage/useGithubRepos.ts @@ -0,0 +1,74 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * Copyright 2020 Roadie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import * as YAML from 'yaml'; +import { useApi, githubAuthApiRef } from '@backstage/core'; +import { Entity } from '@backstage/catalog-model'; +import { catalogImportApiRef } from '../../api/CatalogImportApi'; +import { RecursivePartial } from '../../util/types'; + +export function useGithubRepos() { + const api = useApi(catalogImportApiRef); + const auth = useApi(githubAuthApiRef); + + const submitPRToRepo = async (selectedRepo: { + repo: string; + config: RecursivePartial[]; + }) => { + const token = await auth.getAccessToken(['repo']); + + const [ownerName, repoName] = [ + selectedRepo.repo.split('/')[0], + selectedRepo.repo.split('/')[1], + ]; + const submitPRResponse = await api.submitPRToRepo({ + token, + owner: ownerName, + repo: repoName, + fileContent: selectedRepo.config + .map(entity => `---\n${YAML.stringify(entity)}`) + .join('\n'), + }); + + await api.createRepositoryLocation({ + token, + owner: selectedRepo.repo.split('/')[0], + repo: selectedRepo.repo.split('/')[1], + }); + + return submitPRResponse; + }; + + return { + submitPRToRepo, + generateEntityDefinitions: (repo: string) => + api.generateEntityDefinitions({ repo }), + }; +} diff --git a/plugins/import-component/src/components/Router.tsx b/plugins/import-component/src/components/Router.tsx new file mode 100644 index 0000000000..c877d3a208 --- /dev/null +++ b/plugins/import-component/src/components/Router.tsx @@ -0,0 +1,26 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { Route, Routes } from 'react-router'; +import { ImportComponentPage } from './ImportComponentPage'; + +// As we don't know which path the catalog's router mounted on +// We need to inject this from the app +export const Router = () => ( + + } /> + +); diff --git a/plugins/import-component/src/index.ts b/plugins/import-component/src/index.ts new file mode 100644 index 0000000000..ff7857cacd --- /dev/null +++ b/plugins/import-component/src/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { plugin } from './plugin'; +export { Router } from './components/Router'; diff --git a/plugins/import-component/src/plugin.test.ts b/plugins/import-component/src/plugin.test.ts new file mode 100644 index 0000000000..dd10754a87 --- /dev/null +++ b/plugins/import-component/src/plugin.test.ts @@ -0,0 +1,23 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { plugin } from './plugin'; + +describe('import-component', () => { + it('should export plugin', () => { + expect(plugin).toBeDefined(); + }); +}); diff --git a/plugins/import-component/src/plugin.ts b/plugins/import-component/src/plugin.ts new file mode 100644 index 0000000000..a5db8337da --- /dev/null +++ b/plugins/import-component/src/plugin.ts @@ -0,0 +1,54 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * Copyright 2020 Roadie AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + createApiFactory, + createPlugin, + createRouteRef, + discoveryApiRef, +} from '@backstage/core'; +import { catalogImportApiRef } from './api/CatalogImportApi'; +import { CatalogImportClient } from './api/CatalogImportClient'; + +export const rootRouteRef = createRouteRef({ + path: '', + title: 'import-component', +}); + +export const plugin = createPlugin({ + id: 'import-component', + apis: [ + createApiFactory({ + api: catalogImportApiRef, + deps: { discoveryApi: discoveryApiRef }, + factory: ({ discoveryApi }) => new CatalogImportClient({ discoveryApi }), + }), + ], +}); diff --git a/plugins/import-component/src/setupTests.ts b/plugins/import-component/src/setupTests.ts new file mode 100644 index 0000000000..825bcd4115 --- /dev/null +++ b/plugins/import-component/src/setupTests.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import '@testing-library/jest-dom'; diff --git a/plugins/import-component/src/util/types.ts b/plugins/import-component/src/util/types.ts new file mode 100644 index 0000000000..77b96f5036 --- /dev/null +++ b/plugins/import-component/src/util/types.ts @@ -0,0 +1,22 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export type RecursivePartial = { + [P in keyof T]?: T[P] extends (infer U)[] + ? RecursivePartial[] + : T[P] extends object + ? RecursivePartial + : T[P]; +}; diff --git a/plugins/import-component/src/util/validate.test.ts b/plugins/import-component/src/util/validate.test.ts new file mode 100644 index 0000000000..139c7fb0f8 --- /dev/null +++ b/plugins/import-component/src/util/validate.test.ts @@ -0,0 +1,33 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ComponentIdValidators } from './validate'; + +describe('ComponentIdValidators', () => { + describe('httpsValidator validator', () => { + const errorMessage = 'Must start with https://.'; + test.each([ + [true, 'https://example.com'], + [errorMessage, 'http://example.com'], + [errorMessage, 'example.com'], + [errorMessage, 'www.example.com'], + [errorMessage, ''], + [errorMessage, undefined], + ])('should return %p for %s', (expected: string | boolean, arg: any) => { + expect(ComponentIdValidators.httpsValidator(arg)).toBe(expected); + }); + }); +}); diff --git a/plugins/import-component/src/util/validate.ts b/plugins/import-component/src/util/validate.ts new file mode 100644 index 0000000000..056131aaae --- /dev/null +++ b/plugins/import-component/src/util/validate.ts @@ -0,0 +1,21 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const ComponentIdValidators = { + httpsValidator: (value: any) => + (typeof value === 'string' && value.match(/^https:\/\//) !== null) || + 'Must start with https://.', +}; diff --git a/plugins/import-component/tsconfig.json b/plugins/import-component/tsconfig.json new file mode 100644 index 0000000000..b663b01fa2 --- /dev/null +++ b/plugins/import-component/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src", "dev"], + "compilerOptions": {} +} From 29a3a074c6394b22913bb6bd91b70be9efbe9dda Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Thu, 29 Oct 2020 10:00:16 +0100 Subject: [PATCH 019/758] Add import component button to the create page --- packages/app/package.json | 1 + packages/app/src/App.tsx | 4 +-- packages/app/src/plugins.ts | 2 +- .../src/ingestion/LocationAnalyzer.ts | 1 + .../catalog-backend/src/ingestion/types.ts | 4 +-- .../src/components/Router.tsx | 2 +- .../ScaffolderPage/ScaffolderPage.tsx | 9 +++++++ yarn.lock | 27 ++++++++++++++++++- 8 files changed, 43 insertions(+), 7 deletions(-) diff --git a/packages/app/package.json b/packages/app/package.json index 656de82ade..5a344a98b8 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -22,6 +22,7 @@ "@backstage/plugin-lighthouse": "^0.1.1-alpha.26", "@backstage/plugin-newrelic": "^0.1.1-alpha.26", "@backstage/plugin-register-component": "^0.1.1-alpha.26", + "@backstage/plugin-import-component": "^0.1.1-alpha.26", "@backstage/plugin-rollbar": "^0.1.1-alpha.26", "@backstage/plugin-scaffolder": "^0.1.1-alpha.26", "@backstage/plugin-sentry": "^0.1.1-alpha.26", diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 8b10366c6e..cb85d9afc2 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -27,7 +27,6 @@ import * as plugins from './plugins'; import { apis } from './apis'; import { hot } from 'react-hot-loader/root'; import { providers } from './identityProviders'; -import { Router as CatalogImportRouter } from '@roadiehq/backstage-plugin-catalog-import'; import { Router as CatalogRouter } from '@backstage/plugin-catalog'; import { Router as DocsRouter } from '@backstage/plugin-techdocs'; import { Router as GraphiQLRouter } from '@backstage/plugin-graphiql'; @@ -35,6 +34,7 @@ import { Router as TechRadarRouter } from '@backstage/plugin-tech-radar'; import { Router as LighthouseRouter } from '@backstage/plugin-lighthouse'; import { Router as RegisterComponentRouter } from '@backstage/plugin-register-component'; import { Router as SettingsRouter } from '@backstage/plugin-user-settings'; +import { Router as ImportComponentRouter } from '@backstage/plugin-import-component'; import { Route, Routes, Navigate } from 'react-router'; import { EntityPage } from './components/catalog/EntityPage'; @@ -83,8 +83,8 @@ const AppRoutes = () => ( path="/register-component" element={} /> + } /> } /> - } /> {...deprecatedAppRoutes} ); diff --git a/packages/app/src/plugins.ts b/packages/app/src/plugins.ts index 64c49eceba..30715b617a 100644 --- a/packages/app/src/plugins.ts +++ b/packages/app/src/plugins.ts @@ -37,5 +37,5 @@ export { plugin as Kubernetes } from '@backstage/plugin-kubernetes'; export { plugin as Cloudbuild } from '@backstage/plugin-cloudbuild'; export { plugin as CostInsights } from '@backstage/plugin-cost-insights'; export { plugin as GitHubInsights } from '@roadiehq/backstage-plugin-github-insights'; -export { plugin as CatalogImport } from '@roadiehq/backstage-plugin-catalog-import'; +export { plugin as CatalogImport } from '@backstage/plugin-import-component'; export { plugin as UserSettings } from '@backstage/plugin-user-settings'; diff --git a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts index f6991b1c90..435ed4f872 100644 --- a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts +++ b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts @@ -41,6 +41,7 @@ export class LocationAnalyzerClient implements LocationAnalyzer { spec: { type: 'service', owner: ownerName, lifecycle: 'experimental' }, }; + this.logger.silly(`entity created for ${request.location.target}`); return { existingEntityFiles: [], generateEntities: [{ entity, fields: [] }], diff --git a/plugins/catalog-backend/src/ingestion/types.ts b/plugins/catalog-backend/src/ingestion/types.ts index 19d472b3ab..4f229cbc6c 100644 --- a/plugins/catalog-backend/src/ingestion/types.ts +++ b/plugins/catalog-backend/src/ingestion/types.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import type { +import { Entity, EntityRelationSpec, Location, @@ -68,7 +68,7 @@ export type ReadLocationError = { }; // -// ConfigGenerator +// LocationAnalyzer // export type LocationAnalyzer = { diff --git a/plugins/import-component/src/components/Router.tsx b/plugins/import-component/src/components/Router.tsx index c877d3a208..9b9c445bb8 100644 --- a/plugins/import-component/src/components/Router.tsx +++ b/plugins/import-component/src/components/Router.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ import React from 'react'; -import { Route, Routes } from 'react-router'; +import { Route, Routes } from 'react-router-dom'; import { ImportComponentPage } from './ImportComponentPage'; // As we don't know which path the catalog's router mounted on diff --git a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx index d0cb25f254..306db4db84 100644 --- a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx +++ b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx @@ -77,6 +77,15 @@ export const ScaffolderPage = () => { /> +