From 17e7efdc9f47e41723e858013862b529da9cb6de Mon Sep 17 00:00:00 2001 From: Nikita Nek Dudnik Date: Thu, 8 Oct 2020 15:06:32 +0200 Subject: [PATCH 001/430] 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 002/430] 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 003/430] 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 004/430] 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 005/430] 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 006/430] 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 007/430] 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 008/430] 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 009/430] 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 010/430] 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 011/430] 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 012/430] 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 013/430] 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 014/430] 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 015/430] 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 016/430] 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 017/430] 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 018/430] 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 = () => { /> + {showDialog && ( { the issue and reach out to you if necessary.

{ - - - + {submitting ? ( diff --git a/plugins/catalog-import/src/components/ImportComponentForm.tsx b/plugins/catalog-import/src/components/ImportComponentForm.tsx index da078e10f0..9a4fd38a06 100644 --- a/plugins/catalog-import/src/components/ImportComponentForm.tsx +++ b/plugins/catalog-import/src/components/ImportComponentForm.tsx @@ -26,9 +26,11 @@ import { makeStyles } from '@material-ui/core/styles'; import React from 'react'; import { useForm } from 'react-hook-form'; import { useMountedState } from 'react-use'; +import parseGitUri from 'git-url-parse'; import { ComponentIdValidators } from '../util/validate'; import { useGithubRepos } from '../util/useGithubRepos'; import { ConfigSpec } from './ImportComponentPage'; +import { catalogApiRef } from '@backstage/plugin-catalog'; const useStyles = makeStyles(theme => ({ form: { @@ -53,6 +55,7 @@ export const RegisterComponentForm = ({ nextStep, saveConfig }: Props) => { const classes = useStyles(); const hasErrors = !!errors.componentLocation; const dirty = formState?.isDirty; + const catalogApi = useApi(catalogApiRef); const isMounted = useMountedState(); const errorApi = useApi(errorApiRef); @@ -62,12 +65,22 @@ export const RegisterComponentForm = ({ nextStep, saveConfig }: Props) => { const { componentLocation: target } = formData; try { if (!isMounted()) return; + const type = !parseGitUri(target).filepathtype ? 'repo' : 'file'; - const config = await generateEntityDefinitions(target); - saveConfig({ - repo: target, - config, - }); + if (type === 'repo') { + saveConfig({ + type, + location: target, + config: await generateEntityDefinitions(target), + }); + } else { + const data = await catalogApi.addLocation({ target }); + saveConfig({ + type, + location: data.location.target, + config: data.entities, + }); + } nextStep(); } catch (e) { errorApi.post(e); @@ -111,7 +124,7 @@ export const RegisterComponentForm = ({ nextStep, saveConfig }: Props) => { disabled={!dirty || hasErrors} className={classes.submit} > - Submit + Next ); diff --git a/plugins/catalog-import/src/components/ImportComponentPage.tsx b/plugins/catalog-import/src/components/ImportComponentPage.tsx index 8371eda927..c2eb935e54 100644 --- a/plugins/catalog-import/src/components/ImportComponentPage.tsx +++ b/plugins/catalog-import/src/components/ImportComponentPage.tsx @@ -23,6 +23,7 @@ import { Header, SupportButton, ContentHeader, + RouteRef, } from '@backstage/core'; import { RegisterComponentForm } from './ImportComponentForm'; import ImportStepper from './ImportStepper'; @@ -31,29 +32,35 @@ import { ImportFinished } from './ImportFinished'; import { PartialEntity } from '../util/types'; export type ConfigSpec = { - repo: string; + type: 'repo' | 'file'; + location: string; config: PartialEntity[]; }; -export const ImportComponentPage = () => { +export const ImportComponentPage = ({ + catalogRouteRef, +}: { + catalogRouteRef: RouteRef; +}) => { const [activeStep, setActiveStep] = useState(0); const [configFile, setConfigFile] = useState({ - repo: '', + type: 'repo', + location: '', config: [], }); - const [PRLink, setPRLink] = useState(''); + const [endLink, setEndLink] = 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. + Start tracking your component in Backstage. TODO: Add more + information about what this is. @@ -62,7 +69,7 @@ export const ImportComponentPage = () => { { ), }, { - step: 'Review generated component config files', + step: 'Review', content: ( ), }, { step: 'Finish', content: ( - + ), }, ]} diff --git a/plugins/catalog-import/src/components/ImportFinished.tsx b/plugins/catalog-import/src/components/ImportFinished.tsx index e7680dbf90..d4ff66ddab 100644 --- a/plugins/catalog-import/src/components/ImportFinished.tsx +++ b/plugins/catalog-import/src/components/ImportFinished.tsx @@ -13,52 +13,44 @@ * 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({ - heading: { - fontSize: theme.typography.pxToRem(15), - fontWeight: theme.typography.fontWeightRegular, - }, - }), -); +import React from 'react'; +import { Alert } from '@material-ui/lab'; +import { Link as RouterLink } from 'react-router-dom'; +import { Button, Grid, Link } from '@material-ui/core'; + type Props = { + type: 'repo' | 'file'; nextStep: (options?: { reset: boolean }) => void; PRLink: string; }; -export const ImportFinished = ({ nextStep, PRLink }: Props) => { - const classes = useStyles(); +export const ImportFinished = ({ nextStep, PRLink, type }: Props) => { return ( - Pull requests have been successfully opened. You can start again to - import more repositories + {type === 'repo' + ? 'Pull requests have been successfully opened. You can start again to import more repositories' + : 'Entity added to catalog successfully'} - Pull request link: - {PRLink} - - + {type === 'repo' ? ( + + 'View pull request on GitHub' + + ) : null} diff --git a/plugins/catalog-import/src/components/Router.tsx b/plugins/catalog-import/src/components/Router.tsx index bc3c776d0b..5175f8f077 100644 --- a/plugins/catalog-import/src/components/Router.tsx +++ b/plugins/catalog-import/src/components/Router.tsx @@ -14,12 +14,15 @@ * limitations under the License. */ +import { RouteRef } from '@backstage/core'; import React from 'react'; import { Route, Routes } from 'react-router-dom'; import { ImportComponentPage } from './ImportComponentPage'; -export const Router = () => ( +export const Router = ({ catalogRouteRef }: { catalogRouteRef: RouteRef }) => ( - } /> + } + /> ); diff --git a/plugins/catalog-import/src/util/useGithubRepos.ts b/plugins/catalog-import/src/util/useGithubRepos.ts index 3ec4f56c68..84c594c913 100644 --- a/plugins/catalog-import/src/util/useGithubRepos.ts +++ b/plugins/catalog-import/src/util/useGithubRepos.ts @@ -26,7 +26,7 @@ export function useGithubRepos() { const submitPrToRepo = async (selectedRepo: ConfigSpec) => { const token = await auth.getAccessToken(['repo']); - const [ownerName, repoName] = selectedRepo.repo.split('/').slice(-2); + const [ownerName, repoName] = selectedRepo.location.split('/').slice(-2); const submitPRResponse = await api .submitPrToRepo({ oAuthToken: token, @@ -55,5 +55,7 @@ export function useGithubRepos() { submitPrToRepo, generateEntityDefinitions: (repo: string) => api.generateEntityDefinitions({ repo }), + addLocation: (location: string) => + api.createRepositoryLocation({ location }), }; } diff --git a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx index cc57db2161..8ab8c10ddb 100644 --- a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx +++ b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx @@ -82,15 +82,6 @@ export const ScaffolderPage = () => { color="primary" component={RouterLink} to="/catalog-import" - style={{ marginRight: '8px' }} - > - Import Repository - - From 7008e042f93079ef84a6e09799a7bf48a1a9a7f4 Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Thu, 12 Nov 2020 10:13:18 +0100 Subject: [PATCH 031/430] Fix link to PR --- plugins/catalog-import/src/components/ImportFinished.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/plugins/catalog-import/src/components/ImportFinished.tsx b/plugins/catalog-import/src/components/ImportFinished.tsx index d4ff66ddab..6ea5c9ab82 100644 --- a/plugins/catalog-import/src/components/ImportFinished.tsx +++ b/plugins/catalog-import/src/components/ImportFinished.tsx @@ -37,12 +37,8 @@ export const ImportFinished = ({ nextStep, PRLink, type }: Props) => { {type === 'repo' ? ( - - 'View pull request on GitHub' + + View pull request on GitHub ) : null} From ecd89ca8a8f97101f729f5ddaacceee49cdd378c Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Thu, 12 Nov 2020 22:59:40 +0100 Subject: [PATCH 033/430] Fix leftover codefrom otherwork --- packages/app/src/plugins.ts | 1 - yarn.lock | 355 ++---------------------------------- 2 files changed, 16 insertions(+), 340 deletions(-) diff --git a/packages/app/src/plugins.ts b/packages/app/src/plugins.ts index 81d2c2cdec..90c0287b08 100644 --- a/packages/app/src/plugins.ts +++ b/packages/app/src/plugins.ts @@ -38,5 +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 CatalogImport } from '@backstage/plugin-catalog-import'; -export { plugin as BulkCatalogImport } from '@roadiehq/backstage-plugin-bulk-catalog-import'; export { plugin as UserSettings } from '@backstage/plugin-user-settings'; diff --git a/yarn.lock b/yarn.lock index f0c7c3df0f..695157637c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3228,7 +3228,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.9.10": +"@material-ui/core@^4.11.0", "@material-ui/core@^4.9.1": version "4.11.0" resolved "https://registry.npmjs.org/@material-ui/core/-/core-4.11.0.tgz#b69b26e4553c9e53f2bfaf1053e216a0af9be15a" integrity sha512-bYo9uIub8wGhZySHqLQ833zi4ZML+XCBE1XwJ8EuUVSpTWWG57Pm+YugQToJNFsEyiKFhPh8DPD0bgupz8n01g== @@ -3264,7 +3264,7 @@ prop-types "^15.7.2" react-is "^16.8.0" -"@material-ui/lab@^4.0.0-alpha.49", "@material-ui/lab@^4.0.0-alpha.56": +"@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== @@ -3582,7 +3582,7 @@ dependencies: "@types/node" ">= 8" -"@open-draft/until@^1.0.0", "@open-draft/until@^1.0.3": +"@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== @@ -3663,11 +3663,6 @@ 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" @@ -3776,19 +3771,6 @@ 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" @@ -3819,15 +3801,6 @@ 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" @@ -4759,7 +4732,7 @@ "@babel/runtime" "^7.5.4" "@types/testing-library__react-hooks" "^3.3.0" -"@testing-library/react-hooks@^3.4.1", "@testing-library/react-hooks@^3.4.2": +"@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== @@ -4999,11 +4972,6 @@ 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" @@ -5095,11 +5063,6 @@ 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" @@ -5190,11 +5153,6 @@ 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" @@ -6068,7 +6026,7 @@ resolved "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d" integrity sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg== -"@typescript-eslint/eslint-plugin@^3.8.0", "@typescript-eslint/eslint-plugin@^v3.10.1": +"@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== @@ -6103,7 +6061,7 @@ eslint-scope "^5.0.0" eslint-utils "^2.0.0" -"@typescript-eslint/parser@^3.8.0", "@typescript-eslint/parser@^v3.10.1": +"@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== @@ -9324,7 +9282,7 @@ cross-env@^7.0.0: dependencies: cross-spawn "^7.0.1" -cross-fetch@3.0.6, cross-fetch@^3.0.4, cross-fetch@^3.0.5, cross-fetch@^3.0.6: +cross-fetch@3.0.6, 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== @@ -9690,7 +9648,7 @@ cyclist@^1.0.1: resolved "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= -cypress@*, cypress@^4.11.0, cypress@^4.2.0: +cypress@*, cypress@^4.2.0: version "4.12.1" resolved "https://registry.npmjs.org/cypress/-/cypress-4.12.1.tgz#0ead1b9f4c0917d69d8b57f996b6e01fe693b6ec" integrity sha512-9SGIPEmqU8vuRA6xst2CMTYd9sCFCxKSzrHt0wr+w2iAQMCIIsXsQ5Gplns1sT6LDbZcmLv6uehabAOl3fhc9Q== @@ -9890,7 +9848,7 @@ dateformat@^3.0.0: resolved "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== -dayjs@^1.9.1, dayjs@^1.9.4: +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== @@ -10023,11 +9981,6 @@ 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" @@ -10774,41 +10727,6 @@ 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" @@ -10899,11 +10817,6 @@ 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" @@ -10992,13 +10905,6 @@ 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" @@ -11095,23 +11001,6 @@ 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" @@ -11140,11 +11029,6 @@ eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^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@^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" @@ -11193,49 +11077,6 @@ 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" @@ -11250,15 +11091,6 @@ espree@^7.3.0: acorn-jsx "^5.2.0" eslint-visitor-keys "^1.3.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" @@ -12935,7 +12767,7 @@ graphql-upload@^8.0.2: http-errors "^1.7.3" object-path "^0.11.4" -graphql@15.3.0, graphql@^15.0.0, graphql@^15.3.0: +graphql@15.3.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== @@ -13111,7 +12943,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.1.9, headers-utils@^1.2.0: +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== @@ -13919,11 +13751,6 @@ 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" @@ -13943,13 +13770,6 @@ 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" @@ -14118,11 +13938,6 @@ 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" @@ -14225,7 +14040,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.1.2, is-reference@^1.2.1: +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== @@ -14646,14 +14461,6 @@ 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" @@ -14948,11 +14755,6 @@ 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" @@ -15358,14 +15160,6 @@ 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" @@ -16126,7 +15920,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.2, magic-string@^0.25.7: +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== @@ -16919,22 +16713,6 @@ 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" @@ -17248,7 +17026,7 @@ node-libs-browser@^2.2.1: util "^0.11.0" vm-browserify "^1.0.1" -node-match-path@^0.4.2, node-match-path@^0.4.4: +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== @@ -17307,14 +17085,6 @@ 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" @@ -17577,11 +17347,6 @@ 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" @@ -17614,16 +17379,6 @@ 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" @@ -17644,15 +17399,6 @@ 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" @@ -19303,11 +19049,6 @@ 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" @@ -20062,7 +19803,7 @@ react-router-dom@^5.2.0: tiny-invariant "^1.0.2" tiny-warning "^1.0.0" -react-router@5.2.0, react-router@^5.2.0: +react-router@5.2.0: version "5.2.0" resolved "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz#424e75641ca8747fbf76e5ecca69781aa37ea293" integrity sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw== @@ -20931,14 +20672,6 @@ 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.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== - 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" @@ -21053,20 +20786,13 @@ 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.10: +rollup-plugin-dts@1.4.13: 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" @@ -21074,25 +20800,11 @@ 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" @@ -21131,13 +20843,6 @@ 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" @@ -21153,13 +20858,6 @@ 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" @@ -22281,14 +21979,6 @@ 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" @@ -22305,14 +21995,6 @@ 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" @@ -22415,11 +22097,6 @@ strip-json-comments@^3.1.0, strip-json-comments@^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@^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" From ab0cceaa08777febd7cd1ae54c614b7e050ba40c Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Fri, 13 Nov 2020 11:41:06 +0100 Subject: [PATCH 034/430] refactor types --- plugins/pagerduty/src/api/pagerDutyClient.ts | 19 +++--- .../src/components/PagerDutyServiceCard.tsx | 8 +-- plugins/pagerduty/src/components/Pd.tsx | 16 +++++ plugins/pagerduty/src/components/types.tsx | 62 ++++++++----------- 4 files changed, 58 insertions(+), 47 deletions(-) diff --git a/plugins/pagerduty/src/api/pagerDutyClient.ts b/plugins/pagerduty/src/api/pagerDutyClient.ts index d0cb3719d6..7b1aa442c3 100644 --- a/plugins/pagerduty/src/api/pagerDutyClient.ts +++ b/plugins/pagerduty/src/api/pagerDutyClient.ts @@ -18,10 +18,10 @@ import { createApiRef } from '@backstage/core'; import { Service, Incident, - Options, - PagerDutyClientConfig, + RequestOptions, + ClientApiConfig, ServicesResponse, - IncidentResponse, + IncidentsResponse, OnCallsResponse, OnCall, } from '../components/types'; @@ -41,7 +41,7 @@ export class PagerDutyClientApi implements PagerDutyClient { private API_URL = 'https://api.pagerduty.com'; private EVENTS_API_URL = 'https://events.pagerduty.com/v2'; - constructor(private readonly config?: PagerDutyClientConfig) {} + constructor(private readonly config?: ClientApiConfig) {} async getServiceByIntegrationKey(integrationKey: string): Promise { if (!this.config?.token) { @@ -62,7 +62,7 @@ export class PagerDutyClientApi implements PagerDutyClient { const params = `service_ids[]=${serviceId}`; const url = `${this.API_URL}/incidents?${params}`; - const { incidents } = await this.getByUrl(url); + const { incidents } = await this.getByUrl(url); return incidents; } @@ -74,9 +74,9 @@ export class PagerDutyClientApi implements PagerDutyClient { const params = `include[]=users&escalation_policy_ids[]=${policyId}`; const url = `${this.API_URL}/oncalls?${params}`; - const { onCalls } = await this.getByUrl(url); + const { oncalls } = await this.getByUrl(url); - return onCalls; + return oncalls; } triggerPagerDutyAlarm( @@ -127,7 +127,10 @@ export class PagerDutyClientApi implements PagerDutyClient { return response.json(); } - private async request(url: string, options: Options): Promise { + private async request( + url: string, + options: RequestOptions, + ): Promise { try { const response = await fetch(url, options); if (!response.ok) { diff --git a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx index 5bb7c9b5a9..d343a9fea1 100644 --- a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx +++ b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx @@ -45,13 +45,13 @@ export const PagerDutyServiceCard = ({ entity }: Props) => { const services = await api.getServiceByIntegrationKey(integrationKey); // TODO check services length const service = services[0]; - const incidents = await api.getIncidentsByServiceId(service.id); const oncalls = await api.getOnCallByPolicyId(service.escalation_policy.id); + const users = oncalls.map(item => item.user); return { incidents, - oncalls, + users, id: service.id, name: service.name, homepageUrl: service.html_url, @@ -89,9 +89,9 @@ export const PagerDutyServiceCard = ({ entity }: Props) => { content={ <> - + } - > + /> ); }; diff --git a/plugins/pagerduty/src/components/Pd.tsx b/plugins/pagerduty/src/components/Pd.tsx index eeaea87a7f..36f6e1989d 100644 --- a/plugins/pagerduty/src/components/Pd.tsx +++ b/plugins/pagerduty/src/components/Pd.tsx @@ -1,3 +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 React from 'react'; import SvgIcon, { SvgIconProps } from '@material-ui/core/SvgIcon'; diff --git a/plugins/pagerduty/src/components/types.tsx b/plugins/pagerduty/src/components/types.tsx index dec8f18739..b229949cb4 100644 --- a/plugins/pagerduty/src/components/types.tsx +++ b/plugins/pagerduty/src/components/types.tsx @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + export type Incident = { id: string; title: string; @@ -32,41 +33,14 @@ export type Service = { name: string; html_url: string; integrationKey: string; - escalation_policy: OnCall; + escalation_policy: { + id: string; + user: User; + }; }; -export type ResponseService = { - services: Service[]; - more: boolean; -}; - -export type ResponseOnCall = { - id: string; - name: string; - email: string; - homepageUrl: string; -}; - -export type Options = { - method: string; - headers: HeadersInit; - body?: BodyInit; -}; - -export type PagerDutyClientConfig = { - token?: string; -}; - -export type ServicesResponse = { - services: Service[]; -}; - -export type IncidentResponse = { - incidents: Incident[]; -}; - -export type OnCallsResponse = { - onCalls: OnCall[]; +export type OnCall = { + user: User; }; export type User = { @@ -77,6 +51,24 @@ export type User = { name: string; }; -export type OnCall = { - user: User; +export type ClientApiConfig = { + token?: string; +}; + +export type ServicesResponse = { + services: Service[]; +}; + +export type IncidentsResponse = { + incidents: Incident[]; +}; + +export type OnCallsResponse = { + oncalls: OnCall[]; +}; + +export type RequestOptions = { + method: string; + headers: HeadersInit; + body?: BodyInit; }; From 8c1662fbf9914b831cfd316f436f6dc878b6efbc Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 12 Nov 2020 00:14:22 +0100 Subject: [PATCH 035/430] docs: add stability index Co-authored-by: blam --- docs/overview/stability-index.md | 470 +++++++++++++++++++++++++++++++ microsite/sidebars.json | 1 + 2 files changed, 471 insertions(+) create mode 100644 docs/overview/stability-index.md diff --git a/docs/overview/stability-index.md b/docs/overview/stability-index.md new file mode 100644 index 0000000000..1ca54f150a --- /dev/null +++ b/docs/overview/stability-index.md @@ -0,0 +1,470 @@ +--- +id: stability-index +title: Stability Index +description: + An overview of the commitment to stability for different parts of the + Backstage codebase. +--- + +## Overview + +The purpose of the Backstage Stability Index is to communicate the stability of +various parts of the project. It is tracked using a scoring system where a +higher score indicates a higher level of stability and is a commitment to +smoother transitions between breaking changes. Importantly, the Stability Index +does not supersede [semver](https://semver.org/), meaning we will still adhere +to semver and only do breaking changes in minor releases as long as we are on +`0.x`. + +Each package or section is assigned a stability score between 0 and 3, with each +point building on top of the previous one: + +- **0** - Breaking changes are noted in the changelog, and documentation is + updated. +- **1** - The changelog entry includes a clearly documented upgrade path, + providing guidance for how to migrate previous usage patterns to the new + version. +- **2** - Breaking changes always include a deprecation phase where both the old + and the new APIs can be used in parallel. This deprecation must have been + released for at least two weeks before the deprecated API is removed in a + minor version bump. +- **3** - The time limit for the deprecation is 3 months instead of two weeks. + +TL;DR: + +- **0** - There's a changelog entry. +- **1** - There's a migration guide. +- **2** - 2 weeks of deprecation. +- **3** - 3 months of deprecation. + +## Packages + +### [example-app](https://github.com/backstage/backstage/tree/master/packages/app/) + +This is the `packages/app` package, and it's serves as an example as well as +utility for local development in the main Backstage repo. + +Stability: `N/A` + +### [example-backend](https://github.com/backstage/backstage/tree/master/packages/backend/) + +This is the `packages/backend` package, and it's serves as an example as well as +utility for local development in the main Backstage repo. + +Stability: `N/A` + +### [backend-common](https://github.com/backstage/backstage/tree/master/packages/backend-common/) + +A collection of common helpers to be used by both backend plugins, and for +constructing backend packages. + +Stability: `1` + +### [catalog-client](https://github.com/backstage/backstage/tree/master/packages/catalog-client/) + +An HTTP client for interacting with the catalog backend. Usable both in frontend +and Backend. + +Stability: `0`. This is a very new addition and we have some immediate changes +planned. + +### [catalog-model](https://github.com/backstage/backstage/tree/master/packages/catalog-model/) + +Contains the core catalog model, and utilities for working with entities. Usable +both in frontend and Backend. + +Stability: `2`. The catalog model is evolving, but because of the broad usage we + +want to ensure some stability. + +### [cli](https://github.com/backstage/backstage/tree/master/packages/cli/) + +The main toolchain used for Backstage development. The interface that is +considered for stability are the various commands and options passed to those +commands, as well as the environment variables read by the CLI. The build output +may change over time and is not considered a breaking change unless it is likely +to affect external tooling. + +Stability: `2` + +### [cli-common](https://github.com/backstage/backstage/tree/master/packages/cli-common/) + +Lightweight utilities used by the various Backstage CLIs, not intended for +external use. + +Stability: `N/A` + +### [config](https://github.com/backstage/backstage/tree/master/packages/config/) + +Provides the logic and interfaces for reading static configuration. + +Stability: `2` + +### [config-loader](https://github.com/backstage/backstage/tree/master/packages/config-loader/) + +Used to load in static configuration, mainly for use by the CLI and +@backstage/backend-common. + +Stability: `1`. Mainly intended for internal use. + +### [core](https://github.com/backstage/backstage/tree/master/packages/core/) + +#### Section: React Components + +All of the React components exported from `src/components/` and `src/layout/` + +Stability: `1`. These components have not received a proper review of the API, +but we also want to ensure stability. + +#### Section: Plugin API + +The parts of the core API that are used by plugins, and the way plugins expose +functionality to apps and other plugins. Includes for example `createPlugin`, +`createRouteRef`, `createApiRef`. + +Stability: `2`. There are planned breaking changes around the way that plugins +expose features and do routing. We still commit to keeping a short deprecation +period so that plugins outside of the main repo have time to migrate. + +#### Section: App API + +The APIs used exclusively in the app, such as `createApp` and the system icons. + +Stability: `2` + +#### Section: Utility API Definitions + +The type declarations of the core Utility APIs. + +Stability: `2`. Changes to the Utility API type declarations need time to +propagate. + +#### Section: Utility API Implementations + +The interfaces and default implementations for various Utility APIs, such as +ErrorApi, IdentityApi, the auth APIs, etc. + +Stability: `1`. Most changes to the core utility APIs will not lead to +widespread breaking changes since most apps rely on the default implementations. + +### [core-api](https://github.com/backstage/backstage/tree/master/packages/core-api/) + +The non-visual parts of @backstage/core. Everything in this packages is +re-exported from @backstage/core, and this package should not be used directly. + +Stability: See @backstage/core + +### [create-app](https://github.com/backstage/backstage/tree/master/packages/create-app/) + +The CLI used to scaffold new Backstage projects. + +Stability: `2` + +### [dev-utils](https://github.com/backstage/backstage/tree/master/packages/dev-utils/) + +Provides utilities for developing plugins in isolation. + +Stability: `0`. This package is largely broken and needs updates. + +### [docgen](https://github.com/backstage/backstage/tree/master/packages/docgen/) + +Internal CLI utility for generating API Documentation. + +Stability: `N/A` + +### [e2e-test](https://github.com/backstage/backstage/tree/master/packages/e2e-test/) + +Internal CLI utility for running e2e tests. + +Stability: `N/A` + +### [storybook](https://github.com/backstage/backstage/tree/master/packages/storybook/) + +Internal storybook build for publishing stories to +https://backstage.io/storybook + +Stability: `N/A` + +### [test-utils](https://github.com/backstage/backstage/tree/master/packages/test-utils/) + +Utilities for writing tests for Backstage plugins and apps. + +Stability: `2` + +### [test-utils-core](https://github.com/backstage/backstage/tree/master/packages/test-utils-core/) + +Internal testing utilities that are separated out for usage in +@backstage/core-api. All exports are re-exported by @backstage/test-utils. This +package should not be depended on directly. + +Stability: See @backstage/test-utils + +### [theme](https://github.com/backstage/backstage/tree/master/packages/theme/) + +The core Backstage MUI theme along with customization utilities. + +#### Section: TypeScript + +This is the TypeScript API exported by the theme package. + +Stability: `2` + +#### Section: Visual Theme + +The visual theme exported by the theme packages, where for example changing a +color could be considered a breaking change. + +Stability: `1` + +## Plugins + +Plugins are rarely marked as stable as the `@backstage/core` plugin API is under +heavy development. + +Many backend plugins are split into "REST API" and "TypeScript Interface" +sections. The "TypeScript Interface" refers to the API used to integrate the +plugin into the backend. + +### [api-docs](https://github.com/backstage/backstage/tree/master/plugins/api-docs/) + +Components to discover and display API entities as an extension to the catalog +plugin. + +Stability: `0` + +### [app-backend](https://github.com/backstage/backstage/tree/master/plugins/app-backend/) + +A backend plugin that can be used to serve the frontend app and inject +configuration. + +Stability: `2` + +### [auth-backend](https://github.com/backstage/backstage/tree/master/plugins/auth-backend/) + +A backend plugin that implements the backend portion of the various +authentication flows used in Backstage. + +#### Section: REST API + +Stability: `2` + +#### Section: TypeScript Interface + +Stability: `1` + +### [catalog](https://github.com/backstage/backstage/tree/master/plugins/catalog/) + +The frontend plugin for the catalog, with the table and building blocks for the +entity pages. + +Stability: `1`. We're planning some work to overhaul how entity pages are +constructed. + +### [catalog-backend](https://github.com/backstage/backstage/tree/master/plugins/catalog-backend/) + +The backend API for the catalog, also exposes the processing subsystem for +customization of the catalog. Powers the @backstage/plugin-catalog frontend +plugin. + +#### Section: REST API + +Stability: `1`. There are plans to remove and rework some endpoints. + +#### Section: TypeScript Interface + +Stability: `1`. There are plans to rework parts of the Processor interface. + +### [catalog-graphql](https://github.com/backstage/backstage/tree/master/plugins/catalog-graphql/) + +Provides the catalog schema and resolvers for the graphql backend. + +Stability: `0`. Under heavy development and subject to change. + +### [circleci](https://github.com/backstage/backstage/tree/master/plugins/circleci/) + +Automate your development process with CI hosted in the cloud or on a private +server. + +Stability: `0` + +### [cloudbuild](https://github.com/backstage/backstage/tree/master/plugins/cloudbuild/) + +Visualize Google Cloud Build flows. + +Stability: `0` + +### [cost-insights](https://github.com/backstage/backstage/tree/master/plugins/cost-insights/) + +Visualize, understand and optimize your team's cloud costs. + +Stability: `0` + +### [explore](https://github.com/backstage/backstage/tree/master/plugins/explore/) + +A frontend plugin that introduces the concept of exploring internal and external +tooling in an organization. + +Stability: `0`. Only an example at the moment and not customizable. + +### [gcp-projects](https://github.com/backstage/backstage/tree/master/plugins/gcp-projects/) + +Create, list and manage your Google Cloud Projects. + +Stability: `0` + +### [github-actions](https://github.com/backstage/backstage/tree/master/plugins/github-actions/) + +GitHub Actions makes it easy to automate all your software workflows, now with +world-class CI/CD. Build, test, and deploy your code right from GitHub. + +Stability: `0` + +### [gitops-profiles](https://github.com/backstage/backstage/tree/master/plugins/gitops-profiles/) + +A frontend plugin with a separate backend that can be used to provision EKS +clusters. + +Stability: `0`. This is an early plugin that now has quite a lot of overlap with +the scaffolder plugin. + +### [graphiql](https://github.com/backstage/backstage/tree/master/plugins/graphiql/) + +Integrates GraphiQL as a tool to browse GraphQL API endpoints inside Backstage. + +Stability: `1` + +### [graphql](https://github.com/backstage/backstage/tree/master/plugins/graphql/) + +A backend plugin that provides + +Stability: `0`. Under heavy development and subject to change. + +### [jenkins](https://github.com/backstage/backstage/tree/master/plugins/jenkins/) + +A plugin that visualizes Jenkins workflows for entities. Jenkins offers a simple +way to set up a continuous integration and continuous delivery environment. + +Stability: `0` + +### [kubernetes](https://github.com/backstage/backstage/tree/master/plugins/kubernetes/) + +The frontend component of the Kubernetes plugin, used to browse and visualize +Kubernetes resources. Stability: `1`. + +### [kubernetes-backend](https://github.com/backstage/backstage/tree/master/plugins/kubernetes-backend/) + +The backend component of the Kubernetes plugin, used to fetch Kubernetes +resources from clusters and associate them with entities in the Catalog. +Stability: `1`. + +### [lighthouse](https://github.com/backstage/backstage/tree/master/plugins/lighthouse/) + +Google's Lighthouse tool is a great resource for benchmarking and improving the +accessibility, performance, SEO, and best practices of your website. + +Stability: `0` + +### [newrelic](https://github.com/backstage/backstage/tree/master/plugins/newrelic/) + +Observability platform built to help engineers create and monitor their +software. Stability: `0` + +### [proxy-backend](https://github.com/backstage/backstage/tree/master/plugins/proxy-backend/) + +A backend plugin used to set up proxying to other endpoints based on static +configuration. + +Stability: `1` + +### [register-component](https://github.com/backstage/backstage/tree/master/plugins/register-component/) + +A frontend plugin that allows the user to register entity locations in the +catalog. + +Stability: `0`. This plugin is likely to be replaced by a generic entity import +plugin instead. + +### [rollbar](https://github.com/backstage/backstage/tree/master/plugins/rollbar/) + +The frontend component of the rollbar plugin, which can be used to view Rollbar +errors for your services in Backstage. + +Stability: `0` + +### [rollbar-backend](https://github.com/backstage/backstage/tree/master/plugins/rollbar-backend/) + +The backend component of the rollbar plugin, which can be used to view Rollbar +errors for your services in Backstage. + +Stability: `0` + +### [scaffolder](https://github.com/backstage/backstage/tree/master/plugins/scaffolder/) + +The frontend scaffolder plugin where one can browse templates and initiate +scaffolding jobs. + +Stability: `1` + +### [scaffolder-backend](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend/) + +The backend scaffolder plugin that provides an implementation for templates in +the catalog. + +Stability: `1`. There is planned work to rework the scaffolder in +https://github.com/backstage/backstage/issues/2771. + +### [sentry](https://github.com/backstage/backstage/tree/master/plugins/sentry/) + +The frontend component of the sentry plugin, which can be used to view Sentry +issues in Backstage. + +Stability: `0` + +### [sentry-backend](https://github.com/backstage/backstage/tree/master/plugins/sentry-backend/) + +The backend component of the sentry plugin, which can be used to view Sentry +issues in Backstage. + +Stability: `0` + +### [sonarqube](https://github.com/backstage/backstage/tree/master/plugins/sonarqube/) + +Components to display code quality metrics from SonarCloud and SonarQube. + +Stability: `0` + +### [tech-radar](https://github.com/backstage/backstage/tree/master/plugins/tech-radar/) + +Visualize the your company's official guidelines of different areas of software +development. + +Stability: `0` + +### [techdocs](https://github.com/backstage/backstage/tree/master/plugins/techdocs/) + +The frontend component of the TechDocs plugin, used to browser technical +documentation of entities. + +Stability: `1` + +### [techdocs-backend](https://github.com/backstage/backstage/tree/master/plugins/techdocs-backend/) + +The backend component of the TechDocs plugin, used to transform and serve +TechDocs. + +Stability: `1` + +### [user-settings](https://github.com/backstage/backstage/tree/master/plugins/user-settings/) + +A frontend plugin that provides a page where the user can tweak various +settings. + +Stability: `1` + +### [welcome](https://github.com/backstage/backstage/tree/master/plugins/welcome/) + +A plugin that can be used to welcome the user to Backstage. + +Stability: `0`. This used to be the start page for the example app, but has been +replaced by the catalog plugin. It is still viewable at `/welcome` but may be +removed. diff --git a/microsite/sidebars.json b/microsite/sidebars.json index 64fdd2f420..f3cf642de1 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -7,6 +7,7 @@ "overview/vision", "overview/background", "overview/adopting", + "overview/stability-index", "overview/logos" ], "Getting Started": [ From 148ecbfcd655193593b5cd8fe3e82f61b8f3f656 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 13 Nov 2020 13:21:42 +0100 Subject: [PATCH 036/430] github/vocab: added words --- .github/styles/vocab.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt index 3c5fa34adc..efda3a185d 100644 --- a/.github/styles/vocab.txt +++ b/.github/styles/vocab.txt @@ -20,6 +20,7 @@ Changesets changset chanwit Chanwit +circleci cisphobia cissexist classname @@ -64,6 +65,7 @@ github Github gitlab Gitlab +graphiql graphql graphviz Hackathons @@ -80,6 +82,7 @@ inlinehilite interop javascript Javascript +jenkins jq js json @@ -164,9 +167,11 @@ scaffolded scaffolder Scaffolder semlas +semver Serverless Sinon smartsymobls +sonarqube sparklines Spotifiers spotify @@ -190,6 +195,7 @@ theres toc tolerations Tolerations +toolchain toolsets tooltip touchpoints From f9c971b3f7a8ee85e994aeb5ef79b0d5a097e3f5 Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Fri, 13 Nov 2020 13:41:46 +0100 Subject: [PATCH 037/430] refactor code --- plugins/pagerduty/package.json | 1 + .../src/components/Escalation.test.tsx | 34 ------------- .../components/Escalation/Escalation.test.tsx | 47 +++++++++++++++++ .../Escalation/EscalationPolicy.tsx | 36 +++++++++++++ .../EscalationUser.tsx} | 49 ++---------------- .../Escalation/EscalationUsersEmptyState.tsx | 48 +++++++++++++++++ .../Incident/IncidentEmptyState.tsx | 36 +++++++++++++ .../IncidentListItem.tsx} | 51 +++---------------- .../{ => Incident}/Incidents.test.tsx | 23 +++++++-- .../src/components/Incident/Incidents.tsx | 40 +++++++++++++++ .../src/components/PagerDutyServiceCard.tsx | 4 +- .../src/components/TriggerDialog.test.tsx | 33 ++++++------ 12 files changed, 258 insertions(+), 144 deletions(-) delete mode 100644 plugins/pagerduty/src/components/Escalation.test.tsx create mode 100644 plugins/pagerduty/src/components/Escalation/Escalation.test.tsx create mode 100644 plugins/pagerduty/src/components/Escalation/EscalationPolicy.tsx rename plugins/pagerduty/src/components/{Escalation.tsx => Escalation/EscalationUser.tsx} (63%) create mode 100644 plugins/pagerduty/src/components/Escalation/EscalationUsersEmptyState.tsx create mode 100644 plugins/pagerduty/src/components/Incident/IncidentEmptyState.tsx rename plugins/pagerduty/src/components/{Incidents.tsx => Incident/IncidentListItem.tsx} (72%) rename plugins/pagerduty/src/components/{ => Incident}/Incidents.test.tsx (71%) create mode 100644 plugins/pagerduty/src/components/Incident/Incidents.tsx diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json index e5d0264be3..43aad8579a 100644 --- a/plugins/pagerduty/package.json +++ b/plugins/pagerduty/package.json @@ -23,6 +23,7 @@ "@backstage/core": "^0.1.1-alpha.25", "@backstage/theme": "^0.1.1-alpha.25", "@backstage/catalog-model": "^0.1.1-alpha.25", + "@backstage/test-utils": "^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", diff --git a/plugins/pagerduty/src/components/Escalation.test.tsx b/plugins/pagerduty/src/components/Escalation.test.tsx deleted file mode 100644 index 13e2687b04..0000000000 --- a/plugins/pagerduty/src/components/Escalation.test.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react'; -import { render } from '@testing-library/react'; -import { EscalationPolicy } from './Escalation'; -import { wrapInTestApp } from '@backstage/test-utils'; -import { OnCall } from './types'; - -export const escalations: OnCall[] = [ - { - user: { - name: 'person1', - id: 'p1', - summary: 'person1', - email: 'person1@example.com', - html_url: 'http://a.com/id1', - }, - }, -]; - -describe('Escalation', () => { - it('render emptyState', () => { - const { getByText } = render( - wrapInTestApp(), - ); - expect(getByText('Empty escalation policy')).toBeInTheDocument(); - }); - - it('render Escalation list', () => { - const { getByText } = render( - wrapInTestApp(), - ); - expect(getByText('person1')).toBeInTheDocument(); - expect(getByText('person1@example.com')).toBeInTheDocument(); - }); -}); diff --git a/plugins/pagerduty/src/components/Escalation/Escalation.test.tsx b/plugins/pagerduty/src/components/Escalation/Escalation.test.tsx new file mode 100644 index 0000000000..3e4a59d15a --- /dev/null +++ b/plugins/pagerduty/src/components/Escalation/Escalation.test.tsx @@ -0,0 +1,47 @@ +/* + * 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 { render } from '@testing-library/react'; +import { EscalationPolicy } from './EscalationPolicy'; +import { wrapInTestApp } from '@backstage/test-utils'; +import { User } from '../types'; + +const escalations: User[] = [ + { + name: 'person1', + id: 'p1', + summary: 'person1', + email: 'person1@example.com', + html_url: 'http://a.com/id1', + }, +]; + +describe('Escalation', () => { + it('render emptyState', () => { + const { getByText } = render( + wrapInTestApp(), + ); + expect(getByText('Empty escalation policy')).toBeInTheDocument(); + }); + + it('render Escalation list', () => { + const { getByText } = render( + wrapInTestApp(), + ); + expect(getByText('person1')).toBeInTheDocument(); + expect(getByText('person1@example.com')).toBeInTheDocument(); + }); +}); diff --git a/plugins/pagerduty/src/components/Escalation/EscalationPolicy.tsx b/plugins/pagerduty/src/components/Escalation/EscalationPolicy.tsx new file mode 100644 index 0000000000..8a2aacead9 --- /dev/null +++ b/plugins/pagerduty/src/components/Escalation/EscalationPolicy.tsx @@ -0,0 +1,36 @@ +/* + * 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 } from '@material-ui/core'; +import { User } from '../types'; +import { EscalationUsersEmptyState } from './EscalationUsersEmptyState'; +import { EscalationUser } from './EscalationUser'; + +type EscalationPolicyProps = { + users: User[]; +}; + +export const EscalationPolicy = ({ users }: EscalationPolicyProps) => ( + ON CALL}> + {users.length ? ( + users.map((user, index) => ) + ) : ( + // TODO: how does it look if we used an EmptyState component here + + )} + +); diff --git a/plugins/pagerduty/src/components/Escalation.tsx b/plugins/pagerduty/src/components/Escalation/EscalationUser.tsx similarity index 63% rename from plugins/pagerduty/src/components/Escalation.tsx rename to plugins/pagerduty/src/components/Escalation/EscalationUser.tsx index 56c2c27a03..3fa8ef8d65 100644 --- a/plugins/pagerduty/src/components/Escalation.tsx +++ b/plugins/pagerduty/src/components/Escalation/EscalationUser.tsx @@ -16,8 +16,6 @@ import React from 'react'; import { - List, - ListSubheader, ListItem, ListItemIcon, ListItemSecondaryAction, @@ -29,27 +27,16 @@ import { } from '@material-ui/core'; import Avatar from '@material-ui/core/Avatar'; import EmailIcon from '@material-ui/icons/Email'; -import { StatusWarning } from '@backstage/core'; -import { OnCall } from './types'; -import PagerdutyIcon from './Pd'; +import PagerdutyIcon from '../Pd'; +import { User } from '../types'; const useStyles = makeStyles({ - denseListIcon: { - marginRight: 0, - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - justifyContent: 'center', - }, - svgButtonImage: { - height: '1em', - }, listItemPrimary: { fontWeight: 'bold', }, }); -const EscalationUser = ({ user }: OnCall) => { +export const EscalationUser = ({ user }: { user: User }) => { const classes = useStyles(); return ( @@ -84,33 +71,3 @@ const EscalationUser = ({ user }: OnCall) => { ); }; - -const EscalationUsersEmptyState = () => { - const classes = useStyles(); - return ( - - -
- -
-
- -
- ); -}; - -type EscalationPolicyProps = { - escalation: OnCall[]; -}; - -export const EscalationPolicy = ({ escalation }: EscalationPolicyProps) => ( - ON CALL}> - {escalation.length ? ( - escalation.map((item, index) => ( - - )) - ) : ( - - )} - -); diff --git a/plugins/pagerduty/src/components/Escalation/EscalationUsersEmptyState.tsx b/plugins/pagerduty/src/components/Escalation/EscalationUsersEmptyState.tsx new file mode 100644 index 0000000000..d587011601 --- /dev/null +++ b/plugins/pagerduty/src/components/Escalation/EscalationUsersEmptyState.tsx @@ -0,0 +1,48 @@ +/* + * 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 { + ListItem, + ListItemIcon, + ListItemText, + makeStyles, +} from '@material-ui/core'; +import { StatusWarning } from '@backstage/core'; + +const useStyles = makeStyles({ + denseListIcon: { + marginRight: 0, + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + }, +}); + +export const EscalationUsersEmptyState = () => { + const classes = useStyles(); + return ( + + +
+ +
+
+ +
+ ); +}; diff --git a/plugins/pagerduty/src/components/Incident/IncidentEmptyState.tsx b/plugins/pagerduty/src/components/Incident/IncidentEmptyState.tsx new file mode 100644 index 0000000000..ded161261e --- /dev/null +++ b/plugins/pagerduty/src/components/Incident/IncidentEmptyState.tsx @@ -0,0 +1,36 @@ +/* + * 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 { Grid } from '@material-ui/core'; +import EmptyStateImage from '../../assets/emptyState.svg'; + +export const IncidentsEmptyState = () => { + return ( + + +

Nice! No incidents are assigned to you!

+
+ + EmptyState + +
+ ); +}; diff --git a/plugins/pagerduty/src/components/Incidents.tsx b/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx similarity index 72% rename from plugins/pagerduty/src/components/Incidents.tsx rename to plugins/pagerduty/src/components/Incident/IncidentListItem.tsx index 52247b8f06..f01c99fc8c 100644 --- a/plugins/pagerduty/src/components/Incidents.tsx +++ b/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx @@ -16,7 +16,6 @@ import React from 'react'; import { - List, ListItem, ListItemIcon, ListItemSecondaryAction, @@ -24,14 +23,17 @@ import { ListItemText, makeStyles, IconButton, - ListSubheader, Link, Typography, } from '@material-ui/core'; -import { StatusError, StatusWarning, StatusOK } from '@backstage/core'; +import { StatusError, StatusWarning } from '@backstage/core'; import moment from 'moment'; -import { Incident } from '../components/types'; -import PagerdutyIcon from './Pd'; +import { Incident } from '../types'; +import PagerdutyIcon from '../Pd'; + +type IncidentListItemProps = { + incident: Incident; +}; const useStyles = makeStyles({ denseListIcon: { @@ -41,9 +43,6 @@ const useStyles = makeStyles({ alignItems: 'center', justifyContent: 'center', }, - svgButtonImage: { - height: '1em', - }, listItemPrimary: { fontWeight: 'bold', }, @@ -52,25 +51,7 @@ const useStyles = makeStyles({ }, }); -const IncidentsEmptyState = () => { - const classes = useStyles(); - return ( - - -
- -
-
- -
- ); -}; - -type IncidentListItemProps = { - incident: Incident; -}; - -const IncidentListItem = ({ incident }: IncidentListItemProps) => { +export const IncidentListItem = ({ incident }: IncidentListItemProps) => { const classes = useStyles(); const user = incident.assignments[0].assignee; return ( @@ -117,19 +98,3 @@ const IncidentListItem = ({ incident }: IncidentListItemProps) => { ); }; - -type IncidentsProps = { - incidents: Incident[]; -}; - -export const Incidents = ({ incidents }: IncidentsProps) => ( - INCIDENTS}> - {incidents.length ? ( - incidents.map((incident, index) => ( - - )) - ) : ( - - )} - -); diff --git a/plugins/pagerduty/src/components/Incidents.test.tsx b/plugins/pagerduty/src/components/Incident/Incidents.test.tsx similarity index 71% rename from plugins/pagerduty/src/components/Incidents.test.tsx rename to plugins/pagerduty/src/components/Incident/Incidents.test.tsx index 62c8151af2..fbe3783d30 100644 --- a/plugins/pagerduty/src/components/Incidents.test.tsx +++ b/plugins/pagerduty/src/components/Incident/Incidents.test.tsx @@ -1,10 +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 React from 'react'; import { render } from '@testing-library/react'; import { Incidents } from './Incidents'; import { wrapInTestApp } from '@backstage/test-utils'; -import { Incident } from './types'; +import { Incident } from '../types'; -export const incidents: Incident[] = [ +const incidents: Incident[] = [ { id: 'id1', status: 'triggered', @@ -48,7 +63,9 @@ export const incidents: Incident[] = [ describe('Incidents', () => { it('renders an empty state is there are no incidents', () => { const { getByText } = render(wrapInTestApp()); - expect(getByText('No incidents')).toBeInTheDocument(); + expect( + getByText('Nice! No incidents are assigned to you!'), + ).toBeInTheDocument(); }); it('renders all incidents', () => { diff --git a/plugins/pagerduty/src/components/Incident/Incidents.tsx b/plugins/pagerduty/src/components/Incident/Incidents.tsx new file mode 100644 index 0000000000..a49d913c80 --- /dev/null +++ b/plugins/pagerduty/src/components/Incident/Incidents.tsx @@ -0,0 +1,40 @@ +/* + * 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 } from '@material-ui/core'; +import { Incident } from '../types'; +import { IncidentListItem } from './IncidentListItem'; +import { IncidentsEmptyState } from './IncidentEmptyState'; + +type IncidentsProps = { + incidents: Incident[]; +}; + +export const Incidents = ({ incidents }: IncidentsProps) => ( + {incidents.length && 'INCIDENTS'}} + > + {incidents.length ? ( + incidents.map((incident, index) => ( + + )) + ) : ( + + )} + +); diff --git a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx index d343a9fea1..480e7cdbd6 100644 --- a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx +++ b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx @@ -17,8 +17,8 @@ import React from 'react'; import { useApi } from '@backstage/core'; import { Entity } from '@backstage/catalog-model'; import { LinearProgress } from '@material-ui/core'; -import { Incidents } from './Incidents'; -import { EscalationPolicy } from './Escalation'; +import { Incidents } from './Incident/Incidents'; +import { EscalationPolicy } from './Escalation/EscalationPolicy'; import { TriggerButton } from './TriggerButton'; import { useAsync } from 'react-use'; import { Alert } from '@material-ui/lab'; diff --git a/plugins/pagerduty/src/components/TriggerDialog.test.tsx b/plugins/pagerduty/src/components/TriggerDialog.test.tsx index a99e2c03f4..387b168e05 100644 --- a/plugins/pagerduty/src/components/TriggerDialog.test.tsx +++ b/plugins/pagerduty/src/components/TriggerDialog.test.tsx @@ -1,7 +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. + */ import React from 'react'; -import { render, fireEvent, getByTestId } from '@testing-library/react'; +import { render, fireEvent } from '@testing-library/react'; import { wrapInTestApp } from '@backstage/test-utils'; -import { TriggerDialog } from './TriggerDialog'; import { ApiRegistry, alertApiRef, @@ -38,8 +52,6 @@ describe('TriggerDialog', () => { ]); it('open the dialog and trigger an alarm', async () => { - const onClick = jest.fn(async () => {}); - const entity: Entity = { apiVersion: 'backstage.io/v1alpha1', kind: 'Component', @@ -51,21 +63,10 @@ describe('TriggerDialog', () => { }, }; - const { - getByText, - getByRole, - queryByText, - queryByRole, - getByTestId, - } = render( + const { getByText, getByRole, queryByRole, getByTestId } = render( wrapInTestApp( - {/* */} , ), ); From a969371bf61d50c620587c176e87520e11c9574e Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 13 Nov 2020 13:51:49 +0100 Subject: [PATCH 038/430] docs/stability-index: fix a grammar --- docs/overview/stability-index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/overview/stability-index.md b/docs/overview/stability-index.md index 1ca54f150a..e410015662 100644 --- a/docs/overview/stability-index.md +++ b/docs/overview/stability-index.md @@ -442,7 +442,7 @@ Stability: `0` ### [techdocs](https://github.com/backstage/backstage/tree/master/plugins/techdocs/) -The frontend component of the TechDocs plugin, used to browser technical +The frontend component of the TechDocs plugin, used to browse technical documentation of entities. Stability: `1` From b4c9e09b87aa29b6c9be9284682f98e09aa66d37 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 13 Nov 2020 13:54:49 +0100 Subject: [PATCH 039/430] docs/stability-index: wrap package titles in code blocks --- .github/styles/vocab.txt | 4 -- docs/overview/stability-index.md | 106 +++++++++++++++---------------- 2 files changed, 53 insertions(+), 57 deletions(-) diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt index efda3a185d..bd89163f1f 100644 --- a/.github/styles/vocab.txt +++ b/.github/styles/vocab.txt @@ -20,7 +20,6 @@ Changesets changset chanwit Chanwit -circleci cisphobia cissexist classname @@ -65,7 +64,6 @@ github Github gitlab Gitlab -graphiql graphql graphviz Hackathons @@ -82,7 +80,6 @@ inlinehilite interop javascript Javascript -jenkins jq js json @@ -171,7 +168,6 @@ semver Serverless Sinon smartsymobls -sonarqube sparklines Spotifiers spotify diff --git a/docs/overview/stability-index.md b/docs/overview/stability-index.md index e410015662..96ff33542f 100644 --- a/docs/overview/stability-index.md +++ b/docs/overview/stability-index.md @@ -39,28 +39,28 @@ TL;DR: ## Packages -### [example-app](https://github.com/backstage/backstage/tree/master/packages/app/) +### [`example-app`](https://github.com/backstage/backstage/tree/master/packages/app/) This is the `packages/app` package, and it's serves as an example as well as utility for local development in the main Backstage repo. Stability: `N/A` -### [example-backend](https://github.com/backstage/backstage/tree/master/packages/backend/) +### [`example-backend`](https://github.com/backstage/backstage/tree/master/packages/backend/) This is the `packages/backend` package, and it's serves as an example as well as utility for local development in the main Backstage repo. Stability: `N/A` -### [backend-common](https://github.com/backstage/backstage/tree/master/packages/backend-common/) +### [`backend-common`](https://github.com/backstage/backstage/tree/master/packages/backend-common/) A collection of common helpers to be used by both backend plugins, and for constructing backend packages. Stability: `1` -### [catalog-client](https://github.com/backstage/backstage/tree/master/packages/catalog-client/) +### [`catalog-client`](https://github.com/backstage/backstage/tree/master/packages/catalog-client/) An HTTP client for interacting with the catalog backend. Usable both in frontend and Backend. @@ -68,7 +68,7 @@ and Backend. Stability: `0`. This is a very new addition and we have some immediate changes planned. -### [catalog-model](https://github.com/backstage/backstage/tree/master/packages/catalog-model/) +### [`catalog-model`](https://github.com/backstage/backstage/tree/master/packages/catalog-model/) Contains the core catalog model, and utilities for working with entities. Usable both in frontend and Backend. @@ -77,7 +77,7 @@ Stability: `2`. The catalog model is evolving, but because of the broad usage we want to ensure some stability. -### [cli](https://github.com/backstage/backstage/tree/master/packages/cli/) +### [`cli`](https://github.com/backstage/backstage/tree/master/packages/cli/) The main toolchain used for Backstage development. The interface that is considered for stability are the various commands and options passed to those @@ -87,27 +87,27 @@ to affect external tooling. Stability: `2` -### [cli-common](https://github.com/backstage/backstage/tree/master/packages/cli-common/) +### [`cli-common`](https://github.com/backstage/backstage/tree/master/packages/cli-common/) Lightweight utilities used by the various Backstage CLIs, not intended for external use. Stability: `N/A` -### [config](https://github.com/backstage/backstage/tree/master/packages/config/) +### [`config`](https://github.com/backstage/backstage/tree/master/packages/config/) Provides the logic and interfaces for reading static configuration. Stability: `2` -### [config-loader](https://github.com/backstage/backstage/tree/master/packages/config-loader/) +### [`config-loader`](https://github.com/backstage/backstage/tree/master/packages/config-loader/) Used to load in static configuration, mainly for use by the CLI and @backstage/backend-common. Stability: `1`. Mainly intended for internal use. -### [core](https://github.com/backstage/backstage/tree/master/packages/core/) +### [`core`](https://github.com/backstage/backstage/tree/master/packages/core/) #### Section: React Components @@ -147,51 +147,51 @@ ErrorApi, IdentityApi, the auth APIs, etc. Stability: `1`. Most changes to the core utility APIs will not lead to widespread breaking changes since most apps rely on the default implementations. -### [core-api](https://github.com/backstage/backstage/tree/master/packages/core-api/) +### [`core-api`](https://github.com/backstage/backstage/tree/master/packages/core-api/) The non-visual parts of @backstage/core. Everything in this packages is re-exported from @backstage/core, and this package should not be used directly. Stability: See @backstage/core -### [create-app](https://github.com/backstage/backstage/tree/master/packages/create-app/) +### [`create-app`](https://github.com/backstage/backstage/tree/master/packages/create-app/) The CLI used to scaffold new Backstage projects. Stability: `2` -### [dev-utils](https://github.com/backstage/backstage/tree/master/packages/dev-utils/) +### [`dev-utils`](https://github.com/backstage/backstage/tree/master/packages/dev-utils/) Provides utilities for developing plugins in isolation. Stability: `0`. This package is largely broken and needs updates. -### [docgen](https://github.com/backstage/backstage/tree/master/packages/docgen/) +### [`docgen`](https://github.com/backstage/backstage/tree/master/packages/docgen/) Internal CLI utility for generating API Documentation. Stability: `N/A` -### [e2e-test](https://github.com/backstage/backstage/tree/master/packages/e2e-test/) +### [`e2e-test`](https://github.com/backstage/backstage/tree/master/packages/e2e-test/) Internal CLI utility for running e2e tests. Stability: `N/A` -### [storybook](https://github.com/backstage/backstage/tree/master/packages/storybook/) +### [`storybook`](https://github.com/backstage/backstage/tree/master/packages/storybook/) Internal storybook build for publishing stories to https://backstage.io/storybook Stability: `N/A` -### [test-utils](https://github.com/backstage/backstage/tree/master/packages/test-utils/) +### [`test-utils`](https://github.com/backstage/backstage/tree/master/packages/test-utils/) Utilities for writing tests for Backstage plugins and apps. Stability: `2` -### [test-utils-core](https://github.com/backstage/backstage/tree/master/packages/test-utils-core/) +### [`test-utils-core`](https://github.com/backstage/backstage/tree/master/packages/test-utils-core/) Internal testing utilities that are separated out for usage in @backstage/core-api. All exports are re-exported by @backstage/test-utils. This @@ -199,7 +199,7 @@ package should not be depended on directly. Stability: See @backstage/test-utils -### [theme](https://github.com/backstage/backstage/tree/master/packages/theme/) +### [`theme`](https://github.com/backstage/backstage/tree/master/packages/theme/) The core Backstage MUI theme along with customization utilities. @@ -225,21 +225,21 @@ Many backend plugins are split into "REST API" and "TypeScript Interface" sections. The "TypeScript Interface" refers to the API used to integrate the plugin into the backend. -### [api-docs](https://github.com/backstage/backstage/tree/master/plugins/api-docs/) +### [`api-docs`](https://github.com/backstage/backstage/tree/master/plugins/api-docs/) Components to discover and display API entities as an extension to the catalog plugin. Stability: `0` -### [app-backend](https://github.com/backstage/backstage/tree/master/plugins/app-backend/) +### [`app-backend`](https://github.com/backstage/backstage/tree/master/plugins/app-backend/) A backend plugin that can be used to serve the frontend app and inject configuration. Stability: `2` -### [auth-backend](https://github.com/backstage/backstage/tree/master/plugins/auth-backend/) +### [`auth-backend`](https://github.com/backstage/backstage/tree/master/plugins/auth-backend/) A backend plugin that implements the backend portion of the various authentication flows used in Backstage. @@ -252,7 +252,7 @@ Stability: `2` Stability: `1` -### [catalog](https://github.com/backstage/backstage/tree/master/plugins/catalog/) +### [`catalog`](https://github.com/backstage/backstage/tree/master/plugins/catalog/) The frontend plugin for the catalog, with the table and building blocks for the entity pages. @@ -260,7 +260,7 @@ entity pages. Stability: `1`. We're planning some work to overhaul how entity pages are constructed. -### [catalog-backend](https://github.com/backstage/backstage/tree/master/plugins/catalog-backend/) +### [`catalog-backend`](https://github.com/backstage/backstage/tree/master/plugins/catalog-backend/) The backend API for the catalog, also exposes the processing subsystem for customization of the catalog. Powers the @backstage/plugin-catalog frontend @@ -274,52 +274,52 @@ Stability: `1`. There are plans to remove and rework some endpoints. Stability: `1`. There are plans to rework parts of the Processor interface. -### [catalog-graphql](https://github.com/backstage/backstage/tree/master/plugins/catalog-graphql/) +### [`catalog-graphql`](https://github.com/backstage/backstage/tree/master/plugins/catalog-graphql/) Provides the catalog schema and resolvers for the graphql backend. Stability: `0`. Under heavy development and subject to change. -### [circleci](https://github.com/backstage/backstage/tree/master/plugins/circleci/) +### [`circleci`](https://github.com/backstage/backstage/tree/master/plugins/circleci/) Automate your development process with CI hosted in the cloud or on a private server. Stability: `0` -### [cloudbuild](https://github.com/backstage/backstage/tree/master/plugins/cloudbuild/) +### [`cloudbuild`](https://github.com/backstage/backstage/tree/master/plugins/cloudbuild/) Visualize Google Cloud Build flows. Stability: `0` -### [cost-insights](https://github.com/backstage/backstage/tree/master/plugins/cost-insights/) +### [`cost-insights`](https://github.com/backstage/backstage/tree/master/plugins/cost-insights/) Visualize, understand and optimize your team's cloud costs. Stability: `0` -### [explore](https://github.com/backstage/backstage/tree/master/plugins/explore/) +### [`explore`](https://github.com/backstage/backstage/tree/master/plugins/explore/) A frontend plugin that introduces the concept of exploring internal and external tooling in an organization. Stability: `0`. Only an example at the moment and not customizable. -### [gcp-projects](https://github.com/backstage/backstage/tree/master/plugins/gcp-projects/) +### [`gcp-projects`](https://github.com/backstage/backstage/tree/master/plugins/gcp-projects/) Create, list and manage your Google Cloud Projects. Stability: `0` -### [github-actions](https://github.com/backstage/backstage/tree/master/plugins/github-actions/) +### [`github-actions`](https://github.com/backstage/backstage/tree/master/plugins/github-actions/) GitHub Actions makes it easy to automate all your software workflows, now with world-class CI/CD. Build, test, and deploy your code right from GitHub. Stability: `0` -### [gitops-profiles](https://github.com/backstage/backstage/tree/master/plugins/gitops-profiles/) +### [`gitops-profiles`](https://github.com/backstage/backstage/tree/master/plugins/gitops-profiles/) A frontend plugin with a separate backend that can be used to provision EKS clusters. @@ -327,56 +327,56 @@ clusters. Stability: `0`. This is an early plugin that now has quite a lot of overlap with the scaffolder plugin. -### [graphiql](https://github.com/backstage/backstage/tree/master/plugins/graphiql/) +### [`graphiql`](https://github.com/backstage/backstage/tree/master/plugins/graphiql/) Integrates GraphiQL as a tool to browse GraphQL API endpoints inside Backstage. Stability: `1` -### [graphql](https://github.com/backstage/backstage/tree/master/plugins/graphql/) +### [`graphql`](https://github.com/backstage/backstage/tree/master/plugins/graphql/) A backend plugin that provides Stability: `0`. Under heavy development and subject to change. -### [jenkins](https://github.com/backstage/backstage/tree/master/plugins/jenkins/) +### [`jenkins`](https://github.com/backstage/backstage/tree/master/plugins/jenkins/) A plugin that visualizes Jenkins workflows for entities. Jenkins offers a simple way to set up a continuous integration and continuous delivery environment. Stability: `0` -### [kubernetes](https://github.com/backstage/backstage/tree/master/plugins/kubernetes/) +### [`kubernetes`](https://github.com/backstage/backstage/tree/master/plugins/kubernetes/) The frontend component of the Kubernetes plugin, used to browse and visualize Kubernetes resources. Stability: `1`. -### [kubernetes-backend](https://github.com/backstage/backstage/tree/master/plugins/kubernetes-backend/) +### [`kubernetes-backend`](https://github.com/backstage/backstage/tree/master/plugins/kubernetes-backend/) The backend component of the Kubernetes plugin, used to fetch Kubernetes resources from clusters and associate them with entities in the Catalog. Stability: `1`. -### [lighthouse](https://github.com/backstage/backstage/tree/master/plugins/lighthouse/) +### [`lighthouse`](https://github.com/backstage/backstage/tree/master/plugins/lighthouse/) Google's Lighthouse tool is a great resource for benchmarking and improving the accessibility, performance, SEO, and best practices of your website. Stability: `0` -### [newrelic](https://github.com/backstage/backstage/tree/master/plugins/newrelic/) +### [`newrelic`](https://github.com/backstage/backstage/tree/master/plugins/newrelic/) Observability platform built to help engineers create and monitor their software. Stability: `0` -### [proxy-backend](https://github.com/backstage/backstage/tree/master/plugins/proxy-backend/) +### [`proxy-backend`](https://github.com/backstage/backstage/tree/master/plugins/proxy-backend/) A backend plugin used to set up proxying to other endpoints based on static configuration. Stability: `1` -### [register-component](https://github.com/backstage/backstage/tree/master/plugins/register-component/) +### [`register-component`](https://github.com/backstage/backstage/tree/master/plugins/register-component/) A frontend plugin that allows the user to register entity locations in the catalog. @@ -384,28 +384,28 @@ catalog. Stability: `0`. This plugin is likely to be replaced by a generic entity import plugin instead. -### [rollbar](https://github.com/backstage/backstage/tree/master/plugins/rollbar/) +### [`rollbar`](https://github.com/backstage/backstage/tree/master/plugins/rollbar/) The frontend component of the rollbar plugin, which can be used to view Rollbar errors for your services in Backstage. Stability: `0` -### [rollbar-backend](https://github.com/backstage/backstage/tree/master/plugins/rollbar-backend/) +### [`rollbar-backend`](https://github.com/backstage/backstage/tree/master/plugins/rollbar-backend/) The backend component of the rollbar plugin, which can be used to view Rollbar errors for your services in Backstage. Stability: `0` -### [scaffolder](https://github.com/backstage/backstage/tree/master/plugins/scaffolder/) +### [`scaffolder`](https://github.com/backstage/backstage/tree/master/plugins/scaffolder/) The frontend scaffolder plugin where one can browse templates and initiate scaffolding jobs. Stability: `1` -### [scaffolder-backend](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend/) +### [`scaffolder-backend`](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend/) The backend scaffolder plugin that provides an implementation for templates in the catalog. @@ -413,55 +413,55 @@ the catalog. Stability: `1`. There is planned work to rework the scaffolder in https://github.com/backstage/backstage/issues/2771. -### [sentry](https://github.com/backstage/backstage/tree/master/plugins/sentry/) +### [`sentry`](https://github.com/backstage/backstage/tree/master/plugins/sentry/) The frontend component of the sentry plugin, which can be used to view Sentry issues in Backstage. Stability: `0` -### [sentry-backend](https://github.com/backstage/backstage/tree/master/plugins/sentry-backend/) +### [`sentry-backend`](https://github.com/backstage/backstage/tree/master/plugins/sentry-backend/) The backend component of the sentry plugin, which can be used to view Sentry issues in Backstage. Stability: `0` -### [sonarqube](https://github.com/backstage/backstage/tree/master/plugins/sonarqube/) +### [`sonarqube`](https://github.com/backstage/backstage/tree/master/plugins/sonarqube/) Components to display code quality metrics from SonarCloud and SonarQube. Stability: `0` -### [tech-radar](https://github.com/backstage/backstage/tree/master/plugins/tech-radar/) +### [`tech-radar`](https://github.com/backstage/backstage/tree/master/plugins/tech-radar/) Visualize the your company's official guidelines of different areas of software development. Stability: `0` -### [techdocs](https://github.com/backstage/backstage/tree/master/plugins/techdocs/) +### [`techdocs`](https://github.com/backstage/backstage/tree/master/plugins/techdocs/) The frontend component of the TechDocs plugin, used to browse technical documentation of entities. Stability: `1` -### [techdocs-backend](https://github.com/backstage/backstage/tree/master/plugins/techdocs-backend/) +### [`techdocs-backend`](https://github.com/backstage/backstage/tree/master/plugins/techdocs-backend/) The backend component of the TechDocs plugin, used to transform and serve TechDocs. Stability: `1` -### [user-settings](https://github.com/backstage/backstage/tree/master/plugins/user-settings/) +### [`user-settings`](https://github.com/backstage/backstage/tree/master/plugins/user-settings/) A frontend plugin that provides a page where the user can tweak various settings. Stability: `1` -### [welcome](https://github.com/backstage/backstage/tree/master/plugins/welcome/) +### [`welcome`](https://github.com/backstage/backstage/tree/master/plugins/welcome/) A plugin that can be used to welcome the user to Backstage. From 2d255052f029cb0c628e84ad377d187a97dff096 Mon Sep 17 00:00:00 2001 From: Remi Date: Sun, 15 Nov 2020 16:18:13 +0100 Subject: [PATCH 040/430] feat(core): add missing tests --- packages/core/package.json | 3 +- .../AlertDisplay/AlertDisplay.test.tsx | 65 ++++++++ .../EmptyState/EmptyStateImage.test.tsx | 49 ++++++ .../components/EmptyState/EmptyStateImage.tsx | 9 +- .../FeatureCalloutCircular.test.tsx | 154 ++++++++++++++++++ .../FeatureCalloutCircular.tsx | 2 + yarn.lock | 16 +- 7 files changed, 285 insertions(+), 13 deletions(-) create mode 100644 packages/core/src/components/AlertDisplay/AlertDisplay.test.tsx create mode 100644 packages/core/src/components/EmptyState/EmptyStateImage.test.tsx create mode 100644 packages/core/src/components/FeatureDiscovery/FeatureCalloutCircular.test.tsx diff --git a/packages/core/package.json b/packages/core/package.json index 848f0847bd..f7e8c67fe9 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -60,7 +60,8 @@ "react-sparklines": "^1.7.0", "react-syntax-highlighter": "^13.5.1", "react-use": "^15.3.3", - "remark-gfm": "^1.0.0" + "remark-gfm": "^1.0.0", + "zen-observable": "^0.8.15" }, "devDependencies": { "@backstage/cli": "^0.2.0", diff --git a/packages/core/src/components/AlertDisplay/AlertDisplay.test.tsx b/packages/core/src/components/AlertDisplay/AlertDisplay.test.tsx new file mode 100644 index 0000000000..3f85dab9f3 --- /dev/null +++ b/packages/core/src/components/AlertDisplay/AlertDisplay.test.tsx @@ -0,0 +1,65 @@ +/* + * 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 { AlertDisplay } from './AlertDisplay'; +import { + ApiProvider, + ApiRegistry, + alertApiRef, + AlertApiForwarder, +} from '@backstage/core-api'; +import Observable from 'zen-observable'; +import { renderInTestApp } from '@backstage/test-utils'; + +const TEST_MESSAGE = 'TEST_MESSAGE'; + +describe('', () => { + it('renders without exploding', async () => { + const apiRegistry = ApiRegistry.from([ + [alertApiRef, new AlertApiForwarder()], + ]); + + const { queryByText } = await renderInTestApp( + + + , + ); + expect(queryByText(TEST_MESSAGE)).not.toBeInTheDocument(); + }); + + it('renders with message', async () => { + const apiRegistry = ApiRegistry.from([ + [ + alertApiRef, + { + post() {}, + alert$() { + return Observable.of({ message: TEST_MESSAGE }); + }, + }, + ], + ]); + + const { queryByText } = await renderInTestApp( + + + , + ); + + expect(queryByText(TEST_MESSAGE)).toBeInTheDocument(); + }); +}); diff --git a/packages/core/src/components/EmptyState/EmptyStateImage.test.tsx b/packages/core/src/components/EmptyState/EmptyStateImage.test.tsx new file mode 100644 index 0000000000..942f533dac --- /dev/null +++ b/packages/core/src/components/EmptyState/EmptyStateImage.test.tsx @@ -0,0 +1,49 @@ +/* + * 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 { renderWithEffects, wrapInTestApp } from '@backstage/test-utils'; +import { EmptyStateImage } from './EmptyStateImage'; + +describe('', () => { + it('render EmptyStateImage component with missing field', async () => { + const rendered = await renderWithEffects( + wrapInTestApp(), + ); + expect(rendered.getByTestId('missingAnnotation')).toBeInTheDocument(); + }); + + it('render EmptyStateImage component with missing info', async () => { + const rendered = await renderWithEffects( + wrapInTestApp(), + ); + expect(rendered.getByTestId('noInformation')).toBeInTheDocument(); + }); + + it('render EmptyStateImage component with missing content', async () => { + const rendered = await renderWithEffects( + wrapInTestApp(), + ); + expect(rendered.getByTestId('createComponent')).toBeInTheDocument(); + }); + + it('render EmptyStateImage component with missing data', async () => { + const rendered = await renderWithEffects( + wrapInTestApp(), + ); + expect(rendered.getByTestId('noBuild')).toBeInTheDocument(); + }); +}); diff --git a/packages/core/src/components/EmptyState/EmptyStateImage.tsx b/packages/core/src/components/EmptyState/EmptyStateImage.tsx index d52a0f7dcf..c7a61f7190 100644 --- a/packages/core/src/components/EmptyState/EmptyStateImage.tsx +++ b/packages/core/src/components/EmptyState/EmptyStateImage.tsx @@ -54,6 +54,7 @@ export const EmptyStateImage = ({ missing }: Props) => { src={noInformation} alt="no Information" className={classes.generalImg} + data-testid="noInformation" /> ); case 'content': @@ -62,11 +63,17 @@ export const EmptyStateImage = ({ missing }: Props) => { src={createComponent} alt="create Component" className={classes.generalImg} + data-testid="createComponent" /> ); case 'data': return ( - no Build + no Build ); default: return null; diff --git a/packages/core/src/components/FeatureDiscovery/FeatureCalloutCircular.test.tsx b/packages/core/src/components/FeatureDiscovery/FeatureCalloutCircular.test.tsx new file mode 100644 index 0000000000..5c7f17ba9f --- /dev/null +++ b/packages/core/src/components/FeatureDiscovery/FeatureCalloutCircular.test.tsx @@ -0,0 +1,154 @@ +/* + * 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 { act, fireEvent } from '@testing-library/react'; +import { renderInTestApp } from '@backstage/test-utils'; +import { FeatureCalloutCircular } from './FeatureCalloutCircular'; + +const INITIAL_BOUNDING_RECT: DOMRect = { + width: 100, + height: 100, + x: 0, + y: 0, + bottom: 0, + left: 0, + right: 0, + top: 0, + toJSON: () => {}, +}; + +const UPDATED_BOUNDING_RECT: DOMRect = { + width: 200, + height: 200, + x: 50, + y: 50, + bottom: 0, + left: 0, + right: 0, + top: 0, + toJSON: () => {}, +}; + +beforeEach(() => { + Element.prototype.getBoundingClientRect = jest.fn( + () => INITIAL_BOUNDING_RECT, + ); +}); + +describe('', () => { + it('renders without exploding', async () => { + const rendered = await renderInTestApp( + , + ); + rendered.getByText('description'); + rendered.getByText('title'); + }); + + it('renders with correct style', async () => { + const { getByTestId } = await renderInTestApp( + , + ); + const dot = await getByTestId('dot'); + const text = await getByTestId('text'); + + expect(dot).toBeInTheDocument(); + expect(text).toBeInTheDocument(); + + // Dot style + expect(dot.style.left).toBe('-800px'); + expect(dot.style.top).toBe('-800px'); + expect(dot.style.width).toBe('1700px'); + expect(dot.style.height).toBe('1700px'); + + // Text style + expect(text.style.left).toBe('-400px'); + expect(text.style.top).toBe('120px'); + expect(text.style.width).toBe('450px'); + }); + + it('update when the user scrolls', async () => { + const { getByTestId } = await renderInTestApp( + , + ); + const dot = await getByTestId('dot'); + const text = await getByTestId('text'); + + act(() => { + Element.prototype.getBoundingClientRect = jest.fn( + () => UPDATED_BOUNDING_RECT, + ); + + // Trigger the window resize event. + fireEvent(window, new Event('resize')); + }); + + // Dot style + expect(dot.style.left).toBe('-750px'); + expect(dot.style.top).toBe('-750px'); + expect(dot.style.width).toBe('1800px'); + expect(dot.style.height).toBe('1800px'); + + // Text style + expect(text.style.left).toBe('-300px'); + expect(text.style.top).toBe('270px'); + expect(text.style.width).toBe('450px'); + }); + + it('update when the user resizes the window', async () => { + const { getByTestId } = await renderInTestApp( + , + ); + const dot = await getByTestId('dot'); + const text = await getByTestId('text'); + + act(() => { + Element.prototype.getBoundingClientRect = jest.fn( + () => UPDATED_BOUNDING_RECT, + ); + + // Trigger the window resize event. + fireEvent(window, new Event('scroll')); + }); + + // Dot style + expect(dot.style.left).toBe('-750px'); + expect(dot.style.top).toBe('-750px'); + expect(dot.style.width).toBe('1800px'); + expect(dot.style.height).toBe('1800px'); + + // Text style + expect(text.style.left).toBe('-300px'); + expect(text.style.top).toBe('270px'); + expect(text.style.width).toBe('450px'); + }); +}); diff --git a/packages/core/src/components/FeatureDiscovery/FeatureCalloutCircular.tsx b/packages/core/src/components/FeatureDiscovery/FeatureCalloutCircular.tsx index 38e422dbcc..9dc0d25681 100644 --- a/packages/core/src/components/FeatureDiscovery/FeatureCalloutCircular.tsx +++ b/packages/core/src/components/FeatureDiscovery/FeatureCalloutCircular.tsx @@ -160,6 +160,7 @@ export const FeatureCalloutCircular: FC = ({ <>
= ({
Date: Sun, 15 Nov 2020 16:18:38 +0100 Subject: [PATCH 041/430] fix(core): LinearGauge tooltip + test --- .../ProgressBars/LinearGauge.test.tsx | 37 +++++++++++++++++++ .../components/ProgressBars/LinearGauge.tsx | 14 ++++--- 2 files changed, 45 insertions(+), 6 deletions(-) create mode 100644 packages/core/src/components/ProgressBars/LinearGauge.test.tsx diff --git a/packages/core/src/components/ProgressBars/LinearGauge.test.tsx b/packages/core/src/components/ProgressBars/LinearGauge.test.tsx new file mode 100644 index 0000000000..fc04b642fb --- /dev/null +++ b/packages/core/src/components/ProgressBars/LinearGauge.test.tsx @@ -0,0 +1,37 @@ +/* + * 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 { renderInTestApp } from '@backstage/test-utils'; + +import { LinearGauge } from './LinearGauge'; + +describe('', () => { + it('renders without exploding', async () => { + const { getByTitle } = await renderInTestApp(); + expect(getByTitle('50%')).toBeInTheDocument(); + }); + + it('renders progress and title', async () => { + const { container } = await renderInTestApp(); + expect(container).toBeEmptyDOMElement(); + }); + + it('renders with 100 as max value', async () => { + const { getByTitle } = await renderInTestApp(); + expect(getByTitle('100%')).toBeInTheDocument(); + }); +}); diff --git a/packages/core/src/components/ProgressBars/LinearGauge.tsx b/packages/core/src/components/ProgressBars/LinearGauge.tsx index 2b8e77838d..a6aea59f19 100644 --- a/packages/core/src/components/ProgressBars/LinearGauge.tsx +++ b/packages/core/src/components/ProgressBars/LinearGauge.tsx @@ -40,12 +40,14 @@ export const LinearGauge: FC = ({ value }) => { const strokeColor = getProgressColor(theme.palette, percent, false, 100); return ( - + + + ); }; From 538328e0834d776a79738d816e0a871fd9ddc87f Mon Sep 17 00:00:00 2001 From: Remi Date: Mon, 16 Nov 2020 13:58:37 +0100 Subject: [PATCH 042/430] fix(core): regenerate yarn.lock file --- yarn.lock | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/yarn.lock b/yarn.lock index 9d4984e152..10e70f52fb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1290,34 +1290,40 @@ to-fast-properties "^2.0.0" "@backstage/core@^0.2.0": - version "0.2.0" - resolved "https://registry.npmjs.org/@backstage/core/-/core-0.2.0.tgz#543246b2d87563c9aa4d9fb96e40fdfc7e827520" - integrity sha512-75m2u3FoUngBOvt9l65xZcYTzzB+49OXpY1A9VNFUR1+jMs3cL/0HDfByQV2H0xXaHzMngQ8C5u/sWhkQsij1w== + version "0.3.0" dependencies: "@backstage/config" "^0.1.1" - "@backstage/core-api" "^0.2.0" - "@backstage/theme" "^0.2.0" + "@backstage/core-api" "^0.2.1" + "@backstage/theme" "^0.2.1" "@material-ui/core" "^4.11.0" "@material-ui/icons" "^4.9.1" "@material-ui/lab" "4.0.0-alpha.45" + "@types/dagre" "^0.7.44" "@types/react" "^16.9" "@types/react-sparklines" "^1.7.0" classnames "^2.2.6" clsx "^1.1.0" + d3-selection "^2.0.0" + d3-shape "^2.0.0" + d3-zoom "^2.0.0" + dagre "^0.8.5" immer "^7.0.9" lodash "^4.17.15" material-table "^1.69.1" prop-types "^15.7.2" + qs "^6.9.4" rc-progress "^3.0.0" react "^16.12.0" react-dom "^16.12.0" react-helmet "6.1.0" react-hook-form "^6.6.0" + react-markdown "^5.0.2" react-router "6.0.0-beta.0" react-router-dom "6.0.0-beta.0" react-sparklines "^1.7.0" react-syntax-highlighter "^13.5.1" react-use "^15.3.3" + remark-gfm "^1.0.0" "@bcoe/v8-coverage@^0.2.3": version "0.2.3" From 00a2ead383598e17bf5f5a9d833656520daf4a57 Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Mon, 16 Nov 2020 16:09:45 +0100 Subject: [PATCH 043/430] refactor code an structure --- .../components/Escalation/Escalation.test.tsx | 2 +- .../Escalation/EscalationPolicy.tsx | 1 - .../components/Escalation/EscalationUser.tsx | 2 +- .../src/components/Escalation/index.ts | 17 ++ .../components/Incident/IncidentListItem.tsx | 2 +- .../src/components/Incident/index.ts | 17 ++ .../components/{Pd.tsx => PagerDutyIcon.tsx} | 0 .../src/components/PagerDutyServiceCard.tsx | 97 --------- .../src/components/PagerdutyCard.tsx | 194 ++++++++++-------- .../{ => TriggerButton}/TriggerButton.tsx | 4 +- .../src/components/TriggerButton/index.ts | 17 ++ .../TriggerDialog.test.tsx | 4 +- .../{ => TriggerDialog}/TriggerDialog.tsx | 2 +- .../src/components/TriggerDialog/index.ts | 17 ++ plugins/pagerduty/src/components/types.tsx | 9 +- 15 files changed, 186 insertions(+), 199 deletions(-) create mode 100644 plugins/pagerduty/src/components/Escalation/index.ts create mode 100644 plugins/pagerduty/src/components/Incident/index.ts rename plugins/pagerduty/src/components/{Pd.tsx => PagerDutyIcon.tsx} (100%) delete mode 100644 plugins/pagerduty/src/components/PagerDutyServiceCard.tsx rename plugins/pagerduty/src/components/{ => TriggerButton}/TriggerButton.tsx (93%) create mode 100644 plugins/pagerduty/src/components/TriggerButton/index.ts rename plugins/pagerduty/src/components/{ => TriggerDialog}/TriggerDialog.test.tsx (96%) rename plugins/pagerduty/src/components/{ => TriggerDialog}/TriggerDialog.tsx (98%) create mode 100644 plugins/pagerduty/src/components/TriggerDialog/index.ts diff --git a/plugins/pagerduty/src/components/Escalation/Escalation.test.tsx b/plugins/pagerduty/src/components/Escalation/Escalation.test.tsx index 3e4a59d15a..4fa65eb999 100644 --- a/plugins/pagerduty/src/components/Escalation/Escalation.test.tsx +++ b/plugins/pagerduty/src/components/Escalation/Escalation.test.tsx @@ -37,7 +37,7 @@ describe('Escalation', () => { expect(getByText('Empty escalation policy')).toBeInTheDocument(); }); - it('render Escalation list', () => { + it('render escalation list', () => { const { getByText } = render( wrapInTestApp(), ); diff --git a/plugins/pagerduty/src/components/Escalation/EscalationPolicy.tsx b/plugins/pagerduty/src/components/Escalation/EscalationPolicy.tsx index 8a2aacead9..cbce0ce4a3 100644 --- a/plugins/pagerduty/src/components/Escalation/EscalationPolicy.tsx +++ b/plugins/pagerduty/src/components/Escalation/EscalationPolicy.tsx @@ -29,7 +29,6 @@ export const EscalationPolicy = ({ users }: EscalationPolicyProps) => ( {users.length ? ( users.map((user, index) => ) ) : ( - // TODO: how does it look if we used an EmptyState component here )} diff --git a/plugins/pagerduty/src/components/Escalation/EscalationUser.tsx b/plugins/pagerduty/src/components/Escalation/EscalationUser.tsx index 3fa8ef8d65..e20a5f7567 100644 --- a/plugins/pagerduty/src/components/Escalation/EscalationUser.tsx +++ b/plugins/pagerduty/src/components/Escalation/EscalationUser.tsx @@ -27,7 +27,7 @@ import { } from '@material-ui/core'; import Avatar from '@material-ui/core/Avatar'; import EmailIcon from '@material-ui/icons/Email'; -import PagerdutyIcon from '../Pd'; +import PagerdutyIcon from '../PagerDutyIcon'; import { User } from '../types'; const useStyles = makeStyles({ diff --git a/plugins/pagerduty/src/components/Escalation/index.ts b/plugins/pagerduty/src/components/Escalation/index.ts new file mode 100644 index 0000000000..ac2db62cd9 --- /dev/null +++ b/plugins/pagerduty/src/components/Escalation/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 { EscalationPolicy } from './EscalationPolicy'; diff --git a/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx b/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx index f01c99fc8c..4700583a8b 100644 --- a/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx +++ b/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx @@ -29,7 +29,7 @@ import { import { StatusError, StatusWarning } from '@backstage/core'; import moment from 'moment'; import { Incident } from '../types'; -import PagerdutyIcon from '../Pd'; +import PagerdutyIcon from '../PagerDutyIcon'; type IncidentListItemProps = { incident: Incident; diff --git a/plugins/pagerduty/src/components/Incident/index.ts b/plugins/pagerduty/src/components/Incident/index.ts new file mode 100644 index 0000000000..fb2702602b --- /dev/null +++ b/plugins/pagerduty/src/components/Incident/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 { Incidents } from './Incidents'; diff --git a/plugins/pagerduty/src/components/Pd.tsx b/plugins/pagerduty/src/components/PagerDutyIcon.tsx similarity index 100% rename from plugins/pagerduty/src/components/Pd.tsx rename to plugins/pagerduty/src/components/PagerDutyIcon.tsx diff --git a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx deleted file mode 100644 index 480e7cdbd6..0000000000 --- a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx +++ /dev/null @@ -1,97 +0,0 @@ -/* - * 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 { useApi } from '@backstage/core'; -import { Entity } from '@backstage/catalog-model'; -import { LinearProgress } from '@material-ui/core'; -import { Incidents } from './Incident/Incidents'; -import { EscalationPolicy } from './Escalation/EscalationPolicy'; -import { TriggerButton } from './TriggerButton'; -import { useAsync } from 'react-use'; -import { Alert } from '@material-ui/lab'; -import { pagerDutyApiRef } from '../api/pagerDutyClient'; -import { PagerdutyCard } from './PagerdutyCard'; - -export const PAGERDUTY_INTEGRATION_KEY = 'pagerduty.com/integration-key'; - -export const isPluginApplicableToEntity = (entity: Entity) => - Boolean(entity.metadata.annotations?.[PAGERDUTY_INTEGRATION_KEY]); - -type Props = { - entity: Entity; -}; - -export const PagerDutyServiceCard = ({ entity }: Props) => { - const api = useApi(pagerDutyApiRef); - - const { value, loading, error } = useAsync(async () => { - const integrationKey = entity.metadata.annotations![ - PAGERDUTY_INTEGRATION_KEY - ]; - - const services = await api.getServiceByIntegrationKey(integrationKey); - // TODO check services length - const service = services[0]; - const incidents = await api.getIncidentsByServiceId(service.id); - const oncalls = await api.getOnCallByPolicyId(service.escalation_policy.id); - const users = oncalls.map(item => item.user); - - return { - incidents, - users, - id: service.id, - name: service.name, - homepageUrl: service.html_url, - }; - }); - - if (error) { - return ( -
- - Error encountered while fetching information. {error.message} - -
- ); - } - - if (loading) { - return ; - } - - const pagerdutyLink = { - title: 'View in PagerDuty', - href: value!.homepageUrl, - }; - - const triggerAlarm = { - title: 'Trigger Alarm', - action: , - }; - - return ( - - - - - } - /> - ); -}; diff --git a/plugins/pagerduty/src/components/PagerdutyCard.tsx b/plugins/pagerduty/src/components/PagerdutyCard.tsx index 539bf69540..6b6d466850 100644 --- a/plugins/pagerduty/src/components/PagerdutyCard.tsx +++ b/plugins/pagerduty/src/components/PagerdutyCard.tsx @@ -1,18 +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. + */ import React from 'react'; +import { useApi, EmptyState } from '@backstage/core'; +import { Entity } from '@backstage/catalog-model'; import { Card, - CardHeader, CardContent, + CardHeader, Divider, - Link, + LinearProgress, makeStyles, } from '@material-ui/core'; -import LinkIcon from '@material-ui/icons/Link'; +import { Incidents } from './Incident'; +import { EscalationPolicy } from './Escalation'; +import { TriggerButton } from './TriggerButton'; +import { useAsync } from 'react-use'; +import { Alert } from '@material-ui/lab'; +import { pagerDutyApiRef, UnauthorizedError } from '../api'; +import { IconLinkVertical } from '@backstage/plugin-catalog'; +import PagerDutyIcon from './PagerDutyIcon'; import ReportProblemIcon from '@material-ui/icons/ReportProblem'; -import classnames from 'classnames'; -import PagerdutyIcon from './Pd'; - -// TODO: create a general component to use it in pagerduty and aboutCard const useStyles = makeStyles(theme => ({ links: { @@ -22,102 +42,98 @@ const useStyles = makeStyles(theme => ({ gridAutoColumns: 'min-content', gridGap: theme.spacing(3), }, - link: { - display: 'grid', - justifyItems: 'center', - gridGap: 4, - textAlign: 'center', - }, - label: { - fontSize: '0.7rem', - textTransform: 'uppercase', - fontWeight: 600, - letterSpacing: 1.2, - }, - trigger: { - color: theme.palette.secondary.main, - }, - svgButtonImage: { - height: '1.5em', - color: theme.palette.primary.main, - }, })); -type SubHeaderProps = { - ViewPagerduty: { title: string; href: string }; - TriggerAlarm: { title: string; action: React.ReactNode }; +export const PAGERDUTY_INTEGRATION_KEY = 'pagerduty.com/integration-key'; + +export const isPluginApplicableToEntity = (entity: Entity) => + Boolean(entity.metadata.annotations?.[PAGERDUTY_INTEGRATION_KEY]); + +type Props = { + entity: Entity; }; -type PagerdutyCardProps = { - title: string; - subheader: SubHeaderProps; - content: React.ReactNode; -}; - -type VerticalIconProps = { - label: string; - href?: string; - action?: React.ReactNode; - icon?: React.ReactNode; -}; - -const VerticalIcon = ({ - label, - href, - icon = , - action, -}: VerticalIconProps) => { +export const PagerDutyCard = ({ entity }: Props) => { const classes = useStyles(); - if (action) { + const api = useApi(pagerDutyApiRef); + + const { value, loading, error } = useAsync(async () => { + const integrationKey = entity.metadata.annotations![ + PAGERDUTY_INTEGRATION_KEY + ]; + + const services = await api.getServiceByIntegrationKey(integrationKey); + const incidents = await api.getIncidentsByServiceId(services[0].id); + const oncalls = await api.getOnCallByPolicyId( + services[0].escalation_policy.id, + ); + const users = oncalls.map(oncall => oncall.user); + + return { + incidents, + users, + id: services[0].id, + name: services[0].name, + homepageUrl: services[0].html_url, + }; + }); + + if (error) { + if (error instanceof UnauthorizedError) { + return ( + + ); + } + return ( - <> - - {icon} - {action} - - + + Error encountered while fetching information. {error.message} + ); } - return ( - <> - - {icon} - {label} - - - ); -}; -export const PagerdutyCard = ({ - title, - subheader, - content, -}: PagerdutyCardProps) => { - const classes = useStyles(); - const getSubheader = ({ ViewPagerduty, TriggerAlarm }: SubHeaderProps) => { - return ( - - ); + + if (loading) { + return ; + } + + const pagerdutyLink = { + title: 'View in PagerDuty', + href: value!.homepageUrl, + }; + + const triggerAlarm = { + title: 'Trigger Alarm', + action: , }; return ( + title="PagerDuty" + subheader={ + + } + /> - {content} + + + + ); }; diff --git a/plugins/pagerduty/src/components/TriggerButton.tsx b/plugins/pagerduty/src/components/TriggerButton/TriggerButton.tsx similarity index 93% rename from plugins/pagerduty/src/components/TriggerButton.tsx rename to plugins/pagerduty/src/components/TriggerButton/TriggerButton.tsx index 48bed68e2e..8feebf9e89 100644 --- a/plugins/pagerduty/src/components/TriggerButton.tsx +++ b/plugins/pagerduty/src/components/TriggerButton/TriggerButton.tsx @@ -16,9 +16,9 @@ import React, { useState } from 'react'; import { Button, makeStyles } from '@material-ui/core'; -import { TriggerDialog } from './TriggerDialog'; +import { TriggerDialog } from '../TriggerDialog'; import { Entity } from '@backstage/catalog-model'; -import { PAGERDUTY_INTEGRATION_KEY } from './PagerDutyServiceCard'; +import { PAGERDUTY_INTEGRATION_KEY } from '../PagerDutyCard'; const useStyles = makeStyles({ triggerAlarm: { diff --git a/plugins/pagerduty/src/components/TriggerButton/index.ts b/plugins/pagerduty/src/components/TriggerButton/index.ts new file mode 100644 index 0000000000..6a58a4f43f --- /dev/null +++ b/plugins/pagerduty/src/components/TriggerButton/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 { TriggerButton } from './TriggerButton'; diff --git a/plugins/pagerduty/src/components/TriggerDialog.test.tsx b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx similarity index 96% rename from plugins/pagerduty/src/components/TriggerDialog.test.tsx rename to plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx index 387b168e05..7e26985136 100644 --- a/plugins/pagerduty/src/components/TriggerDialog.test.tsx +++ b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx @@ -24,8 +24,8 @@ import { IdentityApi, identityApiRef, } from '@backstage/core'; -import { pagerDutyApiRef } from '../api/pagerDutyClient'; -import { TriggerButton } from './TriggerButton'; +import { pagerDutyApiRef } from '../../api'; +import { TriggerButton } from '../TriggerButton'; import { Entity } from '@backstage/catalog-model'; import { act } from 'react-dom/test-utils'; diff --git a/plugins/pagerduty/src/components/TriggerDialog.tsx b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx similarity index 98% rename from plugins/pagerduty/src/components/TriggerDialog.tsx rename to plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx index 08044d02f6..91ec3cd27d 100644 --- a/plugins/pagerduty/src/components/TriggerDialog.tsx +++ b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx @@ -26,7 +26,7 @@ import { } from '@material-ui/core'; import { Progress, useApi, alertApiRef, identityApiRef } from '@backstage/core'; import { useAsyncFn } from 'react-use'; -import { pagerDutyApiRef } from '../api/pagerDutyClient'; +import { pagerDutyApiRef } from '../../api'; const useStyles = makeStyles({ warningText: { diff --git a/plugins/pagerduty/src/components/TriggerDialog/index.ts b/plugins/pagerduty/src/components/TriggerDialog/index.ts new file mode 100644 index 0000000000..655cef8504 --- /dev/null +++ b/plugins/pagerduty/src/components/TriggerDialog/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 { TriggerDialog } from './TriggerDialog'; diff --git a/plugins/pagerduty/src/components/types.tsx b/plugins/pagerduty/src/components/types.tsx index b229949cb4..9c9e29cb01 100644 --- a/plugins/pagerduty/src/components/types.tsx +++ b/plugins/pagerduty/src/components/types.tsx @@ -51,10 +51,6 @@ export type User = { name: string; }; -export type ClientApiConfig = { - token?: string; -}; - export type ServicesResponse = { services: Service[]; }; @@ -72,3 +68,8 @@ export type RequestOptions = { headers: HeadersInit; body?: BodyInit; }; + +export type ClientApiConfig = { + api_url: string; + events_url: string; +}; From ba985b1357610282ce41926d001f47d2208ca3a9 Mon Sep 17 00:00:00 2001 From: Remi Date: Mon, 16 Nov 2020 16:18:50 +0100 Subject: [PATCH 044/430] feat(core): remove data-testid --- .../components/EmptyState/EmptyState.test.tsx | 2 +- .../EmptyState/EmptyStateImage.test.tsx | 16 ++++++++-------- .../components/EmptyState/EmptyStateImage.tsx | 10 +--------- .../FeatureCalloutCircular.test.tsx | 2 +- 4 files changed, 11 insertions(+), 19 deletions(-) diff --git a/packages/core/src/components/EmptyState/EmptyState.test.tsx b/packages/core/src/components/EmptyState/EmptyState.test.tsx index c5bc7a9876..32e71f044d 100644 --- a/packages/core/src/components/EmptyState/EmptyState.test.tsx +++ b/packages/core/src/components/EmptyState/EmptyState.test.tsx @@ -34,6 +34,6 @@ describe('', () => { rendered.getByText('Your plugin is missing an annotation'), ).toBeInTheDocument(); expect(rendered.getByLabelText('button')).toBeInTheDocument(); - expect(rendered.getByTestId('missingAnnotation')).toBeInTheDocument(); + expect(rendered.getByAltText('annotation is missing')).toBeInTheDocument(); }); }); diff --git a/packages/core/src/components/EmptyState/EmptyStateImage.test.tsx b/packages/core/src/components/EmptyState/EmptyStateImage.test.tsx index 942f533dac..258eee943d 100644 --- a/packages/core/src/components/EmptyState/EmptyStateImage.test.tsx +++ b/packages/core/src/components/EmptyState/EmptyStateImage.test.tsx @@ -20,30 +20,30 @@ import { EmptyStateImage } from './EmptyStateImage'; describe('', () => { it('render EmptyStateImage component with missing field', async () => { - const rendered = await renderWithEffects( + const { getByAltText } = await renderWithEffects( wrapInTestApp(), ); - expect(rendered.getByTestId('missingAnnotation')).toBeInTheDocument(); + expect(getByAltText('annotation is missing')).toBeInTheDocument(); }); it('render EmptyStateImage component with missing info', async () => { - const rendered = await renderWithEffects( + const { getByAltText } = await renderWithEffects( wrapInTestApp(), ); - expect(rendered.getByTestId('noInformation')).toBeInTheDocument(); + expect(getByAltText('no Information')).toBeInTheDocument(); }); it('render EmptyStateImage component with missing content', async () => { - const rendered = await renderWithEffects( + const { getByAltText } = await renderWithEffects( wrapInTestApp(), ); - expect(rendered.getByTestId('createComponent')).toBeInTheDocument(); + expect(getByAltText('create Component')).toBeInTheDocument(); }); it('render EmptyStateImage component with missing data', async () => { - const rendered = await renderWithEffects( + const { getByAltText } = await renderWithEffects( wrapInTestApp(), ); - expect(rendered.getByTestId('noBuild')).toBeInTheDocument(); + expect(getByAltText('no Build')).toBeInTheDocument(); }); }); diff --git a/packages/core/src/components/EmptyState/EmptyStateImage.tsx b/packages/core/src/components/EmptyState/EmptyStateImage.tsx index c7a61f7190..1973ff9a23 100644 --- a/packages/core/src/components/EmptyState/EmptyStateImage.tsx +++ b/packages/core/src/components/EmptyState/EmptyStateImage.tsx @@ -45,7 +45,6 @@ export const EmptyStateImage = ({ missing }: Props) => { src={missingAnnotation} className={classes.generalImg} alt="annotation is missing" - data-testid="missingAnnotation" /> ); case 'info': @@ -54,7 +53,6 @@ export const EmptyStateImage = ({ missing }: Props) => { src={noInformation} alt="no Information" className={classes.generalImg} - data-testid="noInformation" /> ); case 'content': @@ -63,17 +61,11 @@ export const EmptyStateImage = ({ missing }: Props) => { src={createComponent} alt="create Component" className={classes.generalImg} - data-testid="createComponent" /> ); case 'data': return ( - no Build + no Build ); default: return null; diff --git a/packages/core/src/components/FeatureDiscovery/FeatureCalloutCircular.test.tsx b/packages/core/src/components/FeatureDiscovery/FeatureCalloutCircular.test.tsx index 5c7f17ba9f..83a31f198d 100644 --- a/packages/core/src/components/FeatureDiscovery/FeatureCalloutCircular.test.tsx +++ b/packages/core/src/components/FeatureDiscovery/FeatureCalloutCircular.test.tsx @@ -136,7 +136,7 @@ describe('', () => { () => UPDATED_BOUNDING_RECT, ); - // Trigger the window resize event. + // Trigger the window scroll event. fireEvent(window, new Event('scroll')); }); From 6fc060842ed1de91b0c173b3693e3d3f11c91f9b Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Mon, 16 Nov 2020 16:34:49 +0100 Subject: [PATCH 045/430] refactor api to use token from proxy --- .../app/src/components/catalog/EntityPage.tsx | 4 +- plugins/pagerduty/package.json | 1 + .../src/api/{pagerDutyClient.ts => client.ts} | 60 +++++++------------ plugins/pagerduty/src/api/index.ts | 22 +++++++ plugins/pagerduty/src/api/types.ts | 37 ++++++++++++ plugins/pagerduty/src/index.ts | 4 +- plugins/pagerduty/src/plugin.ts | 7 ++- 7 files changed, 91 insertions(+), 44 deletions(-) rename plugins/pagerduty/src/api/{pagerDutyClient.ts => client.ts} (68%) create mode 100644 plugins/pagerduty/src/api/index.ts create mode 100644 plugins/pagerduty/src/api/types.ts diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index ddc8ed4b13..4fc894ef90 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -68,7 +68,7 @@ import { } from '@roadiehq/backstage-plugin-github-pull-requests'; import { isPluginApplicableToEntity as isPagerDutyAvailable, - PagerDutyServiceCard, + PagerDutyCard, } from '@backstage/plugin-pagerduty'; const CICDSwitcher = ({ entity }: { entity: Entity }) => { @@ -137,7 +137,7 @@ const OverviewContent = ({ entity }: { entity: Entity }) => ( {isPagerDutyAvailable(entity) && ( - + )} diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json index 43aad8579a..29518bd858 100644 --- a/plugins/pagerduty/package.json +++ b/plugins/pagerduty/package.json @@ -24,6 +24,7 @@ "@backstage/theme": "^0.1.1-alpha.25", "@backstage/catalog-model": "^0.1.1-alpha.25", "@backstage/test-utils": "^0.1.1-alpha.25", + "@backstage/plugin-catalog": "^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", diff --git a/plugins/pagerduty/src/api/pagerDutyClient.ts b/plugins/pagerduty/src/api/client.ts similarity index 68% rename from plugins/pagerduty/src/api/pagerDutyClient.ts rename to plugins/pagerduty/src/api/client.ts index 7b1aa442c3..985bb25e5a 100644 --- a/plugins/pagerduty/src/api/pagerDutyClient.ts +++ b/plugins/pagerduty/src/api/client.ts @@ -25,55 +25,41 @@ import { OnCallsResponse, OnCall, } from '../components/types'; +import { PagerDutyClient } from './types'; + +export class UnauthorizedError extends Error { + constructor() { + super(); + } +} export const pagerDutyApiRef = createApiRef({ id: 'plugin.pagerduty.api', description: 'Used to fetch data from PagerDuty API', }); -interface PagerDutyClient { - getServiceByIntegrationKey(integrationKey: string): Promise; - getIncidentsByServiceId(serviceId: string): Promise; - getOnCallByPolicyId(policyId: string): Promise; -} - export class PagerDutyClientApi implements PagerDutyClient { - private API_URL = 'https://api.pagerduty.com'; - private EVENTS_API_URL = 'https://events.pagerduty.com/v2'; - - constructor(private readonly config?: ClientApiConfig) {} + constructor(private readonly config: ClientApiConfig) {} async getServiceByIntegrationKey(integrationKey: string): Promise { - if (!this.config?.token) { - throw new Error('Missing token'); - } - const params = `include[]=integrations&include[]=escalation_policies&query=${integrationKey}`; - const url = `${this.API_URL}/services?${params}`; + const url = `${this.config.api_url}/services?${params}`; const { services } = await this.getByUrl(url); return services; } async getIncidentsByServiceId(serviceId: string): Promise { - if (!this.config?.token) { - throw new Error('Missing token'); - } - const params = `service_ids[]=${serviceId}`; - const url = `${this.API_URL}/incidents?${params}`; + const url = `${this.config.api_url}/incidents?${params}`; const { incidents } = await this.getByUrl(url); return incidents; } async getOnCallByPolicyId(policyId: string): Promise { - if (!this.config?.token) { - throw new Error('Missing token'); - } - const params = `include[]=users&escalation_policy_ids[]=${policyId}`; - const url = `${this.API_URL}/oncalls?${params}`; + const url = `${this.config.api_url}/oncalls?${params}`; const { oncalls } = await this.getByUrl(url); return oncalls; @@ -110,14 +96,13 @@ export class PagerDutyClientApi implements PagerDutyClient { body, }; - return this.request(`${this.EVENTS_API_URL}/enqueue`, options); + return this.request(`${this.config.events_url}/enqueue`, options); } private async getByUrl(url: string): Promise { const options = { method: 'GET', headers: { - Authorization: `Token token=${this.config!.token}`, Accept: 'application/vnd.pagerduty+json;version=2', 'Content-Type': 'application/json', }, @@ -131,17 +116,16 @@ export class PagerDutyClientApi implements PagerDutyClient { url: string, options: RequestOptions, ): Promise { - 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); - } - return response; - } catch (error) { - throw new Error(error); + const response = await fetch(url, options); + if (response.status === 401) { + throw new UnauthorizedError(); } + 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 response; } } diff --git a/plugins/pagerduty/src/api/index.ts b/plugins/pagerduty/src/api/index.ts new file mode 100644 index 0000000000..5ac7470475 --- /dev/null +++ b/plugins/pagerduty/src/api/index.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 { + PagerDutyClientApi, + pagerDutyApiRef, + UnauthorizedError, +} from './client'; +export type { PagerDutyClient } from './types'; diff --git a/plugins/pagerduty/src/api/types.ts b/plugins/pagerduty/src/api/types.ts new file mode 100644 index 0000000000..cb9d7fc5bc --- /dev/null +++ b/plugins/pagerduty/src/api/types.ts @@ -0,0 +1,37 @@ +/* + * 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 { Incident, OnCall, Service } from '../components/types'; + +export interface PagerDutyClient { + /** + * Fetches a list of services, filtered by the provided integration key. + * + */ + getServiceByIntegrationKey(integrationKey: string): Promise; + + /** + * Fetches a list of incidents a provided service has. + * + */ + getIncidentsByServiceId(serviceId: string): Promise; + + /** + * Fetches the list of users in an escalation policy. + * + */ + getOnCallByPolicyId(policyId: string): Promise; +} diff --git a/plugins/pagerduty/src/index.ts b/plugins/pagerduty/src/index.ts index b24018887a..58dc39932d 100644 --- a/plugins/pagerduty/src/index.ts +++ b/plugins/pagerduty/src/index.ts @@ -16,5 +16,5 @@ export { plugin } from './plugin'; export { isPluginApplicableToEntity, - PagerDutyServiceCard, -} from './components/PagerDutyServiceCard'; + PagerDutyCard, +} from './components/PagerDutyCard'; diff --git a/plugins/pagerduty/src/plugin.ts b/plugins/pagerduty/src/plugin.ts index 4073551dc8..2e07b5c430 100644 --- a/plugins/pagerduty/src/plugin.ts +++ b/plugins/pagerduty/src/plugin.ts @@ -19,7 +19,7 @@ import { createRouteRef, configApiRef, } from '@backstage/core'; -import { pagerDutyApiRef, PagerDutyClientApi } from './api/pagerDutyClient'; +import { pagerDutyApiRef, PagerDutyClientApi } from './api'; export const rootRouteRef = createRouteRef({ path: '/pagerduty', @@ -34,7 +34,10 @@ export const plugin = createPlugin({ deps: { configApi: configApiRef }, factory: ({ configApi }) => new PagerDutyClientApi({ - token: configApi.getOptionalString('pagerduty.api.token'), + api_url: `${configApi.getString( + 'backend.baseUrl', + )}/api/proxy/pagerduty`, + events_url: 'https://events.pagerduty.com/v2', }), }), ], From e1c23fa4b318a482c4dff49c62a86fc1268ccb38 Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Mon, 16 Nov 2020 16:36:03 +0100 Subject: [PATCH 046/430] add action prop to support buttons --- .../IconLinkVertical/IconLinkVertical.tsx | 26 +++++++++++++++++-- plugins/catalog/src/index.ts | 1 + 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/plugins/catalog/src/components/AboutCard/IconLinkVertical/IconLinkVertical.tsx b/plugins/catalog/src/components/AboutCard/IconLinkVertical/IconLinkVertical.tsx index 580dcff0f5..63df4feaf9 100644 --- a/plugins/catalog/src/components/AboutCard/IconLinkVertical/IconLinkVertical.tsx +++ b/plugins/catalog/src/components/AboutCard/IconLinkVertical/IconLinkVertical.tsx @@ -23,9 +23,10 @@ export type IconLinkVerticalProps = { href?: string; disabled?: boolean; label: string; + action?: React.ReactNode; }; -const useIconStyles = makeStyles({ +const useIconStyles = makeStyles(theme => ({ link: { display: 'grid', justifyItems: 'center', @@ -41,12 +42,16 @@ const useIconStyles = makeStyles({ fontWeight: 600, letterSpacing: 1.2, }, -}); + linkStyle: { + color: theme.palette.secondary.main, + }, +})); export function IconLinkVertical({ icon = , href = '#', disabled = false, + action, ...props }: IconLinkVerticalProps) { const classes = useIconStyles(); @@ -64,6 +69,23 @@ export function IconLinkVertical({ ); } + if (action) { + return ( + + {icon} + {action} + + ); + } + return ( {icon} diff --git a/plugins/catalog/src/index.ts b/plugins/catalog/src/index.ts index 762f4924c0..101680c552 100644 --- a/plugins/catalog/src/index.ts +++ b/plugins/catalog/src/index.ts @@ -23,3 +23,4 @@ export { Router } from './components/Router'; export { useEntity, EntityContext } from './hooks/useEntity'; export { AboutCard } from './components/AboutCard'; export { EntityPageLayout } from './components/EntityPageLayout'; +export { IconLinkVertical } from './components/AboutCard/IconLinkVertical'; From 00186ab37818c2efbe3f4be7e6a782bf47755996 Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Mon, 16 Nov 2020 16:50:58 +0100 Subject: [PATCH 047/430] remove 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 | 169 ------------------ .../src/service/standaloneServer.ts | 62 ------- plugins/pagerduty-backend/src/setupTests.ts | 18 -- 12 files changed, 431 deletions(-) delete mode 100644 packages/backend/src/plugins/pagerduty.ts delete mode 100644 plugins/pagerduty-backend/.eslintrc.js delete mode 100644 plugins/pagerduty-backend/README.md delete mode 100644 plugins/pagerduty-backend/package.json delete mode 100644 plugins/pagerduty-backend/src/index.ts delete mode 100644 plugins/pagerduty-backend/src/run.ts delete mode 100644 plugins/pagerduty-backend/src/service/router.test.ts delete mode 100644 plugins/pagerduty-backend/src/service/router.ts delete mode 100644 plugins/pagerduty-backend/src/service/standaloneServer.ts delete mode 100644 plugins/pagerduty-backend/src/setupTests.ts diff --git a/packages/backend/package.json b/packages/backend/package.json index c31629e82e..ef55b007a7 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -31,7 +31,6 @@ "@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 0ca17c7d7f..18fd44f089 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -46,7 +46,6 @@ 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(); @@ -80,7 +79,6 @@ 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)); @@ -92,7 +90,6 @@ 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 deleted file mode 100644 index 6dc3d8c9ba..0000000000 --- a/packages/backend/src/plugins/pagerduty.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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 deleted file mode 100644 index 16a033dbc6..0000000000 --- a/plugins/pagerduty-backend/.eslintrc.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - extends: [require.resolve('@backstage/cli/config/eslint.backend')], -}; diff --git a/plugins/pagerduty-backend/README.md b/plugins/pagerduty-backend/README.md deleted file mode 100644 index 099a94a85f..0000000000 --- a/plugins/pagerduty-backend/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# 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 deleted file mode 100644 index cad4ba268e..0000000000 --- a/plugins/pagerduty-backend/package.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "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 deleted file mode 100644 index 7612c392a2..0000000000 --- a/plugins/pagerduty-backend/src/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * 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 deleted file mode 100644 index b96989e4b8..0000000000 --- a/plugins/pagerduty-backend/src/run.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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 deleted file mode 100644 index 0aaeafa379..0000000000 --- a/plugins/pagerduty-backend/src/service/router.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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 deleted file mode 100644 index 1fbab9b551..0000000000 --- a/plugins/pagerduty-backend/src/service/router.ts +++ /dev/null @@ -1,169 +0,0 @@ -/* - * 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, 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'; -import { Response } from 'express'; - -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 - 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); - } - return await response.json(); - } catch (error) { - throw new Error(error); - } -} - -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: PagerDutyService[] = []; - - async function getData() { - const result = await request( - `${API_URL}/services?offset=${offset}`, - options, - ); - - 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 < 2 && result.more) { - ++offset; - await getData(); - } - } - - try { - await getData(); - } catch (error) { - throw new Error(error); - } - - return data; -} - -export interface RouterOptions { - logger: Logger; - 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 { - const { logger, config } = options; - 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; - 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); - - useHotCleanup(module, () => clearInterval(intervalId)); - - router.use(express.json()); - - 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-backend/src/service/standaloneServer.ts b/plugins/pagerduty-backend/src/service/standaloneServer.ts deleted file mode 100644 index 73f928bef4..0000000000 --- a/plugins/pagerduty-backend/src/service/standaloneServer.ts +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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 deleted file mode 100644 index a5907fd52f..0000000000 --- a/plugins/pagerduty-backend/src/setupTests.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * 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 e4bec4e17e484a03561e3ff81c161496bcc988b3 Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Tue, 17 Nov 2020 10:01:59 +0100 Subject: [PATCH 048/430] add proxy to config --- app-config.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app-config.yaml b/app-config.yaml index 715d564feb..fbeba50835 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -41,6 +41,12 @@ proxy: X-Api-Key: $env: NEW_RELIC_REST_API_KEY + '/pagerduty': + target: https://api.pagerduty.com + headers: + Authorization: + $env: PAGERDUTY_TOKEN + organization: name: My Company From 2e8c842ad73c5fe79def3daabc85287a41a26038 Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Tue, 17 Nov 2020 11:11:35 +0100 Subject: [PATCH 049/430] update versions --- .../catalog/src/components/AboutCard/index.ts | 1 + plugins/catalog/src/index.ts | 3 +-- plugins/pagerduty/package.json | 16 ++++++++-------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/plugins/catalog/src/components/AboutCard/index.ts b/plugins/catalog/src/components/AboutCard/index.ts index 93765ff942..0491d467f8 100644 --- a/plugins/catalog/src/components/AboutCard/index.ts +++ b/plugins/catalog/src/components/AboutCard/index.ts @@ -15,3 +15,4 @@ */ export { AboutCard } from './AboutCard'; +export { IconLinkVertical } from './IconLinkVertical'; diff --git a/plugins/catalog/src/index.ts b/plugins/catalog/src/index.ts index f0e8b103e6..5949209128 100644 --- a/plugins/catalog/src/index.ts +++ b/plugins/catalog/src/index.ts @@ -15,9 +15,8 @@ */ export * from '@backstage/catalog-client'; -export { AboutCard } from './components/AboutCard'; +export { AboutCard, IconLinkVertical } from './components/AboutCard'; export { EntityPageLayout } from './components/EntityPageLayout'; -export { IconLinkVertical } from './components/AboutCard/IconLinkVertical'; export { Router } from './components/Router'; export { useEntityCompoundName } from './components/useEntityCompoundName'; export { EntityContext, useEntity } from './hooks/useEntity'; diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json index 29518bd858..2fce2ebef3 100644 --- a/plugins/pagerduty/package.json +++ b/plugins/pagerduty/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-pagerduty", - "version": "0.1.1-alpha.25", + "version": "0.2.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,11 +20,11 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.1.1-alpha.25", - "@backstage/theme": "^0.1.1-alpha.25", - "@backstage/catalog-model": "^0.1.1-alpha.25", - "@backstage/test-utils": "^0.1.1-alpha.25", - "@backstage/plugin-catalog": "^0.1.1-alpha.25", + "@backstage/core": "^0.3.0", + "@backstage/theme": "^0.2.1", + "@backstage/catalog-model": "^0.2.0", + "@backstage/test-utils": "^0.1.2", + "@backstage/plugin-catalog": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -34,8 +34,8 @@ "moment": "^2.27.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.25", - "@backstage/dev-utils": "^0.1.1-alpha.25", + "@backstage/cli": "^0.2.0", + "@backstage/dev-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", From 53b1a29968f3845c554e5a5b5d2db485f8986606 Mon Sep 17 00:00:00 2001 From: Mateusz Lewtak Date: Tue, 17 Nov 2020 12:20:27 +0100 Subject: [PATCH 050/430] Feat: bump GitHub Insights Plugin version --- packages/app/package.json | 2 +- yarn.lock | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/app/package.json b/packages/app/package.json index f013200fbb..8dda24fdf1 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -34,7 +34,7 @@ "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@octokit/rest": "^18.0.0", - "@roadiehq/backstage-plugin-github-insights": "^0.2.12", + "@roadiehq/backstage-plugin-github-insights": "^0.2.14", "@roadiehq/backstage-plugin-github-pull-requests": "^0.6.2", "@roadiehq/backstage-plugin-travis-ci": "^0.2.7", "@roadiehq/backstage-plugin-buildkite": "^0.1.2", diff --git a/yarn.lock b/yarn.lock index 10e70f52fb..947b33e89a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3706,10 +3706,10 @@ react-router-dom "6.0.0-beta.0" react-use "^15.3.3" -"@roadiehq/backstage-plugin-github-insights@^0.2.12": - version "0.2.12" - resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-github-insights/-/backstage-plugin-github-insights-0.2.12.tgz#aeda6306769f376cf6b5b9d74268b060a0f4e764" - integrity sha512-f9g5ajVWQkywoYo0zDUZV3g0SPxOTi3MvMuhUvgaZ/XKJr9mBZTe5qHnEYBSHOCB88l0Y2g0qHNdbjXhbFpokQ== +"@roadiehq/backstage-plugin-github-insights@^0.2.14": + version "0.2.14" + resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-github-insights/-/backstage-plugin-github-insights-0.2.14.tgz#2e4bf61495e650bb2b7a1711f97c9c4995215588" + integrity sha512-xhzHAmmTu7op1V7K3ytomGlmzHMs9jnb2Lc2YTrU3ame4WZcrrMCrqi8X9v8B8VHfwMBrwa55tyxRYObXrPr7A== dependencies: "@backstage/catalog-model" "^0.2.0" "@backstage/core" "^0.2.0" @@ -3722,10 +3722,8 @@ history "^5.0.0" react "^16.13.1" react-dom "^16.13.1" - react-markdown "^5.0.0" react-router "^6.0.0-beta.0" react-use "^15.3.3" - remark-gfm "^1.0.0" "@roadiehq/backstage-plugin-github-pull-requests@^0.6.2": version "0.6.2" @@ -19788,7 +19786,7 @@ react-markdown@^4.3.1: unist-util-visit "^1.3.0" xtend "^4.0.1" -react-markdown@^5.0.0, react-markdown@^5.0.2: +react-markdown@^5.0.2: version "5.0.2" resolved "https://registry.npmjs.org/react-markdown/-/react-markdown-5.0.2.tgz#d15a8beb37b4ec34fc23dd892e7755eb7040b8db" integrity sha512-kmkB4JbV7LqkDAjvaKRKtodB3n3Id76/DalaDun1U8FuLB0SenPfvH+jAQ5Pcpo54cACRQc1LB1yXmuuuIVecw== From ea0fe99f82d3fe504cd58bc0cd32db138fe87710 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Tue, 17 Nov 2020 12:40:52 -0500 Subject: [PATCH 051/430] Add tooltips --- plugins/catalog/src/components/AboutCard/AboutCard.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.tsx index 8da67d7493..89630d6f10 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.tsx @@ -119,6 +119,7 @@ export function AboutCard({ entity, variant }: AboutCardProps) { action={ { window.open(codeLink.edithref || '#', '_blank'); }} @@ -133,7 +134,12 @@ export function AboutCard({ entity, variant }: AboutCardProps) { disabled={ !entity.metadata.annotations?.['backstage.io/techdocs-ref'] } - label="View Techdocs" + label="View TechDocs" + title={ + !entity.metadata.annotations?.['backstage.io/techdocs-ref'] + ? 'No TechDocs available' + : '' + } icon={} href={`/docs/${ entity.metadata.namespace || ENTITY_DEFAULT_NAMESPACE @@ -142,6 +148,7 @@ export function AboutCard({ entity, variant }: AboutCardProps) { } href="api" /> From 8b7737d0b8f1dbe2e1f52ba30d9777241243b253 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Tue, 17 Nov 2020 12:44:13 -0500 Subject: [PATCH 052/430] Add changeset --- .changeset/friendly-dodos-remember.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/friendly-dodos-remember.md diff --git a/.changeset/friendly-dodos-remember.md b/.changeset/friendly-dodos-remember.md new file mode 100644 index 0000000000..463d71f806 --- /dev/null +++ b/.changeset/friendly-dodos-remember.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': patch +--- + +Add About Card tooltips From 5efc00c6a6c2b90c9c102c7b9ca93306893d9cbc Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Tue, 17 Nov 2020 13:45:11 -0500 Subject: [PATCH 053/430] Add typescript support for title --- .../components/AboutCard/IconLinkVertical/IconLinkVertical.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/catalog/src/components/AboutCard/IconLinkVertical/IconLinkVertical.tsx b/plugins/catalog/src/components/AboutCard/IconLinkVertical/IconLinkVertical.tsx index c054bc0d4e..dd267dde35 100644 --- a/plugins/catalog/src/components/AboutCard/IconLinkVertical/IconLinkVertical.tsx +++ b/plugins/catalog/src/components/AboutCard/IconLinkVertical/IconLinkVertical.tsx @@ -23,6 +23,7 @@ export type IconLinkVerticalProps = { icon?: React.ReactNode; href?: string; disabled?: boolean; + title?: string; label: string; }; @@ -57,6 +58,7 @@ export function IconLinkVertical({ {icon} From b3a5e3b150f85517b6127959ddcedfd28794741c Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Tue, 17 Nov 2020 13:50:50 -0500 Subject: [PATCH 054/430] Add tooltips plural --- .github/styles/vocab.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt index 3c5fa34adc..043b83a51f 100644 --- a/.github/styles/vocab.txt +++ b/.github/styles/vocab.txt @@ -192,6 +192,7 @@ tolerations Tolerations toolsets tooltip +tooltips touchpoints ui upvote From 3efd03c0e8304d8d58b79e0ed27346f645c7bf5e Mon Sep 17 00:00:00 2001 From: Andrew Thauer <6507159+andrewthauer@users.noreply.github.com> Date: Tue, 17 Nov 2020 22:11:16 -0500 Subject: [PATCH 055/430] chore: remove obsolete circleci proxy config --- .changeset/loud-pets-clap.md | 5 +++++ packages/app/package.json | 11 +---------- 2 files changed, 6 insertions(+), 10 deletions(-) create mode 100644 .changeset/loud-pets-clap.md diff --git a/.changeset/loud-pets-clap.md b/.changeset/loud-pets-clap.md new file mode 100644 index 0000000000..0d50c066f9 --- /dev/null +++ b/.changeset/loud-pets-clap.md @@ -0,0 +1,5 @@ +--- +'example-app': patch +--- + +Removed obsolete CircleCI proxy config from example-app diff --git a/packages/app/package.json b/packages/app/package.json index f013200fbb..1686306232 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -86,14 +86,5 @@ "last 1 safari version" ] }, - "license": "Apache-2.0", - "proxy": { - "/circleci/api": { - "target": "https://circleci.com/api/v1.1", - "changeOrigin": true, - "pathRewrite": { - "^/circleci/api/": "/" - } - } - } + "license": "Apache-2.0" } From 22b0500d76118aa07aca589a57d627f189facf7e Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Wed, 18 Nov 2020 09:51:33 +0100 Subject: [PATCH 056/430] small fixes --- .../app/src/components/catalog/EntityPage.tsx | 3 +- plugins/pagerduty/src/api/client.ts | 10 +-- plugins/pagerduty/src/api/types.ts | 10 +++ plugins/pagerduty/src/assets/pd.svg | 13 ---- .../components/Escalation/EscalationUser.tsx | 4 +- .../components/Incident/IncidentListItem.tsx | 2 +- .../TriggerButton/TriggerButton.tsx | 2 +- .../TriggerDialog/TriggerDialog.tsx | 65 ++++++++++++------- 8 files changed, 60 insertions(+), 49 deletions(-) delete mode 100644 plugins/pagerduty/src/assets/pd.svg diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index 17231abb9a..6099b6ffe0 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -70,7 +70,8 @@ import { isPluginApplicableToEntity as isPagerDutyAvailable, PagerDutyCard, } from '@backstage/plugin-pagerduty'; -import { Router as BuildKiteRouter, +import { + Router as BuildKiteRouter, isPluginApplicableToEntity as isBuildKiteAvailable, } from '@roadiehq/backstage-plugin-buildkite'; diff --git a/plugins/pagerduty/src/api/client.ts b/plugins/pagerduty/src/api/client.ts index 985bb25e5a..c9d40a1694 100644 --- a/plugins/pagerduty/src/api/client.ts +++ b/plugins/pagerduty/src/api/client.ts @@ -27,13 +27,9 @@ import { } from '../components/types'; import { PagerDutyClient } from './types'; -export class UnauthorizedError extends Error { - constructor() { - super(); - } -} +export class UnauthorizedError extends Error {} -export const pagerDutyApiRef = createApiRef({ +export const pagerDutyApiRef = createApiRef({ id: 'plugin.pagerduty.api', description: 'Used to fetch data from PagerDuty API', }); @@ -65,7 +61,7 @@ export class PagerDutyClientApi implements PagerDutyClient { return oncalls; } - triggerPagerDutyAlarm( + triggerAlarm( integrationKey: string, source: string, description: string, diff --git a/plugins/pagerduty/src/api/types.ts b/plugins/pagerduty/src/api/types.ts index cb9d7fc5bc..c07834a213 100644 --- a/plugins/pagerduty/src/api/types.ts +++ b/plugins/pagerduty/src/api/types.ts @@ -34,4 +34,14 @@ export interface PagerDutyClient { * */ getOnCallByPolicyId(policyId: string): Promise; + + /** + * Triggers an incident to whoever is on-call. + */ + triggerAlarm( + integrationKey: string, + source: string, + description: string, + userName: string, + ): Promise; } diff --git a/plugins/pagerduty/src/assets/pd.svg b/plugins/pagerduty/src/assets/pd.svg deleted file mode 100644 index 237ffa2011..0000000000 --- a/plugins/pagerduty/src/assets/pd.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - pd_icon - Created with Sketch. - - - - - - - - diff --git a/plugins/pagerduty/src/components/Escalation/EscalationUser.tsx b/plugins/pagerduty/src/components/Escalation/EscalationUser.tsx index e20a5f7567..adc8d4e6d7 100644 --- a/plugins/pagerduty/src/components/Escalation/EscalationUser.tsx +++ b/plugins/pagerduty/src/components/Escalation/EscalationUser.tsx @@ -52,12 +52,12 @@ export const EscalationUser = ({ user }: { user: User }) => { secondary={user.email} /> - + - + { } /> - + { setDescription(event.target.value); }; - const [{ value, loading, error }, triggerAlarm] = useAsyncFn( + const [{ value, loading, error }, handleTriggerAlarm] = useAsyncFn( async (desc: string) => { - return await api.triggerPagerDutyAlarm( + return await api.triggerAlarm( integrationKey, window.location.toString(), desc, @@ -82,34 +91,41 @@ export const TriggerDialog = ({ name, integrationKey, onClose }: Props) => { }, [value, error, alertApi, onClose, userId]); return ( - + - This action will send PagerDuty alarms and notifications to on-call - people responsible for {name}. + This action will trigger an incident 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. -

-

+ + {/* "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. You can find information about the owner of + this entity in the "About" card. 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. -

+ possible. Your signed in user and a reference to the current page will + automatically be amended to the alarm so that the receiver can reach + out to you if necessary. +
@@ -119,15 +135,16 @@ export const TriggerDialog = ({ name, integrationKey, onClose }: Props) => { id="trigger" color="secondary" disabled={!description || loading} - onClick={() => triggerAlarm(description)} + variant="contained" + onClick={() => handleTriggerAlarm(description)} + endIcon={loading && } > - Trigger + Trigger Incident - - {loading && }
); }; From 3457c40f81dc178a1e401acbb703314fc96e501a Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 18 Nov 2020 10:24:15 +0100 Subject: [PATCH 057/430] Apply suggestions from code review Co-authored-by: Adam Harvey --- docs/overview/stability-index.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/overview/stability-index.md b/docs/overview/stability-index.md index 96ff33542f..1ec90e106d 100644 --- a/docs/overview/stability-index.md +++ b/docs/overview/stability-index.md @@ -349,12 +349,15 @@ Stability: `0` ### [`kubernetes`](https://github.com/backstage/backstage/tree/master/plugins/kubernetes/) The frontend component of the Kubernetes plugin, used to browse and visualize -Kubernetes resources. Stability: `1`. +Kubernetes resources. + +Stability: `1`. ### [`kubernetes-backend`](https://github.com/backstage/backstage/tree/master/plugins/kubernetes-backend/) The backend component of the Kubernetes plugin, used to fetch Kubernetes resources from clusters and associate them with entities in the Catalog. + Stability: `1`. ### [`lighthouse`](https://github.com/backstage/backstage/tree/master/plugins/lighthouse/) @@ -367,7 +370,9 @@ Stability: `0` ### [`newrelic`](https://github.com/backstage/backstage/tree/master/plugins/newrelic/) Observability platform built to help engineers create and monitor their -software. Stability: `0` +software. + +Stability: `0` ### [`proxy-backend`](https://github.com/backstage/backstage/tree/master/plugins/proxy-backend/) From 102f0b394687a2464ac72e2bf87047f41ca9e2bd Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 18 Nov 2020 11:28:17 +0100 Subject: [PATCH 058/430] docs/stability-index: trim down list of plugins but open up for contributions + set techdocs-backend to 0 --- .github/styles/vocab.txt | 1 + docs/overview/stability-index.md | 101 ++----------------------------- 2 files changed, 6 insertions(+), 96 deletions(-) diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt index bd89163f1f..b9d9e53dfa 100644 --- a/.github/styles/vocab.txt +++ b/.github/styles/vocab.txt @@ -196,6 +196,7 @@ toolsets tooltip touchpoints ui +untracked upvote url utils diff --git a/docs/overview/stability-index.md b/docs/overview/stability-index.md index 1ec90e106d..f5a769c601 100644 --- a/docs/overview/stability-index.md +++ b/docs/overview/stability-index.md @@ -225,6 +225,10 @@ Many backend plugins are split into "REST API" and "TypeScript Interface" sections. The "TypeScript Interface" refers to the API used to integrate the plugin into the backend. +Any plugin that is not listed below is untracked and can generally be considered +unstable with a score of `0`. Open a Pull Request if you want your plugin to be +added! + ### [`api-docs`](https://github.com/backstage/backstage/tree/master/plugins/api-docs/) Components to discover and display API entities as an extension to the catalog @@ -280,25 +284,6 @@ Provides the catalog schema and resolvers for the graphql backend. Stability: `0`. Under heavy development and subject to change. -### [`circleci`](https://github.com/backstage/backstage/tree/master/plugins/circleci/) - -Automate your development process with CI hosted in the cloud or on a private -server. - -Stability: `0` - -### [`cloudbuild`](https://github.com/backstage/backstage/tree/master/plugins/cloudbuild/) - -Visualize Google Cloud Build flows. - -Stability: `0` - -### [`cost-insights`](https://github.com/backstage/backstage/tree/master/plugins/cost-insights/) - -Visualize, understand and optimize your team's cloud costs. - -Stability: `0` - ### [`explore`](https://github.com/backstage/backstage/tree/master/plugins/explore/) A frontend plugin that introduces the concept of exploring internal and external @@ -306,27 +291,6 @@ tooling in an organization. Stability: `0`. Only an example at the moment and not customizable. -### [`gcp-projects`](https://github.com/backstage/backstage/tree/master/plugins/gcp-projects/) - -Create, list and manage your Google Cloud Projects. - -Stability: `0` - -### [`github-actions`](https://github.com/backstage/backstage/tree/master/plugins/github-actions/) - -GitHub Actions makes it easy to automate all your software workflows, now with -world-class CI/CD. Build, test, and deploy your code right from GitHub. - -Stability: `0` - -### [`gitops-profiles`](https://github.com/backstage/backstage/tree/master/plugins/gitops-profiles/) - -A frontend plugin with a separate backend that can be used to provision EKS -clusters. - -Stability: `0`. This is an early plugin that now has quite a lot of overlap with -the scaffolder plugin. - ### [`graphiql`](https://github.com/backstage/backstage/tree/master/plugins/graphiql/) Integrates GraphiQL as a tool to browse GraphQL API endpoints inside Backstage. @@ -339,13 +303,6 @@ A backend plugin that provides Stability: `0`. Under heavy development and subject to change. -### [`jenkins`](https://github.com/backstage/backstage/tree/master/plugins/jenkins/) - -A plugin that visualizes Jenkins workflows for entities. Jenkins offers a simple -way to set up a continuous integration and continuous delivery environment. - -Stability: `0` - ### [`kubernetes`](https://github.com/backstage/backstage/tree/master/plugins/kubernetes/) The frontend component of the Kubernetes plugin, used to browse and visualize @@ -360,20 +317,6 @@ resources from clusters and associate them with entities in the Catalog. Stability: `1`. -### [`lighthouse`](https://github.com/backstage/backstage/tree/master/plugins/lighthouse/) - -Google's Lighthouse tool is a great resource for benchmarking and improving the -accessibility, performance, SEO, and best practices of your website. - -Stability: `0` - -### [`newrelic`](https://github.com/backstage/backstage/tree/master/plugins/newrelic/) - -Observability platform built to help engineers create and monitor their -software. - -Stability: `0` - ### [`proxy-backend`](https://github.com/backstage/backstage/tree/master/plugins/proxy-backend/) A backend plugin used to set up proxying to other endpoints based on static @@ -389,20 +332,6 @@ catalog. Stability: `0`. This plugin is likely to be replaced by a generic entity import plugin instead. -### [`rollbar`](https://github.com/backstage/backstage/tree/master/plugins/rollbar/) - -The frontend component of the rollbar plugin, which can be used to view Rollbar -errors for your services in Backstage. - -Stability: `0` - -### [`rollbar-backend`](https://github.com/backstage/backstage/tree/master/plugins/rollbar-backend/) - -The backend component of the rollbar plugin, which can be used to view Rollbar -errors for your services in Backstage. - -Stability: `0` - ### [`scaffolder`](https://github.com/backstage/backstage/tree/master/plugins/scaffolder/) The frontend scaffolder plugin where one can browse templates and initiate @@ -418,26 +347,6 @@ the catalog. Stability: `1`. There is planned work to rework the scaffolder in https://github.com/backstage/backstage/issues/2771. -### [`sentry`](https://github.com/backstage/backstage/tree/master/plugins/sentry/) - -The frontend component of the sentry plugin, which can be used to view Sentry -issues in Backstage. - -Stability: `0` - -### [`sentry-backend`](https://github.com/backstage/backstage/tree/master/plugins/sentry-backend/) - -The backend component of the sentry plugin, which can be used to view Sentry -issues in Backstage. - -Stability: `0` - -### [`sonarqube`](https://github.com/backstage/backstage/tree/master/plugins/sonarqube/) - -Components to display code quality metrics from SonarCloud and SonarQube. - -Stability: `0` - ### [`tech-radar`](https://github.com/backstage/backstage/tree/master/plugins/tech-radar/) Visualize the your company's official guidelines of different areas of software @@ -457,7 +366,7 @@ Stability: `1` The backend component of the TechDocs plugin, used to transform and serve TechDocs. -Stability: `1` +Stability: `0` ### [`user-settings`](https://github.com/backstage/backstage/tree/master/plugins/user-settings/) From acc226dd6335066db22ad480762a0ad8ed937ce1 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 18 Nov 2020 17:20:13 +0100 Subject: [PATCH 059/430] Update docs/overview/stability-index.md Co-authored-by: Adam Harvey --- docs/overview/stability-index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/overview/stability-index.md b/docs/overview/stability-index.md index f5a769c601..416cdcdd4a 100644 --- a/docs/overview/stability-index.md +++ b/docs/overview/stability-index.md @@ -41,14 +41,14 @@ TL;DR: ### [`example-app`](https://github.com/backstage/backstage/tree/master/packages/app/) -This is the `packages/app` package, and it's serves as an example as well as +This is the `packages/app` package, and it serves as an example as well as utility for local development in the main Backstage repo. Stability: `N/A` ### [`example-backend`](https://github.com/backstage/backstage/tree/master/packages/backend/) -This is the `packages/backend` package, and it's serves as an example as well as +This is the `packages/backend` package, and it serves as an example as well as utility for local development in the main Backstage repo. Stability: `N/A` From 9d792a435b09a38cc4a4ea6437f7ac5219af50c4 Mon Sep 17 00:00:00 2001 From: Ryan Vazquez Date: Wed, 18 Nov 2020 15:05:21 -0500 Subject: [PATCH 060/430] pretty print subdollar values for sku costs --- .../components/ProductInsightsCard/ProductEntityDialog.tsx | 4 ++-- plugins/cost-insights/src/utils/formatters.ts | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/plugins/cost-insights/src/components/ProductInsightsCard/ProductEntityDialog.tsx b/plugins/cost-insights/src/components/ProductInsightsCard/ProductEntityDialog.tsx index 764a18d305..dfa33d15f6 100644 --- a/plugins/cost-insights/src/components/ProductInsightsCard/ProductEntityDialog.tsx +++ b/plugins/cost-insights/src/components/ProductInsightsCard/ProductEntityDialog.tsx @@ -20,7 +20,7 @@ import { Table, TableColumn } from '@backstage/core'; import { Dialog, IconButton, Typography } from '@material-ui/core'; import { default as CloseButton } from '@material-ui/icons/Close'; import { CostGrowthIndicator } from '../CostGrowth'; -import { currencyFormatter, formatPercent } from '../../utils/formatters'; +import { costFormatter, formatPercent } from '../../utils/formatters'; import { useEntityDialogStyles as useStyles } from '../../utils/styles'; import { BarChartOptions, Entity } from '../../types'; @@ -38,7 +38,7 @@ function createRenderer(col: keyof RowData, classes: Record) { case 'current': return ( - {currencyFormatter.format(row[col])} + {costFormatter.format(row[col])} ); case 'ratio': diff --git a/plugins/cost-insights/src/utils/formatters.ts b/plugins/cost-insights/src/utils/formatters.ts index e81fc1f7c9..42505e7072 100644 --- a/plugins/cost-insights/src/utils/formatters.ts +++ b/plugins/cost-insights/src/utils/formatters.ts @@ -24,6 +24,11 @@ export type Period = { periodEnd: string; }; +export const costFormatter = new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD', +}); + export const currencyFormatter = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', From 86017bc2fbc5d34f540b6530a864ba699a897ab9 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Wed, 18 Nov 2020 21:19:06 +0100 Subject: [PATCH 061/430] docs: Add catalog-client to project structure --- docs/support/project-structure.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/support/project-structure.md b/docs/support/project-structure.md index 034fba4cb8..deb8c47777 100644 --- a/docs/support/project-structure.md +++ b/docs/support/project-structure.md @@ -89,6 +89,10 @@ are separated out into their own folder, see further down. There are no "core" packages in the backend. Instead we have `backend-common` which contains helper middleware and other utils. +- [`catalog-client`](https://github.com/backstage/backstage/tree/master/packages/catalog-client) - + A client to communicate with Backstage software catalog. Recommended for backend plugins, compatible + but not recommended for frontend plugins (use `@backstage/plugin-catalog` package instead). + - [`catalog-model/`](https://github.com/backstage/backstage/tree/master/packages/catalog-model) - You can consider this to be a library for working with the catalog of sorts. It contains the definition of an From c645ea174ac2e1a624c90e51bca6f57da3de9635 Mon Sep 17 00:00:00 2001 From: Ryan Vazquez Date: Wed, 18 Nov 2020 15:21:09 -0500 Subject: [PATCH 062/430] widen first column of entity dialog table --- .../src/components/ProductInsightsCard/ProductEntityDialog.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/cost-insights/src/components/ProductInsightsCard/ProductEntityDialog.tsx b/plugins/cost-insights/src/components/ProductInsightsCard/ProductEntityDialog.tsx index dfa33d15f6..4cccd638cb 100644 --- a/plugins/cost-insights/src/components/ProductInsightsCard/ProductEntityDialog.tsx +++ b/plugins/cost-insights/src/components/ProductInsightsCard/ProductEntityDialog.tsx @@ -122,6 +122,7 @@ export const ProductEntityDialog = ({ title: SKU, render: createRenderer('sku', classes), customSort: createSorter('sku'), + width: '33.33%', }, { field: 'previous', From f360395d00eaf6d0c9318fa1ed9e39d391d024f7 Mon Sep 17 00:00:00 2001 From: Ryan Vazquez Date: Wed, 18 Nov 2020 15:41:46 -0500 Subject: [PATCH 063/430] changeset --- .changeset/cost-insights-wet-plants-glow.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/cost-insights-wet-plants-glow.md diff --git a/.changeset/cost-insights-wet-plants-glow.md b/.changeset/cost-insights-wet-plants-glow.md new file mode 100644 index 0000000000..3d2be289d4 --- /dev/null +++ b/.changeset/cost-insights-wet-plants-glow.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-cost-insights': patch +--- + +UI improvements: Increase width of first column in product entity dialog table +UI improvement: Display full cost amount in product entity dialog table From 4787cd04deaadcc531ce27b7ce686b3940bb4b47 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Wed, 18 Nov 2020 22:53:19 +0100 Subject: [PATCH 064/430] docs: Rephrase catalog-client and run prettier --- docs/support/project-structure.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/support/project-structure.md b/docs/support/project-structure.md index deb8c47777..c25e087e57 100644 --- a/docs/support/project-structure.md +++ b/docs/support/project-structure.md @@ -90,8 +90,10 @@ are separated out into their own folder, see further down. which contains helper middleware and other utils. - [`catalog-client`](https://github.com/backstage/backstage/tree/master/packages/catalog-client) - - A client to communicate with Backstage software catalog. Recommended for backend plugins, compatible - but not recommended for frontend plugins (use `@backstage/plugin-catalog` package instead). + An isomorphic client to interact with the Software Catalog. Backend plugins + can use the package directly. Frontend plugins can use the client by using + `@backstage/plugin-catalog` in combination with `useApi` and the + `catalogApiRef`. - [`catalog-model/`](https://github.com/backstage/backstage/tree/master/packages/catalog-model) - You can consider this to be a library for working with the catalog of sorts. From c6332546e6d8445db5d8dbccabfb73d2cd809035 Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Wed, 18 Nov 2020 23:23:54 +0100 Subject: [PATCH 065/430] Apply Code review fixes --- plugins/catalog-backend/src/service/router.ts | 2 +- plugins/catalog-import/package.json | 12 +++++------ .../src/api/CatalogImportApi.ts | 1 - .../src/api/CatalogImportClient.ts | 20 ++++++++++++++----- plugins/catalog-import/src/plugin.ts | 6 ++++-- .../catalog-import/src/util/useGithubRepos.ts | 6 +----- 6 files changed, 27 insertions(+), 20 deletions(-) diff --git a/plugins/catalog-backend/src/service/router.ts b/plugins/catalog-backend/src/service/router.ts index fd9062bf94..e850792936 100644 --- a/plugins/catalog-backend/src/service/router.ts +++ b/plugins/catalog-backend/src/service/router.ts @@ -144,7 +144,7 @@ export async function createRouter( router.post('/analyze-location', async (req, res) => { const input = await validateRequestBody(req, analyzeLocationSchema); const output = await locationAnalyzer.generateConfig(input); - res.status(201).send(output); + res.status(200).send(output); }); } diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index d6a08a35c7..ee157235ca 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-import", - "version": "0.1.1-alpha.26", + "version": "0.2.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,11 +21,11 @@ "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", + "@backstage/catalog-model": "^0.2.0", + "@backstage/core": "^0.2.0", + "@backstage/plugin-catalog": "^0.2.0", + "@backstage/plugin-catalog-backend": "^0.2.0", + "@backstage/theme": "^0.2.0", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", diff --git a/plugins/catalog-import/src/api/CatalogImportApi.ts b/plugins/catalog-import/src/api/CatalogImportApi.ts index 7ecb4a543b..49012764e2 100644 --- a/plugins/catalog-import/src/api/CatalogImportApi.ts +++ b/plugins/catalog-import/src/api/CatalogImportApi.ts @@ -24,7 +24,6 @@ export const catalogImportApiRef = createApiRef({ export interface CatalogImportApi { submitPrToRepo(options: { - oAuthToken: string; owner: string; repo: string; fileContent: string; diff --git a/plugins/catalog-import/src/api/CatalogImportClient.ts b/plugins/catalog-import/src/api/CatalogImportClient.ts index cef4b2da0e..f8b4e5d17e 100644 --- a/plugins/catalog-import/src/api/CatalogImportClient.ts +++ b/plugins/catalog-import/src/api/CatalogImportClient.ts @@ -15,16 +15,26 @@ */ import { Octokit } from '@octokit/rest'; -import { DiscoveryApi } from '@backstage/core'; +import { + DiscoveryApi, + githubAuthApiRef, + OAuthApi, + useApi, +} from '@backstage/core'; import { CatalogImportApi } from './CatalogImportApi'; import { AnalyzeLocationResponse } from '@backstage/plugin-catalog-backend'; import { PartialEntity } from '../util/types'; export class CatalogImportClient implements CatalogImportApi { private readonly discoveryApi: DiscoveryApi; + private readonly githubAuthApi: OAuthApi; - constructor(options: { discoveryApi: DiscoveryApi }) { + constructor(options: { + discoveryApi: DiscoveryApi; + githubAuthApi: OAuthApi; + }) { this.discoveryApi = options.discoveryApi; + this.githubAuthApi = options.githubAuthApi; } async generateEntityDefinitions({ @@ -83,18 +93,18 @@ export class CatalogImportClient implements CatalogImportApi { } async submitPrToRepo({ - oAuthToken, owner, repo, fileContent, }: { - oAuthToken: string; owner: string; repo: string; fileContent: string; }): Promise<{ link: string; location: string }> { + const token = await this.githubAuthApi.getAccessToken(['repo']); + const octo = new Octokit({ - auth: oAuthToken, + auth: token, }); const branchName = 'backstage-integration'; diff --git a/plugins/catalog-import/src/plugin.ts b/plugins/catalog-import/src/plugin.ts index caac764513..3ce4918553 100644 --- a/plugins/catalog-import/src/plugin.ts +++ b/plugins/catalog-import/src/plugin.ts @@ -19,6 +19,7 @@ import { createPlugin, createRouteRef, discoveryApiRef, + githubAuthApiRef, } from '@backstage/core'; import { catalogImportApiRef } from './api/CatalogImportApi'; import { CatalogImportClient } from './api/CatalogImportClient'; @@ -33,8 +34,9 @@ export const plugin = createPlugin({ apis: [ createApiFactory({ api: catalogImportApiRef, - deps: { discoveryApi: discoveryApiRef }, - factory: ({ discoveryApi }) => new CatalogImportClient({ discoveryApi }), + deps: { discoveryApi: discoveryApiRef, githubAuthApi: githubAuthApiRef }, + factory: ({ discoveryApi, githubAuthApi }) => + new CatalogImportClient({ discoveryApi, githubAuthApi }), }), ], }); diff --git a/plugins/catalog-import/src/util/useGithubRepos.ts b/plugins/catalog-import/src/util/useGithubRepos.ts index 84c594c913..a18edc2777 100644 --- a/plugins/catalog-import/src/util/useGithubRepos.ts +++ b/plugins/catalog-import/src/util/useGithubRepos.ts @@ -15,21 +15,17 @@ */ import * as YAML from 'yaml'; -import { useApi, githubAuthApiRef } from '@backstage/core'; +import { useApi } from '@backstage/core'; import { catalogImportApiRef } from '../api/CatalogImportApi'; import { ConfigSpec } from '../components/ImportComponentPage'; export function useGithubRepos() { const api = useApi(catalogImportApiRef); - const auth = useApi(githubAuthApiRef); const submitPrToRepo = async (selectedRepo: ConfigSpec) => { - const token = await auth.getAccessToken(['repo']); - const [ownerName, repoName] = selectedRepo.location.split('/').slice(-2); const submitPRResponse = await api .submitPrToRepo({ - oAuthToken: token, owner: ownerName, repo: repoName, fileContent: selectedRepo.config From 7d7abd50c8befe18f86dab1ba5cb6982539ca5aa Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 18 Nov 2020 19:45:32 +0100 Subject: [PATCH 066/430] create-app: add app-backend plugin to template --- .changeset/add-app-backend.md | 104 ++++++++++++++++++ packages/create-app/package.json | 1 + packages/create-app/src/lib/versions.ts | 2 + .../default-app/app-config.production.yaml | 8 ++ .../templates/default-app/package.json.hbs | 1 + .../default-app/packages/backend/Dockerfile | 2 +- .../packages/backend/package.json.hbs | 4 +- .../default-app/packages/backend/src/index.ts | 5 +- .../packages/backend/src/plugins/app.ts | 13 +++ 9 files changed, 137 insertions(+), 3 deletions(-) create mode 100644 .changeset/add-app-backend.md create mode 100644 packages/create-app/templates/default-app/app-config.production.yaml create mode 100644 packages/create-app/templates/default-app/packages/backend/src/plugins/app.ts diff --git a/.changeset/add-app-backend.md b/.changeset/add-app-backend.md new file mode 100644 index 0000000000..e44a56c885 --- /dev/null +++ b/.changeset/add-app-backend.md @@ -0,0 +1,104 @@ +--- +'@backstage/create-app': patch +--- + +Add `app-backend` as a backend plugin, and make a single docker build of the backend the default way to deploy backstage. + +Note that the `app-backend` currently only is a solution for deployments of the app, it's not a dev server and is not intended for local development. + +## Template changes + +As a part of installing the `app-backend` plugin, the below changes where made. The changes are grouped into two steps, installing the plugin, and updating the Docker build and configuration. + +### Installing the `app-backend` plugin in the backend + +First, install the `@backstage/plugin-app-backend` plugin package in your backend. These changes where made for `v0.3.0` of the plugin, and the installation process might change in the future. Run the following from the root of the repo: + +```bash +cd packages/backend +yarn add @backstage/plugin-app-backend +``` + +For the `app-backend` to get access to the static content in the frontend we also need to add the local `app` package as a dependency. Add the following to your `"dependencies"` in `packages/backend/package.json`, assuming your app package is still named `app` and on version `0.0.0`: + +```json +"app": "0.0.0", +``` + +Don't worry, this will not cause your entire frontend dependency tree to be added to the app, just double check that `packages/app/package.json` has a `"bundled": true` field at top-level. This signals to the backend build process that the package is bundled and that no transitive dependencies should be included. + +Next, create `packages/backend/src/plugins/app.ts` with the following: + +```ts +import { createRouter } from '@backstage/plugin-app-backend'; +import { PluginEnvironment } from '../types'; + +export default async function createPlugin({ + logger, + config, +}: PluginEnvironment) { + return await createRouter({ + logger, + config, + appPackageName: 'app', + }); +} +``` + +In `packages/backend/src/index.ts`, make the following changes: + +Add an import for the newly created plugin setup file: + +```ts +import app from './plugins/app'; +``` + +Setup the following plugin env. + +```ts +const appEnv = useHotMemoize(module, () => createEnv('app')); +``` + +Change service builder setup to include the `app` plugin as follows. Note that the `app` plugin is not installed on the `/api` route with most other plugins. + +```ts +const service = createServiceBuilder(module) + .loadConfig(config) + .addRouter('/api', apiRouter) + .addRouter('', await app(appEnv)); +``` + +You should now have the `app-backend` plugin installed in your backend, ready to serve the frontend bundle! + +### Docker build setup + +Since the backend image is now the only one needed for a simple Backstage deployment, the image tag name in the `build-image` script inside `packages/backend/package.json` was changed to the following: + +```json +"build-image": "backstage-cli backend:build-image --build --tag backstage", +``` + +For convenience, a `build-image` script was also added to the root `package.json` with the following: + +```json +"build-image": "yarn workspace backend build-image", +``` + +In the root of the repo, a new `app-config.production.yaml` file was added. This is used to set the appropriate `app.baseUrl` now that the frontend is served directly by the backend in the production deployment. It has the following contents: + +```yaml +app: + # Should be the same as backend.baseUrl when using the `app-backend` plugin + baseUrl: http://localhost:7000 + +backend: + baseUrl: http://localhost:7000 + listen: + port: 7000 +``` + +In order to load in the new configuration at runtime, the command in the `Dockerfile` at the repo root was changed to the following: + +```dockerfile +CMD ["node", "packages/backend", "--config", "app-config.yaml", "--config", "app-config.production.yaml"] +``` diff --git a/packages/create-app/package.json b/packages/create-app/package.json index bd1d8d1bd1..55cdcb1a2a 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -43,6 +43,7 @@ "@backstage/config": "^0.1.1", "@backstage/core": "^0.3.0", "@backstage/plugin-api-docs": "^0.2.1", + "@backstage/plugin-app-backend": "^0.2.0", "@backstage/plugin-auth-backend": "^0.2.1", "@backstage/plugin-catalog": "^0.2.1", "@backstage/plugin-catalog-backend": "^0.2.0", diff --git a/packages/create-app/src/lib/versions.ts b/packages/create-app/src/lib/versions.ts index 196e0004c1..84f015509c 100644 --- a/packages/create-app/src/lib/versions.ts +++ b/packages/create-app/src/lib/versions.ts @@ -35,6 +35,7 @@ import { version as cli } from '@backstage/cli/package.json'; import { version as config } from '@backstage/config/package.json'; import { version as core } from '@backstage/core/package.json'; import { version as pluginApiDocs } from '@backstage/plugin-api-docs/package.json'; +import { version as pluginAppBackend } from '@backstage/plugin-app-backend/package.json'; import { version as pluginAuthBackend } from '@backstage/plugin-auth-backend/package.json'; import { version as pluginCatalog } from '@backstage/plugin-catalog/package.json'; import { version as pluginCatalogBackend } from '@backstage/plugin-catalog-backend/package.json'; @@ -61,6 +62,7 @@ export const packageVersions = { '@backstage/config': config, '@backstage/core': core, '@backstage/plugin-api-docs': pluginApiDocs, + '@backstage/plugin-app-backend': pluginAppBackend, '@backstage/plugin-auth-backend': pluginAuthBackend, '@backstage/plugin-catalog': pluginCatalog, '@backstage/plugin-catalog-backend': pluginCatalogBackend, diff --git a/packages/create-app/templates/default-app/app-config.production.yaml b/packages/create-app/templates/default-app/app-config.production.yaml new file mode 100644 index 0000000000..92f4574fd1 --- /dev/null +++ b/packages/create-app/templates/default-app/app-config.production.yaml @@ -0,0 +1,8 @@ +app: + # Should be the same as backend.baseUrl when using the `app-backend` plugin + baseUrl: http://localhost:7000 + +backend: + baseUrl: http://localhost:7000 + listen: + port: 7000 diff --git a/packages/create-app/templates/default-app/package.json.hbs b/packages/create-app/templates/default-app/package.json.hbs index b9fb0f3744..0116287a46 100644 --- a/packages/create-app/templates/default-app/package.json.hbs +++ b/packages/create-app/templates/default-app/package.json.hbs @@ -8,6 +8,7 @@ "scripts": { "start": "yarn workspace app start", "build": "lerna run build", + "build-image": "yarn workspace backend build-image", "tsc": "tsc", "tsc:full": "tsc --skipLibCheck false --incremental false", "clean": "backstage-cli clean && lerna run clean", diff --git a/packages/create-app/templates/default-app/packages/backend/Dockerfile b/packages/create-app/templates/default-app/packages/backend/Dockerfile index 93929ceb86..0fcd23f0e1 100644 --- a/packages/create-app/templates/default-app/packages/backend/Dockerfile +++ b/packages/create-app/templates/default-app/packages/backend/Dockerfile @@ -13,4 +13,4 @@ RUN yarn install --frozen-lockfile --production # Do not use this Dockerfile outside of that command, as it will copy in the source code instead. COPY . . -CMD ["node", "packages/backend"] +CMD ["node", "packages/backend", "--config", "app-config.yaml", "--config", "app-config.production.yaml"] diff --git a/packages/create-app/templates/default-app/packages/backend/package.json.hbs b/packages/create-app/templates/default-app/packages/backend/package.json.hbs index df9ac28739..179d604670 100644 --- a/packages/create-app/templates/default-app/packages/backend/package.json.hbs +++ b/packages/create-app/templates/default-app/packages/backend/package.json.hbs @@ -9,7 +9,7 @@ }, "scripts": { "build": "backstage-cli backend:build", - "build-image": "backstage-cli backend:build-image --build --tag example-backend", + "build-image": "backstage-cli backend:build-image --build --tag backstage", "start": "backstage-cli backend:dev", "lint": "backstage-cli lint", "test": "backstage-cli test", @@ -17,9 +17,11 @@ "migrate:create": "knex migrate:make -x ts" }, "dependencies": { + "app": "0.0.0", "@backstage/backend-common": "^{{version '@backstage/backend-common'}}", "@backstage/catalog-model": "^{{version '@backstage/catalog-model'}}", "@backstage/config": "^{{version '@backstage/config'}}", + "@backstage/plugin-app-backend": "^{{version '@backstage/plugin-app-backend'}}", "@backstage/plugin-auth-backend": "^{{version '@backstage/plugin-auth-backend'}}", "@backstage/plugin-catalog-backend": "^{{version '@backstage/plugin-catalog-backend'}}", "@backstage/plugin-proxy-backend": "^{{version '@backstage/plugin-proxy-backend'}}", diff --git a/packages/create-app/templates/default-app/packages/backend/src/index.ts b/packages/create-app/templates/default-app/packages/backend/src/index.ts index 51de1ad6b3..80dc6230a6 100644 --- a/packages/create-app/templates/default-app/packages/backend/src/index.ts +++ b/packages/create-app/templates/default-app/packages/backend/src/index.ts @@ -18,6 +18,7 @@ import { UrlReaders, } from '@backstage/backend-common'; import { Config } from '@backstage/config'; +import app from './plugins/app'; import auth from './plugins/auth'; import catalog from './plugins/catalog'; import scaffolder from './plugins/scaffolder'; @@ -53,6 +54,7 @@ async function main() { const authEnv = useHotMemoize(module, () => createEnv('auth')); const proxyEnv = useHotMemoize(module, () => createEnv('proxy')); const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); + const appEnv = useHotMemoize(module, () => createEnv('app')); const apiRouter = Router(); apiRouter.use('/catalog', await catalog(catalogEnv)); @@ -64,7 +66,8 @@ async function main() { const service = createServiceBuilder(module) .loadConfig(config) - .addRouter('/api', apiRouter); + .addRouter('/api', apiRouter) + .addRouter('', await app(appEnv)); await service.start().catch(err => { console.log(err); diff --git a/packages/create-app/templates/default-app/packages/backend/src/plugins/app.ts b/packages/create-app/templates/default-app/packages/backend/src/plugins/app.ts new file mode 100644 index 0000000000..51316949cb --- /dev/null +++ b/packages/create-app/templates/default-app/packages/backend/src/plugins/app.ts @@ -0,0 +1,13 @@ +import { createRouter } from '@backstage/plugin-app-backend'; +import { PluginEnvironment } from '../types'; + +export default async function createPlugin({ + logger, + config, +}: PluginEnvironment) { + return await createRouter({ + logger, + config, + appPackageName: 'app', + }); +} From 3525e6f687bd5fc9948e58fb2066a3864b39176a Mon Sep 17 00:00:00 2001 From: Stefano Vuerich Date: Thu, 19 Nov 2020 08:40:14 +0100 Subject: [PATCH 067/430] fix: conditional getuid and getgid for Windows environment --- .../stages/templater/helpers.test.ts | 15 +++++++------- .../scaffolder/stages/templater/helpers.ts | 20 ++++++++++++++----- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/templater/helpers.test.ts b/plugins/scaffolder-backend/src/scaffolder/stages/templater/helpers.test.ts index a4441ca23f..ca838fbfaa 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/templater/helpers.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/templater/helpers.test.ts @@ -17,15 +17,9 @@ import Stream, { PassThrough } from 'stream'; import os from 'os'; import fs from 'fs'; import Docker from 'dockerode'; -import { runDockerContainer } from './helpers'; +import { UserOptions, runDockerContainer} from './helpers'; describe('helpers', () => { - if (process.platform === 'win32') { - // eslint-disable-next-line jest/no-focused-tests - it.only('should skip tests on windows', () => { - expect('test').not.toBe('run'); - }); - } const mockDocker = new Docker() as jest.Mocked; @@ -142,12 +136,17 @@ describe('helpers', () => { dockerClient: mockDocker, }); + const userOptions: UserOptions = {}; + if (process.getuid && process.getgid) { + userOptions.User = `${process.getuid()}:${process.getgid()}`; + } + expect(mockDocker.run).toHaveBeenCalledWith( imageName, args, expect.any(Stream), expect.objectContaining({ - User: `${process.getuid()}:${process.getgid()}`, + ...userOptions, Env: ['HOME=/tmp'], }), ); diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/templater/helpers.ts b/plugins/scaffolder-backend/src/scaffolder/stages/templater/helpers.ts index e71b63bfb0..42bdbc8060 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/templater/helpers.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/templater/helpers.ts @@ -36,6 +36,10 @@ export type RunCommandOptions = { logStream?: Writable; }; +export type UserOptions = { + User?: string; +} + /** * Gets the templater key to use for templating from the entity * @param entity Template entity @@ -115,6 +119,16 @@ export const runDockerContainer = async ({ }); }); + const userOptions: UserOptions = {}; + if (process.getuid && process.getgid) { + // Files that are created inside the Docker container will be owned by + // root on the host system on non Mac systems, because of reasons. Mainly the fact that + // volume sharing is done using NFS on Mac and actual mounts in Linux world. + // So we set the user in the container as the same user and group id as the host. + // On Windows we don't have process.getuid nor process.getgid + userOptions.User = `${process.getuid()}:${process.getgid()}`; + } + const [{ Error: error, StatusCode: statusCode }] = await dockerClient.run( imageName, args, @@ -129,11 +143,7 @@ export const runDockerContainer = async ({ `${await fs.promises.realpath(templateDir)}:/template`, ], }, - // Files that are created inside the Docker container will be owned by - // root on the host system on non Mac systems, because of reasons. Mainly the fact that - // volume sharing is done using NFS on Mac and actual mounts in Linux world. - // So we set the user in the container as the same user and group id as the host. - User: `${process.getuid()}:${process.getgid()}`, + ...userOptions, // Set the home directory inside the container as something that applications can // write to, otherwise they will just flop and fail trying to write to / Env: ['HOME=/tmp'], From 00b60af58ae758b0e3d0ba7f4c625a413dea7a12 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Thu, 19 Nov 2020 09:26:31 +0100 Subject: [PATCH 068/430] docs: Correct URL of techdocs-container --- docs/features/techdocs/getting-started.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/features/techdocs/getting-started.md b/docs/features/techdocs/getting-started.md index cbbbc1e6cd..e51c6a122e 100644 --- a/docs/features/techdocs/getting-started.md +++ b/docs/features/techdocs/getting-started.md @@ -95,7 +95,7 @@ environment is compatible with techdocs. You will have to install the `mkdocs` and `mkdocs-techdocs-core` package from pip, as well as `graphviz` and `plantuml` from your OS package manager (e.g. apt). See our -[Dockerfile](https://github.com/spotify/backstage/blob/master/packages/techdocs-container/Dockerfile) +[Dockerfile](https://github.com/backstage/techdocs-container/blob/main/Dockerfile) for the latest requirements. You should be trying to match your Dockerfile with this one. @@ -104,7 +104,7 @@ Note: We recommend Python version 3.7 or higher. Caveat: Please install the `mkdocs-techdocs-core` package after all other Python packages. The order is important to make sure we get correct version of some of the dependencies. For example, we want `Markdown` version to be -[3.2.2](https://github.com/spotify/backstage/blob/f9f70c225548017b6a14daea75b00fbd399c11eb/packages/techdocs-container/techdocs-core/requirements.txt#L11). +[3.2.2](https://github.com/backstage/backstage/blob/f9f70c225548017b6a14daea75b00fbd399c11eb/packages/techdocs-container/techdocs-core/requirements.txt#L11). You can also explicitly install `Markdown==3.2.2` after installing all other Python packages. From 49554d43e37084979c6e3f56f7a3e3f2b9c0c995 Mon Sep 17 00:00:00 2001 From: Mateusz Lewtak <36006588+lewtakm@users.noreply.github.com> Date: Thu, 19 Nov 2020 10:30:34 +0100 Subject: [PATCH 069/430] Feat: update Lighthouse plugin to use new MarkdownComponent (#3328) --- plugins/lighthouse/package.json | 1 - .../lighthouse/src/components/Intro/index.tsx | 7 +- yarn.lock | 156 +----------------- 3 files changed, 7 insertions(+), 157 deletions(-) diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json index 53bfc0e78b..8172847120 100644 --- a/plugins/lighthouse/package.json +++ b/plugins/lighthouse/package.json @@ -34,7 +34,6 @@ "@types/react": "^16.9", "react": "^16.13.1", "react-dom": "^16.13.1", - "react-markdown": "^4.3.1", "react-router-dom": "6.0.0-beta.0", "react-use": "^15.3.3" }, diff --git a/plugins/lighthouse/src/components/Intro/index.tsx b/plugins/lighthouse/src/components/Intro/index.tsx index dc2a8ea283..3e969a3c74 100644 --- a/plugins/lighthouse/src/components/Intro/index.tsx +++ b/plugins/lighthouse/src/components/Intro/index.tsx @@ -15,8 +15,7 @@ */ import React, { useState } from 'react'; import { useLocalStorage } from 'react-use'; -import Markdown from 'react-markdown'; -import { ContentHeader, InfoCard } from '@backstage/core'; +import { ContentHeader, InfoCard, MarkdownContent } from '@backstage/core'; import { makeStyles, Button, Grid, Tabs, Tab } from '@material-ui/core'; import CloseIcon from '@material-ui/icons/Close'; @@ -116,8 +115,8 @@ function GettingStartedCard() { } > - {value === 0 && } - {value === 1 && } + {value === 0 && } + {value === 1 && } ); } diff --git a/yarn.lock b/yarn.lock index feacd32d97..bb59924b80 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8750,11 +8750,6 @@ codeowners-utils@^1.0.2: ignore "^5.1.4" locate-path "^5.0.0" -collapse-white-space@^1.0.2: - version "1.0.6" - resolved "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.6.tgz#e63629c0016665792060dbbeb79c42239d2c5287" - integrity sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ== - collect-v8-coverage@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.0.tgz#150ee634ac3650b71d9c985eb7f608942334feb1" @@ -13624,7 +13619,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: version "2.0.4" resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -13851,7 +13846,7 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" -is-buffer@^1.0.2, is-buffer@^1.1.4, is-buffer@^1.1.5: +is-buffer@^1.0.2, is-buffer@^1.1.5: version "1.1.6" resolved "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== @@ -14263,11 +14258,6 @@ is-utf8@^0.2.0: resolved "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI= -is-whitespace-character@^1.0.0: - version "1.0.4" - resolved "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz#0858edd94a95594c7c9dd0b5c174ec6e45ee4aa7" - integrity sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w== - is-window@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/is-window/-/is-window-1.0.2.tgz#2c896ca53db97de45d3c33133a65d8c9f563480d" @@ -14278,11 +14268,6 @@ is-windows@^1.0.0, is-windows@^1.0.1, is-windows@^1.0.2: resolved "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== -is-word-character@^1.0.0: - version "1.0.4" - resolved "https://registry.npmjs.org/is-word-character/-/is-word-character-1.0.4.tgz#ce0e73216f98599060592f62ff31354ddbeb0230" - integrity sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA== - is-wsl@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" @@ -16134,11 +16119,6 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" -markdown-escapes@^1.0.0: - version "1.0.4" - resolved "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.4.tgz#c95415ef451499d7602b91095f3c8e8975f78535" - integrity sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg== - markdown-it@^10.0.0: version "10.0.0" resolved "https://registry.npmjs.org/markdown-it/-/markdown-it-10.0.0.tgz#abfc64f141b1722d663402044e43927f1f50a8dc" @@ -17996,7 +17976,7 @@ parse-asn1@^5.0.0: pbkdf2 "^3.0.3" safe-buffer "^5.1.1" -parse-entities@^1.1.0, parse-entities@^1.1.2: +parse-entities@^1.1.2: version "1.2.2" resolved "https://registry.npmjs.org/parse-entities/-/parse-entities-1.2.2.tgz#c31bf0f653b6661354f8973559cb86dd1d5edf50" integrity sha512-NzfpbxW/NPrzZ/yYSoQxyqUZMZXIdCfE0OIN4ESsnptHJECoUk3FZktxNuzQf4tjt5UEopnxpYJbvYuxIFDdsg== @@ -19781,20 +19761,6 @@ react-lifecycles-compat@^3.0.0, react-lifecycles-compat@^3.0.4: resolved "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== -react-markdown@^4.3.1: - version "4.3.1" - resolved "https://registry.npmjs.org/react-markdown/-/react-markdown-4.3.1.tgz#39f0633b94a027445b86c9811142d05381300f2f" - integrity sha512-HQlWFTbDxTtNY6bjgp3C3uv1h2xcjCSi1zAEzfBW9OwJJvENSYiLXWNXN5hHLsoqai7RnZiiHzcnWdXk2Splzw== - dependencies: - html-to-react "^1.3.4" - mdast-add-list-metadata "1.0.1" - prop-types "^15.7.2" - react-is "^16.8.6" - remark-parse "^5.0.0" - unified "^6.1.5" - unist-util-visit "^1.3.0" - xtend "^4.0.1" - react-markdown@^5.0.0, react-markdown@^5.0.2: version "5.0.2" resolved "https://registry.npmjs.org/react-markdown/-/react-markdown-5.0.2.tgz#d15a8beb37b4ec34fc23dd892e7755eb7040b8db" @@ -20529,27 +20495,6 @@ remark-gfm@^1.0.0: mdast-util-gfm "^0.1.0" micromark-extension-gfm "^0.3.0" -remark-parse@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/remark-parse/-/remark-parse-5.0.0.tgz#4c077f9e499044d1d5c13f80d7a98cf7b9285d95" - integrity sha512-b3iXszZLH1TLoyUzrATcTQUZrwNl1rE70rVdSruJFlDaJ9z5aMkhrG43Pp68OgfHndL/ADz6V69Zow8cTQu+JA== - dependencies: - collapse-white-space "^1.0.2" - is-alphabetical "^1.0.0" - is-decimal "^1.0.0" - is-whitespace-character "^1.0.0" - is-word-character "^1.0.0" - markdown-escapes "^1.0.0" - parse-entities "^1.1.0" - repeat-string "^1.5.4" - state-toggle "^1.0.0" - trim "0.0.1" - trim-trailing-lines "^1.0.0" - unherit "^1.0.4" - unist-util-remove-position "^1.0.0" - vfile-location "^2.0.0" - xtend "^4.0.1" - remark-parse@^9.0.0: version "9.0.0" resolved "https://registry.npmjs.org/remark-parse/-/remark-parse-9.0.0.tgz#4d20a299665880e4f4af5d90b7c7b8a935853640" @@ -20596,7 +20541,7 @@ repeat-element@^1.1.2: resolved "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== -repeat-string@^1.0.0, repeat-string@^1.5.2, repeat-string@^1.5.4, repeat-string@^1.6.1: +repeat-string@^1.0.0, repeat-string@^1.5.2, repeat-string@^1.6.1: version "1.6.1" resolved "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= @@ -21853,11 +21798,6 @@ start-server-webpack-plugin@^2.2.5: resolved "https://registry.npmjs.org/start-server-webpack-plugin/-/start-server-webpack-plugin-2.2.5.tgz#4a2838759b0f36acd11b0b2f5f196f289ae29d31" integrity sha512-DRCkciwCJoCFZ+wt3wWMkR1M2mpVhJbUKFXqhK3FWyIUKYb42NnocH5sMwqgo+nPNHupqNwK/v8lgfBbr2NKdg== -state-toggle@^1.0.0: - version "1.0.3" - resolved "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.3.tgz#e123b16a88e143139b09c6852221bc9815917dfe" - integrity sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ== - static-extend@^0.1.1: version "0.1.2" resolved "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" @@ -22969,16 +22909,6 @@ trim-off-newlines@^1.0.0: resolved "https://registry.npmjs.org/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz#9f9ba9d9efa8764c387698bcbfeb2c848f11adb3" integrity sha1-n5up2e+odkw4dpi8v+sshI8RrbM= -trim-trailing-lines@^1.0.0: - version "1.1.3" - resolved "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.3.tgz#7f0739881ff76657b7776e10874128004b625a94" - integrity sha512-4ku0mmjXifQcTVfYDfR5lpgV7zVqPg6zV9rdZmwOPqq0+Zq19xDqEgagqVbc4pOOShbncuAOIs59R3+3gcF3ZA== - -trim@0.0.1: - version "0.0.1" - resolved "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd" - integrity sha1-WFhUf2spB1fulczMZm+1AITEYN0= - triple-beam@^1.2.0, triple-beam@^1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9" @@ -23306,14 +23236,6 @@ unfetch@^4.1.0: resolved "https://registry.npmjs.org/unfetch/-/unfetch-4.1.0.tgz#6ec2dd0de887e58a4dee83a050ded80ffc4137db" integrity sha512-crP/n3eAPUJxZXM9T80/yv0YhkTEx2K1D3h7D1AJM6fzsWZrxdyRuLN0JH/dkZh1LNH8LxCnBzoPFCPbb2iGpg== -unherit@^1.0.4: - version "1.1.3" - resolved "https://registry.npmjs.org/unherit/-/unherit-1.1.3.tgz#6c9b503f2b41b262330c80e91c8614abdaa69c22" - integrity sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ== - dependencies: - inherits "^2.0.0" - xtend "^4.0.0" - unicode-canonical-property-names-ecmascript@^1.0.4: version "1.0.4" resolved "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" @@ -23337,18 +23259,6 @@ unicode-property-aliases-ecmascript@^1.0.4: resolved "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz#dd57a99f6207bedff4628abefb94c50db941c8f4" integrity sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg== -unified@^6.1.5: - version "6.2.0" - resolved "https://registry.npmjs.org/unified/-/unified-6.2.0.tgz#7fbd630f719126d67d40c644b7e3f617035f6dba" - integrity sha512-1k+KPhlVtqmG99RaTbAv/usu85fcSRu3wY8X+vnsEhIxNP5VbVIDiXnLqyKIG+UMdyTg0ZX9EI6k2AfjJkHPtA== - dependencies: - bail "^1.0.0" - extend "^3.0.0" - is-plain-obj "^1.1.0" - trough "^1.0.0" - vfile "^2.0.0" - x-is-string "^0.1.0" - unified@^9.0.0: version "9.2.0" resolved "https://registry.npmjs.org/unified/-/unified-9.2.0.tgz#67a62c627c40589edebbf60f53edfd4d822027f8" @@ -23402,28 +23312,11 @@ unique-string@^2.0.0: dependencies: crypto-random-string "^2.0.0" -unist-util-is@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/unist-util-is/-/unist-util-is-3.0.0.tgz#d9e84381c2468e82629e4a5be9d7d05a2dd324cd" - integrity sha512-sVZZX3+kspVNmLWBPAB6r+7D9ZgAFPNWm66f7YNb420RlQSbn+n8rG8dGZSkrER7ZIXGQYNm5pqC3v3HopH24A== - unist-util-is@^4.0.0: version "4.0.3" resolved "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.0.3.tgz#e8b44db55fc20c43752b3346c116344d45d7c91d" integrity sha512-bTofCFVx0iQM8Jqb1TBDVRIQW03YkD3p66JOd/aCWuqzlLyUtx1ZAGw/u+Zw+SttKvSVcvTiKYbfrtLoLefykw== -unist-util-remove-position@^1.0.0: - version "1.1.4" - resolved "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-1.1.4.tgz#ec037348b6102c897703eee6d0294ca4755a2020" - integrity sha512-tLqd653ArxJIPnKII6LMZwH+mb5q+n/GtXQZo6S6csPRs5zB0u79Yw8ouR3wTw8wxvdJFhpP6Y7jorWdCgLO0A== - dependencies: - unist-util-visit "^1.1.0" - -unist-util-stringify-position@^1.0.0, unist-util-stringify-position@^1.1.1: - version "1.1.2" - resolved "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-1.1.2.tgz#3f37fcf351279dcbca7480ab5889bb8a832ee1c6" - integrity sha512-pNCVrk64LZv1kElr0N1wPiHEUoXNVFERp+mlTg/s9R5Lwg87f9bM/3sQB99w+N9D/qnM9ar3+AKDBwo/gm/iQQ== - unist-util-stringify-position@^2.0.0: version "2.0.3" resolved "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz#cce3bfa1cdf85ba7375d1d5b17bdc4cada9bd9da" @@ -23436,13 +23329,6 @@ unist-util-visit-parents@1.1.2: resolved "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-1.1.2.tgz#f6e3afee8bdbf961c0e6f028ea3c0480028c3d06" integrity sha512-yvo+MMLjEwdc3RhhPYSximset7rwjMrdt9E41Smmvg25UQIenzrN83cRnF1JMzoMi9zZOQeYXHSDf7p+IQkW3Q== -unist-util-visit-parents@^2.0.0: - version "2.1.2" - resolved "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-2.1.2.tgz#25e43e55312166f3348cae6743588781d112c1e9" - integrity sha512-DyN5vD4NE3aSeB+PXYNKxzGsfocxp6asDc2XXE3b0ekO2BaRUpBicbbUygfSvYfUz1IkmjFR1YF7dPklraMZ2g== - dependencies: - unist-util-is "^3.0.0" - unist-util-visit-parents@^3.0.0: version "3.1.1" resolved "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz#65a6ce698f78a6b0f56aa0e88f13801886cdaef6" @@ -23451,13 +23337,6 @@ unist-util-visit-parents@^3.0.0: "@types/unist" "^2.0.0" unist-util-is "^4.0.0" -unist-util-visit@^1.1.0, unist-util-visit@^1.3.0: - version "1.4.1" - resolved "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-1.4.1.tgz#4724aaa8486e6ee6e26d7ff3c8685960d560b1e3" - integrity sha512-AvGNk7Bb//EmJZyhtRUnNMEpId/AZ5Ph/KUpTI09WHQuDZHKovQ1oEv3mfmKpWKtoMzyMC4GLBm1Zy5k12fjIw== - dependencies: - unist-util-visit-parents "^2.0.0" - unist-util-visit@^2.0.0: version "2.0.3" resolved "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz#c3703893146df47203bb8a9795af47d7b971208c" @@ -23813,18 +23692,6 @@ verror@1.10.0, verror@^1.8.1: core-util-is "1.0.2" extsprintf "^1.2.0" -vfile-location@^2.0.0: - version "2.0.6" - resolved "https://registry.npmjs.org/vfile-location/-/vfile-location-2.0.6.tgz#8a274f39411b8719ea5728802e10d9e0dff1519e" - integrity sha512-sSFdyCP3G6Ka0CEmN83A2YCMKIieHx0EDaj5IDP4g1pa5ZJ4FJDvpO0WODLxo4LUX4oe52gmSCK7Jw4SBghqxA== - -vfile-message@^1.0.0: - version "1.1.1" - resolved "https://registry.npmjs.org/vfile-message/-/vfile-message-1.1.1.tgz#5833ae078a1dfa2d96e9647886cd32993ab313e1" - integrity sha512-1WmsopSGhWt5laNir+633LszXvZ+Z/lxveBf6yhGsqnQIhlhzooZae7zV6YVM1Sdkw68dtAW3ow0pOdPANugvA== - dependencies: - unist-util-stringify-position "^1.1.1" - vfile-message@^2.0.0: version "2.0.4" resolved "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.4.tgz#5b43b88171d409eae58477d13f23dd41d52c371a" @@ -23833,16 +23700,6 @@ vfile-message@^2.0.0: "@types/unist" "^2.0.0" unist-util-stringify-position "^2.0.0" -vfile@^2.0.0: - version "2.3.0" - resolved "https://registry.npmjs.org/vfile/-/vfile-2.3.0.tgz#e62d8e72b20e83c324bc6c67278ee272488bf84a" - integrity sha512-ASt4mBUHcTpMKD/l5Q+WJXNtshlWxOogYyGYYrg4lt/vuRjC1EFQtlAofL5VmtVNIZJzWYFJjzGWZ0Gw8pzW1w== - dependencies: - is-buffer "^1.1.4" - replace-ext "1.0.0" - unist-util-stringify-position "^1.0.0" - vfile-message "^1.0.0" - vfile@^4.0.0: version "4.2.0" resolved "https://registry.npmjs.org/vfile/-/vfile-4.2.0.tgz#26c78ac92eb70816b01d4565e003b7e65a2a0e01" @@ -24419,11 +24276,6 @@ ws@^7.3.1: resolved "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz#d0547bf67f7ce4f12a72dfe31262c68d7dc551c8" integrity sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA== -x-is-string@^0.1.0: - version "0.1.0" - resolved "https://registry.npmjs.org/x-is-string/-/x-is-string-0.1.0.tgz#474b50865af3a49a9c4657f05acd145458f77d82" - integrity sha1-R0tQhlrzpJqcRlfwWs0UVFj3fYI= - xcase@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/xcase/-/xcase-2.0.1.tgz#c7fa72caa0f440db78fd5673432038ac984450b9" From ef8dff2cbe4e6388c661678bde23cd11cbdc7eff Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Thu, 19 Nov 2020 11:38:36 +0100 Subject: [PATCH 070/430] Replace broken icon for the api-docs plugin Closes #3330 I would guess that https://github.com/vscode-icons/vscode-icons is a more stable location for an icon. --- microsite/data/plugins/api-docs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microsite/data/plugins/api-docs.yaml b/microsite/data/plugins/api-docs.yaml index 3343a46693..03ec818445 100644 --- a/microsite/data/plugins/api-docs.yaml +++ b/microsite/data/plugins/api-docs.yaml @@ -5,5 +5,5 @@ authorUrl: https://sda.se/ category: Discovery description: Components to discover and display API entities as an extension to the catalog plugin. documentation: https://github.com/backstage/backstage/blob/master/plugins/api-docs/README.md -iconUrl: https://thecoders.io/wp-content/uploads/2019/11/tech-swagger.svg +iconUrl: https://raw.githubusercontent.com/vscode-icons/vscode-icons/master/icons/file_type_swagger.svg npmPackageName: '@backstage/plugin-api-docs' From 09d90484ea25b34b28b3f61eef95139385458e83 Mon Sep 17 00:00:00 2001 From: Stefano Vuerich Date: Thu, 19 Nov 2020 11:46:34 +0100 Subject: [PATCH 071/430] fix: runned prettier --- .../src/scaffolder/stages/templater/helpers.test.ts | 3 +-- .../src/scaffolder/stages/templater/helpers.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/templater/helpers.test.ts b/plugins/scaffolder-backend/src/scaffolder/stages/templater/helpers.test.ts index ca838fbfaa..57e47f4c27 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/templater/helpers.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/templater/helpers.test.ts @@ -17,10 +17,9 @@ import Stream, { PassThrough } from 'stream'; import os from 'os'; import fs from 'fs'; import Docker from 'dockerode'; -import { UserOptions, runDockerContainer} from './helpers'; +import { UserOptions, runDockerContainer } from './helpers'; describe('helpers', () => { - const mockDocker = new Docker() as jest.Mocked; beforeEach(() => { diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/templater/helpers.ts b/plugins/scaffolder-backend/src/scaffolder/stages/templater/helpers.ts index 42bdbc8060..e79f2cb066 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/templater/helpers.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/templater/helpers.ts @@ -38,7 +38,7 @@ export type RunCommandOptions = { export type UserOptions = { User?: string; -} +}; /** * Gets the templater key to use for templating from the entity From 019c67a02280e39e25f07c92745c36bf849846ea Mon Sep 17 00:00:00 2001 From: Askar Date: Thu, 19 Nov 2020 13:39:22 +0100 Subject: [PATCH 072/430] fix(search): return EmptyState/Progress bar/Alert depending on result of api call (#3350) * fix(search): return EmptyState/Progress bar/Alert depending on result of api request * Update plugins/search/src/components/SearchResult/SearchResult.tsx Co-authored-by: Emma Indal Co-authored-by: Emma Indal --- .../components/SearchResult/SearchResult.tsx | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/plugins/search/src/components/SearchResult/SearchResult.tsx b/plugins/search/src/components/SearchResult/SearchResult.tsx index a4985c20fb..e4163c0de7 100644 --- a/plugins/search/src/components/SearchResult/SearchResult.tsx +++ b/plugins/search/src/components/SearchResult/SearchResult.tsx @@ -17,7 +17,14 @@ import React, { useState, useEffect } from 'react'; import { useAsync } from 'react-use'; import { makeStyles, Typography, Grid, Divider } from '@material-ui/core'; -import { Table, TableColumn, useApi } from '@backstage/core'; +import { Alert } from '@material-ui/lab'; +import { + EmptyState, + Progress, + Table, + TableColumn, + useApi, +} from '@backstage/core'; import { catalogApiRef } from '@backstage/plugin-catalog'; import { FiltersButton, Filters, FiltersState } from '../Filters'; @@ -159,8 +166,19 @@ export const SearchResult = ({ searchQuery }: SearchResultProps) => { setFilteredResults(withFilters); } }, [filters, searchQuery, results]); - - if (loading || error || !results) return null; + if (loading) { + return ; + } + if (error) { + return ( + + Error encountered while fetching search results. {error.toString()} + + ); + } + if (!results || results.length === 0) { + return ; + } const resetFilters = () => { setFilters({ From 76306d3e4b2cae48606d1172b9490cb612f17c90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Frinnstr=C3=B6m?= Date: Wed, 18 Nov 2020 07:38:18 +0100 Subject: [PATCH 073/430] Extract types --- .../src/scaffolder/stages/publish/types.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/publish/types.ts b/plugins/scaffolder-backend/src/scaffolder/stages/publish/types.ts index 6e9c45ba43..6dfc29dbde 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/publish/types.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/publish/types.ts @@ -29,11 +29,18 @@ export type PublisherBase = { * catalog, plus the values from the form and the directory that has * been templated */ - publish(opts: { - entity: TemplateEntityV1alpha1; - values: RequiredTemplateValues & Record; - directory: string; - }): Promise<{ remoteUrl: string }>; + publish(opts: PublisherOptions): Promise; +}; + +export type PublisherOptions = { + entity: TemplateEntityV1alpha1; + values: RequiredTemplateValues & Record; + directory: string; +}; + +export type PublisherResult = { + remoteUrl: string; + catalogInfoUrl?: string; }; export type PublisherBuilder = { From e0a910bc6dcaeef3a0790a7324893562dbe956f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Frinnstr=C3=B6m?= Date: Thu, 19 Nov 2020 12:54:31 +0100 Subject: [PATCH 074/430] Start using the extracted types --- .../src/scaffolder/stages/publish/azure.ts | 7 ++----- .../src/scaffolder/stages/publish/github.ts | 7 ++----- .../src/scaffolder/stages/publish/gitlab.ts | 7 ++----- .../src/scaffolder/stages/publish/types.ts | 1 - plugins/scaffolder-backend/src/service/router.ts | 1 - 5 files changed, 6 insertions(+), 17 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/publish/azure.ts b/plugins/scaffolder-backend/src/scaffolder/stages/publish/azure.ts index 33f7f89199..8f3ed33423 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/publish/azure.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/publish/azure.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { PublisherBase } from './types'; +import { PublisherBase, PublisherOptions, PublisherResult } from './types'; import { GitApi } from 'azure-devops-node-api/GitApi'; import { GitRepositoryCreateOptions } from 'azure-devops-node-api/interfaces/GitInterfaces'; import { pushToRemoteUserPass } from './helpers'; @@ -33,10 +33,7 @@ export class AzurePublisher implements PublisherBase { async publish({ values, directory, - }: { - values: RequiredTemplateValues & Record; - directory: string; - }): Promise<{ remoteUrl: string }> { + }: PublisherOptions): Promise { const remoteUrl = await this.createRemote(values); await pushToRemoteUserPass(directory, remoteUrl, 'notempty', this.token); diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts index d5542e8800..2e835ba3d4 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { PublisherBase } from './types'; +import { PublisherBase, PublisherOptions, PublisherResult } from './types'; import { Octokit } from '@octokit/rest'; import { pushToRemoteUserPass } from './helpers'; import { JsonValue } from '@backstage/config'; @@ -46,10 +46,7 @@ export class GithubPublisher implements PublisherBase { async publish({ values, directory, - }: { - values: RequiredTemplateValues & Record; - directory: string; - }): Promise<{ remoteUrl: string }> { + }: PublisherOptions): Promise { const remoteUrl = await this.createRemote(values); await pushToRemoteUserPass( directory, diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/publish/gitlab.ts b/plugins/scaffolder-backend/src/scaffolder/stages/publish/gitlab.ts index e748dc53dc..4fae8e8a5c 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/publish/gitlab.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/publish/gitlab.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { PublisherBase } from './types'; +import { PublisherBase, PublisherOptions, PublisherResult } from './types'; import { Gitlab } from '@gitbeaker/core'; import { pushToRemoteUserPass } from './helpers'; import { JsonValue } from '@backstage/config'; @@ -32,10 +32,7 @@ export class GitlabPublisher implements PublisherBase { async publish({ values, directory, - }: { - values: RequiredTemplateValues & Record; - directory: string; - }): Promise<{ remoteUrl: string }> { + }: PublisherOptions): Promise { const remoteUrl = await this.createRemote(values); await pushToRemoteUserPass(directory, remoteUrl, 'oauth2', this.token); diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/publish/types.ts b/plugins/scaffolder-backend/src/scaffolder/stages/publish/types.ts index 6dfc29dbde..f3bc59e19d 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/publish/types.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/publish/types.ts @@ -33,7 +33,6 @@ export type PublisherBase = { }; export type PublisherOptions = { - entity: TemplateEntityV1alpha1; values: RequiredTemplateValues & Record; directory: string; }; diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 2dc6a794fb..fd25f2d845 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -157,7 +157,6 @@ export async function createRouter( const publisher = publishers.get(ctx.entity); ctx.logger.info('Will now store the template'); const { remoteUrl } = await publisher.publish({ - entity: ctx.entity, values: ctx.values, directory: ctx.resultDir, }); From 67b6320e839e504f7c997e19dd3b9171ecc55188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Frinnstr=C3=B6m?= Date: Thu, 19 Nov 2020 13:51:40 +0100 Subject: [PATCH 075/430] Generate catatalog-info.yaml URL in the publishers --- .../scaffolder/stages/publish/azure.test.ts | 10 ++-- .../src/scaffolder/stages/publish/azure.ts | 3 +- .../scaffolder/stages/publish/github.test.ts | 50 +++++++++++++------ .../src/scaffolder/stages/publish/github.ts | 6 ++- .../scaffolder-backend/src/service/router.ts | 4 +- 5 files changed, 51 insertions(+), 22 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/publish/azure.test.ts b/plugins/scaffolder-backend/src/scaffolder/stages/publish/azure.test.ts index 7ab6f81ae5..9ea7d2de5f 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/publish/azure.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/publish/azure.test.ts @@ -41,7 +41,7 @@ describe('Azure Publisher', () => { describe('publish: createRemoteInAzure', () => { it('should use azure-devops-node-api to create a repo in the given project', async () => { mockGitApi.createRepository.mockResolvedValue({ - remoteUrl: 'mockclone', + remoteUrl: 'https://dev.azure.com/organization/project/_git/repo', } as { remoteUrl: string }); const result = await publisher.publish({ @@ -52,7 +52,11 @@ describe('Azure Publisher', () => { directory: '/tmp/test', }); - expect(result).toEqual({ remoteUrl: 'mockclone' }); + expect(result).toEqual({ + remoteUrl: 'https://dev.azure.com/organization/project/_git/repo', + catalogInfoUrl: + 'https://dev.azure.com/organization/project/_git/repo?path=%2Fcatalog-info.yaml', + }); expect(mockGitApi.createRepository).toHaveBeenCalledWith( { name: 'repo', @@ -61,7 +65,7 @@ describe('Azure Publisher', () => { ); expect(pushToRemoteUserPass).toHaveBeenCalledWith( '/tmp/test', - 'mockclone', + 'https://dev.azure.com/organization/project/_git/repo', 'notempty', 'fake-token', ); diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/publish/azure.ts b/plugins/scaffolder-backend/src/scaffolder/stages/publish/azure.ts index 8f3ed33423..1e962bf223 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/publish/azure.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/publish/azure.ts @@ -36,8 +36,9 @@ export class AzurePublisher implements PublisherBase { }: PublisherOptions): Promise { const remoteUrl = await this.createRemote(values); await pushToRemoteUserPass(directory, remoteUrl, 'notempty', this.token); + const catalogInfoUrl = `${remoteUrl}?path=%2Fcatalog-info.yaml`; - return { remoteUrl }; + return { remoteUrl, catalogInfoUrl }; } private async createRemote( diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.test.ts b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.test.ts index c85b4acba3..cda64faf25 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.test.ts @@ -53,7 +53,7 @@ describe('GitHub Publisher', () => { it('should use octokit to create a repo in an organisation if the organisation property is set', async () => { mockGithubClient.repos.createInOrg.mockResolvedValue({ data: { - clone_url: 'mockclone', + clone_url: 'https://github.com/backstage/backstage.git', }, } as OctokitResponse); mockGithubClient.users.getByUsername.mockResolvedValue({ @@ -71,7 +71,11 @@ describe('GitHub Publisher', () => { directory: '/tmp/test', }); - expect(result).toEqual({ remoteUrl: 'mockclone' }); + expect(result).toEqual({ + remoteUrl: 'https://github.com/backstage/backstage.git', + catalogInfoUrl: + 'https://github.com/backstage/backstage/blob/master/catalog-info.yaml', + }); expect(mockGithubClient.repos.createInOrg).toHaveBeenCalledWith({ org: 'blam', name: 'test', @@ -89,7 +93,7 @@ describe('GitHub Publisher', () => { }); expect(pushToRemoteUserPass).toHaveBeenCalledWith( '/tmp/test', - 'mockclone', + 'https://github.com/backstage/backstage.git', 'abc', 'x-oauth-basic', ); @@ -98,7 +102,7 @@ describe('GitHub Publisher', () => { it('should use octokit to create a repo in the authed user if the organisation property is not set', async () => { mockGithubClient.repos.createForAuthenticatedUser.mockResolvedValue({ data: { - clone_url: 'mockclone', + clone_url: 'https://github.com/backstage/backstage.git', }, } as OctokitResponse); mockGithubClient.users.getByUsername.mockResolvedValue({ @@ -116,7 +120,11 @@ describe('GitHub Publisher', () => { directory: '/tmp/test', }); - expect(result).toEqual({ remoteUrl: 'mockclone' }); + expect(result).toEqual({ + remoteUrl: 'https://github.com/backstage/backstage.git', + catalogInfoUrl: + 'https://github.com/backstage/backstage/blob/master/catalog-info.yaml', + }); expect( mockGithubClient.repos.createForAuthenticatedUser, ).toHaveBeenCalledWith({ @@ -126,7 +134,7 @@ describe('GitHub Publisher', () => { expect(mockGithubClient.repos.addCollaborator).not.toHaveBeenCalled(); expect(pushToRemoteUserPass).toHaveBeenCalledWith( '/tmp/test', - 'mockclone', + 'https://github.com/backstage/backstage.git', 'abc', 'x-oauth-basic', ); @@ -136,7 +144,7 @@ describe('GitHub Publisher', () => { it('should invite other user in the authed user', async () => { mockGithubClient.repos.createForAuthenticatedUser.mockResolvedValue({ data: { - clone_url: 'mockclone', + clone_url: 'https://github.com/backstage/backstage.git', }, } as OctokitResponse); mockGithubClient.users.getByUsername.mockResolvedValue({ @@ -155,7 +163,11 @@ describe('GitHub Publisher', () => { directory: '/tmp/test', }); - expect(result).toEqual({ remoteUrl: 'mockclone' }); + expect(result).toEqual({ + remoteUrl: 'https://github.com/backstage/backstage.git', + catalogInfoUrl: + 'https://github.com/backstage/backstage/blob/master/catalog-info.yaml', + }); expect( mockGithubClient.repos.createForAuthenticatedUser, ).toHaveBeenCalledWith({ @@ -171,7 +183,7 @@ describe('GitHub Publisher', () => { }); expect(pushToRemoteUserPass).toHaveBeenCalledWith( '/tmp/test', - 'mockclone', + 'https://github.com/backstage/backstage.git', 'abc', 'x-oauth-basic', ); @@ -188,7 +200,7 @@ describe('GitHub Publisher', () => { it('creates a private repository in the organization with visibility set to internal', async () => { mockGithubClient.repos.createInOrg.mockResolvedValue({ data: { - clone_url: 'mockclone', + clone_url: 'https://github.com/backstage/backstage.git', }, } as OctokitResponse); mockGithubClient.users.getByUsername.mockResolvedValue({ @@ -206,7 +218,11 @@ describe('GitHub Publisher', () => { directory: '/tmp/test', }); - expect(result).toEqual({ remoteUrl: 'mockclone' }); + expect(result).toEqual({ + remoteUrl: 'https://github.com/backstage/backstage.git', + catalogInfoUrl: + 'https://github.com/backstage/backstage/blob/master/catalog-info.yaml', + }); expect(mockGithubClient.repos.createInOrg).toHaveBeenCalledWith({ org: 'blam', name: 'test', @@ -215,7 +231,7 @@ describe('GitHub Publisher', () => { }); expect(pushToRemoteUserPass).toHaveBeenCalledWith( '/tmp/test', - 'mockclone', + 'https://github.com/backstage/backstage.git', 'abc', 'x-oauth-basic', ); @@ -232,7 +248,7 @@ describe('GitHub Publisher', () => { it('creates a private repository', async () => { mockGithubClient.repos.createForAuthenticatedUser.mockResolvedValue({ data: { - clone_url: 'mockclone', + clone_url: 'https://github.com/backstage/backstage.git', }, } as OctokitResponse); mockGithubClient.users.getByUsername.mockResolvedValue({ @@ -249,7 +265,11 @@ describe('GitHub Publisher', () => { directory: '/tmp/test', }); - expect(result).toEqual({ remoteUrl: 'mockclone' }); + expect(result).toEqual({ + remoteUrl: 'https://github.com/backstage/backstage.git', + catalogInfoUrl: + 'https://github.com/backstage/backstage/blob/master/catalog-info.yaml', + }); expect( mockGithubClient.repos.createForAuthenticatedUser, ).toHaveBeenCalledWith({ @@ -258,7 +278,7 @@ describe('GitHub Publisher', () => { }); expect(pushToRemoteUserPass).toHaveBeenCalledWith( '/tmp/test', - 'mockclone', + 'https://github.com/backstage/backstage.git', 'abc', 'x-oauth-basic', ); diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts index 2e835ba3d4..64976b8e41 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts @@ -54,8 +54,12 @@ export class GithubPublisher implements PublisherBase { this.token, 'x-oauth-basic', ); + const catalogInfoUrl = remoteUrl.replace( + /\.git$/, + '/blob/master/catalog-info.yaml', + ); - return { remoteUrl }; + return { remoteUrl, catalogInfoUrl }; } private async createRemote( diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index fd25f2d845..ac3b1c80ec 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -156,11 +156,11 @@ export async function createRouter( handler: async (ctx: StageContext<{ resultDir: string }>) => { const publisher = publishers.get(ctx.entity); ctx.logger.info('Will now store the template'); - const { remoteUrl } = await publisher.publish({ + const result = await publisher.publish({ values: ctx.values, directory: ctx.resultDir, }); - return { remoteUrl }; + return result; }, }, ], From a13a090de99a08cb5e2f4a5f1473bcc3bb237f94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Frinnstr=C3=B6m?= Date: Thu, 19 Nov 2020 13:52:21 +0100 Subject: [PATCH 076/430] Use the new catalogInfoUrl in TemplatePage --- .../src/components/TemplatePage/TemplatePage.tsx | 14 +++----------- plugins/scaffolder/src/types.ts | 1 + 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx index 4836cdab56..ffa2bfcec7 100644 --- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx +++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx @@ -106,18 +106,10 @@ export const TemplatePage = () => { ); const handleCreateComplete = async (job: Job) => { - const target = job.metadata.remoteUrl?.replace( - /\.git$/, - // TODO(Rugvip): This is not the location we want. As part of scaffolder v2 we - // want this to be more flexible, but before that we might want - // to update all templates to use catalog-info.yaml instead. - '/blob/master/component-info.yaml', - ); - - if (!target) { + if (!job.metadata.catalogInfoUrl) { errorApi.post( new Error( - `Failed to find component-info.yaml file in ${job.metadata.remoteUrl}.`, + `Failed to find catalog-info.yaml file in ${job.metadata.remoteUrl}.`, ), ); return; @@ -125,7 +117,7 @@ export const TemplatePage = () => { const { entities: [createdEntity], - } = await catalogApi.addLocation({ target }); + } = await catalogApi.addLocation({ target: job.metadata.catalogInfoUrl }); setEntity((createdEntity as any) as TemplateEntityV1alpha1); }; diff --git a/plugins/scaffolder/src/types.ts b/plugins/scaffolder/src/types.ts index 7106444904..e07581ad28 100644 --- a/plugins/scaffolder/src/types.ts +++ b/plugins/scaffolder/src/types.ts @@ -19,6 +19,7 @@ export type Job = { entity: any; values: any; remoteUrl?: string; + catalogInfoUrl?: string; }; status: 'PENDING' | 'STARTED' | 'COMPLETED' | 'FAILED'; stages: Stage[]; From ef2831dde3567a2c02f78e7bc8d31c62bc212ad5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Frinnstr=C3=B6m?= Date: Thu, 19 Nov 2020 15:00:24 +0100 Subject: [PATCH 077/430] Add changeset --- .changeset/dull-pans-sip.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/dull-pans-sip.md diff --git a/.changeset/dull-pans-sip.md b/.changeset/dull-pans-sip.md new file mode 100644 index 0000000000..13616c1f8d --- /dev/null +++ b/.changeset/dull-pans-sip.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-scaffolder': patch +'@backstage/plugin-scaffolder-backend': patch +--- + +Move constructing the catalog-info.yaml URL for scaffolded components to the publishers From eb422ee1bf2c888ee68aca0ea94e4b8e83ddc3ea Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Thu, 19 Nov 2020 15:35:25 +0100 Subject: [PATCH 078/430] Fixes after merge --- packages/app/package.json | 1 + plugins/catalog-backend/src/ingestion/types.ts | 2 +- plugins/catalog-import/package.json | 8 ++++---- plugins/catalog-import/src/api/CatalogImportClient.ts | 7 +------ 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/packages/app/package.json b/packages/app/package.json index de6b61684a..6d31adfa65 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -9,6 +9,7 @@ "@backstage/core": "^0.3.0", "@backstage/plugin-api-docs": "^0.2.1", "@backstage/plugin-catalog": "^0.2.1", + "@backstage/plugin-catalog-import": "^0.2.0", "@backstage/plugin-circleci": "^0.2.1", "@backstage/plugin-cloudbuild": "^0.2.1", "@backstage/plugin-cost-insights": "^0.3.0", diff --git a/plugins/catalog-backend/src/ingestion/types.ts b/plugins/catalog-backend/src/ingestion/types.ts index 29308fdd92..79a5387247 100644 --- a/plugins/catalog-backend/src/ingestion/types.ts +++ b/plugins/catalog-backend/src/ingestion/types.ts @@ -20,7 +20,7 @@ import { Location, LocationSpec, } from '@backstage/catalog-model'; -import { RecursivePartial } from './processors/ldap/util'; +import { RecursivePartial } from '../util/RecursivePartial'; // // HigherOrderOperation diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index ee157235ca..4666e22f10 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -22,7 +22,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.2.0", + "@backstage/core": "^0.3.0", "@backstage/plugin-catalog": "^0.2.0", "@backstage/plugin-catalog-backend": "^0.2.0", "@backstage/theme": "^0.2.0", @@ -40,9 +40,9 @@ "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", + "@backstage/cli": "^0.2.0", + "@backstage/dev-utils": "^0.1.0", + "@backstage/test-utils": "^0.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/catalog-import/src/api/CatalogImportClient.ts b/plugins/catalog-import/src/api/CatalogImportClient.ts index f8b4e5d17e..94ba260d55 100644 --- a/plugins/catalog-import/src/api/CatalogImportClient.ts +++ b/plugins/catalog-import/src/api/CatalogImportClient.ts @@ -15,12 +15,7 @@ */ import { Octokit } from '@octokit/rest'; -import { - DiscoveryApi, - githubAuthApiRef, - OAuthApi, - useApi, -} from '@backstage/core'; +import { DiscoveryApi, OAuthApi } from '@backstage/core'; import { CatalogImportApi } from './CatalogImportApi'; import { AnalyzeLocationResponse } from '@backstage/plugin-catalog-backend'; import { PartialEntity } from '../util/types'; From 46e9cf5b63b8a10c32bf830a00339d035bf570d8 Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Thu, 19 Nov 2020 15:55:27 +0100 Subject: [PATCH 079/430] Bump package versions --- plugins/catalog-import/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index 4666e22f10..b53c4ec00d 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -25,7 +25,7 @@ "@backstage/core": "^0.3.0", "@backstage/plugin-catalog": "^0.2.0", "@backstage/plugin-catalog-backend": "^0.2.0", - "@backstage/theme": "^0.2.0", + "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -41,8 +41,8 @@ }, "devDependencies": { "@backstage/cli": "^0.2.0", - "@backstage/dev-utils": "^0.1.0", - "@backstage/test-utils": "^0.1.0", + "@backstage/dev-utils": "^0.1.3", + "@backstage/test-utils": "^0.1.2", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", From bc0f47b7b795a09fa2dd955d4aba844b529edcf4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 19 Nov 2020 15:13:57 +0000 Subject: [PATCH 080/430] Version Packages --- .changeset/add-app-backend.md | 104 ----------------- .changeset/add-config-schema-declarations.md | 13 --- .changeset/add-config-schema-support.md | 27 ----- .changeset/app-backend-config.md | 7 -- .../cost-insights-strange-rings-smile.md | 5 - .../cost-insights-ten-carpets-return.md | 5 - .changeset/cost-insights-wet-plants-glow.md | 6 - .changeset/cost-insights-wild-cars-wait.md | 5 - .changeset/friendly-dodos-remember.md | 5 - .changeset/hip-experts-dance.md | 5 - .changeset/loud-pets-clap.md | 5 - .changeset/modern-boats-knock.md | 5 - .changeset/ninety-gifts-guess.md | 6 - .changeset/odd-camels-begin.md | 5 - .changeset/shaggy-turkeys-warn.md | 5 - .changeset/silent-suns-bathe.md | 5 - packages/app/CHANGELOG.md | 24 ++++ packages/app/package.json | 22 ++-- packages/backend-common/CHANGELOG.md | 38 +++++++ packages/backend-common/package.json | 10 +- packages/backend/CHANGELOG.md | 25 +++++ packages/backend/package.json | 28 ++--- packages/catalog-client/CHANGELOG.md | 7 ++ packages/catalog-client/package.json | 4 +- packages/catalog-model/package.json | 2 +- packages/cli/CHANGELOG.md | 33 ++++++ packages/cli/package.json | 12 +- packages/config-loader/CHANGELOG.md | 26 +++++ packages/config-loader/package.json | 2 +- packages/core-api/package.json | 4 +- packages/core/CHANGELOG.md | 6 + packages/core/package.json | 6 +- packages/create-app/CHANGELOG.md | 105 ++++++++++++++++++ packages/create-app/package.json | 32 +++--- packages/dev-utils/CHANGELOG.md | 11 ++ packages/dev-utils/package.json | 8 +- packages/integration/CHANGELOG.md | 7 ++ packages/integration/package.json | 4 +- packages/test-utils/CHANGELOG.md | 9 ++ packages/test-utils/package.json | 4 +- packages/theme/package.json | 2 +- plugins/api-docs/package.json | 10 +- plugins/app-backend/CHANGELOG.md | 17 +++ plugins/app-backend/package.json | 8 +- plugins/auth-backend/CHANGELOG.md | 12 ++ plugins/auth-backend/package.json | 8 +- plugins/catalog-backend/CHANGELOG.md | 11 ++ plugins/catalog-backend/package.json | 8 +- plugins/catalog-graphql/CHANGELOG.md | 10 ++ plugins/catalog-graphql/package.json | 8 +- plugins/catalog/CHANGELOG.md | 11 ++ plugins/catalog/package.json | 14 +-- plugins/circleci/package.json | 10 +- plugins/cloudbuild/package.json | 10 +- plugins/cost-insights/CHANGELOG.md | 17 +++ plugins/cost-insights/package.json | 12 +- plugins/explore/package.json | 8 +- plugins/gcp-projects/package.json | 8 +- plugins/github-actions/package.json | 10 +- plugins/gitops-profiles/package.json | 8 +- plugins/graphiql/package.json | 8 +- plugins/graphql/CHANGELOG.md | 11 ++ plugins/graphql/package.json | 8 +- plugins/jenkins/package.json | 10 +- plugins/kubernetes-backend/CHANGELOG.md | 10 ++ plugins/kubernetes-backend/package.json | 6 +- plugins/kubernetes/package.json | 10 +- plugins/lighthouse/CHANGELOG.md | 10 ++ plugins/lighthouse/package.json | 12 +- plugins/newrelic/package.json | 8 +- plugins/proxy-backend/CHANGELOG.md | 10 ++ plugins/proxy-backend/package.json | 6 +- plugins/register-component/package.json | 10 +- plugins/rollbar-backend/CHANGELOG.md | 10 ++ plugins/rollbar-backend/package.json | 6 +- plugins/rollbar/CHANGELOG.md | 10 ++ plugins/rollbar/package.json | 12 +- plugins/scaffolder-backend/CHANGELOG.md | 11 ++ plugins/scaffolder-backend/package.json | 6 +- plugins/scaffolder/package.json | 10 +- plugins/search/package.json | 10 +- plugins/sentry-backend/CHANGELOG.md | 10 ++ plugins/sentry-backend/package.json | 6 +- plugins/sentry/CHANGELOG.md | 8 ++ plugins/sentry/package.json | 10 +- plugins/sonarqube/package.json | 8 +- plugins/tech-radar/package.json | 10 +- plugins/techdocs-backend/CHANGELOG.md | 10 ++ plugins/techdocs-backend/package.json | 6 +- plugins/techdocs/CHANGELOG.md | 11 ++ plugins/techdocs/package.json | 14 +-- plugins/user-settings/CHANGELOG.md | 8 ++ plugins/user-settings/package.json | 10 +- plugins/welcome/package.json | 8 +- 94 files changed, 716 insertions(+), 441 deletions(-) delete mode 100644 .changeset/add-app-backend.md delete mode 100644 .changeset/add-config-schema-declarations.md delete mode 100644 .changeset/add-config-schema-support.md delete mode 100644 .changeset/app-backend-config.md delete mode 100644 .changeset/cost-insights-strange-rings-smile.md delete mode 100644 .changeset/cost-insights-ten-carpets-return.md delete mode 100644 .changeset/cost-insights-wet-plants-glow.md delete mode 100644 .changeset/cost-insights-wild-cars-wait.md delete mode 100644 .changeset/friendly-dodos-remember.md delete mode 100644 .changeset/hip-experts-dance.md delete mode 100644 .changeset/loud-pets-clap.md delete mode 100644 .changeset/modern-boats-knock.md delete mode 100644 .changeset/ninety-gifts-guess.md delete mode 100644 .changeset/odd-camels-begin.md delete mode 100644 .changeset/shaggy-turkeys-warn.md delete mode 100644 .changeset/silent-suns-bathe.md create mode 100644 packages/catalog-client/CHANGELOG.md create mode 100644 packages/integration/CHANGELOG.md diff --git a/.changeset/add-app-backend.md b/.changeset/add-app-backend.md deleted file mode 100644 index e44a56c885..0000000000 --- a/.changeset/add-app-backend.md +++ /dev/null @@ -1,104 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Add `app-backend` as a backend plugin, and make a single docker build of the backend the default way to deploy backstage. - -Note that the `app-backend` currently only is a solution for deployments of the app, it's not a dev server and is not intended for local development. - -## Template changes - -As a part of installing the `app-backend` plugin, the below changes where made. The changes are grouped into two steps, installing the plugin, and updating the Docker build and configuration. - -### Installing the `app-backend` plugin in the backend - -First, install the `@backstage/plugin-app-backend` plugin package in your backend. These changes where made for `v0.3.0` of the plugin, and the installation process might change in the future. Run the following from the root of the repo: - -```bash -cd packages/backend -yarn add @backstage/plugin-app-backend -``` - -For the `app-backend` to get access to the static content in the frontend we also need to add the local `app` package as a dependency. Add the following to your `"dependencies"` in `packages/backend/package.json`, assuming your app package is still named `app` and on version `0.0.0`: - -```json -"app": "0.0.0", -``` - -Don't worry, this will not cause your entire frontend dependency tree to be added to the app, just double check that `packages/app/package.json` has a `"bundled": true` field at top-level. This signals to the backend build process that the package is bundled and that no transitive dependencies should be included. - -Next, create `packages/backend/src/plugins/app.ts` with the following: - -```ts -import { createRouter } from '@backstage/plugin-app-backend'; -import { PluginEnvironment } from '../types'; - -export default async function createPlugin({ - logger, - config, -}: PluginEnvironment) { - return await createRouter({ - logger, - config, - appPackageName: 'app', - }); -} -``` - -In `packages/backend/src/index.ts`, make the following changes: - -Add an import for the newly created plugin setup file: - -```ts -import app from './plugins/app'; -``` - -Setup the following plugin env. - -```ts -const appEnv = useHotMemoize(module, () => createEnv('app')); -``` - -Change service builder setup to include the `app` plugin as follows. Note that the `app` plugin is not installed on the `/api` route with most other plugins. - -```ts -const service = createServiceBuilder(module) - .loadConfig(config) - .addRouter('/api', apiRouter) - .addRouter('', await app(appEnv)); -``` - -You should now have the `app-backend` plugin installed in your backend, ready to serve the frontend bundle! - -### Docker build setup - -Since the backend image is now the only one needed for a simple Backstage deployment, the image tag name in the `build-image` script inside `packages/backend/package.json` was changed to the following: - -```json -"build-image": "backstage-cli backend:build-image --build --tag backstage", -``` - -For convenience, a `build-image` script was also added to the root `package.json` with the following: - -```json -"build-image": "yarn workspace backend build-image", -``` - -In the root of the repo, a new `app-config.production.yaml` file was added. This is used to set the appropriate `app.baseUrl` now that the frontend is served directly by the backend in the production deployment. It has the following contents: - -```yaml -app: - # Should be the same as backend.baseUrl when using the `app-backend` plugin - baseUrl: http://localhost:7000 - -backend: - baseUrl: http://localhost:7000 - listen: - port: 7000 -``` - -In order to load in the new configuration at runtime, the command in the `Dockerfile` at the repo root was changed to the following: - -```dockerfile -CMD ["node", "packages/backend", "--config", "app-config.yaml", "--config", "app-config.production.yaml"] -``` diff --git a/.changeset/add-config-schema-declarations.md b/.changeset/add-config-schema-declarations.md deleted file mode 100644 index 2f52a2ad3f..0000000000 --- a/.changeset/add-config-schema-declarations.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -'@backstage/backend-common': patch -'@backstage/cli': patch -'@backstage/core': patch -'@backstage/plugin-cost-insights': patch -'@backstage/plugin-lighthouse': patch -'@backstage/plugin-rollbar': patch -'@backstage/plugin-sentry': patch -'@backstage/plugin-techdocs': patch -'@backstage/plugin-user-settings': patch ---- - -Added configuration schema diff --git a/.changeset/add-config-schema-support.md b/.changeset/add-config-schema-support.md deleted file mode 100644 index 300c75f651..0000000000 --- a/.changeset/add-config-schema-support.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -'@backstage/backend-common': minor -'@backstage/cli': minor -'@backstage/config-loader': minor ---- - -Added support for loading and validating configuration schemas, as well as declaring config visibility through schemas. - -The new `loadConfigSchema` function exported by `@backstage/config-loader` allows for the collection and merging of configuration schemas from all nearby dependencies of the project. - -A configuration schema is declared using the `https://backstage.io/schema/config-v1` JSON Schema meta schema, which is based on draft07. The only difference to the draft07 schema is the custom `visibility` keyword, which is used to indicate whether the given config value should be visible in the frontend or not. The possible values are `frontend`, `backend`, and `secret`, where `backend` is the default. A visibility of `secret` has the same scope at runtime, but it will be treated with more care in certain contexts, and defining both `frontend` and `secret` for the same value in two different schemas will result in an error during schema merging. - -Packages that wish to contribute configuration schema should declare it in a root `"configSchema"` field in `package.json`. The field can either contain an inlined JSON schema, or a relative path to a schema file. Schema files can be in either `.json` or `.d.ts` format. - -TypeScript configuration schema files should export a single `Config` type, for example: - -```ts -export interface Config { - app: { - /** - * Frontend root URL - * @visibility frontend - */ - baseUrl: string; - }; -} -``` diff --git a/.changeset/app-backend-config.md b/.changeset/app-backend-config.md deleted file mode 100644 index 39b0b956ad..0000000000 --- a/.changeset/app-backend-config.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/plugin-app-backend': minor ---- - -Use new config schema support to automatically inject config with frontend visibility, in addition to the existing env schema injection. - -This removes the confusing behavior where configuration was only injected into the app at build time. Any runtime configuration (except for environment config) in the backend used to only apply to the backend itself, and not be injected into the frontend. diff --git a/.changeset/cost-insights-strange-rings-smile.md b/.changeset/cost-insights-strange-rings-smile.md deleted file mode 100644 index c5f4ff9b73..0000000000 --- a/.changeset/cost-insights-strange-rings-smile.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-cost-insights': patch ---- - -remove excessive margin from cost overview banner diff --git a/.changeset/cost-insights-ten-carpets-return.md b/.changeset/cost-insights-ten-carpets-return.md deleted file mode 100644 index f378a20eab..0000000000 --- a/.changeset/cost-insights-ten-carpets-return.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-cost-insights': minor ---- - -remove cost insights currency feature flag diff --git a/.changeset/cost-insights-wet-plants-glow.md b/.changeset/cost-insights-wet-plants-glow.md deleted file mode 100644 index 3d2be289d4..0000000000 --- a/.changeset/cost-insights-wet-plants-glow.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-cost-insights': patch ---- - -UI improvements: Increase width of first column in product entity dialog table -UI improvement: Display full cost amount in product entity dialog table diff --git a/.changeset/cost-insights-wild-cars-wait.md b/.changeset/cost-insights-wild-cars-wait.md deleted file mode 100644 index 24f84dc56b..0000000000 --- a/.changeset/cost-insights-wild-cars-wait.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-cost-insights': patch ---- - -Fix savings/excess display calculation diff --git a/.changeset/friendly-dodos-remember.md b/.changeset/friendly-dodos-remember.md deleted file mode 100644 index 463d71f806..0000000000 --- a/.changeset/friendly-dodos-remember.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog': patch ---- - -Add About Card tooltips diff --git a/.changeset/hip-experts-dance.md b/.changeset/hip-experts-dance.md deleted file mode 100644 index f88fe62cd4..0000000000 --- a/.changeset/hip-experts-dance.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': patch ---- - -An entity A, that exists in the catalog, can no longer be overwritten by registering a different location that also tries to supply an entity with the same kind+namespace+name. Writes of that new entity will instead be rejected with a log message similar to `Rejecting write of entity Component:default/artist-lookup from file:/Users/freben/dev/github/backstage/packages/catalog-model/examples/components/artist-lookup-component.yaml because entity existed from github:https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/components/artist-lookup-component.yaml` diff --git a/.changeset/loud-pets-clap.md b/.changeset/loud-pets-clap.md deleted file mode 100644 index 0d50c066f9..0000000000 --- a/.changeset/loud-pets-clap.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'example-app': patch ---- - -Removed obsolete CircleCI proxy config from example-app diff --git a/.changeset/modern-boats-knock.md b/.changeset/modern-boats-knock.md deleted file mode 100644 index 16f80fe041..0000000000 --- a/.changeset/modern-boats-knock.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Support specifying listen host/port for frontend diff --git a/.changeset/ninety-gifts-guess.md b/.changeset/ninety-gifts-guess.md deleted file mode 100644 index bdd20322ec..0000000000 --- a/.changeset/ninety-gifts-guess.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/backend-common': patch -'@backstage/integration': patch ---- - -Added the integration package diff --git a/.changeset/odd-camels-begin.md b/.changeset/odd-camels-begin.md deleted file mode 100644 index d28d729609..0000000000 --- a/.changeset/odd-camels-begin.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-common': minor ---- - -Refactored UrlReader.readTree to be required and accept (url, options) diff --git a/.changeset/shaggy-turkeys-warn.md b/.changeset/shaggy-turkeys-warn.md deleted file mode 100644 index 442cc788f2..0000000000 --- a/.changeset/shaggy-turkeys-warn.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/catalog-client': minor ---- - -Changed the getEntities interface to (1) nest parameters in an object, (2) support field selection, and (3) return an object with an items field for future extension diff --git a/.changeset/silent-suns-bathe.md b/.changeset/silent-suns-bathe.md deleted file mode 100644 index 96e32ea125..0000000000 --- a/.changeset/silent-suns-bathe.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': patch ---- - -Extracted pushToRemote function for reuse between publishers diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index e24e7393ec..ab00924a26 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,29 @@ # example-app +## 0.2.2 + +### Patch Changes + +- 3efd03c0e: Removed obsolete CircleCI proxy config from example-app +- Updated dependencies [1722cb53c] +- Updated dependencies [1722cb53c] +- Updated dependencies [17a9f48f6] +- Updated dependencies [4040d4fcb] +- Updated dependencies [f360395d0] +- Updated dependencies [259d848ee] +- Updated dependencies [8b7737d0b] +- Updated dependencies [902340451] + - @backstage/cli@0.3.0 + - @backstage/core@0.3.1 + - @backstage/plugin-cost-insights@0.4.0 + - @backstage/plugin-lighthouse@0.2.2 + - @backstage/plugin-rollbar@0.2.2 + - @backstage/plugin-sentry@0.2.2 + - @backstage/plugin-techdocs@0.2.2 + - @backstage/plugin-user-settings@0.2.2 + - @backstage/plugin-catalog@0.2.2 + - @backstage/test-utils@0.1.3 + ## 0.2.1 ### Patch Changes diff --git a/packages/app/package.json b/packages/app/package.json index 61b4f7a264..529c5d4f63 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,17 +1,17 @@ { "name": "example-app", - "version": "0.2.1", + "version": "0.2.2", "private": true, "bundled": true, "dependencies": { "@backstage/catalog-model": "^0.2.0", - "@backstage/cli": "^0.2.0", - "@backstage/core": "^0.3.0", + "@backstage/cli": "^0.3.0", + "@backstage/core": "^0.3.1", "@backstage/plugin-api-docs": "^0.2.1", - "@backstage/plugin-catalog": "^0.2.1", + "@backstage/plugin-catalog": "^0.2.2", "@backstage/plugin-circleci": "^0.2.1", "@backstage/plugin-cloudbuild": "^0.2.1", - "@backstage/plugin-cost-insights": "^0.3.0", + "@backstage/plugin-cost-insights": "^0.4.0", "@backstage/plugin-explore": "^0.2.1", "@backstage/plugin-gcp-projects": "^0.2.1", "@backstage/plugin-github-actions": "^0.2.1", @@ -19,18 +19,18 @@ "@backstage/plugin-graphiql": "^0.2.1", "@backstage/plugin-jenkins": "^0.3.0", "@backstage/plugin-kubernetes": "^0.2.1", - "@backstage/plugin-lighthouse": "^0.2.1", + "@backstage/plugin-lighthouse": "^0.2.2", "@backstage/plugin-newrelic": "^0.2.1", "@backstage/plugin-register-component": "^0.2.1", - "@backstage/plugin-rollbar": "^0.2.1", + "@backstage/plugin-rollbar": "^0.2.2", "@backstage/plugin-scaffolder": "^0.3.0", - "@backstage/plugin-sentry": "^0.2.1", + "@backstage/plugin-sentry": "^0.2.2", "@backstage/plugin-search": "^0.2.0", "@backstage/plugin-tech-radar": "^0.3.0", - "@backstage/plugin-techdocs": "^0.2.1", - "@backstage/plugin-user-settings": "^0.2.1", + "@backstage/plugin-techdocs": "^0.2.2", + "@backstage/plugin-user-settings": "^0.2.2", "@backstage/plugin-welcome": "^0.2.1", - "@backstage/test-utils": "^0.1.2", + "@backstage/test-utils": "^0.1.3", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", diff --git a/packages/backend-common/CHANGELOG.md b/packages/backend-common/CHANGELOG.md index 0f18908932..4822032c7f 100644 --- a/packages/backend-common/CHANGELOG.md +++ b/packages/backend-common/CHANGELOG.md @@ -1,5 +1,43 @@ # @backstage/backend-common +## 0.3.0 + +### Minor Changes + +- 1722cb53c: Added support for loading and validating configuration schemas, as well as declaring config visibility through schemas. + + The new `loadConfigSchema` function exported by `@backstage/config-loader` allows for the collection and merging of configuration schemas from all nearby dependencies of the project. + + A configuration schema is declared using the `https://backstage.io/schema/config-v1` JSON Schema meta schema, which is based on draft07. The only difference to the draft07 schema is the custom `visibility` keyword, which is used to indicate whether the given config value should be visible in the frontend or not. The possible values are `frontend`, `backend`, and `secret`, where `backend` is the default. A visibility of `secret` has the same scope at runtime, but it will be treated with more care in certain contexts, and defining both `frontend` and `secret` for the same value in two different schemas will result in an error during schema merging. + + Packages that wish to contribute configuration schema should declare it in a root `"configSchema"` field in `package.json`. The field can either contain an inlined JSON schema, or a relative path to a schema file. Schema files can be in either `.json` or `.d.ts` format. + + TypeScript configuration schema files should export a single `Config` type, for example: + + ```ts + export interface Config { + app: { + /** + * Frontend root URL + * @visibility frontend + */ + baseUrl: string; + }; + } + ``` + +- 8e2effb53: Refactored UrlReader.readTree to be required and accept (url, options) + +### Patch Changes + +- 1722cb53c: Added configuration schema +- 7b37e6834: Added the integration package +- Updated dependencies [1722cb53c] +- Updated dependencies [7b37e6834] + - @backstage/config-loader@0.3.0 + - @backstage/integration@0.1.1 + - @backstage/test-utils@0.1.3 + ## 0.2.1 ### Patch Changes diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 737e9664bf..89150302b5 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-common", "description": "Common functionality library for Backstage backends", - "version": "0.2.1", + "version": "0.3.0", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -31,9 +31,9 @@ "dependencies": { "@backstage/cli-common": "^0.1.1", "@backstage/config": "^0.1.1", - "@backstage/config-loader": "^0.2.0", - "@backstage/integration": "^0.1.0", - "@backstage/test-utils": "^0.1.2", + "@backstage/config-loader": "^0.3.0", + "@backstage/integration": "^0.1.1", + "@backstage/test-utils": "^0.1.3", "@types/cors": "^2.8.6", "@types/express": "^4.17.6", "compression": "^1.7.4", @@ -66,7 +66,7 @@ } }, "devDependencies": { - "@backstage/cli": "^0.2.0", + "@backstage/cli": "^0.3.0", "@types/compression": "^1.7.0", "@types/concat-stream": "^1.6.0", "@types/fs-extra": "^9.0.3", diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index 2fe72cb101..a8bc1fef7d 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,30 @@ # example-backend +## 0.2.2 + +### Patch Changes + +- Updated dependencies [1722cb53c] +- Updated dependencies [1722cb53c] +- Updated dependencies [1722cb53c] +- Updated dependencies [f531d307c] +- Updated dependencies [3efd03c0e] +- Updated dependencies [7b37e6834] +- Updated dependencies [8e2effb53] +- Updated dependencies [d33f5157c] + - @backstage/backend-common@0.3.0 + - @backstage/plugin-app-backend@0.3.0 + - @backstage/plugin-catalog-backend@0.2.1 + - example-app@0.2.2 + - @backstage/plugin-scaffolder-backend@0.3.1 + - @backstage/plugin-auth-backend@0.2.2 + - @backstage/plugin-graphql-backend@0.1.3 + - @backstage/plugin-kubernetes-backend@0.1.3 + - @backstage/plugin-proxy-backend@0.2.1 + - @backstage/plugin-rollbar-backend@0.1.3 + - @backstage/plugin-sentry-backend@0.1.3 + - @backstage/plugin-techdocs-backend@0.2.1 + ## 0.2.1 ### Patch Changes diff --git a/packages/backend/package.json b/packages/backend/package.json index e67cadb44e..014a3f78d5 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.2.1", + "version": "0.2.2", "main": "dist/index.cjs.js", "types": "src/index.ts", "private": true, @@ -18,24 +18,24 @@ "migrate:create": "knex migrate:make -x ts" }, "dependencies": { - "@backstage/backend-common": "^0.2.1", + "@backstage/backend-common": "^0.3.0", "@backstage/catalog-model": "^0.2.0", "@backstage/config": "^0.1.1", - "@backstage/plugin-app-backend": "^0.2.0", - "@backstage/plugin-auth-backend": "^0.2.1", - "@backstage/plugin-catalog-backend": "^0.2.0", - "@backstage/plugin-graphql-backend": "^0.1.2", - "@backstage/plugin-kubernetes-backend": "^0.1.2", - "@backstage/plugin-proxy-backend": "^0.2.0", - "@backstage/plugin-rollbar-backend": "^0.1.2", - "@backstage/plugin-scaffolder-backend": "^0.3.0", - "@backstage/plugin-sentry-backend": "^0.1.2", - "@backstage/plugin-techdocs-backend": "^0.2.0", + "@backstage/plugin-app-backend": "^0.3.0", + "@backstage/plugin-auth-backend": "^0.2.2", + "@backstage/plugin-catalog-backend": "^0.2.1", + "@backstage/plugin-graphql-backend": "^0.1.3", + "@backstage/plugin-kubernetes-backend": "^0.1.3", + "@backstage/plugin-proxy-backend": "^0.2.1", + "@backstage/plugin-rollbar-backend": "^0.1.3", + "@backstage/plugin-scaffolder-backend": "^0.3.1", + "@backstage/plugin-sentry-backend": "^0.1.3", + "@backstage/plugin-techdocs-backend": "^0.2.1", "@gitbeaker/node": "^25.2.0", "@octokit/rest": "^18.0.0", "azure-devops-node-api": "^10.1.1", "dockerode": "^3.2.0", - "example-app": "^0.2.1", + "example-app": "^0.2.2", "express": "^4.17.1", "express-promise-router": "^3.0.3", "knex": "^0.21.6", @@ -45,7 +45,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.2.0", + "@backstage/cli": "^0.3.0", "@types/dockerode": "^2.5.32", "@types/express": "^4.17.6", "@types/express-serve-static-core": "^4.17.5", diff --git a/packages/catalog-client/CHANGELOG.md b/packages/catalog-client/CHANGELOG.md new file mode 100644 index 0000000000..574cd301bf --- /dev/null +++ b/packages/catalog-client/CHANGELOG.md @@ -0,0 +1,7 @@ +# @backstage/catalog-client + +## 0.3.0 + +### Minor Changes + +- 717e43de1: Changed the getEntities interface to (1) nest parameters in an object, (2) support field selection, and (3) return an object with an items field for future extension diff --git a/packages/catalog-client/package.json b/packages/catalog-client/package.json index c4aa632ca9..2482f9579d 100644 --- a/packages/catalog-client/package.json +++ b/packages/catalog-client/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/catalog-client", - "version": "0.2.0", + "version": "0.3.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -25,7 +25,7 @@ "cross-fetch": "^3.0.6" }, "devDependencies": { - "@backstage/cli": "^0.2.0", + "@backstage/cli": "^0.3.0", "@types/jest": "^26.0.7", "msw": "^0.21.2" }, diff --git a/packages/catalog-model/package.json b/packages/catalog-model/package.json index 47d047fe9a..e712e66f58 100644 --- a/packages/catalog-model/package.json +++ b/packages/catalog-model/package.json @@ -29,7 +29,7 @@ "yup": "^0.29.3" }, "devDependencies": { - "@backstage/cli": "^0.2.0", + "@backstage/cli": "^0.3.0", "@types/express": "^4.17.6", "@types/jest": "^26.0.7", "@types/lodash": "^4.14.151", diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 387f5e416f..a5bcc6bcfd 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,38 @@ # @backstage/cli +## 0.3.0 + +### Minor Changes + +- 1722cb53c: Added support for loading and validating configuration schemas, as well as declaring config visibility through schemas. + + The new `loadConfigSchema` function exported by `@backstage/config-loader` allows for the collection and merging of configuration schemas from all nearby dependencies of the project. + + A configuration schema is declared using the `https://backstage.io/schema/config-v1` JSON Schema meta schema, which is based on draft07. The only difference to the draft07 schema is the custom `visibility` keyword, which is used to indicate whether the given config value should be visible in the frontend or not. The possible values are `frontend`, `backend`, and `secret`, where `backend` is the default. A visibility of `secret` has the same scope at runtime, but it will be treated with more care in certain contexts, and defining both `frontend` and `secret` for the same value in two different schemas will result in an error during schema merging. + + Packages that wish to contribute configuration schema should declare it in a root `"configSchema"` field in `package.json`. The field can either contain an inlined JSON schema, or a relative path to a schema file. Schema files can be in either `.json` or `.d.ts` format. + + TypeScript configuration schema files should export a single `Config` type, for example: + + ```ts + export interface Config { + app: { + /** + * Frontend root URL + * @visibility frontend + */ + baseUrl: string; + }; + } + ``` + +### Patch Changes + +- 1722cb53c: Added configuration schema +- 902340451: Support specifying listen host/port for frontend +- Updated dependencies [1722cb53c] + - @backstage/config-loader@0.3.0 + ## 0.2.0 ### Minor Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index 516e354012..ec3aaacdee 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/cli", "description": "CLI for developing Backstage plugins and apps", - "version": "0.2.0", + "version": "0.3.0", "private": false, "publishConfig": { "access": "public" @@ -30,7 +30,7 @@ "dependencies": { "@backstage/cli-common": "^0.1.1", "@backstage/config": "^0.1.1", - "@backstage/config-loader": "^0.2.0", + "@backstage/config-loader": "^0.3.0", "@hot-loader/react-dom": "^16.13.0", "@lerna/package-graph": "^3.18.5", "@lerna/project": "^3.18.0", @@ -109,11 +109,11 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/backend-common": "^0.2.1", + "@backstage/backend-common": "^0.3.0", "@backstage/config": "^0.1.1", - "@backstage/core": "^0.3.0", - "@backstage/dev-utils": "^0.1.3", - "@backstage/test-utils": "^0.1.2", + "@backstage/core": "^0.3.1", + "@backstage/dev-utils": "^0.1.4", + "@backstage/test-utils": "^0.1.3", "@backstage/theme": "^0.2.1", "@types/diff": "^4.0.2", "@types/fs-extra": "^9.0.1", diff --git a/packages/config-loader/CHANGELOG.md b/packages/config-loader/CHANGELOG.md index 9c6bee077f..729dc81206 100644 --- a/packages/config-loader/CHANGELOG.md +++ b/packages/config-loader/CHANGELOG.md @@ -1,5 +1,31 @@ # @backstage/config-loader +## 0.3.0 + +### Minor Changes + +- 1722cb53c: Added support for loading and validating configuration schemas, as well as declaring config visibility through schemas. + + The new `loadConfigSchema` function exported by `@backstage/config-loader` allows for the collection and merging of configuration schemas from all nearby dependencies of the project. + + A configuration schema is declared using the `https://backstage.io/schema/config-v1` JSON Schema meta schema, which is based on draft07. The only difference to the draft07 schema is the custom `visibility` keyword, which is used to indicate whether the given config value should be visible in the frontend or not. The possible values are `frontend`, `backend`, and `secret`, where `backend` is the default. A visibility of `secret` has the same scope at runtime, but it will be treated with more care in certain contexts, and defining both `frontend` and `secret` for the same value in two different schemas will result in an error during schema merging. + + Packages that wish to contribute configuration schema should declare it in a root `"configSchema"` field in `package.json`. The field can either contain an inlined JSON schema, or a relative path to a schema file. Schema files can be in either `.json` or `.d.ts` format. + + TypeScript configuration schema files should export a single `Config` type, for example: + + ```ts + export interface Config { + app: { + /** + * Frontend root URL + * @visibility frontend + */ + baseUrl: string; + }; + } + ``` + ## 0.2.0 ### Minor Changes diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json index 98583cf833..1584a61df5 100644 --- a/packages/config-loader/package.json +++ b/packages/config-loader/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/config-loader", "description": "Config loading functionality used by Backstage backend, and CLI", - "version": "0.2.0", + "version": "0.3.0", "private": false, "publishConfig": { "access": "public", diff --git a/packages/core-api/package.json b/packages/core-api/package.json index 98eaa0fa61..212d4c9046 100644 --- a/packages/core-api/package.json +++ b/packages/core-api/package.json @@ -30,7 +30,7 @@ }, "dependencies": { "@backstage/config": "^0.1.1", - "@backstage/test-utils": "^0.1.2", + "@backstage/test-utils": "^0.1.3", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -42,7 +42,7 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.2.0", + "@backstage/cli": "^0.3.0", "@backstage/test-utils-core": "^0.1.1", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 435b344a6a..213e2b5246 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/core +## 0.3.1 + +### Patch Changes + +- 1722cb53c: Added configuration schema + ## 0.3.0 ### Minor Changes diff --git a/packages/core/package.json b/packages/core/package.json index d0b623ea83..70c1a9e647 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core", "description": "Core API used by Backstage plugins and apps", - "version": "0.3.0", + "version": "0.3.1", "private": false, "publishConfig": { "access": "public", @@ -63,8 +63,8 @@ "remark-gfm": "^1.0.0" }, "devDependencies": { - "@backstage/cli": "^0.2.0", - "@backstage/test-utils": "^0.1.2", + "@backstage/cli": "^0.3.0", + "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index 94b0071e86..4526bd4733 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,110 @@ # @backstage/create-app +## 0.2.2 + +### Patch Changes + +- 7d7abd50c: Add `app-backend` as a backend plugin, and make a single docker build of the backend the default way to deploy backstage. + + Note that the `app-backend` currently only is a solution for deployments of the app, it's not a dev server and is not intended for local development. + + ## Template changes + + As a part of installing the `app-backend` plugin, the below changes where made. The changes are grouped into two steps, installing the plugin, and updating the Docker build and configuration. + + ### Installing the `app-backend` plugin in the backend + + First, install the `@backstage/plugin-app-backend` plugin package in your backend. These changes where made for `v0.3.0` of the plugin, and the installation process might change in the future. Run the following from the root of the repo: + + ```bash + cd packages/backend + yarn add @backstage/plugin-app-backend + ``` + + For the `app-backend` to get access to the static content in the frontend we also need to add the local `app` package as a dependency. Add the following to your `"dependencies"` in `packages/backend/package.json`, assuming your app package is still named `app` and on version `0.0.0`: + + ```json + "app": "0.0.0", + ``` + + Don't worry, this will not cause your entire frontend dependency tree to be added to the app, just double check that `packages/app/package.json` has a `"bundled": true` field at top-level. This signals to the backend build process that the package is bundled and that no transitive dependencies should be included. + + Next, create `packages/backend/src/plugins/app.ts` with the following: + + ```ts + import { createRouter } from '@backstage/plugin-app-backend'; + import { PluginEnvironment } from '../types'; + + export default async function createPlugin({ + logger, + config, + }: PluginEnvironment) { + return await createRouter({ + logger, + config, + appPackageName: 'app', + }); + } + ``` + + In `packages/backend/src/index.ts`, make the following changes: + + Add an import for the newly created plugin setup file: + + ```ts + import app from './plugins/app'; + ``` + + Setup the following plugin env. + + ```ts + const appEnv = useHotMemoize(module, () => createEnv('app')); + ``` + + Change service builder setup to include the `app` plugin as follows. Note that the `app` plugin is not installed on the `/api` route with most other plugins. + + ```ts + const service = createServiceBuilder(module) + .loadConfig(config) + .addRouter('/api', apiRouter) + .addRouter('', await app(appEnv)); + ``` + + You should now have the `app-backend` plugin installed in your backend, ready to serve the frontend bundle! + + ### Docker build setup + + Since the backend image is now the only one needed for a simple Backstage deployment, the image tag name in the `build-image` script inside `packages/backend/package.json` was changed to the following: + + ```json + "build-image": "backstage-cli backend:build-image --build --tag backstage", + ``` + + For convenience, a `build-image` script was also added to the root `package.json` with the following: + + ```json + "build-image": "yarn workspace backend build-image", + ``` + + In the root of the repo, a new `app-config.production.yaml` file was added. This is used to set the appropriate `app.baseUrl` now that the frontend is served directly by the backend in the production deployment. It has the following contents: + + ```yaml + app: + # Should be the same as backend.baseUrl when using the `app-backend` plugin + baseUrl: http://localhost:7000 + + backend: + baseUrl: http://localhost:7000 + listen: + port: 7000 + ``` + + In order to load in the new configuration at runtime, the command in the `Dockerfile` at the repo root was changed to the following: + + ```dockerfile + CMD ["node", "packages/backend", "--config", "app-config.yaml", "--config", "app-config.production.yaml"] + ``` + ## 0.2.1 ### Patch Changes diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 55cdcb1a2a..c01d4d3dbb 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/create-app", "description": "Create app package for Backstage", - "version": "0.2.1", + "version": "0.2.2", "private": false, "publishConfig": { "access": "public" @@ -37,30 +37,30 @@ "recursive-readdir": "^2.2.2" }, "devDependencies": { - "@backstage/backend-common": "^0.2.1", + "@backstage/backend-common": "^0.3.0", "@backstage/catalog-model": "^0.2.0", - "@backstage/cli": "^0.2.0", + "@backstage/cli": "^0.3.0", "@backstage/config": "^0.1.1", - "@backstage/core": "^0.3.0", + "@backstage/core": "^0.3.1", "@backstage/plugin-api-docs": "^0.2.1", - "@backstage/plugin-app-backend": "^0.2.0", - "@backstage/plugin-auth-backend": "^0.2.1", - "@backstage/plugin-catalog": "^0.2.1", - "@backstage/plugin-catalog-backend": "^0.2.0", + "@backstage/plugin-app-backend": "^0.3.0", + "@backstage/plugin-auth-backend": "^0.2.2", + "@backstage/plugin-catalog": "^0.2.2", + "@backstage/plugin-catalog-backend": "^0.2.1", "@backstage/plugin-circleci": "^0.2.1", "@backstage/plugin-explore": "^0.2.1", "@backstage/plugin-github-actions": "^0.2.1", - "@backstage/plugin-lighthouse": "^0.2.1", - "@backstage/plugin-proxy-backend": "^0.2.0", + "@backstage/plugin-lighthouse": "^0.2.2", + "@backstage/plugin-proxy-backend": "^0.2.1", "@backstage/plugin-register-component": "^0.2.1", - "@backstage/plugin-rollbar-backend": "^0.1.2", + "@backstage/plugin-rollbar-backend": "^0.1.3", "@backstage/plugin-scaffolder": "^0.3.0", - "@backstage/plugin-scaffolder-backend": "^0.3.0", + "@backstage/plugin-scaffolder-backend": "^0.3.1", "@backstage/plugin-tech-radar": "^0.3.0", - "@backstage/plugin-techdocs": "^0.2.1", - "@backstage/plugin-techdocs-backend": "^0.2.0", - "@backstage/plugin-user-settings": "^0.2.1", - "@backstage/test-utils": "^0.1.2", + "@backstage/plugin-techdocs": "^0.2.2", + "@backstage/plugin-techdocs-backend": "^0.2.1", + "@backstage/plugin-user-settings": "^0.2.2", + "@backstage/test-utils": "^0.1.3", "@backstage/theme": "^0.2.1", "@types/fs-extra": "^9.0.1", "@types/inquirer": "^7.3.1", diff --git a/packages/dev-utils/CHANGELOG.md b/packages/dev-utils/CHANGELOG.md index 274a61bacb..fd8b11bada 100644 --- a/packages/dev-utils/CHANGELOG.md +++ b/packages/dev-utils/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/dev-utils +## 0.1.4 + +### Patch Changes + +- Updated dependencies [1722cb53c] +- Updated dependencies [1722cb53c] +- Updated dependencies [902340451] + - @backstage/cli@0.3.0 + - @backstage/core@0.3.1 + - @backstage/test-utils@0.1.3 + ## 0.1.3 ### Patch Changes diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index cf25d2e296..bf2d230ba7 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/dev-utils", "description": "Utilities for developing Backstage plugins.", - "version": "0.1.3", + "version": "0.1.4", "private": false, "publishConfig": { "access": "public", @@ -29,9 +29,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/cli": "^0.2.0", - "@backstage/core": "^0.3.0", - "@backstage/test-utils": "^0.1.2", + "@backstage/cli": "^0.3.0", + "@backstage/core": "^0.3.1", + "@backstage/test-utils": "^0.1.3", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", diff --git a/packages/integration/CHANGELOG.md b/packages/integration/CHANGELOG.md new file mode 100644 index 0000000000..fde914fac7 --- /dev/null +++ b/packages/integration/CHANGELOG.md @@ -0,0 +1,7 @@ +# @backstage/integration + +## 0.1.1 + +### Patch Changes + +- 7b37e6834: Added the integration package diff --git a/packages/integration/package.json b/packages/integration/package.json index cfbdcbe57c..2bc65c46a3 100644 --- a/packages/integration/package.json +++ b/packages/integration/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/integration", - "version": "0.1.0", + "version": "0.1.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -24,7 +24,7 @@ "git-url-parse": "^11.4.0" }, "devDependencies": { - "@backstage/cli": "^0.2.0", + "@backstage/cli": "^0.3.0", "@types/jest": "^26.0.7" }, "files": [ diff --git a/packages/test-utils/CHANGELOG.md b/packages/test-utils/CHANGELOG.md index 0d71e2ddd3..b4bc2bbe76 100644 --- a/packages/test-utils/CHANGELOG.md +++ b/packages/test-utils/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/test-utils +## 0.1.3 + +### Patch Changes + +- Updated dependencies [1722cb53c] +- Updated dependencies [1722cb53c] +- Updated dependencies [902340451] + - @backstage/cli@0.3.0 + ## 0.1.2 ### Patch Changes diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index 9a7682c76b..a8f5cd1661 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/test-utils", "description": "Utilities to test Backstage plugins and apps.", - "version": "0.1.2", + "version": "0.1.3", "private": false, "publishConfig": { "access": "public", @@ -29,7 +29,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/cli": "^0.2.0", + "@backstage/cli": "^0.3.0", "@backstage/core-api": "^0.2.0", "@backstage/test-utils-core": "^0.1.1", "@backstage/theme": "^0.2.0", diff --git a/packages/theme/package.json b/packages/theme/package.json index f04ec4d589..68c70c656a 100644 --- a/packages/theme/package.json +++ b/packages/theme/package.json @@ -31,7 +31,7 @@ "@material-ui/core": "^4.11.0" }, "devDependencies": { - "@backstage/cli": "^0.2.0" + "@backstage/cli": "^0.3.0" }, "files": [ "dist" diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index 73764f70ee..bfefaef3a5 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -21,8 +21,8 @@ }, "dependencies": { "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.0", - "@backstage/plugin-catalog": "^0.2.1", + "@backstage/core": "^0.3.1", + "@backstage/plugin-catalog": "^0.2.2", "@backstage/theme": "^0.2.1", "@kyma-project/asyncapi-react": "^0.14.2", "@material-icons/font": "^1.0.2", @@ -39,9 +39,9 @@ "swagger-ui-react": "^3.31.1" }, "devDependencies": { - "@backstage/cli": "^0.2.0", - "@backstage/dev-utils": "^0.1.3", - "@backstage/test-utils": "^0.1.2", + "@backstage/cli": "^0.3.0", + "@backstage/dev-utils": "^0.1.4", + "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/app-backend/CHANGELOG.md b/plugins/app-backend/CHANGELOG.md index 03ecafd574..3555da1959 100644 --- a/plugins/app-backend/CHANGELOG.md +++ b/plugins/app-backend/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-app-backend +## 0.3.0 + +### Minor Changes + +- 1722cb53c: Use new config schema support to automatically inject config with frontend visibility, in addition to the existing env schema injection. + + This removes the confusing behavior where configuration was only injected into the app at build time. Any runtime configuration (except for environment config) in the backend used to only apply to the backend itself, and not be injected into the frontend. + +### Patch Changes + +- Updated dependencies [1722cb53c] +- Updated dependencies [1722cb53c] +- Updated dependencies [7b37e6834] +- Updated dependencies [8e2effb53] + - @backstage/backend-common@0.3.0 + - @backstage/config-loader@0.3.0 + ## 0.2.0 ### Minor Changes diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index 08266acb68..bf6167d46a 100644 --- a/plugins/app-backend/package.json +++ b/plugins/app-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-app-backend", - "version": "0.2.0", + "version": "0.3.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,8 +20,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.2.0", - "@backstage/config-loader": "^0.2.0", + "@backstage/backend-common": "^0.3.0", + "@backstage/config-loader": "^0.3.0", "@backstage/config": "^0.1.1", "@types/express": "^4.17.6", "express": "^4.17.1", @@ -31,7 +31,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.2.0", + "@backstage/cli": "^0.3.0", "@types/supertest": "^2.0.8", "msw": "^0.20.5", "supertest": "^4.0.2" diff --git a/plugins/auth-backend/CHANGELOG.md b/plugins/auth-backend/CHANGELOG.md index e38bc15fe9..546855c92f 100644 --- a/plugins/auth-backend/CHANGELOG.md +++ b/plugins/auth-backend/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-auth-backend +## 0.2.2 + +### Patch Changes + +- Updated dependencies [1722cb53c] +- Updated dependencies [1722cb53c] +- Updated dependencies [7b37e6834] +- Updated dependencies [8e2effb53] +- Updated dependencies [717e43de1] + - @backstage/backend-common@0.3.0 + - @backstage/catalog-client@0.3.0 + ## 0.2.1 ### Patch Changes diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 28ac51034e..8ce955350a 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend", - "version": "0.2.1", + "version": "0.2.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,8 +20,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.2.1", - "@backstage/catalog-client": "^0.2.0", + "@backstage/backend-common": "^0.3.0", + "@backstage/catalog-client": "^0.3.0", "@backstage/catalog-model": "^0.2.0", "@backstage/config": "^0.1.1", "@types/express": "^4.17.6", @@ -53,7 +53,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.2.0", + "@backstage/cli": "^0.3.0", "@types/body-parser": "^1.19.0", "@types/cookie-parser": "^1.4.2", "@types/jwt-decode": "2.2.1", diff --git a/plugins/catalog-backend/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md index e45da37691..1a3199ee14 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-catalog-backend +## 0.2.1 + +### Patch Changes + +- f531d307c: An entity A, that exists in the catalog, can no longer be overwritten by registering a different location that also tries to supply an entity with the same kind+namespace+name. Writes of that new entity will instead be rejected with a log message similar to `Rejecting write of entity Component:default/artist-lookup from file:/Users/freben/dev/github/backstage/packages/catalog-model/examples/components/artist-lookup-component.yaml because entity existed from github:https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/components/artist-lookup-component.yaml` +- Updated dependencies [1722cb53c] +- Updated dependencies [1722cb53c] +- Updated dependencies [7b37e6834] +- Updated dependencies [8e2effb53] + - @backstage/backend-common@0.3.0 + ## 0.2.0 ### Minor Changes diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 390c91551f..711a91dd8d 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend", - "version": "0.2.0", + "version": "0.2.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,7 +20,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.2.0", + "@backstage/backend-common": "^0.3.0", "@backstage/catalog-model": "^0.2.0", "@backstage/config": "^0.1.1", "@octokit/graphql": "^4.5.6", @@ -45,8 +45,8 @@ "yup": "^0.29.3" }, "devDependencies": { - "@backstage/cli": "^0.2.0", - "@backstage/test-utils": "^0.1.2", + "@backstage/cli": "^0.3.0", + "@backstage/test-utils": "^0.1.3", "@types/core-js": "^2.5.4", "@types/git-url-parse": "^9.0.0", "@types/ldapjs": "^1.0.9", diff --git a/plugins/catalog-graphql/CHANGELOG.md b/plugins/catalog-graphql/CHANGELOG.md index 30414d6998..35737b7dd1 100644 --- a/plugins/catalog-graphql/CHANGELOG.md +++ b/plugins/catalog-graphql/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-graphql +## 0.2.1 + +### Patch Changes + +- Updated dependencies [1722cb53c] +- Updated dependencies [1722cb53c] +- Updated dependencies [7b37e6834] +- Updated dependencies [8e2effb53] + - @backstage/backend-common@0.3.0 + ## 0.2.0 ### Minor Changes diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json index aaa5faac36..c55be89b62 100644 --- a/plugins/catalog-graphql/package.json +++ b/plugins/catalog-graphql/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-graphql", - "version": "0.2.0", + "version": "0.2.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,7 +20,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.2.0", + "@backstage/backend-common": "^0.3.0", "@backstage/catalog-model": "^0.2.0", "@backstage/config": "^0.1.1", "@graphql-modules/core": "^0.7.17", @@ -32,8 +32,8 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.2.0", - "@backstage/test-utils": "^0.1.2", + "@backstage/cli": "^0.3.0", + "@backstage/test-utils": "^0.1.3", "@graphql-codegen/cli": "^1.17.7", "@graphql-codegen/typescript": "^1.17.7", "@graphql-codegen/typescript-resolvers": "^1.17.7", diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index 3e808478c8..1b8b884f1c 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-catalog +## 0.2.2 + +### Patch Changes + +- 8b7737d0b: Add About Card tooltips +- Updated dependencies [1722cb53c] +- Updated dependencies [717e43de1] + - @backstage/core@0.3.1 + - @backstage/plugin-techdocs@0.2.2 + - @backstage/catalog-client@0.3.0 + ## 0.2.1 ### Patch Changes diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 02ab709f79..9ee5d68270 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog", - "version": "0.2.1", + "version": "0.2.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,11 +21,11 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-client": "^0.2.0", + "@backstage/catalog-client": "^0.3.0", "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.0", + "@backstage/core": "^0.3.1", "@backstage/plugin-scaffolder": "^0.3.0", - "@backstage/plugin-techdocs": "^0.2.1", + "@backstage/plugin-techdocs": "^0.2.2", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -43,9 +43,9 @@ "swr": "^0.3.0" }, "devDependencies": { - "@backstage/cli": "^0.2.0", - "@backstage/dev-utils": "^0.1.3", - "@backstage/test-utils": "^0.1.2", + "@backstage/cli": "^0.3.0", + "@backstage/dev-utils": "^0.1.4", + "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/react-hooks": "^3.3.0", diff --git a/plugins/circleci/package.json b/plugins/circleci/package.json index fa31fe90f7..c9ad54659f 100644 --- a/plugins/circleci/package.json +++ b/plugins/circleci/package.json @@ -22,8 +22,8 @@ }, "dependencies": { "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.0", - "@backstage/plugin-catalog": "^0.2.1", + "@backstage/core": "^0.3.1", + "@backstage/plugin-catalog": "^0.2.2", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -38,9 +38,9 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.2.0", - "@backstage/dev-utils": "^0.1.3", - "@backstage/test-utils": "^0.1.2", + "@backstage/cli": "^0.3.0", + "@backstage/dev-utils": "^0.1.4", + "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/cloudbuild/package.json b/plugins/cloudbuild/package.json index cb7dab8e91..c0b36ef1e7 100644 --- a/plugins/cloudbuild/package.json +++ b/plugins/cloudbuild/package.json @@ -21,8 +21,8 @@ }, "dependencies": { "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.0", - "@backstage/plugin-catalog": "^0.2.1", + "@backstage/core": "^0.3.1", + "@backstage/plugin-catalog": "^0.2.2", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -39,9 +39,9 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.2.0", - "@backstage/dev-utils": "^0.1.3", - "@backstage/test-utils": "^0.1.2", + "@backstage/cli": "^0.3.0", + "@backstage/dev-utils": "^0.1.4", + "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/cost-insights/CHANGELOG.md b/plugins/cost-insights/CHANGELOG.md index cb7b2df919..157d92f251 100644 --- a/plugins/cost-insights/CHANGELOG.md +++ b/plugins/cost-insights/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-cost-insights +## 0.4.0 + +### Minor Changes + +- 4040d4fcb: remove cost insights currency feature flag + +### Patch Changes + +- 1722cb53c: Added configuration schema +- 17a9f48f6: remove excessive margin from cost overview banner +- f360395d0: UI improvements: Increase width of first column in product entity dialog table + UI improvement: Display full cost amount in product entity dialog table +- 259d848ee: Fix savings/excess display calculation +- Updated dependencies [1722cb53c] + - @backstage/core@0.3.1 + - @backstage/test-utils@0.1.3 + ## 0.3.0 ### Minor Changes diff --git a/plugins/cost-insights/package.json b/plugins/cost-insights/package.json index 1d7850482c..8ebeac91c0 100644 --- a/plugins/cost-insights/package.json +++ b/plugins/cost-insights/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-cost-insights", - "version": "0.3.0", + "version": "0.4.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -22,8 +22,8 @@ }, "dependencies": { "@backstage/config": "^0.1.1", - "@backstage/core": "^0.3.0", - "@backstage/test-utils": "^0.1.2", + "@backstage/core": "^0.3.1", + "@backstage/test-utils": "^0.1.3", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -46,9 +46,9 @@ "yup": "^0.29.3" }, "devDependencies": { - "@backstage/cli": "^0.2.0", - "@backstage/dev-utils": "^0.1.3", - "@backstage/test-utils": "^0.1.2", + "@backstage/cli": "^0.3.0", + "@backstage/dev-utils": "^0.1.4", + "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/explore/package.json b/plugins/explore/package.json index 7770341ffb..153184bdb1 100644 --- a/plugins/explore/package.json +++ b/plugins/explore/package.json @@ -21,7 +21,7 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/core": "^0.3.0", + "@backstage/core": "^0.3.1", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -33,9 +33,9 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.2.0", - "@backstage/dev-utils": "^0.1.3", - "@backstage/test-utils": "^0.1.2", + "@backstage/cli": "^0.3.0", + "@backstage/dev-utils": "^0.1.4", + "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/gcp-projects/package.json b/plugins/gcp-projects/package.json index e4af2bd4a3..0415a5ffb3 100644 --- a/plugins/gcp-projects/package.json +++ b/plugins/gcp-projects/package.json @@ -20,7 +20,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.3.0", + "@backstage/core": "^0.3.1", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -31,9 +31,9 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.2.0", - "@backstage/dev-utils": "^0.1.3", - "@backstage/test-utils": "^0.1.2", + "@backstage/cli": "^0.3.0", + "@backstage/dev-utils": "^0.1.4", + "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/github-actions/package.json b/plugins/github-actions/package.json index e0db90453a..49ecfc5423 100644 --- a/plugins/github-actions/package.json +++ b/plugins/github-actions/package.json @@ -22,9 +22,9 @@ }, "dependencies": { "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.0", + "@backstage/core": "^0.3.1", "@backstage/core-api": "^0.2.1", - "@backstage/plugin-catalog": "^0.2.1", + "@backstage/plugin-catalog": "^0.2.2", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -40,9 +40,9 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.2.0", - "@backstage/dev-utils": "^0.1.3", - "@backstage/test-utils": "^0.1.2", + "@backstage/cli": "^0.3.0", + "@backstage/dev-utils": "^0.1.4", + "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/gitops-profiles/package.json b/plugins/gitops-profiles/package.json index 167970f830..ad80d26e61 100644 --- a/plugins/gitops-profiles/package.json +++ b/plugins/gitops-profiles/package.json @@ -21,7 +21,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.3.0", + "@backstage/core": "^0.3.1", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -32,9 +32,9 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.2.0", - "@backstage/dev-utils": "^0.1.3", - "@backstage/test-utils": "^0.1.2", + "@backstage/cli": "^0.3.0", + "@backstage/dev-utils": "^0.1.4", + "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/graphiql/package.json b/plugins/graphiql/package.json index be20cfb635..a5523afc6f 100644 --- a/plugins/graphiql/package.json +++ b/plugins/graphiql/package.json @@ -31,7 +31,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.3.0", + "@backstage/core": "^0.3.1", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -43,9 +43,9 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.2.0", - "@backstage/dev-utils": "^0.1.3", - "@backstage/test-utils": "^0.1.2", + "@backstage/cli": "^0.3.0", + "@backstage/dev-utils": "^0.1.4", + "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/graphql/CHANGELOG.md b/plugins/graphql/CHANGELOG.md index f9a626d543..477bd1bb8b 100644 --- a/plugins/graphql/CHANGELOG.md +++ b/plugins/graphql/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-graphql-backend +## 0.1.3 + +### Patch Changes + +- Updated dependencies [1722cb53c] +- Updated dependencies [1722cb53c] +- Updated dependencies [7b37e6834] +- Updated dependencies [8e2effb53] + - @backstage/backend-common@0.3.0 + - @backstage/plugin-catalog-graphql@0.2.1 + ## 0.1.2 ### Patch Changes diff --git a/plugins/graphql/package.json b/plugins/graphql/package.json index f62fecc648..53af8d9154 100644 --- a/plugins/graphql/package.json +++ b/plugins/graphql/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-graphql-backend", - "version": "0.1.2", + "version": "0.1.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -19,9 +19,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.2.0", + "@backstage/backend-common": "^0.3.0", "@backstage/config": "^0.1.1", - "@backstage/plugin-catalog-graphql": "^0.2.0", + "@backstage/plugin-catalog-graphql": "^0.2.1", "@graphql-modules/core": "^0.7.17", "@types/express": "^4.17.6", "apollo-server": "^2.16.1", @@ -35,7 +35,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.2.0", + "@backstage/cli": "^0.3.0", "@types/supertest": "^2.0.8", "eslint-plugin-graphql": "^4.0.0", "msw": "^0.20.5", diff --git a/plugins/jenkins/package.json b/plugins/jenkins/package.json index b4f1d25199..0279592a75 100644 --- a/plugins/jenkins/package.json +++ b/plugins/jenkins/package.json @@ -22,8 +22,8 @@ }, "dependencies": { "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.0", - "@backstage/plugin-catalog": "^0.2.1", + "@backstage/core": "^0.3.1", + "@backstage/plugin-catalog": "^0.2.2", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -36,9 +36,9 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.2.0", - "@backstage/dev-utils": "^0.1.3", - "@backstage/test-utils": "^0.1.2", + "@backstage/cli": "^0.3.0", + "@backstage/dev-utils": "^0.1.4", + "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/kubernetes-backend/CHANGELOG.md b/plugins/kubernetes-backend/CHANGELOG.md index 2834082657..7c55a7407d 100644 --- a/plugins/kubernetes-backend/CHANGELOG.md +++ b/plugins/kubernetes-backend/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-kubernetes-backend +## 0.1.3 + +### Patch Changes + +- Updated dependencies [1722cb53c] +- Updated dependencies [1722cb53c] +- Updated dependencies [7b37e6834] +- Updated dependencies [8e2effb53] + - @backstage/backend-common@0.3.0 + ## 0.1.2 ### Patch Changes diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index 7ac5f99f4c..e58bb4f047 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-backend", - "version": "0.1.2", + "version": "0.1.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,7 +20,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.2.0", + "@backstage/backend-common": "^0.3.0", "@backstage/config": "^0.1.1", "@kubernetes/client-node": "^0.12.1", "@types/express": "^4.17.6", @@ -37,7 +37,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.2.0", + "@backstage/cli": "^0.3.0", "supertest": "^4.0.2" }, "files": [ diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index c1457944de..36954cc5b9 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -22,8 +22,8 @@ "dependencies": { "@backstage/catalog-model": "^0.2.0", "@backstage/config": "^0.1.1", - "@backstage/core": "^0.3.0", - "@backstage/plugin-kubernetes-backend": "^0.1.2", + "@backstage/core": "^0.3.1", + "@backstage/plugin-kubernetes-backend": "^0.1.3", "@backstage/theme": "^0.2.1", "@kubernetes/client-node": "^0.12.1", "@material-ui/core": "^4.11.0", @@ -35,9 +35,9 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.2.0", - "@backstage/dev-utils": "^0.1.3", - "@backstage/test-utils": "^0.1.2", + "@backstage/cli": "^0.3.0", + "@backstage/dev-utils": "^0.1.4", + "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/lighthouse/CHANGELOG.md b/plugins/lighthouse/CHANGELOG.md index 3c5e94c476..666e7c4411 100644 --- a/plugins/lighthouse/CHANGELOG.md +++ b/plugins/lighthouse/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-lighthouse +## 0.2.2 + +### Patch Changes + +- 1722cb53c: Added configuration schema +- Updated dependencies [1722cb53c] +- Updated dependencies [8b7737d0b] + - @backstage/core@0.3.1 + - @backstage/plugin-catalog@0.2.2 + ## 0.2.1 ### Patch Changes diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json index 8172847120..90230f5248 100644 --- a/plugins/lighthouse/package.json +++ b/plugins/lighthouse/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-lighthouse", - "version": "0.2.1", + "version": "0.2.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -23,9 +23,9 @@ "dependencies": { "@backstage/catalog-model": "^0.2.0", "@backstage/config": "^0.1.1", - "@backstage/core": "^0.3.0", + "@backstage/core": "^0.3.1", "@backstage/core-api": "^0.2.1", - "@backstage/plugin-catalog": "^0.2.1", + "@backstage/plugin-catalog": "^0.2.2", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -38,9 +38,9 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.2.0", - "@backstage/dev-utils": "^0.1.3", - "@backstage/test-utils": "^0.1.2", + "@backstage/cli": "^0.3.0", + "@backstage/dev-utils": "^0.1.4", + "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/newrelic/package.json b/plugins/newrelic/package.json index 475f19d651..0e1f4e1352 100644 --- a/plugins/newrelic/package.json +++ b/plugins/newrelic/package.json @@ -21,7 +21,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.3.0", + "@backstage/core": "^0.3.1", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -31,9 +31,9 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.2.0", - "@backstage/dev-utils": "^0.1.3", - "@backstage/test-utils": "^0.1.2", + "@backstage/cli": "^0.3.0", + "@backstage/dev-utils": "^0.1.4", + "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/proxy-backend/CHANGELOG.md b/plugins/proxy-backend/CHANGELOG.md index dcbe7eb7e8..b034d26c95 100644 --- a/plugins/proxy-backend/CHANGELOG.md +++ b/plugins/proxy-backend/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-proxy-backend +## 0.2.1 + +### Patch Changes + +- Updated dependencies [1722cb53c] +- Updated dependencies [1722cb53c] +- Updated dependencies [7b37e6834] +- Updated dependencies [8e2effb53] + - @backstage/backend-common@0.3.0 + ## 0.2.0 ### Minor Changes diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index f147e400e4..22acdd3fe8 100644 --- a/plugins/proxy-backend/package.json +++ b/plugins/proxy-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-proxy-backend", - "version": "0.2.0", + "version": "0.2.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -19,7 +19,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.2.0", + "@backstage/backend-common": "^0.3.0", "@backstage/config": "^0.1.1", "@types/express": "^4.17.6", "express": "^4.17.1", @@ -33,7 +33,7 @@ "yup": "^0.29.3" }, "devDependencies": { - "@backstage/cli": "^0.2.0", + "@backstage/cli": "^0.3.0", "@types/http-proxy-middleware": "^0.19.3", "@types/supertest": "^2.0.8", "@types/uuid": "^8.0.0", diff --git a/plugins/register-component/package.json b/plugins/register-component/package.json index 9c87f33e29..06ae17cb0e 100644 --- a/plugins/register-component/package.json +++ b/plugins/register-component/package.json @@ -22,8 +22,8 @@ }, "dependencies": { "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.0", - "@backstage/plugin-catalog": "^0.2.1", + "@backstage/core": "^0.3.1", + "@backstage/plugin-catalog": "^0.2.2", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -36,9 +36,9 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.2.0", - "@backstage/dev-utils": "^0.1.3", - "@backstage/test-utils": "^0.1.2", + "@backstage/cli": "^0.3.0", + "@backstage/dev-utils": "^0.1.4", + "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/rollbar-backend/CHANGELOG.md b/plugins/rollbar-backend/CHANGELOG.md index 21f9ce8090..ec042b9ae1 100644 --- a/plugins/rollbar-backend/CHANGELOG.md +++ b/plugins/rollbar-backend/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-rollbar-backend +## 0.1.3 + +### Patch Changes + +- Updated dependencies [1722cb53c] +- Updated dependencies [1722cb53c] +- Updated dependencies [7b37e6834] +- Updated dependencies [8e2effb53] + - @backstage/backend-common@0.3.0 + ## 0.1.2 ### Patch Changes diff --git a/plugins/rollbar-backend/package.json b/plugins/rollbar-backend/package.json index b1764f238f..307c37e0da 100644 --- a/plugins/rollbar-backend/package.json +++ b/plugins/rollbar-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-rollbar-backend", - "version": "0.1.2", + "version": "0.1.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,7 +20,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.2.0", + "@backstage/backend-common": "^0.3.0", "@backstage/config": "^0.1.1", "@types/express": "^4.17.6", "axios": "^0.20.0", @@ -37,7 +37,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.2.0", + "@backstage/cli": "^0.3.0", "@types/supertest": "^2.0.8", "supertest": "^4.0.2" }, diff --git a/plugins/rollbar/CHANGELOG.md b/plugins/rollbar/CHANGELOG.md index 3d53c1bf0d..e4a6671f90 100644 --- a/plugins/rollbar/CHANGELOG.md +++ b/plugins/rollbar/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-rollbar +## 0.2.2 + +### Patch Changes + +- 1722cb53c: Added configuration schema +- Updated dependencies [1722cb53c] +- Updated dependencies [8b7737d0b] + - @backstage/core@0.3.1 + - @backstage/plugin-catalog@0.2.2 + ## 0.2.1 ### Patch Changes diff --git a/plugins/rollbar/package.json b/plugins/rollbar/package.json index 18e4de3024..c107573ca8 100644 --- a/plugins/rollbar/package.json +++ b/plugins/rollbar/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-rollbar", - "version": "0.2.1", + "version": "0.2.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -22,8 +22,8 @@ }, "dependencies": { "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.0", - "@backstage/plugin-catalog": "^0.2.1", + "@backstage/core": "^0.3.1", + "@backstage/plugin-catalog": "^0.2.2", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -37,9 +37,9 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.2.0", - "@backstage/dev-utils": "^0.1.3", - "@backstage/test-utils": "^0.1.2", + "@backstage/cli": "^0.3.0", + "@backstage/dev-utils": "^0.1.4", + "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/react-hooks": "^3.3.0", diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index 96f4ef75f7..a388a29a00 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-scaffolder-backend +## 0.3.1 + +### Patch Changes + +- d33f5157c: Extracted pushToRemote function for reuse between publishers +- Updated dependencies [1722cb53c] +- Updated dependencies [1722cb53c] +- Updated dependencies [7b37e6834] +- Updated dependencies [8e2effb53] + - @backstage/backend-common@0.3.0 + ## 0.3.0 ### Minor Changes diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 4bed7ea60c..73c07d158f 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend", - "version": "0.3.0", + "version": "0.3.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,7 +20,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.2.1", + "@backstage/backend-common": "^0.3.0", "@backstage/catalog-model": "^0.2.0", "@backstage/config": "^0.1.1", "@gitbeaker/core": "^25.2.0", @@ -48,7 +48,7 @@ "cross-fetch": "^3.0.6" }, "devDependencies": { - "@backstage/cli": "^0.2.0", + "@backstage/cli": "^0.3.0", "@octokit/types": "^5.4.1", "@types/fs-extra": "^9.0.1", "@types/git-url-parse": "^9.0.0", diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index a423c64aa3..439fc0ad9a 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -22,8 +22,8 @@ }, "dependencies": { "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.0", - "@backstage/plugin-catalog": "^0.2.1", + "@backstage/core": "^0.3.1", + "@backstage/plugin-catalog": "^0.2.2", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -41,9 +41,9 @@ "swr": "^0.3.0" }, "devDependencies": { - "@backstage/cli": "^0.2.0", - "@backstage/dev-utils": "^0.1.3", - "@backstage/test-utils": "^0.1.2", + "@backstage/cli": "^0.3.0", + "@backstage/dev-utils": "^0.1.4", + "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/search/package.json b/plugins/search/package.json index 6f356d1bf4..27620f2879 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -20,21 +20,21 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.3.0", + "@backstage/core": "^0.3.1", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", - "@backstage/plugin-catalog": "^0.2.0", + "@backstage/plugin-catalog": "^0.2.2", "react-router-dom": "6.0.0-beta.0", "react": "^16.13.1", "react-dom": "^16.13.1", "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.2.0", - "@backstage/dev-utils": "^0.1.3", - "@backstage/test-utils": "^0.1.2", + "@backstage/cli": "^0.3.0", + "@backstage/dev-utils": "^0.1.4", + "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/sentry-backend/CHANGELOG.md b/plugins/sentry-backend/CHANGELOG.md index 93361e05ff..9ca0a5415e 100644 --- a/plugins/sentry-backend/CHANGELOG.md +++ b/plugins/sentry-backend/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-sentry-backend +## 0.1.3 + +### Patch Changes + +- Updated dependencies [1722cb53c] +- Updated dependencies [1722cb53c] +- Updated dependencies [7b37e6834] +- Updated dependencies [8e2effb53] + - @backstage/backend-common@0.3.0 + ## 0.1.2 ### Patch Changes diff --git a/plugins/sentry-backend/package.json b/plugins/sentry-backend/package.json index a1e1531b8b..2b6b5fc60d 100644 --- a/plugins/sentry-backend/package.json +++ b/plugins/sentry-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-sentry-backend", - "version": "0.1.2", + "version": "0.1.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,7 +20,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.2.0", + "@backstage/backend-common": "^0.3.0", "@types/express": "^4.17.6", "axios": "^0.20.0", "compression": "^1.7.4", @@ -34,7 +34,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.2.0" + "@backstage/cli": "^0.3.0" }, "files": [ "dist" diff --git a/plugins/sentry/CHANGELOG.md b/plugins/sentry/CHANGELOG.md index 88adfe43a1..92327c1668 100644 --- a/plugins/sentry/CHANGELOG.md +++ b/plugins/sentry/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-sentry +## 0.2.2 + +### Patch Changes + +- 1722cb53c: Added configuration schema +- Updated dependencies [1722cb53c] + - @backstage/core@0.3.1 + ## 0.2.1 ### Patch Changes diff --git a/plugins/sentry/package.json b/plugins/sentry/package.json index 2757c02db9..80a37c574e 100644 --- a/plugins/sentry/package.json +++ b/plugins/sentry/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-sentry", - "version": "0.2.1", + "version": "0.2.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -22,7 +22,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.0", + "@backstage/core": "^0.3.1", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -36,9 +36,9 @@ "timeago.js": "^4.0.2" }, "devDependencies": { - "@backstage/cli": "^0.2.0", - "@backstage/dev-utils": "^0.1.3", - "@backstage/test-utils": "^0.1.2", + "@backstage/cli": "^0.3.0", + "@backstage/dev-utils": "^0.1.4", + "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/sonarqube/package.json b/plugins/sonarqube/package.json index d7240aabde..dcde8fab14 100644 --- a/plugins/sonarqube/package.json +++ b/plugins/sonarqube/package.json @@ -22,7 +22,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.0", + "@backstage/core": "^0.3.1", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -35,9 +35,9 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.2.0", - "@backstage/dev-utils": "^0.1.3", - "@backstage/test-utils": "^0.1.2", + "@backstage/cli": "^0.3.0", + "@backstage/dev-utils": "^0.1.4", + "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/tech-radar/package.json b/plugins/tech-radar/package.json index 431fd3d515..7f5707764b 100644 --- a/plugins/tech-radar/package.json +++ b/plugins/tech-radar/package.json @@ -21,8 +21,8 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/core": "^0.3.0", - "@backstage/test-utils": "^0.1.2", + "@backstage/core": "^0.3.1", + "@backstage/test-utils": "^0.1.3", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -35,9 +35,9 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.2.0", - "@backstage/dev-utils": "^0.1.3", - "@backstage/test-utils": "^0.1.2", + "@backstage/cli": "^0.3.0", + "@backstage/dev-utils": "^0.1.4", + "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/techdocs-backend/CHANGELOG.md b/plugins/techdocs-backend/CHANGELOG.md index 4191361a33..4a3fbf6821 100644 --- a/plugins/techdocs-backend/CHANGELOG.md +++ b/plugins/techdocs-backend/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-techdocs-backend +## 0.2.1 + +### Patch Changes + +- Updated dependencies [1722cb53c] +- Updated dependencies [1722cb53c] +- Updated dependencies [7b37e6834] +- Updated dependencies [8e2effb53] + - @backstage/backend-common@0.3.0 + ## 0.2.0 ### Minor Changes diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index c1d1f6ad27..b139bb9acf 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-backend", - "version": "0.2.0", + "version": "0.2.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,7 +20,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.2.0", + "@backstage/backend-common": "^0.3.0", "@backstage/catalog-model": "^0.2.0", "@backstage/config": "^0.1.1", "@types/dockerode": "^2.5.34", @@ -37,7 +37,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.2.0", + "@backstage/cli": "^0.3.0", "supertest": "^4.0.2" }, "files": [ diff --git a/plugins/techdocs/CHANGELOG.md b/plugins/techdocs/CHANGELOG.md index 1c1dcb5c8a..bd3ae21ab8 100644 --- a/plugins/techdocs/CHANGELOG.md +++ b/plugins/techdocs/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-techdocs +## 0.2.2 + +### Patch Changes + +- 1722cb53c: Added configuration schema +- Updated dependencies [1722cb53c] +- Updated dependencies [8b7737d0b] + - @backstage/core@0.3.1 + - @backstage/plugin-catalog@0.2.2 + - @backstage/test-utils@0.1.3 + ## 0.2.1 ### Patch Changes diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index a2b1349d53..35b196e124 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs", - "version": "0.2.1", + "version": "0.2.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -22,10 +22,10 @@ }, "dependencies": { "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.0", + "@backstage/core": "^0.3.1", "@backstage/core-api": "^0.2.1", - "@backstage/plugin-catalog": "^0.2.1", - "@backstage/test-utils": "^0.1.2", + "@backstage/plugin-catalog": "^0.2.2", + "@backstage/test-utils": "^0.1.3", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -39,9 +39,9 @@ "sanitize-html": "^1.27.0" }, "devDependencies": { - "@backstage/cli": "^0.2.0", - "@backstage/dev-utils": "^0.1.3", - "@backstage/test-utils": "^0.1.2", + "@backstage/cli": "^0.3.0", + "@backstage/dev-utils": "^0.1.4", + "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/user-settings/CHANGELOG.md b/plugins/user-settings/CHANGELOG.md index 729d2a669a..9efbc6eba7 100644 --- a/plugins/user-settings/CHANGELOG.md +++ b/plugins/user-settings/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-user-settings +## 0.2.2 + +### Patch Changes + +- 1722cb53c: Added configuration schema +- Updated dependencies [1722cb53c] + - @backstage/core@0.3.1 + ## 0.2.1 ### Patch Changes diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index f82a6b2b6f..1277ae214a 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-user-settings", - "version": "0.2.1", + "version": "0.2.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,7 +21,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.3.0", + "@backstage/core": "^0.3.1", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -32,9 +32,9 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.2.0", - "@backstage/dev-utils": "^0.1.3", - "@backstage/test-utils": "^0.1.2", + "@backstage/cli": "^0.3.0", + "@backstage/dev-utils": "^0.1.4", + "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/welcome/package.json b/plugins/welcome/package.json index fcfbed2af7..47b02f971c 100644 --- a/plugins/welcome/package.json +++ b/plugins/welcome/package.json @@ -21,7 +21,7 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/core": "^0.3.0", + "@backstage/core": "^0.3.1", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -32,9 +32,9 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.2.0", - "@backstage/dev-utils": "^0.1.3", - "@backstage/test-utils": "^0.1.2", + "@backstage/cli": "^0.3.0", + "@backstage/dev-utils": "^0.1.4", + "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", From 69c924f24ae834d71a1534235c5f85ec321e0c6f Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 19 Nov 2020 16:37:51 +0100 Subject: [PATCH 081/430] chore: fixing issue with resolutions for new versions of backstage --- package.json | 2 +- yarn.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index b97462114c..4140886bc3 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ ] }, "resolutions": { - "**/@roadiehq/backstage-plugin-*/@backstage/core": "0.3.0" + "**/@roadiehq/backstage-plugin-*/@backstage/core": "*" }, "version": "1.0.0", "devDependencies": { diff --git a/yarn.lock b/yarn.lock index bc9eea6285..de6ec42c7c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1290,7 +1290,7 @@ to-fast-properties "^2.0.0" "@backstage/core@^0.2.0": - version "0.3.0" + version "0.3.1" dependencies: "@backstage/config" "^0.1.1" "@backstage/core-api" "^0.2.1" From 1adfd1cfacade037d69b3c79ff31b57ebf60e41c Mon Sep 17 00:00:00 2001 From: Mateusz Lewtak Date: Thu, 19 Nov 2020 17:57:29 +0100 Subject: [PATCH 082/430] Feat: Add Buildkite plugin, update plugin logotypes --- microsite/data/plugins/aws-lambda.yaml | 2 +- microsite/data/plugins/buildkite.yaml | 12 ++++++++++++ microsite/data/plugins/firebase-functions.yaml | 2 +- microsite/data/plugins/github-insights.yaml | 2 +- microsite/data/plugins/github-pull-requests.yaml | 2 +- microsite/data/plugins/security-insights.yaml | 2 +- microsite/data/plugins/travis-ci.yaml | 2 +- 7 files changed, 18 insertions(+), 6 deletions(-) create mode 100644 microsite/data/plugins/buildkite.yaml diff --git a/microsite/data/plugins/aws-lambda.yaml b/microsite/data/plugins/aws-lambda.yaml index 7fa276f444..f325c0df8b 100644 --- a/microsite/data/plugins/aws-lambda.yaml +++ b/microsite/data/plugins/aws-lambda.yaml @@ -5,5 +5,5 @@ authorUrl: https://roadie.io category: Monitoring description: View AWS Lambda functions for your components in Backstage. documentation: https://roadie.io/backstage/plugins/aws-lambda -iconUrl: https://roadie.io/static/77f62f79e27ae8565496e4df7eef8be5/45f2b/logo.png +iconUrl: https://roadie.io/images/logos/lambda.png npmPackageName: '@roadiehq/backstage-plugin-aws-lambda' diff --git a/microsite/data/plugins/buildkite.yaml b/microsite/data/plugins/buildkite.yaml new file mode 100644 index 0000000000..eeb9440aac --- /dev/null +++ b/microsite/data/plugins/buildkite.yaml @@ -0,0 +1,12 @@ +--- +title: Buildkite +author: roadie.io +authorUrl: https://roadie.io +category: CI +description: View Buildkite CI builds for your service in Backstage. +documentation: https://roadie.io/backstage/plugins/buildkite +iconUrl: https://roadie.io/images/logos/buildkite.png +npmPackageName: '@roadiehq/backstage-plugin-buildkite' +tags: + - ci + - cd diff --git a/microsite/data/plugins/firebase-functions.yaml b/microsite/data/plugins/firebase-functions.yaml index 097ef00e50..6d399a2350 100644 --- a/microsite/data/plugins/firebase-functions.yaml +++ b/microsite/data/plugins/firebase-functions.yaml @@ -5,5 +5,5 @@ authorUrl: https://roadie.io/ category: Monitoring description: View Firebase Functions details for your service in Backstage. documentation: https://roadie.io/backstage/plugins/firebase-functions -iconUrl: https://roadie.io/static/49fb23200ad0eaa6703b4ddf75c78cf1/45f2b/logo-vertical.png +iconUrl: https://roadie.io/images/logos/github.png npmPackageName: '@roadiehq/backstage-plugin-firebase-functions' diff --git a/microsite/data/plugins/github-insights.yaml b/microsite/data/plugins/github-insights.yaml index 8ac8321da0..1dba3dcfd1 100644 --- a/microsite/data/plugins/github-insights.yaml +++ b/microsite/data/plugins/github-insights.yaml @@ -5,5 +5,5 @@ authorUrl: https://roadie.io category: Monitoring description: View GitHub Insights for your components in Backstage. documentation: https://roadie.io/backstage/plugins/github-insights -iconUrl: https://roadie.io/static/2ad5123c425908efde0c922d707e737b/06c84/code-icon.png +iconUrl: https://roadie.io/images/logos/insights.png npmPackageName: '@roadiehq/backstage-plugin-github-insights' diff --git a/microsite/data/plugins/github-pull-requests.yaml b/microsite/data/plugins/github-pull-requests.yaml index 6479452c8e..8e44a4bf56 100644 --- a/microsite/data/plugins/github-pull-requests.yaml +++ b/microsite/data/plugins/github-pull-requests.yaml @@ -5,5 +5,5 @@ authorUrl: https://roadie.io/ category: CI description: View GitHub pull requests for your service in Backstage. documentation: https://roadie.io/backstage/plugins/github-pull-requests -iconUrl: https://roadie.io/static/7f13bb8d861d8dedc5112fb939d215f9/351f2/GitHub-Mark-Light-120px-plus.png +iconUrl: https://roadie.io/images/logos/github.png npmPackageName: '@roadiehq/backstage-plugin-github-pull-requests' diff --git a/microsite/data/plugins/security-insights.yaml b/microsite/data/plugins/security-insights.yaml index 1cbcfbc3aa..4f34f79027 100644 --- a/microsite/data/plugins/security-insights.yaml +++ b/microsite/data/plugins/security-insights.yaml @@ -5,5 +5,5 @@ authorUrl: https://roadie.io/ category: Security description: View Security Insights for your components in Backstage. documentation: https://roadie.io/backstage/plugins/security-insights -iconUrl: https://roadie.io/static/7f13bb8d861d8dedc5112fb939d215f9/351f2/GitHub-Mark-Light-120px-plus.png +iconUrl: https://roadie.io/images/logos/github.png npmPackageName: '@roadiehq/backstage-plugin-security-insights' diff --git a/microsite/data/plugins/travis-ci.yaml b/microsite/data/plugins/travis-ci.yaml index 520b884c20..fe1b18c9d4 100644 --- a/microsite/data/plugins/travis-ci.yaml +++ b/microsite/data/plugins/travis-ci.yaml @@ -5,5 +5,5 @@ authorUrl: https://roadie.io/ category: CI description: View Travis CI builds for your service in Backstage. documentation: https://roadie.io/backstage/plugins/travis-ci -iconUrl: https://roadie.io/static/af2941eaf0af675facb281d566f42e14/45f2b/travis-ci-mascot-200x200.png +iconUrl: https://roadie.io/images/logos/travis.png npmPackageName: '@roadiehq/backstage-plugin-travis-ci' From 79431253dea96244af77c321d700aa0cd83b48c3 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 19 Nov 2020 18:51:17 +0100 Subject: [PATCH 083/430] Update Governance --- .github/styles/vocab.txt | 8 +++++ GOVERNANCE.md | 69 ++++++++++++++++++---------------------- MAINTAINERS.md | 21 ------------ OWNERS.md | 24 ++++++++++++++ 4 files changed, 63 insertions(+), 59 deletions(-) delete mode 100644 MAINTAINERS.md create mode 100644 OWNERS.md diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt index 9f6c7d386d..61ec1d364c 100644 --- a/.github/styles/vocab.txt +++ b/.github/styles/vocab.txt @@ -3,12 +3,14 @@ Apdex api Api apis +andrewthauer args asciidoc async Avro backrub Balachandran +benjdlambert Bigtable Blackbox bool @@ -50,6 +52,7 @@ Dockerfile Dockerize dockerode Docusaurus +dzolotusky eg Ek env @@ -59,6 +62,7 @@ facto failover Figma Firekube +freben Fredrik github Github @@ -66,6 +70,7 @@ gitlab Gitlab graphql graphviz +Gustavsson Hackathons haproxy heroku @@ -159,6 +164,7 @@ Rollup Rosaceae rst rsync +rugvip ruleset sam scaffolded @@ -174,6 +180,7 @@ spotify Spotify squidfunk src +stefanalund subkey superfences Superfences @@ -209,6 +216,7 @@ xyz yaml Zalando Zhou +Zolotusky Billett cloudbuild Grafana diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 19bbb3bcb9..65841d1c6a 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -1,52 +1,45 @@ -# Backstage Governance +# Process for becoming a maintainer -This document defines project governance for the project. +## Your organization is not yet a maintainer -## Maintainers +- Express interest to the sponsors that your organization is interested in becoming a maintainer. Becoming a maintainer generally means that you are going to be spending substantial time on Backstage for the foreseeable future. You should have domain expertise and be extremely proficient in TypeScript. +- We will expect you to start contributing increasingly complicated PRs, under the guidance of the existing maintainers. +- We may ask you to do some PRs from our backlog. +- As you gain experience with the code base and our standards, we will ask you to do code reviews for incoming PRs. +- After a period of approximately 2-3 months of working together and making sure we see eye to eye, the existing sponsors and maintainers will confer and decide whether to grant maintainer status or not. We make no guarantees on the length of time this will take, but 2-3 months is the approximate goal. -Backstage Maintainers have write access to the Backstage GitHub repository https://github.com/backstage/backstage. The current maintainers can be found in [MAINTAINERS](MAINTAINERS.md). - -This privilege is granted with some expectation of responsibility: maintainers are people who care about the Backstage project and want to help it grow and improve. A maintainer is not just someone who can make changes, but someone who has demonstrated his or her ability to collaborate with the team, get the most knowledgeable people to review code, contribute high-quality code, and follow through to fix issues (in code or tests). - -A maintainer is a contributor to the Backstage project's success and a citizen helping the project succeed. - -## Becoming a Maintainer +## Your organization is currently a maintainer To become a maintainer you need to demonstrate the following: -- commitment to the project - - participate in discussions, contributions, code reviews for 3 months or more, - - perform code reviews for 10 non-trivial pull requests, - - contribute 10 non-trivial pull requests and have them merged into master, -- ability to write good code, -- ability to collaborate with the team, -- understanding of how the team works (policies, processes for testing and code review, etc), -- understanding of the project's code base and coding style. +- First decide whether your organization really needs more people with maintainer access. Valid reasons are "blast radius", a large organization that is working on multiple unrelated projects, etc. +- Contact a sponsor for your organization and express interest. +- Start doing PRs and code reviews under the guidance of your maintainer. +- After a period of 1-2 months the existing sponsors will discuss granting maintainer access. +- Maintainer access can be upgraded to sponsor access after another conference of the existing sponsors. -## Changes in Maintainership +# Maintainer responsibilities -A new maintainer must be proposed by an existing maintainer by opening an issue (with title `Maintainer Nomination`) to the Backstage GitHub repository (https://github.com/backstage/backstage) containing the following information: +- Monitor email aliases. +- Monitor Discord (delayed response is perfectly acceptable). +- Triage GitHub issues and perform pull request reviews for other maintainers and the community. +- Triage build issues - file issues for known flaky builds or bugs, and either fix or find someone to fix any master build breakages. +- During GitHub issue triage, apply all applicable ([labels](https://github.com/backstage/backstage/labels)) to each new issue. Labels are extremely useful for future issue follow up. Which labels to apply is somewhat subjective so just use your best judgment. A few of the most important labels that are not self explanatory are: + - good first issue: Mark any issue that can reasonably be accomplished by a new contributor with this label. + - help wanted: Unless it is immediately obvious that someone is going to work on an issue (and if so assign it), mark it help wanted. +- Make sure that ongoing PRs are moving forward at the right pace or closing them. +- Participate when called upon in the security release process. Note that although this should be a rare occurrence, if a serious vulnerability is found, the process may take up to several full days of work to implement. This reality should be taken into account when discussing time commitment obligations with employers. +- In general, continue to be willing to spend at least 25% of one's time working on Backstage (~1.25 business days per week). +- We currently maintain an "on-call" rotation within the maintainers. Each on-call is 1 week. Although all maintainers are welcome to perform all of the above tasks, it is the on-call maintainer's responsibility to triage incoming issues/questions and marshal ongoing work forward. To reiterate, it is not the responsibility of the on-call maintainer to answer all questions and do all reviews, but it is their responsibility to make sure that everything is being actively covered by someone. -- nominee's first and last name, -- nominee's email address and GitHub user name, -- an explanation of why the nominee should be a maintainer, -- a list of links to non-trivial pull requests (top 10) authored by the nominee. +# When does a maintainer lose maintainer status -Maintainers can be removed by a 2/3 majority vote. +If a maintainer is no longer interested or cannot perform the maintainer duties listed above, they should volunteer to be moved to emeritus status. In extreme cases this can also occur by a vote of the sponsors and maintainers per the voting process below. -## Approving PRs +# Conflict resolution and voting -PRs may be merged after receiving at least one approval from a maintainer. +In general, we prefer that technical issues and maintainer membership are amicably worked out between the persons involved. If a dispute cannot be decided independently, the sponsors and maintainers can be called in to decide an issue. If the sponsors and maintainers themselves cannot decide an issue, the issue will be resolved by voting. The voting process is a simple majority in which each sponsor receives two votes and each maintainer receives one vote. -## GitHub Project Administration +# Adding new projects to the Backstage GitHub organization -Maintainers will be added to the collaborators list of the Backstage repository with "Write" access. - -## Changes in Governance - -All changes in Governance require a 2/3 majority vote. - -## Other Changes - -Unless specified above, all other changes to the project require a 2/3 majority vote. -Additionally, any maintainer may request that any change require a 2/3 majority vote. +New projects will be added to the Backstage organization via GitHub issue discussion in one of the existing projects in the organization. Once sufficient discussion has taken place (~3-5 business days but depending on the volume of conversation), the maintainers of the project where the issue was opened (since different projects in the organization may have different maintainers) will decide whether the new project should be added. See the section above on voting if the maintainers cannot easily decide. diff --git a/MAINTAINERS.md b/MAINTAINERS.md deleted file mode 100644 index 33437692c9..0000000000 --- a/MAINTAINERS.md +++ /dev/null @@ -1,21 +0,0 @@ -# Maintainers - -- See [CONTRIBUTING.md](CONTRIBUTING.md) for general contribution guidelines. - -## Current Maintainers 🏓 - -- Stefan Ålund - Spotify (GitHub: @stefanalund, Discord: @stalund) -- Patrik Oldsberg - Spotify (GitHub: @Rugvip, Discord: @Rugvip) -- Fredrik Adelöw - Spotify (GitHub: @freben, Discord: @freben) -- Ben Lambert - Spotify (GitHub: @benjdlambert, Discord: @blam) - -## Plugin maintainers 🧩 - -Teams and individuals that maintain a plugin (or another non-core module of the code) can get write access to that part of the repo using CODEOWNERS. - -## Hall of Fame 👏 - -People that have made significant contributions to the project and earned write access: - -- Andrew Thauer - Wealthsimple (GitHub: @andrewthauer) -- Oliver Sand - SDA SE (GitHub: @Fox32) diff --git a/OWNERS.md b/OWNERS.md new file mode 100644 index 0000000000..8e5002fc0a --- /dev/null +++ b/OWNERS.md @@ -0,0 +1,24 @@ +- See [CONTRIBUTING.md](CONTRIBUTING.md) for general contribution guidelines. +- See [GOVERNANCE.md](GOVERNANCE.md) for governance guidelines and responsibilities. + +This page lists all active sponsors and maintainers. + +# Sponsors + +- Niklas Gustavsson ([protocol7](https://github.com/protocol7)) (ngn@spotify.com) +- Dave Zolotusky ([dzolotusky](https://github.com/dzolotusky)) (dzolo@spotify.com) +- Lee Mills ([leemills83](https://github.com/leemills83)) (leem@spotify.com) + +# Maintainers + +- Patrik Oldsberg ([rugvip](https://github.com/rugvip)) (Discord: @Rugvip) +- Fredrik Adelöw ([freben](https://github.com/freben)) (Discord: @freben) +- Ben Lambert ([benjdlambert](https://github.com/benjdlambert)) (Discord: @blam) +- Stefan Ålund ([stefanalund](https://github.com/stefanalund)) (Discord: @stalund) + +# Friends of Backstage + +People that have made significant contributions to the project and earned write access. + +- Andrew Thauer - Wealthsimple (GitHub: [andrewthauer](https://github.com/andrewthauer)) +- Oliver Sand - SDA SE (GitHub: [Fox32](https://github.com/Fox32)) From aa22f839caa16275af3890273aad634646164ff3 Mon Sep 17 00:00:00 2001 From: Ryan Vazquez Date: Thu, 19 Nov 2020 15:04:07 -0500 Subject: [PATCH 084/430] fix icon configuration The icon subfield of `products` is optional. The plugin provides a fallback icon for products that do not define them. --- plugins/cost-insights/config.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/cost-insights/config.d.ts b/plugins/cost-insights/config.d.ts index 0f147c2cfe..5099be43cb 100644 --- a/plugins/cost-insights/config.d.ts +++ b/plugins/cost-insights/config.d.ts @@ -31,7 +31,7 @@ interface Config { /** * @visibility frontend */ - icon: 'compute' | 'data' | 'database' | 'storage' | 'search' | 'ml'; + icon?: 'compute' | 'data' | 'database' | 'storage' | 'search' | 'ml'; }; }; From 8e6728e254620883f8c2cf721a4ebb01d0d61a2f Mon Sep 17 00:00:00 2001 From: Ryan Vazquez Date: Thu, 19 Nov 2020 15:07:44 -0500 Subject: [PATCH 085/430] changeset --- .changeset/cost-insights-quiet-bikes-occur.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/cost-insights-quiet-bikes-occur.md diff --git a/.changeset/cost-insights-quiet-bikes-occur.md b/.changeset/cost-insights-quiet-bikes-occur.md new file mode 100644 index 0000000000..aac283bbd4 --- /dev/null +++ b/.changeset/cost-insights-quiet-bikes-occur.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-cost-insights': patch +--- + +fix product icon configuration From 1b3e2db75ff5d30c57792b925e297f385db33729 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Wed, 18 Nov 2020 22:44:57 +0100 Subject: [PATCH 086/430] TechDocs: Add basic and production deployment architecture diagrams --- .../techdocs/architecture-basic.drawio.svg | 462 ++++++++++++++++++ .../architecture-recommended.drawio.svg | 391 +++++++++++++++ docs/assets/techdocs/techdocs_big_picture.png | Bin 113762 -> 0 bytes docs/features/techdocs/architecture.md | 39 +- 4 files changed, 891 insertions(+), 1 deletion(-) create mode 100644 docs/assets/techdocs/architecture-basic.drawio.svg create mode 100644 docs/assets/techdocs/architecture-recommended.drawio.svg delete mode 100644 docs/assets/techdocs/techdocs_big_picture.png diff --git a/docs/assets/techdocs/architecture-basic.drawio.svg b/docs/assets/techdocs/architecture-basic.drawio.svg new file mode 100644 index 0000000000..8e3be76f8b --- /dev/null +++ b/docs/assets/techdocs/architecture-basic.drawio.svg @@ -0,0 +1,462 @@ + + + + + + + + + + + + + + +
+
+
+ Repo 1 +
+ (with docs) +
+
+
+
+ + Repo 1... + +
+
+ + + + +
+
+
+ Repo 2 +
+ (with docs) +
+
+
+
+ + Repo 2... + +
+
+ + + + +
+
+
+ TechDocs plugin +
+
+
+
+ + TechDocs plugin + +
+
+ + + + + +
+
+
+ Request TechDocs site +
+
+
+
+ + Request TechDocs site + +
+
+ + + + + + + + + + + + + + + + + + +
+
+
+ Source code hosting +
+
+
+
+ + Source code hosting + +
+
+ + + + +
+
+
+ TechDocs Backend Plugin +
+
+
+
+ + TechDocs Backend Plugin + +
+
+ + + + +
+
+
+ (Stages) +
+
+
+
+ + (Stages) + +
+
+ + + + + + +
+
+
+ Prepare +
+
+
+
+ + Prepare + +
+
+ + + + + + +
+
+
+ Generate +
+
+
+
+ + Generate + +
+
+ + + + +
+
+
+ Publish +
+
+
+
+ + Publish + +
+
+ + + + + +
+
+
+ Local Filesystem +
+
+
+
+ + Local File... + +
+
+ + + + +
+
+
+ Fetch markdown files +
+
+
+
+ + Fetch mark... + +
+
+ + + + +
+
+
+ Build doc files using mkdocs +
+
+
+
+ + Build doc... + +
+
+ + + + +
+
+
+ Store generated static content +
+
+
+
+ + Store gene... + +
+
+ + + + +
+
+
+ TechDocs Basic (out-of-the-box) architecture +
+
+
+
+ + TechDocs Basic (out-of-the-box) architect... + +
+
+ + + + + + + + +
+
+
+ + Route handler + +
+
+
+
+ + Route hand... + +
+
+ + + + +
+
+
+ or +
+
+
+
+ + or + +
+
+ + + + +
+
+
+ Trigger new build if docs D.N.E. +
+ or are outdated +
+
+
+
+ + Trigger new build... + +
+
+ + + + +
+
+
+ Fetch files to render +
+
+
+
+ + Fetch files... + +
+
+ + + + + + + + + + + +
+
+
+ + GCP +
+ Bucket +
+
+
+
+
+ + GCP... + +
+
+ + + + +
+
+
+ Azure storage +
+
+
+
+ + Azure st... + +
+
+ + + + + +
+
+
+ Storage solutions +
+
+
+
+ + Storage solutions + +
+
+ + + + + + + + +
+
+
+ + Amazon S3 + +
+
+
+
+ + Amazon S3 + +
+
+ +
+ + + + + Viewer does not support full SVG 1.1 + + + +
diff --git a/docs/assets/techdocs/architecture-recommended.drawio.svg b/docs/assets/techdocs/architecture-recommended.drawio.svg new file mode 100644 index 0000000000..e3af4b6b5f --- /dev/null +++ b/docs/assets/techdocs/architecture-recommended.drawio.svg @@ -0,0 +1,391 @@ + + + + + + + + + + + + + + +
+
+
+ Repo 1 +
+ (with docs) +
+
+
+
+ + Repo 1... + +
+
+ + + + +
+
+
+ Repo 2 +
+ (with docs) +
+
+
+
+ + Repo 2... + +
+
+ + + + + + + + +
+
+
+ CI/CD System +
+
+
+
+ + CI/CD System + +
+
+ + + + + + +
+
+
+ techdocs-cli +
+
+
+
+ + techdocs-cli + +
+
+ + + + +
+
+
+ Cloud Storage +
+
+
+
+ + Cloud Storage + +
+
+ + + + + + +
+
+
+ + Amazon S3 + +
+
+
+
+ + Amazon S3 + +
+
+ + + + + + + + + +
+
+
+ + GCP +
+ Bucket +
+
+
+
+
+ + GCP... + +
+
+ + + + +
+
+
+ Azure storage +
+
+
+
+ + Azure st... + +
+
+ + + + +
+
+
+ New commit +
+
+
+
+ + New commit + +
+
+ + + + +
+
+
+ New commit +
+
+
+
+ + New commit + +
+
+ + + + +
+
+
+ Build docs +
+ & +
+ Store generated static content +
+
+
+
+ + Build docs... + +
+
+ + + + + + + +
+
+
+ TechDocs plugin +
+
+
+
+ + TechDocs plugin + +
+
+ + + + +
+
+
+ TechDocs Backend plugin +
+
+
+
+ + TechDocs Backend plu... + +
+
+ + + + + + +
+
+
+ Request TechDocs site +
+
+
+
+ + Request TechDocs site + +
+
+ + + + +
+
+
+ Fetch files to render +
+
+
+
+ + Fetch files to render + +
+
+ + + + + + + + + + + + + + + +
+
+
+ Source code hosting +
+
+
+
+ + Source code hosting + +
+
+ + + + + +
+
+
+ Caching +
+ (Optional) +
+
+
+
+ + Caching... + +
+
+ + + + + + +
+
+
+ Storage solutions +
+
+
+
+ + Storage solutions + +
+
+ + + + +
+
+
+ TechDocs Recommended deployment architecture +
+
+
+
+ + TechDocs Recommended deployment architecture + +
+
+
+ + + + + Viewer does not support full SVG 1.1 + + + +
diff --git a/docs/assets/techdocs/techdocs_big_picture.png b/docs/assets/techdocs/techdocs_big_picture.png deleted file mode 100644 index 8b8f7a2338100d49fff40371797eda704fdf4a51..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 113762 zcmeFYcU03^7dFbwC|F@cMQTuKq5-5!w;(MbT|x&TbVNe$4yX*$qy<3)>4X~T9kCEP zQbI4o&_j~|p@hIaabCfB*ShPjyS{t>_zHpw{Bq7d`#k&E&)(-GMB{Ik7L?E~8wBIG=L7VqJnWl+#QbM>Zo~$xGAIqNsx> zgRd|%DGM@ko;|_ymzfA=k=?Y-*;Q`)-PEUrt7B_`ZH2{KCQt6KA?dr>#9R zF!Mhj$j^V%|M))@RrAF_VD^7Lo+e#^Q@r%gM>Es^`PKieH}snu2~FWF=Y>79*FQIZ z`_`SVI5!}LoN5XmPfku(j3f|$`QgV5WBNvbtp8GlOBDC=GF9 z`Y|@uLYzEi$#dpnix@h5NnPWO1!m7#Ej)gG7eD5)dY1Z93-(K!=#G_s!jzBS)UKOf z$#7y~J{!@ap6n>#gm6>~mr2i6j|z*5I#+LhIB3SXMxiT_jYv%dY;_=G0i7K-$J|Kx zgFwhu(eL*pG#Hq2j*C_}>Ki@gS~!?|~T@#49Aq>;y@nF6Q3KY#u#P~m8A z@2KXQ(iV%><2($3r}sn65?;ld@eUgpUTVorH7N|K-?vL1U32a?laP^-5foheB0nj$ z@|8#5inj4EsGfosZBZT=J9nnK3u?qzYZNoCp=A6rVz06&JKhW(U{;JWIEnh>?kf?jg7BqcwF45Md!8>1I3wVJURY&w6eQ@I5&C! zEr9@#P*;L03keCy%1+*W;8nH%lDD=Svbu;ayytYyl2t+R=Cw~`n;BMndppQPsjbt^ zYvPd`+pbcPjaqU@GMD$gD_-4`larPc4@r&!kqTGY8qIpO)yKfb<7a@&X1Kh4yjcbG z(zxu5BRcktb{4e_wbL73?swaHMfDmm)&S(Oir-=m z$+4eVgSt8v6nM`FJ>cvv?Ue6|Zj0_?S)NJEaoh)kgthmtwcfzPl>P431FQkumB{+_ zZ+#)J@6@^rXptY~RxE*d8QgFY+5|ICH{C+ zgPlHIyoLrQuo}AG*y)cMWjVu~62JlPuZBdkUhFsop`{WrFf&;dfFVNR>iP5M!ANWs z{jue9DdNdF0%g|)01Sp%PHn!pt3VP=Cbb#Ddfrw{?6*0Nc;8D+k5V$U&)ZLnxKWw) zJ9P81b^eRo35-VJZKrD@f`aC9jHIOsTf>Wa1?Ytn)ClM9>&xQ_3Qzk4#(tZbQRF!1 zrO_ZXp*&ljj!zr~VQ=?ElL>6aE~_k7JrITw@h-1z129gHYL2wGTdI>lcg^Kf2oj$1 z)V}sOzvfp@L-uO|@mpW^Cv~*N0(Sbe>3m5el&`HgRBq9@-a~G>eYnqp>v0$f*Q>5^ zVAHbJX4*ckG5<%xB5|u?>>>HQyRKQ<-dRi+tQ@f_*w2`8fl{|Qz8Y5XT{v2BvkS$E0Oy9&YZH-oS+Sm3iZAY$@Ye z1&CZ4rLrb8`9h!e3ZSmlm^pvx5gqA^FW6F6=%Jf32R&ll{jxIR=&tN~ouW}@ZT2RE zMq;E&+BMHM>s8`NCe7lD;NaETsR(&<&wfZkp&9z1=-ASw{+VKyfDrlui8DV8zjipuwS*{dv#T9Fa_9;&WOS0Q=k zoA>C*$Puyhx`oD`H8y(G9t}v=gWr10OxFR zTE0{rRMMX)8F(i?AuA;9-9GxkNC$XTc|(KmmA*=zS(`XX&k`^=Wd;{7nf@FKWE-|i-fuTO`|>L#Ix#xfujkGTI|=WA zP$RZwCll(~BM0OK%)M>bi#hWlx6baqtWkdJpA|Hd@06EbdkV$Y_%6QTjW6~vB5mWt z&CrrDB(_s*=)R4&)7si%L7C303LFAn?SUZPdJM>tFX1*d2tz& z^KKs?@z37&|2+XR?Z_H^ne90d^}x((<`t*BS^=WEzZO8cKm?>j`vj_YJaI96aiBrl zWW0a4_3Y8Ea06K4S$s?Kp_;>*BYpjSIW(Kk#m~=Fn>(hx9*35khl0Gh+ul$#?pNWL zm2Q$uA9sZe-MDen&%Vr21rul=Qyy+)zMvX{jQ2OM>ndDFs%>xx%F^*SYOO|57^*IO ztkOB1BGKX;Vs?G4uED*%3^&(>3fJ>Hwo#CUjrzsB-?JH@IK{ldgN)W=#oq-O=HHBAya{jE+)oc%L>k` z&bVj+oCD30Q5Ys0E{g85ei4|tRt`cb#yKkdl)bc`Q&X9!XvB?OJZbgp^9_Nsbaaa; zFZ%*#fO|Kq3W%-+DR;Fhq$ow(_m6MaV2#LORC+GE_dH10fP>116PfF9L+O7;En4OR z3Q+?rvgYZe9=ZP+WQ@qEKM2TpQDH#qI!w{`atqKqGv$ty94#-4y=W9192}mlC%2&i zUNpYGWL_+OLCEAi2jkKS>P>B7E0<~*K5=kD1;{%;`y{a@kie|`yssvlMm5#JHtDkW z>nXA~BIT@ZEY8Q(R6n8q582b&2Zq`}-Ol13@WLNV?87mymzFjY8q?g}8kfLhLecTJ;SDPwhSMlhBV;cb;5J z;Wch?`!{Lwy^d|LRva$-g0^M&n=H{c^(r54?60(3$SJqjx};Q zeuLKcfBP|Q2JB(b%*KYR;>~%u2Ov)2S;b)rlVj&(HB43*p9wKTfkG#eT3dkzT(IH% zn|xPIRdsK>zsjQ|`8jzZf@U3v2G)TKZdJ1@+IQJHywxDr1U#goEnlWXh4WVNI|+sE zK=^RF4Ke|d*4qmbT7d7A0=A-Tw~=N{0O@osL)}uu3SR9?m|50d{nIZif6FX^pm_R+ zg=Q=HNwxTI-<|+Xk}o~fKb)wuE(J8+ocpn$b?3haK+FP-gFJ;~P(Gdtgdxz{GIin? z2n5%u>KWa@#MiIqPf&xhsfElRV)vrZ0@oH?p&MWtUEK`y)T`Tzh&iB6n05yj+4*)t zm2(eSD#6P6q|Q#aD@{0(ZGQe_JY$k?FNhaSRUeuqXWf~vvaNIU&u08V=bh-$D)PeKk~ z&cF!F=>|9vzkokMEfsjD|LLJE@DC@BiV-U03$sH#OLf`$f5TsaTDB(gjG#L^6S6^t z%Mj1l^jFbQ#Z7+&P7nR5dZV;S8l#VVMroq_k3OJ$53_V@EkcmqF~)}Oz{@pb>a!I1 z=kwH*P<8s;?s(O=*`ZLsRii-GvktZ=lV=Y+-SjLxNOW5w0gDS*wu+tp z-pGl84R-v22m5XbeO=CQ>KSr~Nx9Cw1Nx&Xn2HysjuCle3D))~AOu);@1dQV1t#`y zMiQ*n=R^BTzku4yrdn;)+}^u8Z3EMuVPC93nq9Tujci&WijOn>nZ(|W?F}YQ43yrP zAS~P?am*>&1$Chu(0P6{`W8NxcscFfusbx z*29fM>7PbTc<(yfK69!S#*}4GVT+_mQ+ml`!@XDN_K6$Lb^3VRx5i85WvqCed*uQ9 z9k2Gy7y=O%9yMVySlSkj2myJ_Ax%A1OgM$=9-Jwqf$dD~w4K~qPr)i|B*X)W(D4gy z-4eWz^!^^Oei=*41k3dYz7BA~tL;R?L`CYS8BYr{LpL4EiLQ(^sz-y~E*2`|%vXJ` zx*ClEIK!xix-x+Q`L5&%oO!cg#@kj+u@lR3+rPy26U(YR@Zt4jAEe2#HYXXf=k#8) z(}e7upN4DgIS;i62x;&lriH~=1Vk8zFCqku8wv9}Fo3{$k_D8nQR||LTT%ios;gng z0E`f6>Lu1qO;?Xma|-r?KyM`bL^B=H)a&$(V=#-)`}7Cop%DG}@V7n~SKrNwru}I_ zIP>XGR+8}6V=6vusv@)WQ2U@R%BsV!gjQy8!DTl-ig^{n0=RzKy@$4M%!k-yc+x?f z*+t&2_sv#Y1Le@1$9s2MMt=JOVTEVu9 zA1^O#1AN+L2*RxHhI+kv#? zRgX&5EAZUs<_JrExXST9pHxAAU>VTua-+RV^~nY8C9b}FKed8un@upW9bu_f>baJT zbx(FWN-n8KkR{wjAFCGCGO-0W;l?B0)@%T*V>^wb!_^}Wc+1StvyZJr;Ii8v#S)@z z=$-*qt>4_38cH$W*?UtY@fftv<<_X(n#nS-W( zH^@mv*#zebYru!omO7`OClG|INF$Zh-)5uRS zKYcx~Zxz{*H4{(QQ!kY>{Tu7E%+Ow0K&;@?M8=IM7a39Mw3DK!*w-{9QC>yU<|O9$ zswEVJP*($!-O^(x@G0l6^sD*En<9h+C_7^tWc>C3li(WVJ0ti0>i3Evn1PrR>eH{x ze*!xSgAe%xe)#OclLnQN{+eh$H#z?qebE%H=N~}ORH{0*BQbkcz0_VWnZbCY`>uXL z%{x(mv25hr=K)15&iL-wAGTbd%AY`iexM!GAdTrD5fIjj{Vv^VSL@fxq6N8Wv;CPW zp5A(w{lr7Xa`{{!qBC&jZ>BxbL-BX}>M= zv!A~R_AjeqU8&pD*}ZnwdoP8r4aB6UEaP1UJz?<^oS!l;Qov_nG8;r(^@HU?5k}QT+UM z^|waC^q@5x_Hjg~ws@~GX*E0?7cVO(fx=u$W+m@!eChJ0&ZbztoT^?CEIZ=Uo-Dx@ zG}9+s6yU%6_Z*L+9NG43y^HSEgJI$MZG5gxu3n1wtQ%IZfeE@Mi_1Alh+sKG65}ituCHn=TBkcZy_?eq`r6MH!MN6k%x9P=< zy{T7^ciAdFKNLx_t!Awy!$Kg+u1Q3J77pSh8Gr5&gdb{S_RV$%n)NME(-fQJ{50u; zQ}(B4y3|eg>?i(w_DP+^)a|vQ*Ywg{wt#udjw+J2mL;zMBGu62}&$s5Ik$ZFj)k5BC?3aQIS^aqvZAj}_fuRby ze!--7J8TEN33+-7MQ`Q&TbzTU#G4AkOkf+X*cjx{n=@|DBKU6ZJ<_S)bzj|)_qkQM z&WG(~z4fiIe&~I~$25tfak-`+i)y3H!IoWHxN4@*;X!5&RZuT)N@bOpRJ<9jYRYn(ne? zGMvGaH%ZziWBn?dK?nA8=sh~rZ#12``&(ceBHYe2l&!K|#E0GTFf*L?#A5tNQT6ZT zNAgs7J_!b9F+k5Rs~MyZHwnUNx5C@>4qRsiB3#Z-c<$4!zOC{u(by+0fn`{*eZq?b zlbU)VP|6d;dv0e?z*Aux;Y?G_ZBFNpcI$3joY|09>j{*QkJe*8-t!Cx>S`bS^^{0; zJY@(o5}P%gf@7!t*3aVEdy7#eDQ$Q9Yd3aUy1UdP7K;2`yb*!V?Q~1_bbM6&sbLG& zzzXK_xCUT=5T90L;PqpWiZ5TplMjOYe3*p%)?HVtGcf9Zus>&z1S)uZq=SQ{(!K2r<4!gg3_W2B?FL@OQ<2i3q9xtfiaqD7m=H(7SYrJ}V zLOd}nTfJYTXN;vQCAx|G^e4$q8<1Ed4kp!m6qkLpWwh?1W#Fw|XIVYP_q6)nA1JT7 zI=liTHmz?Fu58yG8GK+Ap!Ip=Q}8-Cd>IN@8*YF34j(uW&VoV~+%q!mlE}8TG=1-_KljLaZVdPQFF5QU`zhqSBCi{2CpSMar z+uJn~X6IR|BqrASMXkn6>YTPwdLE}x#PuGQxempMBvA$0pv>t{fBa@`Onk?qa55?6 zp}AV@O@Ogn$)}W!J&j6b!?HQuMI+NKFd|pe>bzTHHAMeF_Fp+mXkQhmtzYM}7>vQd zFgj@6iR7aVz4YbeyO~1*9*f`_t@&@;|XpFG1kQ}+Cjj+*GDy7}IkLSTYuJ`VMRqS>9 zCzcyJboQEgAt$5&8P$#XjBg*v&?>+18xG9Vk_msC!3tg07R&sm{CgP<46EyiDB)Pn z21;{6%4KtJ4c7EZa#04JYny-log{VJR;N+jE!X?(EuB@#u>!Fo_R+S+75&oM4M+bTJ4MHN^sDC6~jt6m~gwKXa6WD>uNNW<%_ zgo(zU_iW7fW;t3fD3lS=SUtI0!fap1zlFIOfiP_{7RwaP#ThL(YCV`#x86@p_$5%3gZJbSMjzJh~5x#GE*o!Z-v#kuK+yV-{;C*XAf z9&jHq!KN>N5=dDkOi;>pt-uOYH1+&-!b#S4;7@0OnZ5eP#)gKKA{aBLC_9_S(I@>6~*=klwm0yJk!ylhi~Mg-?SX zGvpPK_q8`9W_e}}`)Km| zm^{zp6fJapB5()83(7V>2tNu9hN>k{$XwLld~UM&`}Tsm$-cwt`!1)twP|aapzAlP zX<%e4lu%XgGHyoUwZWs>9el;;9MaWJuVM5w;Q!@MGc(1%Ln#?Ts7j+uGJUenV>Z`> z)pTM9#aSvpV#->5b)?2pnslKFShv{|Ah~|Yf=2-(GHW{S&Guj+M)96h1k6!popf5^ zi41`-Ldi3TjbXr>twtD~)1O)|AC_iM@AJQH6mpsBZPIg!I94Joyxk-E#~T51ubH>$ znE!Lodha5EJN^!BaSpoKGeaZFuZ)tzRn!f-)2Tw4F$u`lSJ|i-cMW3Yl2k z5!Y@1TZ5mt%N9n&Dq=V&lfZ;y_y76_Q2rkp9Nc^|`ahS5{^z+;b=i8~ zee!xx<7y?3_O;1m4E&WADb zOuI8(n7(3Zc0+Djaq8pW;seb>jz2vltmXu1sG#n}I21x3rE9F2Fbw%stsz<_mLXoF zTu6{RSRtO_LhJo3eR}A7s-xmhB1R{@wX^hoAfi7@sg*?%!lXp33X(Cmr@oZ%U&CAW z>xWzMXq<{&rf>sUwneJah0ZLiK-;`P)g z!z@_@d|=Cg8Sc-=1m60eZ!3WV63Y-WYi#` ziE+po`D-6@*wR zxx%MXs<~J6;EuDEBl{8jL+xY(E=hp~r;vRE!0&dC?4x?~7Vq?ttQBPHt#Edm2f@}4 zo|&D@IDN=}uY_IbP~H>Q`S@)MCq^|YOmI*crv5_XZ1qNw*&VdJ^=F9$o$P~dLGKpY zaEUzV5Eho{rIum@?3g@wkdm>WA|zB$IvcMZx{Kk!}v zKQQ4x9n#tDztD$TY1EQId?}uxc$w+_u02SXj(^YcS!Yz3kll=Bg(HGhKs)B@@=w@X zgDBCDF>2i}HXOJRzUv1ID@3c8zhPy5gMKZiqrJFi~Ab^h8!syUw=xF4a*kgF|4f3&O08RP@Bs)1Zx1?R4)BvkKXJ%#5 zucgAH-CxcMoJ1)}{lpWq5U8#t2&$#kd98aLg)#1#zrHyY<4rgNfFhk`hmu`%29c5T z+WVyNDb#R~q)r^ma#3M~c3@uHWBY-93_zpggH8E_kU^(6#O&49aSdGW=FF2BeIZ2e zd?LT1iA)I%7A6h}88&oEGm~&B!}e80DyHrB!3DrMd18bi{-pLndk)-wqNh^jOufgI zx}Q9nV1enAdUW-y+u|nbp^TxIN@?c%t2Wjn$?{xX{y7o7x5H2VNkCrDXEyGa-H?dD zD{b%wfsI4dc9zSS^iSbfGMuT1y|MPmNDNrtbYIl+grK=fMg1liM-Tx<~VGtD~zmW4G#aV9QJx|cV;*%4-OWb&cRo}TvMtl z8yvelEiVt5p}R~)Oxo%>^IQFidg7>OxB*d7_Zy4x!$qo<>3P7#!;xgITeM|$vb@@b zhVN{%t1qv>7MNoM<{;2bL#q`h`}j?#O__EXIKBiKe#2oe3iQ_G`+p|fyBO8j^stst zz||m%!QWb`8}&Inl5UYr7u!%g7oj}C3c5FNPEQz@7b=PxavQu|$X&}tE0u=iFrAhg z*RO>UpMgi-EQ;&t>fq;!kweDgi;0QN^ILq~J_X)JCdQG^ckLIl(b49V0?b-9Hghvi zWXo)Ukicu|*07&IIeCi`{mB=ebe!JnH~9F`fTDIY<>~JGz>5rrtlMvoxFlE!Ge3iJr!;oSiicITr)3gHI{3@E5 z;uzu?jeG0W0G~zpxKw4>KMqg#Pf_m*$5*3Ao>a4{gIh4S{4aWh7cXw@Y>4YEn@50> zMOfRw=us^|Y9LqfQAe^l;etxb^jA2P-mM)OBr^#D2i{L zDO9H48125eUdmukUsPD`IR%ZGp%|mGo!>Y5-1IX zXrJS2x2fB`MbI~Cfgf{Uwyrhb9=_4p6L`0+n8Cc>F2_({Kn%601=rQI)qhx-9-5_u z`7C|+Ut?KzQE1LM+Dgg%f20wA{*=kE_y$AVMAg#MP) z?%^`I*blRHEqu9Vyh8%$h3J@j7DI9mb{I3n;VQnyNiY%^?8Z-2k#Tdqt2qC5?*p_7 zAO{cZH z{)#pyxb9@8K3HGq02D5u2-xXUK%=4<;*TY|OY+^TPA#ppw7oO=644g!W6n%=kQUO= z)zsJ3L|X+u_`XB&HV42%EqC(?^btk;HEu&K`PNt3>LL`=fT*)%Awxm5z(Qe`-D3pZ zyv^t4B&q7@&xnfM4a(BNHUW~`&CTtJ!<7}YTbE{=vRMNw0BQIIBa6Z6cJ&Z~aJM96 z)XXg|xI2g-X(`eoen63SWOcm-Y9etwyDFm-`w-X!wBA%^|0N7t+J|J_T86N}5I%jC0aSZ`$xReEf z0@X%Pbo+phk|*hnbotuW^|CRLX53~Nm!uLlp_T_=KetG}gn&()GcNB2(hsm=8#`ES z-(%ep)=2rylSOv>c?kWUaZA$dZc5%K4XM6JZ7xaB0=knbsc6xY;r-G%3AyJ#L60qo zIc+0%PU5D7gYjm8c(bdV7|B>Vd?A^D1YQVo=ha&}4<2ZFOI<_S$H%th+buBGo zb_d0OOw8FAPOz@bGglNe=SF8Iw%M>|#FxBkgX?HqBc-r1$1Pqa0XZ9bKkY{R7?+*` z&S)qkan=UfvIx3~F^+{3P)9Afb8csfX#k)mD&+BRmoy@TzpE?6D2H~0HkY+q5`M|j z$1MsM)!Z`Nma`|w5Yn1TiS1B%Hm7zpyCJaN%>fdW+4i1~@7e|l)vO10gX`sa=zGSW za`u$WtDFng0ukg8C-}S!T*Xj`VN#?x>4In-2S6gKs9Mg~-q|%jt~ieZG_ptea<_Fi z(|zIY<%@Zb-x4bt9MwTUJ3IQPlJo~FqCS1KK0mx_J~ULWsH-L6OkP$J`J^RxM*pLp z+&RDC-2K)+(ARFQc(ZrN13{^4=ttdmf5lcj+ z(FJ;yy}H zkZO3HP)iye&8lK>yfshCFJv=NWrm+uz5*YXI|+Qczrt9=wOX`P3ve(+1pN}I*-uOY zLyvhRR*@+@@h_==}g0&zmZ7Z=kOw6Dk9*I2G- z?@PH#qLgn^LU<_=aC6M`BUb~Hh1K!yvrUk`2UaqneSw(XuHg2EDOopVJE!PH zVyc;~>4126W6!o{^GK8qCU#I-LWIF)7(LM!1K8oH)!57w>GpZ8Kg^q@#6v!Q`@-_c zc%UcNCoNm_)9zB=*r*|jO!=lJ_LTI;hYpbRAR{dk17 zG_Pj1=`X!b$cw*=i5<{PYXWp!w*!HI?%^$2%e{V`1>X<+&jV}7jkdd>n4p-`imDDrfCoX|@T#7e(+(E2 z2f!}Md53Xdt_FFI7?iXLtvv>lApDa*TT(UA1;uw4_REPcs?YkTbgddjBQbrZcMD&z z>ZY-AGYdV}WXq0cLLnJJ0zjV^49x7)*WVv_AH^dnw%Omz)DowD9iGcnjdUQ2TAf6} zbAFOHYtagpLZ-Na&9-`tT#M1@%Ngq=U$CNxD|SE%Tu*!ynFwT{zqndM*8rs%5CaOw zIMTUXiCB<)cY6WFrpcJE!anBS%o2J-zD(hZVQ}&T!vNOBy}iJNS#}yppGYmK@?QrE zK=eK3BFd?hDQ9mGv?Eur680D%;KR@BS1s{dBxXrs?6TG0w!L3SVNc*kPq?`DbD4*$ z$G@i*_%En>!5?u4H+^I?1{Jd?SGTim{}aW5cm2#U%*6+>DTVBoX`6TJlAg!wb*Chw z{J4Ft^0H=cReT`pquF|Z)WM2-M5OQsa?Nu5#NPyomYa+~5B@Aa|7RC_VcXx*Bd5go zeSH6}E3tS#|BYQ;eqtq*RnBv}`Hy>K8rsc<;dO6}6z3<{Wu{1ZEJrs$Tf8HnE`;!= zQYjB;XVf>O+xKVB2eE$nCd-T$19BvbI0`f!;Bd0za)NAgX~XXm6R%8;*o;|K&5t-f zbHf_@h$qd7UD{WEHL8SGpsc4D+E)A!YPeFY4=N@HhGbp%Hxm_DOe2HxrfU6nMo@oowQ&GQ^ zsTDUCgXS2}wO~oZWs9wPV-2P6G>Q?lZ$QOnqkA90Fgrf8{3r2Q-u3IoR@hEwPynUkT@%!9A2wjo8yY zMXIoFXi0q^=Ki_Vhz8uxWj*BYn>Ap4p4&@R_07&QTEHIxS9gg+?+1g~{bGk7$K-l| z+(*$jGvX96#g)J+fi>ezFWok2D+b#-eRF_smeQe-NN>~{-_0P(4W57C1kPN~A#nOY z=!hK!N-7%N1$?{lfLEAzj-J=8g&G#33YbSi;VsBP=rI1q;wpa^;GN+h7$&x9YKu=0 z0yQo32I{pZ@MF*4^garStjvyw-91WsZsj?zsjj+*(VR9z&?dz(Ohc(Yu zy#jtd{-_|daZMq<{|Ofz_i(mRA;2G)ASqVnZ@{$GOLt5(BjgY4fN};&o^02AH3xZo z8nc<{{F|E8j6Sh#ahp3g9B1D42xIrLu>P;ywoHv+&Wc+=HRYS8WXN(7BXS<7OvrY- zy~2X-?W&W#URU(uyH&*o8*pV?;&AC78cFO%!jdxpU#q6%4^sw35H1!c)ZS7EkWou^|uaC+cyZYYrize1jr7W&Dszf;Pz4yhT zpe415AYWb4yH&%ak>QySOM3V4iJkkjD255C#R3H!EQb``>iv;`{;JH;r!Fs(S+zfh zmz?kG`HVcHAQj(IUrZCfWyAfF6t+B86eL|p>o&`0(xXvv2cqc|v+H^e8Z~#6Dg;1O zi}?~q0c~8@C9=uvg9u+(j#VjcBsg6vY#rm;{FO6A>DOqm=lwuN-I6_j86;#NRx^oW z5T;kz!40p&FU38~D1IeU1vE(>iMh2~Wjw|v!%DoBeYxcRf%~K#ls@jbru{%6P`Sn8 z0yKYlnsxz5xk=|TGmCcDtLy#}J`I`;Dg>R256kt|f5mcWp0vW}=!k%g1kyQrQGhNN z3Dvu&`vJ~r0Ll=AK4_ETF1`}?1|3tp>+~|r)lhrqW=@6SBRt3g5+bve0adq}5^^OW zq4G0RI`wvLPYyq*25UckNOvDL zHdJ$c%jVofGm#+r&bX;?JqKb3ty`@_sRqmz;5UFDcE>ZUnA&{s363DG(y9i80FeQ_ z&<2^N{nOhxbjahDE*y}RP2k4hk|^jWs+Y@duR4TiDnXu zzc79pSKK7eXPfG1zzvMjUKsITtO$$9=|LM=N<6^I;nHW% zB5>NGc!*St=gduA@cy67Wa@CQ;D|=8F>Z~9x6@z-llyfxY51&%@XelQO&p-;Rnh(U z^3*9d7$HNh?0^eTDdhuZdgkqHoeZN8EQ=x&SaDAFU4qFO&@uQVdF zWXJ8?N47CCp$YOE8-SFP z$e)z>;JGS1;-Y=*9%oR+^!d;MTlu%({fr$ibW)Z2?}%D0wCp_y2_y#T6g01ejjlG> z(4k5oPp8gkf*MaYQhUr&GbJd;TDmO3VyhTjI^NX7$_#>&=u3G;tCIE@SBED45}h&+ z7bs{5kBauz2ar*jF8cKSugfIB(U$A0W3bzYb)8u^Q%a@lUm5|0R=RjCT9lpLJLl6) zpyujhu>>vGu^>c%V!Ucwuc_wWIo;1V$Ch6YrPfy02k^QOC>;<`6JJt{@4u#47^16MKB&59U%mbfM-Nqj0IAfnnBHV=5MM-!2)kdubT2MPlABG=Xp1Wvb zK9sd9Sceyw-P4l6vrw`DZbvUECgTF^%X*v|bD~Xbom;jG?0s50Lr97A>>SFD$}cKRSojaqen9h-6!6aC}hsBCq8#}Bq~DY|WD zi;vf|%z*!$N+*~P$Y(%KnKwqP|HQ}V<)$G641HY4R=|%b1N-GN$U*Q7!AoPLtwWUS zIlfVg%^eU=2&!H866U*7@!o`FN`LC{XFGv{dmiEIS~D{y`nu_|7kLQ|WskT%V}{l; z!WEot0%p3Y&3;hE^zHc7gCuOBtB%X#R9ql;{Yg>yD#^v-etOZ38`(9b3T_U$!d?3c|Mll28UlF+$9#6=6=9HVgrIAobfr3e@X$YFMc`mP2% zQ7ia`h0Wg@$TVpQjO$E{18vonN0sSqnWD7BXP?-NgDPePMW+|u58X{aL$WfVy^ZSt zT?RHM2MKICQld!S`t5KWXp@w%1DStaVA}t9t%JivL%hAzqAp<@!X-LO=+E%`rgrg2 zK|VeH*las8`Z^sezH5gW^J0e)I;Re=tMW(7-qGNG{V>xhi_OBBw!Y}<(Gd{F%x}9J zOGC%r@+lk>ekyjSu!`s(5}*1lcsFP)75ps+zS;3S^^Bbw^!$&w$i#rE44G)w@~_JS z?TWb%A=18NZQ(zgdQqWCO~YB5wi3+bU6R zCahmLT;puabDbO6z2~5S4GdAbKrDE3el0Blafq~u&HX-}z>z)Ua5FHT+7{(Nx%c3o z&NY-i^SU578u&>Hyk=)GDM)&tjS*=|)Xh7I`ab6gFz3_SCq%-rphh!LV7_498#qVx z;qgCyI#zQJ6k!1XO{ZsTYiXiENd>BJ(EJ5W&~b1|z3B-~+P+a?qY4mNI^{54Q~hMF=rg(W;10v72` z7d?F7LrZC&qY!`%0LuyRUF&l)V6xebL+FE8$34PSKT(YDYXvZEgQ$eA#!;?L5?An1ynk!RXAH1Lr&at+ri_$eH3A(8d=s*21S# zj>v$)@8O));FK=LZxI0q}D$Xa0$4a$V%ysfwS!6VU&RZ0y~FRlEdz7nmxlu#-!|J!=E9q{=7rgvc_d z3I$)5G~WUAp$Ks_)QkUcz;ojp*)bAvoSCFV8++rv@Z%Y~QzdTAI*T$8vplSfi zU;8Ml9qA^2>JccaL9qsQ-j6y1+;BcC0P-@Zu7RLWP~|;c2P77CqPtk1<2RBo3b72% zkuT%7_|`}$kv_-4iGC9ht`6u1SCA1!t^+)ToT1zwdv$5M4Ej%u0ZjfC8o(LxWx7A^ zbp3a*Q@U6|Eoqbd zf3b-6gi?aYf6p*>qWuWsS=r!&LSA}4CUow>fKM4d?Qr9isC_<8Mikz0$}0gy9ymGU zDhmd2G^K}IzWE&)3Ez?NFU*1ZVWY3l8n6IO%{|bzU})hzBGrF8^FyioVk@Y=|Lc+V znM;Xpoi_TVcfU39un;CI@{l*f)yaXTBEpn#p==7Lz3<;z0MvF}i=mVKAY~n91j0un z|J16rkQa%H>?^)gX0m(kqD8bXzEc%&qZwoebdV5&gZGh(ASE4M=khYYqE^%%6J-7) zB43;CUc~}ZKi}OOy8QVlA-%fv+N8y7dISS{KUN1tGq*3Bb=WvKB)O4_!5aqspbRb( zw-`dv;ccz0R%2ck7U{$OTPtHF2Nt(KB_Olb*Hpp#W}iL>;Vx0-Hj`#>=Hc&$De1C6 z1~IR_J_bvf;dki8zZtA4mtTIRnni>7xHZUj$~}0z#usreS@x&(-nDCJ9qlO|b5^jJ z-uO_cH!LpOjREU#a%}Jv!z}gS+XUbG+%uLk zNEsq*5ZMa4Tej z!T1E#t2uyS3M}RpeuhPcv-ixnhDS#_9m`adS|pRP_9JW3ejPotJ;ZN&=mS4a3KwK2 zeSVPZpbPDxDE*))mLuP5W0L6Cph?nHA7Ix>cODVgNaGc#UW_AxrQa+c-Ifp1G-snT zOn%_=)ASX6k1*QWDSVwbZXe2or?syN7vkt?!Can-i^6_9k#D^{GFGSGH)TKWm-VYo zl_mq5l(~JyF!oM4hFSMjh$GvU_vTn=5Olc@xlf@YJc@K0i)Xuo*>{F4C=Tu34ck!C z)g9R)&-T@DWRJ5g_)h%=z#LYe)35^ei1YxmDCOlaa6_Pt!!UWguxnN)c+7><7e5x& z{Pn4~WJmO!>r~qdEgn}utZIce)`PC!eV=nj7ZP7xiY9YeO>aD%+E58`w=2wu;@SMj z6WIr1W_+M}lh32Bhu=r+W5)0}1bV(>Q>PI*6?3-fZZR^c36{5vYs$UFY?wQarq6e< zm2(h{6z*0jKSPbCf=kQE?C>9>YQ9%D$pL6CWPag`?|)C5wNXVR`>+L>re);>v?Sj| zH2-uL6L}( zkF%mvnX?6wK(!f?rH60q@vQZmFd@C=-b{^937i2KrROr-=>dRYZOaJ*SiIFKiMXkB z{6pIAKd!B(kO2_NfbyCIObn*u1Ak_OmEIe=Cz3!Zz4IqXl4%QZCDd(-{=~s19%k0y zQ8n$iXbbyTftE9x7olvcMGM8}se^Q=pvfu8zL$QbntlUXr2793Z%(PY9>GpG2tF?J z;qsQfPM4O;?;ss{jn;EI%(2uF2h>CcdS&3%UB}dPxY#1Kg)&~EuVcL>IQ~8G7Qg99 z_u#;>)cf^}`IisxbKEvja`(=h^1XvwmYkR`Q?uI(fB(cj&1`6=>CTWcfC_|rhC50D zqx#Pz`F~a47~FR-8$g$E?Ct~T!(|?A=!upHck@XsXQCP}U!)5Cmd!KqoQi5kfgy-Z z)+YicsD`opkFhh}pv+yrqnLt-pUHEBdcGgyL66!@dF;H#>Z!4tO^Q#i{+`2-uLNC96Y_G(oP}6{FPqvn^ zQ-mRuFIrd)wRe{CZt&T+hnWV{H5P?0{x!E;10*gX9`Kv9Loi^iOx06QH94?>0 zNl0sR0H0~SuLLVC_L1PKddQbYlTU6zA_cacUZnUEly9*@SXo_%xFL~FW7TDunOqgY z-aCYu!oot;T3+kUBuys|CnqNhi~ozSFAs!zjsC7~r45CUwM4>1i0s;g6xm0Xk?e{h z`&Ox3*|)J|Df_-JStpg9WErwojAiV5mf<~L>UQt%{k?C0+}rJH=KFn~=bY!9&-t9s z8JZiaUA=6!soDvjiPS1bqLI8#-~O!F21VGFKe-(^ZFrt3Q7UXiNXvdZ8O5#3v>yG( z@7tF@V6O_goO844+c*7t_wI=>EpLWA6zTi=HC$~dD*4Bp1ANJJf0=HPnVDI4sjbE5 z1&}_R$ncDe{QOywG-dhVL9^#VbD4Vs(&Mu5<>#kF2+ZHVbxYD@__zO^CtggLT6c>d z+^^yb?l9kl9qAcRIg0X452H*^Xn1Ws`>m>A1~jE|(HXh9x#{WNv7yBqs#u7>!aXT| zJDndY^uIrqO{R-)i_)VCUEneEG$OzX$L@QC9PT_k7ztR<*Pquu=0DZ-TM2La@VEu6 z%-Qd`mx<4$|3i@|HPuBw{c(d%{qDB5poMDvP$j8VcIq-AkQ`cEgqLMxWE5PqUI_WM z%g2N&f)wulvzANTT&BT}t4w^Yd-)V)g>(M;_Z(Z=Fk8 z94761vhU@1DDR)g-phz2F>VCJW+0Mmi4twc*g11UbONZ?|JSkaWWgnQC zjgO6evAP7vU#&mu43XFtCd$mj)K}qbub|KfPfA8cHdQqrtRyAs@-ydtdrwcAUdP|= zy1Kg7YI&@uHys^IT{%=l34$h{Q?O^&b7RK|jbUY_rEs3{!e#a|*nAP25g)f_EFJ_n zNWK$qvfNR$6JyA)9L&dSoDLg5ACc%gvWC;)fu`Z>IfZHr_(`99rwIOY4jWAog3Qd! z6%`fBbT=Ne6EoqtRw4vVCbypmIV|Vthij2;@$*Xj6+yof`U8*|d+66^>zp`pJxS$h z!Y3>io2gwawbEyE?%cWgS~%o2HP@HwIy1G^*94wHA&H2d*Kvh@ycBLUVjLD0>AAPy z{o!4ff{JY?G`g<~f3YIb`_~p*_LtgDbY41n5(BGO7#m%lb>s1xhxK{u*|xSlPoHdT z5ziayyrb28sye>vdFz~mtURpOR2N2X5q8D8*(kP3|I0fm z^WwbA$Z}Qnc^H8qS9y?`NCtV3Q4-xd{IBVicJ*3oTEr+!?UQ^;+;o>Smd%b53?@bbL?vX9?$ zp7TdwQd%rJMmIQmGP$Ew#-&z=gW)WW9WG@MZ66R%lw}ay=i5O9RDYC#LqORgq$q8q zq(R26{E1EM-PD=(xfh0(>G$T;8IPh?$K`YOKG`?)(O(KX9y;SbyIT_u5t3RzpWZ*Aqj z2#30a%mfxHL>T1P$Xjw{KLcV~g|<5@-ddk4&+xdU&ITw8fhh&|nY@_vBv`_N3;Aj$ zel8zFgngbkz8Vh&ceV{;U4}a!yaQt1=jSh=(_V`+*k351yS&8h1btxhsr?Oi;-PfW ztv4;kB_<|D^&s_(YlH8prC{}y9GB^mQ897x#8o3+bWeOVX)Rs<#6vb4d0pOlCSdQd zTl0GP4TG`E)1Z7;vxyFqfycLlC($HLD%6Q>sP3Ad2LZ#YC%!uJzWyTFruEz%%f^P@ zT3N+P(TC$31jp8=5-d}R3t{Ve;v36zcNNJexXxc?Mj$xbWx28z4lm z&AhEM$|-+5-J&F!JCw?4rz5s9+J_AtjkK3s`s5dEMbYF(lcK2p9De>0?m=Z#*DPuaN}f3?rNJezIW$E^A)Y4Q_VTm8zL z$=d=%FKW)ogJ0%uF0pW}*X!hjFcVL8@>4f>tO7E%|#;_8NK?c|Ps;Rl18DnG z>^;LEW_%^|DPfIC+D&H!l{PWt5a_GVMV4*HRww|Bep~>P*M>5)*PKQ}T-p5JEGPng z$$l@uLYFofW8Z!HvkfQFyum$Z#1uvEfdz*0K-+xL0pO6!_QEC+vFb%wkKDW9>{8dlei{k@Vg!pXMKEfW>JDOtA^MTFAEtj`XgRE%iI0 zB+VZa(+RH%0?28eM{~bN;J7l}ce=XKAFU3D@nQU`4gBn*FBFx)!afqeP5^U(;L2;6 z+P{{NUeiA1+@7p2R{6Y9FYKuzT%%)^S`xW}@Q$vOj*Rlcp1H4_lpu ziz}em(1`%(mCm8x2lOO#3?QZk51^kQ0Z)56h7Y;LhZhu>H%NdZ1)^%+}M`2|b1+JXur_Mqa9Pp|Bd_Lu<_F|X0Zb@C{ zY+qlU94lJU$2wC}1l`N)1Lf@RPkZ{URM`D&E0K;pAyRDLBIQ_p8YgbIpz|3seP~mI zxdzNyRpiJCll`CTvpS63OB|Z#OqWjQ;>N^VZCtaN-4vWdiDVS0B1<#OpQL%ldnfC2 z+vh=(NQ>f&#-T2MriVv9(Qi5FPsdz5={%LA*RSVd=f5rt)W`ez@Tm7R`DPCJk>-B1 z_r~B^?{}~H#H>iL)GRe_wU=$#FE__n3?$Cn^7~Zz5td>+EM=zfqqPGOPM^j6-=xxL zub47Iru-~#%B4o|_%_c(5Qu&u_+~al603IlW>yX!9v)9GUFsLL-W@x>XEhm78VINb zOe*5P3x5S!c%gAa+(4_{@RvD-(M<_q>$iN1&{MP=cSbwLD@L4JvI{>K8911*Re0ZZ zx7F4X0fo#j>;0dzj#Uc~;iuft`i9{QBw~6Mbm>E;S8*ghr zerZ{pqCRaBH_Eh<#9ALQCQ6cXTHdnk)aRnR6_c{OrL(}hUSB!eWGR}O8+e%5Blh_& z;wic>GZ~zpf3SDY(@Q7c+jE|jt{()F@vf7U)!BmqGcS_)lyj1rDhZ8c&jct{wuZEev2u>Lt(*ru5?OBHvwY;^tZC0_$MOr?Nc$*^ z@`)>!Nl;6ggj^B&r~(4Ve-AM0f{7NGZxO4&%hnaT!V53|1;+|Y1I4V7LlEjhhWl+h z!PVa_a?DlyE3?=!$>zjazuy>uN(e0Oc(>&8*|G2y_MUFdv&@Vo`WcpD!q%55t;}sT zB+H>krAAo57DPF`u%*#U=h>-=5vntVYc|X+Bv`TeI6dexiv1bp-Q`1%3hSxV$!t6Z z-hKNrg^PYCz1+oz)Q}*<`O3Tocti=qlMUN%V!LLc1r%8mg1;3jYpxZ_>~@l3598}b z2Qaj{w?n#BgOxugSxAb(W(Tp$XT#5;KReO;O|`EFrg#OxeY{R1KV(pJ-?heV<%TQ$ zMQUV2w64O2$nE=uEQQa6>m3|}Vnq3AG@A?;=p`IU9lZ$c`5uI?tbA!=U-OnC=wka% z*Pm~!YHPeJqUhcv57M`*u-ml|6u_~40ml}Qy`9%MKgcZ7!8%D%u~kK;b4Wi@|KQ0J zkW`#~q0eQrK6$u854RzWGaJkvj8Z9jtmKn6MM`qTNp&mRg*NMt6V4BR4qLl**5){h z`dsn0P5Axi!?S&rbO8T!qGbK9vg?`t!S~QzhiuTz3yrgFtBrXCZNX5*>3q|{`Qg`p zwFu1wg(ojd3tLyQY~<0RBw1c^`z@Qr!=Dq04pZ>+-3PH=w8<5ZH|M=urXz%?1mVhU zIz_<`_`|Lp_@T+7!qXqhSwnPukGOWh?e~qjii+z7=!6kFS9Rg53J z&G0?&YOT>{0}wB+{TOhrOiZu6MNathh$Ko#T8Tqp>fBNFZPa$X<6&0aQVG&FHQOnehHIvsA7@7|<0 zun+m@egFMv^X-a22c78W#9fae02lze7uz#(@c-AGj{RqmQ+LX>2$)0j#P8QbT+2a< z#&UdcV+2~oL*kp88#tqQ$t?)?7KtXnVG;{>Z~)$Y`cNC%-kD?fk-B7?H=NX+ionlo zqm!5|E(@q?BGuc~Z`T&5;BQ@9yb!kMJQzItT($)7Zk4Nlz6=glz$a-VB23`(|h@ z$0hRv!bsU}TxQ$-s%av>k_mhcC|d1&<6E}2wh}DG?m|oyo*(a4lGhB)T{;;sreZk8 zU2Ty2P=v|5T8DzGRWnd~{vx6dR0jmiIemN6jUOEF=FlUxZ3q8G4Bn@{DJ{KY>@hV^ z(fb`&7tw%BhLF_N&=^@+x3I8?-s%sENN2b59a?AeG=xT&8GBSSI9|BWX5k<}l_-08 z!%tzi2%tT)euF+IWw!AIuSGU6$HOAWFI>E+uwosRE}KK65XP$uZxCk}{t*bTw81+$ z)GiSQ`ptVe;*Z&!BL5#Bvmkl=lL3F2BXX;HRT5)<{Fr;U9-}JGz2cgdT{HA(-Pl@= zmn^EMl~=b^jrYY+dxZ{1SiAh9`ilMu?`p%po^eP^B7&!~J7p~-Sqt0q5F{x7=VFB8 zQuU$UjIgoci{!0&=;@zlN#406LGznf@jK!jI2qy_JztgLKe zlI&Ev*;iF{c_|Jy$b4vgd^|5NFD@>wv{Xzq2H=D4jKsw1xd6zueZwFmwB}NIH<-|q z&w>iBxKVRzoKss(?ShC%N+o+)Q2f$5X+?2T zh<4tN-~ZQQFI+hOY3R}TU6plqpVaKMGED}5B_$=YJ-ZvPKcI$Xu;S6o3P~EP&`J~M z!-j^YhI0lVz*`d1iME7vf!fxYm5CEdip@i(3JVI@Uu%Y<=S-h*^HB|r3-Z=%d9srfYp&y$S*Es|leLG&~Sk+7LPiz%`Up-PIacduJ zT-&UnfzipMIW0Bb)dtI;PVXKle$I~jZ5Euqsmp9}VX1CZqa8LJkqxXy#WFH7d8~!n zl4uGFiUxKCGh1%7lW2m21h4J+DT@>_J&y7Cnp55B360uAmF%dWNG`tXorBOKkjEDh z_q4M@K|ejE|7(5WI6>5sjBHni);%j*XpwU@PimyOBuj6k>qhlytlY zN(NU2u*%IHIPIvcwn!n1K1%s!w$Kxa%je~DhPTguKqT;{p*%nH{j|=Z_xq}-E>UgG zj#8Zz`n4$yi+qv<&1JnJmv5LY8B!Z=B+%2OzY>ZZg`}`Uhs(=9g&jK7q1v-D6>Ym7 zDa)eKD?QtPXlArgi!Ubs^8@a`s}0~V85tQ76|vXzZ;GN}*VG3a6qs*vqZ?|VsA29I z^3uIH;c+Oyq9>2uPG9Qr=Bo;djX|7?xnH?0_g7{HoP(Fb?swZ>hjmkDPr^%mq-q)n z1o<{*6G9P~2)+hf<3`C|Ir3j; z0qquC6`q`WDJUkSoJcf}FibW04hsvY_M*@|?^myUxV+4p+v4Kbj!O;Ay_-x+Ns(*r zS2Ow?tpMq+#^%_{da}u3teYTb6S!p=v>Dq8jcNfTmSyAH?Qq;~!Ni zDZwdxADuzgyM3SZfiq{HqjdkT>*jY?y?phm$BiI29PaJg%dYbMS6hJru_nB>fkUH` z?0E2Bh6-fDwg=^aW!h@>)_X&nPUERm7mLSLCBe=Qda{Gu#FnoTCnq~_chuF5 zCW}f+v|NQ+Y0x`ndwx;I53KwfoQ@~<5KsO6Yx&{!J3V+^gh?0@--jX+A|ikmwy!te zefaP^69r>gDA`nyWX}bbAS&DF=CqUGf_|{t8bzj{dm-kD@BlGcz;oKCGyq z;&#Au(GUNhpN0ih0w(^hlXS4xa)-9he#6k8vyXWHRpEy1Uq7e!4s)(%rc3 zl9OEx5E5un3Sa#nc)Qo?5>FRdrS{x=d!7*~1dN7VBjw*h>c)sOOPD03cR}@R;G^t2 znuvg6$s}An5A;t0D-3>9DCr}+f8-q*1X5lyNTRXx>?2*-I_CfZuz=dFL4W*aweo+oXucFfYJ<43UklxojVW>R5fiAGhaxyJ5K3~z zGlONoP4HE^Ai?<67Am`vXs80_O@!KY8#lK1iVu$2-h2zCrOF)wLjY+E)6Yn>>{~t@ zmMloWAcn3n$Q82agpj5dE^W<^P-0^GJZ2w`Du2E*aa%)!77n}fApqUM=S4?HuP*3H zGEu+;2;6cN*R+b^~KoRaFl~CLM+&M81huS|G;I$@C|6d394xIQS-oudzuF z+cqe}W&wfW@9Sv(6tQlx0SZ1#R9uepfw7WBiAK(_YgR6C-{HTK$QtX$pUMhLp276A z<>^-TKt(iC80}xa2cyhMx&gmD;_l`xwscG$gqXQ(_eYIjgKCsG1e` zv7?c6^eE~V$otGltQL!%(!Nu~DPEz%mUtr^j=z};kuHeE_qYL`JI|bP2{ry^Ee4*J z!@Pl&kWM9{R3ScJ*TSNTF*x5g-~HK<+tkt$8#O}zlvXL{i04OZcF6vH0b_G2O-ZTC z%Yf}0t}lKp+72EL&Lt(#piLvGtxBk*12 z9924<>i&ByiOR?h_)8~qtOsc3h(QELSv_SKPrX zXRayIE`Ar;jJ*gLcNCTT@L8gvj3n(c)A&WIq$4mb2;K>f(ZiO&oZMSbyiTc*_m5gi zWbI!33RUppnO=*&fuO27e0ZTX7gWQRAr7=SaPRmRsKLdZxE?zGCj>=nX+;RdXJvhM zc2QA6adX4{bHN+~d?m4Op*)%l$*HtCg|b3J0cq*!cpJRg^gx9(p&_)UUHi_RR~OcJ z8f;V2(}R6DYx~;Te#~)?m#MehSnM2Q%+Sie&g<7xLORri$0uTU;rpuKq9)C4$Fgjq z?6^Tb&&(hbWC4vXG$G=K~38H6QU7?>_SDpEoFp=G%_lu@K_3)JN}#umbn)dMMX2N1|5AjqmJrY%)CMBDs})c& zfg$Sk%m`?DxRx+rr8ul90Pj?|%Sw?3c1rjiu9N}#j+PK%CE)6ve-`(slh@hC%yUh& zf!|s5R_zc7BB88&`^*cr#S5T2*0}JgM9NG8G`fl=X+X+_n9?>eZb2N~P%eKj&T#bY z05H)k2aCHZtuqc`TlU(C?pJWtNbDEs~JULFgPy3B0Qlzn*u9ln8 zcG?gu)r4U8eur=_`sQ*GoG^Kdf!B#nt*||55=ozBO5doljmtxvnN`B1_>T+)|p4==1gMM=NPgY*%2@>cU3K9>8$nR8m}{<^Q;%|G{ad9r4xj6jV!-)D-1?kyrIrtEaBSN zzgsDqxi##0Xqj?auR4RWs-;5e*4CQaqU`XNG_c$zzqnpM;}NJ?r83f%>iFvErT2Q6 zzAoZpms9eW%}2CQw86uI7Cp*`-=}>^d=(O-scsWgnko>pq1-U`?b{o*tFF>;*;pST z26PAqHFvP3P>rhjwkvglWRO|d{FNGmv}r5PA+~g?CI$uupVA~U1^kmG>Ms8WU{2X0|WtO|(zvcEkZ?hN$Ql|P)g$p2+v&uG6l>3ZIzgaE;d?_&;}SLpGIdH+F#h(P{t8=f`YM+j`y)~-BhfvlZ_`bY(3*lnEm#~JOq8xD=+~%OpE0+V<>5DJb0yCgfg5>7| zJ(KYkXn>RR`KI{#n*g;8Wmf*WAY7Ez*}mQX-kSif3;=?q6(Ilg+}ELL)sa zH}r~^XeOq;T^paUq7H_-EyRpSMd@;mj!%^}5E+-pL)Z2;>;zl*)$22`qDTzlMyUW( zHKkg=R9aa9?JQ4j5-!f0Q?n54GREFdfn5T6mg!)zeHYJ^z$iokkYVVV&4{+)c)sc_ zjD1A7xOZymRs$G!JFTuWdRGIo(NP+J=CtX>NGQRh;#QK zo?{RqPT9G<0$LnX8`#^>sd_SWEC#;Sn6!9CX7eS&(utt`6WZ|eJxk=)vmU%Aq;@LU z`E1(zqz?%(63}YHU7boyYH@;=2O(}_X#R3C)G2$NR7qx=!hI36v6++iYKHZ9W|0uL z4$>lC(8p^94Yg@+-9E~Nshi{5eY<^uFaIyWwFg4|9BWk*n%N)~v?-iA+0$L*Nh=4J zH)uuzw;Co59Kz;(N_TZi96)hgyz#Pc{Z28m_$a)v&^GDI+0gVGR@2e`+LfeA)+Ibp zKj$9TIyLAIn{d-JF=eEus|wb40@8pX1Dye`6$-TlihH@?xk_P9=6$BO`@h{bk{A+x z?j&{J%uG4>+EQvndlBIho-MW}C?^$W8V0CkK;|1&lquia-ED@U>Xbyk{P-Ps$}565 zG;`DvZ%eZefmV5Fy(VPK4X;?FjX&d_M%R}TWe6V0%af#l4yvlPW9%agw;RC&8_lX^>`}40z$6MLmSfakAl24;QAYJCH5=3s^ePUI9WpceDig?i5Q?o zJ(TkE;hz*7uPI)O{hN!=97mDodk71N9&=E|eNnx{ha#x5Hq0H}#E}u>p_@zEcVH|j zQES=A4OHUrwztLYCG?lf0!`}kKDJeZ)H~$&;)OqR=F9@)ttHswUqI@wA8uMLc*c}m?SSmX&MiFs$Jm`q(VJy*MgdU}SEX3DW`g0G;x6GI* zcm_g9IM6lpQCh>DAPMf2R@)vTH%X78uDI0%Uh1EF5Md~t4^rF*_Xl@1gb1!xH}Mp@ z(^pfi;#*2pRq56F=jojnIoC*T5n3tFj3ihzn^hjXk=8T_=QdySzHzdEHHc*FS>tlM zR@=HkIE{uViw8t6DkROQ=7E6jbk5m|TR56EO9*Gv($Wk^QBFR6X@?nv3IGjpw^;nk zuXbq7XmOpi-2G~;Z0M1JDSW}qI~V%J7dF9??4*2V@9IVS=5@00?fkNiHCFK!;FGWx z&_AH#3wPOCP)@Ek-EE<1uQ36eNAA5b&?gVWd}DE@jr(Vra+&BFe)1_qs}HjSEPNs>B|!h@vUZ`k#w55_#yu_gU~S zk!j()fHi)92m*og2d9|+ z5*{@E)@Rc&WYuFtmH+yPA$X!K|2uiztk zDfY)df+$CRA9N8Z<8LhWUb^nQHZ_q=%tkt;*3-qOHKgO`>Yj*^OqCq0O$&BZ2;6v) z9`u<8*Ob&KxNs`E(rSV8AeynQ3aDXoiw31TkOZAPqz4AunZyp22%&V)J4PQeq=@Qw z*_a*`DLl3VzRp+eFimH>ZFFAUR1gjxYt9Ds2*~b7juHK{(M9F(wV+cALIe%YLxSz( zOQL`0krhU;FoPOn#Z>)ab#*mpZ&`Dda?UBhIbH2{BH@<~O;l=kgX|>4+e3k-;A`&? zZ2LYQLEh6O^6eGkV87(#oHBuj}xPRC#p{LxMuYv+QPw9GDz*%h@``1@{Nr@}l=vqa9A$ z#7?QLt41K-}&E<3z&ciy|WLT-W`48HLHCnIe)8PQferOU}0g z`Jwc_;A3mUM(SP`=iy|!LDw0mvaM!cnspu|Rf-X|4((-yKflVkCFjqM=B>k&G`O!& z7tT z*B!mX=eTuF&zhhL&%}Z6K3bvXZ#D1z_~yWF?!E+#TCjKh3?^#Om-qPAx-cSwG2KiE z$|@1^LpInF?oRdM?~g@UKydP>FbE@FbN--q3A$ckIFiZ*j0&NsS*evPnCigZ5wjwf z$-vkCFXcR|Vmp12{K~s~1Eq4mfKL zG_2%r#OaAWGzI{Kb=r#jAy?i>f}Q|qEP=RJexJV5yZi@AZ_GyDSo{SFJ|Kd)FVYTF z+~m1uN_@ZtfPCVJ*||=3J>3W6d7~w*>A!9JOifm3z2yb7F1CvVcA^t{oab$uqUXB2jA7n^dzck1cY*;^OG*be`J|5nS7+;8?b z|1l>r)7n=(G)IQpdUNwSAUOxH@I+PU`ySgLf%I{BWBcCE19zEM#_o5Y(Y06eMe!0p zNo1*^W;Fh_7H(?6t;LhLJykQVE9==)6W#Nlin38#wS08Eqyc*z-^Erne;3k#)mN+Z z)d_58_TW1CDlt3O()J+MUvjM-`CC*d+)DopY`D; z$~DYWFlpq`{>8ZXwjZl?{Wr0ltD!ekAD)NKzgDnRIOBN@JU|20U{H47l0SxbBhN?+ z{yFdOHj6vgsN#>BJJ+7?rYRev6@a3rFXcPB6 z5rHY0V8yw$G?VyZjOzyvBOFH5OJz0W*Qb9GQm+21)Dg)1yL&ee?AKcMn$JU@)n#xBLPZj?D?`zF^ZQX#KjdGWFJHDP)&(YTkB!pb=z z(o)NHN+xRrR-AR0bQ-x|`J;}!4EN1stJG|wtX$5uLr~(nUP-=MRRxcv^rw1!;sZG< z-{%iEiJ&b7)AiYXz!OQ z?O-=Yf*G;eEzDE>hANeqdE{sg97~i< z;z6e!$1KSQ%Tl;L;%SDR!|A+J!}%u-l|!lPXXu1ZO%X=O-p-D9az&EXTeAGFr1vLq zY&+DR0L~qT5_ISfKH|#r`twk|Y9-*^9ycTa$*aJy@u5ftXK6=`=JeXDfn}8gSh&=b z%zvy@u9MCw-Cu8|+;sJ_st3b{@q#731o#c&KET9W!C}l(!Sba1K>if@VYmmu@ebDT zQ=_V~bw(Y^8ps*lOC2eO)@^b{58TY> znu_WJpa|a9CJ-iBP*@q@XO=g9&>6PK^#R6z$d#krIkp2XmNAQuJ-Ep6d)xF+bArL8 zeD>eWw3DMic!FlHfvsj)#Z5oQVfO<60+0-sGd#5$=!yJwg%`AcA zB3tgF>tB<6y;tvjS^jW1gZ#C0?1M_a-5c9^oWa6)hZ-OmlN7L}sP3BrEM6e43g8=W zFH}DojERA!O7B3M!ySs=cnza{P}@vFkbt^VZcas=rD zJE`WIYvj_dKKr476~MW33w|wkUt$37FDtT?O~l6598h1r-)dolM? z5LRs=ydTcKYWC4JO&Iz#vEc?#vD zODDg8!5`QQv}U^&GGAX`sBs)U@Yv1{^2g6}!AHHTi%4k^N08T`{Ph|~w!0BzJO{t4 z7;3?YltdO^+5H{D_4^w^oDr}dfPO%*g$G2upT7ruN5TqgMu`Qy!TN`2RKJMmg;IE> znFVt5)&iJwFu(^ek)T-PLkTKfQ0P&+9X_9+z4{oy&~k{}uba(P)L;Js(e@Cu8>8hA z4p(PsUAFd}hzG%^APfP9O1+df61#kE$CSWSAZQR?aqsE?s*UHz@2l(5DqgiifCMKd zCW7<{uQagMzCI%MAG-kw>!r^-k#z33Hxu+t@{(2$g|Rh)RE?LIZ=@cW)CFOB|7)OG ze|~hFcYRw-3wp;1A5%|we*aks+7wVSB%B5vQHEGvd|mL`>;!Z4yZUaoY;KsoXFi?2XeP!(F?mlr~uSz2p8h1>T zHl;)AqTGvCEBF4QyEOlyQYi!)aqVpcBpEaXhMpxt>;G!dg>nGmPY9gIdTaSH?hw{d z#Be&Jb<~jX6}7jqq<>HQYCA`)y8k(yqzV|S0fR9D<0h{KLKOcfRq$N*i@TtP@r(m! zeXC1kX}T`npH%xnA+%b3Lf!xp(E%xjW5o?zw~o!s`J$r0Hu^#SjC+ z?T2oI_(IM8ZN-XIj*Gg_5CKhyzYEFWI(u6O>8u30(tHW@+3LWjNJY)SQLY+W>yhPIN>&1;*)QMLw^*ULG> znptKiqCK5rdj<;5x=kGOF9u?fI9`CoA1Yl60!>qAd5~X2Wz+$&zu4{9|D7@L73zzM zUf5AupwDklF!Pm-gSiEJfYv$M9TxF6p6(nMu~i}|E6T=z zoXNe!e4G$5D{gJ_l6+&@o`y|)cm3Ojl18IqOOD!+pKp3f=Jux^2 z^V&RQmexh&GY;T)Gum*dyLM?_>C8@$$V#FSkC6lIYAH96L{R|Qk}Y@ef6&E>W3VP9 zW;>UcoEa=bq$(56insGXHT>k!uiKq;3cH?OQc&>LAunmxE*)r2I0KVmz>OWzQez$~ z>F47u+LFIvW3vEdP!%P|wXT+cBMaZB!(Ht6%rC~~t-4wn1I?jH_IC^fg@s9lb7(}} zyQC~Z&ufu5M1@LZIewgmC`i%oL1-|tPpy!5gXskwYZx^Fe{%_{1C5&f*_JM6WqDi` zlkRIYcZjdXyB4Leph!}&P9>xV&I!t?VG6>~T$6%Ktw_5+PKAQlUX`As=tQ;wlg$<# zoqKZBGd+GI#^XLlTRXKo1n{g$7hBDtlYhvi!x4*|I~{?@JvRJvLM5 zLf>&ZPn$G!Di*J$8A*U!Yh#0DzL*K>{~)=XtMM7; zPgls#@OR=TW6jJ2s&7*?p`R7PJcT^6lBqbB=LW?rKBPq7D6@>eUxU=J=0)N2ERT<1 z1?awsM}tow(lx~bpHC5=J1@j1pRd%F&d9J$v+(#2pK>*OJN zly4~)xk&4JO42Y9bcB{dHH{cg-_tZ34IN{_)@Y})%7aMXJ|?f9fXcO_9aj|TrZx0hDX&IzuV+MnT*=n?z2e11FdUY zh~`sbofjxK^9L(6r{{BCS2ya2jGz*N8Pm4emybW6EM`*5II1=xAm4{OkF|JPt9h-uucT)U~S*?gKZ zUHWr;V8_-QvP;-s-4VJ2IHz-8nTg&^qbkFnfI^r4(u@uc+mg?oO+mHQMbI2xUDh%q4oxh;A_0xmD>9vgt9Ln}le_yHXTULRh!2-QfC zgVc{C9;>wZw7=47rn`}PzIJ_cd<#p?^0Uib8N>EZoU7!XT&gKW zH1X+;OmzB1&C?Cg@YtYx3hvAhsBGHtP(qzw@6K&grn?LtiRHMOdPod9 zYJe>RaIwl+j?Lz>t>_yPEUZjSlarHBk%3?GtBO&8BoEn)%H(W#mSMH9OJ68AGl9Pu zaaU`}!#y8mF+`0FDgV`SKnIMq#^_wYcs=D*nuO!7|k}mM>*3uHr4U_HCvl-A|js;{#YU zKoPWn6O_t(23r%cDYrCpLQveAN)>$bZ%EBIxX}!B4Q^L&*K#-#$lGS_MtFicVQ=ES zSw&&Ad9f<<`8#<|(ER1N-ItQ<@}Fov$dy*%!!`gV6h8#znXQd2k_Ox`SJqy#Qg}l9 zxvI8bS#EA?qwxY8G^#KnBD6a^x_Jz+U%@kMthw88SsNhwma9t5^sj}`4Q|6)X$&{c za&w}%0%^!h>QxGY=<$zh zphyZ1o`ba~xoSL{0AhxX^-st9AvPlFL{3};zmg_ypO(%K5{+f*kjE%k<)|^zpJDb=M*phf*5PNvnWTgH=(&QT}xw=*UvrXafk}q(WA#bSvOuHmf zAEb{2i>44PfFv8p1`1OyKRS*`P?3Zf`e9l_U zA_Ov1p%O+`YguacYD${9?fg9uVcB;Nzm2T6r zrXPRPy=~b;MGfijut>We){g8akE-WETK;C=wOo*9tO&7+Fd3h_22179IWSI2C}ru?=@T!-OLZb+TVoBac==3dRBOZhmY>7eBFmIs{TV~A>G%k% zk8`iOSr_N`6M6$ao0p60DY|!_3S(=P<$qDN!ka*c)F_;10q8?8xkns=oJS8h_(St4 z5KyYN8q^t7gx#Ba98>Jq{g}N+nL|>s1GqWpz`0?%foTjQMYpU_+V?Lde%RBPSE+_R zc`;nge7j6iyJ_?p+{1uf^Q&R)9 zv73`8ezghspSFsI7OyNw0iQHm`H#M`mk05#cxo@vEN*CPdzB@=rO>pOdb^Ska)ZMTXeeqUC7IW)wob%p+e5Z!=qhdZFB}H_409Z!}P+pZ=cS@{VpOYB`3iZtm*7ln^<>B zK;6>nDz_A0gL^RTUox{O{!Q8=M*-sfdNJUX^sG83zj$dB&Pbg|W1DFpgxcc|<7hWtKI zXh}&)$;d!}5h(O$T?ZZ{sWulTMUSS*0s%u+O)X(lFcnCPgUK$1`T44L=mh@F#v2c` zfYPs&ot`czfO_ zLPr2M$8e**%*2n2%m|9d1I@Fvw1*`5s@edQFoE+ohy&iB)*2q5=uw< z^C{VB*8Da=)$Aj`Sl7u+tn!1+QkN$pHmkr zs=(e$35o##!oWVr-E#R>b{rIK9LC7DmhGhm2UlIj$+6aqjktwXmY!`F_!K^G!s4BYQhDJL^WmmC%U( zfsP{;hz^gsymj8+pU*>`Laec=WZ$ea9wSj0x}pp<-LcBE_D&v&h95kQM`V-W`vcwI zP4EhpO@prp9&kpd8K+t*a|PlF>6o%nmV2Q+5=XGtV?t5`oc6(-d1y21$V0hiYDQF! z!zm+Mx+0ocs07m_vUId`b(^<3b}h{BwD^g+hzUVR2o7E6Ro{P3|AiUTkv+!L+hNzj z2bZlxbMUhRxL_2^bQgOCv>?eP5&9=3I(orxAz-rz#NrPIi#0KfotyZZtXE+u9}9DG zJLH}rXEESYLc$KOljPD0m6N7)a^L@*GZc|5mLVh(%@kJO!^q199cpSD-kB=VAn2=$EfwzPyQvtK@D8-EVeV-WmqQ07l-Czu|@>N&jU?x#qQN z)eDg#6NS7GVAq|3fTdAEDq!`(9hsCDrXF`Ey|ugw+UAne54UHUx`70@USd2%eGn|u zUHVIKfRE2Aw$?rq7#%Pg1mNK-r>m$|y^!t?)F|}bK~k#;jggB7#m1UaBDk@-DPJj5GwPXMF882Aq9M9q~}U~20gNB(%ZP6POH)U z$y3%YOQFDpC&v_N9=73kS=NF246~c3LZA&YND_wb0l^u%nh7vp5c=;IumdPil58A< zE6=6b)?cuNiMGJ<5Lo-xVNX%R&F#D!`{*JOEE~z>C{CgYGDB5t(58of2sXRyx`?_o7Z$$gX8=9qHxmKiqK7l14_^W>&HPuTWTKs{Ak=Ixwd7GdgK#0yoDcpE{Kb!7y{4!L+8ia$ghqtU!df$*KfbjqQ zH^q|cAyrVwftQ5YDVy06appgOD7~Byv(1o68C#gdTR+qo$DGkX%{Irbai#d;`4NzE z6h_WrEXRUgxk7`sQ z@PQl-Ye~;*u}4dPQ`tGa?z3l;?{%mYvCQm(+UnSc@F^&WW4Bx5e;uNO*ruGO z7OM})0ed34t+>awEj7Dizd9%Ab)QqU4otRc%rlgVqTz#oeMoeARav3D z&Q6x50Wj{3nGiZ4a*XoS>w3jSiami+gc60XsFVycbFp68ubmp^E));MfobGJ z+FZbU5bNZd-?EP2mIet_mpSA?@gE?I3Fwh2ymMSY^z;x76iH!Nj@p&iTzCow)ONS$gOy-q2RHY&om+O*VetAu` z4`wYJaf0R@TLc;=62C;8gz-OKXS`5=*1H)T`h11t3#**H3M#vwi2gtnHjQR-N=Wr4M^d0+1U{0oFOYi77V~Goo`Q)ATrkbL;--~Mw~<~kla~=i%M=c(pdQW zttd|vTJPRySlVhC*pjYQXK{$zS|tlmUs^qfedhKxZSSm_OLCt6WW@_(;kyUR?9Ve= zJv27yJY-YB9;@g;@v++5 zM?QiZcZza0W_^X-B?agEVg`74Nlmtz7;5Zua?fK8IVTP`<|_=@seh(Baxr{znz;69 zl}9*3n>9vE6$EYaYK$D>bEW}%-3|w+rSMwnT`mgxC|xe_xoUnEm6X5^Bj+$}yH4ZoP2C?kD&8d~mz~<=);(#AzsWDVgblum16F)F))T>z*X`h-)k&twJP7Jcy5N-Oc`5?}k z=MuVZ8ykMu&FAXX=ek0)dvTv=G;ixGK5(yqei*fI)`}ugWSklSjgoUs#HnExEB`#7 zq({*GP=;%{M}KO7-XRLC9b%7Iz0Ep z#B^%H)gu)(!?Ex$<^~(Suhed~a+Y~(*d=t~K3o#&4~(%$WW=!FfK1-ho{8?@T`s)E%WX^H z2^agTq&)G|?cXOy8J({InEnf@{fhNwkhbGa#Yl}DtA*fjVO=dJkB!)DF{6L+m3PNUKLAVv9R^qE-m-(=kn zGV}h2OeiiALZBz`8yDKk9^r+!{7G0}>Kf8c| zR=%Mpo?c!l;e!5Ca30Ih%NYKW`6JM5=C*hUlcs-PhkLYpe#vI9@cOF&+pvcZ4Fhsx zE?u82S6g447vJ&6{AcNqn%M(_7o(WZJkUjpuj~sxV_a2I0DhizDe^)NgXjyQRfRCh z{t+oJObBINvNn}CPVZOQ_()&3pf_sEGh5KBc}Unw&(W~N%4NF$n1k0^#xVbbE0bkY zA@u9ZwQ3k23QJXQZ|~8~7ko3Y%Y{G;CGxJ@$c11AG z$R*Bj{)xrXy9yktu(gI#uR{r(p)A+@`|qyzz|V$W;?$0mcf-6iZwpS4Sff2XpQJlg ztw9NqQBmPr;^hxh^>S^3Z3(AjwP!yY)(x7ddJ??-``Fk6I%`6m`hD3^7iWdf?L}_G zbsfgR71H42Tue(nooIcNlg`hETQ+`Jj#4iDEo|5cuYOrJv1=bW@em6GdRyCH)Bbcu z7pVj=)YesBJ;R2t7sUy%=%K~Kn(D^7jytD)yK)U@9WTH9K74T+PDrcp< zKU{R{h+FYIL*0z@Ao1#R{>6tYA6lu-Uswz=X(DzS6tO)#qxA#=VcrI8XCRyq!^6e3 z2g}RN%{V-qL)VTtrWx^`AK%=H^NIuu&=s&5eLFpLAVJ0~HdsTQ&bj#HWG=tD-8++>}#g_l?dA(N0njnkMtn{LOHJ2A4o zK)Xk;spu&qSy#u79Nky_9YCLD>IVuzJLY_ zX$2FbS?QjbVi?kGRz>$hVrahDP@ojf+CLS-jQASqN%DglBKL#`&U!_&N+Jgqk;WsI zAHDqjyWNU;;SAx1Hcxmve*8oRP6`Sr;VTXCopQ=5}-~bmdoQ4 zy|2-?s((7grusXhmM*&GIZguQe#i#%&uZKQQ=-!bA74hcp>qj__RVEOb;6{uiK!`8{y!jw(T}!IpL|1d8vwYr)aAKg$Zn+9;II7I&2_!yTJAx`GPh!0 z?svrdG&r#OtcC`K-6o7ACBBEpE3zWQdPGwzNzd1yRgx~X@f_DDV;Eh1&GqWl!;23w zL!D)jxI5tvA8XVZRmUSk*v$QS!>Ej8Y1^ofu$NC0SnIB>FY2SWp&BgsQ?$U)QW8!& z{<0cY!NV=I%(85!LA-B8l0JF^cGIdVeNL8vJ~<8d;DvZ*s^P4K7`@|*Y#d1)L!NG? zZ+zTu_4(!$*cEyRdRO%W);jC~;4Tp9J5+$C9sBg-wZadv?)Lh@!*o zVsAY3h!LlTd|X3kdwY~m$>Yb5>n_DM99f=713MEB3146T8>HV93Ovn*f4&)Ml4gv3 z%(~x;E%-ndEkHFiJlFtJ)t*BiWXUBe(Y!*2v@1(WORE^t z7|CsPBq;v%6QP~&UpXMJ-E6QgdMgd+VqcCn9xX z&D(BTS-o03f?FZZKDsU%c|KQ{StBxm@nr9jYH{iNxJk#e_s(n%93Al=EUrFt^aXLp z|MYUcOPeEO-RK>tV=-598@G2i6(E7rl0PrzZt~Gq8uNrZQ6%s_=Fz> z@k<-as@(V62(*2d(0~2NF7A@_Y*47NFn&>Fk>h5MXA&J}*cfqjdv5&kV7DrPQELmg zBtG#VZ1qtiIz{*O-snEURVkGZHzs>Ut%e#=8qnSRC9I&f@=VLwkaXgXe@1kl zlqVLA>AbyZ7y`{SRcm(q7MVqf97o4_8!awb=0gS-(!glT zQ`a+rO*r-uJ~bbkO?b!i*z7f z^J!9VtSSF%0LmMknZ)rjc?{0_K}rttYLaEn5?%{}8A)v)u*9n36BRrMpAzhZ!`Shf z5f|jm+^*9H-e`w#;)}bBaBf~}75p*|>2}skZ!odcsUc2B!T+J|JWiMUd7D-T{yA{XSR6ITy%h0XPU|p+^_*l$go(~QU9dJEs9c) z^9CTHZ?@xRRmlP&GyM`8@{zINn`0Ee54{x=0RI~&sO46zu>k4QIq;CoR>>tQV5{LB zkOO8rUdSn3iFNn$9SUp-=kxK%TP(8Wt`ik6S90`+&sFm8XBA;0!|ECJ4nX460V`Lnu#yTM(v#;gdv0Dr zxBS>A_em`p+2FL0E%xt<-Chs3Scfe=(;mK&MX-|vV*nd3E_E>WYR~f^EJ)C&^P3a3 zCsg%HiOkkkUPeaoFMZ(Ff()!wT{rF%pHw9DUBE2gn==ZUy5H>q3)D2LH2WQSW5;pF zM~E?xd>(Jgp3Ke$&f|hY*|ggAo|srD!lZ3`V2yH6x_`vIP+6G0qv z5e6AmSh%w*zTVOl0qHq53btP947_Ot6_m33rHok-=g6(V!8LXac|J>1^qYpE;RnEk z2Q-As@Cw^pSuyohw4iUXlcTlirj%MA_feT@Hh!^JMM|@FN0c2rYe-USGY7uq z+&%hR#r|;B@d2=6(ykcFrI^TX7LUwry=TX@v{+quOKz-~*j8V~2~1ps`QWG^*58cA zE&mAfqo-#0z8^KhSzktcY5KP3RoG1=dQ~-GE(EJqJ!DP0$A*@G^!iI65l zwA&%_jUueqV0&Q(=Z~m%qV{4uxEe@M>vUho1~ZX6&fyquz#gg#0mx{|Df2YeK&v!b z@`%)D%g9y}>yuud+lE=M_wb*{J|A0=W@Vj@e>`yT8YadhVP5`YJvkHZiNZ1259SaR zG7)zL9o*_$Rs3eKh3sDBlHrld8%WM<^9>|_dB2UA=kRh|DJ){tW}9_&*nN?C%5cZa<%6A)}o8w3|P^| z7yMdTzb$M~)5kXln8DiLp|D#7eO&OrJ_-d z$iVe5dtQT+#tFRLKL>FdAZSs4Ks&5PF&p01&8lqAut!E&aI#3|4xVJS2XzRL^ zX6|6$ad3)8VzQJaS*6mR?<4i0<>_W-1MhkYlhJqEt%T%OA{|koYHfzI?!ee&eVJz_ z&rfski<#zK*B(BphgPflB?itvb{GSiDu{x_wCB}WKM1ezWbeg`4~GSTXVOsrbbSPk z4&E>N+L;=sa?y*GtgDEd&~UEE8lBiPv@JjKvfNNnkW){i0vg#TNWyM`7dKqo_(A+;JW}8AqP}+J(mC^hq*N6l3cX7_aB{uOsH!vksRO`MC<5F@W&I2Km!*dfL11j9B(f+c^JW=7G`@USAkfZ2(W}0`uwThAs+uua_U->!_OWg6{rtAO5P=Wd z+B{IHT+Xx=^=h^Wdje8ItEpE6yFcAw+}qNr)JSO#Jat33J|+%gfxhlLKt!)N4~I4J zO%)z=4lyqN{W}DHs*^a-$7Y(r>RB@r)D2X*D{LmAX(aI1vmB-<)?c7ypitpTCg=G3 zAfie>K2hikar$I=la^uD9-hx{3V@hhKRhmdQ72UX4)gU3>Y@ESiIeLuQ$$@vxXvD4 zI}xzoA@Q* z7s!i8o_8JE*x2CmQc+UkElOf7=1_53AZvvkH)uL_yyYTcBq z)?mUhcts`0EJXJu)9j9zWf}Cna4T5{7`~qmZa1xK*26oyOJH)`D_W(DySuhsVU>{a zCT@h<$=@1UWw3hA#{H~KVpoV+JFEj~eAA7%+4~!sx68R|-F!yNjb;6rI*6bmufR}F z)fxdFzq*)g9^eGf2Iw`GGfbn?Z3www3(I)nU%sT|;u^Yv;U|fF<}|#4c=h2k1=*kI zSQmXCvJ{*Ez{?~gln9}a-08>rngiXwE<-H*oRP!Dh)cRFgt~-&0fhud?X_jQx=IcwWJ=qb* z_S?emKqzd9MObQ}c{g1N0N#DbNDxMr3U(;D zmr)BYi+uFS6p~as1}U59qhsObWlYh1iw#*SnLo$%9~OoU|2y(Ga0|?2{A#8tf|B6w z^Q@W3tDax|(Gu|M9nXdPHah*lPYW*cOS1iQp)|O_eVDK$`2k~gJTy=T4t@y_`1?-+ zS4OV<%`W_N6+K`B*l+?54ACmC)3tLZw|rb&oH6#w9c{&hXJCr9Xbte2Zn6?bYajCb z&x!u{0?A&EX+lBh0WuvE9Se_iMz4JB;y`8`we^?0UkxJ+bBfG4k{u!wGk;vk9kYCg zf1_U|*$7sec8Ib0w2rd79T-g)kIVnPs?4ktzBCL|JS;OZx`u^c;uWfuFeu{U7s~l_wEVS7)eEb2;)fQD85_{O`ze+m(Q!qXS6_s2GwIj1)4~6ww!Fgi#R6WHlpT z{5HVL5)NL;^a;9|1a$~_gS^*Y_7hqsG$VB4E3aKLcie9D)-pi?+aW;Bb2XZgkHLb@l(>Q#m0awK<3`- zVZHur_^jpg4rOb&vi%%hR-*xn(@*s5^ax|+Oh0xFV*G^;KWG9WY4$u}7ZR}H)WVjY z^P88de^P{9?EAYi1Xs!~pP%IrhNmOtI{0Eb#rHhfYqqC=9QdC-I1d~Bh75SZjiGxV zHJ7p+8&n8PVG%*YPmI3Ukzy21fGdHR!9D-7sW&{Gc zRzBBneAe+Agk(8pW6^sHlAVrT4GycymI|-g8TL)Z_omrx*8ZX)0H+56JxR1zIrq7T zUJ|X|2_Xd&w>=OirhbVN4}A}avoR*=rRj?}ZHyY#EKOeeO$3#phi*$I#t$ztl8Y^v zfpaRPx3^JSz!(HI66D#$GWwP=_Lyw65DHKMq;M*b`!<{0gmhQ_O1oJAR0LqaNLj*J z$E8DsLmI<6;HR$4CAYhuy|SgZ02Qc}%pK}?clDzqfB2E|KRW{Pk$u25sds<4A4y0Cg|jZj8uq>Yd-|fV zYN9Abh*MXuewm!=ErrmQ{wG;-TVTGQ#I|UcU(=|}lF1GuNMwhXIfFJbAz^(JVgY~% z^;R+l#c_tPqhzq9>Al|u&Slfq_$hvOoi3xYrJRggON7cp?oSk1%k3VEYP*6yu9*jx zF|7tP+@2hds?NsDK9ZZ9hCu;we2f}a`zXx}%zI+ego7^txRP&+uu;pOeii6>n#dj`F^f_&mJLl-bUUq7jJ z7^IIY?cXbP(U$qn$)WGK^-Apgur1u=dS7c{dc#~O$+t3Xf00u)pRwtbji|EaMM z^T))DNop$@Bf-VdDzPbl|m7fWK$R=}V111j&rD zkYQv*IidYacRZf3RalEGh!MEXM;v=Cg<+*$Q4N;+4%PW^3t<(LbJ(QoW=|8CIlD5^ zp*YJAJt~s^x!EMC%Nyk|PLCub7tG1Hm+2F_@h4uP!4>HEm*i z7cwQQE?JK_8&>pDF9z`DPK)vAk4!ewPV7XkxaB6X3V9MkMk}T*_ zFCjQcI0a*90h*JNf@hPdK*dpKnw0EQOGyq*J=fgjv^Eerh)VgqGXepQSc{@``?^UcB9bXnCl(;olThuzQKrz6D|Y#Ci*WANS`)wbP`^i_72 zb8baN*Dfk25glEb(CF;!`p+Z|d_l&$PBwg`te$V3r6?O7!-`3wT>U!IJ&Ho*r=-ma z=$97{&Eu>OrBvj7gH%IId|!M*b+j;OR$B%R+E}22&&nzLQhK*~Y%r3k>8>!_=l>m= zMGrjy!N1oV!eu$-yC>JPpPG{O88;N9K^XjwsQ~b(V;#cYtB$6TE&npW{UVJBT}P%#T^P?-zxq%#@mnhtT^f z0#7_tf4@Shf<~G0@Q{*j}Nm}QyMl@e=hh0zzJu=Wu z*@+%WC9##fS%;L87_f3v`Nkq&80C-wP#?ajNbL8nd2{%>DY`&Ln~P)RGfXhl0m39C z1O1ZsF%QM?4M?5_Wf2m`Q*ZJpEW{k(f~JhJ3c{%K+r~qM;1%^}f*p%S!=bSJ6TbRpf`OSPbCwNV#Wxnx znT&E`+f!(bUKK-1HbJD>Zm%Ad3a@YuVpz6Qlz1~^WoOutKQ5R3$UdP?1zcVp7&AgP zCO@klw3RaeG=6lZ6yn?DXnK5zd_N9YEbzN~0hPPWWdV#lR3X~v{UE@+Rp5`IBRE5e zibwN7{*vdC_qmS8puPKTEWKv zhMjV9%NstZJE!eqZ*gRP*W@J-%dC|BU5*g3xBagH{ppy zDtq@B)bl*wY-iZ(@!{!=8bKGu@oWaa-r&rtwcn>p-g1y4~<4*+(EKO4soI z`iT@u4y(Y+0OOCRlO`#9ZSLCCoPyKAhUSw9pC~b{*?A?t6}qN_=&*YLr*vO1Ld%H~;xYsD8yXNTWq{T>`~F{tXn+P?$V&#gxsf1< z3D(i>pP0x8Tq=COc};nW+)*P;zkkQ?aDW~E`suY?1tlfZA-c&W@u-lHkfB~%PBJng z(3>eKmDx5WBv1K!czAewD^&EslcHD1KI9D>`QRR@$!TbR&I@9Y%#Xs^V;gpSvJsRT zs}aL+3GJH+`=mIsYMh`z8$x`4z7J$nNccT(8eLyope%{JzDOmMa$5Mig6(8U-*%}X zxxsgki)7t+OEC1GspkA<-!u~r0z+@_4id3}y-yu7T(QCF+)943PP~E{$U!sR857dh zHCp5q%3x2|=VcTa4WU#_kUGx%Iw$8yYHaDjv;?5w4Sv$SGD$@LFA{u`<&e+LUH|x6 zqknDg@!sO4D=bU&YKv&|;vF_lz80~iy1@mFergjWsS7zE)pB|Ka{bF4))C917}D$= zRu##QdCT;A=azbxqkDiy`8P+uZ`gKQTgRUMm?Lx2WG?kIMl)qitL}%cGR6nh7Ys^T}U-X}7avuAt_#!MkJbbG6<=kbo+sJrOwWlzu>ZR5O-R-e33h1u64$JU@ z4`nS$b4`vofj>ljuF-G9nsrZAfJh}~Q>3DDmw*V>q-sfdrsCMSHkY*Tz_aw@^*Y_Z ze`UH-xt`Ui6kdI8CeG`j(AueVB8uYyvRw}yDqrgJ6OB?u?rw3S+iR+P$*>o`SAk<) zmcXz<*V*wAeo-|TKO+inb+IXKoECF!GDwsk$G~d6eEE{vd)76#4R(5v6QBR|ySite zy>DVzlk})>Z*QOZ((galK>H3Kc+mMh980UM?5ywe1C<2{S~CgC&3h^DpUB3QBf_|K-(u5^D*75ckl_+q{FXr567Q*H zlF^2hR!T!V2$QfY3pw_y_&-4F5!TI{jN=Ld>xC(~Qsm4+-?kQ?YsF%P-=4Rku`Yl1g{ouf-*B7EV41aG>vgCbKzcn;0ta|(1?vRu>uuN52 zcX^-!YN4rmW`Z=^=Qq``CM#Q3;*GDmo;&k8_yC)^_X-c?n{qq_oB{m0oN63Ojp7Oo z4^R2#6`b<0J47`&io-&!_2`~*n^sZDfOl4nXSA|#r8|O_eghMHKpVSP37q>+^@P;l z>IpcqRIX*3ne0~VKM|E3F#J_#!Tkl%bc`9MV95{fElS}z)8{1iX^4C|>E0P`? zC(Jho_QD7v51G>}g-RC3JD*TH26qt>wU!>|etYm_nKWMHQOqvdQa@MYL~@4b({p3D zSh})bsiKLw3S!i({QIUyp>sP~kyfW16nREnY~>vT|5NfXJWvt5HTaiYQ*ood$0x>* zs95^9Oh?j4L-N7vwlyLFTL`V)B{v;L9u307P*7E4+E{^cNl@PCGnTTX$Od!_h`KsT ze=7&@SLim08wOi+;MMehQGZcH{sS8xv}t>?i2c=Bi*daNx5$d57eT&G_hSwf5@_yF6(S0M;oE8^!^2uyAGv@|_s)-@< zugj+0T2yCa_KUxCi?H@RM;>;|MKj;w;#IhD*i7EABB)s-nN@i11r#}@^O>s-ze@c5 z!rA)xwt1!;-@YoZhH~PCCcP}*nV@{$^ji6T|NL2F(ymC*?q6z~8Z=x*V)w{o6n(x4 zNK>dOf%Grs7s<`J?Ktbn+dax&eHr)zKkhcq2k6$Ee*Ym9XNRN2u-o#(DgRn+QN?^B zGrNAW_<1cO4ST{(rbB4!Dz9XdNCRWE;o@_`D`uRbP*IEM`(4z6Kh(RU#NSGV*_5qY znRKW_Ri3X!wQGR`KSQiyds|m>g10q6*$?p|DZ2pO&TFNPAFX--HJ}*2kHjjlZJV>l z6P{8V{VwB!wD~o^UsL~d5NEr`Z!eG4H)Y~D2i|p|tFCGTyzStr)TJKx zCeZMrUwcAo1@hi>EmE=u5N458`xC57<`Oy$ECHOIBOPe)J{BoacpXFK1A-B;y!zf> z6-Cv;|EkI4q%1KfrKI$P*v?i+=Piu&>l;9X4p41#jOvLj#eU6JrRTdimk%lSe=LZE zYr6CXuC`as*difuraat!0ktR2xKZXV?lSsO{%nr&K*b%ZwIz=bN;FaasSl=zuyS4^ z**E!GS_^cHOuve;)#S9%1{sAMp;p_=CbGdI`8H6MT*;4YDk9SMWF+L>C~-(!n*aLQ zd%tMYmvJUCH9^Sv$w>lrs0ngWBP};R|57@FO5EiaMjig9=Kt49(AzobIS3%%1E|Q8 zj?ULcVw?`|n(J8Yh4VP;Bx1b1@5csEC*s?fl`X!nwcIpwWEpbe2GIhHy!Tpg@+h*e z+QVY5Tx^7TC`(JzEclGjhsY~=3MdOG#6GJ4+y^Cti&z0eyBIZbf0vcr>g|eHoV5ah zI%jY*`}&F}fg1Ijt*v3g(v4KAnSt|@K=ML+TI61 z4v0{az;Ax`X4J9^{UI$n2lyn4d`OwL#H|1U>UOy78LQh&AWz-P%8EowfKE*b$^?E! zFDoVYLNOjbK6t8?2Yw(gs+Wr!3) znTvG;!sfun-5FyW(6=6U1PlpLpTPPcqb%9tD(#vLuUfIfsZ%R(9tj}g>H8y)@A9OC z3TWm^tli$d=|2v^RKdESFJPDDPlauRx*OW|Da`${;6>n=?JUl?8y4QE|q629tck1y+(HBe_nH z8>0S&r%E@z{Lynb{$2gBOs}@QJ+6Pe=C`Ypzf|znPFpaD@F7Srr1gaNyxCv`pzF)$o{6?3ma>A@~~do{79UO%_Qw$ zIao91PI8&q`?GU^#!}bIFn54;HujL6X>CC8**UDsaKH&s#jZMAi|C?3-5bfAq%2Gg zg?pE3SzmgXWxlzY_lqtKsa`H-is&~toFo=Wao_g*M`LGN+AfC_Kza@pg6rWQI?Skb z>i!Q~{Q&CbH2syyXG^1fr#Huo)aKlB8Ob{hU-mg@V5&dQpZ+M|qeRhuY-&1y$7e~? z0`o;tTVp5htup{tbE#a-h7EH+F$t*;{ZlD^bsedpRQik+vAF=$IM45ZR6IK~LG8(% z5iDK;0^zm<^I{I=jj)G0QAu|s;?T;m{}UmKv_LWFQCP}2a~2NI_kSM7GH9Pm_5kUoLA#`lib z+)#2|t*c_v2>=m}1$=O4n9Buzj5X%u4?@S*=QR}ODv|c5e1zADRDUXr)YlP-o=qY~ zkeYMh2U~>b%Jv?fGlLSg3k(DbcR{Ku;6I?ir33E(=TQ{{Ww+RR1t=7^z>csJH_({8 z9lzD|TqOTCun5()CT!|W2+&$FQ+nU(n$}Z9RhJT%o00WQ9pMaM}xF4ST97ZnM2seIA zv!t{{jfXREIeE2Em-Y;5IE8z7UJ0@x9C#6;5(T>Mm?o5IRC}e(VHPzbkUKUqU5O#O z`j_00gj-8-vG;);K9E*b1B?zeWp4f*iR#2iu_XKa> zOXmsy(>9G5WKS2c-fdTJn*MOAXXeoMH;<&G;WW9~0*jKx$S10;B|v#3r9L}t;?$PB zB=Gzl)&>mIq~>qPXHo7hs?NY6C>xvu10dJymLc_oz--VhMjXq{WfB_#N=pG+Bprs9 z)}MD}aartbitfAOD4)RrfdG5v4Pgk#pn__BFLzXl|VD|Lkj4(-sl2eq2 z$kToMY)fus77;rd z^rhphZSIn8;Ez z7P)kbdEHnDwP`K`o}ouD!PdU}KGzlIKzR!EhKSOpJlnpNl=oDGiK5--k#BC~FKql7RKhY}-n%;~z4BuQ%^#WN}?~u7-X23+AKUEH;Z-LLrpwWkk4SYTJ zNsVxone!|hL6bNWYQOaN&AA zG1Jk!YPbG`wyiPRrj2ZXzP}_Mi)cqjIy^)DZQmah4~=u->q1yt5paNe_#F_j2>Se{ zo92Zcj&o**G=Xld>y{6`d3k8ts{$r6!k;x5+px4!W68t}^rzS({`>C@DfTwyIz@F7GX(u{%-BvV^xIo4ij&&*kaZ@|7V1E&(=pNXg% zt_rVj!Z?{9FfNhTr%(5FMaij%<|j!m4{6Y{s$Ru;r3$8?=gYZK!>0tPMJjJVGAfZdM|=pIi%Gt1#+bFF6itOKU_tQS)1n00wShrDO@SOBG)=uky+=l z`58HVh}WSkWCD-t`UqQ;pwvxtUWW~W$iHZoC5dr>5OAh2 ztIaViEY6dYqxS>Mhoysq-M|wAWnBwh)lU+f(a`7XqQwOfMO)zu>Y{&1ibJ}JRD&r)F=k#2*$0d^WocS?I?SQQ`6=+g}hm-(6D3%bC{ z<{}P$5?704C75`ZO#<%af9~SssbAW`7g{&F4fZn@=6gLIKi_^kUl>In<_}z)hFv6o z16(c6{y56e8L2kpc0p5cs1_NaqXYXNdcVyr$?#nxoX%9bMV-W)1g+j^a-12J>jfl; z$>GphYwE*{q4q+X0BA670*QFCuS%*>#$Z(#bp+`|D(2+NiC?paQ1m!nm36Ip`~Nvu z|M4l<;+w69V-A_9m@e!qDCU5bQjc>t9*4*f@=PXRC>>PIWQdnJrrvVpHRJRBey4c< zuut}Ls`*{gpFAESG+ya81jINZV$0v=WXCIg#_`!pSCelk#GrGq{IM-QJ#*jsMI+a; zT+N|NaH`KI#~eJ*p@rOJ&`sS=n%~T;K0$8;^d&_5F35s=Je58~g*r}gfQ{Y8Aa}mI z4#ieH_~?bE520QK@v&*j?4bCM0H`CIu}B-q>W2>Ln*8Fr;aOyD^x(svE&lhz53I66 z1*|V{$?V66?-{mugZT>f;tsQq`Pg<`4y!GJ>Q`Nv1rN2)6HzE45{XQmxC)n4AruCh zZ)H;NHPk|gGPLbH4cRp-eUAV7=hM*Y@xQlzN~;nplyjTuxb(b57Ga(b0G;JsD{OpE zbbIqRK+6C{z8Yjt_0Nzy$X)gZ=7v74N+&et3Bx`79R^cjmu%l;{w3~rI{FNM+qmr3 zRuGq+=(-B+JomdLrY2$IZP_?FsSGNwkvqs>%IU~-x~Y;+iu@!}1hJVQ?CKQ;KlD~_ zWs{cp{woq``-hfj(AYWK`^k@=wz0jY@Wjs;^EtWudKC)z?w7R)Zn1{ zFMiOWM9__Pc732mAVS05zyQXD+)SM21KAPU1JKT9S3A=`jGC(0 z6zL*b11tO!@lyp%dkTgR6=73yeYPKZp(_M_-U?i9S~?pjv7Z0FA86#hHFi9Yoa4EO zSJi^iY8iCtE1<#IhE!IS@QJiB4SJjI>z#k|b9R{Xk0EpRfX`TlN2F|QtoPF!z0SHF zn!3&4)?d7?xYX}XSH^CXBk8r%aYk~|X$Z#2S|sS3;}SdyU#akXyC^axz%MKW60HK( zG*1jd?Q=8Lv?;uTHu|s-(#yboL@V@uj_2%97lVZfej)a6iyIGk*oLIv_uD_KpRFy_ z7g<`|2=W0^qn)X+ALaP=ccidES{1?1?3gJ+mLZ%KklBWulI$s0oX6(2KCs3JDc%7hDOjD6aw>yQ zujEjsLvAN^uW-Oy5O)oHk~EXrIKgWW?|xE7M*n0OPzi8Pvn6BMbER80FWoRp0Ra@W zo-EX3)IV|j-Bx-S7Y5DohO6kyr#bS7JwPeg0m zA81p`2zLK$wMV40P)^AR0EEE=>>7t3AhUE93u_G5h(&OO6x)5Or(|aSDKaUq=EzPK zP&xERsJyz67z?$Ou|sf1^3+uLSY3&cz-Cf8vK~P5u>W}gz}78J$Zlhk1LzXF)?_dC z_#BV;o^CL?sGBTvyBQc5uoF6;T$eu1>_^y*zf4g1= z-5$_cyNK^|_a(GT+d5PewLvNs8p0Mt$}24`g;s5|3&Cd9nvbB!LGZ3>oKF~9Y6I$; zze)?xa%vg>x0rmb#Dd61WpZDRb6)?fw1B+&o70XVV&gp)0%;0X&wQFq`dwS>x! z)0iffp2^&u&}Y6Wv#eR-VB9HR_};}6Y6}O1QCpvvZ_4G61$R%od%Ej2aYuB@CP^X= zXG~lLLF{7b6!-tU0=O0!#?tS%Y(T2{hu>z|8nM(7$a`L2ghK@2RVJ}D>_^gHz3@xY zzvWipW(V%S?g^Uek8LC{%d9kw!?%bM`wgfYPJ&MzR$b30Y`L{a+sw2W|NaxWrqJrCuIOtJs_5btNKRQnjGHkJn+suSs{xd~wngm@THpafFTQ!@7mbZg?|Hqw_9o?EW zy;*b^$3g`Lf>LGRybB9h7idfWwJt#a@Gif7e_TeSxjryyANn-aw{Dr()YxwtyVjXw z5ZBghmdtlFgKT<>o#L-6_RoAKd;gNhZ26;fQ;hfb%nG4?CPKq*#m0(I?SNvrzDR=` zs7T>n@HLWhO9fDlF%>U*dq=vL8n=2gZ;N2bne3z+gTOUXu=%Uq`~H9~mMzVm|1nZV zGR_s$r^hv=Rs&tnLU;iAFe_`5&5&N&(_@;TX1x+V0PfSx@PGvwJd2mt5|9YFk8-R_ zDDD0IiN`dnZhqMo|GljkKe+ISib4cYzrz7ZuMhoqj@&RpsJQ9yR9Ivy2p`}W;fRQa zt_k>PIBNnJJlmeUdk3u8@$USiu&sl&ewQ}wd$5J$(=XM1a3FIoPU?SH{%|3ivub(} z@+-GuQ<2DIQTue2Sm!d*E=s^qjQ#oKT?sOPW!!}=9Qda_bhx|HxK|xBbwdvg!Ej>X1D$sTh|)Gr5Q!wL!q6Xi7{fK<s!OD5R zh5%WHpP8AV*+m=!WHjk>@)sCAg*QA$&3U>;5^Yednl^UKV!#jWG@m1VPrV zP+0CtZqV4|wt_R`eB%m!grvi@r9}sSN$tHi0k1t!vb<+SrvjI^OWUf+d zTbe;QzomIU`1Y(PYJ}$LtW^_>RsqbARlx<^ftn*8-kSkmFO5wkaoSEO6#zD88k zsa`mQij9?prJBN)okW|UJbQDa;FXT{%J6t!xuSbP&=6~msJWKwQ#CA?U+oPs+_&^D zE(SPPDT)lI!Zdj&e)<8!Pyh5(TkQ610_=%X>AePGsEo$@55g{%lrgtFb55Q)FK<9N zd%j8bf+})k4)twIcgQJ`UN)#w0LvejO_NR-c++?TP=d#!!XP^6{V z@!C=5SsoKz1yFp?`IHiTiG?BlWMjI(pyMQ9UB_=xd?EEa8tU0)%`h?9yC^zrI{hAk z;=FGG2U6PA{qNTq?aNLg1`G?~9E3Mcm^xC-v^F&?3gs`xp+3}c0bA5!SG6TSMl2w^jHvQ{!wlIC%k)pUW&HZQE+R9!Po zmF_iyU)I&fWMp2YFzBA#3v`A${jN!e=qu>OaVrMHuTgZUv1NVvbh7C>8=m?uoLd~F z0eaD+N@Om4WR%6oXzb*B1HQLVoR}|o%7-Y?S55A7nwuKRE471LEs;%J+ze{fi%*U= zfy58Gm>P69W5BjQv~&?dk+TLB6{KCHh}kf9^j% za6_!x=#+{|1>k8oeuFEVILnAM;rty0w!Gc}2yE=wh4d@wQ*wm&qmz?+=XMXZSM}um z@K%yfBsi32&fcMFSaKS?Bq}{IX_UEOLH2j?mKm4xIG6kQewN;drgS(wb>0me0`Hn* zV;9I&R##R?eWb%y_fvH}a7lkwIS);o#Lk7MQlS$y)zNpRW%1Cx>-$w`4smF74g@S%hnf(M*Tl_<=^kjudX%%kK zB4YRa`}Yt>D_p<82~pDF7roBOzA~a^o+`=#@53t0>%Pc;3C{V;)jY(_ZE`E0i?dOi z)y;Qn_k1r`ZEbCpcQO;H-bpde0G)8MTf#8A4P@mBDqUmQrOq|%hN4a0a6jekdSMw! zXp{}#v*9BROD>*R#cg&SztA3=NaXKu<+u~lr2uSQ=(Sw*&?j!vgkXy3Z3snSy^WM3 zt%j?QY!Qx^qtWwHf8i~HYs09~4I5|h>srlDE#XVl+Z zZ~#T4Nkr4GO?~FysN?4@)Jio%SHEt>`z0Cw`1OAQpC`!V;y^s_xT-K`oHhz^|`ur&To9b&-dB0K~J@-9h#6F+U;uc z4^k0`lq`bDCJrR<50t;rT-caTllEgl0>1A5B86m^p{D|X5>|L0F1{0)K42^%<+~c- z?RsbH0Y?3kaZp!td!ftO{a;Y>;Z+o1ZeQg|2mcjBrdA)$P`QyS(mp{`Jl6=PN(Hdp zcEIvqL;a(n-u^JBo=_y71%LTq0LVqQ)uH>>eb7XZa!r&KhekoADY*!9+j=|7Zfx#? zTnUKm0&M%2&-1+p1T{7P=LQZOgg60Y8ApO68?KEDW@Z+G(#pm5SZeyc08_9#C;%iU zs%zc7U0<cw=?6_uVBreTz8QJHTMZ=eK32j| z|GzeLR?3$>d*A=%en<_+olPVI4H_PN2kNK)lJ8D7^V|pEmO39VDKj6is;&>c#B_N`d`eCf!Zcj?WZ%Ax~?C<&EJU(rn zrEBY=mK~aKR49>p%<0| zKL6PEwCOmAA~o5~@6x*7qQ~!;&`DooA(k$@NH5tXjuBkp2@#~t)V}I;WZPm&D*$ay zNm-~&xJW?YTrCH;S{7!@P8#<*E69!kg@t4t8p&dhAH zz?%{a*hy={0u%Yhxy93M4Ezy8uOT)Zj!%O(hMN2)&eVX!h;Pjj`=HY}zXtH`!4o#(Q$6 z<7>-Rf?M0CyL`-A7E7zne>CqXu=|3XOLT{s^rlX`r?83@vX|o9pp**{d{CVHaF@2`bSEDH*dPDXBP_0L$w!6`nJq z3D;<`9&&QB3u8k`=$D3HV%o=H1!e54)HDf9Jy>TTV@U-AaAH=Rmyn9&@8ps8Cz44CNbk!+*S!*056d%! z`l`HSz|M4dSaRoVh^iND^17_S?{aS_S+dvG+L}G2 zdKyP;;u5Vd=R{x6JotRKE8ty#z}ggS&U3(cnb_-mrLQj9_Feu$6gcRto(7t+5gb?G zndUhISyT)~M?Fq-p3~R*%MH^BDcavg+P{N@*-}8Qs}=66%#r zVVBZA=;f+1+VBa!5?VNMolH9X7po7)76k2ai*e~Ga@!9Qsj1;&9)N^FFL&^Fl^1h2 zsX(TZ@ldjpOk!T~?|F9G6=<0lw%V{frl;d7kV2TwL>(4dnGqP&lw{;VPt7NHFv)4& zE;N!cF|{3F`wq^(_gy2Hz4a8TF9?;@=co;@;CrgA{rB2g1Q5LQ_} zd*cBJ)*l!dBH+Hug9aNYifam%Lyz(`y&y(kO3=$?=SgY-VpdWwi1(mhpc_IdbKGPI z=b%Xd4?VZPttUbd#^x;==9t#TOjP>NVneUUX$h$uOdoBndg_1EqAxMxXGt=G_(lUZ zT2h!<2ysF{HM2KKm@;U;P}n=GBq$dDQOq9z_O#9-g~(mt<-Xu{BuFQ5kcdgab#jew z_dyn5-&9p9u1e03+tU=rRmNECUYs;QfM}PEaoPh zoN#gK_R`O?aMy*DyDVQI*D-E{5C14s{m4WKhk6|0jco}y8BWNb6zAg*Umi8%tkKXY zh`If5&-2-Z8o$f}y^O;bnJy4o3}6BkS0dCaQZCZ5L^a~C)n8?+_o zkIblO%`MHcGZFkQ^3=MF$5azjfEOoeuw;#xm0*clGXMrYd)zxq6;T@Rxf-awT`X~F z@MDx{&V!xLD6c9H+<#MB3as8`Bn{6a=`#OHm)1wtRVR^^puM`q``EKPu%hX3w~Rm( zCL+jKeA{LMTX9o9zAQ_e3>%*iylsLsZBxbEu;u9(F}aun=9Xw{YGNVRgy}rg!`;wP z3Tij48Eob83}|Yxz_Vv7b)L3tfjnhRXor4EcNR$cn85nY%)UJv@&Q$U*A8mjENCA9 z-kEr;hu%?|Q%4A2Jl!8c8V9bXq@B2pgr;VovdBX|VoM41rGccCp#Y9Q3FDue>hrj* zzu40zzSW?cgLAWcR52@)H*0!e^ejpujwT^b!ACaRVH{3m=zouj;1$4@2=y76>f^=TVm=UF(j- zjWF3vINTW>*0de6sY&Y!seYCzWGljLKbg~dY-21!#7tGEw%UQc=hWw;xu9hL3)Ek_ zlrBu0E|!JMZuOc(;YpLXL#)9Z0lVUxrEmy|be~p*EXO8Jvca)skpOQv;D<0uGPgv! zR&1FeA6bBFZ0Wa%@nZPrrrPU^XyJ$|j+hCEf{SSKw~|Ufp>s4lFc9_w*@f};1y5cQ$%5gRsEn@kl%HXnb-6G_py+``SWVIJ6Z6o``25x z454p3*))Wbp?}jg-~P&krY*w(@;HAoJixGXl2cLfWkK{}-fgxKvM8DhprAv$4G`Oi zPVm#V5NJ_`Nw8{jAxv#M(C^d(LycsS!Y<513C}}BYeJYe5?K9e%&86|)}9|rjtyaM z%laZi2st+j3d$sfe^{g%VA{Z0f3IC!wxR|!o69$#0b`J&?o3m%W(Z3K_yDQ?_#fXw z9hI%EtuG7I7aCAl_CM0weD?z+Qbj?*cn6%>Qoe0H2Q}?2F@RseaMo$TqcnUT=e1v+rPAQ z1Bd4FGHM^?XZ-e82D@P%lr6qi-85fct0LzhE}71Ssyq4(NqIfVtGA+Y|AYPud3%br zeVTr5EYj4 zg?U&}J7mfMhDkvsvDe_z4bi1nKV14^Ko9%t^3Ptma;3N$TKf3bQ=DD;8P*hyrY*P+ z)u;!=ZHGY7m=knL;nB8Daj^@e{~psvK7>?P7}X$_YFyh#_NtKmJrv^X5b5ctN<*XQ zss5hc24+179S!SY#of0*H67wRr==RYyUqW(|9%^a1ggf}92p&zM)E=G3@D(P)>ov3 zZr&6Wf&LI~M&h`TRdQt~9PCv*^jjtOqJryQL-YC_6cl>K&=Xn87otSSX}m7k4g~9? zUr*!aSvsoWJKIiK+?kB~s?b_JM}wcfE_M59E<@~-Ou*o<_1a~H0@TegS)Z14<$=`S z^|tm+072jukh}9Mn}BHNm7N#-02XTe;0P0iOXsQ0kCG7@iS$rVcte;Nb|%Gr;erwG z>z&(IO+^na;=`bQ7Z-h<;r1n!!C=Bu-*}nT;ce7(OOD^91(=4mc0|p=3(v@l*eIBU zIVQc6alT}ff)FxbAD0Sv^!J5rZv6Q6x8y(y95^ARe8HK0Lw^rr@wUS=cnIOB3-|{Q zc0}z6Lr#`GL~>)I957d3%c&J{vpqmOt`rmo`@>F=7YT5iCq9A;!0mU&9lk97Dp?5ev1&EmH{SL0s^cXk1+CAAK1 zvIbf*YZxS`7W)oudt{X-Ky0RbUl(PrGf5px;gADZ?LJR4S=Lg_*$~9Z5^WP5<6%4UP zph8I6M+hZ@cKbHZ9q}x!Q-xp)R5&yIWq?RR!-3TSAEbx!z17U@%`=0P6JY0;mCyRa z;0$Q4Fggki&3FCY_7Fj1!R@p}L#8E9dHp@62UU^Dvi2g(tkArQjyXOJtYS-;*Sd zInz*S-}@fvX&u?eNk?V}n_EaS04`>Qg)AzF7sG135%(Zye$eb@Q-B*>FTIV7dO);{ z!W(gS#9v2m>PTQ0Zb|C=%x*of>x_YxADH+;e0@d&1Ppjz8s4?Y&dXCXl(@e zQ++Y`B|%|ndx#Lx>$Q*SYWw%^r>8dZyl`)QC$t8^84@qy@3X;8&oqmNU|gw#1)fqx z%Q4x_uQQ4X+bk0P9A*=KIL%#wLDd(V80?+N052(rGxSf5h&s4W@`{ z3J;bnW2^Dzg^sKK0?!LqD|-&bQZu>|gJ|WBop%uS)7LtGZ+KCre1(MjD~%+_@Ypx?kNd3UCJy)&Q~rDiyw)x5ssMd$liV) z4~9>L5~VvOeV9e>rmc)IuAinBc5GhD85MAyTdvkF=u38-=$cCw;FAV zghKPw@$mBb@!GM;);`X0JA-C;N{&vuNZx|*`q)$ea!ZvSku^!fdY;&ux;QK$eeasa zPC?kIhkU5Z$mC2=8!@h=R?KqjBY*}*vNE3Q?}LcQsy~Khh-z6=Q_rV?8+!%lpga&o>E+bOJw$j-6mU@389DU@UEN zlXZR^tc(nNWtpLp&j~&|=Ht_!a{Wxr$6|5O~qvr1L<;ni^N=GzM0Zq)U zvd5y_npa|26$_^)IBiJw!fT~k{;|^%dT#adv2n^+^yM7OTD+_V?*-=2n z)~UK=EATAh_EGgti1>6D^?BHKE{LPk_-L>~>lP{+!u*3RL=^(KUoN)>PL@ty!W0i! zd1O3}y~zah>8w)hHum=H%)i7~qW`}ES9Iq-K9B(7gkIoy&7F+aRNt+Q=tOejb9o$0 zS!)dxR|I^c(-U`ZmvI+<6#gvauUF?<5j6#_PUN)bBxV~7BDV5D!M={^MoZWfr`;~LmB!CVk~sl$Y{5|SRbH$ra;l`gGBXBWjFz&>b}r(D?Z1dN&T3ZMHOFNYV&o_cz zW7WjCiy9|eIjdu%O}>zRTziKVApXLuCBq&op`2w$J{4e2Sv z2JzjJI49WY(i;V?|9*OGdd| zQSPK$v@wKPK_)@%Gw65EvEfEJP;asGxAZqSneGP`5cGvZ%j}EP>^U9aPhb-|E-7Dh zuxX>i_U+;-UOMX&;H#)pua!E=(&4+H--fAHko{Ct)O}O>t!FW}&A$lE=hx~EuD@bD zpE?av_*K^#&ZU53B!iYp=|>!Q5SUJq@e8W!0H)_HAv;lngBHdoOiVB_G4cPc0F4Cl zRtG;PNPn64qv{8#^ij&XT-!b>%BT$lab#)0F3`EXs$CyV(Bp&=C+r@mzyZzWzBXvB zkcWED4e=4k+`6z=+J2x@`k0Uc|Kaz|Nmb-{)Z_ej|1lpJzl!qdWF|-H>j#mXpem>#cSoo+E z)F1ihg~Rm2EeNu08J7wkkdd$Q#9BxCLJ%z4ye2lzD5Vez+_p61oLyGf@7v4GNrgs2 zLL*sDk#P>oIka9^9Oslgo)^Jd>{j2WC+`RYwfg~0EW#OIAAr$XHw-9K5ef$#@xx;+x?ma!=E#6DaJ{|z%Kf%3k7)Bf zC68%6?)gFOL)ue8P}7DJkh#udQW6;Z<)npgiz2Je6oZ~{y$sa1Dt_3{=vtw`dk(ZH zj9VjUvYah`&q?-9fOgY!*&uQ^_p)_c0Ne(X1NIQu*fj*1br_c8PqNB$R`CojOn}_V zU#w(Ee(=0t7r51s8V$tbd(pJwU=)tG;08TDuDQF__U-wls-CD=?I5#9r$swI7OsJC z@2xn~EMBg)^PA0a-z_TZPK5$GSnCWG!hcKtj<6d~s**r}4A>3IyPin%q22p|O^(Df zY3`IBBUA3C6^G)@JSBay->sgKnUybyZnQlEuWj-iG|P z!&SQ+ca#3;(I=U@kVp;U2@}b81{H88@p8_j{rZ5)=?KAmlET!0K(~lf?=09bp0U?t zJ2p}YDae}aog#i-leqDoOx?kGM9KP82SEX83+rpf*E!z*4JOa>Fh1oC!-u;kO8{{^ zHVn*E?S7RDn>OI{$dB(G4oY<~20#kJI zMRQ$al+w-bee-|7BzCaLqs`5c`F~J1jz(KSU0p860Aj*Fv#*{~={gUR32mRrJ#(EV z++|dxS=>V#!6<+uA`AFm~=_AWJROreWZx%g4HL*TG1i;;m7ISY0cvL>tk%F2g+o3#cR_$O;4c!5ytz1 zE0&HXy!ggVWm!&2*Xh-6G1ut~#nuP<-F$E<*|}Ed!ae#CNpS^r8382j7BucpJE*Vy zgCU%efRi%`z1!N^361@}><>WgbBlhP__3AtyDd*)%E{Byz{sHrNoLnopQdwu_d8Hv zpHw}@-}S&l=;36E69kJYuZ24QVC~~R!;nQtWsYIh*uQUI;9V)pGpNhDr)mn3-uat> z4hnQI_)cN?jQl%TYIO8%Lz-WgAguv2icv?}yStH`M~fVjy9R;C-h0Qu1Mt-`a9{4F z>e(h)jv&fk9{_$drC z`{b|BUsX#*A3Js|(S)OO$oJZ>fcmtzTGt`7WwW9Iyb>@tmW~;EO@0T)3|W5PQUl)= z%=Cl$M=2tS)T{yC6Mz5nD?j*y?gv3wMuQ{3kORCgg5Y~s4hWONoH@&OZ~ihOZ#H93 z6xRO);Gy*gph{j|`@_^QnO`rVf|^DsPXJeM&>uSB2$9Xx0CBM$MYvRyXubHxEesWZ zIbLy|BYWMTFa^LCgU5q=3jPkhKscW#*j>K@smhaU+ycC9#5_$XmV{xgA3l8eH}ZA9 ziSe%h3$%sId+>~<>zTakiJne_?o{7lMUIeB+`0&Fhv>}uptHs07#j}+MJLQ10YdeVQ8lWDE zBY@%PQ8YvoV)DVItp#ID!AUEm%_00h9_Wp;7=t0hjTHe%X1!NwnnBJbb-OH2T4rWDEw>I8W3{`N#}`Y>PeAbV0ACjnx(7qwUQ1FHG)H-v$}?Dk$C&zOC<<`BH5Aq7^ zs}V?EZ(D#|qL9V?T;+7wBHID|5|zTjIdud^0v{+|=>Nf+^ zV{xH6nlB4II)*zJ${Ko~Z7ixb_BFgsOg`Vf^OV$d3TN){N}QIyd`|_Z^HT1tFw9qv zg&Gv6-GMq7``OQ*7i^!8$5~!fBlFS=WYxs#E!Q8Gc6RS<+~{b085tR&{oI5b-5FE} zTIHAs-;vP-hr9$4KlzCur)hv$$XZ5G3@Lpi{c)I;iSArWzj<(E_uA*!1T()0R6>?0 z=BU2?iQ+QKyIMxtOhqek^0jDm(_HR_NL(mhNF3&Q)W_*SSyTP5V3Z z2V!QQMKKGJR;xsdiY!rMJ=D7jCj*w4qs?RNO8q086aUk+GnuYnOI{rl9I`5{7ParW z9CX5T1{+c=T2wd(?02a|Udygt61!m1WuZ7CrOFXWAivi*l^3!aTn%ax-;0Xyx(r1A}N2@`mGY$;{B3ED+To=7fwry z2$%UB@;ikA@vj@wldn%J(BJYL)KWmH zTaJcS@kv6A+d!RZ_f?2x&Zwv*;k?$G%4>u;({)4Bu;CZ2OI=q@8b`S_&wkw7e-a-M z8}cSTeVHl5iQBTIG(MzsNY?yV1h0thq{M=mMprsTn69*vGV{vf5>?8iU?-Q$SpG}` zzq~)|1W@HGL$(Ah3MA9dJc7`l~h>^<;HzaJ;go|3K-sWYxxq{t7@V07wtgaoj7B00awYTD&XL4 z?0OJ1M{$=VoeQY)s>)(VWxFf#XzJ(OvMvoMbn4tGRL;~Q$iLi4iZ-`-w zcu({ykftDVW+B-fuO>Kk3J&CX5BCj)Exa6nwgXCIM;ds36EM#)1cd0t62lXXuG1TN zRh@VXm(T8IDE!OT0j7-0&?XVvOv&qN&ZpFyhwV)GpGcrBI2(}@y$ z@vTKWs|s;mVuL}R$F&}(qfi@wN4?b3!=2qL@WN6yE-iK_uj+_-fY-w8xqAiCktujD zlRE}sIP}ohv#z`hBA2m3NhDg$1`YyxL!yr3Uilcu53_1fA=#^jdWCAhAA#SMdiOz^S`UCJpmm5)E2^?|iRgRgW=PN5 z_$H>vzvB`wK+j*unr&CKn+9eauLt}S|4N=%IkX~F&OPcD6B2v=DUV1S0{0;N7j12; zdXJ&|;kGu6^&}-4ERbuyT}3oWCaAE#*D|^p<*jlx=27Ut=tyX>#XO0^x(X*e6#6cI zGQ8N&eOo2oMFnvPXjEw42LI@QH2rOwn)YRicASuL0%(_0_VZxV#lnE*WT zvTk#-CeU^_OiQkMlUC!E&ZGXO-lV&Dddowdi-lpUcRH85&BI{Mj<(hHT&noozlQ4- z6{BojIp=@JxGRUo`p2DsQrSWNXQ@ip7WT?ByG*3iaQv04KV31qpAGssCD9Sa2ZwD* zvdrk^U5+rqpvI+TjB`fp*&kXC`*>ea_o>gOd$Vx8^Ac&w|XUp>2k zh{8ReM&f>RKv$PF*N}I^C&+Hu0J2O)|Bm=Z*@d{d52^%&-P6*`t>5Qa4La`Q87y@owu|5pqR1>e{F2@X8fNn4B0ycav!J#)h>SrY?`YxXpMT+*4Wge2-n; z8qK^a6uu4Zo(fnA0k>n`U1PRfz7mLF1H!2*@jB3(dp;$>@+;mu-HHiA`j{^FIl&ub zn_Duei7Bal{i(mn7ONlq<-1QsIMu|Y_dQ;*D0$h zv^+`Dk44b6)EvCbS?}EjyymK`)MLzVExim6j}L-=PHrE84W<}TBZB(l<=aqM7?h)N zZ?C2=3ycPktj2lS?vUyJC*ulCJ;U2o8ijU14+WwP+L}Xe@I1oRt>o!Ad|GR9p=$`X zrls26hgvv?y5-Z+g+ggHk-8_OSiUCzX18xv3SUQ)MY`RX0b??-V)BZ;cG^6S))4W_gqG20CWwtx^~+P)jU* z#21$-KLHI@eVkWURXHluVO+WV(N3PFJKj&7GXowcjn#x$+oOg*uM$UqPNpCXI&ZJ$GUlNP zjqD4puI3Af=Rb=gG~QZ{1kG_3kjy8{)+MpL!EJz~xzsv~y_eMiS`%Ama*e~N)3#;n@q$5aM`38vT( zB%D372#TN(B#d@1eU2?En(@f=OqjJSWjpR{4@Dn50T-P#i8t8Sa)BKEKN3GIRXepB zxeb7t@DV=-T9Bv87fCEBVBfQ%;L3VjZiq>kze=P1ln35iC*At!O4r-n`3#Fvu=Y%E6+1I!qBQ z3h)WK%qIrA5%TRuO{}P>5$_diHkxNri$oasom*L&zXR?TX z(AzC&;!}x)tN+IJj`NmJV>oLNu<9sktyotVbgMZ)A6r#Q6}e?rTNoC<-%+n`azi@M z853kyy&xmHv=%0o7%JNyynxGxAZN+H-I{o8r&GHCcuk@0258+BpH264lvn`2P6_oS z(6|kNP+Ca!iBPeQHyHaHc=0u2^l8GHVstAsY7r=wSxH#+xsZh0x27=QO+m1I=ryMW z117_RmkVx&1h$HXKLI?Dl$UjO9?gysa-X$NOyxCj$TC>AI}4tzYyRCqTBf?h5{B744bmyGJcmnUpbmP{L$AR=l&V z0C%io)gPKyLJq$2ewg0JZG|PqnS3C*j1Y)?VmkJdpxq&ymN+TfjG0jZ{n%p^^gbV#1xIq* zR}!x$i(Eq~~fApfW_Ll^`F0?46@f$j;(G>8K-7 zZOExE{R#m>E_EKs(kr(eM~IN+!f6T~k&ZNh?hI1VeZM*EUb-+t0Ue)s zQ@O^)BVx^62rS3> zbvBw)djWp4m*mZEsJ%@Lb=EJ^sdX7caTct0gEtLJ4#HEsCEMJ+W72t{Yb~l!9UlNx zU`)0>DL%_?`b(m{Yt})4+p?=;Ar3m2g2VvcATfaNcRPlyt=O(|b+j(b=S=_&C2gvV z3Idyke*pOSVCAyDZ;V^hAc+FR6S?CD1;w{3Y=l=3+-8w-a1dLznhJz88b%~qMsYuP z%k7atY}L*j>}HXI4zxwcJ0!>g?%NaiMONf#edcCec%^(3QkHz-2?w@4fz;~ykJw8j zsS-FGY?O`cB>cY7q}ug5Q*}QUqFPqswBC=06tn4rYM9V>%?(in{m+dY1^Etvyofc3 z$C4F27)ezKxqmd9YSL@7EzgehmoX$N?@w9s>+Sf4^W^PU6~h$Vuk3qd>!oSDHS2&N zAZ&RHxDvr!fhO8N0tV%szEPQ1WCvT*I{G7D*z(d~VW>QO5A$zbnQ7gDtSDim2t42$ z>P-qD`5LIkjj2?2#PNely;{D7E zZIPl!q)3_{h}r%-F<-g_t`DR@i!2TJoVPFY(wjqj$@lk+K=WdgoB?o#aJiC=847Fg zAwIbZCV_gPjk+%j6f!|3@}SQTD<-}mFU>L+drB$|sRI z%;!@o1CNXOjuIzMfMW)b#aga84XKt#kiYsTm?zGMKlH6~U|udEd7lYl2w?O90L-xBa&pH_vGkM>p#+ez?(Cz*Nq+A8_JZ6lDtspR=Qi&N)CK4%EfjZ+jzB zj{M7O>}}2TeOV%M$1Hxh8nTd}Dk+VkHHQtz4C;M>gKQ?F0?(IEK*m=8_^1eF&nHN& z(^i5eXab^1GzTDS^J_XNAf=NU`-u~bAyAqHZ62Vm4hVNMA(Y~Cb!;ObY$`N=Kt{Ea znch}th+<@rV07RBu?5i85~=ZTOZ;I6$ZM~bLuHyVNHcICBU=C08&=!#9P+;X6CH9t zY-=e)4RC~i6H#k4eE`q5twAsr>H7@@rDwK(6$IP}YGr|REjxUEV17&Q)q+E8ITpu} zUzI{Z-}PR$?qhqjBdVjK$HipCmfKucuCWO_IMm)*t`~6Gp!A#`Rxer!nvLQVv30u6 zl;qGryHKC)vcN<1&^pVc{Yp}v6a=@Gkz6s?nViRqoKLM87_Ly$4%FwmOwx|kSNY5@ zPi)k}p#=4XTv>Ep_HpHYTv>8+=^2)<69xyo}Ey$%-L= zntrKwTWZhz$_M#3=Zx>g%Wkg1KDe3I1xLTT364H!6YS7P0Wu$+Zarn@K_<}UEocV;v+kgN zcCe7mHGrbw>$gGVZ#P98bRftFRO8s9b|*aq)dGFAaVNe?$ZwpbHZeM9iRijABEQkD z;{E7Jh6M;>(0NAGnqCzw<^7&f=(_3`iTt|Re|w$@dv&!C?dm-IX5IS>QsAN8K~v(~ zE$F6AQ`{|2Gnn&rRjHW}(tATlgA)2`HEK{twv_m~1}77Ckb1ZE*W%$~4KB@b@4fE5r${9+em=yxA5td?Oy!Q4rf zivAD(6(77%p0ZzbCE-ymN7XkXC@}`A$sqb8)v>212tNo1Cp!R64D?<5j`~B*P_5|j z6S2Dda*l9VEhOBKf5!-T`iff5x2 zm@(3pGc|t{t--G;+!8(QT@O8x;7Za$DlQWq7s?{2!MzG4Oz;3!=Snb|pgWfw20-8t97A~k4!4()adES{wzE!RHRg7vcE z)p512d(iiM@~NTF8mYexZDtKF>aMRmYfxUK2~z~f?BFPm(1Rm%Ptm?l-OroD@4!k_osvJ&eMA~lrVVAqK!kCo!&}Q`; zy@@ts2ce1bS{3jNy1u_pc(yA;=+t_4!p**H^J2X<-hvh4#^gloM#+y0H~k6tcdk!+ z3z7Dk4j>I59*Ph*mznMoOvYdH<}G=*WQCl;e_&%hEKb`@m8YAqg>^}jAZm;K~PhBeY|A;VwYY$^S+;Qe3sMTD*MS3{c&do~v~DPJhSpQstv zNGu8b1;R6C2M!pPLwSoilpgyb=!2AQUl#WL^`X=7&@4Rkv!KMk56`|`3Xkt)s`~Xv z8~`>AmgU+G6Lyvh_KT$fmuUWWacMsRjNci`z$Mg?4hHHyF9u1rvnYnJN4u$Enj?%^ zf=-yWq%IiOQhcDMyeCZ9-h$Ls0D)upfpG5y`|1AQnBSYoDV!Hd?+{(HIE)3nT-Hu0 zS_jN>#;n^w4{bo2w;{EK(K+@*54&C9R~bg4+!#Cw4#*O29E5gE84L0*KQ~%;k*sYQ z7=xSkGc8zuDc~vNyZ%r#Dtc`RWt_r4+#gC1Q8qh$dJ;EeNb93{> zN+lIdf58<1g;8s31`=I`-Gb=J zN%);1A*MxDE?d+n4|++4r(jNv<8=mkHa-?iLX3zUF4 zW2wD!ezOTqqAomM8Dl^B% zx{}Z*IH2fQ6=XmagMb;#w;An}-|#@>G|k|5B9zfk6MM!Bv%l2}L6<}CgkehZnl|>v za)}y=#oSfN37XAr?Z?d8GU6;3R(w}4qkOFQzDcf0giLUOKA?izwP^XgjGjY%b78h!$RYuQ0n%30{F>VD0Je<@DjxcKxmz6oY9dxyzY z(kJBR=AKQ6X^FU8P*BkGH(~r%V)C1XGs-p1@p@%yr)-pys+3^20`MwxXX zN5O9cF3QOQO-)VA4V`62V^jyd_T}-QC+Uzov71?I5U9Z}i0CKVbiTw{bETj);^0VoTYwjI`z9@vMso9yu<9@i{zu!ct5{Oh(G6Wwcht_!4MJ9nMo z@o_3n{;JZ%W0Q8CbzBh^5aT00lBdPq^8jAg^P+cupAk$}0rG}k2b=V!pk%+*PY7C7 znOd!aWX?5mJ#;!1roj8L%0HJoneICRgr%HWkq3-KPs5VWgf1barB-*jw8f(lW@sz` zldTkd&8K|DTX*D``G10)A0mY9x${@g*IPp8jDf4}<`J;e1y#X|Jc{bdVI6{Exfwez$HV!jAb4}C;XI-vd)JIVuV8+gzDRk(9!&JdzA!%@A*_c zL_Bs0?AQF~G#l+HQTypT!$L4?T3_IYv3{S!xc8mV} z31HjYg{U`r#h4h9CaJ>%sIp{bn7l@T&{V_n|`e7p+Pb> zaP*I)K<}w@%Kji@V!S7mT2H`W@x%K0Pj&K}bsYZfu?kalsIF}z?0|qmrK+!a;ga4v zzCq~T1H8DFms`42ncwnWMH8_DgdTv@%W^TENxZSi6ogu%peI0|RX#j4xxmvFf*Baq z%;7L+jQvoUZJBRcLc%DBsl|>V(SRTfog5Ndqt{m@t8?#DmS#0Tzk$JJfj5A0wD*pr zM8+fjqUfm+MAlnXH<{4qop(D&&{l2=4N^iIC;A7+P4tc=`ma>o)F8;-^~x?ul+NFI z+xw5peUWAD=afb6$D11KCHJEsb~(|Ec5E=h0lD6lODO!Wa&L|ihE8p$id~RETjJGe zwA^qlf>8Ba5kaZ#-s{^K)uoV4yT8c(`JT48>|HkZs| z_OADROU@qs;g20wF}l9Wy1cVHoQ6+kzBsw_2JwxOM$D@>k2AQA$I672Q0t4w?|l55 ztdJ`?_ldaHDq<-AgaC#fdeGc(K9=9oU`hF!sI z&e5#qbvj!|(p)%6Ei%a8`+0SBH9I@|ktUzA6;r52P+x@PI7LOpv+@4U6uLgwf<=bS zTBAP4oV}&d7wGIJQJC}>icNBOjM1M8d;9}-{7KJ_k2XRoIFzIPs`IBpD^pX`R^7}B z+Oy$Qyeld8rLMXkkZ{8--Bg}V)Y$4E12yvdC-w^N^BF%67jNma0jlv;Y{=|)yJwYH zEcQ+Yv_U?uQM%JnkM{Iw={ta8{QjkVnM{HfEcUoM6L&996azhdmRTn<1H9jmlI?eV z;8bvMu&<98fBoJBR>cJdAq}T1Jy^wRz7>kzNab|uzk?Qfv9hwV=WsA>*u9y({lMtx zsNVtJu3G~V#btkXHZ{>O%da-eh=d>g&Cf?z^v#_=p%}R}RuI{jOI_NMplI9R;NT>Q zY45!eTn3CNT~#*$F}tKNWR%d%ht|q3n(JLl%3QKyCsURNpJQWnFZ1#0Ke5u;id_vY z8LQZvgG?xiuxl|NVq#+8Sw~0y<&nM|cV}?lX#2l+u0_AQqklF}H;b{%Tc-bzbL=ZD>V6gtWhcCONWchl$3uY@;l8(q9JzvE-H$CP1NLag$IGTQXW zNDLuH7S0ofdSLqc|E>Bwng!~^yJ`)|R`!*YL&@Pz7c51>Ctuvw_VUy%>GPugyU~m- zw1k^u7n~aWc(X=UB_H#y=3aWpD(|i~A1z(@sWJ4u#g0e1?0d*gOqJ>h>yv2ulW48J zr8DVa+{c!!TSKEQ4(!6`+{U11XqD65xL?KJzE`R>CKGpvrYw-Q_90z1!0=-rwe3Vl zYgZSOS$yy6{cP{D_$ zrlxlmslf!xnDvW84#GmV6JF}4wNq9P<=N_VO{g{^YEvU++H+_CNDQPzSRYCOiWrE8~r<{KfOE&)CNDT zZ0_QG&1d-WHYsp@{{e%PYk=wbZbn)U_ENu|&fzIZxK~Gn&t-9bZfxvaTF};hd~VD# zL8ZB|aoo;9#m$syb>4Pl(57Uh5tA-NT6TEH!jFfo`gMG18}i;M(o?!R^Dsv#fyt?# z`FCm0Gup#WbLvUdDeJQbliT?_3N4xxIf`!u1>8MR@j2+Q#AV8&qci(op!BBpy^0H^ zb7iKPpZIY#8)h^%F$-(A9lAMx$B)vZiWVWv*L-WK>_NCvg0kCHdqQ<~$8v!7pVWoP z$rDZOg@2=yTTT{sH94)s(%U$%1qiszc?dR$8ooD%u|3;Xo#UX z!d!zBttE}i$IOjLkoLa{!%G^8*bH^Mu0@)=vTEJKJkqs$E70kLPpx2CP^YVQ!6)4Awds!OjtDip8l)t;zGtMBRx7qK3;j{i(KR}?e3 z^oBno`46&lIzdk|{Q!y5kmeXYi$O9Z=IALhC-2g)#~3vvGkiyV%q z)t;Kz!hB*vh2;%*|5w?j??gg_8g5_vkY_%#u(>HoOcUZr~E9&*k5HJru1n&l;e~LApg( z)BMh>S)s6Q$>^U~2|R55y**l&uYEW;ovE;4e2`2qG<-tA)s_3Ov%?mMr_-`1Sg! zMyiOEgapUPnaqKV``n?=xHH|kbG$56R%X_sSo8h&VE;zfgnY#e1Aa=OB8NeJ;Fi%b+;?flligX zxVwGu=N|Pgs~Ps9x9@cACVKc%26My~KiT%;*S};#%zt)ma`Fad8g}3`C9N&DCIp_d z4a-v3l!l^pO>LhAi%etF^h^tD35kv2MPFmTPRZxvfN?+sxd{V>>cPQj_BSFAqcG8b z;KlNB3)GxrN!8z}zutsq17(8q%A9s@OlW6vTm{g>ik8X*iu_G)#j$HU=x#`-j8U^E z0l7uvNB<<{kB(YfTPG+d?e&lr;a?_EJFf`EnI@k_C#xwH^%3>c4yl|_K7^t!5cV!7dM|)y>-UMcM&AWXTQy6J<1a$h-hh^O{Q)J$b-{v&I@LfPwIa7or zPUCE*MWWs5V@=j7E0#Vc&b%2FV;?ki!t--diOG(n6`&BUVJF!wP^fJpYH~1RwH`o5 zp~wtfuw0i{?i-M;DSFr_{*qqyzJ=*h&dw>~!KJv9ui${HVcY75J|r&R5)828Z8mzS zYd0AeaDv0A!y(#tp)R|~PxG+@px0HV>-?f#pGOacm%_>WI!wepWZ1tJTp&EBal6QA ze(CvGQFFY~+_}QCTlSe@+;k{f^`tRoMv|xnO7#@9Kuxe@5B4&(m=-$&TkF3FNPpAL5lDc$jgi4w z%vw=}0VQTz5*)6vA!MS@?IW0?BxMQ?PY-QDR!Fgs~ePfz(sxD0+QHzne zOhdqWxKk&tm_FRG)iq#J9=kepwZN(;G`M1wPP_lJ%}qu>w-+l>9&Ky$Lv# zTmLt%QpubWGBu&H5i(C5DizX6vCBMc3Pog|I)qb5kuCG-oG6(#GK&ym8yX0iGG_=| znWy)=_C`9-d4AXPzSsZyPuFu_Cx^Z7Vclze$Io|ITe41g}YQxg&wy;t`Suwl{`x$rhz(pZ_B`Xu4QTmq6j zC0Qdaf}!03Trg%_jIM~tLv@7-_FH92XEu}g7=i`dPSzYA0K#Uq#C-`!bV(UDiq`*H z@4Ib~^dKeJGikDmc@LWR9Enbzdrg-${M7lXqBx_93Q0+i&-iW1rw#SyqrnG67~PS& zK{al?or)QwQTHv$!gC7#Wnb~ZIbJf5#QO2Mw&1+&YdN>Nnmp0Vvb-qm)}571(0gTc z$uYiv;5t^zg`>Opq1%n_LjtCH^l4KdBKq^gFAy;EZ9TtQmQ{3}%C5Q?WhmG_7(G2P zV$V&-g30uM^)gjE?dqwDH#S*{T-v8Ad~(l(c*lFR6K>7_u{2)V0W7UUI1aEQ@2(A0 zCGV4em+8!1a_^ZdFD#vy_|3rSqN4m&^{90Ji;VhCON&jRSH(H&EjchfK56DIS1iMf z#ds6=+tdm>@3RONe`=JUw!P9quobtCaIXzjO6fOTmfcKz%cf=D>et%S21{tH>`I@s zSLK#Fh{&dV*V2qr9Onry(m3r}wz4Y5(co$DRG-WCQSHI*F0uQSn)p6~QGCM)%zQYL z=$v|=5Hg-X0{GXO$f?Jdxa(kxyykpBE96~hSg-#7bVh$2alRAHN|E%9JH2{4(%2=7KzDYZmepzs(vT_)aq5_y%*9QF z6?WSPOT0);72fh@UqOTCk2AU&`1ARZ@lwUJ6`RmMZy*J@8-|B%%;u%DSJL{|y0qQC z=*Hq~#pK#aUEl7kKVds&Ga6Y5%POT)tAP+8KlLPm8f-Tt8PM91KV7*CdEmEVvG;~$;^8SI*b|fi| z!IZH88>*QueX}W=i1Yf!iYD_U!HhIN(-T}aQ9aTT2qTBRxajdWO8C^?`N|bJcHK+c zhUDHVXRwN{x(h%RGCQQFs)WV0jrSDY&akqEq8syp{p|V*G)|(mR3pF!L_#q?;Y4Ccg zXc%uoVU@_uFvKlNz4&(|z}K|v2OocwH~P`c=hwF>#E@}25<1b@j^X^7c!hTbR54H! zai4>Em-^w~$@Rp=zB*CnL^?L%g;A$swU~OKyLBT$s}B@*jC>jSo09kJrh%f~e0#7a zl?T@t20mM5mZ_Nn_#KpqmS>$*s#0fe$FNc0cIdIR-(B&i6$1$Xj0Q9fB7p|)67}ej zw8KyX9FqkBgM)+V>FIz_yJmr3g~nOI{)0&cmC=T7CxH~+zJ2@s`%On8oIE^j)B$MY zI900EE>^_-rtlv;7|kx3PV^c|#LfsiCjUYh86KvamXnbgY`Tc%TO(*dX}Mo;c~;7h zB+Cw$8If{rE1QJdND?J1popl6tlKfLE8No^XJh3#K6O1-@i#X&x7>w!nvighS4mM( zany-?qae`=_7aw7d@EN)`}C1|Dz(BjH8p`lV1Gw8THi?WLX#))(w36WiueS>D7c#;GfDXsXcXlq%boqNNjH~=@3ITSczkOpvU!hMQyhiiHmv?{d*s%j% z27Z#JlD6TEGi}>p;r)hrWN*G1I|7qYv#}1uDKMxTH&7n3qnwM1{p$sO484vo@9vF` z5-~l|tp16%Jp0FT+8mhBt+7J80=h~Q6Au-ap*N-w+#X;a=N((Azy;q-1m^xrH5fq2 z5j(50hlUZZXWmXhQ;hk~o=YVJZ+cF#~fK-q~~uF4JbkgjU{Ug%99vn9|u=DV`bl zH%_Ly`FvxNyH~NZ*Kql~?utCvOa$1oA>ANzfI#nW!_CX9+5Y+M+xu`Uq^8JRS5z#r z#hb6=HAq6ekP@*AWf$($#75Xbm9}1po|%k-0=F`f80*fbqgZ@@8p*^NL8%j5& zmjp30F|(7Z!8{D2=YiiK3u8XNWZH>reS9vXJV{PYPE_;(<^j+XtaaCNT?gzC4JFWU zi0lf;?5lG`a*gW#U%{%CiagxkWT~2)7y<_7-Gr3DNTn>X6gfW{F?3{cH=7(TZ#%c*b3u7vzpd7uw%s(64nJMg|6}r6#2qM7en9{ z2EegkGyFW}Vq%k;bh6bIJkTaQ0Ki}_qwJgwq%i}6NS|1e;q}z%-Sy=0NkSsfwtmsNtcZ%a<>Qiz_g#iAX%i{`z}!TU+$v)pRVqQaiwJg;~&B!PeN+w8xKub2lfasZ*gzruDUJ z44iOV8-Sz$yCpq<_ol_mc&?veFgB@R?OrC|_HJ%y*r{yf=mQ`YAeC9!*>w)>d!jgd z=7=+YF-vP%?L)KmXH`a{IHz{#MF#*-57hVWeru909eJbo%>!n!q(grJft#-oIH-E) zzLw@T3$IPfIu0nSeaaL1VHg}I(I5x;Hr?`QZxW7X z&43PTto)ft>}B0VGmQF)>T^3nPl&fU4?TJG_=e=sqh`UhZYPYP22fvLS68<$>rvH} z-J|Ky@*y)bv)XEQsp!ob>mYmbP0Okrh1D-|+}qDrPs?#uC*pY^oUf@kYc@2|0C>Nu zFCea3j=z}IM3Z{MQ&Lh`>l7j`V0M*lVg$wma-14z(K2o~n;7-R<97{X(kiE?FMEP= zN1hScxCbj5)}O0zezf3m7vClWjC-P%vU%{>?%=Ia`6hbh?7OVYbB@Dc4eytlRUmva%rRlY)Yb)40U=70P}+b_6&IZXJ`^z*2RPYf&OkTa0F_ z2ZL&W5T{(|KwjXYU20z-%mz$-TkZ{0F#~Ov=w`?f0_+`()|~Wo(Y%`zcXj0Ldf(_( z7EUheuW)HM9TVlw`f{Y3VGvkhh}$KsnHol4`p!h<4gIpyVMl%`8r9#gSz9Ofmw+)^ zDXwAgbq5FF(=B3IhhHR*zv{oCT2xF&mQ%dD7WKiDr#iFZ>^BWl85vGzU9quAD+p;2 zd)@nsp~`-Bq2JZ7GvlvjXJ?zwo3u!Tlk!#XaV0WeBJx+U8}_vO@;$-@&wpxZ;TU}* zHSN2&WI#kJk1Xg{`Jjrf{078=^!p%6l{g~y+F8Cak8wKk)1F4QvKYJqm(>(k#32Nf zGhK)(?6bYlTrI_i74|_||EK$04Y2qlI*GAj@?Ii?@*DbUo>2)dR!JUf$;l74A8U|g z`c#?QZJ%myQ1SMWM-9q0;OV(rKOPq)d9AwRtTX*Zo}`aI#C)Srwh$3B9gM-b882J~T_dIr`8~e&*o3opBUtL~Bjk5plV7j~B~e%l zC;G`*T3H3KfrfsomAN(Z=M|#@Hj!YdpTSPOP5u>RwWWj^>}RiFml~?!#~CL`Tk#MJ z+h~DB+1AD-W|nPY4k3h!AhPI8NlLP^v8g}V@<(1?9#{y9glf&Ul{WyBR79TY73;zh zNx*Y?W(Qe|3=G`eYN(M_b)kcf$RU_q7Z^_{EzvJKBoGSSz0b{(UYkc?9+2{J!Pinn2zk_g@rOgGxq@4E4B@SzPTCWQTw-adUHnpHmO~emci!ThTbxt1~x9OCJNKb6kS3ly!psO@CJmB8S`5-tHm;r-BkU? zRIzry;crTwGg0u5(~DsPdUz(&^RCD$O1<#XHz#^1JH zwLgzkJPPB&)Kk17LluaYBdK{PeFq0vAA1a0e9?oJc5kE-{02mG`cBLnDuT0jJOEAl zgrmr^X8eT;)D0OMLvBrw=t;7%wa(dG9T{GvghlQo**iW@z7`ZG`O4Qkh)yw zi~XoDd%-}09{XU50W#2WXNQ2|+?T(4HN9quh5!t7hiWQDL`QS+@We($knYaHBCq<6 zw0Io_r}lZJA@VV5nP=mfGp_ly-d{KOTG`j#f$)OKp8*;D7BsvlL2Z=yzkx#_Z*vt3 z3fClX3C1SYW}JZe*-y4ICH^x&0^y^pkc65X7J&1ngpFvfV9u#;C4=(-oSLv3S9<~mgF_x1kEeB9VZDdRG6uDy|3i;;==2gPn&GVvf=H4VF=@~*|4YUR9YWl~ifV2`Hd;*HeAyzH6|hp>{2(wFr;9KP3{=Scv$ z<~8f(_E@ov&P1Y>_($LZ&G^cy_SwaMy z_FSvXPSVpT(Cs;w3Bh^cZ<0v2&LHdTz~X*$3oUdVx38xSKN zVhk@*qm|OGw0(1LVbGRxS8T+Dx`-Td`QF5iF@3J?3cz*7up;ry7J&L9@SXgFiw zc_2W!Vyu|4{OO|J3KX)f!Kn>yfXReMulRUP%(wW$g+%mn#|t_a zg&h>t)?q|;d|QZSs?loFAFXQc1Gt8d$;(z3@P=&5%LW~kkWw@NG2rTr-%4d zPfuQj5eJ*0Uy#(|5t3Xh8hMr)kYBY(;Rvd!pgXfGyGTcF$ITH%`-=bhm}I}TWo7uM+CoJ)~RQ5 zQgndG%z!LDc||Tz(6#i+P8_w`E(|t7`zXJ6XIY|UZhqG{U^2E0!weDr;HWuGw=+_1 zYFZyIZbod-d?r*Es9{%5AdG0txZGl>gVOB91He?h;}SC=AC7Fnr2Fc=$O z0AKpv+fl56xi!t`7>L|x?>-hfJP5eNZxR)lX`o@Ols`;pk}X}80lQ7g98g*VR(t4X zRAZalBav%!L##O{bE5is4;Mn}si-hu4XwM?<5|8<#$po?Pge}vWOLQJnuo&yV$88d zVzBjF8h`yRmy0E0z3(qhPF9_gX?kY`LN;@nhv{-ui1vQS{|JAH zMC`T|J!ukLOaxTciV*lN-Rt}D7EF_e*|=|AlVg@rr-vC~La6X#V`kFgER0_FS>ZG+ zHt-50Av||kXzX-r%P18RnXxAu6JVRI2qS=c*FAv-Kf?{6JqXQ-{Jfig47~r12ke(y zVYBO$1V!)HtoU;CKhI_!wYs``w5)@rrRC6Ed-?b8-vI)w3Ke+y2^2cOiU`mFs|R5@ z$Z`W{^Ko%;vD{w(U|hL!#nn}|0AY-iZb!xnznyiaYLW<;EA?4-;hG`&&uHqJO#=2J zXh;Ns(BK96u&`^yS%GNC8-7uy;?G!HQ`3OKp4HIcR`PUrcW)be4geLJhD&Z<&cPqJvCKl;x8-yUjGgZY~wvZiwC3&4#2;vtRQhPC@2WWG%PC)&B1qW z*}4@@@%Qsfc=|L`*H0}(pbnA+;ba6}4S|0G2N*npQwPEo#p%}U$#pZk9n-Vd7~0qM z)8YX}g(K0?8EQJ9XCZj#4Zd9;L*N-&x_-kI$9G3w$vqIQC4Q(yVv!LdCWs_p0zR*R zI?R}GTLN^lj6QpCSAZyU#pZ|K81XP@VTThsWlezm0zngCO$x_x=O zx44XA=nDU_l0`Dv#0>5Mf@iJBBt>2ynXSaU13IF0a#%mXgpYc$a!$fGD-ZkNCSGL% z>ZK2;m zp8(YrgPF`sv2v^6V%2|usIIX5y7e75{*@5zMW7dWO#kkNEjo@IeRIK2-LK{g;NyEw zA|sN_gnTeYEzB;!2!R3MXE2wHSg@|NUGFLIA$4@Z*`Pbf%1~1OFp_~Yeu7+u=f7G3 z!@0W`Vlj1ixmGj@Ywt0A=0js+V_Tcv^i|l<3Q&WtVf!%1zrC!ZlVe=AMI>8A`GMwZOJB5L>9JQ6Ez6vJU??zsxx)7f2p(pLYQT&<69JkXC^2On{U| zNVUi`*C2DAshBRnrsiqRfcAms+Yk)Fs@ZjZqEVhTpa8;L0@>#dKJnbCeHua(&S4PO zM$kI3{D*G}spxpKrB(HW14qg>`utFe!n*x^g{zX=n@8vuIr7}2SgAhOS7J%VpC6mb$ zNcK=ek|f2%-p9Oyf}f3=%B%AVfcBJwxUB4DLqpz=+S<*@NlAjo5wO88!n9Y5Ots|L zKvkm*R3AWCiyWxwga&cqVqzxOR;iRS2(hf39DkNeQ#->>8;j!X?Z1tTSf^v#@*S*l zpQNT<2P6n^%OD}~EktO02MM(Rt-;aOR%lTln~_s!KTro5$pGrYx;2Pi?O{N<)i==r zFPBxSv3JFpC#|mC#u?`quY9Do8ftCCc(91k-&>F7j)ciX(d(gtaB!ZRUoq!o@QuRY|coc zB6#0bxP#k|T3rc8vjI2^6&S7=|0EWT}y1IhayMnhC0CD)z;sMFa?_Q+E{~T~x zTP`H0M_J7vbn=)cJ1Z-!-3X|L!d>vw&ZqFX5r0*@givB+=pl(T3;-X$mT<8sJGR=Af=8MpqJI zehdkxu}CWGT!4QwRy0P2PC*WkRo+Z^Jo*7PLWC&H?%=w}^O#*dLp3hc#Fmy8kS1mv z3_9wm6FV_%Re!_1DFH?l`mkFMSIBV18u&!c=~-oih{8?2_nTW^>d_8~}*OVPKtOF79gbej(+LM-_ z4!Z>_PQBJDK=lwm*z z0!j;C^-AonEHuD{7jip`Oe;HOXB>^YeQd|j6{BTi3E3{gpIb@MG3f6c#-fW@6atU_-uB87}M$YJzc;v!wQ}9+g|%6qxpMgP4o;C zTU(uO>k6v7Gw-^IA3X}@cVhulF=zIOqWjU_vfhx&@#CHZkIHEtkCDN+rI9`zuU{Q~ z`{3^P2Kv=g37D6RB2Io=TRdj2I3G}1_B?ETKmWj%x(iYg2xa}*?u7Ig0|!F1Rmh1W z-}5eP=9-(}=DpB<76RZ~tojIG#TRI$H0;p5E& z?T6YfhUlFjCHf-E59B*1i1H0y$>Uk$8l(nw+BqN98xF7P3|=Kj~k4f$2HH$)c> zvSPeo{q&|=mzRTtP7xMhvdgvX@F@fma~7$kygX**)=g)B!18@Om{2Q*&|gx4pO_g1 zJ1@)k^(NUk@M(@dvb+uZ_k3%JsdLCC<}vcfHSNg58*#)bFOOsOT3VFkrxG{+p|scn z^n#IpV=JcivYIk-I^`d?7xFWjO`*y z@tz~!{M4PqNcQ2Y6OyKKfo#B`q%J<2Bhmcgvk!Z|Y4kS}9*K2)e(QWSs_3^K`0#zDa)O1Q{%*=FH%yvURAZ6&wz=O78hgl8|ymx!hAQRjaJTzGFA zpxRKqvl^n`}y7yO4 zF|nQZPiM&ahS)_tg;eUy=6i=JL`FRC5Ai(O4^`y6JmGR~Zz`&QuWRuccJIEKG*7FV zeOz()a4SM|Wpw161FSuWIZ#>sv<~p|u+4qFV=_lQ4=0pZj^;!L?Xc)vxYkm6WVXvj zGiKIdNcCxgCpT{%GcFLR-&Jgg{K5gEWD^#ByppO7K`KNJ6lH%M#V3os7PhUe6*+nI zJqk}C6p0cxBSTH(tA2{{fOQO~kyRduVoKzPk|(v|Yf^wufDp)1=qh;EI(L5n2~^k! z6wM>!*N`Skalh>yDF8vDhc`euHo5LRN{f(ha=^=V@R+8s(xUT=`wN4R-(){K2!S&E z!r=1axK!T4ixDN)BBjMN^wGOzg=u4S>P^h-2Z$c_PkiW9S~@=Q6pFlhCq7us@-A&$ zI+`ZMU~@=qScs4Oi-f1zN^T*T2y2!MEq=(`C=sClj`P%NHiyVCE=(W9!N?XAJ{i(( z@@3_eHyCYSPM=}{GD4|$um;F$prj>|r;ZlVN!M;lj$2T^B+m=N^WZE>Bn_DZ6rYWp zf{c*e+b1lSmlk0ci(y#UA2SJ=p5%a>Wy)WF^<>78dqd=0Ywd4@P#`3Yk=Jh}fC~@H zN)3Alb+DK$>VNuHrlI-@5ib-P8fw4`sjzE{72V{RDs6@#anEFvvYa z^g*$8VCWcAHW35Z9YfJ$?ge^nnWpNOZqs0>LWl}n#ooRsDI3Sz*p)lS??m7fNN{3f zluc2dY2`iWL4lHK?|Y@FFCC2#YD(3guMw|zez)=*G96N@-QxTDeeeUIVDK=#Dv{Eb zObf^|tdCXT6yf0E=}0%N{B6@#L{3;tmKf~D4G%qg{bO63bs{L+HR6TbBG@R6o_?e` zeSgc4_8+i~2)CSLU~sU2Na4-F_Zf4hU!Ay1LQY(V77aSdVfI25iw=4 zy!l?SG`6=3Vf`i_CK~0nEn(;NRS9OIt=ZRfC$q#JMMoD7#JY7~l)T>d1-CNeWQw6y zOWXA7PlvhhgRrLtIOAi`1_Bc84A|=$a$zFi!}xo5WTCTjT8M)ANEZZFcop3T-^asu zUt4p=;QhNMGA-);tl-*WXgAWbzt+zuZL~E9b~*NdJ?vXd)0YM{y<{Cnnmb;)_5Ag+ z`%GmuQ&vHNoMHnkNkaBECL@?$(g~V+9Aurr=V3=e>xJD^EAMd+kB%}r9c}V~$z;?N zlJ`K--u_F1rkU!gnInUPB$JI4;zS@Yif06zlkOOVjWs%)dx9W_3A4zh=su$v&a@?J z=Z?!*N7Us7pc&gRSYEzNliwX-!y+{U(PS-prv`AEJs%co7gpL%!gV9<1yWfrMBDn| zZnBWQCJBxgYb35sarRtj!JQDNCMXB~Wae(vRel;aqoTPc1_r{+XV~l*j?!fXdHHD9 z18&o!3{3p^&kaHcmiLC}umP(3tKhP`dlPJZa5$|9N5ngqUu(^FQ9!ZxRpC~`a_hvw z11|^-WAEeDx|GV6JxJZ%kXe3d;|YgD`r{pSgJoEd74TCd7XFO zUiuPtnXY8p%ZgEHQtx;JM65C&7=9aST{&%T>*m4}oqsXO(5Pq%U=tbrlqDDesi4$q z4b_@0g)a6IPvg})O7TLv&kH+YYU>S7YiWIeb|O+X9c}O4rL)7XmRp%BbSi8Z|0XH0 zmrFBBn;Xw3u`+Q<-@m*yV(4C#ExQi2-?i=sMrF0JiXO6gX5-!E0ej#1F4HGTYE}Ni zFwG-E&m@so60a5>A{F9c+2X(Zy0~JRHWzT2T#rdydZbar&FOY0&9i4yqm3>d+?frl z0{<<}PtTv&Tq0KK;`VI3S94l^_Je{*vBRsRy{ozka+Zuv2ULo+1F-{VXf+DK9OBxb z^Uu)9vvGv&6s%B0ET9A~I5|GM$zA2}|C=V8ZHfr+w_XE5qH8q1#3X7T`_M}wuRp-pH zXFL$ont=5uac(CY2IQ1??7?th{oSy~5FKyNZ^l|B6Tl_V1}f6(w2Az}RY`2crdJiZ zXSr*k5bq##vZ%$S=MH}Bs4i?(3poBLa|ww7ve$4%WW5tHw12)Yf?)dMj)^mwm) zcli#raN%vr?w zh0?ZSujSreGeBGA>x^B>5vX1O;%G=}7v?t{2c}bQvZPhTS2}&aAN8Y!cWzG=-zZ>< zJPMgb+-FlZIdyypkyakD*fq6XxXFmasxL^Y&(S`mjPV3@>%k3u}2 zbt#4FAL(cZy$qXLy2DhANbc2y(d zKUPlXcUBO(mzJ{7W?-N?TjKGc0ot8gfNfNs%R7&M1_K20KcQAAO_h*r*8`H7O{5iO z2U;a1xgV(#gjQg-sTMbawu=^?YixUWv0Z1#e0r=?>By0DeLXG3US94;bmsL!no^cJ zxdGXX4DXb`pW=htJwPheQ%bQTS`*yoooD$k17*5=aqBGZhW%jKpI1dR%4ZR3&jl5LIBYBnV4(oBQlsCl6l$1Qm+I?k}GHhuoGas zP{;#Rf(tH>bbZlM09ztuwE92gYPD$QcX{-24_CTm-X5_mqwl~-Hie{$d$&P>3ox;` zm8+D3HN?+r30ZnWfw4$^3SPO^x3oB)HZ+8CR0T2v14Im=>6-KGq_jVd0Ax=ffnX
GWDo>hHbE&%1P8S8RgMr*GQpdyjJz2#PE7?}C$<@IA$I*9!l-I2m_( z@k`fOb~_OZ~D;G7tLm(?35z3;mG$?Ib6`lRjr!{(DEu*c>f){r99A7N1~ERbSb4)df#gNI$b zSW^qGx69VDl-P+<6Q$1B)7d%@&sCU4d^S|m%x5x~sod6-{qD6X8 z=rrVGrl&q<7=V^oImH>Yxc++OP~} z>y|Cu+&H&ph3U2(UilzVKxxF14=^ePhOMV$`1g0WJ9X+$-330>;2F7@YINzbHuOvZ z{)FlFTAXZhb9FU<|HCU+&afHYhO6(>_Z+SOqrEn~d~0an9}pKNnF}f{*_Jy$t*ZR+ z?%e}-T9790bH<5Q?&Xp;9b9P{FiHOYCkQoC*dm1szpJs9MZWQ831G9qeqhd7SPJ%x zz7jDhwRpnRE~v+F`Ku0SxTvK+NeRVQFmB4#f?J5aTQm&}CT0dd=zi>+)2YSrRvFy@=<1djkJpip9RPWpF|Kj;EbQ6n@vXlW7Q4xl zkyy$#HP#uI{G>(vdc|#g?d@Ntj%2qq{@H>DGo^btRPVzjNvw8iSQsxVpyoN*S5{x4 zV*^bGTbkruG80eyN#ae<%ad^{cTRY3O;BIjXbn3;mfAIr9L6$ZtT-^Hh6}^R?GL4+ zdV0MI^f(MXhYibo)29}vi!JZ*iMS+G*&2&svlU1^#OaCR9$zK5tDS`qL=W*VYSNy% z9j-HfOUcP4Gq<*^b8#ugDMx;Gghl$WwOEPW%We3IM|^A;(-cUzd8;H#F0&y#(Jyos zjTNy6<^pk|5&LpN7UERuv3F5{=nk+4MBi?hl8q>;d?9jDKpJv-W2yu7>K z_)-uUm3aLvy9;{sDzk&8L#jQ~U)D}O0(uFlERGS}2h)(4ft|Y7t#yjKD9erzm^Q1I zWZE;=)VoDzq@YUo>}Fz~L6tRMt?)2)Nd_Lun+zr&k7d7H=r69AEhtc<<0wv_E&1N4 zOUuQYdK>OP^WF``PCz3GgBD7L7jDXu!2$s;FaBuF6cO{3yx=knz#wxG`C zcXi>N3r&3A>korzSivsdr1$hPxuS}|Ufow%zX27?R)sv2_s3z+M<@jM01w0{KrX*Y z`4+tFi;+Tjz4#2PW5{Q^(NmFU*AH<*WH;4XlS+Z|X@d!rlx)2@-{|>WZ}) zm6;4NB3xL9)c6S8rY^*Eu`_yC3D8A@t&WvDG+vSwW7?fUU~x*Fq4mZILA#F+QvALu zPqXzE3x~iA)SJC$LQuaZ6KoWr;NKS$SNANDUZ!{}SMY`?`Le#U&TG1d1qe4!HAkX= zq1qL{3Qhf$*CHbWS$J80oZk5e`UsM$Q>Vu?C{-;PaX-)jRle{I)awkyQ-_;={b*Nims6dvW+9-5U=8d(abA8sOO=cc(Vs8kj`qQ{dq=qOrUm5;Wz z4h2P_mjOjNv^s(5-$o0@rj(?hU4u{Gz{jHxV~Pvry?R+OJ&?yAo1shqbyc>Qu=MS2 ztysYY(b!%)k`xg}bbJ zIcO`DteWh;uO_3!4g_|i2z`os?mHMJvJRjFDVVb0$lvP{3R~=VqHi@jj%q@*4&^9s zROfb`2B!(iG=Lv@bmq2tMBr|9-(K7A?>RuB@f z2ShbkJ!PepW&*Hj3s!qaPWS9~Vr**0xr5g5f~l&pwVoG+k0vv34-HT>odzWY-u}~% z1b)Njj5J+|l8MhHKDyb~hunyy-^vHrbxwOuWyYl}9PJV0@ab#hDVB2S)&vp5h2hO_ z+pM*19LDc;(PI%wTLaJGSAZ|hcO+Kj?)tXe-WI9X!6sa6tf3-pIlSQAN3>ZQZmpy< zUYd>fSXTIz0ZDC5<=F{cFV9`qfGG<}F-nV_UPXyHOIZ_}?n%v?cfOlGtKqq~y>Y z9V?}`j6f4i6$xY>kCeugWmGphlcZBc!?%zM@B9jWEwET7MXP~6v&b}buZCn^U|NH` zCkf9j;SNK4a;mH&E>B`*uzq&(O*_U02diSsK-f&&b%JVGSl=oFv_bJ3TfQ<7s?=^j zpdjScF=K~uKbYQ2@{?fja&jVqnh7Z!g^nH3#OHH!Z-2=YJfvdg0Ulgpe#EtORvz1l z!LfzG*qmTC1J7^AV54{U(EufELS$cg0Gr3{UF2xr3V2s#cce)M18#3j4ZiDG+Ex)a z=%E|xuOsBfNVGL-22;(5XMm6_hQ+rlUJ^tu`-cyq)t;owI329tglw@<9>6~hrdVjm z8m9W1o1Oj3moL9>JtxFzBDj^$7G1Q6;+K~uBm2{&cn6tP&!ml zdtSyLM~|;EclrEk1Mg!1VQcOx9T*wlX4yqf_z&s(18iYv6op-s!KlR34G_T4BqH&5 zzVFqmSCex7INz_};)l!UnfqGtsp(>T>*c?x`?{fZ4M3b2AW~5n4#K#lH2m(p#sj@( z+3H2EV2CaJ>dPk2i2;+?)FGAd;%JLRPf%rJ1gE6+c=3Kn&EdtUY%Uy}xN*+Z$J(vC zvZWQ-^qP~Uy35aHBDSR|U_GyEl(8*4eo!iwACQ4`<--S>ZdDXLlGVY5G3Aa(cFx2x z;a_H*fOdMB7X%TyZ0@2T6<QEGUf~W`hFRz^atFo|3x#*ouv8 zV+xK1v=-_Zw^Pl|#(hQXR#!$G?N7W018GJ%e%}>DzZutkPXwj!=WlwdO@qIoVc|P@ zkHpK@!r$Y|de0zAe$Fbx&u=T*W1Lp=K(zW%u9MBnZOj<9vO+S@$!P zvuLBrL(e2IK5$j=hJCPqpS1-nH}Q#%af@N0cE(q}`R-S*jph~@KA;O%|I85Fu;tv7bbVoI1pz)rWQPAIF8WO?3Mo(S18T+2yR z9jf7k9QjthR$brUrDbFyrcx`8Y% zFFh|2GkdQ5_z0XjqA6--K;ptrx7lmVX4CIO>czx zlDRMzI|S~i3(vWOI7pjm05AKz!;wp}nQA-(a~5}V@bbT0J*<%6`OxEmX?~sX!$OnB z>({SODIKK*0|1Lk)gsW>+HJ5}jpHb%@P(ILv}Ys=>1IEvGv?49=8bz3j|1%o z8z%Hr+~*vSRkUw6Dqs+A9stwr)9}{R+Wd zOedo9U7xG^sb1{j3WCv*N-V~%z=@=La88K4^2oyVBZ?E~JsImc<7V_M*couH@I5#M zRh+>@sR37Opjl}5?&@BWv(!rx^uReabAui*?UGuW?qMqnxpWlMsmv;KLO7641#;KNOD;bGTx zBcnZTjB07k@54quKx29Jr7vJqkc)_2pj9iGzX6nl9vS2~p0Y;xWo<7;HGE~{r+Tn9 z5GXEv%6bAB$jXmDXDP3b5>aATSsx*(4_t6SBMtt<@jeBe@l1Q)x?m$U6*-(0?W3)# zS_$?UNDk1J+;zrPc8fYwOwcIg?{5Gcu@)Glum=>RtqV`B1kF&lmg7GasjUxkQ3L{< z3Vlf6b4rMGrAqA|p9t;)3RObF2AK#~2O&|uHai<3%89~-kn4jOW_{?6937!(7!XAN zC#R8H%KT#(we%4h6?v8B0;v9NyfwMdXj`(`3PQMnN}InVghlQFD&?EtgUBRGVS~!S zb=edDKRk7p3gP?Vy*YH0F!y)14P@G*h6P34G*Cv`8a*LAnG(ueSvIIi&0(Ve^lKD= zQrFORYpV@~7GF;Q&A}Q&F3O7ezgDeBA$9b)g(+YG%A8Z`Cn+OX$7(=K;5@9Y0lA+G zYD%;kfTvuBK3-37?UaJxl?2rl5)270euJD4kU4;p!z;fYrPKvdK)BUWRaF|At>cB2 z@bhYt<^T9P|2NOxx>2sHD@8AjDtytmchg|5IVhkN5r$ z+5`Q|QV0(jae&40aSwKCa?*D`COZ22Fa@7j6{(OLkaym@@AK!+H%pf2781Zl<($j7IGy-cuB^M(e&it?q z>PEJd=H9aFD6xZ@B5RlLU*6rhb0;w|5q>()Ln$laQtl|sVwnRIw`?z&T`4(EWMYP{ zDgMx}v-fZXba=HbZ-^v9>bmbJ76+vs&e>RuA03n){3~Y?`*vZV3?i6E;WB1drY_i9 z@=l|A&DJI+`y84ey57jkBI(c$8UCye7qD^ozsV83Y{>j(gc{qzPevTjr$V2BW7nBF z5v*Th#J{rlP7I_NSDEIoE6i9GB1`*3)Cua`!xG4EocRcWhXw{l28L}Q%0Mrn?9$aU zz5C>SaL@b_St1m~`=Ep(_dwnoG+npVfdKx-@~VVrsA1;fQV)9r4W90^gmR!hN2F3n zzJ@gP2M-=}bQt{0Rq(-oSXDw9(kVsnK6mdvz|){I@@%7e4$D|t%MfNw2hXKCM>!SWwTWC|It z<3^NRjqbmu;y{5Ezg(vjR@(#qT}s^PH2AxHghuc$()PeKqC+*Og%ea{mjyN3{M#I& zrVl?8;HoD4`#{%KGlCgK8Nu4f|49v!S_A&Z25SlMs}>POovs(6t9k3Iwi=QWXhtZq z*<_m1i0)sAON`98D>rIZprs07B0M^^lX6|M`p>Nyke&aNSw(5bKt&}ON@>M_vJ68G zqLu{Ikorcgc4p-G7MQv>;m3{1Z2hYG0QS6zfEd_~$BGWnQjCybHyN3heBfc&lD$P>sfqPn#coQi@ju)g#T{9{){IEUQa9u>?mY|jgAWMq=hSxO+$4| z81atZCg{LNf|{Dw-N@cSTd?7!)&_OEB%`?4E7eFwl6=5>6vBQO4b9EiDMBhd9UNj? zTLPT2QnZZAJ>b$^18u|LbWzdmsU6Uw435su;*FYigCE4;?zW3GD_qytz3uO(m)v^*sJqYtnA(zPIbBjqj zyKr=5 zL`ES0@$I6pCl3ting~B&Z4vs;v!XqM1FCL8kinjPLI7WWyH{n1L?Y5x4gq8lew;&bO zx^vY2eZ?u-zM@wR6aks-!joX)Mp5%xRtFZ?s{zgLi#6Wiko)I?QFIRs3({?68&I7a z*qc=6-2VIb|10LB7uuq&s9mz5fc~N^4!K9vKYxinrJ}kIZK^20y@A%#l;3)hEkZu* zki9@Y@{sw34sydR@^8nkBX=7esAm@CxBuSmzjOCrLZR@&f4Snn66OCH@wu$@inchX zsHyWKP$J*W6WE((#y4@Yrgk{^AC=e100MBs). + +## Recommended deployment + +This is how we recommend deploying TechDocs for potential production use. + +![TechDocs Architecture diagram](../../assets/techdocs/architecture-recommended.drawio.svg) + +TechDocs Backend: Responsible for access control and sending files over to +TechDocs. + +Tokens (if possible): Read and Write tokens. + +## FAQ + +Q: Why don't use Backstage server to serve static files? A: Not scalable. +[Investigate more] + +# Future work + +Instead of storing frontend assets in cloud storage, it can only store texts and +images. And all of frontend rendering is handled by Backstage and TechDocs +reader. From d90bbb8380ed2bc54e1aab840252e214751f36aa Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Thu, 19 Nov 2020 20:32:47 -0500 Subject: [PATCH 087/430] FAQ Updates --- docs/FAQ.md | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/docs/FAQ.md b/docs/FAQ.md index 7b906a341b..ba87891205 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -48,10 +48,9 @@ source candidates. (And we'll probably end up writing some brand new ones, too.) ### What's the roadmap for Backstage? We envision three phases, which you can learn about in -[our project roadmap](https://github.com/backstage/backstage#project-roadmap). -Even though the open source version of Backstage is relatively new compared to -our internal version, we have already begun work on various aspects of all three -phases. Looking at the +[our project roadmap](overview/roadmap/). Even though the open source version of +Backstage is relatively new compared to our internal version, we have already +begun work on various aspects of all three phases. Looking at the [milestones for active issues](https://github.com/backstage/backstage/milestones) will also give you a sense of our progress. @@ -115,8 +114,7 @@ type of content. Plugins all use a common set of platform APIs and reusable UI components. Plugins can fetch data either from the backend or an API exposed through the proxy. -Learn more about -[the different components](https://github.com/backstage/backstage#overview) that +Learn more about [the different components](overview/what-is-backstage) that make up Backstage. ### Do I have to write plugins in TypeScript? @@ -126,17 +124,17 @@ APIs in TypeScript, but aren't forcing it on individual plugins. ### How do I find out if a plugin already exists? -Before you write a plugin, +You can browse and search for all available plugins in the +[Plugin Marketplace](https://backstage.io/plugins). + +If you can't find it in the marketplace, before you write a plugin, [search the plugin issues](https://github.com/backstage/backstage/issues?q=is%3Aissue+label%3Aplugin+) -to see if it already exists or is in the works. If no one's thought of it yet, -great! Open a new issue as +to see if is in the works. If no one's thought of it yet, great! Open a new +issue as [a plugin suggestion](https://github.com/backstage/backstage/issues/new/choose) and describe what your plugin will do. This will help coordinate our contributors' efforts and avoid duplicating existing functionality. -You can browse and search for all available plugins in the -[Plugin Marketplace](https://backstage.io/plugins). - ### Which plugin is used the most at Spotify? By far, our most-used plugin is our TechDocs plugin, which we use for creating @@ -182,6 +180,10 @@ comes to [deployment](https://backstage.io/docs/getting-started/deployment-k8s), the system integrator (typically, the infrastructure team in your organization) maintains Backstage in your own environment. +For more information, see our +[Owners](https://github.com/backstage/backstage/blob/master/OWNERS.md) and +[Governance](https://github.com/backstage/backstage/blob/master/GOVERNANCE.md). + ### Does Spotify provide a managed version of Backstage? No, this is not a service offering. We build the piece of software, and someone @@ -215,14 +217,14 @@ data is shared with. Yes. The core frontend framework could be used for building any large-scale web application where (1) multiple teams are building separate parts of the app, and (2) you want the overall experience to be consistent. That being said, in -[Phase 2](https://github.com/backstage/backstage#project-roadmap) of the project -we will add features that are needed for developer portals and systems for -managing software ecosystems. Our ambition will be to keep Backstage modular. +[Phase 2](overview/roadmap) of the project we will add features that are needed +for developer portals and systems for managing software ecosystems. Our ambition +will be to keep Backstage modular. ### How can I get involved? Jump right in! Come help us fix some of the -[early bugs and first issues](https://github.com/backstage/backstage/labels/good%20first%20issue) +[early bugs and good first issues](https://github.com/backstage/backstage/contribute) or reach [a new milestone](https://github.com/backstage/backstage/milestones). Or write an open source plugin for Backstage, like this [Lighthouse plugin](https://github.com/backstage/backstage/tree/master/plugins/lighthouse). From 3cada418620b7788316c1eb819a6d79981b1d966 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Thu, 19 Nov 2020 20:34:54 -0500 Subject: [PATCH 088/430] Remove trailing slash --- docs/FAQ.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/FAQ.md b/docs/FAQ.md index ba87891205..d28923b9d7 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -48,7 +48,7 @@ source candidates. (And we'll probably end up writing some brand new ones, too.) ### What's the roadmap for Backstage? We envision three phases, which you can learn about in -[our project roadmap](overview/roadmap/). Even though the open source version of +[our project roadmap](overview/roadmap). Even though the open source version of Backstage is relatively new compared to our internal version, we have already begun work on various aspects of all three phases. Looking at the [milestones for active issues](https://github.com/backstage/backstage/milestones) From 90dfe05c82532bdb5c43d97e82803731aae6b291 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Thu, 19 Nov 2020 20:35:48 -0500 Subject: [PATCH 089/430] Remove extra comma --- docs/FAQ.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/FAQ.md b/docs/FAQ.md index d28923b9d7..9abe5ea1e7 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -127,7 +127,7 @@ APIs in TypeScript, but aren't forcing it on individual plugins. You can browse and search for all available plugins in the [Plugin Marketplace](https://backstage.io/plugins). -If you can't find it in the marketplace, before you write a plugin, +If you can't find it in the marketplace, before you write a plugin [search the plugin issues](https://github.com/backstage/backstage/issues?q=is%3Aissue+label%3Aplugin+) to see if is in the works. If no one's thought of it yet, great! Open a new issue as From 7f6ebc4c3238918ee7306eda7cb56f48cf3a712b Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Thu, 19 Nov 2020 20:38:53 -0500 Subject: [PATCH 090/430] Fix links for mkdocs/relative --- docs/FAQ.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/FAQ.md b/docs/FAQ.md index 9abe5ea1e7..9c11b015ea 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -48,8 +48,8 @@ source candidates. (And we'll probably end up writing some brand new ones, too.) ### What's the roadmap for Backstage? We envision three phases, which you can learn about in -[our project roadmap](overview/roadmap). Even though the open source version of -Backstage is relatively new compared to our internal version, we have already +[our project roadmap](overview/roadmap.md). Even though the open source version +of Backstage is relatively new compared to our internal version, we have already begun work on various aspects of all three phases. Looking at the [milestones for active issues](https://github.com/backstage/backstage/milestones) will also give you a sense of our progress. @@ -114,7 +114,7 @@ type of content. Plugins all use a common set of platform APIs and reusable UI components. Plugins can fetch data either from the backend or an API exposed through the proxy. -Learn more about [the different components](overview/what-is-backstage) that +Learn more about [the different components](overview/what-is-backstage.md) that make up Backstage. ### Do I have to write plugins in TypeScript? @@ -217,9 +217,9 @@ data is shared with. Yes. The core frontend framework could be used for building any large-scale web application where (1) multiple teams are building separate parts of the app, and (2) you want the overall experience to be consistent. That being said, in -[Phase 2](overview/roadmap) of the project we will add features that are needed -for developer portals and systems for managing software ecosystems. Our ambition -will be to keep Backstage modular. +[Phase 2](overview/roadmap.md) of the project we will add features that are +needed for developer portals and systems for managing software ecosystems. Our +ambition will be to keep Backstage modular. ### How can I get involved? From 2a71f4babca0ffe7f5d560008322e0bc9f10c420 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Fri, 20 Nov 2020 03:28:19 -0500 Subject: [PATCH 091/430] register-component: Remove link to catalog item on validation popup (#3359) * Remove catalog link on validate popup * Add changeset --- .changeset/empty-kids-look.md | 5 +++++ .../RegisterComponentResultDialog.tsx | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 .changeset/empty-kids-look.md diff --git a/.changeset/empty-kids-look.md b/.changeset/empty-kids-look.md new file mode 100644 index 0000000000..680cd7f25c --- /dev/null +++ b/.changeset/empty-kids-look.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-register-component': patch +--- + +Remove catalog link on validate popup diff --git a/plugins/register-component/src/components/RegisterComponentResultDialog/RegisterComponentResultDialog.tsx b/plugins/register-component/src/components/RegisterComponentResultDialog/RegisterComponentResultDialog.tsx index 2b57050661..c31d1344cb 100644 --- a/plugins/register-component/src/components/RegisterComponentResultDialog/RegisterComponentResultDialog.tsx +++ b/plugins/register-component/src/components/RegisterComponentResultDialog/RegisterComponentResultDialog.tsx @@ -100,7 +100,9 @@ export const RegisterComponentResultDialog = ({ metadata={{ name: entity.metadata.name, type: entity.spec.type, - link: ( + link: dryRun ? ( + entityPath + ) : ( {entityPath} From 9183042be7a4ffb5c10d7ae16819f2b4e295caa3 Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Fri, 20 Nov 2020 09:37:56 +0100 Subject: [PATCH 092/430] Bump backstage-cli dependency --- plugins/catalog-import/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index b53c4ec00d..25d2523dd8 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -40,7 +40,7 @@ "yaml": "^1.10.0" }, "devDependencies": { - "@backstage/cli": "^0.2.0", + "@backstage/cli": "^0.3.0", "@backstage/dev-utils": "^0.1.3", "@backstage/test-utils": "^0.1.2", "@testing-library/jest-dom": "^5.10.1", From b1a4e6a237938d3e8b282ee59082ea00e3b72a71 Mon Sep 17 00:00:00 2001 From: Mateusz Lewtak Date: Fri, 20 Nov 2020 11:27:41 +0100 Subject: [PATCH 093/430] Feat: update Firebase logo --- microsite/data/plugins/firebase-functions.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microsite/data/plugins/firebase-functions.yaml b/microsite/data/plugins/firebase-functions.yaml index 6d399a2350..8c1ea0be0a 100644 --- a/microsite/data/plugins/firebase-functions.yaml +++ b/microsite/data/plugins/firebase-functions.yaml @@ -5,5 +5,5 @@ authorUrl: https://roadie.io/ category: Monitoring description: View Firebase Functions details for your service in Backstage. documentation: https://roadie.io/backstage/plugins/firebase-functions -iconUrl: https://roadie.io/images/logos/github.png +iconUrl: https://roadie.io/images/logos/firebase.png npmPackageName: '@roadiehq/backstage-plugin-firebase-functions' From bc161fba4ebeedac9b39774ee4aacf8fab488de4 Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Fri, 20 Nov 2020 11:33:53 +0100 Subject: [PATCH 094/430] add readme --- plugins/pagerduty/README.md | 92 +++++++++++++++++++++++++++++++++---- 1 file changed, 84 insertions(+), 8 deletions(-) diff --git a/plugins/pagerduty/README.md b/plugins/pagerduty/README.md index 0335cb5a9b..9c5c0b2485 100644 --- a/plugins/pagerduty/README.md +++ b/plugins/pagerduty/README.md @@ -1,13 +1,89 @@ -# pagerduty +# PagerDuty -Welcome to the pagerduty plugin! +## Overview -_This plugin was created through the Backstage CLI_ +This plugin displays PagerDuty information about an entity such as if there are any active incidents and how the escalation policy looks like. -## Getting started +There is also an easy way to trigger an alarm directly to the person who is currently on-call. -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). +This plugin requires that entities are annotated with an [integration key](https://support.pagerduty.com/docs/services-and-integrations#add-integrations-to-an-existing-service). See more further down in this document. -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. +This plugin provides: + +- A list of incidents +- A way to trigger an alarm to the person on-call +- Information details about the person on-call + +## Setup instructions + +Install the plugin: + +```bash +yarn add @backstage/plugin-pagerduty +``` + +Add it to the app in `plugins.ts`: + +```ts +export { plugin as Pagerduty } from '@backstage/plugin-pagerduty'; +``` + +Add it to the `EntityPage.ts`: + +```ts +import { + isPluginApplicableToEntity as isPagerDutyAvailable, + PagerDutyCard, +} from '@backstage/plugin-pagerduty'; +// add to code +{ + isPagerDutyAvailable(entity) && ( + + + + ); +} +``` + +## Client configuration + +The PagerDutyClient can be configured with the appropriate urls: + +In `apis.ts`: + +```ts +createApiFactory({ + api: pagerDutyApiRef, + deps: { configApi: configApiRef }, + factory: ({ configApi }) => + new PagerDutyClientApi({ + api_url: `https://api.pagerduty.com`, + events_url: 'https://events.pagerduty.com/v2', + }), +}), +``` + +## Providing the API Token + +In order for the client to make requests to the [PagerDuty API](https://developer.pagerduty.com/docs/rest-api-v2/rest-api/) it needs an [API Token](https://support.pagerduty.com/docs/generating-api-keys#generating-a-general-access-rest-api-key). + +Then start the backend passing the token as an environment variable: + +```bash +$ PAGERDUTY_TOKEN='Token token=' yarn start +``` + +This will proxy the request by adding `Authorization` header with the provided token. + +## Integration Key + +The information displayed for each entity is based on the [integration key](https://support.pagerduty.com/docs/services-and-integrations#add-integrations-to-an-existing-service). + +### Adding the integration key to the entity annotation + +If you want to use this plugin for an entity, you need to label it with the below annotation: + +```yml +annotations: + pagerduty.com/integration-key: [INTEGRATION_KEY] +``` From 8cc862ebfc4ee0dbcfe08ba86913a6f57be24166 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 20 Nov 2020 10:25:24 +0100 Subject: [PATCH 095/430] cli: enable transformation of workspace packages outside the workspace root --- packages/cli/src/lib/bundler/config.ts | 34 ++++++++++++++++++---- packages/cli/src/lib/bundler/transforms.ts | 21 ++++++++----- 2 files changed, 42 insertions(+), 13 deletions(-) diff --git a/packages/cli/src/lib/bundler/config.ts b/packages/cli/src/lib/bundler/config.ts index 9e150e0ccb..b5a27dd624 100644 --- a/packages/cli/src/lib/bundler/config.ts +++ b/packages/cli/src/lib/bundler/config.ts @@ -70,13 +70,27 @@ async function readBuildInfo() { }; } +async function loadLernaPackages(): Promise< + { name: string; location: string }[] +> { + const LernaProject = require('@lerna/project'); + const project = new LernaProject(cliPaths.targetDir); + return project.getPackages(); +} + export async function createConfig( paths: BundlingPaths, options: BundlingOptions, ): Promise { const { checksEnabled, isDev, frontendConfig } = options; - const { plugins, loaders } = transforms(options); + const packages = await loadLernaPackages(); + const { plugins, loaders } = transforms({ + ...options, + externalTransforms: packages.map(({ name }) => + cliPaths.resolveTargetRoot('node_modules', name), + ), + }); const baseUrl = frontendConfig.getString('app.baseUrl'); const validBaseUrl = new URL(baseUrl); @@ -159,6 +173,10 @@ export async function createConfig( alias: { 'react-dom': '@hot-loader/react-dom', }, + // Enables proper resolution of packages when linking in external packages. + // Without this the packages would depend on dependencies in the node_modules + // of the external packages themselves, leading to module duplication + symlinks: false, }, module: { rules: loaders, @@ -181,17 +199,20 @@ export async function createBackendConfig( ): Promise { const { checksEnabled, isDev } = options; - const { loaders } = transforms(options); - // Find all local monorepo packages and their node_modules, and mark them as external. - const LernaProject = require('@lerna/project'); - const project = new LernaProject(cliPaths.targetDir); - const packages = await project.getPackages(); + const packages = await await loadLernaPackages(); const localPackageNames = packages.map((p: any) => p.name); const moduleDirs = packages.map((p: any) => resolvePath(p.location, 'node_modules'), ); + const { loaders } = transforms({ + ...options, + externalTransforms: packages.map(({ name }) => + cliPaths.resolveTargetRoot('node_modules', name), + ), + }); + return { mode: isDev ? 'development' : 'production', profile: false, @@ -240,6 +261,7 @@ export async function createBackendConfig( alias: { 'react-dom': '@hot-loader/react-dom', }, + symlinks: false, // See frontend config, added here for the same reason }, module: { rules: loaders, diff --git a/packages/cli/src/lib/bundler/transforms.ts b/packages/cli/src/lib/bundler/transforms.ts index d8186dac66..6dc32e6563 100644 --- a/packages/cli/src/lib/bundler/transforms.ts +++ b/packages/cli/src/lib/bundler/transforms.ts @@ -16,7 +16,6 @@ import webpack, { Module, Plugin } from 'webpack'; import MiniCssExtractPlugin from 'mini-css-extract-plugin'; -import { BundlingOptions, BackendBundlingOptions } from './types'; import { svgrTemplate } from '../svgrTemplate'; type Transforms = { @@ -24,17 +23,25 @@ type Transforms = { plugins: Plugin[]; }; -export const transforms = ( - options: BundlingOptions | BackendBundlingOptions, -): Transforms => { - const { isDev } = options; +type TransformOptions = { + isDev: boolean; + // External paths that should be transformed + externalTransforms: string[]; +}; + +export const transforms = (options: TransformOptions): Transforms => { + const { isDev, externalTransforms } = options; const extraTransforms = isDev ? ['react-hot-loader'] : []; + const transformExcludeCondition = { + and: [/node_modules/, { not: externalTransforms }], + }; + const loaders = [ { test: /\.(tsx?)$/, - exclude: /node_modules/, + exclude: transformExcludeCondition, loader: require.resolve('@sucrase/webpack-loader'), options: { transforms: ['typescript', 'jsx', ...extraTransforms], @@ -43,7 +50,7 @@ export const transforms = ( }, { test: /\.(jsx?|mjs)$/, - exclude: /node_modules/, + exclude: transformExcludeCondition, loader: require.resolve('@sucrase/webpack-loader'), options: { transforms: ['jsx', ...extraTransforms], From 206dce0bf827734252ce4580d4afb15adc119f48 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 20 Nov 2020 10:49:41 +0100 Subject: [PATCH 096/430] docs/create-app: document external package linking --- docs/getting-started/create-an-app.md | 36 +++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/docs/getting-started/create-an-app.md b/docs/getting-started/create-an-app.md index c35eded6a5..552b6c3ec2 100644 --- a/docs/getting-started/create-an-app.md +++ b/docs/getting-started/create-an-app.md @@ -38,6 +38,42 @@ app-folder is the name that was provided when prompted. Inside that directory, it will generate all the files and folder structure needed for you to run your app. +### Linking in local Backstage packages + +It can often be useful to try out changes to the packages in the main Backstage +repo within your own app. For example if you want to make modifications to +`@backstage/core` and try them out in your app. + +To link in external packages, add them to your `package.json` and `lerna.json` +workspace paths. These can be either relative or absolute paths with or without +globs. For example: + +```json +"packages": [ + "packages/*", + "plugins/*", + "../backstage/packages/core", // New path added to work on @backstage/core +], +``` + +Then reinstall packages to make yarn set up symlinks: + +```bash +yarn install +``` + +With this in place you can now modify the `@backstage/core` package within the +main repo, and have those changes be reflected and tested in your app. Simply +run your app using `yarn start` as normal. + +Note that for backend packages you need to make sure that linked packages are +not dependencies of any non-linked package. If you for example want to work on +`@backstage/backend-common`, you need to also link in other backend plugins and +packages that depend on `@backstage/backend-common`, or temporarily disable +those plugins in your backend. This is because the transformation of backend +module tree stops whenever a non-local package is encountered, and from that +point node will `require` packages directly for that entire module subtree. + ### Troubleshooting The create app command doesn't always work as expected, this is a collection of From 29a0ccab2e8b713811a465c442825d24aa1c17cd Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 20 Nov 2020 10:52:53 +0100 Subject: [PATCH 097/430] changesets: add cli links change --- .changeset/cli-links.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/cli-links.md diff --git a/.changeset/cli-links.md b/.changeset/cli-links.md new file mode 100644 index 0000000000..0a9779de93 --- /dev/null +++ b/.changeset/cli-links.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +The CLI now detects and transforms linked packages. You can link in external packages by adding them to both the `lerna.json` and `package.json` workspace paths. From 7bf16061005ce6f904ec24c026cbb0a980eec1f3 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 20 Nov 2020 11:25:12 +0100 Subject: [PATCH 098/430] github/vocab: add subtree ._. --- .github/styles/vocab.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt index 9f6c7d386d..301c77ca6e 100644 --- a/.github/styles/vocab.txt +++ b/.github/styles/vocab.txt @@ -175,6 +175,7 @@ Spotify squidfunk src subkey +subtree superfences Superfences superset From d14f125ed1769b2c8fee7c51f572eb304b4af43d Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Fri, 20 Nov 2020 12:03:27 +0100 Subject: [PATCH 099/430] update pagerduty after triggering new alarm --- .../Escalation/EscalationPolicy.tsx | 52 ++++++++--- .../components/Escalation/EscalationUser.tsx | 7 +- .../components/Incident/IncidentListItem.tsx | 43 +++++---- .../src/components/Incident/Incidents.tsx | 67 ++++++++++---- .../src/components/PagerdutyCard.tsx | 88 ++++++++++++++----- .../TriggerButton/TriggerButton.tsx | 71 --------------- .../src/components/TriggerButton/index.ts | 17 ---- .../TriggerDialog/TriggerDialog.tsx | 65 +++++++------- plugins/pagerduty/src/components/types.tsx | 10 ++- plugins/pagerduty/src/index.ts | 7 +- 10 files changed, 229 insertions(+), 198 deletions(-) delete mode 100644 plugins/pagerduty/src/components/TriggerButton/TriggerButton.tsx delete mode 100644 plugins/pagerduty/src/components/TriggerButton/index.ts diff --git a/plugins/pagerduty/src/components/Escalation/EscalationPolicy.tsx b/plugins/pagerduty/src/components/Escalation/EscalationPolicy.tsx index cbce0ce4a3..ccf70abb0b 100644 --- a/plugins/pagerduty/src/components/Escalation/EscalationPolicy.tsx +++ b/plugins/pagerduty/src/components/Escalation/EscalationPolicy.tsx @@ -15,21 +15,47 @@ */ import React from 'react'; -import { List, ListSubheader } from '@material-ui/core'; -import { User } from '../types'; +import { List, ListSubheader, LinearProgress } from '@material-ui/core'; import { EscalationUsersEmptyState } from './EscalationUsersEmptyState'; import { EscalationUser } from './EscalationUser'; +import { useAsync } from 'react-use'; +import { pagerDutyApiRef } from '../../api'; +import { useApi } from '@backstage/core'; +import { Alert } from '@material-ui/lab'; -type EscalationPolicyProps = { - users: User[]; +type Props = { + policyId: string; }; -export const EscalationPolicy = ({ users }: EscalationPolicyProps) => ( - ON CALL}> - {users.length ? ( - users.map((user, index) => ) - ) : ( - - )} - -); +export const EscalationPolicy = ({ policyId }: Props) => { + const api = useApi(pagerDutyApiRef); + + const { value: users, loading, error } = useAsync(async () => { + const oncalls = await api.getOnCallByPolicyId(policyId); + const users = oncalls.map(oncall => oncall.user); + + return users; + }); + + if (error) { + return ( + + Error encountered while fetching information. {error.message} + + ); + } + + if (loading) { + return ; + } + + return users!.length ? ( + ON CALL}> + {users!.map((user, index) => ( + + ))} + + ) : ( + + ); +}; diff --git a/plugins/pagerduty/src/components/Escalation/EscalationUser.tsx b/plugins/pagerduty/src/components/Escalation/EscalationUser.tsx index adc8d4e6d7..642c9ecd96 100644 --- a/plugins/pagerduty/src/components/Escalation/EscalationUser.tsx +++ b/plugins/pagerduty/src/components/Escalation/EscalationUser.tsx @@ -36,8 +36,13 @@ const useStyles = makeStyles({ }, }); -export const EscalationUser = ({ user }: { user: User }) => { +type Props = { + user: User; +}; + +export const EscalationUser = ({ user }: Props) => { const classes = useStyles(); + return ( diff --git a/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx b/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx index 6e22ccbbac..7a97dad281 100644 --- a/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx +++ b/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx @@ -31,10 +31,6 @@ import moment from 'moment'; import { Incident } from '../types'; import PagerdutyIcon from '../PagerDutyIcon'; -type IncidentListItemProps = { - incident: Incident; -}; - const useStyles = makeStyles({ denseListIcon: { marginRight: 0, @@ -51,9 +47,15 @@ const useStyles = makeStyles({ }, }); -export const IncidentListItem = ({ incident }: IncidentListItemProps) => { +type Props = { + incident: Incident; +}; + +export const IncidentListItem = ({ incident }: Props) => { const classes = useStyles(); - const user = incident.assignments[0].assignee; + const user = incident.assignments[0]?.assignee; + const createdAt = moment(incident.created_at).fromNow(); + return ( @@ -68,25 +70,28 @@ export const IncidentListItem = ({ incident }: IncidentListItemProps) => { - {incident.title} - - } + primary={incident.title} + primaryTypographyProps={{ + variant: 'body1', + className: classes.listItemPrimary, + }} secondary={ - - Created {moment(incident.created_at).fromNow()}, assigned to{' '} - {(incident?.assignments[0]?.assignee?.summary && ( - {user.summary} - )) || - 'nobody'} - + + Created {createdAt} and assigned to{' '} + + {user?.summary ?? 'nobody'} + + } /> void; }; -export const Incidents = ({ incidents }: IncidentsProps) => ( - {incidents.length && 'INCIDENTS'}} - > - {incidents.length ? ( - incidents.map((incident, index) => ( +export const Incidents = ({ + serviceId, + shouldRefreshIncidents, + setShouldRefreshIncidents, +}: Props) => { + const api = useApi(pagerDutyApiRef); + + const [{ value: incidents, loading, error }, getIncidents] = useAsyncFn( + async () => { + setShouldRefreshIncidents(false); + return await api.getIncidentsByServiceId(serviceId); + }, + ); + + useEffect(() => { + getIncidents(); + }, [shouldRefreshIncidents, getIncidents]); + + if (error) { + return ( + + Error encountered while fetching information. {error.message} + + ); + } + + if (loading) { + return ; + } + + return incidents?.length ? ( + INCIDENTS}> + {incidents!.map((incident, index) => ( - )) - ) : ( - - )} - -); + ))} + + ) : ( + + ); +}; diff --git a/plugins/pagerduty/src/components/PagerdutyCard.tsx b/plugins/pagerduty/src/components/PagerdutyCard.tsx index 6b6d466850..f24e91a9e0 100644 --- a/plugins/pagerduty/src/components/PagerdutyCard.tsx +++ b/plugins/pagerduty/src/components/PagerdutyCard.tsx @@ -13,10 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; +import React, { useState } from 'react'; import { useApi, EmptyState } from '@backstage/core'; import { Entity } from '@backstage/catalog-model'; import { + Button, Card, CardContent, CardHeader, @@ -26,13 +27,13 @@ import { } from '@material-ui/core'; import { Incidents } from './Incident'; import { EscalationPolicy } from './Escalation'; -import { TriggerButton } from './TriggerButton'; import { useAsync } from 'react-use'; import { Alert } from '@material-ui/lab'; import { pagerDutyApiRef, UnauthorizedError } from '../api'; import { IconLinkVertical } from '@backstage/plugin-catalog'; import PagerDutyIcon from './PagerDutyIcon'; -import ReportProblemIcon from '@material-ui/icons/ReportProblem'; +import AlarmAddIcon from '@material-ui/icons/AlarmAdd'; +import { TriggerDialog } from './TriggerDialog'; const useStyles = makeStyles(theme => ({ links: { @@ -42,6 +43,19 @@ const useStyles = makeStyles(theme => ({ gridAutoColumns: 'min-content', gridGap: theme.spacing(3), }, + triggerAlarm: { + paddingTop: 0, + paddingBottom: 0, + fontSize: '0.7rem', + textTransform: 'uppercase', + fontWeight: 600, + letterSpacing: 1.2, + lineHeight: 1.5, + '&:hover, &:focus, &.focus': { + backgroundColor: 'transparent', + textDecoration: 'none', + }, + }, })); export const PAGERDUTY_INTEGRATION_KEY = 'pagerduty.com/integration-key'; @@ -56,35 +70,45 @@ type Props = { export const PagerDutyCard = ({ entity }: Props) => { const classes = useStyles(); const api = useApi(pagerDutyApiRef); + const [showDialog, setShowDialog] = useState(false); + const [shouldRefreshIncidents, setShouldRefreshIncidents] = useState( + false, + ); + const integrationKey = entity.metadata.annotations![ + PAGERDUTY_INTEGRATION_KEY + ]; - const { value, loading, error } = useAsync(async () => { - const integrationKey = entity.metadata.annotations![ - PAGERDUTY_INTEGRATION_KEY - ]; - + const { value: service, loading, error } = useAsync(async () => { const services = await api.getServiceByIntegrationKey(integrationKey); - const incidents = await api.getIncidentsByServiceId(services[0].id); - const oncalls = await api.getOnCallByPolicyId( - services[0].escalation_policy.id, - ); - const users = oncalls.map(oncall => oncall.user); return { - incidents, - users, id: services[0].id, name: services[0].name, - homepageUrl: services[0].html_url, + url: services[0].html_url, + policyId: services[0].escalation_policy.id, }; }); + const handleDialog = () => { + setShowDialog(!showDialog); + }; + if (error) { if (error instanceof UnauthorizedError) { return ( + Read More + + } /> ); } @@ -102,12 +126,21 @@ export const PagerDutyCard = ({ entity }: Props) => { const pagerdutyLink = { title: 'View in PagerDuty', - href: value!.homepageUrl, + href: service!.url, }; const triggerAlarm = { title: 'Trigger Alarm', - action: , + action: ( + + ), }; return ( @@ -123,7 +156,7 @@ export const PagerDutyCard = ({ entity }: Props) => { /> } + icon={} action={triggerAlarm.action} /> @@ -131,8 +164,19 @@ export const PagerDutyCard = ({ entity }: Props) => { /> - - + + + ); diff --git a/plugins/pagerduty/src/components/TriggerButton/TriggerButton.tsx b/plugins/pagerduty/src/components/TriggerButton/TriggerButton.tsx deleted file mode 100644 index a4f012ac47..0000000000 --- a/plugins/pagerduty/src/components/TriggerButton/TriggerButton.tsx +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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, makeStyles } from '@material-ui/core'; -import { TriggerDialog } from '../TriggerDialog'; -import { Entity } from '@backstage/catalog-model'; -import { PAGERDUTY_INTEGRATION_KEY } from '../PagerDutyCard'; - -const useStyles = makeStyles({ - triggerAlarm: { - paddingTop: 0, - paddingBottom: 0, - fontSize: '0.7rem', - textTransform: 'uppercase', - fontWeight: 600, - letterSpacing: 1.2, - lineHeight: 1.5, - '&:hover, &:focus, &.focus': { - backgroundColor: 'transparent', - textDecoration: 'none', - }, - }, -}); - -type Props = { - entity: Entity; -}; - -export const TriggerButton = ({ entity }: Props) => { - const [showDialog, setShowDialog] = useState(false); - const classes = useStyles(); - - const handleDialog = () => { - setShowDialog(!showDialog); - }; - - return ( - <> - - {showDialog && ( - - )} - - ); -}; diff --git a/plugins/pagerduty/src/components/TriggerButton/index.ts b/plugins/pagerduty/src/components/TriggerButton/index.ts deleted file mode 100644 index 6a58a4f43f..0000000000 --- a/plugins/pagerduty/src/components/TriggerButton/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * 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 { TriggerButton } from './TriggerButton'; diff --git a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx index 4156074460..e6bb6d107d 100644 --- a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx +++ b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx @@ -19,67 +19,61 @@ import { Dialog, DialogTitle, TextField, - makeStyles, DialogActions, Button, DialogContent, Typography, CircularProgress, } from '@material-ui/core'; -import { - Progress, - useApi, - alertApiRef, - identityApiRef, - WarningPanel, -} from '@backstage/core'; +import { useApi, alertApiRef, identityApiRef } from '@backstage/core'; import { useAsyncFn } from 'react-use'; import { pagerDutyApiRef } from '../../api'; -import { Alert, AlertTitle } from '@material-ui/lab'; - -const useStyles = makeStyles({ - warningText: { - border: '1px solid rgba(245, 155, 35, 0.5)', - backgroundColor: 'rgba(245, 155, 35, 0.2)', - padding: '0.5em 1em', - }, -}); +import { Alert } from '@material-ui/lab'; type Props = { name: string; integrationKey: string; - onClose: () => void; + showDialog: boolean; + handleDialog: () => void; + setShouldRefreshIncidents: (shouldRefreshIncidents: boolean) => void; }; -export const TriggerDialog = ({ name, integrationKey, onClose }: Props) => { - const classes = useStyles(); - const [description, setDescription] = useState(''); +export const TriggerDialog = ({ + name, + integrationKey, + showDialog, + handleDialog, + setShouldRefreshIncidents, +}: Props) => { const alertApi = useApi(alertApiRef); const identityApi = useApi(identityApiRef); const userId = identityApi.getUserId(); const api = useApi(pagerDutyApiRef); - - const descriptionChanged = (event: any) => { - setDescription(event.target.value); - }; + const [description, setDescription] = useState(''); const [{ value, loading, error }, handleTriggerAlarm] = useAsyncFn( - async (desc: string) => { - return await api.triggerAlarm( + async (desc: string) => + await api.triggerAlarm( integrationKey, window.location.toString(), desc, userId, - ); - }, + ), ); + const descriptionChanged = ( + event: React.ChangeEvent, + ) => { + setDescription(event.target.value); + }; + useEffect(() => { if (value) { alertApi.post({ message: `Alarm successfully triggered by ${userId}`, }); - onClose(); + setShouldRefreshIncidents(true); + handleDialog(); } if (error) { @@ -88,16 +82,19 @@ export const TriggerDialog = ({ name, integrationKey, onClose }: Props) => { severity: 'error', }); } - }, [value, error, alertApi, onClose, userId]); + }, [value, error, alertApi, handleDialog, userId, setShouldRefreshIncidents]); + + if (!showDialog) { + return null; + } return ( - + This action will trigger an incident 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 @@ -141,7 +138,7 @@ export const TriggerDialog = ({ name, integrationKey, onClose }: Props) => { > Trigger Incident - diff --git a/plugins/pagerduty/src/components/types.tsx b/plugins/pagerduty/src/components/types.tsx index 9c9e29cb01..e968656e65 100644 --- a/plugins/pagerduty/src/components/types.tsx +++ b/plugins/pagerduty/src/components/types.tsx @@ -18,10 +18,10 @@ export type Incident = { id: string; title: string; status: string; - homepageUrl: string; + html_url: string; assignments: [ { - assignee: User; + assignee: Assignee; }, ]; serviceId: string; @@ -43,6 +43,12 @@ export type OnCall = { user: User; }; +export type Assignee = { + id: string; + summary: string; + html_url: string; +}; + export type User = { id: string; summary: string; diff --git a/plugins/pagerduty/src/index.ts b/plugins/pagerduty/src/index.ts index 58dc39932d..1e8e549ac9 100644 --- a/plugins/pagerduty/src/index.ts +++ b/plugins/pagerduty/src/index.ts @@ -17,4 +17,9 @@ export { plugin } from './plugin'; export { isPluginApplicableToEntity, PagerDutyCard, -} from './components/PagerDutyCard'; +} from './components/PagerdutyCard'; +export { + PagerDutyClientApi, + pagerDutyApiRef, + UnauthorizedError, +} from './api/client'; From 5519358df8ec1d4d2e39b24d991ec9f110ef92d9 Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Fri, 20 Nov 2020 12:05:51 +0100 Subject: [PATCH 100/430] update tests --- .../components/Escalation/Escalation.test.tsx | 80 +++++++-- .../components/Incident/Incidents.test.tsx | 151 +++++++++++------ .../src/components/PagerDutyCard.test.tsx | 153 ++++++++++++++++++ .../TriggerDialog/TriggerDialog.test.tsx | 27 ++-- 4 files changed, 335 insertions(+), 76 deletions(-) create mode 100644 plugins/pagerduty/src/components/PagerDutyCard.test.tsx diff --git a/plugins/pagerduty/src/components/Escalation/Escalation.test.tsx b/plugins/pagerduty/src/components/Escalation/Escalation.test.tsx index 4fa65eb999..15ff3278a0 100644 --- a/plugins/pagerduty/src/components/Escalation/Escalation.test.tsx +++ b/plugins/pagerduty/src/components/Escalation/Escalation.test.tsx @@ -14,34 +14,82 @@ * limitations under the License. */ import React from 'react'; -import { render } from '@testing-library/react'; +import { render, waitFor } from '@testing-library/react'; import { EscalationPolicy } from './EscalationPolicy'; import { wrapInTestApp } from '@backstage/test-utils'; import { User } from '../types'; +import { ApiProvider, ApiRegistry } from '@backstage/core'; +import { pagerDutyApiRef } from '../../api'; -const escalations: User[] = [ - { - name: 'person1', - id: 'p1', - summary: 'person1', - email: 'person1@example.com', - html_url: 'http://a.com/id1', - }, -]; +const mockPagerDutyApi = { + getOnCallByPolicyId: () => [], +}; +const apis = ApiRegistry.from([[pagerDutyApiRef, mockPagerDutyApi]]); describe('Escalation', () => { - it('render emptyState', () => { - const { getByText } = render( - wrapInTestApp(), + it('Handles an empty response', async () => { + mockPagerDutyApi.getOnCallByPolicyId = jest + .fn() + .mockImplementationOnce(async () => []); + + const { getByText, queryByTestId } = render( + wrapInTestApp( + + + , + ), ); + await waitFor(() => !queryByTestId('progress')); + expect(getByText('Empty escalation policy')).toBeInTheDocument(); + expect(mockPagerDutyApi.getOnCallByPolicyId).toHaveBeenCalledWith('456'); }); - it('render escalation list', () => { - const { getByText } = render( - wrapInTestApp(), + it('Render a list of users', async () => { + mockPagerDutyApi.getOnCallByPolicyId = jest + .fn() + .mockImplementationOnce(async () => [ + { + user: { + name: 'person1', + id: 'p1', + summary: 'person1', + email: 'person1@example.com', + html_url: 'http://a.com/id1', + } as User, + }, + ]); + + const { getByText, queryByTestId } = render( + wrapInTestApp( + + + , + ), ); + await waitFor(() => !queryByTestId('progress')); + expect(getByText('person1')).toBeInTheDocument(); expect(getByText('person1@example.com')).toBeInTheDocument(); + expect(mockPagerDutyApi.getOnCallByPolicyId).toHaveBeenCalledWith('abc'); + }); + + it('Handles errors', async () => { + mockPagerDutyApi.getOnCallByPolicyId = jest + .fn() + .mockRejectedValueOnce(new Error('Error message')); + + const { getByText, queryByTestId } = render( + wrapInTestApp( + + + , + ), + ); + await waitFor(() => !queryByTestId('progress')); + + expect( + getByText('Error encountered while fetching information. Error message'), + ).toBeInTheDocument(); }); }); diff --git a/plugins/pagerduty/src/components/Incident/Incidents.test.tsx b/plugins/pagerduty/src/components/Incident/Incidents.test.tsx index fbe3783d30..6baa410021 100644 --- a/plugins/pagerduty/src/components/Incident/Incidents.test.tsx +++ b/plugins/pagerduty/src/components/Incident/Incidents.test.tsx @@ -14,65 +14,100 @@ * limitations under the License. */ import React from 'react'; -import { render } from '@testing-library/react'; +import { render, waitFor } from '@testing-library/react'; import { Incidents } from './Incidents'; import { wrapInTestApp } from '@backstage/test-utils'; +import { ApiProvider, ApiRegistry } from '@backstage/core'; +import { pagerDutyApiRef } from '../../api'; import { Incident } from '../types'; -const incidents: Incident[] = [ - { - id: 'id1', - status: 'triggered', - title: 'title1', - created_at: '2020-11-06T00:00:00Z', - assignments: [ - { - assignee: { - name: 'person1', - id: 'p1', - summary: 'person1', - email: 'person1@example.com', - html_url: 'http://a.com/id1', - }, - }, - ], - homepageUrl: 'http://a.com/id1', - serviceId: 'sId1', - }, - { - id: 'id2', - status: 'acknowledged', - title: 'title2', - created_at: '2020-11-07T00:00:00Z', - assignments: [ - { - assignee: { - name: 'person2', - id: 'p2', - summary: 'person2', - email: 'person2@example.com', - html_url: 'http://a.com/id2', - }, - }, - ], - homepageUrl: 'http://a.com/id2', - serviceId: 'sId2', - }, -]; +const mockPagerDutyApi = { + getIncidentsByServiceId: () => [], +}; +const apis = ApiRegistry.from([[pagerDutyApiRef, mockPagerDutyApi]]); describe('Incidents', () => { - it('renders an empty state is there are no incidents', () => { - const { getByText } = render(wrapInTestApp()); + it('Renders an empty state when there are no incidents', async () => { + mockPagerDutyApi.getIncidentsByServiceId = jest + .fn() + .mockImplementationOnce(async () => []); + + const { getByText, queryByTestId } = render( + wrapInTestApp( + + {}} + /> + , + ), + ); + await waitFor(() => !queryByTestId('progress')); expect( getByText('Nice! No incidents are assigned to you!'), ).toBeInTheDocument(); }); - it('renders all incidents', () => { - const { getByText, getByTitle, getAllByTitle, getByLabelText } = render( - wrapInTestApp(), - ); + it('Renders all incidents', async () => { + mockPagerDutyApi.getIncidentsByServiceId = jest.fn().mockImplementationOnce( + async () => + [ + { + id: 'id1', + status: 'triggered', + title: 'title1', + created_at: '2020-11-06T00:00:00Z', + assignments: [ + { + assignee: { + id: 'p1', + summary: 'person1', + html_url: 'http://a.com/id1', + }, + }, + ], + html_url: 'http://a.com/id1', + serviceId: 'sId1', + }, + { + id: 'id2', + status: 'acknowledged', + title: 'title2', + created_at: '2020-11-07T00:00:00Z', + assignments: [ + { + assignee: { + id: 'p2', + summary: 'person2', + html_url: 'http://a.com/id2', + }, + }, + ], + html_url: 'http://a.com/id2', + serviceId: 'sId2', + }, + ] as Incident[], + ); + const { + getByText, + getByTitle, + getAllByTitle, + getByLabelText, + queryByTestId, + } = render( + wrapInTestApp( + + {}} + /> + , + ), + ); + await waitFor(() => !queryByTestId('progress')); expect(getByText('title1')).toBeInTheDocument(); expect(getByText('title2')).toBeInTheDocument(); expect(getByText('person1')).toBeInTheDocument(); @@ -85,4 +120,26 @@ describe('Incidents', () => { // assert links, mailto and hrefs, date calculation expect(getAllByTitle('View in PagerDuty').length).toEqual(2); }); + + it('Handle errors', async () => { + mockPagerDutyApi.getIncidentsByServiceId = jest + .fn() + .mockRejectedValueOnce(new Error('Error occurred')); + + const { getByText, queryByTestId } = render( + wrapInTestApp( + + {}} + /> + , + ), + ); + await waitFor(() => !queryByTestId('progress')); + expect( + getByText('Error encountered while fetching information. Error occurred'), + ).toBeInTheDocument(); + }); }); diff --git a/plugins/pagerduty/src/components/PagerDutyCard.test.tsx b/plugins/pagerduty/src/components/PagerDutyCard.test.tsx new file mode 100644 index 0000000000..bca6f9f940 --- /dev/null +++ b/plugins/pagerduty/src/components/PagerDutyCard.test.tsx @@ -0,0 +1,153 @@ +/* + * 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 { render, waitFor, fireEvent } from '@testing-library/react'; +import { PagerDutyCard } from './PagerdutyCard'; +import { Entity } from '@backstage/catalog-model'; +import { wrapInTestApp } from '@backstage/test-utils'; +import { + alertApiRef, + ApiProvider, + ApiRegistry, + createApiRef, +} from '@backstage/core'; +import { pagerDutyApiRef, UnauthorizedError, PagerDutyClient } from '../api'; +import { Service } from './types'; +import { act } from 'react-dom/test-utils'; + +const mockPagerDutyApi: Partial = { + getServiceByIntegrationKey: async () => [], + getOnCallByPolicyId: async () => [], + getIncidentsByServiceId: async () => [], +}; + +const apis = ApiRegistry.from([ + [pagerDutyApiRef, mockPagerDutyApi], + [ + alertApiRef, + createApiRef({ + id: 'core.alert', + description: 'Used to report alerts and forward them to the app', + }), + ], +]); +const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'pagerduty-test', + annotations: { + 'pagerduty.com/integration-key': 'abc123', + }, + }, +}; + +const service: Service = { + id: 'abc', + name: 'pagerduty-name', + html_url: 'www.example.com', + escalation_policy: { + id: 'def', + user: { + name: 'person1', + id: 'p1', + summary: 'person1', + email: 'person1@example.com', + html_url: 'http://a.com/id1', + }, + }, + integrationKey: 'abcd', +}; + +describe('PageDutyCard', () => { + it('Render pagerduty', async () => { + mockPagerDutyApi.getServiceByIntegrationKey = jest + .fn() + .mockImplementationOnce(async () => [service]); + + const { getByText, queryByTestId } = render( + wrapInTestApp( + + + , + ), + ); + await waitFor(() => !queryByTestId('progress')); + expect(getByText('View in PagerDuty')).toBeInTheDocument(); + expect(getByText('Trigger Alarm')).toBeInTheDocument(); + expect( + getByText('Nice! No incidents are assigned to you!'), + ).toBeInTheDocument(); + expect(getByText('Empty escalation policy')).toBeInTheDocument(); + }); + + it('Handles custom error for missing token', async () => { + mockPagerDutyApi.getServiceByIntegrationKey = jest + .fn() + .mockRejectedValueOnce(new UnauthorizedError()); + + const { getByText, queryByTestId } = render( + wrapInTestApp( + + + , + ), + ); + await waitFor(() => !queryByTestId('progress')); + expect(getByText('Missing or invalid PagerDuty Token')).toBeInTheDocument(); + }); + + it('handles general error', async () => { + mockPagerDutyApi.getServiceByIntegrationKey = jest + .fn() + .mockRejectedValueOnce(new Error('An error occurred')); + const { getByText, queryByTestId } = render( + wrapInTestApp( + + + , + ), + ); + await waitFor(() => !queryByTestId('progress')); + + expect( + getByText( + 'Error encountered while fetching information. An error occurred', + ), + ).toBeInTheDocument(); + }); + it('opens the dialog when trigger button is clicked', async () => { + mockPagerDutyApi.getServiceByIntegrationKey = jest + .fn() + .mockImplementationOnce(async () => [service]); + + const { getByText, queryByTestId, getByTestId, getByRole } = render( + wrapInTestApp( + + + , + ), + ); + await waitFor(() => !queryByTestId('progress')); + expect(getByText('View in PagerDuty')).toBeInTheDocument(); + expect(getByText('Trigger Alarm')).toBeInTheDocument(); + const triggerButton = getByTestId('trigger-button'); + await act(async () => { + fireEvent.click(triggerButton); + }); + expect(getByRole('dialog')).toBeInTheDocument(); + }); +}); diff --git a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx index 7e26985136..440621dcf6 100644 --- a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx +++ b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx @@ -25,9 +25,9 @@ import { identityApiRef, } from '@backstage/core'; import { pagerDutyApiRef } from '../../api'; -import { TriggerButton } from '../TriggerButton'; import { Entity } from '@backstage/catalog-model'; import { act } from 'react-dom/test-utils'; +import { TriggerDialog } from './TriggerDialog'; describe('TriggerDialog', () => { const mockIdentityApi: Partial = { @@ -36,7 +36,7 @@ describe('TriggerDialog', () => { const mockTriggerAlarmFn = jest.fn(); const mockPagerDutyApi = { - triggerPagerDutyAlarm: mockTriggerAlarmFn, + triggerAlarm: mockTriggerAlarmFn, }; const apis = ApiRegistry.from([ @@ -63,24 +63,25 @@ describe('TriggerDialog', () => { }, }; - const { getByText, getByRole, queryByRole, getByTestId } = render( + const { getByText, getByRole, getByTestId } = render( wrapInTestApp( - + {}} + name={entity.metadata.name} + integrationKey="abc123" + setShouldRefreshIncidents={() => {}} + /> , ), ); - expect(queryByRole('dialog')).toBeNull(); - const alarmButton = getByText('Trigger Alarm'); - fireEvent.click(alarmButton); + expect(getByRole('dialog')).toBeInTheDocument(); expect( - getByText( - 'This action will send PagerDuty alarms and notifications to on-call people responsible for', - { - exact: false, - }, - ), + getByText('This action will trigger an incident for ', { + exact: false, + }), ).toBeInTheDocument(); const input = getByTestId('trigger-input'); const description = 'Test Trigger Alarm'; From 6011b7d3e5476bf6c0e582cb132d282c434d872c Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Fri, 20 Nov 2020 12:07:43 +0100 Subject: [PATCH 101/430] add changeset --- .changeset/great-spiders-repair.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/great-spiders-repair.md diff --git a/.changeset/great-spiders-repair.md b/.changeset/great-spiders-repair.md new file mode 100644 index 0000000000..0952612d84 --- /dev/null +++ b/.changeset/great-spiders-repair.md @@ -0,0 +1,7 @@ +--- +'example-app': patch +'@backstage/plugin-catalog': patch +'@backstage/plugin-pagerduty': patch +--- + +Added pagerduty plugin to example app From ecf1e06cdac980f203f7d0b4ace19bddd2815a70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Fri, 20 Nov 2020 12:10:40 +0100 Subject: [PATCH 102/430] docs: flesh out the catalog Extending the model section (#3352) --- .github/styles/vocab.txt | 21 +- .../software-catalog/extending-the-model.md | 315 ++++++++++++++++-- 2 files changed, 299 insertions(+), 37 deletions(-) diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt index dcd10e5266..95c6854db6 100644 --- a/.github/styles/vocab.txt +++ b/.github/styles/vocab.txt @@ -1,9 +1,9 @@ abc +andrewthauer Apdex api Api apis -andrewthauer args asciidoc async @@ -12,20 +12,22 @@ backrub Balachandran benjdlambert Bigtable +Billett Blackbox bool boolean +builtins Chai changeset changesets Changesets -changset chanwit Chanwit cisphobia cissexist classname cli +cloudbuild cncf codeblocks Codecov @@ -68,6 +70,7 @@ github Github gitlab Gitlab +Grafana graphql graphviz Gustavsson @@ -79,6 +82,7 @@ horizontalpodautoscalers Hostname http https +Iain img incentivised inlined @@ -98,8 +102,8 @@ learnings lerna Lerna magiclink -Maintainership mailto +maintainership Malus md microsite @@ -116,6 +120,7 @@ msw namespace namespaces Namespaces +namespacing neuro newrelic nginx @@ -175,6 +180,7 @@ semver Serverless Sinon smartsymobls +Snyk sparklines Spotifiers spotify @@ -215,15 +221,10 @@ Voi Wealthsimple Weaveworks Webpack +www +WWW xyz yaml Zalando Zhou Zolotusky -Billett -cloudbuild -Grafana -Iain -Snyk -www -WWW diff --git a/docs/features/software-catalog/extending-the-model.md b/docs/features/software-catalog/extending-the-model.md index cbacce40c7..d4dca0c059 100644 --- a/docs/features/software-catalog/extending-the-model.md +++ b/docs/features/software-catalog/extending-the-model.md @@ -1,7 +1,7 @@ --- id: extending-the-model title: Extending the model -description: Documentation on Extending the model +description: Documentation on extending the catalog model --- The Backstage catalog [entity data model](descriptor-format.md) is based on the @@ -28,63 +28,324 @@ Backstage comes with a number of catalog concepts out of the box: We'll list different possibilities for extending this below. +## Adding a New apiVersion of an Existing Kind + +Example intents: + +> "I want to evolve this core kind, tweaking the semantics a bit so I will bump +> the apiVersion a step" + +> "This core kind is a decent fit but we want to evolve it at will so we'll move +> it to our own company's apiVersion space and use that instead of +> `backstage.io`." + +The `backstage.io` apiVersion space is reserved for use by the Backstage +maintainers. Please do not change or add versions within that space. + +If you add an [apiVersion](descriptor-format.md#apiversion-and-kind-required) +space of your own, you are effectively branching out from the underlying kind +and making your own. An entity kind is identified by the apiVersion + kind pair, +so even though the resulting entity may be similar to the core one, there will +be no guarantees that plugins will be able to parse or understand its data. See +below about adding a new kind. + ## Adding a New Kind -> TODO: Fill in +Example intents: + +> "The kinds that come with the package are lacking. I want to model this other +> thing that is a poor fit for either of the builtins." + +> "This core kind is a decent fit but we want to evolve it at will so we'll move +> it to our own company's apiVersion space and use that instead of +> `backstage.io`." + +A [kind](descriptor-format.md#apiversion-and-kind-required) is an overarching +family, or an idea if you will, of entities that also share a schema. Backstage +comes with a number of builtin ones that we believe are useful for a large +variety of needs that one may want to model in Backstage. The primary ambition +is to map things to these kinds, but sometimes you may want or need to extend +beyond them. + +Introducing a new apiVersion is basically the same as adding a new kind. Bear in +mind that most plugins will be compiled against the builtin +`@backstage/catalog-model` package and have expectations that kinds align with +that. + +The catalog backend itself, from a storage and API standpoint, does not care +about the kind of entities it stores. Extending with new kinds is mainly a +matter of permitting them to pass validation when building the backend catalog +using the `CatalogBuilder`, and then to make plugins be able to understand the +new kind. + +For the consuming side, it's a different story. Adding a kind has a very large +impact. The very foundation of Backstage is to attach behavior and views and +functionality to entities that we ascribe some meaning to. There will be many +places where code checks `if (kind === 'X')` for some hard coded `X`, and casts +it to a concrete type that it imported from a package such as +`@backstage/catalog-model`. + +If you want to model something that doesn't feel like a fit for either of the +builtin kinds, feel free to reach out to the Backstage maintainers to discuss +how to best proceed. + +If you end up adding that new kind, you must namespace its `apiVersion` +accordingly with a prefix that makes sense, typically based on your organization +name - e.g. `my-company.net/v1`. Also do pick a new `kind` identifier that does +not collide with the builtin kinds. ## Adding a New Type of an Existing Kind -Backstage natively supports tracking of the following component -[`type`](descriptor-format.md)'s: +Example intents: -- Services -- Websites -- Libraries -- Documentation -- Other +> "This is clearly a component, but it's of a type that doesn't quite fit with +> the ones I've seen before." -![](../../assets/software-catalog/bsc-extend.png) +> "We don't call our teams "team", can't we put "flock" as the group type?" -Since these types are likely not the only kind of software you will want to -track in Backstage, it is possible to add your own software types that fit your -organization's data model. Inside Spotify our model has grown significantly over -the years, and now includes ML models, Apps, data pipelines and many more. +Some entity kinds have a `type` field in its spec. This is where an organization +are free to express the variety of entities within a kind. This field is +expected to follow some taxonomy that makes sense for yourself. The chosen value +may affect what operations and views are enabled in Backstage for that entity. +Inside Spotify our model has grown significantly over the years, and our +component types now include ML models, apps, data pipelines and many more. It might be tempting to put software that doesn't fit into any of the existing -types into Other. There are a few reasons why we advise against this; firstly, -we have found that it is preferred to match the conceptual model that your -engineers have when describing your software. Secondly, Backstage helps your -engineers manage their software by integrating the infrastructure tooling -through plugins. Different plugins are used for managing different types of -components. +types into an Other catch-all type. There are a few reasons why we advise +against this; firstly, we have found that it is preferred to match the +conceptual model that your engineers have when describing your software. +Secondly, Backstage helps your engineers manage their software by integrating +the infrastructure tooling through plugins. Different plugins are used for +managing different types of components. For example, the [Lighthouse plugin](https://github.com/backstage/backstage/tree/master/plugins/lighthouse) only makes sense for Websites. The more specific you can be in how you model your software, the easier it is to provide plugins that are contextual. -> TODO: Fill in +Adding a new type takes relatively little effort and carries little risk. Any +type value is accepted by the catalog backend, but plugins may have to be +updated if you want particular behaviors attached to that new type. + +## Changing the Validation Rules for The Entity Envelope or Metadata Fields + +Example intents: + +> "We want to import our old catalog but the default set of allowed characters +> for a metadata.name are too strict." + +> "I want to change the rules for annotations so that I'm allowed to store any +> data in annotation values, not just strings." + +After pieces of raw entity data have been read from a location, they are passed +through a fixed number of so called `Validators`, as part of the entity policy +check step. They ensure that the types and syntax of the base envelope and +metadata make sense - in short, things that aren't entity-kind-specific. Some or +all of these validators can be replaced when building the backend catalog using +the `CatalogBuilder`. + +The risk and impact of this type of extension varies, based on what it is that +you want to do. For example, extending the valid character set for kinds, +namespaces and names can be fairly harmless, with a few notable exceptions - +there is code that expects these to never ever contain a colon or slash, for +example, and introducing URL-unsafe characters risks breaking plugins that +aren't careful about encoding arguments. Supporting non-strings in annotations +may be possible but has not yet been tried out in the real world - there is +likely to be some level of plugin breakage that can be hard to predict. + +Before making this kind of extension, we recommend that you contact the +Backstage maintainers or a support partner to discuss your use case. ## Changing the Validation Rules for Core Entity Fields -> TODO: Fill in +Example intent: + +> "I don't like that the owner is mandatory. I'd like it to be optional." + +After reading and policy-checked entity data from a location, it is sent through +the processor chain looking for processors that implement the +`validateEntityKind` step, to see that the data is of a known kind and abides by +its schema. There is a builtin processor that implements this for all known core +kinds and matches the data against their fixed validation schema. This processor +can be replaced when building the backend catalog using the `CatalogBuilder`, +with a processor of your own that validates the data differently. + +This type of extension is high risk, and may have high impact across the +ecosystem depending on the type of change that is made. It is therefore not +recommended in normal cases. There will be a large number of plugins and +processors - and even the core itself - that make assumptions about the shape of +the data and import the typescript data type from the `@backstage/catalog-model` +package. ## Adding New Fields to the Metadata Object -> TODO: Fill in +Example intent: + +> "Our entities have this auxiliary property that I would like to express for +> several entity kinds and it doesn't really fit as a spec field." + +The metadata object is currently left open for extension. Any unknown fields +found in the metadata will just be stored verbatim in the catalog. However we +want to caution against extending the metadata excessively. Firstly, you run the +risk of colliding with future extensions to the model. Secondly, it is common +that this type of extension lives more comfortably elsewhere - primarily in the +metadata labels or annotations, but sometimes you even may want to make a new +component type or similar instead. + +There are some situations where metadata can be the right place. If you feel +that you have run into such a case and that it would apply to others, do feel +free to contact the Backstage maintainers or a support partner to discuss your +use case. Maybe we can extend the core model to benefit both you and others. ## Adding New Fields to the Spec Object of an Existing Kind -> TODO: Fill in +Example intent: + +> "The builtin Component kind is fine but we want to add an additional field to +> the spec for describing whether it's in prod or staging." + +A kind's schema validation typically doesn't forbid "unknown" fields in an +entity `spec`, and the catalog will happily store whatever is in it. So doing +this will usually work from the catalog's point of view. + +Adding fields like this is subject to the same risks as mentioned about metadata +extensions above. Firstly, you run the risk of colliding with future extensions +to the model. Secondly, it is common that this type of extension lives more +comfortably elsewhere - primarily in the metadata labels or annotations, but +sometimes you even may want to make a new component type or similar instead. + +There are some situations where the spec can be the right place. If you feel +that you have run into such a case and that it would apply to others, do feel +free to contact the Backstage maintainers or a support partner to discuss your +use case. Maybe we can extend the core model to benefit both you and others. ## Adding a New Annotation -> TODO: Fill in +Example intents: + +> "Our custom made build system has the concept of a named pipeline-set, and we +> want to associate individual components with their corresponding pipeline-sets +> so we can show their build status." + +> "We have an alerting system that automatically monitors service health, and +> there's this integration key that binds the service to an alerts pool. We want +> to be able to show the ongoing alerts for our services in Backstage so it'd be +> nice to attach that integration key to the entity somehow." + +Annotations are mainly intended to be consumed by plugins, for feature detection +or linking into external systems. Sometimes they are added by humans, but often +they are automatically generated at ingestion time by processors. There is a set +of [well-known annotations](well-known-annotations.md), but you are free to add +additional ones. This carries no risk or impact to other systems as long as you +abide by the following naming rules. + +- The `backstage.io` annotation prefix is reserved for use by the Backstage + maintainers. Reach out to us if you feel that you would like to make an + addition to that prefix. +- Annotations that pertain to a well known third party system should ideally be + prefixed with a domain, in a way that makes sense to a reader and connects it + clearly to the system (or the maker of the system). For example, you might use + a `pagerduty.com` prefix for pagerduty related annotations, but maybe not + `ldap.com` for LDAP annotations since it's not directly affiliated with or + owned by an LDAP foundation/company/similar. +- Annotations that have no prefix at all, are considered local to your Backstage + instance and can be used freely as such, but you should not make use of them + outside of your organization. For example, if you were to open source a plugin + that generates or consumes annotations, then those annotations must be + properly prefixed with your company domain or a domain that pertains to the + annotation at hand. ## Adding a New Label -> TODO: Fill in +Example intents: + +> "Our process reaping system wants to periodically scrape for components that +> have a certain property." + +> "It'd be nice if our service owners could just tag their components somehow to +> let the CD system know to automatically generate SRV records or not for that +> service." + +Labels are mainly intended to be used for filtering of entities, by external +systems that want to find entities that have some certain property. This is +sometimes used for feature detection / selection. An example could be to add a +label `deployments.my-company.net/register-srv: "true"`. + +At the time of writing this, the use of labels is very limited and we are still +settling together with the community on how to best use them. If you feel that +your use case fits the labels best, we would appreciate if you let the Backstage +maintainers know. + +You are free to add labels. This carries no risk or impact to other systems as +long as you abide by the following naming rules. + +- The `backstage.io` label prefix is reserved for use by the Backstage + maintainers. Reach out to us if you feel that you would like to make an + addition to that prefix. +- Labels that pertain to a well known third party system should ideally be + prefixed with a domain, in a way that makes sense to a reader and connects it + clearly to the system (or the maker of the system). For example, you might use + a `pagerduty.com` prefix for pagerduty related labels, but maybe not + `ldap.com` for LDAP labels since it's not directly affiliated with or owned by + an LDAP foundation/company/similar. +- Labels that have no prefix at all, are considered local to your Backstage + instance and can be used freely as such, but you should not make use of them + outside of your organization. For example, if you were to open source a plugin + that generates or consumes labels, then those labels must be properly prefixed + with your company domain or a domain that pertains to the label at hand. ## Adding a New Relation Type -> TODO: Fill in +Example intents: + +> "We have this concept of service maintainership, separate from ownership, that +> we would like to make relations to individual users for." + +> We feel that we want to explicitly model the team-to-global-department mapping +> as a relation, because it is core to our org setup and we frequently query for +> it. + +Any processor can emit relations for entities as they are being processed, and +new processors can be added when building the backend catalog using the +`CatalogBuilder`. They can emit relations based on the entity data itself, or +based on information gathered from elsewhere. Relations are directed and go from +a source entity to a target entity. They are also tied to the entity that +originated them - the one that was subject to processing when the relation was +emitted. Relations may be dangling (referencing something that does not actually +exist by that name in the catalog), and callers need to be aware of that. + +There is a set of [well-known relations](well-known-relations.md), but you are +free to emit your own as well. You cannot change the fact that they are directed +and have a source and target that have to be an +[entity reference](references.md), but you can invent your own types. You do not +have to make any changes to the catalog backend in order to accept new relation +types. + +At the time of writing this, we do not have any namespacing/prefixing scheme for +relation types. The type is also not validated to contain only some particular +set of characters. Until rules for this are settled, you should stick to using +only letters, dashes and digits, and to avoid collisions with future core +relation types, you may want to prefix the type somehow. For example: +`myCompany-maintainerOf` + `myCompany-maintainedBy`. + +If you have a suggestion for a relation type to be elevated to the core +offering, reach out to the Backstage maintainers or a support partner. + +## Using a Well-Known Relation Type for a New Purpose + +Example intents: + +> "The ownerOf/ownedBy relation types sound like a good fit for expressing how +> users are technical owners of our company specific ServiceAccount kind, and we +> want to reuse those relation types for that." + +At the time of writing, this is uncharted territory. If the documented use of a +relation states that one end of the relation commonly is a User or a Group, for +example, then consumers are likely to have conditional statements on the form +`if (x.kind === 'User') {} else {}`, which get confused when an unexpected kind +appears. + +If you want to extend the use of an established relation type in a way that has +an effect outside of your organization, reach out to the Backstage maintainers +or a support partner to discuss risk/impact. It may even be that one end of the +relation could be considered for addition to the core. From cb27aac51506134c5be4a91eed8837444f154b1e Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Fri, 20 Nov 2020 12:42:10 +0100 Subject: [PATCH 103/430] Bump package versions --- plugins/catalog-import/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index 25d2523dd8..2fbea6a17c 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -22,7 +22,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.0", + "@backstage/core": "^0.3.1", "@backstage/plugin-catalog": "^0.2.0", "@backstage/plugin-catalog-backend": "^0.2.0", "@backstage/theme": "^0.2.1", @@ -41,8 +41,8 @@ }, "devDependencies": { "@backstage/cli": "^0.3.0", - "@backstage/dev-utils": "^0.1.3", - "@backstage/test-utils": "^0.1.2", + "@backstage/dev-utils": "^0.1.4", + "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", From 0c21212400c0a9828c35f8e8c15f5b60163c25be Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Fri, 20 Nov 2020 12:53:12 +0100 Subject: [PATCH 104/430] Add support for reading groups and users from the Microsoft Graph API. (#3293) * Add support for reading groups and users from the Microsoft Graph API. * Limit amount of parallel requests * Add helper for paging in odata collections * Add tests for the microsoft graph reader * Output the correct relations between groups and users --- .changeset/curly-yaks-invite.md | 5 + app-config.yaml | 13 + .../well-known-annotations.md | 18 +- plugins/catalog-backend/package.json | 2 + .../MicrosoftGraphOrgReaderProcessor.ts | 100 +++++ .../src/ingestion/processors/index.ts | 1 + .../processors/microsoftGraph/client.test.ts | 324 +++++++++++++++++ .../processors/microsoftGraph/client.ts | 201 ++++++++++ .../processors/microsoftGraph/config.test.ts | 79 ++++ .../processors/microsoftGraph/config.ts | 58 +++ .../processors/microsoftGraph/constants.ts | 32 ++ .../processors/microsoftGraph/index.ts | 19 + .../processors/microsoftGraph/read.test.ts | 339 +++++++++++++++++ .../processors/microsoftGraph/read.ts | 342 ++++++++++++++++++ .../src/ingestion/processors/util/org.test.ts | 23 +- .../src/ingestion/processors/util/org.ts | 21 +- .../src/service/CatalogBuilder.ts | 2 + plugins/catalog/package.json | 1 + yarn.lock | 23 ++ 19 files changed, 1599 insertions(+), 4 deletions(-) create mode 100644 .changeset/curly-yaks-invite.md create mode 100644 plugins/catalog-backend/src/ingestion/processors/MicrosoftGraphOrgReaderProcessor.ts create mode 100644 plugins/catalog-backend/src/ingestion/processors/microsoftGraph/client.test.ts create mode 100644 plugins/catalog-backend/src/ingestion/processors/microsoftGraph/client.ts create mode 100644 plugins/catalog-backend/src/ingestion/processors/microsoftGraph/config.test.ts create mode 100644 plugins/catalog-backend/src/ingestion/processors/microsoftGraph/config.ts create mode 100644 plugins/catalog-backend/src/ingestion/processors/microsoftGraph/constants.ts create mode 100644 plugins/catalog-backend/src/ingestion/processors/microsoftGraph/index.ts create mode 100644 plugins/catalog-backend/src/ingestion/processors/microsoftGraph/read.test.ts create mode 100644 plugins/catalog-backend/src/ingestion/processors/microsoftGraph/read.ts diff --git a/.changeset/curly-yaks-invite.md b/.changeset/curly-yaks-invite.md new file mode 100644 index 0000000000..b9191a3895 --- /dev/null +++ b/.changeset/curly-yaks-invite.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Add support for reading groups and users from the Microsoft Graph API. diff --git a/app-config.yaml b/app-config.yaml index a331d72d7c..84464a98cf 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -140,6 +140,19 @@ catalog: # dn: ou=access,ou=groups,ou=example,dc=example,dc=net # options: # filter: (&(objectClass=some-group-class)(!(groupType=email))) + microsoftGraphOrg: + ### Example for how to add your Microsoft Graph tenant + #providers: + # - target: https://graph.microsoft.com/v1.0/ + # authority: https://login.microsoftonline.com/ + # tenantId: + # $env: MICROSOFT_GRAPH_TENANT_ID + # clientId: + # $env: MICROSOFT_GRAPH_CLIENT_ID + # clientSecret: + # $env: MICROSOFT_GRAPH_CLIENT_SECRET_TOKEN + # userFilter: accountEnabled eq true and userType eq 'member' + # groupFilter: securityEnabled eq false and mailEnabled eq true and groupTypes/any(c:c+eq+'Unified') locations: # Backstage example components diff --git a/docs/features/software-catalog/well-known-annotations.md b/docs/features/software-catalog/well-known-annotations.md index 4d227ea568..7d16427a3c 100644 --- a/docs/features/software-catalog/well-known-annotations.md +++ b/docs/features/software-catalog/well-known-annotations.md @@ -190,9 +190,25 @@ metadata: ``` The value of these annotations are the corresponding attributes that were found -when ingestion the entity from LDAP. Not all of them may be present, depending +when ingesting the entity from LDAP. Not all of them may be present, depending on what attributes that the server presented at ingestion time. +### graph.microsoft.com/tenant-id, graph.microsoft.com/group-id, graph.microsoft.com/user-id + +```yaml +# Example: +metadata: + annotations: + graph.microsoft.com/tenant-id: 6902611b-ffc1-463f-8af3-4d5285dc057b + graph.microsoft.com/group-id: c57e8ba2-6cc4-1039-9ebc-d5f241a7ca21 + graph.microsoft.com/user-id: 2de244b5-104b-4e8f-a3b8-dce3c31e54b6 +``` + +The value of these annotations are the corresponding attributes that were found +when ingesting the entity from the Microsoft Graph API. Not all of them may be +present, depending on what attributes that the server presented at ingestion +time. + ### sonarqube.org/project-key ```yaml diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 711a91dd8d..12a17d6365 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -20,6 +20,7 @@ "clean": "backstage-cli clean" }, "dependencies": { + "@azure/msal-node": "^1.0.0-alpha.8", "@backstage/backend-common": "^0.3.0", "@backstage/catalog-model": "^0.2.0", "@backstage/config": "^0.1.1", @@ -37,6 +38,7 @@ "lodash": "^4.17.15", "morgan": "^1.10.0", "p-limit": "^3.0.2", + "qs": "^6.9.4", "sqlite3": "^5.0.0", "uuid": "^8.0.0", "winston": "^3.2.1", diff --git a/plugins/catalog-backend/src/ingestion/processors/MicrosoftGraphOrgReaderProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/MicrosoftGraphOrgReaderProcessor.ts new file mode 100644 index 0000000000..3456f2cafa --- /dev/null +++ b/plugins/catalog-backend/src/ingestion/processors/MicrosoftGraphOrgReaderProcessor.ts @@ -0,0 +1,100 @@ +/* + * 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 { LocationSpec } from '@backstage/catalog-model'; +import { Config } from '@backstage/config'; +import { Logger } from 'winston'; +import { + MicrosoftGraphClient, + MicrosoftGraphProviderConfig, + readMicrosoftGraphConfig, + readMicrosoftGraphOrg, +} from './microsoftGraph'; +import * as results from './results'; +import { CatalogProcessor, CatalogProcessorEmit } from './types'; + +/** + * Extracts teams and users out of an LDAP server. + */ +export class MicrosoftGraphOrgReaderProcessor implements CatalogProcessor { + private readonly providers: MicrosoftGraphProviderConfig[]; + private readonly logger: Logger; + + static fromConfig(config: Config, options: { logger: Logger }) { + const c = config.getOptionalConfig('catalog.processors.microsoftGraphOrg'); + return new MicrosoftGraphOrgReaderProcessor({ + ...options, + providers: c ? readMicrosoftGraphConfig(c) : [], + }); + } + + constructor(options: { + providers: MicrosoftGraphProviderConfig[]; + logger: Logger; + }) { + this.providers = options.providers; + this.logger = options.logger; + } + + async readLocation( + location: LocationSpec, + _optional: boolean, + emit: CatalogProcessorEmit, + ): Promise { + if (location.type !== 'microsoft-graph-org') { + return false; + } + + const provider = this.providers.find(p => + location.target.startsWith(p.target), + ); + if (!provider) { + throw new Error( + `There is no Microsoft Graph Org provider that matches ${location.target}. Please add a configuration entry for it under catalog.processors.microsoftGraphOrg.providers.`, + ); + } + + // Read out all of the raw data + const startTimestamp = Date.now(); + this.logger.info('Reading Microsoft Graph users and groups'); + + // We create a client each time as we need one that matches the specific provider + const client = MicrosoftGraphClient.create(provider); + const { users, groups } = await readMicrosoftGraphOrg( + client, + provider.tenantId, + { + userFilter: provider.userFilter, + groupFilter: provider.groupFilter, + }, + ); + + const duration = ((Date.now() - startTimestamp) / 1000).toFixed(1); + this.logger.debug( + `Read ${users.length} users and ${groups.length} groups from Microsoft Graph in ${duration} seconds`, + ); + + // Done! + for (const group of groups) { + emit(results.entity(location, group)); + } + for (const user of users) { + emit(results.entity(location, user)); + } + + return true; + } +} diff --git a/plugins/catalog-backend/src/ingestion/processors/index.ts b/plugins/catalog-backend/src/ingestion/processors/index.ts index ef9c6332cd..118f977125 100644 --- a/plugins/catalog-backend/src/ingestion/processors/index.ts +++ b/plugins/catalog-backend/src/ingestion/processors/index.ts @@ -27,6 +27,7 @@ export { FileReaderProcessor } from './FileReaderProcessor'; export { GithubOrgReaderProcessor } from './GithubOrgReaderProcessor'; export { OwnerRelationProcessor } from './OwnerRelationProcessor'; export { LocationRefProcessor } from './LocationEntityProcessor'; +export { MicrosoftGraphOrgReaderProcessor } from './MicrosoftGraphOrgReaderProcessor'; export { PlaceholderProcessor } from './PlaceholderProcessor'; export type { PlaceholderResolver } from './PlaceholderProcessor'; export { StaticLocationProcessor } from './StaticLocationProcessor'; diff --git a/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/client.test.ts b/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/client.test.ts new file mode 100644 index 0000000000..526db38349 --- /dev/null +++ b/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/client.test.ts @@ -0,0 +1,324 @@ +/* + * 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 * as msal from '@azure/msal-node'; +import { msw } from '@backstage/test-utils'; +import { rest } from 'msw'; +import { setupServer } from 'msw/node'; +import { MicrosoftGraphClient } from './client'; + +describe('MicrosoftGraphClient', () => { + const confidentialClientApplication: jest.Mocked = { + acquireTokenByClientCredential: jest.fn(), + } as any; + let client: MicrosoftGraphClient; + const worker = setupServer(); + + msw.setupDefaultHandlers(worker); + + beforeEach(() => { + confidentialClientApplication.acquireTokenByClientCredential.mockResolvedValue( + { token: 'ACCESS_TOKEN' } as any, + ); + client = new MicrosoftGraphClient( + 'https://example.com', + confidentialClientApplication, + ); + }); + + afterEach(() => { + jest.resetAllMocks(); + worker.resetHandlers(); + }); + + it('should perform raw request', async () => { + worker.use( + rest.get('https://other.example.com/', (_, res, ctx) => + res(ctx.status(200), ctx.json({ value: 'example' })), + ), + ); + + const response = await client.requestRaw('https://other.example.com/'); + + expect(response.status).toBe(200); + expect(await response.json()).toEqual({ value: 'example' }); + expect( + confidentialClientApplication.acquireTokenByClientCredential, + ).toBeCalledTimes(1); + expect( + confidentialClientApplication.acquireTokenByClientCredential, + ).toBeCalledWith({ scopes: ['https://graph.microsoft.com/.default'] }); + }); + + it('should perform simple api request', async () => { + worker.use( + rest.get('https://example.com/users', (_, res, ctx) => + res(ctx.status(200), ctx.json({ value: 'example' })), + ), + ); + + const response = await client.requestApi('users'); + + expect(response.status).toBe(200); + expect(await response.json()).toEqual({ value: 'example' }); + }); + + it('should perform api request with filter, select and expand', async () => { + worker.use( + rest.get('https://example.com/users', (req, res, ctx) => + res(ctx.status(200), ctx.json({ queryString: req.url.search })), + ), + ); + + const response = await client.requestApi('users', { + filter: 'test eq true', + expand: ['children'], + select: ['id', 'children'], + }); + + expect(response.status).toBe(200); + expect(await response.json()).toEqual({ + queryString: + '?$filter=test%20eq%20true&$select=id,children&$expand=children', + }); + }); + + it('should perform collection request for a single page', async () => { + worker.use( + rest.get('https://example.com/users', (_, res, ctx) => + res( + ctx.status(200), + ctx.json({ + value: ['first'], + }), + ), + ), + ); + + const values = await collectAsyncIterable( + client.requestCollection('users'), + ); + + expect(values).toEqual(['first']); + }); + + it('should perform collection request for multiple pages', async () => { + worker.use( + rest.get('https://example.com/users', (_, res, ctx) => + res( + ctx.status(200), + ctx.json({ + value: ['first'], + '@odata.nextLink': 'https://example.com/users2', + }), + ), + ), + ); + worker.use( + rest.get('https://example.com/users2', (_, res, ctx) => + res(ctx.status(200), ctx.json({ value: ['second'] })), + ), + ); + + const values = await collectAsyncIterable( + client.requestCollection('users'), + ); + + expect(values).toEqual(['first', 'second']); + }); + + it('should load user profile', async () => { + worker.use( + rest.get('https://example.com/users/user-id', (_, res, ctx) => + res( + ctx.status(200), + ctx.json({ + surname: 'Example', + }), + ), + ), + ); + + const userProfile = await client.getUserProfile('user-id'); + + expect(userProfile).toEqual({ surname: 'Example' }); + }); + + it('should throw expection if load user profile fails', async () => { + worker.use( + rest.get('https://example.com/users/user-id', (_, res, ctx) => + res(ctx.status(404)), + ), + ); + + await expect(() => client.getUserProfile('user-id')).rejects.toThrowError(); + }); + + it('should load user profile photo with max size of 120', async () => { + worker.use( + rest.get('https://example.com/users/user-id/photos', (_, res, ctx) => + res( + ctx.status(200), + ctx.json({ + value: [ + { + height: 120, + id: 120, + }, + { + height: 500, + id: 500, + }, + ], + }), + ), + ), + ); + worker.use( + rest.get( + 'https://example.com/users/user-id/photos/120/*', + (_, res, ctx) => res(ctx.status(200), ctx.text('911')), + ), + ); + + const photo = await client.getUserPhotoWithSizeLimit('user-id', 120); + + expect(photo).toEqual('data:image/jpeg;base64,OTEx'); + }); + + it('should not fail if user has no profile photo', async () => { + worker.use( + rest.get('https://example.com/users/user-id/photos', (_, res, ctx) => + res(ctx.status(404)), + ), + ); + + const photo = await client.getUserPhotoWithSizeLimit('user-id', 120); + + expect(photo).toBeFalsy(); + }); + + it('should load profile photo', async () => { + worker.use( + rest.get('https://example.com/users/user-id/photo/*', (_, res, ctx) => + res(ctx.status(200), ctx.text('911')), + ), + ); + + const photo = await client.getUserPhoto('user-id'); + + expect(photo).toEqual('data:image/jpeg;base64,OTEx'); + }); + + it('should load profile photo for size 120', async () => { + worker.use( + rest.get( + 'https://example.com/users/user-id/photos/120/*', + (_, res, ctx) => res(ctx.status(200), ctx.text('911')), + ), + ); + + const photo = await client.getUserPhoto('user-id', '120'); + + expect(photo).toEqual('data:image/jpeg;base64,OTEx'); + }); + + it('should load users', async () => { + worker.use( + rest.get('https://example.com/users', (_, res, ctx) => + res( + ctx.status(200), + ctx.json({ + value: [{ surname: 'Example' }], + }), + ), + ), + ); + + const values = await collectAsyncIterable(client.getUsers()); + + expect(values).toEqual([{ surname: 'Example' }]); + }); + + it('should load groups', async () => { + worker.use( + rest.get('https://example.com/groups', (_, res, ctx) => + res( + ctx.status(200), + ctx.json({ + value: [{ displayName: 'Example' }], + }), + ), + ), + ); + + const values = await collectAsyncIterable(client.getGroups()); + + expect(values).toEqual([{ displayName: 'Example' }]); + }); + + it('should load group members', async () => { + worker.use( + rest.get('https://example.com/groups/group-id/members', (_, res, ctx) => + res( + ctx.status(200), + ctx.json({ + value: [ + { '@odata.type': '#microsoft.graph.user' }, + { '@odata.type': '#microsoft.graph.group' }, + ], + }), + ), + ), + ); + + const values = await collectAsyncIterable( + client.getGroupMembers('group-id'), + ); + + expect(values).toEqual([ + { '@odata.type': '#microsoft.graph.user' }, + { '@odata.type': '#microsoft.graph.group' }, + ]); + }); + + it('should load organization', async () => { + worker.use( + rest.get('https://example.com/organization/tentant-id', (_, res, ctx) => + res( + ctx.status(200), + ctx.json({ + displayName: 'Example', + }), + ), + ), + ); + + const organization = await client.getOrganization('tentant-id'); + + expect(organization).toEqual({ displayName: 'Example' }); + }); +}); + +async function collectAsyncIterable( + iterable: AsyncIterable, +): Promise { + const values = []; + for await (const value of iterable) { + values.push(value); + } + return values; +} diff --git a/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/client.ts b/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/client.ts new file mode 100644 index 0000000000..98d4570e91 --- /dev/null +++ b/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/client.ts @@ -0,0 +1,201 @@ +/* + * 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 * as msal from '@azure/msal-node'; +import * as MicrosoftGraph from '@microsoft/microsoft-graph-types'; +import fetch from 'cross-fetch'; +import qs from 'qs'; +import { MicrosoftGraphProviderConfig } from './config'; + +export type ODataQuery = { + filter?: string; + expand?: string[]; + select?: string[]; +}; + +export type GroupMember = + | (MicrosoftGraph.Group & { '@odata.type': '#microsoft.graph.user' }) + | (MicrosoftGraph.User & { '@odata.type': '#microsoft.graph.group' }); + +export class MicrosoftGraphClient { + static create(config: MicrosoftGraphProviderConfig): MicrosoftGraphClient { + const clientConfig: msal.Configuration = { + auth: { + clientId: config.clientId, + clientSecret: config.clientSecret, + authority: `${config.authority}/${config.tenantId}`, + }, + }; + const pca = new msal.ConfidentialClientApplication(clientConfig); + return new MicrosoftGraphClient(config.target, pca); + } + + constructor( + private readonly baseUrl: string, + private readonly pca: msal.ConfidentialClientApplication, + ) {} + + async *requestCollection( + path: string, + query?: ODataQuery, + ): AsyncIterable { + let response = await this.requestApi(path, query); + + for (;;) { + if (response.status !== 200) { + await this.handleError(path, response); + } + + const result = await response.json(); + const elements: T[] = result.value; + + yield* elements; + + // Follow cursor to the next page if one is available + if (!result['@odata.nextLink']) { + return; + } + + response = await this.requestRaw(result['@odata.nextLink']); + } + } + + async requestApi(path: string, query?: ODataQuery): Promise { + const queryString = qs.stringify( + { + $filter: query?.filter, + $select: query?.select?.join(','), + $expand: query?.expand?.join(','), + }, + { + addQueryPrefix: true, + // Microsoft Graph doesn't like an encoded query string + encode: false, + }, + ); + + return await this.requestRaw(`${this.baseUrl}/${path}${queryString}`); + } + + async requestRaw(url: string): Promise { + // Make sure that we always have a valid access token (might be cached) + const token = await this.pca.acquireTokenByClientCredential({ + scopes: ['https://graph.microsoft.com/.default'], + }); + + return await fetch(url, { + headers: { + Authorization: `Bearer ${token.accessToken}`, + }, + }); + } + + async getUserProfile(userId: string): Promise { + const response = await this.requestApi(`users/${userId}`); + + if (response.status !== 200) { + await this.handleError('user profile', response); + } + + return await response.json(); + } + + async getUserPhotoWithSizeLimit( + userId: string, + maxSize: number, + ): Promise { + const response = await this.requestApi(`users/${userId}/photos`); + + if (response.status === 404) { + return undefined; + } else if (response.status !== 200) { + await this.handleError('user photos', response); + } + + const result = await response.json(); + const photos = result.value as MicrosoftGraph.ProfilePhoto[]; + let selectedPhoto: MicrosoftGraph.ProfilePhoto | undefined = undefined; + + // Find the biggest picture that is small than the max size + for (const p of photos) { + if ( + !selectedPhoto || + (p.height! >= selectedPhoto.height! && p.height! <= maxSize) + ) { + selectedPhoto = p; + } + } + + if (!selectedPhoto) { + return undefined; + } + + return await this.getUserPhoto(userId, selectedPhoto.id!); + } + + async getUserPhoto( + userId: string, + sizeId?: string, + ): Promise { + const path = sizeId + ? `users/${userId}/photos/${sizeId}/$value` + : `users/${userId}/photo/$value`; + const response = await this.requestApi(path); + + if (response.status === 404) { + return undefined; + } else if (response.status !== 200) { + await this.handleError('photo', response); + } + + return `data:image/jpeg;base64,${Buffer.from( + await response.arrayBuffer(), + ).toString('base64')}`; + } + + async *getUsers(query?: ODataQuery): AsyncIterable { + yield* this.requestCollection(`users`, query); + } + + async *getGroups(query?: ODataQuery): AsyncIterable { + yield* this.requestCollection(`groups`, query); + } + + async *getGroupMembers(groupId: string): AsyncIterable { + yield* this.requestCollection(`groups/${groupId}/members`); + } + + async getOrganization( + tenantId: string, + ): Promise { + const response = await this.requestApi(`organization/${tenantId}`); + + if (response.status !== 200) { + await this.handleError('organization/${tenantId}', response); + } + + return await response.json(); + } + + private async handleError(path: string, response: Response): Promise { + const result = await response.json(); + const error = result.error as MicrosoftGraph.PublicError; + + throw new Error( + `Error while reading ${path} from Microsoft Graph: ${error.code} - ${error.message}`, + ); + } +} diff --git a/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/config.test.ts b/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/config.test.ts new file mode 100644 index 0000000000..11c63828d4 --- /dev/null +++ b/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/config.test.ts @@ -0,0 +1,79 @@ +/* + * 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 { ConfigReader } from '@backstage/config'; +import { readMicrosoftGraphConfig } from './config'; + +describe('readMicrosoftGraphConfig', () => { + it('applies all of the defaults', () => { + const config = { + providers: [ + { + target: 'target', + tenantId: 'tenantId', + clientId: 'clientId', + clientSecret: 'clientSecret', + }, + ], + }; + const actual = readMicrosoftGraphConfig( + ConfigReader.fromConfigs([{ context: '', data: config }]), + ); + const expected = [ + { + target: 'target', + tenantId: 'tenantId', + clientId: 'clientId', + clientSecret: 'clientSecret', + authority: 'https://login.microsoftonline.com', + userFilter: undefined, + groupFilter: undefined, + }, + ]; + expect(actual).toEqual(expected); + }); + + it('reads all the values', () => { + const config = { + providers: [ + { + target: 'target', + tenantId: 'tenantId', + clientId: 'clientId', + clientSecret: 'clientSecret', + authority: 'https://login.example.com/', + userFilter: 'accountEnabled eq true', + groupFilter: 'securityEnabled eq false', + }, + ], + }; + const actual = readMicrosoftGraphConfig( + ConfigReader.fromConfigs([{ context: '', data: config }]), + ); + const expected = [ + { + target: 'target', + tenantId: 'tenantId', + clientId: 'clientId', + clientSecret: 'clientSecret', + authority: 'https://login.example.com', + userFilter: 'accountEnabled eq true', + groupFilter: 'securityEnabled eq false', + }, + ]; + expect(actual).toEqual(expected); + }); +}); diff --git a/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/config.ts b/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/config.ts new file mode 100644 index 0000000000..c4d09e1372 --- /dev/null +++ b/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/config.ts @@ -0,0 +1,58 @@ +/* + * 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 { Config } from '@backstage/config'; + +export type MicrosoftGraphProviderConfig = { + target: string; + authority: string; + tenantId: string; + clientId: string; + clientSecret: string; + userFilter?: string; + groupFilter?: string; +}; + +export function readMicrosoftGraphConfig( + config: Config, +): MicrosoftGraphProviderConfig[] { + const providers: MicrosoftGraphProviderConfig[] = []; + const providerConfigs = config.getOptionalConfigArray('providers') ?? []; + + for (const providerConfig of providerConfigs) { + const target = providerConfig.getString('target').replace(/\/+$/, ''); + const authority = + providerConfig.getOptionalString('authority')?.replace(/\/+$/, '') || + 'https://login.microsoftonline.com'; + const tenantId = providerConfig.getString('tenantId'); + const clientId = providerConfig.getString('clientId'); + const clientSecret = providerConfig.getString('clientSecret'); + const userFilter = providerConfig.getOptionalString('userFilter'); + const groupFilter = providerConfig.getOptionalString('groupFilter'); + + providers.push({ + target, + authority, + tenantId, + clientId, + clientSecret, + userFilter, + groupFilter, + }); + } + + return providers; +} diff --git a/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/constants.ts b/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/constants.ts new file mode 100644 index 0000000000..6d34d0c159 --- /dev/null +++ b/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/constants.ts @@ -0,0 +1,32 @@ +/* + * 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. + */ + +/** + * The tenant id used by the Microsoft Graph API + */ +export const MICROSOFT_GRAPH_TENANT_ID_ANNOTATION = + 'graph.microsoft.com/tenant-id'; + +/** + * The group id used by the Microsoft Graph API + */ +export const MICROSOFT_GRAPH_GROUP_ID_ANNOTATION = + 'graph.microsoft.com/group-id'; + +/** + * The user id used by the Microsoft Graph API + */ +export const MICROSOFT_GRAPH_USER_ID_ANNOTATION = 'graph.microsoft.com/user-id'; diff --git a/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/index.ts b/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/index.ts new file mode 100644 index 0000000000..1ab567c78c --- /dev/null +++ b/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/index.ts @@ -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. + */ +export { MicrosoftGraphClient } from './client'; +export type { MicrosoftGraphProviderConfig } from './config'; +export { readMicrosoftGraphConfig } from './config'; +export { readMicrosoftGraphOrg } from './read'; diff --git a/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/read.test.ts b/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/read.test.ts new file mode 100644 index 0000000000..3c2f33e922 --- /dev/null +++ b/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/read.test.ts @@ -0,0 +1,339 @@ +/* + * 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 { GroupEntity, UserEntity } from '@backstage/catalog-model'; +import merge from 'lodash/merge'; +import { RecursivePartial } from '../../../util'; +import { GroupMember, MicrosoftGraphClient } from './client'; +import { + normalizeEntityName, + readMicrosoftGraphGroups, + readMicrosoftGraphOrganization, + readMicrosoftGraphUsers, + resolveRelations, +} from './read'; + +function user(data: RecursivePartial): UserEntity { + return merge( + {}, + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'User', + metadata: { name: 'name' }, + spec: { profile: {}, memberOf: [] }, + } as UserEntity, + data, + ); +} + +function group(data: RecursivePartial): GroupEntity { + return merge( + {}, + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + metadata: { + name: 'name', + }, + spec: { + ancestors: [], + children: [], + descendants: [], + type: 'team', + }, + } as GroupEntity, + data, + ); +} + +describe('read microsoft graph', () => { + const client: jest.Mocked = { + getUsers: jest.fn(), + getGroups: jest.fn(), + getGroupMembers: jest.fn(), + getUserPhotoWithSizeLimit: jest.fn(), + getOrganization: jest.fn(), + } as any; + + afterEach(() => jest.resetAllMocks()); + + describe('normalizeEntityName', () => { + it('should normalize name to valid entity name', () => { + expect(normalizeEntityName('User Name')).toBe('user_name'); + }); + + it('should normalize e-mail to valid entity name', () => { + expect(normalizeEntityName('user.name@example.com')).toBe( + 'user.name_example.com', + ); + }); + }); + + describe('readMicrosoftGraphUsers', () => { + it('should read users', async () => { + async function* getExampleUsers() { + yield { + id: 'userid', + displayName: 'User Name', + mail: 'user.name@example.com', + }; + } + + client.getUsers.mockImplementation(getExampleUsers); + client.getUserPhotoWithSizeLimit.mockResolvedValue( + 'data:image/jpeg;base64,...', + ); + + const { users } = await readMicrosoftGraphUsers(client, { + userFilter: 'accountEnabled eq true', + }); + + expect(users).toEqual([ + user({ + metadata: { + annotations: { + 'graph.microsoft.com/user-id': 'userid', + }, + name: 'user.name_example.com', + }, + spec: { + profile: { + displayName: 'User Name', + email: 'user.name@example.com', + picture: 'data:image/jpeg;base64,...', + }, + }, + }), + ]); + + expect(client.getUsers).toBeCalledTimes(1); + expect(client.getUsers).toBeCalledWith({ + filter: 'accountEnabled eq true', + select: ['id', 'displayName', 'mail'], + }); + expect(client.getUserPhotoWithSizeLimit).toBeCalledTimes(1); + expect(client.getUserPhotoWithSizeLimit).toBeCalledWith('userid', 120); + }); + }); + + describe('readMicrosoftGraphOrganization', () => { + it('should read organization', async () => { + client.getOrganization.mockResolvedValue({ + id: 'tenantid', + displayName: 'Organization Name', + }); + + const { rootGroup } = await readMicrosoftGraphOrganization( + client, + 'tenantid', + ); + + expect(rootGroup).toEqual( + group({ + metadata: { + annotations: { + 'graph.microsoft.com/tenant-id': 'tenantid', + }, + name: 'organization_name', + description: 'Organization Name', + }, + spec: { + type: 'root', + }, + }), + ); + + expect(client.getOrganization).toBeCalledTimes(1); + expect(client.getOrganization).toBeCalledWith('tenantid'); + }); + }); + + describe('readMicrosoftGraphGroups', () => { + it('should read groups', async () => { + async function* getExampleGroups() { + yield { + id: 'groupid', + displayName: 'Group Name', + }; + } + + async function* getExampleGroupMembers(): AsyncIterable { + yield { + '@odata.type': '#microsoft.graph.group', + id: 'childgroupid', + }; + yield { + '@odata.type': '#microsoft.graph.user', + id: 'userid', + }; + } + + client.getGroups.mockImplementation(getExampleGroups); + client.getGroupMembers.mockImplementation(getExampleGroupMembers); + client.getOrganization.mockResolvedValue({ + id: 'tenantid', + displayName: 'Organization Name', + }); + + const { + groups, + groupMember, + groupMemberOf, + rootGroup, + } = await readMicrosoftGraphGroups(client, 'tenantid', { + groupFilter: 'securityEnabled eq false', + }); + + const expectedRootGroup = group({ + metadata: { + annotations: { + 'graph.microsoft.com/tenant-id': 'tenantid', + }, + name: 'organization_name', + description: 'Organization Name', + }, + spec: { + type: 'root', + }, + }); + expect(groups).toEqual([ + expectedRootGroup, + group({ + metadata: { + annotations: { + 'graph.microsoft.com/group-id': 'groupid', + }, + name: 'group_name', + description: 'Group Name', + }, + spec: { + type: 'team', + }, + }), + ]); + expect(rootGroup).toEqual(expectedRootGroup); + expect(groupMember.get('groupid')).toEqual(new Set(['childgroupid'])); + expect(groupMemberOf.get('userid')).toEqual(new Set(['groupid'])); + expect(groupMember.get('organization_name')).toEqual(new Set()); + + expect(client.getGroups).toBeCalledTimes(1); + expect(client.getGroups).toBeCalledWith({ + filter: 'securityEnabled eq false', + select: ['id', 'displayName', 'mailNickname'], + }); + expect(client.getGroupMembers).toBeCalledTimes(1); + expect(client.getGroupMembers).toBeCalledWith('groupid'); + }); + }); + + describe('resolveRelations', () => { + it('should resolve relations', async () => { + const rootGroup = group({ + metadata: { + annotations: { + 'graph.microsoft.com/tenant-id': 'tenant-id-root', + }, + name: 'root', + }, + spec: { + type: 'root', + }, + }); + const groupA = group({ + metadata: { + annotations: { + 'graph.microsoft.com/group-id': 'group-id-a', + }, + name: 'a', + }, + }); + const groupB = group({ + metadata: { + annotations: { + 'graph.microsoft.com/group-id': 'group-id-b', + }, + name: 'b', + }, + }); + const groupC = group({ + metadata: { + annotations: { + 'graph.microsoft.com/group-id': 'group-id-c', + }, + name: 'c', + }, + }); + const user1 = user({ + metadata: { + annotations: { + 'graph.microsoft.com/user-id': 'user-id-1', + }, + name: 'user1', + }, + }); + const user2 = user({ + metadata: { + annotations: { + 'graph.microsoft.com/user-id': 'user-id-2', + }, + name: 'user2', + }, + }); + const groups = [rootGroup, groupA, groupB, groupC]; + const users = [user1, user2]; + const groupMember = new Map>(); + groupMember.set('group-id-b', new Set(['group-id-c'])); + const groupMemberOf = new Map>(); + groupMemberOf.set('user-id-1', new Set(['group-id-a'])); + groupMemberOf.set('user-id-2', new Set(['group-id-c'])); + + // We have a root groups + // We have three groups: a, b, c. c is child of b + // we have two users: u1, u2. u1 is member of a, u2 is member of c + resolveRelations(rootGroup, groups, users, groupMember, groupMemberOf); + + expect(rootGroup.spec.parent).toBeUndefined(); + expect(rootGroup.spec.ancestors).toEqual(expect.arrayContaining([])); + expect(rootGroup.spec.children).toEqual( + expect.arrayContaining(['a', 'b']), + ); + expect(rootGroup.spec.descendants).toEqual( + expect.arrayContaining(['a', 'b', 'c']), + ); + + expect(groupA.spec.parent).toEqual('root'); + expect(groupA.spec.ancestors).toEqual(expect.arrayContaining(['root'])); + expect(groupA.spec.children).toEqual(expect.arrayContaining([])); + expect(groupA.spec.descendants).toEqual(expect.arrayContaining([])); + + expect(groupB.spec.parent).toEqual('root'); + expect(groupB.spec.ancestors).toEqual(expect.arrayContaining(['root'])); + expect(groupB.spec.children).toEqual(expect.arrayContaining(['c'])); + expect(groupB.spec.descendants).toEqual(expect.arrayContaining(['c'])); + + expect(groupC.spec.parent).toEqual('b'); + expect(groupC.spec.ancestors).toEqual( + expect.arrayContaining(['root', 'b']), + ); + expect(groupC.spec.children).toEqual(expect.arrayContaining([])); + expect(groupC.spec.descendants).toEqual(expect.arrayContaining([])); + + expect(user1.spec.memberOf).toEqual(expect.arrayContaining(['a'])); + + expect(user2.spec.memberOf).toEqual(expect.arrayContaining(['b', 'c'])); + }); + }); +}); diff --git a/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/read.ts b/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/read.ts new file mode 100644 index 0000000000..6cde2649c4 --- /dev/null +++ b/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/read.ts @@ -0,0 +1,342 @@ +/* + * 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 { GroupEntity, UserEntity } from '@backstage/catalog-model'; +import { buildMemberOf, buildOrgHierarchy } from '../util/org'; +import { MicrosoftGraphClient } from './client'; +import { + MICROSOFT_GRAPH_GROUP_ID_ANNOTATION, + MICROSOFT_GRAPH_TENANT_ID_ANNOTATION, + MICROSOFT_GRAPH_USER_ID_ANNOTATION, +} from './constants'; +import limiterFactory from 'p-limit'; + +export function normalizeEntityName(name: string): string { + return name + .trim() + .toLocaleLowerCase() + .replace(/[^a-zA-Z0-9_\-\.]/g, '_'); +} + +export async function readMicrosoftGraphUsers( + client: MicrosoftGraphClient, + options?: { userFilter?: string }, +): Promise<{ + users: UserEntity[]; // With all relations empty +}> { + const entities: UserEntity[] = []; + const picturePromises: Promise[] = []; + const limiter = limiterFactory(10); + + for await (const user of client.getUsers({ + filter: options?.userFilter, + select: ['id', 'displayName', 'mail'], + })) { + if (!user.id || !user.displayName || !user.mail) { + continue; + } + + const name = normalizeEntityName(user.mail); + const entity: UserEntity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'User', + metadata: { + name, + annotations: { + [MICROSOFT_GRAPH_USER_ID_ANNOTATION]: user.id!, + }, + }, + spec: { + profile: { + displayName: user.displayName!, + email: user.mail!, + + // TODO: Additional fields? + // jobTitle: user.jobTitle || undefined, + // officeLocation: user.officeLocation || undefined, + // mobilePhone: user.mobilePhone || undefined, + }, + memberOf: [], + }, + }; + + // Download the photos in parallel, otherwise it can take quite some time + const loadPhoto = limiter(async () => { + entity.spec.profile!.picture = await client.getUserPhotoWithSizeLimit( + user.id!, + // We are limiting the photo size, as users with full resolution photos + // can make the Backstage API slow + 120, + ); + }); + + picturePromises.push(loadPhoto); + entities.push(entity); + } + + // Wait for all photos to be downloaded + await Promise.all(picturePromises); + + return { users: entities }; +} + +export async function readMicrosoftGraphOrganization( + client: MicrosoftGraphClient, + tenantId: string, +): Promise<{ + rootGroup: GroupEntity; // With all relations empty +}> { + // For now we expect a single root orgranization + const organization = await client.getOrganization(tenantId); + const name = normalizeEntityName(organization.displayName!); + const rootGroup: GroupEntity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + metadata: { + name: name, + description: organization.displayName!, + annotations: { + [MICROSOFT_GRAPH_TENANT_ID_ANNOTATION]: organization.id!, + }, + }, + spec: { + type: 'root', + ancestors: [], + children: [], + descendants: [], + }, + }; + + return { rootGroup }; +} + +export async function readMicrosoftGraphGroups( + client: MicrosoftGraphClient, + tenantId: string, + options?: { groupFilter?: string }, +): Promise<{ + groups: GroupEntity[]; // With all relations empty + rootGroup: GroupEntity | undefined; // With all relations empty + groupMember: Map>; + groupMemberOf: Map>; +}> { + const groups: GroupEntity[] = []; + const groupMember: Map> = new Map(); + const groupMemberOf: Map> = new Map(); + const limiter = limiterFactory(10); + + const { rootGroup } = await readMicrosoftGraphOrganization(client, tenantId); + groupMember.set(rootGroup.metadata.name, new Set()); + groups.push(rootGroup); + + const groupMemberPromises: Promise[] = []; + + for await (const group of client.getGroups({ + filter: options?.groupFilter, + select: ['id', 'displayName', 'mailNickname'], + })) { + if (!group.id || !group.displayName) { + continue; + } + + const name = normalizeEntityName(group.mailNickname || group.displayName); + const entity: GroupEntity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + metadata: { + name: name, + description: group.displayName, + annotations: { + [MICROSOFT_GRAPH_GROUP_ID_ANNOTATION]: group.id, + }, + }, + spec: { + type: 'team', + // TODO: We could include a group email and picture + ancestors: [], + children: [], + descendants: [], + }, + }; + + // Download the members in parallel, otherwise it can take quite some time + const loadGroupMembers = limiter(async () => { + for await (const member of client.getGroupMembers(group.id!)) { + if (!member.id) { + continue; + } + + if (member['@odata.type'] === '#microsoft.graph.user') { + ensureItem(groupMemberOf, member.id, group.id!); + } + + if (member['@odata.type'] === '#microsoft.graph.group') { + ensureItem(groupMember, group.id!, member.id); + } + } + }); + + groupMemberPromises.push(loadGroupMembers); + groups.push(entity); + } + + // Wait for all group members to be loaded + await Promise.all(groupMemberPromises); + + return { + groups, + rootGroup, + groupMember, + groupMemberOf, + }; +} + +export function resolveRelations( + rootGroup: GroupEntity | undefined, + groups: GroupEntity[], + users: UserEntity[], + groupMember: Map>, + groupMemberOf: Map>, +) { + // Build reference lookup tables, we reference them by the id the the graph + const groupMap: Map = new Map(); // by group-id or tenant-id + + for (const group of groups) { + if (group.metadata.annotations![MICROSOFT_GRAPH_GROUP_ID_ANNOTATION]) { + groupMap.set( + group.metadata.annotations![MICROSOFT_GRAPH_GROUP_ID_ANNOTATION], + group, + ); + } + if (group.metadata.annotations![MICROSOFT_GRAPH_TENANT_ID_ANNOTATION]) { + groupMap.set( + group.metadata.annotations![MICROSOFT_GRAPH_TENANT_ID_ANNOTATION], + group, + ); + } + } + + // Resolve all member relationships into the reverse direction + const parentGroups = new Map>(); + + groupMember.forEach((members, groupId) => + members.forEach(m => ensureItem(parentGroups, m, groupId)), + ); + + // Make sure every group (except root) has at least one parent. If the parent is missing, add the root. + if (rootGroup) { + const tenantId = rootGroup.metadata.annotations![ + MICROSOFT_GRAPH_TENANT_ID_ANNOTATION + ]; + + groups.forEach(group => { + const groupId = group.metadata.annotations![ + MICROSOFT_GRAPH_GROUP_ID_ANNOTATION + ]; + + if (!groupId) { + return; + } + + if (retrieveItems(parentGroups, groupId).size === 0) { + ensureItem(parentGroups, groupId, tenantId); + ensureItem(groupMember, tenantId, groupId); + } + }); + } + + groups.forEach(group => { + const id = + group.metadata.annotations![MICROSOFT_GRAPH_GROUP_ID_ANNOTATION] ?? + group.metadata.annotations![MICROSOFT_GRAPH_TENANT_ID_ANNOTATION]; + + retrieveItems(groupMember, id).forEach(m => { + const childGroup = groupMap.get(m); + if (childGroup) { + group.spec.children.push(childGroup.metadata.name); + } + }); + + retrieveItems(parentGroups, id).forEach(p => { + const parentGroup = groupMap.get(p); + if (parentGroup) { + // TODO: Only having a single parent group might not match every companies model, but fine for now. + group.spec.parent = parentGroup.metadata.name; + } + }); + }); + + // Make sure that all groups have proper ancestors and descendants + buildOrgHierarchy(groups); + + // Set relations for all users + users.forEach(user => { + const id = user.metadata.annotations![MICROSOFT_GRAPH_USER_ID_ANNOTATION]; + + retrieveItems(groupMemberOf, id).forEach(p => { + const parentGroup = groupMap.get(p); + if (parentGroup) { + user.spec.memberOf.push(parentGroup.metadata.name); + } + }); + }); + + // Make sure all transitive memberships are available + buildMemberOf(groups, users); +} + +export async function readMicrosoftGraphOrg( + client: MicrosoftGraphClient, + tenantId: string, + options?: { userFilter?: string; groupFilter?: string }, +): Promise<{ users: UserEntity[]; groups: GroupEntity[] }> { + const { users } = await readMicrosoftGraphUsers(client, { + userFilter: options?.userFilter, + }); + const { + groups, + rootGroup, + groupMember, + groupMemberOf, + } = await readMicrosoftGraphGroups(client, tenantId, { + groupFilter: options?.groupFilter, + }); + + resolveRelations(rootGroup, groups, users, groupMember, groupMemberOf); + users.sort((a, b) => a.metadata.name.localeCompare(b.metadata.name)); + groups.sort((a, b) => a.metadata.name.localeCompare(b.metadata.name)); + + return { users, groups }; +} + +function ensureItem( + target: Map>, + key: string, + value: string, +) { + let set = target.get(key); + if (!set) { + set = new Set(); + target.set(key, set); + } + set!.add(value); +} + +function retrieveItems( + target: Map>, + key: string, +): Set { + return target.get(key) ?? new Set(); +} diff --git a/plugins/catalog-backend/src/ingestion/processors/util/org.test.ts b/plugins/catalog-backend/src/ingestion/processors/util/org.test.ts index e9dddc8226..f7afd63101 100644 --- a/plugins/catalog-backend/src/ingestion/processors/util/org.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/util/org.test.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import { GroupEntity } from '@backstage/catalog-model'; -import { buildOrgHierarchy } from './org'; +import { GroupEntity, UserEntity } from '@backstage/catalog-model'; +import { buildMemberOf, buildOrgHierarchy } from './org'; function g( name: string, @@ -67,3 +67,22 @@ describe('buildOrgHierarchy', () => { expect(d.spec.ancestors).toEqual(expect.arrayContaining(['a'])); }); }); + +describe('buildMemberOf', () => { + it('fills indirect member of groups', () => { + const a = g('a', undefined, []); + const b = g('b', 'a', []); + const c = g('c', 'b', []); + const u: UserEntity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'User', + metadata: { name }, + spec: { profile: {}, memberOf: ['c'] }, + }; + + const groups = [a, b, c]; + buildOrgHierarchy(groups); + buildMemberOf(groups, [u]); + expect(u.spec.memberOf).toEqual(expect.arrayContaining(['a', 'b', 'c'])); + }); +}); diff --git a/plugins/catalog-backend/src/ingestion/processors/util/org.ts b/plugins/catalog-backend/src/ingestion/processors/util/org.ts index a280a265a8..b033fe99d7 100644 --- a/plugins/catalog-backend/src/ingestion/processors/util/org.ts +++ b/plugins/catalog-backend/src/ingestion/processors/util/org.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { GroupEntity } from '@backstage/catalog-model'; +import { GroupEntity, UserEntity } from '@backstage/catalog-model'; export function buildOrgHierarchy(groups: GroupEntity[]) { const groupsByName = new Map(groups.map(g => [g.metadata.name, g])); @@ -93,3 +93,22 @@ export function buildOrgHierarchy(groups: GroupEntity[]) { visitAncestors(group); } } + +// Ensure that users have their transitive group memberships. Requires that +// the groups were previously processed with buildOrgHierarchy() +export function buildMemberOf(groups: GroupEntity[], users: UserEntity[]) { + const groupsByName = new Map(groups.map(g => [g.metadata.name, g])); + + users.forEach(user => { + const transitiveMemberOf = new Set([...user.spec.memberOf]); + + user.spec.memberOf.forEach(groupName => { + const group = groupsByName.get(groupName); + + if (group) { + group.spec.ancestors.forEach(g => transitiveMemberOf.add(g)); + } + }); + user.spec.memberOf = [...transitiveMemberOf]; + }); +} diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts index b041dac5ab..3f95dcd0d3 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.ts @@ -46,6 +46,7 @@ import { LocationReaders, LocationRefProcessor, OwnerRelationProcessor, + MicrosoftGraphOrgReaderProcessor, PlaceholderProcessor, PlaceholderResolver, StaticLocationProcessor, @@ -278,6 +279,7 @@ export class CatalogBuilder { new FileReaderProcessor(), GithubOrgReaderProcessor.fromConfig(config, { logger }), LdapOrgReaderProcessor.fromConfig(config, { logger }), + MicrosoftGraphOrgReaderProcessor.fromConfig(config, { logger }), new UrlReaderProcessor({ reader, logger }), new CodeOwnersProcessor({ reader }), new LocationRefProcessor(), diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 9ee5d68270..2c65096fd6 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -46,6 +46,7 @@ "@backstage/cli": "^0.3.0", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", + "@microsoft/microsoft-graph-types": "^1.25.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/react-hooks": "^3.3.0", diff --git a/yarn.lock b/yarn.lock index de6ec42c7c..58d9c546ee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -87,6 +87,24 @@ resolved "https://registry.npmjs.org/@asyncapi/specs/-/specs-2.7.5.tgz#3a516d198fc41a1103695bd889fdd4fbbebe7f5d" integrity sha512-T1Ham9sqZKCtSowXRPaBCRy2oz3KHglqqrKiaO7lEudpP6lwH5SwXaq4qliyKzWaqd22srJHE4szdsorbFZKlw== +"@azure/msal-common@^1.6.2": + version "1.6.2" + resolved "https://registry.npmjs.org/@azure/msal-common/-/msal-common-1.6.2.tgz#91f3732866d727e20f1e142e6e88a981268fbff2" + integrity sha512-GShzp1q7Ld8SwYiDEjQZ9PmFOY4x+2stE86maiguylE9/d/c2muqKjc8aepmEqyjbV7o/omDvEf2Sr9QcIqkSA== + dependencies: + debug "^4.1.1" + +"@azure/msal-node@^1.0.0-alpha.8": + version "1.0.0-alpha.12" + resolved "https://registry.npmjs.org/@azure/msal-node/-/msal-node-1.0.0-alpha.12.tgz#09d8d52f5cea90b133c3d48fe4ec477693040c91" + integrity sha512-uGLOJRWiEhfJIrTv/lwdm4RxQFm++00h83zNgDn0O3NkXlzAoCCq9QFYW84PjMR/Q2PUvVy7uW+6yKL/Nq3gBA== + dependencies: + "@azure/msal-common" "^1.6.2" + axios "^0.19.2" + debug "^4.1.1" + jsonwebtoken "^8.5.1" + uuid "^8.3.0" + "@babel/code-frame@7.0.0": version "7.0.0" resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz#06e2ab19bdb535385559aabb5ba59729482800f8" @@ -3372,6 +3390,11 @@ resolved "https://registry.npmjs.org/@mdx-js/react/-/react-1.5.9.tgz#31873ab097fbe58c61c7585fc0be64e83182b6df" integrity sha512-rengdUSedIdIQbXPSeafItCacTYocARAjUA51b6R1KNHmz+59efz7UmyTKr73viJQZ98ouu7iRGmOTtjRrbbWA== +"@microsoft/microsoft-graph-types@^1.25.0": + version "1.25.0" + resolved "https://registry.npmjs.org/@microsoft/microsoft-graph-types/-/microsoft-graph-types-1.25.0.tgz#1f543ebc029a115dd1d48a1ae99d7ddd5ee9af57" + integrity sha512-RsuA+ROaU3voWzG9TVBkRKxmLatteRGduFDi5p0k3FUHho49rm9SvrA7DUyYbSXLy2xXRx9AnjKM9klYBeKEiQ== + "@mrmlnc/readdir-enhanced@^2.2.1": version "2.2.1" resolved "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde" From c6cf38c6a4297e6a4a3108a0308a98d54726f0f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Frinnstr=C3=B6m?= Date: Fri, 13 Nov 2020 19:26:57 +0100 Subject: [PATCH 105/430] Rename ArchiveResponse to TarArchiveResponse --- .../reading/tree/ReadTreeResponseFactory.ts | 4 ++-- ...onse.test.ts => TarArchiveResponse.test.ts} | 18 +++++++++--------- ...rchiveResponse.ts => TarArchiveResponse.ts} | 4 ++-- 3 files changed, 13 insertions(+), 13 deletions(-) rename packages/backend-common/src/reading/tree/{ArchiveResponse.test.ts => TarArchiveResponse.test.ts} (86%) rename packages/backend-common/src/reading/tree/{ArchiveResponse.ts => TarArchiveResponse.ts} (96%) diff --git a/packages/backend-common/src/reading/tree/ReadTreeResponseFactory.ts b/packages/backend-common/src/reading/tree/ReadTreeResponseFactory.ts index a134d185f3..90d74e1f20 100644 --- a/packages/backend-common/src/reading/tree/ReadTreeResponseFactory.ts +++ b/packages/backend-common/src/reading/tree/ReadTreeResponseFactory.ts @@ -18,7 +18,7 @@ import os from 'os'; import { Readable } from 'stream'; import { Config } from '@backstage/config'; import { ReadTreeResponse } from '../types'; -import { ArchiveResponse } from './ArchiveResponse'; +import { TarArchiveResponse } from './TarArchiveResponse'; type FromArchiveOptions = { // A binary stream of a tar archive. @@ -40,7 +40,7 @@ export class ReadTreeResponseFactory { constructor(private readonly workDir: string) {} async fromArchive(options: FromArchiveOptions): Promise { - return new ArchiveResponse( + return new TarArchiveResponse( options.stream, options.path ?? '', this.workDir, diff --git a/packages/backend-common/src/reading/tree/ArchiveResponse.test.ts b/packages/backend-common/src/reading/tree/TarArchiveResponse.test.ts similarity index 86% rename from packages/backend-common/src/reading/tree/ArchiveResponse.test.ts rename to packages/backend-common/src/reading/tree/TarArchiveResponse.test.ts index fd351863b2..3589a8d333 100644 --- a/packages/backend-common/src/reading/tree/ArchiveResponse.test.ts +++ b/packages/backend-common/src/reading/tree/TarArchiveResponse.test.ts @@ -17,13 +17,13 @@ import fs from 'fs-extra'; import mockFs from 'mock-fs'; import { resolve as resolvePath } from 'path'; -import { ArchiveResponse } from './ArchiveResponse'; +import { TarArchiveResponse } from './TarArchiveResponse'; const archiveData = fs.readFileSync( resolvePath(__filename, '../../__fixtures__/repo.tar.gz'), ); -describe('ArchiveResponse', () => { +describe('TarArchiveResponse', () => { beforeEach(() => { mockFs({ '/test-archive.tar.gz': archiveData, @@ -38,7 +38,7 @@ describe('ArchiveResponse', () => { it('should read files', async () => { const stream = fs.createReadStream('/test-archive.tar.gz'); - const res = new ArchiveResponse(stream, 'mock-repo/', '/tmp'); + const res = new TarArchiveResponse(stream, 'mock-repo/', '/tmp'); const files = await res.files(); expect(files).toEqual([ @@ -61,7 +61,7 @@ describe('ArchiveResponse', () => { it('should read files with filter', async () => { const stream = fs.createReadStream('/test-archive.tar.gz'); - const res = new ArchiveResponse(stream, 'mock-repo/', '/tmp', path => + const res = new TarArchiveResponse(stream, 'mock-repo/', '/tmp', path => path.endsWith('.yml'), ); const files = await res.files(); @@ -79,14 +79,14 @@ describe('ArchiveResponse', () => { it('should read as archive and files', async () => { const stream = fs.createReadStream('/test-archive.tar.gz'); - const res = new ArchiveResponse(stream, 'mock-repo/', '/tmp'); + const res = new TarArchiveResponse(stream, 'mock-repo/', '/tmp'); const buffer = await res.archive(); await expect(res.archive()).rejects.toThrow( 'Response has already been read', ); - const res2 = new ArchiveResponse(buffer, '', '/tmp'); + const res2 = new TarArchiveResponse(buffer, '', '/tmp'); const files = await res2.files(); expect(files).toEqual([ @@ -109,7 +109,7 @@ describe('ArchiveResponse', () => { it('should extract entire archive into directory', async () => { const stream = fs.createReadStream('/test-archive.tar.gz'); - const res = new ArchiveResponse(stream, '', '/tmp'); + const res = new TarArchiveResponse(stream, '', '/tmp'); const dir = await res.dir(); await expect( @@ -123,7 +123,7 @@ describe('ArchiveResponse', () => { it('should extract archive into directory with a subpath', async () => { const stream = fs.createReadStream('/test-archive.tar.gz'); - const res = new ArchiveResponse(stream, 'mock-repo/docs/', '/tmp'); + const res = new TarArchiveResponse(stream, 'mock-repo/docs/', '/tmp'); const dir = await res.dir(); expect(dir).toMatch(/^\/tmp\/.*$/); @@ -135,7 +135,7 @@ describe('ArchiveResponse', () => { it('should extract archive into directory with a subpath and filter', async () => { const stream = fs.createReadStream('/test-archive.tar.gz'); - const res = new ArchiveResponse(stream, 'mock-repo/', '/tmp', path => + const res = new TarArchiveResponse(stream, 'mock-repo/', '/tmp', path => path.endsWith('.yml'), ); const dir = await res.dir({ targetDir: '/tmp' }); diff --git a/packages/backend-common/src/reading/tree/ArchiveResponse.ts b/packages/backend-common/src/reading/tree/TarArchiveResponse.ts similarity index 96% rename from packages/backend-common/src/reading/tree/ArchiveResponse.ts rename to packages/backend-common/src/reading/tree/TarArchiveResponse.ts index e16be63127..5d18ec7dc6 100644 --- a/packages/backend-common/src/reading/tree/ArchiveResponse.ts +++ b/packages/backend-common/src/reading/tree/TarArchiveResponse.ts @@ -34,7 +34,7 @@ const pipeline = promisify(pipelineCb); /** * Wraps a tar archive stream into a tree response reader. */ -export class ArchiveResponse implements ReadTreeResponse { +export class TarArchiveResponse implements ReadTreeResponse { private read = false; constructor( @@ -49,7 +49,7 @@ export class ArchiveResponse implements ReadTreeResponse { } if (subPath.startsWith('/')) { throw new TypeError( - `ArchiveResponse subPath must not start with a /, got '${subPath}'`, + `TarArchiveResponse subPath must not start with a /, got '${subPath}'`, ); } } From 9367dde00d4b8d731143274ed13cbbf866eff2ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Frinnstr=C3=B6m?= Date: Mon, 16 Nov 2020 16:08:05 +0100 Subject: [PATCH 106/430] Introduce ZipArchiveResponse --- packages/backend-common/package.json | 4 + .../src/reading/__fixtures__/repo.zip | Bin 0 -> 629 bytes .../reading/tree/ZipArchiveResponse.test.ts | 151 +++++++++++++ .../src/reading/tree/ZipArchiveResponse.ts | 154 +++++++++++++ yarn.lock | 208 +++++++++++++++++- 5 files changed, 512 insertions(+), 5 deletions(-) create mode 100644 packages/backend-common/src/reading/__fixtures__/repo.zip create mode 100644 packages/backend-common/src/reading/tree/ZipArchiveResponse.test.ts create mode 100644 packages/backend-common/src/reading/tree/ZipArchiveResponse.ts diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 89150302b5..d4f0052d28 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -36,6 +36,7 @@ "@backstage/test-utils": "^0.1.3", "@types/cors": "^2.8.6", "@types/express": "^4.17.6", + "archiver": "^5.0.2", "compression": "^1.7.4", "concat-stream": "^2.0.0", "cors": "^2.8.5", @@ -55,6 +56,7 @@ "selfsigned": "^1.10.7", "stoppable": "^1.1.0", "tar": "^6.0.5", + "unzipper": "^0.10.11", "winston": "^3.2.1" }, "peerDependencies": { @@ -67,6 +69,7 @@ }, "devDependencies": { "@backstage/cli": "^0.3.0", + "@types/archiver": "^3.1.1", "@types/compression": "^1.7.0", "@types/concat-stream": "^1.6.0", "@types/fs-extra": "^9.0.3", @@ -78,6 +81,7 @@ "@types/stoppable": "^1.1.0", "@types/supertest": "^2.0.8", "@types/tar": "^4.0.3", + "@types/unzipper": "^0.10.3", "@types/webpack-env": "^1.15.2", "@types/yaml": "^1.9.7", "get-port": "^5.1.1", diff --git a/packages/backend-common/src/reading/__fixtures__/repo.zip b/packages/backend-common/src/reading/__fixtures__/repo.zip new file mode 100644 index 0000000000000000000000000000000000000000..47956335edad571c8153646cd5288916c9d8e5f4 GIT binary patch literal 629 zcmWIWW@Zs#0D+CQ(ScwFl;8r=x%tW2x<#o4`T7BHb=%O?@uR3q$xki@D+Xz2U;rte zCUQQ49mob@aUez*3Raw%my%kcmz$!j5RzJ4!UeJjqIUyO@7K0B`vib`Kp5mqgx=h2 zkZ!%o+??XflGOOT#N1RXm-p=~S=0I>~^!vFvP literal 0 HcmV?d00001 diff --git a/packages/backend-common/src/reading/tree/ZipArchiveResponse.test.ts b/packages/backend-common/src/reading/tree/ZipArchiveResponse.test.ts new file mode 100644 index 0000000000..922284c759 --- /dev/null +++ b/packages/backend-common/src/reading/tree/ZipArchiveResponse.test.ts @@ -0,0 +1,151 @@ +/* + * 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 fs from 'fs-extra'; +import mockFs from 'mock-fs'; +import { resolve as resolvePath } from 'path'; +import { ZipArchiveResponse } from './ZipArchiveResponse'; + +const archiveData = fs.readFileSync( + resolvePath(__filename, '../../__fixtures__/repo.zip'), +); + +describe('ZipArchiveResponse', () => { + beforeEach(() => { + mockFs({ + '/test-archive.zip': archiveData, + '/tmp': mockFs.directory(), + }); + }); + + afterEach(() => { + mockFs.restore(); + }); + + it('should read files', async () => { + const stream = fs.createReadStream('/test-archive.zip'); + + const res = new ZipArchiveResponse(stream, 'mock-repo/', '/tmp'); + const files = await res.files(); + + expect(files).toEqual([ + { + path: 'docs/index.md', + content: expect.any(Function), + }, + { + path: 'mkdocs.yml', + content: expect.any(Function), + }, + ]); + const contents = await Promise.all(files.map(f => f.content())); + expect(contents.map(c => c.toString('utf8').trim())).toEqual([ + '# Test', + 'site_name: Test', + ]); + }); + + it('should read files with filter', async () => { + const stream = fs.createReadStream('/test-archive.zip'); + + const res = new ZipArchiveResponse(stream, 'mock-repo/', '/tmp', path => + path.endsWith('.yml'), + ); + const files = await res.files(); + + expect(files).toEqual([ + { + path: 'mkdocs.yml', + content: expect.any(Function), + }, + ]); + const content = await files[0].content(); + expect(content.toString('utf8').trim()).toEqual('site_name: Test'); + }); + + it('should read as archive and files', async () => { + const stream = fs.createReadStream('/test-archive.zip'); + + const res = new ZipArchiveResponse(stream, 'mock-repo/', '/tmp'); + const buffer = await res.archive(); + + await expect(res.archive()).rejects.toThrow( + 'Response has already been read', + ); + + const res2 = new ZipArchiveResponse(buffer, '', '/tmp'); + const files = await res2.files(); + + expect(files).toEqual([ + { + path: 'docs/index.md', + content: expect.any(Function), + }, + { + path: 'mkdocs.yml', + content: expect.any(Function), + }, + ]); + const contents = await Promise.all(files.map(f => f.content())); + expect(contents.map(c => c.toString('utf8').trim())).toEqual([ + '# Test', + 'site_name: Test', + ]); + }); + + it('should extract entire archive into directory', async () => { + const stream = fs.createReadStream('/test-archive.zip'); + + const res = new ZipArchiveResponse(stream, '', '/tmp'); + const dir = await res.dir(); + + await expect( + fs.readFile(resolvePath(dir, 'mock-repo/mkdocs.yml'), 'utf8'), + ).resolves.toBe('site_name: Test\n'); + await expect( + fs.readFile(resolvePath(dir, 'mock-repo/docs/index.md'), 'utf8'), + ).resolves.toBe('# Test\n'); + }); + + it('should extract archive into directory with a subpath', async () => { + const stream = fs.createReadStream('/test-archive.zip'); + + const res = new ZipArchiveResponse(stream, 'mock-repo/docs/', '/tmp'); + const dir = await res.dir(); + + expect(dir).toMatch(/^\/tmp\/.*$/); + await expect( + fs.readFile(resolvePath(dir, 'index.md'), 'utf8'), + ).resolves.toBe('# Test\n'); + }); + + it('should extract archive into directory with a subpath and filter', async () => { + const stream = fs.createReadStream('/test-archive.zip'); + + const res = new ZipArchiveResponse(stream, 'mock-repo/', '/tmp', path => + path.endsWith('.yml'), + ); + const dir = await res.dir({ targetDir: '/tmp' }); + + expect(dir).toBe('/tmp'); + await expect(fs.pathExists(resolvePath(dir, 'mkdocs.yml'))).resolves.toBe( + true, + ); + await expect( + fs.pathExists(resolvePath(dir, 'docs/index.md')), + ).resolves.toBe(false); + }); +}); diff --git a/packages/backend-common/src/reading/tree/ZipArchiveResponse.ts b/packages/backend-common/src/reading/tree/ZipArchiveResponse.ts new file mode 100644 index 0000000000..23545cd96e --- /dev/null +++ b/packages/backend-common/src/reading/tree/ZipArchiveResponse.ts @@ -0,0 +1,154 @@ +/* + * 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 path from 'path'; +import fs from 'fs-extra'; +import unzipper, { Entry } from 'unzipper'; +import archiver from 'archiver'; +import { Readable } from 'stream'; +import { + ReadTreeResponse, + ReadTreeResponseFile, + ReadTreeResponseDirOptions, +} from '../types'; + +/** + * Wraps a zip archive stream into a tree response reader. + */ +export class ZipArchiveResponse implements ReadTreeResponse { + private read = false; + + constructor( + private readonly stream: Readable, + private readonly subPath: string, + private readonly workDir: string, + private readonly filter?: (path: string) => boolean, + ) { + if (subPath) { + if (!subPath.endsWith('/')) { + this.subPath += '/'; + } + if (subPath.startsWith('/')) { + throw new TypeError( + `ZipArchiveResponse subPath must not start with a /, got '${subPath}'`, + ); + } + } + } + + // Make sure the input stream is only read once + private onlyOnce() { + if (this.read) { + throw new Error('Response has already been read'); + } + this.read = true; + } + + private getPath(entry: Entry): string { + return entry.path.slice(this.subPath.length); + } + + private shouldBeIncluded(entry: Entry): boolean { + if (this.subPath) { + if (!entry.path.startsWith(this.subPath)) { + return false; + } + } + if (this.filter) { + return this.filter(this.getPath(entry)); + } + return true; + } + + async files(): Promise { + this.onlyOnce(); + + const files = Array(); + + await this.stream + .pipe(unzipper.Parse()) + .on('entry', (entry: Entry) => { + if (entry.type === 'Directory') { + entry.resume(); + return; + } + + if (this.shouldBeIncluded(entry)) { + files.push({ + path: this.getPath(entry), + content: () => entry.buffer(), + }); + } else { + entry.autodrain(); + } + }) + .promise(); + + return files; + } + + async archive(): Promise { + this.onlyOnce(); + + if (!this.subPath) { + return this.stream; + } + + const archive = archiver('zip'); + await this.stream + .pipe(unzipper.Parse()) + .on('entry', (entry: Entry) => { + if (entry.type === 'File' && this.shouldBeIncluded(entry)) { + archive.append(entry, { name: this.getPath(entry) }); + } else { + entry.autodrain(); + } + }) + .promise(); + archive.finalize(); + + return archive; + } + + async dir(options?: ReadTreeResponseDirOptions): Promise { + this.onlyOnce(); + + const dir = + options?.targetDir ?? + (await fs.mkdtemp(path.join(this.workDir, 'backstage-'))); + + await this.stream + .pipe(unzipper.Parse()) + .on('entry', (entry: Entry) => { + if (this.shouldBeIncluded(entry)) { + if (entry.type === 'Directory') { + const directoryPath = this.getPath(entry); + if (directoryPath) { + fs.mkdirSync(path.join(dir, this.getPath(entry))); + } + entry.resume(); + return; + } + entry.pipe(fs.createWriteStream(path.join(dir, this.getPath(entry)))); + } else { + entry.autodrain(); + } + }) + .promise(); + + return dir; + } +} diff --git a/yarn.lock b/yarn.lock index 58d9c546ee..a35f6499e8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4873,6 +4873,13 @@ resolved "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a" integrity sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA== +"@types/archiver@^3.1.1": + version "3.1.1" + resolved "https://registry.npmjs.org/@types/archiver/-/archiver-3.1.1.tgz#10cc1be44af8911e57484342c7b3b32a5f178a1a" + integrity sha512-TzVZ9204sH1TuFylfr1cw/AA/3/VldAAXswEwKLXUOzA9mDg+m6gHF9EaqKNlozcjc6knX5m1KAqJzksPLSEfw== + dependencies: + "@types/glob" "*" + "@types/aria-query@^4.2.0": version "4.2.0" resolved "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.0.tgz#14264692a9d6e2fa4db3df5e56e94b5e25647ac0" @@ -6016,6 +6023,13 @@ resolved "https://registry.npmjs.org/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e" integrity sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ== +"@types/unzipper@^0.10.3": + version "0.10.3" + resolved "https://registry.npmjs.org/@types/unzipper/-/unzipper-0.10.3.tgz#9eea872fb1fa460da76f253878b6275af588f464" + integrity sha512-01mQdTLp3/KuBVDhP82FNBf+enzVOjJ9dGsCWa5z8fcYAFVgA9bqIQ2NmsgNFzN/DhD0PUQj4n5p7k6I9mq80g== + dependencies: + "@types/node" "*" + "@types/uuid@^8.0.0": version "8.0.0" resolved "https://registry.npmjs.org/@types/uuid/-/uuid-8.0.0.tgz#165aae4819ad2174a17476dbe66feebd549556c0" @@ -6927,6 +6941,35 @@ arch@^2.1.2: resolved "https://registry.npmjs.org/arch/-/arch-2.1.2.tgz#0c52bbe7344bb4fa260c443d2cbad9c00ff2f0bf" integrity sha512-NTBIIbAfkJeIletyABbVtdPgeKfDafR+1mZV/AyyfC1UkVkp9iUjV+wwmqtUgphHYajbI86jejBJp5e+jkGTiQ== +archiver-utils@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz#e8a460e94b693c3e3da182a098ca6285ba9249e2" + integrity sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw== + dependencies: + glob "^7.1.4" + graceful-fs "^4.2.0" + lazystream "^1.0.0" + lodash.defaults "^4.2.0" + lodash.difference "^4.5.0" + lodash.flatten "^4.4.0" + lodash.isplainobject "^4.0.6" + lodash.union "^4.6.0" + normalize-path "^3.0.0" + readable-stream "^2.0.0" + +archiver@^5.0.2: + version "5.0.2" + resolved "https://registry.npmjs.org/archiver/-/archiver-5.0.2.tgz#b2c435823499b1f46eb07aa18e7bcb332f6ca3fc" + integrity sha512-Tq3yV/T4wxBsD2Wign8W9VQKhaUxzzRmjEiSoOK0SLqPgDP/N1TKdYyBeIEu56T4I9iO4fKTTR0mN9NWkBA0sg== + dependencies: + archiver-utils "^2.1.0" + async "^3.2.0" + buffer-crc32 "^0.2.1" + readable-stream "^3.6.0" + readdir-glob "^1.0.0" + tar-stream "^2.1.4" + zip-stream "^4.0.0" + are-we-there-yet@~1.1.2: version "1.1.5" resolved "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" @@ -7680,6 +7723,11 @@ base64-js@^1.0.2, base64-js@^1.2.0: resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + base64url@3.x.x, base64url@^3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d" @@ -7751,6 +7799,11 @@ bfj@^7.0.2: hoopy "^0.1.4" tryer "^1.0.1" +big-integer@^1.6.17: + version "1.6.48" + resolved "https://registry.npmjs.org/big-integer/-/big-integer-1.6.48.tgz#8fd88bd1632cba4a1c8c3e3d7159f08bb95b4b9e" + integrity sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w== + big.js@^5.2.2: version "5.2.2" resolved "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" @@ -7766,6 +7819,14 @@ binary-extensions@^2.0.0: resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c" integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow== +binary@~0.3.0: + version "0.3.0" + resolved "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz#9f60553bc5ce8c3386f3b553cff47462adecaa79" + integrity sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk= + dependencies: + buffers "~0.1.1" + chainsaw "~0.1.0" + bindings@^1.5.0: version "1.5.0" resolved "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" @@ -7786,7 +7847,7 @@ bl@^1.0.0: readable-stream "^2.3.5" safe-buffer "^5.1.1" -bl@^4.0.1: +bl@^4.0.1, bl@^4.0.3: version "4.0.3" resolved "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz#12d6287adc29080e22a705e5764b2a9522cdc489" integrity sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg== @@ -7807,6 +7868,11 @@ bluebird@3.7.2, bluebird@^3.3.5, bluebird@^3.5.1, bluebird@^3.5.3, bluebird@^3.5 resolved "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== +bluebird@~3.4.1: + version "3.4.7" + resolved "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3" + integrity sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM= + bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: version "4.11.8" resolved "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" @@ -8041,7 +8107,7 @@ buffer-alloc@^1.2.0: buffer-alloc-unsafe "^1.1.0" buffer-fill "^1.0.0" -buffer-crc32@~0.2.3: +buffer-crc32@^0.2.1, buffer-crc32@^0.2.13, buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= @@ -8061,6 +8127,11 @@ buffer-from@1.x, buffer-from@^1.0.0: resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== +buffer-indexof-polyfill@~1.0.0: + version "1.0.2" + resolved "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz#d2732135c5999c64b277fcf9b1abe3498254729c" + integrity sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A== + buffer-indexof@^1.0.0: version "1.1.1" resolved "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c" @@ -8090,6 +8161,14 @@ buffer@^4.3.0: ieee754 "^1.1.4" isarray "^1.0.0" +buffer@^5.1.0: + version "5.7.1" + resolved "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + buffer@^5.5.0, buffer@^5.6.0: version "5.6.0" resolved "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786" @@ -8098,6 +8177,11 @@ buffer@^5.5.0, buffer@^5.6.0: base64-js "^1.0.2" ieee754 "^1.1.4" +buffers@~0.1.1: + version "0.1.1" + resolved "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb" + integrity sha1-skV5w77U1tOWru5tmorn9Ugqt7s= + bufferutil@^4.0.1: version "4.0.1" resolved "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.1.tgz#3a177e8e5819a1243fe16b63a199951a7ad8d4a7" @@ -8391,6 +8475,13 @@ caseless@~0.12.0: resolved "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= +chainsaw@~0.1.0: + version "0.1.0" + resolved "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz#5eab50b28afe58074d0d58291388828b5e5fbc98" + integrity sha1-XqtQsor+WAdNDVgpE4iCi15fvJg= + dependencies: + traverse ">=0.3.0 <0.4" + chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.3.2, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -8933,6 +9024,16 @@ component-emitter@^1.2.0, component-emitter@^1.2.1: resolved "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== +compress-commons@^4.0.0: + version "4.0.1" + resolved "https://registry.npmjs.org/compress-commons/-/compress-commons-4.0.1.tgz#c5fa908a791a0c71329fba211d73cd2a32005ea8" + integrity sha512-xZm9o6iikekkI0GnXCmAl3LQGZj5TBDj0zLowsqi7tJtEa3FMGSEcHcqrSJIrOAk1UG/NBbDn/F1q+MG/p/EsA== + dependencies: + buffer-crc32 "^0.2.13" + crc32-stream "^4.0.0" + normalize-path "^3.0.0" + readable-stream "^3.6.0" + compressible@~2.0.16: version "2.0.18" resolved "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" @@ -9300,6 +9401,21 @@ cosmiconfig@^7.0.0: path-type "^4.0.0" yaml "^1.10.0" +crc32-stream@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.0.tgz#05b7ca047d831e98c215538666f372b756d91893" + integrity sha512-tyMw2IeUX6t9jhgXI6um0eKfWq4EIDpfv5m7GX4Jzp7eVelQ360xd8EPXJhp2mHwLQIkqlnMLjzqSZI3a+0wRw== + dependencies: + crc "^3.4.4" + readable-stream "^3.4.0" + +crc@^3.4.4: + version "3.8.0" + resolved "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz#ad60269c2c856f8c299e2c4cc0de4556914056c6" + integrity sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ== + dependencies: + buffer "^5.1.0" + create-ecdh@^4.0.0: version "4.0.3" resolved "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff" @@ -10623,6 +10739,13 @@ dotenv@^8.0.0, dotenv@^8.2.0: resolved "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== +duplexer2@~0.1.4: + version "0.1.4" + resolved "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" + integrity sha1-ixLauHjA1p4+eJEFFmKjL8a93ME= + dependencies: + readable-stream "^2.0.2" + duplexer3@^0.1.4: version "0.1.4" resolved "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" @@ -15414,6 +15537,13 @@ lazy-universal-dotenv@^3.0.1: dotenv "^8.0.0" dotenv-expand "^5.1.0" +lazystream@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz#f6995fe0f820392f61396be89462407bb77168e4" + integrity sha1-9plf4PggOS9hOWvolGJAe7dxaOQ= + dependencies: + readable-stream "^2.0.5" + lcid@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" @@ -15558,6 +15688,11 @@ lint-staged@^10.1.0: string-argv "0.3.1" stringify-object "^3.3.0" +listenercount@~1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz#84c8a72ab59c4725321480c975e6508342e70937" + integrity sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc= + listr-silent-renderer@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz#924b5a3757153770bf1a8e3fbf74b8bbf3f9242e" @@ -15760,6 +15895,16 @@ lodash.debounce@^4, lodash.debounce@^4.0.8: resolved "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= +lodash.defaults@^4.2.0: + version "4.2.0" + resolved "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" + integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw= + +lodash.difference@^4.5.0: + version "4.5.0" + resolved "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c" + integrity sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw= + lodash.flatten@^4.4.0: version "4.4.0" resolved "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" @@ -15855,6 +16000,11 @@ lodash.throttle@^4.1.1: resolved "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" integrity sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ= +lodash.union@^4.6.0: + version "4.6.0" + resolved "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" + integrity sha1-SLtQiECfFvGCFmZkHETdGqrjzYg= + lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" @@ -20205,7 +20355,7 @@ read@1, read@~1.0.1: dependencies: mute-stream "~0.0.4" -"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.0, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6: +"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.0, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6: version "2.3.7" resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -20218,7 +20368,7 @@ read@1, read@~1.0.1: string_decoder "~1.1.1" util-deprecate "~1.0.1" -"readable-stream@2 || 3", readable-stream@^3.0.2, readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0: +"readable-stream@2 || 3", readable-stream@^3.0.2, readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -20227,6 +20377,13 @@ read@1, read@~1.0.1: string_decoder "^1.1.1" util-deprecate "^1.0.1" +readdir-glob@^1.0.0: + version "1.1.1" + resolved "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.1.tgz#f0e10bb7bf7bfa7e0add8baffdc54c3f7dbee6c4" + integrity sha512-91/k1EzZwDx6HbERR+zucygRFfiPl2zkIYZtv3Jjr6Mn7SkKcVct8aVO+sSRiGMc6fLf72du3d92/uY63YPdEA== + dependencies: + minimatch "^3.0.4" + readdir-scoped-modules@^1.0.0: version "1.1.0" resolved "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz#8d45407b4f870a0dcaebc0e28670d18e74514309" @@ -21216,7 +21373,7 @@ set-value@^2.0.0, set-value@^2.0.1: is-plain-object "^2.0.3" split-string "^3.0.1" -setimmediate@^1.0.4, setimmediate@^1.0.5: +setimmediate@^1.0.4, setimmediate@^1.0.5, setimmediate@~1.0.4: version "1.0.5" resolved "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= @@ -22471,6 +22628,17 @@ tar-stream@^2.0.0: inherits "^2.0.3" readable-stream "^3.1.1" +tar-stream@^2.1.4: + version "2.1.4" + resolved "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.4.tgz#c4fb1a11eb0da29b893a5b25476397ba2d053bfa" + integrity sha512-o3pS2zlG4gxr67GmFYBLlq+dM8gyRGUOvsrHclSkvtVtQbjV0s/+ZE8OpICbaj8clrX3tjeHngYGP7rweaBnuw== + dependencies: + bl "^4.0.3" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + tar@^2.0.0: version "2.2.2" resolved "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz#0ca8848562c7299b8b446ff6a4d60cdbb23edc40" @@ -22905,6 +23073,11 @@ tr46@^2.0.2: dependencies: punycode "^2.1.1" +"traverse@>=0.3.0 <0.4": + version "0.3.9" + resolved "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" + integrity sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk= + traverse@~0.6.6: version "0.6.6" resolved "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz#cbdf560fd7b9af632502fed40f918c157ea97137" @@ -23426,6 +23599,22 @@ untildify@^4.0.0: resolved "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== +unzipper@^0.10.11: + version "0.10.11" + resolved "https://registry.npmjs.org/unzipper/-/unzipper-0.10.11.tgz#0b4991446472cbdb92ee7403909f26c2419c782e" + integrity sha512-+BrAq2oFqWod5IESRjL3S8baohbevGcVA+teAIOYWM3pDVdseogqbzhhvvmiyQrUNKFUnDMtELW3X8ykbyDCJw== + dependencies: + big-integer "^1.6.17" + binary "~0.3.0" + bluebird "~3.4.1" + buffer-indexof-polyfill "~1.0.0" + duplexer2 "~0.1.4" + fstream "^1.0.12" + graceful-fs "^4.2.2" + listenercount "~1.0.1" + readable-stream "~2.3.6" + setimmediate "~1.0.4" + upath@^1.1.1, upath@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" @@ -24624,6 +24813,15 @@ zenscroll@^4.0.2: resolved "https://registry.npmjs.org/zenscroll/-/zenscroll-4.0.2.tgz#e8d5774d1c0738a47bcfa8729f3712e2deddeb25" integrity sha1-6NV3TRwHOKR7z6hynzcS4t7d6yU= +zip-stream@^4.0.0: + version "4.0.2" + resolved "https://registry.npmjs.org/zip-stream/-/zip-stream-4.0.2.tgz#3a20f1bd7729c2b59fd4efa04df5eb7a5a217d2e" + integrity sha512-TGxB2g+1ur6MHkvM644DuZr8Uzyz0k0OYWtS3YlpfWBEmK4woaC2t3+pozEL3dBfIPmpgmClR5B2QRcMgGt22g== + dependencies: + archiver-utils "^2.1.0" + compress-commons "^4.0.0" + readable-stream "^3.6.0" + zombie@^6.1.4: version "6.1.4" resolved "https://registry.npmjs.org/zombie/-/zombie-6.1.4.tgz#9f0f53f3d9a032beb7f3fe5b382146a3475a4d47" From 34a51a288a2a2b931c98261ef312c744b9329882 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Frinnstr=C3=B6m?= Date: Mon, 16 Nov 2020 16:08:37 +0100 Subject: [PATCH 107/430] Expose ZipArchiveResponse through ReadTreeResponseFactory --- .../src/reading/AzureUrlReader.test.ts | 245 +++++++++++------- .../src/reading/AzureUrlReader.ts | 61 ++++- .../src/reading/GithubUrlReader.ts | 2 +- .../reading/tree/ReadTreeResponseFactory.ts | 12 +- 4 files changed, 221 insertions(+), 99 deletions(-) diff --git a/packages/backend-common/src/reading/AzureUrlReader.test.ts b/packages/backend-common/src/reading/AzureUrlReader.test.ts index 6efc7641b8..ded54cba62 100644 --- a/packages/backend-common/src/reading/AzureUrlReader.test.ts +++ b/packages/backend-common/src/reading/AzureUrlReader.test.ts @@ -14,11 +14,13 @@ * limitations under the License. */ +import fs from 'fs'; +import path from 'path'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; import { ConfigReader } from '@backstage/config'; import { getVoidLogger } from '../logging'; -import { AzureUrlReader } from './AzureUrlReader'; +import { AzureUrlReader, getDownloadUrl } from './AzureUrlReader'; import { msw } from '@backstage/test-utils'; import { ReadTreeResponseFactory } from './tree'; @@ -32,104 +34,165 @@ describe('AzureUrlReader', () => { const worker = setupServer(); msw.setupDefaultHandlers(worker); - beforeEach(() => { - worker.use( - rest.get('*', (req, res, ctx) => - res( - ctx.status(200), - ctx.json({ - url: req.url.toString(), - headers: req.headers.getAllHeaders(), - }), + describe('read', () => { + beforeEach(() => { + worker.use( + rest.get('*', (req, res, ctx) => + res( + ctx.status(200), + ctx.json({ + url: req.url.toString(), + headers: req.headers.getAllHeaders(), + }), + ), ), - ), - ); - }); - - const createConfig = (token?: string) => - new ConfigReader( - { - integrations: { azure: [{ host: 'dev.azure.com', token }] }, - }, - 'test-config', - ); - - it.each([ - { - url: - 'https://dev.azure.com/org-name/project-name/_git/repo-name?path=my-template.yaml&version=GBmaster', - config: createConfig(), - response: expect.objectContaining({ - url: - 'https://dev.azure.com/org-name/project-name/_apis/git/repositories/repo-name/items?path=my-template.yaml&version=master', - }), - }, - { - url: - 'https://dev.azure.com/org-name/project-name/_git/repo-name?path=my-template.yaml', - config: createConfig(), - response: expect.objectContaining({ - url: - 'https://dev.azure.com/org-name/project-name/_apis/git/repositories/repo-name/items?path=my-template.yaml', - }), - }, - { - url: 'https://dev.azure.com/a/b/_git/repo-name?path=my-template.yaml', - config: createConfig('0123456789'), - response: expect.objectContaining({ - headers: expect.objectContaining({ - authorization: 'Basic OjAxMjM0NTY3ODk=', - }), - }), - }, - { - url: 'https://dev.azure.com/a/b/_git/repo-name?path=my-template.yaml', - config: createConfig(undefined), - response: expect.objectContaining({ - headers: expect.not.objectContaining({ - authorization: expect.anything(), - }), - }), - }, - ])('should handle happy path %#', async ({ url, config, response }) => { - const [{ reader }] = AzureUrlReader.factory({ - config, - logger, - treeResponseFactory, + ); }); - const data = await reader.read(url); - const res = await JSON.parse(data.toString('utf-8')); - expect(res).toEqual(response); - }); + const createConfig = (token?: string) => + new ConfigReader( + { + integrations: { azure: [{ host: 'dev.azure.com', token }] }, + }, + 'test-config', + ); - it.each([ - { - url: 'https://api.com/a/b/blob/master/path/to/c.yaml', - config: createConfig(), - error: - 'Incorrect url: https://api.com/a/b/blob/master/path/to/c.yaml, Error: Wrong Azure Devops URL or Invalid file path', - }, - { - url: 'com/a/b/blob/master/path/to/c.yaml', - config: createConfig(), - error: - 'Incorrect url: com/a/b/blob/master/path/to/c.yaml, TypeError: Invalid URL: com/a/b/blob/master/path/to/c.yaml', - }, - { - url: '', - config: createConfig(''), - error: - "Invalid type in config for key 'integrations.azure[0].token' in 'test-config', got empty-string, wanted string", - }, - ])('should handle error path %#', async ({ url, config, error }) => { - await expect(async () => { + it.each([ + { + url: + 'https://dev.azure.com/org-name/project-name/_git/repo-name?path=my-template.yaml&version=GBmaster', + config: createConfig(), + response: expect.objectContaining({ + url: + 'https://dev.azure.com/org-name/project-name/_apis/git/repositories/repo-name/items?path=my-template.yaml&version=master', + }), + }, + { + url: + 'https://dev.azure.com/org-name/project-name/_git/repo-name?path=my-template.yaml', + config: createConfig(), + response: expect.objectContaining({ + url: + 'https://dev.azure.com/org-name/project-name/_apis/git/repositories/repo-name/items?path=my-template.yaml', + }), + }, + { + url: 'https://dev.azure.com/a/b/_git/repo-name?path=my-template.yaml', + config: createConfig('0123456789'), + response: expect.objectContaining({ + headers: expect.objectContaining({ + authorization: 'Basic OjAxMjM0NTY3ODk=', + }), + }), + }, + { + url: 'https://dev.azure.com/a/b/_git/repo-name?path=my-template.yaml', + config: createConfig(undefined), + response: expect.objectContaining({ + headers: expect.not.objectContaining({ + authorization: expect.anything(), + }), + }), + }, + ])('should handle happy path %#', async ({ url, config, response }) => { const [{ reader }] = AzureUrlReader.factory({ config, logger, treeResponseFactory, }); - await reader.read(url); - }).rejects.toThrow(error); + + const data = await reader.read(url); + const res = await JSON.parse(data.toString('utf-8')); + expect(res).toEqual(response); + }); + + it.each([ + { + url: 'https://api.com/a/b/blob/master/path/to/c.yaml', + config: createConfig(), + error: + 'Incorrect url: https://api.com/a/b/blob/master/path/to/c.yaml, Error: Wrong Azure Devops URL or Invalid file path', + }, + { + url: 'com/a/b/blob/master/path/to/c.yaml', + config: createConfig(), + error: + 'Incorrect url: com/a/b/blob/master/path/to/c.yaml, TypeError: Invalid URL: com/a/b/blob/master/path/to/c.yaml', + }, + { + url: '', + config: createConfig(''), + error: + "Invalid type in config for key 'integrations.azure[0].token' in 'test-config', got empty-string, wanted string", + }, + ])('should handle error path %#', async ({ url, config, error }) => { + await expect(async () => { + const [{ reader }] = AzureUrlReader.factory({ + config, + logger, + treeResponseFactory, + }); + await reader.read(url); + }).rejects.toThrow(error); + }); + }); + + describe('readTree', () => { + const repoBuffer = fs.readFileSync( + path.resolve('src', 'reading', '__fixtures__', 'repo.zip'), + ); + + beforeEach(() => { + worker.use( + rest.get( + 'https://dev.azure.com/organization/project/_apis/git/repositories/repository/items', + (_, res, ctx) => + res( + ctx.status(200), + ctx.set('Content-Type', 'application/zip'), + ctx.body(repoBuffer), + ), + ), + ); + }); + + it('returns the wanted files from an archive', async () => { + const processor = new AzureUrlReader( + { + host: 'dev.azure.com', + }, + treeResponseFactory, + ); + + const response = await processor.readTree( + 'https://dev.azure.com/organization/project/_git/repository', + ); + + const files = await response.files(); + + expect(files.length).toBe(2); + const mkDocsFile = await files[1].content(); + const indexMarkdownFile = await files[0].content(); + + expect(mkDocsFile.toString()).toBe('site_name: Test\n'); + expect(indexMarkdownFile.toString()).toBe('# Test\n'); + }); + }); + + describe('getDownloadUrl', () => { + it('add no scopePath if no path is specified', async () => { + const result = getDownloadUrl( + 'https://dev.azure.com/organization/project/_git/repository', + ); + + expect(result.searchParams.get('scopePath')).toBeNull(); + }); + + it('add the scopePath if a path is specified', async () => { + const result = getDownloadUrl( + 'https://dev.azure.com/organization/project/_git/repository?path=%2Fdocs', + ); + expect(result.searchParams.get('scopePath')).toEqual('docs'); + }); }); }); diff --git a/packages/backend-common/src/reading/AzureUrlReader.ts b/packages/backend-common/src/reading/AzureUrlReader.ts index db8b738667..068f4d2409 100644 --- a/packages/backend-common/src/reading/AzureUrlReader.ts +++ b/packages/backend-common/src/reading/AzureUrlReader.ts @@ -19,22 +19,53 @@ import { readAzureIntegrationConfigs, } from '@backstage/integration'; import fetch from 'cross-fetch'; +import { Readable } from 'stream'; +import parseGitUri from 'git-url-parse'; import { NotFoundError } from '../errors'; -import { ReaderFactory, ReadTreeResponse, UrlReader } from './types'; +import { + ReaderFactory, + ReadTreeOptions, + ReadTreeResponse, + UrlReader, +} from './types'; +import { ReadTreeResponseFactory } from './tree'; + +export function getDownloadUrl(url: string): URL { + const { + name: repoName, + owner: project, + organization, + protocol, + resource, + filepath, + } = parseGitUri(url); + + // scopePath will limit the downloaded content + // /docs will only download the docs folder and everything below it + // /docs/index.md will only download index.md but put it in the root of the archive + const scopePath = filepath ? `&scopePath=${filepath}` : ''; + + return new URL( + `${protocol}://${resource}/${organization}/${project}/_apis/git/repositories/${repoName}/items?recursionLevel=full&download=true&api-version=6.0${scopePath}`, + ); +} export class AzureUrlReader implements UrlReader { - static factory: ReaderFactory = ({ config }) => { + static factory: ReaderFactory = ({ config, treeResponseFactory }) => { const configs = readAzureIntegrationConfigs( config.getOptionalConfigArray('integrations.azure') ?? [], ); return configs.map(options => { - const reader = new AzureUrlReader(options); + const reader = new AzureUrlReader(options, treeResponseFactory); const predicate = (url: URL) => url.host === options.host; return { reader, predicate }; }); }; - constructor(private readonly options: AzureIntegrationConfig) { + constructor( + private readonly options: AzureIntegrationConfig, + private readonly treeResponseFactory: ReadTreeResponseFactory, + ) { if (options.host !== 'dev.azure.com') { throw Error( `Azure integration currently only supports 'dev.azure.com', tried to use host '${options.host}'`, @@ -64,8 +95,26 @@ export class AzureUrlReader implements UrlReader { throw new Error(message); } - readTree(): Promise { - throw new Error('AzureUrlReader does not implement readTree'); + async readTree( + url: string, + options?: ReadTreeOptions, + ): Promise { + const response = await fetch(getDownloadUrl(url).toString(), { + ...this.getRequestOptions(), + headers: { Accept: 'application/zip' }, + }); + if (!response.ok) { + const message = `Failed to read tree from ${url}, ${response.status} ${response.statusText}`; + if (response.status === 404) { + throw new NotFoundError(message); + } + throw new Error(message); + } + + return this.treeResponseFactory.fromZipArchive({ + stream: (response.body as unknown) as Readable, + filter: options?.filter, + }); } // Converts diff --git a/packages/backend-common/src/reading/GithubUrlReader.ts b/packages/backend-common/src/reading/GithubUrlReader.ts index 2fbaa0b32b..907f2ada7a 100644 --- a/packages/backend-common/src/reading/GithubUrlReader.ts +++ b/packages/backend-common/src/reading/GithubUrlReader.ts @@ -207,7 +207,7 @@ export class GithubUrlReader implements UrlReader { const path = `${repoName}-${ref}/${filepath}`; - return this.deps.treeResponseFactory.fromArchive({ + return this.deps.treeResponseFactory.fromTarArchive({ // TODO(Rugvip): Underlying implementation of fetch will be node-fetch, we probably want // to stick to using that in exclusively backend code. stream: (response.body as unknown) as Readable, diff --git a/packages/backend-common/src/reading/tree/ReadTreeResponseFactory.ts b/packages/backend-common/src/reading/tree/ReadTreeResponseFactory.ts index 90d74e1f20..986a0302bc 100644 --- a/packages/backend-common/src/reading/tree/ReadTreeResponseFactory.ts +++ b/packages/backend-common/src/reading/tree/ReadTreeResponseFactory.ts @@ -19,6 +19,7 @@ import { Readable } from 'stream'; import { Config } from '@backstage/config'; import { ReadTreeResponse } from '../types'; import { TarArchiveResponse } from './TarArchiveResponse'; +import { ZipArchiveResponse } from './ZipArchiveResponse'; type FromArchiveOptions = { // A binary stream of a tar archive. @@ -39,7 +40,7 @@ export class ReadTreeResponseFactory { constructor(private readonly workDir: string) {} - async fromArchive(options: FromArchiveOptions): Promise { + async fromTarArchive(options: FromArchiveOptions): Promise { return new TarArchiveResponse( options.stream, options.path ?? '', @@ -47,4 +48,13 @@ export class ReadTreeResponseFactory { options.filter, ); } + + async fromZipArchive(options: FromArchiveOptions): Promise { + return new ZipArchiveResponse( + options.stream, + options.path ?? '', + this.workDir, + options.filter, + ); + } } From bff3305aa9a36c597b7387df1bf2993de3ca8e70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Frinnstr=C3=B6m?= Date: Mon, 16 Nov 2020 16:16:21 +0100 Subject: [PATCH 108/430] Add changeset --- .changeset/clever-moons-shake.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/clever-moons-shake.md diff --git a/.changeset/clever-moons-shake.md b/.changeset/clever-moons-shake.md new file mode 100644 index 0000000000..0fb6c690c2 --- /dev/null +++ b/.changeset/clever-moons-shake.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +Added readTree support to AzureUrlReader From a9155afb83aff9ba4d31df635ac96708f50be2e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Frinnstr=C3=B6m?= Date: Tue, 17 Nov 2020 21:18:55 +0100 Subject: [PATCH 109/430] Some small changes --- .../src/reading/tree/ZipArchiveResponse.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/backend-common/src/reading/tree/ZipArchiveResponse.ts b/packages/backend-common/src/reading/tree/ZipArchiveResponse.ts index 23545cd96e..048b2ab46d 100644 --- a/packages/backend-common/src/reading/tree/ZipArchiveResponse.ts +++ b/packages/backend-common/src/reading/tree/ZipArchiveResponse.ts @@ -134,15 +134,15 @@ export class ZipArchiveResponse implements ReadTreeResponse { .pipe(unzipper.Parse()) .on('entry', (entry: Entry) => { if (this.shouldBeIncluded(entry)) { + const entryPath = this.getPath(entry); if (entry.type === 'Directory') { - const directoryPath = this.getPath(entry); - if (directoryPath) { - fs.mkdirSync(path.join(dir, this.getPath(entry))); + if (entryPath) { + fs.mkdirSync(path.join(dir, entryPath)); } entry.resume(); - return; + } else { + entry.pipe(fs.createWriteStream(path.join(dir, entryPath))); } - entry.pipe(fs.createWriteStream(path.join(dir, this.getPath(entry)))); } else { entry.autodrain(); } From c08e5cbd6ffad15d69a6c27c1bd95ac1bc46e8b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Frinnstr=C3=B6m?= Date: Wed, 18 Nov 2020 11:27:31 +0100 Subject: [PATCH 110/430] Fix parameters and encode URI component --- .../backend-common/src/reading/AzureUrlReader.test.ts | 7 ++++--- packages/backend-common/src/reading/AzureUrlReader.ts | 10 ++++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/backend-common/src/reading/AzureUrlReader.test.ts b/packages/backend-common/src/reading/AzureUrlReader.test.ts index ded54cba62..91a1e40c10 100644 --- a/packages/backend-common/src/reading/AzureUrlReader.test.ts +++ b/packages/backend-common/src/reading/AzureUrlReader.test.ts @@ -161,7 +161,7 @@ describe('AzureUrlReader', () => { { host: 'dev.azure.com', }, - treeResponseFactory, + { treeResponseFactory }, ); const response = await processor.readTree( @@ -180,7 +180,7 @@ describe('AzureUrlReader', () => { }); describe('getDownloadUrl', () => { - it('add no scopePath if no path is specified', async () => { + it('do not add scopePath if no path is specified', async () => { const result = getDownloadUrl( 'https://dev.azure.com/organization/project/_git/repository', ); @@ -188,10 +188,11 @@ describe('AzureUrlReader', () => { expect(result.searchParams.get('scopePath')).toBeNull(); }); - it('add the scopePath if a path is specified', async () => { + it('add scopePath if a path is specified', async () => { const result = getDownloadUrl( 'https://dev.azure.com/organization/project/_git/repository?path=%2Fdocs', ); + console.log(result.searchParams); expect(result.searchParams.get('scopePath')).toEqual('docs'); }); }); diff --git a/packages/backend-common/src/reading/AzureUrlReader.ts b/packages/backend-common/src/reading/AzureUrlReader.ts index 068f4d2409..e5e290eb13 100644 --- a/packages/backend-common/src/reading/AzureUrlReader.ts +++ b/packages/backend-common/src/reading/AzureUrlReader.ts @@ -43,7 +43,9 @@ export function getDownloadUrl(url: string): URL { // scopePath will limit the downloaded content // /docs will only download the docs folder and everything below it // /docs/index.md will only download index.md but put it in the root of the archive - const scopePath = filepath ? `&scopePath=${filepath}` : ''; + const scopePath = filepath + ? `&scopePath=${encodeURIComponent(filepath)}` + : ''; return new URL( `${protocol}://${resource}/${organization}/${project}/_apis/git/repositories/${repoName}/items?recursionLevel=full&download=true&api-version=6.0${scopePath}`, @@ -56,7 +58,7 @@ export class AzureUrlReader implements UrlReader { config.getOptionalConfigArray('integrations.azure') ?? [], ); return configs.map(options => { - const reader = new AzureUrlReader(options, treeResponseFactory); + const reader = new AzureUrlReader(options, { treeResponseFactory }); const predicate = (url: URL) => url.host === options.host; return { reader, predicate }; }); @@ -64,7 +66,7 @@ export class AzureUrlReader implements UrlReader { constructor( private readonly options: AzureIntegrationConfig, - private readonly treeResponseFactory: ReadTreeResponseFactory, + private readonly deps: { treeResponseFactory: ReadTreeResponseFactory }, ) { if (options.host !== 'dev.azure.com') { throw Error( @@ -111,7 +113,7 @@ export class AzureUrlReader implements UrlReader { throw new Error(message); } - return this.treeResponseFactory.fromZipArchive({ + return this.deps.treeResponseFactory.fromZipArchive({ stream: (response.body as unknown) as Readable, filter: options?.filter, }); From b1b6537bc360922980fb45c2e972a6b4f836991a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Frinnstr=C3=B6m?= Date: Wed, 18 Nov 2020 16:28:11 +0100 Subject: [PATCH 111/430] Handle request headers correctly --- .../src/reading/AzureUrlReader.test.ts | 1 - .../backend-common/src/reading/AzureUrlReader.ts | 14 ++++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/backend-common/src/reading/AzureUrlReader.test.ts b/packages/backend-common/src/reading/AzureUrlReader.test.ts index 91a1e40c10..ab97d1b073 100644 --- a/packages/backend-common/src/reading/AzureUrlReader.test.ts +++ b/packages/backend-common/src/reading/AzureUrlReader.test.ts @@ -192,7 +192,6 @@ describe('AzureUrlReader', () => { const result = getDownloadUrl( 'https://dev.azure.com/organization/project/_git/repository?path=%2Fdocs', ); - console.log(result.searchParams); expect(result.searchParams.get('scopePath')).toEqual('docs'); }); }); diff --git a/packages/backend-common/src/reading/AzureUrlReader.ts b/packages/backend-common/src/reading/AzureUrlReader.ts index e5e290eb13..ad990d1d5d 100644 --- a/packages/backend-common/src/reading/AzureUrlReader.ts +++ b/packages/backend-common/src/reading/AzureUrlReader.ts @@ -101,10 +101,10 @@ export class AzureUrlReader implements UrlReader { url: string, options?: ReadTreeOptions, ): Promise { - const response = await fetch(getDownloadUrl(url).toString(), { - ...this.getRequestOptions(), - headers: { Accept: 'application/zip' }, - }); + const response = await fetch( + getDownloadUrl(url).toString(), + this.getRequestOptions({ Accept: 'application/zip' }), + ); if (!response.ok) { const message = `Failed to read tree from ${url}, ${response.status} ${response.statusText}`; if (response.status === 404) { @@ -178,8 +178,10 @@ export class AzureUrlReader implements UrlReader { } } - private getRequestOptions(): RequestInit { - const headers: HeadersInit = {}; + private getRequestOptions(additionalHeaders?: { + [key: string]: string; + }): RequestInit { + const headers: HeadersInit = additionalHeaders ?? {}; if (this.options.token) { headers.Authorization = `Basic ${Buffer.from( From 3092806838fe337c7d2e8089430e7d464cef4c63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Frinnstr=C3=B6m?= Date: Wed, 18 Nov 2020 16:28:27 +0100 Subject: [PATCH 112/430] Support ZIP files without directory entries --- .../src/reading/__fixtures__/repo.zip | Bin 629 -> 387 bytes .../src/reading/tree/ZipArchiveResponse.ts | 13 ++++++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/backend-common/src/reading/__fixtures__/repo.zip b/packages/backend-common/src/reading/__fixtures__/repo.zip index 47956335edad571c8153646cd5288916c9d8e5f4..f66bf2d612eafc3c4b65216b83d1b3e884726236 100644 GIT binary patch literal 387 zcmWIWW@h1H0D*0_(Sg%M&PT8V*&r;=Aj6QGpPa2*lv0=yB1 jV>%w$@CX#ck-Y*m8RQiVlUdn74q^hr?Lc}Ph{FH?Ktx*D literal 629 zcmWIWW@Zs#0D+CQ(ScwFl;8r=x%tW2x<#o4`T7BHb=%O?@uR3q$xki@D+Xz2U;rte zCUQQ49mob@aUez*3Raw%my%kcmz$!j5RzJ4!UeJjqIUyO@7K0B`vib`Kp5mqgx=h2 zkZ!%o+??XflGOOT#N1RXm-p=~S=0I>~^!vFvP diff --git a/packages/backend-common/src/reading/tree/ZipArchiveResponse.ts b/packages/backend-common/src/reading/tree/ZipArchiveResponse.ts index 048b2ab46d..89f9803bd2 100644 --- a/packages/backend-common/src/reading/tree/ZipArchiveResponse.ts +++ b/packages/backend-common/src/reading/tree/ZipArchiveResponse.ts @@ -136,13 +136,16 @@ export class ZipArchiveResponse implements ReadTreeResponse { if (this.shouldBeIncluded(entry)) { const entryPath = this.getPath(entry); if (entry.type === 'Directory') { - if (entryPath) { - fs.mkdirSync(path.join(dir, entryPath)); - } + // Ignore directory entries since we handle that with the file entries + // since a zip can have files with directories without directory entries entry.resume(); - } else { - entry.pipe(fs.createWriteStream(path.join(dir, entryPath))); + return; } + const dirname = path.dirname(entryPath); + if (dirname) { + fs.mkdirSync(path.join(dir, dirname), { recursive: true }); + } + entry.pipe(fs.createWriteStream(path.join(dir, entryPath))); } else { entry.autodrain(); } From f31d689d826e32e3c65b39e573a4bca1227dfe3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Frinnstr=C3=B6m?= Date: Wed, 18 Nov 2020 20:28:38 +0100 Subject: [PATCH 113/430] Switch to using async fs operations --- .../src/reading/tree/ZipArchiveResponse.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/packages/backend-common/src/reading/tree/ZipArchiveResponse.ts b/packages/backend-common/src/reading/tree/ZipArchiveResponse.ts index 89f9803bd2..4106d49a11 100644 --- a/packages/backend-common/src/reading/tree/ZipArchiveResponse.ts +++ b/packages/backend-common/src/reading/tree/ZipArchiveResponse.ts @@ -132,18 +132,14 @@ export class ZipArchiveResponse implements ReadTreeResponse { await this.stream .pipe(unzipper.Parse()) - .on('entry', (entry: Entry) => { - if (this.shouldBeIncluded(entry)) { + .on('entry', async (entry: Entry) => { + // Ignore directory entries since we handle that with the file entries + // as a zip can have files with directories without directory entries + if (entry.type === 'File' && this.shouldBeIncluded(entry)) { const entryPath = this.getPath(entry); - if (entry.type === 'Directory') { - // Ignore directory entries since we handle that with the file entries - // since a zip can have files with directories without directory entries - entry.resume(); - return; - } const dirname = path.dirname(entryPath); if (dirname) { - fs.mkdirSync(path.join(dir, dirname), { recursive: true }); + await fs.mkdirp(path.join(dir, dirname)); } entry.pipe(fs.createWriteStream(path.join(dir, entryPath))); } else { From 742196725f78d444324adc9ce88dc8e010dece13 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Fri, 20 Nov 2020 13:40:16 +0100 Subject: [PATCH 114/430] Fix some minor things from my previous PR See #3293 --- app-config.yaml | 4 ++-- .../src/ingestion/processors/microsoftGraph/client.test.ts | 1 - .../src/ingestion/processors/microsoftGraph/client.ts | 2 +- .../src/ingestion/processors/microsoftGraph/index.ts | 5 +++++ 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app-config.yaml b/app-config.yaml index 84464a98cf..32d3470f1b 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -143,8 +143,8 @@ catalog: microsoftGraphOrg: ### Example for how to add your Microsoft Graph tenant #providers: - # - target: https://graph.microsoft.com/v1.0/ - # authority: https://login.microsoftonline.com/ + # - target: https://graph.microsoft.com/v1.0 + # authority: https://login.microsoftonline.com # tenantId: # $env: MICROSOFT_GRAPH_TENANT_ID # clientId: diff --git a/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/client.test.ts b/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/client.test.ts index 526db38349..e51a7753af 100644 --- a/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/client.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/client.test.ts @@ -41,7 +41,6 @@ describe('MicrosoftGraphClient', () => { afterEach(() => { jest.resetAllMocks(); - worker.resetHandlers(); }); it('should perform raw request', async () => { diff --git a/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/client.ts b/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/client.ts index 98d4570e91..6dded56aa1 100644 --- a/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/client.ts +++ b/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/client.ts @@ -129,7 +129,7 @@ export class MicrosoftGraphClient { const photos = result.value as MicrosoftGraph.ProfilePhoto[]; let selectedPhoto: MicrosoftGraph.ProfilePhoto | undefined = undefined; - // Find the biggest picture that is small than the max size + // Find the biggest picture that is smaller than the max size for (const p of photos) { if ( !selectedPhoto || diff --git a/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/index.ts b/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/index.ts index 1ab567c78c..882125fd84 100644 --- a/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/index.ts +++ b/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/index.ts @@ -17,3 +17,8 @@ export { MicrosoftGraphClient } from './client'; export type { MicrosoftGraphProviderConfig } from './config'; export { readMicrosoftGraphConfig } from './config'; export { readMicrosoftGraphOrg } from './read'; +export { + MICROSOFT_GRAPH_GROUP_ID_ANNOTATION, + MICROSOFT_GRAPH_TENANT_ID_ANNOTATION, + MICROSOFT_GRAPH_USER_ID_ANNOTATION, +} from './constants'; From bd613dd8509b5173e050fff196e480f46b0f0d42 Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Fri, 20 Nov 2020 14:30:49 +0100 Subject: [PATCH 115/430] Code review adjustments --- plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts | 6 +++--- plugins/catalog-backend/src/ingestion/types.ts | 8 +++++++- plugins/catalog-backend/src/service/CatalogBuilder.ts | 4 ++-- plugins/catalog-backend/src/service/router.ts | 2 +- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts index 611505d707..97df326fc5 100644 --- a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts +++ b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts @@ -22,13 +22,13 @@ import { LocationAnalyzer, } from './types'; -export class LocationAnalyzerClient implements LocationAnalyzer { +export class RepoLocationAnalyzer implements LocationAnalyzer { private readonly logger: Logger; constructor(logger: Logger) { this.logger = logger; } - async generateConfig( + async analyzeLocation( request: AnalyzeLocationRequest, ): Promise { const { owner, name, source } = parseGitUri(request.location.target); @@ -40,7 +40,7 @@ export class LocationAnalyzerClient implements LocationAnalyzer { // Probably won't handle properly self-hosted git providers with custom url annotations: { [`${source}/project-slug`]: `${owner}/${name}` }, }, - spec: { type: 'other', owner: owner, lifecycle: 'unknown' }, + spec: { type: 'other', lifecycle: 'unknown' }, }; this.logger.debug(`entity created for ${request.location.target}`); diff --git a/plugins/catalog-backend/src/ingestion/types.ts b/plugins/catalog-backend/src/ingestion/types.ts index 79a5387247..c8d642b6b5 100644 --- a/plugins/catalog-backend/src/ingestion/types.ts +++ b/plugins/catalog-backend/src/ingestion/types.ts @@ -75,7 +75,13 @@ export type ReadLocationError = { // export type LocationAnalyzer = { - generateConfig( + /** + * Generates an entity configuration for given git repository. It's used for + * importing new component to the backstage app. + * + * @param location Git repository to analyze and generate config for. + */ + analyzeLocation( location: AnalyzeLocationRequest, ): Promise; }; diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts index 439b85591f..0fee610ce6 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 { LocationAnalyzerClient } from '../ingestion/LocationAnalyzer'; +import { RepoLocationAnalyzer } from '../ingestion/LocationAnalyzer'; import { BuiltinKindsEntityProcessor } from '../ingestion/processors/BuiltinKindsEntityProcessor'; import { LdapOrgReaderProcessor } from '../ingestion/processors/LdapOrgReaderProcessor'; import { @@ -232,7 +232,7 @@ export class CatalogBuilder { locationReader, logger, ); - const locationAnalyzer = new LocationAnalyzerClient(logger); + const locationAnalyzer = new RepoLocationAnalyzer(logger); return { entitiesCatalog, diff --git a/plugins/catalog-backend/src/service/router.ts b/plugins/catalog-backend/src/service/router.ts index d11ed49253..43c2967233 100644 --- a/plugins/catalog-backend/src/service/router.ts +++ b/plugins/catalog-backend/src/service/router.ts @@ -139,7 +139,7 @@ export async function createRouter( if (locationAnalyzer) { router.post('/analyze-location', async (req, res) => { const input = await validateRequestBody(req, analyzeLocationSchema); - const output = await locationAnalyzer.generateConfig(input); + const output = await locationAnalyzer.analyzeLocation(input); res.status(200).send(output); }); } From 11598f6324397c26352d926e1619f8dd8b0731cd Mon Sep 17 00:00:00 2001 From: blam Date: Fri, 20 Nov 2020 14:37:09 +0100 Subject: [PATCH 116/430] chore: fixing up the test timeout --- .../components/MultistepJsonForm/MultistepJsonForm.tsx | 9 +++------ .../src/components/TemplatePage/TemplatePage.tsx | 8 +++++--- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx index 9700d43c00..528d42b94f 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, useEffect } from 'react'; +import React, { useState } from 'react'; const Form = withTheme(MuiTheme); type Step = { @@ -54,7 +54,6 @@ export const MultistepJsonForm = ({ onFinish, }: Props) => { const [activeStep, setActiveStep] = useState(0); - const [formDataEvent, setFormDataEvent] = useState({ formData: {} }); const handleReset = () => { setActiveStep(0); @@ -63,9 +62,7 @@ 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 ( <> @@ -77,7 +74,7 @@ export const MultistepJsonForm = ({ key={label} noHtml5Validate formData={formData} - onChange={e => setFormDataEvent(e)} + onChange={onChange} 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 ebefc6522d..d8b803f893 100644 --- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx +++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx @@ -26,7 +26,7 @@ import { import { catalogApiRef } from '@backstage/plugin-catalog'; import { LinearProgress } from '@material-ui/core'; import { IChangeEvent } from '@rjsf/core'; -import React, { useState } from 'react'; +import React, { useState, useCallback } from 'react'; import { Navigate } from 'react-router'; import { useParams } from 'react-router-dom'; import { useAsync } from 'react-use'; @@ -86,8 +86,10 @@ export const TemplatePage = () => { const [formState, setFormState] = useState({}); const handleFormReset = () => setFormState({}); - const handleChange = (e: IChangeEvent) => - setFormState({ ...formState, ...e.formData }); + const handleChange = useCallback( + (e: IChangeEvent) => setFormState({ ...formState, ...e.formData }), + [setFormState, formState], + ); const [jobId, setJobId] = useState(null); From 475fc0aaa33b044265cf03724676fdafab489be6 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Fri, 20 Nov 2020 15:14:18 +0100 Subject: [PATCH 117/430] Make sidebar search field work (#3362) * Make sidebar search field work Extend the search page to have the ability to react to query parameters. The search in the sidebar now navigates to the search page and passes the query parameter. The search box on the search page is now debounced. Closes #3341 * Fix sidebar search while the search page is already open --- .changeset/breezy-cobras-deny.md | 5 +++ .changeset/small-worms-check.md | 6 ++++ packages/app/src/components/Root/Root.tsx | 10 ++---- packages/core/src/hooks/useQueryParamState.ts | 11 +++++- packages/core/src/layout/Sidebar/Items.tsx | 2 ++ plugins/search/package.json | 9 ++--- .../src/components/SearchPage/SearchPage.tsx | 22 ++++++++---- .../SidebarSearch/SidebarSearch.tsx | 35 +++++++++++++++++++ .../src/components/SidebarSearch/index.ts | 16 +++++++++ plugins/search/src/components/index.tsx | 1 + plugins/search/src/index.ts | 1 + 11 files changed, 99 insertions(+), 19 deletions(-) create mode 100644 .changeset/breezy-cobras-deny.md create mode 100644 .changeset/small-worms-check.md create mode 100644 plugins/search/src/components/SidebarSearch/SidebarSearch.tsx create mode 100644 plugins/search/src/components/SidebarSearch/index.ts diff --git a/.changeset/breezy-cobras-deny.md b/.changeset/breezy-cobras-deny.md new file mode 100644 index 0000000000..5f035c05ce --- /dev/null +++ b/.changeset/breezy-cobras-deny.md @@ -0,0 +1,5 @@ +--- +'@backstage/core': patch +--- + +Clear sidebar search field once a search is executed diff --git a/.changeset/small-worms-check.md b/.changeset/small-worms-check.md new file mode 100644 index 0000000000..e39265f8f9 --- /dev/null +++ b/.changeset/small-worms-check.md @@ -0,0 +1,6 @@ +--- +'example-app': patch +'@backstage/plugin-search': patch +--- + +Using the search field in the sidebar now navigates to the search result page. diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx index e947e5c91d..47e7d32d1a 100644 --- a/packages/app/src/components/Root/Root.tsx +++ b/packages/app/src/components/Root/Root.tsx @@ -33,12 +33,12 @@ import { SidebarContext, SidebarItem, SidebarDivider, - SidebarSearchField, SidebarSpace, } from '@backstage/core'; import { NavLink } from 'react-router-dom'; import { graphiQLRouteRef } from '@backstage/plugin-graphiql'; import { Settings as SidebarSettings } from '@backstage/plugin-user-settings'; +import { SidebarSearch } from '@backstage/plugin-search'; const useSidebarLogoStyles = makeStyles({ root: { @@ -73,17 +73,11 @@ const SidebarLogo: FC<{}> = () => { ); }; -const handleSearch = (query: string): void => { - // XXX (@koroeskohr): for testing purposes - // eslint-disable-next-line no-console - console.log(query); -}; - const Root: FC<{}> = ({ children }) => ( - + {/* Global nav, not org-specific */} diff --git a/packages/core/src/hooks/useQueryParamState.ts b/packages/core/src/hooks/useQueryParamState.ts index 9317dcd3e1..dc2279cb53 100644 --- a/packages/core/src/hooks/useQueryParamState.ts +++ b/packages/core/src/hooks/useQueryParamState.ts @@ -14,8 +14,9 @@ * limitations under the License. */ +import { isEqual } from 'lodash'; import qs from 'qs'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; import { useDebounce } from 'react-use'; @@ -64,6 +65,14 @@ export function useQueryParamState( extractState(location.search, stateName), ); + useEffect(() => { + const newState = extractState(location.search, stateName); + + setQueryParamState(oldState => + isEqual(newState, oldState) ? oldState : newState, + ); + }, [location, stateName]); + useDebounce( () => { const queryString = joinQueryString( diff --git a/packages/core/src/layout/Sidebar/Items.tsx b/packages/core/src/layout/Sidebar/Items.tsx index bb47701280..498946577d 100644 --- a/packages/core/src/layout/Sidebar/Items.tsx +++ b/packages/core/src/layout/Sidebar/Items.tsx @@ -221,6 +221,7 @@ export const SidebarSearchField: FC = props => { const handleEnter: KeyboardEventHandler = ev => { if (ev.key === 'Enter') { props.onSearch(input); + setInput(''); } }; @@ -233,6 +234,7 @@ export const SidebarSearchField: FC = props => { { - const [searchQuery, setSearchQuery] = useState(''); + const [queryString, setQueryString] = useQueryParamState('query'); + const [searchQuery, setSearchQuery] = useState(queryString ?? ''); const handleSearch = (event: React.ChangeEvent) => { event.preventDefault(); setSearchQuery(event.target.value); }; + useEffect(() => setSearchQuery(queryString ?? ''), [queryString]); + + useDebounce( + () => { + setQueryString(searchQuery); + }, + 200, + [searchQuery], + ); + const handleClearSearchBar = () => { setSearchQuery(''); }; @@ -46,7 +56,7 @@ export const SearchPage = () => { /> - + diff --git a/plugins/search/src/components/SidebarSearch/SidebarSearch.tsx b/plugins/search/src/components/SidebarSearch/SidebarSearch.tsx new file mode 100644 index 0000000000..6a279e0743 --- /dev/null +++ b/plugins/search/src/components/SidebarSearch/SidebarSearch.tsx @@ -0,0 +1,35 @@ +/* + * 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, { useCallback } from 'react'; +import qs from 'qs'; +import { useNavigate } from 'react-router-dom'; +import { SidebarSearchField } from '@backstage/core'; + +export const SidebarSearch = () => { + const navigate = useNavigate(); + const handleSearch = useCallback( + (query: string): void => { + const queryString = qs.stringify({ query }, { addQueryPrefix: true }); + + // TODO: Here the url to the search plugin is hardcoded. We need a way to query the route in the future. + // Maybe an API that I can just call from other places? + navigate(`/search${queryString}`); + }, + [navigate], + ); + + return ; +}; diff --git a/plugins/search/src/components/SidebarSearch/index.ts b/plugins/search/src/components/SidebarSearch/index.ts new file mode 100644 index 0000000000..33869ffb77 --- /dev/null +++ b/plugins/search/src/components/SidebarSearch/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 { SidebarSearch } from './SidebarSearch'; diff --git a/plugins/search/src/components/index.tsx b/plugins/search/src/components/index.tsx index f8e6a5a09e..ac47860dc2 100644 --- a/plugins/search/src/components/index.tsx +++ b/plugins/search/src/components/index.tsx @@ -18,3 +18,4 @@ export * from './Filters'; export * from './SearchBar'; export * from './SearchPage'; export * from './SearchResult'; +export * from './SidebarSearch'; diff --git a/plugins/search/src/index.ts b/plugins/search/src/index.ts index 224e293890..77ad7f9266 100644 --- a/plugins/search/src/index.ts +++ b/plugins/search/src/index.ts @@ -14,3 +14,4 @@ * limitations under the License. */ export { plugin } from './plugin'; +export * from './components'; From 74c43ce23193ea6eb4db4f3109be32cc3e152f6c Mon Sep 17 00:00:00 2001 From: Shashank Bairy R Date: Fri, 20 Nov 2020 19:52:49 +0530 Subject: [PATCH 118/430] feat: improve search match (#3365) --- plugins/search/src/components/SearchResult/SearchResult.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/search/src/components/SearchResult/SearchResult.tsx b/plugins/search/src/components/SearchResult/SearchResult.tsx index e4163c0de7..1670c13963 100644 --- a/plugins/search/src/components/SearchResult/SearchResult.tsx +++ b/plugins/search/src/components/SearchResult/SearchResult.tsx @@ -159,6 +159,9 @@ export const SearchResult = ({ searchQuery }: SearchResultProps) => { withFilters = withFilters.filter( (result: Result) => result.name?.toLowerCase().includes(searchQuery) || + result.name + ?.toLowerCase() + .includes(searchQuery.split(' ').join('-')) || result.description?.toLowerCase().includes(searchQuery), ); } From a9fd599f7a374ec14c83c964ed7c6054813feb33 Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Fri, 20 Nov 2020 15:41:47 +0100 Subject: [PATCH 119/430] Add chengeset --- .changeset/smart-turkeys-bathe.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .changeset/smart-turkeys-bathe.md diff --git a/.changeset/smart-turkeys-bathe.md b/.changeset/smart-turkeys-bathe.md new file mode 100644 index 0000000000..9e8ca1270e --- /dev/null +++ b/.changeset/smart-turkeys-bathe.md @@ -0,0 +1,10 @@ +--- +'@backstage/plugin-catalog-backend': minor +'@backstage/plugin-catalog-import': minor +'example-app': patch +'example-backend': patch +'@backstage/catalog-model': patch +'@backstage/plugin-scaffolder': patch +--- + +Add Analyze location endpoint to catalog backend. Add catalog-import plugin and replace import-component with it. From ece530ab9538231885220c015158d89edd857a44 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 20 Nov 2020 15:42:25 +0100 Subject: [PATCH 120/430] remove dependabot config --- .github/dependabot.yml | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 59847fd65f..0000000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,18 +0,0 @@ -version: 2 -updates: - - package-ecosystem: npm - directory: '/' - schedule: - interval: daily - time: '04:00' - open-pull-requests-limit: 5 - labels: - - dependencies - - package-ecosystem: npm - directory: '/microsite/' - schedule: - interval: daily - time: '04:00' - open-pull-requests-limit: 2 - labels: - - dependencies From 7a8476a3fba78f21e35ad567670838ea180b0917 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 20 Nov 2020 15:44:18 +0100 Subject: [PATCH 121/430] Create dependabot.yml --- .github/dependabot.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..c3ece8a6f0 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,18 @@ +version: 2 +updates: + - package-ecosystem: npm + directory: '/' + schedule: + interval: daily + time: '04:00' + open-pull-requests-limit: 5 + labels: + - dependencies + - package-ecosystem: npm + directory: '/microsite/' + schedule: + interval: daily + time: '04:00' + open-pull-requests-limit: 2 + labels: + - dependencies From 2c74aa7e796e7fa7eceef63325d47b3a9b72e3c1 Mon Sep 17 00:00:00 2001 From: Mateusz Lewtak Date: Fri, 20 Nov 2020 15:50:33 +0100 Subject: [PATCH 122/430] Feat: update plugin versions --- packages/app/package.json | 8 +- .../components/catalog/EntityPage.test.tsx | 6 +- .../app/src/components/catalog/EntityPage.tsx | 8 +- packages/app/src/plugins.ts | 2 +- yarn.lock | 86 ++++++------------- 5 files changed, 37 insertions(+), 73 deletions(-) diff --git a/packages/app/package.json b/packages/app/package.json index 529c5d4f63..042fd47188 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -35,10 +35,10 @@ "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@octokit/rest": "^18.0.0", - "@roadiehq/backstage-plugin-github-insights": "^0.2.14", - "@roadiehq/backstage-plugin-github-pull-requests": "^0.6.2", - "@roadiehq/backstage-plugin-travis-ci": "^0.2.7", - "@roadiehq/backstage-plugin-buildkite": "^0.1.2", + "@roadiehq/backstage-plugin-github-insights": "^0.2.15", + "@roadiehq/backstage-plugin-github-pull-requests": "^0.6.3", + "@roadiehq/backstage-plugin-travis-ci": "^0.2.8", + "@roadiehq/backstage-plugin-buildkite": "^0.1.3", "history": "^5.0.0", "prop-types": "^15.7.2", "react": "^16.12.0", diff --git a/packages/app/src/components/catalog/EntityPage.test.tsx b/packages/app/src/components/catalog/EntityPage.test.tsx index f4dfafc7d3..1392f77a6d 100644 --- a/packages/app/src/components/catalog/EntityPage.test.tsx +++ b/packages/app/src/components/catalog/EntityPage.test.tsx @@ -18,7 +18,7 @@ import { CICDSwitcher } from './EntityPage'; import { UrlPatternDiscovery, ApiProvider, ApiRegistry } from '@backstage/core'; import { buildKiteApiRef, - BuildKiteApi, + BuildkiteApi, } from '@roadiehq/backstage-plugin-buildkite'; import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils'; @@ -42,11 +42,11 @@ describe('EntityPage Test', () => { const discoveryApi = UrlPatternDiscovery.compile('http://exampleapi.com'); const apis = ApiRegistry.from([ - [buildKiteApiRef, new BuildKiteApi({ discoveryApi })], + [buildKiteApiRef, new BuildkiteApi({ discoveryApi })], ]); describe('CICDSwitcher Test', () => { - it('Should render BuildKite View', async () => { + it('Should render Buildkite View', async () => { const renderedComponent = await renderWithEffects( wrapInTestApp( diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index 73d84ef1ca..d826bfa25e 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -67,8 +67,8 @@ import { PullRequestsStatsCard, } from '@roadiehq/backstage-plugin-github-pull-requests'; import { - Router as BuildKiteRouter, - isPluginApplicableToEntity as isBuildKiteAvailable, + Router as BuildkiteRouter, + isPluginApplicableToEntity as isBuildkiteAvailable, } from '@roadiehq/backstage-plugin-buildkite'; export const CICDSwitcher = ({ entity }: { entity: Entity }) => { @@ -77,8 +77,8 @@ export const CICDSwitcher = ({ entity }: { entity: Entity }) => { switch (true) { case isJenkinsAvailable(entity): return ; - case isBuildKiteAvailable(entity): - return ; + case isBuildkiteAvailable(entity): + return ; case isGitHubActionsAvailable(entity): return ; case isCircleCIAvailable(entity): diff --git a/packages/app/src/plugins.ts b/packages/app/src/plugins.ts index d6577b4ce4..87b4c0ceea 100644 --- a/packages/app/src/plugins.ts +++ b/packages/app/src/plugins.ts @@ -38,5 +38,5 @@ 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 BuildKite } from '@roadiehq/backstage-plugin-buildkite'; +export { plugin as Buildkite } from '@roadiehq/backstage-plugin-buildkite'; export { plugin as Search } from '@backstage/plugin-search'; diff --git a/yarn.lock b/yarn.lock index 58d9c546ee..96e3c4bf89 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1307,42 +1307,6 @@ lodash "^4.17.19" to-fast-properties "^2.0.0" -"@backstage/core@^0.2.0": - version "0.3.1" - dependencies: - "@backstage/config" "^0.1.1" - "@backstage/core-api" "^0.2.1" - "@backstage/theme" "^0.2.1" - "@material-ui/core" "^4.11.0" - "@material-ui/icons" "^4.9.1" - "@material-ui/lab" "4.0.0-alpha.45" - "@types/dagre" "^0.7.44" - "@types/react" "^16.9" - "@types/react-sparklines" "^1.7.0" - classnames "^2.2.6" - clsx "^1.1.0" - d3-selection "^2.0.0" - d3-shape "^2.0.0" - d3-zoom "^2.0.0" - dagre "^0.8.5" - immer "^7.0.9" - lodash "^4.17.15" - material-table "^1.69.1" - prop-types "^15.7.2" - qs "^6.9.4" - rc-progress "^3.0.0" - react "^16.12.0" - react-dom "^16.12.0" - react-helmet "6.1.0" - react-hook-form "^6.6.0" - react-markdown "^5.0.2" - react-router "6.0.0-beta.0" - react-router-dom "6.0.0-beta.0" - react-sparklines "^1.7.0" - react-syntax-highlighter "^13.5.1" - react-use "^15.3.3" - remark-gfm "^1.0.0" - "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" @@ -3710,14 +3674,14 @@ resolved "https://registry.npmjs.org/@rjsf/material-ui/-/material-ui-2.4.0.tgz#1b5859298bf3f61137d7b05084f058a775d6fd73" integrity sha512-U8F/suzg4MuV+8mK1/ufs0Y6c3O8hc1wnuD2IKoOVJvegGfz5JCafyoyGAW6iyuT1DZBMPzVWEqfiuYPmoE7pw== -"@roadiehq/backstage-plugin-buildkite@^0.1.2": - version "0.1.2" - resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-buildkite/-/backstage-plugin-buildkite-0.1.2.tgz#2f2b414acc18ed7820abc6fb0c042f00c31d7c9a" - integrity sha512-NO1ogAK6lfh/YUmftqbYn6pkUmMJXk9cfda0YDnG23XlcKIst1d3xO5FggozVtU5IZiJDxxwxOOo4ylhBWrGeQ== +"@roadiehq/backstage-plugin-buildkite@^0.1.3": + version "0.1.3" + resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-buildkite/-/backstage-plugin-buildkite-0.1.3.tgz#5a116bf677dfad22088212dde398cf3e9b48d6e4" + integrity sha512-q+cnAvZmjLu0DcqRKJZJhWTVcKaA9j7cM44tx6JcshEyb3UgzrlECLvx4ethnYyrmhM1/FSpMLa+t3N5A1Xz4g== dependencies: "@backstage/catalog-model" "^0.2.0" - "@backstage/core" "^0.2.0" - "@backstage/plugin-catalog" "^0.2.0" + "@backstage/core" "^0.3.0" + "@backstage/plugin-catalog" "^0.2.1" "@material-ui/core" "^4.11.0" "@material-ui/icons" "^4.9.1" "@material-ui/lab" "4.0.0-alpha.45" @@ -3729,14 +3693,14 @@ react-router-dom "6.0.0-beta.0" react-use "^15.3.3" -"@roadiehq/backstage-plugin-github-insights@^0.2.14": - version "0.2.14" - resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-github-insights/-/backstage-plugin-github-insights-0.2.14.tgz#2e4bf61495e650bb2b7a1711f97c9c4995215588" - integrity sha512-xhzHAmmTu7op1V7K3ytomGlmzHMs9jnb2Lc2YTrU3ame4WZcrrMCrqi8X9v8B8VHfwMBrwa55tyxRYObXrPr7A== +"@roadiehq/backstage-plugin-github-insights@^0.2.15": + version "0.2.15" + resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-github-insights/-/backstage-plugin-github-insights-0.2.15.tgz#b127e7795d3a2440286548fc33af49f12ba42b7f" + integrity sha512-4jLDHlr5B77Kveb7Q1q78mbMATufJLgS/zy4T+1LW/54KKmJrset4AWrZLTAOwjkjOfDfO85NEwmgsbrprAGrg== dependencies: "@backstage/catalog-model" "^0.2.0" - "@backstage/core" "^0.2.0" - "@backstage/theme" "^0.2.0" + "@backstage/core" "^0.3.0" + "@backstage/theme" "^0.2.1" "@material-ui/core" "^4.11.0" "@material-ui/icons" "^4.9.1" "@material-ui/lab" "4.0.0-alpha.45" @@ -3748,13 +3712,13 @@ react-router "^6.0.0-beta.0" react-use "^15.3.3" -"@roadiehq/backstage-plugin-github-pull-requests@^0.6.2": - version "0.6.2" - resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-github-pull-requests/-/backstage-plugin-github-pull-requests-0.6.2.tgz#02f4a7a03e1a7dc24342ec695130017ce7d3ad8d" - integrity sha512-OdBWO6NdiBKlol1WlbFQGeHxV2C8wl8EiI2NgA15lozsqFA22kWis6Z16cAHe4ZdRCjihDmqINtAIJzeZZ1gOg== +"@roadiehq/backstage-plugin-github-pull-requests@^0.6.3": + version "0.6.3" + resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-github-pull-requests/-/backstage-plugin-github-pull-requests-0.6.3.tgz#46b63e90f3f5412a4b8f0df96ada62cff7046478" + integrity sha512-ofWH9k4WVVwTbK/XAVqmtH03QW3rsT822p4neOse0Wy0dAKlqlSU4nwl5jBKMJXsf8lfc0c7gbc50R7VumzoOQ== dependencies: "@backstage/catalog-model" "^0.2.0" - "@backstage/core" "^0.2.0" + "@backstage/core" "^0.3.0" "@backstage/plugin-catalog" "^0.2.0" "@backstage/theme" "^0.2.0" "@material-ui/core" "^4.11.0" @@ -3770,16 +3734,16 @@ react-router "6.0.0-beta.0" react-use "^15.3.3" -"@roadiehq/backstage-plugin-travis-ci@^0.2.7": - version "0.2.7" - resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-travis-ci/-/backstage-plugin-travis-ci-0.2.7.tgz#bc7968b461016b2710794d10766266de9ab4f759" - integrity sha512-uXF5t2uZqd9TNGFSMYlyk6NAweVb5KLlM4GMiltattzxRWiqbCp5VumubrLTTsA8dvsWCrXWfhe95MStcUnl+A== +"@roadiehq/backstage-plugin-travis-ci@^0.2.8": + version "0.2.8" + resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-travis-ci/-/backstage-plugin-travis-ci-0.2.8.tgz#c730531519bf3e3dba35522d5e6c822176ddb9c8" + integrity sha512-8R8waHoviT5chHGybAMVVRHHmeytn+8Po7sU08WCYIt4Vkw2/gwu36fd90y+lW4x6T0s3ZiaJrsQ3KlA7LmRnQ== dependencies: "@backstage/catalog-model" "^0.2.0" - "@backstage/core" "^0.2.0" - "@backstage/core-api" "^0.2.0" - "@backstage/plugin-catalog" "^0.2.0" - "@backstage/theme" "^0.2.0" + "@backstage/core" "^0.3.0" + "@backstage/core-api" "^0.2.1" + "@backstage/plugin-catalog" "^0.2.1" + "@backstage/theme" "^0.2.1" "@material-ui/core" "^4.9.1" "@material-ui/icons" "^4.9.1" "@material-ui/lab" "4.0.0-alpha.45" From 0a559f4500e015cfb719a61d4d74e9b8cef1437f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 20 Nov 2020 15:51:17 +0100 Subject: [PATCH 123/430] Update dependabot.yml --- .github/dependabot.yml | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index c3ece8a6f0..59847fd65f 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,18 +1,18 @@ -version: 2 -updates: - - package-ecosystem: npm - directory: '/' - schedule: - interval: daily - time: '04:00' - open-pull-requests-limit: 5 - labels: - - dependencies - - package-ecosystem: npm - directory: '/microsite/' - schedule: - interval: daily - time: '04:00' - open-pull-requests-limit: 2 - labels: +version: 2 +updates: + - package-ecosystem: npm + directory: '/' + schedule: + interval: daily + time: '04:00' + open-pull-requests-limit: 5 + labels: + - dependencies + - package-ecosystem: npm + directory: '/microsite/' + schedule: + interval: daily + time: '04:00' + open-pull-requests-limit: 2 + labels: - dependencies From d74f239d5cf4e29af2767ebf28016b3242e5a2d3 Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Fri, 20 Nov 2020 16:03:51 +0100 Subject: [PATCH 124/430] Bump packages and add migration instruction Migration instruction added to the changeset file. --- .changeset/smart-turkeys-bathe.md | 22 ++++++++++++++++++++- packages/backend/package.json | 2 +- plugins/catalog-backend/package.json | 2 +- plugins/catalog-import/package.json | 2 +- yarn.lock | 29 ++++++++++++++++++++++++++++ 5 files changed, 53 insertions(+), 4 deletions(-) diff --git a/.changeset/smart-turkeys-bathe.md b/.changeset/smart-turkeys-bathe.md index 9e8ca1270e..f7776a81aa 100644 --- a/.changeset/smart-turkeys-bathe.md +++ b/.changeset/smart-turkeys-bathe.md @@ -7,4 +7,24 @@ '@backstage/plugin-scaffolder': patch --- -Add Analyze location endpoint to catalog backend. Add catalog-import plugin and replace import-component with it. +Add Analyze location endpoint to catalog backend. Add catalog-import plugin and replace import-component with it. To start using Analyze location endpoint, you have add it to the `createRouter` function options in the `\backstage\packages\backend\src\plugins\catalog.ts` file: + +```ts +export default async function createPlugin(env: PluginEnvironment) { + const builder = new CatalogBuilder(env); + const { + entitiesCatalog, + locationsCatalog, + higherOrderOperation, + locationAnalyzer, //<-- + } = await builder.build(); + + return await createRouter({ + entitiesCatalog, + locationsCatalog, + higherOrderOperation, + locationAnalyzer, //<-- + logger: env.logger, + }); +} +``` diff --git a/packages/backend/package.json b/packages/backend/package.json index 014a3f78d5..5011c21c14 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -23,7 +23,7 @@ "@backstage/config": "^0.1.1", "@backstage/plugin-app-backend": "^0.3.0", "@backstage/plugin-auth-backend": "^0.2.2", - "@backstage/plugin-catalog-backend": "^0.2.1", + "@backstage/plugin-catalog-backend": "^0.3.0", "@backstage/plugin-graphql-backend": "^0.1.3", "@backstage/plugin-kubernetes-backend": "^0.1.3", "@backstage/plugin-proxy-backend": "^0.2.1", diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 711a91dd8d..36ebbedab4 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend", - "version": "0.2.1", + "version": "0.3.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index 2fbea6a17c..ac41d70c30 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -24,7 +24,7 @@ "@backstage/catalog-model": "^0.2.0", "@backstage/core": "^0.3.1", "@backstage/plugin-catalog": "^0.2.0", - "@backstage/plugin-catalog-backend": "^0.2.0", + "@backstage/plugin-catalog-backend": "^0.3.0", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", diff --git a/yarn.lock b/yarn.lock index c7e518ede3..23e422b9ad 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1325,6 +1325,35 @@ react-use "^15.3.3" remark-gfm "^1.0.0" +"@backstage/plugin-catalog-backend@^0.2.1": + version "0.2.1" + resolved "https://registry.npmjs.org/@backstage/plugin-catalog-backend/-/plugin-catalog-backend-0.2.1.tgz#d63d1d145d97a5f1021c3e6c2ab680729be43e74" + integrity sha512-9OwM7JobjCvmNomsT2HbMyzgoWX+ztVGG4ebN3x/r75+sI55Dj3J29O9CRP7r86BIJ7bi1RWaLTycXX+CmuQ0Q== + dependencies: + "@backstage/backend-common" "^0.3.0" + "@backstage/catalog-model" "^0.2.0" + "@backstage/config" "^0.1.1" + "@octokit/graphql" "^4.5.6" + "@types/express" "^4.17.6" + codeowners-utils "^1.0.2" + core-js "^3.6.5" + cross-fetch "^3.0.6" + express "^4.17.1" + express-promise-router "^3.0.3" + fs-extra "^9.0.0" + git-url-parse "^11.4.0" + knex "^0.21.6" + ldapjs "^2.2.0" + lodash "^4.17.15" + morgan "^1.10.0" + p-limit "^3.0.2" + sqlite3 "^5.0.0" + uuid "^8.0.0" + winston "^3.2.1" + yaml "^1.9.2" + yn "^4.0.0" + yup "^0.29.3" + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" From 36b9dfd41cae69b32bdfa78d0fc8aef757e4f3fa Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Fri, 20 Nov 2020 16:20:13 +0100 Subject: [PATCH 125/430] Rework the search field behavior in the sidebar We just tested it with another user an noticed some more issues: * If the user is on the search route it should be reflected in the sidebar * Clicking on the search icon alone should lead to the search page --- packages/core/src/layout/Sidebar/Items.tsx | 12 +++++++++--- .../src/components/SidebarSearch/SidebarSearch.tsx | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/core/src/layout/Sidebar/Items.tsx b/packages/core/src/layout/Sidebar/Items.tsx index 498946577d..b8786886cc 100644 --- a/packages/core/src/layout/Sidebar/Items.tsx +++ b/packages/core/src/layout/Sidebar/Items.tsx @@ -26,7 +26,6 @@ import { IconComponent } from '@backstage/core-api'; import SearchIcon from '@material-ui/icons/Search'; import clsx from 'clsx'; import React, { - FC, useContext, useState, KeyboardEventHandler, @@ -212,9 +211,10 @@ export const SidebarItem = forwardRef( type SidebarSearchFieldProps = { onSearch: (input: string) => void; + to?: string; }; -export const SidebarSearchField: FC = props => { +export const SidebarSearchField = (props: SidebarSearchFieldProps) => { const [input, setInput] = useState(''); const classes = useStyles(); @@ -229,12 +229,18 @@ export const SidebarSearchField: FC = props => { setInput(ev.target.value); }; + const handleClick = (ev: React.MouseEvent) => { + // Clicking into the search fields shouldn't navigate to the search page + ev.preventDefault(); + }; + return (
- + { [navigate], ); - return ; + return ; }; From 9d69a87ee28d8f271a26f4a8749cb7260c4f4ad9 Mon Sep 17 00:00:00 2001 From: Ryan Vazquez Date: Fri, 20 Nov 2020 10:26:11 -0500 Subject: [PATCH 126/430] truncate large percentages --- .../src/components/BarChart/BarChartTooltip.tsx | 11 ++++++++--- .../cost-insights/src/utils/formatters.test.ts | 15 +++++++++++++++ plugins/cost-insights/src/utils/formatters.ts | 6 ++++++ plugins/cost-insights/src/utils/styles.ts | 3 +++ 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/plugins/cost-insights/src/components/BarChart/BarChartTooltip.tsx b/plugins/cost-insights/src/components/BarChart/BarChartTooltip.tsx index 8ab787a443..64a35e2658 100644 --- a/plugins/cost-insights/src/components/BarChart/BarChartTooltip.tsx +++ b/plugins/cost-insights/src/components/BarChart/BarChartTooltip.tsx @@ -15,6 +15,7 @@ */ import React, { ReactNode, PropsWithChildren } from 'react'; +import classnames from 'classnames'; import { Box, Divider, Typography } from '@material-ui/core'; import { useTooltipStyles as useStyles } from '../../utils/styles'; @@ -35,6 +36,10 @@ export const BarChartTooltip = ({ children, }: PropsWithChildren) => { const classes = useStyles(); + const titleClassName = classnames(classes.truncate, { + [classes.maxWidth]: topRight === undefined, + }); + return ( - + {title} {subtitle && ( @@ -55,10 +60,10 @@ export const BarChartTooltip = ({ )} - {topRight && {topRight}} + {topRight && {topRight}} {content && ( - + {content} diff --git a/plugins/cost-insights/src/utils/formatters.test.ts b/plugins/cost-insights/src/utils/formatters.test.ts index 69e9086be1..db25b6f3b7 100644 --- a/plugins/cost-insights/src/utils/formatters.test.ts +++ b/plugins/cost-insights/src/utils/formatters.test.ts @@ -16,6 +16,7 @@ import { formatPeriod, + formatPercent, lengthyCurrencyFormatter, quarterOf, } from './formatters'; @@ -69,3 +70,17 @@ describe.each` expect(formatPeriod(duration, date, isEndDate)).toBe(output); }); }); + +describe.each` + ratio | expected + ${0.0} | ${'0%'} + ${0.000000000001} | ${'0%'} + ${-0.00000000001} | ${'0%'} + ${0.123123} | ${'12%'} + ${1.123} | ${'112%'} + ${10.123} | ${'>1000%'} +`('formatPercent', ({ ratio, expected }) => { + it(`correctly formats ${ratio} as ${expected}`, () => { + expect(formatPercent(ratio)).toBe(expected); + }); +}); diff --git a/plugins/cost-insights/src/utils/formatters.ts b/plugins/cost-insights/src/utils/formatters.ts index 42505e7072..19255d3c56 100644 --- a/plugins/cost-insights/src/utils/formatters.ts +++ b/plugins/cost-insights/src/utils/formatters.ts @@ -83,9 +83,15 @@ export function formatPercent(n: number): string { if (isNaN(n) || Math.abs(n) < 0.01) { return '0%'; } + + if (Math.abs(n) > 10) { + return `>1000%`; + } + if (Math.abs(n) >= 1e19) { return '∞%'; } + return `${(n * 100).toFixed(0)}%`; } diff --git a/plugins/cost-insights/src/utils/styles.ts b/plugins/cost-insights/src/utils/styles.ts index 1e8fd50899..542896d6fc 100644 --- a/plugins/cost-insights/src/utils/styles.ts +++ b/plugins/cost-insights/src/utils/styles.ts @@ -386,6 +386,9 @@ export const useTooltipStyles = makeStyles( boxShadow: theme.shadows[1], color: theme.palette.tooltip.color, fontSize: theme.typography.fontSize, + minWidth: 300, + }, + maxWidth: { maxWidth: 300, }, actions: { From c93a14b496cb3dd005dc1b96b23179bc96fc34c0 Mon Sep 17 00:00:00 2001 From: Ryan Vazquez Date: Fri, 20 Nov 2020 10:29:14 -0500 Subject: [PATCH 127/430] changeset --- .changeset/cost-insights-tiny-llamas-perform.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/cost-insights-tiny-llamas-perform.md diff --git a/.changeset/cost-insights-tiny-llamas-perform.md b/.changeset/cost-insights-tiny-llamas-perform.md new file mode 100644 index 0000000000..c81c28f8f8 --- /dev/null +++ b/.changeset/cost-insights-tiny-llamas-perform.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-cost-insights': patch +--- + +truncate large percentages > 1000% From 4ff4a25e4096cc9dc7084d12ecd7d845cfc7fc67 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Nov 2020 15:33:39 +0000 Subject: [PATCH 128/430] build(deps-dev): bump prettier from 2.1.2 to 2.2.0 in /microsite Bumps [prettier](https://github.com/prettier/prettier) from 2.1.2 to 2.2.0. - [Release notes](https://github.com/prettier/prettier/releases) - [Changelog](https://github.com/prettier/prettier/blob/master/CHANGELOG.md) - [Commits](https://github.com/prettier/prettier/compare/2.1.2...2.2.0) Signed-off-by: dependabot[bot] --- microsite/package.json | 2 +- microsite/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/microsite/package.json b/microsite/package.json index a84364c214..3b5ab2eeb4 100644 --- a/microsite/package.json +++ b/microsite/package.json @@ -17,7 +17,7 @@ "@spotify/prettier-config": "^9.0.0", "docusaurus": "^2.0.0-alpha.66", "js-yaml": "^3.14.0", - "prettier": "^2.0.5" + "prettier": "^2.2.0" }, "prettier": "@spotify/prettier-config" } diff --git a/microsite/yarn.lock b/microsite/yarn.lock index b120cb2d47..2147373bfe 100644 --- a/microsite/yarn.lock +++ b/microsite/yarn.lock @@ -5204,10 +5204,10 @@ prepend-http@^2.0.0: resolved "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= -prettier@^2.0.5: - version "2.1.2" - resolved "https://registry.npmjs.org/prettier/-/prettier-2.1.2.tgz#3050700dae2e4c8b67c4c3f666cdb8af405e1ce5" - integrity sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg== +prettier@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.2.0.tgz#8a03c7777883b29b37fb2c4348c66a78e980418b" + integrity sha512-yYerpkvseM4iKD/BXLYUkQV5aKt4tQPqaGW6EsZjzyu0r7sVZZNPJW4Y8MyKmicp6t42XUPcBVA+H6sB3gqndw== prismjs@^1.17.1: version "1.21.0" From 640c59fae735e4fb251f5678a4008da7ff8b2f87 Mon Sep 17 00:00:00 2001 From: Ryan Vazquez Date: Fri, 20 Nov 2020 10:59:34 -0500 Subject: [PATCH 129/430] remove unreachable condition --- plugins/cost-insights/src/utils/formatters.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/plugins/cost-insights/src/utils/formatters.ts b/plugins/cost-insights/src/utils/formatters.ts index 19255d3c56..9c3b2d643e 100644 --- a/plugins/cost-insights/src/utils/formatters.ts +++ b/plugins/cost-insights/src/utils/formatters.ts @@ -88,10 +88,6 @@ export function formatPercent(n: number): string { return `>1000%`; } - if (Math.abs(n) >= 1e19) { - return '∞%'; - } - return `${(n * 100).toFixed(0)}%`; } From 26484d413304cdfacaa6a53af9a7fc6678a2213d Mon Sep 17 00:00:00 2001 From: Dominik Henneke Date: Fri, 20 Nov 2020 16:54:10 +0100 Subject: [PATCH 130/430] Add config schema for the sonarqube plugin --- .changeset/long-flowers-wink.md | 5 +++++ plugins/sonarqube/config.d.ts | 26 ++++++++++++++++++++++++++ plugins/sonarqube/package.json | 6 ++++-- 3 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 .changeset/long-flowers-wink.md create mode 100644 plugins/sonarqube/config.d.ts diff --git a/.changeset/long-flowers-wink.md b/.changeset/long-flowers-wink.md new file mode 100644 index 0000000000..7f9cf12315 --- /dev/null +++ b/.changeset/long-flowers-wink.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-sonarqube': patch +--- + +Add configuration schema diff --git a/plugins/sonarqube/config.d.ts b/plugins/sonarqube/config.d.ts new file mode 100644 index 0000000000..5facd9cef7 --- /dev/null +++ b/plugins/sonarqube/config.d.ts @@ -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. + */ + +export interface Config { + /** Optional configurations for the SonarQube plugin */ + sonarQube?: { + /** + * The base url of the sonarqube installation. Defaults to https://sonarcloud.io. + * @visibility frontend + */ + baseUrl: string; + }; +} diff --git a/plugins/sonarqube/package.json b/plugins/sonarqube/package.json index dcde8fab14..be8a878e50 100644 --- a/plugins/sonarqube/package.json +++ b/plugins/sonarqube/package.json @@ -47,6 +47,8 @@ "msw": "^0.21.2" }, "files": [ - "dist" - ] + "dist", + "config.d.ts" + ], + "configSchema": "config.d.ts" } From b3fb9a08cee409ec309b041e96db4048ffaa41f1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Nov 2020 16:41:01 +0000 Subject: [PATCH 131/430] build(deps-dev): bump @types/lodash from 4.14.161 to 4.14.165 Bumps [@types/lodash](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/lodash) from 4.14.161 to 4.14.165. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/lodash) Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index a90d885927..bd1757cea0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5434,9 +5434,9 @@ "@types/node" "*" "@types/lodash@^4.14.151": - version "4.14.161" - resolved "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.161.tgz#a21ca0777dabc6e4f44f3d07f37b765f54188b18" - integrity sha512-EP6O3Jkr7bXvZZSZYlsgt5DIjiGr0dXP1/jVEwVLTFgg0d+3lWVQkRavYVQszV7dYUwvg0B8R0MBDpcmXg7XIA== + version "4.14.165" + resolved "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.165.tgz#74d55d947452e2de0742bad65270433b63a8c30f" + integrity sha512-tjSSOTHhI5mCHTy/OOXYIhi2Wt1qcbHmuXD1Ha7q70CgI/I71afO4XtLb/cVexki1oVYchpul/TOuu3Arcdxrg== "@types/long@^4.0.0": version "4.0.1" From 4afc446c24caac9d508aa879ab8c70861558e066 Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Fri, 20 Nov 2020 18:17:58 +0100 Subject: [PATCH 132/430] fixes dark mode issue on searchQuery + changes its class name from searchTerm to be more consistent --- .../search/src/components/SearchResult/SearchResult.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/search/src/components/SearchResult/SearchResult.tsx b/plugins/search/src/components/SearchResult/SearchResult.tsx index 1670c13963..a5dc11aa89 100644 --- a/plugins/search/src/components/SearchResult/SearchResult.tsx +++ b/plugins/search/src/components/SearchResult/SearchResult.tsx @@ -31,8 +31,9 @@ import { FiltersButton, Filters, FiltersState } from '../Filters'; import SearchApi, { Result, SearchResults } from '../../apis'; const useStyles = makeStyles(theme => ({ - searchTerm: { - background: '#eee', + searchQuery: { + color: theme.palette.text.primary, + background: theme.palette.background.default, borderRadius: '10%', }, tableHeader: { @@ -107,7 +108,7 @@ const TableHeader = ({ {`${numberOfResults} `} {numberOfResults > 1 ? `results for ` : `result for `} - "{searchQuery}"{' '} + "{searchQuery}"{' '} ) : ( {`${numberOfResults} results`} From 8d948b243b778378622ffa76d19b0d6eb8d76bec Mon Sep 17 00:00:00 2001 From: Brian Leathem Date: Mon, 16 Nov 2020 14:04:24 -0800 Subject: [PATCH 133/430] Implement a generic OpenIdConnect auth-provider --- app-config.yaml | 14 ++ packages/app/src/identityProviders.ts | 7 + .../core-api/src/apis/definitions/auth.ts | 14 ++ .../src/apis/implementations/auth/index.ts | 1 + .../implementations/auth/oidc/Oidc.test.ts | 152 ++++++++++++ .../apis/implementations/auth/oidc/Oidc.ts | 183 +++++++++++++++ .../apis/implementations/auth/oidc/index.ts | 18 ++ .../apis/implementations/auth/oidc/types.ts | 28 +++ packages/core/src/api-wrappers/defaultApis.ts | 11 + plugins/auth-backend/package.json | 4 + .../auth-backend/src/providers/factories.ts | 2 + .../auth-backend/src/providers/oidc/index.ts | 17 ++ .../src/providers/oidc/provider.ts | 216 ++++++++++++++++++ plugins/auth-backend/src/service/router.ts | 5 +- yarn.lock | 58 ++++- 15 files changed, 727 insertions(+), 3 deletions(-) create mode 100644 packages/core-api/src/apis/implementations/auth/oidc/Oidc.test.ts create mode 100644 packages/core-api/src/apis/implementations/auth/oidc/Oidc.ts create mode 100644 packages/core-api/src/apis/implementations/auth/oidc/index.ts create mode 100644 packages/core-api/src/apis/implementations/auth/oidc/types.ts create mode 100644 plugins/auth-backend/src/providers/oidc/index.ts create mode 100644 plugins/auth-backend/src/providers/oidc/provider.ts diff --git a/app-config.yaml b/app-config.yaml index 32d3470f1b..cfa47618e8 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -235,6 +235,20 @@ auth: $env: AUTH_OAUTH2_AUTH_URL tokenUrl: $env: AUTH_OAUTH2_TOKEN_URL + oidc: + development: + adUrl: + $env: AUTH_OIDC_AD_URL + clientId: + $env: AUTH_OIDC_CLIENT_ID + clientSecret: + $env: AUTH_OIDC_CLIENT_SECRET + authorizationUrl: + $env: AUTH_OIDC_AUTH_URL + tokenUrl: + $env: AUTH_OIDC_TOKEN_URL + tokenSignedResponseAlg: + $env: AUTH_OIDC_TOKEN_SIGNED_RESPONSE_ALG auth0: development: clientId: diff --git a/packages/app/src/identityProviders.ts b/packages/app/src/identityProviders.ts index 06ff6b07d3..ba657328f2 100644 --- a/packages/app/src/identityProviders.ts +++ b/packages/app/src/identityProviders.ts @@ -22,9 +22,16 @@ import { samlAuthApiRef, microsoftAuthApiRef, oneloginAuthApiRef, + oidcApiRef, } from '@backstage/core'; export const providers = [ + { + id: 'oidc-auth-provider', + title: 'Oidc', + message: 'Sign In using OpenId Connect', + apiRef: oidcApiRef, + }, { id: 'google-auth-provider', title: 'Google', diff --git a/packages/core-api/src/apis/definitions/auth.ts b/packages/core-api/src/apis/definitions/auth.ts index c538065f3a..5dae5532ad 100644 --- a/packages/core-api/src/apis/definitions/auth.ts +++ b/packages/core-api/src/apis/definitions/auth.ts @@ -311,6 +311,20 @@ export const oauth2ApiRef: ApiRef< description: 'Example of how to use oauth2 custom provider', }); +/** + * Provides authentication for custom OpenID Connect identity providers. + */ +export const oidcApiRef: ApiRef< + OAuthApi & + OpenIdConnectApi & + ProfileInfoApi & + BackstageIdentityApi & + SessionApi +> = createApiRef({ + id: 'core.auth.oidc', + description: 'Example of how to use oidc custom provider', +}); + /** * Provides authentication for saml based identity providers */ diff --git a/packages/core-api/src/apis/implementations/auth/index.ts b/packages/core-api/src/apis/implementations/auth/index.ts index 7ef78d19cf..5adfc6f621 100644 --- a/packages/core-api/src/apis/implementations/auth/index.ts +++ b/packages/core-api/src/apis/implementations/auth/index.ts @@ -23,3 +23,4 @@ export * from './saml'; export * from './auth0'; export * from './microsoft'; export * from './onelogin'; +export * from './oidc'; diff --git a/packages/core-api/src/apis/implementations/auth/oidc/Oidc.test.ts b/packages/core-api/src/apis/implementations/auth/oidc/Oidc.test.ts new file mode 100644 index 0000000000..4286fe7e5d --- /dev/null +++ b/packages/core-api/src/apis/implementations/auth/oidc/Oidc.test.ts @@ -0,0 +1,152 @@ +/* + * 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 OpenIdConnect from './Oidc'; + +const theFuture = new Date(Date.now() + 3600000); +const thePast = new Date(Date.now() - 10); + +const PREFIX = 'https://www.googleapis.com/auth/'; + +const scopeTransform = (x: string[]) => x; + +describe('OpenIdConnect', () => { + it('should get refreshed access token', async () => { + const getSession = jest.fn().mockResolvedValue({ + providerInfo: { accessToken: 'access-token', expiresAt: theFuture }, + }); + const openIdConnect = new OpenIdConnect({ + sessionManager: { getSession } as any, + scopeTransform, + }); + + expect(await openIdConnect.getAccessToken('my-scope my-scope2')).toBe( + 'access-token', + ); + expect(getSession).toBeCalledTimes(1); + expect(getSession.mock.calls[0][0].scopes).toEqual( + new Set(['my-scope', 'my-scope2']), + ); + }); + + it('should transform scopes', async () => { + const getSession = jest.fn().mockResolvedValue({ + providerInfo: { accessToken: 'access-token', expiresAt: theFuture }, + }); + const openIdConnect = new OpenIdConnect({ + sessionManager: { getSession } as any, + scopeTransform: scopes => scopes.map(scope => `my-prefix/${scope}`), + }); + + expect(await openIdConnect.getAccessToken('my-scope')).toBe('access-token'); + expect(getSession).toBeCalledTimes(1); + expect(getSession.mock.calls[0][0].scopes).toEqual( + new Set(['my-prefix/my-scope']), + ); + }); + + it('should get refreshed id token', async () => { + const getSession = jest.fn().mockResolvedValue({ + providerInfo: { idToken: 'id-token', expiresAt: theFuture }, + }); + const openIdConnect = new OpenIdConnect({ + sessionManager: { getSession } as any, + scopeTransform, + }); + + expect(await openIdConnect.getIdToken()).toBe('id-token'); + expect(getSession).toBeCalledTimes(1); + }); + + it('should get optional id token', async () => { + const getSession = jest.fn().mockResolvedValue({ + providerInfo: { idToken: 'id-token', expiresAt: theFuture }, + }); + const openIdConnect = new OpenIdConnect({ + sessionManager: { getSession } as any, + scopeTransform, + }); + + expect(await openIdConnect.getIdToken({ optional: true })).toBe('id-token'); + expect(getSession).toBeCalledTimes(1); + }); + + it('should share popup closed errors', async () => { + const error = new Error('NOPE'); + error.name = 'RejectedError'; + const getSession = jest + .fn() + .mockResolvedValueOnce({ + providerInfo: { + accessToken: 'access-token', + expiresAt: theFuture, + scopes: new Set([`${PREFIX}not-enough`]), + }, + }) + .mockRejectedValue(error); + const openIdConnect = new OpenIdConnect({ + sessionManager: { getSession } as any, + scopeTransform, + }); + + // Make sure we have a session before we do the double request, so that we get past the !this.currentSession check + await expect(openIdConnect.getAccessToken()).resolves.toBe('access-token'); + + const promise1 = openIdConnect.getAccessToken('more'); + const promise2 = openIdConnect.getAccessToken('more'); + await expect(promise1).rejects.toBe(error); + await expect(promise2).rejects.toBe(error); + expect(getSession).toBeCalledTimes(3); + }); + + it('should wait for all session refreshes', async () => { + const initialSession = { + providerInfo: { + idToken: 'token1', + expiresAt: theFuture, + scopes: new Set(), + }, + }; + const getSession = jest + .fn() + .mockResolvedValueOnce(initialSession) + .mockResolvedValue({ + providerInfo: { + idToken: 'token2', + expiresAt: theFuture, + scopes: new Set(), + }, + }); + const openIdConnect = new OpenIdConnect({ + sessionManager: { getSession } as any, + scopeTransform, + }); + + // Grab the expired session first + await expect(openIdConnect.getIdToken()).resolves.toBe('token1'); + expect(getSession).toBeCalledTimes(1); + + initialSession.providerInfo.expiresAt = thePast; + + const promise1 = openIdConnect.getIdToken(); + const promise2 = openIdConnect.getIdToken(); + const promise3 = openIdConnect.getIdToken(); + await expect(promise1).resolves.toBe('token2'); + await expect(promise2).resolves.toBe('token2'); + await expect(promise3).resolves.toBe('token2'); + expect(getSession).toBeCalledTimes(4); // De-duping of session requests happens in client + }); +}); diff --git a/packages/core-api/src/apis/implementations/auth/oidc/Oidc.ts b/packages/core-api/src/apis/implementations/auth/oidc/Oidc.ts new file mode 100644 index 0000000000..deb8331432 --- /dev/null +++ b/packages/core-api/src/apis/implementations/auth/oidc/Oidc.ts @@ -0,0 +1,183 @@ +/* + * 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 OpenIdConnectIcon from '@material-ui/icons/AcUnit'; +import { DefaultAuthConnector } from '../../../../lib/AuthConnector'; +import { RefreshingAuthSessionManager } from '../../../../lib/AuthSessionManager'; +import { SessionManager } from '../../../../lib/AuthSessionManager/types'; +import { Observable } from '../../../../types'; +import { + AuthRequestOptions, + BackstageIdentity, + OAuthApi, + OpenIdConnectApi, + ProfileInfo, + ProfileInfoApi, + SessionState, + SessionApi, + BackstageIdentityApi, +} from '../../../definitions/auth'; +import { OpenIdConnectSession } from './types'; +import { OAuthApiCreateOptions } from '../types'; + +type Options = { + sessionManager: SessionManager; + scopeTransform: (scopes: string[]) => string[]; +}; + +type CreateOptions = OAuthApiCreateOptions & { + scopeTransform?: (scopes: string[]) => string[]; +}; + +export type OpenIdConnectResponse = { + providerInfo: { + accessToken: string; + idToken: string; + scope: string; + expiresInSeconds: number; + }; + profile: ProfileInfo; + backstageIdentity: BackstageIdentity; +}; + +const DEFAULT_PROVIDER = { + id: 'oidc', + title: 'Open ID Connect Identity Provider', + icon: OpenIdConnectIcon, +}; + +class OpenIdConnect + implements + OAuthApi, + OpenIdConnectApi, + ProfileInfoApi, + BackstageIdentityApi, + SessionApi { + static create({ + discoveryApi, + environment = 'development', + provider = DEFAULT_PROVIDER, + oauthRequestApi, + defaultScopes = [], + scopeTransform = x => x, + }: CreateOptions) { + const connector = new DefaultAuthConnector({ + discoveryApi, + environment, + provider, + oauthRequestApi: oauthRequestApi, + sessionTransform(res: OpenIdConnectResponse): OpenIdConnectSession { + return { + ...res, + providerInfo: { + idToken: res.providerInfo.idToken, + accessToken: res.providerInfo.accessToken, + scopes: OpenIdConnect.normalizeScopes( + scopeTransform, + res.providerInfo.scope, + ), + expiresAt: new Date( + Date.now() + res.providerInfo.expiresInSeconds * 1000, + ), + }, + }; + }, + }); + + const sessionManager = new RefreshingAuthSessionManager({ + connector, + defaultScopes: new Set(defaultScopes), + sessionScopes: (session: OpenIdConnectSession) => + session.providerInfo.scopes, + sessionShouldRefresh: (session: OpenIdConnectSession) => { + const expiresInSec = + (session.providerInfo.expiresAt.getTime() - Date.now()) / 1000; + return expiresInSec < 60 * 5; + }, + }); + + return new OpenIdConnect({ sessionManager, scopeTransform }); + } + + private readonly sessionManager: SessionManager; + private readonly scopeTransform: (scopes: string[]) => string[]; + + constructor(options: Options) { + this.sessionManager = options.sessionManager; + this.scopeTransform = options.scopeTransform; + } + + async signIn() { + await this.getAccessToken(); + } + + async signOut() { + await this.sessionManager.removeSession(); + } + + sessionState$(): Observable { + return this.sessionManager.sessionState$(); + } + + async getAccessToken( + scope?: string | string[], + options?: AuthRequestOptions, + ) { + const normalizedScopes = OpenIdConnect.normalizeScopes( + this.scopeTransform, + scope, + ); + const session = await this.sessionManager.getSession({ + ...options, + scopes: normalizedScopes, + }); + return session?.providerInfo.accessToken ?? ''; + } + + async getIdToken(options: AuthRequestOptions = {}) { + const session = await this.sessionManager.getSession(options); + return session?.providerInfo.idToken ?? ''; + } + + async getBackstageIdentity( + options: AuthRequestOptions = {}, + ): Promise { + const session = await this.sessionManager.getSession(options); + return session?.backstageIdentity; + } + + async getProfile(options: AuthRequestOptions = {}) { + const session = await this.sessionManager.getSession(options); + return session?.profile; + } + + private static normalizeScopes( + scopeTransform: (scopes: string[]) => string[], + scopes?: string | string[], + ): Set { + if (!scopes) { + return new Set(); + } + + const scopeList = Array.isArray(scopes) + ? scopes + : scopes.split(/[\s|,]/).filter(Boolean); + + return new Set(scopeTransform(scopeList)); + } +} + +export default OpenIdConnect; diff --git a/packages/core-api/src/apis/implementations/auth/oidc/index.ts b/packages/core-api/src/apis/implementations/auth/oidc/index.ts new file mode 100644 index 0000000000..cf9add0757 --- /dev/null +++ b/packages/core-api/src/apis/implementations/auth/oidc/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 { default as OpenIdConnect } from './Oidc'; +export * from './types'; diff --git a/packages/core-api/src/apis/implementations/auth/oidc/types.ts b/packages/core-api/src/apis/implementations/auth/oidc/types.ts new file mode 100644 index 0000000000..ae9696c229 --- /dev/null +++ b/packages/core-api/src/apis/implementations/auth/oidc/types.ts @@ -0,0 +1,28 @@ +/* + * 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 { ProfileInfo, BackstageIdentity } from '../../../definitions'; + +export type OpenIdConnectSession = { + providerInfo: { + idToken: string; + accessToken: string; + scopes: Set; + expiresAt: Date; + }; + profile: ProfileInfo; + backstageIdentity: BackstageIdentity; +}; diff --git a/packages/core/src/api-wrappers/defaultApis.ts b/packages/core/src/api-wrappers/defaultApis.ts index 61d4f3a7e1..adc5fce30a 100644 --- a/packages/core/src/api-wrappers/defaultApis.ts +++ b/packages/core/src/api-wrappers/defaultApis.ts @@ -46,6 +46,8 @@ import { SamlAuth, oneloginAuthApiRef, OneLoginAuth, + oidcApiRef, + OpenIdConnect, } from '@backstage/core-api'; export const defaultApis = [ @@ -153,4 +155,13 @@ export const defaultApis = [ factory: ({ discoveryApi, oauthRequestApi }) => OneLoginAuth.create({ discoveryApi, oauthRequestApi }), }), + createApiFactory({ + api: oidcApiRef, + deps: { + discoveryApi: discoveryApiRef, + oauthRequestApi: oauthRequestApiRef, + }, + factory: ({ discoveryApi, oauthRequestApi }) => + OpenIdConnect.create({ discoveryApi, oauthRequestApi }), + }), ]; diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 8ce955350a..d28251badf 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -25,12 +25,15 @@ "@backstage/catalog-model": "^0.2.0", "@backstage/config": "^0.1.1", "@types/express": "^4.17.6", + "@types/express-session": "^1.17.2", + "@types/openid-client": "^3.7.0", "compression": "^1.7.4", "cookie-parser": "^1.4.5", "cors": "^2.8.5", "cross-fetch": "^3.0.6", "express": "^4.17.1", "express-promise-router": "^3.0.3", + "express-session": "^1.17.1", "fs-extra": "^9.0.0", "got": "^11.5.2", "helmet": "^4.0.0", @@ -39,6 +42,7 @@ "knex": "^0.21.6", "moment": "^2.26.0", "morgan": "^1.10.0", + "openid-client": "^4.2.1", "passport": "^0.4.1", "passport-github2": "^0.1.12", "passport-gitlab2": "^5.0.0", diff --git a/plugins/auth-backend/src/providers/factories.ts b/plugins/auth-backend/src/providers/factories.ts index a764d0b50e..d4a29c0959 100644 --- a/plugins/auth-backend/src/providers/factories.ts +++ b/plugins/auth-backend/src/providers/factories.ts @@ -18,6 +18,7 @@ import { createGithubProvider } from './github'; import { createGitlabProvider } from './gitlab'; import { createGoogleProvider } from './google'; import { createOAuth2Provider } from './oauth2'; +import { createOidcProvider } from './oidc'; import { createOktaProvider } from './okta'; import { createSamlProvider } from './saml'; import { createAuth0Provider } from './auth0'; @@ -34,6 +35,7 @@ const factories: { [providerId: string]: AuthProviderFactory } = { auth0: createAuth0Provider, microsoft: createMicrosoftProvider, oauth2: createOAuth2Provider, + oidc: createOidcProvider, onelogin: createOneLoginProvider, }; diff --git a/plugins/auth-backend/src/providers/oidc/index.ts b/plugins/auth-backend/src/providers/oidc/index.ts new file mode 100644 index 0000000000..acacbde73d --- /dev/null +++ b/plugins/auth-backend/src/providers/oidc/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 { createOidcProvider } from './provider'; diff --git a/plugins/auth-backend/src/providers/oidc/provider.ts b/plugins/auth-backend/src/providers/oidc/provider.ts new file mode 100644 index 0000000000..3217c2a537 --- /dev/null +++ b/plugins/auth-backend/src/providers/oidc/provider.ts @@ -0,0 +1,216 @@ +/* + * 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 express from 'express'; +import { + Issuer, + Client, + Strategy as OidcStrategy, + TokenSet, + UserinfoResponse, +} from 'openid-client'; +import { + OAuthAdapter, + OAuthProviderOptions, + OAuthHandlers, + OAuthResponse, + OAuthEnvironmentHandler, + OAuthStartRequest, + encodeState, + OAuthRefreshRequest, +} from '../../lib/oauth'; +import { + executeFetchUserProfileStrategy, + executeFrameHandlerStrategy, + executeRedirectStrategy, + executeRefreshTokenStrategy, + PassportDoneCallback, +} from '../../lib/passport'; +import { RedirectInfo, AuthProviderFactory, ProfileInfo } from '../types'; + +type PrivateInfo = { + refreshToken: string; +}; + +export type OidcAuthProviderOptions = OAuthProviderOptions & { + authorizationUrl: string; + tokenUrl: string; + adUrl: string; + tokenSignedResponseAlg?: string; +}; + +export class OidcAuthProvider implements OAuthHandlers { + private readonly _strategy: Promise>; + + constructor(options: OidcAuthProviderOptions) { + this._strategy = this.setupStrategy(options); + } + + async start(req: OAuthStartRequest): Promise { + const strategy = await this._strategy; + return await executeRedirectStrategy(req, strategy, { + accessType: 'offline', + prompt: 'consent', + scope: `${req.scope} default`, + state: encodeState(req.state), + }); + } + + async handler( + req: express.Request, + ): Promise<{ response: OAuthResponse; refreshToken: string }> { + const strategy = await this._strategy; + const { response, privateInfo } = await executeFrameHandlerStrategy< + OAuthResponse, + PrivateInfo + >(req, strategy); + + return { + response: await this.populateIdentity(response), + refreshToken: privateInfo.refreshToken, + }; + } + + async refresh(req: OAuthRefreshRequest): Promise { + const strategy = await this._strategy; + const refreshTokenResponse = await executeRefreshTokenStrategy( + strategy, + req.refreshToken, + req.scope, + ); + const { + accessToken, + params, + refreshToken: updatedRefreshToken, + } = refreshTokenResponse; + + const profile = await executeFetchUserProfileStrategy( + strategy, + accessToken, + params.id_token, + ); + + return this.populateIdentity({ + providerInfo: { + accessToken, + refreshToken: updatedRefreshToken, + idToken: params.id_token, + expiresInSeconds: params.expires_in, + scope: params.scope, + }, + profile, + }); + } + + private async setupStrategy( + options: OidcAuthProviderOptions, + ): Promise> { + const issuer = await Issuer.discover(options.adUrl); + // console.log('Discovered issuer %s %O', issuer.issuer, issuer.metadata); + const client = new issuer.Client({ + client_id: options.clientId, + client_secret: options.clientSecret, + redirect_uris: [options.callbackUrl], + response_types: ['code'], + id_token_signed_response_alg: options.tokenSignedResponseAlg || 'RS256', + }); + + const strategy = new OidcStrategy( + { + client, + passReqToCallback: false as true, + }, + ( + tokenset: TokenSet, + userinfo: UserinfoResponse, + done: PassportDoneCallback, + ) => { + const profile: ProfileInfo = { + displayName: userinfo.name, + email: userinfo.email, + picture: userinfo.picture, + }; + + done( + undefined, + { + providerInfo: { + idToken: tokenset.id_token || '', + accessToken: tokenset.access_token || '', + scope: tokenset.scope || '', + expiresInSeconds: tokenset.expires_in, + }, + profile, + }, + { + refreshToken: tokenset.refresh_token || '', + }, + ); + }, + ); + strategy.error = console.error; + return strategy; + } + + // Use this function to grab the user profile info from the token + // Then populate the profile with it + private async populateIdentity( + response: OAuthResponse, + ): Promise { + const { profile } = response; + + if (!profile.email) { + throw new Error('Profile does not contain an email'); + } + const id = profile.email.split('@')[0]; + + return { ...response, backstageIdentity: { id } }; + } +} + +export const createOidcProvider: AuthProviderFactory = ({ + globalConfig, + config, + tokenIssuer, +}) => + OAuthEnvironmentHandler.mapConfig(config, envConfig => { + const providerId = 'oidc'; + const clientId = envConfig.getString('clientId'); + const clientSecret = envConfig.getString('clientSecret'); + const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`; + const authorizationUrl = envConfig.getString('authorizationUrl'); + const adUrl = envConfig.getString('adUrl'); + const tokenUrl = envConfig.getString('tokenUrl'); + const tokenSignedResponseAlg = envConfig.getString( + 'tokenSignedResponseAlg', + ); + + const provider = new OidcAuthProvider({ + clientId, + clientSecret, + callbackUrl, + authorizationUrl, + tokenUrl, + tokenSignedResponseAlg, + adUrl, + }); + + return OAuthAdapter.fromConfig(globalConfig, provider, { + disableRefresh: false, + providerId, + tokenIssuer, + }); + }); diff --git a/plugins/auth-backend/src/service/router.ts b/plugins/auth-backend/src/service/router.ts index 0d19e67fc4..b74f7d8d38 100644 --- a/plugins/auth-backend/src/service/router.ts +++ b/plugins/auth-backend/src/service/router.ts @@ -27,6 +27,7 @@ import Router from 'express-promise-router'; import { Logger } from 'winston'; import { createOidcRouter, DatabaseKeyStore, TokenFactory } from '../identity'; import { createAuthProvider } from '../providers'; +import session from 'express-session'; export interface RouterOptions { logger: Logger; @@ -59,7 +60,9 @@ export async function createRouter({ }); const catalogApi = new CatalogClient({ discoveryApi: discovery }); - router.use(cookieParser()); + const secret = 'backstage secret'; // TODO: Allow an override here + router.use(cookieParser(secret)); + router.use(session({ secret })); router.use(express.urlencoded({ extended: false })); router.use(express.json()); diff --git a/yarn.lock b/yarn.lock index bd1757cea0..2c4d23e8d7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5138,6 +5138,13 @@ "@types/qs" "*" "@types/range-parser" "*" +"@types/express-session@^1.17.2": + version "1.17.2" + resolved "https://registry.npmjs.org/@types/express-session/-/express-session-1.17.2.tgz#ed6a36dd9f267c7fef86004f653bfb9b5cea3c21" + integrity sha512-QRm/fUuvr/BAosL9CvK351SDQP7wpD8+h3S8ZEE/8IvHJ/ZqHrjZbjx/flYfazyPw7yNi9O5fbjFZbh0vZ1ccg== + dependencies: + "@types/express" "*" + "@types/express@*", "@types/express@4.17.7", "@types/express@^4.17.6", "@types/express@^4.17.7": version "4.17.7" resolved "https://registry.npmjs.org/@types/express/-/express-4.17.7.tgz#42045be6475636d9801369cd4418ef65cdb0dd59" @@ -5566,6 +5573,13 @@ dependencies: "@types/node" "*" +"@types/openid-client@^3.7.0": + version "3.7.0" + resolved "https://registry.npmjs.org/@types/openid-client/-/openid-client-3.7.0.tgz#8406e12798d16083df09cc3625973f5a00dd57fa" + integrity sha512-hW+QbAeUyfUHABwUjUECOcv56Mf5tN29xK3GPN7wy3R3XfIQdkm37XELA4wbe2RNG9GYUpN8l2ytfoW05XmpgQ== + dependencies: + openid-client "*" + "@types/ora@^3.2.0": version "3.2.0" resolved "https://registry.npmjs.org/@types/ora/-/ora-3.2.0.tgz#b2f65d1283a8f36d8b0f9ee767e0732a2f429362" @@ -11564,6 +11578,20 @@ express-promise-router@^3.0.3: lodash.flattendeep "^4.0.0" methods "^1.0.0" +express-session@^1.17.1: + version "1.17.1" + resolved "https://registry.npmjs.org/express-session/-/express-session-1.17.1.tgz#36ecbc7034566d38c8509885c044d461c11bf357" + integrity sha512-UbHwgqjxQZJiWRTMyhvWGvjBQduGCSBDhhZXYenziMFjxst5rMV+aJZ6hKPHZnPyHGsrqRICxtX8jtEbm/z36Q== + dependencies: + cookie "0.4.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "~2.0.0" + on-headers "~1.0.2" + parseurl "~1.3.3" + safe-buffer "5.2.0" + uid-safe "~2.1.5" + express@^4.0.0, express@^4.17.0, express@^4.17.1: version "4.17.1" resolved "https://registry.npmjs.org/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" @@ -12792,7 +12820,7 @@ got@^11.6.2: p-cancelable "^2.0.0" responselike "^2.0.0" -got@^11.7.0: +got@^11.7.0, got@^11.8.0: version "11.8.0" resolved "https://registry.npmjs.org/got/-/got-11.8.0.tgz#be0920c3586b07fd94add3b5b27cb28f49e6545f" integrity sha512-k9noyoIIY9EejuhaBNLyZ31D5328LeqnyPNXJQb2XlJZcKakLqN5m6O/ikhq/0lw56kUYS54fVm+D1x57YC9oQ== @@ -17740,6 +17768,20 @@ opencollective-postinstall@^2.0.2: resolved "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.2.tgz#5657f1bede69b6e33a45939b061eb53d3c6c3a89" integrity sha512-pVOEP16TrAO2/fjej1IdOyupJY8KDUM1CvsaScRbw6oddvpQoOfGk4ywha0HKKVAD6RkW4x6Q+tNBwhf3Bgpuw== +openid-client@*, openid-client@^4.2.1: + version "4.2.1" + resolved "https://registry.npmjs.org/openid-client/-/openid-client-4.2.1.tgz#8200c0ab6a3b8e954727dfa790847dc5cb8999c2" + integrity sha512-07eOcJeMH3ZHNvx5DVMZQmy3vZSTQqKSSunbtM1pXb+k5LBPi5hMum1vJCFReXlo4wuLEqZ/OgbsZvXPhbGRtA== + dependencies: + base64url "^3.0.1" + got "^11.8.0" + jose "^2.0.2" + lru-cache "^6.0.0" + make-error "^1.3.6" + object-hash "^2.0.1" + oidc-token-hash "^5.0.0" + p-any "^3.0.0" + openid-client@^4.1.1: version "4.1.1" resolved "https://registry.npmjs.org/openid-client/-/openid-client-4.1.1.tgz#3e8a25584c4292e9b9b03e60358f5549fb85197a" @@ -19573,6 +19615,11 @@ ramda@^0.26, ramda@~0.26.1: resolved "https://registry.npmjs.org/ramda/-/ramda-0.26.1.tgz#8d41351eb8111c55353617fc3bbffad8e4d35d06" integrity sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ== +random-bytes@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b" + integrity sha1-T2ih3Arli9P7lYSMMDJNt11kNgs= + randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" @@ -21086,7 +21133,7 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: +safe-buffer@5.2.0, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: version "5.2.0" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== @@ -23357,6 +23404,13 @@ uid-number@0.0.6: resolved "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" integrity sha1-DqEOgDXo61uOREnwbaHHMGY7qoE= +uid-safe@~2.1.5: + version "2.1.5" + resolved "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz#2b3d5c7240e8fc2e58f8aa269e5ee49c0857bd3a" + integrity sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA== + dependencies: + random-bytes "~1.0.0" + uid2@0.0.3, uid2@0.0.x: version "0.0.3" resolved "https://registry.npmjs.org/uid2/-/uid2-0.0.3.tgz#483126e11774df2f71b8b639dcd799c376162b82" From 563533dc6c13b35c3e46f3ec6ee5f284e18f4ce5 Mon Sep 17 00:00:00 2001 From: Brian Leathem Date: Mon, 16 Nov 2020 16:13:01 -0800 Subject: [PATCH 134/430] Moved @types deps to devDependencies --- plugins/auth-backend/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index d28251badf..f1ad45d9e5 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -25,8 +25,6 @@ "@backstage/catalog-model": "^0.2.0", "@backstage/config": "^0.1.1", "@types/express": "^4.17.6", - "@types/express-session": "^1.17.2", - "@types/openid-client": "^3.7.0", "compression": "^1.7.4", "cookie-parser": "^1.4.5", "cors": "^2.8.5", @@ -60,7 +58,9 @@ "@backstage/cli": "^0.3.0", "@types/body-parser": "^1.19.0", "@types/cookie-parser": "^1.4.2", + "@types/express-session": "^1.17.2", "@types/jwt-decode": "2.2.1", + "@types/openid-client": "^3.7.0", "@types/passport": "^1.0.3", "@types/passport-github2": "^1.2.4", "@types/passport-google-oauth20": "^2.0.3", From 5d27349000cb702b81258a5a551533798b99daca Mon Sep 17 00:00:00 2001 From: Brian Leathem Date: Mon, 16 Nov 2020 16:13:33 -0800 Subject: [PATCH 135/430] Initialized saveUninitialized in express-session --- plugins/auth-backend/src/service/router.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/auth-backend/src/service/router.ts b/plugins/auth-backend/src/service/router.ts index b74f7d8d38..3ae3b6cb95 100644 --- a/plugins/auth-backend/src/service/router.ts +++ b/plugins/auth-backend/src/service/router.ts @@ -62,7 +62,7 @@ export async function createRouter({ const secret = 'backstage secret'; // TODO: Allow an override here router.use(cookieParser(secret)); - router.use(session({ secret })); + router.use(session({ secret, saveUninitialized: false })); router.use(express.urlencoded({ extended: false })); router.use(express.json()); From b5bb6935223a93bac93158d09a034304d065bca2 Mon Sep 17 00:00:00 2001 From: Brian Leathem Date: Mon, 16 Nov 2020 17:13:19 -0800 Subject: [PATCH 136/430] Initialized resave in express-session --- plugins/auth-backend/src/service/router.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/auth-backend/src/service/router.ts b/plugins/auth-backend/src/service/router.ts index 3ae3b6cb95..e8624d36b3 100644 --- a/plugins/auth-backend/src/service/router.ts +++ b/plugins/auth-backend/src/service/router.ts @@ -62,7 +62,7 @@ export async function createRouter({ const secret = 'backstage secret'; // TODO: Allow an override here router.use(cookieParser(secret)); - router.use(session({ secret, saveUninitialized: false })); + router.use(session({ secret, saveUninitialized: false, resave: false })); router.use(express.urlencoded({ extended: false })); router.use(express.json()); From 14faf3ad37caa68115f6101f69c6d86b9107bcf6 Mon Sep 17 00:00:00 2001 From: Brian Leathem Date: Wed, 18 Nov 2020 12:55:11 -0800 Subject: [PATCH 137/430] Changed the oidc prompt parameter value to "none" --- plugins/auth-backend/src/providers/oidc/provider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/auth-backend/src/providers/oidc/provider.ts b/plugins/auth-backend/src/providers/oidc/provider.ts index 3217c2a537..ed1488851e 100644 --- a/plugins/auth-backend/src/providers/oidc/provider.ts +++ b/plugins/auth-backend/src/providers/oidc/provider.ts @@ -63,7 +63,7 @@ export class OidcAuthProvider implements OAuthHandlers { const strategy = await this._strategy; return await executeRedirectStrategy(req, strategy, { accessType: 'offline', - prompt: 'consent', + prompt: 'none', scope: `${req.scope} default`, state: encodeState(req.state), }); From 52e79a003238ffd0a7950b89a765787687ffbc53 Mon Sep 17 00:00:00 2001 From: Brian Leathem Date: Wed, 18 Nov 2020 18:23:59 -0800 Subject: [PATCH 138/430] Addressed PR feedback from @Rugvip --- .../src/apis/implementations/auth/index.ts | 1 - .../implementations/auth/oidc/Oidc.test.ts | 152 --------------- .../apis/implementations/auth/oidc/Oidc.ts | 183 ------------------ .../apis/implementations/auth/oidc/index.ts | 18 -- .../apis/implementations/auth/oidc/types.ts | 28 --- packages/core/src/api-wrappers/defaultApis.ts | 3 +- .../src/providers/oidc/provider.ts | 15 +- 7 files changed, 5 insertions(+), 395 deletions(-) delete mode 100644 packages/core-api/src/apis/implementations/auth/oidc/Oidc.test.ts delete mode 100644 packages/core-api/src/apis/implementations/auth/oidc/Oidc.ts delete mode 100644 packages/core-api/src/apis/implementations/auth/oidc/index.ts delete mode 100644 packages/core-api/src/apis/implementations/auth/oidc/types.ts diff --git a/packages/core-api/src/apis/implementations/auth/index.ts b/packages/core-api/src/apis/implementations/auth/index.ts index 5adfc6f621..7ef78d19cf 100644 --- a/packages/core-api/src/apis/implementations/auth/index.ts +++ b/packages/core-api/src/apis/implementations/auth/index.ts @@ -23,4 +23,3 @@ export * from './saml'; export * from './auth0'; export * from './microsoft'; export * from './onelogin'; -export * from './oidc'; diff --git a/packages/core-api/src/apis/implementations/auth/oidc/Oidc.test.ts b/packages/core-api/src/apis/implementations/auth/oidc/Oidc.test.ts deleted file mode 100644 index 4286fe7e5d..0000000000 --- a/packages/core-api/src/apis/implementations/auth/oidc/Oidc.test.ts +++ /dev/null @@ -1,152 +0,0 @@ -/* - * 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 OpenIdConnect from './Oidc'; - -const theFuture = new Date(Date.now() + 3600000); -const thePast = new Date(Date.now() - 10); - -const PREFIX = 'https://www.googleapis.com/auth/'; - -const scopeTransform = (x: string[]) => x; - -describe('OpenIdConnect', () => { - it('should get refreshed access token', async () => { - const getSession = jest.fn().mockResolvedValue({ - providerInfo: { accessToken: 'access-token', expiresAt: theFuture }, - }); - const openIdConnect = new OpenIdConnect({ - sessionManager: { getSession } as any, - scopeTransform, - }); - - expect(await openIdConnect.getAccessToken('my-scope my-scope2')).toBe( - 'access-token', - ); - expect(getSession).toBeCalledTimes(1); - expect(getSession.mock.calls[0][0].scopes).toEqual( - new Set(['my-scope', 'my-scope2']), - ); - }); - - it('should transform scopes', async () => { - const getSession = jest.fn().mockResolvedValue({ - providerInfo: { accessToken: 'access-token', expiresAt: theFuture }, - }); - const openIdConnect = new OpenIdConnect({ - sessionManager: { getSession } as any, - scopeTransform: scopes => scopes.map(scope => `my-prefix/${scope}`), - }); - - expect(await openIdConnect.getAccessToken('my-scope')).toBe('access-token'); - expect(getSession).toBeCalledTimes(1); - expect(getSession.mock.calls[0][0].scopes).toEqual( - new Set(['my-prefix/my-scope']), - ); - }); - - it('should get refreshed id token', async () => { - const getSession = jest.fn().mockResolvedValue({ - providerInfo: { idToken: 'id-token', expiresAt: theFuture }, - }); - const openIdConnect = new OpenIdConnect({ - sessionManager: { getSession } as any, - scopeTransform, - }); - - expect(await openIdConnect.getIdToken()).toBe('id-token'); - expect(getSession).toBeCalledTimes(1); - }); - - it('should get optional id token', async () => { - const getSession = jest.fn().mockResolvedValue({ - providerInfo: { idToken: 'id-token', expiresAt: theFuture }, - }); - const openIdConnect = new OpenIdConnect({ - sessionManager: { getSession } as any, - scopeTransform, - }); - - expect(await openIdConnect.getIdToken({ optional: true })).toBe('id-token'); - expect(getSession).toBeCalledTimes(1); - }); - - it('should share popup closed errors', async () => { - const error = new Error('NOPE'); - error.name = 'RejectedError'; - const getSession = jest - .fn() - .mockResolvedValueOnce({ - providerInfo: { - accessToken: 'access-token', - expiresAt: theFuture, - scopes: new Set([`${PREFIX}not-enough`]), - }, - }) - .mockRejectedValue(error); - const openIdConnect = new OpenIdConnect({ - sessionManager: { getSession } as any, - scopeTransform, - }); - - // Make sure we have a session before we do the double request, so that we get past the !this.currentSession check - await expect(openIdConnect.getAccessToken()).resolves.toBe('access-token'); - - const promise1 = openIdConnect.getAccessToken('more'); - const promise2 = openIdConnect.getAccessToken('more'); - await expect(promise1).rejects.toBe(error); - await expect(promise2).rejects.toBe(error); - expect(getSession).toBeCalledTimes(3); - }); - - it('should wait for all session refreshes', async () => { - const initialSession = { - providerInfo: { - idToken: 'token1', - expiresAt: theFuture, - scopes: new Set(), - }, - }; - const getSession = jest - .fn() - .mockResolvedValueOnce(initialSession) - .mockResolvedValue({ - providerInfo: { - idToken: 'token2', - expiresAt: theFuture, - scopes: new Set(), - }, - }); - const openIdConnect = new OpenIdConnect({ - sessionManager: { getSession } as any, - scopeTransform, - }); - - // Grab the expired session first - await expect(openIdConnect.getIdToken()).resolves.toBe('token1'); - expect(getSession).toBeCalledTimes(1); - - initialSession.providerInfo.expiresAt = thePast; - - const promise1 = openIdConnect.getIdToken(); - const promise2 = openIdConnect.getIdToken(); - const promise3 = openIdConnect.getIdToken(); - await expect(promise1).resolves.toBe('token2'); - await expect(promise2).resolves.toBe('token2'); - await expect(promise3).resolves.toBe('token2'); - expect(getSession).toBeCalledTimes(4); // De-duping of session requests happens in client - }); -}); diff --git a/packages/core-api/src/apis/implementations/auth/oidc/Oidc.ts b/packages/core-api/src/apis/implementations/auth/oidc/Oidc.ts deleted file mode 100644 index deb8331432..0000000000 --- a/packages/core-api/src/apis/implementations/auth/oidc/Oidc.ts +++ /dev/null @@ -1,183 +0,0 @@ -/* - * 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 OpenIdConnectIcon from '@material-ui/icons/AcUnit'; -import { DefaultAuthConnector } from '../../../../lib/AuthConnector'; -import { RefreshingAuthSessionManager } from '../../../../lib/AuthSessionManager'; -import { SessionManager } from '../../../../lib/AuthSessionManager/types'; -import { Observable } from '../../../../types'; -import { - AuthRequestOptions, - BackstageIdentity, - OAuthApi, - OpenIdConnectApi, - ProfileInfo, - ProfileInfoApi, - SessionState, - SessionApi, - BackstageIdentityApi, -} from '../../../definitions/auth'; -import { OpenIdConnectSession } from './types'; -import { OAuthApiCreateOptions } from '../types'; - -type Options = { - sessionManager: SessionManager; - scopeTransform: (scopes: string[]) => string[]; -}; - -type CreateOptions = OAuthApiCreateOptions & { - scopeTransform?: (scopes: string[]) => string[]; -}; - -export type OpenIdConnectResponse = { - providerInfo: { - accessToken: string; - idToken: string; - scope: string; - expiresInSeconds: number; - }; - profile: ProfileInfo; - backstageIdentity: BackstageIdentity; -}; - -const DEFAULT_PROVIDER = { - id: 'oidc', - title: 'Open ID Connect Identity Provider', - icon: OpenIdConnectIcon, -}; - -class OpenIdConnect - implements - OAuthApi, - OpenIdConnectApi, - ProfileInfoApi, - BackstageIdentityApi, - SessionApi { - static create({ - discoveryApi, - environment = 'development', - provider = DEFAULT_PROVIDER, - oauthRequestApi, - defaultScopes = [], - scopeTransform = x => x, - }: CreateOptions) { - const connector = new DefaultAuthConnector({ - discoveryApi, - environment, - provider, - oauthRequestApi: oauthRequestApi, - sessionTransform(res: OpenIdConnectResponse): OpenIdConnectSession { - return { - ...res, - providerInfo: { - idToken: res.providerInfo.idToken, - accessToken: res.providerInfo.accessToken, - scopes: OpenIdConnect.normalizeScopes( - scopeTransform, - res.providerInfo.scope, - ), - expiresAt: new Date( - Date.now() + res.providerInfo.expiresInSeconds * 1000, - ), - }, - }; - }, - }); - - const sessionManager = new RefreshingAuthSessionManager({ - connector, - defaultScopes: new Set(defaultScopes), - sessionScopes: (session: OpenIdConnectSession) => - session.providerInfo.scopes, - sessionShouldRefresh: (session: OpenIdConnectSession) => { - const expiresInSec = - (session.providerInfo.expiresAt.getTime() - Date.now()) / 1000; - return expiresInSec < 60 * 5; - }, - }); - - return new OpenIdConnect({ sessionManager, scopeTransform }); - } - - private readonly sessionManager: SessionManager; - private readonly scopeTransform: (scopes: string[]) => string[]; - - constructor(options: Options) { - this.sessionManager = options.sessionManager; - this.scopeTransform = options.scopeTransform; - } - - async signIn() { - await this.getAccessToken(); - } - - async signOut() { - await this.sessionManager.removeSession(); - } - - sessionState$(): Observable { - return this.sessionManager.sessionState$(); - } - - async getAccessToken( - scope?: string | string[], - options?: AuthRequestOptions, - ) { - const normalizedScopes = OpenIdConnect.normalizeScopes( - this.scopeTransform, - scope, - ); - const session = await this.sessionManager.getSession({ - ...options, - scopes: normalizedScopes, - }); - return session?.providerInfo.accessToken ?? ''; - } - - async getIdToken(options: AuthRequestOptions = {}) { - const session = await this.sessionManager.getSession(options); - return session?.providerInfo.idToken ?? ''; - } - - async getBackstageIdentity( - options: AuthRequestOptions = {}, - ): Promise { - const session = await this.sessionManager.getSession(options); - return session?.backstageIdentity; - } - - async getProfile(options: AuthRequestOptions = {}) { - const session = await this.sessionManager.getSession(options); - return session?.profile; - } - - private static normalizeScopes( - scopeTransform: (scopes: string[]) => string[], - scopes?: string | string[], - ): Set { - if (!scopes) { - return new Set(); - } - - const scopeList = Array.isArray(scopes) - ? scopes - : scopes.split(/[\s|,]/).filter(Boolean); - - return new Set(scopeTransform(scopeList)); - } -} - -export default OpenIdConnect; diff --git a/packages/core-api/src/apis/implementations/auth/oidc/index.ts b/packages/core-api/src/apis/implementations/auth/oidc/index.ts deleted file mode 100644 index cf9add0757..0000000000 --- a/packages/core-api/src/apis/implementations/auth/oidc/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * 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 { default as OpenIdConnect } from './Oidc'; -export * from './types'; diff --git a/packages/core-api/src/apis/implementations/auth/oidc/types.ts b/packages/core-api/src/apis/implementations/auth/oidc/types.ts deleted file mode 100644 index ae9696c229..0000000000 --- a/packages/core-api/src/apis/implementations/auth/oidc/types.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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 { ProfileInfo, BackstageIdentity } from '../../../definitions'; - -export type OpenIdConnectSession = { - providerInfo: { - idToken: string; - accessToken: string; - scopes: Set; - expiresAt: Date; - }; - profile: ProfileInfo; - backstageIdentity: BackstageIdentity; -}; diff --git a/packages/core/src/api-wrappers/defaultApis.ts b/packages/core/src/api-wrappers/defaultApis.ts index adc5fce30a..9b3d26fb4d 100644 --- a/packages/core/src/api-wrappers/defaultApis.ts +++ b/packages/core/src/api-wrappers/defaultApis.ts @@ -47,7 +47,6 @@ import { oneloginAuthApiRef, OneLoginAuth, oidcApiRef, - OpenIdConnect, } from '@backstage/core-api'; export const defaultApis = [ @@ -162,6 +161,6 @@ export const defaultApis = [ oauthRequestApi: oauthRequestApiRef, }, factory: ({ discoveryApi, oauthRequestApi }) => - OpenIdConnect.create({ discoveryApi, oauthRequestApi }), + OAuth2.create({ discoveryApi, oauthRequestApi }), }), ]; diff --git a/plugins/auth-backend/src/providers/oidc/provider.ts b/plugins/auth-backend/src/providers/oidc/provider.ts index ed1488851e..71ea2c4103 100644 --- a/plugins/auth-backend/src/providers/oidc/provider.ts +++ b/plugins/auth-backend/src/providers/oidc/provider.ts @@ -46,9 +46,7 @@ type PrivateInfo = { }; export type OidcAuthProviderOptions = OAuthProviderOptions & { - authorizationUrl: string; - tokenUrl: string; - adUrl: string; + metadataUrl: string; tokenSignedResponseAlg?: string; }; @@ -118,8 +116,7 @@ export class OidcAuthProvider implements OAuthHandlers { private async setupStrategy( options: OidcAuthProviderOptions, ): Promise> { - const issuer = await Issuer.discover(options.adUrl); - // console.log('Discovered issuer %s %O', issuer.issuer, issuer.metadata); + const issuer = await Issuer.discover(options.metadataUrl); const client = new issuer.Client({ client_id: options.clientId, client_secret: options.clientSecret, @@ -191,9 +188,7 @@ export const createOidcProvider: AuthProviderFactory = ({ const clientId = envConfig.getString('clientId'); const clientSecret = envConfig.getString('clientSecret'); const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`; - const authorizationUrl = envConfig.getString('authorizationUrl'); - const adUrl = envConfig.getString('adUrl'); - const tokenUrl = envConfig.getString('tokenUrl'); + const metadataUrl = envConfig.getString('metadataUrl'); const tokenSignedResponseAlg = envConfig.getString( 'tokenSignedResponseAlg', ); @@ -202,10 +197,8 @@ export const createOidcProvider: AuthProviderFactory = ({ clientId, clientSecret, callbackUrl, - authorizationUrl, - tokenUrl, tokenSignedResponseAlg, - adUrl, + metadataUrl, }); return OAuthAdapter.fromConfig(globalConfig, provider, { From 5c485ff60ff2c381e7083449c57cea58586b4acc Mon Sep 17 00:00:00 2001 From: Brian Leathem Date: Thu, 19 Nov 2020 09:11:13 -0800 Subject: [PATCH 139/430] Added a auth.session.secret config value for setting the express session secret --- app-config.yaml | 2 ++ plugins/auth-backend/package.json | 23 +++++++++++++++++++++- plugins/auth-backend/src/service/router.ts | 4 +++- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/app-config.yaml b/app-config.yaml index cfa47618e8..34a0b09520 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -191,6 +191,8 @@ scaffolder: $env: AZURE_TOKEN auth: + session: + secret: yaml session secret providers: google: development: diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index f1ad45d9e5..94502775e4 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -71,5 +71,26 @@ "files": [ "dist", "migrations" - ] + ], + "configSchema": { + "$schema": "https://backstage.io/schema/config-v1", + "title": "@backstage/auth-backend", + "type": "object", + "properties": { + "auth": { + "type": "object", + "properties": { + "session": { + "type": "object", + "properties": { + "secret": { + "type": "string", + "visibility": "secret" + } + } + } + } + } + } + } } diff --git a/plugins/auth-backend/src/service/router.ts b/plugins/auth-backend/src/service/router.ts index e8624d36b3..1baa1d4892 100644 --- a/plugins/auth-backend/src/service/router.ts +++ b/plugins/auth-backend/src/service/router.ts @@ -60,7 +60,9 @@ export async function createRouter({ }); const catalogApi = new CatalogClient({ discoveryApi: discovery }); - const secret = 'backstage secret'; // TODO: Allow an override here + const secret = + config.getOptionalString('auth.session.secret') ?? 'backstage secret'; + console.log('using secret', secret); router.use(cookieParser(secret)); router.use(session({ secret, saveUninitialized: false, resave: false })); router.use(express.urlencoded({ extended: false })); From 78b390483df0dbc4f4369d4887a157c4df2692d5 Mon Sep 17 00:00:00 2001 From: Brian Leathem Date: Thu, 19 Nov 2020 11:13:04 -0800 Subject: [PATCH 140/430] Added a oidc provider test --- plugins/auth-backend/package.json | 4 +- .../src/providers/oidc/provider.test.ts | 60 +++++++++++++++++++ .../src/providers/oidc/provider.ts | 2 +- yarn.lock | 22 +++++++ 4 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 plugins/auth-backend/src/providers/oidc/provider.test.ts diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 94502775e4..4d1e52aa21 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -60,13 +60,15 @@ "@types/cookie-parser": "^1.4.2", "@types/express-session": "^1.17.2", "@types/jwt-decode": "2.2.1", + "@types/nock": "^11.1.0", "@types/openid-client": "^3.7.0", "@types/passport": "^1.0.3", "@types/passport-github2": "^1.2.4", "@types/passport-google-oauth20": "^2.0.3", "@types/passport-microsoft": "^0.0.0", "@types/passport-saml": "^1.1.2", - "msw": "^0.21.2" + "msw": "^0.21.2", + "nock": "^13.0.5" }, "files": [ "dist", diff --git a/plugins/auth-backend/src/providers/oidc/provider.test.ts b/plugins/auth-backend/src/providers/oidc/provider.test.ts new file mode 100644 index 0000000000..e8b414edf2 --- /dev/null +++ b/plugins/auth-backend/src/providers/oidc/provider.test.ts @@ -0,0 +1,60 @@ +/* + * 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 nock from 'nock'; +import { ClientMetadata, IssuerMetadata } from 'openid-client'; +import { OidcAuthProvider } from './provider'; + +const issuerMetadata = { + issuer: 'https://oidc.test', + authorization_endpoint: 'https://oidc.test/as/authorization.oauth2', + token_endpoint: 'https://oidc.test/as/token.oauth2', + revocation_endpoint: 'https://oidc.test/as/revoke_token.oauth2', + userinfo_endpoint: 'https://oidc.test/idp/userinfo.openid', + introspection_endpoint: 'https://oidc.test/as/introspect.oauth2', + jwks_uri: 'https://oidc.test/pf/JWKS', + scopes_supported: ['openid'], + claims_supported: ['email'], + response_types_supported: ['code'], + id_token_signing_alg_values_supported: ['RS256', 'RS512'], + token_endpoint_auth_signing_alg_values_supported: ['RS256', 'RS512'], + request_object_signing_alg_values_supported: ['RS256', 'RS512'], +}; + +const clientMetadata = { + callbackUrl: 'https://oidc.test/callback', + clientId: 'testclientid', + clientSecret: 'testclientsecret', + metadataUrl: 'https://oidc.test/.well-known/openid-configuration', +}; + +describe('OidcAuthProvider', () => { + it('hit the metadataurl', async () => { + const scope = nock('https://oidc.test') + .get('/.well-known/openid-configuration') + .reply(200, issuerMetadata); + const provider = new OidcAuthProvider(clientMetadata); + const strategy = ((await provider._strategy) as any) as { + _client: ClientMetadata; + _issuer: IssuerMetadata; + }; + // Assert that the expected request to the metadaurl was made. + expect(scope.isDone()).toBeTruthy(); + const { _client, _issuer } = strategy; + expect(_client.client_id).toBe(clientMetadata.clientId); + expect(_issuer.token_endpoint).toBe(issuerMetadata.token_endpoint); + }); +}); diff --git a/plugins/auth-backend/src/providers/oidc/provider.ts b/plugins/auth-backend/src/providers/oidc/provider.ts index 71ea2c4103..5ebb859f2f 100644 --- a/plugins/auth-backend/src/providers/oidc/provider.ts +++ b/plugins/auth-backend/src/providers/oidc/provider.ts @@ -51,7 +51,7 @@ export type OidcAuthProviderOptions = OAuthProviderOptions & { }; export class OidcAuthProvider implements OAuthHandlers { - private readonly _strategy: Promise>; + readonly _strategy: Promise>; constructor(options: OidcAuthProviderOptions) { this._strategy = this.setupStrategy(options); diff --git a/yarn.lock b/yarn.lock index 2c4d23e8d7..ba25713fdc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5521,6 +5521,13 @@ dependencies: "@types/node" "*" +"@types/nock@^11.1.0": + version "11.1.0" + resolved "https://registry.npmjs.org/@types/nock/-/nock-11.1.0.tgz#0a8c1056a31ba32a959843abccf99626dd90a538" + integrity sha512-jI/ewavBQ7X5178262JQR0ewicPAcJhXS/iFaNJl0VHLfyosZ/kwSrsa6VNQNSO8i9d8SqdRgOtZSOKJ/+iNMw== + dependencies: + nock "*" + "@types/node-fetch@2.5.7", "@types/node-fetch@^2.5.4": version "2.5.7" resolved "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.7.tgz#20a2afffa882ab04d44ca786449a276f9f6bbf3c" @@ -17139,6 +17146,16 @@ no-case@^3.0.3: lower-case "^2.0.1" tslib "^1.10.0" +nock@*, nock@^13.0.5: + version "13.0.5" + resolved "https://registry.npmjs.org/nock/-/nock-13.0.5.tgz#a618c6f86372cb79fac04ca9a2d1e4baccdb2414" + integrity sha512-1ILZl0zfFm2G4TIeJFW0iHknxr2NyA+aGCMTjDVUsBY4CkMRispF1pfIYkTRdAR/3Bg+UzdEuK0B6HczMQZcCg== + dependencies: + debug "^4.1.0" + json-stringify-safe "^5.0.1" + lodash.set "^4.3.2" + propagate "^2.0.0" + node-addon-api@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.0.tgz#f9afb8d777a91525244b01775ea0ddbe1125483b" @@ -19385,6 +19402,11 @@ prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, object-assign "^4.1.1" react-is "^16.8.1" +propagate@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz#40cdedab18085c792334e64f0ac17256d38f9a45" + integrity sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag== + property-expr@^2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/property-expr/-/property-expr-2.0.2.tgz#fff2a43919135553a3bc2fdd94bdb841965b2330" From 3712c47974e67a6a4d4d3e29569ecf470d27a355 Mon Sep 17 00:00:00 2001 From: Brian Leathem Date: Thu, 19 Nov 2020 17:35:03 -0800 Subject: [PATCH 141/430] Added a test for the OidcAuthProvider handler method --- .../src/providers/oidc/provider.test.ts | 43 +++++++++++++++++-- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/plugins/auth-backend/src/providers/oidc/provider.test.ts b/plugins/auth-backend/src/providers/oidc/provider.test.ts index e8b414edf2..0ad464f607 100644 --- a/plugins/auth-backend/src/providers/oidc/provider.test.ts +++ b/plugins/auth-backend/src/providers/oidc/provider.test.ts @@ -14,9 +14,12 @@ * limitations under the License. */ +import express from 'express'; +import { Session } from 'express-session'; import nock from 'nock'; import { ClientMetadata, IssuerMetadata } from 'openid-client'; import { OidcAuthProvider } from './provider'; +import { JWT, JWK } from 'jose'; const issuerMetadata = { issuer: 'https://oidc.test', @@ -29,9 +32,9 @@ const issuerMetadata = { scopes_supported: ['openid'], claims_supported: ['email'], response_types_supported: ['code'], - id_token_signing_alg_values_supported: ['RS256', 'RS512'], - token_endpoint_auth_signing_alg_values_supported: ['RS256', 'RS512'], - request_object_signing_alg_values_supported: ['RS256', 'RS512'], + id_token_signing_alg_values_supported: ['RS256', 'RS512', 'HS256'], + token_endpoint_auth_signing_alg_values_supported: ['RS256', 'RS512', 'HS256'], + request_object_signing_alg_values_supported: ['RS256', 'RS512', 'HS256'], }; const clientMetadata = { @@ -39,10 +42,11 @@ const clientMetadata = { clientId: 'testclientid', clientSecret: 'testclientsecret', metadataUrl: 'https://oidc.test/.well-known/openid-configuration', + tokenSignedResponseAlg: 'none', }; describe('OidcAuthProvider', () => { - it('hit the metadataurl', async () => { + it('hit the metadata url', async () => { const scope = nock('https://oidc.test') .get('/.well-known/openid-configuration') .reply(200, issuerMetadata); @@ -57,4 +61,35 @@ describe('OidcAuthProvider', () => { expect(_client.client_id).toBe(clientMetadata.clientId); expect(_issuer.token_endpoint).toBe(issuerMetadata.token_endpoint); }); + + it('OidcAuthProvider#handler successfully invokes the oidc endpoints', async () => { + const jwt = { + sub: 'alice', + iss: 'https://oidc.test', + aud: clientMetadata.clientId, + exp: Date.now() + 10000, + }; + const scope = nock('https://oidc.test') + .get('/.well-known/openid-configuration') + .reply(200, issuerMetadata) + .post('/as/token.oauth2') + .reply(200, { + id_token: JWT.sign(jwt, JWK.None), + access_token: 'test', + authorization_signed_response_alg: 'HS256', + }) + .get('/idp/userinfo.openid') + .reply(200, { + sub: 'alice', + email: 'alice@oidc.test', + }); + const provider = new OidcAuthProvider(clientMetadata); + const req = { + method: 'GET', + url: '/?code=test2', + session: ({ 'oidc:oidc.test': 'test' } as any) as Session, + } as express.Request; + await provider.handler(req); + expect(scope.isDone()).toBeTruthy(); + }); }); From afaaf96eccccd91ea914f3e52a9ea14e1e3ae6e6 Mon Sep 17 00:00:00 2001 From: Brian Leathem Date: Thu, 19 Nov 2020 23:49:12 -0800 Subject: [PATCH 142/430] Cleanup and fixes after refactoring --- app-config.yaml | 4 ++-- packages/app/src/identityProviders.ts | 4 ++-- packages/core-api/src/apis/definitions/auth.ts | 2 +- packages/core/src/api-wrappers/defaultApis.ts | 16 +++++++++++++--- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/app-config.yaml b/app-config.yaml index 34a0b09520..8dc870a7cd 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -239,8 +239,8 @@ auth: $env: AUTH_OAUTH2_TOKEN_URL oidc: development: - adUrl: - $env: AUTH_OIDC_AD_URL + metadataUrl: + $env: AUTH_OIDC_METADATA_URL clientId: $env: AUTH_OIDC_CLIENT_ID clientSecret: diff --git a/packages/app/src/identityProviders.ts b/packages/app/src/identityProviders.ts index ba657328f2..01828b1405 100644 --- a/packages/app/src/identityProviders.ts +++ b/packages/app/src/identityProviders.ts @@ -22,7 +22,7 @@ import { samlAuthApiRef, microsoftAuthApiRef, oneloginAuthApiRef, - oidcApiRef, + oidcAuthApiRef, } from '@backstage/core'; export const providers = [ @@ -30,7 +30,7 @@ export const providers = [ id: 'oidc-auth-provider', title: 'Oidc', message: 'Sign In using OpenId Connect', - apiRef: oidcApiRef, + apiRef: oidcAuthApiRef, }, { id: 'google-auth-provider', diff --git a/packages/core-api/src/apis/definitions/auth.ts b/packages/core-api/src/apis/definitions/auth.ts index 5dae5532ad..30b07887ad 100644 --- a/packages/core-api/src/apis/definitions/auth.ts +++ b/packages/core-api/src/apis/definitions/auth.ts @@ -314,7 +314,7 @@ export const oauth2ApiRef: ApiRef< /** * Provides authentication for custom OpenID Connect identity providers. */ -export const oidcApiRef: ApiRef< +export const oidcAuthApiRef: ApiRef< OAuthApi & OpenIdConnectApi & ProfileInfoApi & diff --git a/packages/core/src/api-wrappers/defaultApis.ts b/packages/core/src/api-wrappers/defaultApis.ts index 9b3d26fb4d..1f18f54d2a 100644 --- a/packages/core/src/api-wrappers/defaultApis.ts +++ b/packages/core/src/api-wrappers/defaultApis.ts @@ -46,9 +46,11 @@ import { SamlAuth, oneloginAuthApiRef, OneLoginAuth, - oidcApiRef, + oidcAuthApiRef, } from '@backstage/core-api'; +import OAuth2Icon from '@material-ui/icons/AcUnit'; + export const defaultApis = [ createApiFactory({ api: discoveryApiRef, @@ -155,12 +157,20 @@ export const defaultApis = [ OneLoginAuth.create({ discoveryApi, oauthRequestApi }), }), createApiFactory({ - api: oidcApiRef, + api: oidcAuthApiRef, deps: { discoveryApi: discoveryApiRef, oauthRequestApi: oauthRequestApiRef, }, factory: ({ discoveryApi, oauthRequestApi }) => - OAuth2.create({ discoveryApi, oauthRequestApi }), + OAuth2.create({ + discoveryApi, + oauthRequestApi, + provider: { + id: 'oidc', + title: 'Your Identity Provider', + icon: OAuth2Icon, + }, + }), }), ]; From 68be35912b8e0c52c701e5a9e172a3b1cd9afb6f Mon Sep 17 00:00:00 2001 From: Brian Leathem Date: Fri, 20 Nov 2020 10:07:59 -0800 Subject: [PATCH 143/430] Conditionally enable the auth-backend session support --- app-config.yaml | 6 +++--- plugins/auth-backend/src/service/router.ts | 16 +++++++++++----- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/app-config.yaml b/app-config.yaml index 8dc870a7cd..fcc54a1a35 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -189,10 +189,10 @@ scaffolder: api: token: $env: AZURE_TOKEN - auth: - session: - secret: yaml session secret + ### Providing an auth.session.secret will enable session support in the auth-backend + # session: + # secret: custom session secret providers: google: development: diff --git a/plugins/auth-backend/src/service/router.ts b/plugins/auth-backend/src/service/router.ts index 1baa1d4892..47e0d14caa 100644 --- a/plugins/auth-backend/src/service/router.ts +++ b/plugins/auth-backend/src/service/router.ts @@ -28,6 +28,7 @@ import { Logger } from 'winston'; import { createOidcRouter, DatabaseKeyStore, TokenFactory } from '../identity'; import { createAuthProvider } from '../providers'; import session from 'express-session'; +import passport from 'passport'; export interface RouterOptions { logger: Logger; @@ -60,11 +61,16 @@ export async function createRouter({ }); const catalogApi = new CatalogClient({ discoveryApi: discovery }); - const secret = - config.getOptionalString('auth.session.secret') ?? 'backstage secret'; - console.log('using secret', secret); - router.use(cookieParser(secret)); - router.use(session({ secret, saveUninitialized: false, resave: false })); + const secret = config.getOptionalString('auth.session.secret'); + if (secret) { + router.use(cookieParser(secret)); + // TODO: Configure the server-side session storage. The default MemoryStore is not designed for production + router.use(session({ secret, saveUninitialized: false, resave: false })); + router.use(passport.initialize()); + router.use(passport.session()); + } else { + router.use(cookieParser()); + } router.use(express.urlencoded({ extended: false })); router.use(express.json()); From 40b3036fac715bb7ee81b673ec397b7951876a35 Mon Sep 17 00:00:00 2001 From: Brian Leathem Date: Fri, 20 Nov 2020 11:57:00 -0800 Subject: [PATCH 144/430] Added a test for the createOidcProvider method --- .../src/providers/oidc/provider.test.ts | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/plugins/auth-backend/src/providers/oidc/provider.test.ts b/plugins/auth-backend/src/providers/oidc/provider.test.ts index 0ad464f607..ade69255b7 100644 --- a/plugins/auth-backend/src/providers/oidc/provider.test.ts +++ b/plugins/auth-backend/src/providers/oidc/provider.test.ts @@ -18,8 +18,11 @@ import express from 'express'; import { Session } from 'express-session'; import nock from 'nock'; import { ClientMetadata, IssuerMetadata } from 'openid-client'; -import { OidcAuthProvider } from './provider'; +import { createOidcProvider, OidcAuthProvider } from './provider'; import { JWT, JWK } from 'jose'; +import { AuthProviderFactoryOptions } from '../types'; +import { Config } from '@backstage/config'; +import { OAuthAdapter } from '../../lib/oauth'; const issuerMetadata = { issuer: 'https://oidc.test', @@ -92,4 +95,21 @@ describe('OidcAuthProvider', () => { await provider.handler(req); expect(scope.isDone()).toBeTruthy(); }); + + const options = { + globalConfig: { + appUrl: 'https://oidc.test', + baseUrl: 'https://oidc.test', + }, + config: ({ + keys: jest.fn(() => ['test']), + getConfig: jest.fn(() => ({ getString: () => '' })), + } as any) as Config, + } as AuthProviderFactoryOptions; + + it('createOidcProvider', () => { + const provider = createOidcProvider(options) as OAuthAdapter; + console.log(provider); + expect(provider.start).toBeDefined(); + }); }); From c220f7e35a9159b6540150a6a0f1710275c74aff Mon Sep 17 00:00:00 2001 From: blam Date: Sun, 22 Nov 2020 02:50:35 +0100 Subject: [PATCH 145/430] chore(scaffolder/template): fixing catalog-info.yaml reference for templates with new version --- .../{component-info.yaml => catalog-info.yaml} | 0 .../{component-info.yaml => catalog-info.yaml} | 0 .../{component-info.yaml => catalog-info.yaml} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename plugins/scaffolder-backend/sample-templates/docs-template/{{cookiecutter.component_id}}/{component-info.yaml => catalog-info.yaml} (100%) rename plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/{component-info.yaml => catalog-info.yaml} (100%) rename plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/{component-info.yaml => catalog-info.yaml} (100%) diff --git a/plugins/scaffolder-backend/sample-templates/docs-template/{{cookiecutter.component_id}}/component-info.yaml b/plugins/scaffolder-backend/sample-templates/docs-template/{{cookiecutter.component_id}}/catalog-info.yaml similarity index 100% rename from plugins/scaffolder-backend/sample-templates/docs-template/{{cookiecutter.component_id}}/component-info.yaml rename to plugins/scaffolder-backend/sample-templates/docs-template/{{cookiecutter.component_id}}/catalog-info.yaml diff --git a/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/component-info.yaml b/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/catalog-info.yaml similarity index 100% rename from plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/component-info.yaml rename to plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/catalog-info.yaml diff --git a/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/component-info.yaml b/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/catalog-info.yaml similarity index 100% rename from plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/component-info.yaml rename to plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/catalog-info.yaml From 9075c4816b73716988bdad7daa2fd7a1eb94bd50 Mon Sep 17 00:00:00 2001 From: blam Date: Sun, 22 Nov 2020 02:51:10 +0100 Subject: [PATCH 146/430] docs: added a note about templates should have catalog-info --- docs/features/software-templates/adding-templates.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/features/software-templates/adding-templates.md b/docs/features/software-templates/adding-templates.md index 75a75003f7..e2b69d6767 100644 --- a/docs/features/software-templates/adding-templates.md +++ b/docs/features/software-templates/adding-templates.md @@ -55,6 +55,10 @@ contains more information about the required fields. Once we have a `template.yaml` ready, we can then add it to the service catalog for use by the scaffolder. +_NOTE_: When the `publish` step is completed, it is currently assumed by the +scaffolder that the final repository should contain a `catalog-info.yaml` in +order to register this with the Catalog in Backstage. + Currently the catalog supports loading definitions from GitHub + Local Files. To load from other places, not only will there need to be another preparer, but the support to load the location will also need to be added to the Catalog. From ab4794c70272da8deb01df99367a234242cdf3eb Mon Sep 17 00:00:00 2001 From: blam Date: Sun, 22 Nov 2020 02:51:40 +0100 Subject: [PATCH 147/430] chore(scaffolder/cra): needs to create catalog-info yaml --- .../scaffolder-backend/src/scaffolder/stages/templater/cra.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/templater/cra.ts b/plugins/scaffolder-backend/src/scaffolder/stages/templater/cra.ts index da33149c24..045feabc62 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/templater/cra.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/templater/cra.ts @@ -69,7 +69,7 @@ export class CreateReactAppTemplater implements TemplaterBase { ); await fs.promises.writeFile( - `${finalDir}/component-info.yaml`, + `${finalDir}/catalog-info.yaml`, yaml.stringify(componentInfo), ); From f12a7cd906fe000263d82d4b37e8803ae9c49bac Mon Sep 17 00:00:00 2001 From: blam Date: Sun, 22 Nov 2020 02:52:00 +0100 Subject: [PATCH 148/430] chore(scaffolder): a little housekeeping and better error handling for the scaffolder frontend. --- .../JobStatusModal/JobStatusModal.tsx | 61 ++++++++-------- .../components/TemplatePage/TemplatePage.tsx | 73 ++++++++++--------- .../useJobPolling.ts | 51 +++++-------- plugins/scaffolder/src/types.ts | 6 +- 4 files changed, 94 insertions(+), 97 deletions(-) rename plugins/scaffolder/src/components/{JobStatusModal => hooks}/useJobPolling.ts (55%) diff --git a/plugins/scaffolder/src/components/JobStatusModal/JobStatusModal.tsx b/plugins/scaffolder/src/components/JobStatusModal/JobStatusModal.tsx index d8b194eb41..f0aa5ccd81 100644 --- a/plugins/scaffolder/src/components/JobStatusModal/JobStatusModal.tsx +++ b/plugins/scaffolder/src/components/JobStatusModal/JobStatusModal.tsx @@ -13,43 +13,48 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; import { Button } from '@backstage/core'; -import { entityRoute, entityRouteParams } from '@backstage/plugin-catalog'; import { + Button as Action, Dialog, DialogActions, DialogContent, DialogTitle, LinearProgress, } from '@material-ui/core'; -import React, { useEffect, useState } from 'react'; -import { generatePath } from 'react-router-dom'; + +import React, { useCallback, useState } from 'react'; import { Job } from '../../types'; import { JobStage } from '../JobStage/JobStage'; -import { useJobPolling } from './useJobPolling'; type Props = { - onComplete: (job: Job) => void; - jobId: string; - entity: TemplateEntityV1alpha1 | null; + job: Job; + toCatalogLink?: string; }; -export const JobStatusModal = ({ jobId, onComplete, entity }: Props) => { - const job = useJobPolling(jobId); - const [dialogTitle, setDialogTitle] = useState('Creating component...'); +export const JobStatusModal = ({ job, toCatalogLink }: Props) => { + const [isOpen, setOpen] = useState(true); - useEffect(() => { - if (job?.status === 'COMPLETED') { - setDialogTitle('Successfully created component'); - onComplete(job); - } else if (job?.status === 'FAILED') - setDialogTitle('Failed to create component'); - }, [job, onComplete, setDialogTitle]); + const renderTitle = () => { + switch (job?.status) { + case 'COMPLETED': + return 'Successfully created component'; + case 'FAILED': + return 'Failed to create component'; + default: + return 'Create component'; + } + }; + + const onClose = useCallback(() => { + if (job.status === 'COMPLETED' || job.status === 'FAILED') { + setOpen(false); + } + }, [job]); return ( - - {dialogTitle} + + {renderTitle()} {!job ? ( @@ -66,16 +71,14 @@ export const JobStatusModal = ({ jobId, onComplete, entity }: Props) => { )) )} - {entity && ( + {job?.status && toCatalogLink && ( - + + + )} + {job?.status === 'FAILED' && ( + + setOpen(false)}>Close )} diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx index d8b803f893..0754b64d7f 100644 --- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx +++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx @@ -23,18 +23,22 @@ import { Page, useApi, } from '@backstage/core'; -import { catalogApiRef } from '@backstage/plugin-catalog'; +import { + catalogApiRef, + entityRoute, + entityRouteParams, +} from '@backstage/plugin-catalog'; import { LinearProgress } from '@material-ui/core'; import { IChangeEvent } from '@rjsf/core'; import React, { useState, useCallback } from 'react'; -import { Navigate } from 'react-router'; +import { generatePath, Navigate } from 'react-router'; import { useParams } from 'react-router-dom'; import { useAsync } from 'react-use'; import { scaffolderApiRef } from '../../api'; import { rootRoute } from '../../routes'; -import { Job } from '../../types'; import { JobStatusModal } from '../JobStatusModal'; import { MultistepJsonForm } from '../MultistepJsonForm'; +import { useJobPolling } from '../hooks/useJobPolling'; const useTemplate = ( templateName: string, @@ -81,10 +85,10 @@ export const TemplatePage = () => { const catalogApi = useApi(catalogApiRef); const scaffolderApi = useApi(scaffolderApiRef); const { templateName } = useParams(); + const [catalogLink, setCatalogLink] = useState(); const { template, loading } = useTemplate(templateName, catalogApi); - const [formState, setFormState] = useState({}); - + const [modalOpen, setModalOpen] = useState(false); const handleFormReset = () => setFormState({}); const handleChange = useCallback( (e: IChangeEvent) => setFormState({ ...formState, ...e.formData }), @@ -92,35 +96,42 @@ export const TemplatePage = () => { ); const [jobId, setJobId] = useState(null); - - const handleCreate = async () => { - try { - const job = await scaffolderApi.scaffold(templateName, formState); - setJobId(job); - } catch (e) { - errorApi.post(e); - } - }; - - const [entity, setEntity] = React.useState( - null, - ); - - const handleCreateComplete = async (job: Job) => { + const job = useJobPolling(jobId, async job => { if (!job.metadata.catalogInfoUrl) { errorApi.post( - new Error( - `Failed to find catalog-info.yaml file in ${job.metadata.remoteUrl}.`, - ), + new Error(`No catalogInfoUrl returned from the scaffolder`), ); return; } - const { - entities: [createdEntity], - } = await catalogApi.addLocation({ target: job.metadata.catalogInfoUrl }); + try { + const { + entities: [createdEntity], + } = await catalogApi.addLocation({ target: job.metadata.catalogInfoUrl }); - setEntity((createdEntity as any) as TemplateEntityV1alpha1); + const resolvedPath = generatePath( + `/catalog/${entityRoute.path}`, + entityRouteParams(createdEntity), + ); + + setCatalogLink(resolvedPath); + } catch (ex) { + errorApi.post( + new Error( + `Something went wrong trying to add the new 'catalog-info.yaml' to the catalog`, + ), + ); + } + }); + + const handleCreate = async () => { + try { + const jobId = await scaffolderApi.scaffold(templateName, formState); + setJobId(jobId); + setModalOpen(true); + } catch (e) { + errorApi.post(e); + } }; if (!loading && !template) { @@ -150,13 +161,7 @@ export const TemplatePage = () => { /> {loading && } - {jobId && ( - - )} + {modalOpen && } {template && ( Promise, ms: number) => { - let shouldStop = false; - (async () => { - while (!shouldStop) { - await thunk(); - await new Promise(res => setTimeout(res, ms)); - } - })(); - - return () => { - shouldStop = true; - }; -}; export const useJobPolling = ( jobId: string | null, + onFinish?: (j: Job) => void, pollingInterval = DEFAULT_POLLING_INTERVAL, ) => { const scaffolderApi = useApi(scaffolderApiRef); - const [job, setJob] = useState(null); - - useEffect(() => { - if (!jobId) return () => {}; - - const stopPolling = poll(async () => { - const nextJobState = await scaffolderApi.getJob(jobId); - if ( - nextJobState.status === 'FAILED' || - nextJobState.status === 'COMPLETED' - ) { - stopPolling(); + const [currentJob, setCurrentJob] = useState(null); + const shouldBeRunningInterval = + jobId && + currentJob?.status !== 'COMPLETED' && + currentJob?.status !== 'FAILED'; + useInterval( + async () => { + if (jobId) { + const job = await scaffolderApi.getJob(jobId); + if (job?.status === 'COMPLETED' || job?.status === 'FAILED') { + onFinish?.(job); + } + setCurrentJob(job); } - setJob(nextJobState); - }, pollingInterval); - return () => { - stopPolling(); - }; - }, [jobId, setJob, scaffolderApi, pollingInterval]); + }, + shouldBeRunningInterval ? pollingInterval : null, + ); - return job; + return currentJob; }; diff --git a/plugins/scaffolder/src/types.ts b/plugins/scaffolder/src/types.ts index e07581ad28..45672c603d 100644 --- a/plugins/scaffolder/src/types.ts +++ b/plugins/scaffolder/src/types.ts @@ -13,6 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +export type JobStatus = 'PENDING' | 'STARTED' | 'COMPLETED' | 'FAILED'; export type Job = { id: string; metadata: { @@ -21,7 +23,7 @@ export type Job = { remoteUrl?: string; catalogInfoUrl?: string; }; - status: 'PENDING' | 'STARTED' | 'COMPLETED' | 'FAILED'; + status: JobStatus; stages: Stage[]; error?: Error; }; @@ -29,7 +31,7 @@ export type Job = { export type Stage = { name: string; log: string[]; - status: 'PENDING' | 'STARTED' | 'COMPLETED' | 'FAILED'; + status: JobStatus; startedAt: string; endedAt?: string; }; From b877f46fde512dd7b7168c5977b65bdd01963982 Mon Sep 17 00:00:00 2001 From: blam Date: Sun, 22 Nov 2020 03:07:23 +0100 Subject: [PATCH 149/430] chore(lint): fixing linting for hooks --- plugins/scaffolder/src/components/hooks/useJobPolling.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder/src/components/hooks/useJobPolling.ts b/plugins/scaffolder/src/components/hooks/useJobPolling.ts index 91705c108f..1bfdfc53b8 100644 --- a/plugins/scaffolder/src/components/hooks/useJobPolling.ts +++ b/plugins/scaffolder/src/components/hooks/useJobPolling.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { useState, useEffect } from 'react'; +import { useState } from 'react'; import { Job } from '../../types'; import { useApi } from '@backstage/core'; import { scaffolderApiRef } from '../../api'; From 9cdf4413db1a45c313eaa7a15d0689ff3914a8bd Mon Sep 17 00:00:00 2001 From: blam Date: Sun, 22 Nov 2020 03:20:42 +0100 Subject: [PATCH 150/430] chore(scaffolder): trying to fix the linting and tsc issues --- .../src/components/JobStatusModal/JobStatusModal.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/scaffolder/src/components/JobStatusModal/JobStatusModal.tsx b/plugins/scaffolder/src/components/JobStatusModal/JobStatusModal.tsx index f0aa5ccd81..01a6346d24 100644 --- a/plugins/scaffolder/src/components/JobStatusModal/JobStatusModal.tsx +++ b/plugins/scaffolder/src/components/JobStatusModal/JobStatusModal.tsx @@ -28,7 +28,7 @@ import { Job } from '../../types'; import { JobStage } from '../JobStage/JobStage'; type Props = { - job: Job; + job: Job | null; toCatalogLink?: string; }; @@ -47,6 +47,9 @@ export const JobStatusModal = ({ job, toCatalogLink }: Props) => { }; const onClose = useCallback(() => { + if (!job) { + return; + } if (job.status === 'COMPLETED' || job.status === 'FAILED') { setOpen(false); } From 031ead5f5a81a8004017327434ac44f4b3bc42bd Mon Sep 17 00:00:00 2001 From: Kevin Kelly Date: Sat, 21 Nov 2020 23:33:32 -0500 Subject: [PATCH 151/430] docs: update local install example for node v14 --- docs/getting-started/running-backstage-locally.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/getting-started/running-backstage-locally.md b/docs/getting-started/running-backstage-locally.md index a30a918fd0..6de9d234e3 100644 --- a/docs/getting-started/running-backstage-locally.md +++ b/docs/getting-started/running-backstage-locally.md @@ -8,19 +8,19 @@ description: Documentation on How to run Backstage Locally - Node.js -First make sure you are using Node.js with an Active LTS Release, currently v12. +First make sure you are using Node.js with an Active LTS Release, currently v14. This is made easy with a version manager such as [nvm](https://github.com/nvm-sh/nvm) which allows for version switching. ```bash # Installing a new version -nvm install 12 -> Downloading and installing node v12.18.3... -> Now using node v12.18.3 (npm v6.14.6) +nvm install 14 +> Downloading and installing node v14.15.1... +> Now using node v14.15.1 (npm v6.14.8) # Checking your version node --version -> v12.18.3 +> v14.15.1 ``` - Yarn From ed08863e17cb02b32242f5474c4d8295d1a94375 Mon Sep 17 00:00:00 2001 From: Kevin Kelly Date: Sat, 21 Nov 2020 23:48:36 -0500 Subject: [PATCH 152/430] docs: create an app, node version update --- docs/getting-started/create-an-app.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting-started/create-an-app.md b/docs/getting-started/create-an-app.md index 552b6c3ec2..6aef4c48c1 100644 --- a/docs/getting-started/create-an-app.md +++ b/docs/getting-started/create-an-app.md @@ -14,7 +14,7 @@ need to run Backstage in your own environment. To create a Backstage app, you will need to have [Node.js](https://nodejs.org/en/download/) Active LTS Release installed -(currently v12). +(currently v14). Backstage provides a utility for creating new apps. It guides you through the initial setup of selecting the name of the app and a database for the backend. From 0145faa6ee2faf8d4938c7d518f053a634009811 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 22 Nov 2020 14:20:13 +0100 Subject: [PATCH 153/430] cli: add Lockfile utility --- packages/cli/package.json | 3 + .../cli/src/lib/versioning/Lockfile.test.ts | 150 +++++++++++ packages/cli/src/lib/versioning/Lockfile.ts | 242 ++++++++++++++++++ packages/cli/src/lib/versioning/index.ts | 17 ++ yarn.lock | 10 + 5 files changed, 422 insertions(+) create mode 100644 packages/cli/src/lib/versioning/Lockfile.test.ts create mode 100644 packages/cli/src/lib/versioning/Lockfile.ts create mode 100644 packages/cli/src/lib/versioning/index.ts diff --git a/packages/cli/package.json b/packages/cli/package.json index ec3aaacdee..eb56b19971 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -51,6 +51,7 @@ "@types/webpack-node-externals": "^2.5.0", "@typescript-eslint/eslint-plugin": "^v3.10.1", "@typescript-eslint/parser": "^v3.10.1", + "@yarnpkg/lockfile": "^1.1.0", "bfj": "^7.0.2", "chalk": "^4.0.0", "chokidar": "^3.3.1", @@ -92,6 +93,7 @@ "rollup-plugin-postcss": "^3.1.1", "rollup-plugin-typescript2": "^0.27.3", "rollup-pluginutils": "^2.8.2", + "semver": "^7.3.2", "start-server-webpack-plugin": "^2.2.5", "style-loader": "^1.2.1", "sucrase": "^3.16.0", @@ -131,6 +133,7 @@ "@types/tar": "^4.0.3", "@types/webpack": "^4.41.7", "@types/webpack-dev-server": "^3.11.0", + "@types/yarnpkg__lockfile": "^1.1.4", "del": "^5.1.0", "mock-fs": "^4.13.0", "nodemon": "^2.0.2", diff --git a/packages/cli/src/lib/versioning/Lockfile.test.ts b/packages/cli/src/lib/versioning/Lockfile.test.ts new file mode 100644 index 0000000000..5459ed53fb --- /dev/null +++ b/packages/cli/src/lib/versioning/Lockfile.test.ts @@ -0,0 +1,150 @@ +/* + * 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 mockFs from 'mock-fs'; +import { Lockfile } from './Lockfile'; + +const HEADER = `# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + +`; + +const mockA = `${HEADER} +a@^1: + version "1.0.1" + resolved "https://my-registry/a-1.0.01.tgz#abc123" + integrity sha512-xyz + dependencies: + b "^2" + +b@2.0.x: + version "2.0.1" + +b@^2: + version "2.0.0" +`; + +const mockADedup = `${HEADER} +a@^1: + version "1.0.1" + resolved "https://my-registry/a-1.0.01.tgz#abc123" + integrity sha512-xyz + dependencies: + b "^2" + +b@2.0.x, b@^2: + version "2.0.1" +`; + +const mockB = `${HEADER} +"@s/a@*", "@s/a@1 || 2", "@s/a@^1": + version "1.0.1" + +"@s/a@^2.0.x": + version "2.0.0" +`; + +const mockBDedup = `${HEADER} +"@s/a@*", "@s/a@1 || 2", "@s/a@^2.0.x": + version "2.0.0" + +"@s/a@^1": + version "1.0.1" +`; + +describe('Lockfile', () => { + afterEach(() => { + mockFs.restore(); + }); + + it('should load and serialize mockA', async () => { + mockFs({ + '/yarn.lock': mockA, + }); + + const lockfile = await Lockfile.load('/yarn.lock'); + expect(lockfile.get('a')).toEqual([{ range: '^1', version: '1.0.1' }]); + expect(lockfile.get('b')).toEqual([ + { range: '2.0.x', version: '2.0.1' }, + { range: '^2', version: '2.0.0' }, + ]); + expect(lockfile.toString()).toBe(mockA); + }); + + it('should deduplicate mockA', async () => { + mockFs({ + '/yarn.lock': mockA, + }); + + const lockfile = await Lockfile.load('/yarn.lock'); + const result = lockfile.analyze(); + expect(result).toEqual({ + invalidRanges: [], + newRanges: [], + newVersions: [ + { + name: 'b', + range: '^2', + oldVersion: '2.0.0', + newVersion: '2.0.1', + }, + ], + }); + + expect(lockfile.toString()).toBe(mockA); + lockfile.replaceVersions(result.newVersions); + expect(lockfile.toString()).toBe(mockADedup); + }); + + it('should deduplicate mockB', async () => { + mockFs({ + '/yarn.lock': mockB, + }); + + const lockfile = await Lockfile.load('/yarn.lock'); + const result = lockfile.analyze(); + expect(result).toEqual({ + invalidRanges: [], + newRanges: [ + { + name: '@s/a', + oldRange: '^1', + newRange: '*', + oldVersion: '1.0.1', + newVersion: '2.0.0', + }, + ], + newVersions: [ + { + name: '@s/a', + range: '*', + oldVersion: '1.0.1', + newVersion: '2.0.0', + }, + { + name: '@s/a', + range: '1 || 2', + oldVersion: '1.0.1', + newVersion: '2.0.0', + }, + ], + }); + + expect(lockfile.toString()).toBe(mockB); + lockfile.replaceVersions(result.newVersions); + expect(lockfile.toString()).toBe(mockBDedup); + }); +}); diff --git a/packages/cli/src/lib/versioning/Lockfile.ts b/packages/cli/src/lib/versioning/Lockfile.ts new file mode 100644 index 0000000000..e03fa8ec3f --- /dev/null +++ b/packages/cli/src/lib/versioning/Lockfile.ts @@ -0,0 +1,242 @@ +/* + * 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 fs from 'fs-extra'; +import semver from 'semver'; +import { + parse as parseLockfile, + stringify as stringifyLockfile, +} from '@yarnpkg/lockfile'; + +const ENTRY_PATTERN = /^((?:@[^/]+\/)?[^@/]+)@(.+)$/; + +type LockfileData = { + [entry: string]: { + version: string; + resolved?: string; + integrity?: string; + dependencies?: { [name: string]: string }; + }; +}; + +type LockfileQueryEntry = { + range: string; + version: string; +}; + +/** Entries that have an invalid version range, for example an NPM tag */ +type AnalyzeResultInvalidRange = { + name: string; + range: string; +}; + +/** Entries that can be deduplicated by bumping to an existing higher version */ +type AnalyzeResultNewVersion = { + name: string; + range: string; + oldVersion: string; + newVersion: string; +}; + +/** Entries that {would need a dependency update in package.json to be deduplicated */ +type AnalyzeResultNewRange = { + name: string; + oldRange: string; + newRange: string; + oldVersion: string; + newVersion: string; +}; + +type AnalyzeResult = { + invalidRanges: AnalyzeResultInvalidRange[]; + newVersions: AnalyzeResultNewVersion[]; + newRanges: AnalyzeResultNewRange[]; +}; + +export class Lockfile { + static async load(path: string) { + const lockfileContents = await fs.readFile(path, 'utf8'); + const lockfile = parseLockfile(lockfileContents); + if (lockfile.type !== 'success') { + throw new Error(`Failed yarn.lock parse with ${lockfile.type}`); + } + + const data = lockfile.object as LockfileData; + const packages = new Map(); + + for (const [key, value] of Object.entries(data)) { + const [, name, range] = ENTRY_PATTERN.exec(key) ?? []; + if (!name) { + throw new Error(`Failed to parse yarn.lock entry '${key}'`); + } + + let queries = packages.get(name); + if (!queries) { + queries = []; + packages.set(name, queries); + } + queries.push({ range, version: value.version }); + } + + return new Lockfile(packages, data); + } + + constructor( + private readonly packages: Map, + private readonly data: LockfileData, + ) {} + + get(name: string): LockfileQueryEntry[] | undefined { + return this.packages.get(name); + } + + /** Analyzes the lockfile to identify possible actions and warnings for the entries */ + analyze(options?: { filter?: (name: string) => boolean }): AnalyzeResult { + const { filter } = options ?? {}; + const result: AnalyzeResult = { + invalidRanges: [], + newVersions: [], + newRanges: [], + }; + + for (const [name, allEntries] of this.packages) { + if (filter && !filter(name)) { + continue; + } + + // Get rid of an signal any invalid ranges upfront + const invalid = allEntries.filter(e => !semver.validRange(e.range)); + result.invalidRanges.push( + ...invalid.map(({ range }) => ({ name, range })), + ); + + // Grab all valid entries, if there isn't at least 2 different valid ones we're done + const entries = allEntries.filter(e => semver.validRange(e.range)); + if (entries.length < 2) { + continue; + } + + // Find all versions currently in use + const versions = Array.from( + new Set(entries.map(e => e.version)), + ).sort((v1, v2) => semver.rcompare(v1, v2)); + + // If we're not using at least 2 different versions we're done + if (versions.length < 2) { + continue; + } + + const acceptedVersions = new Set(); + for (const { version, range } of entries) { + // Finds the highest matching version from the the known versions + // TODO(Rugvip): We may want to select the version that satisfies the most ranges rather than the highest one + const acceptedVersion = versions.find(v => semver.satisfies(v, range)); + if (!acceptedVersion) { + throw new Error( + `No existing version was accepted for range ${range}, searching through ${versions}`, + ); + } + + if (acceptedVersion !== version) { + result.newVersions.push({ + name, + range, + newVersion: acceptedVersion, + oldVersion: version, + }); + } + + acceptedVersions.add(acceptedVersion); + } + + // If all ranges where able to accept the same version, we're done + if (acceptedVersions.size === 1) { + continue; + } + + // Find the max version and range that we may want bump older packages to + const maxVersion = Array.from(acceptedVersions).sort(semver.rcompare)[0]; + const maxEntry = entries.find(e => semver.satisfies(maxVersion, e.range)); + if (!maxEntry) { + throw new Error( + `No entry found that satisfies max version '${maxVersion}'`, + ); + } + + // Find all entries that don't satisfy the max version + for (const { version, range } of entries) { + if (semver.satisfies(maxVersion, range)) { + continue; + } + + result.newRanges.push({ + name, + oldRange: range, + newRange: maxEntry.range, + oldVersion: version, + newVersion: maxVersion, + }); + } + } + + return result; + } + + /** Modifies the lockfile by bumping packages to the suggested versions */ + replaceVersions(results: AnalyzeResultNewVersion[]) { + for (const { name, range, oldVersion, newVersion } of results) { + const query = `${name}@${range}`; + + // Update the backing data + const entryData = this.data[query]; + if (!entryData) { + throw new Error(`No entry data for ${query}`); + } + if (entryData.version !== oldVersion) { + throw new Error( + `Expected existing version data for ${query} to be ${oldVersion}, was ${entryData.version}`, + ); + } + + // Modifying the data in the entry is not enough, we need to reference an existing version object + const matchingEntry = Object.entries(this.data).find( + ([q, e]) => q.startsWith(`${name}@`) && e.version === newVersion, + ); + if (!matchingEntry) { + throw new Error( + `No matching entry found for ${name} at version ${newVersion}`, + ); + } + this.data[query] = matchingEntry[1]; + + // Update out internal data structure + const entry = this.packages.get(name)?.find(e => e.range === range); + if (!entry) { + throw new Error(`No entry data for ${query}`); + } + if (entry.version !== oldVersion) { + throw new Error( + `Expected existing version data for ${query} to be ${oldVersion}, was ${entryData.version}`, + ); + } + entry.version = newVersion; + } + } + + toString() { + return stringifyLockfile(this.data); + } +} diff --git a/packages/cli/src/lib/versioning/index.ts b/packages/cli/src/lib/versioning/index.ts new file mode 100644 index 0000000000..6f0a649ee4 --- /dev/null +++ b/packages/cli/src/lib/versioning/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 { Lockfile } from './Lockfile'; diff --git a/yarn.lock b/yarn.lock index a90d885927..92aecbba36 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6088,6 +6088,11 @@ dependencies: "@types/yargs-parser" "*" +"@types/yarnpkg__lockfile@^1.1.4": + version "1.1.4" + resolved "https://registry.npmjs.org/@types/yarnpkg__lockfile/-/yarnpkg__lockfile-1.1.4.tgz#445251eb00bd9c1e751f82c7c6bf4f714edfd464" + integrity sha512-/emrKCfQMQmFCqRqqBJ0JueHBT06jBRM3e8OgnvDUcvuExONujIk2hFA5dNsN9Nt41ljGVDdChvCydATZ+KOZw== + "@types/yup@^0.29.8": version "0.29.8" resolved "https://registry.npmjs.org/@types/yup/-/yup-0.29.8.tgz#83db15735987db9fe5a38772a0fb9500e3c5bf39" @@ -6384,6 +6389,11 @@ resolved "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== +"@yarnpkg/lockfile@^1.1.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" + integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== + "@zkochan/cmd-shim@^3.1.0": version "3.1.0" resolved "https://registry.npmjs.org/@zkochan/cmd-shim/-/cmd-shim-3.1.0.tgz#2ab8ed81f5bb5452a85f25758eb9b8681982fd2e" From 4c642721376961ecc06c9abde42ba70534d9b63c Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 22 Nov 2020 14:48:12 +0100 Subject: [PATCH 154/430] cli: make Lockfile analyze pick the most specific new version range --- packages/cli/src/lib/versioning/Lockfile.test.ts | 2 +- packages/cli/src/lib/versioning/Lockfile.ts | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/lib/versioning/Lockfile.test.ts b/packages/cli/src/lib/versioning/Lockfile.test.ts index 5459ed53fb..20847242d3 100644 --- a/packages/cli/src/lib/versioning/Lockfile.test.ts +++ b/packages/cli/src/lib/versioning/Lockfile.test.ts @@ -122,7 +122,7 @@ describe('Lockfile', () => { { name: '@s/a', oldRange: '^1', - newRange: '*', + newRange: '^2.0.x', oldVersion: '1.0.1', newVersion: '2.0.0', }, diff --git a/packages/cli/src/lib/versioning/Lockfile.ts b/packages/cli/src/lib/versioning/Lockfile.ts index e03fa8ec3f..8acb387704 100644 --- a/packages/cli/src/lib/versioning/Lockfile.ts +++ b/packages/cli/src/lib/versioning/Lockfile.ts @@ -167,9 +167,15 @@ export class Lockfile { continue; } - // Find the max version and range that we may want bump older packages to + // Find the max version that we may want bump older packages to const maxVersion = Array.from(acceptedVersions).sort(semver.rcompare)[0]; - const maxEntry = entries.find(e => semver.satisfies(maxVersion, e.range)); + // Find all existing ranges that satisfy the new max version, and pick the one that + // results in the highest minimum allowed version, usually being the more specific one + const maxEntry = entries + .filter(e => semver.satisfies(maxVersion, e.range)) + .map(e => ({ e, min: semver.minVersion(e.range) })) + .filter(p => p.min) + .sort((a, b) => semver.rcompare(a.min!, b.min!))[0]?.e; if (!maxEntry) { throw new Error( `No entry found that satisfies max version '${maxVersion}'`, From 46a20ad22fbfd2f5cf3965e8eedbf3a2118b6f35 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 22 Nov 2020 15:58:28 +0100 Subject: [PATCH 155/430] cli: add versions:lint command --- packages/cli/src/commands/index.ts | 6 ++ packages/cli/src/commands/versions/lint.ts | 114 +++++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 packages/cli/src/commands/versions/lint.ts diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index 1ebe518cac..3f3e8bfe83 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -154,6 +154,12 @@ export function registerCommands(program: CommanderStatic) { ) .action(lazy(() => import('./config/validate').then(m => m.default))); + program + .command('versions:lint') + .option('--fix', 'Fix any auto-fixable versioning problems') + .description('Lint Backstage package versioning') + .action(lazy(() => import('./versions/lint').then(m => m.default))); + program .command('prepack') .description('Prepares a package for packaging before publishing') diff --git a/packages/cli/src/commands/versions/lint.ts b/packages/cli/src/commands/versions/lint.ts new file mode 100644 index 0000000000..2f200a75a9 --- /dev/null +++ b/packages/cli/src/commands/versions/lint.ts @@ -0,0 +1,114 @@ +/* + * 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 fs from 'fs-extra'; +import { Command } from 'commander'; +import { Lockfile } from '../../lib/versioning'; +import { paths } from '../../lib/paths'; +import partition from 'lodash/partition'; + +// Packages that we try to avoid duplicates for +const INCLUDED = [/^@backstage\//]; + +// Packages that are not allowed to have any duplicates +const FORBID_DUPLICATES = [ + /^@backstage\/core$/, + /^@backstage\/core-api$/, + /^@backstage\/plugin-/, +]; + +export default async (cmd: Command) => { + const fix = Boolean(cmd.fix); + + let success = true; + + const lockfilePath = paths.resolveTargetRoot('yarn.lock'); + const lockfile = await Lockfile.load(lockfilePath); + const result = lockfile.analyze({ + filter: name => INCLUDED.some(pattern => pattern.test(name)), + }); + + logArray( + result.invalidRanges, + "The following packages versions are invalid and can't be analyzed:", + e => ` ${e.name} @ ${e.range}`, + ); + + if (fix) { + lockfile.replaceVersions(result.newVersions); + + await fs.writeFile(lockfilePath, lockfile.toString(), 'utf8'); + } else { + const [ + newVersionsForbidden, + newVersionsAllowed, + ] = partition(result.newVersions, ({ name }) => + FORBID_DUPLICATES.some(pattern => pattern.test(name)), + ); + if (newVersionsForbidden.length && !fix) { + success = false; + } + + logArray( + newVersionsForbidden, + 'The following packages must be deduplicated, this can be done automatically with --fix', + e => + ` ${e.name} @ ${e.range} bumped from ${e.oldVersion} to ${e.newVersion}`, + ); + logArray( + newVersionsAllowed, + 'The following packages can be deduplicated, this can be done automatically with --fix', + e => + ` ${e.name} @ ${e.range} bumped from ${e.oldVersion} to ${e.newVersion}`, + ); + } + + const [newRangesForbidden, newRangesAllowed] = partition( + result.newRanges, + ({ name }) => FORBID_DUPLICATES.some(pattern => pattern.test(name)), + ); + if (newRangesForbidden.length) { + success = false; + } + + logArray( + newRangesForbidden, + 'The following packages must be deduplicated by updating dependencies in package.json', + e => ` ${e.name} @ ${e.oldRange} should be changed to ${e.newRange}`, + ); + logArray( + newRangesAllowed, + 'The following packages can be deduplicated by updating dependencies in package.json', + e => ` ${e.name} @ ${e.oldRange} should be changed to ${e.newRange}`, + ); + + if (!success) { + throw new Error('Failed versioning check'); + } +}; + +function logArray(arr: T[], header: string, each: (item: T) => void) { + if (arr.length === 0) { + return; + } + + console.log(header); + console.log(); + for (const e of arr) { + console.log(each(e)); + } + console.log(); +} From 06432a0e9973be53a73bd833e1b42eddb1901b5f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 22 Nov 2020 17:17:44 +0100 Subject: [PATCH 156/430] cli: add versions:bump command --- packages/cli/src/commands/index.ts | 5 + packages/cli/src/commands/versions/bump.ts | 199 +++++++++++++++++++++ 2 files changed, 204 insertions(+) create mode 100644 packages/cli/src/commands/versions/bump.ts diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index 3f3e8bfe83..9f967a2727 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -154,6 +154,11 @@ export function registerCommands(program: CommanderStatic) { ) .action(lazy(() => import('./config/validate').then(m => m.default))); + program + .command('versions:bump') + .description('Bump Backstage packages to the latest versions') + .action(lazy(() => import('./versions/bump').then(m => m.default))); + program .command('versions:lint') .option('--fix', 'Fix any auto-fixable versioning problems') diff --git a/packages/cli/src/commands/versions/bump.ts b/packages/cli/src/commands/versions/bump.ts new file mode 100644 index 0000000000..87f6d6c12b --- /dev/null +++ b/packages/cli/src/commands/versions/bump.ts @@ -0,0 +1,199 @@ +/* + * 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 fs from 'fs-extra'; +import semver from 'semver'; +import { resolve as resolvePath } from 'path'; +import { run, runPlain } from '../../lib/run'; +import { paths } from '../../lib/paths'; +import { Lockfile } from '../../lib/versioning'; + +const PREFIX = '@backstage'; + +const DEP_TYPES = [ + 'dependencies', + 'devDependencies', + 'peerDependencies', + 'optionalDependencies', +]; + +// Package data as returned by `yarn info` +type YarnInfoInspectData = { + name: string; + 'dist-tags': { latest: string }; + versions: string[]; + time: { [version: string]: string }; +}; + +// Possible `yarn info` output +type YarnInfo = { + type: 'inspect'; + data: YarnInfoInspectData | { type: string; data: unknown }; +}; + +type PkgVersionInfo = { + range: string; + name: string; + location: string; +}; + +export default async () => { + const LernaProject = require('@lerna/project'); + const project = new LernaProject(paths.targetDir); + const packages = await project.getPackages(); + + // First we discover all Backstage dependencies within our own repo + const dependencyMap = new Map(); + for (const pkg of packages) { + const deps = DEP_TYPES.flatMap( + t => Object.entries(pkg.get(t) ?? {}) as [string, string][], + ); + + for (const [name, range] of deps) { + if (name.startsWith(PREFIX)) { + dependencyMap.set( + name, + (dependencyMap.get(name) ?? []).concat({ + range, + name: pkg.name, + location: pkg.location, + }), + ); + } + } + } + + // Next check with the package registry what the latest version of all of those dependencies are + const targetVersions = new Map(); + await workerThreads(16, dependencyMap.keys(), async name => { + console.log(`Checking for updates of ${name}`); + const output = await runPlain('yarn', 'info', '--json', name); + const info = JSON.parse(output) as YarnInfo; + if (info.type !== 'inspect') { + throw new Error(`Received unknown yarn info for ${name}, ${output}`); + } + + const data = info.data as YarnInfoInspectData; + const latest = data['dist-tags'].latest; + if (!latest) { + throw new Error(`No latest version found for ${name}`); + } + + targetVersions.set(name, latest); + }); + + // Then figure out which local packages need to have their dependencies bumped + const versionBumps = new Map(); + for (const [name, pkgs] of dependencyMap) { + const targetVersion = targetVersions.get(name)!; + for (const pkg of pkgs) { + if (semver.satisfies(targetVersion, pkg.range)) { + continue; + } + + versionBumps.set( + pkg.name, + (versionBumps.get(pkg.name) ?? []).concat({ + name, + location: pkg.location, + range: `^${targetVersion}`, // TODO(Rugvip): Option to use something else than ^? + }), + ); + } + } + + console.log(); + + // Write all discovered version bumps to package.json in this repo + if (versionBumps.size === 0) { + console.log('All Backstage packages are up to date!'); + } else { + console.log('Some packages are outdated, updating'); + console.log(); + + await workerThreads(16, versionBumps.entries(), async ([name, deps]) => { + const pkgPath = resolvePath(deps[0].location, 'package.json'); + const pkgJson = await fs.readJson(pkgPath); + + for (const dep of deps) { + console.log(`Bumping ${dep.name} in ${name} to ${dep.range}`); + + for (const depType of DEP_TYPES) { + if (depType in pkgJson && dep.name in pkgJson[depType]) { + pkgJson[depType][dep.name] = dep.range; + } + } + } + + await fs.writeJson(pkgPath, pkgJson, { spaces: 2 }); + }); + + console.log(); + console.log("Running 'yarn install' to install new versions"); + console.log(); + await run('yarn', ['install']); + } + + console.log(); + + // Finally we make sure the new lockfile doesn't have any duplicates + const lockfilePath = paths.resolveTargetRoot('yarn.lock'); + const lockfile = await Lockfile.load(lockfilePath); + const result = lockfile.analyze({ + filter: name => dependencyMap.has(name), + }); + + if (result.newVersions.length > 0) { + console.log(); + console.log('Removing duplicate dependencies from yarn.lock'); + lockfile.replaceVersions(result.newVersions); + await fs.writeFile(lockfilePath, lockfile.toString(), 'utf8'); + + console.log( + "Running 'yarn install' to remove duplicates from node_modules", + ); + console.log(); + await run('yarn', ['install']); + + console.log(); + } + + if (result.newRanges.length > 0) { + throw new Error( + `Version bump failed for ${result.newRanges.map(i => i.name).join(', ')}`, + ); + } +}; + +async function workerThreads( + count: number, + items: IterableIterator, + fn: (item: T) => Promise, +) { + const queue = Array.from(items); + + async function pop() { + const item = queue.pop()!; + if (!item) { + return; + } + + await fn(item); + await pop(); + } + + return Promise.all(Array(count).fill(0).map(pop)); +} From b90ad40d1c6c4e3e235fca9c1b5a652eedc625b8 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 22 Nov 2020 17:42:52 +0100 Subject: [PATCH 157/430] cli: add Lockfile.save --- packages/cli/src/commands/versions/bump.ts | 11 +++++++---- packages/cli/src/commands/versions/lint.ts | 7 ++----- packages/cli/src/lib/versioning/Lockfile.test.ts | 7 ++++++- packages/cli/src/lib/versioning/Lockfile.ts | 9 +++++++-- 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/packages/cli/src/commands/versions/bump.ts b/packages/cli/src/commands/versions/bump.ts index 87f6d6c12b..c2db4eff6e 100644 --- a/packages/cli/src/commands/versions/bump.ts +++ b/packages/cli/src/commands/versions/bump.ts @@ -150,8 +150,7 @@ export default async () => { console.log(); // Finally we make sure the new lockfile doesn't have any duplicates - const lockfilePath = paths.resolveTargetRoot('yarn.lock'); - const lockfile = await Lockfile.load(lockfilePath); + const lockfile = await Lockfile.load(paths.resolveTargetRoot('yarn.lock')); const result = lockfile.analyze({ filter: name => dependencyMap.has(name), }); @@ -160,7 +159,7 @@ export default async () => { console.log(); console.log('Removing duplicate dependencies from yarn.lock'); lockfile.replaceVersions(result.newVersions); - await fs.writeFile(lockfilePath, lockfile.toString(), 'utf8'); + await lockfile.save(); console.log( "Running 'yarn install' to remove duplicates from node_modules", @@ -195,5 +194,9 @@ async function workerThreads( await pop(); } - return Promise.all(Array(count).fill(0).map(pop)); + return Promise.all( + Array(count) + .fill(0) + .map(pop), + ); } diff --git a/packages/cli/src/commands/versions/lint.ts b/packages/cli/src/commands/versions/lint.ts index 2f200a75a9..f91d2b74eb 100644 --- a/packages/cli/src/commands/versions/lint.ts +++ b/packages/cli/src/commands/versions/lint.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import fs from 'fs-extra'; import { Command } from 'commander'; import { Lockfile } from '../../lib/versioning'; import { paths } from '../../lib/paths'; @@ -35,8 +34,7 @@ export default async (cmd: Command) => { let success = true; - const lockfilePath = paths.resolveTargetRoot('yarn.lock'); - const lockfile = await Lockfile.load(lockfilePath); + const lockfile = await Lockfile.load(paths.resolveTargetRoot('yarn.lock')); const result = lockfile.analyze({ filter: name => INCLUDED.some(pattern => pattern.test(name)), }); @@ -49,8 +47,7 @@ export default async (cmd: Command) => { if (fix) { lockfile.replaceVersions(result.newVersions); - - await fs.writeFile(lockfilePath, lockfile.toString(), 'utf8'); + await lockfile.save(); } else { const [ newVersionsForbidden, diff --git a/packages/cli/src/lib/versioning/Lockfile.test.ts b/packages/cli/src/lib/versioning/Lockfile.test.ts index 20847242d3..89126c7ce1 100644 --- a/packages/cli/src/lib/versioning/Lockfile.test.ts +++ b/packages/cli/src/lib/versioning/Lockfile.test.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import fs from 'fs-extra'; import mockFs from 'mock-fs'; import { Lockfile } from './Lockfile'; @@ -84,7 +85,7 @@ describe('Lockfile', () => { expect(lockfile.toString()).toBe(mockA); }); - it('should deduplicate mockA', async () => { + it('should deduplicate and save mockA', async () => { mockFs({ '/yarn.lock': mockA, }); @@ -107,6 +108,10 @@ describe('Lockfile', () => { expect(lockfile.toString()).toBe(mockA); lockfile.replaceVersions(result.newVersions); expect(lockfile.toString()).toBe(mockADedup); + + await expect(fs.readFile('/yarn.lock', 'utf8')).resolves.toBe(mockA); + await expect(lockfile.save()).resolves.toBeUndefined(); + await expect(fs.readFile('/yarn.lock', 'utf8')).resolves.toBe(mockADedup); }); it('should deduplicate mockB', async () => { diff --git a/packages/cli/src/lib/versioning/Lockfile.ts b/packages/cli/src/lib/versioning/Lockfile.ts index 8acb387704..59a734c314 100644 --- a/packages/cli/src/lib/versioning/Lockfile.ts +++ b/packages/cli/src/lib/versioning/Lockfile.ts @@ -91,10 +91,11 @@ export class Lockfile { queries.push({ range, version: value.version }); } - return new Lockfile(packages, data); + return new Lockfile(path, packages, data); } - constructor( + private constructor( + private readonly path: string, private readonly packages: Map, private readonly data: LockfileData, ) {} @@ -242,6 +243,10 @@ export class Lockfile { } } + async save() { + await fs.writeFile(this.path, this.toString(), 'utf8'); + } + toString() { return stringifyLockfile(this.data); } From 99134596986b1e5c7b1594a2c50b60d8a13d3995 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 22 Nov 2020 17:44:25 +0100 Subject: [PATCH 158/430] changesets: add cli versions command change --- .changeset/poor-mails-marry.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/poor-mails-marry.md diff --git a/.changeset/poor-mails-marry.md b/.changeset/poor-mails-marry.md new file mode 100644 index 0000000000..586bf91c47 --- /dev/null +++ b/.changeset/poor-mails-marry.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Add new `versions:lint` and `versions:bump` commands to simplify version management and avoid conflicts From c0f7591dd7ab805ef16e1be727636acbcd7110fd Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 22 Nov 2020 18:17:55 +0100 Subject: [PATCH 159/430] cli: refactor out parts of versioning:bump + add tests --- packages/cli/src/commands/versions/bump.ts | 58 ++-------- packages/cli/src/lib/versioning/index.ts | 1 + .../cli/src/lib/versioning/packages.test.ts | 109 ++++++++++++++++++ packages/cli/src/lib/versioning/packages.ts | 90 +++++++++++++++ 4 files changed, 209 insertions(+), 49 deletions(-) create mode 100644 packages/cli/src/lib/versioning/packages.test.ts create mode 100644 packages/cli/src/lib/versioning/packages.ts diff --git a/packages/cli/src/commands/versions/bump.ts b/packages/cli/src/commands/versions/bump.ts index c2db4eff6e..1d8b9cecf3 100644 --- a/packages/cli/src/commands/versions/bump.ts +++ b/packages/cli/src/commands/versions/bump.ts @@ -17,11 +17,13 @@ import fs from 'fs-extra'; import semver from 'semver'; import { resolve as resolvePath } from 'path'; -import { run, runPlain } from '../../lib/run'; +import { run } from '../../lib/run'; import { paths } from '../../lib/paths'; -import { Lockfile } from '../../lib/versioning'; - -const PREFIX = '@backstage'; +import { + mapDependencies, + fetchPackageInfo, + Lockfile, +} from '../../lib/versioning'; const DEP_TYPES = [ 'dependencies', @@ -30,20 +32,6 @@ const DEP_TYPES = [ 'optionalDependencies', ]; -// Package data as returned by `yarn info` -type YarnInfoInspectData = { - name: string; - 'dist-tags': { latest: string }; - versions: string[]; - time: { [version: string]: string }; -}; - -// Possible `yarn info` output -type YarnInfo = { - type: 'inspect'; - data: YarnInfoInspectData | { type: string; data: unknown }; -}; - type PkgVersionInfo = { range: string; name: string; @@ -51,43 +39,15 @@ type PkgVersionInfo = { }; export default async () => { - const LernaProject = require('@lerna/project'); - const project = new LernaProject(paths.targetDir); - const packages = await project.getPackages(); - // First we discover all Backstage dependencies within our own repo - const dependencyMap = new Map(); - for (const pkg of packages) { - const deps = DEP_TYPES.flatMap( - t => Object.entries(pkg.get(t) ?? {}) as [string, string][], - ); - - for (const [name, range] of deps) { - if (name.startsWith(PREFIX)) { - dependencyMap.set( - name, - (dependencyMap.get(name) ?? []).concat({ - range, - name: pkg.name, - location: pkg.location, - }), - ); - } - } - } + const dependencyMap = await mapDependencies(); // Next check with the package registry what the latest version of all of those dependencies are const targetVersions = new Map(); await workerThreads(16, dependencyMap.keys(), async name => { console.log(`Checking for updates of ${name}`); - const output = await runPlain('yarn', 'info', '--json', name); - const info = JSON.parse(output) as YarnInfo; - if (info.type !== 'inspect') { - throw new Error(`Received unknown yarn info for ${name}, ${output}`); - } - - const data = info.data as YarnInfoInspectData; - const latest = data['dist-tags'].latest; + const info = await fetchPackageInfo(name); + const latest = info['dist-tags'].latest; if (!latest) { throw new Error(`No latest version found for ${name}`); } diff --git a/packages/cli/src/lib/versioning/index.ts b/packages/cli/src/lib/versioning/index.ts index 6f0a649ee4..71fb7647ce 100644 --- a/packages/cli/src/lib/versioning/index.ts +++ b/packages/cli/src/lib/versioning/index.ts @@ -15,3 +15,4 @@ */ export { Lockfile } from './Lockfile'; +export { fetchPackageInfo, mapDependencies } from './packages'; diff --git a/packages/cli/src/lib/versioning/packages.test.ts b/packages/cli/src/lib/versioning/packages.test.ts new file mode 100644 index 0000000000..ba328fa356 --- /dev/null +++ b/packages/cli/src/lib/versioning/packages.test.ts @@ -0,0 +1,109 @@ +/* + * 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 mockFs from 'mock-fs'; +import * as runObj from '../run'; +import { paths } from '../paths'; +import { fetchPackageInfo, mapDependencies } from './packages'; + +describe('fetchPackageInfo', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + + it('should forward info', async () => { + jest + .spyOn(runObj, 'runPlain') + .mockResolvedValue(`{"type":"inspect","data":{"the":"data"}}`); + + await expect(fetchPackageInfo('my-package')).resolves.toEqual({ + the: 'data', + }); + expect(runObj.runPlain).toHaveBeenCalledWith( + 'yarn', + 'info', + '--json', + 'my-package', + ); + }); +}); + +describe('mapDependencies', () => { + afterEach(() => { + mockFs.restore(); + jest.resetAllMocks(); + }); + + it('should derp', async () => { + // Make sure all modules involved in package discovery are in the module cache before we mock fs + const LernaProject = require('@lerna/project'); + const project = new LernaProject(paths.targetDir); + await project.getPackages(); + + mockFs({ + '/lerna.json': JSON.stringify({ + packages: ['pkgs/*'], + }), + '/pkgs/a/package.json': JSON.stringify({ + name: 'a', + dependencies: { + '@backstage/core': '1 || 2', + }, + }), + '/pkgs/b/package.json': JSON.stringify({ + name: 'b', + dependencies: { + '@backstage/core': '3', + '@backstage/cli': '^0', + }, + }), + }); + + const oldDir = paths.targetDir; + paths.targetDir = '/'; + + const dependencyMap = await mapDependencies(); + expect(Array.from(dependencyMap)).toEqual([ + [ + '@backstage/core', + [ + { + name: 'a', + range: '1 || 2', + location: '/pkgs/a', + }, + { + name: 'b', + range: '3', + location: '/pkgs/b', + }, + ], + ], + [ + '@backstage/cli', + [ + { + name: 'b', + range: '^0', + location: '/pkgs/b', + }, + ], + ], + ]); + + paths.targetDir = oldDir; + }); +}); diff --git a/packages/cli/src/lib/versioning/packages.ts b/packages/cli/src/lib/versioning/packages.ts new file mode 100644 index 0000000000..239fe575d6 --- /dev/null +++ b/packages/cli/src/lib/versioning/packages.ts @@ -0,0 +1,90 @@ +/* + * 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 { runPlain } from '../../lib/run'; +import { paths } from '../../lib/paths'; + +const PREFIX = '@backstage'; + +const DEP_TYPES = [ + 'dependencies', + 'devDependencies', + 'peerDependencies', + 'optionalDependencies', +]; + +// Package data as returned by `yarn info` +type YarnInfoInspectData = { + name: string; + 'dist-tags': { latest: string }; + versions: string[]; + time: { [version: string]: string }; +}; + +// Possible `yarn info` output +type YarnInfo = { + type: 'inspect'; + data: YarnInfoInspectData | { type: string; data: unknown }; +}; + +type PkgVersionInfo = { + range: string; + name: string; + location: string; +}; + +export async function fetchPackageInfo( + name: string, +): Promise { + const output = await runPlain('yarn', 'info', '--json', name); + const info = JSON.parse(output) as YarnInfo; + if (info.type !== 'inspect') { + throw new Error(`Received unknown yarn info for ${name}, ${output}`); + } + + return info.data as YarnInfoInspectData; +} + +/** Map all dependencies in the repo as dependency => dependents */ +export async function mapDependencies(): Promise< + Map +> { + const LernaProject = require('@lerna/project'); + const project = new LernaProject(paths.targetDir); + const packages = await project.getPackages(); + + const dependencyMap = new Map(); + for (const pkg of packages) { + const deps = DEP_TYPES.flatMap( + t => Object.entries(pkg.get(t) ?? {}) as [string, string][], + ); + + for (const [name, range] of deps) { + if (name.startsWith(PREFIX)) { + dependencyMap.set( + name, + (dependencyMap.get(name) ?? []).concat({ + range, + name: pkg.name, + location: pkg.location, + }), + ); + } + } + } + + return dependencyMap; +} From 1af14032a090d599e6e9fc438350eb18528a21f1 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 22 Nov 2020 18:32:32 +0100 Subject: [PATCH 160/430] cli: rename versions:lint to versions:check --- .changeset/poor-mails-marry.md | 2 +- packages/cli/src/commands/index.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.changeset/poor-mails-marry.md b/.changeset/poor-mails-marry.md index 586bf91c47..fdc7e6f1b8 100644 --- a/.changeset/poor-mails-marry.md +++ b/.changeset/poor-mails-marry.md @@ -2,4 +2,4 @@ '@backstage/cli': patch --- -Add new `versions:lint` and `versions:bump` commands to simplify version management and avoid conflicts +Add new `versions:check` and `versions:bump` commands to simplify version management and avoid conflicts diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index 9f967a2727..60c1b45cfc 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -160,9 +160,9 @@ export function registerCommands(program: CommanderStatic) { .action(lazy(() => import('./versions/bump').then(m => m.default))); program - .command('versions:lint') + .command('versions:check') .option('--fix', 'Fix any auto-fixable versioning problems') - .description('Lint Backstage package versioning') + .description('Check Backstage package versioning') .action(lazy(() => import('./versions/lint').then(m => m.default))); program From 351539680bcf0eb1406591a0e81590cd89eb8114 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 22 Nov 2020 20:37:06 +0100 Subject: [PATCH 161/430] cli: review feedback on versions:* commands --- packages/cli/src/commands/versions/bump.ts | 2 +- packages/cli/src/commands/versions/lint.ts | 2 +- packages/cli/src/lib/versioning/Lockfile.ts | 10 +++++----- packages/cli/src/lib/versioning/packages.test.ts | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/cli/src/commands/versions/bump.ts b/packages/cli/src/commands/versions/bump.ts index 1d8b9cecf3..526ab9d4cf 100644 --- a/packages/cli/src/commands/versions/bump.ts +++ b/packages/cli/src/commands/versions/bump.ts @@ -145,7 +145,7 @@ async function workerThreads( const queue = Array.from(items); async function pop() { - const item = queue.pop()!; + const item = queue.pop(); if (!item) { return; } diff --git a/packages/cli/src/commands/versions/lint.ts b/packages/cli/src/commands/versions/lint.ts index f91d2b74eb..e2c36ba094 100644 --- a/packages/cli/src/commands/versions/lint.ts +++ b/packages/cli/src/commands/versions/lint.ts @@ -97,7 +97,7 @@ export default async (cmd: Command) => { } }; -function logArray(arr: T[], header: string, each: (item: T) => void) { +function logArray(arr: T[], header: string, each: (item: T) => string) { if (arr.length === 0) { return; } diff --git a/packages/cli/src/lib/versioning/Lockfile.ts b/packages/cli/src/lib/versioning/Lockfile.ts index 59a734c314..893beca6e2 100644 --- a/packages/cli/src/lib/versioning/Lockfile.ts +++ b/packages/cli/src/lib/versioning/Lockfile.ts @@ -51,7 +51,7 @@ type AnalyzeResultNewVersion = { newVersion: string; }; -/** Entries that {would need a dependency update in package.json to be deduplicated */ +/** Entries that would need a dependency update in package.json to be deduplicated */ type AnalyzeResultNewRange = { name: string; oldRange: string; @@ -118,13 +118,13 @@ export class Lockfile { continue; } - // Get rid of an signal any invalid ranges upfront + // Get rid of and signal any invalid ranges upfront const invalid = allEntries.filter(e => !semver.validRange(e.range)); result.invalidRanges.push( ...invalid.map(({ range }) => ({ name, range })), ); - // Grab all valid entries, if there isn't at least 2 different valid ones we're done + // Grab all valid entries, if there aren't at least 2 different valid ones we're done const entries = allEntries.filter(e => semver.validRange(e.range)); if (entries.length < 2) { continue; @@ -163,7 +163,7 @@ export class Lockfile { acceptedVersions.add(acceptedVersion); } - // If all ranges where able to accept the same version, we're done + // If all ranges were able to accept the same version, we're done if (acceptedVersions.size === 1) { continue; } @@ -229,7 +229,7 @@ export class Lockfile { } this.data[query] = matchingEntry[1]; - // Update out internal data structure + // Update our internal data structure const entry = this.packages.get(name)?.find(e => e.range === range); if (!entry) { throw new Error(`No entry data for ${query}`); diff --git a/packages/cli/src/lib/versioning/packages.test.ts b/packages/cli/src/lib/versioning/packages.test.ts index ba328fa356..fd37d7af95 100644 --- a/packages/cli/src/lib/versioning/packages.test.ts +++ b/packages/cli/src/lib/versioning/packages.test.ts @@ -47,7 +47,7 @@ describe('mapDependencies', () => { jest.resetAllMocks(); }); - it('should derp', async () => { + it('should read dependencies', async () => { // Make sure all modules involved in package discovery are in the module cache before we mock fs const LernaProject = require('@lerna/project'); const project = new LernaProject(paths.targetDir); From 0c597a8d62b448a152c951bdcee988a84657cba1 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 22 Nov 2020 20:18:19 +0100 Subject: [PATCH 162/430] cli: work around flaky formatting in bump.ts --- packages/cli/src/commands/versions/bump.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/commands/versions/bump.ts b/packages/cli/src/commands/versions/bump.ts index 526ab9d4cf..eec8abed9b 100644 --- a/packages/cli/src/commands/versions/bump.ts +++ b/packages/cli/src/commands/versions/bump.ts @@ -157,6 +157,6 @@ async function workerThreads( return Promise.all( Array(count) .fill(0) - .map(pop), + .map(() => pop()), ); } From 5c8e99ce191b6e5fb9b14771fe5eb64cdbc6ad62 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Sun, 22 Nov 2020 20:38:57 -0500 Subject: [PATCH 163/430] Fix entity YAML name --- docs/features/software-catalog/descriptor-format.md | 2 +- plugins/circleci/README.md | 2 +- plugins/jenkins/README.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/features/software-catalog/descriptor-format.md b/docs/features/software-catalog/descriptor-format.md index 5d7a5b9af0..c3adaabc45 100644 --- a/docs/features/software-catalog/descriptor-format.md +++ b/docs/features/software-catalog/descriptor-format.md @@ -43,7 +43,7 @@ software catalog API. "kind": "Component", "metadata": { "annotations": { - "backstage.io/managed-by-location": "file:/tmp/component-info.yaml", + "backstage.io/managed-by-location": "file:/tmp/catalog-info.yaml", "example.com/service-discovery": "artistweb", "circleci.com/project-slug": "github/example-org/artist-website" }, diff --git a/plugins/circleci/README.md b/plugins/circleci/README.md index 00a39ce6c8..c96de56617 100644 --- a/plugins/circleci/README.md +++ b/plugins/circleci/README.md @@ -61,7 +61,7 @@ proxy: ``` 5. Get and provide `CIRCLECI_AUTH_TOKEN` as env variable (https://circleci.com/docs/api/#add-an-api-token) -6. Add `circleci.com/project-slug` annotation to your component-info.yaml file in format // (https://backstage.io/docs/architecture-decisions/adrs-adr002#format) +6. Add `circleci.com/project-slug` annotation to your catalog-info.yaml file in format // (https://backstage.io/docs/architecture-decisions/adrs-adr002#format) ```yaml apiVersion: backstage.io/v1alpha1 diff --git a/plugins/jenkins/README.md b/plugins/jenkins/README.md index 1d3e7f60e5..5e0a701caf 100644 --- a/plugins/jenkins/README.md +++ b/plugins/jenkins/README.md @@ -41,7 +41,7 @@ export JENKINS_BASIC_AUTH_HEADER="Basic $HEADER" ``` 5. Run app with `yarn start` -6. Add the Jenkins folder annotation to your `component-info.yaml`, (note: currently this plugin only supports folders and Git SCM) +6. Add the Jenkins folder annotation to your `catalog-info.yaml`, (note: currently this plugin only supports folders and Git SCM) ```yaml apiVersion: backstage.io/v1alpha1 From da8ca6cd1c13e13390b36da8f9ae84b7d0396317 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Sun, 22 Nov 2020 20:51:05 -0500 Subject: [PATCH 164/430] Fix typo --- plugins/circleci/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/circleci/README.md b/plugins/circleci/README.md index c96de56617..4802e8bee2 100644 --- a/plugins/circleci/README.md +++ b/plugins/circleci/README.md @@ -88,4 +88,4 @@ spec: ## Limitations - CircleCI has pretty strict rate limits per token, be careful with opened tabs -- CircelCI doesn't provide a way to auth by 3rd party (e.g. GitHub) token, nor by calling their OAuth endpoints, which currently stands in the way of better auth integration with Backstage (https://discuss.circleci.com/t/circleci-api-authorization-with-github-token/5356) +- CircleCI doesn't provide a way to auth by 3rd party (e.g. GitHub) token, nor by calling their OAuth endpoints, which currently stands in the way of better auth integration with Backstage (https://discuss.circleci.com/t/circleci-api-authorization-with-github-token/5356) From fe3619782bbbbe2cc5d4b2a2b0fca731315d88da Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Sun, 22 Nov 2020 20:57:40 -0500 Subject: [PATCH 165/430] Change entity YAML name --- .../scaffolder-backend/src/scaffolder/stages/templater/cra.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/templater/cra.ts b/plugins/scaffolder-backend/src/scaffolder/stages/templater/cra.ts index da33149c24..0fcd8a4f15 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/templater/cra.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/templater/cra.ts @@ -48,7 +48,7 @@ export class CreateReactAppTemplater implements TemplaterBase { }, }); - // Need to also make a component-info.yaml to store the data about the service. + // Need to also make a catalog-info.yaml to store the data about the service. const componentInfo = { apiVersion: 'backstage.io/v1alpha1', kind: 'Component', @@ -69,7 +69,7 @@ export class CreateReactAppTemplater implements TemplaterBase { ); await fs.promises.writeFile( - `${finalDir}/component-info.yaml`, + `${finalDir}/catalog-info.yaml`, yaml.stringify(componentInfo), ); From 5a1d8dca38ffa26303e8a9ab2bf02b214bb1eda7 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Sun, 22 Nov 2020 21:01:08 -0500 Subject: [PATCH 166/430] Add changeset --- .changeset/tidy-brooms-look.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tidy-brooms-look.md diff --git a/.changeset/tidy-brooms-look.md b/.changeset/tidy-brooms-look.md new file mode 100644 index 0000000000..f2c2a9064e --- /dev/null +++ b/.changeset/tidy-brooms-look.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +Fix React entity YAML filename to new standard From af45dc5320457f660b408dfe4b91f0a909de7c2c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Nov 2020 06:01:33 +0000 Subject: [PATCH 167/430] build(deps-dev): bump eslint-plugin-cypress from 2.11.1 to 2.11.2 Bumps [eslint-plugin-cypress](https://github.com/cypress-io/eslint-plugin-cypress) from 2.11.1 to 2.11.2. - [Release notes](https://github.com/cypress-io/eslint-plugin-cypress/releases) - [Commits](https://github.com/cypress-io/eslint-plugin-cypress/compare/v2.11.1...v2.11.2) Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index bd1757cea0..bdf52a4bd5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11122,9 +11122,9 @@ eslint-module-utils@^2.1.1, eslint-module-utils@^2.6.0: pkg-dir "^2.0.0" eslint-plugin-cypress@^2.10.3: - version "2.11.1" - resolved "https://registry.npmjs.org/eslint-plugin-cypress/-/eslint-plugin-cypress-2.11.1.tgz#a945e2774b88211e2c706a059d431e262b5c2862" - integrity sha512-MxMYoReSO5+IZMGgpBZHHSx64zYPSPTpXDwsgW7ChlJTF/sA+obqRbHplxD6sBStE+g4Mi0LCLkG4t9liu//mQ== + 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" From 0e352e981ddcb3619c24360449b0488bae8fae22 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Mon, 23 Nov 2020 09:33:09 +0100 Subject: [PATCH 168/430] Make clicking on the item to execute search work --- packages/core/src/layout/Sidebar/Items.tsx | 35 ++++++++++++++-------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/packages/core/src/layout/Sidebar/Items.tsx b/packages/core/src/layout/Sidebar/Items.tsx index b8786886cc..929828cee4 100644 --- a/packages/core/src/layout/Sidebar/Items.tsx +++ b/packages/core/src/layout/Sidebar/Items.tsx @@ -14,23 +14,23 @@ * limitations under the License. */ +import { IconComponent } from '@backstage/core-api'; +import { BackstageTheme } from '@backstage/theme'; import { + Badge, makeStyles, styled, TextField, Typography, - Badge, } from '@material-ui/core'; -import { BackstageTheme } from '@backstage/theme'; -import { IconComponent } from '@backstage/core-api'; import SearchIcon from '@material-ui/icons/Search'; import clsx from 'clsx'; import React, { + forwardRef, + KeyboardEventHandler, + ReactNode, useContext, useState, - KeyboardEventHandler, - forwardRef, - ReactNode, } from 'react'; import { NavLink } from 'react-router-dom'; import { sidebarConfig, SidebarContext } from './config'; @@ -120,7 +120,7 @@ type SidebarItemProps = { // If 'to' is set the item will act as a nav link with highlight, otherwise it's just a button to?: string; hasNotifications?: boolean; - onClick?: () => void; + onClick?: (ev: React.MouseEvent) => void; children?: ReactNode; }; @@ -218,10 +218,14 @@ export const SidebarSearchField = (props: SidebarSearchFieldProps) => { const [input, setInput] = useState(''); const classes = useStyles(); + const search = () => { + props.onSearch(input); + setInput(''); + }; + const handleEnter: KeyboardEventHandler = ev => { if (ev.key === 'Enter') { - props.onSearch(input); - setInput(''); + search(); } }; @@ -229,18 +233,25 @@ export const SidebarSearchField = (props: SidebarSearchFieldProps) => { setInput(ev.target.value); }; - const handleClick = (ev: React.MouseEvent) => { + const handleInputClick = (ev: React.MouseEvent) => { // Clicking into the search fields shouldn't navigate to the search page ev.preventDefault(); + ev.stopPropagation(); + }; + + const handleItemClick = (ev: React.MouseEvent) => { + // Clicking on the search icon while should execute a query with the current field content + search(); + ev.preventDefault(); }; return (
- + Date: Mon, 23 Nov 2020 11:25:45 +0100 Subject: [PATCH 169/430] backend-common: make test-utils a dev dep --- packages/backend-common/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index d4f0052d28..b22e4638f5 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -33,7 +33,6 @@ "@backstage/config": "^0.1.1", "@backstage/config-loader": "^0.3.0", "@backstage/integration": "^0.1.1", - "@backstage/test-utils": "^0.1.3", "@types/cors": "^2.8.6", "@types/express": "^4.17.6", "archiver": "^5.0.2", @@ -69,6 +68,7 @@ }, "devDependencies": { "@backstage/cli": "^0.3.0", + "@backstage/test-utils": "^0.1.3", "@types/archiver": "^3.1.1", "@types/compression": "^1.7.0", "@types/concat-stream": "^1.6.0", From 31d8b6979d99f627d678a2cdedde06f32ea1e0c3 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 23 Nov 2020 11:29:26 +0100 Subject: [PATCH 170/430] cli: add experimental backend:bundle command --- .changeset/orange-cherries-run.md | 5 +++ .gitignore | 1 + packages/cli/src/commands/backend/bundle.ts | 39 +++++++++++++++++++++ packages/cli/src/commands/index.ts | 5 +++ 4 files changed, 50 insertions(+) create mode 100644 .changeset/orange-cherries-run.md create mode 100644 packages/cli/src/commands/backend/bundle.ts diff --git a/.changeset/orange-cherries-run.md b/.changeset/orange-cherries-run.md new file mode 100644 index 0000000000..3c06c27b47 --- /dev/null +++ b/.changeset/orange-cherries-run.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Add experimental backend:bundle command diff --git a/.gitignore b/.gitignore index 5c27601791..3334bf956d 100644 --- a/.gitignore +++ b/.gitignore @@ -96,6 +96,7 @@ typings/ .nuxt dist dist-types +dist-workspace # Gatsby files .cache/ diff --git a/packages/cli/src/commands/backend/bundle.ts b/packages/cli/src/commands/backend/bundle.ts new file mode 100644 index 0000000000..e604ae9733 --- /dev/null +++ b/packages/cli/src/commands/backend/bundle.ts @@ -0,0 +1,39 @@ +/* + * 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 { Command } from 'commander'; +import fs from 'fs-extra'; +import { createDistWorkspace } from '../../lib/packager'; +import { paths } from '../../lib/paths'; +import { parseParallel, PARALLEL_ENV_VAR } from '../../lib/parallel'; + +const PKG_PATH = 'package.json'; +const TARGET_DIR = 'dist-workspace'; + +export default async (cmd: Command) => { + const targetDir = paths.resolveTarget(TARGET_DIR); + const pkgPath = paths.resolveTarget(PKG_PATH); + const pkg = await fs.readJson(pkgPath); + + await fs.remove(targetDir); + await fs.mkdir(targetDir); + await createDistWorkspace([pkg.name], { + targetDir: targetDir, + buildDependencies: Boolean(cmd.build), + parallel: parseParallel(process.env[PARALLEL_ENV_VAR]), + skeleton: 'skeleton.tar', + }); +}; diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index 1ebe518cac..bbd577d069 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -44,6 +44,11 @@ export function registerCommands(program: CommanderStatic) { .description('Build a backend plugin') .action(lazy(() => import('./backend/build').then(m => m.default))); + program + .command('backend:__experimental__bundle__', { hidden: true }) + .description('Bundle all backend packages into dist-workspace') + .action(lazy(() => import('./backend/bundle').then(m => m.default))); + program .command('backend:build-image') .allowUnknownOption(true) From 1185919f399ea9df8340daed9a679d35101a2550 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 23 Nov 2020 11:29:37 +0100 Subject: [PATCH 171/430] catalog: mark ancestors and descendants for deprecation (#3392) --- .changeset/forty-buses-watch.md | 14 ++++++ .../software-catalog/descriptor-format.md | 10 ++++ .../src/kinds/GroupEntityV1alpha1.ts | 14 ++++++ .../BuiltinKindsEntityProcessor.test.ts | 47 +++++++++++++++++++ .../processors/BuiltinKindsEntityProcessor.ts | 19 ++++++++ 5 files changed, 104 insertions(+) create mode 100644 .changeset/forty-buses-watch.md create mode 100644 plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts diff --git a/.changeset/forty-buses-watch.md b/.changeset/forty-buses-watch.md new file mode 100644 index 0000000000..4074566fe7 --- /dev/null +++ b/.changeset/forty-buses-watch.md @@ -0,0 +1,14 @@ +--- +'@backstage/catalog-model': patch +'@backstage/plugin-catalog-backend': patch +--- + +Marked the `Group` entity fields `ancestors` and `descendants` for deprecation on Dec 6th, 2020. See https://github.com/backstage/backstage/issues/3049 for details. + +Code that consumes these fields should remove those usages as soon as possible. There is no current or planned replacement for these fields. + +The BuiltinKindsEntityProcessor has been updated to inject these fields as empty arrays if they are missing. Therefore, if you are on a catalog instance that uses the updated version of this code, you can start removing the fields from your source catalog-info.yaml data as well, without breaking validation. + +After Dec 6th, the fields will be removed from types and classes of the Backstage repository. At the first release after that, they will not be present in released packages either. + +If your catalog-info.yaml files still contain these fields after the deletion, they will still be valid and your ingestion will not break, but they won't be visible in the types for consuming code. diff --git a/docs/features/software-catalog/descriptor-format.md b/docs/features/software-catalog/descriptor-format.md index c3adaabc45..f5cb361b81 100644 --- a/docs/features/software-catalog/descriptor-format.md +++ b/docs/features/software-catalog/descriptor-format.md @@ -700,6 +700,11 @@ sufficient to enter only the `metadata.name` field of that group. ### `spec.ancestors` [required] +**NOTE**: This field was marked for deprecation on Nov 22nd, 2020. It will be +removed entirely from the model on Dec 6th, 2020 in the repository and will not +be present in released packages following the next release after that. Please +update your code to not consume this field before the removal date. + The recursive list of parents up the hierarchy, by stepping through parents one by one. The list must be present, but may be empty if `parent` is not present. The first entry in the list is equal to `parent`, and then the following ones @@ -728,6 +733,11 @@ sufficient to enter only the `metadata.name` field of those groups. ### `spec.descendants` [required] +**NOTE**: This field was marked for deprecation on Nov 22nd, 2020. It will be +removed entirely from the model on Dec 6th, 2020 in the repository and will not +be present in released packages following the next release after that. Please +update your code to not consume this field before the removal date. + The immediate and recursive child groups of this group in the hierarchy (children, and children's children, etc.). The list must be present, but may be empty if there are no child groups. The items are not guaranteed to be ordered diff --git a/packages/catalog-model/src/kinds/GroupEntityV1alpha1.ts b/packages/catalog-model/src/kinds/GroupEntityV1alpha1.ts index f6345741f7..7be432ad1f 100644 --- a/packages/catalog-model/src/kinds/GroupEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/GroupEntityV1alpha1.ts @@ -57,8 +57,22 @@ export interface GroupEntityV1alpha1 extends Entity { spec: { type: string; parent?: string; + /** + * @deprecated This field will disappear on Dec 6th, 2020. Please remove + * any consuming code. Producers can stop producing this field + * before that date, as long as the catalog backend uses the + * BuiltinKindsEntityProcessor which inserts the fields in the + * mean time. + */ ancestors: string[]; children: string[]; + /** + * @deprecated This field will disappear on Dec 6th, 2020. Please remove + * any consuming code. Producers can stop producing this field + * before that date, as long as the catalog backend uses the + * BuiltinKindsEntityProcessor which inserts the fields in the + * mean time. + */ descendants: string[]; }; } diff --git a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts new file mode 100644 index 0000000000..e1328e827a --- /dev/null +++ b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts @@ -0,0 +1,47 @@ +/* + * 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 { BuiltinKindsEntityProcessor } from './BuiltinKindsEntityProcessor'; + +describe('BuiltinKindsEntityProcessor', () => { + it('fills in fields for #3049', async () => { + const p = new BuiltinKindsEntityProcessor(); + const result = await p.preProcessEntity({ + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + metadata: { + name: 'n', + }, + spec: { + type: 't', + children: [], + } as any, + }); + expect(result).toEqual({ + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + metadata: { + name: 'n', + }, + spec: { + type: 't', + children: [], + ancestors: [], + descendants: [], + }, + }); + }); +}); diff --git a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts index 4d1eb5e583..472dffeb16 100644 --- a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts @@ -35,6 +35,25 @@ export class BuiltinKindsEntityProcessor implements CatalogProcessor { userEntityV1alpha1Validator, ]; + async preProcessEntity(entity: Entity): Promise { + // NOTE(freben): Part of Group field deprecation on Nov 22nd, 2020. Fields + // scheduled for removal Dec 6th, 2020. This code can be deleted after that + // point. See https://github.com/backstage/backstage/issues/3049 + if ( + entity.apiVersion === 'backstage.io/v1alpha1' && + entity.kind === 'Group' && + entity.spec + ) { + if (!entity.spec.ancestors) { + entity.spec.ancestors = []; + } + if (!entity.spec.descendants) { + entity.spec.descendants = []; + } + } + return entity; + } + async validateEntityKind(entity: Entity): Promise { for (const validator of this.validators) { const result = await validator.check(entity); From da2f4cd41a3034aa26b8c598057ef308b40bde07 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Mon, 23 Nov 2020 12:08:46 +0100 Subject: [PATCH 172/430] Add graphql API type to descriptor format (#3395) --- docs/features/software-catalog/descriptor-format.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/features/software-catalog/descriptor-format.md b/docs/features/software-catalog/descriptor-format.md index f5cb361b81..60f1901807 100644 --- a/docs/features/software-catalog/descriptor-format.md +++ b/docs/features/software-catalog/descriptor-format.md @@ -592,6 +592,9 @@ The current set of well-known and common values for this field is: [OpenAPI](https://swagger.io/specification/) version 2 or version 3 spec. - `asyncapi` - An API definition based on the [AsyncAPI](https://www.asyncapi.com/docs/specifications/latest/) spec. +- `graphql` - An API definition based on + [GraphQL schemas](https://spec.graphql.org/) for consuming + [GraphQL](https://graphql.org/) based APIs. - `grpc` - An API definition based on [Protocol Buffers](https://developers.google.com/protocol-buffers) to use with [gRPC](https://grpc.io/). From e2085293a635a4c4e356f54a7bf213d0958b2447 Mon Sep 17 00:00:00 2001 From: Moustafa Baiou Date: Mon, 19 Oct 2020 23:22:08 -0400 Subject: [PATCH 173/430] feat(kubernetes): update k8s-backend plugin to use label selectors update plugin to use label selectors when querying backend --- .../src/service/KubernetesFetcher.test.ts | 6 +- .../src/service/KubernetesFetcher.ts | 97 +++++++++---------- ...ubernetesObjectsByServiceIdHandler.test.ts | 10 +- ... getKubernetesObjectsForServiceHandler.ts} | 8 +- .../src/service/router.test.ts | 2 +- .../kubernetes-backend/src/service/router.ts | 12 ++- plugins/kubernetes-backend/src/types/types.ts | 3 +- plugins/kubernetes/README.md | 25 ++++- .../src/api/KubernetesBackendClient.ts | 18 +++- plugins/kubernetes/src/api/types.ts | 4 +- .../KubernetesContent/KubernetesContent.tsx | 21 +++- 11 files changed, 132 insertions(+), 74 deletions(-) rename plugins/kubernetes-backend/src/service/{getKubernetesObjectsByServiceIdHandler.ts => getKubernetesObjectsForServiceHandler.ts} (90%) diff --git a/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts b/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts index 6294b3a7ed..8c562b5200 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts @@ -57,7 +57,7 @@ describe('KubernetesClientProvider', () => { clientMock.listServiceForAllNamespaces.mockRejectedValue(errorResponse); - const result = await sut.fetchObjectsByServiceId( + const result = await sut.fetchObjectsForService( 'some-service', { name: 'cluster1', @@ -120,7 +120,7 @@ describe('KubernetesClientProvider', () => { }, }); - const result = await sut.fetchObjectsByServiceId( + const result = await sut.fetchObjectsForService( 'some-service', { name: 'cluster1', @@ -169,7 +169,7 @@ describe('KubernetesClientProvider', () => { }); it('should throw error on unknown type', () => { expect(() => - sut.fetchObjectsByServiceId( + sut.fetchObjectsForService( 'some-service', { name: 'cluster1', diff --git a/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts b/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts index f66a45816a..3bfb651d52 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts @@ -105,15 +105,19 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { this.logger = logger; } - fetchObjectsByServiceId( + fetchObjectsForService( serviceId: string, clusterDetails: ClusterDetails, objectTypesToFetch: Set, + labelSelector: string, ): Promise { const fetchResults = Array.from(objectTypesToFetch).map(type => { - return this.fetchByObjectType(serviceId, clusterDetails, type).catch( - captureKubernetesErrorsRethrowOthers, - ); + return this.fetchByObjectType( + serviceId, + clusterDetails, + type, + labelSelector, + ).catch(captureKubernetesErrorsRethrowOthers); }); return Promise.all(fetchResults).then(fetchResultsToResponseWrapper); @@ -124,42 +128,53 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { serviceId: string, clusterDetails: ClusterDetails, type: KubernetesObjectTypes, + labelSelector: string, ): Promise { switch (type) { case 'pods': - return this.fetchPodsByServiceId(serviceId, clusterDetails).then(r => ({ + return this.fetchPodsForService( + serviceId, + clusterDetails, + labelSelector, + ).then(r => ({ type: type, resources: r, })); case 'configmaps': - return this.fetchConfigMapsByServiceId( + return this.fetchConfigMapsForService( serviceId, clusterDetails, + labelSelector, ).then(r => ({ type: type, resources: r })); case 'deployments': - return this.fetchDeploymentsByServiceId( + return this.fetchDeploymentsForService( serviceId, clusterDetails, + labelSelector, ).then(r => ({ type: type, resources: r })); case 'replicasets': - return this.fetchReplicaSetsByServiceId( + return this.fetchReplicaSetsForService( serviceId, clusterDetails, + labelSelector, ).then(r => ({ type: type, resources: r })); case 'services': - return this.fetchServicesByServiceId( + return this.fetchServicesForService( serviceId, clusterDetails, + labelSelector, ).then(r => ({ type: type, resources: r })); case 'horizontalpodautoscalers': - return this.fetchHorizontalPodAutoscalersByServiceId( + return this.fetchHorizontalPodAutoscalersForService( serviceId, clusterDetails, + labelSelector, ).then(r => ({ type: type, resources: r })); case 'ingresses': - return this.fetchIngressesByServiceId( + return this.fetchIngressesForService( serviceId, clusterDetails, + labelSelector, ).then(r => ({ type: type, resources: r })); default: // unrecognised type @@ -192,79 +207,60 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { }); } - private fetchServicesByServiceId( + private fetchServicesForService( serviceId: string, clusterDetails: ClusterDetails, + labelSelector: string, ): Promise> { return this.singleClusterFetch(clusterDetails, ({ core }) => - core.listServiceForAllNamespaces( - false, - '', - '', - `backstage.io/kubernetes-id=${serviceId}`, - ), + core.listServiceForAllNamespaces(false, '', '', labelSelector), ); } - private fetchPodsByServiceId( + private fetchPodsForService( serviceId: string, clusterDetails: ClusterDetails, + labelSelector: string, ): Promise> { return this.singleClusterFetch(clusterDetails, ({ core }) => - core.listPodForAllNamespaces( - false, - '', - '', - `backstage.io/kubernetes-id=${serviceId}`, - ), + core.listPodForAllNamespaces(false, '', '', labelSelector), ); } - private fetchConfigMapsByServiceId( + private fetchConfigMapsForService( serviceId: string, clusterDetails: ClusterDetails, + labelSelector: string, ): Promise> { return this.singleClusterFetch(clusterDetails, ({ core }) => - core.listConfigMapForAllNamespaces( - false, - '', - '', - `backstage.io/kubernetes-id=${serviceId}`, - ), + core.listConfigMapForAllNamespaces(false, '', '', labelSelector), ); } - private fetchDeploymentsByServiceId( + private fetchDeploymentsForService( serviceId: string, clusterDetails: ClusterDetails, + labelSelector: string, ): Promise> { return this.singleClusterFetch(clusterDetails, ({ apps }) => - apps.listDeploymentForAllNamespaces( - false, - '', - '', - `backstage.io/kubernetes-id=${serviceId}`, - ), + apps.listDeploymentForAllNamespaces(false, '', '', labelSelector), ); } - private fetchReplicaSetsByServiceId( + private fetchReplicaSetsForService( serviceId: string, clusterDetails: ClusterDetails, + labelSelector: string, ): Promise> { return this.singleClusterFetch(clusterDetails, ({ apps }) => - apps.listReplicaSetForAllNamespaces( - false, - '', - '', - `backstage.io/kubernetes-id=${serviceId}`, - ), + apps.listReplicaSetForAllNamespaces(false, '', '', labelSelector), ); } - private fetchHorizontalPodAutoscalersByServiceId( + private fetchHorizontalPodAutoscalersForService( serviceId: string, clusterDetails: ClusterDetails, + labelSelector: string, ): Promise> { return this.singleClusterFetch( clusterDetails, @@ -273,14 +269,15 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { false, '', '', - `backstage.io/kubernetes-id=${serviceId}`, + labelSelector, ), ); } - private fetchIngressesByServiceId( + private fetchIngressesForService( serviceId: string, clusterDetails: ClusterDetails, + labelSelector: string, ): Promise> { return this.singleClusterFetch( clusterDetails, @@ -289,7 +286,7 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { false, '', '', - `backstage.io/kubernetes-id=${serviceId}`, + labelSelector, ), ); } diff --git a/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts b/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts index 8c26ded2d4..f413d080c6 100644 --- a/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts +++ b/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { handleGetKubernetesObjectsByServiceId } from './getKubernetesObjectsByServiceIdHandler'; +import { handleGetKubernetesObjectsForService } from './getKubernetesObjectsForServiceHandler'; import { getVoidLogger } from '@backstage/backend-common'; import { ClusterDetails } from '..'; @@ -81,10 +81,10 @@ describe('handleGetKubernetesObjectsByServiceId', () => { mockFetch(fetchObjectsByServiceId); - const result = await handleGetKubernetesObjectsByServiceId( + const result = await handleGetKubernetesObjectsForService( TEST_SERVICE_ID, { - fetchObjectsByServiceId, + fetchObjectsForService: fetchObjectsByServiceId, }, { getClustersByServiceId, @@ -155,10 +155,10 @@ describe('handleGetKubernetesObjectsByServiceId', () => { mockFetch(fetchObjectsByServiceId); - const result = await handleGetKubernetesObjectsByServiceId( + const result = await handleGetKubernetesObjectsForService( TEST_SERVICE_ID, { - fetchObjectsByServiceId, + fetchObjectsForService: fetchObjectsByServiceId, }, { getClustersByServiceId, diff --git a/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.ts b/plugins/kubernetes-backend/src/service/getKubernetesObjectsForServiceHandler.ts similarity index 90% rename from plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.ts rename to plugins/kubernetes-backend/src/service/getKubernetesObjectsForServiceHandler.ts index 6fc6092bd6..5f31248790 100644 --- a/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.ts +++ b/plugins/kubernetes-backend/src/service/getKubernetesObjectsForServiceHandler.ts @@ -26,12 +26,13 @@ import { import { KubernetesAuthTranslator } from '../kubernetes-auth-translator/types'; import { KubernetesAuthTranslatorGenerator } from '../kubernetes-auth-translator/KubernetesAuthTranslatorGenerator'; -export type GetKubernetesObjectsByServiceIdHandler = ( +export type GetKubernetesObjectsForServiceHandler = ( serviceId: string, fetcher: KubernetesFetcher, serviceLocator: KubernetesServiceLocator, logger: Logger, requestBody: AuthRequestBody, + labelSelector: string, objectsToFetch?: Set, ) => Promise; @@ -46,12 +47,13 @@ const DEFAULT_OBJECTS = new Set([ ]); // Fans out the request to all clusters that the service lives in, aggregates their responses together -export const handleGetKubernetesObjectsByServiceId: GetKubernetesObjectsByServiceIdHandler = async ( +export const handleGetKubernetesObjectsForService: GetKubernetesObjectsForServiceHandler = async ( serviceId, fetcher, serviceLocator, logger, requestBody, + labelSelector: string, objectsToFetch = DEFAULT_OBJECTS, ) => { const clusterDetails: ClusterDetails[] = await serviceLocator.getClustersByServiceId( @@ -81,7 +83,7 @@ export const handleGetKubernetesObjectsByServiceId: GetKubernetesObjectsByServic return Promise.all( clusterDetailsDecoratedForAuth.map(cd => { return fetcher - .fetchObjectsByServiceId(serviceId, cd, objectsToFetch) + .fetchObjectsForService(serviceId, cd, objectsToFetch, labelSelector) .then(result => { return { cluster: { diff --git a/plugins/kubernetes-backend/src/service/router.test.ts b/plugins/kubernetes-backend/src/service/router.test.ts index 6f9fbbf70f..b63b63ead6 100644 --- a/plugins/kubernetes-backend/src/service/router.test.ts +++ b/plugins/kubernetes-backend/src/service/router.test.ts @@ -32,7 +32,7 @@ describe('router', () => { beforeAll(async () => { kubernetesFetcher = { - fetchObjectsByServiceId: jest.fn(), + fetchObjectsForService: jest.fn(), }; kubernetesServiceLocator = { diff --git a/plugins/kubernetes-backend/src/service/router.ts b/plugins/kubernetes-backend/src/service/router.ts index b73115e1e4..5462b5c9fb 100644 --- a/plugins/kubernetes-backend/src/service/router.ts +++ b/plugins/kubernetes-backend/src/service/router.ts @@ -22,9 +22,9 @@ import { MultiTenantServiceLocator } from '../service-locator/MultiTenantService import { KubernetesClientBasedFetcher } from './KubernetesFetcher'; import { KubernetesClientProvider } from './KubernetesClientProvider'; import { - GetKubernetesObjectsByServiceIdHandler, - handleGetKubernetesObjectsByServiceId, -} from './getKubernetesObjectsByServiceIdHandler'; + GetKubernetesObjectsForServiceHandler, + handleGetKubernetesObjectsForService, +} from './getKubernetesObjectsForServiceHandler'; import { AuthRequestBody, KubernetesServiceLocator, @@ -64,13 +64,14 @@ export const makeRouter = ( logger: Logger, fetcher: KubernetesFetcher, serviceLocator: KubernetesServiceLocator, - handleGetByServiceId: GetKubernetesObjectsByServiceIdHandler, + handleGetByServiceId: GetKubernetesObjectsForServiceHandler, ): express.Router => { const router = Router(); router.use(express.json()); router.post('/services/:serviceId', async (req, res) => { const serviceId = req.params.serviceId; + const labelSelector = req.query.labelSelector; const requestBody: AuthRequestBody = req.body; try { const response = await handleGetByServiceId( @@ -79,6 +80,7 @@ export const makeRouter = ( serviceLocator, logger, requestBody, + labelSelector, ); res.send(response); } catch (e) { @@ -119,6 +121,6 @@ export async function createRouter( logger, fetcher, serviceLocator, - handleGetKubernetesObjectsByServiceId, + handleGetKubernetesObjectsForService, ); } diff --git a/plugins/kubernetes-backend/src/types/types.ts b/plugins/kubernetes-backend/src/types/types.ts index 1422511c21..0425687f48 100644 --- a/plugins/kubernetes-backend/src/types/types.ts +++ b/plugins/kubernetes-backend/src/types/types.ts @@ -110,10 +110,11 @@ export interface IngressesFetchResponse { // Fetches information from a kubernetes cluster using the cluster details object // to target a specific cluster export interface KubernetesFetcher { - fetchObjectsByServiceId( + fetchObjectsForService( serviceId: string, clusterDetails: ClusterDetails, objectTypesToFetch: Set, + labelSelector: string, ): Promise; } diff --git a/plugins/kubernetes/README.md b/plugins/kubernetes/README.md index 1ad478bd89..e6f1f2355a 100644 --- a/plugins/kubernetes/README.md +++ b/plugins/kubernetes/README.md @@ -14,7 +14,12 @@ It is only meant for local development, and the setup for it can be found inside ## Surfacing your Kubernetes components as part of an entity -### Adding the entity annotation +There are 2 ways to surface your kubernetes components as part of an entity. +The label selector takes precedence over the annotation/service id. + +### Common `backstage.io/kubernetes-id` label + +#### Adding the entity annotation In order for Backstage to detect that an entity has Kubernetes components, the following annotation should be added to the entity. @@ -24,7 +29,7 @@ annotations: 'backstage.io/kubernetes-id': dice-roller ``` -### Labeling Kubernetes components +#### Labeling Kubernetes components In order for Kubernetes components to show up in the service catalog as a part of an entity, Kubernetes components must be labeled with the following label: @@ -32,3 +37,19 @@ as a part of an entity, Kubernetes components must be labeled with the following ```yaml 'backstage.io/kubernetes-id': ``` + +### Full label selector + +#### Adding the entity label selector to the spec + +In order for Backstage to detect that an entity has kubernetes components the `kubernetes.selector` must have a valid selector. (Currently only matchLabels is supported) + +```yaml +spec: + kubernetes: + selector: + matchLabels: + someKey: someValue + other-key: other-value + app.kubernetes.io/name: dice-roller +``` diff --git a/plugins/kubernetes/src/api/KubernetesBackendClient.ts b/plugins/kubernetes/src/api/KubernetesBackendClient.ts index 86bb830046..faf9aaf024 100644 --- a/plugins/kubernetes/src/api/KubernetesBackendClient.ts +++ b/plugins/kubernetes/src/api/KubernetesBackendClient.ts @@ -20,6 +20,7 @@ import { AuthRequestBody, ObjectsByServiceIdResponse, } from '@backstage/plugin-kubernetes-backend'; +import { V1LabelSelector } from '@kubernetes/client-node'; export class KubernetesBackendClient implements KubernetesApi { private readonly discoveryApi: DiscoveryApi; @@ -50,10 +51,23 @@ export class KubernetesBackendClient implements KubernetesApi { return await response.json(); } - async getObjectsByServiceId( + private parseLabelSelector(params: V1LabelSelector): string { + // TODO: figure out how to convert the selector to the full query param from the yaml + // (as shown here https://github.com/kubernetes/apimachinery/blob/master/pkg/labels/selector.go) + return Object.keys(params.matchLabels) + .map(key => `${key}=${params[key]}`) + .join(','); + } + + async getObjectsByLabelSelector( serviceId: String, + labelSelector: V1LabelSelector, requestBody: AuthRequestBody, ): Promise { - return await this.getRequired(`/services/${serviceId}`, requestBody); + const labelSelectorQueryParams = this.parseLabelSelector(labelSelector); + return await this.getRequired( + `/services/${serviceId}?labelSelector=${labelSelectorQueryParams}`, + requestBody, + ); } } diff --git a/plugins/kubernetes/src/api/types.ts b/plugins/kubernetes/src/api/types.ts index 8e44d58e6a..2f13dcb02b 100644 --- a/plugins/kubernetes/src/api/types.ts +++ b/plugins/kubernetes/src/api/types.ts @@ -19,6 +19,7 @@ import { AuthRequestBody, ObjectsByServiceIdResponse, } from '@backstage/plugin-kubernetes-backend'; +import { V1LabelSelector } from '@kubernetes/client-node'; export const kubernetesApiRef = createApiRef({ id: 'plugin.kubernetes.service', @@ -27,8 +28,9 @@ export const kubernetesApiRef = createApiRef({ }); export interface KubernetesApi { - getObjectsByServiceId( + getObjectsByLabelSelector( serviceId: String, + labelSelector: V1LabelSelector, requestBody: AuthRequestBody, ): Promise; } diff --git a/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx b/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx index a3e82e79a4..d46a888b39 100644 --- a/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx +++ b/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx @@ -41,6 +41,7 @@ import { ExtensionsV1beta1Ingress, V1ConfigMap, V1HorizontalPodAutoscaler, + V1LabelSelector, V1Service, } from '@kubernetes/client-node'; import { Services } from '../Services'; @@ -129,9 +130,27 @@ export const KubernetesContent = ({ entity }: KubernetesContentProps) => { ); } + // decide label selector to search by defaulting to this label + let labelSelector: V1LabelSelector = { + matchLabels: { + 'backstage.io/kubernetes-id': entity.metadata.name, + }, + }; + + if ( + entity.spec.kubernetes && + (entity.spec.kubernetes.selector as V1LabelSelector) + ) { + labelSelector = entity.spec.kubernetes.selector; + } + // TODO: Add validation on contents/format of requestBody kubernetesApi - .getObjectsByServiceId(entity.metadata.name, requestBody) + .getObjectsByLabelSelector( + entity.metadata.name, + labelSelector, + requestBody, + ) .then(result => { setKubernetesObjects(result); }) From beaa0a82dbd267193fe7877cf806e977a8b1241e Mon Sep 17 00:00:00 2001 From: Moustafa Baiou Date: Tue, 20 Oct 2020 00:22:44 -0400 Subject: [PATCH 174/430] fix tests, types and cleanup --- .../src/service/KubernetesFetcher.test.ts | 45 +++++++++++++++++++ .../src/service/KubernetesFetcher.ts | 4 +- ...ubernetesObjectsByServiceIdHandler.test.ts | 18 ++++---- .../kubernetes-backend/src/service/router.ts | 2 +- .../src/api/KubernetesBackendClient.ts | 5 ++- 5 files changed, 62 insertions(+), 12 deletions(-) diff --git a/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts b/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts index 8c562b5200..f37a81f9f8 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts @@ -66,6 +66,7 @@ describe('KubernetesClientProvider', () => { authProvider: 'serviceAccount', }, new Set(['pods', 'services']), + '', ); expect(result).toStrictEqual({ @@ -129,6 +130,7 @@ describe('KubernetesClientProvider', () => { authProvider: 'serviceAccount', }, new Set(['pods', 'services']), + '', ); expect(result).toStrictEqual({ @@ -178,6 +180,7 @@ describe('KubernetesClientProvider', () => { authProvider: 'serviceAccount', }, new Set(['foo']), + '', ), ).toThrow('unrecognised type=foo'); @@ -254,4 +257,46 @@ describe('KubernetesClientProvider', () => { }, ); }); + it('should always add a labelSelector query', async () => { + clientMock.listPodForAllNamespaces.mockResolvedValueOnce({ + body: { + items: [ + { + metadata: { + name: 'pod-name', + }, + }, + ], + }, + }); + + clientMock.listServiceForAllNamespaces.mockResolvedValueOnce({ + body: { + items: [ + { + metadata: { + name: 'service-name', + }, + }, + ], + }, + }); + + await sut.fetchObjectsForService( + 'some-service', + { + name: 'cluster1', + url: 'http://localhost:9999', + serviceAccountToken: 'token', + authProvider: 'serviceAccount', + }, + new Set(['pods', 'services']), + '', + ); + + const mockCall = clientMock.listPodForAllNamespaces.mock.calls[0]; + const actualSelector = mockCall[mockCall.length - 1]; + const expectedSelector = 'backstage.io/kubernetes-id=some-service'; + expect(actualSelector).toBe(expectedSelector); + }); }); diff --git a/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts b/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts index 3bfb651d52..a1ed21552b 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts @@ -116,7 +116,9 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { serviceId, clusterDetails, type, - labelSelector, + labelSelector.length !== 0 + ? labelSelector + : `backstage.io/kubernetes-id=${serviceId}`, ).catch(captureKubernetesErrorsRethrowOthers); }); diff --git a/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts b/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts index f413d080c6..2f779adfe8 100644 --- a/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts +++ b/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts @@ -20,7 +20,7 @@ import { ClusterDetails } from '..'; const TEST_SERVICE_ID = 'my-service'; -const fetchObjectsByServiceId = jest.fn(); +const fetchObjectsForService = jest.fn(); const getClustersByServiceId = jest.fn(); @@ -64,7 +64,7 @@ const mockFetch = (mock: jest.Mock) => { ); }; -describe('handleGetKubernetesObjectsByServiceId', () => { +describe('handleGetKubernetesObjectsForService', () => { beforeEach(() => { jest.resetAllMocks(); }); @@ -79,22 +79,23 @@ describe('handleGetKubernetesObjectsByServiceId', () => { ]), ); - mockFetch(fetchObjectsByServiceId); + mockFetch(fetchObjectsForService); const result = await handleGetKubernetesObjectsForService( TEST_SERVICE_ID, { - fetchObjectsForService: fetchObjectsByServiceId, + fetchObjectsForService: fetchObjectsForService, }, { getClustersByServiceId, }, getVoidLogger(), {}, + '', ); expect(getClustersByServiceId.mock.calls.length).toBe(1); - expect(fetchObjectsByServiceId.mock.calls.length).toBe(1); + expect(fetchObjectsForService.mock.calls.length).toBe(1); expect(result).toStrictEqual({ items: [ { @@ -153,12 +154,12 @@ describe('handleGetKubernetesObjectsByServiceId', () => { ]), ); - mockFetch(fetchObjectsByServiceId); + mockFetch(fetchObjectsForService); const result = await handleGetKubernetesObjectsForService( TEST_SERVICE_ID, { - fetchObjectsForService: fetchObjectsByServiceId, + fetchObjectsForService: fetchObjectsForService, }, { getClustersByServiceId, @@ -169,10 +170,11 @@ describe('handleGetKubernetesObjectsByServiceId', () => { google: 'google_token_123', }, }, + '', ); expect(getClustersByServiceId.mock.calls.length).toBe(1); - expect(fetchObjectsByServiceId.mock.calls.length).toBe(2); + expect(fetchObjectsForService.mock.calls.length).toBe(2); expect(result).toStrictEqual({ items: [ { diff --git a/plugins/kubernetes-backend/src/service/router.ts b/plugins/kubernetes-backend/src/service/router.ts index 5462b5c9fb..fada4c7888 100644 --- a/plugins/kubernetes-backend/src/service/router.ts +++ b/plugins/kubernetes-backend/src/service/router.ts @@ -80,7 +80,7 @@ export const makeRouter = ( serviceLocator, logger, requestBody, - labelSelector, + labelSelector ? labelSelector.toString() : '', ); res.send(response); } catch (e) { diff --git a/plugins/kubernetes/src/api/KubernetesBackendClient.ts b/plugins/kubernetes/src/api/KubernetesBackendClient.ts index faf9aaf024..c6d0153538 100644 --- a/plugins/kubernetes/src/api/KubernetesBackendClient.ts +++ b/plugins/kubernetes/src/api/KubernetesBackendClient.ts @@ -54,8 +54,9 @@ export class KubernetesBackendClient implements KubernetesApi { private parseLabelSelector(params: V1LabelSelector): string { // TODO: figure out how to convert the selector to the full query param from the yaml // (as shown here https://github.com/kubernetes/apimachinery/blob/master/pkg/labels/selector.go) - return Object.keys(params.matchLabels) - .map(key => `${key}=${params[key]}`) + const { matchLabels } = params; + return Object.keys(matchLabels) + .map(key => `${key}=${matchLabels[key]}`) .join(','); } From 61a552fac6133af81be6ee1d59aeec29240ef53e Mon Sep 17 00:00:00 2001 From: Moustafa Baiou Date: Tue, 20 Oct 2020 00:49:23 -0400 Subject: [PATCH 175/430] fix type checks --- .../src/service/KubernetesFetcher.ts | 29 ++++--------------- .../src/api/KubernetesBackendClient.ts | 5 +++- 2 files changed, 10 insertions(+), 24 deletions(-) diff --git a/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts b/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts index a1ed21552b..11917efc52 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts @@ -113,7 +113,6 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { ): Promise { const fetchResults = Array.from(objectTypesToFetch).map(type => { return this.fetchByObjectType( - serviceId, clusterDetails, type, labelSelector.length !== 0 @@ -127,54 +126,45 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { // TODO could probably do with a tidy up private fetchByObjectType( - serviceId: string, clusterDetails: ClusterDetails, type: KubernetesObjectTypes, labelSelector: string, ): Promise { switch (type) { case 'pods': - return this.fetchPodsForService( - serviceId, - clusterDetails, - labelSelector, - ).then(r => ({ - type: type, - resources: r, - })); + return this.fetchPodsForService(clusterDetails, labelSelector).then( + r => ({ + type: type, + resources: r, + }), + ); case 'configmaps': return this.fetchConfigMapsForService( - serviceId, clusterDetails, labelSelector, ).then(r => ({ type: type, resources: r })); case 'deployments': return this.fetchDeploymentsForService( - serviceId, clusterDetails, labelSelector, ).then(r => ({ type: type, resources: r })); case 'replicasets': return this.fetchReplicaSetsForService( - serviceId, clusterDetails, labelSelector, ).then(r => ({ type: type, resources: r })); case 'services': return this.fetchServicesForService( - serviceId, clusterDetails, labelSelector, ).then(r => ({ type: type, resources: r })); case 'horizontalpodautoscalers': return this.fetchHorizontalPodAutoscalersForService( - serviceId, clusterDetails, labelSelector, ).then(r => ({ type: type, resources: r })); case 'ingresses': return this.fetchIngressesForService( - serviceId, clusterDetails, labelSelector, ).then(r => ({ type: type, resources: r })); @@ -210,7 +200,6 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { } private fetchServicesForService( - serviceId: string, clusterDetails: ClusterDetails, labelSelector: string, ): Promise> { @@ -220,7 +209,6 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { } private fetchPodsForService( - serviceId: string, clusterDetails: ClusterDetails, labelSelector: string, ): Promise> { @@ -230,7 +218,6 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { } private fetchConfigMapsForService( - serviceId: string, clusterDetails: ClusterDetails, labelSelector: string, ): Promise> { @@ -240,7 +227,6 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { } private fetchDeploymentsForService( - serviceId: string, clusterDetails: ClusterDetails, labelSelector: string, ): Promise> { @@ -250,7 +236,6 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { } private fetchReplicaSetsForService( - serviceId: string, clusterDetails: ClusterDetails, labelSelector: string, ): Promise> { @@ -260,7 +245,6 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { } private fetchHorizontalPodAutoscalersForService( - serviceId: string, clusterDetails: ClusterDetails, labelSelector: string, ): Promise> { @@ -277,7 +261,6 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { } private fetchIngressesForService( - serviceId: string, clusterDetails: ClusterDetails, labelSelector: string, ): Promise> { diff --git a/plugins/kubernetes/src/api/KubernetesBackendClient.ts b/plugins/kubernetes/src/api/KubernetesBackendClient.ts index c6d0153538..088196b0c8 100644 --- a/plugins/kubernetes/src/api/KubernetesBackendClient.ts +++ b/plugins/kubernetes/src/api/KubernetesBackendClient.ts @@ -55,8 +55,11 @@ export class KubernetesBackendClient implements KubernetesApi { // TODO: figure out how to convert the selector to the full query param from the yaml // (as shown here https://github.com/kubernetes/apimachinery/blob/master/pkg/labels/selector.go) const { matchLabels } = params; + if (!matchLabels) { + return ''; + } return Object.keys(matchLabels) - .map(key => `${key}=${matchLabels[key]}`) + .map(key => `${key}=${matchLabels[key.toString()]}`) .join(','); } From 99fffed216b5f2f0030627b45cea7c20e9c72fbd Mon Sep 17 00:00:00 2001 From: Moustafa Baiou Date: Sat, 7 Nov 2020 19:44:19 -0500 Subject: [PATCH 176/430] fix types --- .../src/kinds/ComponentEntityV1alpha1.ts | 16 ++++++++++++++++ .../KubernetesContent/KubernetesContent.tsx | 10 ++++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts index 9d46dea03f..0e1e369e44 100644 --- a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts @@ -30,6 +30,15 @@ const schema = yup.object>({ lifecycle: yup.string().required().min(1), owner: yup.string().required().min(1), implementsApis: yup.array(yup.string().required()).notRequired(), + kubernetes: yup + .object({ + selector: yup + .object({ + matchLabels: yup.object().required(), + }) + .required(), + }) + .notRequired(), }) .required(), }); @@ -42,6 +51,13 @@ export interface ComponentEntityV1alpha1 extends Entity { lifecycle: string; owner: string; implementsApis?: string[]; + kubernetes?: { + selector: { + matchLabels: { + [key: string]: string; + }; + }; + }; }; } diff --git a/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx b/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx index d46a888b39..3f9c1a866f 100644 --- a/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx +++ b/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx @@ -26,7 +26,7 @@ import { TabbedCard, useApi, } from '@backstage/core'; -import { Entity } from '@backstage/catalog-model'; +import { ComponentEntityV1alpha1, Entity } from '@backstage/catalog-model'; import { kubernetesApiRef } from '../../api/types'; import { AuthRequestBody, @@ -137,11 +137,13 @@ export const KubernetesContent = ({ entity }: KubernetesContentProps) => { }, }; + const componentEntity = entity as ComponentEntityV1alpha1; if ( - entity.spec.kubernetes && - (entity.spec.kubernetes.selector as V1LabelSelector) + componentEntity.spec && + componentEntity.spec.kubernetes && + (componentEntity.spec.kubernetes.selector as V1LabelSelector) ) { - labelSelector = entity.spec.kubernetes.selector; + labelSelector = componentEntity.spec.kubernetes.selector; } // TODO: Add validation on contents/format of requestBody From 4639d20720b0ba3c4fa48d0f1fbc2b417b90e6bf Mon Sep 17 00:00:00 2001 From: Moustafa Baiou Date: Mon, 9 Nov 2020 01:42:08 -0500 Subject: [PATCH 177/430] modify approach to post entire entity to k8s backend --- .../GoogleKubernetesAuthTranslator.ts | 4 +-- .../ServiceAccountKubernetesAuthTranslator.ts | 4 +-- .../src/kubernetes-auth-translator/types.ts | 4 +-- ...ubernetesObjectsByServiceIdHandler.test.ts | 1 - .../getKubernetesObjectsForServiceHandler.ts | 33 +++++++++++++++---- .../src/service/router.test.ts | 4 +-- .../kubernetes-backend/src/service/router.ts | 10 +++--- plugins/kubernetes-backend/src/types/types.ts | 6 ++-- .../src/api/KubernetesBackendClient.ts | 28 ++++------------ plugins/kubernetes/src/api/types.ts | 10 +++--- .../KubernetesContent/KubernetesContent.tsx | 32 ++++-------------- .../GoogleKubernetesAuthProvider.ts | 6 ++-- .../KubernetesAuthProviders.ts | 6 ++-- .../ServiceAccountKubernetesAuthProvider.ts | 6 ++-- .../src/kubernetes-auth-provider/types.ts | 10 +++--- 15 files changed, 74 insertions(+), 90 deletions(-) diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/GoogleKubernetesAuthTranslator.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/GoogleKubernetesAuthTranslator.ts index 7f9b2f0bae..0dd2a4bcc2 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/GoogleKubernetesAuthTranslator.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/GoogleKubernetesAuthTranslator.ts @@ -15,13 +15,13 @@ */ import { KubernetesAuthTranslator } from './types'; -import { AuthRequestBody, ClusterDetails } from '../types/types'; +import { KubernetesRequestBody, ClusterDetails } from '../types/types'; export class GoogleKubernetesAuthTranslator implements KubernetesAuthTranslator { async decorateClusterDetailsWithAuth( clusterDetails: ClusterDetails, - requestBody: AuthRequestBody, + requestBody: KubernetesRequestBody, ): Promise { const clusterDetailsWithAuthToken: ClusterDetails = Object.assign( {}, diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/ServiceAccountKubernetesAuthTranslator.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/ServiceAccountKubernetesAuthTranslator.ts index ecf2f12b72..6433e41546 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/ServiceAccountKubernetesAuthTranslator.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/ServiceAccountKubernetesAuthTranslator.ts @@ -15,7 +15,7 @@ */ import { KubernetesAuthTranslator } from './types'; -import { AuthRequestBody, ClusterDetails } from '../types/types'; +import { KubernetesRequestBody, ClusterDetails } from '../types/types'; export class ServiceAccountKubernetesAuthTranslator implements KubernetesAuthTranslator { @@ -23,7 +23,7 @@ export class ServiceAccountKubernetesAuthTranslator clusterDetails: ClusterDetails, // To ignore TS6133 linting error where it detects 'requestBody' is declared but its value is never read. // @ts-ignore-start - requestBody: AuthRequestBody, // eslint-disable-line @typescript-eslint/no-unused-vars + requestBody: KubernetesRequestBody, // eslint-disable-line @typescript-eslint/no-unused-vars // @ts-ignore-end ): Promise { return clusterDetails; diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/types.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/types.ts index f89e04456f..c01a57889c 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/types.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/types.ts @@ -14,11 +14,11 @@ * limitations under the License. */ -import { AuthRequestBody, ClusterDetails } from '../types/types'; +import { KubernetesRequestBody, ClusterDetails } from '../types/types'; export interface KubernetesAuthTranslator { decorateClusterDetailsWithAuth( clusterDetails: ClusterDetails, - requestBody: AuthRequestBody, + requestBody: KubernetesRequestBody, ): Promise; } diff --git a/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts b/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts index 2f779adfe8..f95b9a7242 100644 --- a/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts +++ b/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts @@ -91,7 +91,6 @@ describe('handleGetKubernetesObjectsForService', () => { }, getVoidLogger(), {}, - '', ); expect(getClustersByServiceId.mock.calls.length).toBe(1); diff --git a/plugins/kubernetes-backend/src/service/getKubernetesObjectsForServiceHandler.ts b/plugins/kubernetes-backend/src/service/getKubernetesObjectsForServiceHandler.ts index 5f31248790..46f3f27c73 100644 --- a/plugins/kubernetes-backend/src/service/getKubernetesObjectsForServiceHandler.ts +++ b/plugins/kubernetes-backend/src/service/getKubernetesObjectsForServiceHandler.ts @@ -16,25 +16,25 @@ import { Logger } from 'winston'; import { - AuthRequestBody, + KubernetesRequestBody, ClusterDetails, KubernetesServiceLocator, KubernetesFetcher, KubernetesObjectTypes, - ObjectsByServiceIdResponse, + ObjectsByEntityResponse, } from '../types/types'; import { KubernetesAuthTranslator } from '../kubernetes-auth-translator/types'; import { KubernetesAuthTranslatorGenerator } from '../kubernetes-auth-translator/KubernetesAuthTranslatorGenerator'; +import { ComponentEntityV1alpha1 } from '@backstage/catalog-model'; export type GetKubernetesObjectsForServiceHandler = ( serviceId: string, fetcher: KubernetesFetcher, serviceLocator: KubernetesServiceLocator, logger: Logger, - requestBody: AuthRequestBody, - labelSelector: string, + requestBody: KubernetesRequestBody, objectsToFetch?: Set, -) => Promise; +) => Promise; const DEFAULT_OBJECTS = new Set([ 'pods', @@ -46,6 +46,26 @@ const DEFAULT_OBJECTS = new Set([ 'ingresses', ]); +function parseLabelSelector(entity: ComponentEntityV1alpha1): string { + if ( + entity && + entity.spec && + entity.spec.kubernetes && + entity.spec.kubernetes.selector + ) { + // TODO: figure out how to convert the selector to the full query param from the yaml + // (as shown here https://github.com/kubernetes/apimachinery/blob/master/pkg/labels/selector.go) + const { matchLabels } = entity.spec.kubernetes.selector; + if (!matchLabels) { + return ''; + } + return Object.keys(matchLabels) + .map(key => `${key}=${matchLabels[key.toString()]}`) + .join(','); + } + return ''; +} + // Fans out the request to all clusters that the service lives in, aggregates their responses together export const handleGetKubernetesObjectsForService: GetKubernetesObjectsForServiceHandler = async ( serviceId, @@ -53,7 +73,6 @@ export const handleGetKubernetesObjectsForService: GetKubernetesObjectsForServic serviceLocator, logger, requestBody, - labelSelector: string, objectsToFetch = DEFAULT_OBJECTS, ) => { const clusterDetails: ClusterDetails[] = await serviceLocator.getClustersByServiceId( @@ -80,6 +99,8 @@ export const handleGetKubernetesObjectsForService: GetKubernetesObjectsForServic .join(', ')}]`, ); + const labelSelector = parseLabelSelector(requestBody.entity); + return Promise.all( clusterDetailsDecoratedForAuth.map(cd => { return fetcher diff --git a/plugins/kubernetes-backend/src/service/router.test.ts b/plugins/kubernetes-backend/src/service/router.test.ts index b63b63ead6..a407ce9693 100644 --- a/plugins/kubernetes-backend/src/service/router.test.ts +++ b/plugins/kubernetes-backend/src/service/router.test.ts @@ -21,14 +21,14 @@ import { makeRouter } from './router'; import { KubernetesServiceLocator, KubernetesFetcher, - ObjectsByServiceIdResponse, + ObjectsByEntityResponse, } from '..'; describe('router', () => { let app: express.Express; let kubernetesFetcher: jest.Mocked; let kubernetesServiceLocator: jest.Mocked; - let handleGetByServiceId: jest.Mock>; + let handleGetByServiceId: jest.Mock>; beforeAll(async () => { kubernetesFetcher = { diff --git a/plugins/kubernetes-backend/src/service/router.ts b/plugins/kubernetes-backend/src/service/router.ts index fada4c7888..38a02ad970 100644 --- a/plugins/kubernetes-backend/src/service/router.ts +++ b/plugins/kubernetes-backend/src/service/router.ts @@ -26,7 +26,7 @@ import { handleGetKubernetesObjectsForService, } from './getKubernetesObjectsForServiceHandler'; import { - AuthRequestBody, + KubernetesRequestBody, KubernetesServiceLocator, KubernetesFetcher, ServiceLocatorMethod, @@ -64,23 +64,21 @@ export const makeRouter = ( logger: Logger, fetcher: KubernetesFetcher, serviceLocator: KubernetesServiceLocator, - handleGetByServiceId: GetKubernetesObjectsForServiceHandler, + handleGetByEntity: GetKubernetesObjectsForServiceHandler, ): express.Router => { const router = Router(); router.use(express.json()); router.post('/services/:serviceId', async (req, res) => { const serviceId = req.params.serviceId; - const labelSelector = req.query.labelSelector; - const requestBody: AuthRequestBody = req.body; + const requestBody: KubernetesRequestBody = req.body; try { - const response = await handleGetByServiceId( + const response = await handleGetByEntity( serviceId, fetcher, serviceLocator, logger, requestBody, - labelSelector ? labelSelector.toString() : '', ); res.send(response); } catch (e) { diff --git a/plugins/kubernetes-backend/src/types/types.ts b/plugins/kubernetes-backend/src/types/types.ts index 0425687f48..738bc89b21 100644 --- a/plugins/kubernetes-backend/src/types/types.ts +++ b/plugins/kubernetes-backend/src/types/types.ts @@ -23,6 +23,7 @@ import { V1ReplicaSet, V1Service, } from '@kubernetes/client-node'; +import { ComponentEntityV1alpha1 } from '@backstage/catalog-model'; export interface ClusterDetails { name: string; @@ -31,10 +32,11 @@ export interface ClusterDetails { serviceAccountToken?: string | undefined; } -export interface AuthRequestBody { +export interface KubernetesRequestBody { auth?: { google?: string; }; + entity: ComponentEntityV1alpha1; } export interface ClusterObjects { @@ -43,7 +45,7 @@ export interface ClusterObjects { errors: KubernetesFetchError[]; } -export interface ObjectsByServiceIdResponse { +export interface ObjectsByEntityResponse { items: ClusterObjects[]; } diff --git a/plugins/kubernetes/src/api/KubernetesBackendClient.ts b/plugins/kubernetes/src/api/KubernetesBackendClient.ts index 088196b0c8..d8fa800ab6 100644 --- a/plugins/kubernetes/src/api/KubernetesBackendClient.ts +++ b/plugins/kubernetes/src/api/KubernetesBackendClient.ts @@ -17,10 +17,9 @@ import { DiscoveryApi } from '@backstage/core'; import { KubernetesApi } from './types'; import { - AuthRequestBody, - ObjectsByServiceIdResponse, + KubernetesRequestBody, + ObjectsByEntityResponse, } from '@backstage/plugin-kubernetes-backend'; -import { V1LabelSelector } from '@kubernetes/client-node'; export class KubernetesBackendClient implements KubernetesApi { private readonly discoveryApi: DiscoveryApi; @@ -31,7 +30,7 @@ export class KubernetesBackendClient implements KubernetesApi { private async getRequired( path: string, - requestBody: AuthRequestBody, + requestBody: KubernetesRequestBody, ): Promise { const url = `${await this.discoveryApi.getBaseUrl('kubernetes')}${path}`; const response = await fetch(url, { @@ -51,26 +50,11 @@ export class KubernetesBackendClient implements KubernetesApi { return await response.json(); } - private parseLabelSelector(params: V1LabelSelector): string { - // TODO: figure out how to convert the selector to the full query param from the yaml - // (as shown here https://github.com/kubernetes/apimachinery/blob/master/pkg/labels/selector.go) - const { matchLabels } = params; - if (!matchLabels) { - return ''; - } - return Object.keys(matchLabels) - .map(key => `${key}=${matchLabels[key.toString()]}`) - .join(','); - } - async getObjectsByLabelSelector( - serviceId: String, - labelSelector: V1LabelSelector, - requestBody: AuthRequestBody, - ): Promise { - const labelSelectorQueryParams = this.parseLabelSelector(labelSelector); + requestBody: KubernetesRequestBody, + ): Promise { return await this.getRequired( - `/services/${serviceId}?labelSelector=${labelSelectorQueryParams}`, + `/services/${requestBody.entity.metadata.name}`, requestBody, ); } diff --git a/plugins/kubernetes/src/api/types.ts b/plugins/kubernetes/src/api/types.ts index 2f13dcb02b..663841ddf9 100644 --- a/plugins/kubernetes/src/api/types.ts +++ b/plugins/kubernetes/src/api/types.ts @@ -16,8 +16,8 @@ import { createApiRef } from '@backstage/core'; import { - AuthRequestBody, - ObjectsByServiceIdResponse, + KubernetesRequestBody, + ObjectsByEntityResponse, } from '@backstage/plugin-kubernetes-backend'; import { V1LabelSelector } from '@kubernetes/client-node'; @@ -29,8 +29,6 @@ export const kubernetesApiRef = createApiRef({ export interface KubernetesApi { getObjectsByLabelSelector( - serviceId: String, - labelSelector: V1LabelSelector, - requestBody: AuthRequestBody, - ): Promise; + requestBody: KubernetesRequestBody, + ): Promise; } diff --git a/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx b/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx index 3f9c1a866f..4e1560be70 100644 --- a/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx +++ b/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx @@ -29,10 +29,10 @@ import { import { ComponentEntityV1alpha1, Entity } from '@backstage/catalog-model'; import { kubernetesApiRef } from '../../api/types'; import { - AuthRequestBody, + KubernetesRequestBody, ClusterObjects, FetchResponse, - ObjectsByServiceIdResponse, + ObjectsByEntityResponse, } from '@backstage/plugin-kubernetes-backend'; import { kubernetesAuthProvidersApiRef } from '../../kubernetes-auth-provider/types'; import { DeploymentTables } from '../DeploymentTables'; @@ -105,7 +105,7 @@ export const KubernetesContent = ({ entity }: KubernetesContentProps) => { const kubernetesApi = useApi(kubernetesApiRef); const [kubernetesObjects, setKubernetesObjects] = useState< - ObjectsByServiceIdResponse | undefined + ObjectsByEntityResponse | undefined >(undefined); const [error, setError] = useState(undefined); @@ -121,7 +121,9 @@ export const KubernetesContent = ({ entity }: KubernetesContentProps) => { useEffect(() => { (async () => { // For each auth type, invoke decorateRequestBodyForAuth on corresponding KubernetesAuthProvider - let requestBody: AuthRequestBody = {}; + let requestBody: KubernetesRequestBody = { + entity: entity as ComponentEntityV1alpha1, + }; for (const authProviderStr of authProviders) { // Multiple asyncs done sequentially instead of all at once to prevent same requestBody from being modified simultaneously requestBody = await kubernetesAuthProvidersApi.decorateRequestBodyForAuth( @@ -130,29 +132,9 @@ export const KubernetesContent = ({ entity }: KubernetesContentProps) => { ); } - // decide label selector to search by defaulting to this label - let labelSelector: V1LabelSelector = { - matchLabels: { - 'backstage.io/kubernetes-id': entity.metadata.name, - }, - }; - - const componentEntity = entity as ComponentEntityV1alpha1; - if ( - componentEntity.spec && - componentEntity.spec.kubernetes && - (componentEntity.spec.kubernetes.selector as V1LabelSelector) - ) { - labelSelector = componentEntity.spec.kubernetes.selector; - } - // TODO: Add validation on contents/format of requestBody kubernetesApi - .getObjectsByLabelSelector( - entity.metadata.name, - labelSelector, - requestBody, - ) + .getObjectsByLabelSelector(requestBody) .then(result => { setKubernetesObjects(result); }) diff --git a/plugins/kubernetes/src/kubernetes-auth-provider/GoogleKubernetesAuthProvider.ts b/plugins/kubernetes/src/kubernetes-auth-provider/GoogleKubernetesAuthProvider.ts index 7de5c7c0a9..570806a1a4 100644 --- a/plugins/kubernetes/src/kubernetes-auth-provider/GoogleKubernetesAuthProvider.ts +++ b/plugins/kubernetes/src/kubernetes-auth-provider/GoogleKubernetesAuthProvider.ts @@ -16,7 +16,7 @@ import { OAuthApi } from '@backstage/core'; import { KubernetesAuthProvider } from './types'; -import { AuthRequestBody } from '@backstage/plugin-kubernetes-backend'; +import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-backend'; export class GoogleKubernetesAuthProvider implements KubernetesAuthProvider { authProvider: OAuthApi; @@ -26,8 +26,8 @@ export class GoogleKubernetesAuthProvider implements KubernetesAuthProvider { } async decorateRequestBodyForAuth( - requestBody: AuthRequestBody, - ): Promise { + requestBody: KubernetesRequestBody, + ): Promise { const googleAuthToken: string = await this.authProvider.getAccessToken( 'https://www.googleapis.com/auth/cloud-platform', ); diff --git a/plugins/kubernetes/src/kubernetes-auth-provider/KubernetesAuthProviders.ts b/plugins/kubernetes/src/kubernetes-auth-provider/KubernetesAuthProviders.ts index ac909d6695..9f64af9c2e 100644 --- a/plugins/kubernetes/src/kubernetes-auth-provider/KubernetesAuthProviders.ts +++ b/plugins/kubernetes/src/kubernetes-auth-provider/KubernetesAuthProviders.ts @@ -15,7 +15,7 @@ */ import { OAuthApi } from '@backstage/core'; -import { AuthRequestBody } from '@backstage/plugin-kubernetes-backend'; +import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-backend'; import { KubernetesAuthProvider, KubernetesAuthProvidersApi } from './types'; import { GoogleKubernetesAuthProvider } from './GoogleKubernetesAuthProvider'; import { ServiceAccountKubernetesAuthProvider } from './ServiceAccountKubernetesAuthProvider'; @@ -40,8 +40,8 @@ export class KubernetesAuthProviders implements KubernetesAuthProvidersApi { async decorateRequestBodyForAuth( authProvider: string, - requestBody: AuthRequestBody, - ): Promise { + requestBody: KubernetesRequestBody, + ): Promise { const kubernetesAuthProvider: | KubernetesAuthProvider | undefined = this.kubernetesAuthProviderMap.get(authProvider); diff --git a/plugins/kubernetes/src/kubernetes-auth-provider/ServiceAccountKubernetesAuthProvider.ts b/plugins/kubernetes/src/kubernetes-auth-provider/ServiceAccountKubernetesAuthProvider.ts index 3ac5a9494b..5d33521ae4 100644 --- a/plugins/kubernetes/src/kubernetes-auth-provider/ServiceAccountKubernetesAuthProvider.ts +++ b/plugins/kubernetes/src/kubernetes-auth-provider/ServiceAccountKubernetesAuthProvider.ts @@ -15,13 +15,13 @@ */ import { KubernetesAuthProvider } from './types'; -import { AuthRequestBody } from '@backstage/plugin-kubernetes-backend'; +import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-backend'; export class ServiceAccountKubernetesAuthProvider implements KubernetesAuthProvider { async decorateRequestBodyForAuth( - requestBody: AuthRequestBody, - ): Promise { + requestBody: KubernetesRequestBody, + ): Promise { // No-op, with service account for auth, cluster config/details should already have serviceAccountToken return requestBody; } diff --git a/plugins/kubernetes/src/kubernetes-auth-provider/types.ts b/plugins/kubernetes/src/kubernetes-auth-provider/types.ts index 24fee0f94c..5bd253d2c1 100644 --- a/plugins/kubernetes/src/kubernetes-auth-provider/types.ts +++ b/plugins/kubernetes/src/kubernetes-auth-provider/types.ts @@ -15,12 +15,12 @@ */ import { createApiRef } from '@backstage/core'; -import { AuthRequestBody } from '@backstage/plugin-kubernetes-backend'; +import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-backend'; export interface KubernetesAuthProvider { decorateRequestBodyForAuth( - requestBody: AuthRequestBody, - ): Promise; + requestBody: KubernetesRequestBody, + ): Promise; } export const kubernetesAuthProvidersApiRef = createApiRef< @@ -33,6 +33,6 @@ export const kubernetesAuthProvidersApiRef = createApiRef< export interface KubernetesAuthProvidersApi { decorateRequestBodyForAuth( authProvider: string, - requestBody: AuthRequestBody, - ): Promise; + requestBody: KubernetesRequestBody, + ): Promise; } From 1166fcc36e22c7e4de184ca9e02479d84a14d72c Mon Sep 17 00:00:00 2001 From: Moustafa Baiou Date: Mon, 9 Nov 2020 01:44:15 -0500 Subject: [PATCH 178/430] add changeset --- .changeset/chatty-radios-beam.md | 7 +++++++ plugins/kubernetes-backend/package.json | 1 + plugins/kubernetes/src/api/types.ts | 1 - .../src/components/KubernetesContent/KubernetesContent.tsx | 1 - 4 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 .changeset/chatty-radios-beam.md diff --git a/.changeset/chatty-radios-beam.md b/.changeset/chatty-radios-beam.md new file mode 100644 index 0000000000..91ad76ee88 --- /dev/null +++ b/.changeset/chatty-radios-beam.md @@ -0,0 +1,7 @@ +--- +'@backstage/catalog-model': minor +'@backstage/plugin-kubernetes': minor +'@backstage/plugin-kubernetes-backend': minor +--- + +add kubernetes selector to component model diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index e58bb4f047..be7b86dd52 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -21,6 +21,7 @@ }, "dependencies": { "@backstage/backend-common": "^0.3.0", + "@backstage/catalog-model": "^0.2.0", "@backstage/config": "^0.1.1", "@kubernetes/client-node": "^0.12.1", "@types/express": "^4.17.6", diff --git a/plugins/kubernetes/src/api/types.ts b/plugins/kubernetes/src/api/types.ts index 663841ddf9..90c9e33504 100644 --- a/plugins/kubernetes/src/api/types.ts +++ b/plugins/kubernetes/src/api/types.ts @@ -19,7 +19,6 @@ import { KubernetesRequestBody, ObjectsByEntityResponse, } from '@backstage/plugin-kubernetes-backend'; -import { V1LabelSelector } from '@kubernetes/client-node'; export const kubernetesApiRef = createApiRef({ id: 'plugin.kubernetes.service', diff --git a/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx b/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx index 4e1560be70..f87ab04ece 100644 --- a/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx +++ b/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx @@ -41,7 +41,6 @@ import { ExtensionsV1beta1Ingress, V1ConfigMap, V1HorizontalPodAutoscaler, - V1LabelSelector, V1Service, } from '@kubernetes/client-node'; import { Services } from '../Services'; From a2f791836e80386fca7fb7613cb3e073fb8e6ab5 Mon Sep 17 00:00:00 2001 From: Moustafa Baiou Date: Mon, 9 Nov 2020 01:45:54 -0500 Subject: [PATCH 179/430] refactor name of api method --- plugins/kubernetes/src/api/KubernetesBackendClient.ts | 2 +- plugins/kubernetes/src/api/types.ts | 2 +- .../src/components/KubernetesContent/KubernetesContent.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/kubernetes/src/api/KubernetesBackendClient.ts b/plugins/kubernetes/src/api/KubernetesBackendClient.ts index d8fa800ab6..b81a799d6b 100644 --- a/plugins/kubernetes/src/api/KubernetesBackendClient.ts +++ b/plugins/kubernetes/src/api/KubernetesBackendClient.ts @@ -50,7 +50,7 @@ export class KubernetesBackendClient implements KubernetesApi { return await response.json(); } - async getObjectsByLabelSelector( + async getObjectsByEntity( requestBody: KubernetesRequestBody, ): Promise { return await this.getRequired( diff --git a/plugins/kubernetes/src/api/types.ts b/plugins/kubernetes/src/api/types.ts index 90c9e33504..498e74a626 100644 --- a/plugins/kubernetes/src/api/types.ts +++ b/plugins/kubernetes/src/api/types.ts @@ -27,7 +27,7 @@ export const kubernetesApiRef = createApiRef({ }); export interface KubernetesApi { - getObjectsByLabelSelector( + getObjectsByEntity( requestBody: KubernetesRequestBody, ): Promise; } diff --git a/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx b/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx index f87ab04ece..14cda3b5f7 100644 --- a/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx +++ b/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx @@ -133,7 +133,7 @@ export const KubernetesContent = ({ entity }: KubernetesContentProps) => { // TODO: Add validation on contents/format of requestBody kubernetesApi - .getObjectsByLabelSelector(requestBody) + .getObjectsByEntity(requestBody) .then(result => { setKubernetesObjects(result); }) From 75d63ee0a08cd07169dae07eae4bda871de4d718 Mon Sep 17 00:00:00 2001 From: Moustafa Baiou Date: Mon, 9 Nov 2020 01:58:33 -0500 Subject: [PATCH 180/430] fix test --- ...ubernetesObjectsByServiceIdHandler.test.ts | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts b/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts index f95b9a7242..4fdd65f474 100644 --- a/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts +++ b/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts @@ -16,6 +16,7 @@ import { handleGetKubernetesObjectsForService } from './getKubernetesObjectsForServiceHandler'; import { getVoidLogger } from '@backstage/backend-common'; +import { ComponentEntityV1alpha1 } from '@backstage/catalog-model'; import { ClusterDetails } from '..'; const TEST_SERVICE_ID = 'my-service'; @@ -24,6 +25,26 @@ const fetchObjectsForService = jest.fn(); const getClustersByServiceId = jest.fn(); +const goodEntity: ComponentEntityV1alpha1 = { + apiVersion: 'backstage.io/v1beta1', + kind: 'Component', + metadata: { + name: 'test-component', + }, + spec: { + type: 'service', + lifecycle: 'production', + owner: 'joe', + kubernetes: { + selector: { + matchLabels: { + 'backstage.io/test-label': 'test-component', + }, + }, + }, + }, +}; + const mockFetch = (mock: jest.Mock) => { mock.mockImplementation((serviceId: string, clusterDetails: ClusterDetails) => Promise.resolve({ @@ -90,7 +111,7 @@ describe('handleGetKubernetesObjectsForService', () => { getClustersByServiceId, }, getVoidLogger(), - {}, + { entity: goodEntity }, ); expect(getClustersByServiceId.mock.calls.length).toBe(1); @@ -165,11 +186,11 @@ describe('handleGetKubernetesObjectsForService', () => { }, getVoidLogger(), { + entity: goodEntity, auth: { google: 'google_token_123', }, }, - '', ); expect(getClustersByServiceId.mock.calls.length).toBe(1); From 728767e3d39a0fd0c149357ecf96da2275f91809 Mon Sep 17 00:00:00 2001 From: Moustafa Baiou Date: Mon, 9 Nov 2020 14:00:13 -0500 Subject: [PATCH 181/430] cleanup --- .../src/service/KubernetesFetcher.test.ts | 49 ++++++++++--------- .../src/service/KubernetesFetcher.ts | 15 +++--- ...ubernetesObjectsByServiceIdHandler.test.ts | 2 +- .../getKubernetesObjectsForServiceHandler.ts | 28 +++++------ plugins/kubernetes-backend/src/types/types.ts | 12 +++-- plugins/kubernetes/README.md | 36 +++++++------- 6 files changed, 71 insertions(+), 71 deletions(-) diff --git a/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts b/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts index f37a81f9f8..29233cf7fd 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts @@ -16,6 +16,7 @@ import { getVoidLogger } from '@backstage/backend-common'; import { KubernetesClientBasedFetcher } from './KubernetesFetcher'; +import { ObjectFetchParams } from '..'; describe('KubernetesClientProvider', () => { let clientMock: any; @@ -57,17 +58,17 @@ describe('KubernetesClientProvider', () => { clientMock.listServiceForAllNamespaces.mockRejectedValue(errorResponse); - const result = await sut.fetchObjectsForService( - 'some-service', - { + const result = await sut.fetchObjectsForService({ + serviceId: 'some-service', + clusterDetails: { name: 'cluster1', url: 'http://localhost:9999', serviceAccountToken: 'token', authProvider: 'serviceAccount', }, - new Set(['pods', 'services']), - '', - ); + objectTypesToFetch: new Set(['pods', 'services']), + labelSelector: '', + }); expect(result).toStrictEqual({ errors: [expectedResult], @@ -121,17 +122,17 @@ describe('KubernetesClientProvider', () => { }, }); - const result = await sut.fetchObjectsForService( - 'some-service', - { + const result = await sut.fetchObjectsForService({ + serviceId: 'some-service', + clusterDetails: { name: 'cluster1', url: 'http://localhost:9999', serviceAccountToken: 'token', authProvider: 'serviceAccount', }, - new Set(['pods', 'services']), - '', - ); + objectTypesToFetch: new Set(['pods', 'services']), + labelSelector: '', + }); expect(result).toStrictEqual({ errors: [], @@ -171,17 +172,17 @@ describe('KubernetesClientProvider', () => { }); it('should throw error on unknown type', () => { expect(() => - sut.fetchObjectsForService( - 'some-service', - { + sut.fetchObjectsForService({ + serviceId: 'some-service', + clusterDetails: { name: 'cluster1', url: 'http://localhost:9999', serviceAccountToken: 'token', authProvider: 'serviceAccount', }, - new Set(['foo']), - '', - ), + objectTypesToFetch: new Set(['foo']), + labelSelector: '', + }), ).toThrow('unrecognised type=foo'); expect(clientMock.listPodForAllNamespaces.mock.calls.length).toBe(0); @@ -282,17 +283,17 @@ describe('KubernetesClientProvider', () => { }, }); - await sut.fetchObjectsForService( - 'some-service', - { + await sut.fetchObjectsForService({ + serviceId: 'some-service', + clusterDetails: { name: 'cluster1', url: 'http://localhost:9999', serviceAccountToken: 'token', authProvider: 'serviceAccount', }, - new Set(['pods', 'services']), - '', - ); + objectTypesToFetch: new Set(['pods', 'services']), + labelSelector: '', + }); const mockCall = clientMock.listPodForAllNamespaces.mock.calls[0]; const actualSelector = mockCall[mockCall.length - 1]; diff --git a/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts b/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts index 11917efc52..d9c6ee6d1a 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts @@ -38,6 +38,7 @@ import { FetchResponseWrapper, KubernetesFetchError, KubernetesErrorTypes, + ObjectFetchParams, } from '..'; import lodash, { Dictionary } from 'lodash'; @@ -106,18 +107,14 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { } fetchObjectsForService( - serviceId: string, - clusterDetails: ClusterDetails, - objectTypesToFetch: Set, - labelSelector: string, + params: ObjectFetchParams, ): Promise { - const fetchResults = Array.from(objectTypesToFetch).map(type => { + const fetchResults = Array.from(params.objectTypesToFetch).map(type => { return this.fetchByObjectType( - clusterDetails, + params.clusterDetails, type, - labelSelector.length !== 0 - ? labelSelector - : `backstage.io/kubernetes-id=${serviceId}`, + params.labelSelector || + `backstage.io/kubernetes-id=${params.serviceId}`, ).catch(captureKubernetesErrorsRethrowOthers); }); diff --git a/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts b/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts index 4fdd65f474..789c7062b9 100644 --- a/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts +++ b/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts @@ -25,7 +25,7 @@ const fetchObjectsForService = jest.fn(); const getClustersByServiceId = jest.fn(); -const goodEntity: ComponentEntityV1alpha1 = { +const goodEntity: ComponentEntityV1alpha1 = { apiVersion: 'backstage.io/v1beta1', kind: 'Component', metadata: { diff --git a/plugins/kubernetes-backend/src/service/getKubernetesObjectsForServiceHandler.ts b/plugins/kubernetes-backend/src/service/getKubernetesObjectsForServiceHandler.ts index 46f3f27c73..cec4fa8b66 100644 --- a/plugins/kubernetes-backend/src/service/getKubernetesObjectsForServiceHandler.ts +++ b/plugins/kubernetes-backend/src/service/getKubernetesObjectsForServiceHandler.ts @@ -15,6 +15,7 @@ */ import { Logger } from 'winston'; +import { ComponentEntityV1alpha1 } from '@backstage/catalog-model'; import { KubernetesRequestBody, ClusterDetails, @@ -22,10 +23,10 @@ import { KubernetesFetcher, KubernetesObjectTypes, ObjectsByEntityResponse, + ObjectFetchParams, } from '../types/types'; import { KubernetesAuthTranslator } from '../kubernetes-auth-translator/types'; import { KubernetesAuthTranslatorGenerator } from '../kubernetes-auth-translator/KubernetesAuthTranslatorGenerator'; -import { ComponentEntityV1alpha1 } from '@backstage/catalog-model'; export type GetKubernetesObjectsForServiceHandler = ( serviceId: string, @@ -47,18 +48,10 @@ const DEFAULT_OBJECTS = new Set([ ]); function parseLabelSelector(entity: ComponentEntityV1alpha1): string { - if ( - entity && - entity.spec && - entity.spec.kubernetes && - entity.spec.kubernetes.selector - ) { + const matchLabels = entity?.spec?.kubernetes?.selector?.matchLabels; + if (matchLabels) { // TODO: figure out how to convert the selector to the full query param from the yaml // (as shown here https://github.com/kubernetes/apimachinery/blob/master/pkg/labels/selector.go) - const { matchLabels } = entity.spec.kubernetes.selector; - if (!matchLabels) { - return ''; - } return Object.keys(matchLabels) .map(key => `${key}=${matchLabels[key.toString()]}`) .join(','); @@ -73,7 +66,7 @@ export const handleGetKubernetesObjectsForService: GetKubernetesObjectsForServic serviceLocator, logger, requestBody, - objectsToFetch = DEFAULT_OBJECTS, + objectTypesToFetch = DEFAULT_OBJECTS, ) => { const clusterDetails: ClusterDetails[] = await serviceLocator.getClustersByServiceId( serviceId, @@ -102,13 +95,18 @@ export const handleGetKubernetesObjectsForService: GetKubernetesObjectsForServic const labelSelector = parseLabelSelector(requestBody.entity); return Promise.all( - clusterDetailsDecoratedForAuth.map(cd => { + clusterDetailsDecoratedForAuth.map(clusterDetails => { return fetcher - .fetchObjectsForService(serviceId, cd, objectsToFetch, labelSelector) + .fetchObjectsForService({ + serviceId, + clusterDetails, + objectTypesToFetch, + labelSelector, + }) .then(result => { return { cluster: { - name: cd.name, + name: clusterDetails.name, }, resources: result.responses, errors: result.errors, diff --git a/plugins/kubernetes-backend/src/types/types.ts b/plugins/kubernetes-backend/src/types/types.ts index 738bc89b21..b2a5ef432c 100644 --- a/plugins/kubernetes-backend/src/types/types.ts +++ b/plugins/kubernetes-backend/src/types/types.ts @@ -109,14 +109,18 @@ export interface IngressesFetchResponse { resources: Array; } +export interface ObjectFetchParams { + serviceId: string; + clusterDetails: ClusterDetails; + objectTypesToFetch: Set; + labelSelector: string; +} + // Fetches information from a kubernetes cluster using the cluster details object // to target a specific cluster export interface KubernetesFetcher { fetchObjectsForService( - serviceId: string, - clusterDetails: ClusterDetails, - objectTypesToFetch: Set, - labelSelector: string, + params: ObjectFetchParams, ): Promise; } diff --git a/plugins/kubernetes/README.md b/plugins/kubernetes/README.md index e6f1f2355a..8f3542a46e 100644 --- a/plugins/kubernetes/README.md +++ b/plugins/kubernetes/README.md @@ -14,10 +14,26 @@ It is only meant for local development, and the setup for it can be found inside ## Surfacing your Kubernetes components as part of an entity -There are 2 ways to surface your kubernetes components as part of an entity. +There are two ways to surface your kubernetes components as part of an entity. The label selector takes precedence over the annotation/service id. -### Common `backstage.io/kubernetes-id` label +### Full label selector + +#### Adding the entity label selector to the spec + +In order for Backstage to detect that an entity has kubernetes components the `kubernetes.selector` must have a valid selector. (Currently only matchLabels is supported) + +```yaml +spec: + kubernetes: + selector: + matchLabels: + someKey: someValue + other-key: other-value + app.kubernetes.io/name: dice-roller +``` + +### Common `backstage.io/kubernetes-id` label on objects #### Adding the entity annotation @@ -37,19 +53,3 @@ as a part of an entity, Kubernetes components must be labeled with the following ```yaml 'backstage.io/kubernetes-id': ``` - -### Full label selector - -#### Adding the entity label selector to the spec - -In order for Backstage to detect that an entity has kubernetes components the `kubernetes.selector` must have a valid selector. (Currently only matchLabels is supported) - -```yaml -spec: - kubernetes: - selector: - matchLabels: - someKey: someValue - other-key: other-value - app.kubernetes.io/name: dice-roller -``` From 74102d917635bf59c3062d64b846151844c0d8cd Mon Sep 17 00:00:00 2001 From: Moustafa Baiou Date: Mon, 9 Nov 2020 14:20:32 -0500 Subject: [PATCH 182/430] fix test after refactor --- .../getKubernetesObjectsByServiceIdHandler.test.ts | 8 ++++---- .../src/service/getKubernetesObjectsForServiceHandler.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts b/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts index 789c7062b9..fd76cab662 100644 --- a/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts +++ b/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts @@ -46,7 +46,7 @@ const goodEntity: ComponentEntityV1alpha1 = { }; const mockFetch = (mock: jest.Mock) => { - mock.mockImplementation((serviceId: string, clusterDetails: ClusterDetails) => + mock.mockImplementation((params: ObjectFetchParams) => Promise.resolve({ errors: [], responses: [ @@ -55,7 +55,7 @@ const mockFetch = (mock: jest.Mock) => { resources: [ { metadata: { - name: `my-pods-${serviceId}-${clusterDetails.name}`, + name: `my-pods-${params.serviceId}-${params.clusterDetails.name}`, }, }, ], @@ -65,7 +65,7 @@ const mockFetch = (mock: jest.Mock) => { resources: [ { metadata: { - name: `my-configmaps-${serviceId}-${clusterDetails.name}`, + name: `my-configmaps-${params.serviceId}-${params.clusterDetails.name}`, }, }, ], @@ -75,7 +75,7 @@ const mockFetch = (mock: jest.Mock) => { resources: [ { metadata: { - name: `my-services-${serviceId}-${clusterDetails.name}`, + name: `my-services-${params.serviceId}-${params.clusterDetails.name}`, }, }, ], diff --git a/plugins/kubernetes-backend/src/service/getKubernetesObjectsForServiceHandler.ts b/plugins/kubernetes-backend/src/service/getKubernetesObjectsForServiceHandler.ts index cec4fa8b66..1b0cf4f783 100644 --- a/plugins/kubernetes-backend/src/service/getKubernetesObjectsForServiceHandler.ts +++ b/plugins/kubernetes-backend/src/service/getKubernetesObjectsForServiceHandler.ts @@ -34,7 +34,7 @@ export type GetKubernetesObjectsForServiceHandler = ( serviceLocator: KubernetesServiceLocator, logger: Logger, requestBody: KubernetesRequestBody, - objectsToFetch?: Set, + objectTypesToFetch?: Set, ) => Promise; const DEFAULT_OBJECTS = new Set([ From 44ea3101a7e6659a6dbae7e20dd1e953078b9367 Mon Sep 17 00:00:00 2001 From: Moustafa Baiou Date: Mon, 9 Nov 2020 14:23:40 -0500 Subject: [PATCH 183/430] remove unneeded import --- .../src/service/getKubernetesObjectsByServiceIdHandler.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts b/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts index fd76cab662..bb0ffddc26 100644 --- a/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts +++ b/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts @@ -17,7 +17,6 @@ import { handleGetKubernetesObjectsForService } from './getKubernetesObjectsForServiceHandler'; import { getVoidLogger } from '@backstage/backend-common'; import { ComponentEntityV1alpha1 } from '@backstage/catalog-model'; -import { ClusterDetails } from '..'; const TEST_SERVICE_ID = 'my-service'; From 18d0fd10a03c941ed87b734eada71a284586f8f3 Mon Sep 17 00:00:00 2001 From: Moustafa Baiou Date: Mon, 9 Nov 2020 14:49:12 -0500 Subject: [PATCH 184/430] add needed import --- .../src/service/getKubernetesObjectsByServiceIdHandler.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts b/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts index bb0ffddc26..adebebe1c5 100644 --- a/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts +++ b/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts @@ -17,6 +17,7 @@ import { handleGetKubernetesObjectsForService } from './getKubernetesObjectsForServiceHandler'; import { getVoidLogger } from '@backstage/backend-common'; import { ComponentEntityV1alpha1 } from '@backstage/catalog-model'; +import { ObjectFetchParams } from '..'; const TEST_SERVICE_ID = 'my-service'; From d20f605082f3f2c1a0812c9d8172aafb298d6fd1 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 23 Nov 2020 12:43:54 +0100 Subject: [PATCH 185/430] cli: add test for versions:bump command --- .../cli/src/commands/versions/bump.test.ts | 161 ++++++++++++++++++ packages/cli/src/commands/versions/bump.ts | 2 +- .../cli/src/lib/versioning/packages.test.ts | 2 +- packages/cli/src/lib/versioning/packages.ts | 9 +- 4 files changed, 167 insertions(+), 7 deletions(-) create mode 100644 packages/cli/src/commands/versions/bump.test.ts diff --git a/packages/cli/src/commands/versions/bump.test.ts b/packages/cli/src/commands/versions/bump.test.ts new file mode 100644 index 0000000000..687708b074 --- /dev/null +++ b/packages/cli/src/commands/versions/bump.test.ts @@ -0,0 +1,161 @@ +/* + * 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 fs from 'fs-extra'; +import mockFs from 'mock-fs'; +import { resolve as resolvePath } from 'path'; +import { paths } from '../../lib/paths'; +import { mapDependencies } from '../../lib/versioning'; +import * as runObj from '../../lib/run'; +import bump from './bump'; +import { withLogCollector } from '@backstage/test-utils'; + +const REGISTRY_VERSIONS: { [name: string]: string } = { + '@backstage/core': '1.0.7', + '@backstage/theme': '2.0.0', +}; + +const HEADER = `# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + +`; + +const lockfileMock = `${HEADER} +"@backstage/core@^1.0.5": + version "1.0.6" + resolved "https://my-registry/a-1.0.01.tgz#abc123" + integrity sha512-xyz + +"@backstage/core@^1.0.3": + version "1.0.3" + resolved "https://my-registry/a-1.0.01.tgz#abc123" + integrity sha512-xyz + +"@backstage/theme@^1.0.0": + version "1.0.0" + resolved "https://my-registry/a-1.0.01.tgz#abc123" + integrity sha512-xyz +`; + +const lockfileMockResult = `${HEADER} +"@backstage/core@^1.0.3", "@backstage/core@^1.0.5": + version "1.0.6" + resolved "https://my-registry/a-1.0.01.tgz#abc123" + integrity sha512-xyz + +"@backstage/theme@^1.0.0": + version "1.0.0" + resolved "https://my-registry/a-1.0.01.tgz#abc123" + integrity sha512-xyz +`; + +describe('bump', () => { + afterEach(() => { + mockFs.restore(); + jest.resetAllMocks(); + }); + + it('should bump backstage dependencies', async () => { + // Make sure all modules involved in package discovery are in the module cache before we mock fs + await mapDependencies(paths.targetDir); + + mockFs({ + '/yarn.lock': lockfileMock, + '/lerna.json': JSON.stringify({ + packages: ['packages/*'], + }), + '/packages/a/package.json': JSON.stringify({ + name: 'a', + dependencies: { + '@backstage/core': '^1.0.5', + }, + }), + '/packages/b/package.json': JSON.stringify({ + name: 'b', + dependencies: { + '@backstage/core': '^1.0.3', + '@backstage/theme': '^1.0.0', + }, + }), + }); + + paths.targetDir = '/'; + jest + .spyOn(paths, 'resolveTargetRoot') + .mockImplementation((...paths) => resolvePath('/', ...paths)); + jest.spyOn(runObj, 'runPlain').mockImplementation(async (...[, , , name]) => + JSON.stringify({ + type: 'inspect', + data: { + name: name, + 'dist-tags': { + latest: REGISTRY_VERSIONS[name], + }, + }, + }), + ); + jest.spyOn(runObj, 'run').mockResolvedValue(undefined); + + const { log: logs } = await withLogCollector(['log'], async () => { + await bump(); + }); + expect(logs.filter(Boolean)).toEqual([ + 'Checking for updates of @backstage/theme', + 'Checking for updates of @backstage/core', + 'Some packages are outdated, updating', + 'Bumping @backstage/theme in b to ^2.0.0', + "Running 'yarn install' to install new versions", + 'Removing duplicate dependencies from yarn.lock', + "Running 'yarn install' to remove duplicates from node_modules", + ]); + + expect(runObj.runPlain).toHaveBeenCalledTimes(2); + expect(runObj.runPlain).toHaveBeenCalledWith( + 'yarn', + 'info', + '--json', + '@backstage/core', + ); + expect(runObj.runPlain).toHaveBeenCalledWith( + 'yarn', + 'info', + '--json', + '@backstage/theme', + ); + + expect(runObj.run).toHaveBeenCalledTimes(2); + expect(runObj.run).toHaveBeenCalledWith('yarn', ['install']); + + const lockfileContents = await fs.readFile('/yarn.lock', 'utf8'); + expect(lockfileContents).toBe(lockfileMockResult); + + const packageA = await fs.readJson('/packages/a/package.json'); + expect(packageA).toEqual({ + name: 'a', + dependencies: { + '@backstage/core': '^1.0.5', // not bumped since new version is within range + }, + }); + const packageB = await fs.readJson('/packages/b/package.json'); + expect(packageB).toEqual({ + name: 'b', + dependencies: { + '@backstage/core': '^1.0.3', // not bumped + '@backstage/theme': '^2.0.0', // bumped since newer + }, + }); + }); +}); diff --git a/packages/cli/src/commands/versions/bump.ts b/packages/cli/src/commands/versions/bump.ts index eec8abed9b..00944c5db9 100644 --- a/packages/cli/src/commands/versions/bump.ts +++ b/packages/cli/src/commands/versions/bump.ts @@ -40,7 +40,7 @@ type PkgVersionInfo = { export default async () => { // First we discover all Backstage dependencies within our own repo - const dependencyMap = await mapDependencies(); + const dependencyMap = await mapDependencies(paths.targetDir); // Next check with the package registry what the latest version of all of those dependencies are const targetVersions = new Map(); diff --git a/packages/cli/src/lib/versioning/packages.test.ts b/packages/cli/src/lib/versioning/packages.test.ts index fd37d7af95..8155d4b5c0 100644 --- a/packages/cli/src/lib/versioning/packages.test.ts +++ b/packages/cli/src/lib/versioning/packages.test.ts @@ -75,7 +75,7 @@ describe('mapDependencies', () => { const oldDir = paths.targetDir; paths.targetDir = '/'; - const dependencyMap = await mapDependencies(); + const dependencyMap = await mapDependencies(paths.targetDir); expect(Array.from(dependencyMap)).toEqual([ [ '@backstage/core', diff --git a/packages/cli/src/lib/versioning/packages.ts b/packages/cli/src/lib/versioning/packages.ts index 239fe575d6..76e5dc49b0 100644 --- a/packages/cli/src/lib/versioning/packages.ts +++ b/packages/cli/src/lib/versioning/packages.ts @@ -15,7 +15,6 @@ */ import { runPlain } from '../../lib/run'; -import { paths } from '../../lib/paths'; const PREFIX = '@backstage'; @@ -59,11 +58,11 @@ export async function fetchPackageInfo( } /** Map all dependencies in the repo as dependency => dependents */ -export async function mapDependencies(): Promise< - Map -> { +export async function mapDependencies( + targetDir: string, +): Promise> { const LernaProject = require('@lerna/project'); - const project = new LernaProject(paths.targetDir); + const project = new LernaProject(targetDir); const packages = await project.getPackages(); const dependencyMap = new Map(); From eeda19c7cefb9ab9650c1c0546cdc69a0c313f1f Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Mon, 23 Nov 2020 17:07:38 +0100 Subject: [PATCH 186/430] [Search] name in search result clickable (#3349) * fix(search): wrap value in link component for rows in component id column * fix(search result): change name of column from component id to name * fix(search): construct url in search api instead of search result component * fix(search): use entity default constant from catalog model as fallback to namespace, add entity type * fix(search): replace unknown with undefined if owner or lifecycle doesn't exist on entity, lowercase namespace and kind in url * fix owner fallback and delete description fallback to show empty cells instead --- plugins/search/package.json | 1 + plugins/search/src/apis.ts | 27 ++++++++++++------- .../components/SearchResult/SearchResult.tsx | 11 +++++--- 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/plugins/search/package.json b/plugins/search/package.json index 0b1941d9e0..df5c6c25b9 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -22,6 +22,7 @@ "dependencies": { "@backstage/core": "^0.3.1", "@backstage/plugin-catalog": "^0.2.2", + "@backstage/catalog-model": "^0.2.0", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", diff --git a/plugins/search/src/apis.ts b/plugins/search/src/apis.ts index f3a050b2a2..e74741e27b 100644 --- a/plugins/search/src/apis.ts +++ b/plugins/search/src/apis.ts @@ -15,13 +15,15 @@ */ import { CatalogApi } from '@backstage/plugin-catalog'; +import { Entity, ENTITY_DEFAULT_NAMESPACE } from '@backstage/catalog-model'; export type Result = { name: string; - description: string; - owner: string; + description: string | undefined; + owner: string | undefined; kind: string; - lifecycle: string; + lifecycle: string | undefined; + url: string; }; export type SearchResults = Array; @@ -35,12 +37,19 @@ class SearchApi { private async entities() { const entities = await this.catalogApi.getEntities(); - return entities.items.map((result: any) => ({ - name: result.metadata.name, - description: result.metadata.description, - owner: result.spec.owner, - kind: result.kind, - lifecycle: result.spec.lifecycle, + return entities.items.map((entity: Entity) => ({ + name: entity.metadata.name, + description: entity.metadata.description, + owner: + typeof entity.spec?.owner === 'string' ? entity.spec?.owner : undefined, + kind: entity.kind, + lifecycle: + typeof entity.spec?.lifecycle === 'string' + ? entity.spec?.lifecycle + : undefined, + url: `/catalog/${ + entity.metadata.namespace?.toLowerCase() || ENTITY_DEFAULT_NAMESPACE + }/${entity.kind.toLowerCase()}/${entity.metadata.name}`, })); } diff --git a/plugins/search/src/components/SearchResult/SearchResult.tsx b/plugins/search/src/components/SearchResult/SearchResult.tsx index a5dc11aa89..f1e160e792 100644 --- a/plugins/search/src/components/SearchResult/SearchResult.tsx +++ b/plugins/search/src/components/SearchResult/SearchResult.tsx @@ -19,6 +19,7 @@ import { useAsync } from 'react-use'; import { makeStyles, Typography, Grid, Divider } from '@material-ui/core'; import { Alert } from '@material-ui/lab'; import { + Link, EmptyState, Progress, Table, @@ -66,9 +67,12 @@ type Filters = { // TODO: move out column to make the search result component more generic const columns: TableColumn[] = [ { - title: 'Component Id', + title: 'Name', field: 'name', highlight: true, + render: (result: Partial) => ( + {result.name} + ), }, { title: 'Description', @@ -150,8 +154,9 @@ export const SearchResult = ({ searchQuery }: SearchResultProps) => { // filter on checked if (filters.checked.length > 0) { - withFilters = withFilters.filter((result: Result) => - filters.checked.includes(result.lifecycle), + withFilters = withFilters.filter( + (result: Result) => + result.lifecycle && filters.checked.includes(result.lifecycle), ); } From b47dce06fc8538da931abef8ef740abaff119c34 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 23 Nov 2020 17:14:28 +0100 Subject: [PATCH 187/430] backend-common: make integrations host and urls visibile in the frontend --- .changeset/tame-rockets-explode.md | 5 +++++ packages/backend-common/config.d.ts | 35 +++++++++++++++++++++++------ 2 files changed, 33 insertions(+), 7 deletions(-) create mode 100644 .changeset/tame-rockets-explode.md diff --git a/.changeset/tame-rockets-explode.md b/.changeset/tame-rockets-explode.md new file mode 100644 index 0000000000..123472701e --- /dev/null +++ b/.changeset/tame-rockets-explode.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +Make integration host and url configurations visible in the frontend diff --git a/packages/backend-common/config.d.ts b/packages/backend-common/config.d.ts index 7ebcd01e8c..7a134ac506 100644 --- a/packages/backend-common/config.d.ts +++ b/packages/backend-common/config.d.ts @@ -87,7 +87,10 @@ export interface Config { integrations?: { /** Integration configuration for Azure */ azure?: Array<{ - /** The hostname of the given Azure instance */ + /** + * The hostname of the given Azure instance + * @visibility frontend + */ host: string; /** * Token used to authenticate requests. @@ -98,14 +101,20 @@ export interface Config { /** Integration configuration for BitBucket */ bitbucket?: Array<{ - /** The hostname of the given Bitbucket instance */ + /** + * The hostname of the given Bitbucket instance + * @visibility frontend + */ host: string; /** * Token used to authenticate requests. * @visibility secret */ token?: string; - /** The base url for the BitBucket API, for example https://api.bitbucket.org/2.0 */ + /** + * The base url for the BitBucket API, for example https://api.bitbucket.org/2.0 + * @visibility frontend + */ apiBaseUrl?: string; /** * The username to use for authenticated requests. @@ -121,22 +130,34 @@ export interface Config { /** Integration configuration for GitHub */ github?: Array<{ - /** The hostname of the given GitHub instance */ + /** + * The hostname of the given GitHub instance + * @visibility frontend + */ host: string; /** * Token used to authenticate requests. * @visibility secret */ token?: string; - /** The base url for the GitHub API, for example https://api.github.com */ + /** + * The base url for the GitHub API, for example https://api.github.com + * @visibility frontend + */ apiBaseUrl?: string; - /** The base url for GitHub raw resources, for example https://raw.githubusercontent.com */ + /** + * The base url for GitHub raw resources, for example https://raw.githubusercontent.com + * @visibility frontend + */ rawBaseUrl?: string; }>; /** Integration configuration for GitLab */ gitlab?: Array<{ - /** The hostname of the given GitLab instance */ + /** + * The hostname of the given GitLab instance + * @visibility frontend + */ host: string; /** * Token used to authenticate requests. From 8fc3fc9629bc605521fc4d6641974f79852cb2d3 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Fri, 20 Nov 2020 13:29:26 +0100 Subject: [PATCH 188/430] TechDocs: Document basic and recommended architecture --- docs/features/techdocs/architecture.md | 149 +++++++++++++++++++++---- mkdocs.yml | 6 +- plugins/techdocs/src/api.ts | 18 +++ plugins/techdocs/src/plugin.ts | 1 + plugins/techdocs/src/types.ts | 6 + 5 files changed, 157 insertions(+), 23 deletions(-) diff --git a/docs/features/techdocs/architecture.md b/docs/features/techdocs/architecture.md index 45a7713c39..01497b31bc 100644 --- a/docs/features/techdocs/architecture.md +++ b/docs/features/techdocs/architecture.md @@ -1,44 +1,153 @@ --- id: architecture -title: Architecture -description: Documentation on Architecture +title: TechDocs Architecture +description: Documentation on TechDocs Architecture --- ## Basic (out-of-the-box) When you deploy Backstage (with TechDocs enabled by default), you get a basic -out-of-the box experience. This is how the architecture of it looks. - -Note: See below for our recommended production architecture which takes care of -stability, scalability and speed. +out-of-the box experience. ![TechDocs Architecture diagram](../../assets/techdocs/architecture-basic.drawio.svg) -We store the timestamp in memory. +> Note: See below for our recommended deployment architecture which takes care +> of stability, scalability and speed. -Publishing to external storage is also possible. But status: none of the cloud -storage solutions are supported yet. +When you open a TechDocs site in Backstage, the +[TechDocs Reader](./concepts.md#techdocs-reader) makes a request to +`techdocs-backend` with the entity ID and the path of the current page you are +looking at. In response, it receives the static files (HTML, CSS, JSON, etc.) to +render on the page in TechDocs/Backstage. -Speed is a bottleneck if doc repositories are very large (>100MBs). +The static files consist of HTML, CSS and Images generated by MkDocs. We remove +all the Javascript before adding them to Backstage for security reasons. And +there are some additional techdocs metadata JSON files that TechDocs needs to +render a site. + +The TechDocs Reader then applies a list of "Transformers" (see +[Concepts](./concepts.md)) which modify the generated static HTML files for a +number of use cases e.g. Remove certain headers, filter out some HTML tags, etc. + +Currently, we use the Backstage server's (or techdocs-backend's) local file +system to store the generated files. Publishing to an external storage system +(AWS S3, GCS, etc.) is also possible, but has not been implemented yet. + +A word about `UrlReader` vs Git preparer - Right now, we have two ways to fetch +files from its source repository for docs site generation. 1. By using Git +and 2. By directly using Source control (GitHub, Azure, etc.) APIs. This work is +heavily in progress. Please reach out to us on Discord in the #docs-like-code +channel to talk about it. ## Recommended deployment -This is how we recommend deploying TechDocs for potential production use. +This is how we recommend deploying TechDocs in production environment. ![TechDocs Architecture diagram](../../assets/techdocs/architecture-recommended.drawio.svg) -TechDocs Backend: Responsible for access control and sending files over to -TechDocs. +The key difference in the recommended deployment approach is where the docs are +built. -Tokens (if possible): Read and Write tokens. +We assume each entity lives in a repository somewhere (GitHub, GitLab, etc.). We +recommend using a CI/CD pipeline with the repository that has a dedicated +step/job to build docs for TechDocs. The generated static files are then stored +in a cloud storage solution of your choice. +[Track progress here](https://github.com/backstage/backstage/issues/3096). -## FAQ +Similar to how it is done in the Basic setup, the TechDocs Reader requests +`techdocs-backend` plugin for the docs site. `techdocs-backend` then requests +your configured storage solution for the necessary files and returns them to +TechDocs Reader. -Q: Why don't use Backstage server to serve static files? A: Not scalable. -[Investigate more] +We will provide instructions, scripts and/or templates (e.g. GitHub actions) to +build docs in your CI/CD system. +[Track progress here.](https://github.com/backstage/backstage/issues/3400) You +will be able to use `techdocs-cli` to build docs and publish the generated docs +site files to your cloud storage system. + +Note about caching: We have noticed internally that some storage providers can +be quite slow, which is why we are recommending a cache that sits between the +TechDocs Reader and the Storage. + +_Feel free to suggest better ideas to us in #docs-like-code channel in Discord +or via a GitHub issue._ + +### Security consideration + +Our biggest security concern is managing the access to the docs in the cloud +storage. We also want to have only one security solution for all different types +of storage (GCS, AWS, custom SFTP server, etc.) Restricting access to the +storage and only allowing `techdocs-backend` to fetch files is a good way to +achieve this. + +This would also allow us to use the access control management Backstage when +that is ready. +[Track progress here.](https://github.com/backstage/backstage/issues/3218) + +In theory, you can directly enable TechDocs Reader to read from your storage. +But, you will have to think about how to do it without the docs being public and +how access to user groups is managed. + +For cloud storage access tokens, `techdocs-backend` only needs a token with Read +permissions. But in your CI/CD system, there needs to be a token with Write +permissions to publish the generated docs site files. + +## FAQs + +**Q: Why do you have separate "basic" and "recommended" deployment approaches?** + +A: The basic or out-of-the-box setup is what you get when you create a new app +or do a git clone of the Backstage repository. We want the first experience to +_just work magically_ so that you can have your first experience with TechDocs +which is smooth. However, if you decide to deploy Backstage/TechDocs for +production use, the basic setup would work but there are going to be downsides +as you scale with the number of documentation sites and sizes of them. So you +would want to make sure the deployment is as stable as possible. Hence there is +a recommended approach. There can be even more deployment approaches to TechDocs +and we welcome such "Alternative" ideas from the community. + +**Q: Why don't you recommend techdocs-backend local filesystem to serve static +files?** + +A: It would make scaling a Backstage instance harder. Think about the case where +we have distributed Backstage deployments. Using a separate file storage system +for TechDocs makes it easier to do some operations like delete a docs site and +wipe its contents. + +**Q: Why aren't docs built on the fly i.e. when users visits a page, generate +docs site in real-time?** + +A: Generating the content from Markdown on the fly is not optimal (although that +is how the basic out-of-the-box setup is implemented). Storage solutions act as +a cache for the generated static content. TechDocs is also currently built on +MkDocs which does not allow us to build docs per-page, so we would have to build +all docs for a entity on every request. # Future work -Instead of storing frontend assets in cloud storage, it can only store texts and -images. And all of frontend rendering is handled by Backstage and TechDocs -reader. +_Ideas here are far fetched and not in the project's milestone for near future +(~6 months)._ + +We currently depend on MkDocs to parse doc sites written in Markdown. And we +store the generated static assets and re-use it later to render in Backstage. A +better (futuristic) approach will be to directly parse whatever type of source +files you have in your docs repository and directly render in Backstage in +real-time. + +# Features status + +Status of all the features mentioned above. + +**In place ✅** + +- Basic setup with techdocs-backend file server as storage. + +**Work in progress 🚧** + +- Basic setup with cloud storage solution. + +**Not implemented yet ❌** + +- `techdocs-cli` is able to generate docs in CI/CD environment. +- `techdocs-cli` is able to publish docs site to any storage. +- `techdocs-backend` integration with Backstage access control management. diff --git a/mkdocs.yml b/mkdocs.yml index 8a4a78ddf1..1086259710 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,6 +1,9 @@ site_name: 'Backstage' site_description: 'Main documentation for Backstage features and platform APIs' +plugins: + - techdocs-core + nav: - Overview: - What is Backstage?: 'overview/what-is-backstage.md' @@ -111,6 +114,3 @@ nav: - 'support/support.md' - 'support/project-structure.md' - FAQ: FAQ.md - -plugins: - - techdocs-core diff --git a/plugins/techdocs/src/api.ts b/plugins/techdocs/src/api.ts index 31e4a10e4c..194ad14d30 100644 --- a/plugins/techdocs/src/api.ts +++ b/plugins/techdocs/src/api.ts @@ -41,6 +41,11 @@ export interface TechDocs { getMetadata(metadataType: string, entityId: ParsedEntityId): Promise; } +/** + * API to talk to techdocs-backend. + * + * @property {string} apiOrigin Set to techdocs.requestUrl as the URL for techdocs-backend API + */ export class TechDocsApi implements TechDocs { public apiOrigin: string; @@ -60,6 +65,11 @@ export class TechDocsApi implements TechDocs { } } +/** + * API which talks to TechDocs storage to fetch files to render. + * + * @property {string} apiOrigin Set to techdocs.requestUrl as the URL for techdocs-backend API + */ export class TechDocsStorageApi implements TechDocsStorage { public apiOrigin: string; @@ -67,6 +77,14 @@ export class TechDocsStorageApi implements TechDocsStorage { this.apiOrigin = apiOrigin; } + /** + * Fetch HTML content as text for an individual docs page in an entity's docs site. + * + * @param {ParsedEntityId} entityId Object containing entity data like name, namespace, etc. + * @param {string} path The unique path to an individual docs page e.g. overview/what-is-new + * @returns {string} HTML content of the docs page as string + * @throws {Error} Throws error when the page is not found. + */ async getEntityDocs(entityId: ParsedEntityId, path: string) { const { kind, namespace, name } = entityId; diff --git a/plugins/techdocs/src/plugin.ts b/plugins/techdocs/src/plugin.ts index ee2cab60fa..576ba0a292 100644 --- a/plugins/techdocs/src/plugin.ts +++ b/plugins/techdocs/src/plugin.ts @@ -57,6 +57,7 @@ export const rootCatalogDocsRouteRef = createRouteRef({ title: 'Docs', }); +// TODO: Use discovery API for frontend to get URL for techdocs-backend instead of requestUrl export const plugin = createPlugin({ id: 'techdocs', apis: [ diff --git a/plugins/techdocs/src/types.ts b/plugins/techdocs/src/types.ts index ec907cc6fa..341d3f0c92 100644 --- a/plugins/techdocs/src/types.ts +++ b/plugins/techdocs/src/types.ts @@ -14,6 +14,12 @@ * limitations under the License. */ +/** + * To uniquely identify an entity in Backstage. + * @property {string} kind + * @property {string} namespace + * @property {string} name + */ export type ParsedEntityId = { kind: string; namespace?: string; From a8de7f554ca590e8419405d6b7bcca8c47f40ded Mon Sep 17 00:00:00 2001 From: Andrew Thauer <6507159+andrewthauer@users.noreply.github.com> Date: Mon, 23 Nov 2020 11:44:42 -0500 Subject: [PATCH 189/430] feat: improve circleci builds table --- .changeset/mighty-cycles-impress.md | 6 + .../app/src/components/catalog/EntityPage.tsx | 4 +- plugins/circleci/package.json | 2 + .../BuildWithStepsPage/BuildWithStepsPage.tsx | 14 +- .../BuildsPage/lib/Builds/Builds.tsx | 4 +- .../BuildsPage/lib/CITable/CITable.tsx | 128 ++++++++++++++++-- plugins/circleci/src/state/useBuilds.ts | 50 +++++-- plugins/circleci/src/util/index.ts | 17 +++ plugins/circleci/src/util/time.test.ts | 31 +++++ plugins/circleci/src/util/time.ts | 35 +++++ 10 files changed, 263 insertions(+), 28 deletions(-) create mode 100644 .changeset/mighty-cycles-impress.md create mode 100644 plugins/circleci/src/util/index.ts create mode 100644 plugins/circleci/src/util/time.test.ts create mode 100644 plugins/circleci/src/util/time.ts diff --git a/.changeset/mighty-cycles-impress.md b/.changeset/mighty-cycles-impress.md new file mode 100644 index 0000000000..33b2aa008c --- /dev/null +++ b/.changeset/mighty-cycles-impress.md @@ -0,0 +1,6 @@ +--- +'example-app': patch +'@backstage/plugin-circleci': patch +--- + +Improved CircleCI builds table to show more information and relevant links diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index d826bfa25e..c201fe88ad 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -79,10 +79,10 @@ export const CICDSwitcher = ({ entity }: { entity: Entity }) => { return ; case isBuildkiteAvailable(entity): return ; - case isGitHubActionsAvailable(entity): - return ; case isCircleCIAvailable(entity): return ; + case isGitHubActionsAvailable(entity): + return ; case isCloudbuildAvailable(entity): return ; case isTravisCIAvailable(entity): diff --git a/plugins/circleci/package.json b/plugins/circleci/package.json index c9ad54659f..eaa8c319a0 100644 --- a/plugins/circleci/package.json +++ b/plugins/circleci/package.json @@ -29,6 +29,8 @@ "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", "circleci-api": "^4.0.0", + "dayjs": "^1.9.4", + "lodash": "^4.17.15", "moment": "^2.25.3", "react": "^16.13.1", "react-dom": "^16.13.1", diff --git a/plugins/circleci/src/components/BuildWithStepsPage/BuildWithStepsPage.tsx b/plugins/circleci/src/components/BuildWithStepsPage/BuildWithStepsPage.tsx index 30994f5ce3..18ae5a2d04 100644 --- a/plugins/circleci/src/components/BuildWithStepsPage/BuildWithStepsPage.tsx +++ b/plugins/circleci/src/components/BuildWithStepsPage/BuildWithStepsPage.tsx @@ -13,7 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { FC, useEffect } from 'react'; + +import React, { useEffect } from 'react'; import { useParams } from 'react-router-dom'; import { InfoCard, Progress, Link } from '@backstage/core'; import { BuildWithSteps, BuildStepAction } from '../../api'; @@ -31,7 +32,8 @@ import LaunchIcon from '@material-ui/icons/Launch'; import { useBuildWithSteps } from '../../state/useBuildWithSteps'; const IconLink = (IconButton as any) as typeof MaterialLink; -const BuildName: FC<{ build?: BuildWithSteps }> = ({ build }) => ( + +const BuildName = ({ build }: { build?: BuildWithSteps }) => ( #{build?.build_num} - {build?.subject} @@ -39,6 +41,7 @@ const BuildName: FC<{ build?: BuildWithSteps }> = ({ build }) => ( ); + const useStyles = makeStyles(theme => ({ neutral: {}, failed: { @@ -96,7 +99,7 @@ const pickClassName = ( return classes.neutral; }; -const BuildsList: FC<{ build?: BuildWithSteps }> = ({ build }) => ( +const BuildsList = ({ build }: { build?: BuildWithSteps }) => ( {build && build.steps && @@ -108,8 +111,11 @@ const BuildsList: FC<{ build?: BuildWithSteps }> = ({ build }) => ( ); -const ActionsList: FC<{ actions: BuildStepAction[]; name: string }> = ({ +const ActionsList = ({ actions, +}: { + actions: BuildStepAction[]; + name: string; }) => { const classes = useStyles(); return ( diff --git a/plugins/circleci/src/components/BuildsPage/lib/Builds/Builds.tsx b/plugins/circleci/src/components/BuildsPage/lib/Builds/Builds.tsx index bed96bb24c..685e61a5af 100644 --- a/plugins/circleci/src/components/BuildsPage/lib/Builds/Builds.tsx +++ b/plugins/circleci/src/components/BuildsPage/lib/Builds/Builds.tsx @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { FC } from 'react'; +import React from 'react'; import { CITable } from '../CITable'; import { useBuilds } from '../../../../state'; -export const Builds: FC<{}> = () => { +export const Builds = () => { const [ { total, loading, value, projectName, page, pageSize }, { setPage, retry, setPageSize }, diff --git a/plugins/circleci/src/components/BuildsPage/lib/CITable/CITable.tsx b/plugins/circleci/src/components/BuildsPage/lib/CITable/CITable.tsx index 8e693429c2..e5e8b951be 100644 --- a/plugins/circleci/src/components/BuildsPage/lib/CITable/CITable.tsx +++ b/plugins/circleci/src/components/BuildsPage/lib/CITable/CITable.tsx @@ -13,10 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { FC } from 'react'; -import { Link, Typography, Box, IconButton } from '@material-ui/core'; + +import React from 'react'; +import { + Avatar, + Link, + Typography, + Box, + IconButton, + makeStyles, +} from '@material-ui/core'; import RetryIcon from '@material-ui/icons/Replay'; import GitHubIcon from '@material-ui/icons/GitHub'; +import LaunchIcon from '@material-ui/icons/Launch'; import { Link as RouterLink, generatePath } from 'react-router-dom'; import { StatusError, @@ -27,17 +36,22 @@ import { Table, TableColumn, } from '@backstage/core'; +import { durationHumanized, relativeTimeTo } from '../../../../util'; import { circleCIBuildRouteRef } from '../../../../route-refs'; export type CITableBuildInfo = { id: string; buildName: string; buildUrl?: string; + startTime?: string; + stopTime?: string; source: { branchName: string; commit: { hash: string; + shortHash: string; url: string; + committerName?: string; }; }; status: string; @@ -48,6 +62,18 @@ export type CITableBuildInfo = { failed: number; testUrl: string; // fixme better name }; + workflow: { + id: string; + url: string; + name?: string; + jobName?: string; + }; + user: { + isUser: boolean; + login: string; + name?: string; + avatarUrl?: string; + }; onRestartClick: () => void; }; @@ -69,6 +95,43 @@ const getStatusComponent = (status: string | undefined = '') => { } }; +const useStyles = makeStyles(theme => ({ + root: { + display: 'flex', + '& > *': { + margin: theme.spacing(1), + verticalAlign: 'center', + }, + }, + small: { + width: theme.spacing(3), + height: theme.spacing(3), + }, +})); + +const SourceInfo = ({ build }: { build: CITableBuildInfo }) => { + const classes = useStyles(); + const { user, source } = build; + + return ( + + + + {source?.branchName} + + {source?.commit?.url !== undefined ? ( + + {source?.commit.shortHash} + + ) : ( + source?.commit.shortHash + )} + + + + ); +}; + const generatedColumns: TableColumn[] = [ { title: 'ID', @@ -80,26 +143,43 @@ const generatedColumns: TableColumn[] = [ title: 'Build', field: 'buildName', highlight: true, + width: '20%', render: (row: Partial) => ( - {row.buildName} + {row.buildName ? row.buildName : row?.workflow?.name} + + ), + }, + { + title: 'Job', + field: 'buildName', + highlight: true, + render: (row: Partial) => ( + + + + + {row?.workflow?.jobName} + ), }, { title: 'Source', + field: 'source.commit.hash', + highlight: true, render: (row: Partial) => ( - <> -

{row.source?.branchName}

-

{row.source?.commit.hash}

- + ), }, { title: 'Status', + field: 'status', render: (row: Partial) => ( {getStatusComponent(row.status)} @@ -108,14 +188,32 @@ const generatedColumns: TableColumn[] = [ ), }, + { + title: 'Time', + field: 'startTime', + render: (row: Partial) => ( + <> + + run {relativeTimeTo(row?.startTime)} + + + took {durationHumanized(row?.startTime, row?.stopTime)} + + + ), + }, + { + title: 'Workflow', + field: 'workflow.name', + }, { title: 'Actions', + width: '10%', render: (row: Partial) => ( ), - width: '10%', }, ]; @@ -130,7 +228,8 @@ type Props = { pageSize: number; onChangePageSize: (pageSize: number) => void; }; -export const CITable: FC = ({ + +export const CITable = ({ projectName, loading, pageSize, @@ -140,11 +239,16 @@ export const CITable: FC = ({ onChangePage, onChangePageSize, total, -}) => { +}: Props) => { return ( { if (!status) return ''; @@ -41,6 +43,39 @@ const makeReadableStatus = (status: string | undefined) => { } as Record)[status]; }; +const mapWorkflowDetails = (buildData: BuildSummary) => { + // Workflows should be an object: fixed in https://github.com/worldturtlemedia/circleci-api/pull/787 + const { workflows } = (buildData as any) ?? {}; + + return { + id: workflows?.workflow_id, + url: `${buildData.build_url}/workflows/${workflows?.workflow_id}`, + jobName: workflows?.job_name, + name: workflows?.workflow_name, + }; +}; + +const mapSourceDetails = (buildData: BuildSummary) => { + const commitDetails = getOr({}, 'all_commit_details[0]', buildData); + + return { + branchName: String(buildData.branch), + commit: { + hash: String(buildData.vcs_revision), + shortHash: String(buildData.vcs_revision).substr(0, 7), + committerName: buildData.committer_name, + url: commitDetails.commit_url, + }, + }; +}; + +const mapUser = (buildData: BuildSummary) => ({ + isUser: buildData?.user?.is_user || false, + login: buildData?.user?.login || 'none', + name: (buildData?.user as any)?.name, + avatarUrl: (buildData?.user as any)?.avatar_url, +}); + export const transform = ( buildsData: BuildSummary[], restartBuild: { (buildId: number): Promise }, @@ -52,16 +87,14 @@ export const transform = ( ? buildData.subject + (buildData.retry_of ? ` (retry of #${buildData.retry_of})` : '') : '', + startTime: buildData.start_time, + stopTime: buildData.stop_time, onRestartClick: () => typeof buildData.build_num !== 'undefined' && restartBuild(buildData.build_num), - source: { - branchName: String(buildData.branch), - commit: { - hash: String(buildData.vcs_revision), - url: 'todo', - }, - }, + source: mapSourceDetails(buildData), + workflow: mapWorkflowDetails(buildData), + user: mapUser(buildData), status: makeReadableStatus(buildData.status), buildUrl: buildData.build_url, }; @@ -79,6 +112,7 @@ export const useProjectSlugFromEntity = () => { export function mapVcsType(vcs: string): GitType { switch (vcs) { + case 'gh': case 'github': return GitType.GITHUB; default: @@ -93,7 +127,7 @@ export function useBuilds() { const [total, setTotal] = useState(0); const [page, setPage] = useState(0); - const [pageSize, setPageSize] = useState(5); + const [pageSize, setPageSize] = useState(10); const getBuilds = useCallback( async ({ limit, offset }: { limit: number; offset: number }) => { diff --git a/plugins/circleci/src/util/index.ts b/plugins/circleci/src/util/index.ts new file mode 100644 index 0000000000..55bb001ae4 --- /dev/null +++ b/plugins/circleci/src/util/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 './time'; diff --git a/plugins/circleci/src/util/time.test.ts b/plugins/circleci/src/util/time.test.ts new file mode 100644 index 0000000000..dae029c8d2 --- /dev/null +++ b/plugins/circleci/src/util/time.test.ts @@ -0,0 +1,31 @@ +/* + * 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 { durationHumanized, relativeTimeTo } from './time'; + +describe('times utils', () => { + describe('toRelativeTime', () => { + it('should give a relative time of x from today', () => { + expect(relativeTimeTo('2020-01-01')).toEqual(expect.any(String)); + }); + }); + + describe('durationHumanized', () => { + it('should give a humanized duration', () => { + expect(durationHumanized('2020-11-01', '2020-11-03')).toBe('2 days'); + }); + }); +}); diff --git a/plugins/circleci/src/util/time.ts b/plugins/circleci/src/util/time.ts new file mode 100644 index 0000000000..8756333a87 --- /dev/null +++ b/plugins/circleci/src/util/time.ts @@ -0,0 +1,35 @@ +/* + * 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 dayjs from 'dayjs'; +import durationPlugin from 'dayjs/plugin/duration'; +import relativeTimePlugin from 'dayjs/plugin/relativeTime'; + +dayjs.extend(durationPlugin); +dayjs.extend(relativeTimePlugin); + +type DateTimeObject = Date | string | number | undefined; + +export function relativeTimeTo(time: DateTimeObject, withoutSuffix = false) { + return dayjs().to(dayjs(time), withoutSuffix); +} + +export function durationHumanized( + startTime: DateTimeObject, + endTime: DateTimeObject, +) { + return dayjs.duration(dayjs(startTime).diff(dayjs(endTime))).humanize(); +} From cb16f9ebfc61caaa65d5c3d3f3fdfe81977c1b97 Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Mon, 23 Nov 2020 19:34:15 +0100 Subject: [PATCH 190/430] Code review fixes --- packages/app/src/App.tsx | 2 +- .../src/components/ComponentConfigDisplay.tsx | 32 ++++++++++++++++--- .../src/components/ImportFinished.tsx | 7 +++- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index c02d83841f..28aebeeb79 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -69,7 +69,7 @@ const AppRoutes = () => ( } /> void; + nextStep: (options?: { reset: boolean }) => void; configFile: ConfigSpec; savePRLink: (PRLink: string) => void; catalogRouteRef: RouteRef; @@ -74,6 +74,7 @@ const ComponentConfigDisplay = ({ savePRLink, catalogRouteRef, }: Props) => { + const [errorOccured, setErrorOccured] = useState(false); const [submitting, setSubmitting] = useState(false); const errorApi = useApi(errorApiRef); const { submitPrToRepo, addLocation } = useGithubRepos(); @@ -91,6 +92,7 @@ const ComponentConfigDisplay = ({ nextStep(); } } catch (e) { + setErrorOccured(true); setSubmitting(false); errorApi.post(e); } @@ -102,13 +104,25 @@ const ComponentConfigDisplay = ({ Following config object will be submitted in a pull request to the repository{' '} - {configFile.location} and - added as a new location to the backend + + {configFile.location} + {' '} + and added as a new location to the backend ) : ( Following config object will be added as a new location to the backend{' '} - {configFile.location} + + {configFile.location} + )} @@ -164,6 +178,16 @@ const ComponentConfigDisplay = ({ > Next + {errorOccured ? ( + + ) : null} diff --git a/plugins/catalog-import/src/components/ImportFinished.tsx b/plugins/catalog-import/src/components/ImportFinished.tsx index 9a06de234b..caba678999 100644 --- a/plugins/catalog-import/src/components/ImportFinished.tsx +++ b/plugins/catalog-import/src/components/ImportFinished.tsx @@ -36,7 +36,12 @@ export const ImportFinished = ({ nextStep, PRLink, type }: Props) => { {type === 'repo' ? ( - + View pull request on GitHub ) : null} From 05b916761d036f1366abf084f1fb7f4d1782b491 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Mon, 23 Nov 2020 21:05:37 +0100 Subject: [PATCH 191/430] fix(kubernetes-backend): Fix Typescript error stopping backend to start --- .../src/service/getKubernetesObjectsForServiceHandler.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/kubernetes-backend/src/service/getKubernetesObjectsForServiceHandler.ts b/plugins/kubernetes-backend/src/service/getKubernetesObjectsForServiceHandler.ts index 1b0cf4f783..82220f5d6c 100644 --- a/plugins/kubernetes-backend/src/service/getKubernetesObjectsForServiceHandler.ts +++ b/plugins/kubernetes-backend/src/service/getKubernetesObjectsForServiceHandler.ts @@ -97,12 +97,12 @@ export const handleGetKubernetesObjectsForService: GetKubernetesObjectsForServic return Promise.all( clusterDetailsDecoratedForAuth.map(clusterDetails => { return fetcher - .fetchObjectsForService({ + .fetchObjectsForService({ serviceId, clusterDetails, objectTypesToFetch, labelSelector, - }) + } as ObjectFetchParams) .then(result => { return { cluster: { From ef5810f51358fb8752f1917af5480746f0c4bbbc Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Mon, 23 Nov 2020 21:40:12 +0100 Subject: [PATCH 192/430] eslint: Enforce consistent TS type assertions https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/consistent-type-assertions.md --- packages/cli/config/eslint.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/cli/config/eslint.js b/packages/cli/config/eslint.js index 9b1880c1db..e2a807e42e 100644 --- a/packages/cli/config/eslint.js +++ b/packages/cli/config/eslint.js @@ -58,6 +58,7 @@ module.exports = { ], 'no-unused-expressions': 'off', '@typescript-eslint/no-unused-expressions': 'error', + '@typescript-eslint/consistent-type-assertions': 'error', '@typescript-eslint/no-unused-vars': [ 'warn', { From faf311c261be51c547469432c6551fa926741d08 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Mon, 23 Nov 2020 21:57:34 +0100 Subject: [PATCH 193/430] backstage-cli: Add changeset about new lint rule --- .changeset/new-ladybugs-nail.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/new-ladybugs-nail.md diff --git a/.changeset/new-ladybugs-nail.md b/.changeset/new-ladybugs-nail.md new file mode 100644 index 0000000000..5351b6dff3 --- /dev/null +++ b/.changeset/new-ladybugs-nail.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +New lint rule to disallow assertions and promote `as` assertions. - @typescript-eslint/consistent-type-assertions From 8072bc85c35a3d46b39f1cb85cb3ad473d31cfc4 Mon Sep 17 00:00:00 2001 From: Brenda Sukh Date: Mon, 23 Nov 2020 18:45:02 -0500 Subject: [PATCH 194/430] Update daily cost data to return groupedCosts --- plugins/cost-insights/src/client.ts | 6 ++++-- plugins/cost-insights/src/types/Cost.ts | 5 +++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/plugins/cost-insights/src/client.ts b/plugins/cost-insights/src/client.ts index 2180385424..c78a3dc2dc 100644 --- a/plugins/cost-insights/src/client.ts +++ b/plugins/cost-insights/src/client.ts @@ -35,7 +35,7 @@ import { UnlabeledDataflowAlert, } from '../src/utils/alerts'; import { inclusiveStartDateOf } from '../src/utils/duration'; -import { trendlineOf, changeOf } from './utils/mockData'; +import { trendlineOf, changeOf, getGroupedProducts } from './utils/mockData'; type IntervalFields = { duration: Duration; @@ -56,7 +56,7 @@ function parseIntervals(intervals: string): IntervalFields { }; } -function aggregationFor( +export function aggregationFor( intervals: string, baseline: number, ): DateAggregation[] { @@ -140,6 +140,7 @@ export class ExampleCostInsightsClient implements CostInsightsApi { aggregation: aggregation, change: changeOf(aggregation), trendline: trendlineOf(aggregation), + groupedCosts: getGroupedProducts(intervals), }, ); @@ -155,6 +156,7 @@ export class ExampleCostInsightsClient implements CostInsightsApi { aggregation: aggregation, change: changeOf(aggregation), trendline: trendlineOf(aggregation), + groupedCosts: getGroupedProducts(intervals), }, ); diff --git a/plugins/cost-insights/src/types/Cost.ts b/plugins/cost-insights/src/types/Cost.ts index 84a8bfae51..dd28bba6b0 100644 --- a/plugins/cost-insights/src/types/Cost.ts +++ b/plugins/cost-insights/src/types/Cost.ts @@ -21,6 +21,7 @@ import { Trendline } from './Trendline'; export interface Cost { id: string; aggregation: DateAggregation[]; - change: ChangeStatistic; - trendline: Trendline; + change?: ChangeStatistic; + trendline?: Trendline; + groupedCosts?: Cost[]; } From f1272644191124975c9e00c0b5e25d701ceaed6d Mon Sep 17 00:00:00 2001 From: Brenda Sukh Date: Mon, 23 Nov 2020 18:45:40 -0500 Subject: [PATCH 195/430] Add aggregation sum util --- plugins/cost-insights/src/utils/change.ts | 9 +++++---- plugins/cost-insights/src/utils/sum.ts | 20 ++++++++++++++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) create mode 100644 plugins/cost-insights/src/utils/sum.ts diff --git a/plugins/cost-insights/src/utils/change.ts b/plugins/cost-insights/src/utils/change.ts index 1718462948..fc50439aad 100644 --- a/plugins/cost-insights/src/utils/change.ts +++ b/plugins/cost-insights/src/utils/change.ts @@ -23,6 +23,7 @@ import { MetricData, Duration, DEFAULT_DATE_FORMAT, + DateAggregation, } from '../types'; import dayjs, { OpUnitType } from 'dayjs'; import duration from 'dayjs/plugin/duration'; @@ -54,9 +55,9 @@ export function getComparedChange( duration: Duration, lastCompleteBillingDate: string, // YYYY-MM-DD, ): ChangeStatistic { - const ratio = dailyCost.change.ratio - metricData.change.ratio; + const ratio = dailyCost.change!.ratio - metricData.change.ratio; const previousPeriodTotal = getPreviousPeriodTotalCost( - dailyCost, + dailyCost.aggregation, duration, lastCompleteBillingDate, ); @@ -67,7 +68,7 @@ export function getComparedChange( } export function getPreviousPeriodTotalCost( - dailyCost: Cost, + aggregation: DateAggregation[], duration: Duration, inclusiveEndDate: string, ): number { @@ -83,7 +84,7 @@ export function getPreviousPeriodTotalCost( const nextPeriodStart = dayjs(startDate).add(amount, type); // Add up costs that incurred before the start of the next period. - return dailyCost.aggregation.reduce((acc, costByDate) => { + return aggregation.reduce((acc, costByDate) => { return dayjs(costByDate.date).isBefore(nextPeriodStart) ? acc + costByDate.amount : acc; diff --git a/plugins/cost-insights/src/utils/sum.ts b/plugins/cost-insights/src/utils/sum.ts new file mode 100644 index 0000000000..6fc181fea5 --- /dev/null +++ b/plugins/cost-insights/src/utils/sum.ts @@ -0,0 +1,20 @@ +/* + * 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 { DateAggregation } from '../types'; + +export const aggregationSum = (aggregation: DateAggregation[]) => + aggregation.reduce((total, curAgg) => total + curAgg.amount, 0); From b9705638e969278c5bb9d456d177288b9f7c20cc Mon Sep 17 00:00:00 2001 From: Brenda Sukh Date: Mon, 23 Nov 2020 18:46:20 -0500 Subject: [PATCH 196/430] Add top panel breakdown view --- .../CostOverviewByProduct.tsx | 211 ++++++++++++++++++ .../CostOverviewCard/CostOverviewChart.tsx | 2 +- .../CostOverviewCard/CostOverviewHeader.tsx | 4 +- .../cost-insights/src/utils/change.test.ts | 2 +- 4 files changed, 216 insertions(+), 3 deletions(-) create mode 100644 plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProduct.tsx diff --git a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProduct.tsx b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProduct.tsx new file mode 100644 index 0000000000..1496fcdaf8 --- /dev/null +++ b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProduct.tsx @@ -0,0 +1,211 @@ +/* + * 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 dayjs from 'dayjs'; +import utc from 'dayjs/plugin/utc'; +import { useTheme, Box } from '@material-ui/core'; +import { + AreaChart, + ContentRenderer, + TooltipProps, + XAxis, + YAxis, + Tooltip as RechartsTooltip, + Area, + ResponsiveContainer, + CartesianGrid, +} from 'recharts'; +import { Cost, DEFAULT_DATE_FORMAT, CostInsightsTheme } from '../../types'; +import { + BarChartTooltip as Tooltip, + BarChartTooltipItem as TooltipItem, +} from '../BarChart'; +import { + overviewGraphTickFormatter, + formatGraphValue, + isInvalid, +} from '../../utils/graphs'; +import { + useCostOverviewStyles as useStyles, + DataVizColors, +} from '../../utils/styles'; +import { useFilters, useLastCompleteBillingDate } from '../../hooks'; +import { mapFiltersToProps } from './selector'; +import { getPreviousPeriodTotalCost } from '../../utils/change'; +import { LegendItem } from '../LegendItem'; +import { currencyFormatter } from '../../utils/formatters'; +import { aggregationSum } from '../../utils/sum'; + +dayjs.extend(utc); + +export type CostOverviewByProductProps = { + dailyCostData: Cost; +}; + +const LOW_COST_THRESHOLD = 0.01; + +export const CostOverviewByProduct = ({ + dailyCostData, +}: CostOverviewByProductProps) => { + const theme = useTheme(); + const styles = useStyles(theme); + const lastCompleteBillingDate = useLastCompleteBillingDate(); + const { duration } = useFilters(mapFiltersToProps); + const groupedCosts = dailyCostData.groupedCosts; + + if (!groupedCosts) { + return null; + } + + const flattenedAggregation = groupedCosts + .map(cost => cost.aggregation) + .flat(); + const totalCost = flattenedAggregation.reduce( + (acc, agg) => acc + agg.amount, + 0, + ); + + const productsByDate = groupedCosts.reduce((prodByDate, group) => { + const product = group.id; + group.aggregation.forEach(curAggregation => { + const productCostsForDate = prodByDate[curAggregation.date] || {}; + // Group products with less than 10% of the total cost into "Other" category + if (aggregationSum(group.aggregation) < totalCost * LOW_COST_THRESHOLD) { + prodByDate[curAggregation.date] = { + ...productCostsForDate, + Other: + (prodByDate[curAggregation.date].Other || 0) + + curAggregation.amount, + }; + } else { + prodByDate[curAggregation.date] = { + ...productCostsForDate, + [product]: curAggregation.amount, + }; + } + }); + + return prodByDate; + }, {} as Record>); + + const chartData: any[] = Object.keys(productsByDate).map(date => { + return { + ...productsByDate[date], + date: Date.parse(date), + }; + }); + + const tooltipRenderer: ContentRenderer = ({ + label, + payload = [], + }) => { + if (isInvalid({ label, payload })) return null; + + const title = dayjs(label).utc().format(DEFAULT_DATE_FORMAT); + const items = payload.map(p => ({ + label: p.dataKey as string, + value: formatGraphValue(p.value as number), + fill: p.fill!, + })); + + return ( + + {items.reverse().map((item, index) => ( + + ))} + + ); + }; + + const previousPeriodTotal = getPreviousPeriodTotalCost( + flattenedAggregation, + duration, + lastCompleteBillingDate, + ); + const currentPeriodTotal = totalCost - previousPeriodTotal; + + const renderAreas = () => { + const highCostGroups = groupedCosts.filter( + group => + aggregationSum(group.aggregation) >= totalCost * LOW_COST_THRESHOLD, + ); + const sortedCostGroups = highCostGroups + .sort( + (a, b) => aggregationSum(a.aggregation) - aggregationSum(b.aggregation), + ) + .map(group => group.id); + // Keep 'Other' category at the bottom of the stack + return ['Other', ...sortedCostGroups].map((group, i) => ( + + )); + }; + + return ( + + + + + {currencyFormatter.format(previousPeriodTotal)} + + + + + {currencyFormatter.format(currentPeriodTotal)} + + + + + + + + 0, 'dataMax']} + tick={{ fill: styles.axis.fill }} + tickFormatter={formatGraphValue} + width={styles.yAxis.width} + /> + {renderAreas()} + + + + + + ); +}; diff --git a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewChart.tsx b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewChart.tsx index 1910013581..4311081f20 100644 --- a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewChart.tsx +++ b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewChart.tsx @@ -93,7 +93,7 @@ export const CostOverviewChart = ({ .sort(aggregationSort) .map(entry => ({ date: Date.parse(entry.date), - trend: trendFrom(data.dailyCost.data.trendline, Date.parse(entry.date)), + trend: trendFrom(data.dailyCost.data.trendline!, Date.parse(entry.date)), dailyCost: entry.amount, ...(metric && data.metric.data ? { [data.metric.dataKey]: metricsByDate[`${entry.date}`] } diff --git a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewHeader.tsx b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewHeader.tsx index e46b0c3a78..b23dd2041d 100644 --- a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewHeader.tsx +++ b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewHeader.tsx @@ -27,7 +27,9 @@ export const CostOverviewHeader = ({ children, }: PropsWithChildren) => ( { const exclusiveEndDate = '2020-09-30'; expect( getPreviousPeriodTotalCost( - mockGroupDailyCost, + mockGroupDailyCost.aggregation, Duration.P1M, exclusiveEndDate, ), From 2ca1cc32aa7158fb17e1e113a391968fd3ebe3e3 Mon Sep 17 00:00:00 2001 From: Brenda Sukh Date: Mon, 23 Nov 2020 18:47:32 -0500 Subject: [PATCH 197/430] Add top panel tabs --- .../CostOverviewCard/CostOverviewCard.tsx | 128 ++++++++++++------ plugins/cost-insights/src/utils/styles.ts | 26 ++++ 2 files changed, 115 insertions(+), 39 deletions(-) diff --git a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx index 81dc118ea9..742b89af7a 100644 --- a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx +++ b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx @@ -14,10 +14,19 @@ * limitations under the License. */ -import React from 'react'; -import { Box, Card, CardContent, Divider, useTheme } from '@material-ui/core'; +import React, { useState } from 'react'; +import { + Box, + Card, + CardContent, + Divider, + useTheme, + Tab, + Tabs, +} from '@material-ui/core'; import { CostGrowth } from '../CostGrowth'; import { CostOverviewChart } from './CostOverviewChart'; +import { CostOverviewByProduct } from './CostOverviewByProduct'; import { CostOverviewHeader } from './CostOverviewHeader'; import { LegendItem } from '../LegendItem'; import { MetricSelect } from '../MetricSelect'; @@ -34,6 +43,7 @@ import { formatPercent } from '../../utils/formatters'; import { findAlways } from '../../utils/assert'; import { getComparedChange } from '../../utils/change'; import { Cost, CostInsightsTheme, MetricData } from '../../types'; +import { useOverviewTabsStyles } from '../../utils/styles'; export type CostOverviewCardProps = { dailyCostData: Cost; @@ -47,6 +57,8 @@ export const CostOverviewCard = ({ const theme = useTheme(); const config = useConfig(); const lastCompleteBillingDate = useLastCompleteBillingDate(); + const [tabIndex, setTabIndex] = useState(0); + const { ScrollAnchor } = useScroll(DefaultNavigation.CostOverviewCard); const { setDuration, setProject, setMetric, ...filters } = useFilters( mapFiltersToProps, @@ -63,53 +75,91 @@ export const CostOverviewCard = ({ lastCompleteBillingDate, ) : null; + const styles = useOverviewTabsStyles(theme); + + const tabs = [ + { id: 'overview', label: 'OVERVIEW' }, + { id: 'breakdown', label: 'BREAKDOWN BY PRODUCT' }, + ]; + + const OverviewTabs = () => { + return ( + <> + setTabIndex(index)} + value={tabIndex} + > + {tabs.map((tab, index) => ( + + ))} + + + ); + }; + + const OverviewLegend = () => { + return ( + + + + {formatPercent(dailyCostData.change!.ratio)} + + + {metric && metricData && comparedChange && ( + <> + + + {formatPercent(metricData.change.ratio)} + + + + + + + )} + + ); + }; return ( - + {dailyCostData.groupedCosts && } + - - - - - {formatPercent(dailyCostData.change.ratio)} - - - {metric && metricData && comparedChange && ( - <> - - - {formatPercent(metricData.change.ratio)} - - - - - - - )} - - + + {tabIndex === 0 ? ( + <> + + + + ) : ( + + )} - {config.metrics.length && ( + {config.metrics.length && tabIndex === 0 && ( ({ }, }); +export const useOverviewTabsStyles = makeStyles( + (theme: CostInsightsTheme) => ({ + default: { + padding: theme.spacing(2, 2), + fontWeight: 'bold', + color: theme.palette.text.secondary, + }, + selected: { + color: theme.palette.text.primary, + }, + }), +); + export const useBarChartStyles = (theme: CostInsightsTheme) => ({ axis: { fill: theme.palette.text.primary, From bed5c128da786022c83dec58e8af0ba347f23870 Mon Sep 17 00:00:00 2001 From: Brenda Sukh Date: Mon, 23 Nov 2020 18:48:10 -0500 Subject: [PATCH 198/430] Add mock data for grouped Costs --- plugins/cost-insights/src/utils/mockData.ts | 36 +++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/plugins/cost-insights/src/utils/mockData.ts b/plugins/cost-insights/src/utils/mockData.ts index 708ce733dd..4432753c32 100644 --- a/plugins/cost-insights/src/utils/mockData.ts +++ b/plugins/cost-insights/src/utils/mockData.ts @@ -34,6 +34,7 @@ import { getDefaultState as getDefaultLoadingState, } from '../utils/loading'; import { findAlways } from '../utils/assert'; +import { aggregationFor } from '../client'; type mockAlertRenderer = (alert: T) => T; type mockEntityRenderer = (entity: T) => T; @@ -462,3 +463,38 @@ export const MockAggregatedDailyCosts: DateAggregation[] = [ amount: 5500, }, ]; + +export const getGroupedProducts = (intervals: string) => [ + { + id: 'Cloud Dataflow', + aggregation: aggregationFor(intervals, 1_000), + }, + { + id: 'Compute Engine', + aggregation: aggregationFor(intervals, 100), + }, + { + id: 'Cloud Storage', + aggregation: aggregationFor(intervals, 500), + }, + { + id: 'BigQuery', + aggregation: aggregationFor(intervals, 2_000), + }, + { + id: 'Cloud SQL', + aggregation: aggregationFor(intervals, 10), + }, + { + id: 'Cloud Spanner', + aggregation: aggregationFor(intervals, 50), + }, + { + id: 'Cloud Pub/Sub', + aggregation: aggregationFor(intervals, 30), + }, + { + id: 'Cloud Bigtable', + aggregation: aggregationFor(intervals, 250), + }, +]; From da82e823a4241dc08ae515d93c899b5cc2a46283 Mon Sep 17 00:00:00 2001 From: Brenda Sukh Date: Mon, 23 Nov 2020 19:11:04 -0500 Subject: [PATCH 199/430] Add comments on groupedCosts --- plugins/cost-insights/src/client.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/cost-insights/src/client.ts b/plugins/cost-insights/src/client.ts index c78a3dc2dc..96e9025f5d 100644 --- a/plugins/cost-insights/src/client.ts +++ b/plugins/cost-insights/src/client.ts @@ -140,6 +140,8 @@ export class ExampleCostInsightsClient implements CostInsightsApi { aggregation: aggregation, change: changeOf(aggregation), trendline: trendlineOf(aggregation), + // Optional field on Cost which needs to be supplied in order to see + // the product breakdown view in the top panel. groupedCosts: getGroupedProducts(intervals), }, ); @@ -156,6 +158,8 @@ export class ExampleCostInsightsClient implements CostInsightsApi { aggregation: aggregation, change: changeOf(aggregation), trendline: trendlineOf(aggregation), + // Optional field on Cost which needs to be supplied in order to see + // the product breakdown view in the top panel. groupedCosts: getGroupedProducts(intervals), }, ); From 5a8c24c7110782b02ba745bf9f37b92646fa0b37 Mon Sep 17 00:00:00 2001 From: Mateusz Lewtak <36006588+lewtakm@users.noreply.github.com> Date: Tue, 24 Nov 2020 10:44:08 +0100 Subject: [PATCH 200/430] Feat: Add Jira plugin to marketplace (#3405) * Feat: Add Jira plugin to marketplace * Update jira.yaml --- microsite/data/plugins/jira.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 microsite/data/plugins/jira.yaml diff --git a/microsite/data/plugins/jira.yaml b/microsite/data/plugins/jira.yaml new file mode 100644 index 0000000000..579243d11c --- /dev/null +++ b/microsite/data/plugins/jira.yaml @@ -0,0 +1,9 @@ +--- +title: Jira +author: roadie.io +authorUrl: https://roadie.io +category: Project Management +description: View Jira summary for your projects in Backstage. +documentation: https://roadie.io/backstage/plugins/jira +iconUrl: https://roadie.io/images/logos/jira.png +npmPackageName: '@roadiehq/backstage-plugin-jira' From 27733fdfc6844148e2706b49f6188d4f1349e2fa Mon Sep 17 00:00:00 2001 From: Andrew Thauer <6507159+andrewthauer@users.noreply.github.com> Date: Tue, 24 Nov 2020 04:49:05 -0500 Subject: [PATCH 201/430] Update .changeset/mighty-cycles-impress.md Co-authored-by: Ben Lambert --- .changeset/mighty-cycles-impress.md | 1 - 1 file changed, 1 deletion(-) diff --git a/.changeset/mighty-cycles-impress.md b/.changeset/mighty-cycles-impress.md index 33b2aa008c..56ec35098f 100644 --- a/.changeset/mighty-cycles-impress.md +++ b/.changeset/mighty-cycles-impress.md @@ -1,5 +1,4 @@ --- -'example-app': patch '@backstage/plugin-circleci': patch --- From fefb0e18e02d67adca5839d27bb855ff4aee2ed5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 24 Nov 2020 10:17:02 +0000 Subject: [PATCH 202/430] Version Packages --- .changeset/breezy-cobras-deny.md | 5 --- .changeset/chatty-radios-beam.md | 7 ---- .changeset/clever-moons-shake.md | 5 --- .changeset/cli-links.md | 5 --- .changeset/cost-insights-quiet-bikes-occur.md | 5 --- .../cost-insights-tiny-llamas-perform.md | 5 --- .changeset/curly-yaks-invite.md | 5 --- .changeset/dull-pans-sip.md | 6 --- .changeset/empty-kids-look.md | 5 --- .changeset/forty-buses-watch.md | 14 ------- .changeset/long-flowers-wink.md | 5 --- .changeset/mighty-cycles-impress.md | 5 --- .changeset/new-ladybugs-nail.md | 5 --- .changeset/orange-cherries-run.md | 5 --- .changeset/poor-mails-marry.md | 5 --- .changeset/small-worms-check.md | 6 --- .changeset/tame-rockets-explode.md | 5 --- .changeset/tidy-brooms-look.md | 5 --- packages/app/CHANGELOG.md | 37 ++++++++++++++++++ packages/app/package.json | 38 +++++++++---------- packages/backend-common/CHANGELOG.md | 7 ++++ packages/backend-common/package.json | 4 +- packages/backend/CHANGELOG.md | 21 ++++++++++ packages/backend/package.json | 20 +++++----- packages/catalog-client/CHANGELOG.md | 8 ++++ packages/catalog-client/package.json | 6 +-- packages/catalog-model/CHANGELOG.md | 18 +++++++++ packages/catalog-model/package.json | 4 +- packages/cli/CHANGELOG.md | 9 +++++ packages/cli/package.json | 6 +-- packages/core/CHANGELOG.md | 6 +++ packages/core/package.json | 4 +- packages/create-app/package.json | 32 ++++++++-------- plugins/api-docs/CHANGELOG.md | 11 ++++++ plugins/api-docs/package.json | 10 ++--- plugins/auth-backend/CHANGELOG.md | 12 ++++++ plugins/auth-backend/package.json | 10 ++--- plugins/catalog-backend/CHANGELOG.md | 22 +++++++++++ plugins/catalog-backend/package.json | 8 ++-- plugins/catalog-graphql/CHANGELOG.md | 11 ++++++ plugins/catalog-graphql/package.json | 8 ++-- plugins/catalog/CHANGELOG.md | 14 +++++++ plugins/catalog/package.json | 14 +++---- plugins/circleci/CHANGELOG.md | 12 ++++++ plugins/circleci/package.json | 10 ++--- plugins/cloudbuild/CHANGELOG.md | 11 ++++++ plugins/cloudbuild/package.json | 10 ++--- plugins/cost-insights/CHANGELOG.md | 9 +++++ plugins/cost-insights/package.json | 6 +-- plugins/github-actions/CHANGELOG.md | 11 ++++++ plugins/github-actions/package.json | 10 ++--- plugins/jenkins/CHANGELOG.md | 11 ++++++ plugins/jenkins/package.json | 10 ++--- plugins/kubernetes-backend/CHANGELOG.md | 15 ++++++++ plugins/kubernetes-backend/package.json | 8 ++-- plugins/kubernetes/CHANGELOG.md | 15 ++++++++ plugins/kubernetes/package.json | 10 ++--- plugins/lighthouse/CHANGELOG.md | 11 ++++++ plugins/lighthouse/package.json | 10 ++--- plugins/register-component/CHANGELOG.md | 12 ++++++ plugins/register-component/package.json | 10 ++--- plugins/rollbar/CHANGELOG.md | 11 ++++++ plugins/rollbar/package.json | 10 ++--- plugins/scaffolder-backend/CHANGELOG.md | 13 +++++++ plugins/scaffolder-backend/package.json | 8 ++-- plugins/scaffolder/CHANGELOG.md | 12 ++++++ plugins/scaffolder/package.json | 10 ++--- plugins/search/CHANGELOG.md | 13 +++++++ plugins/search/package.json | 10 ++--- plugins/sentry/CHANGELOG.md | 10 +++++ plugins/sentry/package.json | 8 ++-- plugins/sonarqube/CHANGELOG.md | 11 ++++++ plugins/sonarqube/package.json | 8 ++-- plugins/techdocs-backend/CHANGELOG.md | 11 ++++++ plugins/techdocs-backend/package.json | 8 ++-- plugins/techdocs/CHANGELOG.md | 11 ++++++ plugins/techdocs/package.json | 10 ++--- 77 files changed, 535 insertions(+), 263 deletions(-) delete mode 100644 .changeset/breezy-cobras-deny.md delete mode 100644 .changeset/chatty-radios-beam.md delete mode 100644 .changeset/clever-moons-shake.md delete mode 100644 .changeset/cli-links.md delete mode 100644 .changeset/cost-insights-quiet-bikes-occur.md delete mode 100644 .changeset/cost-insights-tiny-llamas-perform.md delete mode 100644 .changeset/curly-yaks-invite.md delete mode 100644 .changeset/dull-pans-sip.md delete mode 100644 .changeset/empty-kids-look.md delete mode 100644 .changeset/forty-buses-watch.md delete mode 100644 .changeset/long-flowers-wink.md delete mode 100644 .changeset/mighty-cycles-impress.md delete mode 100644 .changeset/new-ladybugs-nail.md delete mode 100644 .changeset/orange-cherries-run.md delete mode 100644 .changeset/poor-mails-marry.md delete mode 100644 .changeset/small-worms-check.md delete mode 100644 .changeset/tame-rockets-explode.md delete mode 100644 .changeset/tidy-brooms-look.md create mode 100644 plugins/search/CHANGELOG.md diff --git a/.changeset/breezy-cobras-deny.md b/.changeset/breezy-cobras-deny.md deleted file mode 100644 index 5f035c05ce..0000000000 --- a/.changeset/breezy-cobras-deny.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core': patch ---- - -Clear sidebar search field once a search is executed diff --git a/.changeset/chatty-radios-beam.md b/.changeset/chatty-radios-beam.md deleted file mode 100644 index 91ad76ee88..0000000000 --- a/.changeset/chatty-radios-beam.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/catalog-model': minor -'@backstage/plugin-kubernetes': minor -'@backstage/plugin-kubernetes-backend': minor ---- - -add kubernetes selector to component model diff --git a/.changeset/clever-moons-shake.md b/.changeset/clever-moons-shake.md deleted file mode 100644 index 0fb6c690c2..0000000000 --- a/.changeset/clever-moons-shake.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-common': patch ---- - -Added readTree support to AzureUrlReader diff --git a/.changeset/cli-links.md b/.changeset/cli-links.md deleted file mode 100644 index 0a9779de93..0000000000 --- a/.changeset/cli-links.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -The CLI now detects and transforms linked packages. You can link in external packages by adding them to both the `lerna.json` and `package.json` workspace paths. diff --git a/.changeset/cost-insights-quiet-bikes-occur.md b/.changeset/cost-insights-quiet-bikes-occur.md deleted file mode 100644 index aac283bbd4..0000000000 --- a/.changeset/cost-insights-quiet-bikes-occur.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-cost-insights': patch ---- - -fix product icon configuration diff --git a/.changeset/cost-insights-tiny-llamas-perform.md b/.changeset/cost-insights-tiny-llamas-perform.md deleted file mode 100644 index c81c28f8f8..0000000000 --- a/.changeset/cost-insights-tiny-llamas-perform.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-cost-insights': patch ---- - -truncate large percentages > 1000% diff --git a/.changeset/curly-yaks-invite.md b/.changeset/curly-yaks-invite.md deleted file mode 100644 index b9191a3895..0000000000 --- a/.changeset/curly-yaks-invite.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': patch ---- - -Add support for reading groups and users from the Microsoft Graph API. diff --git a/.changeset/dull-pans-sip.md b/.changeset/dull-pans-sip.md deleted file mode 100644 index 13616c1f8d..0000000000 --- a/.changeset/dull-pans-sip.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-scaffolder': patch -'@backstage/plugin-scaffolder-backend': patch ---- - -Move constructing the catalog-info.yaml URL for scaffolded components to the publishers diff --git a/.changeset/empty-kids-look.md b/.changeset/empty-kids-look.md deleted file mode 100644 index 680cd7f25c..0000000000 --- a/.changeset/empty-kids-look.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-register-component': patch ---- - -Remove catalog link on validate popup diff --git a/.changeset/forty-buses-watch.md b/.changeset/forty-buses-watch.md deleted file mode 100644 index 4074566fe7..0000000000 --- a/.changeset/forty-buses-watch.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -'@backstage/catalog-model': patch -'@backstage/plugin-catalog-backend': patch ---- - -Marked the `Group` entity fields `ancestors` and `descendants` for deprecation on Dec 6th, 2020. See https://github.com/backstage/backstage/issues/3049 for details. - -Code that consumes these fields should remove those usages as soon as possible. There is no current or planned replacement for these fields. - -The BuiltinKindsEntityProcessor has been updated to inject these fields as empty arrays if they are missing. Therefore, if you are on a catalog instance that uses the updated version of this code, you can start removing the fields from your source catalog-info.yaml data as well, without breaking validation. - -After Dec 6th, the fields will be removed from types and classes of the Backstage repository. At the first release after that, they will not be present in released packages either. - -If your catalog-info.yaml files still contain these fields after the deletion, they will still be valid and your ingestion will not break, but they won't be visible in the types for consuming code. diff --git a/.changeset/long-flowers-wink.md b/.changeset/long-flowers-wink.md deleted file mode 100644 index 7f9cf12315..0000000000 --- a/.changeset/long-flowers-wink.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-sonarqube': patch ---- - -Add configuration schema diff --git a/.changeset/mighty-cycles-impress.md b/.changeset/mighty-cycles-impress.md deleted file mode 100644 index 56ec35098f..0000000000 --- a/.changeset/mighty-cycles-impress.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-circleci': patch ---- - -Improved CircleCI builds table to show more information and relevant links diff --git a/.changeset/new-ladybugs-nail.md b/.changeset/new-ladybugs-nail.md deleted file mode 100644 index 5351b6dff3..0000000000 --- a/.changeset/new-ladybugs-nail.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -New lint rule to disallow assertions and promote `as` assertions. - @typescript-eslint/consistent-type-assertions diff --git a/.changeset/orange-cherries-run.md b/.changeset/orange-cherries-run.md deleted file mode 100644 index 3c06c27b47..0000000000 --- a/.changeset/orange-cherries-run.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Add experimental backend:bundle command diff --git a/.changeset/poor-mails-marry.md b/.changeset/poor-mails-marry.md deleted file mode 100644 index fdc7e6f1b8..0000000000 --- a/.changeset/poor-mails-marry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Add new `versions:check` and `versions:bump` commands to simplify version management and avoid conflicts diff --git a/.changeset/small-worms-check.md b/.changeset/small-worms-check.md deleted file mode 100644 index e39265f8f9..0000000000 --- a/.changeset/small-worms-check.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'example-app': patch -'@backstage/plugin-search': patch ---- - -Using the search field in the sidebar now navigates to the search result page. diff --git a/.changeset/tame-rockets-explode.md b/.changeset/tame-rockets-explode.md deleted file mode 100644 index 123472701e..0000000000 --- a/.changeset/tame-rockets-explode.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-common': patch ---- - -Make integration host and url configurations visible in the frontend diff --git a/.changeset/tidy-brooms-look.md b/.changeset/tidy-brooms-look.md deleted file mode 100644 index f2c2a9064e..0000000000 --- a/.changeset/tidy-brooms-look.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': patch ---- - -Fix React entity YAML filename to new standard diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index ab00924a26..f70cd198b4 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,42 @@ # example-app +## 0.2.3 + +### Patch Changes + +- 475fc0aaa: Using the search field in the sidebar now navigates to the search result page. +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [29a0ccab2] +- Updated dependencies [8e6728e25] +- Updated dependencies [c93a14b49] +- Updated dependencies [ef2831dde] +- Updated dependencies [2a71f4bab] +- Updated dependencies [1185919f3] +- Updated dependencies [a8de7f554] +- Updated dependencies [faf311c26] +- Updated dependencies [31d8b6979] +- Updated dependencies [991345969] +- Updated dependencies [475fc0aaa] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-kubernetes@0.3.0 + - @backstage/cli@0.3.1 + - @backstage/plugin-cost-insights@0.4.1 + - @backstage/plugin-scaffolder@0.3.1 + - @backstage/plugin-register-component@0.2.2 + - @backstage/plugin-circleci@0.2.2 + - @backstage/plugin-search@0.2.1 + - @backstage/plugin-api-docs@0.2.2 + - @backstage/plugin-catalog@0.2.3 + - @backstage/plugin-cloudbuild@0.2.2 + - @backstage/plugin-github-actions@0.2.2 + - @backstage/plugin-jenkins@0.3.1 + - @backstage/plugin-lighthouse@0.2.3 + - @backstage/plugin-rollbar@0.2.3 + - @backstage/plugin-sentry@0.2.3 + - @backstage/plugin-techdocs@0.2.3 + ## 0.2.2 ### Patch Changes diff --git a/packages/app/package.json b/packages/app/package.json index 042fd47188..9e54005cd3 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,33 +1,33 @@ { "name": "example-app", - "version": "0.2.2", + "version": "0.2.3", "private": true, "bundled": true, "dependencies": { - "@backstage/catalog-model": "^0.2.0", - "@backstage/cli": "^0.3.0", - "@backstage/core": "^0.3.1", - "@backstage/plugin-api-docs": "^0.2.1", - "@backstage/plugin-catalog": "^0.2.2", - "@backstage/plugin-circleci": "^0.2.1", - "@backstage/plugin-cloudbuild": "^0.2.1", - "@backstage/plugin-cost-insights": "^0.4.0", + "@backstage/catalog-model": "^0.3.0", + "@backstage/cli": "^0.3.1", + "@backstage/core": "^0.3.2", + "@backstage/plugin-api-docs": "^0.2.2", + "@backstage/plugin-catalog": "^0.2.3", + "@backstage/plugin-circleci": "^0.2.2", + "@backstage/plugin-cloudbuild": "^0.2.2", + "@backstage/plugin-cost-insights": "^0.4.1", "@backstage/plugin-explore": "^0.2.1", "@backstage/plugin-gcp-projects": "^0.2.1", - "@backstage/plugin-github-actions": "^0.2.1", + "@backstage/plugin-github-actions": "^0.2.2", "@backstage/plugin-gitops-profiles": "^0.2.1", "@backstage/plugin-graphiql": "^0.2.1", - "@backstage/plugin-jenkins": "^0.3.0", - "@backstage/plugin-kubernetes": "^0.2.1", - "@backstage/plugin-lighthouse": "^0.2.2", + "@backstage/plugin-jenkins": "^0.3.1", + "@backstage/plugin-kubernetes": "^0.3.0", + "@backstage/plugin-lighthouse": "^0.2.3", "@backstage/plugin-newrelic": "^0.2.1", - "@backstage/plugin-register-component": "^0.2.1", - "@backstage/plugin-rollbar": "^0.2.2", - "@backstage/plugin-scaffolder": "^0.3.0", - "@backstage/plugin-sentry": "^0.2.2", - "@backstage/plugin-search": "^0.2.0", + "@backstage/plugin-register-component": "^0.2.2", + "@backstage/plugin-rollbar": "^0.2.3", + "@backstage/plugin-scaffolder": "^0.3.1", + "@backstage/plugin-sentry": "^0.2.3", + "@backstage/plugin-search": "^0.2.1", "@backstage/plugin-tech-radar": "^0.3.0", - "@backstage/plugin-techdocs": "^0.2.2", + "@backstage/plugin-techdocs": "^0.2.3", "@backstage/plugin-user-settings": "^0.2.2", "@backstage/plugin-welcome": "^0.2.1", "@backstage/test-utils": "^0.1.3", diff --git a/packages/backend-common/CHANGELOG.md b/packages/backend-common/CHANGELOG.md index 4822032c7f..793a554905 100644 --- a/packages/backend-common/CHANGELOG.md +++ b/packages/backend-common/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/backend-common +## 0.3.1 + +### Patch Changes + +- bff3305aa: Added readTree support to AzureUrlReader +- b47dce06f: Make integration host and url configurations visible in the frontend + ## 0.3.0 ### Minor Changes diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index b22e4638f5..731e2679cb 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-common", "description": "Common functionality library for Backstage backends", - "version": "0.3.0", + "version": "0.3.1", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -67,7 +67,7 @@ } }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/test-utils": "^0.1.3", "@types/archiver": "^3.1.1", "@types/compression": "^1.7.0", diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index a8bc1fef7d..bc2a08e983 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,26 @@ # example-backend +## 0.2.3 + +### Patch Changes + +- Updated dependencies [1166fcc36] +- Updated dependencies [bff3305aa] +- Updated dependencies [0c2121240] +- Updated dependencies [ef2831dde] +- Updated dependencies [1185919f3] +- Updated dependencies [475fc0aaa] +- Updated dependencies [b47dce06f] +- Updated dependencies [5a1d8dca3] + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-kubernetes-backend@0.2.0 + - @backstage/backend-common@0.3.1 + - @backstage/plugin-catalog-backend@0.2.2 + - @backstage/plugin-scaffolder-backend@0.3.2 + - example-app@0.2.3 + - @backstage/plugin-auth-backend@0.2.3 + - @backstage/plugin-techdocs-backend@0.2.2 + ## 0.2.2 ### Patch Changes diff --git a/packages/backend/package.json b/packages/backend/package.json index 014a3f78d5..5d3678a284 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.2.2", + "version": "0.2.3", "main": "dist/index.cjs.js", "types": "src/index.ts", "private": true, @@ -18,24 +18,24 @@ "migrate:create": "knex migrate:make -x ts" }, "dependencies": { - "@backstage/backend-common": "^0.3.0", - "@backstage/catalog-model": "^0.2.0", + "@backstage/backend-common": "^0.3.1", + "@backstage/catalog-model": "^0.3.0", "@backstage/config": "^0.1.1", "@backstage/plugin-app-backend": "^0.3.0", - "@backstage/plugin-auth-backend": "^0.2.2", - "@backstage/plugin-catalog-backend": "^0.2.1", + "@backstage/plugin-auth-backend": "^0.2.3", + "@backstage/plugin-catalog-backend": "^0.2.2", "@backstage/plugin-graphql-backend": "^0.1.3", - "@backstage/plugin-kubernetes-backend": "^0.1.3", + "@backstage/plugin-kubernetes-backend": "^0.2.0", "@backstage/plugin-proxy-backend": "^0.2.1", "@backstage/plugin-rollbar-backend": "^0.1.3", - "@backstage/plugin-scaffolder-backend": "^0.3.1", + "@backstage/plugin-scaffolder-backend": "^0.3.2", "@backstage/plugin-sentry-backend": "^0.1.3", - "@backstage/plugin-techdocs-backend": "^0.2.1", + "@backstage/plugin-techdocs-backend": "^0.2.2", "@gitbeaker/node": "^25.2.0", "@octokit/rest": "^18.0.0", "azure-devops-node-api": "^10.1.1", "dockerode": "^3.2.0", - "example-app": "^0.2.2", + "example-app": "^0.2.3", "express": "^4.17.1", "express-promise-router": "^3.0.3", "knex": "^0.21.6", @@ -45,7 +45,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@types/dockerode": "^2.5.32", "@types/express": "^4.17.6", "@types/express-serve-static-core": "^4.17.5", diff --git a/packages/catalog-client/CHANGELOG.md b/packages/catalog-client/CHANGELOG.md index 574cd301bf..3454a6c8e0 100644 --- a/packages/catalog-client/CHANGELOG.md +++ b/packages/catalog-client/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/catalog-client +## 0.3.1 + +### Patch Changes + +- Updated dependencies [1166fcc36] +- Updated dependencies [1185919f3] + - @backstage/catalog-model@0.3.0 + ## 0.3.0 ### Minor Changes diff --git a/packages/catalog-client/package.json b/packages/catalog-client/package.json index 2482f9579d..4509b7d547 100644 --- a/packages/catalog-client/package.json +++ b/packages/catalog-client/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/catalog-client", - "version": "0.3.0", + "version": "0.3.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,12 +20,12 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.2.0", + "@backstage/catalog-model": "^0.3.0", "@backstage/config": "^0.1.1", "cross-fetch": "^3.0.6" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@types/jest": "^26.0.7", "msw": "^0.21.2" }, diff --git a/packages/catalog-model/CHANGELOG.md b/packages/catalog-model/CHANGELOG.md index 051aa82c80..10bf540b46 100644 --- a/packages/catalog-model/CHANGELOG.md +++ b/packages/catalog-model/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/catalog-model +## 0.3.0 + +### Minor Changes + +- 1166fcc36: add kubernetes selector to component model + +### Patch Changes + +- 1185919f3: Marked the `Group` entity fields `ancestors` and `descendants` for deprecation on Dec 6th, 2020. See https://github.com/backstage/backstage/issues/3049 for details. + + Code that consumes these fields should remove those usages as soon as possible. There is no current or planned replacement for these fields. + + The BuiltinKindsEntityProcessor has been updated to inject these fields as empty arrays if they are missing. Therefore, if you are on a catalog instance that uses the updated version of this code, you can start removing the fields from your source catalog-info.yaml data as well, without breaking validation. + + After Dec 6th, the fields will be removed from types and classes of the Backstage repository. At the first release after that, they will not be present in released packages either. + + If your catalog-info.yaml files still contain these fields after the deletion, they will still be valid and your ingestion will not break, but they won't be visible in the types for consuming code. + ## 0.2.0 ### Minor Changes diff --git a/packages/catalog-model/package.json b/packages/catalog-model/package.json index e712e66f58..7956454ca9 100644 --- a/packages/catalog-model/package.json +++ b/packages/catalog-model/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/catalog-model", - "version": "0.2.0", + "version": "0.3.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,7 +29,7 @@ "yup": "^0.29.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@types/express": "^4.17.6", "@types/jest": "^26.0.7", "@types/lodash": "^4.14.151", diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index a5bcc6bcfd..d198444c86 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/cli +## 0.3.1 + +### Patch Changes + +- 29a0ccab2: The CLI now detects and transforms linked packages. You can link in external packages by adding them to both the `lerna.json` and `package.json` workspace paths. +- faf311c26: New lint rule to disallow assertions and promote `as` assertions. - @typescript-eslint/consistent-type-assertions +- 31d8b6979: Add experimental backend:bundle command +- 991345969: Add new `versions:check` and `versions:bump` commands to simplify version management and avoid conflicts + ## 0.3.0 ### Minor Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index eb56b19971..034c8a3de5 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/cli", "description": "CLI for developing Backstage plugins and apps", - "version": "0.3.0", + "version": "0.3.1", "private": false, "publishConfig": { "access": "public" @@ -111,9 +111,9 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/backend-common": "^0.3.0", + "@backstage/backend-common": "^0.3.1", "@backstage/config": "^0.1.1", - "@backstage/core": "^0.3.1", + "@backstage/core": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@backstage/theme": "^0.2.1", diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 213e2b5246..d29bc53452 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/core +## 0.3.2 + +### Patch Changes + +- 475fc0aaa: Clear sidebar search field once a search is executed + ## 0.3.1 ### Patch Changes diff --git a/packages/core/package.json b/packages/core/package.json index d65f0a16e5..f667ffe7be 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core", "description": "Core API used by Backstage plugins and apps", - "version": "0.3.1", + "version": "0.3.2", "private": false, "publishConfig": { "access": "public", @@ -64,7 +64,7 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/packages/create-app/package.json b/packages/create-app/package.json index c01d4d3dbb..6e91a916a1 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -37,28 +37,28 @@ "recursive-readdir": "^2.2.2" }, "devDependencies": { - "@backstage/backend-common": "^0.3.0", - "@backstage/catalog-model": "^0.2.0", - "@backstage/cli": "^0.3.0", + "@backstage/backend-common": "^0.3.1", + "@backstage/catalog-model": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/config": "^0.1.1", - "@backstage/core": "^0.3.1", - "@backstage/plugin-api-docs": "^0.2.1", + "@backstage/core": "^0.3.2", + "@backstage/plugin-api-docs": "^0.2.2", "@backstage/plugin-app-backend": "^0.3.0", - "@backstage/plugin-auth-backend": "^0.2.2", - "@backstage/plugin-catalog": "^0.2.2", - "@backstage/plugin-catalog-backend": "^0.2.1", - "@backstage/plugin-circleci": "^0.2.1", + "@backstage/plugin-auth-backend": "^0.2.3", + "@backstage/plugin-catalog": "^0.2.3", + "@backstage/plugin-catalog-backend": "^0.2.2", + "@backstage/plugin-circleci": "^0.2.2", "@backstage/plugin-explore": "^0.2.1", - "@backstage/plugin-github-actions": "^0.2.1", - "@backstage/plugin-lighthouse": "^0.2.2", + "@backstage/plugin-github-actions": "^0.2.2", + "@backstage/plugin-lighthouse": "^0.2.3", "@backstage/plugin-proxy-backend": "^0.2.1", - "@backstage/plugin-register-component": "^0.2.1", + "@backstage/plugin-register-component": "^0.2.2", "@backstage/plugin-rollbar-backend": "^0.1.3", - "@backstage/plugin-scaffolder": "^0.3.0", - "@backstage/plugin-scaffolder-backend": "^0.3.1", + "@backstage/plugin-scaffolder": "^0.3.1", + "@backstage/plugin-scaffolder-backend": "^0.3.2", "@backstage/plugin-tech-radar": "^0.3.0", - "@backstage/plugin-techdocs": "^0.2.2", - "@backstage/plugin-techdocs-backend": "^0.2.1", + "@backstage/plugin-techdocs": "^0.2.3", + "@backstage/plugin-techdocs-backend": "^0.2.2", "@backstage/plugin-user-settings": "^0.2.2", "@backstage/test-utils": "^0.1.3", "@backstage/theme": "^0.2.1", diff --git a/plugins/api-docs/CHANGELOG.md b/plugins/api-docs/CHANGELOG.md index 5f1ad97e64..60a5caf55b 100644 --- a/plugins/api-docs/CHANGELOG.md +++ b/plugins/api-docs/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-api-docs +## 0.2.2 + +### Patch Changes + +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [1185919f3] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-catalog@0.2.3 + ## 0.2.1 ### Patch Changes diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index bfefaef3a5..691f2680b2 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-api-docs", - "version": "0.2.1", + "version": "0.2.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,9 +20,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.1", - "@backstage/plugin-catalog": "^0.2.2", + "@backstage/catalog-model": "^0.3.0", + "@backstage/core": "^0.3.2", + "@backstage/plugin-catalog": "^0.2.3", "@backstage/theme": "^0.2.1", "@kyma-project/asyncapi-react": "^0.14.2", "@material-icons/font": "^1.0.2", @@ -39,7 +39,7 @@ "swagger-ui-react": "^3.31.1" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/auth-backend/CHANGELOG.md b/plugins/auth-backend/CHANGELOG.md index 546855c92f..4a2161ad71 100644 --- a/plugins/auth-backend/CHANGELOG.md +++ b/plugins/auth-backend/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-auth-backend +## 0.2.3 + +### Patch Changes + +- Updated dependencies [1166fcc36] +- Updated dependencies [bff3305aa] +- Updated dependencies [1185919f3] +- Updated dependencies [b47dce06f] + - @backstage/catalog-model@0.3.0 + - @backstage/backend-common@0.3.1 + - @backstage/catalog-client@0.3.1 + ## 0.2.2 ### Patch Changes diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 4d1e52aa21..43e2c59f88 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend", - "version": "0.2.2", + "version": "0.2.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,9 +20,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.3.0", - "@backstage/catalog-client": "^0.3.0", - "@backstage/catalog-model": "^0.2.0", + "@backstage/backend-common": "^0.3.1", + "@backstage/catalog-client": "^0.3.1", + "@backstage/catalog-model": "^0.3.0", "@backstage/config": "^0.1.1", "@types/express": "^4.17.6", "compression": "^1.7.4", @@ -55,7 +55,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@types/body-parser": "^1.19.0", "@types/cookie-parser": "^1.4.2", "@types/express-session": "^1.17.2", diff --git a/plugins/catalog-backend/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md index 1a3199ee14..3bf25a494b 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-catalog-backend +## 0.2.2 + +### Patch Changes + +- 0c2121240: Add support for reading groups and users from the Microsoft Graph API. +- 1185919f3: Marked the `Group` entity fields `ancestors` and `descendants` for deprecation on Dec 6th, 2020. See https://github.com/backstage/backstage/issues/3049 for details. + + Code that consumes these fields should remove those usages as soon as possible. There is no current or planned replacement for these fields. + + The BuiltinKindsEntityProcessor has been updated to inject these fields as empty arrays if they are missing. Therefore, if you are on a catalog instance that uses the updated version of this code, you can start removing the fields from your source catalog-info.yaml data as well, without breaking validation. + + After Dec 6th, the fields will be removed from types and classes of the Backstage repository. At the first release after that, they will not be present in released packages either. + + If your catalog-info.yaml files still contain these fields after the deletion, they will still be valid and your ingestion will not break, but they won't be visible in the types for consuming code. + +- Updated dependencies [1166fcc36] +- Updated dependencies [bff3305aa] +- Updated dependencies [1185919f3] +- Updated dependencies [b47dce06f] + - @backstage/catalog-model@0.3.0 + - @backstage/backend-common@0.3.1 + ## 0.2.1 ### Patch Changes diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 12a17d6365..51a9978f30 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend", - "version": "0.2.1", + "version": "0.2.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,8 +21,8 @@ }, "dependencies": { "@azure/msal-node": "^1.0.0-alpha.8", - "@backstage/backend-common": "^0.3.0", - "@backstage/catalog-model": "^0.2.0", + "@backstage/backend-common": "^0.3.1", + "@backstage/catalog-model": "^0.3.0", "@backstage/config": "^0.1.1", "@octokit/graphql": "^4.5.6", "@types/express": "^4.17.6", @@ -47,7 +47,7 @@ "yup": "^0.29.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/test-utils": "^0.1.3", "@types/core-js": "^2.5.4", "@types/git-url-parse": "^9.0.0", diff --git a/plugins/catalog-graphql/CHANGELOG.md b/plugins/catalog-graphql/CHANGELOG.md index 35737b7dd1..56911b9c45 100644 --- a/plugins/catalog-graphql/CHANGELOG.md +++ b/plugins/catalog-graphql/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-catalog-graphql +## 0.2.2 + +### Patch Changes + +- Updated dependencies [1166fcc36] +- Updated dependencies [bff3305aa] +- Updated dependencies [1185919f3] +- Updated dependencies [b47dce06f] + - @backstage/catalog-model@0.3.0 + - @backstage/backend-common@0.3.1 + ## 0.2.1 ### Patch Changes diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json index c55be89b62..a489d66e5b 100644 --- a/plugins/catalog-graphql/package.json +++ b/plugins/catalog-graphql/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-graphql", - "version": "0.2.1", + "version": "0.2.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,8 +20,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.3.0", - "@backstage/catalog-model": "^0.2.0", + "@backstage/backend-common": "^0.3.1", + "@backstage/catalog-model": "^0.3.0", "@backstage/config": "^0.1.1", "@graphql-modules/core": "^0.7.17", "apollo-server": "^2.16.1", @@ -32,7 +32,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/test-utils": "^0.1.3", "@graphql-codegen/cli": "^1.17.7", "@graphql-codegen/typescript": "^1.17.7", diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index 1b8b884f1c..9137b7e948 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-catalog +## 0.2.3 + +### Patch Changes + +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [ef2831dde] +- Updated dependencies [1185919f3] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-scaffolder@0.3.1 + - @backstage/catalog-client@0.3.1 + - @backstage/plugin-techdocs@0.2.3 + ## 0.2.2 ### Patch Changes diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 2c65096fd6..7b58358734 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog", - "version": "0.2.2", + "version": "0.2.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,11 +21,11 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-client": "^0.3.0", - "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.1", - "@backstage/plugin-scaffolder": "^0.3.0", - "@backstage/plugin-techdocs": "^0.2.2", + "@backstage/catalog-client": "^0.3.1", + "@backstage/catalog-model": "^0.3.0", + "@backstage/core": "^0.3.2", + "@backstage/plugin-scaffolder": "^0.3.1", + "@backstage/plugin-techdocs": "^0.2.3", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -43,7 +43,7 @@ "swr": "^0.3.0" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@microsoft/microsoft-graph-types": "^1.25.0", diff --git a/plugins/circleci/CHANGELOG.md b/plugins/circleci/CHANGELOG.md index 2894ce1bd3..a81531e28d 100644 --- a/plugins/circleci/CHANGELOG.md +++ b/plugins/circleci/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-circleci +## 0.2.2 + +### Patch Changes + +- a8de7f554: Improved CircleCI builds table to show more information and relevant links +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [1185919f3] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-catalog@0.2.3 + ## 0.2.1 ### Patch Changes diff --git a/plugins/circleci/package.json b/plugins/circleci/package.json index eaa8c319a0..fd32ff9891 100644 --- a/plugins/circleci/package.json +++ b/plugins/circleci/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-circleci", - "version": "0.2.1", + "version": "0.2.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,9 +21,9 @@ "postpack": "backstage-cli postpack" }, "dependencies": { - "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.1", - "@backstage/plugin-catalog": "^0.2.2", + "@backstage/catalog-model": "^0.3.0", + "@backstage/core": "^0.3.2", + "@backstage/plugin-catalog": "^0.2.3", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -40,7 +40,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/cloudbuild/CHANGELOG.md b/plugins/cloudbuild/CHANGELOG.md index 9d02fd1ea3..4cb2d01377 100644 --- a/plugins/cloudbuild/CHANGELOG.md +++ b/plugins/cloudbuild/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-cloudbuild +## 0.2.2 + +### Patch Changes + +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [1185919f3] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-catalog@0.2.3 + ## 0.2.1 ### Patch Changes diff --git a/plugins/cloudbuild/package.json b/plugins/cloudbuild/package.json index c0b36ef1e7..3e7f0c9175 100644 --- a/plugins/cloudbuild/package.json +++ b/plugins/cloudbuild/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-cloudbuild", - "version": "0.2.1", + "version": "0.2.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,9 +20,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.1", - "@backstage/plugin-catalog": "^0.2.2", + "@backstage/catalog-model": "^0.3.0", + "@backstage/core": "^0.3.2", + "@backstage/plugin-catalog": "^0.2.3", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -39,7 +39,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/cost-insights/CHANGELOG.md b/plugins/cost-insights/CHANGELOG.md index 157d92f251..b7bdfa9d50 100644 --- a/plugins/cost-insights/CHANGELOG.md +++ b/plugins/cost-insights/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-cost-insights +## 0.4.1 + +### Patch Changes + +- 8e6728e25: fix product icon configuration +- c93a14b49: truncate large percentages > 1000% +- Updated dependencies [475fc0aaa] + - @backstage/core@0.3.2 + ## 0.4.0 ### Minor Changes diff --git a/plugins/cost-insights/package.json b/plugins/cost-insights/package.json index 8ebeac91c0..06083b855b 100644 --- a/plugins/cost-insights/package.json +++ b/plugins/cost-insights/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-cost-insights", - "version": "0.4.0", + "version": "0.4.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -22,7 +22,7 @@ }, "dependencies": { "@backstage/config": "^0.1.1", - "@backstage/core": "^0.3.1", + "@backstage/core": "^0.3.2", "@backstage/test-utils": "^0.1.3", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", @@ -46,7 +46,7 @@ "yup": "^0.29.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/github-actions/CHANGELOG.md b/plugins/github-actions/CHANGELOG.md index b679d6844b..96f3876802 100644 --- a/plugins/github-actions/CHANGELOG.md +++ b/plugins/github-actions/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-github-actions +## 0.2.2 + +### Patch Changes + +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [1185919f3] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-catalog@0.2.3 + ## 0.2.1 ### Patch Changes diff --git a/plugins/github-actions/package.json b/plugins/github-actions/package.json index 49ecfc5423..b1924034f1 100644 --- a/plugins/github-actions/package.json +++ b/plugins/github-actions/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-github-actions", - "version": "0.2.1", + "version": "0.2.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,10 +21,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.1", + "@backstage/catalog-model": "^0.3.0", + "@backstage/core": "^0.3.2", "@backstage/core-api": "^0.2.1", - "@backstage/plugin-catalog": "^0.2.2", + "@backstage/plugin-catalog": "^0.2.3", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -40,7 +40,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/jenkins/CHANGELOG.md b/plugins/jenkins/CHANGELOG.md index 44febbc63d..aeeb069f19 100644 --- a/plugins/jenkins/CHANGELOG.md +++ b/plugins/jenkins/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-jenkins +## 0.3.1 + +### Patch Changes + +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [1185919f3] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-catalog@0.2.3 + ## 0.3.0 ### Minor Changes diff --git a/plugins/jenkins/package.json b/plugins/jenkins/package.json index 0279592a75..961f551dfa 100644 --- a/plugins/jenkins/package.json +++ b/plugins/jenkins/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-jenkins", - "version": "0.3.0", + "version": "0.3.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,9 +21,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.1", - "@backstage/plugin-catalog": "^0.2.2", + "@backstage/catalog-model": "^0.3.0", + "@backstage/core": "^0.3.2", + "@backstage/plugin-catalog": "^0.2.3", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -36,7 +36,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/kubernetes-backend/CHANGELOG.md b/plugins/kubernetes-backend/CHANGELOG.md index 7c55a7407d..2877264e54 100644 --- a/plugins/kubernetes-backend/CHANGELOG.md +++ b/plugins/kubernetes-backend/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-kubernetes-backend +## 0.2.0 + +### Minor Changes + +- 1166fcc36: add kubernetes selector to component model + +### Patch Changes + +- Updated dependencies [1166fcc36] +- Updated dependencies [bff3305aa] +- Updated dependencies [1185919f3] +- Updated dependencies [b47dce06f] + - @backstage/catalog-model@0.3.0 + - @backstage/backend-common@0.3.1 + ## 0.1.3 ### Patch Changes diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index be7b86dd52..e607e51f80 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-backend", - "version": "0.1.3", + "version": "0.2.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,8 +20,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.3.0", - "@backstage/catalog-model": "^0.2.0", + "@backstage/backend-common": "^0.3.1", + "@backstage/catalog-model": "^0.3.0", "@backstage/config": "^0.1.1", "@kubernetes/client-node": "^0.12.1", "@types/express": "^4.17.6", @@ -38,7 +38,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "supertest": "^4.0.2" }, "files": [ diff --git a/plugins/kubernetes/CHANGELOG.md b/plugins/kubernetes/CHANGELOG.md index e5611e4953..652fc394a2 100644 --- a/plugins/kubernetes/CHANGELOG.md +++ b/plugins/kubernetes/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-kubernetes +## 0.3.0 + +### Minor Changes + +- 1166fcc36: add kubernetes selector to component model + +### Patch Changes + +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [1185919f3] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-kubernetes-backend@0.2.0 + ## 0.2.1 ### Patch Changes diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index 36954cc5b9..2afff2ae3f 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes", - "version": "0.2.1", + "version": "0.3.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,10 +20,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.2.0", + "@backstage/catalog-model": "^0.3.0", "@backstage/config": "^0.1.1", - "@backstage/core": "^0.3.1", - "@backstage/plugin-kubernetes-backend": "^0.1.3", + "@backstage/core": "^0.3.2", + "@backstage/plugin-kubernetes-backend": "^0.2.0", "@backstage/theme": "^0.2.1", "@kubernetes/client-node": "^0.12.1", "@material-ui/core": "^4.11.0", @@ -35,7 +35,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/lighthouse/CHANGELOG.md b/plugins/lighthouse/CHANGELOG.md index 666e7c4411..e35f3dece0 100644 --- a/plugins/lighthouse/CHANGELOG.md +++ b/plugins/lighthouse/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-lighthouse +## 0.2.3 + +### Patch Changes + +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [1185919f3] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-catalog@0.2.3 + ## 0.2.2 ### Patch Changes diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json index 90230f5248..ad1264cfbb 100644 --- a/plugins/lighthouse/package.json +++ b/plugins/lighthouse/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-lighthouse", - "version": "0.2.2", + "version": "0.2.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,11 +21,11 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/catalog-model": "^0.2.0", + "@backstage/catalog-model": "^0.3.0", "@backstage/config": "^0.1.1", - "@backstage/core": "^0.3.1", + "@backstage/core": "^0.3.2", "@backstage/core-api": "^0.2.1", - "@backstage/plugin-catalog": "^0.2.2", + "@backstage/plugin-catalog": "^0.2.3", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -38,7 +38,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/register-component/CHANGELOG.md b/plugins/register-component/CHANGELOG.md index 3e966ef0d0..c569046855 100644 --- a/plugins/register-component/CHANGELOG.md +++ b/plugins/register-component/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-register-component +## 0.2.2 + +### Patch Changes + +- 2a71f4bab: Remove catalog link on validate popup +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [1185919f3] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-catalog@0.2.3 + ## 0.2.1 ### Patch Changes diff --git a/plugins/register-component/package.json b/plugins/register-component/package.json index 06ae17cb0e..1564faae0e 100644 --- a/plugins/register-component/package.json +++ b/plugins/register-component/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-register-component", - "version": "0.2.1", + "version": "0.2.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,9 +21,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.1", - "@backstage/plugin-catalog": "^0.2.2", + "@backstage/catalog-model": "^0.3.0", + "@backstage/core": "^0.3.2", + "@backstage/plugin-catalog": "^0.2.3", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -36,7 +36,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/rollbar/CHANGELOG.md b/plugins/rollbar/CHANGELOG.md index e4a6671f90..1ccfe16c41 100644 --- a/plugins/rollbar/CHANGELOG.md +++ b/plugins/rollbar/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-rollbar +## 0.2.3 + +### Patch Changes + +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [1185919f3] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-catalog@0.2.3 + ## 0.2.2 ### Patch Changes diff --git a/plugins/rollbar/package.json b/plugins/rollbar/package.json index c107573ca8..1177a83eb6 100644 --- a/plugins/rollbar/package.json +++ b/plugins/rollbar/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-rollbar", - "version": "0.2.2", + "version": "0.2.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,9 +21,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.1", - "@backstage/plugin-catalog": "^0.2.2", + "@backstage/catalog-model": "^0.3.0", + "@backstage/core": "^0.3.2", + "@backstage/plugin-catalog": "^0.2.3", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -37,7 +37,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index a388a29a00..ea7e5c85ee 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-scaffolder-backend +## 0.3.2 + +### Patch Changes + +- ef2831dde: Move constructing the catalog-info.yaml URL for scaffolded components to the publishers +- 5a1d8dca3: Fix React entity YAML filename to new standard +- Updated dependencies [1166fcc36] +- Updated dependencies [bff3305aa] +- Updated dependencies [1185919f3] +- Updated dependencies [b47dce06f] + - @backstage/catalog-model@0.3.0 + - @backstage/backend-common@0.3.1 + ## 0.3.1 ### Patch Changes diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 73c07d158f..a095167a08 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend", - "version": "0.3.1", + "version": "0.3.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,8 +20,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.3.0", - "@backstage/catalog-model": "^0.2.0", + "@backstage/backend-common": "^0.3.1", + "@backstage/catalog-model": "^0.3.0", "@backstage/config": "^0.1.1", "@gitbeaker/core": "^25.2.0", "@gitbeaker/node": "^25.2.0", @@ -48,7 +48,7 @@ "cross-fetch": "^3.0.6" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@octokit/types": "^5.4.1", "@types/fs-extra": "^9.0.1", "@types/git-url-parse": "^9.0.0", diff --git a/plugins/scaffolder/CHANGELOG.md b/plugins/scaffolder/CHANGELOG.md index 9eac595997..c78e05c3e0 100644 --- a/plugins/scaffolder/CHANGELOG.md +++ b/plugins/scaffolder/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-scaffolder +## 0.3.1 + +### Patch Changes + +- ef2831dde: Move constructing the catalog-info.yaml URL for scaffolded components to the publishers +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [1185919f3] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-catalog@0.2.3 + ## 0.3.0 ### Minor Changes diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 439fc0ad9a..791221bc62 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder", - "version": "0.3.0", + "version": "0.3.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,9 +21,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.1", - "@backstage/plugin-catalog": "^0.2.2", + "@backstage/catalog-model": "^0.3.0", + "@backstage/core": "^0.3.2", + "@backstage/plugin-catalog": "^0.2.3", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -41,7 +41,7 @@ "swr": "^0.3.0" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/search/CHANGELOG.md b/plugins/search/CHANGELOG.md new file mode 100644 index 0000000000..3545914e96 --- /dev/null +++ b/plugins/search/CHANGELOG.md @@ -0,0 +1,13 @@ +# @backstage/plugin-search + +## 0.2.1 + +### Patch Changes + +- 475fc0aaa: Using the search field in the sidebar now navigates to the search result page. +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [1185919f3] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-catalog@0.2.3 diff --git a/plugins/search/package.json b/plugins/search/package.json index df5c6c25b9..021e3f71f3 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search", - "version": "0.2.0", + "version": "0.2.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,9 +20,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.3.1", - "@backstage/plugin-catalog": "^0.2.2", - "@backstage/catalog-model": "^0.2.0", + "@backstage/core": "^0.3.2", + "@backstage/plugin-catalog": "^0.2.3", + "@backstage/catalog-model": "^0.3.0", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -34,7 +34,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/sentry/CHANGELOG.md b/plugins/sentry/CHANGELOG.md index 92327c1668..b388548d8e 100644 --- a/plugins/sentry/CHANGELOG.md +++ b/plugins/sentry/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-sentry +## 0.2.3 + +### Patch Changes + +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [1185919f3] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + ## 0.2.2 ### Patch Changes diff --git a/plugins/sentry/package.json b/plugins/sentry/package.json index 80a37c574e..4c10936148 100644 --- a/plugins/sentry/package.json +++ b/plugins/sentry/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-sentry", - "version": "0.2.2", + "version": "0.2.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,8 +21,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.1", + "@backstage/catalog-model": "^0.3.0", + "@backstage/core": "^0.3.2", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -36,7 +36,7 @@ "timeago.js": "^4.0.2" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/sonarqube/CHANGELOG.md b/plugins/sonarqube/CHANGELOG.md index 0296bd0416..9ebd73b812 100644 --- a/plugins/sonarqube/CHANGELOG.md +++ b/plugins/sonarqube/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-sonarqube +## 0.1.4 + +### Patch Changes + +- 26484d413: Add configuration schema +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [1185919f3] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + ## 0.1.3 ### Patch Changes diff --git a/plugins/sonarqube/package.json b/plugins/sonarqube/package.json index be8a878e50..6a9d0fe0d4 100644 --- a/plugins/sonarqube/package.json +++ b/plugins/sonarqube/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-sonarqube", - "version": "0.1.3", + "version": "0.1.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,8 +21,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.1", + "@backstage/catalog-model": "^0.3.0", + "@backstage/core": "^0.3.2", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -35,7 +35,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/techdocs-backend/CHANGELOG.md b/plugins/techdocs-backend/CHANGELOG.md index 4a3fbf6821..d9054230e2 100644 --- a/plugins/techdocs-backend/CHANGELOG.md +++ b/plugins/techdocs-backend/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-techdocs-backend +## 0.2.2 + +### Patch Changes + +- Updated dependencies [1166fcc36] +- Updated dependencies [bff3305aa] +- Updated dependencies [1185919f3] +- Updated dependencies [b47dce06f] + - @backstage/catalog-model@0.3.0 + - @backstage/backend-common@0.3.1 + ## 0.2.1 ### Patch Changes diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index b139bb9acf..1d9f3ea7c1 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-backend", - "version": "0.2.1", + "version": "0.2.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,8 +20,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.3.0", - "@backstage/catalog-model": "^0.2.0", + "@backstage/backend-common": "^0.3.1", + "@backstage/catalog-model": "^0.3.0", "@backstage/config": "^0.1.1", "@types/dockerode": "^2.5.34", "@types/express": "^4.17.6", @@ -37,7 +37,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "supertest": "^4.0.2" }, "files": [ diff --git a/plugins/techdocs/CHANGELOG.md b/plugins/techdocs/CHANGELOG.md index bd3ae21ab8..3f46e1358b 100644 --- a/plugins/techdocs/CHANGELOG.md +++ b/plugins/techdocs/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-techdocs +## 0.2.3 + +### Patch Changes + +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [1185919f3] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-catalog@0.2.3 + ## 0.2.2 ### Patch Changes diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index 35b196e124..23d7a25b57 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs", - "version": "0.2.2", + "version": "0.2.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,10 +21,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.1", + "@backstage/catalog-model": "^0.3.0", + "@backstage/core": "^0.3.2", "@backstage/core-api": "^0.2.1", - "@backstage/plugin-catalog": "^0.2.2", + "@backstage/plugin-catalog": "^0.2.3", "@backstage/test-utils": "^0.1.3", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", @@ -39,7 +39,7 @@ "sanitize-html": "^1.27.0" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", From f3bb55ee31ca217ccdaf1b9ced8dc5018e3952bf Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Tue, 20 Oct 2020 10:40:58 +0200 Subject: [PATCH 203/430] Make api-docs customizable --- .changeset/chilled-zoos-rush.md | 7 + .../app/src/components/catalog/EntityPage.tsx | 133 ++++++++++++------ plugins/api-docs/package.json | 2 +- .../ApiEntityPage/ApiEntityPage.test.tsx | 71 ---------- .../ApiEntityPage/ApiEntityPage.tsx | 127 ----------------- .../src/components/ApiEntityPage/index.ts | 17 --- .../ApiExplorerTable/ApiExplorerTable.tsx | 16 +-- plugins/api-docs/src/index.ts | 3 +- plugins/api-docs/src/plugin.ts | 4 +- plugins/api-docs/src/routes.ts | 6 - 10 files changed, 106 insertions(+), 280 deletions(-) create mode 100644 .changeset/chilled-zoos-rush.md delete mode 100644 plugins/api-docs/src/components/ApiEntityPage/ApiEntityPage.test.tsx delete mode 100644 plugins/api-docs/src/components/ApiEntityPage/ApiEntityPage.tsx delete mode 100644 plugins/api-docs/src/components/ApiEntityPage/index.ts diff --git a/.changeset/chilled-zoos-rush.md b/.changeset/chilled-zoos-rush.md new file mode 100644 index 0000000000..6830cb7fcf --- /dev/null +++ b/.changeset/chilled-zoos-rush.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-api-docs': minor +--- + +APIs now have real entity pages that are customizable in the app. +Therefore the old entity page from this plugin is removed. +See the `packages/app` on how to create and customize the API entity page. diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index c201fe88ad..d30de6abd1 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -13,63 +13,66 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { ApiEntity, Entity } from '@backstage/catalog-model'; +import { EmptyState } from '@backstage/core'; import { - isPluginApplicableToEntity as isTravisCIAvailable, - RecentTravisCIBuildsWidget, - Router as TravisCIRouter, -} from '@roadiehq/backstage-plugin-travis-ci'; + ApiDefinitionCard, + Router as ApiDocsRouter, +} from '@backstage/plugin-api-docs'; +import { + AboutCard, + EntityPageLayout, + useEntity, +} from '@backstage/plugin-catalog'; +import { + isPluginApplicableToEntity as isCircleCIAvailable, + Router as CircleCIRouter, +} from '@backstage/plugin-circleci'; +import { + isPluginApplicableToEntity as isCloudbuildAvailable, + Router as CloudbuildRouter, +} from '@backstage/plugin-cloudbuild'; import { isPluginApplicableToEntity as isGitHubActionsAvailable, RecentWorkflowRunsCard, Router as GitHubActionsRouter, } from '@backstage/plugin-github-actions'; import { - Router as CloudbuildRouter, - isPluginApplicableToEntity as isCloudbuildAvailable, -} from '@backstage/plugin-cloudbuild'; -import { - Router as JenkinsRouter, isPluginApplicableToEntity as isJenkinsAvailable, LatestRunCard as JenkinsLatestRunCard, + Router as JenkinsRouter, } from '@backstage/plugin-jenkins'; -import { - isPluginApplicableToEntity as isCircleCIAvailable, - Router as CircleCIRouter, -} from '@backstage/plugin-circleci'; -import { Router as ApiDocsRouter } from '@backstage/plugin-api-docs'; -import { Router as SentryRouter } from '@backstage/plugin-sentry'; -import { EmbeddedDocsRouter as DocsRouter } from '@backstage/plugin-techdocs'; import { Router as KubernetesRouter } from '@backstage/plugin-kubernetes'; -import { - Router as GitHubInsightsRouter, - isPluginApplicableToEntity as isGitHubAvailable, - ReadMeCard, - LanguagesCard, - ReleasesCard, -} from '@roadiehq/backstage-plugin-github-insights'; -import React, { ReactNode } from 'react'; -import { - AboutCard, - EntityPageLayout, - useEntity, -} from '@backstage/plugin-catalog'; -import { Entity } from '@backstage/catalog-model'; -import { Button, Grid } from '@material-ui/core'; -import { EmptyState } from '@backstage/core'; import { EmbeddedRouter as LighthouseRouter, - LastLighthouseAuditCard, isPluginApplicableToEntity as isLighthouseAvailable, + LastLighthouseAuditCard, } from '@backstage/plugin-lighthouse/'; +import { Router as SentryRouter } from '@backstage/plugin-sentry'; +import { EmbeddedDocsRouter as DocsRouter } from '@backstage/plugin-techdocs'; +import { Button, Grid } from '@material-ui/core'; +import { + isPluginApplicableToEntity as isBuildkiteAvailable, + Router as BuildkiteRouter, +} from '@roadiehq/backstage-plugin-buildkite'; +import { + isPluginApplicableToEntity as isGitHubAvailable, + LanguagesCard, + ReadMeCard, + ReleasesCard, + Router as GitHubInsightsRouter, +} from '@roadiehq/backstage-plugin-github-insights'; import { - Router as PullRequestsRouter, isPluginApplicableToEntity as isPullRequestsAvailable, PullRequestsStatsCard, + Router as PullRequestsRouter, } from '@roadiehq/backstage-plugin-github-pull-requests'; import { - Router as BuildkiteRouter, - isPluginApplicableToEntity as isBuildkiteAvailable, -} from '@roadiehq/backstage-plugin-buildkite'; + isPluginApplicableToEntity as isTravisCIAvailable, + RecentTravisCIBuildsWidget, + Router as TravisCIRouter, +} from '@roadiehq/backstage-plugin-travis-ci'; +import React, { ReactNode } from 'react'; export const CICDSwitcher = ({ entity }: { entity: Entity }) => { // This component is just an example of how you can implement your company's logic in entity page. @@ -134,7 +137,7 @@ const RecentCICDRunsSwitcher = ({ entity }: { entity: Entity }) => { ); }; -const OverviewContent = ({ entity }: { entity: Entity }) => ( +const ComponentOverviewContent = ({ entity }: { entity: Entity }) => ( @@ -169,7 +172,7 @@ const ServiceEntityPage = ({ entity }: { entity: Entity }) => ( } + element={} /> ( } + element={} /> ( /> ); + const DefaultEntityPage = ({ entity }: { entity: Entity }) => ( } + element={} /> ( ); -export const EntityPage = () => { - const { entity } = useEntity(); +export const ComponentEntityPage = ({ entity }: { entity: Entity }) => { switch (entity?.spec?.type) { case 'service': return ; @@ -279,3 +282,47 @@ export const EntityPage = () => { return ; } }; + +const ApiOverviewContent = ({ entity }: { entity: Entity }) => ( + + + + + +); + +const ApiDefinitionContent = ({ entity }: { entity: ApiEntity }) => ( + + + + + +); + +const ApiEntityPage = ({ entity }: { entity: Entity }) => ( + + } + /> + } + /> + +); + +export const EntityPage = () => { + const { entity } = useEntity(); + + switch (entity?.kind?.toLowerCase()) { + case 'component': + return ; + case 'api': + return ; + default: + return ; + } +}; diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index bfefaef3a5..e8127b7732 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -29,6 +29,7 @@ "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", + "@types/react": "^16.9", "graphiql": "^1.0.0-alpha.10", "graphql": "^15.3.0", "react": "^16.13.1", @@ -47,7 +48,6 @@ "@testing-library/user-event": "^12.0.7", "@types/jest": "^26.0.7", "@types/node": "^12.0.0", - "@types/react": "^16.9", "@types/swagger-ui-react": "^3.23.3", "cross-fetch": "^3.0.6", "msw": "^0.21.2" diff --git a/plugins/api-docs/src/components/ApiEntityPage/ApiEntityPage.test.tsx b/plugins/api-docs/src/components/ApiEntityPage/ApiEntityPage.test.tsx deleted file mode 100644 index 9a2bc0cfb1..0000000000 --- a/plugins/api-docs/src/components/ApiEntityPage/ApiEntityPage.test.tsx +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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 { ApiProvider, ApiRegistry, errorApiRef } from '@backstage/core'; -import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog'; -import { wrapInTestApp } from '@backstage/test-utils'; -import { render, waitFor } from '@testing-library/react'; -import * as React from 'react'; -import { ApiEntityPage } from './ApiEntityPage'; - -jest.mock('react-router-dom', () => { - const actual = jest.requireActual('react-router-dom'); - const mockNavigate = jest.fn(); - return { - ...actual, - useNavigate: jest.fn(() => mockNavigate), - useParams: jest.fn(), - }; -}); - -const { - useParams, - useNavigate, -}: { useParams: jest.Mock; useNavigate: () => jest.Mock } = jest.requireMock( - 'react-router-dom', -); - -const errorApi = { post: () => {} }; - -describe('ApiEntityPage', () => { - it('should redirect to catalog page when name is not provided', async () => { - useParams.mockReturnValue({ - kind: 'Component', - optionalNamespaceAndName: '', - }); - - render( - wrapInTestApp( - ) as CatalogApi, - ], - ])} - > - - , - ), - ); - - await waitFor(() => - expect(useNavigate()).toHaveBeenCalledWith('/api-docs'), - ); - }); -}); diff --git a/plugins/api-docs/src/components/ApiEntityPage/ApiEntityPage.tsx b/plugins/api-docs/src/components/ApiEntityPage/ApiEntityPage.tsx deleted file mode 100644 index f755705ac7..0000000000 --- a/plugins/api-docs/src/components/ApiEntityPage/ApiEntityPage.tsx +++ /dev/null @@ -1,127 +0,0 @@ -/* - * 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 { ApiEntity, Entity } from '@backstage/catalog-model'; -import { - Content, - errorApiRef, - Header, - Page, - Progress, - useApi, -} from '@backstage/core'; -import { catalogApiRef } from '@backstage/plugin-catalog'; -import { Box } from '@material-ui/core'; -import { Alert } from '@material-ui/lab'; -import React, { useEffect } from 'react'; -import { useNavigate, useParams } from 'react-router-dom'; -import { useAsync } from 'react-use'; -import { ApiDefinitionCard } from '../ApiDefinitionCard'; - -const REDIRECT_DELAY = 1000; - -function headerProps( - kind: string, - namespace: string | undefined, - name: string, - entity: Entity | undefined, -): { headerTitle: string; headerType: string } { - return { - headerTitle: `${name}${namespace ? ` in ${namespace}` : ''}`, - headerType: (() => { - let t = kind.toLowerCase(); - if (entity && entity.spec && 'type' in entity.spec) { - t += ' — '; - t += (entity.spec as { type: string }).type.toLowerCase(); - } - return t; - })(), - }; -} - -type EntityPageTitleProps = { - title: string; - entity: Entity | undefined; -}; - -const EntityPageTitle = ({ title }: EntityPageTitleProps) => ( - - {title} - -); - -export const ApiEntityPage = () => { - const { optionalNamespaceAndName } = useParams() as { - optionalNamespaceAndName: string; - }; - const navigate = useNavigate(); - const [name, namespace] = optionalNamespaceAndName.split(':').reverse(); - - const errorApi = useApi(errorApiRef); - const catalogApi = useApi(catalogApiRef); - - const { value: entity, error, loading } = useAsync( - () => catalogApi.getEntityByName({ kind: 'API', namespace, name }), - [catalogApi, namespace, name], - ); - - useEffect(() => { - if (!error && !loading && !entity) { - errorApi.post(new Error('Entity not found!')); - setTimeout(() => { - navigate('/'); - }, REDIRECT_DELAY); - } - }, [errorApi, navigate, error, loading, entity]); - - if (!name) { - navigate('/api-docs'); - return null; - } - - const { headerTitle, headerType } = headerProps( - 'API', - namespace, - name, - entity, - ); - - return ( - -
} - pageTitleOverride={headerTitle} - type={headerType} - /> - - {loading && } - - {error && ( - - {error.toString()} - - )} - - {entity && ( - <> - - - - - )} - - ); -}; diff --git a/plugins/api-docs/src/components/ApiEntityPage/index.ts b/plugins/api-docs/src/components/ApiEntityPage/index.ts deleted file mode 100644 index 561350744b..0000000000 --- a/plugins/api-docs/src/components/ApiEntityPage/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * 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 { ApiEntityPage } from './ApiEntityPage'; diff --git a/plugins/api-docs/src/components/ApiExplorerTable/ApiExplorerTable.tsx b/plugins/api-docs/src/components/ApiExplorerTable/ApiExplorerTable.tsx index 9222623aed..f4a66f02f2 100644 --- a/plugins/api-docs/src/components/ApiExplorerTable/ApiExplorerTable.tsx +++ b/plugins/api-docs/src/components/ApiExplorerTable/ApiExplorerTable.tsx @@ -23,12 +23,12 @@ import { useApi, useQueryParamState, } from '@backstage/core'; +import { entityRoute, entityRouteParams } from '@backstage/plugin-catalog'; import { Chip, Link } from '@material-ui/core'; import { Alert } from '@material-ui/lab'; import React from 'react'; import { generatePath, Link as RouterLink } from 'react-router-dom'; import { apiDocsConfigRef } from '../../config'; -import { entityRoute } from '../../routes'; const ApiTypeTitle = ({ apiEntity }: { apiEntity: ApiEntityV1alpha1 }) => { const config = useApi(apiDocsConfigRef); @@ -46,16 +46,10 @@ const columns: TableColumn[] = [ render: (entity: any) => ( {entity.metadata.name} diff --git a/plugins/api-docs/src/index.ts b/plugins/api-docs/src/index.ts index 958355f063..dbb32cee7b 100644 --- a/plugins/api-docs/src/index.ts +++ b/plugins/api-docs/src/index.ts @@ -14,5 +14,6 @@ * limitations under the License. */ -export { Router } from './catalog'; +export * from './catalog'; +export * from './components'; export { plugin } from './plugin'; diff --git a/plugins/api-docs/src/plugin.ts b/plugins/api-docs/src/plugin.ts index 3fcbe8a1f4..06db03f2e1 100644 --- a/plugins/api-docs/src/plugin.ts +++ b/plugins/api-docs/src/plugin.ts @@ -18,8 +18,7 @@ import { ApiEntity } from '@backstage/catalog-model'; import { createApiFactory, createPlugin } from '@backstage/core'; import { ApiExplorerPage } from './components/ApiExplorerPage/ApiExplorerPage'; import { defaultDefinitionWidgets } from './components/ApiDefinitionCard'; -import { ApiEntityPage } from './components/ApiEntityPage/ApiEntityPage'; -import { entityRoute, rootRoute } from './routes'; +import { rootRoute } from './routes'; import { apiDocsConfigRef } from './config'; export const plugin = createPlugin({ @@ -40,6 +39,5 @@ export const plugin = createPlugin({ ], register({ router }) { router.addRoute(rootRoute, ApiExplorerPage); - router.addRoute(entityRoute, ApiEntityPage); }, }); diff --git a/plugins/api-docs/src/routes.ts b/plugins/api-docs/src/routes.ts index d2d8ef10c5..6adff78e47 100644 --- a/plugins/api-docs/src/routes.ts +++ b/plugins/api-docs/src/routes.ts @@ -24,12 +24,6 @@ export const rootRoute = createRouteRef({ title: 'APIs', }); -export const entityRoute = createRouteRef({ - icon: NoIcon, - path: '/api-docs/:optionalNamespaceAndName/', - title: 'API', -}); - export const catalogRoute = createRouteRef({ icon: NoIcon, path: '', From 61152848127c77716322109734b94c5eb5196717 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 24 Nov 2020 14:11:41 +0100 Subject: [PATCH 204/430] chore: yarn.lock update --- yarn.lock | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/yarn.lock b/yarn.lock index 6bc39ab733..3679e7a60f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1307,6 +1307,19 @@ lodash "^4.17.19" to-fast-properties "^2.0.0" +"@backstage/catalog-model@^0.2.0": + version "0.2.0" + resolved "https://registry.npmjs.org/@backstage/catalog-model/-/catalog-model-0.2.0.tgz#e3fe2a4ddeb6a9b6ec480c80cb2b9c39cb245576" + integrity sha512-Y1ocdRpBlxK/VrJQjHlQd0bgADECd1B2NRjwd8ss46ibT5hwLvMOfD80+Fa7oPLu0ktJrH4lq0pNIIJIml48zA== + dependencies: + "@backstage/config" "^0.1.1" + "@types/json-schema" "^7.0.5" + "@types/yup" "^0.29.8" + json-schema "^0.2.5" + lodash "^4.17.15" + uuid "^8.0.0" + yup "^0.29.3" + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" From a39968ca57900777172d2cfb56d3a83549371528 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 24 Nov 2020 14:33:39 +0100 Subject: [PATCH 205/430] chore(diff): accept diff changes --- plugins/explore/package.json | 4 ++-- plugins/gcp-projects/package.json | 4 ++-- plugins/gitops-profiles/package.json | 4 ++-- plugins/graphiql/package.json | 4 ++-- plugins/newrelic/package.json | 4 ++-- plugins/tech-radar/package.json | 4 ++-- plugins/user-settings/package.json | 4 ++-- plugins/welcome/package.json | 4 ++-- 8 files changed, 16 insertions(+), 16 deletions(-) diff --git a/plugins/explore/package.json b/plugins/explore/package.json index 153184bdb1..f5f73b4faa 100644 --- a/plugins/explore/package.json +++ b/plugins/explore/package.json @@ -21,7 +21,7 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/core": "^0.3.1", + "@backstage/core": "^0.3.2", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -33,7 +33,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/gcp-projects/package.json b/plugins/gcp-projects/package.json index 0415a5ffb3..59bdcc547c 100644 --- a/plugins/gcp-projects/package.json +++ b/plugins/gcp-projects/package.json @@ -20,7 +20,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.3.1", + "@backstage/core": "^0.3.2", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -31,7 +31,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/gitops-profiles/package.json b/plugins/gitops-profiles/package.json index ad80d26e61..67edcca51e 100644 --- a/plugins/gitops-profiles/package.json +++ b/plugins/gitops-profiles/package.json @@ -21,7 +21,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.3.1", + "@backstage/core": "^0.3.2", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -32,7 +32,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/graphiql/package.json b/plugins/graphiql/package.json index a5523afc6f..bde5dea9a3 100644 --- a/plugins/graphiql/package.json +++ b/plugins/graphiql/package.json @@ -31,7 +31,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.3.1", + "@backstage/core": "^0.3.2", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -43,7 +43,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/newrelic/package.json b/plugins/newrelic/package.json index 0e1f4e1352..1c2b951bc6 100644 --- a/plugins/newrelic/package.json +++ b/plugins/newrelic/package.json @@ -21,7 +21,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.3.1", + "@backstage/core": "^0.3.2", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -31,7 +31,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/tech-radar/package.json b/plugins/tech-radar/package.json index 7f5707764b..849ce3bc4d 100644 --- a/plugins/tech-radar/package.json +++ b/plugins/tech-radar/package.json @@ -21,7 +21,7 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/core": "^0.3.1", + "@backstage/core": "^0.3.2", "@backstage/test-utils": "^0.1.3", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", @@ -35,7 +35,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index 1277ae214a..80bcdeffdb 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -21,7 +21,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.3.1", + "@backstage/core": "^0.3.2", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -32,7 +32,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/welcome/package.json b/plugins/welcome/package.json index 47b02f971c..5d81e569b8 100644 --- a/plugins/welcome/package.json +++ b/plugins/welcome/package.json @@ -21,7 +21,7 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/core": "^0.3.1", + "@backstage/core": "^0.3.2", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -32,7 +32,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", From 7d564164fdd07b0eab5ea851922b5a737d7117f7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 24 Nov 2020 13:34:37 +0000 Subject: [PATCH 206/430] Version Packages --- .changeset/breezy-cobras-deny.md | 5 --- .changeset/chatty-radios-beam.md | 7 ---- .changeset/clever-moons-shake.md | 5 --- .changeset/cli-links.md | 5 --- .changeset/cost-insights-quiet-bikes-occur.md | 5 --- .../cost-insights-tiny-llamas-perform.md | 5 --- .changeset/curly-yaks-invite.md | 5 --- .changeset/dull-pans-sip.md | 6 --- .changeset/empty-kids-look.md | 5 --- .changeset/forty-buses-watch.md | 14 ------- .changeset/long-flowers-wink.md | 5 --- .changeset/mighty-cycles-impress.md | 5 --- .changeset/new-ladybugs-nail.md | 5 --- .changeset/orange-cherries-run.md | 5 --- .changeset/poor-mails-marry.md | 5 --- .changeset/small-worms-check.md | 6 --- .changeset/tame-rockets-explode.md | 5 --- .changeset/tidy-brooms-look.md | 5 --- packages/app/CHANGELOG.md | 37 ++++++++++++++++++ packages/app/package.json | 38 +++++++++---------- packages/backend-common/CHANGELOG.md | 7 ++++ packages/backend-common/package.json | 4 +- packages/backend/CHANGELOG.md | 21 ++++++++++ packages/backend/package.json | 20 +++++----- packages/catalog-client/CHANGELOG.md | 8 ++++ packages/catalog-client/package.json | 6 +-- packages/catalog-model/CHANGELOG.md | 18 +++++++++ packages/catalog-model/package.json | 4 +- packages/cli/CHANGELOG.md | 9 +++++ packages/cli/package.json | 6 +-- packages/core/CHANGELOG.md | 6 +++ packages/core/package.json | 4 +- packages/create-app/package.json | 32 ++++++++-------- plugins/api-docs/CHANGELOG.md | 11 ++++++ plugins/api-docs/package.json | 10 ++--- plugins/auth-backend/CHANGELOG.md | 12 ++++++ plugins/auth-backend/package.json | 10 ++--- plugins/catalog-backend/CHANGELOG.md | 22 +++++++++++ plugins/catalog-backend/package.json | 8 ++-- plugins/catalog-graphql/CHANGELOG.md | 11 ++++++ plugins/catalog-graphql/package.json | 8 ++-- plugins/catalog/CHANGELOG.md | 14 +++++++ plugins/catalog/package.json | 14 +++---- plugins/circleci/CHANGELOG.md | 12 ++++++ plugins/circleci/package.json | 10 ++--- plugins/cloudbuild/CHANGELOG.md | 11 ++++++ plugins/cloudbuild/package.json | 10 ++--- plugins/cost-insights/CHANGELOG.md | 9 +++++ plugins/cost-insights/package.json | 6 +-- plugins/github-actions/CHANGELOG.md | 11 ++++++ plugins/github-actions/package.json | 10 ++--- plugins/jenkins/CHANGELOG.md | 11 ++++++ plugins/jenkins/package.json | 10 ++--- plugins/kubernetes-backend/CHANGELOG.md | 15 ++++++++ plugins/kubernetes-backend/package.json | 8 ++-- plugins/kubernetes/CHANGELOG.md | 15 ++++++++ plugins/kubernetes/package.json | 10 ++--- plugins/lighthouse/CHANGELOG.md | 11 ++++++ plugins/lighthouse/package.json | 10 ++--- plugins/register-component/CHANGELOG.md | 12 ++++++ plugins/register-component/package.json | 10 ++--- plugins/rollbar/CHANGELOG.md | 11 ++++++ plugins/rollbar/package.json | 10 ++--- plugins/scaffolder-backend/CHANGELOG.md | 13 +++++++ plugins/scaffolder-backend/package.json | 8 ++-- plugins/scaffolder/CHANGELOG.md | 12 ++++++ plugins/scaffolder/package.json | 10 ++--- plugins/search/CHANGELOG.md | 13 +++++++ plugins/search/package.json | 10 ++--- plugins/sentry/CHANGELOG.md | 10 +++++ plugins/sentry/package.json | 8 ++-- plugins/sonarqube/CHANGELOG.md | 11 ++++++ plugins/sonarqube/package.json | 8 ++-- plugins/techdocs-backend/CHANGELOG.md | 11 ++++++ plugins/techdocs-backend/package.json | 8 ++-- plugins/techdocs/CHANGELOG.md | 11 ++++++ plugins/techdocs/package.json | 10 ++--- 77 files changed, 535 insertions(+), 263 deletions(-) delete mode 100644 .changeset/breezy-cobras-deny.md delete mode 100644 .changeset/chatty-radios-beam.md delete mode 100644 .changeset/clever-moons-shake.md delete mode 100644 .changeset/cli-links.md delete mode 100644 .changeset/cost-insights-quiet-bikes-occur.md delete mode 100644 .changeset/cost-insights-tiny-llamas-perform.md delete mode 100644 .changeset/curly-yaks-invite.md delete mode 100644 .changeset/dull-pans-sip.md delete mode 100644 .changeset/empty-kids-look.md delete mode 100644 .changeset/forty-buses-watch.md delete mode 100644 .changeset/long-flowers-wink.md delete mode 100644 .changeset/mighty-cycles-impress.md delete mode 100644 .changeset/new-ladybugs-nail.md delete mode 100644 .changeset/orange-cherries-run.md delete mode 100644 .changeset/poor-mails-marry.md delete mode 100644 .changeset/small-worms-check.md delete mode 100644 .changeset/tame-rockets-explode.md delete mode 100644 .changeset/tidy-brooms-look.md create mode 100644 plugins/search/CHANGELOG.md diff --git a/.changeset/breezy-cobras-deny.md b/.changeset/breezy-cobras-deny.md deleted file mode 100644 index 5f035c05ce..0000000000 --- a/.changeset/breezy-cobras-deny.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core': patch ---- - -Clear sidebar search field once a search is executed diff --git a/.changeset/chatty-radios-beam.md b/.changeset/chatty-radios-beam.md deleted file mode 100644 index 91ad76ee88..0000000000 --- a/.changeset/chatty-radios-beam.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/catalog-model': minor -'@backstage/plugin-kubernetes': minor -'@backstage/plugin-kubernetes-backend': minor ---- - -add kubernetes selector to component model diff --git a/.changeset/clever-moons-shake.md b/.changeset/clever-moons-shake.md deleted file mode 100644 index 0fb6c690c2..0000000000 --- a/.changeset/clever-moons-shake.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-common': patch ---- - -Added readTree support to AzureUrlReader diff --git a/.changeset/cli-links.md b/.changeset/cli-links.md deleted file mode 100644 index 0a9779de93..0000000000 --- a/.changeset/cli-links.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -The CLI now detects and transforms linked packages. You can link in external packages by adding them to both the `lerna.json` and `package.json` workspace paths. diff --git a/.changeset/cost-insights-quiet-bikes-occur.md b/.changeset/cost-insights-quiet-bikes-occur.md deleted file mode 100644 index aac283bbd4..0000000000 --- a/.changeset/cost-insights-quiet-bikes-occur.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-cost-insights': patch ---- - -fix product icon configuration diff --git a/.changeset/cost-insights-tiny-llamas-perform.md b/.changeset/cost-insights-tiny-llamas-perform.md deleted file mode 100644 index c81c28f8f8..0000000000 --- a/.changeset/cost-insights-tiny-llamas-perform.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-cost-insights': patch ---- - -truncate large percentages > 1000% diff --git a/.changeset/curly-yaks-invite.md b/.changeset/curly-yaks-invite.md deleted file mode 100644 index b9191a3895..0000000000 --- a/.changeset/curly-yaks-invite.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': patch ---- - -Add support for reading groups and users from the Microsoft Graph API. diff --git a/.changeset/dull-pans-sip.md b/.changeset/dull-pans-sip.md deleted file mode 100644 index 13616c1f8d..0000000000 --- a/.changeset/dull-pans-sip.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-scaffolder': patch -'@backstage/plugin-scaffolder-backend': patch ---- - -Move constructing the catalog-info.yaml URL for scaffolded components to the publishers diff --git a/.changeset/empty-kids-look.md b/.changeset/empty-kids-look.md deleted file mode 100644 index 680cd7f25c..0000000000 --- a/.changeset/empty-kids-look.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-register-component': patch ---- - -Remove catalog link on validate popup diff --git a/.changeset/forty-buses-watch.md b/.changeset/forty-buses-watch.md deleted file mode 100644 index 4074566fe7..0000000000 --- a/.changeset/forty-buses-watch.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -'@backstage/catalog-model': patch -'@backstage/plugin-catalog-backend': patch ---- - -Marked the `Group` entity fields `ancestors` and `descendants` for deprecation on Dec 6th, 2020. See https://github.com/backstage/backstage/issues/3049 for details. - -Code that consumes these fields should remove those usages as soon as possible. There is no current or planned replacement for these fields. - -The BuiltinKindsEntityProcessor has been updated to inject these fields as empty arrays if they are missing. Therefore, if you are on a catalog instance that uses the updated version of this code, you can start removing the fields from your source catalog-info.yaml data as well, without breaking validation. - -After Dec 6th, the fields will be removed from types and classes of the Backstage repository. At the first release after that, they will not be present in released packages either. - -If your catalog-info.yaml files still contain these fields after the deletion, they will still be valid and your ingestion will not break, but they won't be visible in the types for consuming code. diff --git a/.changeset/long-flowers-wink.md b/.changeset/long-flowers-wink.md deleted file mode 100644 index 7f9cf12315..0000000000 --- a/.changeset/long-flowers-wink.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-sonarqube': patch ---- - -Add configuration schema diff --git a/.changeset/mighty-cycles-impress.md b/.changeset/mighty-cycles-impress.md deleted file mode 100644 index 56ec35098f..0000000000 --- a/.changeset/mighty-cycles-impress.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-circleci': patch ---- - -Improved CircleCI builds table to show more information and relevant links diff --git a/.changeset/new-ladybugs-nail.md b/.changeset/new-ladybugs-nail.md deleted file mode 100644 index 5351b6dff3..0000000000 --- a/.changeset/new-ladybugs-nail.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -New lint rule to disallow assertions and promote `as` assertions. - @typescript-eslint/consistent-type-assertions diff --git a/.changeset/orange-cherries-run.md b/.changeset/orange-cherries-run.md deleted file mode 100644 index 3c06c27b47..0000000000 --- a/.changeset/orange-cherries-run.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Add experimental backend:bundle command diff --git a/.changeset/poor-mails-marry.md b/.changeset/poor-mails-marry.md deleted file mode 100644 index fdc7e6f1b8..0000000000 --- a/.changeset/poor-mails-marry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Add new `versions:check` and `versions:bump` commands to simplify version management and avoid conflicts diff --git a/.changeset/small-worms-check.md b/.changeset/small-worms-check.md deleted file mode 100644 index e39265f8f9..0000000000 --- a/.changeset/small-worms-check.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'example-app': patch -'@backstage/plugin-search': patch ---- - -Using the search field in the sidebar now navigates to the search result page. diff --git a/.changeset/tame-rockets-explode.md b/.changeset/tame-rockets-explode.md deleted file mode 100644 index 123472701e..0000000000 --- a/.changeset/tame-rockets-explode.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-common': patch ---- - -Make integration host and url configurations visible in the frontend diff --git a/.changeset/tidy-brooms-look.md b/.changeset/tidy-brooms-look.md deleted file mode 100644 index f2c2a9064e..0000000000 --- a/.changeset/tidy-brooms-look.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': patch ---- - -Fix React entity YAML filename to new standard diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index ab00924a26..f70cd198b4 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,42 @@ # example-app +## 0.2.3 + +### Patch Changes + +- 475fc0aaa: Using the search field in the sidebar now navigates to the search result page. +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [29a0ccab2] +- Updated dependencies [8e6728e25] +- Updated dependencies [c93a14b49] +- Updated dependencies [ef2831dde] +- Updated dependencies [2a71f4bab] +- Updated dependencies [1185919f3] +- Updated dependencies [a8de7f554] +- Updated dependencies [faf311c26] +- Updated dependencies [31d8b6979] +- Updated dependencies [991345969] +- Updated dependencies [475fc0aaa] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-kubernetes@0.3.0 + - @backstage/cli@0.3.1 + - @backstage/plugin-cost-insights@0.4.1 + - @backstage/plugin-scaffolder@0.3.1 + - @backstage/plugin-register-component@0.2.2 + - @backstage/plugin-circleci@0.2.2 + - @backstage/plugin-search@0.2.1 + - @backstage/plugin-api-docs@0.2.2 + - @backstage/plugin-catalog@0.2.3 + - @backstage/plugin-cloudbuild@0.2.2 + - @backstage/plugin-github-actions@0.2.2 + - @backstage/plugin-jenkins@0.3.1 + - @backstage/plugin-lighthouse@0.2.3 + - @backstage/plugin-rollbar@0.2.3 + - @backstage/plugin-sentry@0.2.3 + - @backstage/plugin-techdocs@0.2.3 + ## 0.2.2 ### Patch Changes diff --git a/packages/app/package.json b/packages/app/package.json index 042fd47188..9e54005cd3 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,33 +1,33 @@ { "name": "example-app", - "version": "0.2.2", + "version": "0.2.3", "private": true, "bundled": true, "dependencies": { - "@backstage/catalog-model": "^0.2.0", - "@backstage/cli": "^0.3.0", - "@backstage/core": "^0.3.1", - "@backstage/plugin-api-docs": "^0.2.1", - "@backstage/plugin-catalog": "^0.2.2", - "@backstage/plugin-circleci": "^0.2.1", - "@backstage/plugin-cloudbuild": "^0.2.1", - "@backstage/plugin-cost-insights": "^0.4.0", + "@backstage/catalog-model": "^0.3.0", + "@backstage/cli": "^0.3.1", + "@backstage/core": "^0.3.2", + "@backstage/plugin-api-docs": "^0.2.2", + "@backstage/plugin-catalog": "^0.2.3", + "@backstage/plugin-circleci": "^0.2.2", + "@backstage/plugin-cloudbuild": "^0.2.2", + "@backstage/plugin-cost-insights": "^0.4.1", "@backstage/plugin-explore": "^0.2.1", "@backstage/plugin-gcp-projects": "^0.2.1", - "@backstage/plugin-github-actions": "^0.2.1", + "@backstage/plugin-github-actions": "^0.2.2", "@backstage/plugin-gitops-profiles": "^0.2.1", "@backstage/plugin-graphiql": "^0.2.1", - "@backstage/plugin-jenkins": "^0.3.0", - "@backstage/plugin-kubernetes": "^0.2.1", - "@backstage/plugin-lighthouse": "^0.2.2", + "@backstage/plugin-jenkins": "^0.3.1", + "@backstage/plugin-kubernetes": "^0.3.0", + "@backstage/plugin-lighthouse": "^0.2.3", "@backstage/plugin-newrelic": "^0.2.1", - "@backstage/plugin-register-component": "^0.2.1", - "@backstage/plugin-rollbar": "^0.2.2", - "@backstage/plugin-scaffolder": "^0.3.0", - "@backstage/plugin-sentry": "^0.2.2", - "@backstage/plugin-search": "^0.2.0", + "@backstage/plugin-register-component": "^0.2.2", + "@backstage/plugin-rollbar": "^0.2.3", + "@backstage/plugin-scaffolder": "^0.3.1", + "@backstage/plugin-sentry": "^0.2.3", + "@backstage/plugin-search": "^0.2.1", "@backstage/plugin-tech-radar": "^0.3.0", - "@backstage/plugin-techdocs": "^0.2.2", + "@backstage/plugin-techdocs": "^0.2.3", "@backstage/plugin-user-settings": "^0.2.2", "@backstage/plugin-welcome": "^0.2.1", "@backstage/test-utils": "^0.1.3", diff --git a/packages/backend-common/CHANGELOG.md b/packages/backend-common/CHANGELOG.md index 4822032c7f..793a554905 100644 --- a/packages/backend-common/CHANGELOG.md +++ b/packages/backend-common/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/backend-common +## 0.3.1 + +### Patch Changes + +- bff3305aa: Added readTree support to AzureUrlReader +- b47dce06f: Make integration host and url configurations visible in the frontend + ## 0.3.0 ### Minor Changes diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index b22e4638f5..731e2679cb 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-common", "description": "Common functionality library for Backstage backends", - "version": "0.3.0", + "version": "0.3.1", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -67,7 +67,7 @@ } }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/test-utils": "^0.1.3", "@types/archiver": "^3.1.1", "@types/compression": "^1.7.0", diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index a8bc1fef7d..bc2a08e983 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,26 @@ # example-backend +## 0.2.3 + +### Patch Changes + +- Updated dependencies [1166fcc36] +- Updated dependencies [bff3305aa] +- Updated dependencies [0c2121240] +- Updated dependencies [ef2831dde] +- Updated dependencies [1185919f3] +- Updated dependencies [475fc0aaa] +- Updated dependencies [b47dce06f] +- Updated dependencies [5a1d8dca3] + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-kubernetes-backend@0.2.0 + - @backstage/backend-common@0.3.1 + - @backstage/plugin-catalog-backend@0.2.2 + - @backstage/plugin-scaffolder-backend@0.3.2 + - example-app@0.2.3 + - @backstage/plugin-auth-backend@0.2.3 + - @backstage/plugin-techdocs-backend@0.2.2 + ## 0.2.2 ### Patch Changes diff --git a/packages/backend/package.json b/packages/backend/package.json index 014a3f78d5..5d3678a284 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.2.2", + "version": "0.2.3", "main": "dist/index.cjs.js", "types": "src/index.ts", "private": true, @@ -18,24 +18,24 @@ "migrate:create": "knex migrate:make -x ts" }, "dependencies": { - "@backstage/backend-common": "^0.3.0", - "@backstage/catalog-model": "^0.2.0", + "@backstage/backend-common": "^0.3.1", + "@backstage/catalog-model": "^0.3.0", "@backstage/config": "^0.1.1", "@backstage/plugin-app-backend": "^0.3.0", - "@backstage/plugin-auth-backend": "^0.2.2", - "@backstage/plugin-catalog-backend": "^0.2.1", + "@backstage/plugin-auth-backend": "^0.2.3", + "@backstage/plugin-catalog-backend": "^0.2.2", "@backstage/plugin-graphql-backend": "^0.1.3", - "@backstage/plugin-kubernetes-backend": "^0.1.3", + "@backstage/plugin-kubernetes-backend": "^0.2.0", "@backstage/plugin-proxy-backend": "^0.2.1", "@backstage/plugin-rollbar-backend": "^0.1.3", - "@backstage/plugin-scaffolder-backend": "^0.3.1", + "@backstage/plugin-scaffolder-backend": "^0.3.2", "@backstage/plugin-sentry-backend": "^0.1.3", - "@backstage/plugin-techdocs-backend": "^0.2.1", + "@backstage/plugin-techdocs-backend": "^0.2.2", "@gitbeaker/node": "^25.2.0", "@octokit/rest": "^18.0.0", "azure-devops-node-api": "^10.1.1", "dockerode": "^3.2.0", - "example-app": "^0.2.2", + "example-app": "^0.2.3", "express": "^4.17.1", "express-promise-router": "^3.0.3", "knex": "^0.21.6", @@ -45,7 +45,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@types/dockerode": "^2.5.32", "@types/express": "^4.17.6", "@types/express-serve-static-core": "^4.17.5", diff --git a/packages/catalog-client/CHANGELOG.md b/packages/catalog-client/CHANGELOG.md index 574cd301bf..3454a6c8e0 100644 --- a/packages/catalog-client/CHANGELOG.md +++ b/packages/catalog-client/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/catalog-client +## 0.3.1 + +### Patch Changes + +- Updated dependencies [1166fcc36] +- Updated dependencies [1185919f3] + - @backstage/catalog-model@0.3.0 + ## 0.3.0 ### Minor Changes diff --git a/packages/catalog-client/package.json b/packages/catalog-client/package.json index 2482f9579d..4509b7d547 100644 --- a/packages/catalog-client/package.json +++ b/packages/catalog-client/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/catalog-client", - "version": "0.3.0", + "version": "0.3.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,12 +20,12 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.2.0", + "@backstage/catalog-model": "^0.3.0", "@backstage/config": "^0.1.1", "cross-fetch": "^3.0.6" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@types/jest": "^26.0.7", "msw": "^0.21.2" }, diff --git a/packages/catalog-model/CHANGELOG.md b/packages/catalog-model/CHANGELOG.md index 051aa82c80..10bf540b46 100644 --- a/packages/catalog-model/CHANGELOG.md +++ b/packages/catalog-model/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/catalog-model +## 0.3.0 + +### Minor Changes + +- 1166fcc36: add kubernetes selector to component model + +### Patch Changes + +- 1185919f3: Marked the `Group` entity fields `ancestors` and `descendants` for deprecation on Dec 6th, 2020. See https://github.com/backstage/backstage/issues/3049 for details. + + Code that consumes these fields should remove those usages as soon as possible. There is no current or planned replacement for these fields. + + The BuiltinKindsEntityProcessor has been updated to inject these fields as empty arrays if they are missing. Therefore, if you are on a catalog instance that uses the updated version of this code, you can start removing the fields from your source catalog-info.yaml data as well, without breaking validation. + + After Dec 6th, the fields will be removed from types and classes of the Backstage repository. At the first release after that, they will not be present in released packages either. + + If your catalog-info.yaml files still contain these fields after the deletion, they will still be valid and your ingestion will not break, but they won't be visible in the types for consuming code. + ## 0.2.0 ### Minor Changes diff --git a/packages/catalog-model/package.json b/packages/catalog-model/package.json index e712e66f58..7956454ca9 100644 --- a/packages/catalog-model/package.json +++ b/packages/catalog-model/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/catalog-model", - "version": "0.2.0", + "version": "0.3.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,7 +29,7 @@ "yup": "^0.29.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@types/express": "^4.17.6", "@types/jest": "^26.0.7", "@types/lodash": "^4.14.151", diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index a5bcc6bcfd..d198444c86 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/cli +## 0.3.1 + +### Patch Changes + +- 29a0ccab2: The CLI now detects and transforms linked packages. You can link in external packages by adding them to both the `lerna.json` and `package.json` workspace paths. +- faf311c26: New lint rule to disallow assertions and promote `as` assertions. - @typescript-eslint/consistent-type-assertions +- 31d8b6979: Add experimental backend:bundle command +- 991345969: Add new `versions:check` and `versions:bump` commands to simplify version management and avoid conflicts + ## 0.3.0 ### Minor Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index eb56b19971..034c8a3de5 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/cli", "description": "CLI for developing Backstage plugins and apps", - "version": "0.3.0", + "version": "0.3.1", "private": false, "publishConfig": { "access": "public" @@ -111,9 +111,9 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/backend-common": "^0.3.0", + "@backstage/backend-common": "^0.3.1", "@backstage/config": "^0.1.1", - "@backstage/core": "^0.3.1", + "@backstage/core": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@backstage/theme": "^0.2.1", diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 213e2b5246..d29bc53452 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/core +## 0.3.2 + +### Patch Changes + +- 475fc0aaa: Clear sidebar search field once a search is executed + ## 0.3.1 ### Patch Changes diff --git a/packages/core/package.json b/packages/core/package.json index d65f0a16e5..f667ffe7be 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core", "description": "Core API used by Backstage plugins and apps", - "version": "0.3.1", + "version": "0.3.2", "private": false, "publishConfig": { "access": "public", @@ -64,7 +64,7 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/packages/create-app/package.json b/packages/create-app/package.json index c01d4d3dbb..6e91a916a1 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -37,28 +37,28 @@ "recursive-readdir": "^2.2.2" }, "devDependencies": { - "@backstage/backend-common": "^0.3.0", - "@backstage/catalog-model": "^0.2.0", - "@backstage/cli": "^0.3.0", + "@backstage/backend-common": "^0.3.1", + "@backstage/catalog-model": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/config": "^0.1.1", - "@backstage/core": "^0.3.1", - "@backstage/plugin-api-docs": "^0.2.1", + "@backstage/core": "^0.3.2", + "@backstage/plugin-api-docs": "^0.2.2", "@backstage/plugin-app-backend": "^0.3.0", - "@backstage/plugin-auth-backend": "^0.2.2", - "@backstage/plugin-catalog": "^0.2.2", - "@backstage/plugin-catalog-backend": "^0.2.1", - "@backstage/plugin-circleci": "^0.2.1", + "@backstage/plugin-auth-backend": "^0.2.3", + "@backstage/plugin-catalog": "^0.2.3", + "@backstage/plugin-catalog-backend": "^0.2.2", + "@backstage/plugin-circleci": "^0.2.2", "@backstage/plugin-explore": "^0.2.1", - "@backstage/plugin-github-actions": "^0.2.1", - "@backstage/plugin-lighthouse": "^0.2.2", + "@backstage/plugin-github-actions": "^0.2.2", + "@backstage/plugin-lighthouse": "^0.2.3", "@backstage/plugin-proxy-backend": "^0.2.1", - "@backstage/plugin-register-component": "^0.2.1", + "@backstage/plugin-register-component": "^0.2.2", "@backstage/plugin-rollbar-backend": "^0.1.3", - "@backstage/plugin-scaffolder": "^0.3.0", - "@backstage/plugin-scaffolder-backend": "^0.3.1", + "@backstage/plugin-scaffolder": "^0.3.1", + "@backstage/plugin-scaffolder-backend": "^0.3.2", "@backstage/plugin-tech-radar": "^0.3.0", - "@backstage/plugin-techdocs": "^0.2.2", - "@backstage/plugin-techdocs-backend": "^0.2.1", + "@backstage/plugin-techdocs": "^0.2.3", + "@backstage/plugin-techdocs-backend": "^0.2.2", "@backstage/plugin-user-settings": "^0.2.2", "@backstage/test-utils": "^0.1.3", "@backstage/theme": "^0.2.1", diff --git a/plugins/api-docs/CHANGELOG.md b/plugins/api-docs/CHANGELOG.md index 5f1ad97e64..60a5caf55b 100644 --- a/plugins/api-docs/CHANGELOG.md +++ b/plugins/api-docs/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-api-docs +## 0.2.2 + +### Patch Changes + +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [1185919f3] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-catalog@0.2.3 + ## 0.2.1 ### Patch Changes diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index bfefaef3a5..691f2680b2 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-api-docs", - "version": "0.2.1", + "version": "0.2.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,9 +20,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.1", - "@backstage/plugin-catalog": "^0.2.2", + "@backstage/catalog-model": "^0.3.0", + "@backstage/core": "^0.3.2", + "@backstage/plugin-catalog": "^0.2.3", "@backstage/theme": "^0.2.1", "@kyma-project/asyncapi-react": "^0.14.2", "@material-icons/font": "^1.0.2", @@ -39,7 +39,7 @@ "swagger-ui-react": "^3.31.1" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/auth-backend/CHANGELOG.md b/plugins/auth-backend/CHANGELOG.md index 546855c92f..4a2161ad71 100644 --- a/plugins/auth-backend/CHANGELOG.md +++ b/plugins/auth-backend/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-auth-backend +## 0.2.3 + +### Patch Changes + +- Updated dependencies [1166fcc36] +- Updated dependencies [bff3305aa] +- Updated dependencies [1185919f3] +- Updated dependencies [b47dce06f] + - @backstage/catalog-model@0.3.0 + - @backstage/backend-common@0.3.1 + - @backstage/catalog-client@0.3.1 + ## 0.2.2 ### Patch Changes diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 4d1e52aa21..43e2c59f88 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend", - "version": "0.2.2", + "version": "0.2.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,9 +20,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.3.0", - "@backstage/catalog-client": "^0.3.0", - "@backstage/catalog-model": "^0.2.0", + "@backstage/backend-common": "^0.3.1", + "@backstage/catalog-client": "^0.3.1", + "@backstage/catalog-model": "^0.3.0", "@backstage/config": "^0.1.1", "@types/express": "^4.17.6", "compression": "^1.7.4", @@ -55,7 +55,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@types/body-parser": "^1.19.0", "@types/cookie-parser": "^1.4.2", "@types/express-session": "^1.17.2", diff --git a/plugins/catalog-backend/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md index 1a3199ee14..3bf25a494b 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-catalog-backend +## 0.2.2 + +### Patch Changes + +- 0c2121240: Add support for reading groups and users from the Microsoft Graph API. +- 1185919f3: Marked the `Group` entity fields `ancestors` and `descendants` for deprecation on Dec 6th, 2020. See https://github.com/backstage/backstage/issues/3049 for details. + + Code that consumes these fields should remove those usages as soon as possible. There is no current or planned replacement for these fields. + + The BuiltinKindsEntityProcessor has been updated to inject these fields as empty arrays if they are missing. Therefore, if you are on a catalog instance that uses the updated version of this code, you can start removing the fields from your source catalog-info.yaml data as well, without breaking validation. + + After Dec 6th, the fields will be removed from types and classes of the Backstage repository. At the first release after that, they will not be present in released packages either. + + If your catalog-info.yaml files still contain these fields after the deletion, they will still be valid and your ingestion will not break, but they won't be visible in the types for consuming code. + +- Updated dependencies [1166fcc36] +- Updated dependencies [bff3305aa] +- Updated dependencies [1185919f3] +- Updated dependencies [b47dce06f] + - @backstage/catalog-model@0.3.0 + - @backstage/backend-common@0.3.1 + ## 0.2.1 ### Patch Changes diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 12a17d6365..51a9978f30 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend", - "version": "0.2.1", + "version": "0.2.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,8 +21,8 @@ }, "dependencies": { "@azure/msal-node": "^1.0.0-alpha.8", - "@backstage/backend-common": "^0.3.0", - "@backstage/catalog-model": "^0.2.0", + "@backstage/backend-common": "^0.3.1", + "@backstage/catalog-model": "^0.3.0", "@backstage/config": "^0.1.1", "@octokit/graphql": "^4.5.6", "@types/express": "^4.17.6", @@ -47,7 +47,7 @@ "yup": "^0.29.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/test-utils": "^0.1.3", "@types/core-js": "^2.5.4", "@types/git-url-parse": "^9.0.0", diff --git a/plugins/catalog-graphql/CHANGELOG.md b/plugins/catalog-graphql/CHANGELOG.md index 35737b7dd1..56911b9c45 100644 --- a/plugins/catalog-graphql/CHANGELOG.md +++ b/plugins/catalog-graphql/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-catalog-graphql +## 0.2.2 + +### Patch Changes + +- Updated dependencies [1166fcc36] +- Updated dependencies [bff3305aa] +- Updated dependencies [1185919f3] +- Updated dependencies [b47dce06f] + - @backstage/catalog-model@0.3.0 + - @backstage/backend-common@0.3.1 + ## 0.2.1 ### Patch Changes diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json index c55be89b62..a489d66e5b 100644 --- a/plugins/catalog-graphql/package.json +++ b/plugins/catalog-graphql/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-graphql", - "version": "0.2.1", + "version": "0.2.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,8 +20,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.3.0", - "@backstage/catalog-model": "^0.2.0", + "@backstage/backend-common": "^0.3.1", + "@backstage/catalog-model": "^0.3.0", "@backstage/config": "^0.1.1", "@graphql-modules/core": "^0.7.17", "apollo-server": "^2.16.1", @@ -32,7 +32,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/test-utils": "^0.1.3", "@graphql-codegen/cli": "^1.17.7", "@graphql-codegen/typescript": "^1.17.7", diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index 1b8b884f1c..9137b7e948 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-catalog +## 0.2.3 + +### Patch Changes + +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [ef2831dde] +- Updated dependencies [1185919f3] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-scaffolder@0.3.1 + - @backstage/catalog-client@0.3.1 + - @backstage/plugin-techdocs@0.2.3 + ## 0.2.2 ### Patch Changes diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 2c65096fd6..7b58358734 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog", - "version": "0.2.2", + "version": "0.2.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,11 +21,11 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-client": "^0.3.0", - "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.1", - "@backstage/plugin-scaffolder": "^0.3.0", - "@backstage/plugin-techdocs": "^0.2.2", + "@backstage/catalog-client": "^0.3.1", + "@backstage/catalog-model": "^0.3.0", + "@backstage/core": "^0.3.2", + "@backstage/plugin-scaffolder": "^0.3.1", + "@backstage/plugin-techdocs": "^0.2.3", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -43,7 +43,7 @@ "swr": "^0.3.0" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@microsoft/microsoft-graph-types": "^1.25.0", diff --git a/plugins/circleci/CHANGELOG.md b/plugins/circleci/CHANGELOG.md index 2894ce1bd3..a81531e28d 100644 --- a/plugins/circleci/CHANGELOG.md +++ b/plugins/circleci/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-circleci +## 0.2.2 + +### Patch Changes + +- a8de7f554: Improved CircleCI builds table to show more information and relevant links +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [1185919f3] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-catalog@0.2.3 + ## 0.2.1 ### Patch Changes diff --git a/plugins/circleci/package.json b/plugins/circleci/package.json index eaa8c319a0..fd32ff9891 100644 --- a/plugins/circleci/package.json +++ b/plugins/circleci/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-circleci", - "version": "0.2.1", + "version": "0.2.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,9 +21,9 @@ "postpack": "backstage-cli postpack" }, "dependencies": { - "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.1", - "@backstage/plugin-catalog": "^0.2.2", + "@backstage/catalog-model": "^0.3.0", + "@backstage/core": "^0.3.2", + "@backstage/plugin-catalog": "^0.2.3", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -40,7 +40,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/cloudbuild/CHANGELOG.md b/plugins/cloudbuild/CHANGELOG.md index 9d02fd1ea3..4cb2d01377 100644 --- a/plugins/cloudbuild/CHANGELOG.md +++ b/plugins/cloudbuild/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-cloudbuild +## 0.2.2 + +### Patch Changes + +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [1185919f3] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-catalog@0.2.3 + ## 0.2.1 ### Patch Changes diff --git a/plugins/cloudbuild/package.json b/plugins/cloudbuild/package.json index c0b36ef1e7..3e7f0c9175 100644 --- a/plugins/cloudbuild/package.json +++ b/plugins/cloudbuild/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-cloudbuild", - "version": "0.2.1", + "version": "0.2.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,9 +20,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.1", - "@backstage/plugin-catalog": "^0.2.2", + "@backstage/catalog-model": "^0.3.0", + "@backstage/core": "^0.3.2", + "@backstage/plugin-catalog": "^0.2.3", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -39,7 +39,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/cost-insights/CHANGELOG.md b/plugins/cost-insights/CHANGELOG.md index 157d92f251..b7bdfa9d50 100644 --- a/plugins/cost-insights/CHANGELOG.md +++ b/plugins/cost-insights/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-cost-insights +## 0.4.1 + +### Patch Changes + +- 8e6728e25: fix product icon configuration +- c93a14b49: truncate large percentages > 1000% +- Updated dependencies [475fc0aaa] + - @backstage/core@0.3.2 + ## 0.4.0 ### Minor Changes diff --git a/plugins/cost-insights/package.json b/plugins/cost-insights/package.json index 8ebeac91c0..06083b855b 100644 --- a/plugins/cost-insights/package.json +++ b/plugins/cost-insights/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-cost-insights", - "version": "0.4.0", + "version": "0.4.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -22,7 +22,7 @@ }, "dependencies": { "@backstage/config": "^0.1.1", - "@backstage/core": "^0.3.1", + "@backstage/core": "^0.3.2", "@backstage/test-utils": "^0.1.3", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", @@ -46,7 +46,7 @@ "yup": "^0.29.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/github-actions/CHANGELOG.md b/plugins/github-actions/CHANGELOG.md index b679d6844b..96f3876802 100644 --- a/plugins/github-actions/CHANGELOG.md +++ b/plugins/github-actions/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-github-actions +## 0.2.2 + +### Patch Changes + +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [1185919f3] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-catalog@0.2.3 + ## 0.2.1 ### Patch Changes diff --git a/plugins/github-actions/package.json b/plugins/github-actions/package.json index 49ecfc5423..b1924034f1 100644 --- a/plugins/github-actions/package.json +++ b/plugins/github-actions/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-github-actions", - "version": "0.2.1", + "version": "0.2.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,10 +21,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.1", + "@backstage/catalog-model": "^0.3.0", + "@backstage/core": "^0.3.2", "@backstage/core-api": "^0.2.1", - "@backstage/plugin-catalog": "^0.2.2", + "@backstage/plugin-catalog": "^0.2.3", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -40,7 +40,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/jenkins/CHANGELOG.md b/plugins/jenkins/CHANGELOG.md index 44febbc63d..aeeb069f19 100644 --- a/plugins/jenkins/CHANGELOG.md +++ b/plugins/jenkins/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-jenkins +## 0.3.1 + +### Patch Changes + +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [1185919f3] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-catalog@0.2.3 + ## 0.3.0 ### Minor Changes diff --git a/plugins/jenkins/package.json b/plugins/jenkins/package.json index 0279592a75..961f551dfa 100644 --- a/plugins/jenkins/package.json +++ b/plugins/jenkins/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-jenkins", - "version": "0.3.0", + "version": "0.3.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,9 +21,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.1", - "@backstage/plugin-catalog": "^0.2.2", + "@backstage/catalog-model": "^0.3.0", + "@backstage/core": "^0.3.2", + "@backstage/plugin-catalog": "^0.2.3", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -36,7 +36,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/kubernetes-backend/CHANGELOG.md b/plugins/kubernetes-backend/CHANGELOG.md index 7c55a7407d..2877264e54 100644 --- a/plugins/kubernetes-backend/CHANGELOG.md +++ b/plugins/kubernetes-backend/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-kubernetes-backend +## 0.2.0 + +### Minor Changes + +- 1166fcc36: add kubernetes selector to component model + +### Patch Changes + +- Updated dependencies [1166fcc36] +- Updated dependencies [bff3305aa] +- Updated dependencies [1185919f3] +- Updated dependencies [b47dce06f] + - @backstage/catalog-model@0.3.0 + - @backstage/backend-common@0.3.1 + ## 0.1.3 ### Patch Changes diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index be7b86dd52..e607e51f80 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-backend", - "version": "0.1.3", + "version": "0.2.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,8 +20,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.3.0", - "@backstage/catalog-model": "^0.2.0", + "@backstage/backend-common": "^0.3.1", + "@backstage/catalog-model": "^0.3.0", "@backstage/config": "^0.1.1", "@kubernetes/client-node": "^0.12.1", "@types/express": "^4.17.6", @@ -38,7 +38,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "supertest": "^4.0.2" }, "files": [ diff --git a/plugins/kubernetes/CHANGELOG.md b/plugins/kubernetes/CHANGELOG.md index e5611e4953..652fc394a2 100644 --- a/plugins/kubernetes/CHANGELOG.md +++ b/plugins/kubernetes/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-kubernetes +## 0.3.0 + +### Minor Changes + +- 1166fcc36: add kubernetes selector to component model + +### Patch Changes + +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [1185919f3] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-kubernetes-backend@0.2.0 + ## 0.2.1 ### Patch Changes diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index 36954cc5b9..2afff2ae3f 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes", - "version": "0.2.1", + "version": "0.3.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,10 +20,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.2.0", + "@backstage/catalog-model": "^0.3.0", "@backstage/config": "^0.1.1", - "@backstage/core": "^0.3.1", - "@backstage/plugin-kubernetes-backend": "^0.1.3", + "@backstage/core": "^0.3.2", + "@backstage/plugin-kubernetes-backend": "^0.2.0", "@backstage/theme": "^0.2.1", "@kubernetes/client-node": "^0.12.1", "@material-ui/core": "^4.11.0", @@ -35,7 +35,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/lighthouse/CHANGELOG.md b/plugins/lighthouse/CHANGELOG.md index 666e7c4411..e35f3dece0 100644 --- a/plugins/lighthouse/CHANGELOG.md +++ b/plugins/lighthouse/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-lighthouse +## 0.2.3 + +### Patch Changes + +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [1185919f3] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-catalog@0.2.3 + ## 0.2.2 ### Patch Changes diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json index 90230f5248..ad1264cfbb 100644 --- a/plugins/lighthouse/package.json +++ b/plugins/lighthouse/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-lighthouse", - "version": "0.2.2", + "version": "0.2.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,11 +21,11 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/catalog-model": "^0.2.0", + "@backstage/catalog-model": "^0.3.0", "@backstage/config": "^0.1.1", - "@backstage/core": "^0.3.1", + "@backstage/core": "^0.3.2", "@backstage/core-api": "^0.2.1", - "@backstage/plugin-catalog": "^0.2.2", + "@backstage/plugin-catalog": "^0.2.3", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -38,7 +38,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/register-component/CHANGELOG.md b/plugins/register-component/CHANGELOG.md index 3e966ef0d0..c569046855 100644 --- a/plugins/register-component/CHANGELOG.md +++ b/plugins/register-component/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-register-component +## 0.2.2 + +### Patch Changes + +- 2a71f4bab: Remove catalog link on validate popup +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [1185919f3] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-catalog@0.2.3 + ## 0.2.1 ### Patch Changes diff --git a/plugins/register-component/package.json b/plugins/register-component/package.json index 06ae17cb0e..1564faae0e 100644 --- a/plugins/register-component/package.json +++ b/plugins/register-component/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-register-component", - "version": "0.2.1", + "version": "0.2.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,9 +21,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.1", - "@backstage/plugin-catalog": "^0.2.2", + "@backstage/catalog-model": "^0.3.0", + "@backstage/core": "^0.3.2", + "@backstage/plugin-catalog": "^0.2.3", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -36,7 +36,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/rollbar/CHANGELOG.md b/plugins/rollbar/CHANGELOG.md index e4a6671f90..1ccfe16c41 100644 --- a/plugins/rollbar/CHANGELOG.md +++ b/plugins/rollbar/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-rollbar +## 0.2.3 + +### Patch Changes + +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [1185919f3] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-catalog@0.2.3 + ## 0.2.2 ### Patch Changes diff --git a/plugins/rollbar/package.json b/plugins/rollbar/package.json index c107573ca8..1177a83eb6 100644 --- a/plugins/rollbar/package.json +++ b/plugins/rollbar/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-rollbar", - "version": "0.2.2", + "version": "0.2.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,9 +21,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.1", - "@backstage/plugin-catalog": "^0.2.2", + "@backstage/catalog-model": "^0.3.0", + "@backstage/core": "^0.3.2", + "@backstage/plugin-catalog": "^0.2.3", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -37,7 +37,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index a388a29a00..ea7e5c85ee 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-scaffolder-backend +## 0.3.2 + +### Patch Changes + +- ef2831dde: Move constructing the catalog-info.yaml URL for scaffolded components to the publishers +- 5a1d8dca3: Fix React entity YAML filename to new standard +- Updated dependencies [1166fcc36] +- Updated dependencies [bff3305aa] +- Updated dependencies [1185919f3] +- Updated dependencies [b47dce06f] + - @backstage/catalog-model@0.3.0 + - @backstage/backend-common@0.3.1 + ## 0.3.1 ### Patch Changes diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 73c07d158f..a095167a08 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend", - "version": "0.3.1", + "version": "0.3.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,8 +20,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.3.0", - "@backstage/catalog-model": "^0.2.0", + "@backstage/backend-common": "^0.3.1", + "@backstage/catalog-model": "^0.3.0", "@backstage/config": "^0.1.1", "@gitbeaker/core": "^25.2.0", "@gitbeaker/node": "^25.2.0", @@ -48,7 +48,7 @@ "cross-fetch": "^3.0.6" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@octokit/types": "^5.4.1", "@types/fs-extra": "^9.0.1", "@types/git-url-parse": "^9.0.0", diff --git a/plugins/scaffolder/CHANGELOG.md b/plugins/scaffolder/CHANGELOG.md index 9eac595997..c78e05c3e0 100644 --- a/plugins/scaffolder/CHANGELOG.md +++ b/plugins/scaffolder/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-scaffolder +## 0.3.1 + +### Patch Changes + +- ef2831dde: Move constructing the catalog-info.yaml URL for scaffolded components to the publishers +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [1185919f3] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-catalog@0.2.3 + ## 0.3.0 ### Minor Changes diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 439fc0ad9a..791221bc62 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder", - "version": "0.3.0", + "version": "0.3.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,9 +21,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.1", - "@backstage/plugin-catalog": "^0.2.2", + "@backstage/catalog-model": "^0.3.0", + "@backstage/core": "^0.3.2", + "@backstage/plugin-catalog": "^0.2.3", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -41,7 +41,7 @@ "swr": "^0.3.0" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/search/CHANGELOG.md b/plugins/search/CHANGELOG.md new file mode 100644 index 0000000000..3545914e96 --- /dev/null +++ b/plugins/search/CHANGELOG.md @@ -0,0 +1,13 @@ +# @backstage/plugin-search + +## 0.2.1 + +### Patch Changes + +- 475fc0aaa: Using the search field in the sidebar now navigates to the search result page. +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [1185919f3] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-catalog@0.2.3 diff --git a/plugins/search/package.json b/plugins/search/package.json index df5c6c25b9..021e3f71f3 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search", - "version": "0.2.0", + "version": "0.2.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,9 +20,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.3.1", - "@backstage/plugin-catalog": "^0.2.2", - "@backstage/catalog-model": "^0.2.0", + "@backstage/core": "^0.3.2", + "@backstage/plugin-catalog": "^0.2.3", + "@backstage/catalog-model": "^0.3.0", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -34,7 +34,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/sentry/CHANGELOG.md b/plugins/sentry/CHANGELOG.md index 92327c1668..b388548d8e 100644 --- a/plugins/sentry/CHANGELOG.md +++ b/plugins/sentry/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-sentry +## 0.2.3 + +### Patch Changes + +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [1185919f3] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + ## 0.2.2 ### Patch Changes diff --git a/plugins/sentry/package.json b/plugins/sentry/package.json index 80a37c574e..4c10936148 100644 --- a/plugins/sentry/package.json +++ b/plugins/sentry/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-sentry", - "version": "0.2.2", + "version": "0.2.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,8 +21,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.1", + "@backstage/catalog-model": "^0.3.0", + "@backstage/core": "^0.3.2", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -36,7 +36,7 @@ "timeago.js": "^4.0.2" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/sonarqube/CHANGELOG.md b/plugins/sonarqube/CHANGELOG.md index 0296bd0416..9ebd73b812 100644 --- a/plugins/sonarqube/CHANGELOG.md +++ b/plugins/sonarqube/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-sonarqube +## 0.1.4 + +### Patch Changes + +- 26484d413: Add configuration schema +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [1185919f3] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + ## 0.1.3 ### Patch Changes diff --git a/plugins/sonarqube/package.json b/plugins/sonarqube/package.json index be8a878e50..6a9d0fe0d4 100644 --- a/plugins/sonarqube/package.json +++ b/plugins/sonarqube/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-sonarqube", - "version": "0.1.3", + "version": "0.1.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,8 +21,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.1", + "@backstage/catalog-model": "^0.3.0", + "@backstage/core": "^0.3.2", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -35,7 +35,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/techdocs-backend/CHANGELOG.md b/plugins/techdocs-backend/CHANGELOG.md index 4a3fbf6821..d9054230e2 100644 --- a/plugins/techdocs-backend/CHANGELOG.md +++ b/plugins/techdocs-backend/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-techdocs-backend +## 0.2.2 + +### Patch Changes + +- Updated dependencies [1166fcc36] +- Updated dependencies [bff3305aa] +- Updated dependencies [1185919f3] +- Updated dependencies [b47dce06f] + - @backstage/catalog-model@0.3.0 + - @backstage/backend-common@0.3.1 + ## 0.2.1 ### Patch Changes diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index b139bb9acf..1d9f3ea7c1 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-backend", - "version": "0.2.1", + "version": "0.2.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,8 +20,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.3.0", - "@backstage/catalog-model": "^0.2.0", + "@backstage/backend-common": "^0.3.1", + "@backstage/catalog-model": "^0.3.0", "@backstage/config": "^0.1.1", "@types/dockerode": "^2.5.34", "@types/express": "^4.17.6", @@ -37,7 +37,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "supertest": "^4.0.2" }, "files": [ diff --git a/plugins/techdocs/CHANGELOG.md b/plugins/techdocs/CHANGELOG.md index bd3ae21ab8..3f46e1358b 100644 --- a/plugins/techdocs/CHANGELOG.md +++ b/plugins/techdocs/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-techdocs +## 0.2.3 + +### Patch Changes + +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [1185919f3] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-catalog@0.2.3 + ## 0.2.2 ### Patch Changes diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index 35b196e124..23d7a25b57 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs", - "version": "0.2.2", + "version": "0.2.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,10 +21,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.1", + "@backstage/catalog-model": "^0.3.0", + "@backstage/core": "^0.3.2", "@backstage/core-api": "^0.2.1", - "@backstage/plugin-catalog": "^0.2.2", + "@backstage/plugin-catalog": "^0.2.3", "@backstage/test-utils": "^0.1.3", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", @@ -39,7 +39,7 @@ "sanitize-html": "^1.27.0" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", From 451edeee30198975e7be519dafbe38a6db1512fc Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 24 Nov 2020 14:59:04 +0100 Subject: [PATCH 207/430] chore: yarn lock fixes and resolution for catalog-model --- package.json | 3 ++- yarn.lock | 4 +--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 4140886bc3..3be94c19cf 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,8 @@ ] }, "resolutions": { - "**/@roadiehq/backstage-plugin-*/@backstage/core": "*" + "**/@roadiehq/**/@backstage/core": "*", + "**/@roadiehq/**/@backstage/catalog-model": "*" }, "version": "1.0.0", "devDependencies": { diff --git a/yarn.lock b/yarn.lock index 3679e7a60f..967fbb016d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1308,9 +1308,7 @@ to-fast-properties "^2.0.0" "@backstage/catalog-model@^0.2.0": - version "0.2.0" - resolved "https://registry.npmjs.org/@backstage/catalog-model/-/catalog-model-0.2.0.tgz#e3fe2a4ddeb6a9b6ec480c80cb2b9c39cb245576" - integrity sha512-Y1ocdRpBlxK/VrJQjHlQd0bgADECd1B2NRjwd8ss46ibT5hwLvMOfD80+Fa7oPLu0ktJrH4lq0pNIIJIml48zA== + version "0.3.0" dependencies: "@backstage/config" "^0.1.1" "@types/json-schema" "^7.0.5" From b0f286b83cee6dc513b56e0f069249874217988b Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Tue, 24 Nov 2020 16:02:44 +0100 Subject: [PATCH 208/430] Document Substitutions In The Descriptor Format --- .../software-catalog/descriptor-format.md | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/docs/features/software-catalog/descriptor-format.md b/docs/features/software-catalog/descriptor-format.md index 60f1901807..ff2f92beab 100644 --- a/docs/features/software-catalog/descriptor-format.md +++ b/docs/features/software-catalog/descriptor-format.md @@ -93,6 +93,41 @@ significance and have reserved purposes and distinct shapes. See below for details about these fields. +## Substitutions In The Descriptor Format + +The descriptor format supports substitutions using `$text`, `$json`, and +`$yaml`. + +Placeholders like `$json: https://example.com/entity.json` are substituded by +the content of the referenced file. Files can be referenced from any configured +integration similar to locations by passing an absolute URL. It's also possible +to reference relative files like `./referenced.yaml` from the same location. +There are three different types of placeholders: + +- `$text`: Interprets the content of the referenced file as plain text and embed + it as a string. +- `$json`: Interprets the content of the referenced file as JSON and embed the + parsed structure. +- `$yaml`: Interprets the content of the referenced file as YAML and embed the + parsed structure. + +For example, this can be used to load the definition of an API entity from a web +server and embed it into the entity: + +```yaml +apiVersion: backstage.io/v1alpha1 +kind: API +metadata: + name: petstore + description: The Petstore API +spec: + type: openapi + lifecycle: production + owner: petstore@example.com + definition: + $text: https://petstore.swagger.io/v2/swagger.json +``` + ## Common to All Kinds: The Envelope The root envelope object has the following structure. From 6579b08725d97309d3528f9d4159d120007ef0de Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Tue, 24 Nov 2020 16:06:30 +0100 Subject: [PATCH 209/430] Link to substitutions instead of duplicating the content --- .../software-catalog/descriptor-format.md | 2 +- .../well-known-annotations.md | 19 +++---------------- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/docs/features/software-catalog/descriptor-format.md b/docs/features/software-catalog/descriptor-format.md index ff2f92beab..7171e0c7b8 100644 --- a/docs/features/software-catalog/descriptor-format.md +++ b/docs/features/software-catalog/descriptor-format.md @@ -98,7 +98,7 @@ See below for details about these fields. The descriptor format supports substitutions using `$text`, `$json`, and `$yaml`. -Placeholders like `$json: https://example.com/entity.json` are substituded by +Placeholders like `$json: https://example.com/entity.json` are substituted by the content of the referenced file. Files can be referenced from any configured integration similar to locations by passing an absolute URL. It's also possible to reference relative files like `./referenced.yaml` from the same location. diff --git a/docs/features/software-catalog/well-known-annotations.md b/docs/features/software-catalog/well-known-annotations.md index 7d16427a3c..3627c74b9a 100644 --- a/docs/features/software-catalog/well-known-annotations.md +++ b/docs/features/software-catalog/well-known-annotations.md @@ -238,22 +238,9 @@ annotation, with the same value format. ### backstage.io/definition-at-location -This annotation allowed to load the API definition from another location. Now -placeholders can be used instead: - -``` -apiVersion: backstage.io/v1alpha1 -kind: API -metadata: - name: petstore - description: The Petstore API -spec: - type: openapi - lifecycle: production - owner: petstore@example.com - definition: - $text: https://petstore.swagger.io/v2/swagger.json -``` +This annotation allowed to load the API definition from another location. Use +[substitution](./descriptor-format.md#substitutions-in-the-descriptor-format) +instead. ## Links From 11e4a9cbe87677752c21cb37e5b9c587c21628d5 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Tue, 24 Nov 2020 16:23:41 +0100 Subject: [PATCH 210/430] Integrate review comments --- .../features/software-catalog/descriptor-format.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/features/software-catalog/descriptor-format.md b/docs/features/software-catalog/descriptor-format.md index 7171e0c7b8..f1d426f407 100644 --- a/docs/features/software-catalog/descriptor-format.md +++ b/docs/features/software-catalog/descriptor-format.md @@ -102,17 +102,19 @@ Placeholders like `$json: https://example.com/entity.json` are substituted by the content of the referenced file. Files can be referenced from any configured integration similar to locations by passing an absolute URL. It's also possible to reference relative files like `./referenced.yaml` from the same location. -There are three different types of placeholders: +Relative references are handled relative to the folder of the +`catalog-info.yaml` that contains the placeholder. There are three different +types of placeholders: -- `$text`: Interprets the content of the referenced file as plain text and embed - it as a string. -- `$json`: Interprets the content of the referenced file as JSON and embed the +- `$text`: Interprets the contents of the referenced file as plain text and + embeds it as a string. +- `$json`: Interprets the contents of the referenced file as JSON and embeds the parsed structure. -- `$yaml`: Interprets the content of the referenced file as YAML and embed the +- `$yaml`: Interprets the contents of the referenced file as YAML and embeds the parsed structure. For example, this can be used to load the definition of an API entity from a web -server and embed it into the entity: +server and embed it as a string in the field `spec.definition`: ```yaml apiVersion: backstage.io/v1alpha1 From 121af544b105421c8afb705f053e03b77fbf39d0 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 24 Nov 2020 17:29:29 +0100 Subject: [PATCH 211/430] feat: added config schema for k8s plugins --- plugins/kubernetes-backend/package.json | 4 ++- plugins/kubernetes-backend/schema.d.ts | 27 +++++++++++++++ plugins/kubernetes/package.json | 4 ++- plugins/kubernetes/schema.d.ts | 45 +++++++++++++++++++++++++ 4 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 plugins/kubernetes-backend/schema.d.ts create mode 100644 plugins/kubernetes/schema.d.ts diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index be7b86dd52..dfc9e5377c 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -10,6 +10,7 @@ "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, + "configSchema": "schema.d.ts", "scripts": { "start": "backstage-cli backend:dev", "build": "backstage-cli backend:build", @@ -42,6 +43,7 @@ "supertest": "^4.0.2" }, "files": [ - "dist" + "dist", + "schema.d.ts" ] } diff --git a/plugins/kubernetes-backend/schema.d.ts b/plugins/kubernetes-backend/schema.d.ts new file mode 100644 index 0000000000..862cb6d2ce --- /dev/null +++ b/plugins/kubernetes-backend/schema.d.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. + */ +export interface Config { + kubernetes?: { + serviceLocatorMethod: 'multiTenant'; + clusterLocatorMethods: 'config'[]; + clusters: { + url: string; + name: string; + serviceAccountToken: string; + authProvider: 'serviceAccount'; + }[]; + }; +} diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index 36954cc5b9..3b6d48249d 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -9,6 +9,7 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "configSchema": "schema.d.ts", "scripts": { "build": "backstage-cli plugin:build", "start": "backstage-cli plugin:serve", @@ -47,6 +48,7 @@ "msw": "^0.21.2" }, "files": [ - "dist" + "dist", + "schema.d.ts" ] } diff --git a/plugins/kubernetes/schema.d.ts b/plugins/kubernetes/schema.d.ts new file mode 100644 index 0000000000..f3f3c98f8c --- /dev/null +++ b/plugins/kubernetes/schema.d.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. + */ +export interface Config { + kubernetes?: { + /** + * @visibility frontend + */ + serviceLocatorMethod: 'multiTenant'; + /** + * @visibility frontend + */ + clusterLocatorMethods: 'config'[]; + clusters: { + /** + * @visibility frontend + */ + url: string; + /** + * @visibility frontend + */ + name: string; + /** + * @visibility secret + */ + serviceAccountToken: string; + /** + * @visibility frontend + */ + authProvider: 'serviceAccount'; + }[]; + }; +} From 0c0c48bcc5c166d001167263119d2ab6dda833e7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 24 Nov 2020 16:56:00 +0000 Subject: [PATCH 212/430] Version Packages --- .changeset/breezy-cobras-deny.md | 5 --- .changeset/chatty-radios-beam.md | 7 ---- .changeset/clever-moons-shake.md | 5 --- .changeset/cli-links.md | 5 --- .changeset/cost-insights-quiet-bikes-occur.md | 5 --- .../cost-insights-tiny-llamas-perform.md | 5 --- .changeset/curly-yaks-invite.md | 5 --- .changeset/dull-pans-sip.md | 6 --- .changeset/empty-kids-look.md | 5 --- .changeset/forty-buses-watch.md | 14 ------- .changeset/long-flowers-wink.md | 5 --- .changeset/mighty-cycles-impress.md | 5 --- .changeset/new-ladybugs-nail.md | 5 --- .changeset/orange-cherries-run.md | 5 --- .changeset/poor-mails-marry.md | 5 --- .changeset/small-worms-check.md | 6 --- .changeset/tame-rockets-explode.md | 5 --- .changeset/tidy-brooms-look.md | 5 --- packages/app/CHANGELOG.md | 37 ++++++++++++++++++ packages/app/package.json | 38 +++++++++---------- packages/backend-common/CHANGELOG.md | 7 ++++ packages/backend-common/package.json | 4 +- packages/backend/CHANGELOG.md | 21 ++++++++++ packages/backend/package.json | 20 +++++----- packages/catalog-client/CHANGELOG.md | 8 ++++ packages/catalog-client/package.json | 6 +-- packages/catalog-model/CHANGELOG.md | 18 +++++++++ packages/catalog-model/package.json | 4 +- packages/cli/CHANGELOG.md | 9 +++++ packages/cli/package.json | 6 +-- packages/core/CHANGELOG.md | 6 +++ packages/core/package.json | 4 +- packages/create-app/package.json | 32 ++++++++-------- plugins/api-docs/CHANGELOG.md | 11 ++++++ plugins/api-docs/package.json | 10 ++--- plugins/auth-backend/CHANGELOG.md | 12 ++++++ plugins/auth-backend/package.json | 10 ++--- plugins/catalog-backend/CHANGELOG.md | 22 +++++++++++ plugins/catalog-backend/package.json | 8 ++-- plugins/catalog-graphql/CHANGELOG.md | 11 ++++++ plugins/catalog-graphql/package.json | 8 ++-- plugins/catalog/CHANGELOG.md | 14 +++++++ plugins/catalog/package.json | 14 +++---- plugins/circleci/CHANGELOG.md | 12 ++++++ plugins/circleci/package.json | 10 ++--- plugins/cloudbuild/CHANGELOG.md | 11 ++++++ plugins/cloudbuild/package.json | 10 ++--- plugins/cost-insights/CHANGELOG.md | 9 +++++ plugins/cost-insights/package.json | 6 +-- plugins/github-actions/CHANGELOG.md | 11 ++++++ plugins/github-actions/package.json | 10 ++--- plugins/jenkins/CHANGELOG.md | 11 ++++++ plugins/jenkins/package.json | 10 ++--- plugins/kubernetes-backend/CHANGELOG.md | 15 ++++++++ plugins/kubernetes-backend/package.json | 8 ++-- plugins/kubernetes/CHANGELOG.md | 15 ++++++++ plugins/kubernetes/package.json | 10 ++--- plugins/lighthouse/CHANGELOG.md | 11 ++++++ plugins/lighthouse/package.json | 10 ++--- plugins/register-component/CHANGELOG.md | 12 ++++++ plugins/register-component/package.json | 10 ++--- plugins/rollbar/CHANGELOG.md | 11 ++++++ plugins/rollbar/package.json | 10 ++--- plugins/scaffolder-backend/CHANGELOG.md | 13 +++++++ plugins/scaffolder-backend/package.json | 8 ++-- plugins/scaffolder/CHANGELOG.md | 12 ++++++ plugins/scaffolder/package.json | 10 ++--- plugins/search/CHANGELOG.md | 13 +++++++ plugins/search/package.json | 10 ++--- plugins/sentry/CHANGELOG.md | 10 +++++ plugins/sentry/package.json | 8 ++-- plugins/sonarqube/CHANGELOG.md | 11 ++++++ plugins/sonarqube/package.json | 8 ++-- plugins/techdocs-backend/CHANGELOG.md | 11 ++++++ plugins/techdocs-backend/package.json | 8 ++-- plugins/techdocs/CHANGELOG.md | 11 ++++++ plugins/techdocs/package.json | 10 ++--- 77 files changed, 535 insertions(+), 263 deletions(-) delete mode 100644 .changeset/breezy-cobras-deny.md delete mode 100644 .changeset/chatty-radios-beam.md delete mode 100644 .changeset/clever-moons-shake.md delete mode 100644 .changeset/cli-links.md delete mode 100644 .changeset/cost-insights-quiet-bikes-occur.md delete mode 100644 .changeset/cost-insights-tiny-llamas-perform.md delete mode 100644 .changeset/curly-yaks-invite.md delete mode 100644 .changeset/dull-pans-sip.md delete mode 100644 .changeset/empty-kids-look.md delete mode 100644 .changeset/forty-buses-watch.md delete mode 100644 .changeset/long-flowers-wink.md delete mode 100644 .changeset/mighty-cycles-impress.md delete mode 100644 .changeset/new-ladybugs-nail.md delete mode 100644 .changeset/orange-cherries-run.md delete mode 100644 .changeset/poor-mails-marry.md delete mode 100644 .changeset/small-worms-check.md delete mode 100644 .changeset/tame-rockets-explode.md delete mode 100644 .changeset/tidy-brooms-look.md create mode 100644 plugins/search/CHANGELOG.md diff --git a/.changeset/breezy-cobras-deny.md b/.changeset/breezy-cobras-deny.md deleted file mode 100644 index 5f035c05ce..0000000000 --- a/.changeset/breezy-cobras-deny.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core': patch ---- - -Clear sidebar search field once a search is executed diff --git a/.changeset/chatty-radios-beam.md b/.changeset/chatty-radios-beam.md deleted file mode 100644 index 91ad76ee88..0000000000 --- a/.changeset/chatty-radios-beam.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/catalog-model': minor -'@backstage/plugin-kubernetes': minor -'@backstage/plugin-kubernetes-backend': minor ---- - -add kubernetes selector to component model diff --git a/.changeset/clever-moons-shake.md b/.changeset/clever-moons-shake.md deleted file mode 100644 index 0fb6c690c2..0000000000 --- a/.changeset/clever-moons-shake.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-common': patch ---- - -Added readTree support to AzureUrlReader diff --git a/.changeset/cli-links.md b/.changeset/cli-links.md deleted file mode 100644 index 0a9779de93..0000000000 --- a/.changeset/cli-links.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -The CLI now detects and transforms linked packages. You can link in external packages by adding them to both the `lerna.json` and `package.json` workspace paths. diff --git a/.changeset/cost-insights-quiet-bikes-occur.md b/.changeset/cost-insights-quiet-bikes-occur.md deleted file mode 100644 index aac283bbd4..0000000000 --- a/.changeset/cost-insights-quiet-bikes-occur.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-cost-insights': patch ---- - -fix product icon configuration diff --git a/.changeset/cost-insights-tiny-llamas-perform.md b/.changeset/cost-insights-tiny-llamas-perform.md deleted file mode 100644 index c81c28f8f8..0000000000 --- a/.changeset/cost-insights-tiny-llamas-perform.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-cost-insights': patch ---- - -truncate large percentages > 1000% diff --git a/.changeset/curly-yaks-invite.md b/.changeset/curly-yaks-invite.md deleted file mode 100644 index b9191a3895..0000000000 --- a/.changeset/curly-yaks-invite.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': patch ---- - -Add support for reading groups and users from the Microsoft Graph API. diff --git a/.changeset/dull-pans-sip.md b/.changeset/dull-pans-sip.md deleted file mode 100644 index 13616c1f8d..0000000000 --- a/.changeset/dull-pans-sip.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-scaffolder': patch -'@backstage/plugin-scaffolder-backend': patch ---- - -Move constructing the catalog-info.yaml URL for scaffolded components to the publishers diff --git a/.changeset/empty-kids-look.md b/.changeset/empty-kids-look.md deleted file mode 100644 index 680cd7f25c..0000000000 --- a/.changeset/empty-kids-look.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-register-component': patch ---- - -Remove catalog link on validate popup diff --git a/.changeset/forty-buses-watch.md b/.changeset/forty-buses-watch.md deleted file mode 100644 index 4074566fe7..0000000000 --- a/.changeset/forty-buses-watch.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -'@backstage/catalog-model': patch -'@backstage/plugin-catalog-backend': patch ---- - -Marked the `Group` entity fields `ancestors` and `descendants` for deprecation on Dec 6th, 2020. See https://github.com/backstage/backstage/issues/3049 for details. - -Code that consumes these fields should remove those usages as soon as possible. There is no current or planned replacement for these fields. - -The BuiltinKindsEntityProcessor has been updated to inject these fields as empty arrays if they are missing. Therefore, if you are on a catalog instance that uses the updated version of this code, you can start removing the fields from your source catalog-info.yaml data as well, without breaking validation. - -After Dec 6th, the fields will be removed from types and classes of the Backstage repository. At the first release after that, they will not be present in released packages either. - -If your catalog-info.yaml files still contain these fields after the deletion, they will still be valid and your ingestion will not break, but they won't be visible in the types for consuming code. diff --git a/.changeset/long-flowers-wink.md b/.changeset/long-flowers-wink.md deleted file mode 100644 index 7f9cf12315..0000000000 --- a/.changeset/long-flowers-wink.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-sonarqube': patch ---- - -Add configuration schema diff --git a/.changeset/mighty-cycles-impress.md b/.changeset/mighty-cycles-impress.md deleted file mode 100644 index 56ec35098f..0000000000 --- a/.changeset/mighty-cycles-impress.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-circleci': patch ---- - -Improved CircleCI builds table to show more information and relevant links diff --git a/.changeset/new-ladybugs-nail.md b/.changeset/new-ladybugs-nail.md deleted file mode 100644 index 5351b6dff3..0000000000 --- a/.changeset/new-ladybugs-nail.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -New lint rule to disallow assertions and promote `as` assertions. - @typescript-eslint/consistent-type-assertions diff --git a/.changeset/orange-cherries-run.md b/.changeset/orange-cherries-run.md deleted file mode 100644 index 3c06c27b47..0000000000 --- a/.changeset/orange-cherries-run.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Add experimental backend:bundle command diff --git a/.changeset/poor-mails-marry.md b/.changeset/poor-mails-marry.md deleted file mode 100644 index fdc7e6f1b8..0000000000 --- a/.changeset/poor-mails-marry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Add new `versions:check` and `versions:bump` commands to simplify version management and avoid conflicts diff --git a/.changeset/small-worms-check.md b/.changeset/small-worms-check.md deleted file mode 100644 index e39265f8f9..0000000000 --- a/.changeset/small-worms-check.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'example-app': patch -'@backstage/plugin-search': patch ---- - -Using the search field in the sidebar now navigates to the search result page. diff --git a/.changeset/tame-rockets-explode.md b/.changeset/tame-rockets-explode.md deleted file mode 100644 index 123472701e..0000000000 --- a/.changeset/tame-rockets-explode.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-common': patch ---- - -Make integration host and url configurations visible in the frontend diff --git a/.changeset/tidy-brooms-look.md b/.changeset/tidy-brooms-look.md deleted file mode 100644 index f2c2a9064e..0000000000 --- a/.changeset/tidy-brooms-look.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': patch ---- - -Fix React entity YAML filename to new standard diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index ab00924a26..f70cd198b4 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,42 @@ # example-app +## 0.2.3 + +### Patch Changes + +- 475fc0aaa: Using the search field in the sidebar now navigates to the search result page. +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [29a0ccab2] +- Updated dependencies [8e6728e25] +- Updated dependencies [c93a14b49] +- Updated dependencies [ef2831dde] +- Updated dependencies [2a71f4bab] +- Updated dependencies [1185919f3] +- Updated dependencies [a8de7f554] +- Updated dependencies [faf311c26] +- Updated dependencies [31d8b6979] +- Updated dependencies [991345969] +- Updated dependencies [475fc0aaa] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-kubernetes@0.3.0 + - @backstage/cli@0.3.1 + - @backstage/plugin-cost-insights@0.4.1 + - @backstage/plugin-scaffolder@0.3.1 + - @backstage/plugin-register-component@0.2.2 + - @backstage/plugin-circleci@0.2.2 + - @backstage/plugin-search@0.2.1 + - @backstage/plugin-api-docs@0.2.2 + - @backstage/plugin-catalog@0.2.3 + - @backstage/plugin-cloudbuild@0.2.2 + - @backstage/plugin-github-actions@0.2.2 + - @backstage/plugin-jenkins@0.3.1 + - @backstage/plugin-lighthouse@0.2.3 + - @backstage/plugin-rollbar@0.2.3 + - @backstage/plugin-sentry@0.2.3 + - @backstage/plugin-techdocs@0.2.3 + ## 0.2.2 ### Patch Changes diff --git a/packages/app/package.json b/packages/app/package.json index 042fd47188..9e54005cd3 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,33 +1,33 @@ { "name": "example-app", - "version": "0.2.2", + "version": "0.2.3", "private": true, "bundled": true, "dependencies": { - "@backstage/catalog-model": "^0.2.0", - "@backstage/cli": "^0.3.0", - "@backstage/core": "^0.3.1", - "@backstage/plugin-api-docs": "^0.2.1", - "@backstage/plugin-catalog": "^0.2.2", - "@backstage/plugin-circleci": "^0.2.1", - "@backstage/plugin-cloudbuild": "^0.2.1", - "@backstage/plugin-cost-insights": "^0.4.0", + "@backstage/catalog-model": "^0.3.0", + "@backstage/cli": "^0.3.1", + "@backstage/core": "^0.3.2", + "@backstage/plugin-api-docs": "^0.2.2", + "@backstage/plugin-catalog": "^0.2.3", + "@backstage/plugin-circleci": "^0.2.2", + "@backstage/plugin-cloudbuild": "^0.2.2", + "@backstage/plugin-cost-insights": "^0.4.1", "@backstage/plugin-explore": "^0.2.1", "@backstage/plugin-gcp-projects": "^0.2.1", - "@backstage/plugin-github-actions": "^0.2.1", + "@backstage/plugin-github-actions": "^0.2.2", "@backstage/plugin-gitops-profiles": "^0.2.1", "@backstage/plugin-graphiql": "^0.2.1", - "@backstage/plugin-jenkins": "^0.3.0", - "@backstage/plugin-kubernetes": "^0.2.1", - "@backstage/plugin-lighthouse": "^0.2.2", + "@backstage/plugin-jenkins": "^0.3.1", + "@backstage/plugin-kubernetes": "^0.3.0", + "@backstage/plugin-lighthouse": "^0.2.3", "@backstage/plugin-newrelic": "^0.2.1", - "@backstage/plugin-register-component": "^0.2.1", - "@backstage/plugin-rollbar": "^0.2.2", - "@backstage/plugin-scaffolder": "^0.3.0", - "@backstage/plugin-sentry": "^0.2.2", - "@backstage/plugin-search": "^0.2.0", + "@backstage/plugin-register-component": "^0.2.2", + "@backstage/plugin-rollbar": "^0.2.3", + "@backstage/plugin-scaffolder": "^0.3.1", + "@backstage/plugin-sentry": "^0.2.3", + "@backstage/plugin-search": "^0.2.1", "@backstage/plugin-tech-radar": "^0.3.0", - "@backstage/plugin-techdocs": "^0.2.2", + "@backstage/plugin-techdocs": "^0.2.3", "@backstage/plugin-user-settings": "^0.2.2", "@backstage/plugin-welcome": "^0.2.1", "@backstage/test-utils": "^0.1.3", diff --git a/packages/backend-common/CHANGELOG.md b/packages/backend-common/CHANGELOG.md index 4822032c7f..793a554905 100644 --- a/packages/backend-common/CHANGELOG.md +++ b/packages/backend-common/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/backend-common +## 0.3.1 + +### Patch Changes + +- bff3305aa: Added readTree support to AzureUrlReader +- b47dce06f: Make integration host and url configurations visible in the frontend + ## 0.3.0 ### Minor Changes diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index b22e4638f5..731e2679cb 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-common", "description": "Common functionality library for Backstage backends", - "version": "0.3.0", + "version": "0.3.1", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -67,7 +67,7 @@ } }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/test-utils": "^0.1.3", "@types/archiver": "^3.1.1", "@types/compression": "^1.7.0", diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index a8bc1fef7d..bc2a08e983 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,26 @@ # example-backend +## 0.2.3 + +### Patch Changes + +- Updated dependencies [1166fcc36] +- Updated dependencies [bff3305aa] +- Updated dependencies [0c2121240] +- Updated dependencies [ef2831dde] +- Updated dependencies [1185919f3] +- Updated dependencies [475fc0aaa] +- Updated dependencies [b47dce06f] +- Updated dependencies [5a1d8dca3] + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-kubernetes-backend@0.2.0 + - @backstage/backend-common@0.3.1 + - @backstage/plugin-catalog-backend@0.2.2 + - @backstage/plugin-scaffolder-backend@0.3.2 + - example-app@0.2.3 + - @backstage/plugin-auth-backend@0.2.3 + - @backstage/plugin-techdocs-backend@0.2.2 + ## 0.2.2 ### Patch Changes diff --git a/packages/backend/package.json b/packages/backend/package.json index 014a3f78d5..5d3678a284 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.2.2", + "version": "0.2.3", "main": "dist/index.cjs.js", "types": "src/index.ts", "private": true, @@ -18,24 +18,24 @@ "migrate:create": "knex migrate:make -x ts" }, "dependencies": { - "@backstage/backend-common": "^0.3.0", - "@backstage/catalog-model": "^0.2.0", + "@backstage/backend-common": "^0.3.1", + "@backstage/catalog-model": "^0.3.0", "@backstage/config": "^0.1.1", "@backstage/plugin-app-backend": "^0.3.0", - "@backstage/plugin-auth-backend": "^0.2.2", - "@backstage/plugin-catalog-backend": "^0.2.1", + "@backstage/plugin-auth-backend": "^0.2.3", + "@backstage/plugin-catalog-backend": "^0.2.2", "@backstage/plugin-graphql-backend": "^0.1.3", - "@backstage/plugin-kubernetes-backend": "^0.1.3", + "@backstage/plugin-kubernetes-backend": "^0.2.0", "@backstage/plugin-proxy-backend": "^0.2.1", "@backstage/plugin-rollbar-backend": "^0.1.3", - "@backstage/plugin-scaffolder-backend": "^0.3.1", + "@backstage/plugin-scaffolder-backend": "^0.3.2", "@backstage/plugin-sentry-backend": "^0.1.3", - "@backstage/plugin-techdocs-backend": "^0.2.1", + "@backstage/plugin-techdocs-backend": "^0.2.2", "@gitbeaker/node": "^25.2.0", "@octokit/rest": "^18.0.0", "azure-devops-node-api": "^10.1.1", "dockerode": "^3.2.0", - "example-app": "^0.2.2", + "example-app": "^0.2.3", "express": "^4.17.1", "express-promise-router": "^3.0.3", "knex": "^0.21.6", @@ -45,7 +45,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@types/dockerode": "^2.5.32", "@types/express": "^4.17.6", "@types/express-serve-static-core": "^4.17.5", diff --git a/packages/catalog-client/CHANGELOG.md b/packages/catalog-client/CHANGELOG.md index 574cd301bf..3454a6c8e0 100644 --- a/packages/catalog-client/CHANGELOG.md +++ b/packages/catalog-client/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/catalog-client +## 0.3.1 + +### Patch Changes + +- Updated dependencies [1166fcc36] +- Updated dependencies [1185919f3] + - @backstage/catalog-model@0.3.0 + ## 0.3.0 ### Minor Changes diff --git a/packages/catalog-client/package.json b/packages/catalog-client/package.json index 2482f9579d..4509b7d547 100644 --- a/packages/catalog-client/package.json +++ b/packages/catalog-client/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/catalog-client", - "version": "0.3.0", + "version": "0.3.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,12 +20,12 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.2.0", + "@backstage/catalog-model": "^0.3.0", "@backstage/config": "^0.1.1", "cross-fetch": "^3.0.6" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@types/jest": "^26.0.7", "msw": "^0.21.2" }, diff --git a/packages/catalog-model/CHANGELOG.md b/packages/catalog-model/CHANGELOG.md index 051aa82c80..10bf540b46 100644 --- a/packages/catalog-model/CHANGELOG.md +++ b/packages/catalog-model/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/catalog-model +## 0.3.0 + +### Minor Changes + +- 1166fcc36: add kubernetes selector to component model + +### Patch Changes + +- 1185919f3: Marked the `Group` entity fields `ancestors` and `descendants` for deprecation on Dec 6th, 2020. See https://github.com/backstage/backstage/issues/3049 for details. + + Code that consumes these fields should remove those usages as soon as possible. There is no current or planned replacement for these fields. + + The BuiltinKindsEntityProcessor has been updated to inject these fields as empty arrays if they are missing. Therefore, if you are on a catalog instance that uses the updated version of this code, you can start removing the fields from your source catalog-info.yaml data as well, without breaking validation. + + After Dec 6th, the fields will be removed from types and classes of the Backstage repository. At the first release after that, they will not be present in released packages either. + + If your catalog-info.yaml files still contain these fields after the deletion, they will still be valid and your ingestion will not break, but they won't be visible in the types for consuming code. + ## 0.2.0 ### Minor Changes diff --git a/packages/catalog-model/package.json b/packages/catalog-model/package.json index e712e66f58..7956454ca9 100644 --- a/packages/catalog-model/package.json +++ b/packages/catalog-model/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/catalog-model", - "version": "0.2.0", + "version": "0.3.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,7 +29,7 @@ "yup": "^0.29.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@types/express": "^4.17.6", "@types/jest": "^26.0.7", "@types/lodash": "^4.14.151", diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index a5bcc6bcfd..d198444c86 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/cli +## 0.3.1 + +### Patch Changes + +- 29a0ccab2: The CLI now detects and transforms linked packages. You can link in external packages by adding them to both the `lerna.json` and `package.json` workspace paths. +- faf311c26: New lint rule to disallow assertions and promote `as` assertions. - @typescript-eslint/consistent-type-assertions +- 31d8b6979: Add experimental backend:bundle command +- 991345969: Add new `versions:check` and `versions:bump` commands to simplify version management and avoid conflicts + ## 0.3.0 ### Minor Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index eb56b19971..034c8a3de5 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/cli", "description": "CLI for developing Backstage plugins and apps", - "version": "0.3.0", + "version": "0.3.1", "private": false, "publishConfig": { "access": "public" @@ -111,9 +111,9 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/backend-common": "^0.3.0", + "@backstage/backend-common": "^0.3.1", "@backstage/config": "^0.1.1", - "@backstage/core": "^0.3.1", + "@backstage/core": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@backstage/theme": "^0.2.1", diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 213e2b5246..d29bc53452 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/core +## 0.3.2 + +### Patch Changes + +- 475fc0aaa: Clear sidebar search field once a search is executed + ## 0.3.1 ### Patch Changes diff --git a/packages/core/package.json b/packages/core/package.json index d65f0a16e5..f667ffe7be 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core", "description": "Core API used by Backstage plugins and apps", - "version": "0.3.1", + "version": "0.3.2", "private": false, "publishConfig": { "access": "public", @@ -64,7 +64,7 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/packages/create-app/package.json b/packages/create-app/package.json index c01d4d3dbb..6e91a916a1 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -37,28 +37,28 @@ "recursive-readdir": "^2.2.2" }, "devDependencies": { - "@backstage/backend-common": "^0.3.0", - "@backstage/catalog-model": "^0.2.0", - "@backstage/cli": "^0.3.0", + "@backstage/backend-common": "^0.3.1", + "@backstage/catalog-model": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/config": "^0.1.1", - "@backstage/core": "^0.3.1", - "@backstage/plugin-api-docs": "^0.2.1", + "@backstage/core": "^0.3.2", + "@backstage/plugin-api-docs": "^0.2.2", "@backstage/plugin-app-backend": "^0.3.0", - "@backstage/plugin-auth-backend": "^0.2.2", - "@backstage/plugin-catalog": "^0.2.2", - "@backstage/plugin-catalog-backend": "^0.2.1", - "@backstage/plugin-circleci": "^0.2.1", + "@backstage/plugin-auth-backend": "^0.2.3", + "@backstage/plugin-catalog": "^0.2.3", + "@backstage/plugin-catalog-backend": "^0.2.2", + "@backstage/plugin-circleci": "^0.2.2", "@backstage/plugin-explore": "^0.2.1", - "@backstage/plugin-github-actions": "^0.2.1", - "@backstage/plugin-lighthouse": "^0.2.2", + "@backstage/plugin-github-actions": "^0.2.2", + "@backstage/plugin-lighthouse": "^0.2.3", "@backstage/plugin-proxy-backend": "^0.2.1", - "@backstage/plugin-register-component": "^0.2.1", + "@backstage/plugin-register-component": "^0.2.2", "@backstage/plugin-rollbar-backend": "^0.1.3", - "@backstage/plugin-scaffolder": "^0.3.0", - "@backstage/plugin-scaffolder-backend": "^0.3.1", + "@backstage/plugin-scaffolder": "^0.3.1", + "@backstage/plugin-scaffolder-backend": "^0.3.2", "@backstage/plugin-tech-radar": "^0.3.0", - "@backstage/plugin-techdocs": "^0.2.2", - "@backstage/plugin-techdocs-backend": "^0.2.1", + "@backstage/plugin-techdocs": "^0.2.3", + "@backstage/plugin-techdocs-backend": "^0.2.2", "@backstage/plugin-user-settings": "^0.2.2", "@backstage/test-utils": "^0.1.3", "@backstage/theme": "^0.2.1", diff --git a/plugins/api-docs/CHANGELOG.md b/plugins/api-docs/CHANGELOG.md index 5f1ad97e64..60a5caf55b 100644 --- a/plugins/api-docs/CHANGELOG.md +++ b/plugins/api-docs/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-api-docs +## 0.2.2 + +### Patch Changes + +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [1185919f3] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-catalog@0.2.3 + ## 0.2.1 ### Patch Changes diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index bfefaef3a5..691f2680b2 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-api-docs", - "version": "0.2.1", + "version": "0.2.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,9 +20,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.1", - "@backstage/plugin-catalog": "^0.2.2", + "@backstage/catalog-model": "^0.3.0", + "@backstage/core": "^0.3.2", + "@backstage/plugin-catalog": "^0.2.3", "@backstage/theme": "^0.2.1", "@kyma-project/asyncapi-react": "^0.14.2", "@material-icons/font": "^1.0.2", @@ -39,7 +39,7 @@ "swagger-ui-react": "^3.31.1" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/auth-backend/CHANGELOG.md b/plugins/auth-backend/CHANGELOG.md index 546855c92f..4a2161ad71 100644 --- a/plugins/auth-backend/CHANGELOG.md +++ b/plugins/auth-backend/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-auth-backend +## 0.2.3 + +### Patch Changes + +- Updated dependencies [1166fcc36] +- Updated dependencies [bff3305aa] +- Updated dependencies [1185919f3] +- Updated dependencies [b47dce06f] + - @backstage/catalog-model@0.3.0 + - @backstage/backend-common@0.3.1 + - @backstage/catalog-client@0.3.1 + ## 0.2.2 ### Patch Changes diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 4d1e52aa21..43e2c59f88 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend", - "version": "0.2.2", + "version": "0.2.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,9 +20,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.3.0", - "@backstage/catalog-client": "^0.3.0", - "@backstage/catalog-model": "^0.2.0", + "@backstage/backend-common": "^0.3.1", + "@backstage/catalog-client": "^0.3.1", + "@backstage/catalog-model": "^0.3.0", "@backstage/config": "^0.1.1", "@types/express": "^4.17.6", "compression": "^1.7.4", @@ -55,7 +55,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@types/body-parser": "^1.19.0", "@types/cookie-parser": "^1.4.2", "@types/express-session": "^1.17.2", diff --git a/plugins/catalog-backend/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md index 1a3199ee14..3bf25a494b 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-catalog-backend +## 0.2.2 + +### Patch Changes + +- 0c2121240: Add support for reading groups and users from the Microsoft Graph API. +- 1185919f3: Marked the `Group` entity fields `ancestors` and `descendants` for deprecation on Dec 6th, 2020. See https://github.com/backstage/backstage/issues/3049 for details. + + Code that consumes these fields should remove those usages as soon as possible. There is no current or planned replacement for these fields. + + The BuiltinKindsEntityProcessor has been updated to inject these fields as empty arrays if they are missing. Therefore, if you are on a catalog instance that uses the updated version of this code, you can start removing the fields from your source catalog-info.yaml data as well, without breaking validation. + + After Dec 6th, the fields will be removed from types and classes of the Backstage repository. At the first release after that, they will not be present in released packages either. + + If your catalog-info.yaml files still contain these fields after the deletion, they will still be valid and your ingestion will not break, but they won't be visible in the types for consuming code. + +- Updated dependencies [1166fcc36] +- Updated dependencies [bff3305aa] +- Updated dependencies [1185919f3] +- Updated dependencies [b47dce06f] + - @backstage/catalog-model@0.3.0 + - @backstage/backend-common@0.3.1 + ## 0.2.1 ### Patch Changes diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 12a17d6365..51a9978f30 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend", - "version": "0.2.1", + "version": "0.2.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,8 +21,8 @@ }, "dependencies": { "@azure/msal-node": "^1.0.0-alpha.8", - "@backstage/backend-common": "^0.3.0", - "@backstage/catalog-model": "^0.2.0", + "@backstage/backend-common": "^0.3.1", + "@backstage/catalog-model": "^0.3.0", "@backstage/config": "^0.1.1", "@octokit/graphql": "^4.5.6", "@types/express": "^4.17.6", @@ -47,7 +47,7 @@ "yup": "^0.29.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/test-utils": "^0.1.3", "@types/core-js": "^2.5.4", "@types/git-url-parse": "^9.0.0", diff --git a/plugins/catalog-graphql/CHANGELOG.md b/plugins/catalog-graphql/CHANGELOG.md index 35737b7dd1..56911b9c45 100644 --- a/plugins/catalog-graphql/CHANGELOG.md +++ b/plugins/catalog-graphql/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-catalog-graphql +## 0.2.2 + +### Patch Changes + +- Updated dependencies [1166fcc36] +- Updated dependencies [bff3305aa] +- Updated dependencies [1185919f3] +- Updated dependencies [b47dce06f] + - @backstage/catalog-model@0.3.0 + - @backstage/backend-common@0.3.1 + ## 0.2.1 ### Patch Changes diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json index c55be89b62..a489d66e5b 100644 --- a/plugins/catalog-graphql/package.json +++ b/plugins/catalog-graphql/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-graphql", - "version": "0.2.1", + "version": "0.2.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,8 +20,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.3.0", - "@backstage/catalog-model": "^0.2.0", + "@backstage/backend-common": "^0.3.1", + "@backstage/catalog-model": "^0.3.0", "@backstage/config": "^0.1.1", "@graphql-modules/core": "^0.7.17", "apollo-server": "^2.16.1", @@ -32,7 +32,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/test-utils": "^0.1.3", "@graphql-codegen/cli": "^1.17.7", "@graphql-codegen/typescript": "^1.17.7", diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index 1b8b884f1c..9137b7e948 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-catalog +## 0.2.3 + +### Patch Changes + +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [ef2831dde] +- Updated dependencies [1185919f3] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-scaffolder@0.3.1 + - @backstage/catalog-client@0.3.1 + - @backstage/plugin-techdocs@0.2.3 + ## 0.2.2 ### Patch Changes diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 2c65096fd6..7b58358734 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog", - "version": "0.2.2", + "version": "0.2.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,11 +21,11 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-client": "^0.3.0", - "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.1", - "@backstage/plugin-scaffolder": "^0.3.0", - "@backstage/plugin-techdocs": "^0.2.2", + "@backstage/catalog-client": "^0.3.1", + "@backstage/catalog-model": "^0.3.0", + "@backstage/core": "^0.3.2", + "@backstage/plugin-scaffolder": "^0.3.1", + "@backstage/plugin-techdocs": "^0.2.3", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -43,7 +43,7 @@ "swr": "^0.3.0" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@microsoft/microsoft-graph-types": "^1.25.0", diff --git a/plugins/circleci/CHANGELOG.md b/plugins/circleci/CHANGELOG.md index 2894ce1bd3..a81531e28d 100644 --- a/plugins/circleci/CHANGELOG.md +++ b/plugins/circleci/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-circleci +## 0.2.2 + +### Patch Changes + +- a8de7f554: Improved CircleCI builds table to show more information and relevant links +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [1185919f3] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-catalog@0.2.3 + ## 0.2.1 ### Patch Changes diff --git a/plugins/circleci/package.json b/plugins/circleci/package.json index eaa8c319a0..fd32ff9891 100644 --- a/plugins/circleci/package.json +++ b/plugins/circleci/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-circleci", - "version": "0.2.1", + "version": "0.2.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,9 +21,9 @@ "postpack": "backstage-cli postpack" }, "dependencies": { - "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.1", - "@backstage/plugin-catalog": "^0.2.2", + "@backstage/catalog-model": "^0.3.0", + "@backstage/core": "^0.3.2", + "@backstage/plugin-catalog": "^0.2.3", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -40,7 +40,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/cloudbuild/CHANGELOG.md b/plugins/cloudbuild/CHANGELOG.md index 9d02fd1ea3..4cb2d01377 100644 --- a/plugins/cloudbuild/CHANGELOG.md +++ b/plugins/cloudbuild/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-cloudbuild +## 0.2.2 + +### Patch Changes + +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [1185919f3] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-catalog@0.2.3 + ## 0.2.1 ### Patch Changes diff --git a/plugins/cloudbuild/package.json b/plugins/cloudbuild/package.json index c0b36ef1e7..3e7f0c9175 100644 --- a/plugins/cloudbuild/package.json +++ b/plugins/cloudbuild/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-cloudbuild", - "version": "0.2.1", + "version": "0.2.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,9 +20,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.1", - "@backstage/plugin-catalog": "^0.2.2", + "@backstage/catalog-model": "^0.3.0", + "@backstage/core": "^0.3.2", + "@backstage/plugin-catalog": "^0.2.3", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -39,7 +39,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/cost-insights/CHANGELOG.md b/plugins/cost-insights/CHANGELOG.md index 157d92f251..b7bdfa9d50 100644 --- a/plugins/cost-insights/CHANGELOG.md +++ b/plugins/cost-insights/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-cost-insights +## 0.4.1 + +### Patch Changes + +- 8e6728e25: fix product icon configuration +- c93a14b49: truncate large percentages > 1000% +- Updated dependencies [475fc0aaa] + - @backstage/core@0.3.2 + ## 0.4.0 ### Minor Changes diff --git a/plugins/cost-insights/package.json b/plugins/cost-insights/package.json index 8ebeac91c0..06083b855b 100644 --- a/plugins/cost-insights/package.json +++ b/plugins/cost-insights/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-cost-insights", - "version": "0.4.0", + "version": "0.4.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -22,7 +22,7 @@ }, "dependencies": { "@backstage/config": "^0.1.1", - "@backstage/core": "^0.3.1", + "@backstage/core": "^0.3.2", "@backstage/test-utils": "^0.1.3", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", @@ -46,7 +46,7 @@ "yup": "^0.29.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/github-actions/CHANGELOG.md b/plugins/github-actions/CHANGELOG.md index b679d6844b..96f3876802 100644 --- a/plugins/github-actions/CHANGELOG.md +++ b/plugins/github-actions/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-github-actions +## 0.2.2 + +### Patch Changes + +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [1185919f3] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-catalog@0.2.3 + ## 0.2.1 ### Patch Changes diff --git a/plugins/github-actions/package.json b/plugins/github-actions/package.json index 49ecfc5423..b1924034f1 100644 --- a/plugins/github-actions/package.json +++ b/plugins/github-actions/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-github-actions", - "version": "0.2.1", + "version": "0.2.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,10 +21,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.1", + "@backstage/catalog-model": "^0.3.0", + "@backstage/core": "^0.3.2", "@backstage/core-api": "^0.2.1", - "@backstage/plugin-catalog": "^0.2.2", + "@backstage/plugin-catalog": "^0.2.3", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -40,7 +40,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/jenkins/CHANGELOG.md b/plugins/jenkins/CHANGELOG.md index 44febbc63d..aeeb069f19 100644 --- a/plugins/jenkins/CHANGELOG.md +++ b/plugins/jenkins/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-jenkins +## 0.3.1 + +### Patch Changes + +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [1185919f3] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-catalog@0.2.3 + ## 0.3.0 ### Minor Changes diff --git a/plugins/jenkins/package.json b/plugins/jenkins/package.json index 0279592a75..961f551dfa 100644 --- a/plugins/jenkins/package.json +++ b/plugins/jenkins/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-jenkins", - "version": "0.3.0", + "version": "0.3.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,9 +21,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.1", - "@backstage/plugin-catalog": "^0.2.2", + "@backstage/catalog-model": "^0.3.0", + "@backstage/core": "^0.3.2", + "@backstage/plugin-catalog": "^0.2.3", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -36,7 +36,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/kubernetes-backend/CHANGELOG.md b/plugins/kubernetes-backend/CHANGELOG.md index 7c55a7407d..2877264e54 100644 --- a/plugins/kubernetes-backend/CHANGELOG.md +++ b/plugins/kubernetes-backend/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-kubernetes-backend +## 0.2.0 + +### Minor Changes + +- 1166fcc36: add kubernetes selector to component model + +### Patch Changes + +- Updated dependencies [1166fcc36] +- Updated dependencies [bff3305aa] +- Updated dependencies [1185919f3] +- Updated dependencies [b47dce06f] + - @backstage/catalog-model@0.3.0 + - @backstage/backend-common@0.3.1 + ## 0.1.3 ### Patch Changes diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index dfc9e5377c..8fe2f4c58f 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-backend", - "version": "0.1.3", + "version": "0.2.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,8 +21,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.3.0", - "@backstage/catalog-model": "^0.2.0", + "@backstage/backend-common": "^0.3.1", + "@backstage/catalog-model": "^0.3.0", "@backstage/config": "^0.1.1", "@kubernetes/client-node": "^0.12.1", "@types/express": "^4.17.6", @@ -39,7 +39,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "supertest": "^4.0.2" }, "files": [ diff --git a/plugins/kubernetes/CHANGELOG.md b/plugins/kubernetes/CHANGELOG.md index e5611e4953..652fc394a2 100644 --- a/plugins/kubernetes/CHANGELOG.md +++ b/plugins/kubernetes/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-kubernetes +## 0.3.0 + +### Minor Changes + +- 1166fcc36: add kubernetes selector to component model + +### Patch Changes + +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [1185919f3] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-kubernetes-backend@0.2.0 + ## 0.2.1 ### Patch Changes diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index 3b6d48249d..66929077f3 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes", - "version": "0.2.1", + "version": "0.3.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,10 +21,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.2.0", + "@backstage/catalog-model": "^0.3.0", "@backstage/config": "^0.1.1", - "@backstage/core": "^0.3.1", - "@backstage/plugin-kubernetes-backend": "^0.1.3", + "@backstage/core": "^0.3.2", + "@backstage/plugin-kubernetes-backend": "^0.2.0", "@backstage/theme": "^0.2.1", "@kubernetes/client-node": "^0.12.1", "@material-ui/core": "^4.11.0", @@ -36,7 +36,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/lighthouse/CHANGELOG.md b/plugins/lighthouse/CHANGELOG.md index 666e7c4411..e35f3dece0 100644 --- a/plugins/lighthouse/CHANGELOG.md +++ b/plugins/lighthouse/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-lighthouse +## 0.2.3 + +### Patch Changes + +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [1185919f3] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-catalog@0.2.3 + ## 0.2.2 ### Patch Changes diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json index 90230f5248..ad1264cfbb 100644 --- a/plugins/lighthouse/package.json +++ b/plugins/lighthouse/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-lighthouse", - "version": "0.2.2", + "version": "0.2.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,11 +21,11 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/catalog-model": "^0.2.0", + "@backstage/catalog-model": "^0.3.0", "@backstage/config": "^0.1.1", - "@backstage/core": "^0.3.1", + "@backstage/core": "^0.3.2", "@backstage/core-api": "^0.2.1", - "@backstage/plugin-catalog": "^0.2.2", + "@backstage/plugin-catalog": "^0.2.3", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -38,7 +38,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/register-component/CHANGELOG.md b/plugins/register-component/CHANGELOG.md index 3e966ef0d0..c569046855 100644 --- a/plugins/register-component/CHANGELOG.md +++ b/plugins/register-component/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-register-component +## 0.2.2 + +### Patch Changes + +- 2a71f4bab: Remove catalog link on validate popup +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [1185919f3] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-catalog@0.2.3 + ## 0.2.1 ### Patch Changes diff --git a/plugins/register-component/package.json b/plugins/register-component/package.json index 06ae17cb0e..1564faae0e 100644 --- a/plugins/register-component/package.json +++ b/plugins/register-component/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-register-component", - "version": "0.2.1", + "version": "0.2.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,9 +21,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.1", - "@backstage/plugin-catalog": "^0.2.2", + "@backstage/catalog-model": "^0.3.0", + "@backstage/core": "^0.3.2", + "@backstage/plugin-catalog": "^0.2.3", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -36,7 +36,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/rollbar/CHANGELOG.md b/plugins/rollbar/CHANGELOG.md index e4a6671f90..1ccfe16c41 100644 --- a/plugins/rollbar/CHANGELOG.md +++ b/plugins/rollbar/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-rollbar +## 0.2.3 + +### Patch Changes + +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [1185919f3] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-catalog@0.2.3 + ## 0.2.2 ### Patch Changes diff --git a/plugins/rollbar/package.json b/plugins/rollbar/package.json index c107573ca8..1177a83eb6 100644 --- a/plugins/rollbar/package.json +++ b/plugins/rollbar/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-rollbar", - "version": "0.2.2", + "version": "0.2.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,9 +21,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.1", - "@backstage/plugin-catalog": "^0.2.2", + "@backstage/catalog-model": "^0.3.0", + "@backstage/core": "^0.3.2", + "@backstage/plugin-catalog": "^0.2.3", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -37,7 +37,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index a388a29a00..ea7e5c85ee 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-scaffolder-backend +## 0.3.2 + +### Patch Changes + +- ef2831dde: Move constructing the catalog-info.yaml URL for scaffolded components to the publishers +- 5a1d8dca3: Fix React entity YAML filename to new standard +- Updated dependencies [1166fcc36] +- Updated dependencies [bff3305aa] +- Updated dependencies [1185919f3] +- Updated dependencies [b47dce06f] + - @backstage/catalog-model@0.3.0 + - @backstage/backend-common@0.3.1 + ## 0.3.1 ### Patch Changes diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 73c07d158f..a095167a08 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend", - "version": "0.3.1", + "version": "0.3.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,8 +20,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.3.0", - "@backstage/catalog-model": "^0.2.0", + "@backstage/backend-common": "^0.3.1", + "@backstage/catalog-model": "^0.3.0", "@backstage/config": "^0.1.1", "@gitbeaker/core": "^25.2.0", "@gitbeaker/node": "^25.2.0", @@ -48,7 +48,7 @@ "cross-fetch": "^3.0.6" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@octokit/types": "^5.4.1", "@types/fs-extra": "^9.0.1", "@types/git-url-parse": "^9.0.0", diff --git a/plugins/scaffolder/CHANGELOG.md b/plugins/scaffolder/CHANGELOG.md index 9eac595997..c78e05c3e0 100644 --- a/plugins/scaffolder/CHANGELOG.md +++ b/plugins/scaffolder/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-scaffolder +## 0.3.1 + +### Patch Changes + +- ef2831dde: Move constructing the catalog-info.yaml URL for scaffolded components to the publishers +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [1185919f3] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-catalog@0.2.3 + ## 0.3.0 ### Minor Changes diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 439fc0ad9a..791221bc62 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder", - "version": "0.3.0", + "version": "0.3.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,9 +21,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.1", - "@backstage/plugin-catalog": "^0.2.2", + "@backstage/catalog-model": "^0.3.0", + "@backstage/core": "^0.3.2", + "@backstage/plugin-catalog": "^0.2.3", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -41,7 +41,7 @@ "swr": "^0.3.0" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/search/CHANGELOG.md b/plugins/search/CHANGELOG.md new file mode 100644 index 0000000000..3545914e96 --- /dev/null +++ b/plugins/search/CHANGELOG.md @@ -0,0 +1,13 @@ +# @backstage/plugin-search + +## 0.2.1 + +### Patch Changes + +- 475fc0aaa: Using the search field in the sidebar now navigates to the search result page. +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [1185919f3] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-catalog@0.2.3 diff --git a/plugins/search/package.json b/plugins/search/package.json index df5c6c25b9..021e3f71f3 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search", - "version": "0.2.0", + "version": "0.2.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,9 +20,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.3.1", - "@backstage/plugin-catalog": "^0.2.2", - "@backstage/catalog-model": "^0.2.0", + "@backstage/core": "^0.3.2", + "@backstage/plugin-catalog": "^0.2.3", + "@backstage/catalog-model": "^0.3.0", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -34,7 +34,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/sentry/CHANGELOG.md b/plugins/sentry/CHANGELOG.md index 92327c1668..b388548d8e 100644 --- a/plugins/sentry/CHANGELOG.md +++ b/plugins/sentry/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-sentry +## 0.2.3 + +### Patch Changes + +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [1185919f3] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + ## 0.2.2 ### Patch Changes diff --git a/plugins/sentry/package.json b/plugins/sentry/package.json index 80a37c574e..4c10936148 100644 --- a/plugins/sentry/package.json +++ b/plugins/sentry/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-sentry", - "version": "0.2.2", + "version": "0.2.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,8 +21,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.1", + "@backstage/catalog-model": "^0.3.0", + "@backstage/core": "^0.3.2", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -36,7 +36,7 @@ "timeago.js": "^4.0.2" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/sonarqube/CHANGELOG.md b/plugins/sonarqube/CHANGELOG.md index 0296bd0416..9ebd73b812 100644 --- a/plugins/sonarqube/CHANGELOG.md +++ b/plugins/sonarqube/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-sonarqube +## 0.1.4 + +### Patch Changes + +- 26484d413: Add configuration schema +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [1185919f3] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + ## 0.1.3 ### Patch Changes diff --git a/plugins/sonarqube/package.json b/plugins/sonarqube/package.json index be8a878e50..6a9d0fe0d4 100644 --- a/plugins/sonarqube/package.json +++ b/plugins/sonarqube/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-sonarqube", - "version": "0.1.3", + "version": "0.1.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,8 +21,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.1", + "@backstage/catalog-model": "^0.3.0", + "@backstage/core": "^0.3.2", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -35,7 +35,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/techdocs-backend/CHANGELOG.md b/plugins/techdocs-backend/CHANGELOG.md index 4a3fbf6821..d9054230e2 100644 --- a/plugins/techdocs-backend/CHANGELOG.md +++ b/plugins/techdocs-backend/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-techdocs-backend +## 0.2.2 + +### Patch Changes + +- Updated dependencies [1166fcc36] +- Updated dependencies [bff3305aa] +- Updated dependencies [1185919f3] +- Updated dependencies [b47dce06f] + - @backstage/catalog-model@0.3.0 + - @backstage/backend-common@0.3.1 + ## 0.2.1 ### Patch Changes diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index b139bb9acf..1d9f3ea7c1 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-backend", - "version": "0.2.1", + "version": "0.2.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,8 +20,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.3.0", - "@backstage/catalog-model": "^0.2.0", + "@backstage/backend-common": "^0.3.1", + "@backstage/catalog-model": "^0.3.0", "@backstage/config": "^0.1.1", "@types/dockerode": "^2.5.34", "@types/express": "^4.17.6", @@ -37,7 +37,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "supertest": "^4.0.2" }, "files": [ diff --git a/plugins/techdocs/CHANGELOG.md b/plugins/techdocs/CHANGELOG.md index bd3ae21ab8..3f46e1358b 100644 --- a/plugins/techdocs/CHANGELOG.md +++ b/plugins/techdocs/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-techdocs +## 0.2.3 + +### Patch Changes + +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [1185919f3] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-catalog@0.2.3 + ## 0.2.2 ### Patch Changes diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index 35b196e124..23d7a25b57 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs", - "version": "0.2.2", + "version": "0.2.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,10 +21,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.1", + "@backstage/catalog-model": "^0.3.0", + "@backstage/core": "^0.3.2", "@backstage/core-api": "^0.2.1", - "@backstage/plugin-catalog": "^0.2.2", + "@backstage/plugin-catalog": "^0.2.3", "@backstage/test-utils": "^0.1.3", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", @@ -39,7 +39,7 @@ "sanitize-html": "^1.27.0" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", From 5f1c1c2edf2b3f9c4463dc7d58afb1e8e7e843dd Mon Sep 17 00:00:00 2001 From: Brian Leathem Date: Mon, 23 Nov 2020 13:34:44 -0800 Subject: [PATCH 213/430] Fixed the OIDC token refresh --- .../src/providers/oidc/provider.ts | 51 ++++++++----------- 1 file changed, 21 insertions(+), 30 deletions(-) diff --git a/plugins/auth-backend/src/providers/oidc/provider.ts b/plugins/auth-backend/src/providers/oidc/provider.ts index 5ebb859f2f..765fd8627e 100644 --- a/plugins/auth-backend/src/providers/oidc/provider.ts +++ b/plugins/auth-backend/src/providers/oidc/provider.ts @@ -33,10 +33,8 @@ import { OAuthRefreshRequest, } from '../../lib/oauth'; import { - executeFetchUserProfileStrategy, executeFrameHandlerStrategy, executeRedirectStrategy, - executeRefreshTokenStrategy, PassportDoneCallback, } from '../../lib/passport'; import { RedirectInfo, AuthProviderFactory, ProfileInfo } from '../types'; @@ -45,20 +43,25 @@ type PrivateInfo = { refreshToken: string; }; +type OidcImpl = { + strategy: OidcStrategy; + client: Client; +}; + export type OidcAuthProviderOptions = OAuthProviderOptions & { metadataUrl: string; tokenSignedResponseAlg?: string; }; export class OidcAuthProvider implements OAuthHandlers { - readonly _strategy: Promise>; + readonly _implementation: Promise; constructor(options: OidcAuthProviderOptions) { - this._strategy = this.setupStrategy(options); + this._implementation = this.setupStrategy(options); } async start(req: OAuthStartRequest): Promise { - const strategy = await this._strategy; + const { strategy } = await this._implementation; return await executeRedirectStrategy(req, strategy, { accessType: 'offline', prompt: 'none', @@ -70,7 +73,7 @@ export class OidcAuthProvider implements OAuthHandlers { async handler( req: express.Request, ): Promise<{ response: OAuthResponse; refreshToken: string }> { - const strategy = await this._strategy; + const { strategy } = await this._implementation; const { response, privateInfo } = await executeFrameHandlerStrategy< OAuthResponse, PrivateInfo @@ -83,31 +86,19 @@ export class OidcAuthProvider implements OAuthHandlers { } async refresh(req: OAuthRefreshRequest): Promise { - const strategy = await this._strategy; - const refreshTokenResponse = await executeRefreshTokenStrategy( - strategy, - req.refreshToken, - req.scope, - ); - const { - accessToken, - params, - refreshToken: updatedRefreshToken, - } = refreshTokenResponse; - - const profile = await executeFetchUserProfileStrategy( - strategy, - accessToken, - params.id_token, - ); + const { client } = await this._implementation; + const tokenset = await client.refresh(req.refreshToken); + if (!tokenset.access_token) { + throw new Error('Refresh failed'); + } + const profile = await client.userinfo(tokenset.access_token); return this.populateIdentity({ providerInfo: { - accessToken, - refreshToken: updatedRefreshToken, - idToken: params.id_token, - expiresInSeconds: params.expires_in, - scope: params.scope, + accessToken: tokenset.access_token, + refreshToken: tokenset.refresh_token, + expiresInSeconds: tokenset.expires_at, + scope: tokenset.scope || '', }, profile, }); @@ -115,7 +106,7 @@ export class OidcAuthProvider implements OAuthHandlers { private async setupStrategy( options: OidcAuthProviderOptions, - ): Promise> { + ): Promise { const issuer = await Issuer.discover(options.metadataUrl); const client = new issuer.Client({ client_id: options.clientId, @@ -159,7 +150,7 @@ export class OidcAuthProvider implements OAuthHandlers { }, ); strategy.error = console.error; - return strategy; + return { strategy, client }; } // Use this function to grab the user profile info from the token From 636cc58dca2ddd8c19ce25e128b682295f8071d8 Mon Sep 17 00:00:00 2001 From: Brian Leathem Date: Mon, 23 Nov 2020 13:41:37 -0800 Subject: [PATCH 214/430] Fixed a TS type error in the OIDC provider test --- plugins/auth-backend/src/providers/oidc/provider.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/auth-backend/src/providers/oidc/provider.test.ts b/plugins/auth-backend/src/providers/oidc/provider.test.ts index ade69255b7..5073cd88d1 100644 --- a/plugins/auth-backend/src/providers/oidc/provider.test.ts +++ b/plugins/auth-backend/src/providers/oidc/provider.test.ts @@ -54,7 +54,7 @@ describe('OidcAuthProvider', () => { .get('/.well-known/openid-configuration') .reply(200, issuerMetadata); const provider = new OidcAuthProvider(clientMetadata); - const strategy = ((await provider._strategy) as any) as { + const strategy = ((await (provider as any)._strategy) as any) as { _client: ClientMetadata; _issuer: IssuerMetadata; }; From e3ad37e39d126a0c3dc9ccc8a22c061137ff0098 Mon Sep 17 00:00:00 2001 From: Brian Leathem Date: Mon, 23 Nov 2020 14:48:24 -0800 Subject: [PATCH 215/430] Fix broken tests --- .../src/providers/oidc/provider.test.ts | 49 ++++++++++++------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/plugins/auth-backend/src/providers/oidc/provider.test.ts b/plugins/auth-backend/src/providers/oidc/provider.test.ts index 5073cd88d1..1851d1a473 100644 --- a/plugins/auth-backend/src/providers/oidc/provider.test.ts +++ b/plugins/auth-backend/src/providers/oidc/provider.test.ts @@ -22,7 +22,7 @@ import { createOidcProvider, OidcAuthProvider } from './provider'; import { JWT, JWK } from 'jose'; import { AuthProviderFactoryOptions } from '../types'; import { Config } from '@backstage/config'; -import { OAuthAdapter } from '../../lib/oauth'; +import { OAuthAdapter, OAuthStartRequest } from '../../lib/oauth'; const issuerMetadata = { issuer: 'https://oidc.test', @@ -54,9 +54,11 @@ describe('OidcAuthProvider', () => { .get('/.well-known/openid-configuration') .reply(200, issuerMetadata); const provider = new OidcAuthProvider(clientMetadata); - const strategy = ((await (provider as any)._strategy) as any) as { - _client: ClientMetadata; - _issuer: IssuerMetadata; + const { strategy } = ((await provider._implementation) as any) as { + strategy: { + _client: ClientMetadata; + _issuer: IssuerMetadata; + }; }; // Assert that the expected request to the metadaurl was made. expect(scope.isDone()).toBeTruthy(); @@ -89,27 +91,38 @@ describe('OidcAuthProvider', () => { const provider = new OidcAuthProvider(clientMetadata); const req = { method: 'GET', - url: '/?code=test2', + url: 'https://oidc.test/?code=test2', session: ({ 'oidc:oidc.test': 'test' } as any) as Session, } as express.Request; await provider.handler(req); expect(scope.isDone()).toBeTruthy(); }); - const options = { - globalConfig: { - appUrl: 'https://oidc.test', - baseUrl: 'https://oidc.test', - }, - config: ({ - keys: jest.fn(() => ['test']), - getConfig: jest.fn(() => ({ getString: () => '' })), - } as any) as Config, - } as AuthProviderFactoryOptions; - - it('createOidcProvider', () => { + it('createOidcProvider', async () => { + const scope = nock('https://oidc.test') + .get('/.well-known/openid-configuration') + .reply(200, issuerMetadata); + const options = { + globalConfig: { + appUrl: 'https://oidc.test', + baseUrl: 'https://oidc.test', + }, + config: ({ + keys: jest.fn(() => ['test']), + getConfig: jest.fn(() => ({ + getString: (key: string) => { + const conf = { + ...clientMetadata, + metadataUrl: 'https://oidc.test/.well-known/openid-configuration', + } as any; + return conf[key] as string; + }, + })), + } as any) as Config, + } as AuthProviderFactoryOptions; const provider = createOidcProvider(options) as OAuthAdapter; - console.log(provider); expect(provider.start).toBeDefined(); + await new Promise(resolve => process.nextTick(resolve)); // advance a tick to give nock a chance to intercept the request + expect(scope.isDone()).toBeTruthy(); }); }); From 78177f24b7770e81ef414b64b29069606cac3ea8 Mon Sep 17 00:00:00 2001 From: Brian Leathem Date: Mon, 23 Nov 2020 14:54:24 -0800 Subject: [PATCH 216/430] Fixed a lint failure on the oidc provider test --- plugins/auth-backend/src/providers/oidc/provider.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/auth-backend/src/providers/oidc/provider.test.ts b/plugins/auth-backend/src/providers/oidc/provider.test.ts index 1851d1a473..3732e7b1a7 100644 --- a/plugins/auth-backend/src/providers/oidc/provider.test.ts +++ b/plugins/auth-backend/src/providers/oidc/provider.test.ts @@ -22,7 +22,7 @@ import { createOidcProvider, OidcAuthProvider } from './provider'; import { JWT, JWK } from 'jose'; import { AuthProviderFactoryOptions } from '../types'; import { Config } from '@backstage/config'; -import { OAuthAdapter, OAuthStartRequest } from '../../lib/oauth'; +import { OAuthAdapter } from '../../lib/oauth'; const issuerMetadata = { issuer: 'https://oidc.test', From cec3da2c901998a71074936a62ed9383dd314bf6 Mon Sep 17 00:00:00 2001 From: Brian Leathem Date: Mon, 23 Nov 2020 15:23:21 -0800 Subject: [PATCH 217/430] Used ts private instead of _ naming convention --- .../auth-backend/src/providers/oidc/provider.test.ts | 2 +- plugins/auth-backend/src/providers/oidc/provider.ts | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/auth-backend/src/providers/oidc/provider.test.ts b/plugins/auth-backend/src/providers/oidc/provider.test.ts index 3732e7b1a7..f2bd8dcb90 100644 --- a/plugins/auth-backend/src/providers/oidc/provider.test.ts +++ b/plugins/auth-backend/src/providers/oidc/provider.test.ts @@ -54,7 +54,7 @@ describe('OidcAuthProvider', () => { .get('/.well-known/openid-configuration') .reply(200, issuerMetadata); const provider = new OidcAuthProvider(clientMetadata); - const { strategy } = ((await provider._implementation) as any) as { + const { strategy } = ((await (provider as any).implementation) as any) as { strategy: { _client: ClientMetadata; _issuer: IssuerMetadata; diff --git a/plugins/auth-backend/src/providers/oidc/provider.ts b/plugins/auth-backend/src/providers/oidc/provider.ts index 765fd8627e..4f2054c5f6 100644 --- a/plugins/auth-backend/src/providers/oidc/provider.ts +++ b/plugins/auth-backend/src/providers/oidc/provider.ts @@ -54,14 +54,14 @@ export type OidcAuthProviderOptions = OAuthProviderOptions & { }; export class OidcAuthProvider implements OAuthHandlers { - readonly _implementation: Promise; + private readonly implementation: Promise; constructor(options: OidcAuthProviderOptions) { - this._implementation = this.setupStrategy(options); + this.implementation = this.setupStrategy(options); } async start(req: OAuthStartRequest): Promise { - const { strategy } = await this._implementation; + const { strategy } = await this.implementation; return await executeRedirectStrategy(req, strategy, { accessType: 'offline', prompt: 'none', @@ -73,7 +73,7 @@ export class OidcAuthProvider implements OAuthHandlers { async handler( req: express.Request, ): Promise<{ response: OAuthResponse; refreshToken: string }> { - const { strategy } = await this._implementation; + const { strategy } = await this.implementation; const { response, privateInfo } = await executeFrameHandlerStrategy< OAuthResponse, PrivateInfo @@ -86,7 +86,7 @@ export class OidcAuthProvider implements OAuthHandlers { } async refresh(req: OAuthRefreshRequest): Promise { - const { client } = await this._implementation; + const { client } = await this.implementation; const tokenset = await client.refresh(req.refreshToken); if (!tokenset.access_token) { throw new Error('Refresh failed'); From 9c9d12e8993bd196165578abb083c883d29e3d96 Mon Sep 17 00:00:00 2001 From: Brian Leathem Date: Tue, 24 Nov 2020 11:30:25 -0800 Subject: [PATCH 218/430] Added back the id_token when calling refresh --- plugins/auth-backend/src/providers/oidc/provider.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/auth-backend/src/providers/oidc/provider.ts b/plugins/auth-backend/src/providers/oidc/provider.ts index 4f2054c5f6..fbf38c69c4 100644 --- a/plugins/auth-backend/src/providers/oidc/provider.ts +++ b/plugins/auth-backend/src/providers/oidc/provider.ts @@ -97,7 +97,8 @@ export class OidcAuthProvider implements OAuthHandlers { providerInfo: { accessToken: tokenset.access_token, refreshToken: tokenset.refresh_token, - expiresInSeconds: tokenset.expires_at, + expiresInSeconds: tokenset.expires_in, + idToken: tokenset.id_token, scope: tokenset.scope || '', }, profile, From e338648910fc3348a7e9180845e95f32655b530b Mon Sep 17 00:00:00 2001 From: jothisubaramaniam Date: Tue, 24 Nov 2020 12:16:33 -0500 Subject: [PATCH 219/430] add a postMessage with targetOrigin * --- packages/core-api/src/lib/loginPopup.test.ts | 46 +++++++++++++++++++ packages/core-api/src/lib/loginPopup.ts | 15 +++++- .../src/lib/flow/authFlowHelpers.test.ts | 46 +++++++++++++++++++ .../src/lib/flow/authFlowHelpers.ts | 18 +++++++- 4 files changed, 122 insertions(+), 3 deletions(-) diff --git a/packages/core-api/src/lib/loginPopup.test.ts b/packages/core-api/src/lib/loginPopup.test.ts index 55f1408e8d..6527618ef7 100644 --- a/packages/core-api/src/lib/loginPopup.test.ts +++ b/packages/core-api/src/lib/loginPopup.test.ts @@ -156,6 +156,15 @@ describe('showLoginPopup', () => { expect(addEventListenerSpy).toBeCalledTimes(1); expect(removeEventListenerSpy).toBeCalledTimes(0); + const listener = addEventListenerSpy.mock.calls[0][1] as EventListener; + listener({ + source: popupMock, + origin: 'origin', + data: { + targetOrigin: 'http://localhost', + }, + } as MessageEvent); + setTimeout(() => { popupMock.closed = true; }, 150); @@ -167,4 +176,41 @@ describe('showLoginPopup', () => { expect(addEventListenerSpy).toBeCalledTimes(1); expect(removeEventListenerSpy).toBeCalledTimes(1); }); + + it('should indicate if origin does not match', async () => { + const openSpy = jest + .spyOn(window, 'open') + .mockReturnValue({ closed: false } as Window); + const addEventListenerSpy = jest.spyOn(window, 'addEventListener'); + const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener'); + const popupMock = { closed: false }; + + openSpy.mockReturnValue(popupMock as Window); + + const payloadPromise = showLoginPopup({ + url: 'url', + name: 'name', + origin: 'origin', + }); + + const listener = addEventListenerSpy.mock.calls[0][1] as EventListener; + listener({ + source: popupMock, + origin: 'origin', + data: { + targetOrigin: 'http://differenthost', + }, + } as MessageEvent); + + setTimeout(() => { + popupMock.closed = true; + }, 150); + await expect(payloadPromise).rejects.toThrow( + 'Login failed, incorrect app origin', + ); + + expect(openSpy).toBeCalledTimes(1); + expect(addEventListenerSpy).toBeCalledTimes(1); + expect(removeEventListenerSpy).toBeCalledTimes(1); + }); }); diff --git a/packages/core-api/src/lib/loginPopup.ts b/packages/core-api/src/lib/loginPopup.ts index 10d6efd7a4..5c6d424363 100644 --- a/packages/core-api/src/lib/loginPopup.ts +++ b/packages/core-api/src/lib/loginPopup.ts @@ -79,6 +79,8 @@ export function showLoginPopup(options: LoginPopupOptions): Promise { `menubar=no,location=no,resizable=no,scrollbars=no,status=no,width=${width},height=${height},top=${top},left=${left}`, ); + let targetOrigin = ''; + if (!popup || typeof popup.closed === 'undefined' || popup.closed) { reject(new Error('Failed to open auth popup.')); return; @@ -92,6 +94,12 @@ export function showLoginPopup(options: LoginPopupOptions): Promise { return; } const { data } = event; + + if (data.targetOrigin) { + targetOrigin = data.targetOrigin; + return; + } + if (data.type !== 'authorization_response') { return; } @@ -111,7 +119,12 @@ export function showLoginPopup(options: LoginPopupOptions): Promise { const intervalId = setInterval(() => { if (popup.closed) { - const error = new Error('Login failed, popup was closed'); + const errMessage = `Login failed, ${ + targetOrigin !== window.location.origin + ? 'incorrect app origin' + : 'popup was closed' + }`; + const error = new Error(errMessage); error.name = 'PopupClosedError'; reject(error); done(); diff --git a/plugins/auth-backend/src/lib/flow/authFlowHelpers.test.ts b/plugins/auth-backend/src/lib/flow/authFlowHelpers.test.ts index 6c49eaf351..5e4e5f875b 100644 --- a/plugins/auth-backend/src/lib/flow/authFlowHelpers.test.ts +++ b/plugins/auth-backend/src/lib/flow/authFlowHelpers.test.ts @@ -81,6 +81,52 @@ describe('oauth helpers', () => { expect(mockResponse.end).toBeCalledWith(expect.stringContaining(encoded)); }); + it('should call postMessage twice but only one of them with target *', () => { + let responseBody: String; + + const mockResponse = ({ + end: jest.fn(function (body) { + responseBody = body; + return this; + }), + setHeader: jest.fn().mockReturnThis(), + } as unknown) as express.Response; + + const data: WebMessageResponse = { + type: 'authorization_response', + response: { + providerInfo: { + accessToken: 'ACCESS_TOKEN', + idToken: 'ID_TOKEN', + expiresInSeconds: 10, + scope: 'email', + }, + profile: { + email: 'foo@bar.com', + }, + backstageIdentity: { + id: 'a', + idToken: 'a.b.c', + }, + }, + }; + postMessageResponse(mockResponse, appOrigin, data); + expect(responseBody.match(/.postMessage\(/g)).toHaveLength(2); + expect( + responseBody.match(/.postMessage\([a-zA-z.()]*, \'\*\'\)/g), + ).toHaveLength(1); + + const errData: WebMessageResponse = { + type: 'authorization_response', + error: new Error('Unknown error occurred'), + }; + postMessageResponse(mockResponse, appOrigin, errData); + expect(responseBody.match(/.postMessage\(/g)).toHaveLength(2); + expect( + responseBody.match(/.postMessage\([a-zA-z.()]*, \'\*\'\)/g), + ).toHaveLength(1); + }); + it('handles single quotes and unicode chars safely', () => { const mockResponse = ({ end: jest.fn().mockReturnThis(), diff --git a/plugins/auth-backend/src/lib/flow/authFlowHelpers.ts b/plugins/auth-backend/src/lib/flow/authFlowHelpers.ts index d2a9557a28..5aba427905 100644 --- a/plugins/auth-backend/src/lib/flow/authFlowHelpers.ts +++ b/plugins/auth-backend/src/lib/flow/authFlowHelpers.ts @@ -38,10 +38,24 @@ export const postMessageResponse = ( // data. // TODO: Make target app origin configurable globally + + // + // postMessage fails silently if the targetOrigin is disallowed. + // So 2 postMessages are sent from the popup to the parent window. + // First, the origin being used to post the actual authorization response is + // shared with the parent window with a postMessage with targetOrigin '*'. + // Second, the actual authorization response is sent with the app origin + // as the targetOrigin. + // If the first message was received but the actual auth response was + // never received, the event listener can conclude that targetOrigin + // was disallowed, indicating potential misconfiguration. + // const script = ` - var json = decodeURIComponent('${base64Data}'); + var authResponse = decodeURIComponent('${base64Data}'); var origin = decodeURIComponent('${base64Origin}'); - (window.opener || window.parent).postMessage(JSON.parse(json), origin); + var originInfo = {'targetOrigin': origin}; + (window.opener || window.parent).postMessage(originInfo, '*'); + (window.opener || window.parent).postMessage(JSON.parse(authResponse), origin); window.close(); `; const hash = crypto.createHash('sha256').update(script).digest('base64'); From 86ab16244a01b0183648c9e818179836e1f1c45e Mon Sep 17 00:00:00 2001 From: Brenda Sukh Date: Tue, 24 Nov 2020 16:55:41 -0500 Subject: [PATCH 220/430] Update wording to product in cost by product component --- ...uct.tsx => CostOverviewByProductChart.tsx} | 161 +++++++++--------- 1 file changed, 82 insertions(+), 79 deletions(-) rename plugins/cost-insights/src/components/CostOverviewCard/{CostOverviewByProduct.tsx => CostOverviewByProductChart.tsx} (63%) diff --git a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProduct.tsx b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProductChart.tsx similarity index 63% rename from plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProduct.tsx rename to plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProductChart.tsx index 1496fcdaf8..710c085b48 100644 --- a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProduct.tsx +++ b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProductChart.tsx @@ -32,6 +32,7 @@ import { Cost, DEFAULT_DATE_FORMAT, CostInsightsTheme } from '../../types'; import { BarChartTooltip as Tooltip, BarChartTooltipItem as TooltipItem, + BarChartLegend, } from '../BarChart'; import { overviewGraphTickFormatter, @@ -45,68 +46,100 @@ import { import { useFilters, useLastCompleteBillingDate } from '../../hooks'; import { mapFiltersToProps } from './selector'; import { getPreviousPeriodTotalCost } from '../../utils/change'; -import { LegendItem } from '../LegendItem'; -import { currencyFormatter } from '../../utils/formatters'; +import { formatPeriod } from '../../utils/formatters'; import { aggregationSum } from '../../utils/sum'; +import { BarChartLegendOptions } from '../BarChart/BarChartLegend'; dayjs.extend(utc); -export type CostOverviewByProductProps = { - dailyCostData: Cost; +export type CostOverviewByProductChartProps = { + costsByProduct: Cost[]; }; -const LOW_COST_THRESHOLD = 0.01; +const LOW_COST_THRESHOLD = 0.1; -export const CostOverviewByProduct = ({ - dailyCostData, -}: CostOverviewByProductProps) => { +export const CostOverviewByProductChart = ({ + costsByProduct, +}: CostOverviewByProductChartProps) => { const theme = useTheme(); const styles = useStyles(theme); const lastCompleteBillingDate = useLastCompleteBillingDate(); const { duration } = useFilters(mapFiltersToProps); - const groupedCosts = dailyCostData.groupedCosts; - if (!groupedCosts) { + if (!costsByProduct) { return null; } - const flattenedAggregation = groupedCosts + const flattenedAggregation = costsByProduct .map(cost => cost.aggregation) .flat(); - const totalCost = flattenedAggregation.reduce( - (acc, agg) => acc + agg.amount, - 0, - ); - const productsByDate = groupedCosts.reduce((prodByDate, group) => { - const product = group.id; - group.aggregation.forEach(curAggregation => { - const productCostsForDate = prodByDate[curAggregation.date] || {}; - // Group products with less than 10% of the total cost into "Other" category - if (aggregationSum(group.aggregation) < totalCost * LOW_COST_THRESHOLD) { - prodByDate[curAggregation.date] = { - ...productCostsForDate, - Other: - (prodByDate[curAggregation.date].Other || 0) + - curAggregation.amount, - }; - } else { - prodByDate[curAggregation.date] = { - ...productCostsForDate, - [product]: curAggregation.amount, - }; - } + const totalCost = aggregationSum(flattenedAggregation); + + const previousPeriodTotal = getPreviousPeriodTotalCost( + flattenedAggregation, + duration, + lastCompleteBillingDate, + ); + const currentPeriodTotal = totalCost - previousPeriodTotal; + const productsByDate = costsByProduct.reduce((prodByDate, product) => { + const productTotal = aggregationSum(product.aggregation); + // Group products with less than 10% of the total cost into "Other" category + // when there we have >= 5 products. + const isOtherProduct = + costsByProduct.length >= 5 && + productTotal < totalCost * LOW_COST_THRESHOLD; + const productName = isOtherProduct ? 'Other' : product.id; + const updatedProdByDate = { ...prodByDate }; + + product.aggregation.forEach(curAggregation => { + const productCostsForDate = updatedProdByDate[curAggregation.date] || {}; + + updatedProdByDate[curAggregation.date] = { + ...productCostsForDate, + [productName]: + (productCostsForDate[productName] || 0) + curAggregation.amount, + }; }); - return prodByDate; + return updatedProdByDate; }, {} as Record>); - const chartData: any[] = Object.keys(productsByDate).map(date => { - return { - ...productsByDate[date], - date: Date.parse(date), - }; - }); + const chartData: Record[] = Object.keys(productsByDate).map( + date => { + return { + ...productsByDate[date], + date: Date.parse(date), + }; + }, + ); + + const renderAreas = () => { + const productGroupNames = new Set( + Object.values(productsByDate) + .map(d => Object.keys(d)) + .flat(), + ); + const sortedProducts = costsByProduct + // Check that product is a separate group and hasn't been added to 'Other' + .filter( + product => product.id !== 'Other' && productGroupNames.has(product.id), + ) + .sort( + (a, b) => aggregationSum(a.aggregation) - aggregationSum(b.aggregation), + ) + .map(product => product.id); + // Keep 'Other' category at the bottom of the stack + return ['Other', ...sortedProducts].map((product, i) => ( + + )); + }; const tooltipRenderer: ContentRenderer = ({ label, @@ -130,53 +163,24 @@ export const CostOverviewByProduct = ({ ); }; - const previousPeriodTotal = getPreviousPeriodTotalCost( - flattenedAggregation, - duration, - lastCompleteBillingDate, - ); - const currentPeriodTotal = totalCost - previousPeriodTotal; - - const renderAreas = () => { - const highCostGroups = groupedCosts.filter( - group => - aggregationSum(group.aggregation) >= totalCost * LOW_COST_THRESHOLD, - ); - const sortedCostGroups = highCostGroups - .sort( - (a, b) => aggregationSum(a.aggregation) - aggregationSum(b.aggregation), - ) - .map(group => group.id); - // Keep 'Other' category at the bottom of the stack - return ['Other', ...sortedCostGroups].map((group, i) => ( - - )); + const options: Partial = { + previousName: formatPeriod(duration, lastCompleteBillingDate, false), + currentName: formatPeriod(duration, lastCompleteBillingDate, true), + hideMarker: true, }; return ( - - - {currencyFormatter.format(previousPeriodTotal)} - - - - - {currencyFormatter.format(currentPeriodTotal)} - - + {renderAreas()} - From bf9d87dc16336cfe83f1c0855f46239ff026557d Mon Sep 17 00:00:00 2001 From: Brenda Sukh Date: Tue, 24 Nov 2020 16:56:20 -0500 Subject: [PATCH 221/430] Move cost overview chart legend to separate component --- .../CostOverviewCard/CostOverviewCard.tsx | 75 +++-------- .../CostOverviewCard/CostOverviewChart.tsx | 120 ++++++++++-------- .../CostOverviewCard/CostOverviewLegend.tsx | 84 ++++++++++++ 3 files changed, 163 insertions(+), 116 deletions(-) create mode 100644 plugins/cost-insights/src/components/CostOverviewCard/CostOverviewLegend.tsx diff --git a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx index 742b89af7a..c385d5d424 100644 --- a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx +++ b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx @@ -24,24 +24,15 @@ import { Tab, Tabs, } from '@material-ui/core'; -import { CostGrowth } from '../CostGrowth'; import { CostOverviewChart } from './CostOverviewChart'; -import { CostOverviewByProduct } from './CostOverviewByProduct'; +import { CostOverviewByProductChart } from './CostOverviewByProductChart'; import { CostOverviewHeader } from './CostOverviewHeader'; -import { LegendItem } from '../LegendItem'; import { MetricSelect } from '../MetricSelect'; import { PeriodSelect } from '../PeriodSelect'; -import { - useScroll, - useFilters, - useConfig, - useLastCompleteBillingDate, -} from '../../hooks'; +import { useScroll, useFilters, useConfig } from '../../hooks'; import { mapFiltersToProps } from './selector'; import { DefaultNavigation } from '../../utils/navigation'; -import { formatPercent } from '../../utils/formatters'; import { findAlways } from '../../utils/assert'; -import { getComparedChange } from '../../utils/change'; import { Cost, CostInsightsTheme, MetricData } from '../../types'; import { useOverviewTabsStyles } from '../../utils/styles'; @@ -56,7 +47,6 @@ export const CostOverviewCard = ({ }: CostOverviewCardProps) => { const theme = useTheme(); const config = useConfig(); - const lastCompleteBillingDate = useLastCompleteBillingDate(); const [tabIndex, setTabIndex] = useState(0); const { ScrollAnchor } = useScroll(DefaultNavigation.CostOverviewCard); @@ -67,19 +57,11 @@ export const CostOverviewCard = ({ const metric = filters.metric ? findAlways(config.metrics, m => m.kind === filters.metric) : null; - const comparedChange = metricData - ? getComparedChange( - dailyCostData, - metricData, - filters.duration, - lastCompleteBillingDate, - ) - : null; const styles = useOverviewTabsStyles(theme); const tabs = [ - { id: 'overview', label: 'OVERVIEW' }, - { id: 'breakdown', label: 'BREAKDOWN BY PRODUCT' }, + { id: 'overview', label: 'Total cost' }, + { id: 'breakdown', label: 'Breakdown by product' }, ]; const OverviewTabs = () => { @@ -104,34 +86,8 @@ export const CostOverviewCard = ({ ); }; - const OverviewLegend = () => { - return ( - - - - {formatPercent(dailyCostData.change!.ratio)} - - - {metric && metricData && comparedChange && ( - <> - - - {formatPercent(metricData.change.ratio)} - - - - - - - )} - - ); - }; + // Metrics can only be selected on the total cost graph + const showMetricSelect = config.metrics.length && tabIndex === 0; return ( @@ -146,20 +102,19 @@ export const CostOverviewCard = ({ {tabIndex === 0 ? ( - <> - - - + ) : ( - + )} - {config.metrics.length && tabIndex === 0 && ( + {showMetricSelect && ( - - - - 0, 'dataMax']} - tick={{ fill: styles.axis.fill }} - tickFormatter={formatGraphValue} - width={styles.yAxis.width} - yAxisId={data.dailyCost.dataKey} - /> - {metric && ( - 0, toDataMax(data.metric.dataKey, chartData)]} - width={styles.yAxis.width} - yAxisId={data.metric.dataKey} + + + + + + + 0, 'dataMax']} + tick={{ fill: styles.axis.fill }} + tickFormatter={formatGraphValue} + width={styles.yAxis.width} + yAxisId={data.dailyCost.dataKey} + /> + {metric && ( + 0, toDataMax(data.metric.dataKey, chartData)]} + width={styles.yAxis.width} + yAxisId={data.metric.dataKey} + /> + )} + - )} - - - {metric && ( - )} - - - + {metric && ( + + )} + + + + ); }; diff --git a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewLegend.tsx b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewLegend.tsx new file mode 100644 index 0000000000..ebba5173a9 --- /dev/null +++ b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewLegend.tsx @@ -0,0 +1,84 @@ +/* + * 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, { PropsWithChildren } from 'react'; +import { Box, useTheme } from '@material-ui/core'; +import { LegendItem } from '../LegendItem'; +import { + CostInsightsTheme, + MetricData, + Maybe, + Cost, + Metric, +} from '../../types'; +import { useLastCompleteBillingDate, useFilters } from '../../hooks'; +import { getComparedChange } from '../../utils/change'; +import { mapFiltersToProps } from './selector'; +import { formatPercent } from '../../utils/formatters'; +import { CostGrowth } from '../CostGrowth'; + +type CostOverviewLegendProps = { + metric: Maybe; + metricData: Maybe; + dailyCostData: Cost; +}; + +export const CostOverviewLegend = ({ + dailyCostData, + metric, + metricData, +}: PropsWithChildren) => { + const theme = useTheme(); + + const lastCompleteBillingDate = useLastCompleteBillingDate(); + const { duration } = useFilters(mapFiltersToProps); + + const comparedChange = metricData + ? getComparedChange( + dailyCostData, + metricData, + duration, + lastCompleteBillingDate, + ) + : null; + + return ( + + + + {formatPercent(dailyCostData.change!.ratio)} + + + {metric && metricData && comparedChange && ( + <> + + + {formatPercent(metricData.change.ratio)} + + + + + + + )} + + ); +}; From 62f1c825cf2fd0630b160e876bb412ae8c0e9a50 Mon Sep 17 00:00:00 2001 From: Brenda Sukh Date: Tue, 24 Nov 2020 16:56:43 -0500 Subject: [PATCH 222/430] Move mock data utils --- plugins/cost-insights/src/client.ts | 54 +++---------------- .../components/BarChart/BarChartLegend.tsx | 13 +++-- plugins/cost-insights/src/utils/styles.ts | 5 +- 3 files changed, 19 insertions(+), 53 deletions(-) diff --git a/plugins/cost-insights/src/client.ts b/plugins/cost-insights/src/client.ts index 96e9025f5d..a20344ffb3 100644 --- a/plugins/cost-insights/src/client.ts +++ b/plugins/cost-insights/src/client.ts @@ -20,9 +20,7 @@ import { CostInsightsApi, ProductInsightsOptions } from '../src/api'; import { Alert, Cost, - DateAggregation, DEFAULT_DATE_FORMAT, - Duration, Entity, Group, MetricData, @@ -34,52 +32,12 @@ import { ProjectGrowthAlert, UnlabeledDataflowAlert, } from '../src/utils/alerts'; -import { inclusiveStartDateOf } from '../src/utils/duration'; -import { trendlineOf, changeOf, getGroupedProducts } from './utils/mockData'; - -type IntervalFields = { - duration: Duration; - endDate: string; -}; - -function parseIntervals(intervals: string): IntervalFields { - const match = intervals.match( - /\/(?P\d+[DM])\/(?\d{4}-\d{2}-\d{2})/, - ); - if (Object.keys(match?.groups || {}).length !== 2) { - throw new Error(`Invalid intervals: ${intervals}`); - } - const { duration, date } = match!.groups!; - return { - duration: duration as Duration, - endDate: date, - }; -} - -export function aggregationFor( - intervals: string, - baseline: number, -): DateAggregation[] { - const { duration, endDate } = parseIntervals(intervals); - const days = dayjs(endDate).diff( - inclusiveStartDateOf(duration, endDate), - 'day', - ); - - return [...Array(days).keys()].reduce( - (values: DateAggregation[], i: number): DateAggregation[] => { - const last = values.length ? values[values.length - 1].amount : baseline; - values.push({ - date: dayjs(inclusiveStartDateOf(duration, endDate)) - .add(i, 'day') - .format(DEFAULT_DATE_FORMAT), - amount: Math.max(0, last + (baseline / 20) * (Math.random() * 2 - 1)), - }); - return values; - }, - [], - ); -} +import { + trendlineOf, + changeOf, + getGroupedProducts, + aggregationFor, +} from './utils/mockData'; export class ExampleCostInsightsClient implements CostInsightsApi { private request(_: any, res: any): Promise { diff --git a/plugins/cost-insights/src/components/BarChart/BarChartLegend.tsx b/plugins/cost-insights/src/components/BarChart/BarChartLegend.tsx index 6db0e13572..7ed6854d35 100644 --- a/plugins/cost-insights/src/components/BarChart/BarChartLegend.tsx +++ b/plugins/cost-insights/src/components/BarChart/BarChartLegend.tsx @@ -21,11 +21,12 @@ import { currencyFormatter } from '../../utils/formatters'; import { CostInsightsTheme } from '../../types'; import { useBarChartLayoutStyles as useStyles } from '../../utils/styles'; -type BarChartLegendOptions = { +export type BarChartLegendOptions = { previousName: string; previousFill: string; currentName: string; currentFill: string; + hideMarker?: boolean; }; export type BarChartLegendProps = { @@ -56,12 +57,18 @@ export const BarChartLegend = ({ return ( - + {currencyFormatter.format(costStart)} - + {currencyFormatter.format(costEnd)} diff --git a/plugins/cost-insights/src/utils/styles.ts b/plugins/cost-insights/src/utils/styles.ts index f149ac9be7..b0378b6c98 100644 --- a/plugins/cost-insights/src/utils/styles.ts +++ b/plugins/cost-insights/src/utils/styles.ts @@ -103,9 +103,10 @@ export const useCostOverviewStyles = (theme: CostInsightsTheme) => ({ export const useOverviewTabsStyles = makeStyles( (theme: CostInsightsTheme) => ({ default: { - padding: theme.spacing(2, 2), - fontWeight: 'bold', + padding: theme.spacing(2), + fontWeight: theme.typography.fontWeightBold, color: theme.palette.text.secondary, + textTransform: 'uppercase', }, selected: { color: theme.palette.text.primary, From 3c66f864b2f10695708a3e4ccebcd5e91d3e4351 Mon Sep 17 00:00:00 2001 From: jothisubaramaniam Date: Tue, 24 Nov 2020 17:12:22 -0500 Subject: [PATCH 223/430] changeset for bugfix issue 3223 --- .changeset/auth-backend-ten-doors-exist.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/auth-backend-ten-doors-exist.md diff --git a/.changeset/auth-backend-ten-doors-exist.md b/.changeset/auth-backend-ten-doors-exist.md new file mode 100644 index 0000000000..97a3a421ee --- /dev/null +++ b/.changeset/auth-backend-ten-doors-exist.md @@ -0,0 +1,6 @@ +--- +'@backstage/core-api': patch +'@backstage/plugin-auth-backend': patch +--- + +bugfix: issue 3223 - detect mismatching origin and indicate it in the message at auth failure From 7e49365080205c97e4937b9109bf681270c029e0 Mon Sep 17 00:00:00 2001 From: jothisubaramaniam Date: Tue, 24 Nov 2020 17:24:03 -0500 Subject: [PATCH 224/430] fix ci --- .changeset/auth-backend-ten-doors-exist.md | 2 +- plugins/auth-backend/src/lib/flow/authFlowHelpers.test.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.changeset/auth-backend-ten-doors-exist.md b/.changeset/auth-backend-ten-doors-exist.md index 97a3a421ee..2dafaf728d 100644 --- a/.changeset/auth-backend-ten-doors-exist.md +++ b/.changeset/auth-backend-ten-doors-exist.md @@ -3,4 +3,4 @@ '@backstage/plugin-auth-backend': patch --- -bugfix: issue 3223 - detect mismatching origin and indicate it in the message at auth failure +bug fix: issue 3223 - detect mismatching origin and indicate it in the message at auth failure diff --git a/plugins/auth-backend/src/lib/flow/authFlowHelpers.test.ts b/plugins/auth-backend/src/lib/flow/authFlowHelpers.test.ts index 5e4e5f875b..10fb82eb36 100644 --- a/plugins/auth-backend/src/lib/flow/authFlowHelpers.test.ts +++ b/plugins/auth-backend/src/lib/flow/authFlowHelpers.test.ts @@ -82,10 +82,10 @@ describe('oauth helpers', () => { }); it('should call postMessage twice but only one of them with target *', () => { - let responseBody: String; + let responseBody = ''; const mockResponse = ({ - end: jest.fn(function (body) { + end: jest.fn(body => { responseBody = body; return this; }), From 1b5e8fbae61dff3e0b6ced8f8b103242b1c7ef4e Mon Sep 17 00:00:00 2001 From: Brenda Sukh Date: Tue, 24 Nov 2020 17:48:01 -0500 Subject: [PATCH 225/430] Add data viz colors for both themes --- .../CostOverviewByProductChart.tsx | 9 ++--- .../CostOverviewCard/CostOverviewCard.tsx | 12 +++--- plugins/cost-insights/src/types/Theme.ts | 1 + plugins/cost-insights/src/utils/styles.ts | 37 ++++++++++++------- 4 files changed, 35 insertions(+), 24 deletions(-) diff --git a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProductChart.tsx b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProductChart.tsx index 710c085b48..1a466fb4d7 100644 --- a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProductChart.tsx +++ b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProductChart.tsx @@ -39,10 +39,7 @@ import { formatGraphValue, isInvalid, } from '../../utils/graphs'; -import { - useCostOverviewStyles as useStyles, - DataVizColors, -} from '../../utils/styles'; +import { useCostOverviewStyles as useStyles } from '../../utils/styles'; import { useFilters, useLastCompleteBillingDate } from '../../hooks'; import { mapFiltersToProps } from './selector'; import { getPreviousPeriodTotalCost } from '../../utils/change'; @@ -135,8 +132,8 @@ export const CostOverviewByProductChart = ({ dataKey={product} stackId="1" fillOpacity="1" - stroke={DataVizColors[i]} - fill={DataVizColors[i]} + stroke={theme.palette.dataViz[i]} + fill={theme.palette.dataViz[i]} /> )); }; diff --git a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx index c385d5d424..39adc4bbf1 100644 --- a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx +++ b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx @@ -60,8 +60,12 @@ export const CostOverviewCard = ({ const styles = useOverviewTabsStyles(theme); const tabs = [ - { id: 'overview', label: 'Total cost' }, - { id: 'breakdown', label: 'Breakdown by product' }, + { id: 'overview', label: 'Total cost', title: 'Cloud Cost' }, + { + id: 'breakdown', + label: 'Breakdown by product', + title: 'Cloud Cost By Product', + }, ]; const OverviewTabs = () => { @@ -94,9 +98,7 @@ export const CostOverviewCard = ({ {dailyCostData.groupedCosts && } - + diff --git a/plugins/cost-insights/src/types/Theme.ts b/plugins/cost-insights/src/types/Theme.ts index 9053283351..0f4096adc3 100644 --- a/plugins/cost-insights/src/types/Theme.ts +++ b/plugins/cost-insights/src/types/Theme.ts @@ -30,6 +30,7 @@ type CostInsightsPaletteAdditions = { tooltip: CostInsightsTooltipOptions; navigationText: string; alertBackground: string; + dataViz: string[]; }; export type CostInsightsPalette = BackstagePalette & diff --git a/plugins/cost-insights/src/utils/styles.ts b/plugins/cost-insights/src/utils/styles.ts index b0378b6c98..f318f347f9 100644 --- a/plugins/cost-insights/src/utils/styles.ts +++ b/plugins/cost-insights/src/utils/styles.ts @@ -24,19 +24,6 @@ import { import { BackstageTheme } from '@backstage/theme'; import { CostInsightsTheme, CostInsightsThemeOptions } from '../types'; -export const DataVizColors = [ - '#509BF5', - '#FF6437', - '#4B917D', - '#F573A0', - '#F59B23', - '#B49BC8', - '#C39687', - '#A0C3D2', - '#FFC864', - '#BABABA', -]; - export const costInsightsLightTheme = { palette: { blue: '#509AF5', @@ -50,6 +37,18 @@ export const costInsightsLightTheme = { }, navigationText: '#b5b5b5', alertBackground: 'rgba(219, 219, 219, 0.13)', + dataViz: [ + '#509BF5', + '#FF6437', + '#4B917D', + '#F573A0', + '#F59B23', + '#B49BC8', + '#C39687', + '#A0C3D2', + '#FFC864', + '#BABABA', + ], }, } as CostInsightsThemeOptions; @@ -68,6 +67,18 @@ export const costInsightsDarkTheme = { }, navigationText: '#b5b5b5', alertBackground: 'rgba(32, 32, 32, 0.13)', + dataViz: [ + '#B9D6FB', + '#FFC1AF', + '#B7D3CB', + '#FBC7D9', + '#FBD6A7', + '#E1D7E9', + '#E7D5CF', + '#D9E7ED', + '#FFE9C1', + '#E3E3E3', + ], }, } as CostInsightsThemeOptions; From fa5ce266dbbfd98011e967e5202695a8ca080af9 Mon Sep 17 00:00:00 2001 From: Brenda Sukh Date: Tue, 24 Nov 2020 17:49:49 -0500 Subject: [PATCH 226/430] Move mock data utils --- plugins/cost-insights/src/utils/mockData.ts | 58 ++++++++++++++++++--- 1 file changed, 52 insertions(+), 6 deletions(-) diff --git a/plugins/cost-insights/src/utils/mockData.ts b/plugins/cost-insights/src/utils/mockData.ts index 4432753c32..cf9f13daa7 100644 --- a/plugins/cost-insights/src/utils/mockData.ts +++ b/plugins/cost-insights/src/utils/mockData.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import dayjs from 'dayjs'; import regression, { DataPoint } from 'regression'; import { Config } from '@backstage/config'; import { ConfigApi } from '@backstage/core'; @@ -28,17 +29,23 @@ import { UnlabeledDataflowAlertProject, UnlabeledDataflowData, DateAggregation, + DEFAULT_DATE_FORMAT, } from '../types'; import { DefaultLoadingAction, getDefaultState as getDefaultLoadingState, } from '../utils/loading'; import { findAlways } from '../utils/assert'; -import { aggregationFor } from '../client'; +import { inclusiveStartDateOf } from './duration'; type mockAlertRenderer = (alert: T) => T; type mockEntityRenderer = (entity: T) => T; +type IntervalFields = { + duration: Duration; + endDate: string; +}; + export const createMockEntity = ( callback?: mockEntityRenderer, ): Entity => { @@ -217,6 +224,45 @@ export function changeOf(aggregation: DateAggregation[]): ChangeStatistic { }; } +export function aggregationFor( + intervals: string, + baseline: number, +): DateAggregation[] { + const { duration, endDate } = parseIntervals(intervals); + const days = dayjs(endDate).diff( + inclusiveStartDateOf(duration, endDate), + 'day', + ); + + return [...Array(days).keys()].reduce( + (values: DateAggregation[], i: number): DateAggregation[] => { + const last = values.length ? values[values.length - 1].amount : baseline; + values.push({ + date: dayjs(inclusiveStartDateOf(duration, endDate)) + .add(i, 'day') + .format(DEFAULT_DATE_FORMAT), + amount: Math.max(0, last + (baseline / 20) * (Math.random() * 2 - 1)), + }); + return values; + }, + [], + ); +} + +function parseIntervals(intervals: string): IntervalFields { + const match = intervals.match( + /\/(?P\d+[DM])\/(?\d{4}-\d{2}-\d{2})/, + ); + if (Object.keys(match?.groups || {}).length !== 2) { + throw new Error(`Invalid intervals: ${intervals}`); + } + const { duration, date } = match!.groups!; + return { + duration: duration as Duration, + endDate: date, + }; +} + export const MockAggregatedDailyCosts: DateAggregation[] = [ { date: '2020-08-07', @@ -467,15 +513,15 @@ export const MockAggregatedDailyCosts: DateAggregation[] = [ export const getGroupedProducts = (intervals: string) => [ { id: 'Cloud Dataflow', - aggregation: aggregationFor(intervals, 1_000), + aggregation: aggregationFor(intervals, 1_700), }, { id: 'Compute Engine', - aggregation: aggregationFor(intervals, 100), + aggregation: aggregationFor(intervals, 350), }, { id: 'Cloud Storage', - aggregation: aggregationFor(intervals, 500), + aggregation: aggregationFor(intervals, 1_300), }, { id: 'BigQuery', @@ -483,7 +529,7 @@ export const getGroupedProducts = (intervals: string) => [ }, { id: 'Cloud SQL', - aggregation: aggregationFor(intervals, 10), + aggregation: aggregationFor(intervals, 750), }, { id: 'Cloud Spanner', @@ -491,7 +537,7 @@ export const getGroupedProducts = (intervals: string) => [ }, { id: 'Cloud Pub/Sub', - aggregation: aggregationFor(intervals, 30), + aggregation: aggregationFor(intervals, 1_000), }, { id: 'Cloud Bigtable', From 8972be02324e8d9546ae104a6b1033d6b5e536c6 Mon Sep 17 00:00:00 2001 From: Brenda Sukh Date: Tue, 24 Nov 2020 18:08:50 -0500 Subject: [PATCH 227/430] Update data viz dark theme colors --- .../CostOverviewByProductChart.tsx | 4 ++-- plugins/cost-insights/src/utils/styles.ts | 22 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProductChart.tsx b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProductChart.tsx index 1a466fb4d7..6cc576c04f 100644 --- a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProductChart.tsx +++ b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProductChart.tsx @@ -132,8 +132,8 @@ export const CostOverviewByProductChart = ({ dataKey={product} stackId="1" fillOpacity="1" - stroke={theme.palette.dataViz[i]} - fill={theme.palette.dataViz[i]} + stroke={theme.palette.dataViz[sortedProducts.length - i]} + fill={theme.palette.dataViz[sortedProducts.length - i]} /> )); }; diff --git a/plugins/cost-insights/src/utils/styles.ts b/plugins/cost-insights/src/utils/styles.ts index f318f347f9..3f762491e1 100644 --- a/plugins/cost-insights/src/utils/styles.ts +++ b/plugins/cost-insights/src/utils/styles.ts @@ -39,8 +39,8 @@ export const costInsightsLightTheme = { alertBackground: 'rgba(219, 219, 219, 0.13)', dataViz: [ '#509BF5', - '#FF6437', '#4B917D', + '#FF6437', '#F573A0', '#F59B23', '#B49BC8', @@ -68,16 +68,16 @@ export const costInsightsDarkTheme = { navigationText: '#b5b5b5', alertBackground: 'rgba(32, 32, 32, 0.13)', dataViz: [ - '#B9D6FB', - '#FFC1AF', - '#B7D3CB', - '#FBC7D9', - '#FBD6A7', - '#E1D7E9', - '#E7D5CF', - '#D9E7ED', - '#FFE9C1', - '#E3E3E3', + '#c1dffd', + '#baddd5', + '#ff9664', + '#ffa5d1', + '#ffcc57', + '#e6ccfb', + '#f7c7b7', + '#d2f6ff', + '#fffb94', + '#ececec', ], }, } as CostInsightsThemeOptions; From 2739de3fdc308e153043bbc1c656c3e537038c5a Mon Sep 17 00:00:00 2001 From: Ryan Vazquez Date: Tue, 24 Nov 2020 18:16:13 -0500 Subject: [PATCH 228/430] stack rank product panels --- plugins/cost-insights/src/client.ts | 6 +- .../ProductInsights/ProductInsights.tsx | 8 +- .../ProductInsightsCard.test.tsx | 43 ++-- .../ProductInsightsCard.tsx | 156 +++++++-------- .../ProductInsightsCardList.test.tsx | 156 +++++++++++++++ .../ProductInsightsCardList.tsx | 188 ++++++++++++++++++ .../ProductInsightsErrorCard.tsx | 50 +++++ .../components/ProductInsightsCard/index.ts | 2 +- .../cost-insights/src/hooks/useLoading.tsx | 9 +- plugins/cost-insights/src/utils/loading.ts | 10 +- plugins/cost-insights/src/utils/mockData.ts | 4 +- 11 files changed, 499 insertions(+), 133 deletions(-) create mode 100644 plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCardList.test.tsx create mode 100644 plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCardList.tsx create mode 100644 plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsErrorCard.tsx diff --git a/plugins/cost-insights/src/client.ts b/plugins/cost-insights/src/client.ts index 2180385424..db999584a0 100644 --- a/plugins/cost-insights/src/client.ts +++ b/plugins/cost-insights/src/client.ts @@ -332,14 +332,14 @@ export class ExampleCostInsightsClient implements CostInsightsApi { project: 'example-project', periodStart: '2020-Q2', periodEnd: '2020-Q3', - aggregation: [60_000, 120_000], + aggregation: [100, 120_000], change: { - ratio: 1, + ratio: 10.123123, amount: 60000, }, products: [ { id: 'Compute Engine', aggregation: [58_000, 118_000] }, - { id: 'Cloud Dataflow', aggregation: [1200, 1500] }, + { id: 'Cloud Dataflow', aggregation: [0, 15_000] }, { id: 'Cloud Storage', aggregation: [800, 500] }, ], }; diff --git a/plugins/cost-insights/src/components/ProductInsights/ProductInsights.tsx b/plugins/cost-insights/src/components/ProductInsights/ProductInsights.tsx index c5d39a5139..affef5d182 100644 --- a/plugins/cost-insights/src/components/ProductInsights/ProductInsights.tsx +++ b/plugins/cost-insights/src/components/ProductInsights/ProductInsights.tsx @@ -16,7 +16,7 @@ import React from 'react'; import { Box, Typography, Grid } from '@material-ui/core'; -import { ProductInsightsCard } from '../ProductInsightsCard'; +import { ProductInsightsCardList } from '../ProductInsightsCard'; import { useConfig } from '../../hooks'; export const ProductInsights = ({}) => { @@ -30,11 +30,7 @@ export const ProductInsights = ({}) => { - {config.products.map(product => ( - - - - ))} + ); diff --git a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.test.tsx b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.test.tsx index cdbbb79806..385ff82f9b 100644 --- a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.test.tsx +++ b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.test.tsx @@ -22,19 +22,19 @@ import { createMockEntity, mockDefaultLoadingState, MockComputeEngine, - MockProductFilters, } from '../../utils/mockData'; import { - MockCostInsightsApiProvider, - MockBillingDateProvider, MockConfigProvider, + MockCostInsightsApiProvider, MockCurrencyProvider, - MockFilterProvider, - MockGroupsProvider, + MockBillingDateProvider, MockScrollProvider, MockLoadingProvider, } from '../../utils/tests'; -import { Duration, Entity, Product, ProductPeriod } from '../../types'; +import { Duration, Entity, Product } from '../../types'; + +// suppress recharts componentDidUpdate warnings +jest.spyOn(console, 'warn').mockImplementation(() => {}); const costInsightsApi = (entity: Entity): Partial => ({ getProductInsights: () => Promise.resolve(entity), @@ -53,29 +53,25 @@ const mockProductCost = createMockEntity(() => ({ const renderProductInsightsCardInTestApp = async ( entity: Entity, product: Product, - duration: Duration, + duration = Duration.P30D, + onSelectAsync = jest.fn(() => Promise.resolve(mockProductCost)), ) => await renderInTestApp( - - + + - ({ - ...p, - duration: duration, - }))} - > - - - - - - + + + - - + + , ); @@ -100,7 +96,6 @@ describe('', () => { const rendered = await renderProductInsightsCardInTestApp( entity, MockComputeEngine, - Duration.P1M, ); const subheader = 'entities, sorted by cost'; const subheaderRgx = new RegExp(`${entity.entities.length} ${subheader}`); diff --git a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.tsx b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.tsx index 2a1f962000..6d6d8d5cd9 100644 --- a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.tsx +++ b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.tsx @@ -14,46 +14,84 @@ * limitations under the License. */ -import React, { useCallback, useEffect, useState } from 'react'; -import { InfoCard, useApi } from '@backstage/core'; -import Alert from '@material-ui/lab/Alert'; +import React, { + PropsWithChildren, + useCallback, + useEffect, + useRef, + useState, +} from 'react'; +import { InfoCard } from '@backstage/core'; import { Typography } from '@material-ui/core'; -import { costInsightsApiRef } from '../../api'; -import { ProductInsightsChart } from './ProductInsightsChart'; import { PeriodSelect } from '../PeriodSelect'; -import { - useFilters, - useLastCompleteBillingDate, - useLoading, - useScroll, -} from '../../hooks'; +import { ProductInsightsChart } from './ProductInsightsChart'; +import { ProductInsightsErrorCard } from './ProductInsightsErrorCard'; import { useProductInsightsCardStyles as useStyles } from '../../utils/styles'; -import { mapFiltersToProps, mapLoadingToProps } from './selector'; -import { Duration, Maybe, Product, Entity } from '../../types'; +import { DefaultLoadingAction } from '../../utils/loading'; +import { Duration, Entity, Maybe, Product } from '../../types'; +import { + useLastCompleteBillingDate, + useScroll, + useLoading, + MapLoadingToProps, +} from '../../hooks'; import { pluralOf } from '../../utils/grammar'; -type ProductInsightsCardProps = { +type LoadingProps = (isLoading: boolean) => void; + +export type ProductInsightsCardProps = { product: Product; + initialState: { + entity: Maybe; + duration: Duration; + }; + onSelectAsync: (product: Product, duration: Duration) => Promise; }; -export const ProductInsightsCard = ({ product }: ProductInsightsCardProps) => { - const client = useApi(costInsightsApiRef); +const mapLoadingToProps: MapLoadingToProps = ({ dispatch }) => ( + isLoading: boolean, +) => dispatch({ [DefaultLoadingAction.CostInsightsProducts]: isLoading }); + +export const ProductInsightsCard = ({ + initialState, + product, + onSelectAsync, +}: PropsWithChildren) => { const classes = useStyles(); + const mountedRef = useRef(false); const { ScrollAnchor } = useScroll(product.kind); - const lastCompleteBillingDate = useLastCompleteBillingDate(); - const [entity, setEntity] = useState>(null); const [error, setError] = useState>(null); + const dispatchLoading = useLoading(mapLoadingToProps); + const lastCompleteBillingDate = useLastCompleteBillingDate(); + const [entity, setEntity] = useState>(initialState.entity); + const [duration, setDuration] = useState(initialState.duration); - const { group, product: productFilter, setProduct, project } = useFilters( - mapFiltersToProps(product.kind), - ); - const { loadingProduct, dispatchLoading } = useLoading( - mapLoadingToProps(product.kind), - ); + /* eslint-disable react-hooks/exhaustive-deps */ + const dispatchLoadingProduct = useCallback(dispatchLoading, []); + /* eslint-enable react-hooks/exhaustive-deps */ - // @see CostInsightsPage - // eslint-disable-next-line react-hooks/exhaustive-deps - const dispatchLoadingProduct = useCallback(dispatchLoading, [product.kind]); + useEffect(() => { + async function handleOnSelectAsync() { + dispatchLoadingProduct(true); + try { + const e = await onSelectAsync(product, duration); + setEntity(e); + } catch (e) { + setEntity(null); + setError(e); + } finally { + dispatchLoadingProduct(false); + } + } + + if (mountedRef.current) { + handleOnSelectAsync(); + } + }, [product, duration, onSelectAsync, dispatchLoadingProduct]); + + useEffect(function hasComponentMounted() { + mountedRef.current = true; + }, []); const amount = entity?.entities?.length || 0; const hasCostsWithinTimeframe = !!(entity?.change && amount); @@ -62,77 +100,31 @@ export const ProductInsightsCard = ({ product }: ProductInsightsCardProps) => { ? `${amount} ${pluralOf(amount, 'entity', 'entities')}, sorted by cost` : null; - useEffect(() => { - async function load() { - if (loadingProduct) { - try { - const e: Entity = await client.getProductInsights({ - product: product.kind, - group: group!, - duration: productFilter!.duration, - lastCompleteBillingDate, - project, - }); - setEntity(e); - } catch (e) { - setError(e); - } finally { - dispatchLoadingProduct(false); - } - } - } - load(); - }, [ - client, - product, - setEntity, - loadingProduct, - dispatchLoadingProduct, - productFilter, - group, - product.kind, - project, - lastCompleteBillingDate, - ]); - - const onPeriodSelect = (duration: Duration) => { - dispatchLoadingProduct(true); - setProduct(duration); - }; - const infoCardProps = { headerProps: { classes: classes, - action: ( - - ), + action: , }, }; - if (error) { + if (error || !entity) { return ( - - - {`Error: Could not fetch product insights for ${product.name}`} - + ); } - if (!entity) { - return null; - } - return ( {hasCostsWithinTimeframe ? ( ) : ( diff --git a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCardList.test.tsx b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCardList.test.tsx new file mode 100644 index 0000000000..256ee8ba96 --- /dev/null +++ b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCardList.test.tsx @@ -0,0 +1,156 @@ +/* + * 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 { renderInTestApp } from '@backstage/test-utils'; +import { ProductInsightsCardList } from './ProductInsightsCardList'; +import { ProductInsightsOptions } from '../../api'; +import { mockDefaultLoadingState, MockProducts } from '../../utils/mockData'; +import { + MockConfigProvider, + MockCostInsightsApiProvider, + MockCurrencyProvider, + MockFilterProvider, + MockBillingDateProvider, + MockScrollProvider, + MockLoadingProvider, +} from '../../utils/tests'; +import { Entity } from '../../types'; + +// suppress recharts componentDidUpdate warnings +jest.spyOn(console, 'warn').mockImplementation(() => {}); + +const MockComputeEngineInsights: Entity = { + id: 'compute-engine', + entities: [], + aggregation: [0, 0], + change: { + ratio: 0, + amount: 0, + }, +}; + +const MockCloudDataflowInsights: Entity = { + id: 'cloud-dataflow', + entities: [], + aggregation: [1_000, 2_000], + change: { + ratio: 1, + amount: 1_000, + }, +}; + +const MockCloudStorageInsights: Entity = { + id: 'cloud-storage', + entities: [], + aggregation: [2_000, 4_000], + change: { + ratio: 1, + amount: 2_000, + }, +}; + +const MockBigQueryInsights: Entity = { + id: 'big-query', + entities: [], + aggregation: [8_000, 16_000], + change: { + ratio: 1, + amount: 8_000, + }, +}; + +const MockBigTableInsights: Entity = { + id: 'big-table', + entities: [], + aggregation: [16_000, 32_000], + change: { + ratio: 1, + amount: 16_000, + }, +}; + +const MockCloudPubSubInsights: Entity = { + id: 'cloud-pub-sub', + entities: [], + aggregation: [32_000, 64_000], + change: { + ratio: 1, + amount: 32_000, + }, +}; + +const ProductEntityMap = { + [MockBigQueryInsights.id!]: MockBigQueryInsights, + [MockBigTableInsights.id!]: MockBigTableInsights, + [MockCloudPubSubInsights.id!]: MockCloudPubSubInsights, + [MockCloudStorageInsights.id!]: MockCloudStorageInsights, + [MockCloudDataflowInsights.id!]: MockCloudDataflowInsights, + [MockComputeEngineInsights.id!]: MockComputeEngineInsights, +}; + +const costInsightsApi = { + getProductInsights: ({ product }: ProductInsightsOptions): Promise => + Promise.resolve(ProductEntityMap[product]), +}; + +function renderInContext(children: JSX.Element) { + return renderInTestApp( + + + + + + + {children} + + + + + + , + ); +} + +describe('', () => { + it('should render each product panel', async () => { + const noComputeEngineCostsRgx = /There are no Compute Engine costs within this timeframe for your team's projects./; + const { getByText } = await renderInContext( + , + ); + expect(getByText(noComputeEngineCostsRgx)).toBeInTheDocument(); + MockProducts.forEach(product => + expect(getByText(product.name)).toBeInTheDocument(), + ); + }); + + it('product panels should be sorted by total aggregated cost', async () => { + const { queryAllByTestId } = await renderInContext( + , + ); + const expectedOrder = MockProducts.map( + product => `product-list-item-${product.kind}`, + ).reverse(); + const productPanels = queryAllByTestId(/^product-list-item/); + + expect(productPanels.length).toBe(MockProducts.length); + Array.from(productPanels) + .map(el => el.getAttribute('data-testid')) + .forEach((id, i) => { + expect(id).toBe(expectedOrder[i]); + }); + }); +}); diff --git a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCardList.tsx b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCardList.tsx new file mode 100644 index 0000000000..5c096553e1 --- /dev/null +++ b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCardList.tsx @@ -0,0 +1,188 @@ +/* + * 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, { useCallback, useEffect, useReducer, useRef } from 'react'; +import { Box, CircularProgress } from '@material-ui/core'; +import { useApi } from '@backstage/core'; +import { costInsightsApiRef } from '../../api'; +import { ProductInsightsCard } from './ProductInsightsCard'; +import { Duration, Entity, Maybe, Product } from '../../types'; +import { DefaultLoadingAction } from '../../utils/loading'; +import { + useFilters, + useLoading, + useLastCompleteBillingDate, + MapLoadingToProps, +} from '../../hooks'; + +type State = { + [kind: string]: ProductState; +}; + +type ProductState = { + product: Product; + entity: Maybe; + duration: Duration; +}; + +type LoadingProps = (isLoading: boolean) => void; + +type ProductInsightsCardListProps = { + products: Product[]; +}; + +const DEFAULT_DURATION = Duration.P30D; + +function totalAggregationSort(a: ProductState, b: ProductState): number { + const [prevA, currA] = a.entity?.aggregation ?? [0, 0]; + const [prevB, currB] = b.entity?.aggregation ?? [0, 0]; + return prevB + currB - (prevA + currA); +} + +const mapLoadingToProps: MapLoadingToProps = ({ dispatch }) => ( + isLoading: boolean, +) => dispatch({ [DefaultLoadingAction.CostInsightsProducts]: isLoading }); + +function reducer(prevState: State, action: State): State { + return { + ...prevState, + ...action, + }; +} + +export const ProductInsightsCardList = ({ + products, +}: ProductInsightsCardListProps) => { + const onceRef = useRef(false); + const client = useApi(costInsightsApiRef); + const filters = useFilters(p => p.pageFilters); + const [state, dispatch] = useReducer(reducer, {}); + const lastCompleteBillingDate = useLastCompleteBillingDate(); + const dispatchLoading = useLoading(mapLoadingToProps); + + /* eslint-disable react-hooks/exhaustive-deps */ + // See @CostInsightsPage + const dispatchLoadingProducts = useCallback(dispatchLoading, []); + /* eslint-enable react-hooks/exhaustive-deps */ + + const initialProductStates = onceRef.current + ? Object.values(state).sort(totalAggregationSort) + : Object.values(state); + + const onSelectAsyncMemo = useCallback( + async function onSelectAsync( + product: Product, + duration: Duration, + ): Promise { + if (filters.group) { + return client.getProductInsights({ + group: filters.group, + project: filters.project, + product: product.kind, + duration: duration, + lastCompleteBillingDate: lastCompleteBillingDate, + }); + } + return Promise.reject( + new Error( + `Cannot fetch insights for ${product.name}: Expected to have a group but found none.`, + ), + ); + }, + [client, filters.group, filters.project, lastCompleteBillingDate], + ); + + useEffect(() => { + async function getAllProductInsights( + products: Product[], + group: string, + project: Maybe, + lastCompleteBillingDate: string, + ) { + const requests = products.map(product => + client.getProductInsights({ + group: group, + project: project, + product: product.kind, + duration: DEFAULT_DURATION, + lastCompleteBillingDate: lastCompleteBillingDate, + }), + ); + + try { + dispatchLoadingProducts(true); + const responses = await Promise.allSettled(requests); + const results = responses.map(response => + response.status === 'fulfilled' ? response.value : null, + ); + const initialState = results.reduce((acc, entity, index) => { + const product = products[index]; + return { + ...acc, + [product.kind]: { + product: product, + entity: entity, + duration: DEFAULT_DURATION, + }, + }; + }, {}); + dispatch(initialState); + } finally { + dispatchLoadingProducts(false); + } + } + + if (filters.group) { + onceRef.current = true; + getAllProductInsights( + products, + filters.group, + filters.project, + lastCompleteBillingDate, + ); + } + }, [ + client, + products, + filters.group, + filters.project, + lastCompleteBillingDate, + dispatchLoadingProducts, + ]); + + if (!initialProductStates.length) { + return ; + } + + return ( + <> + {initialProductStates.map(({ product, entity, duration }) => ( + + + + ))} + + ); +}; diff --git a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsErrorCard.tsx b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsErrorCard.tsx new file mode 100644 index 0000000000..6f6c5a21c8 --- /dev/null +++ b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsErrorCard.tsx @@ -0,0 +1,50 @@ +/* + * 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 } from '@backstage/core'; +import { default as Alert } from '@material-ui/lab/Alert'; +import { PeriodSelect } from '../PeriodSelect'; +import { useScroll } from '../../hooks'; +import { useProductInsightsCardStyles as useStyles } from '../../utils/styles'; +import { Duration, Product } from '../../types'; + +export type ProductInsightsErrorCardProps = { + product: Product; + duration: Duration; + onSelect: (duration: Duration) => void; +}; + +export const ProductInsightsErrorCard = ({ + product, + duration, + onSelect, +}: ProductInsightsErrorCardProps) => { + const classes = useStyles(); + const { ScrollAnchor } = useScroll(product.kind); + + const headerProps = { + classes: classes, + action: , + }; + + return ( + + + {`Error: Could not fetch product insights for ${product.name}`} + + ); +}; diff --git a/plugins/cost-insights/src/components/ProductInsightsCard/index.ts b/plugins/cost-insights/src/components/ProductInsightsCard/index.ts index 33a402234c..0a67bf459e 100644 --- a/plugins/cost-insights/src/components/ProductInsightsCard/index.ts +++ b/plugins/cost-insights/src/components/ProductInsightsCard/index.ts @@ -14,5 +14,5 @@ * limitations under the License. */ -export { ProductInsightsCard } from './ProductInsightsCard'; +export { ProductInsightsCardList } from './ProductInsightsCardList'; export { ProductInsightsChart } from './ProductInsightsChart'; diff --git a/plugins/cost-insights/src/hooks/useLoading.tsx b/plugins/cost-insights/src/hooks/useLoading.tsx index 420ac7ed56..07f8a0f4e7 100644 --- a/plugins/cost-insights/src/hooks/useLoading.tsx +++ b/plugins/cost-insights/src/hooks/useLoading.tsx @@ -21,7 +21,6 @@ import React, { SetStateAction, useContext, useEffect, - useMemo, useReducer, useState, } from 'react'; @@ -30,10 +29,9 @@ import { Loading } from '../types'; import { DefaultLoadingAction, getDefaultState, - getLoadingActions, + INITIAL_LOADING_ACTIONS, } from '../utils/loading'; import { useBackdropStyles as useStyles } from '../utils/styles'; -import { useConfig } from './useConfig'; export type LoadingContextProps = { state: Loading; @@ -56,10 +54,7 @@ function reducer(prevState: Loading, action: Partial): Loading { export const LoadingProvider = ({ children }: PropsWithChildren<{}>) => { const classes = useStyles(); - const { products } = useConfig(); - const actions = useMemo(() => getLoadingActions(products.map(p => p.kind)), [ - products, - ]); + const actions = INITIAL_LOADING_ACTIONS; const [state, dispatch] = useReducer(reducer, getDefaultState(actions)); const [isBackdropVisible, setBackdropVisible] = useState(false); diff --git a/plugins/cost-insights/src/utils/loading.ts b/plugins/cost-insights/src/utils/loading.ts index 4652603926..40101051b8 100644 --- a/plugins/cost-insights/src/utils/loading.ts +++ b/plugins/cost-insights/src/utils/loading.ts @@ -21,11 +21,13 @@ export enum DefaultLoadingAction { LastCompleteBillingDate = 'billing-date', CostInsightsInitial = 'cost-insights-initial', CostInsightsPage = 'cost-insights-page', + CostInsightsProducts = 'cost-insights-products', } export const INITIAL_LOADING_ACTIONS = [ DefaultLoadingAction.UserGroups, DefaultLoadingAction.CostInsightsInitial, + DefaultLoadingAction.CostInsightsProducts, ]; export const getDefaultState = (loadingActions: string[]): Loading => { @@ -54,11 +56,3 @@ export const getResetStateWithoutInitial = ( return { ...defaultState, [action]: loadingActionState }; }, {}); }; - -export function getLoadingActions(products: string[]): string[] { - return ([ - DefaultLoadingAction.UserGroups, - DefaultLoadingAction.CostInsightsInitial, - DefaultLoadingAction.CostInsightsPage, - ] as string[]).concat(products); -} diff --git a/plugins/cost-insights/src/utils/mockData.ts b/plugins/cost-insights/src/utils/mockData.ts index 708ce733dd..e40e5e1ddc 100644 --- a/plugins/cost-insights/src/utils/mockData.ts +++ b/plugins/cost-insights/src/utils/mockData.ts @@ -145,14 +145,14 @@ export const MockProducts: Product[] = Object.keys(MockProductTypes).map( })), ); -export const MockLoadingActions = ([ +export const MockDefaultLoadingActions = ([ DefaultLoadingAction.UserGroups, DefaultLoadingAction.CostInsightsInitial, DefaultLoadingAction.CostInsightsPage, ] as string[]).concat(MockProducts.map(product => product.kind)); export const mockDefaultLoadingState = getDefaultLoadingState( - MockLoadingActions, + MockDefaultLoadingActions, ); export const MockComputeEngine = findAlways( From a2cfa311ab48a69b6bbdd3e2dfadefe4a5e6dc52 Mon Sep 17 00:00:00 2001 From: Brenda Sukh Date: Tue, 24 Nov 2020 18:46:05 -0500 Subject: [PATCH 229/430] Update bar chart legend type usage --- .changeset/cost-insights-cyan-nails-film.md | 5 +++++ plugins/cost-insights/src/components/BarChart/index.ts | 5 ++++- .../components/ProductInsightsCard/ProductInsightsChart.tsx | 5 +++-- .../ProjectGrowthAlertCard/ProjectGrowthAlertChart.tsx | 6 +++--- 4 files changed, 15 insertions(+), 6 deletions(-) create mode 100644 .changeset/cost-insights-cyan-nails-film.md diff --git a/.changeset/cost-insights-cyan-nails-film.md b/.changeset/cost-insights-cyan-nails-film.md new file mode 100644 index 0000000000..69e21d87fe --- /dev/null +++ b/.changeset/cost-insights-cyan-nails-film.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-cost-insights': patch +--- + +Add breakdown view to the Cost Overview panel diff --git a/plugins/cost-insights/src/components/BarChart/index.ts b/plugins/cost-insights/src/components/BarChart/index.ts index 76f859636a..73676dfa81 100644 --- a/plugins/cost-insights/src/components/BarChart/index.ts +++ b/plugins/cost-insights/src/components/BarChart/index.ts @@ -17,7 +17,10 @@ export { BarChart } from './BarChart'; export type { BarChartProps } from './BarChart'; export { BarChartLegend } from './BarChartLegend'; -export type { BarChartLegendProps } from './BarChartLegend'; +export type { + BarChartLegendProps, + BarChartLegendOptions, +} from './BarChartLegend'; export { BarChartTooltip } from './BarChartTooltip'; export type { BarChartTooltipProps } from './BarChartTooltip'; export { BarChartTooltipItem } from './BarChartTooltipItem'; diff --git a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsChart.tsx b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsChart.tsx index 6050dae9c7..1209493e0a 100644 --- a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsChart.tsx +++ b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsChart.tsx @@ -30,6 +30,7 @@ import { BarChartLegend, BarChartTooltip, BarChartTooltipItem, + BarChartLegendOptions, } from '../BarChart'; import { pluralOf } from '../../utils/grammar'; import { findAlways, notEmpty } from '../../utils/assert'; @@ -45,7 +46,7 @@ import { useProductInsightsChartStyles as useStyles, useBarChartLayoutStyles as useLayoutStyles, } from '../../utils/styles'; -import { BarChartOptions, Duration, Entity, Maybe } from '../../types'; +import { Duration, Entity, Maybe } from '../../types'; export type ProductInsightsChartProps = { billingDate: string; @@ -70,7 +71,7 @@ export const ProductInsightsChart = ({ const costEnd = entity.aggregation[1]; const resources = entity.entities.map(resourceOf); - const options: Partial = { + const options: Partial = { previousName: formatPeriod(duration, billingDate, false), currentName: formatPeriod(duration, billingDate, true), }; diff --git a/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertChart.tsx b/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertChart.tsx index 53e52f18e8..f541719e4a 100644 --- a/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertChart.tsx +++ b/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertChart.tsx @@ -17,10 +17,10 @@ import React from 'react'; import moment from 'moment'; import { Box } from '@material-ui/core'; -import { BarChart, BarChartLegend } from '../BarChart'; +import { BarChart, BarChartLegend, BarChartLegendOptions } from '../BarChart'; import { LegendItem } from '../LegendItem'; import { CostGrowth } from '../CostGrowth'; -import { BarChartOptions, Duration, ProjectGrowthData } from '../../types'; +import { Duration, ProjectGrowthData } from '../../types'; import { useBarChartLayoutStyles as useStyles } from '../../utils/styles'; import { resourceOf } from '../../utils/graphs'; @@ -37,7 +37,7 @@ export const ProjectGrowthAlertChart = ({ const costEnd = alert.aggregation[1]; const resourceData = alert.products.map(resourceOf); - const options: Partial = { + const options: Partial = { previousName: moment(alert.periodStart, 'YYYY-[Q]Q').format('[Q]Q YYYY'), currentName: moment(alert.periodEnd, 'YYYY-[Q]Q').format('[Q]Q YYYY'), }; From b467106c1ffe7f60c2376764ca66429446605537 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Wed, 25 Nov 2020 09:49:35 +0100 Subject: [PATCH 230/430] Reformat EntityPage --- packages/app/src/components/catalog/EntityPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index d30de6abd1..068019c622 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -47,7 +47,7 @@ import { EmbeddedRouter as LighthouseRouter, isPluginApplicableToEntity as isLighthouseAvailable, LastLighthouseAuditCard, -} from '@backstage/plugin-lighthouse/'; +} from '@backstage/plugin-lighthouse'; import { Router as SentryRouter } from '@backstage/plugin-sentry'; import { EmbeddedDocsRouter as DocsRouter } from '@backstage/plugin-techdocs'; import { Button, Grid } from '@material-ui/core'; From 8fd5e64351c2f25a821dadfd0816958fbe6ceb53 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Wed, 25 Nov 2020 09:52:19 +0100 Subject: [PATCH 231/430] TechDocs: Tell users when index.md is missing (better error message) (#3429) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * TechDocs: Logger already prints the name of plugin * TechDocs: Display a custom error message if provided * TechDocs: throw custom error message if index.md is not present * Language improvements. Thanks @freben Co-authored-by: Fredrik Adelöw Co-authored-by: Fredrik Adelöw --- .../src/techdocs/stages/publish/local.ts | 6 ++---- plugins/techdocs/src/api.ts | 8 +++++++- plugins/techdocs/src/reader/components/Reader.tsx | 2 +- .../reader/components/TechDocsNotFound.test.tsx | 14 ++++++++++++++ .../src/reader/components/TechDocsNotFound.tsx | 8 ++++++-- 5 files changed, 30 insertions(+), 8 deletions(-) diff --git a/plugins/techdocs-backend/src/techdocs/stages/publish/local.ts b/plugins/techdocs-backend/src/techdocs/stages/publish/local.ts index 07cef2f4d2..464c4b90a0 100644 --- a/plugins/techdocs-backend/src/techdocs/stages/publish/local.ts +++ b/plugins/techdocs-backend/src/techdocs/stages/publish/local.ts @@ -53,9 +53,7 @@ export class LocalPublish implements PublisherBase { ); if (!fs.existsSync(publishDir)) { - this.logger.info( - `[TechDocs]: Could not find ${publishDir}, creates the directory.`, - ); + this.logger.info(`Could not find ${publishDir}, creating the directory.`); fs.mkdirSync(publishDir, { recursive: true }); } @@ -63,7 +61,7 @@ export class LocalPublish implements PublisherBase { fs.copy(directory, publishDir, err => { if (err) { this.logger.debug( - `[TechDocs]: Failed to copy docs from ${directory} to ${publishDir}`, + `Failed to copy docs from ${directory} to ${publishDir}`, ); reject(err); } diff --git a/plugins/techdocs/src/api.ts b/plugins/techdocs/src/api.ts index 194ad14d30..7281ebdf13 100644 --- a/plugins/techdocs/src/api.ts +++ b/plugins/techdocs/src/api.ts @@ -95,7 +95,13 @@ export class TechDocsStorageApi implements TechDocsStorage { ); if (request.status === 404) { - throw new Error('Page not found'); + let errorMessage = 'Page not found. '; + // path is empty for the home page of an entity's docs site + if (!path) { + errorMessage += + 'This could be because there is no index.md file in the root of the docs directory of this repository.'; + } + throw new Error(errorMessage); } return request.text(); diff --git a/plugins/techdocs/src/reader/components/Reader.tsx b/plugins/techdocs/src/reader/components/Reader.tsx index 785c6f1c6b..fe3a34564c 100644 --- a/plugins/techdocs/src/reader/components/Reader.tsx +++ b/plugins/techdocs/src/reader/components/Reader.tsx @@ -153,7 +153,7 @@ export const Reader = ({ entityId, onReady }: Props) => { ]); if (error) { - return ; + return ; } return ( diff --git a/plugins/techdocs/src/reader/components/TechDocsNotFound.test.tsx b/plugins/techdocs/src/reader/components/TechDocsNotFound.test.tsx index 7b0ec80619..d3c98fec78 100644 --- a/plugins/techdocs/src/reader/components/TechDocsNotFound.test.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsNotFound.test.tsx @@ -27,3 +27,17 @@ describe('', () => { expect(rendered.getByTestId('go-back-link')).toBeDefined(); }); }); + +describe('', () => { + it('should render with status code, custom error message and go back link', () => { + const rendered = render( + wrapInTestApp( + , + ), + ); + rendered.getByText(/This is a custom error message/i); + rendered.getByText(/404/i); + rendered.getByText(/Looks like someone dropped the mic!/i); + expect(rendered.getByTestId('go-back-link')).toBeDefined(); + }); +}); diff --git a/plugins/techdocs/src/reader/components/TechDocsNotFound.tsx b/plugins/techdocs/src/reader/components/TechDocsNotFound.tsx index c774000bd5..744242e343 100644 --- a/plugins/techdocs/src/reader/components/TechDocsNotFound.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsNotFound.tsx @@ -17,11 +17,15 @@ import React from 'react'; import { ErrorPage } from '@backstage/core'; -export const TechDocsNotFound = () => { +type Props = { + errorMessage?: string; +}; + +export const TechDocsNotFound = ({ errorMessage }: Props) => { return ( ); From fae3fafcfa3edcea32c18a53097cd46908161c8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Wed, 25 Nov 2020 09:57:16 +0100 Subject: [PATCH 232/430] silence some new ts squigglies in the editor (#3435) --- packages/cli/src/lib/bundler/server.ts | 2 +- packages/cli/src/lib/run.ts | 2 +- .../src/apis/implementations/OAuthRequestApi/MockOAuthApi.ts | 4 ++-- .../src/apis/implementations/StorageApi/WebStorage.test.ts | 4 ++-- packages/e2e-test/src/lib/helpers.ts | 4 ++-- .../src/testUtils/apis/StorageApi/MockStorageApi.test.ts | 4 ++-- plugins/auth-backend/src/providers/microsoft/provider.ts | 4 ++-- plugins/catalog-backend/src/util/runPeriodically.ts | 2 +- .../src/scaffolder/stages/templater/helpers.ts | 5 +++-- .../techdocs-backend/src/techdocs/stages/generate/helpers.ts | 4 ++-- 10 files changed, 18 insertions(+), 17 deletions(-) diff --git a/packages/cli/src/lib/bundler/server.ts b/packages/cli/src/lib/bundler/server.ts index d198f5507c..a5cdd1300c 100644 --- a/packages/cli/src/lib/bundler/server.ts +++ b/packages/cli/src/lib/bundler/server.ts @@ -60,7 +60,7 @@ export async function serveBundle(options: ServeOptions) { proxy: pkg.proxy, }); - await new Promise((resolve, reject) => { + await new Promise((resolve, reject) => { server.listen(port, url.hostname, (err?: Error) => { if (err) { reject(err); diff --git a/packages/cli/src/lib/run.ts b/packages/cli/src/lib/run.ts index 69d3d92640..571c75a39d 100644 --- a/packages/cli/src/lib/run.ts +++ b/packages/cli/src/lib/run.ts @@ -102,7 +102,7 @@ export async function waitForExit( return; } - await new Promise((resolve, reject) => { + await new Promise((resolve, reject) => { child.once('error', error => reject(error)); child.once('exit', code => { if (code) { diff --git a/packages/core-api/src/apis/implementations/OAuthRequestApi/MockOAuthApi.ts b/packages/core-api/src/apis/implementations/OAuthRequestApi/MockOAuthApi.ts index 5d539dca27..523f713f45 100644 --- a/packages/core-api/src/apis/implementations/OAuthRequestApi/MockOAuthApi.ts +++ b/packages/core-api/src/apis/implementations/OAuthRequestApi/MockOAuthApi.ts @@ -31,7 +31,7 @@ export default class MockOAuthApi implements OAuthRequestApi { async triggerAll() { await Promise.resolve(); // Wait a tick to allow new requests to get forwarded - return new Promise(resolve => { + return new Promise(resolve => { const subscription = this.authRequest$().subscribe(requests => { subscription.unsubscribe(); Promise.all(requests.map(request => request.trigger())).then(() => @@ -44,7 +44,7 @@ export default class MockOAuthApi implements OAuthRequestApi { async rejectAll() { await Promise.resolve(); // Wait a tick to allow new requests to get forwarded - return new Promise(resolve => { + return new Promise(resolve => { const subscription = this.authRequest$().subscribe(requests => { subscription.unsubscribe(); requests.map(request => request.reject()); diff --git a/packages/core-api/src/apis/implementations/StorageApi/WebStorage.test.ts b/packages/core-api/src/apis/implementations/StorageApi/WebStorage.test.ts index 971b023629..81140ffcc9 100644 --- a/packages/core-api/src/apis/implementations/StorageApi/WebStorage.test.ts +++ b/packages/core-api/src/apis/implementations/StorageApi/WebStorage.test.ts @@ -63,7 +63,7 @@ describe('WebStorage Storage API', () => { const selectedKeyNextHandler = jest.fn(); const mockData = { hello: 'im a great new value' }; - await new Promise(resolve => { + await new Promise(resolve => { storage.observe$('correctKey').subscribe({ next: (...args) => { selectedKeyNextHandler(...args); @@ -93,7 +93,7 @@ describe('WebStorage Storage API', () => { storage.set('correctKey', mockData); - await new Promise(resolve => { + await new Promise(resolve => { storage.observe$('correctKey').subscribe({ next: (...args) => { selectedKeyNextHandler(...args); diff --git a/packages/e2e-test/src/lib/helpers.ts b/packages/e2e-test/src/lib/helpers.ts index aded184f5c..0b05eaf4b4 100644 --- a/packages/e2e-test/src/lib/helpers.ts +++ b/packages/e2e-test/src/lib/helpers.ts @@ -93,7 +93,7 @@ export function exitWithError(err: Error & { code?: unknown }) { */ export function waitFor(fn: () => boolean, maxSeconds: number = 120) { let count = 0; - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { const handle = setInterval(() => { if (count++ > maxSeconds * 10) { reject(new Error('Timed out while waiting for condition')); @@ -112,7 +112,7 @@ export async function waitForExit(child: ChildProcess) { if (child.exitCode !== null) { throw new Error(`Child already exited with code ${child.exitCode}`); } - await new Promise((resolve, reject) => + await new Promise((resolve, reject) => child.once('exit', code => { if (code) { reject(new Error(`Child exited with code ${code}`)); diff --git a/packages/test-utils/src/testUtils/apis/StorageApi/MockStorageApi.test.ts b/packages/test-utils/src/testUtils/apis/StorageApi/MockStorageApi.test.ts index 668d973830..7c92c71460 100644 --- a/packages/test-utils/src/testUtils/apis/StorageApi/MockStorageApi.test.ts +++ b/packages/test-utils/src/testUtils/apis/StorageApi/MockStorageApi.test.ts @@ -58,7 +58,7 @@ describe('WebStorage Storage API', () => { const selectedKeyNextHandler = jest.fn(); const mockData = { hello: 'im a great new value' }; - await new Promise(resolve => { + await new Promise(resolve => { storage.observe$('correctKey').subscribe({ next: (...args) => { selectedKeyNextHandler(...args); @@ -88,7 +88,7 @@ describe('WebStorage Storage API', () => { storage.set('correctKey', mockData); - await new Promise(resolve => { + await new Promise(resolve => { storage.observe$('correctKey').subscribe({ next: (...args) => { selectedKeyNextHandler(...args); diff --git a/plugins/auth-backend/src/providers/microsoft/provider.ts b/plugins/auth-backend/src/providers/microsoft/provider.ts index baa66f0662..a09d5871d2 100644 --- a/plugins/auth-backend/src/providers/microsoft/provider.ts +++ b/plugins/auth-backend/src/providers/microsoft/provider.ts @@ -163,7 +163,7 @@ export class MicrosoftAuthProvider implements OAuthHandlers { }); } - private getUserPhoto(accessToken: string): Promise { + private getUserPhoto(accessToken: string): Promise { return new Promise(resolve => { got .get('https://graph.microsoft.com/v1.0/me/photos/48x48/$value', { @@ -184,7 +184,7 @@ export class MicrosoftAuthProvider implements OAuthHandlers { `Could not retrieve user profile photo from Microsoft Graph API: ${error}`, ); // User profile photo is optional, ignore errors and resolve undefined - resolve(); + resolve(undefined); }); }); } diff --git a/plugins/catalog-backend/src/util/runPeriodically.ts b/plugins/catalog-backend/src/util/runPeriodically.ts index 2f65d0e6f3..c6d478fcbe 100644 --- a/plugins/catalog-backend/src/util/runPeriodically.ts +++ b/plugins/catalog-backend/src/util/runPeriodically.ts @@ -27,7 +27,7 @@ export function runPeriodically(fn: () => any, delayMs: number): () => void { let cancel: () => void; let cancelled = false; - const cancellationPromise = new Promise(resolve => { + const cancellationPromise = new Promise(resolve => { cancel = () => { resolve(); cancelled = true; diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/templater/helpers.ts b/plugins/scaffolder-backend/src/scaffolder/stages/templater/helpers.ts index e79f2cb066..645133a4ae 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/templater/helpers.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/templater/helpers.ts @@ -66,7 +66,7 @@ export const runCommand = async ({ args, logStream = new PassThrough(), }: RunCommandOptions) => { - await new Promise((resolve, reject) => { + await new Promise((resolve, reject) => { const process = spawn(command, args); process.stdout.on('data', stream => { @@ -109,7 +109,7 @@ export const runDockerContainer = async ({ dockerClient, createOptions = {}, }: RunDockerContainerOptions) => { - await new Promise((resolve, reject) => { + await new Promise((resolve, reject) => { dockerClient.pull(imageName, {}, (err, stream) => { if (err) return reject(err); stream.pipe(logStream, { end: false }); @@ -120,6 +120,7 @@ export const runDockerContainer = async ({ }); const userOptions: UserOptions = {}; + // @ts-ignore if (process.getuid && process.getgid) { // Files that are created inside the Docker container will be owned by // root on the host system on non Mac systems, because of reasons. Mainly the fact that diff --git a/plugins/techdocs-backend/src/techdocs/stages/generate/helpers.ts b/plugins/techdocs-backend/src/techdocs/stages/generate/helpers.ts index d68bb74aa6..d0ffa72f1c 100644 --- a/plugins/techdocs-backend/src/techdocs/stages/generate/helpers.ts +++ b/plugins/techdocs-backend/src/techdocs/stages/generate/helpers.ts @@ -63,7 +63,7 @@ export async function runDockerContainer({ ); } - await new Promise((resolve, reject) => { + await new Promise((resolve, reject) => { dockerClient.pull(imageName, {}, (err, stream) => { if (err) return reject(err); stream.pipe(logStream, { end: false }); @@ -119,7 +119,7 @@ export const runCommand = async ({ options, logStream = new PassThrough(), }: RunCommandOptions) => { - await new Promise((resolve, reject) => { + await new Promise((resolve, reject) => { const process = spawn(command, args, options); process.stdout.on('data', stream => { From f538e2c560d4a4d9172cf643cb90235f3ae01453 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 25 Nov 2020 10:33:45 +0100 Subject: [PATCH 233/430] cli: update versions:bump to install new deps for accepted version ranges --- .changeset/long-birds-rush.md | 5 ++ .../cli/src/commands/versions/bump.test.ts | 31 ++++++---- packages/cli/src/commands/versions/bump.ts | 59 +++++++++++++------ packages/cli/src/commands/versions/lint.ts | 12 +++- packages/cli/src/lib/versioning/Lockfile.ts | 13 ++++ 5 files changed, 86 insertions(+), 34 deletions(-) create mode 100644 .changeset/long-birds-rush.md diff --git a/.changeset/long-birds-rush.md b/.changeset/long-birds-rush.md new file mode 100644 index 0000000000..db4da34789 --- /dev/null +++ b/.changeset/long-birds-rush.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Make versions:bump install new versions of dependencies that were within the specified range diff --git a/packages/cli/src/commands/versions/bump.test.ts b/packages/cli/src/commands/versions/bump.test.ts index 687708b074..f4d69c52be 100644 --- a/packages/cli/src/commands/versions/bump.test.ts +++ b/packages/cli/src/commands/versions/bump.test.ts @@ -24,7 +24,7 @@ import bump from './bump'; import { withLogCollector } from '@backstage/test-utils'; const REGISTRY_VERSIONS: { [name: string]: string } = { - '@backstage/core': '1.0.7', + '@backstage/core': '1.0.6', '@backstage/theme': '2.0.0', }; @@ -36,30 +36,36 @@ const HEADER = `# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. const lockfileMock = `${HEADER} "@backstage/core@^1.0.5": version "1.0.6" - resolved "https://my-registry/a-1.0.01.tgz#abc123" - integrity sha512-xyz + dependencies: + "@backstage/core-api" "^1.0.6" "@backstage/core@^1.0.3": version "1.0.3" - resolved "https://my-registry/a-1.0.01.tgz#abc123" - integrity sha512-xyz + dependencies: + "@backstage/core-api" "^1.0.3" "@backstage/theme@^1.0.0": version "1.0.0" - resolved "https://my-registry/a-1.0.01.tgz#abc123" - integrity sha512-xyz + +"@backstage/core-api@^1.0.6": + version "1.0.6" + +"@backstage/core-api@^1.0.3": + version "1.0.3" `; +// This resulting lockfile isn't a real world example, since it doesn't include the package bumps const lockfileMockResult = `${HEADER} -"@backstage/core@^1.0.3", "@backstage/core@^1.0.5": +"@backstage/core-api@^1.0.3", "@backstage/core-api@^1.0.6": version "1.0.6" - resolved "https://my-registry/a-1.0.01.tgz#abc123" - integrity sha512-xyz + +"@backstage/core@^1.0.5": + version "1.0.6" + dependencies: + "@backstage/core-api" "^1.0.6" "@backstage/theme@^1.0.0": version "1.0.0" - resolved "https://my-registry/a-1.0.01.tgz#abc123" - integrity sha512-xyz `; describe('bump', () => { @@ -116,6 +122,7 @@ describe('bump', () => { 'Checking for updates of @backstage/theme', 'Checking for updates of @backstage/core', 'Some packages are outdated, updating', + 'Removing lockfile entry for @backstage/core@^1.0.3 to bump to 1.0.6', 'Bumping @backstage/theme in b to ^2.0.0', "Running 'yarn install' to install new versions", 'Removing duplicate dependencies from yarn.lock', diff --git a/packages/cli/src/commands/versions/bump.ts b/packages/cli/src/commands/versions/bump.ts index 00944c5db9..1883d87bac 100644 --- a/packages/cli/src/commands/versions/bump.ts +++ b/packages/cli/src/commands/versions/bump.ts @@ -24,6 +24,7 @@ import { fetchPackageInfo, Lockfile, } from '../../lib/versioning'; +import { includedFilter, forbiddenDuplicatesFilter } from './lint'; const DEP_TYPES = [ 'dependencies', @@ -39,12 +40,16 @@ type PkgVersionInfo = { }; export default async () => { + const lockfilePath = paths.resolveTargetRoot('yarn.lock'); + // First we discover all Backstage dependencies within our own repo const dependencyMap = await mapDependencies(paths.targetDir); - // Next check with the package registry what the latest version of all of those dependencies are - const targetVersions = new Map(); - await workerThreads(16, dependencyMap.keys(), async name => { + // Next check with the package registry to see which dependency ranges we need to bump + const versionBumps = new Map(); + // Track package versions that we want to remove from yarn.lock in order to trigger a bump + const unlocked = Array<{ name: string; range: string; latest: string }>(); + await workerThreads(16, dependencyMap.entries(), async ([name, pkgs]) => { console.log(`Checking for updates of ${name}`); const info = await fetchPackageInfo(name); const latest = info['dist-tags'].latest; @@ -52,38 +57,49 @@ export default async () => { throw new Error(`No latest version found for ${name}`); } - targetVersions.set(name, latest); - }); - - // Then figure out which local packages need to have their dependencies bumped - const versionBumps = new Map(); - for (const [name, pkgs] of dependencyMap) { - const targetVersion = targetVersions.get(name)!; for (const pkg of pkgs) { - if (semver.satisfies(targetVersion, pkg.range)) { + if (semver.satisfies(latest, pkg.range)) { + if (semver.minVersion(pkg.range)?.version !== latest) { + unlocked.push({ name, range: pkg.range, latest }); + } continue; } - versionBumps.set( pkg.name, (versionBumps.get(pkg.name) ?? []).concat({ name, location: pkg.location, - range: `^${targetVersion}`, // TODO(Rugvip): Option to use something else than ^? + range: `^${latest}`, // TODO(Rugvip): Option to use something else than ^? }), ); } - } + }); console.log(); // Write all discovered version bumps to package.json in this repo - if (versionBumps.size === 0) { + if (versionBumps.size === 0 && unlocked.length === 0) { console.log('All Backstage packages are up to date!'); } else { console.log('Some packages are outdated, updating'); console.log(); + if (unlocked.length > 0) { + const lockfile = await Lockfile.load(lockfilePath); + for (const { name, range, latest } of unlocked) { + // Don't bother removing lockfile entries if they're already on the correct version + const existingEntry = lockfile.get(name)?.find(e => e.range === range); + if (existingEntry?.version === latest) { + continue; + } + console.log( + `Removing lockfile entry for ${name}@${range} to bump to ${latest}`, + ); + lockfile.remove(name, range); + } + await lockfile.save(); + } + await workerThreads(16, versionBumps.entries(), async ([name, deps]) => { const pkgPath = resolvePath(deps[0].location, 'package.json'); const pkgJson = await fs.readJson(pkgPath); @@ -110,9 +126,9 @@ export default async () => { console.log(); // Finally we make sure the new lockfile doesn't have any duplicates - const lockfile = await Lockfile.load(paths.resolveTargetRoot('yarn.lock')); + const lockfile = await Lockfile.load(lockfilePath); const result = lockfile.analyze({ - filter: name => dependencyMap.has(name), + filter: includedFilter, }); if (result.newVersions.length > 0) { @@ -130,9 +146,14 @@ export default async () => { console.log(); } - if (result.newRanges.length > 0) { + const forbiddenNewRanges = result.newRanges.filter(({ name }) => + forbiddenDuplicatesFilter(name), + ); + if (forbiddenNewRanges.length > 0) { throw new Error( - `Version bump failed for ${result.newRanges.map(i => i.name).join(', ')}`, + `Version bump failed for ${forbiddenNewRanges + .map(i => i.name) + .join(', ')}`, ); } }; diff --git a/packages/cli/src/commands/versions/lint.ts b/packages/cli/src/commands/versions/lint.ts index e2c36ba094..aa0bcd6f6a 100644 --- a/packages/cli/src/commands/versions/lint.ts +++ b/packages/cli/src/commands/versions/lint.ts @@ -22,6 +22,9 @@ import partition from 'lodash/partition'; // Packages that we try to avoid duplicates for const INCLUDED = [/^@backstage\//]; +export const includedFilter = (name: string) => + INCLUDED.some(pattern => pattern.test(name)); + // Packages that are not allowed to have any duplicates const FORBID_DUPLICATES = [ /^@backstage\/core$/, @@ -29,6 +32,9 @@ const FORBID_DUPLICATES = [ /^@backstage\/plugin-/, ]; +export const forbiddenDuplicatesFilter = (name: string) => + FORBID_DUPLICATES.some(pattern => pattern.test(name)); + export default async (cmd: Command) => { const fix = Boolean(cmd.fix); @@ -36,7 +42,7 @@ export default async (cmd: Command) => { const lockfile = await Lockfile.load(paths.resolveTargetRoot('yarn.lock')); const result = lockfile.analyze({ - filter: name => INCLUDED.some(pattern => pattern.test(name)), + filter: includedFilter, }); logArray( @@ -53,7 +59,7 @@ export default async (cmd: Command) => { newVersionsForbidden, newVersionsAllowed, ] = partition(result.newVersions, ({ name }) => - FORBID_DUPLICATES.some(pattern => pattern.test(name)), + forbiddenDuplicatesFilter(name), ); if (newVersionsForbidden.length && !fix) { success = false; @@ -75,7 +81,7 @@ export default async (cmd: Command) => { const [newRangesForbidden, newRangesAllowed] = partition( result.newRanges, - ({ name }) => FORBID_DUPLICATES.some(pattern => pattern.test(name)), + ({ name }) => forbiddenDuplicatesFilter(name), ); if (newRangesForbidden.length) { success = false; diff --git a/packages/cli/src/lib/versioning/Lockfile.ts b/packages/cli/src/lib/versioning/Lockfile.ts index 893beca6e2..f9bc789d95 100644 --- a/packages/cli/src/lib/versioning/Lockfile.ts +++ b/packages/cli/src/lib/versioning/Lockfile.ts @@ -202,6 +202,19 @@ export class Lockfile { return result; } + remove(name: string, range: string): boolean { + const query = `${name}@${range}`; + const existed = Boolean(this.data[query]); + delete this.data[query]; + + const newEntries = this.packages.get(name)?.filter(e => e.range !== range); + if (newEntries) { + this.packages.set(name, newEntries); + } + + return existed; + } + /** Modifies the lockfile by bumping packages to the suggested versions */ replaceVersions(results: AnalyzeResultNewVersion[]) { for (const { name, range, oldVersion, newVersion } of results) { From 2daf18e8095c86ed71b0f059e9b895f3cdf7d6ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Wed, 25 Nov 2020 10:51:22 +0100 Subject: [PATCH 234/430] catalog: emit all common relations based on specs (#3408) --- .changeset/pink-goats-sort.md | 6 + packages/catalog-model/src/entity/ref.ts | 16 +- packages/catalog-model/src/kinds/relations.ts | 2 + .../20201123205611_relations_table_uniq.js | 93 +++++++++ plugins/catalog-backend/package.json | 2 +- .../src/database/CommonDatabase.ts | 17 +- .../BuiltinKindsEntityProcessor.test.ts | 180 ++++++++++++++++++ .../processors/BuiltinKindsEntityProcessor.ts | 128 ++++++++++++- .../processors/OwnerRelationProcessor.ts | 74 ------- .../src/ingestion/processors/index.ts | 3 +- .../src/service/CatalogBuilder.test.ts | 2 +- .../src/service/CatalogBuilder.ts | 6 +- 12 files changed, 437 insertions(+), 92 deletions(-) create mode 100644 .changeset/pink-goats-sort.md create mode 100644 plugins/catalog-backend/migrations/20201123205611_relations_table_uniq.js delete mode 100644 plugins/catalog-backend/src/ingestion/processors/OwnerRelationProcessor.ts diff --git a/.changeset/pink-goats-sort.md b/.changeset/pink-goats-sort.md new file mode 100644 index 0000000000..828cccba84 --- /dev/null +++ b/.changeset/pink-goats-sort.md @@ -0,0 +1,6 @@ +--- +'@backstage/catalog-model': patch +'@backstage/plugin-catalog-backend': patch +--- + +Start emitting all known relation types from the core entity kinds, based on their spec data. diff --git a/packages/catalog-model/src/entity/ref.ts b/packages/catalog-model/src/entity/ref.ts index 2cacfce53a..9f6f175d8d 100644 --- a/packages/catalog-model/src/entity/ref.ts +++ b/packages/catalog-model/src/entity/ref.ts @@ -84,6 +84,14 @@ export function parseEntityName( * @param context The context of defaults that the parsing happens within * @returns The compound form of the reference */ +export function parseEntityRef( + ref: EntityRef, + context?: { defaultKind: string; defaultNamespace: string }, +): { + kind: string; + namespace: string; + name: string; +}; export function parseEntityRef( ref: EntityRef, context?: { defaultKind: string }, @@ -100,14 +108,6 @@ export function parseEntityRef( namespace: string; name: string; }; -export function parseEntityRef( - ref: EntityRef, - context?: { defaultKind: string; defaultNamespace: string }, -): { - kind: string; - namespace: string; - name: string; -}; export function parseEntityRef( ref: EntityRef, context: EntityRefContext = {}, diff --git a/packages/catalog-model/src/kinds/relations.ts b/packages/catalog-model/src/kinds/relations.ts index 846313ec8c..3d5d629b9e 100644 --- a/packages/catalog-model/src/kinds/relations.ts +++ b/packages/catalog-model/src/kinds/relations.ts @@ -33,7 +33,9 @@ export const RELATION_OWNER_OF = 'ownerOf'; * A relation with an API entity, typically from a component or system */ export const RELATION_CONSUMES_API = 'consumesApi'; +export const RELATION_API_CONSUMED_BY = 'apiConsumedBy'; export const RELATION_PROVIDES_API = 'providesApi'; +export const RELATION_API_PROVIDED_BY = 'apiProvidedBy'; /** * A relation denoting a dependency on another entity. diff --git a/plugins/catalog-backend/migrations/20201123205611_relations_table_uniq.js b/plugins/catalog-backend/migrations/20201123205611_relations_table_uniq.js new file mode 100644 index 0000000000..a8cf62b4c4 --- /dev/null +++ b/plugins/catalog-backend/migrations/20201123205611_relations_table_uniq.js @@ -0,0 +1,93 @@ +/* + * 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. + */ + +// @ts-check + +/** + * @param {import('knex')} knex + */ +exports.up = async function up(knex) { + if (knex.client.config.client === 'sqlite3') { + // sqlite doesn't support dropPrimary so we recreate it properly instead + await knex.schema.dropTable('entities_relations'); + await knex.schema.createTable('entities_relations', table => { + table.comment('All relations between entities in the catalog'); + table + .uuid('originating_entity_id') + .references('id') + .inTable('entities') + .onDelete('CASCADE') + .notNullable() + .comment('The entity that provided the relation'); + table + .string('source_full_name') + .notNullable() + .comment('The full name of the source entity of the relation'); + table + .string('type') + .notNullable() + .comment('The type of the relation between the entities'); + table + .string('target_full_name') + .notNullable() + .comment('The full name of the target entity of the relation'); + table.index('source_full_name', 'source_full_name_idx'); + }); + } else { + await knex.schema.alterTable('entities_relations', table => { + table.dropPrimary(); + table.index('source_full_name', 'source_full_name_idx'); + }); + } +}; + +/** + * @param {import('knex')} knex + */ +exports.down = async function down(knex) { + if (knex.client.config.client === 'sqlite3') { + await knex.schema.dropTable('entities_relations'); + await knex.schema.createTable('entities_relations', table => { + table.comment('All relations between entities in the catalog'); + table + .uuid('originating_entity_id') + .references('id') + .inTable('entities') + .onDelete('CASCADE') + .notNullable() + .comment('The entity that provided the relation'); + table + .string('source_full_name') + .notNullable() + .comment('The full name of the source entity of the relation'); + table + .string('type') + .notNullable() + .comment('The type of the relation between the entities'); + table + .string('target_full_name') + .notNullable() + .comment('The full name of the target entity of the relation'); + + table.primary(['source_full_name', 'type', 'target_full_name']); + }); + } else { + await knex.schema.alterTable('entities_relations', table => { + table.dropIndex([], 'source_full_name_idx'); + table.primary(['source_full_name', 'type', 'target_full_name']); + }); + } +}; diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 51a9978f30..26f7180d43 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -26,6 +26,7 @@ "@backstage/config": "^0.1.1", "@octokit/graphql": "^4.5.6", "@types/express": "^4.17.6", + "@types/ldapjs": "^1.0.9", "codeowners-utils": "^1.0.2", "core-js": "^3.6.5", "cross-fetch": "^3.0.6", @@ -51,7 +52,6 @@ "@backstage/test-utils": "^0.1.3", "@types/core-js": "^2.5.4", "@types/git-url-parse": "^9.0.0", - "@types/ldapjs": "^1.0.9", "@types/lodash": "^4.14.151", "@types/supertest": "^2.0.8", "@types/uuid": "^8.0.0", diff --git a/plugins/catalog-backend/src/database/CommonDatabase.ts b/plugins/catalog-backend/src/database/CommonDatabase.ts index f86ab9e598..287756b0a8 100644 --- a/plugins/catalog-backend/src/database/CommonDatabase.ts +++ b/plugins/catalog-backend/src/database/CommonDatabase.ts @@ -345,7 +345,11 @@ export class CommonDatabase implements Database { ); // TODO(blam): translate constraint failures to sane NotFoundError instead - await tx.batchInsert('entities_relations', relationsRows, BATCH_SIZE); + await tx.batchInsert( + 'entities_relations', + deduplicateRelations(relationsRows), + BATCH_SIZE, + ); } async addLocation( @@ -506,7 +510,7 @@ export class CommonDatabase implements Database { .orderBy(['type', 'target_full_name']) .select(); - entity.relations = relations.map(r => ({ + entity.relations = deduplicateRelations(relations).map(r => ({ target: parseEntityName(r.target_full_name), type: r.type, })); @@ -517,3 +521,12 @@ export class CommonDatabase implements Database { }; } } + +function deduplicateRelations( + rows: DbEntitiesRelationsRow[], +): DbEntitiesRelationsRow[] { + return lodash.uniqBy( + rows, + r => `${r.source_full_name}:${r.target_full_name}:${r.type}`, + ); +} diff --git a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts index e1328e827a..94d35cc109 100644 --- a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts @@ -14,6 +14,12 @@ * limitations under the License. */ +import { + ApiEntity, + ComponentEntity, + GroupEntity, + UserEntity, +} from '@backstage/catalog-model'; import { BuiltinKindsEntityProcessor } from './BuiltinKindsEntityProcessor'; describe('BuiltinKindsEntityProcessor', () => { @@ -44,4 +50,178 @@ describe('BuiltinKindsEntityProcessor', () => { }, }); }); + + describe('postProcessEntity', () => { + const processor = new BuiltinKindsEntityProcessor(); + const location = { type: 'a', target: 'b' }; + const emit = jest.fn(); + + afterEach(() => jest.resetAllMocks()); + + it('generates relations for component entities', async () => { + const entity: ComponentEntity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { name: 'n' }, + spec: { + type: 'service', + owner: 'o', + lifecycle: 'l', + implementsApis: ['a'], + }, + }; + + await processor.postProcessEntity(entity, location, emit); + + expect(emit).toBeCalledTimes(4); + expect(emit).toBeCalledWith({ + type: 'relation', + relation: { + source: { kind: 'Group', namespace: 'default', name: 'o' }, + type: 'ownerOf', + target: { kind: 'Component', namespace: 'default', name: 'n' }, + }, + }); + expect(emit).toBeCalledWith({ + type: 'relation', + relation: { + source: { kind: 'Component', namespace: 'default', name: 'n' }, + type: 'ownedBy', + target: { kind: 'Group', namespace: 'default', name: 'o' }, + }, + }); + expect(emit).toBeCalledWith({ + type: 'relation', + relation: { + source: { kind: 'API', namespace: 'default', name: 'a' }, + type: 'apiProvidedBy', + target: { kind: 'Component', namespace: 'default', name: 'n' }, + }, + }); + expect(emit).toBeCalledWith({ + type: 'relation', + relation: { + source: { kind: 'Component', namespace: 'default', name: 'n' }, + type: 'providesApi', + target: { kind: 'API', namespace: 'default', name: 'a' }, + }, + }); + }); + + it('generates relations for api entities', async () => { + const entity: ApiEntity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'API', + metadata: { name: 'n' }, + spec: { + type: 'service', + owner: 'o', + lifecycle: 'l', + definition: 'd', + }, + }; + + await processor.postProcessEntity(entity, location, emit); + + expect(emit).toBeCalledTimes(2); + expect(emit).toBeCalledWith({ + type: 'relation', + relation: { + source: { kind: 'Group', namespace: 'default', name: 'o' }, + type: 'ownerOf', + target: { kind: 'API', namespace: 'default', name: 'n' }, + }, + }); + expect(emit).toBeCalledWith({ + type: 'relation', + relation: { + source: { kind: 'API', namespace: 'default', name: 'n' }, + type: 'ownedBy', + target: { kind: 'Group', namespace: 'default', name: 'o' }, + }, + }); + }); + + it('generates relations for user entities', async () => { + const entity: UserEntity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'User', + metadata: { name: 'n' }, + spec: { + memberOf: ['g'], + }, + }; + + await processor.postProcessEntity(entity, location, emit); + + expect(emit).toBeCalledTimes(2); + expect(emit).toBeCalledWith({ + type: 'relation', + relation: { + source: { kind: 'User', namespace: 'default', name: 'n' }, + type: 'memberOf', + target: { kind: 'Group', namespace: 'default', name: 'g' }, + }, + }); + expect(emit).toBeCalledWith({ + type: 'relation', + relation: { + source: { kind: 'Group', namespace: 'default', name: 'g' }, + type: 'hasMember', + target: { kind: 'User', namespace: 'default', name: 'n' }, + }, + }); + }); + + it('generates relations for group entities', async () => { + const entity: GroupEntity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + metadata: { name: 'n' }, + spec: { + type: 't', + parent: 'p', + ancestors: [], + children: ['c'], + descendants: [], + }, + }; + + await processor.postProcessEntity(entity, location, emit); + + expect(emit).toBeCalledTimes(4); + expect(emit).toBeCalledWith({ + type: 'relation', + relation: { + source: { kind: 'Group', namespace: 'default', name: 'n' }, + type: 'childOf', + target: { kind: 'Group', namespace: 'default', name: 'p' }, + }, + }); + expect(emit).toBeCalledWith({ + type: 'relation', + relation: { + source: { kind: 'Group', namespace: 'default', name: 'p' }, + type: 'parentOf', + target: { kind: 'Group', namespace: 'default', name: 'n' }, + }, + }); + expect(emit).toBeCalledWith({ + type: 'relation', + relation: { + source: { kind: 'Group', namespace: 'default', name: 'c' }, + type: 'childOf', + target: { kind: 'Group', namespace: 'default', name: 'n' }, + }, + }); + expect(emit).toBeCalledWith({ + type: 'relation', + relation: { + source: { kind: 'Group', namespace: 'default', name: 'n' }, + type: 'parentOf', + target: { kind: 'Group', namespace: 'default', name: 'c' }, + }, + }); + }); + }); }); diff --git a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts index 472dffeb16..a4e62e5b8c 100644 --- a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts @@ -15,15 +15,31 @@ */ import { + ApiEntity, apiEntityV1alpha1Validator, + ComponentEntity, componentEntityV1alpha1Validator, Entity, + getEntityName, + GroupEntity, groupEntityV1alpha1Validator, locationEntityV1alpha1Validator, + LocationSpec, + parseEntityRef, + RELATION_API_PROVIDED_BY, + RELATION_CHILD_OF, + RELATION_HAS_MEMBER, + RELATION_MEMBER_OF, + RELATION_OWNED_BY, + RELATION_OWNER_OF, + RELATION_PARENT_OF, + RELATION_PROVIDES_API, templateEntityV1alpha1Validator, + UserEntity, userEntityV1alpha1Validator, } from '@backstage/catalog-model'; -import { CatalogProcessor } from './types'; +import * as result from './results'; +import { CatalogProcessor, CatalogProcessorEmit } from './types'; export class BuiltinKindsEntityProcessor implements CatalogProcessor { private readonly validators = [ @@ -64,4 +80,114 @@ export class BuiltinKindsEntityProcessor implements CatalogProcessor { return false; } + + async postProcessEntity( + entity: Entity, + _location: LocationSpec, + emit: CatalogProcessorEmit, + ): Promise { + const selfRef = getEntityName(entity); + + /* + * Utilities + */ + + function doEmit( + targets: string | string[] | undefined, + context: { defaultKind: string; defaultNamespace: string }, + outgoingRelation: string, + incomingRelation: string, + ): void { + if (!targets) { + return; + } + for (const target of [targets].flat()) { + const targetRef = parseEntityRef(target, context); + emit( + result.relation({ + source: selfRef, + type: outgoingRelation, + target: targetRef, + }), + ); + emit( + result.relation({ + source: targetRef, + type: incomingRelation, + target: selfRef, + }), + ); + } + } + + /* + * Emit relations for the Component kind + */ + + if (entity.kind === 'Component') { + const component = entity as ComponentEntity; + doEmit( + component.spec.owner, + { defaultKind: 'Group', defaultNamespace: selfRef.namespace }, + RELATION_OWNED_BY, + RELATION_OWNER_OF, + ); + doEmit( + component.spec.implementsApis, + { defaultKind: 'API', defaultNamespace: selfRef.namespace }, + RELATION_PROVIDES_API, + RELATION_API_PROVIDED_BY, + ); + } + + /* + * Emit relations for the API kind + */ + + if (entity.kind === 'API') { + const api = entity as ApiEntity; + doEmit( + api.spec.owner, + { defaultKind: 'Group', defaultNamespace: selfRef.namespace }, + RELATION_OWNED_BY, + RELATION_OWNER_OF, + ); + } + + /* + * Emit relations for the User kind + */ + + if (entity.kind === 'User') { + const user = entity as UserEntity; + doEmit( + user.spec.memberOf, + { defaultKind: 'Group', defaultNamespace: selfRef.namespace }, + RELATION_MEMBER_OF, + RELATION_HAS_MEMBER, + ); + } + + /* + * Emit relations for the Group kind + */ + + if (entity.kind === 'Group') { + const group = entity as GroupEntity; + doEmit( + group.spec.parent, + { defaultKind: 'Group', defaultNamespace: selfRef.namespace }, + RELATION_CHILD_OF, + RELATION_PARENT_OF, + ); + doEmit( + group.spec.children, + { defaultKind: 'Group', defaultNamespace: selfRef.namespace }, + RELATION_PARENT_OF, + RELATION_CHILD_OF, + ); + } + + return entity; + } } diff --git a/plugins/catalog-backend/src/ingestion/processors/OwnerRelationProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/OwnerRelationProcessor.ts deleted file mode 100644 index 4c67589789..0000000000 --- a/plugins/catalog-backend/src/ingestion/processors/OwnerRelationProcessor.ts +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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, - ENTITY_DEFAULT_NAMESPACE, - LocationSpec, - parseEntityRef, - ApiEntityV1alpha1, - ComponentEntityV1alpha1, - RELATION_OWNED_BY, - RELATION_OWNER_OF, - getEntityName, -} from '@backstage/catalog-model'; -import { CatalogProcessor, CatalogProcessorEmit } from './types'; -import * as result from './results'; - -const includedKinds = new Set(['api', 'component']); - -export class OwnerRelationProcessor implements CatalogProcessor { - async postProcessEntity( - entity: Entity, - _location: LocationSpec, - emit: CatalogProcessorEmit, - ): Promise { - if (!includedKinds.has(entity.kind.toLowerCase())) { - return entity; - } - const apiOrComponentEntity = entity as - | ApiEntityV1alpha1 - | ComponentEntityV1alpha1; - - const owner = apiOrComponentEntity.spec?.owner; - if (owner) { - const namespace = entity.metadata.namespace ?? ENTITY_DEFAULT_NAMESPACE; - - const selfRef = getEntityName(entity); - const ownerRef = parseEntityRef(owner, { - defaultKind: 'group', - defaultNamespace: namespace, - }); - - emit( - result.relation({ - source: selfRef, - type: RELATION_OWNED_BY, - target: ownerRef, - }), - ); - emit( - result.relation({ - source: ownerRef, - type: RELATION_OWNER_OF, - target: selfRef, - }), - ); - } - - return entity; - } -} diff --git a/plugins/catalog-backend/src/ingestion/processors/index.ts b/plugins/catalog-backend/src/ingestion/processors/index.ts index 118f977125..49d4e03b64 100644 --- a/plugins/catalog-backend/src/ingestion/processors/index.ts +++ b/plugins/catalog-backend/src/ingestion/processors/index.ts @@ -22,10 +22,11 @@ export * from './types'; export { parseEntityYaml } from './util/parse'; export { AnnotateLocationEntityProcessor } from './AnnotateLocationEntityProcessor'; +export { BuiltinKindsEntityProcessor } from './BuiltinKindsEntityProcessor'; export { CodeOwnersProcessor } from './CodeOwnersProcessor'; export { FileReaderProcessor } from './FileReaderProcessor'; export { GithubOrgReaderProcessor } from './GithubOrgReaderProcessor'; -export { OwnerRelationProcessor } from './OwnerRelationProcessor'; +export { LdapOrgReaderProcessor } from './LdapOrgReaderProcessor'; export { LocationRefProcessor } from './LocationEntityProcessor'; export { MicrosoftGraphOrgReaderProcessor } from './MicrosoftGraphOrgReaderProcessor'; export { PlaceholderProcessor } from './PlaceholderProcessor'; diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.test.ts b/plugins/catalog-backend/src/service/CatalogBuilder.test.ts index ee7c6f2246..58897a10a7 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.test.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.test.ts @@ -144,7 +144,7 @@ describe('CatalogBuilder', () => { owner: 'o', lifecycle: 'l', }, - relations: [], + relations: expect.anything(), }, ]); }); diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts index 3f95dcd0d3..4be0f1799b 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.ts @@ -37,15 +37,16 @@ import { import { DatabaseManager } from '../database'; import { AnnotateLocationEntityProcessor, + BuiltinKindsEntityProcessor, CatalogProcessor, CodeOwnersProcessor, FileReaderProcessor, GithubOrgReaderProcessor, HigherOrderOperation, HigherOrderOperations, + LdapOrgReaderProcessor, LocationReaders, LocationRefProcessor, - OwnerRelationProcessor, MicrosoftGraphOrgReaderProcessor, PlaceholderProcessor, PlaceholderResolver, @@ -53,8 +54,6 @@ import { UrlReaderProcessor, } from '../ingestion'; import { CatalogRulesEnforcer } from '../ingestion/CatalogRules'; -import { BuiltinKindsEntityProcessor } from '../ingestion/processors/BuiltinKindsEntityProcessor'; -import { LdapOrgReaderProcessor } from '../ingestion/processors/LdapOrgReaderProcessor'; import { jsonPlaceholderResolver, textPlaceholderResolver, @@ -283,7 +282,6 @@ export class CatalogBuilder { new UrlReaderProcessor({ reader, logger }), new CodeOwnersProcessor({ reader }), new LocationRefProcessor(), - new OwnerRelationProcessor(), new AnnotateLocationEntityProcessor(), ); } From 3aa7efb3ff18078f8e236763d45225e48721ef8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Wed, 25 Nov 2020 10:51:35 +0100 Subject: [PATCH 235/430] backend-common: add support for passing false as a CSP field value, to drop it from the defaults (#3437) --- .changeset/few-kings-agree.md | 5 ++ packages/backend-common/config.d.ts | 11 +++- .../service/lib/ServiceBuilderImpl.test.ts | 36 +++++++++++++ .../src/service/lib/ServiceBuilderImpl.ts | 45 ++++++++++------ .../src/service/lib/config.test.ts | 51 +++++++++++++++++++ .../backend-common/src/service/lib/config.ts | 17 +++++-- 6 files changed, 143 insertions(+), 22 deletions(-) create mode 100644 .changeset/few-kings-agree.md create mode 100644 packages/backend-common/src/service/lib/ServiceBuilderImpl.test.ts create mode 100644 packages/backend-common/src/service/lib/config.test.ts diff --git a/.changeset/few-kings-agree.md b/.changeset/few-kings-agree.md new file mode 100644 index 0000000000..0019611503 --- /dev/null +++ b/.changeset/few-kings-agree.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +Added support for passing false as a CSP field value, to drop it from the defaults in the backend diff --git a/packages/backend-common/config.d.ts b/packages/backend-common/config.d.ts index 7a134ac506..c4ec84ab62 100644 --- a/packages/backend-common/config.d.ts +++ b/packages/backend-common/config.d.ts @@ -79,8 +79,15 @@ export interface Config { optionsSuccessStatus?: number; }; - /** */ - csp?: object; + /** + * Content Security Policy options. + * + * The keys are the plain policy ID, e.g. "upgrade-insecure-requests". The + * values are on the format that the helmet library expects them, as an + * array of strings. There is also the special value false, which means to + * remove the default value that Backstage puts in place for that policy. + */ + csp?: { [policyId: string]: string[] | false }; }; /** Configuration for integrations towards various external repository provider systems */ diff --git a/packages/backend-common/src/service/lib/ServiceBuilderImpl.test.ts b/packages/backend-common/src/service/lib/ServiceBuilderImpl.test.ts new file mode 100644 index 0000000000..cffb0a88de --- /dev/null +++ b/packages/backend-common/src/service/lib/ServiceBuilderImpl.test.ts @@ -0,0 +1,36 @@ +/* + * 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 { applyCspDirectives } from './ServiceBuilderImpl'; + +describe('ServiceBuilderImpl', () => { + describe('applyCspDirectives', () => { + it('copies actual values', () => { + const result = applyCspDirectives({ key: ['value'] }); + expect(result).toEqual( + expect.objectContaining({ + 'default-src': ["'self'"], + key: ['value'], + }), + ); + }); + + it('removes false value keys', () => { + const result = applyCspDirectives({ 'upgrade-insecure-requests': false }); + expect(result!['upgrade-insecure-requests']).toBeUndefined(); + }); + }); +}); diff --git a/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts b/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts index 710de6f1d7..d8b9ffa2d9 100644 --- a/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts +++ b/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts @@ -18,7 +18,7 @@ import { Config } from '@backstage/config'; import compression from 'compression'; import cors from 'cors'; import express, { Router } from 'express'; -import helmet from 'helmet'; +import helmet, { HelmetOptions } from 'helmet'; import * as http from 'http'; import stoppable from 'stoppable'; import { Logger } from 'winston'; @@ -56,7 +56,7 @@ const DEFAULT_CSP = { 'script-src': ["'self'"], 'script-src-attr': ["'none'"], 'style-src': ["'self'", 'https:', "'unsafe-inline'"], - 'upgrade-insecure-requests': [], + 'upgrade-insecure-requests': [] as string[], }; export class ServiceBuilderImpl implements ServiceBuilder { @@ -64,7 +64,7 @@ export class ServiceBuilderImpl implements ServiceBuilder { private host: string | undefined; private logger: Logger | undefined; private corsOptions: cors.CorsOptions | undefined; - private cspOptions: CspOptions | undefined; + private cspOptions: Record | undefined; private httpsSettings: HttpsSettings | undefined; private enableMetrics: boolean = true; private routers: [string, Router][]; @@ -154,20 +154,11 @@ export class ServiceBuilderImpl implements ServiceBuilder { host, logger, corsOptions, - cspOptions, httpsSettings, + helmetOptions, } = this.getOptions(); - app.use( - helmet({ - contentSecurityPolicy: { - directives: { - ...DEFAULT_CSP, - ...cspOptions, - }, - }, - }), - ); + app.use(helmet(helmetOptions)); if (corsOptions) { app.use(cors(corsOptions)); } @@ -214,16 +205,38 @@ export class ServiceBuilderImpl implements ServiceBuilder { host: string; logger: Logger; corsOptions?: cors.CorsOptions; - cspOptions?: CspOptions; httpsSettings?: HttpsSettings; + helmetOptions: HelmetOptions; } { return { port: this.port ?? DEFAULT_PORT, host: this.host ?? DEFAULT_HOST, logger: this.logger ?? getRootLogger(), corsOptions: this.corsOptions, - cspOptions: this.cspOptions, httpsSettings: this.httpsSettings, + helmetOptions: { + contentSecurityPolicy: { + directives: applyCspDirectives(this.cspOptions), + }, + }, }; } } + +export function applyCspDirectives( + directives: Record | undefined, +): CspOptions | undefined { + const result: CspOptions = { ...DEFAULT_CSP }; + + if (directives) { + for (const [key, value] of Object.entries(directives)) { + if (value === false) { + delete result[key]; + } else { + result[key] = value; + } + } + } + + return result; +} diff --git a/packages/backend-common/src/service/lib/config.test.ts b/packages/backend-common/src/service/lib/config.test.ts new file mode 100644 index 0000000000..8a35f147ca --- /dev/null +++ b/packages/backend-common/src/service/lib/config.test.ts @@ -0,0 +1,51 @@ +/* + * 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 { ConfigReader } from '@backstage/config'; +import { readCspOptions } from './config'; + +describe('config', () => { + describe('readCspOptions', () => { + it('reads valid values', () => { + const config = ConfigReader.fromConfigs([ + { context: '', data: { csp: { key: ['value'] } } }, + ]); + expect(readCspOptions(config)).toEqual( + expect.objectContaining({ + key: ['value'], + }), + ); + }); + + it('accepts false', () => { + const config = ConfigReader.fromConfigs([ + { context: '', data: { csp: { key: false } } }, + ]); + expect(readCspOptions(config)).toEqual( + expect.objectContaining({ + key: false, + }), + ); + }); + + it('rejects invalid value types', () => { + const config = ConfigReader.fromConfigs([ + { context: '', data: { csp: { key: [4] } } }, + ]); + expect(() => readCspOptions(config)).toThrow(/wanted string-array/); + }); + }); +}); diff --git a/packages/backend-common/src/service/lib/config.ts b/packages/backend-common/src/service/lib/config.ts index bfd966b499..541170b704 100644 --- a/packages/backend-common/src/service/lib/config.ts +++ b/packages/backend-common/src/service/lib/config.ts @@ -134,24 +134,33 @@ export function readCorsOptions(config: Config): CorsOptions | undefined { * Attempts to read a CSP options object from the root of a config object. * * @param config The root of a backend config object - * @returns A CSP options object, or undefined if not specified + * @returns A CSP options object, or undefined if not specified. Values can be + * false as well, which means to remove the default behavior for that + * key. * * @example * ```yaml * backend: * csp: * connect-src: ["'self'", 'http:', 'https:'] + * upgrade-insecure-requests: false * ``` */ -export function readCspOptions(config: Config): CspOptions | undefined { +export function readCspOptions( + config: Config, +): Record | undefined { const cc = config.getOptionalConfig('csp'); if (!cc) { return undefined; } - const result: CspOptions = {}; + const result: Record = {}; for (const key of cc.keys()) { - result[key] = cc.getStringArray(key); + if (cc.get(key) === false) { + result[key] = false; + } else { + result[key] = cc.getStringArray(key); + } } return result; From 76ff21c2d5c02e34357143a350820b8eb77075a0 Mon Sep 17 00:00:00 2001 From: Askar Date: Wed, 25 Nov 2020 11:06:39 +0100 Subject: [PATCH 236/430] fix(search): compile possible filter options from available entities (#3370) * load filters from entities * fix(search): compile possible filter options from available entities * rename filter state and add text in case no filter can be applied --- .../search/src/components/Filters/Filters.tsx | 116 ++++++++++-------- .../components/SearchResult/SearchResult.tsx | 45 +++++-- 2 files changed, 97 insertions(+), 64 deletions(-) diff --git a/plugins/search/src/components/Filters/Filters.tsx b/plugins/search/src/components/Filters/Filters.tsx index 19d4e53d72..7bd344e12e 100644 --- a/plugins/search/src/components/Filters/Filters.tsx +++ b/plugins/search/src/components/Filters/Filters.tsx @@ -49,8 +49,14 @@ export type FiltersState = { checked: Array; }; +export type FilterOptions = { + kind: Array; + lifecycle: Array; +}; + type FiltersProps = { filters: FiltersState; + filterOptions: FilterOptions; resetFilters: () => void; updateSelected: (filter: string) => void; updateChecked: (filter: string) => void; @@ -58,16 +64,13 @@ type FiltersProps = { export const Filters = ({ filters, + filterOptions, resetFilters, updateSelected, updateChecked, }: FiltersProps) => { const classes = useStyles(); - // TODO: move mocked filters out of filters component to make it more generic - const filter1 = ['All', 'API', 'Component', 'Location', 'Template']; - const filter2 = ['deprecated', 'recommended', 'experimental', 'production']; - return ( - - Kind - - - - Lifecycle - - {filter2.map(filter => ( - updateChecked(filter)} - > - + + Filters cannot be applied to available results + + + )} + {filterOptions.kind.length > 0 && ( + + Kind + + + )} + {filterOptions.lifecycle.length > 0 && ( + + Lifecycle + + {filterOptions.lifecycle.map(filter => ( + updateChecked(filter)} + > + + + + ))} + + + )} ); }; diff --git a/plugins/search/src/components/SearchResult/SearchResult.tsx b/plugins/search/src/components/SearchResult/SearchResult.tsx index f1e160e792..9fd54ac06c 100644 --- a/plugins/search/src/components/SearchResult/SearchResult.tsx +++ b/plugins/search/src/components/SearchResult/SearchResult.tsx @@ -126,7 +126,7 @@ export const SearchResult = ({ searchQuery }: SearchResultProps) => { const catalogApi = useApi(catalogApiRef); const [showFilters, toggleFilters] = useState(false); - const [filters, setFilters] = useState({ + const [selectedFilters, setSelectedFilters] = useState({ selected: 'All', checked: [], }); @@ -146,17 +146,18 @@ export const SearchResult = ({ searchQuery }: SearchResultProps) => { // apply filters // filter on selected - if (filters.selected !== 'All') { + if (selectedFilters.selected !== 'All') { withFilters = results.filter((result: Result) => - filters.selected.includes(result.kind), + selectedFilters.selected.includes(result.kind), ); } // filter on checked - if (filters.checked.length > 0) { + if (selectedFilters.checked.length > 0) { withFilters = withFilters.filter( (result: Result) => - result.lifecycle && filters.checked.includes(result.lifecycle), + result.lifecycle && + selectedFilters.checked.includes(result.lifecycle), ); } @@ -174,7 +175,7 @@ export const SearchResult = ({ searchQuery }: SearchResultProps) => { setFilteredResults(withFilters); } - }, [filters, searchQuery, results]); + }, [selectedFilters, searchQuery, results]); if (loading) { return ; } @@ -190,41 +191,58 @@ export const SearchResult = ({ searchQuery }: SearchResultProps) => { } const resetFilters = () => { - setFilters({ + setSelectedFilters({ selected: 'All', checked: [], }); }; const updateSelected = (filter: string) => { - setFilters(prevState => ({ + setSelectedFilters(prevState => ({ ...prevState, selected: filter, })); }; const updateChecked = (filter: string) => { - if (filters.checked.includes(filter)) { - setFilters(prevState => ({ + if (selectedFilters.checked.includes(filter)) { + setSelectedFilters(prevState => ({ ...prevState, checked: prevState.checked.filter(item => item !== filter), })); return; } - setFilters(prevState => ({ + setSelectedFilters(prevState => ({ ...prevState, checked: [...prevState.checked, filter], })); }; + const filterOptions = results.reduce( + (acc, curr) => { + if (curr.kind && acc.kind.indexOf(curr.kind) < 0) { + acc.kind.push(curr.kind); + } + if (curr.lifecycle && acc.lifecycle.indexOf(curr.lifecycle) < 0) { + acc.lifecycle.push(curr.lifecycle); + } + return acc; + }, + { + kind: [] as Array, + lifecycle: [] as Array, + }, + ); + return ( <> {showFilters && ( { searchQuery={searchQuery} numberOfResults={filteredResults.length} numberOfSelectedFilters={ - (filters.selected !== 'All' ? 1 : 0) + filters.checked.length + (selectedFilters.selected !== 'All' ? 1 : 0) + + selectedFilters.checked.length } handleToggleFilters={() => toggleFilters(!showFilters)} /> From 9b9e86f8afd3ec593aca08f0599a832c7b6748f7 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 25 Nov 2020 14:30:52 +0100 Subject: [PATCH 237/430] export oidcAuthRef --- .changeset/honest-gifts-push.md | 5 +++++ .github/styles/vocab.txt | 1 + 2 files changed, 6 insertions(+) create mode 100644 .changeset/honest-gifts-push.md diff --git a/.changeset/honest-gifts-push.md b/.changeset/honest-gifts-push.md new file mode 100644 index 0000000000..ad2df98a6e --- /dev/null +++ b/.changeset/honest-gifts-push.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-api': patch +--- + +export oidc provider diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt index c31e19e35c..a0841fe7d0 100644 --- a/.github/styles/vocab.txt +++ b/.github/styles/vocab.txt @@ -131,6 +131,7 @@ npm nvm oauth Oauth +oidc Okta Oldsberg onboarding From a1f6e07d325e840eefc6f2c2b0ac252282f9467a Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 25 Nov 2020 14:32:49 +0100 Subject: [PATCH 238/430] created release log --- .changeset/honest-gifts-push.md | 5 ----- packages/core-api/CHANGELOG.md | 6 ++++++ packages/core-api/package.json | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) delete mode 100644 .changeset/honest-gifts-push.md diff --git a/.changeset/honest-gifts-push.md b/.changeset/honest-gifts-push.md deleted file mode 100644 index ad2df98a6e..0000000000 --- a/.changeset/honest-gifts-push.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-api': patch ---- - -export oidc provider diff --git a/packages/core-api/CHANGELOG.md b/packages/core-api/CHANGELOG.md index b936715c70..63926daf24 100644 --- a/packages/core-api/CHANGELOG.md +++ b/packages/core-api/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/core-api +## 0.2.2 + +### Patch Changes + +- 9b9e86f8a: export oidc provider + ## 0.2.1 ### Patch Changes diff --git a/packages/core-api/package.json b/packages/core-api/package.json index 212d4c9046..c39dca3b7a 100644 --- a/packages/core-api/package.json +++ b/packages/core-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-api", "description": "Internal Core API used by Backstage plugins and apps", - "version": "0.2.1", + "version": "0.2.2", "private": false, "publishConfig": { "access": "public", From fb19e0241dc831ba238457df10d8f89234cc9c49 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Wed, 25 Nov 2020 16:01:34 +0100 Subject: [PATCH 239/430] Add providesApis and consumesApis to component spec --- .../software-catalog/descriptor-format.md | 21 +++++++++- docs/features/software-catalog/references.md | 4 +- .../src/kinds/ComponentEntityV1alpha1.test.ts | 42 +++++++++++++++++++ .../src/kinds/ComponentEntityV1alpha1.ts | 4 ++ plugins/api-docs/README.md | 2 +- .../BuiltinKindsEntityProcessor.test.ts | 36 +++++++++++++++- .../processors/BuiltinKindsEntityProcessor.ts | 14 +++++++ 7 files changed, 118 insertions(+), 5 deletions(-) diff --git a/docs/features/software-catalog/descriptor-format.md b/docs/features/software-catalog/descriptor-format.md index f1d426f407..c86be1be83 100644 --- a/docs/features/software-catalog/descriptor-format.md +++ b/docs/features/software-catalog/descriptor-format.md @@ -381,7 +381,7 @@ spec: type: website lifecycle: production owner: artist-relations@example.com - implementsApis: + providesApis: - artist-api ``` @@ -451,6 +451,25 @@ is optional. The software catalog expects a list of one or more strings that references the names of other entities of the `kind` `API`. +This field has the same behavior as `spec.providesApis` and will be deprecated +in the future. + +### `spec.providesApis` [optional] + +Links APIs that are provided by the component, e.g. `artist-api`. This field is +optional. + +The software catalog expects a list of one or more strings that references the +names of other entities of the `kind` `API`. + +### `spec.consumesApis` [optional] + +Links APIs that are consumed by the component, e.g. `artist-api`. This field is +optional. + +The software catalog expects a list of one or more strings that references the +names of other entities of the `kind` `API`. + ## Kind: Template Describes the following entity kind: diff --git a/docs/features/software-catalog/references.md b/docs/features/software-catalog/references.md index e438ddc977..d347035950 100644 --- a/docs/features/software-catalog/references.md +++ b/docs/features/software-catalog/references.md @@ -51,7 +51,7 @@ spec: type: service lifecycle: experimental owner: group:pet-managers - implementsApis: + providesApis: - petstore - internal/streetlights - hello-world @@ -66,7 +66,7 @@ catalog that is of kind `Group`, namespace `default` (which, actually, also can be left out in its own yaml file because that's the default value there too), and name `pet-managers`. -The entries in `implementsApis` are also references. In this case, none of them +The entries in `providesApis` are also references. In this case, none of them needs to specify a kind since we know from the context that that's the only kind that's supported here. The second entry specifies a namespace but the other ones don't, and in this context, the default is to refer to the same namespace as the diff --git a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.test.ts b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.test.ts index 64f9d05978..8eec63daac 100644 --- a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.test.ts +++ b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.test.ts @@ -34,6 +34,8 @@ describe('ComponentV1alpha1Validator', () => { lifecycle: 'production', owner: 'me', implementsApis: ['api-0'], + providesApis: ['api-0'], + consumesApis: ['api-0'], }, }; }); @@ -121,4 +123,44 @@ describe('ComponentV1alpha1Validator', () => { (entity as any).spec.implementsApis = []; await expect(validator.check(entity)).resolves.toBe(true); }); + + it('accepts missing providesApis', async () => { + delete (entity as any).spec.providesApis; + await expect(validator.check(entity)).resolves.toBe(true); + }); + + it('rejects empty providesApis', async () => { + (entity as any).spec.providesApis = ['']; + await expect(validator.check(entity)).rejects.toThrow(/providesApis/); + }); + + it('rejects undefined providesApis', async () => { + (entity as any).spec.providesApis = [undefined]; + await expect(validator.check(entity)).rejects.toThrow(/providesApis/); + }); + + it('accepts no providesApis', async () => { + (entity as any).spec.providesApis = []; + await expect(validator.check(entity)).resolves.toBe(true); + }); + + it('accepts missing consumesApis', async () => { + delete (entity as any).spec.consumesApis; + await expect(validator.check(entity)).resolves.toBe(true); + }); + + it('rejects empty consumesApis', async () => { + (entity as any).spec.consumesApis = ['']; + await expect(validator.check(entity)).rejects.toThrow(/consumesApis/); + }); + + it('rejects undefined consumesApis', async () => { + (entity as any).spec.consumesApis = [undefined]; + await expect(validator.check(entity)).rejects.toThrow(/consumesApis/); + }); + + it('accepts no consumesApis', async () => { + (entity as any).spec.consumesApis = []; + await expect(validator.check(entity)).resolves.toBe(true); + }); }); diff --git a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts index 0e1e369e44..97c9140e61 100644 --- a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts @@ -30,6 +30,8 @@ const schema = yup.object>({ lifecycle: yup.string().required().min(1), owner: yup.string().required().min(1), implementsApis: yup.array(yup.string().required()).notRequired(), + providesApis: yup.array(yup.string().required()).notRequired(), + consumesApis: yup.array(yup.string().required()).notRequired(), kubernetes: yup .object({ selector: yup @@ -51,6 +53,8 @@ export interface ComponentEntityV1alpha1 extends Entity { lifecycle: string; owner: string; implementsApis?: string[]; + providesApis?: string[]; + consumesApis?: string[]; kubernetes?: { selector: { matchLabels: { diff --git a/plugins/api-docs/README.md b/plugins/api-docs/README.md index ecc0e5a560..f5b9a79a85 100644 --- a/plugins/api-docs/README.md +++ b/plugins/api-docs/README.md @@ -21,7 +21,7 @@ Right now, the following API formats are supported: Other formats are displayed as plain text, but this can easily be extended. To fill the catalog with APIs, [provide entities of kind API](https://backstage.io/docs/features/software-catalog/descriptor-format#kind-api). -To link that an component implements an API, see [`implementsApis` property on components](https://backstage.io/docs/features/software-catalog/descriptor-format#specimplementsapis-optional). +To link that an component provides or consumes an API, see [`providesApis`](https://backstage.io/docs/features/software-catalog/descriptor-format#specprovidesapis-optional) and [`consumesApis`](https://backstage.io/docs/features/software-catalog/descriptor-format#specconsumesapis-optional) property on components. ## Links diff --git a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts index 94d35cc109..e456dfd7a2 100644 --- a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts @@ -68,12 +68,14 @@ describe('BuiltinKindsEntityProcessor', () => { owner: 'o', lifecycle: 'l', implementsApis: ['a'], + providesApis: ['b'], + consumesApis: ['c'], }, }; await processor.postProcessEntity(entity, location, emit); - expect(emit).toBeCalledTimes(4); + expect(emit).toBeCalledTimes(8); expect(emit).toBeCalledWith({ type: 'relation', relation: { @@ -106,6 +108,38 @@ describe('BuiltinKindsEntityProcessor', () => { target: { kind: 'API', namespace: 'default', name: 'a' }, }, }); + expect(emit).toBeCalledWith({ + type: 'relation', + relation: { + source: { kind: 'API', namespace: 'default', name: 'b' }, + type: 'apiProvidedBy', + target: { kind: 'Component', namespace: 'default', name: 'n' }, + }, + }); + expect(emit).toBeCalledWith({ + type: 'relation', + relation: { + source: { kind: 'Component', namespace: 'default', name: 'n' }, + type: 'providesApi', + target: { kind: 'API', namespace: 'default', name: 'b' }, + }, + }); + expect(emit).toBeCalledWith({ + type: 'relation', + relation: { + source: { kind: 'API', namespace: 'default', name: 'c' }, + type: 'apiConsumedBy', + target: { kind: 'Component', namespace: 'default', name: 'n' }, + }, + }); + expect(emit).toBeCalledWith({ + type: 'relation', + relation: { + source: { kind: 'Component', namespace: 'default', name: 'n' }, + type: 'consumesApi', + target: { kind: 'API', namespace: 'default', name: 'c' }, + }, + }); }); it('generates relations for api entities', async () => { diff --git a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts index a4e62e5b8c..62b496dd65 100644 --- a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts @@ -26,8 +26,10 @@ import { locationEntityV1alpha1Validator, LocationSpec, parseEntityRef, + RELATION_API_CONSUMED_BY, RELATION_API_PROVIDED_BY, RELATION_CHILD_OF, + RELATION_CONSUMES_API, RELATION_HAS_MEMBER, RELATION_MEMBER_OF, RELATION_OWNED_BY, @@ -138,6 +140,18 @@ export class BuiltinKindsEntityProcessor implements CatalogProcessor { RELATION_PROVIDES_API, RELATION_API_PROVIDED_BY, ); + doEmit( + component.spec.providesApis, + { defaultKind: 'API', defaultNamespace: selfRef.namespace }, + RELATION_PROVIDES_API, + RELATION_API_PROVIDED_BY, + ); + doEmit( + component.spec.consumesApis, + { defaultKind: 'API', defaultNamespace: selfRef.namespace }, + RELATION_CONSUMES_API, + RELATION_API_CONSUMED_BY, + ); } /* From ab94c9542fdc32fe9407daad9f477dbface59099 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Wed, 25 Nov 2020 16:03:02 +0100 Subject: [PATCH 240/430] Add changeset --- .changeset/good-cycles-shave.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/good-cycles-shave.md diff --git a/.changeset/good-cycles-shave.md b/.changeset/good-cycles-shave.md new file mode 100644 index 0000000000..90a9410b9c --- /dev/null +++ b/.changeset/good-cycles-shave.md @@ -0,0 +1,6 @@ +--- +'@backstage/catalog-model': patch +'@backstage/plugin-catalog-backend': patch +--- + +Add `providesApis` and `consumesApis` to the component entity spec. From 069cda35f43c1a775e98d07bf548eccb1d075821 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Wed, 25 Nov 2020 16:21:08 +0100 Subject: [PATCH 241/430] Deprecate implementsApis --- .changeset/short-singers-serve.md | 15 +++++++++++++++ .../software-catalog/descriptor-format.md | 8 ++++++-- .../src/kinds/ComponentEntityV1alpha1.ts | 5 +++++ 3 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 .changeset/short-singers-serve.md diff --git a/.changeset/short-singers-serve.md b/.changeset/short-singers-serve.md new file mode 100644 index 0000000000..ae8e8d171c --- /dev/null +++ b/.changeset/short-singers-serve.md @@ -0,0 +1,15 @@ +--- +'@backstage/catalog-model': patch +--- + +Marked the field `spec.implementsApis` on `Component` entities for deprecation on Dec 14th, 2020. + +Code that consumes these fields should remove those usages as soon as possible as migrate to using +relations instead. Producers should fill the field `spec.providesApis` instead, which has the same +semantic. + +After Dec 14th, the fields will be removed from types and classes of the Backstage repository. At +the first release after that, they will not be present in released packages either. + +If your catalog-info.yaml files still contain this fields after the deletion, they will still be +valid and your ingestion will not break, but they won't be visible in the types for consuming code. diff --git a/docs/features/software-catalog/descriptor-format.md b/docs/features/software-catalog/descriptor-format.md index c86be1be83..438f874990 100644 --- a/docs/features/software-catalog/descriptor-format.md +++ b/docs/features/software-catalog/descriptor-format.md @@ -445,14 +445,18 @@ group of people in an organizational structure. ### `spec.implementsApis` [optional] +**NOTE**: This field was marked for deprecation on Nov 25nd, 2020. It will be +removed entirely from the model on Dec 14th, 2020 in the repository and will not +be present in released packages following the next release after that. Please +update your code to not consume this field before the removal date. + Links APIs that are implemented by the component, e.g. `artist-api`. This field is optional. The software catalog expects a list of one or more strings that references the names of other entities of the `kind` `API`. -This field has the same behavior as `spec.providesApis` and will be deprecated -in the future. +This field has the same behavior as `spec.providesApis`. ### `spec.providesApis` [optional] diff --git a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts index 97c9140e61..5fd4f88537 100644 --- a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts @@ -52,6 +52,11 @@ export interface ComponentEntityV1alpha1 extends Entity { type: string; lifecycle: string; owner: string; + /** + * @deprecated This field will disappear on Dec 14th, 2020. Please remove + * any consuming code. The new field providesApis provides the + * same functionality like before. + */ implementsApis?: string[]; providesApis?: string[]; consumesApis?: string[]; From bd9578870d3787c1f0b03e6647b6bff6a564fc1f Mon Sep 17 00:00:00 2001 From: Ryan Vazquez Date: Wed, 25 Nov 2020 11:13:59 -0500 Subject: [PATCH 242/430] use ES2020 --- packages/cli/config/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/config/tsconfig.json b/packages/cli/config/tsconfig.json index 62609aad5b..a55b215f74 100644 --- a/packages/cli/config/tsconfig.json +++ b/packages/cli/config/tsconfig.json @@ -11,7 +11,7 @@ "incremental": true, "isolatedModules": true, "jsx": "react", - "lib": ["DOM", "DOM.Iterable", "ScriptHost", "ES2019", "ESNext.Promise"], + "lib": ["DOM", "DOM.Iterable", "ScriptHost", "ES2020", "ESNext.Promise"], "module": "ESNext", "moduleResolution": "node", "noEmit": false, From ff1301d28f1d5567ecf65ee21b67f45cd0ccba78 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Wed, 25 Nov 2020 16:52:29 +0100 Subject: [PATCH 243/430] Warn if app-backend can't start-up because missing dir --- .changeset/brave-rats-obey.md | 5 +++++ plugins/app-backend/src/service/router.ts | 12 +++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 .changeset/brave-rats-obey.md diff --git a/.changeset/brave-rats-obey.md b/.changeset/brave-rats-obey.md new file mode 100644 index 0000000000..a09d922763 --- /dev/null +++ b/.changeset/brave-rats-obey.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-app-backend': patch +--- + +Warn if the app-backend can't start-up because the static directory that should be served is unavailable. diff --git a/plugins/app-backend/src/service/router.ts b/plugins/app-backend/src/service/router.ts index b435ec15f1..65cbddaae6 100644 --- a/plugins/app-backend/src/service/router.ts +++ b/plugins/app-backend/src/service/router.ts @@ -21,6 +21,7 @@ import { Logger } from 'winston'; import { notFoundHandler, resolvePackagePath } from '@backstage/backend-common'; import { Config } from '@backstage/config'; import { injectConfig, readConfigs } from '../lib/config'; +import fs from 'fs-extra'; export interface RouterOptions { config: Config; @@ -35,9 +36,18 @@ export async function createRouter( const { config, logger, appPackageName, staticFallbackHandler } = options; const appDistDir = resolvePackagePath(appPackageName, 'dist'); - logger.info(`Serving static app content from ${appDistDir}`); const staticDir = resolvePath(appDistDir, 'static'); + if (!(await fs.pathExists(staticDir))) { + logger.warn( + `Can't serve static app content from ${staticDir}, directory doesn't exist`, + ); + + return Router(); + } + + logger.info(`Serving static app content from ${appDistDir}`); + const appConfigs = await readConfigs({ config, appDistDir, From 6f70ed7a95e94d40e2eb5634b851c0ea6487e664 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Wed, 25 Nov 2020 17:23:54 +0100 Subject: [PATCH 244/430] Replace usage of implementsApis with relations --- .changeset/fifty-nails-pay.md | 6 ++++++ .changeset/short-singers-serve.md | 2 +- .../MissingImplementsApisEmptyState.tsx | 7 +++---- plugins/api-docs/src/catalog/Router.tsx | 5 +++-- .../api-docs/src/components/useComponentApiNames.ts | 12 ++++++++++-- .../catalog/src/components/AboutCard/AboutCard.tsx | 7 +++++-- 6 files changed, 28 insertions(+), 11 deletions(-) create mode 100644 .changeset/fifty-nails-pay.md diff --git a/.changeset/fifty-nails-pay.md b/.changeset/fifty-nails-pay.md new file mode 100644 index 0000000000..5880d9a8cc --- /dev/null +++ b/.changeset/fifty-nails-pay.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-api-docs': patch +'@backstage/plugin-catalog': patch +--- + +Replace usage of implementsApis with relations diff --git a/.changeset/short-singers-serve.md b/.changeset/short-singers-serve.md index ae8e8d171c..c2f1c1914f 100644 --- a/.changeset/short-singers-serve.md +++ b/.changeset/short-singers-serve.md @@ -4,7 +4,7 @@ Marked the field `spec.implementsApis` on `Component` entities for deprecation on Dec 14th, 2020. -Code that consumes these fields should remove those usages as soon as possible as migrate to using +Code that consumes these fields should remove those usages as soon as possible and migrate to using relations instead. Producers should fill the field `spec.providesApis` instead, which has the same semantic. diff --git a/plugins/api-docs/src/catalog/MissingImplementsApisEmptyState/MissingImplementsApisEmptyState.tsx b/plugins/api-docs/src/catalog/MissingImplementsApisEmptyState/MissingImplementsApisEmptyState.tsx index 9160aca514..f13848bd19 100644 --- a/plugins/api-docs/src/catalog/MissingImplementsApisEmptyState/MissingImplementsApisEmptyState.tsx +++ b/plugins/api-docs/src/catalog/MissingImplementsApisEmptyState/MissingImplementsApisEmptyState.tsx @@ -28,7 +28,7 @@ spec: type: service lifecycle: production owner: guest - implementsApis: + providesApis: - example-api `; @@ -49,8 +49,7 @@ export const MissingImplementsApisEmptyState = () => { description={ Components can implement APIs that are displayed on this page. You - need to fill the implementsApis field to enable this - tool. + need to fill the providesApis field to enable this tool. } action={ @@ -71,7 +70,7 @@ export const MissingImplementsApisEmptyState = () => { diff --git a/plugins/api-docs/src/catalog/Router.tsx b/plugins/api-docs/src/catalog/Router.tsx index d3fe7f7553..6991ad4b57 100644 --- a/plugins/api-docs/src/catalog/Router.tsx +++ b/plugins/api-docs/src/catalog/Router.tsx @@ -15,14 +15,15 @@ */ import React from 'react'; -import { Entity } from '@backstage/catalog-model'; +import { Entity, RELATION_PROVIDES_API } from '@backstage/catalog-model'; import { Route, Routes } from 'react-router'; import { catalogRoute } from '../routes'; import { EntityPageApi } from './EntityPageApi'; import { MissingImplementsApisEmptyState } from './MissingImplementsApisEmptyState'; const isPluginApplicableToEntity = (entity: Entity) => { - return ((entity.spec?.implementsApis as string[]) || []).length > 0; + // TODO: Als support RELATION_CONSUMES_API + return entity.relations?.some(r => r.type === RELATION_PROVIDES_API); }; export const Router = ({ entity }: { entity: Entity }) => diff --git a/plugins/api-docs/src/components/useComponentApiNames.ts b/plugins/api-docs/src/components/useComponentApiNames.ts index 0eabe2b6c7..1303967895 100644 --- a/plugins/api-docs/src/components/useComponentApiNames.ts +++ b/plugins/api-docs/src/components/useComponentApiNames.ts @@ -14,8 +14,16 @@ * limitations under the License. */ -import { ComponentEntity } from '@backstage/catalog-model'; +import { + ComponentEntity, + RELATION_PROVIDES_API, +} from '@backstage/catalog-model'; export const useComponentApiNames = (entity: ComponentEntity) => { - return (entity.spec?.implementsApis as string[]) || []; + // TODO: This code doesn't handle namespaces and kinds correctly, but will be removed soon + return ( + entity.relations + ?.filter(r => r.type === RELATION_PROVIDES_API) + ?.map(r => r.target.name) || [] + ); }; diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.tsx index 89630d6f10..49653fe981 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.tsx @@ -18,6 +18,7 @@ import { Entity, ENTITY_DEFAULT_NAMESPACE, RELATION_OWNED_BY, + RELATION_PROVIDES_API, serializeEntityRef, } from '@backstage/catalog-model'; import { @@ -111,6 +112,8 @@ type AboutCardProps = { export function AboutCard({ entity, variant }: AboutCardProps) { const classes = useStyles(); const codeLink = getCodeLinkInfo(entity); + // TODO: Also support RELATION_CONSUMES_API here + const hasApis = entity.relations?.some(r => r.type === RELATION_PROVIDES_API); return ( @@ -146,9 +149,9 @@ export function AboutCard({ entity, variant }: AboutCardProps) { }/${entity.kind}/${entity.metadata.name}`} /> } href="api" /> From 266e93b47831b2c058612d4ee643b1a0df4e8030 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Tue, 24 Nov 2020 23:29:18 +0100 Subject: [PATCH 245/430] TechDocs: Refactor metadata retrieval 1. Don't use mkdocs in name of APIs or variables. It is an implementation detail and liable to change in future. 2. techDocsApi.getMetadata('mkdocs') and techDocsAPI.getMetadata('entity') should be two separate functions just because their responses differ in structure. They should also be type checked. 3. Use either 'techdocs' or 'TechDocs' consistently. 'techDocs' seems like an unnecessary third way to write TechDocs, which can be avoided. 4. Remove unused /plugins/techdocs-backend/src/service/metadata.ts file --- .../techdocs-backend/src/service/metadata.ts | 41 ------------------- .../techdocs-backend/src/service/router.ts | 6 +-- plugins/techdocs/src/api.ts | 36 ++++++++++++++-- .../reader/components/TechDocsPage.test.tsx | 11 ++--- .../src/reader/components/TechDocsPage.tsx | 14 +++---- .../components/TechDocsPageHeader.test.tsx | 4 +- .../reader/components/TechDocsPageHeader.tsx | 11 +++-- 7 files changed, 57 insertions(+), 66 deletions(-) delete mode 100644 plugins/techdocs-backend/src/service/metadata.ts diff --git a/plugins/techdocs-backend/src/service/metadata.ts b/plugins/techdocs-backend/src/service/metadata.ts deleted file mode 100644 index 760180f8d2..0000000000 --- a/plugins/techdocs-backend/src/service/metadata.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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 fetch from 'cross-fetch'; - -export class TechDocsMetadata { - private async getMetadataFile(docsUrl: String) { - const metadataURL = `${docsUrl}/techdocs_metadata.json`; - - try { - const req = await fetch(metadataURL); - - return await req.json(); - } catch (error) { - throw new Error(error); - } - } - - public async getMkDocsMetaData(docsUrl: any) { - const mkDocsMetadata = await this.getMetadataFile(docsUrl); - - if (!mkDocsMetadata) return null; - - return { - ...mkDocsMetadata, - }; - } -} diff --git a/plugins/techdocs-backend/src/service/router.ts b/plugins/techdocs-backend/src/service/router.ts index 1d932a1fb9..625252dd7e 100644 --- a/plugins/techdocs-backend/src/service/router.ts +++ b/plugins/techdocs-backend/src/service/router.ts @@ -61,7 +61,7 @@ export async function createRouter({ }: RouterOptions): Promise { const router = Router(); - router.get('/metadata/mkdocs/*', async (req, res) => { + router.get('/metadata/techdocs/*', async (req, res) => { let storageUrl = config.getString('techdocs.storageUrl'); if (publisher instanceof LocalPublish) { storageUrl = new URL( @@ -74,8 +74,8 @@ export async function createRouter({ const metadataURL = `${storageUrl}/${path}/techdocs_metadata.json`; try { - const mkDocsMetadata = await (await fetch(metadataURL)).json(); - res.send(mkDocsMetadata); + const techdocsMetadata = await (await fetch(metadataURL)).json(); + res.send(techdocsMetadata); } catch (err) { logger.info(`Unable to get metadata for ${path} with error ${err}`); throw new Error(`Unable to get metadata for ${path} with error ${err}`); diff --git a/plugins/techdocs/src/api.ts b/plugins/techdocs/src/api.ts index 7281ebdf13..98e1e6c310 100644 --- a/plugins/techdocs/src/api.ts +++ b/plugins/techdocs/src/api.ts @@ -15,7 +15,6 @@ */ import { createApiRef } from '@backstage/core'; - import { ParsedEntityId } from './types'; export const techdocsStorageApiRef = createApiRef({ @@ -38,7 +37,8 @@ export interface TechDocsStorage { } export interface TechDocs { - getMetadata(metadataType: string, entityId: ParsedEntityId): Promise; + getTechDocsMetadata(entityId: ParsedEntityId): Promise; + getEntityMetadata(entityId: ParsedEntityId): Promise; } /** @@ -53,10 +53,38 @@ export class TechDocsApi implements TechDocs { this.apiOrigin = apiOrigin; } - async getMetadata(metadataType: string, entityId: ParsedEntityId) { + /** + * Retrieve TechDocs metadata. + * + * When docs are built, we generate a techdocs_metadata.json and store it along with the generated + * static files. It includes necessary data about the docs site. This method requests techdocs-backend + * which retries the TechDocs metadata. + * + * @param {ParsedEntityId} entityId Object containing entity data like name, namespace, etc. + */ + async getTechDocsMetadata(entityId: ParsedEntityId) { const { kind, namespace, name } = entityId; - const requestUrl = `${this.apiOrigin}/metadata/${metadataType}/${namespace}/${kind}/${name}`; + const requestUrl = `${this.apiOrigin}/metadata/techdocs/${namespace}/${kind}/${name}`; + + const request = await fetch(`${requestUrl}`); + const res = await request.json(); + + return res; + } + + /** + * Retrieve metadata about an entity. + * + * This method requests techdocs-backend which uses the catalog APIs to respond with filtered + * information required here. + * + * @param {ParsedEntityId} entityId Object containing entity data like name, namespace, etc. + */ + async getEntityMetadata(entityId: ParsedEntityId) { + const { kind, namespace, name } = entityId; + + const requestUrl = `${this.apiOrigin}/metadata/entity/${namespace}/${kind}/${name}`; const request = await fetch(`${requestUrl}`); const res = await request.json(); diff --git a/plugins/techdocs/src/reader/components/TechDocsPage.test.tsx b/plugins/techdocs/src/reader/components/TechDocsPage.test.tsx index 464115bab7..c05c0488ff 100644 --- a/plugins/techdocs/src/reader/components/TechDocsPage.test.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsPage.test.tsx @@ -50,17 +50,18 @@ describe('', () => { entityId: 'Component::backstage', }); - const techDocsApi: Partial = { - getMetadata: () => Promise.resolve([]), + const techdocsApi: Partial = { + getEntityMetadata: () => Promise.resolve([]), + getTechDocsMetadata: () => Promise.resolve([]), }; - const techDocsStorageApi: Partial = { + const techdocsStorageApi: Partial = { getEntityDocs: (): Promise => Promise.resolve('String'), getBaseUrl: (): string => '', }; const apiRegistry = ApiRegistry.from([ - [techdocsApiRef, techDocsApi], - [techdocsStorageApiRef, techDocsStorageApi], + [techdocsApiRef, techdocsApi], + [techdocsStorageApiRef, techdocsStorageApi], ]); await act(async () => { diff --git a/plugins/techdocs/src/reader/components/TechDocsPage.tsx b/plugins/techdocs/src/reader/components/TechDocsPage.tsx index 00f0b3c9d3..f2635c0dfb 100644 --- a/plugins/techdocs/src/reader/components/TechDocsPage.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsPage.tsx @@ -26,19 +26,19 @@ export const TechDocsPage = () => { const [documentReady, setDocumentReady] = useState(false); const { namespace, kind, name } = useParams(); - const techDocsApi = useApi(techdocsApiRef); + const techdocsApi = useApi(techdocsApiRef); - const mkdocsMetadataRequest = useAsync(() => { + const techdocsMetadataRequest = useAsync(() => { if (documentReady) { - return techDocsApi.getMetadata('mkdocs', { kind, namespace, name }); + return techdocsApi.getTechDocsMetadata({ kind, namespace, name }); } return Promise.resolve({ loading: true }); - }, [kind, namespace, name, techDocsApi, documentReady]); + }, [kind, namespace, name, techdocsApi, documentReady]); const entityMetadataRequest = useAsync(() => { - return techDocsApi.getMetadata('entity', { kind, namespace, name }); - }, [kind, namespace, name, techDocsApi]); + return techdocsApi.getEntityMetadata({ kind, namespace, name }); + }, [kind, namespace, name, techdocsApi]); const onReady = () => { setDocumentReady(true); @@ -48,7 +48,7 @@ export const TechDocsPage = () => { ', () => { }, }, }, - mkdocs: { + techdocs: { loading: false, value: { site_name: 'test-site-name', @@ -73,7 +73,7 @@ describe('', () => { entity: { loading: false, }, - mkdocs: { + techdocs: { loading: false, }, }} diff --git a/plugins/techdocs/src/reader/components/TechDocsPageHeader.tsx b/plugins/techdocs/src/reader/components/TechDocsPageHeader.tsx index f4c447bae2..475b87da6f 100644 --- a/plugins/techdocs/src/reader/components/TechDocsPageHeader.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsPageHeader.tsx @@ -25,7 +25,7 @@ type TechDocsPageHeaderProps = { entityId: ParsedEntityId; metadataRequest: { entity: AsyncState; - mkdocs: AsyncState; + techdocs: AsyncState; }; }; @@ -33,15 +33,18 @@ export const TechDocsPageHeader = ({ entityId, metadataRequest, }: TechDocsPageHeaderProps) => { - const { mkdocs: mkdocsMetadata, entity: entityMetadata } = metadataRequest; + const { + techdocs: techdocsMetadata, + entity: entityMetadata, + } = metadataRequest; - const { value: mkDocsMetadataValues } = mkdocsMetadata; + const { value: techdocsMetadataValues } = techdocsMetadata; const { value: entityMetadataValues } = entityMetadata; const { kind, name } = entityId; const { site_name: siteName, site_description: siteDescription } = - mkDocsMetadataValues || {}; + techdocsMetadataValues || {}; const { locationMetadata, From 039970d7106c537f8d51cb0b9eda1e575b9bb7b4 Mon Sep 17 00:00:00 2001 From: jothisubaramaniam Date: Wed, 25 Nov 2020 16:28:16 -0500 Subject: [PATCH 246/430] introduce data.type=config_info for the new postMessage in auth --- packages/core-api/src/lib/loginPopup.test.ts | 2 ++ packages/core-api/src/lib/loginPopup.ts | 2 +- plugins/auth-backend/src/lib/flow/authFlowHelpers.ts | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/core-api/src/lib/loginPopup.test.ts b/packages/core-api/src/lib/loginPopup.test.ts index 6527618ef7..6b3b49c781 100644 --- a/packages/core-api/src/lib/loginPopup.test.ts +++ b/packages/core-api/src/lib/loginPopup.test.ts @@ -161,6 +161,7 @@ describe('showLoginPopup', () => { source: popupMock, origin: 'origin', data: { + type: 'config_info', targetOrigin: 'http://localhost', }, } as MessageEvent); @@ -198,6 +199,7 @@ describe('showLoginPopup', () => { source: popupMock, origin: 'origin', data: { + type: 'config_info', targetOrigin: 'http://differenthost', }, } as MessageEvent); diff --git a/packages/core-api/src/lib/loginPopup.ts b/packages/core-api/src/lib/loginPopup.ts index 5c6d424363..00a5efce66 100644 --- a/packages/core-api/src/lib/loginPopup.ts +++ b/packages/core-api/src/lib/loginPopup.ts @@ -95,7 +95,7 @@ export function showLoginPopup(options: LoginPopupOptions): Promise { } const { data } = event; - if (data.targetOrigin) { + if (data.type === 'config_info') { targetOrigin = data.targetOrigin; return; } diff --git a/plugins/auth-backend/src/lib/flow/authFlowHelpers.ts b/plugins/auth-backend/src/lib/flow/authFlowHelpers.ts index 5aba427905..22ffc7af88 100644 --- a/plugins/auth-backend/src/lib/flow/authFlowHelpers.ts +++ b/plugins/auth-backend/src/lib/flow/authFlowHelpers.ts @@ -53,7 +53,7 @@ export const postMessageResponse = ( const script = ` var authResponse = decodeURIComponent('${base64Data}'); var origin = decodeURIComponent('${base64Origin}'); - var originInfo = {'targetOrigin': origin}; + var originInfo = {'type': 'config_info', 'targetOrigin': origin}; (window.opener || window.parent).postMessage(originInfo, '*'); (window.opener || window.parent).postMessage(JSON.parse(authResponse), origin); window.close(); From a85a1bedd632bfedc11e6fc7cca0022f5ce35351 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 25 Nov 2020 23:45:21 +0100 Subject: [PATCH 247/430] github/workflows: use service account auth for changeset By switching to using a service account instead of the regular token we allowed builds to be triggered in created PR --- .github/workflows/changeset.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/changeset.yml b/.github/workflows/changeset.yml index a557b4921b..4bd03b4d96 100644 --- a/.github/workflows/changeset.yml +++ b/.github/workflows/changeset.yml @@ -19,4 +19,4 @@ jobs: # Calls out to `changeset version`, but also runs prettier version: yarn release env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} From feaec380e66292413fb0d21d00e8a0cdbbf24039 Mon Sep 17 00:00:00 2001 From: jothisubaramaniam Date: Wed, 25 Nov 2020 18:43:34 -0500 Subject: [PATCH 248/430] improve error message --- packages/core-api/src/lib/loginPopup.test.ts | 2 +- packages/core-api/src/lib/loginPopup.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core-api/src/lib/loginPopup.test.ts b/packages/core-api/src/lib/loginPopup.test.ts index 6b3b49c781..98541c268e 100644 --- a/packages/core-api/src/lib/loginPopup.test.ts +++ b/packages/core-api/src/lib/loginPopup.test.ts @@ -208,7 +208,7 @@ describe('showLoginPopup', () => { popupMock.closed = true; }, 150); await expect(payloadPromise).rejects.toThrow( - 'Login failed, incorrect app origin', + 'Login failed, Incorrect app origin, expected http://differenthost', ); expect(openSpy).toBeCalledTimes(1); diff --git a/packages/core-api/src/lib/loginPopup.ts b/packages/core-api/src/lib/loginPopup.ts index 00a5efce66..2e14447882 100644 --- a/packages/core-api/src/lib/loginPopup.ts +++ b/packages/core-api/src/lib/loginPopup.ts @@ -121,7 +121,7 @@ export function showLoginPopup(options: LoginPopupOptions): Promise { if (popup.closed) { const errMessage = `Login failed, ${ targetOrigin !== window.location.origin - ? 'incorrect app origin' + ? `Incorrect app origin, expected ${targetOrigin}` : 'popup was closed' }`; const error = new Error(errMessage); From 700a212b4692857f00a8dd93c04b83d30584da36 Mon Sep 17 00:00:00 2001 From: jothisubaramaniam Date: Wed, 25 Nov 2020 18:44:52 -0500 Subject: [PATCH 249/430] leave the changeset filename as is --- .../{auth-backend-ten-doors-exist.md => ten-doors-exist.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .changeset/{auth-backend-ten-doors-exist.md => ten-doors-exist.md} (100%) diff --git a/.changeset/auth-backend-ten-doors-exist.md b/.changeset/ten-doors-exist.md similarity index 100% rename from .changeset/auth-backend-ten-doors-exist.md rename to .changeset/ten-doors-exist.md From f03cbc879cf6da601d0b51619dacad093710a77d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 26 Nov 2020 05:09:16 +0000 Subject: [PATCH 250/430] build(deps): bump eslint-plugin-react from 7.19.0 to 7.21.5 Bumps [eslint-plugin-react](https://github.com/yannickcr/eslint-plugin-react) from 7.19.0 to 7.21.5. - [Release notes](https://github.com/yannickcr/eslint-plugin-react/releases) - [Changelog](https://github.com/yannickcr/eslint-plugin-react/blob/master/CHANGELOG.md) - [Commits](https://github.com/yannickcr/eslint-plugin-react/compare/v7.19.0...v7.21.5) Signed-off-by: dependabot[bot] --- yarn.lock | 132 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 108 insertions(+), 24 deletions(-) diff --git a/yarn.lock b/yarn.lock index 967fbb016d..576db3b66c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1196,7 +1196,7 @@ core-js "^2.6.5" regenerator-runtime "^0.13.4" -"@babel/runtime-corejs3@^7.10.2", "@babel/runtime-corejs3@^7.8.3": +"@babel/runtime-corejs3@^7.10.2": version "7.10.3" resolved "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.10.3.tgz#931ed6941d3954924a7aa967ee440e60c507b91a" integrity sha512-HA7RPj5xvJxQl429r5Cxr2trJwOfPjKiqhCXcdQPSqO2G0RHPZpXu4fkYmBaTKCp2c/jRaMK9GB/lN+7zvvFPw== @@ -8340,6 +8340,14 @@ cachedir@^2.3.0: resolved "https://registry.npmjs.org/cachedir/-/cachedir-2.3.0.tgz#0c75892a052198f0b21c7c1804d8331edfcae0e8" integrity sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw== +call-bind@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.0.tgz#24127054bb3f9bdcb4b1fb82418186072f77b8ce" + integrity sha512-AEXsYIyyDY3MCzbwdhzG3Jx1R0J2wetQyUynn6dYHAO+bg8l1k7jwZtRv4ryryFs7EP+NDlikJlVe59jr0cM2w== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.0" + call-me-maybe@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b" @@ -10992,6 +11000,23 @@ 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-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" @@ -11249,22 +11274,21 @@ eslint-plugin-react-hooks@^4.0.0: integrity sha512-equAdEIsUETLFNCmmCkiCGq6rkSK5MoJhXFPFYeUebcjKgBmWWcgVOqZyQC8Bv1BwVCnTq9tBxgJFgAJTWoJtA== eslint-plugin-react@^7.12.4: - version "7.19.0" - resolved "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.19.0.tgz#6d08f9673628aa69c5559d33489e855d83551666" - integrity sha512-SPT8j72CGuAP+JFbT0sJHOB80TX/pu44gQ4vXH/cq+hQTiY2PuZ6IHkqXJV6x1b28GDdo1lbInjKUrrdUf0LOQ== + 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.2.3" - object.entries "^1.1.1" + 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.15.1" - semver "^6.3.0" + resolve "^1.18.1" string.prototype.matchall "^4.0.2" - xregexp "^4.3.0" eslint-scope@^4.0.3: version "4.0.3" @@ -12436,6 +12460,15 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5: resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== +get-intrinsic@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.0.1.tgz#94a9768fcbdd0595a1c9273aacf4c89d075631be" + integrity sha512-ZnWP+AmS1VUaLgTRy47+zKtjTxz+0xMpx3I52i+aalBK1QP19ggLF3Db89KJX7kjfOfP2eoa01qc++GwPgufPg== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + get-monorepo-packages@^1.1.0: version "1.2.0" resolved "https://registry.npmjs.org/get-monorepo-packages/-/get-monorepo-packages-1.2.0.tgz#3eee88d30b11a5f65955dec6ae331958b2a168e4" @@ -14025,6 +14058,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" @@ -14044,6 +14082,13 @@ is-color-stop@^1.0.0: rgb-regex "^1.0.1" rgba-regex "^1.0.0" +is-core-module@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.1.0.tgz#a4cc031d9b1aca63eecbd18a650e13cb4eeab946" + integrity sha512-YcV7BgVMRFRua2FqQzKtTDMz8iCuLEyGKjr70q8Zm1yy2qKcurbFEd79PAdHV77oL3NrAaOVQIbMmiHQCHB7ZA== + 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" @@ -15406,7 +15451,7 @@ jss@10.1.1, jss@^10.0.3: is-in-browser "^1.1.3" tiny-warning "^1.0.2" -jsx-ast-utils@^2.2.1, jsx-ast-utils@^2.2.3: +jsx-ast-utils@^2.2.1: version "2.2.3" resolved "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.2.3.tgz#8a9364e402448a3ce7f14d357738310d9248054f" integrity sha512-EdIHFMm+1BPynpKOpdPqiOsvnIrInRGJD7bzPZdPkjitQEqpdpUuFpq4T0npZFKTiB3RhWFdGN+oqOJIdhDhQA== @@ -15414,6 +15459,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" @@ -17628,6 +17681,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" @@ -17660,6 +17718,16 @@ object.assign@^4.1.0: has-symbols "^1.0.0" object-keys "^1.0.11" +object.assign@^4.1.1: + version "4.1.2" + resolved "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" + integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + 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" @@ -17670,14 +17738,13 @@ object.defaults@^1.1.0: for-own "^1.0.0" isobject "^3.0.0" -object.entries@^1.1.0, object.entries@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/object.entries/-/object.entries-1.1.1.tgz#ee1cf04153de02bb093fec33683900f57ce5399b" - integrity sha512-ilqR7BgdyZetJutmDPfXCDffGa0/Yzl2ivVNpbx/g4UeWrCdRnFDUBrKJGLhGieRHDATnyZXWBeCb29k9CJysQ== +object.entries@^1.1.0, 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.0-next.1" - function-bind "^1.1.1" + es-abstract "^1.17.5" has "^1.0.3" "object.fromentries@^2.0.0 || ^1.0.0", object.fromentries@^2.0.2: @@ -20935,13 +21002,21 @@ resolve-url@^0.2.1: resolved "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= -resolve@1.17.0, resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.15.1, resolve@^1.16.1, resolve@^1.17.0, resolve@^1.3.2: +resolve@1.17.0: version "1.17.0" resolved "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== dependencies: path-parse "^1.0.6" +resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.16.1, resolve@^1.17.0, resolve@^1.18.1, resolve@^1.3.2: + version "1.19.0" + resolved "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz#1af5bf630409734a067cae29318aac7fa29a267c" + integrity sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg== + dependencies: + is-core-module "^2.1.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" @@ -22230,6 +22305,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.3" + resolved "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.3.tgz#a22bd53cca5c7cf44d7c9d5c732118873d6cd18b" + integrity sha512-ayH0pB+uf0U28CtjlLvL7NaohvR1amUvVZk+y3DYb0Ey2PUV5zPkkKy9+U1ndVEIXO8hNg18eIv9Jntbii+dKw== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + 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" @@ -22246,6 +22329,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.3" + resolved "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.3.tgz#9b4cb590e123bb36564401d59824298de50fd5aa" + integrity sha512-oBIBUy5lea5tt0ovtOFiEQaBkoBBkyJhZXzJYrSmDo5IUUqbOPvVezuRs/agBIdZ2p2Eo1FD6bD9USyBLfl3xg== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + 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" @@ -24620,13 +24711,6 @@ xpath@0.0.27: resolved "https://registry.npmjs.org/xpath/-/xpath-0.0.27.tgz#dd3421fbdcc5646ac32c48531b4d7e9d0c2cfa92" integrity sha512-fg03WRxtkCV6ohClePNAECYsmpKKTv5L8y/X3Dn1hQrec3POx2jHZ/0P2qQ6HvsrU1BmeqXcof3NGGueG6LxwQ== -xregexp@^4.3.0: - version "4.3.0" - resolved "https://registry.npmjs.org/xregexp/-/xregexp-4.3.0.tgz#7e92e73d9174a99a59743f67a4ce879a04b5ae50" - integrity sha512-7jXDIFXh5yJ/orPn4SXjuVrWWoi4Cr8jfV1eHv9CixKSbU+jY4mxfrBwAuDvupPNKpMUY+FeIqsVw/JLT9+B8g== - dependencies: - "@babel/runtime-corejs3" "^7.8.3" - xss@^1.0.6: version "1.0.7" resolved "https://registry.npmjs.org/xss/-/xss-1.0.7.tgz#a554cbd5e909324bd6893fb47fff441ad54e2a95" From faa73389f2c87f7afcd99674c6655a2bf4c628f5 Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Thu, 26 Nov 2020 06:29:29 +0100 Subject: [PATCH 251/430] Change package versions --- .changeset/smart-turkeys-bathe.md | 2 -- packages/app/package.json | 2 +- packages/backend/package.json | 4 ++-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.changeset/smart-turkeys-bathe.md b/.changeset/smart-turkeys-bathe.md index f7776a81aa..5f429b5b37 100644 --- a/.changeset/smart-turkeys-bathe.md +++ b/.changeset/smart-turkeys-bathe.md @@ -1,8 +1,6 @@ --- '@backstage/plugin-catalog-backend': minor '@backstage/plugin-catalog-import': minor -'example-app': patch -'example-backend': patch '@backstage/catalog-model': patch '@backstage/plugin-scaffolder': patch --- diff --git a/packages/app/package.json b/packages/app/package.json index 0ee1b47e57..52906bb65a 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "example-app", - "version": "0.2.2", + "version": "0.2.1", "private": true, "bundled": true, "dependencies": { diff --git a/packages/backend/package.json b/packages/backend/package.json index 5011c21c14..410aca1087 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.2.2", + "version": "0.2.1", "main": "dist/index.cjs.js", "types": "src/index.ts", "private": true, @@ -35,7 +35,7 @@ "@octokit/rest": "^18.0.0", "azure-devops-node-api": "^10.1.1", "dockerode": "^3.2.0", - "example-app": "^0.2.2", + "example-app": "^0.2.1", "express": "^4.17.1", "express-promise-router": "^3.0.3", "knex": "^0.21.6", From 8e7696d533c31610f66b977c561f7118994c2ea1 Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Thu, 26 Nov 2020 09:24:30 +0100 Subject: [PATCH 252/430] Fix after merge --- packages/app/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/app/package.json b/packages/app/package.json index 9e54005cd3..37919b2d31 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -9,6 +9,7 @@ "@backstage/core": "^0.3.2", "@backstage/plugin-api-docs": "^0.2.2", "@backstage/plugin-catalog": "^0.2.3", + "@backstage/plugin-catalog-import": "^0.2.0", "@backstage/plugin-circleci": "^0.2.2", "@backstage/plugin-cloudbuild": "^0.2.2", "@backstage/plugin-cost-insights": "^0.4.1", From 8bef6b9a5f65e5f4fa33002fe89975f051a62217 Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Thu, 26 Nov 2020 10:18:45 +0100 Subject: [PATCH 253/430] Fix after merge --- plugins/catalog-import/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index ac41d70c30..2a44d7555a 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -22,7 +22,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.1", + "@backstage/core": "^0.3.2", "@backstage/plugin-catalog": "^0.2.0", "@backstage/plugin-catalog-backend": "^0.3.0", "@backstage/theme": "^0.2.1", @@ -40,7 +40,7 @@ "yaml": "^1.10.0" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", From 4b53294a6cb75b145f90cd53e97d13498f05c125 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Thu, 26 Nov 2020 00:29:46 +0100 Subject: [PATCH 254/430] TechDocs: Update mkdocs.yml with repo_url when possible before building docs --- .changeset/eleven-lobsters-train.md | 7 + plugins/techdocs-backend/package.json | 2 + .../techdocs-backend/src/service/helpers.ts | 3 + .../stages/generate/__fixtures__/mkdocs.yml | 2 + .../__fixtures__/mkdocs_with_repo_url.yml | 4 + .../techdocs/stages/generate/helpers.test.ts | 197 +++++++++++++++++- .../src/techdocs/stages/generate/helpers.ts | 137 +++++++++++- .../src/techdocs/stages/generate/techdocs.ts | 16 +- .../src/techdocs/stages/generate/types.ts | 15 +- 9 files changed, 373 insertions(+), 10 deletions(-) create mode 100644 .changeset/eleven-lobsters-train.md create mode 100644 plugins/techdocs-backend/src/techdocs/stages/generate/__fixtures__/mkdocs.yml create mode 100644 plugins/techdocs-backend/src/techdocs/stages/generate/__fixtures__/mkdocs_with_repo_url.yml diff --git a/.changeset/eleven-lobsters-train.md b/.changeset/eleven-lobsters-train.md new file mode 100644 index 0000000000..95b9145ee6 --- /dev/null +++ b/.changeset/eleven-lobsters-train.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-techdocs': minor +'@backstage/plugin-techdocs-backend': minor +--- + +- Use techdocs annotation to add repo_url if missing in mkdocs.yml. Having repo_url creates a Edit button on techdocs pages. +- techdocs-backend: API endpoint `/metadata/mkdocs/*` renamed to `/metadata/techdocs/*` diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index 1d9f3ea7c1..a09adb7380 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -32,7 +32,9 @@ "express-promise-router": "^3.0.3", "fs-extra": "^9.0.1", "git-url-parse": "^11.4.0", + "js-yaml": "^3.14.0", "knex": "^0.21.6", + "mock-fs": "^4.13.0", "nodegit": "^0.27.0", "winston": "^3.2.1" }, diff --git a/plugins/techdocs-backend/src/service/helpers.ts b/plugins/techdocs-backend/src/service/helpers.ts index 3017a68461..4a736f7388 100644 --- a/plugins/techdocs-backend/src/service/helpers.ts +++ b/plugins/techdocs-backend/src/service/helpers.ts @@ -69,10 +69,13 @@ export class DocsBuilder { this.logger.info(`Running preparer on entity ${getEntityId(this.entity)}`); const preparedDir = await this.preparer.prepare(this.entity); + const parsedLocationAnnotation = getLocationForEntity(this.entity); + this.logger.info(`Running generator on entity ${getEntityId(this.entity)}`); const { resultDir } = await this.generator.run({ directory: preparedDir, dockerClient: this.dockerClient, + parsedLocationAnnotation, }); this.logger.info(`Running publisher on entity ${getEntityId(this.entity)}`); diff --git a/plugins/techdocs-backend/src/techdocs/stages/generate/__fixtures__/mkdocs.yml b/plugins/techdocs-backend/src/techdocs/stages/generate/__fixtures__/mkdocs.yml new file mode 100644 index 0000000000..1d1186840f --- /dev/null +++ b/plugins/techdocs-backend/src/techdocs/stages/generate/__fixtures__/mkdocs.yml @@ -0,0 +1,2 @@ +site_name: Test site name +site_description: Test site description diff --git a/plugins/techdocs-backend/src/techdocs/stages/generate/__fixtures__/mkdocs_with_repo_url.yml b/plugins/techdocs-backend/src/techdocs/stages/generate/__fixtures__/mkdocs_with_repo_url.yml new file mode 100644 index 0000000000..37b004bfe4 --- /dev/null +++ b/plugins/techdocs-backend/src/techdocs/stages/generate/__fixtures__/mkdocs_with_repo_url.yml @@ -0,0 +1,4 @@ +site_name: Test site name +site_description: Test site description + +repo_url: https://github.com/backstage/backstage diff --git a/plugins/techdocs-backend/src/techdocs/stages/generate/helpers.test.ts b/plugins/techdocs-backend/src/techdocs/stages/generate/helpers.test.ts index 283560f0ce..4938739fdf 100644 --- a/plugins/techdocs-backend/src/techdocs/stages/generate/helpers.test.ts +++ b/plugins/techdocs-backend/src/techdocs/stages/generate/helpers.test.ts @@ -13,10 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import Stream, { PassThrough } from 'stream'; +import fs from 'fs'; import os from 'os'; +import { resolve as resolvePath } from 'path'; +import Stream, { PassThrough } from 'stream'; import Docker from 'dockerode'; -import { runDockerContainer, getGeneratorKey } from './helpers'; +import mockFs from 'mock-fs'; +import * as winston from 'winston'; +import { + runDockerContainer, + getGeneratorKey, + isValidRepoUrlForMkdocs, + getRepoUrlFromLocationAnnotation, + patchMkdocsYmlPreBuild, +} from './helpers'; +import { RemoteProtocol } from '../prepare/types'; +import { ParsedLocationAnnotation } from '../../../helpers'; const mockEntity = { apiVersion: 'version', @@ -28,6 +40,14 @@ const mockEntity = { const mockDocker = new Docker() as jest.Mocked; +const mkdocsYml = fs.readFileSync( + resolvePath(__filename, '../__fixtures__/mkdocs.yml'), +); +const mkdocsYmlWithRepoUrl = fs.readFileSync( + resolvePath(__filename, '../__fixtures__/mkdocs_with_repo_url.yml'), +); +const mockLogger = winston.createLogger(); + describe('helpers', () => { describe('getGeneratorKey', () => { it('should return techdocs as the only generator key', () => { @@ -138,4 +158,177 @@ describe('helpers', () => { }); }); }); + + describe('isValidRepoUrlForMkdocs', () => { + it('should return true for valid repo_url values for mkdocs', () => { + const validRepoUrls = [ + 'https://github.com/org/repo', + 'https://github.com/backstage/backstage/', + 'https://github.com/org123/repo1-2-3/', + 'http://github.com/insecureOrg/insecureRepo', + 'https://gitlab.com/org/repo', + 'https://gitlab.com/backstage/backstage/', + 'https://gitlab.com/org123/repo1-2-3/', + 'http://gitlab.com/insecureOrg/insecureRepo', + ]; + + const validRemoteProtocols = ['github', 'gitlab']; + + validRepoUrls.forEach(url => { + validRemoteProtocols.forEach(targetType => { + expect( + isValidRepoUrlForMkdocs(url, targetType as RemoteProtocol), + ).toBe(true); + }); + }); + }); + + it('should return false for invalid repo_urls values for mkdocs', () => { + const invalidRepoUrls = [ + 'git@github.com:org/repo', + 'https://github.com/backstage/backstage/tree/master/plugins/techdocs-backend', + ]; + + invalidRepoUrls.forEach(url => { + expect(isValidRepoUrlForMkdocs(url, 'github')).toBe(false); + }); + }); + + it('should return false for unsupported remote protocols', () => { + const validRepoUrl = 'https://github.com/backstage/backstage'; + + const unsupportedRemoteProtocols = ['dir', 'file', 'url']; + + unsupportedRemoteProtocols.forEach(targetType => { + expect( + isValidRepoUrlForMkdocs(validRepoUrl, targetType as RemoteProtocol), + ).toBe(false); + }); + }); + }); + + describe('getRepoUrlFromLocationAnnotation', () => { + it('should return undefined for unsupported location type', () => { + const parsedLocationAnnotation1: ParsedLocationAnnotation = { + type: 'dir', + target: '/home/user/workspace/docs-repository', + }; + + const parsedLocationAnnotation2: ParsedLocationAnnotation = { + type: 'file', + target: '/home/user/workspace/docs-repository/catalog-info.yaml', + }; + + const parsedLocationAnnotation3: ParsedLocationAnnotation = { + type: 'url', + target: 'https://my-website.com/storage/this/docs/repository', + }; + + expect(getRepoUrlFromLocationAnnotation(parsedLocationAnnotation1)).toBe( + undefined, + ); + expect(getRepoUrlFromLocationAnnotation(parsedLocationAnnotation2)).toBe( + undefined, + ); + + expect(getRepoUrlFromLocationAnnotation(parsedLocationAnnotation3)).toBe( + undefined, + ); + }); + + it('should return correct target url for supported hosts', () => { + const parsedLocationAnnotation1: ParsedLocationAnnotation = { + type: 'github', + target: 'https://github.com/backstage/backstage.git', + }; + + expect(getRepoUrlFromLocationAnnotation(parsedLocationAnnotation1)).toBe( + 'https://github.com/backstage/backstage', + ); + + const parsedLocationAnnotation2: ParsedLocationAnnotation = { + type: 'github', + target: 'https://github.com/org/repo', + }; + + expect(getRepoUrlFromLocationAnnotation(parsedLocationAnnotation2)).toBe( + 'https://github.com/org/repo', + ); + + const parsedLocationAnnotation3: ParsedLocationAnnotation = { + type: 'gitlab', + target: 'https://gitlab.com/org/repo', + }; + + expect(getRepoUrlFromLocationAnnotation(parsedLocationAnnotation3)).toBe( + 'https://gitlab.com/org/repo', + ); + + const parsedLocationAnnotation4: ParsedLocationAnnotation = { + type: 'github', + target: + 'github.com/backstage/backstage/blob/master/plugins/techdocs-backend/examples/documented-component', + }; + + expect(getRepoUrlFromLocationAnnotation(parsedLocationAnnotation4)).toBe( + 'github.com/backstage/backstage/blob/master/plugins/techdocs-backend/examples/documented-component', + ); + }); + }); + + describe('pathMkdocsPreBuild', () => { + beforeEach(() => { + mockFs({ + '/mkdocs.yml': mkdocsYml, + '/mkdocs_with_repo_url.yml': mkdocsYmlWithRepoUrl, + }); + }); + + afterEach(() => { + mockFs.restore(); + }); + + it('should add repo_url to mkdocs.yml', () => { + const parsedLocationAnnotation: ParsedLocationAnnotation = { + type: 'github', + target: 'https://github.com/backstage/backstage', + }; + + patchMkdocsYmlPreBuild( + '/mkdocs.yml', + mockLogger, + parsedLocationAnnotation, + ); + + const updatedMkdocsYml = fs.readFileSync('/mkdocs.yml').toString(); + + expect(updatedMkdocsYml).toContain( + "repo_url: 'https://github.com/backstage/backstage'", + ); + }); + + it('should not override existing repo_url in mkdocs.yml', () => { + const parsedLocationAnnotation: ParsedLocationAnnotation = { + type: 'github', + target: 'https://github.com/neworg/newrepo', + }; + + patchMkdocsYmlPreBuild( + '/mkdocs_with_repo_url.yml', + mockLogger, + parsedLocationAnnotation, + ); + + const updatedMkdocsYml = fs + .readFileSync('/mkdocs_with_repo_url.yml') + .toString(); + + expect(updatedMkdocsYml).toContain( + "repo_url: 'https://github.com/backstage/backstage'", + ); + expect(updatedMkdocsYml).not.toContain( + "repo_url: 'https://github.com/neworg/newrepo'", + ); + }); + }); }); diff --git a/plugins/techdocs-backend/src/techdocs/stages/generate/helpers.ts b/plugins/techdocs-backend/src/techdocs/stages/generate/helpers.ts index d0ffa72f1c..254186b484 100644 --- a/plugins/techdocs-backend/src/techdocs/stages/generate/helpers.ts +++ b/plugins/techdocs-backend/src/techdocs/stages/generate/helpers.ts @@ -14,11 +14,16 @@ * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; +import fs from 'fs'; +import { spawn } from 'child_process'; import { Writable, PassThrough } from 'stream'; import Docker from 'dockerode'; +import yaml from 'js-yaml'; +import { Logger } from 'winston'; +import { Entity } from '@backstage/catalog-model'; import { SupportedGeneratorKey } from './types'; -import { spawn } from 'child_process'; +import { ParsedLocationAnnotation } from '../../../helpers'; +import { RemoteProtocol } from '../prepare/types'; // TODO: Implement proper support for more generators. export function getGeneratorKey(entity: Entity): SupportedGeneratorKey { @@ -142,3 +147,131 @@ export const runCommand = async ({ }); }); }; + +/** + * Return true if mkdocs can compile docs with provided repo_url + * + * Valid repo_url examples in mkdocs.yml + * - https://github.com/backstage/backstage + * - https://gitlab.com/org/repo/ + * - http://github.com/backstage/backstage + * - A http(s) protocol URL to the root of the repository + * + * Invalid repo_url examples in mkdocs.yml + * - https://github.com/backstage/backstage/blob/master/plugins/techdocs-backend/examples/documented-component + * - (anything that is not valid as described above) + * + * @param {string} repoUrl URL supposed to be used as repo_url in mkdocs.yml + * @param {RemoteProtocol} locationType Type of source code host - github, gitlab, dir, url, etc. + * @returns {boolean} + */ +export const isValidRepoUrlForMkdocs = ( + repoUrl: string, + locationType: RemoteProtocol, +): boolean => { + // Trim trailing slash + const cleanRepoUrl = repoUrl.replace(/\/$/, ''); + + if (locationType === 'github' || locationType === 'gitlab') { + // A valid repoUrl to the root of the repository will be split into 5 strings if split using the / delimiter. + // We do not want URLs which have more than that number of forward slashes since they will signify a non-root location + // Note: This is not the best possible implementation but will work most of the times.. Feel free to improve or + // highlight edge cases. + return cleanRepoUrl.split('/').length === 5; + } + + return false; +}; + +/** + * Return a valid URL of the repository used in backstage.io/techdocs-ref annotation. + * Return undefined if the `target` is not valid in context of repo_url in mkdocs.yml + * Alter URL so that it is a valid repo_url config in mkdocs.yml + * + * @param {ParsedLocationAnnotation} parsedLocationAnnotation Object with location url and type + * @returns {string | undefined} + */ +export const getRepoUrlFromLocationAnnotation = ( + parsedLocationAnnotation: ParsedLocationAnnotation, +): string | undefined => { + const { type: locationType, target } = parsedLocationAnnotation; + + // Add more options from the RemoteProtocol type of parsedLocationAnnotation.type here + // when TechDocs supports more hosts and if mkdocs can generated an Edit URL for them. + const supportedHosts = ['github', 'gitlab']; + + if (supportedHosts.includes(locationType)) { + // Trim .git or .git/ from the end of repository url + return target.replace(/.git\/*$/, ''); + } + + return undefined; +}; + +/** + * Update the mkdocs.yml file before TechDocs generator uses it to build docs site. + * + * List of tasks: + * - Add repo_url if it does not exists + * If mkdocs.yml has a repo_url, the generated docs site gets an Edit button on the pages by default. + * If repo_url is missing in mkdocs.yml, we will use techdocs annotation of the entity to possibly get + * the repository URL. + * + * This function will not throw an error since this is not critical to the whole TechDocs pipeline. + * Instead it will log warnings if there are any errors in reading, parsing or writing YAML. + * + * @param {string} mkdocsYmlPath Absolute path to mkdocs.yml or equivalent of a docs site + * @param {Logger} logger + * @param {ParsedLocationAnnotation} parsedLocationAnnotation Object with location url and type + */ +export const patchMkdocsYmlPreBuild = async ( + mkdocsYmlPath: string, + logger: Logger, + parsedLocationAnnotation: ParsedLocationAnnotation, +) => { + let mkdocsYmlFileString; + try { + mkdocsYmlFileString = fs.readFileSync(mkdocsYmlPath, 'utf8'); + } catch (error) { + logger.warn( + `Could not read file ${mkdocsYmlPath} before running the generator. ${error.message}`, + ); + return; + } + + let mkdocsYml: any; + try { + mkdocsYml = yaml.safeLoad(mkdocsYmlFileString); + + // mkdocsYml should be an object type after successful parsing. + // But based on its type definition, it can also be a string or undefined, which we don't want. + if (typeof mkdocsYml === 'string' || typeof mkdocsYml === 'undefined') { + throw new Error('Bad YAML format.'); + } + } catch (error) { + logger.warn( + `Error in parsing YAML at ${mkdocsYmlPath} before running the generator. ${error.message}`, + ); + return; + } + + // Add repo_url to mkdocs.yml if it is missing. This will enable the Page edit button generated by MkDocs. + if (!('repo_url' in mkdocsYml)) { + const repoUrl = getRepoUrlFromLocationAnnotation(parsedLocationAnnotation); + if (repoUrl !== undefined) { + // mkdocs.yml will not build with invalid repo_url. So, make sure it is valid. + if (isValidRepoUrlForMkdocs(repoUrl, parsedLocationAnnotation.type)) { + mkdocsYml.repo_url = repoUrl; + } + } + } + + try { + fs.writeFileSync(mkdocsYmlPath, yaml.safeDump(mkdocsYml), 'utf8'); + } catch (error) { + logger.warn( + `Could not write to ${mkdocsYmlPath} after updating it before running the generator. ${error.message}`, + ); + return; + } +}; diff --git a/plugins/techdocs-backend/src/techdocs/stages/generate/techdocs.ts b/plugins/techdocs-backend/src/techdocs/stages/generate/techdocs.ts index 1160ff1ade..faefd8ac16 100644 --- a/plugins/techdocs-backend/src/techdocs/stages/generate/techdocs.ts +++ b/plugins/techdocs-backend/src/techdocs/stages/generate/techdocs.ts @@ -26,7 +26,11 @@ import { GeneratorRunOptions, GeneratorRunResult, } from './types'; -import { runDockerContainer, runCommand } from './helpers'; +import { + runDockerContainer, + runCommand, + patchMkdocsYmlPreBuild, +} from './helpers'; type TechdocsGeneratorOptions = { // This option enables users to configure if they want to use TechDocs container @@ -62,6 +66,7 @@ export class TechdocsGenerator implements GeneratorBase { public async run({ directory, dockerClient, + parsedLocationAnnotation, }: GeneratorRunOptions): Promise { const tmpdirPath = os.tmpdir(); // Fixes a problem with macOS returning a path that is a symlink @@ -71,6 +76,15 @@ export class TechdocsGenerator implements GeneratorBase { ); const [log, logStream] = createStream(); + // TODO: In future mkdocs.yml can be mkdocs.yaml. So, use a config variable here to find out + // the correct file name. + // Do some updates to mkdocs.yml before generating docs e.g. adding repo_url + await patchMkdocsYmlPreBuild( + path.join(directory, 'mkdocs.yml'), + this.logger, + parsedLocationAnnotation, + ); + try { switch (this.options.runGeneratorIn) { case 'local': diff --git a/plugins/techdocs-backend/src/techdocs/stages/generate/types.ts b/plugins/techdocs-backend/src/techdocs/stages/generate/types.ts index 398e4dcdd0..6d9ce5afca 100644 --- a/plugins/techdocs-backend/src/techdocs/stages/generate/types.ts +++ b/plugins/techdocs-backend/src/techdocs/stages/generate/types.ts @@ -13,9 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import type { Writable } from 'stream'; +import { Writable } from 'stream'; import Docker from 'dockerode'; import { Entity } from '@backstage/catalog-model'; +import { ParsedLocationAnnotation } from '../../../helpers'; /** * The returned directory from the generator which is ready @@ -26,14 +27,18 @@ export type GeneratorRunResult = { }; /** - * The values that the generator will receive. The directory of the - * uncompiled documentation, with the values from the frontend. A dedicated log stream and a docker - * client to run any generator on top of your directory. + * The values that the generator will receive. + * + * @param {string} directory The directory of the uncompiled documentation, with the values from the frontend + * @param {Docker} dockerClient A docker client to run any generator on top of your directory + * @param {ParsedLocationAnnotation} parsedLocationAnnotation backstage.io/techdocs-ref annotation of an entity + * @param {Writable} [logStream] A dedicated log stream */ export type GeneratorRunOptions = { directory: string; - logStream?: Writable; dockerClient: Docker; + parsedLocationAnnotation: ParsedLocationAnnotation; + logStream?: Writable; }; export type GeneratorBase = { From 202da8ab36ed6942c10db994031329ec0a4f5e77 Mon Sep 17 00:00:00 2001 From: Govind Date: Thu, 26 Nov 2020 11:27:00 +0100 Subject: [PATCH 255/430] Allow application to register custom `AuthProviderFactory`. Refs #1984 (#3329) * Allow application to register custom `AuthProviderFactory`. Refs #1984 * PR comments: Explicit exports * Export `OAuth` specific types from the package * Update documentation on how to use the interfaces * Re-export from base `index.ts` as per ADR-004 * pass `providerId` as a parameter to `AuthFactory` instead of hardcoding Co-authored-by: Govindarajan Nagarajan --- docs/auth/auth-backend-classes.md | 118 +++++++++++++----- plugins/auth-backend/src/index.ts | 8 ++ plugins/auth-backend/src/lib/flow/index.ts | 2 + .../src/providers/auth0/provider.ts | 2 +- .../auth-backend/src/providers/factories.ts | 16 +-- .../src/providers/github/provider.ts | 2 +- .../src/providers/gitlab/provider.ts | 2 +- .../src/providers/google/provider.ts | 2 +- plugins/auth-backend/src/providers/index.ts | 14 ++- .../src/providers/microsoft/provider.ts | 3 +- .../src/providers/oauth2/provider.ts | 2 +- .../src/providers/oidc/provider.ts | 2 +- .../src/providers/okta/provider.ts | 2 +- .../src/providers/onelogin/provider.ts | 2 +- .../src/providers/saml/provider.ts | 2 +- plugins/auth-backend/src/providers/types.ts | 1 + plugins/auth-backend/src/service/router.ts | 29 ++++- 17 files changed, 148 insertions(+), 61 deletions(-) diff --git a/docs/auth/auth-backend-classes.md b/docs/auth/auth-backend-classes.md index 442553d4b8..ae6602e62a 100644 --- a/docs/auth/auth-backend-classes.md +++ b/docs/auth/auth-backend-classes.md @@ -6,45 +6,59 @@ description: Documentation on Auth backend classes ## How Does Authentication Work? -The Backstage application can use various authentication providers for -authentication. A provider has to implement an `AuthProviderRouteHandlers` -interface for handling authentication. This interface consists of four methods. -Each of these methods is hosted at an endpoint `/auth/[provider]/method`, where -`method` performs a certain operation as follows: +The Backstage application can use various external authentication providers for +authentication. An external provider is wrapped using an +`AuthProviderRouteHandlers` interface for handling authentication. This +interface consists of four methods. Each of these methods is hosted at an +endpoint (by default)`/api/auth/[provider]/method`, where `method` performs a +certain operation as follows: ``` - /auth/[provider]/start -> start - /auth/[provider]/handler/frame -> frameHandler - /auth/[provider]/refresh -> refresh - /auth/[provider]/logout -> logout + /auth/[provider]/start -> Initiate a login from the web page + /auth/[provider]/handler/frame -> Handle a `finished` authentication operation + /auth/[provider]/refresh -> refresh -> Refresh the validity of a login + /auth/[provider]/logout -> logout a logged-in user ``` -For more information on how these methods are used and for which purpose, refer -to the [OAuth documentation](oauth.md). +The flow is as follows: -For details on the parameters, input and output conditions for each method, -refer to the type documentation under -`plugins/auth-backend/src/providers/types.ts`. +1. `User` attempts to sign-in. +2. The `auth` wrapper re-directs the user to an external authenticator. +3. The authenticator validates the user and returns the result of the validation + (success OR failure), to the wrapper's endpoint (`handler/frame`) +4. `handler/frame` will issue the appropriate response to the webpage. +5. User signs out by clicking on an UI interface and the webpage makes a request + to logout the user. There are currently two different classes for two authentication mechanisms that implement this interface: an `OAuthAdapter` for [OAuth](https://oauth.net/2/) based mechanisms and a `SAMLAuthProvider` for [SAML](http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0.html) + +If you do not have a `OAuth2` or a `SAML` based authentication provider, look in +the section [below](#Implementing-Your-Own-Auth-Wrapper) + based mechanisms. ### OAuth mechanisms +For more information on how these methods are used and for which purpose, refer +to the [OAuth documentation](oauth.md). + Currently OAuth is assumed to be the de facto authentication mechanism for Backstage based applications. Backstage comes with a "batteries-included" set of supported commonly used OAuth -providers: Okta, GitHub, Google, GitLab, and a generic OAuth2 provider. +providers: Okta, GitHub, Google, GitLab, and a generic OAuth2 provider. For a +list of available providers, look at the available wrappers in +`backstage/plugins/auth-backend/src/providers/` -All of these use the authorization flow of OAuth2 to implement authentication. +All of these use the **authorization flow** of OAuth2 to implement +authentication. -If your authentication provider is any of the above mentioned (except generic -OAuth2) providers, you can configure them by setting the right variables in -`app-config.yaml` under the `auth` section. +If your authentication provider is any of the above mentioned providers, you can +configure them by setting the right variables in `app-config.yaml` under the +`auth` section. ### Configuration @@ -85,18 +99,59 @@ auth: ... ``` -## Technical Notes +## Implementing Your Own Auth Wrapper -### OAuthEnvironmentHandler +The core interface of any Auth wrapper is the `AuthProviderRouteHandlers` +interface. This interface has 4 methods corresponding to API described in the +initial section. Any Auth Wrapper will have to implement this interface. -The concept of an "env" is core to the way the auth backend works. It uses an +When initiating a login, a pop-up window is created by the frontend, to allow +the user to initiate a login. This login request is done to the `/start` +endpoint which is handled by the `start` method. + +The `start` method re-directs to the external auth provider who authenticates +the request and re-directs the request to the `/frame/handler` endpoint, which +is handled by the `frameHandler` method. + +The `frameHandler` returns a `html` response, containing a script that does a +`postMessage` to the frontend's window containing the result of the request. The +`WebMessageResponse` type is the message sent by the `postMessage` to the +frontend. + +A `postMessageResponse` utility function wraps the logic of generating a +`postMessage` response that ensures that `CORS` is successfully handled. This +function takes an `express.Response`, a `WebMessageResponse` and the URL of the +frontend (`appOrigin`) as parameters and return a `HTML` page with the script +and the message + +### OAuth wrapping Interfaces. + +Each OAuth external provider is supported by a corresponding `Passport` +strategy. For a generic OAuth2 provider, passport has a `passport-oauth2` +strategy. The strategy class handles the implementation details of working with +each provider. + +Each strategy is wrapped by a `OAuthHandlers` interface. + +This interface cannot be directly used as an `express` HTTP Request handler. + +To do so, `OAuthHandlers` are wrapped in a `OAuthAdapter`, which implements the +`AuthProviderRouterHandlers` interface. + +#### Env + +The concept of an `env` is core to the way the auth backend works. It uses an `env` query parameter to identify the environment in which the application is -running (`development`, `staging`, `production`, etc). Each runtime can support -multiple environments at the same time and the right handler for each request is -identified and dispatched to based on the `env` parameter. All -`AuthProviderRouteHandlers` are wrapped within an `OAuthEnvironmentHandler`. +running (`development`, `staging`, `production`, etc). Each runtime can +simultaneously support multiple environments at the same time and the right +handler for each request is identified and dispatched to, based on the `env` +parameter. -To instantiate multiple OAuth providers for different environments, use +`OAuthEnvironmentHandler` is a utility wrapper for an `OAuthHandlers` that +implements the `AuthProviderRouteHandlers` interface while supporting multiple +`env`s. + +To instantiate OAuth providers (the same but for different environments), use `OAuthEnvironmentHandler.mapConfig`. It's a helper to iterate over a configuration object that is a map of environment to configurations. See one of the existing OAuth providers for an example of how it is used. @@ -116,8 +171,13 @@ The `OAuthEnvironmentHandler.mapConfig(config, envConfig => ...)` call will split the `config` by the top level `development` and `production` keys, and pass on each block as `envConfig`. -For a list of currently available providers, look in the `factories` module -located in `plugins/auth-backend/src/providers/factories.ts` +For convenience, the `AuthProviderFactory` is a factory function that has to be +implemented which can then generate a `AuthProviderRouteHandlers` for a given +`provider`. + +All of the supported providers provide a `AuthProviderFactory` that returns a +`OAuthEnvironmentHandler` ,capable of handling authentication for multiple +`env`s. ### OAuth2 provider diff --git a/plugins/auth-backend/src/index.ts b/plugins/auth-backend/src/index.ts index 7612c392a2..3d3f059dcc 100644 --- a/plugins/auth-backend/src/index.ts +++ b/plugins/auth-backend/src/index.ts @@ -15,3 +15,11 @@ */ export * from './service/router'; +export * from './providers'; + +// flow package provides 2 functions +// ensuresXRequestedWith and postMessageResponse to safely handle CORS requests for login. The WebMessageResponse type in flow is used to type the response from the login-popup +export * from './lib/flow'; + +// OAuth wrapper over a passport or a custom `startegy`. +export * from './lib/oauth'; diff --git a/plugins/auth-backend/src/lib/flow/index.ts b/plugins/auth-backend/src/lib/flow/index.ts index a5f2f7a3ac..7f0627994f 100644 --- a/plugins/auth-backend/src/lib/flow/index.ts +++ b/plugins/auth-backend/src/lib/flow/index.ts @@ -15,3 +15,5 @@ */ export { ensuresXRequestedWith, postMessageResponse } from './authFlowHelpers'; + +export type { WebMessageResponse } from './types'; diff --git a/plugins/auth-backend/src/providers/auth0/provider.ts b/plugins/auth-backend/src/providers/auth0/provider.ts index f851fb1eb5..8772842e2a 100644 --- a/plugins/auth-backend/src/providers/auth0/provider.ts +++ b/plugins/auth-backend/src/providers/auth0/provider.ts @@ -149,12 +149,12 @@ export class Auth0AuthProvider implements OAuthHandlers { } export const createAuth0Provider: AuthProviderFactory = ({ + providerId, globalConfig, config, tokenIssuer, }) => OAuthEnvironmentHandler.mapConfig(config, envConfig => { - const providerId = 'auth0'; const clientId = envConfig.getString('clientId'); const clientSecret = envConfig.getString('clientSecret'); const domain = envConfig.getString('domain'); diff --git a/plugins/auth-backend/src/providers/factories.ts b/plugins/auth-backend/src/providers/factories.ts index d4a29c0959..7bad4f4d81 100644 --- a/plugins/auth-backend/src/providers/factories.ts +++ b/plugins/auth-backend/src/providers/factories.ts @@ -24,9 +24,9 @@ import { createSamlProvider } from './saml'; import { createAuth0Provider } from './auth0'; import { createMicrosoftProvider } from './microsoft'; import { createOneLoginProvider } from './onelogin'; -import { AuthProviderFactory, AuthProviderFactoryOptions } from './types'; +import { AuthProviderFactory } from './types'; -const factories: { [providerId: string]: AuthProviderFactory } = { +export const factories: { [providerId: string]: AuthProviderFactory } = { google: createGoogleProvider, github: createGithubProvider, gitlab: createGitlabProvider, @@ -38,15 +38,3 @@ const factories: { [providerId: string]: AuthProviderFactory } = { oidc: createOidcProvider, onelogin: createOneLoginProvider, }; - -export function createAuthProvider( - providerId: string, - options: AuthProviderFactoryOptions, -) { - const factory = factories[providerId]; - if (!factory) { - throw Error(`No auth provider available for '${providerId}'`); - } - - return factory(options); -} diff --git a/plugins/auth-backend/src/providers/github/provider.ts b/plugins/auth-backend/src/providers/github/provider.ts index bab7b3fc28..6e3f9a6000 100644 --- a/plugins/auth-backend/src/providers/github/provider.ts +++ b/plugins/auth-backend/src/providers/github/provider.ts @@ -137,12 +137,12 @@ export class GithubAuthProvider implements OAuthHandlers { } export const createGithubProvider: AuthProviderFactory = ({ + providerId, globalConfig, config, tokenIssuer, }) => OAuthEnvironmentHandler.mapConfig(config, envConfig => { - const providerId = 'github'; const clientId = envConfig.getString('clientId'); const clientSecret = envConfig.getString('clientSecret'); const enterpriseInstanceUrl = envConfig.getOptionalString( diff --git a/plugins/auth-backend/src/providers/gitlab/provider.ts b/plugins/auth-backend/src/providers/gitlab/provider.ts index 4d4ecc3b56..df6d2daeb7 100644 --- a/plugins/auth-backend/src/providers/gitlab/provider.ts +++ b/plugins/auth-backend/src/providers/gitlab/provider.ts @@ -140,12 +140,12 @@ export class GitlabAuthProvider implements OAuthHandlers { } export const createGitlabProvider: AuthProviderFactory = ({ + providerId, globalConfig, config, tokenIssuer, }) => OAuthEnvironmentHandler.mapConfig(config, envConfig => { - const providerId = 'gitlab'; const clientId = envConfig.getString('clientId'); const clientSecret = envConfig.getString('clientSecret'); const audience = envConfig.getString('audience'); diff --git a/plugins/auth-backend/src/providers/google/provider.ts b/plugins/auth-backend/src/providers/google/provider.ts index dd5b51eb01..ae4f5fabd0 100644 --- a/plugins/auth-backend/src/providers/google/provider.ts +++ b/plugins/auth-backend/src/providers/google/provider.ts @@ -175,6 +175,7 @@ export class GoogleAuthProvider implements OAuthHandlers { } export const createGoogleProvider: AuthProviderFactory = ({ + providerId, globalConfig, config, logger, @@ -182,7 +183,6 @@ export const createGoogleProvider: AuthProviderFactory = ({ catalogApi, }) => OAuthEnvironmentHandler.mapConfig(config, envConfig => { - const providerId = 'google'; const clientId = envConfig.getString('clientId'); const clientSecret = envConfig.getString('clientSecret'); const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`; diff --git a/plugins/auth-backend/src/providers/index.ts b/plugins/auth-backend/src/providers/index.ts index 99348438be..3f3d16f28f 100644 --- a/plugins/auth-backend/src/providers/index.ts +++ b/plugins/auth-backend/src/providers/index.ts @@ -14,4 +14,16 @@ * limitations under the License. */ -export { createAuthProvider } from './factories'; +export { factories as defaultAuthProviderFactories } from './factories'; + +// Export the minimal interface required for implementing a +// custom Authorization Handler +export type { + AuthProviderRouteHandlers, + AuthProviderFactoryOptions, + AuthProviderFactory, +} from './types'; + +// These types are needed for a postMessage from the login pop-up +// to the frontend +export type { AuthResponse, BackstageIdentity, ProfileInfo } from './types'; diff --git a/plugins/auth-backend/src/providers/microsoft/provider.ts b/plugins/auth-backend/src/providers/microsoft/provider.ts index a09d5871d2..b0c3635be8 100644 --- a/plugins/auth-backend/src/providers/microsoft/provider.ts +++ b/plugins/auth-backend/src/providers/microsoft/provider.ts @@ -206,13 +206,12 @@ export class MicrosoftAuthProvider implements OAuthHandlers { } export const createMicrosoftProvider: AuthProviderFactory = ({ + providerId, globalConfig, config, tokenIssuer, }) => OAuthEnvironmentHandler.mapConfig(config, envConfig => { - const providerId = 'microsoft'; - const clientId = envConfig.getString('clientId'); const clientSecret = envConfig.getString('clientSecret'); const tenantID = envConfig.getString('tenantId'); diff --git a/plugins/auth-backend/src/providers/oauth2/provider.ts b/plugins/auth-backend/src/providers/oauth2/provider.ts index 6342d556e1..04cb7670f3 100644 --- a/plugins/auth-backend/src/providers/oauth2/provider.ts +++ b/plugins/auth-backend/src/providers/oauth2/provider.ts @@ -157,12 +157,12 @@ export class OAuth2AuthProvider implements OAuthHandlers { } export const createOAuth2Provider: AuthProviderFactory = ({ + providerId, globalConfig, config, tokenIssuer, }) => OAuthEnvironmentHandler.mapConfig(config, envConfig => { - const providerId = 'oauth2'; const clientId = envConfig.getString('clientId'); const clientSecret = envConfig.getString('clientSecret'); const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`; diff --git a/plugins/auth-backend/src/providers/oidc/provider.ts b/plugins/auth-backend/src/providers/oidc/provider.ts index fbf38c69c4..9e44a82c37 100644 --- a/plugins/auth-backend/src/providers/oidc/provider.ts +++ b/plugins/auth-backend/src/providers/oidc/provider.ts @@ -171,12 +171,12 @@ export class OidcAuthProvider implements OAuthHandlers { } export const createOidcProvider: AuthProviderFactory = ({ + providerId, globalConfig, config, tokenIssuer, }) => OAuthEnvironmentHandler.mapConfig(config, envConfig => { - const providerId = 'oidc'; const clientId = envConfig.getString('clientId'); const clientSecret = envConfig.getString('clientSecret'); const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`; diff --git a/plugins/auth-backend/src/providers/okta/provider.ts b/plugins/auth-backend/src/providers/okta/provider.ts index 09597696ba..84b258e489 100644 --- a/plugins/auth-backend/src/providers/okta/provider.ts +++ b/plugins/auth-backend/src/providers/okta/provider.ts @@ -168,12 +168,12 @@ export class OktaAuthProvider implements OAuthHandlers { } export const createOktaProvider: AuthProviderFactory = ({ + providerId, globalConfig, config, tokenIssuer, }) => OAuthEnvironmentHandler.mapConfig(config, envConfig => { - const providerId = 'okta'; const clientId = envConfig.getString('clientId'); const clientSecret = envConfig.getString('clientSecret'); const audience = envConfig.getString('audience'); diff --git a/plugins/auth-backend/src/providers/onelogin/provider.ts b/plugins/auth-backend/src/providers/onelogin/provider.ts index 97cf182e5a..838b655d59 100644 --- a/plugins/auth-backend/src/providers/onelogin/provider.ts +++ b/plugins/auth-backend/src/providers/onelogin/provider.ts @@ -147,12 +147,12 @@ export class OneLoginProvider implements OAuthHandlers { } export const createOneLoginProvider: AuthProviderFactory = ({ + providerId, globalConfig, config, tokenIssuer, }) => OAuthEnvironmentHandler.mapConfig(config, envConfig => { - const providerId = 'onelogin'; const clientId = envConfig.getString('clientId'); const clientSecret = envConfig.getString('clientSecret'); const issuer = envConfig.getString('issuer'); diff --git a/plugins/auth-backend/src/providers/saml/provider.ts b/plugins/auth-backend/src/providers/saml/provider.ts index a8edc9a714..7b663a5528 100644 --- a/plugins/auth-backend/src/providers/saml/provider.ts +++ b/plugins/auth-backend/src/providers/saml/provider.ts @@ -121,12 +121,12 @@ type SAMLProviderOptions = { }; export const createSamlProvider: AuthProviderFactory = ({ + providerId, globalConfig, config, tokenIssuer, }) => { const url = new URL(globalConfig.baseUrl); - const providerId = 'saml'; const entryPoint = config.getString('entryPoint'); const issuer = config.getString('issuer'); const opts = { diff --git a/plugins/auth-backend/src/providers/types.ts b/plugins/auth-backend/src/providers/types.ts index 0e500e027e..a40f2a96ee 100644 --- a/plugins/auth-backend/src/providers/types.ts +++ b/plugins/auth-backend/src/providers/types.ts @@ -113,6 +113,7 @@ export interface AuthProviderRouteHandlers { } export type AuthProviderFactoryOptions = { + providerId: string; globalConfig: AuthProviderConfig; config: Config; logger: Logger; diff --git a/plugins/auth-backend/src/service/router.ts b/plugins/auth-backend/src/service/router.ts index 47e0d14caa..85d60661e9 100644 --- a/plugins/auth-backend/src/service/router.ts +++ b/plugins/auth-backend/src/service/router.ts @@ -14,6 +14,14 @@ * limitations under the License. */ +import express from 'express'; +import Router from 'express-promise-router'; +import cookieParser from 'cookie-parser'; +import { Logger } from 'winston'; +import { + defaultAuthProviderFactories, + AuthProviderFactory, +} from '../providers'; import { NotFoundError, PluginDatabaseManager, @@ -21,20 +29,18 @@ import { } from '@backstage/backend-common'; import { CatalogClient } from '@backstage/catalog-client'; import { Config } from '@backstage/config'; -import cookieParser from 'cookie-parser'; -import express from 'express'; -import Router from 'express-promise-router'; -import { Logger } from 'winston'; import { createOidcRouter, DatabaseKeyStore, TokenFactory } from '../identity'; -import { createAuthProvider } from '../providers'; import session from 'express-session'; import passport from 'passport'; +type ProviderFactories = { [s: string]: AuthProviderFactory }; + export interface RouterOptions { logger: Logger; database: PluginDatabaseManager; config: Config; discovery: PluginEndpointDiscovery; + providerFactories?: ProviderFactories; } export async function createRouter({ @@ -42,6 +48,7 @@ export async function createRouter({ config, discovery, database, + providerFactories, }: RouterOptions): Promise { const router = Router(); @@ -74,13 +81,23 @@ export async function createRouter({ router.use(express.urlencoded({ extended: false })); router.use(express.json()); + const allProviderFactories = { + ...defaultAuthProviderFactories, + ...providerFactories, + }; const providersConfig = config.getConfig('auth.providers'); const providers = providersConfig.keys(); for (const providerId of providers) { logger.info(`Configuring provider, ${providerId}`); try { - const provider = createAuthProvider(providerId, { + const providerFactory = allProviderFactories[providerId]; + if (!providerFactory) { + throw Error(`No auth provider available for '${providerId}'`); + } + + const provider = providerFactory({ + providerId, globalConfig: { baseUrl: authUrl, appUrl }, config: providersConfig.getConfig(providerId), logger, From 01ea1cc6ccb65c57bff206a2c4e616f63897014a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20=C3=85lund?= Date: Thu, 26 Nov 2020 11:27:51 +0100 Subject: [PATCH 256/430] Clarify options in Governance (#3455) --- GOVERNANCE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 65841d1c6a..0ab8d8390d 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -1,6 +1,6 @@ # Process for becoming a maintainer -## Your organization is not yet a maintainer +## a) Your organization is not yet a maintainer - Express interest to the sponsors that your organization is interested in becoming a maintainer. Becoming a maintainer generally means that you are going to be spending substantial time on Backstage for the foreseeable future. You should have domain expertise and be extremely proficient in TypeScript. - We will expect you to start contributing increasingly complicated PRs, under the guidance of the existing maintainers. @@ -8,7 +8,7 @@ - As you gain experience with the code base and our standards, we will ask you to do code reviews for incoming PRs. - After a period of approximately 2-3 months of working together and making sure we see eye to eye, the existing sponsors and maintainers will confer and decide whether to grant maintainer status or not. We make no guarantees on the length of time this will take, but 2-3 months is the approximate goal. -## Your organization is currently a maintainer +## b) Your organization is currently a maintainer To become a maintainer you need to demonstrate the following: From 0dedb26e10e4972951c1c7d9c6cdf72c07bb086e Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Thu, 26 Nov 2020 11:54:20 +0100 Subject: [PATCH 257/430] Revert catalog-backend version to 0.2.2 --- packages/backend/package.json | 2 +- plugins/catalog-backend/package.json | 2 +- plugins/catalog-import/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/backend/package.json b/packages/backend/package.json index b9daf224f0..5d3678a284 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -23,7 +23,7 @@ "@backstage/config": "^0.1.1", "@backstage/plugin-app-backend": "^0.3.0", "@backstage/plugin-auth-backend": "^0.2.3", - "@backstage/plugin-catalog-backend": "^0.3.0", + "@backstage/plugin-catalog-backend": "^0.2.2", "@backstage/plugin-graphql-backend": "^0.1.3", "@backstage/plugin-kubernetes-backend": "^0.2.0", "@backstage/plugin-proxy-backend": "^0.2.1", diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index d3c77db6dc..26f7180d43 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend", - "version": "0.3.0", + "version": "0.2.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index 2a44d7555a..8efa765224 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -24,7 +24,7 @@ "@backstage/catalog-model": "^0.2.0", "@backstage/core": "^0.3.2", "@backstage/plugin-catalog": "^0.2.0", - "@backstage/plugin-catalog-backend": "^0.3.0", + "@backstage/plugin-catalog-backend": "^0.2.2", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", From 3660463e2a5120d4a225b44a7ef64788d89305d8 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 26 Nov 2020 13:00:42 +0100 Subject: [PATCH 258/430] chore: use path.join to fix master build on windows --- packages/config-loader/src/lib/schema/collect.test.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/config-loader/src/lib/schema/collect.test.ts b/packages/config-loader/src/lib/schema/collect.test.ts index 7aca4f7c0c..2427ab994d 100644 --- a/packages/config-loader/src/lib/schema/collect.test.ts +++ b/packages/config-loader/src/lib/schema/collect.test.ts @@ -16,6 +16,7 @@ import mockFs from 'mock-fs'; import { collectConfigSchemas } from './collect'; +import path from 'path'; const mockSchema = { type: 'object', @@ -56,7 +57,7 @@ describe('collectConfigSchemas', () => { await expect(collectConfigSchemas(['a'])).resolves.toEqual([ { - path: 'node_modules/a/package.json', + path: path.join('node_modules', 'a', 'package.json'), value: mockSchema, }, ]); @@ -163,15 +164,15 @@ describe('collectConfigSchemas', () => { await expect(collectConfigSchemas(['a', 'b', 'c'])).resolves.toEqual([ { - path: 'node_modules/a/package.json', + path: path.join('node_modules', 'a', 'package.json'), value: { ...mockSchema, title: 'inline' }, }, { - path: 'node_modules/b/schema.json', + path: path.join('node_modules', 'b', 'schema.json'), value: { ...mockSchema, title: 'external' }, }, { - path: 'node_modules/c/schema.d.ts', + path: path.join('node_modules', 'c', 'schema.d.ts'), value: { $schema: 'http://json-schema.org/draft-07/schema#', type: 'object', From 50eff1d003b50c2982cb429595327545dc128062 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 26 Nov 2020 13:14:12 +0100 Subject: [PATCH 259/430] auth-backend: add changeset that was missing (plus minor docs tweaks) (#3458) --- .changeset/beige-mugs-visit.md | 5 ++ docs/auth/auth-backend-classes.md | 101 +++++++++++++++--------------- 2 files changed, 55 insertions(+), 51 deletions(-) create mode 100644 .changeset/beige-mugs-visit.md diff --git a/.changeset/beige-mugs-visit.md b/.changeset/beige-mugs-visit.md new file mode 100644 index 0000000000..4b5f0f1bdd --- /dev/null +++ b/.changeset/beige-mugs-visit.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': patch +--- + +Allow the backend to register custom AuthProviderFactories diff --git a/docs/auth/auth-backend-classes.md b/docs/auth/auth-backend-classes.md index ae6602e62a..8dbae0daa7 100644 --- a/docs/auth/auth-backend-classes.md +++ b/docs/auth/auth-backend-classes.md @@ -10,37 +10,38 @@ The Backstage application can use various external authentication providers for authentication. An external provider is wrapped using an `AuthProviderRouteHandlers` interface for handling authentication. This interface consists of four methods. Each of these methods is hosted at an -endpoint (by default)`/api/auth/[provider]/method`, where `method` performs a +endpoint (by default) `/api/auth/[provider]/method`, where `method` performs a certain operation as follows: ``` /auth/[provider]/start -> Initiate a login from the web page - /auth/[provider]/handler/frame -> Handle a `finished` authentication operation - /auth/[provider]/refresh -> refresh -> Refresh the validity of a login - /auth/[provider]/logout -> logout a logged-in user + /auth/[provider]/handler/frame -> Handle a finished authentication operation + /auth/[provider]/refresh -> Refresh the validity of a login + /auth/[provider]/logout -> Log out a logged-in user ``` The flow is as follows: -1. `User` attempts to sign-in. -2. The `auth` wrapper re-directs the user to an external authenticator. +1. A user attempts to sign in. +2. A popup window is opened, pointing to the `auth` endpoint. That endpoint does + initial preparations and then re-directs the user to an external + authenticator, still inside the popup. 3. The authenticator validates the user and returns the result of the validation - (success OR failure), to the wrapper's endpoint (`handler/frame`) -4. `handler/frame` will issue the appropriate response to the webpage. -5. User signs out by clicking on an UI interface and the webpage makes a request - to logout the user. + (success OR failure), to the wrapper's endpoint (`handler/frame`). +4. The `handler/frame` rendered b´webpage will issue the appropriate response to + the webpage that opened the popup window, and the popup is closed. +5. The user signs out by clicking on a UI interface and the webpage makes a + request to logout the user. There are currently two different classes for two authentication mechanisms that implement this interface: an `OAuthAdapter` for [OAuth](https://oauth.net/2/) based mechanisms and a `SAMLAuthProvider` for -[SAML](http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0.html) +[SAML](http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0.html). -If you do not have a `OAuth2` or a `SAML` based authentication provider, look in -the section [below](#Implementing-Your-Own-Auth-Wrapper) +If you do not have an `OAuth2` or `SAML` based authentication provider, look in +the section [below](#implementing-your-own-auth-wrapper). -based mechanisms. - -### OAuth mechanisms +### OAuth Mechanisms For more information on how these methods are used and for which purpose, refer to the [OAuth documentation](oauth.md). @@ -51,7 +52,7 @@ Backstage based applications. Backstage comes with a "batteries-included" set of supported commonly used OAuth providers: Okta, GitHub, Google, GitLab, and a generic OAuth2 provider. For a list of available providers, look at the available wrappers in -`backstage/plugins/auth-backend/src/providers/` +`backstage/plugins/auth-backend/src/providers/`. All of these use the **authorization flow** of OAuth2 to implement authentication. @@ -63,14 +64,13 @@ configure them by setting the right variables in `app-config.yaml` under the ### Configuration Each authentication provider (except SAML) needs five parameters: an OAuth -client ID, a client secret, an authorization endpoint and a token endpoint, and -an app origin. The app origin is the URL at which the frontend of the -application is hosted, and it is read from the `app.baseUrl` config. This is -required because the application opens a popup window to perform the -authentication, and once the flow is completed, the popup window sends a -`postMessage` to the frontend application to indicate the result of the -operation. Also this URL is used to verify that authentication requests are -coming from only this endpoint. +client ID, a client secret, an authorization endpoint, a token endpoint, and an +app origin. The app origin is the URL at which the frontend of the application +is hosted, and it is read from the `app.baseUrl` config. This is required +because the application opens a popup window to perform the authentication, and +once the flow is completed, the popup window sends a `postMessage` to the +frontend application to indicate the result of the operation. Also this URL is +used to verify that authentication requests are coming from only this endpoint. These values are configured via the `app-config.yaml` present in the root of your app folder. @@ -101,9 +101,9 @@ auth: ## Implementing Your Own Auth Wrapper -The core interface of any Auth wrapper is the `AuthProviderRouteHandlers` -interface. This interface has 4 methods corresponding to API described in the -initial section. Any Auth Wrapper will have to implement this interface. +The core interface of any auth wrapper is the `AuthProviderRouteHandlers` +interface. This interface has four methods corresponding to the API described in +the initial section. Any auth wrapper will have to implement this interface. When initiating a login, a pop-up window is created by the frontend, to allow the user to initiate a login. This login request is done to the `/start` @@ -113,29 +113,28 @@ The `start` method re-directs to the external auth provider who authenticates the request and re-directs the request to the `/frame/handler` endpoint, which is handled by the `frameHandler` method. -The `frameHandler` returns a `html` response, containing a script that does a -`postMessage` to the frontend's window containing the result of the request. The -`WebMessageResponse` type is the message sent by the `postMessage` to the +The `frameHandler` returns an HTML response, containing a script that does a +`postMessage` to the frontend's window, containing the result of the request. +The `WebMessageResponse` type is the message sent by the `postMessage` to the frontend. A `postMessageResponse` utility function wraps the logic of generating a -`postMessage` response that ensures that `CORS` is successfully handled. This +`postMessage` response that ensures that CORS is successfully handled. This function takes an `express.Response`, a `WebMessageResponse` and the URL of the -frontend (`appOrigin`) as parameters and return a `HTML` page with the script -and the message +frontend (`appOrigin`) as parameters and return an HTML page with the script and +the message. -### OAuth wrapping Interfaces. +### OAuth Wrapping Interfaces. -Each OAuth external provider is supported by a corresponding `Passport` -strategy. For a generic OAuth2 provider, passport has a `passport-oauth2` -strategy. The strategy class handles the implementation details of working with -each provider. +Each OAuth external provider is supported by a corresponding +[Passport](https://github.com/jaredhanson/passport) strategy. For a generic +OAuth2 provider, passport has a `passport-oauth2` strategy. The strategy class +handles the implementation details of working with each provider. -Each strategy is wrapped by a `OAuthHandlers` interface. +Each strategy is wrapped by an `OAuthHandlers` interface. -This interface cannot be directly used as an `express` HTTP Request handler. - -To do so, `OAuthHandlers` are wrapped in a `OAuthAdapter`, which implements the +This interface cannot be directly used as an Express HTTP request handler. To do +so, `OAuthHandlers` are wrapped in an `OAuthAdapter`, which implements the `AuthProviderRouterHandlers` interface. #### Env @@ -153,7 +152,7 @@ implements the `AuthProviderRouteHandlers` interface while supporting multiple To instantiate OAuth providers (the same but for different environments), use `OAuthEnvironmentHandler.mapConfig`. It's a helper to iterate over a -configuration object that is a map of environment to configurations. See one of +configuration object that is a map of environments to configurations. See one of the existing OAuth providers for an example of how it is used. Given the following configuration: @@ -168,18 +167,18 @@ production: ``` The `OAuthEnvironmentHandler.mapConfig(config, envConfig => ...)` call will -split the `config` by the top level `development` and `production` keys, and -pass on each block as `envConfig`. +split the config by the top level `development` and `production` keys, and pass +on each block as `envConfig`. For convenience, the `AuthProviderFactory` is a factory function that has to be implemented which can then generate a `AuthProviderRouteHandlers` for a given -`provider`. +provider. -All of the supported providers provide a `AuthProviderFactory` that returns a -`OAuthEnvironmentHandler` ,capable of handling authentication for multiple -`env`s. +All of the supported providers provide an `AuthProviderFactory` that returns an +`OAuthEnvironmentHandler`, capable of handling authentication for multiple +environments. -### OAuth2 provider +### OAuth2 Provider The `oauth2` provider abstracts a generic **OAuth2 + OIDC** based authentication provider. What this means is that after the application has been given From a00c393b38bcb794c241a98072fc850374c2ca5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20=C3=85lund?= Date: Thu, 26 Nov 2020 14:20:47 +0100 Subject: [PATCH 260/430] Add API docs plugin to new apps --- .../configure-app-with-plugins.md | 61 ++++++++++++++++++- .../default-app/packages/app/package.json.hbs | 1 - .../default-app/packages/app/src/plugins.ts | 1 - .../default-app/packages/app/src/sidebar.tsx | 2 + 4 files changed, 62 insertions(+), 3 deletions(-) diff --git a/docs/getting-started/configure-app-with-plugins.md b/docs/getting-started/configure-app-with-plugins.md index 1d02f147c1..8b071fb646 100644 --- a/docs/getting-started/configure-app-with-plugins.md +++ b/docs/getting-started/configure-app-with-plugins.md @@ -6,7 +6,66 @@ description: Documentation on How Configuring App with plugins ## Adding existing plugins to your app -Coming soon! +The following steps assumes you have created a new Backstage app and want to add +an existing plugin to it. We are using the +[CircleCI](https://github.com/backstage/backstage/blob/master/plugins/circleci/README.md) +in this example. + +0. Add to the repo: + +```bash +yarn add @backstage/plugin-circleci +``` + +1. Add plugin API to your Backstage instance: + +```js +// packages/app/src/api.ts +import { ApiHolder } from '@backstage/core'; +import { CircleCIApi, circleCIApiRef } from '@backstage/plugin-circleci'; + +const builder = ApiRegistry.builder(); +builder.add(circleCIApiRef, new CircleCIApi(/* optional custom url for your own CircleCI instance */)); + +export default builder.build() as ApiHolder; +``` + +2. Add plugin itself: + +```js +// packages/app/src/plugins.ts +export { plugin as Circleci } from '@backstage/plugin-circleci'; +``` + +3. Register the plugin router: + +```jsx +// packages/app/src/components/catalog/EntityPage.tsx + +import { Router as CircleCIRouter } from '@backstage/plugin-circleci'; + +// Then somewhere inside +} +/>; +``` + +Note that stand-alone plugins that are not "attached" to the Software Catalog +would be added outside the `EntityPage`. + +4. [Optional] Add proxy config: + +```yaml +// app-config.yaml +proxy: + '/circleci/api': + target: https://circleci.com/api/v1.1 + headers: + Circle-Token: + $env: CIRCLECI_AUTH_TOKEN +``` ### Adding a plugin page to the Sidebar diff --git a/packages/create-app/templates/default-app/packages/app/package.json.hbs b/packages/create-app/templates/default-app/packages/app/package.json.hbs index fd0d5cbf36..3f04773e31 100644 --- a/packages/create-app/templates/default-app/packages/app/package.json.hbs +++ b/packages/create-app/templates/default-app/packages/app/package.json.hbs @@ -15,7 +15,6 @@ "@backstage/plugin-techdocs": "^{{version '@backstage/plugin-techdocs'}}", "@backstage/catalog-model": "^{{version '@backstage/catalog-model'}}", "@backstage/plugin-circleci": "^{{version '@backstage/plugin-circleci'}}", - "@backstage/plugin-explore": "^{{version '@backstage/plugin-explore'}}", "@backstage/plugin-lighthouse": "^{{version '@backstage/plugin-lighthouse'}}", "@backstage/plugin-tech-radar": "^{{version '@backstage/plugin-tech-radar'}}", "@backstage/plugin-github-actions": "^{{version '@backstage/plugin-github-actions'}}", diff --git a/packages/create-app/templates/default-app/packages/app/src/plugins.ts b/packages/create-app/templates/default-app/packages/app/src/plugins.ts index b719594a0b..9eabff0044 100644 --- a/packages/create-app/templates/default-app/packages/app/src/plugins.ts +++ b/packages/create-app/templates/default-app/packages/app/src/plugins.ts @@ -3,7 +3,6 @@ export { plugin as CatalogPlugin } from '@backstage/plugin-catalog'; export { plugin as RegisterComponent } from '@backstage/plugin-register-component'; export { plugin as ScaffolderPlugin } from '@backstage/plugin-scaffolder'; export { plugin as TechDocsPlugin } from '@backstage/plugin-techdocs'; -export { plugin as Explore } from '@backstage/plugin-explore'; export { plugin as Circleci } from '@backstage/plugin-circleci'; export { plugin as LighthousePlugin } from '@backstage/plugin-lighthouse'; export { plugin as TechRadar } from '@backstage/plugin-tech-radar'; diff --git a/packages/create-app/templates/default-app/packages/app/src/sidebar.tsx b/packages/create-app/templates/default-app/packages/app/src/sidebar.tsx index 901347645e..b5425b66b6 100644 --- a/packages/create-app/templates/default-app/packages/app/src/sidebar.tsx +++ b/packages/create-app/templates/default-app/packages/app/src/sidebar.tsx @@ -1,6 +1,7 @@ import React, { FC, useContext } from 'react'; import HomeIcon from '@material-ui/icons/Home'; import LibraryBooks from '@material-ui/icons/LibraryBooks'; +import ExtensionIcon from '@material-ui/icons/Extension'; import CreateComponentIcon from '@material-ui/icons/AddCircleOutline'; import BuildIcon from '@material-ui/icons/BuildRounded'; import RuleIcon from '@material-ui/icons/AssignmentTurnedIn'; @@ -26,6 +27,7 @@ export const AppSidebar = () => ( {/* Global nav, not org-specific */} + From 3a201c5d54a536755a51b89c401ecd1bcbd61ec2 Mon Sep 17 00:00:00 2001 From: Andrew Thauer <6507159+andrewthauer@users.noreply.github.com> Date: Thu, 26 Nov 2020 08:30:12 -0500 Subject: [PATCH 261/430] feat: add config schema for rollbar plugins (#3404) * feat: add config schema for rollbar plugins * remove rollbar from create-app template --- .changeset/gorgeous-games-build.md | 6 +++++ app-config.yaml | 5 ---- .../packages/backend/package.json.hbs | 1 - plugins/rollbar-backend/README.md | 1 - plugins/rollbar-backend/config.d.ts | 26 ++++++++++++++++++ plugins/rollbar-backend/package.json | 6 +++-- plugins/rollbar/README.md | 1 + plugins/rollbar/config.d.ts | 27 +++++++++++++++++++ plugins/rollbar/package.json | 20 +++----------- 9 files changed, 67 insertions(+), 26 deletions(-) create mode 100644 .changeset/gorgeous-games-build.md create mode 100644 plugins/rollbar-backend/config.d.ts create mode 100644 plugins/rollbar/config.d.ts diff --git a/.changeset/gorgeous-games-build.md b/.changeset/gorgeous-games-build.md new file mode 100644 index 0000000000..ff050c43f6 --- /dev/null +++ b/.changeset/gorgeous-games-build.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-rollbar': patch +'@backstage/plugin-rollbar-backend': patch +--- + +Add config schema for the rollbar & rollbar-backend plugins diff --git a/app-config.yaml b/app-config.yaml index fcc54a1a35..546d00466a 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -64,11 +64,6 @@ techdocs: sentry: organization: my-company -rollbar: - organization: my-company - accountToken: - $env: ROLLBAR_ACCOUNT_TOKEN - lighthouse: baseUrl: http://localhost:3003 diff --git a/packages/create-app/templates/default-app/packages/backend/package.json.hbs b/packages/create-app/templates/default-app/packages/backend/package.json.hbs index 179d604670..07d1b0be58 100644 --- a/packages/create-app/templates/default-app/packages/backend/package.json.hbs +++ b/packages/create-app/templates/default-app/packages/backend/package.json.hbs @@ -25,7 +25,6 @@ "@backstage/plugin-auth-backend": "^{{version '@backstage/plugin-auth-backend'}}", "@backstage/plugin-catalog-backend": "^{{version '@backstage/plugin-catalog-backend'}}", "@backstage/plugin-proxy-backend": "^{{version '@backstage/plugin-proxy-backend'}}", - "@backstage/plugin-rollbar-backend": "^{{version '@backstage/plugin-rollbar-backend'}}", "@backstage/plugin-scaffolder-backend": "^{{version '@backstage/plugin-scaffolder-backend'}}", "@backstage/plugin-techdocs-backend": "^{{version '@backstage/plugin-techdocs-backend'}}", "@octokit/rest": "^18.0.0", diff --git a/plugins/rollbar-backend/README.md b/plugins/rollbar-backend/README.md index 8f13eb5f26..6cf3b55948 100644 --- a/plugins/rollbar-backend/README.md +++ b/plugins/rollbar-backend/README.md @@ -8,7 +8,6 @@ The following values are read from the configuration file. ```yaml rollbar: - organization: organization-name accountToken: $env: ROLLBAR_ACCOUNT_TOKEN ``` diff --git a/plugins/rollbar-backend/config.d.ts b/plugins/rollbar-backend/config.d.ts new file mode 100644 index 0000000000..473b27a506 --- /dev/null +++ b/plugins/rollbar-backend/config.d.ts @@ -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. + */ + +export interface Config { + /** Configuration options for the rollbar-backend plugin */ + rollbar?: { + /** + * The autentication token for accessing the Rollbar API + * @see https://explorer.docs.rollbar.com/#section/Authentication + */ + accountToken: string; + }; +} diff --git a/plugins/rollbar-backend/package.json b/plugins/rollbar-backend/package.json index 307c37e0da..0f0b8bc366 100644 --- a/plugins/rollbar-backend/package.json +++ b/plugins/rollbar-backend/package.json @@ -42,6 +42,8 @@ "supertest": "^4.0.2" }, "files": [ - "dist" - ] + "dist", + "config.d.ts" + ], + "configSchema": "config.d.ts" } diff --git a/plugins/rollbar/README.md b/plugins/rollbar/README.md index a232462dde..cc862051f7 100644 --- a/plugins/rollbar/README.md +++ b/plugins/rollbar/README.md @@ -54,6 +54,7 @@ const ServiceEntityPage = ({ entity }: { entity: Entity }) => ( # app.config.yaml rollbar: organization: organization-name + # used by rollbar-backend accountToken: $env: ROLLBAR_ACCOUNT_TOKEN ``` diff --git a/plugins/rollbar/config.d.ts b/plugins/rollbar/config.d.ts new file mode 100644 index 0000000000..63526d19b0 --- /dev/null +++ b/plugins/rollbar/config.d.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. + */ + +export interface Config { + /** Configuration options for the rollbar plugin */ + rollbar?: { + /** + * The Rollbar organization name. This can be omitted by using the `rollbar.com/project-slug` annotation. + * @see https://backstage.io/docs/features/software-catalog/well-known-annotations#rollbarcomproject-slug + * @visibility frontend + */ + organization?: string; + }; +} diff --git a/plugins/rollbar/package.json b/plugins/rollbar/package.json index 1177a83eb6..64850c0dee 100644 --- a/plugins/rollbar/package.json +++ b/plugins/rollbar/package.json @@ -51,22 +51,8 @@ "msw": "^0.21.2" }, "files": [ - "dist" + "dist", + "config.d.ts" ], - "configSchema": { - "$schema": "https://backstage.io/schema/config-v1", - "title": "@backstage/rollbar", - "type": "object", - "properties": { - "rollbar": { - "type": "object", - "properties": { - "organization": { - "type": "string", - "visibility": "frontend" - } - } - } - } - } + "configSchema": "config.d.ts" } From 0783fd28f043bc68ba5984af2d3f7d9628ebe081 Mon Sep 17 00:00:00 2001 From: David Tuite Date: Thu, 26 Nov 2020 14:19:31 +0000 Subject: [PATCH 262/430] Improve phrasing of CLI description (#3390) The replaced wording referred to both "is" and "are" in the same sentence. --- docs/overview/stability-index.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/overview/stability-index.md b/docs/overview/stability-index.md index 416cdcdd4a..567e9dcbff 100644 --- a/docs/overview/stability-index.md +++ b/docs/overview/stability-index.md @@ -79,11 +79,11 @@ want to ensure some stability. ### [`cli`](https://github.com/backstage/backstage/tree/master/packages/cli/) -The main toolchain used for Backstage development. The interface that is -considered for stability are the various commands and options passed to those -commands, as well as the environment variables read by the CLI. The build output -may change over time and is not considered a breaking change unless it is likely -to affect external tooling. +The main toolchain used for Backstage development. The various CLI commands and +options passed to those commands, as well as the environment variables read by +the CLI, are considered to be the interface that the stability index refers to. +The build output may change over time and is not considered a breaking change +unless it is likely to affect external tooling. Stability: `2` From 852229a3b88b77f1f3221a84967e61e6178deb1a Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Thu, 26 Nov 2020 15:47:21 +0100 Subject: [PATCH 263/430] Update yarn.lock --- yarn.lock | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/yarn.lock b/yarn.lock index 3a64cb0f25..b8093d125d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1320,37 +1320,6 @@ uuid "^8.0.0" yup "^0.29.3" -"@backstage/plugin-catalog-backend@^0.2.2": - version "0.2.2" - resolved "https://registry.npmjs.org/@backstage/plugin-catalog-backend/-/plugin-catalog-backend-0.2.2.tgz#4e17e0376a920f1425de5b0997d34134ff101a11" - integrity sha512-2pxcqQ3C+FGqYDwL7dkMm76zNM75IF1P8rb3iKrbv8qRRwPqtT0wUx+Gctv3RSL8WgGyCxP1Ho9dQ+FC7o2YSQ== - dependencies: - "@azure/msal-node" "^1.0.0-alpha.8" - "@backstage/backend-common" "^0.3.1" - "@backstage/catalog-model" "^0.3.0" - "@backstage/config" "^0.1.1" - "@octokit/graphql" "^4.5.6" - "@types/express" "^4.17.6" - codeowners-utils "^1.0.2" - core-js "^3.6.5" - cross-fetch "^3.0.6" - express "^4.17.1" - express-promise-router "^3.0.3" - fs-extra "^9.0.0" - git-url-parse "^11.4.0" - knex "^0.21.6" - ldapjs "^2.2.0" - lodash "^4.17.15" - morgan "^1.10.0" - p-limit "^3.0.2" - qs "^6.9.4" - sqlite3 "^5.0.0" - uuid "^8.0.0" - winston "^3.2.1" - yaml "^1.9.2" - yn "^4.0.0" - yup "^0.29.3" - "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" From 3619ea4c47e87a5ab20eb26290ed3d542d7f7c71 Mon Sep 17 00:00:00 2001 From: Dominik Henneke Date: Thu, 26 Nov 2020 16:13:51 +0100 Subject: [PATCH 264/430] Add a configuration schema for the proxy plugin --- .changeset/weak-roses-search.md | 5 +++ app-config.yaml | 2 +- plugins/proxy-backend/config.d.ts | 53 ++++++++++++++++++++++++++++++ plugins/proxy-backend/package.json | 6 ++-- 4 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 .changeset/weak-roses-search.md create mode 100644 plugins/proxy-backend/config.d.ts diff --git a/.changeset/weak-roses-search.md b/.changeset/weak-roses-search.md new file mode 100644 index 0000000000..9f5074ce48 --- /dev/null +++ b/.changeset/weak-roses-search.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-proxy-backend': patch +--- + +Add configuration schema for the commonly used properties diff --git a/app-config.yaml b/app-config.yaml index 546d00466a..95c6b9006a 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -38,7 +38,7 @@ proxy: headers: Authorization: $env: TRAVISCI_AUTH_TOKEN - travis-api-version: 3 + travis-api-version: '3' '/newrelic/apm/api': target: https://api.newrelic.com/v2 diff --git a/plugins/proxy-backend/config.d.ts b/plugins/proxy-backend/config.d.ts new file mode 100644 index 0000000000..3db4f47d68 --- /dev/null +++ b/plugins/proxy-backend/config.d.ts @@ -0,0 +1,53 @@ +/* + * 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 interface Config { + /** + * A list of forwarding-proxies. Each key is a route to match, + * below the prefix that the proxy plugin is mounted on. I must + * start with a '/'. + */ + proxy: { [key: string]: string | ProxyConfig }; +} + +interface ProxyConfig { + /** + * Target of the proxy. Url string to be parsed with the url module. + */ + target: string; + /** + * Object with extra headers to be added to target requests. + */ + headers?: { [key: string]: string }; + /** + * Changes the origin of the host header to the target URL. Default: true. + */ + changeOrigin?: boolean; + /** + * Rewrite target's url path. Object-keys will be used as RegExp to match paths. + * If pathRewrite is not specified, it is set to a single rewrite that removes the entire prefix and route. + */ + pathRewrite?: { [regexp: string]: string }; + /** + * Limit the forwarded HTTP methods, for example allowedMethods: ['GET'] to enforce read-only access. + */ + allowedMethods?: string[]; + /** + * Limit the forwarded HTTP methods. By default, only the headers that are considered safe for CORS + * and headers that are set by the proxy will be forwarded. + */ + allowedHeaders?: string[]; +} diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index 22acdd3fe8..1283f9d261 100644 --- a/plugins/proxy-backend/package.json +++ b/plugins/proxy-backend/package.json @@ -41,6 +41,8 @@ "supertest": "^4.0.2" }, "files": [ - "dist" - ] + "dist", + "config.d.ts" + ], + "configSchema": "config.d.ts" } From 032aba5920514011ca74f965b29fec9782f149e0 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Thu, 26 Nov 2020 16:40:18 +0100 Subject: [PATCH 265/430] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw --- .changeset/short-singers-serve.md | 4 ++-- plugins/api-docs/README.md | 2 +- plugins/api-docs/src/catalog/Router.tsx | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.changeset/short-singers-serve.md b/.changeset/short-singers-serve.md index c2f1c1914f..f1e90f3b55 100644 --- a/.changeset/short-singers-serve.md +++ b/.changeset/short-singers-serve.md @@ -11,5 +11,5 @@ semantic. After Dec 14th, the fields will be removed from types and classes of the Backstage repository. At the first release after that, they will not be present in released packages either. -If your catalog-info.yaml files still contain this fields after the deletion, they will still be -valid and your ingestion will not break, but they won't be visible in the types for consuming code. +If your catalog-info.yaml files still contain this field after the deletion, they will still be +valid and your ingestion will not break, but they won't be visible in the types for consuming code, and the expected relations will not be generated based on them either.``` diff --git a/plugins/api-docs/README.md b/plugins/api-docs/README.md index f5b9a79a85..182ee5a882 100644 --- a/plugins/api-docs/README.md +++ b/plugins/api-docs/README.md @@ -21,7 +21,7 @@ Right now, the following API formats are supported: Other formats are displayed as plain text, but this can easily be extended. To fill the catalog with APIs, [provide entities of kind API](https://backstage.io/docs/features/software-catalog/descriptor-format#kind-api). -To link that an component provides or consumes an API, see [`providesApis`](https://backstage.io/docs/features/software-catalog/descriptor-format#specprovidesapis-optional) and [`consumesApis`](https://backstage.io/docs/features/software-catalog/descriptor-format#specconsumesapis-optional) property on components. +To link that a component provides or consumes an API, see the [`providesApis`](https://backstage.io/docs/features/software-catalog/descriptor-format#specprovidesapis-optional) and [`consumesApis`](https://backstage.io/docs/features/software-catalog/descriptor-format#specconsumesapis-optional) properties on the Component kind. ## Links diff --git a/plugins/api-docs/src/catalog/Router.tsx b/plugins/api-docs/src/catalog/Router.tsx index 6991ad4b57..64c074fc46 100644 --- a/plugins/api-docs/src/catalog/Router.tsx +++ b/plugins/api-docs/src/catalog/Router.tsx @@ -22,7 +22,7 @@ import { EntityPageApi } from './EntityPageApi'; import { MissingImplementsApisEmptyState } from './MissingImplementsApisEmptyState'; const isPluginApplicableToEntity = (entity: Entity) => { - // TODO: Als support RELATION_CONSUMES_API + // TODO: Also support RELATION_CONSUMES_API return entity.relations?.some(r => r.type === RELATION_PROVIDES_API); }; From 1b7a9d7317aee61398460f46572ba9f4621794ec Mon Sep 17 00:00:00 2001 From: Dominik Henneke Date: Thu, 26 Nov 2020 16:14:42 +0100 Subject: [PATCH 266/430] Refactor the proxying docs so the paragraph structure makes more sense --- docs/plugins/proxying.md | 29 ++++++++++++++++------------- plugins/proxy-backend/config.d.ts | 2 +- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/docs/plugins/proxying.md b/docs/plugins/proxying.md index f55b4d9229..ac0d110010 100644 --- a/docs/plugins/proxying.md +++ b/docs/plugins/proxying.md @@ -52,19 +52,7 @@ configuration will lead to the proxy acting on backend requests to The value inside each route is either a simple URL string, or an object on the format accepted by -[http-proxy-middleware](https://www.npmjs.com/package/http-proxy-middleware). It -is also possible to limit the forwarded HTTP methods with the configuration -`allowedMethods`, for example `allowedMethods: ['GET']` to enforce read-only -access. - -By default, the proxy will only forward safe HTTP request headers to the target. -Those are based on the headers that are considered safe for CORS and includes -headers like `content-type` or `last-modified`, as well as all headers that are -set by the proxy. If the proxy should forward other headers like -`authorization`, this must be enabled by the `allowedHeaders` config, for -example `allowedHeaders: ['Authorization']`. This should help to not -accidentally forward confidential headers (`cookie`, `X-Auth-Request-User`) to -third-parties. +[http-proxy-middleware](https://www.npmjs.com/package/http-proxy-middleware). If the value is a string, it is assumed to correspond to: @@ -85,3 +73,18 @@ except with the following caveats for convenience: `'^/api/proxy/larger-example/v1/': '/'` is added. That means that a request to `/api/proxy/larger-example/v1/some/path` will be translated to a request to `http://larger.example.com:8080/svc.v1/some/path`. + +There are also additional settings: + +- `allowedMethods`: Limit the forwarded HTTP methods. For example + `allowedMethods: ['GET']` enforces read-only access. +- `allowedHeaders`: A list of headers that should be forwarded to the target. + +By default, the proxy will only forward safe HTTP request headers to the target. +Those are based on the headers that are considered safe for CORS and includes +headers like `content-type` or `last-modified`, as well as all headers that are +set by the proxy. If the proxy should forward other headers like +`authorization`, this must be enabled by the `allowedHeaders` config, for +example `allowedHeaders: ['Authorization']`. This should help to not +accidentally forward confidential headers (`cookie`, `X-Auth-Request-User`) to +third-parties. diff --git a/plugins/proxy-backend/config.d.ts b/plugins/proxy-backend/config.d.ts index 3db4f47d68..122d052c1c 100644 --- a/plugins/proxy-backend/config.d.ts +++ b/plugins/proxy-backend/config.d.ts @@ -17,7 +17,7 @@ export interface Config { /** * A list of forwarding-proxies. Each key is a route to match, - * below the prefix that the proxy plugin is mounted on. I must + * below the prefix that the proxy plugin is mounted on. It must * start with a '/'. */ proxy: { [key: string]: string | ProxyConfig }; From 1ec19a3f4a5666638649f26e2b8cb187335d09e2 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Thu, 26 Nov 2020 17:30:10 +0100 Subject: [PATCH 267/430] Handle empty YAML documents One of our users just run into this. Kubernetes has the same behavior and doesn't complain about an empty document. --- .changeset/five-dryers-shout.md | 18 ++++++++++ .../ingestion/processors/util/parse.test.ts | 35 +++++++++++++++++++ .../src/ingestion/processors/util/parse.ts | 3 ++ 3 files changed, 56 insertions(+) create mode 100644 .changeset/five-dryers-shout.md diff --git a/.changeset/five-dryers-shout.md b/.changeset/five-dryers-shout.md new file mode 100644 index 0000000000..fbcc85182d --- /dev/null +++ b/.changeset/five-dryers-shout.md @@ -0,0 +1,18 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Ignore empty YAML documents. Having a YAML file like this is now ingested without an error: + +```yaml +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: web +spec: + type: website +--- + +``` + +This behaves now the same way as Kubernetes handles multiple documents in a single YAML file. diff --git a/plugins/catalog-backend/src/ingestion/processors/util/parse.test.ts b/plugins/catalog-backend/src/ingestion/processors/util/parse.test.ts index 8cb91fb2ce..53201486bc 100644 --- a/plugins/catalog-backend/src/ingestion/processors/util/parse.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/util/parse.test.ts @@ -115,6 +115,41 @@ describe('parseEntityYaml', () => { ]); }); + it('should handle empty yaml documents', () => { + // This happens if the user accidentially adds a "---" + // at the end of a file + const results = Array.from( + parseEntityYaml( + Buffer.from( + ` + apiVersion: backstage.io/v1alpha1 + kind: Component + metadata: + name: web + spec: + type: website +--- + `, + 'utf8', + ), + testLoc, + ), + ); + + expect(results).toEqual([ + result.entity(testLoc, { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'web', + }, + spec: { + type: 'website', + }, + }), + ]); + }); + it('should emit parsing errors', () => { const results = Array.from( parseEntityYaml(Buffer.from('`', 'utf8'), testLoc), diff --git a/plugins/catalog-backend/src/ingestion/processors/util/parse.ts b/plugins/catalog-backend/src/ingestion/processors/util/parse.ts index 3f07a815b0..c3dcd42d62 100644 --- a/plugins/catalog-backend/src/ingestion/processors/util/parse.ts +++ b/plugins/catalog-backend/src/ingestion/processors/util/parse.ts @@ -40,6 +40,9 @@ export function* parseEntityYaml( const json = document.toJSON(); if (lodash.isPlainObject(json)) { yield result.entity(location, json as Entity); + } else if (json === null) { + // Ignore null values, these happen if there is an empty document in the + // YAML file, for example if --- is added to the end of the file. } else { const message = `Expected object at root, got ${typeof json}`; yield result.generalError(location, message); From 68650afeb2c2b2e0407dc29ea8b424147d79592b Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Thu, 26 Nov 2020 17:57:51 +0100 Subject: [PATCH 268/430] Clarify that consumesApi and providesApi are two different relationships This follows the implementation in #3408 --- .../software-catalog/well-known-relations.md | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/docs/features/software-catalog/well-known-relations.md b/docs/features/software-catalog/well-known-relations.md index 07a4fc0a5c..54f7833d1b 100644 --- a/docs/features/software-catalog/well-known-relations.md +++ b/docs/features/software-catalog/well-known-relations.md @@ -45,17 +45,28 @@ entity, but there will always be one ultimate owner. This relation is commonly generated based on `spec.owner` of the owned entity, where present. -### `consumesApi` and `providesApi` +### `providesApi` and `apiProvidedBy` A relation with an [API](descriptor-format.md#kind-api) entity, typically from a [Component](descriptor-format.md#kind-component) or [System](descriptor-format.md#kind-system). -These relations express that a component or system either exposes an API - -meaning that it hosts callable endpoints from which you can consume that API - -or that they are dependent on being able to consume that API. +These relations express that a component or system exposes an API - meaning that +it hosts callable endpoints from which you can consume that API. -This relation is commonly generated based on `spec.implementsApis` of the +This relation is commonly generated based on `spec.providesApis` of the +component or system in question. + +### `consumesApi` and `apiConsumedBy` + +A relation with an [API](descriptor-format.md#kind-api) entity, typically from a +[Component](descriptor-format.md#kind-component) or +[System](descriptor-format.md#kind-system). + +These relations express that a component or system consumes an API - meaning +that it depends on endpoints of the API. + +This relation is commonly generated based on `spec.consumesApis` of the component or system in question. ### `dependsOn` and `dependencyOf` From 83b6e0c1fc347e9d86fcf7435c005fe47a8488bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Fri, 20 Nov 2020 16:30:57 +0100 Subject: [PATCH 269/430] catalog: remove group ancestors and descendants --- .changeset/silent-boxes-exercise.md | 8 ++ .../software-catalog/descriptor-format.md | 40 ---------- .../examples/acme/backstage-group.yaml | 2 - .../examples/acme/boxoffice-group.yaml | 2 - .../examples/acme/infrastructure-group.yaml | 2 - packages/catalog-model/examples/acme/org.yaml | 3 - .../examples/acme/team-a-group.yaml | 2 - .../examples/acme/team-b-group.yaml | 2 - .../examples/acme/team-c-group.yaml | 2 - .../examples/acme/team-d-group.yaml | 2 - .../src/kinds/GroupEntityV1alpha1.test.ts | 42 ---------- .../src/kinds/GroupEntityV1alpha1.ts | 26 ------ .../BuiltinKindsEntityProcessor.test.ts | 30 ------- .../processors/BuiltinKindsEntityProcessor.ts | 19 ----- .../ingestion/processors/ldap/read.test.ts | 4 +- .../src/ingestion/processors/ldap/read.ts | 2 - .../processors/microsoftGraph/read.test.ts | 15 ---- .../processors/microsoftGraph/read.ts | 10 +-- .../ingestion/processors/util/github.test.ts | 2 - .../src/ingestion/processors/util/github.ts | 2 - .../src/ingestion/processors/util/org.test.ts | 34 +++----- .../src/ingestion/processors/util/org.ts | 80 +++++-------------- 22 files changed, 45 insertions(+), 286 deletions(-) create mode 100644 .changeset/silent-boxes-exercise.md diff --git a/.changeset/silent-boxes-exercise.md b/.changeset/silent-boxes-exercise.md new file mode 100644 index 0000000000..0d2003cec9 --- /dev/null +++ b/.changeset/silent-boxes-exercise.md @@ -0,0 +1,8 @@ +--- +'@backstage/catalog-model': minor +'@backstage/plugin-catalog-backend': minor +--- + +Remove the deprecated fields `ancestors` and `descendants` from the `Group` entity. + +See https://github.com/backstage/backstage/issues/3049 and the PRs linked from it for details. diff --git a/docs/features/software-catalog/descriptor-format.md b/docs/features/software-catalog/descriptor-format.md index 438f874990..7e3e774d63 100644 --- a/docs/features/software-catalog/descriptor-format.md +++ b/docs/features/software-catalog/descriptor-format.md @@ -723,9 +723,7 @@ metadata: spec: type: business-unit parent: ops - ancestors: [ops, global-synergies, acme-corp] children: [backstage, other] - descendants: [backstage, other, team-a, team-b, team-c, team-d] ``` In addition to the [common envelope metadata](#common-to-all-kinds-the-metadata) @@ -761,25 +759,6 @@ namespace as the user. Only `Group` entities may be referenced. Most commonly, this field points to a group in the same namespace, so in those cases it is sufficient to enter only the `metadata.name` field of that group. -### `spec.ancestors` [required] - -**NOTE**: This field was marked for deprecation on Nov 22nd, 2020. It will be -removed entirely from the model on Dec 6th, 2020 in the repository and will not -be present in released packages following the next release after that. Please -update your code to not consume this field before the removal date. - -The recursive list of parents up the hierarchy, by stepping through parents one -by one. The list must be present, but may be empty if `parent` is not present. -The first entry in the list is equal to `parent`, and then the following ones -are progressively farther up the hierarchy. - -The entries of this array are -[entity references](https://backstage.io/docs/features/software-catalog/references), -with the default kind `Group` and the default namespace equal to the same -namespace as the user. Only `Group` entities may be referenced. Most commonly, -these entries point to groups in the same namespace, so in those cases it is -sufficient to enter only the `metadata.name` field of those groups. - ### `spec.children` [required] The immediate child groups of this group in the hierarchy (whose `parent` field @@ -794,25 +773,6 @@ namespace as the user. Only `Group` entities may be referenced. Most commonly, these entries point to groups in the same namespace, so in those cases it is sufficient to enter only the `metadata.name` field of those groups. -### `spec.descendants` [required] - -**NOTE**: This field was marked for deprecation on Nov 22nd, 2020. It will be -removed entirely from the model on Dec 6th, 2020 in the repository and will not -be present in released packages following the next release after that. Please -update your code to not consume this field before the removal date. - -The immediate and recursive child groups of this group in the hierarchy -(children, and children's children, etc.). The list must be present, but may be -empty if there are no child groups. The items are not guaranteed to be ordered -in any particular way. - -The entries of this array are -[entity references](https://backstage.io/docs/features/software-catalog/references), -with the default kind `Group` and the default namespace equal to the same -namespace as the user. Only `Group` entities may be referenced. Most commonly, -these entries point to groups in the same namespace, so in those cases it is -sufficient to enter only the `metadata.name` field of those groups. - ## Kind: User Describes the following entity kind: diff --git a/packages/catalog-model/examples/acme/backstage-group.yaml b/packages/catalog-model/examples/acme/backstage-group.yaml index f992d155bc..be5baf091e 100644 --- a/packages/catalog-model/examples/acme/backstage-group.yaml +++ b/packages/catalog-model/examples/acme/backstage-group.yaml @@ -6,6 +6,4 @@ metadata: spec: type: sub-department parent: infrastructure - ancestors: [infrastructure, acme-corp] children: [team-a, team-b] - descendants: [team-a, team-b] diff --git a/packages/catalog-model/examples/acme/boxoffice-group.yaml b/packages/catalog-model/examples/acme/boxoffice-group.yaml index 0be1fe58cb..37e9816b50 100644 --- a/packages/catalog-model/examples/acme/boxoffice-group.yaml +++ b/packages/catalog-model/examples/acme/boxoffice-group.yaml @@ -6,6 +6,4 @@ metadata: spec: type: sub-department parent: infrastructure - ancestors: [infrastructure, acme-corp] children: [team-c, team-d] - descendants: [team-c, team-d] diff --git a/packages/catalog-model/examples/acme/infrastructure-group.yaml b/packages/catalog-model/examples/acme/infrastructure-group.yaml index 2341782944..45bad12b7e 100644 --- a/packages/catalog-model/examples/acme/infrastructure-group.yaml +++ b/packages/catalog-model/examples/acme/infrastructure-group.yaml @@ -6,6 +6,4 @@ metadata: spec: type: department parent: acme-corp - ancestors: [acme-corp] children: [backstage, boxoffice] - descendants: [backstage, boxoffice, team-a, team-b, team-c, team-d] diff --git a/packages/catalog-model/examples/acme/org.yaml b/packages/catalog-model/examples/acme/org.yaml index 8c562aaf89..21a890a1aa 100644 --- a/packages/catalog-model/examples/acme/org.yaml +++ b/packages/catalog-model/examples/acme/org.yaml @@ -5,10 +5,7 @@ metadata: description: The acme-corp organization spec: type: organization - ancestors: [] children: [infrastructure] - descendants: - [infrastructure, backstage, boxoffice, team-a, team-b, team-c, team-d] --- apiVersion: backstage.io/v1alpha1 kind: Location diff --git a/packages/catalog-model/examples/acme/team-a-group.yaml b/packages/catalog-model/examples/acme/team-a-group.yaml index dacc4c6c3c..a22559c38b 100644 --- a/packages/catalog-model/examples/acme/team-a-group.yaml +++ b/packages/catalog-model/examples/acme/team-a-group.yaml @@ -6,9 +6,7 @@ metadata: spec: type: team parent: backstage - ancestors: [backstage, infrastructure, acme-corp] children: [] - descendants: [] --- apiVersion: backstage.io/v1alpha1 kind: User diff --git a/packages/catalog-model/examples/acme/team-b-group.yaml b/packages/catalog-model/examples/acme/team-b-group.yaml index 00e9e41d80..0c6604ff79 100644 --- a/packages/catalog-model/examples/acme/team-b-group.yaml +++ b/packages/catalog-model/examples/acme/team-b-group.yaml @@ -6,9 +6,7 @@ metadata: spec: type: team parent: backstage - ancestors: [backstage, infrastructure, acme-corp] children: [] - descendants: [] --- apiVersion: backstage.io/v1alpha1 kind: User diff --git a/packages/catalog-model/examples/acme/team-c-group.yaml b/packages/catalog-model/examples/acme/team-c-group.yaml index 0f97f69cb3..ccc95b7f60 100644 --- a/packages/catalog-model/examples/acme/team-c-group.yaml +++ b/packages/catalog-model/examples/acme/team-c-group.yaml @@ -6,9 +6,7 @@ metadata: spec: type: team parent: boxoffice - ancestors: [boxoffice, infrastructure, acme-corp] children: [] - descendants: [] --- apiVersion: backstage.io/v1alpha1 kind: User diff --git a/packages/catalog-model/examples/acme/team-d-group.yaml b/packages/catalog-model/examples/acme/team-d-group.yaml index 5a1a53a2e6..e032ab8b9e 100644 --- a/packages/catalog-model/examples/acme/team-d-group.yaml +++ b/packages/catalog-model/examples/acme/team-d-group.yaml @@ -6,9 +6,7 @@ metadata: spec: type: team parent: boxoffice - ancestors: [boxoffice, infrastructure, acme-corp] children: [] - descendants: [] --- apiVersion: backstage.io/v1alpha1 kind: User diff --git a/packages/catalog-model/src/kinds/GroupEntityV1alpha1.test.ts b/packages/catalog-model/src/kinds/GroupEntityV1alpha1.test.ts index 8b7bef8ff7..755a68cede 100644 --- a/packages/catalog-model/src/kinds/GroupEntityV1alpha1.test.ts +++ b/packages/catalog-model/src/kinds/GroupEntityV1alpha1.test.ts @@ -34,9 +34,7 @@ describe('GroupV1alpha1Validator', () => { spec: { type: 'squad', parent: 'group-a', - ancestors: ['group-a', 'global-synergies', 'acme-corp'], children: ['child-a', 'child-b'], - descendants: ['desc-a', 'desc-b'], }, }; }); @@ -85,26 +83,6 @@ describe('GroupV1alpha1Validator', () => { await expect(validator.check(entity)).rejects.toThrow(/parent/); }); - it('rejects missing ancestors', async () => { - delete (entity as any).spec.ancestors; - await expect(validator.check(entity)).rejects.toThrow(/ancestor/); - }); - - it('rejects empty ancestors', async () => { - (entity as any).spec.ancestors = ['']; - await expect(validator.check(entity)).rejects.toThrow(/ancestor/); - }); - - it('rejects undefined ancestors', async () => { - (entity as any).spec.ancestors = [undefined]; - await expect(validator.check(entity)).rejects.toThrow(/ancestor/); - }); - - it('accepts no ancestors', async () => { - (entity as any).spec.ancestors = []; - await expect(validator.check(entity)).resolves.toBe(true); - }); - it('rejects missing children', async () => { delete (entity as any).spec.children; await expect(validator.check(entity)).rejects.toThrow(/children/); @@ -124,24 +102,4 @@ describe('GroupV1alpha1Validator', () => { (entity as any).spec.children = []; await expect(validator.check(entity)).resolves.toBe(true); }); - - it('rejects missing descendants', async () => { - delete (entity as any).spec.descendants; - await expect(validator.check(entity)).rejects.toThrow(/descendants/); - }); - - it('rejects empty descendants', async () => { - (entity as any).spec.descendants = ['']; - await expect(validator.check(entity)).rejects.toThrow(/descendants/); - }); - - it('rejects undefined descendants', async () => { - (entity as any).spec.descendants = [undefined]; - await expect(validator.check(entity)).rejects.toThrow(/descendants/); - }); - - it('accepts no descendants', async () => { - (entity as any).spec.descendants = []; - await expect(validator.check(entity)).resolves.toBe(true); - }); }); diff --git a/packages/catalog-model/src/kinds/GroupEntityV1alpha1.ts b/packages/catalog-model/src/kinds/GroupEntityV1alpha1.ts index 7be432ad1f..5f73d00e66 100644 --- a/packages/catalog-model/src/kinds/GroupEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/GroupEntityV1alpha1.ts @@ -32,21 +32,11 @@ const schema = yup.object>({ // one element and there is no simple workaround -_- // the cast is there to convince typescript that the array itself is // required without using .required() - ancestors: yup.array(yup.string().required()).test({ - name: 'isDefined', - message: 'ancestors must be defined', - test: v => Boolean(v), - }) as yup.ArraySchema, children: yup.array(yup.string().required()).test({ name: 'isDefined', message: 'children must be defined', test: v => Boolean(v), }) as yup.ArraySchema, - descendants: yup.array(yup.string().required()).test({ - name: 'isDefined', - message: 'descendants must be defined', - test: v => Boolean(v), - }) as yup.ArraySchema, }) .required(), }); @@ -57,23 +47,7 @@ export interface GroupEntityV1alpha1 extends Entity { spec: { type: string; parent?: string; - /** - * @deprecated This field will disappear on Dec 6th, 2020. Please remove - * any consuming code. Producers can stop producing this field - * before that date, as long as the catalog backend uses the - * BuiltinKindsEntityProcessor which inserts the fields in the - * mean time. - */ - ancestors: string[]; children: string[]; - /** - * @deprecated This field will disappear on Dec 6th, 2020. Please remove - * any consuming code. Producers can stop producing this field - * before that date, as long as the catalog backend uses the - * BuiltinKindsEntityProcessor which inserts the fields in the - * mean time. - */ - descendants: string[]; }; } diff --git a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts index e456dfd7a2..5fa63d800a 100644 --- a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts @@ -23,34 +23,6 @@ import { import { BuiltinKindsEntityProcessor } from './BuiltinKindsEntityProcessor'; describe('BuiltinKindsEntityProcessor', () => { - it('fills in fields for #3049', async () => { - const p = new BuiltinKindsEntityProcessor(); - const result = await p.preProcessEntity({ - apiVersion: 'backstage.io/v1alpha1', - kind: 'Group', - metadata: { - name: 'n', - }, - spec: { - type: 't', - children: [], - } as any, - }); - expect(result).toEqual({ - apiVersion: 'backstage.io/v1alpha1', - kind: 'Group', - metadata: { - name: 'n', - }, - spec: { - type: 't', - children: [], - ancestors: [], - descendants: [], - }, - }); - }); - describe('postProcessEntity', () => { const processor = new BuiltinKindsEntityProcessor(); const location = { type: 'a', target: 'b' }; @@ -215,9 +187,7 @@ describe('BuiltinKindsEntityProcessor', () => { spec: { type: 't', parent: 'p', - ancestors: [], children: ['c'], - descendants: [], }, }; diff --git a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts index 62b496dd65..aa68222a4e 100644 --- a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts @@ -53,25 +53,6 @@ export class BuiltinKindsEntityProcessor implements CatalogProcessor { userEntityV1alpha1Validator, ]; - async preProcessEntity(entity: Entity): Promise { - // NOTE(freben): Part of Group field deprecation on Nov 22nd, 2020. Fields - // scheduled for removal Dec 6th, 2020. This code can be deleted after that - // point. See https://github.com/backstage/backstage/issues/3049 - if ( - entity.apiVersion === 'backstage.io/v1alpha1' && - entity.kind === 'Group' && - entity.spec - ) { - if (!entity.spec.ancestors) { - entity.spec.ancestors = []; - } - if (!entity.spec.descendants) { - entity.spec.descendants = []; - } - } - return entity; - } - async validateEntityKind(entity: Entity): Promise { for (const validator of this.validators) { const result = await validator.check(entity); diff --git a/plugins/catalog-backend/src/ingestion/processors/ldap/read.test.ts b/plugins/catalog-backend/src/ingestion/processors/ldap/read.test.ts index e644bab22a..e964a03ce0 100644 --- a/plugins/catalog-backend/src/ingestion/processors/ldap/read.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/ldap/read.test.ts @@ -47,7 +47,7 @@ function group(data: RecursivePartial): GroupEntity { apiVersion: 'backstage.io/v1alpha1', kind: 'Group', metadata: { name: 'name' }, - spec: { type: 'type', ancestors: [], children: [], descendants: [] }, + spec: { type: 'type', children: [] }, } as GroupEntity, data, ); @@ -173,9 +173,7 @@ describe('readLdapGroups', () => { }, spec: { type: 'type-value', - ancestors: [], children: [], - descendants: [], }, }), ]); diff --git a/plugins/catalog-backend/src/ingestion/processors/ldap/read.ts b/plugins/catalog-backend/src/ingestion/processors/ldap/read.ts index 63fce00bd8..b7315fd291 100644 --- a/plugins/catalog-backend/src/ingestion/processors/ldap/read.ts +++ b/plugins/catalog-backend/src/ingestion/processors/ldap/read.ts @@ -150,9 +150,7 @@ export async function readLdapGroups( }, spec: { type: 'unknown', - ancestors: [], children: [], - descendants: [], }, }; diff --git a/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/read.test.ts b/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/read.test.ts index 3c2f33e922..b0d446c417 100644 --- a/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/read.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/read.test.ts @@ -49,9 +49,7 @@ function group(data: RecursivePartial): GroupEntity { name: 'name', }, spec: { - ancestors: [], children: [], - descendants: [], type: 'team', }, } as GroupEntity, @@ -306,33 +304,20 @@ describe('read microsoft graph', () => { resolveRelations(rootGroup, groups, users, groupMember, groupMemberOf); expect(rootGroup.spec.parent).toBeUndefined(); - expect(rootGroup.spec.ancestors).toEqual(expect.arrayContaining([])); expect(rootGroup.spec.children).toEqual( expect.arrayContaining(['a', 'b']), ); - expect(rootGroup.spec.descendants).toEqual( - expect.arrayContaining(['a', 'b', 'c']), - ); expect(groupA.spec.parent).toEqual('root'); - expect(groupA.spec.ancestors).toEqual(expect.arrayContaining(['root'])); expect(groupA.spec.children).toEqual(expect.arrayContaining([])); - expect(groupA.spec.descendants).toEqual(expect.arrayContaining([])); expect(groupB.spec.parent).toEqual('root'); - expect(groupB.spec.ancestors).toEqual(expect.arrayContaining(['root'])); expect(groupB.spec.children).toEqual(expect.arrayContaining(['c'])); - expect(groupB.spec.descendants).toEqual(expect.arrayContaining(['c'])); expect(groupC.spec.parent).toEqual('b'); - expect(groupC.spec.ancestors).toEqual( - expect.arrayContaining(['root', 'b']), - ); expect(groupC.spec.children).toEqual(expect.arrayContaining([])); - expect(groupC.spec.descendants).toEqual(expect.arrayContaining([])); expect(user1.spec.memberOf).toEqual(expect.arrayContaining(['a'])); - expect(user2.spec.memberOf).toEqual(expect.arrayContaining(['b', 'c'])); }); }); diff --git a/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/read.ts b/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/read.ts index 6cde2649c4..532e1c3777 100644 --- a/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/read.ts +++ b/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/read.ts @@ -14,6 +14,7 @@ * limitations under the License. */ import { GroupEntity, UserEntity } from '@backstage/catalog-model'; +import limiterFactory from 'p-limit'; import { buildMemberOf, buildOrgHierarchy } from '../util/org'; import { MicrosoftGraphClient } from './client'; import { @@ -21,7 +22,6 @@ import { MICROSOFT_GRAPH_TENANT_ID_ANNOTATION, MICROSOFT_GRAPH_USER_ID_ANNOTATION, } from './constants'; -import limiterFactory from 'p-limit'; export function normalizeEntityName(name: string): string { return name @@ -98,7 +98,7 @@ export async function readMicrosoftGraphOrganization( ): Promise<{ rootGroup: GroupEntity; // With all relations empty }> { - // For now we expect a single root orgranization + // For now we expect a single root organization const organization = await client.getOrganization(tenantId); const name = normalizeEntityName(organization.displayName!); const rootGroup: GroupEntity = { @@ -113,9 +113,7 @@ export async function readMicrosoftGraphOrganization( }, spec: { type: 'root', - ancestors: [], children: [], - descendants: [], }, }; @@ -165,9 +163,7 @@ export async function readMicrosoftGraphGroups( spec: { type: 'team', // TODO: We could include a group email and picture - ancestors: [], children: [], - descendants: [], }, }; @@ -278,7 +274,7 @@ export function resolveRelations( }); }); - // Make sure that all groups have proper ancestors and descendants + // Make sure that all groups have proper parents and children buildOrgHierarchy(groups); // Set relations for all users diff --git a/plugins/catalog-backend/src/ingestion/processors/util/github.test.ts b/plugins/catalog-backend/src/ingestion/processors/util/github.test.ts index 54afdd1bdc..1e35f44712 100644 --- a/plugins/catalog-backend/src/ingestion/processors/util/github.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/util/github.test.ts @@ -100,9 +100,7 @@ describe('github', () => { spec: { type: 'team', parent: 'parent', - ancestors: [], children: [], - descendants: [], }, }), ], diff --git a/plugins/catalog-backend/src/ingestion/processors/util/github.ts b/plugins/catalog-backend/src/ingestion/processors/util/github.ts index 96cf4a7668..8be96c41de 100644 --- a/plugins/catalog-backend/src/ingestion/processors/util/github.ts +++ b/plugins/catalog-backend/src/ingestion/processors/util/github.ts @@ -162,9 +162,7 @@ export async function getOrganizationTeams( }, spec: { type: 'team', - ancestors: [], children: [], - descendants: [], }, }; diff --git a/plugins/catalog-backend/src/ingestion/processors/util/org.test.ts b/plugins/catalog-backend/src/ingestion/processors/util/org.test.ts index f7afd63101..c9056eebb4 100644 --- a/plugins/catalog-backend/src/ingestion/processors/util/org.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/util/org.test.ts @@ -26,7 +26,7 @@ function g( apiVersion: 'backstage.io/v1alpha1', kind: 'Group', metadata: { name }, - spec: { type: 'team', parent, children, ancestors: [], descendants: [] }, + spec: { type: 'team', parent, children }, }; } @@ -43,28 +43,16 @@ describe('buildOrgHierarchy', () => { expect(d.spec.children).toEqual([]); }); - it('fills out descendants', () => { - const a = g('a', undefined, []); - const b = g('b', 'a', []); - const c = g('c', 'b', []); - const d = g('d', 'a', []); + it('sets parent of groups children', () => { + const a = g('a', undefined, ['b', 'd']); + const b = g('b', undefined, ['c']); + const c = g('c', undefined, []); + const d = g('d', undefined, []); buildOrgHierarchy([a, b, c, d]); - expect(a.spec.descendants).toEqual(expect.arrayContaining(['b', 'c', 'd'])); - expect(b.spec.descendants).toEqual(expect.arrayContaining(['c'])); - expect(c.spec.descendants).toEqual([]); - expect(d.spec.descendants).toEqual([]); - }); - - it('fills out ancestors', () => { - const a = g('a', undefined, []); - const b = g('b', 'a', []); - const c = g('c', 'b', []); - const d = g('d', 'a', []); - buildOrgHierarchy([a, b, c, d]); - expect(a.spec.ancestors).toEqual([]); - expect(b.spec.ancestors).toEqual(expect.arrayContaining(['a'])); - expect(c.spec.ancestors).toEqual(expect.arrayContaining(['a', 'b'])); - expect(d.spec.ancestors).toEqual(expect.arrayContaining(['a'])); + expect(a.spec.parent).toBeUndefined(); + expect(b.spec.parent).toBe('a'); + expect(c.spec.parent).toBe('b'); + expect(d.spec.parent).toBe('a'); }); }); @@ -76,7 +64,7 @@ describe('buildMemberOf', () => { const u: UserEntity = { apiVersion: 'backstage.io/v1alpha1', kind: 'User', - metadata: { name }, + metadata: { name: 'n' }, spec: { profile: {}, memberOf: ['c'] }, }; diff --git a/plugins/catalog-backend/src/ingestion/processors/util/org.ts b/plugins/catalog-backend/src/ingestion/processors/util/org.ts index b033fe99d7..787e408e60 100644 --- a/plugins/catalog-backend/src/ingestion/processors/util/org.ts +++ b/plugins/catalog-backend/src/ingestion/processors/util/org.ts @@ -35,62 +35,17 @@ export function buildOrgHierarchy(groups: GroupEntity[]) { } // - // Make sure that g.descendants is complete + // Make sure that g.children.parent is g // - function visitDescendants(current: GroupEntity): string[] { - if (current.spec.descendants.length) { - return current.spec.descendants; - } - - const accumulator = new Set(); - for (const childName of current.spec.children) { - accumulator.add(childName); + for (const group of groups) { + const selfName = group.metadata.name; + for (const childName of group.spec.children) { const child = groupsByName.get(childName); - if (child) { - for (const d of visitDescendants(child)) { - accumulator.add(d); - } + if (child && !child.spec.parent) { + child.spec.parent = selfName; } } - - const descendants = Array.from(accumulator); - current.spec.descendants = descendants; - return descendants; - } - - for (const group of groups) { - visitDescendants(group); - } - - // - // Make sure that g.ancestors is complete - // - - function visitAncestors(current: GroupEntity): string[] { - if (current.spec.ancestors.length) { - return current.spec.ancestors; - } - - let ancestors: string[]; - const parentName = current.spec.parent; - if (!parentName) { - ancestors = []; - } else { - const parent = groupsByName.get(parentName); - if (parent) { - ancestors = [parentName, ...visitAncestors(parent)]; - } else { - ancestors = [parentName]; - } - } - - current.spec.ancestors = ancestors; - return ancestors; - } - - for (const group of groups) { - visitAncestors(group); } } @@ -100,15 +55,24 @@ export function buildMemberOf(groups: GroupEntity[], users: UserEntity[]) { const groupsByName = new Map(groups.map(g => [g.metadata.name, g])); users.forEach(user => { - const transitiveMemberOf = new Set([...user.spec.memberOf]); + const transitiveMemberOf = new Set(); - user.spec.memberOf.forEach(groupName => { - const group = groupsByName.get(groupName); - - if (group) { - group.spec.ancestors.forEach(g => transitiveMemberOf.add(g)); + const todo = [...user.spec.memberOf]; + for (;;) { + const current = todo.pop(); + if (!current) { + break; } - }); + + if (!transitiveMemberOf.has(current)) { + transitiveMemberOf.add(current); + const group = groupsByName.get(current); + if (group?.spec.parent) { + todo.push(group.spec.parent); + } + } + } + user.spec.memberOf = [...transitiveMemberOf]; }); } From 8697dea5b46d721c0e218a1f8925da9ff843198c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 26 Nov 2020 19:30:05 +0100 Subject: [PATCH 270/430] build(deps): bump rollup from 2.23.0 to 2.33.3 (#3434) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * build(deps): bump rollup from 2.23.0 to 2.33.3 Bumps [rollup](https://github.com/rollup/rollup) from 2.23.0 to 2.33.3. - [Release notes](https://github.com/rollup/rollup/releases) - [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md) - [Commits](https://github.com/rollup/rollup/compare/v2.23.0...v2.33.3) Signed-off-by: dependabot[bot] * fix types Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Fredrik Adelöw --- .changeset/shy-humans-protect.md | 5 +++++ packages/cli/package.json | 2 +- packages/cli/src/lib/builder/plugins.test.ts | 12 ++++++------ yarn.lock | 8 ++++---- 4 files changed, 16 insertions(+), 11 deletions(-) create mode 100644 .changeset/shy-humans-protect.md diff --git a/.changeset/shy-humans-protect.md b/.changeset/shy-humans-protect.md new file mode 100644 index 0000000000..612cb9faf8 --- /dev/null +++ b/.changeset/shy-humans-protect.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Bump Rollup diff --git a/packages/cli/package.json b/packages/cli/package.json index 034c8a3de5..b79a8ec37a 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -86,7 +86,7 @@ "react-hot-loader": "^4.12.21", "recursive-readdir": "^2.2.2", "replace-in-file": "^6.0.0", - "rollup": "2.23.x", + "rollup": "2.33.x", "rollup-plugin-dts": "1.4.13", "rollup-plugin-esbuild": "^2.0.0", "rollup-plugin-peer-deps-external": "^2.2.2", diff --git a/packages/cli/src/lib/builder/plugins.test.ts b/packages/cli/src/lib/builder/plugins.test.ts index 41504a700e..d1e1b3e2bd 100644 --- a/packages/cli/src/lib/builder/plugins.test.ts +++ b/packages/cli/src/lib/builder/plugins.test.ts @@ -38,11 +38,11 @@ describe('forwardFileImports', () => { expect(plugin.name).toBe('forward-file-imports'); }); - it('should call through to original external option', () => { + it('should call through to original external option', async () => { const plugin = forwardFileImports({ include: /\.png$/ }); const external = jest.fn((id: string) => id.endsWith('external')); - const options = plugin.options?.call(context, { external })!; + const options = (await plugin.options?.call(context, { external }))!; if (typeof options.external !== 'function') { throw new Error('options.external is not a function'); } @@ -70,12 +70,12 @@ describe('forwardFileImports', () => { ).toThrow('Unknown importer of file module ./my-image.png'); }); - it('should handle original external array', () => { + it('should handle original external array', async () => { const plugin = forwardFileImports({ include: /\.png$/ }); - const options = plugin.options?.call(context, { + const options = (await plugin.options?.call(context, { external: ['my-external'], - })!; + }))!; if (typeof options.external !== 'function') { throw new Error('options.external is not a function'); } @@ -106,7 +106,7 @@ describe('forwardFileImports', () => { it('should extract files', async () => { const plugin = forwardFileImports({ include: /\.png$/ }); - const options = plugin.options?.call(context, {})!; + const options = (await plugin.options?.call(context, {}))!; if (typeof options.external !== 'function') { throw new Error('options.external is not a function'); } diff --git a/yarn.lock b/yarn.lock index 576db3b66c..4856559521 100644 --- a/yarn.lock +++ b/yarn.lock @@ -21188,10 +21188,10 @@ rollup-pluginutils@^2.8.2: dependencies: estree-walker "^0.6.1" -rollup@2.23.x: - version "2.23.0" - resolved "https://registry.npmjs.org/rollup/-/rollup-2.23.0.tgz#b7ab1fee0c0e60132fd0553c4df1e9cdacfada9d" - integrity sha512-vLNmZFUGVwrnqNAJ/BvuLk1MtWzu4IuoqsH9UWK5AIdO3rt8/CSiJNvPvCIvfzrbNsqKbNzPAG1V2O4eTe2XZg== +rollup@2.33.x: + version "2.33.3" + resolved "https://registry.npmjs.org/rollup/-/rollup-2.33.3.tgz#ae72ce31f992b09a580072951bfea76e9df17342" + integrity sha512-RpayhPTe4Gu/uFGCmk7Gp5Z9Qic2VsqZ040G+KZZvsZYdcuWaJg678JeDJJvJeEQXminu24a2au+y92CUWVd+w== optionalDependencies: fsevents "~2.1.2" From 6321746e20695095706625443bcf78c3a7f0e0f6 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 26 Nov 2020 13:33:06 +0100 Subject: [PATCH 271/430] cli: make versions:bump properly bump the versions of all @backstage packages --- .changeset/long-birds-rush.md | 2 +- .../cli/src/commands/versions/bump.test.ts | 15 ++- packages/cli/src/commands/versions/bump.ts | 96 ++++++++++++------- packages/cli/src/lib/versioning/Lockfile.ts | 6 ++ 4 files changed, 78 insertions(+), 41 deletions(-) diff --git a/.changeset/long-birds-rush.md b/.changeset/long-birds-rush.md index db4da34789..b1ce6aa125 100644 --- a/.changeset/long-birds-rush.md +++ b/.changeset/long-birds-rush.md @@ -2,4 +2,4 @@ '@backstage/cli': patch --- -Make versions:bump install new versions of dependencies that were within the specified range +Make versions:bump install new versions of dependencies that were within the specified range as well as install new versions of transitive @backstage dependencies. diff --git a/packages/cli/src/commands/versions/bump.test.ts b/packages/cli/src/commands/versions/bump.test.ts index f4d69c52be..1325f0e2fd 100644 --- a/packages/cli/src/commands/versions/bump.test.ts +++ b/packages/cli/src/commands/versions/bump.test.ts @@ -25,6 +25,7 @@ import { withLogCollector } from '@backstage/test-utils'; const REGISTRY_VERSIONS: { [name: string]: string } = { '@backstage/core': '1.0.6', + '@backstage/core-api': '1.0.7', '@backstage/theme': '2.0.0', }; @@ -54,11 +55,8 @@ const lockfileMock = `${HEADER} version "1.0.3" `; -// This resulting lockfile isn't a real world example, since it doesn't include the package bumps +// This is the lockfile that we produce to unlock versions before we run yarn install const lockfileMockResult = `${HEADER} -"@backstage/core-api@^1.0.3", "@backstage/core-api@^1.0.6": - version "1.0.6" - "@backstage/core@^1.0.5": version "1.0.6" dependencies: @@ -121,15 +119,16 @@ describe('bump', () => { expect(logs.filter(Boolean)).toEqual([ 'Checking for updates of @backstage/theme', 'Checking for updates of @backstage/core', + 'Checking for updates of @backstage/core-api', 'Some packages are outdated, updating', 'Removing lockfile entry for @backstage/core@^1.0.3 to bump to 1.0.6', + 'Removing lockfile entry for @backstage/core-api@^1.0.6 to bump to 1.0.7', + 'Removing lockfile entry for @backstage/core-api@^1.0.3 to bump to 1.0.7', 'Bumping @backstage/theme in b to ^2.0.0', "Running 'yarn install' to install new versions", - 'Removing duplicate dependencies from yarn.lock', - "Running 'yarn install' to remove duplicates from node_modules", ]); - expect(runObj.runPlain).toHaveBeenCalledTimes(2); + expect(runObj.runPlain).toHaveBeenCalledTimes(3); expect(runObj.runPlain).toHaveBeenCalledWith( 'yarn', 'info', @@ -143,7 +142,7 @@ describe('bump', () => { '@backstage/theme', ); - expect(runObj.run).toHaveBeenCalledTimes(2); + expect(runObj.run).toHaveBeenCalledTimes(1); expect(runObj.run).toHaveBeenCalledWith('yarn', ['install']); const lockfileContents = await fs.readFile('/yarn.lock', 'utf8'); diff --git a/packages/cli/src/commands/versions/bump.ts b/packages/cli/src/commands/versions/bump.ts index 1883d87bac..441f65f353 100644 --- a/packages/cli/src/commands/versions/bump.ts +++ b/packages/cli/src/commands/versions/bump.ts @@ -41,6 +41,9 @@ type PkgVersionInfo = { export default async () => { const lockfilePath = paths.resolveTargetRoot('yarn.lock'); + const lockfile = await Lockfile.load(lockfilePath); + + const findTargetVersion = createVersionFinder(); // First we discover all Backstage dependencies within our own repo const dependencyMap = await mapDependencies(paths.targetDir); @@ -48,20 +51,16 @@ export default async () => { // Next check with the package registry to see which dependency ranges we need to bump const versionBumps = new Map(); // Track package versions that we want to remove from yarn.lock in order to trigger a bump - const unlocked = Array<{ name: string; range: string; latest: string }>(); + const unlocked = Array<{ name: string; range: string; target: string }>(); await workerThreads(16, dependencyMap.entries(), async ([name, pkgs]) => { - console.log(`Checking for updates of ${name}`); - const info = await fetchPackageInfo(name); - const latest = info['dist-tags'].latest; - if (!latest) { - throw new Error(`No latest version found for ${name}`); - } + const target = await findTargetVersion(name); for (const pkg of pkgs) { - if (semver.satisfies(latest, pkg.range)) { - if (semver.minVersion(pkg.range)?.version !== latest) { - unlocked.push({ name, range: pkg.range, latest }); + if (semver.satisfies(target, pkg.range)) { + if (semver.minVersion(pkg.range)?.version !== target) { + unlocked.push({ name, range: pkg.range, target }); } + continue; } versionBumps.set( @@ -69,12 +68,32 @@ export default async () => { (versionBumps.get(pkg.name) ?? []).concat({ name, location: pkg.location, - range: `^${latest}`, // TODO(Rugvip): Option to use something else than ^? + range: `^${target}`, // TODO(Rugvip): Option to use something else than ^? }), ); } }); + // Check for updates of transitive backstage dependencies + await workerThreads(16, lockfile.keys(), async name => { + // Only check @backstage packages and friends, we don't want this to do a full update of all deps + if (!includedFilter(name)) { + return; + } + + const target = await findTargetVersion(name); + + for (const entry of lockfile.get(name) ?? []) { + // Ignore lockfile entries that don't satisfy the version range, since + // these can't cause the package to be locked to an older version + if (!semver.satisfies(target, entry.range)) { + continue; + } + // Unlock all entries that are within range but on the old version + unlocked.push({ name, range: entry.range, target }); + } + }); + console.log(); // Write all discovered version bumps to package.json in this repo @@ -85,17 +104,21 @@ export default async () => { console.log(); if (unlocked.length > 0) { - const lockfile = await Lockfile.load(lockfilePath); - for (const { name, range, latest } of unlocked) { + const removed = new Set(); + for (const { name, range, target } of unlocked) { // Don't bother removing lockfile entries if they're already on the correct version const existingEntry = lockfile.get(name)?.find(e => e.range === range); - if (existingEntry?.version === latest) { + if (existingEntry?.version === target) { continue; } - console.log( - `Removing lockfile entry for ${name}@${range} to bump to ${latest}`, - ); - lockfile.remove(name, range); + const key = JSON.stringify({ name, range }); + if (!removed.has(key)) { + removed.add(key); + console.log( + `Removing lockfile entry for ${name}@${range} to bump to ${target}`, + ); + lockfile.remove(name, range); + } } await lockfile.save(); } @@ -126,24 +149,13 @@ export default async () => { console.log(); // Finally we make sure the new lockfile doesn't have any duplicates - const lockfile = await Lockfile.load(lockfilePath); - const result = lockfile.analyze({ + const dedupLockfile = await Lockfile.load(lockfilePath); + const result = dedupLockfile.analyze({ filter: includedFilter, }); if (result.newVersions.length > 0) { - console.log(); - console.log('Removing duplicate dependencies from yarn.lock'); - lockfile.replaceVersions(result.newVersions); - await lockfile.save(); - - console.log( - "Running 'yarn install' to remove duplicates from node_modules", - ); - console.log(); - await run('yarn', ['install']); - - console.log(); + throw new Error('Duplicate versions present after package bump'); } const forbiddenNewRanges = result.newRanges.filter(({ name }) => @@ -158,6 +170,26 @@ export default async () => { } }; +function createVersionFinder() { + const found = new Map(); + + return async function findTargetVersion(name: string) { + const existing = found.get(name); + if (existing) { + return existing; + } + + console.log(`Checking for updates of ${name}`); + const info = await fetchPackageInfo(name); + const latest = info['dist-tags'].latest; + if (!latest) { + throw new Error(`No latest version found for ${name}`); + } + found.set(name, latest); + return latest; + }; +} + async function workerThreads( count: number, items: IterableIterator, diff --git a/packages/cli/src/lib/versioning/Lockfile.ts b/packages/cli/src/lib/versioning/Lockfile.ts index f9bc789d95..fd7c189b40 100644 --- a/packages/cli/src/lib/versioning/Lockfile.ts +++ b/packages/cli/src/lib/versioning/Lockfile.ts @@ -100,10 +100,16 @@ export class Lockfile { private readonly data: LockfileData, ) {} + /** Get the entries for a single package in the lockfile */ get(name: string): LockfileQueryEntry[] | undefined { return this.packages.get(name); } + /** Returns the name of all packages available in the lockfile */ + keys(): IterableIterator { + return this.packages.keys(); + } + /** Analyzes the lockfile to identify possible actions and warnings for the entries */ analyze(options?: { filter?: (name: string) => boolean }): AnalyzeResult { const { filter } = options ?? {}; From c70d68005d500529bfc6a00825a96a1a57aac52a Mon Sep 17 00:00:00 2001 From: Andrew Thauer <6507159+andrewthauer@users.noreply.github.com> Date: Thu, 26 Nov 2020 14:14:30 -0500 Subject: [PATCH 272/430] fix: add rollbar config to fix example-backend error --- app-config.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app-config.yaml b/app-config.yaml index 546d00466a..66e16506b9 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -64,6 +64,11 @@ techdocs: sentry: organization: my-company +rollbar: + organization: my-company + # NOTE: The rollbar-backend & accountToken key may be deprecated in the future (replaced by a proxy config) + accountToken: my-rollbar-account-token + lighthouse: baseUrl: http://localhost:3003 From 05b54d8b17c9618abe244a3487ba43638dfc1307 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20=C3=85lund?= Date: Thu, 26 Nov 2020 20:26:53 +0100 Subject: [PATCH 273/430] Update docs/getting-started/configure-app-with-plugins.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw --- docs/getting-started/configure-app-with-plugins.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting-started/configure-app-with-plugins.md b/docs/getting-started/configure-app-with-plugins.md index 8b071fb646..44f0abe6a0 100644 --- a/docs/getting-started/configure-app-with-plugins.md +++ b/docs/getting-started/configure-app-with-plugins.md @@ -6,7 +6,7 @@ description: Documentation on How Configuring App with plugins ## Adding existing plugins to your app -The following steps assumes you have created a new Backstage app and want to add +The following steps assume that you have created a new Backstage app and want to add an existing plugin to it. We are using the [CircleCI](https://github.com/backstage/backstage/blob/master/plugins/circleci/README.md) in this example. From 276a9e5919ddb74f9c0a34af4cdb823222969636 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20=C3=85lund?= Date: Thu, 26 Nov 2020 20:27:02 +0100 Subject: [PATCH 274/430] Update docs/getting-started/configure-app-with-plugins.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw --- docs/getting-started/configure-app-with-plugins.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting-started/configure-app-with-plugins.md b/docs/getting-started/configure-app-with-plugins.md index 44f0abe6a0..b32a08b273 100644 --- a/docs/getting-started/configure-app-with-plugins.md +++ b/docs/getting-started/configure-app-with-plugins.md @@ -17,7 +17,7 @@ in this example. yarn add @backstage/plugin-circleci ``` -1. Add plugin API to your Backstage instance: +1. Add the plugin API to your Backstage instance: ```js // packages/app/src/api.ts From 63998e65b1c7d5b7e6086c813e408eb5b0374739 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20=C3=85lund?= Date: Thu, 26 Nov 2020 20:27:10 +0100 Subject: [PATCH 275/430] Update docs/getting-started/configure-app-with-plugins.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw --- docs/getting-started/configure-app-with-plugins.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting-started/configure-app-with-plugins.md b/docs/getting-started/configure-app-with-plugins.md index b32a08b273..aa2bc7ef4d 100644 --- a/docs/getting-started/configure-app-with-plugins.md +++ b/docs/getting-started/configure-app-with-plugins.md @@ -30,7 +30,7 @@ builder.add(circleCIApiRef, new CircleCIApi(/* optional custom url for your own export default builder.build() as ApiHolder; ``` -2. Add plugin itself: +2. Add the plugin itself: ```js // packages/app/src/plugins.ts From dbca620ff9ea28456df27439c9ee77b0f402a63b Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Thu, 26 Nov 2020 14:56:23 +0100 Subject: [PATCH 276/430] Use fs-extra and async file read method --- .../techdocs/stages/generate/helpers.test.ts | 22 +++++++++---------- .../src/techdocs/stages/generate/helpers.ts | 6 ++--- plugins/techdocs/src/api.ts | 2 +- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/plugins/techdocs-backend/src/techdocs/stages/generate/helpers.test.ts b/plugins/techdocs-backend/src/techdocs/stages/generate/helpers.test.ts index 4938739fdf..54ef0b1e83 100644 --- a/plugins/techdocs-backend/src/techdocs/stages/generate/helpers.test.ts +++ b/plugins/techdocs-backend/src/techdocs/stages/generate/helpers.test.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import fs from 'fs'; +import fs from 'fs-extra'; import os from 'os'; import { resolve as resolvePath } from 'path'; import Stream, { PassThrough } from 'stream'; @@ -288,45 +288,43 @@ describe('helpers', () => { mockFs.restore(); }); - it('should add repo_url to mkdocs.yml', () => { + it('should add repo_url to mkdocs.yml', async () => { const parsedLocationAnnotation: ParsedLocationAnnotation = { type: 'github', target: 'https://github.com/backstage/backstage', }; - patchMkdocsYmlPreBuild( + await patchMkdocsYmlPreBuild( '/mkdocs.yml', mockLogger, parsedLocationAnnotation, ); - const updatedMkdocsYml = fs.readFileSync('/mkdocs.yml').toString(); + const updatedMkdocsYml = await fs.readFile('/mkdocs.yml'); - expect(updatedMkdocsYml).toContain( + expect(updatedMkdocsYml.toString()).toContain( "repo_url: 'https://github.com/backstage/backstage'", ); }); - it('should not override existing repo_url in mkdocs.yml', () => { + it('should not override existing repo_url in mkdocs.yml', async () => { const parsedLocationAnnotation: ParsedLocationAnnotation = { type: 'github', target: 'https://github.com/neworg/newrepo', }; - patchMkdocsYmlPreBuild( + await patchMkdocsYmlPreBuild( '/mkdocs_with_repo_url.yml', mockLogger, parsedLocationAnnotation, ); - const updatedMkdocsYml = fs - .readFileSync('/mkdocs_with_repo_url.yml') - .toString(); + const updatedMkdocsYml = await fs.readFile('/mkdocs_with_repo_url.yml'); - expect(updatedMkdocsYml).toContain( + expect(updatedMkdocsYml.toString()).toContain( "repo_url: 'https://github.com/backstage/backstage'", ); - expect(updatedMkdocsYml).not.toContain( + expect(updatedMkdocsYml.toString()).not.toContain( "repo_url: 'https://github.com/neworg/newrepo'", ); }); diff --git a/plugins/techdocs-backend/src/techdocs/stages/generate/helpers.ts b/plugins/techdocs-backend/src/techdocs/stages/generate/helpers.ts index 254186b484..0f60712f5e 100644 --- a/plugins/techdocs-backend/src/techdocs/stages/generate/helpers.ts +++ b/plugins/techdocs-backend/src/techdocs/stages/generate/helpers.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import fs from 'fs'; +import fs from 'fs-extra'; import { spawn } from 'child_process'; import { Writable, PassThrough } from 'stream'; import Docker from 'dockerode'; @@ -231,7 +231,7 @@ export const patchMkdocsYmlPreBuild = async ( ) => { let mkdocsYmlFileString; try { - mkdocsYmlFileString = fs.readFileSync(mkdocsYmlPath, 'utf8'); + mkdocsYmlFileString = await fs.readFile(mkdocsYmlPath, 'utf8'); } catch (error) { logger.warn( `Could not read file ${mkdocsYmlPath} before running the generator. ${error.message}`, @@ -267,7 +267,7 @@ export const patchMkdocsYmlPreBuild = async ( } try { - fs.writeFileSync(mkdocsYmlPath, yaml.safeDump(mkdocsYml), 'utf8'); + await fs.writeFile(mkdocsYmlPath, yaml.safeDump(mkdocsYml), 'utf8'); } catch (error) { logger.warn( `Could not write to ${mkdocsYmlPath} after updating it before running the generator. ${error.message}`, diff --git a/plugins/techdocs/src/api.ts b/plugins/techdocs/src/api.ts index 98e1e6c310..368705fa22 100644 --- a/plugins/techdocs/src/api.ts +++ b/plugins/techdocs/src/api.ts @@ -58,7 +58,7 @@ export class TechDocsApi implements TechDocs { * * When docs are built, we generate a techdocs_metadata.json and store it along with the generated * static files. It includes necessary data about the docs site. This method requests techdocs-backend - * which retries the TechDocs metadata. + * which retrieves the TechDocs metadata. * * @param {ParsedEntityId} entityId Object containing entity data like name, namespace, etc. */ From 0ebfc75b4157129dd96401d55e7db9056a7e3132 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20=C3=85lund?= Date: Thu, 26 Nov 2020 20:50:32 +0100 Subject: [PATCH 277/430] Fix docs --- .../configure-app-with-plugins.md | 21 ++++--------------- plugins/circleci/README.md | 15 +------------ 2 files changed, 5 insertions(+), 31 deletions(-) diff --git a/docs/getting-started/configure-app-with-plugins.md b/docs/getting-started/configure-app-with-plugins.md index aa2bc7ef4d..042d8964ca 100644 --- a/docs/getting-started/configure-app-with-plugins.md +++ b/docs/getting-started/configure-app-with-plugins.md @@ -6,30 +6,17 @@ description: Documentation on How Configuring App with plugins ## Adding existing plugins to your app -The following steps assume that you have created a new Backstage app and want to add -an existing plugin to it. We are using the +The following steps assume that you have created a new Backstage app and want to +add an existing plugin to it. We are using the [CircleCI](https://github.com/backstage/backstage/blob/master/plugins/circleci/README.md) -in this example. +plugin in this example. -0. Add to the repo: +1. Add the plugin's NPM package to the repo: ```bash yarn add @backstage/plugin-circleci ``` -1. Add the plugin API to your Backstage instance: - -```js -// packages/app/src/api.ts -import { ApiHolder } from '@backstage/core'; -import { CircleCIApi, circleCIApiRef } from '@backstage/plugin-circleci'; - -const builder = ApiRegistry.builder(); -builder.add(circleCIApiRef, new CircleCIApi(/* optional custom url for your own CircleCI instance */)); - -export default builder.build() as ApiHolder; -``` - 2. Add the plugin itself: ```js diff --git a/plugins/circleci/README.md b/plugins/circleci/README.md index 4802e8bee2..35fed49827 100644 --- a/plugins/circleci/README.md +++ b/plugins/circleci/README.md @@ -7,25 +7,12 @@ Website: [https://circleci.com/](https://circleci.com/) ## Setup -0. If you have standalone app (you didn't clone this repo), then do +1. If you have standalone app (you didn't clone this repo), then do ```bash yarn add @backstage/plugin-circleci ``` -1. Add plugin API to your Backstage instance: - -```js -// packages/app/src/api.ts -import { ApiHolder } from '@backstage/core'; -import { CircleCIApi, circleCIApiRef } from '@backstage/plugin-circleci'; - -const builder = ApiRegistry.builder(); -builder.add(circleCIApiRef, new CircleCIApi(/* optional custom url for your own CircleCI instance */)); - -export default builder.build() as ApiHolder; -``` - 2. Add plugin itself: ```js From ea475893d72ca0ac30e7d87f371aa7817cf98592 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20=C3=85lund?= Date: Thu, 26 Nov 2020 20:53:22 +0100 Subject: [PATCH 278/430] Create slow-insects-fail.md --- .changeset/slow-insects-fail.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/slow-insects-fail.md diff --git a/.changeset/slow-insects-fail.md b/.changeset/slow-insects-fail.md new file mode 100644 index 0000000000..214092d5ad --- /dev/null +++ b/.changeset/slow-insects-fail.md @@ -0,0 +1,6 @@ +--- +"example-app": patch +"@backstage/create-app": patch +--- + +Add API docs plugin to new apps being created through the CLI. From 5490e16b59db9d6bb7641d07ac5c87cf48a530c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20=C3=85lund?= Date: Thu, 26 Nov 2020 20:58:28 +0100 Subject: [PATCH 279/430] Fix changeset --- .changeset/slow-insects-fail.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.changeset/slow-insects-fail.md b/.changeset/slow-insects-fail.md index 214092d5ad..1e7b3f4087 100644 --- a/.changeset/slow-insects-fail.md +++ b/.changeset/slow-insects-fail.md @@ -1,6 +1,6 @@ --- -"example-app": patch -"@backstage/create-app": patch +'example-app': patch +'@backstage/create-app': patch --- -Add API docs plugin to new apps being created through the CLI. +Add [API docs plugin](https://github.com/backstage/backstage/tree/master/plugins/api-docs) to new apps being created through the CLI. From cd72d6acc418e7b3ae731b7fbc8ffa15e91f5e33 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Thu, 26 Nov 2020 17:33:35 +0100 Subject: [PATCH 280/430] Remove wrong markdown --- .changeset/short-singers-serve.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/short-singers-serve.md b/.changeset/short-singers-serve.md index f1e90f3b55..1bd0f590da 100644 --- a/.changeset/short-singers-serve.md +++ b/.changeset/short-singers-serve.md @@ -12,4 +12,4 @@ After Dec 14th, the fields will be removed from types and classes of the Backsta the first release after that, they will not be present in released packages either. If your catalog-info.yaml files still contain this field after the deletion, they will still be -valid and your ingestion will not break, but they won't be visible in the types for consuming code, and the expected relations will not be generated based on them either.``` +valid and your ingestion will not break, but they won't be visible in the types for consuming code, and the expected relations will not be generated based on them either. From 29429545359f748b9caa0947b37105abf4be50bd Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 26 Nov 2020 22:18:47 +0100 Subject: [PATCH 281/430] cli: only load config schema that applies to the target package --- .changeset/angry-bees-brush.md | 5 +++ packages/cli/src/commands/app/build.ts | 8 +++- packages/cli/src/commands/app/serve.ts | 8 +++- packages/cli/src/commands/config/print.ts | 5 ++- packages/cli/src/commands/config/validate.ts | 5 ++- packages/cli/src/commands/index.ts | 8 ++++ packages/cli/src/commands/plugin/serve.ts | 8 +++- packages/cli/src/lib/config.ts | 46 ++++++++++++++++++-- 8 files changed, 85 insertions(+), 8 deletions(-) create mode 100644 .changeset/angry-bees-brush.md diff --git a/.changeset/angry-bees-brush.md b/.changeset/angry-bees-brush.md new file mode 100644 index 0000000000..aa42d6ef6b --- /dev/null +++ b/.changeset/angry-bees-brush.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Only load config that applies to the target package for frontend build and serve tasks. Also added `--package ` flag to scope the config schema used by the `config:print` and `config:check` commands. diff --git a/packages/cli/src/commands/app/build.ts b/packages/cli/src/commands/app/build.ts index c5f5f5aa2f..21189e9427 100644 --- a/packages/cli/src/commands/app/build.ts +++ b/packages/cli/src/commands/app/build.ts @@ -14,16 +14,22 @@ * limitations under the License. */ +import fs from 'fs-extra'; import { Command } from 'commander'; import { buildBundle } from '../../lib/bundler'; import { parseParallel, PARALLEL_ENV_VAR } from '../../lib/parallel'; import { loadCliConfig } from '../../lib/config'; +import { paths } from '../../lib/paths'; export default async (cmd: Command) => { + const { name } = await fs.readJson(paths.resolveTarget('package.json')); await buildBundle({ entry: 'src/index', parallel: parseParallel(process.env[PARALLEL_ENV_VAR]), statsJsonEnabled: cmd.stats, - ...(await loadCliConfig(cmd.config)), + ...(await loadCliConfig({ + args: cmd.config, + fromPackage: name, + })), }); }; diff --git a/packages/cli/src/commands/app/serve.ts b/packages/cli/src/commands/app/serve.ts index c3f58774b5..dc165ecb90 100644 --- a/packages/cli/src/commands/app/serve.ts +++ b/packages/cli/src/commands/app/serve.ts @@ -14,15 +14,21 @@ * limitations under the License. */ +import fs from 'fs-extra'; import { Command } from 'commander'; import { serveBundle } from '../../lib/bundler'; import { loadCliConfig } from '../../lib/config'; +import { paths } from '../../lib/paths'; export default async (cmd: Command) => { + const { name } = await fs.readJson(paths.resolveTarget('package.json')); const waitForExit = await serveBundle({ entry: 'src/index', checksEnabled: cmd.check, - ...(await loadCliConfig(cmd.config)), + ...(await loadCliConfig({ + args: cmd.config, + fromPackage: name, + })), }); await waitForExit(); diff --git a/packages/cli/src/commands/config/print.ts b/packages/cli/src/commands/config/print.ts index 9649c70ef3..8148bfc9f8 100644 --- a/packages/cli/src/commands/config/print.ts +++ b/packages/cli/src/commands/config/print.ts @@ -21,7 +21,10 @@ import { loadCliConfig } from '../../lib/config'; import { ConfigSchema, ConfigVisibility } from '@backstage/config-loader'; export default async (cmd: Command) => { - const { schema, appConfigs } = await loadCliConfig(cmd.config); + const { schema, appConfigs } = await loadCliConfig({ + args: cmd.config, + fromPackage: cmd.package, + }); const visibility = getVisiblityOption(cmd); const data = serializeConfigData(appConfigs, schema, visibility); diff --git a/packages/cli/src/commands/config/validate.ts b/packages/cli/src/commands/config/validate.ts index 229f9e07e9..581e4bae43 100644 --- a/packages/cli/src/commands/config/validate.ts +++ b/packages/cli/src/commands/config/validate.ts @@ -18,5 +18,8 @@ import { Command } from 'commander'; import { loadCliConfig } from '../../lib/config'; export default async (cmd: Command) => { - await loadCliConfig(cmd.config); + await loadCliConfig({ + args: cmd.config, + fromPackage: cmd.package, + }); }; diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index 7302559498..2b0e3e25b9 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -141,6 +141,10 @@ export function registerCommands(program: CommanderStatic) { program .command('config:print') + .option( + '--package ', + 'Only load config schema that applies to the given package', + ) .option('--frontend', 'Print only the frontend configuration') .option('--with-secrets', 'Include secrets in the printed configuration') .option( @@ -153,6 +157,10 @@ export function registerCommands(program: CommanderStatic) { program .command('config:check') + .option( + '--package ', + 'Only load config schema that applies to the given package', + ) .option(...configOption) .description( 'Validate that the given configuration loads and matches schema', diff --git a/packages/cli/src/commands/plugin/serve.ts b/packages/cli/src/commands/plugin/serve.ts index cb9def3158..27db824d2f 100644 --- a/packages/cli/src/commands/plugin/serve.ts +++ b/packages/cli/src/commands/plugin/serve.ts @@ -14,15 +14,21 @@ * limitations under the License. */ +import fs from 'fs-extra'; import { Command } from 'commander'; import { serveBundle } from '../../lib/bundler'; import { loadCliConfig } from '../../lib/config'; +import { paths } from '../../lib/paths'; export default async (cmd: Command) => { + const { name } = await fs.readJson(paths.resolveTarget('package.json')); const waitForExit = await serveBundle({ entry: 'dev/index', checksEnabled: cmd.check, - ...(await loadCliConfig(cmd.config)), + ...(await loadCliConfig({ + args: cmd.config, + fromPackage: name, + })), }); await waitForExit(); diff --git a/packages/cli/src/lib/config.ts b/packages/cli/src/lib/config.ts index 08a8193818..f6cc9c4269 100644 --- a/packages/cli/src/lib/config.ts +++ b/packages/cli/src/lib/config.ts @@ -18,14 +18,23 @@ import { loadConfig, loadConfigSchema } from '@backstage/config-loader'; import { ConfigReader } from '@backstage/config'; import { paths } from './paths'; -export async function loadCliConfig(configArgs: string[]) { - const configPaths = configArgs.map(arg => paths.resolveTarget(arg)); +type Options = { + args: string[]; + fromPackage?: string; +}; + +export async function loadCliConfig(options: Options) { + const configPaths = options.args.map(arg => paths.resolveTarget(arg)); // Consider all packages in the monorepo when loading in config const LernaProject = require('@lerna/project'); const project = new LernaProject(paths.targetDir); const packages = await project.getPackages(); - const localPackageNames = packages.map((p: any) => p.name); + + const localPackageNames = options.fromPackage + ? findPackages(packages, options.fromPackage) + : packages.map((p: any) => p.name); + const schema = await loadConfigSchema({ dependencies: localPackageNames, }); @@ -61,3 +70,34 @@ export async function loadCliConfig(configArgs: string[]) { throw error; } } + +function findPackages(packages: any[], fromPackage: string): string[] { + const PackageGraph = require('@lerna/package-graph'); + + const graph = new PackageGraph(packages); + + const targets = new Set(); + const searchNames = [fromPackage]; + + while (searchNames.length) { + const name = searchNames.pop()!; + + if (targets.has(name)) { + continue; + } + + const node = graph.get(name); + if (!node) { + throw new Error(`Package '${name}' not found`); + } + + targets.add(name); + + // Workaround for Backstage main repo only, since the CLI has some artificial devDependencies + if (name !== '@backstage/cli') { + searchNames.push(...node.localDependencies.keys()); + } + } + + return Array.from(targets); +} From b3d4e4e57a6c44da31bacf7df3e2dd1d3fbcb04f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 26 Nov 2020 22:27:22 +0100 Subject: [PATCH 282/430] backend-common: move the frontend visibility of config to @backstage/integration --- .changeset/sixty-eyes-shout.md | 6 ++++ packages/backend-common/config.d.ts | 7 ----- packages/integration/config.d.ts | 45 +++++++++++++++++++++++++++++ packages/integration/package.json | 6 ++-- 4 files changed, 55 insertions(+), 9 deletions(-) create mode 100644 .changeset/sixty-eyes-shout.md create mode 100644 packages/integration/config.d.ts diff --git a/.changeset/sixty-eyes-shout.md b/.changeset/sixty-eyes-shout.md new file mode 100644 index 0000000000..36e1c93a26 --- /dev/null +++ b/.changeset/sixty-eyes-shout.md @@ -0,0 +1,6 @@ +--- +'@backstage/backend-common': patch +'@backstage/integration': patch +--- + +Move the frontend visibility declarations of integrations config from @backstage/backend-common to @backstage/integration diff --git a/packages/backend-common/config.d.ts b/packages/backend-common/config.d.ts index c4ec84ab62..8a6c6d1209 100644 --- a/packages/backend-common/config.d.ts +++ b/packages/backend-common/config.d.ts @@ -96,7 +96,6 @@ export interface Config { azure?: Array<{ /** * The hostname of the given Azure instance - * @visibility frontend */ host: string; /** @@ -110,7 +109,6 @@ export interface Config { bitbucket?: Array<{ /** * The hostname of the given Bitbucket instance - * @visibility frontend */ host: string; /** @@ -120,7 +118,6 @@ export interface Config { token?: string; /** * The base url for the BitBucket API, for example https://api.bitbucket.org/2.0 - * @visibility frontend */ apiBaseUrl?: string; /** @@ -139,7 +136,6 @@ export interface Config { github?: Array<{ /** * The hostname of the given GitHub instance - * @visibility frontend */ host: string; /** @@ -149,12 +145,10 @@ export interface Config { token?: string; /** * The base url for the GitHub API, for example https://api.github.com - * @visibility frontend */ apiBaseUrl?: string; /** * The base url for GitHub raw resources, for example https://raw.githubusercontent.com - * @visibility frontend */ rawBaseUrl?: string; }>; @@ -163,7 +157,6 @@ export interface Config { gitlab?: Array<{ /** * The hostname of the given GitLab instance - * @visibility frontend */ host: string; /** diff --git a/packages/integration/config.d.ts b/packages/integration/config.d.ts new file mode 100644 index 0000000000..a03a07409b --- /dev/null +++ b/packages/integration/config.d.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. + */ + +export interface Config { + integrations?: { + azure?: Array<{ + /** @visibility frontend */ + host: string; + }>; + + bitbucket?: Array<{ + /** @visibility frontend */ + host: string; + /** @visibility frontend */ + apiBaseUrl?: string; + }>; + + github?: Array<{ + /** @visibility frontend */ + host: string; + /** @visibility frontend */ + apiBaseUrl?: string; + /** @visibility frontend */ + rawBaseUrl?: string; + }>; + + gitlab?: Array<{ + /** @visibility frontend */ + host: string; + }>; + }; +} diff --git a/packages/integration/package.json b/packages/integration/package.json index 2bc65c46a3..119ce51370 100644 --- a/packages/integration/package.json +++ b/packages/integration/package.json @@ -28,6 +28,8 @@ "@types/jest": "^26.0.7" }, "files": [ - "dist" - ] + "dist", + "config.d.ts" + ], + "configSchema": "config.d.ts" } From 773084bf87987a3ff1474e9f0833e23fc1adac97 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Nov 2020 04:52:26 +0000 Subject: [PATCH 283/430] build(deps-dev): bump @graphql-codegen/typescript-resolvers Bumps [@graphql-codegen/typescript-resolvers](https://github.com/dotansimha/graphql-code-generator/tree/HEAD/packages/plugins/typescript/resolvers) from 1.17.8 to 1.17.12. - [Release notes](https://github.com/dotansimha/graphql-code-generator/releases) - [Changelog](https://github.com/dotansimha/graphql-code-generator/blob/master/packages/plugins/typescript/resolvers/CHANGELOG.md) - [Commits](https://github.com/dotansimha/graphql-code-generator/commits/@graphql-codegen/typescript-resolvers@1.17.12/packages/plugins/typescript/resolvers) Signed-off-by: dependabot[bot] --- yarn.lock | 177 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 103 insertions(+), 74 deletions(-) diff --git a/yarn.lock b/yarn.lock index 4856559521..64c636790a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1880,10 +1880,10 @@ "@graphql-tools/utils" "^6.0.18" tslib "~2.0.0" -"@graphql-codegen/plugin-helpers@^1.17.8", "@graphql-codegen/plugin-helpers@^1.17.9": - version "1.17.9" - resolved "https://registry.npmjs.org/@graphql-codegen/plugin-helpers/-/plugin-helpers-1.17.9.tgz#429f7f75b9f09f5229e42546027613f62ef0d4b1" - integrity sha512-kyj+qsnLGd1JLqXuLpvI6Q/VW7frhoHHNxYJWerpwsSV9m4cttmFj8d9JTfYPZbg6cLIRR7+10lVugzMKozjzA== +"@graphql-codegen/plugin-helpers@^1.17.8", "@graphql-codegen/plugin-helpers@^1.17.9", "@graphql-codegen/plugin-helpers@^1.18.2": + version "1.18.2" + resolved "https://registry.npmjs.org/@graphql-codegen/plugin-helpers/-/plugin-helpers-1.18.2.tgz#57011076cb8b8f5d04d37d226a5eda300c01be94" + integrity sha512-SvX+Ryq2naLcoD6jJNxtzc/moWTgHJ+X0KRfvhGWTa+xtFTS02i+PWOR89YYPcD8+LF6GmyFBjx2FmLCx4JwMg== dependencies: "@graphql-tools/utils" "^6" camel-case "4.1.1" @@ -1898,41 +1898,42 @@ upper-case "2.0.1" "@graphql-codegen/typescript-resolvers@^1.17.7": - version "1.17.8" - resolved "https://registry.npmjs.org/@graphql-codegen/typescript-resolvers/-/typescript-resolvers-1.17.8.tgz#c10af67e54c00520aa33be0403c9e19981d820b5" - integrity sha512-0uMipQjqdRWCW/4I9ZmuZmPuR9jMqKqKph+G/9iAVkfRz3XEa8v1ho+pZ7NhJLWIeiW9NC7B/BKB1UUhKecysg== + version "1.17.12" + resolved "https://registry.npmjs.org/@graphql-codegen/typescript-resolvers/-/typescript-resolvers-1.17.12.tgz#1f8f608d68b46780351c8224a61750b9b9307497" + integrity sha512-qICKxl06R1yCBh03cdyiOYlHTebQ1n/FOiQPCxo4bsiF6HfbrgpMS8fdGEZ8v+VBPxQFZZ7HQ2KcuPNsuw5R6g== dependencies: - "@graphql-codegen/plugin-helpers" "^1.17.8" - "@graphql-codegen/typescript" "^1.17.8" - "@graphql-codegen/visitor-plugin-common" "^1.17.13" - "@graphql-tools/utils" "^6.0.18" + "@graphql-codegen/plugin-helpers" "^1.18.2" + "@graphql-codegen/typescript" "^1.18.1" + "@graphql-codegen/visitor-plugin-common" "^1.17.20" + "@graphql-tools/utils" "^6" auto-bind "~4.0.0" - tslib "~2.0.0" + tslib "~2.0.1" -"@graphql-codegen/typescript@^1.17.7", "@graphql-codegen/typescript@^1.17.8": - version "1.17.8" - resolved "https://registry.npmjs.org/@graphql-codegen/typescript/-/typescript-1.17.8.tgz#08477e532d8342c8a74f75f46f97c85831418e23" - integrity sha512-OQCD4CbnKfHG63u1Sh+UsSKdzkoRHI8NmqnV0Z5ECO4Mm3mrm6fGoYZkKmHbA2tgWIFEsQLevDW+CfduyMd9BA== +"@graphql-codegen/typescript@^1.17.7", "@graphql-codegen/typescript@^1.18.1": + version "1.18.1" + resolved "https://registry.npmjs.org/@graphql-codegen/typescript/-/typescript-1.18.1.tgz#3d730472a01f18aea6331046f4ebfe3d91326801" + integrity sha512-Ee37NutKmaNrgAo2d5mv42RqPd8jJ6zyUKAH669Gbv0dFn2EK3sdC9PYQC9gXptv+H/eQn2gYgaa2nVpEPAIzg== dependencies: - "@graphql-codegen/plugin-helpers" "^1.17.8" - "@graphql-codegen/visitor-plugin-common" "^1.17.13" + "@graphql-codegen/plugin-helpers" "^1.18.2" + "@graphql-codegen/visitor-plugin-common" "^1.17.20" auto-bind "~4.0.0" - tslib "~2.0.0" + tslib "~2.0.1" -"@graphql-codegen/visitor-plugin-common@^1.17.13": - version "1.17.13" - resolved "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-1.17.13.tgz#88dfd8b266aab140a36018807449a1162612e393" - integrity sha512-RWL5VcUQh1mcNb+nr7pDnfoMZ9cn2MurDcZMYGTblIXox+FT33y9Cg4erRjzkVuTvxSf7xXc2TSrSkt9MeZRCw== +"@graphql-codegen/visitor-plugin-common@^1.17.20": + version "1.17.20" + resolved "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-1.17.20.tgz#cff95cdd49cef270b3811fdb141a412ffe2bdfd7" + integrity sha512-buIpcNNyTqVubknancX8m9jARCZsUA5eKuskg+CylWKL/8CSaD2Tiq7CfbbNO10o7XIgRrPtJMl1c9hQ6N4ytw== dependencies: - "@graphql-codegen/plugin-helpers" "^1.17.8" - "@graphql-tools/relay-operation-optimizer" "^6.0.18" - array.prototype.flatmap "^1.2.3" + "@graphql-codegen/plugin-helpers" "^1.18.2" + "@graphql-tools/optimize" "^1.0.1" + "@graphql-tools/relay-operation-optimizer" "^6" + array.prototype.flatmap "^1.2.4" auto-bind "~4.0.0" dependency-graph "^0.9.0" graphql-tag "^2.11.0" parse-filepath "^1.0.2" pascal-case "^3.1.1" - tslib "~2.0.0" + tslib "~2.0.1" "@graphql-modules/core@^0.7.17": version "0.7.17" @@ -2108,6 +2109,13 @@ "@graphql-tools/utils" "^6.2.4" tslib "~2.0.1" +"@graphql-tools/optimize@^1.0.1": + version "1.0.1" + resolved "https://registry.npmjs.org/@graphql-tools/optimize/-/optimize-1.0.1.tgz#9933fffc5a3c63f95102b1cb6076fb16ac7bb22d" + integrity sha512-cRlUNsbErYoBtzzS6zXahXeTBZGPVlPHXCpnEZ0XiK/KY/sQL96cyzak0fM/Gk6qEI9/l32MYEICjasiBQrl5w== + dependencies: + tslib "~2.0.1" + "@graphql-tools/prisma-loader@^6": version "6.2.4" resolved "https://registry.npmjs.org/@graphql-tools/prisma-loader/-/prisma-loader-6.2.4.tgz#3f902b9f1d36ae0c4731e1fe963178bea300af49" @@ -2138,13 +2146,14 @@ tslib "~2.0.1" yaml-ast-parser "^0.0.43" -"@graphql-tools/relay-operation-optimizer@^6.0.18": - version "6.0.18" - resolved "https://registry.npmjs.org/@graphql-tools/relay-operation-optimizer/-/relay-operation-optimizer-6.0.18.tgz#0089d4f222d323aae6d5b266daa5f2bc3689a14e" - integrity sha512-X11M/njvdeGUvER+oJCCFMmnoPPq1Y+W6AXWX8WKeaM4LxdMeYY9mAWfhUyNOX4XZlxUA6zNNH+QwBAyUyZvRA== +"@graphql-tools/relay-operation-optimizer@^6": + version "6.3.0" + resolved "https://registry.npmjs.org/@graphql-tools/relay-operation-optimizer/-/relay-operation-optimizer-6.3.0.tgz#f8c7f6c8aa4a9cf50ab151fbc5db4f4282a79532" + integrity sha512-Or3UgRvkY9Fq1AAx7q38oPqFmTepLz7kp6wDHKyR0ceG7AvHv5En22R12mAeISInbhff4Rpwgf6cE8zHRu6bCw== dependencies: - "@graphql-tools/utils" "6.0.18" - relay-compiler "10.0.1" + "@graphql-tools/utils" "^7.1.0" + relay-compiler "10.1.0" + tslib "~2.0.1" "@graphql-tools/schema@6.0.15": version "6.0.15" @@ -2210,6 +2219,15 @@ camel-case "4.1.1" tslib "~2.0.1" +"@graphql-tools/utils@^7.1.0": + version "7.1.0" + resolved "https://registry.npmjs.org/@graphql-tools/utils/-/utils-7.1.0.tgz#0cbcfa7b6e7741650f78e8bde4823780ed9e8c57" + integrity sha512-lMTgy08BdqQ31zyyCRrxcKZ6gAKQB9amGKJGg0mb3b4I3iJQKeaw9a7T96yGe1frGak1UK9y7OoYqx8RG7KitA== + dependencies: + "@ardatan/aggregate-error" "0.0.6" + camel-case "4.1.1" + tslib "~2.0.1" + "@graphql-tools/wrap@^6.2.4": version "6.2.4" resolved "https://registry.npmjs.org/@graphql-tools/wrap/-/wrap-6.2.4.tgz#2709817da6e469753735a9fe038c9e99736b2c57" @@ -7115,6 +7133,16 @@ array.prototype.flatmap@^1.2.1, array.prototype.flatmap@^1.2.3: es-abstract "^1.17.0-next.1" function-bind "^1.1.1" +array.prototype.flatmap@^1.2.4: + version "1.2.4" + resolved "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.2.4.tgz#94cfd47cc1556ec0747d97f7c7738c58122004c9" + integrity sha512-r9Z0zYoxqHz60vvQbWEdXIEtCwHF0yxaWfno9qzXeNHvfyl3BZqygmGzb84dsubyaXLH4husF+NFgMSdpZhk2Q== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + es-abstract "^1.18.0-next.1" + function-bind "^1.1.1" + array.prototype.map@^1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/array.prototype.map/-/array.prototype.map-1.0.2.tgz#9a4159f416458a23e9483078de1106b2ef68f8ec" @@ -9365,7 +9393,7 @@ core-js@3, core-js@^3.0.1, core-js@^3.0.4, core-js@^3.5.0, core-js@^3.6.0, core- resolved "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz#7395dc273af37fb2e50e9bd3d9fe841285231d1a" integrity sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA== -core-js@^2.4.0, core-js@^2.4.1, core-js@^2.5.7, core-js@^2.6.10, core-js@^2.6.11, core-js@^2.6.5: +core-js@^2.4.0, core-js@^2.5.7, core-js@^2.6.10, core-js@^2.6.11, core-js@^2.6.5: version "2.6.11" resolved "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg== @@ -9476,7 +9504,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== @@ -11017,6 +11045,24 @@ es-abstract@^1.17.5: string.prototype.trimend "^1.0.1" string.prototype.trimstart "^1.0.1" +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" @@ -11881,14 +11927,13 @@ fbjs-css-vars@^1.0.0: resolved "https://registry.npmjs.org/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz#216551136ae02fe255932c3ec8775f18e2c078b8" integrity sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ== -fbjs@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/fbjs/-/fbjs-1.0.0.tgz#52c215e0883a3c86af2a7a776ed51525ae8e0a5a" - integrity sha512-MUgcMEJaFhCaF1QtWGnmq9ZDRAzECTCRAF7O6UZIlAlkTs1SasiX9aP0Iw7wfD2mJ7wDTNfg2w7u5fSCwJk1OA== +fbjs@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/fbjs/-/fbjs-3.0.0.tgz#0907067fb3f57a78f45d95f1eacffcacd623c165" + integrity sha512-dJd4PiDOFuhe7vk4F80Mba83Vr2QuK86FoxtgPmzBqEJahncp+13YCmfoa53KHCo6OnlXLG7eeMWPfB5CrpVKg== dependencies: - core-js "^2.4.1" + cross-fetch "^3.0.4" fbjs-css-vars "^1.0.0" - isomorphic-fetch "^2.1.1" loose-envify "^1.0.0" object-assign "^4.1.0" promise "^7.1.1" @@ -14257,6 +14302,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" @@ -14407,7 +14457,7 @@ is-ssh@^1.3.0: dependencies: protocols "^1.1.0" -is-stream@^1.0.1, is-stream@^1.1.0: +is-stream@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= @@ -14531,14 +14581,6 @@ isobject@^4.0.0: resolved "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz#3f1c9155e73b192022a80819bacd0343711697b0" integrity sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA== -isomorphic-fetch@^2.1.1: - version "2.2.1" - resolved "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" - integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk= - dependencies: - node-fetch "^1.0.1" - whatwg-fetch ">=0.10.0" - isomorphic-fetch@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz#0267b005049046d2421207215d45d6a262b8b8b4" @@ -17256,14 +17298,6 @@ node-fetch@2.6.1, node-fetch@^2.1.2, node-fetch@^2.2.0, node-fetch@^2.3.0, node- resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== -node-fetch@^1.0.1: - version "1.7.3" - resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" - integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ== - dependencies: - encoding "^0.1.11" - is-stream "^1.0.1" - node-forge@^0.10.0: version "0.10.0" resolved "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" @@ -20756,10 +20790,10 @@ relateurl@^0.2.7: resolved "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk= -relay-compiler@10.0.1: - version "10.0.1" - resolved "https://registry.npmjs.org/relay-compiler/-/relay-compiler-10.0.1.tgz#d3029a5121cad52e1e55073210365b827cee5f3b" - integrity sha512-hrTqh81XXxPB4EgvxPmvojICr0wJnRoumxOsMZnS9dmhDHSqcBAT7+C3+rdGm5sSdNH8mbMcZM7YSPDh8ABxQw== +relay-compiler@10.1.0: + version "10.1.0" + resolved "https://registry.npmjs.org/relay-compiler/-/relay-compiler-10.1.0.tgz#fb4672cdbe9b54869a3a79759edd8c2d91609cbe" + integrity sha512-HPqc3N3tNgEgUH5+lTr5lnLbgnsZMt+MRiyS0uAVNhuPY2It0X1ZJG+9qdA3L9IqKFUNwVn6zTO7RArjMZbARQ== dependencies: "@babel/core" "^7.0.0" "@babel/generator" "^7.5.0" @@ -20770,21 +20804,21 @@ relay-compiler@10.0.1: babel-preset-fbjs "^3.3.0" chalk "^4.0.0" fb-watchman "^2.0.0" - fbjs "^1.0.0" + fbjs "^3.0.0" glob "^7.1.1" immutable "~3.7.6" nullthrows "^1.1.1" - relay-runtime "10.0.1" + relay-runtime "10.1.0" signedsource "^1.0.0" yargs "^15.3.1" -relay-runtime@10.0.1: - version "10.0.1" - resolved "https://registry.npmjs.org/relay-runtime/-/relay-runtime-10.0.1.tgz#c83bd7e6e37234ece2a62a254e37dd199a4f74f9" - integrity sha512-sPYiuosq+5gQ7zXs2EKg2O8qRSsF8vmMYo6SIHEi4juBLg1HrdTEvqcaNztc2ZFmUc4vYZpTbbS4j/TZCtHuyA== +relay-runtime@10.1.0: + version "10.1.0" + resolved "https://registry.npmjs.org/relay-runtime/-/relay-runtime-10.1.0.tgz#4753bf36e95e8d862cef33608e3d98b4ed730d16" + integrity sha512-bxznLnQ1ST6APN/cFi7l0FpjbZVchWQjjhj9mAuJBuUqNNCh9uV+UTRhpQF7Q8ycsPp19LHTpVyGhYb0ustuRQ== dependencies: "@babel/runtime" "^7.0.0" - fbjs "^1.0.0" + fbjs "^3.0.0" remark-gfm@^1.0.0: version "1.0.0" @@ -24344,11 +24378,6 @@ whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5: dependencies: iconv-lite "0.4.24" -whatwg-fetch@>=0.10.0: - version "3.3.1" - resolved "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.3.1.tgz#6c1acf37dec176b0fd6bc9a74b616bec2f612935" - integrity sha512-faXTmGDcLuEPBpJwb5LQfyxvubKiE+RlbmmweFGKjvIPFj4uHTTfdtTIkdTRhC6OSH9S9eyYbx8kZ0UEaQqYTA== - whatwg-fetch@^2.0.4: version "2.0.4" resolved "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f" From 7c3e07a9986e015484be128e201676f0d4e7e7f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20=C3=85lund?= Date: Fri, 27 Nov 2020 10:58:24 +0100 Subject: [PATCH 284/430] Fix changeset --- .changeset/slow-insects-fail.md | 1 - 1 file changed, 1 deletion(-) diff --git a/.changeset/slow-insects-fail.md b/.changeset/slow-insects-fail.md index 1e7b3f4087..a07e025f34 100644 --- a/.changeset/slow-insects-fail.md +++ b/.changeset/slow-insects-fail.md @@ -1,5 +1,4 @@ --- -'example-app': patch '@backstage/create-app': patch --- From 60b290cd0bcbe4e1fccf04897041836c3f051f0d Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Fri, 27 Nov 2020 11:10:04 +0100 Subject: [PATCH 285/430] use discoveryApi and update readme --- plugins/pagerduty/README.md | 9 ++++----- plugins/pagerduty/src/api/client.ts | 17 +++++++++++++---- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/plugins/pagerduty/README.md b/plugins/pagerduty/README.md index 9c5c0b2485..da37282aa7 100644 --- a/plugins/pagerduty/README.md +++ b/plugins/pagerduty/README.md @@ -47,18 +47,17 @@ import { ## Client configuration -The PagerDutyClient can be configured with the appropriate urls: +The client takes a config object which contains an instance of the `discoveryApi` which is used to look up the `baseUrl` of the `proxy`, and a `eventUrl` which is defaulted to: "https://events.pagerduty.com/v2". In `apis.ts`: ```ts createApiFactory({ api: pagerDutyApiRef, - deps: { configApi: configApiRef }, - factory: ({ configApi }) => + deps: { discoveryApi: discoveryApiRef }, + factory: ({ discoveryApi }) => new PagerDutyClientApi({ - api_url: `https://api.pagerduty.com`, - events_url: 'https://events.pagerduty.com/v2', + discoveryApi: discoveryApi }), }), ``` diff --git a/plugins/pagerduty/src/api/client.ts b/plugins/pagerduty/src/api/client.ts index c9d40a1694..4ed994eec2 100644 --- a/plugins/pagerduty/src/api/client.ts +++ b/plugins/pagerduty/src/api/client.ts @@ -39,7 +39,9 @@ export class PagerDutyClientApi implements PagerDutyClient { async getServiceByIntegrationKey(integrationKey: string): Promise { const params = `include[]=integrations&include[]=escalation_policies&query=${integrationKey}`; - const url = `${this.config.api_url}/services?${params}`; + const url = `${await this.config.discoveryApi.getBaseUrl( + 'proxy', + )}/pagerduty/services?${params}`; const { services } = await this.getByUrl(url); return services; @@ -47,7 +49,9 @@ export class PagerDutyClientApi implements PagerDutyClient { async getIncidentsByServiceId(serviceId: string): Promise { const params = `service_ids[]=${serviceId}`; - const url = `${this.config.api_url}/incidents?${params}`; + const url = `${await this.config.discoveryApi.getBaseUrl( + 'proxy', + )}/pagerduty/incidents?${params}`; const { incidents } = await this.getByUrl(url); return incidents; @@ -55,7 +59,9 @@ export class PagerDutyClientApi implements PagerDutyClient { async getOnCallByPolicyId(policyId: string): Promise { const params = `include[]=users&escalation_policy_ids[]=${policyId}`; - const url = `${this.config.api_url}/oncalls?${params}`; + const url = `${await this.config.discoveryApi.getBaseUrl( + 'proxy', + )}/pagerduty/oncalls?${params}`; const { oncalls } = await this.getByUrl(url); return oncalls; @@ -92,7 +98,10 @@ export class PagerDutyClientApi implements PagerDutyClient { body, }; - return this.request(`${this.config.events_url}/enqueue`, options); + return this.request( + `${this.config.eventsUrl ?? 'https://events.pagerduty.com/v2'}/enqueue`, + options, + ); } private async getByUrl(url: string): Promise { From 8432bde46ab2c8bbdd5bee59de35e905b85c3572 Mon Sep 17 00:00:00 2001 From: Dominik Henneke Date: Fri, 27 Nov 2020 11:11:03 +0100 Subject: [PATCH 286/430] Use inline type instead of a dedicated interface --- plugins/proxy-backend/config.d.ts | 62 ++++++++++++++++--------------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/plugins/proxy-backend/config.d.ts b/plugins/proxy-backend/config.d.ts index 122d052c1c..8bb7e958b8 100644 --- a/plugins/proxy-backend/config.d.ts +++ b/plugins/proxy-backend/config.d.ts @@ -20,34 +20,36 @@ export interface Config { * below the prefix that the proxy plugin is mounted on. It must * start with a '/'. */ - proxy: { [key: string]: string | ProxyConfig }; -} - -interface ProxyConfig { - /** - * Target of the proxy. Url string to be parsed with the url module. - */ - target: string; - /** - * Object with extra headers to be added to target requests. - */ - headers?: { [key: string]: string }; - /** - * Changes the origin of the host header to the target URL. Default: true. - */ - changeOrigin?: boolean; - /** - * Rewrite target's url path. Object-keys will be used as RegExp to match paths. - * If pathRewrite is not specified, it is set to a single rewrite that removes the entire prefix and route. - */ - pathRewrite?: { [regexp: string]: string }; - /** - * Limit the forwarded HTTP methods, for example allowedMethods: ['GET'] to enforce read-only access. - */ - allowedMethods?: string[]; - /** - * Limit the forwarded HTTP methods. By default, only the headers that are considered safe for CORS - * and headers that are set by the proxy will be forwarded. - */ - allowedHeaders?: string[]; + proxy: { + [key: string]: + | string + | { + /** + * Target of the proxy. Url string to be parsed with the url module. + */ + target: string; + /** + * Object with extra headers to be added to target requests. + */ + headers?: { [key: string]: string }; + /** + * Changes the origin of the host header to the target URL. Default: true. + */ + changeOrigin?: boolean; + /** + * Rewrite target's url path. Object-keys will be used as RegExp to match paths. + * If pathRewrite is not specified, it is set to a single rewrite that removes the entire prefix and route. + */ + pathRewrite?: { [regexp: string]: string }; + /** + * Limit the forwarded HTTP methods, for example allowedMethods: ['GET'] to enforce read-only access. + */ + allowedMethods?: string[]; + /** + * Limit the forwarded HTTP methods. By default, only the headers that are considered safe for CORS + * and headers that are set by the proxy will be forwarded. + */ + allowedHeaders?: string[]; + }; + }; } From aa88559488d9070f93471a716b235ab07c55eb3e Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Fri, 27 Nov 2020 11:24:19 +0100 Subject: [PATCH 287/430] use discoveryApi --- plugins/pagerduty/src/components/types.tsx | 6 ++++-- plugins/pagerduty/src/plugin.ts | 11 ++++------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/plugins/pagerduty/src/components/types.tsx b/plugins/pagerduty/src/components/types.tsx index e968656e65..5e5ee0a61b 100644 --- a/plugins/pagerduty/src/components/types.tsx +++ b/plugins/pagerduty/src/components/types.tsx @@ -14,6 +14,8 @@ * limitations under the License. */ +import { DiscoveryApi } from '@backstage/core'; + export type Incident = { id: string; title: string; @@ -76,6 +78,6 @@ export type RequestOptions = { }; export type ClientApiConfig = { - api_url: string; - events_url: string; + eventsUrl?: string; + discoveryApi: DiscoveryApi; }; diff --git a/plugins/pagerduty/src/plugin.ts b/plugins/pagerduty/src/plugin.ts index 2e07b5c430..02b11ed773 100644 --- a/plugins/pagerduty/src/plugin.ts +++ b/plugins/pagerduty/src/plugin.ts @@ -17,7 +17,7 @@ import { createApiFactory, createPlugin, createRouteRef, - configApiRef, + discoveryApiRef, } from '@backstage/core'; import { pagerDutyApiRef, PagerDutyClientApi } from './api'; @@ -31,13 +31,10 @@ export const plugin = createPlugin({ apis: [ createApiFactory({ api: pagerDutyApiRef, - deps: { configApi: configApiRef }, - factory: ({ configApi }) => + deps: { discoveryApi: discoveryApiRef }, + factory: ({ discoveryApi }) => new PagerDutyClientApi({ - api_url: `${configApi.getString( - 'backend.baseUrl', - )}/api/proxy/pagerduty`, - events_url: 'https://events.pagerduty.com/v2', + discoveryApi: discoveryApi, }), }), ], From 0e1f240a99dff96087b626365d5b23f73e487594 Mon Sep 17 00:00:00 2001 From: Dominik Henneke Date: Fri, 27 Nov 2020 11:24:59 +0100 Subject: [PATCH 288/430] Change the proxy setting to be optional --- plugins/proxy-backend/config.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/proxy-backend/config.d.ts b/plugins/proxy-backend/config.d.ts index 8bb7e958b8..e9b0f45893 100644 --- a/plugins/proxy-backend/config.d.ts +++ b/plugins/proxy-backend/config.d.ts @@ -20,7 +20,7 @@ export interface Config { * below the prefix that the proxy plugin is mounted on. It must * start with a '/'. */ - proxy: { + proxy?: { [key: string]: | string | { From b623cc275ce5aef11bf8d8ffa71aba76a025b35b Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 27 Nov 2020 13:15:23 +0100 Subject: [PATCH 289/430] cli: narrow version range of rollup-plugin-esbuild to avoid breaking change --- .changeset/yellow-colts-burn.md | 5 +++++ packages/cli/package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/yellow-colts-burn.md diff --git a/.changeset/yellow-colts-burn.md b/.changeset/yellow-colts-burn.md new file mode 100644 index 0000000000..5d4a132b66 --- /dev/null +++ b/.changeset/yellow-colts-burn.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Narrow down the version range of rollup-plugin-esbuild to avoid breaking change in newer version diff --git a/packages/cli/package.json b/packages/cli/package.json index b79a8ec37a..db7c127128 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -88,7 +88,7 @@ "replace-in-file": "^6.0.0", "rollup": "2.33.x", "rollup-plugin-dts": "1.4.13", - "rollup-plugin-esbuild": "^2.0.0", + "rollup-plugin-esbuild": "2.3.x", "rollup-plugin-peer-deps-external": "^2.2.2", "rollup-plugin-postcss": "^3.1.1", "rollup-plugin-typescript2": "^0.27.3", From 892645b8151e96f2a7ef437e1698b2ee45678294 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 27 Nov 2020 12:51:09 +0000 Subject: [PATCH 290/430] Version Packages --- .changeset/angry-bees-brush.md | 5 ----- .changeset/beige-mugs-visit.md | 5 ----- .changeset/brave-rats-obey.md | 5 ----- .changeset/chilled-zoos-rush.md | 7 ------- .changeset/eleven-lobsters-train.md | 7 ------- .changeset/few-kings-agree.md | 5 ----- .changeset/fifty-nails-pay.md | 6 ------ .changeset/five-dryers-shout.md | 18 ----------------- .changeset/good-cycles-shave.md | 6 ------ .changeset/gorgeous-games-build.md | 6 ------ .changeset/long-birds-rush.md | 5 ----- .changeset/pink-goats-sort.md | 6 ------ .changeset/short-singers-serve.md | 15 -------------- .changeset/shy-humans-protect.md | 5 ----- .changeset/sixty-eyes-shout.md | 6 ------ .changeset/ten-doors-exist.md | 6 ------ .changeset/yellow-colts-burn.md | 5 ----- packages/app/CHANGELOG.md | 22 ++++++++++++++++++++ packages/app/package.json | 14 ++++++------- packages/backend-common/CHANGELOG.md | 9 +++++++++ packages/backend-common/package.json | 6 +++--- packages/backend/CHANGELOG.md | 24 ++++++++++++++++++++++ packages/backend/package.json | 20 +++++++++--------- packages/catalog-model/CHANGELOG.md | 18 +++++++++++++++++ packages/catalog-model/package.json | 4 ++-- packages/cli/CHANGELOG.md | 9 +++++++++ packages/cli/package.json | 4 ++-- packages/core-api/CHANGELOG.md | 6 ++++++ packages/core-api/package.json | 4 ++-- packages/create-app/package.json | 22 ++++++++++---------- packages/integration/CHANGELOG.md | 6 ++++++ packages/integration/package.json | 4 ++-- plugins/api-docs/CHANGELOG.md | 18 +++++++++++++++++ plugins/api-docs/package.json | 8 ++++---- plugins/app-backend/CHANGELOG.md | 9 +++++++++ plugins/app-backend/package.json | 6 +++--- plugins/auth-backend/CHANGELOG.md | 14 +++++++++++++ plugins/auth-backend/package.json | 8 ++++---- plugins/catalog-backend/CHANGELOG.md | 29 +++++++++++++++++++++++++++ plugins/catalog-backend/package.json | 8 ++++---- plugins/catalog/CHANGELOG.md | 12 +++++++++++ plugins/catalog/package.json | 8 ++++---- plugins/rollbar-backend/CHANGELOG.md | 9 +++++++++ plugins/rollbar-backend/package.json | 6 +++--- plugins/rollbar/CHANGELOG.md | 12 +++++++++++ plugins/rollbar/package.json | 8 ++++---- plugins/techdocs-backend/CHANGELOG.md | 17 ++++++++++++++++ plugins/techdocs-backend/package.json | 8 ++++---- plugins/techdocs/CHANGELOG.md | 18 +++++++++++++++++ plugins/techdocs/package.json | 10 ++++----- 50 files changed, 306 insertions(+), 192 deletions(-) delete mode 100644 .changeset/angry-bees-brush.md delete mode 100644 .changeset/beige-mugs-visit.md delete mode 100644 .changeset/brave-rats-obey.md delete mode 100644 .changeset/chilled-zoos-rush.md delete mode 100644 .changeset/eleven-lobsters-train.md delete mode 100644 .changeset/few-kings-agree.md delete mode 100644 .changeset/fifty-nails-pay.md delete mode 100644 .changeset/five-dryers-shout.md delete mode 100644 .changeset/good-cycles-shave.md delete mode 100644 .changeset/gorgeous-games-build.md delete mode 100644 .changeset/long-birds-rush.md delete mode 100644 .changeset/pink-goats-sort.md delete mode 100644 .changeset/short-singers-serve.md delete mode 100644 .changeset/shy-humans-protect.md delete mode 100644 .changeset/sixty-eyes-shout.md delete mode 100644 .changeset/ten-doors-exist.md delete mode 100644 .changeset/yellow-colts-burn.md diff --git a/.changeset/angry-bees-brush.md b/.changeset/angry-bees-brush.md deleted file mode 100644 index aa42d6ef6b..0000000000 --- a/.changeset/angry-bees-brush.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Only load config that applies to the target package for frontend build and serve tasks. Also added `--package ` flag to scope the config schema used by the `config:print` and `config:check` commands. diff --git a/.changeset/beige-mugs-visit.md b/.changeset/beige-mugs-visit.md deleted file mode 100644 index 4b5f0f1bdd..0000000000 --- a/.changeset/beige-mugs-visit.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-auth-backend': patch ---- - -Allow the backend to register custom AuthProviderFactories diff --git a/.changeset/brave-rats-obey.md b/.changeset/brave-rats-obey.md deleted file mode 100644 index a09d922763..0000000000 --- a/.changeset/brave-rats-obey.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-app-backend': patch ---- - -Warn if the app-backend can't start-up because the static directory that should be served is unavailable. diff --git a/.changeset/chilled-zoos-rush.md b/.changeset/chilled-zoos-rush.md deleted file mode 100644 index 6830cb7fcf..0000000000 --- a/.changeset/chilled-zoos-rush.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/plugin-api-docs': minor ---- - -APIs now have real entity pages that are customizable in the app. -Therefore the old entity page from this plugin is removed. -See the `packages/app` on how to create and customize the API entity page. diff --git a/.changeset/eleven-lobsters-train.md b/.changeset/eleven-lobsters-train.md deleted file mode 100644 index 95b9145ee6..0000000000 --- a/.changeset/eleven-lobsters-train.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/plugin-techdocs': minor -'@backstage/plugin-techdocs-backend': minor ---- - -- Use techdocs annotation to add repo_url if missing in mkdocs.yml. Having repo_url creates a Edit button on techdocs pages. -- techdocs-backend: API endpoint `/metadata/mkdocs/*` renamed to `/metadata/techdocs/*` diff --git a/.changeset/few-kings-agree.md b/.changeset/few-kings-agree.md deleted file mode 100644 index 0019611503..0000000000 --- a/.changeset/few-kings-agree.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-common': patch ---- - -Added support for passing false as a CSP field value, to drop it from the defaults in the backend diff --git a/.changeset/fifty-nails-pay.md b/.changeset/fifty-nails-pay.md deleted file mode 100644 index 5880d9a8cc..0000000000 --- a/.changeset/fifty-nails-pay.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-api-docs': patch -'@backstage/plugin-catalog': patch ---- - -Replace usage of implementsApis with relations diff --git a/.changeset/five-dryers-shout.md b/.changeset/five-dryers-shout.md deleted file mode 100644 index fbcc85182d..0000000000 --- a/.changeset/five-dryers-shout.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': patch ---- - -Ignore empty YAML documents. Having a YAML file like this is now ingested without an error: - -```yaml -apiVersion: backstage.io/v1alpha1 -kind: Component -metadata: - name: web -spec: - type: website ---- - -``` - -This behaves now the same way as Kubernetes handles multiple documents in a single YAML file. diff --git a/.changeset/good-cycles-shave.md b/.changeset/good-cycles-shave.md deleted file mode 100644 index 90a9410b9c..0000000000 --- a/.changeset/good-cycles-shave.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/catalog-model': patch -'@backstage/plugin-catalog-backend': patch ---- - -Add `providesApis` and `consumesApis` to the component entity spec. diff --git a/.changeset/gorgeous-games-build.md b/.changeset/gorgeous-games-build.md deleted file mode 100644 index ff050c43f6..0000000000 --- a/.changeset/gorgeous-games-build.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-rollbar': patch -'@backstage/plugin-rollbar-backend': patch ---- - -Add config schema for the rollbar & rollbar-backend plugins diff --git a/.changeset/long-birds-rush.md b/.changeset/long-birds-rush.md deleted file mode 100644 index b1ce6aa125..0000000000 --- a/.changeset/long-birds-rush.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Make versions:bump install new versions of dependencies that were within the specified range as well as install new versions of transitive @backstage dependencies. diff --git a/.changeset/pink-goats-sort.md b/.changeset/pink-goats-sort.md deleted file mode 100644 index 828cccba84..0000000000 --- a/.changeset/pink-goats-sort.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/catalog-model': patch -'@backstage/plugin-catalog-backend': patch ---- - -Start emitting all known relation types from the core entity kinds, based on their spec data. diff --git a/.changeset/short-singers-serve.md b/.changeset/short-singers-serve.md deleted file mode 100644 index 1bd0f590da..0000000000 --- a/.changeset/short-singers-serve.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -'@backstage/catalog-model': patch ---- - -Marked the field `spec.implementsApis` on `Component` entities for deprecation on Dec 14th, 2020. - -Code that consumes these fields should remove those usages as soon as possible and migrate to using -relations instead. Producers should fill the field `spec.providesApis` instead, which has the same -semantic. - -After Dec 14th, the fields will be removed from types and classes of the Backstage repository. At -the first release after that, they will not be present in released packages either. - -If your catalog-info.yaml files still contain this field after the deletion, they will still be -valid and your ingestion will not break, but they won't be visible in the types for consuming code, and the expected relations will not be generated based on them either. diff --git a/.changeset/shy-humans-protect.md b/.changeset/shy-humans-protect.md deleted file mode 100644 index 612cb9faf8..0000000000 --- a/.changeset/shy-humans-protect.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Bump Rollup diff --git a/.changeset/sixty-eyes-shout.md b/.changeset/sixty-eyes-shout.md deleted file mode 100644 index 36e1c93a26..0000000000 --- a/.changeset/sixty-eyes-shout.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/backend-common': patch -'@backstage/integration': patch ---- - -Move the frontend visibility declarations of integrations config from @backstage/backend-common to @backstage/integration diff --git a/.changeset/ten-doors-exist.md b/.changeset/ten-doors-exist.md deleted file mode 100644 index 2dafaf728d..0000000000 --- a/.changeset/ten-doors-exist.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/core-api': patch -'@backstage/plugin-auth-backend': patch ---- - -bug fix: issue 3223 - detect mismatching origin and indicate it in the message at auth failure diff --git a/.changeset/yellow-colts-burn.md b/.changeset/yellow-colts-burn.md deleted file mode 100644 index 5d4a132b66..0000000000 --- a/.changeset/yellow-colts-burn.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Narrow down the version range of rollup-plugin-esbuild to avoid breaking change in newer version diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index f70cd198b4..a6c8da7fd9 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,27 @@ # example-app +## 0.2.4 + +### Patch Changes + +- Updated dependencies [294295453] +- Updated dependencies [f3bb55ee3] +- Updated dependencies [4b53294a6] +- Updated dependencies [6f70ed7a9] +- Updated dependencies [ab94c9542] +- Updated dependencies [3a201c5d5] +- Updated dependencies [f538e2c56] +- Updated dependencies [2daf18e80] +- Updated dependencies [069cda35f] +- Updated dependencies [8697dea5b] +- Updated dependencies [b623cc275] + - @backstage/cli@0.3.2 + - @backstage/plugin-api-docs@0.3.0 + - @backstage/plugin-techdocs@0.3.0 + - @backstage/plugin-catalog@0.2.4 + - @backstage/catalog-model@0.3.1 + - @backstage/plugin-rollbar@0.2.4 + ## 0.2.3 ### Patch Changes diff --git a/packages/app/package.json b/packages/app/package.json index 9e54005cd3..bde325ed46 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,14 +1,14 @@ { "name": "example-app", - "version": "0.2.3", + "version": "0.2.4", "private": true, "bundled": true, "dependencies": { - "@backstage/catalog-model": "^0.3.0", - "@backstage/cli": "^0.3.1", + "@backstage/catalog-model": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/core": "^0.3.2", - "@backstage/plugin-api-docs": "^0.2.2", - "@backstage/plugin-catalog": "^0.2.3", + "@backstage/plugin-api-docs": "^0.3.0", + "@backstage/plugin-catalog": "^0.2.4", "@backstage/plugin-circleci": "^0.2.2", "@backstage/plugin-cloudbuild": "^0.2.2", "@backstage/plugin-cost-insights": "^0.4.1", @@ -22,12 +22,12 @@ "@backstage/plugin-lighthouse": "^0.2.3", "@backstage/plugin-newrelic": "^0.2.1", "@backstage/plugin-register-component": "^0.2.2", - "@backstage/plugin-rollbar": "^0.2.3", + "@backstage/plugin-rollbar": "^0.2.4", "@backstage/plugin-scaffolder": "^0.3.1", "@backstage/plugin-sentry": "^0.2.3", "@backstage/plugin-search": "^0.2.1", "@backstage/plugin-tech-radar": "^0.3.0", - "@backstage/plugin-techdocs": "^0.2.3", + "@backstage/plugin-techdocs": "^0.3.0", "@backstage/plugin-user-settings": "^0.2.2", "@backstage/plugin-welcome": "^0.2.1", "@backstage/test-utils": "^0.1.3", diff --git a/packages/backend-common/CHANGELOG.md b/packages/backend-common/CHANGELOG.md index 793a554905..aec6c37a49 100644 --- a/packages/backend-common/CHANGELOG.md +++ b/packages/backend-common/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/backend-common +## 0.3.2 + +### Patch Changes + +- 3aa7efb3f: Added support for passing false as a CSP field value, to drop it from the defaults in the backend +- b3d4e4e57: Move the frontend visibility declarations of integrations config from @backstage/backend-common to @backstage/integration +- Updated dependencies [b3d4e4e57] + - @backstage/integration@0.1.2 + ## 0.3.1 ### Patch Changes diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 731e2679cb..ca92f70e98 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-common", "description": "Common functionality library for Backstage backends", - "version": "0.3.1", + "version": "0.3.2", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -32,7 +32,7 @@ "@backstage/cli-common": "^0.1.1", "@backstage/config": "^0.1.1", "@backstage/config-loader": "^0.3.0", - "@backstage/integration": "^0.1.1", + "@backstage/integration": "^0.1.2", "@types/cors": "^2.8.6", "@types/express": "^4.17.6", "archiver": "^5.0.2", @@ -67,7 +67,7 @@ } }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/test-utils": "^0.1.3", "@types/archiver": "^3.1.1", "@types/compression": "^1.7.0", diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index bc2a08e983..d47e9003a5 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,29 @@ # example-backend +## 0.2.4 + +### Patch Changes + +- Updated dependencies [50eff1d00] +- Updated dependencies [ff1301d28] +- Updated dependencies [4b53294a6] +- Updated dependencies [3aa7efb3f] +- Updated dependencies [1ec19a3f4] +- Updated dependencies [ab94c9542] +- Updated dependencies [3a201c5d5] +- Updated dependencies [2daf18e80] +- Updated dependencies [069cda35f] +- Updated dependencies [b3d4e4e57] +- Updated dependencies [700a212b4] + - @backstage/plugin-auth-backend@0.2.4 + - @backstage/plugin-app-backend@0.3.1 + - @backstage/plugin-techdocs-backend@0.3.0 + - @backstage/backend-common@0.3.2 + - @backstage/plugin-catalog-backend@0.2.3 + - @backstage/catalog-model@0.3.1 + - @backstage/plugin-rollbar-backend@0.1.4 + - example-app@0.2.4 + ## 0.2.3 ### Patch Changes diff --git a/packages/backend/package.json b/packages/backend/package.json index 5d3678a284..beb2567bd7 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.2.3", + "version": "0.2.4", "main": "dist/index.cjs.js", "types": "src/index.ts", "private": true, @@ -18,24 +18,24 @@ "migrate:create": "knex migrate:make -x ts" }, "dependencies": { - "@backstage/backend-common": "^0.3.1", - "@backstage/catalog-model": "^0.3.0", + "@backstage/backend-common": "^0.3.2", + "@backstage/catalog-model": "^0.3.1", "@backstage/config": "^0.1.1", - "@backstage/plugin-app-backend": "^0.3.0", - "@backstage/plugin-auth-backend": "^0.2.3", - "@backstage/plugin-catalog-backend": "^0.2.2", + "@backstage/plugin-app-backend": "^0.3.1", + "@backstage/plugin-auth-backend": "^0.2.4", + "@backstage/plugin-catalog-backend": "^0.2.3", "@backstage/plugin-graphql-backend": "^0.1.3", "@backstage/plugin-kubernetes-backend": "^0.2.0", "@backstage/plugin-proxy-backend": "^0.2.1", - "@backstage/plugin-rollbar-backend": "^0.1.3", + "@backstage/plugin-rollbar-backend": "^0.1.4", "@backstage/plugin-scaffolder-backend": "^0.3.2", "@backstage/plugin-sentry-backend": "^0.1.3", - "@backstage/plugin-techdocs-backend": "^0.2.2", + "@backstage/plugin-techdocs-backend": "^0.3.0", "@gitbeaker/node": "^25.2.0", "@octokit/rest": "^18.0.0", "azure-devops-node-api": "^10.1.1", "dockerode": "^3.2.0", - "example-app": "^0.2.3", + "example-app": "^0.2.4", "express": "^4.17.1", "express-promise-router": "^3.0.3", "knex": "^0.21.6", @@ -45,7 +45,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@types/dockerode": "^2.5.32", "@types/express": "^4.17.6", "@types/express-serve-static-core": "^4.17.5", diff --git a/packages/catalog-model/CHANGELOG.md b/packages/catalog-model/CHANGELOG.md index 10bf540b46..630664e491 100644 --- a/packages/catalog-model/CHANGELOG.md +++ b/packages/catalog-model/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/catalog-model +## 0.3.1 + +### Patch Changes + +- ab94c9542: Add `providesApis` and `consumesApis` to the component entity spec. +- 2daf18e80: Start emitting all known relation types from the core entity kinds, based on their spec data. +- 069cda35f: Marked the field `spec.implementsApis` on `Component` entities for deprecation on Dec 14th, 2020. + + Code that consumes these fields should remove those usages as soon as possible and migrate to using + relations instead. Producers should fill the field `spec.providesApis` instead, which has the same + semantic. + + After Dec 14th, the fields will be removed from types and classes of the Backstage repository. At + the first release after that, they will not be present in released packages either. + + If your catalog-info.yaml files still contain this field after the deletion, they will still be + valid and your ingestion will not break, but they won't be visible in the types for consuming code, and the expected relations will not be generated based on them either. + ## 0.3.0 ### Minor Changes diff --git a/packages/catalog-model/package.json b/packages/catalog-model/package.json index 7956454ca9..53f56c6600 100644 --- a/packages/catalog-model/package.json +++ b/packages/catalog-model/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/catalog-model", - "version": "0.3.0", + "version": "0.3.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,7 +29,7 @@ "yup": "^0.29.3" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@types/express": "^4.17.6", "@types/jest": "^26.0.7", "@types/lodash": "^4.14.151", diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index d198444c86..d1770eaf1f 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/cli +## 0.3.2 + +### Patch Changes + +- 294295453: Only load config that applies to the target package for frontend build and serve tasks. Also added `--package ` flag to scope the config schema used by the `config:print` and `config:check` commands. +- f538e2c56: Make versions:bump install new versions of dependencies that were within the specified range as well as install new versions of transitive @backstage dependencies. +- 8697dea5b: Bump Rollup +- b623cc275: Narrow down the version range of rollup-plugin-esbuild to avoid breaking change in newer version + ## 0.3.1 ### Patch Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index db7c127128..5d8826703c 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/cli", "description": "CLI for developing Backstage plugins and apps", - "version": "0.3.1", + "version": "0.3.2", "private": false, "publishConfig": { "access": "public" @@ -111,7 +111,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/backend-common": "^0.3.1", + "@backstage/backend-common": "^0.3.2", "@backstage/config": "^0.1.1", "@backstage/core": "^0.3.2", "@backstage/dev-utils": "^0.1.4", diff --git a/packages/core-api/CHANGELOG.md b/packages/core-api/CHANGELOG.md index 63926daf24..79bed2dfa3 100644 --- a/packages/core-api/CHANGELOG.md +++ b/packages/core-api/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/core-api +## 0.2.3 + +### Patch Changes + +- 700a212b4: bug fix: issue 3223 - detect mismatching origin and indicate it in the message at auth failure + ## 0.2.2 ### Patch Changes diff --git a/packages/core-api/package.json b/packages/core-api/package.json index c39dca3b7a..fe56d11b9e 100644 --- a/packages/core-api/package.json +++ b/packages/core-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-api", "description": "Internal Core API used by Backstage plugins and apps", - "version": "0.2.2", + "version": "0.2.3", "private": false, "publishConfig": { "access": "public", @@ -42,7 +42,7 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.2", "@backstage/test-utils-core": "^0.1.1", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 6e91a916a1..adb9d30465 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -37,28 +37,28 @@ "recursive-readdir": "^2.2.2" }, "devDependencies": { - "@backstage/backend-common": "^0.3.1", - "@backstage/catalog-model": "^0.3.0", - "@backstage/cli": "^0.3.1", + "@backstage/backend-common": "^0.3.2", + "@backstage/catalog-model": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/config": "^0.1.1", "@backstage/core": "^0.3.2", - "@backstage/plugin-api-docs": "^0.2.2", - "@backstage/plugin-app-backend": "^0.3.0", - "@backstage/plugin-auth-backend": "^0.2.3", - "@backstage/plugin-catalog": "^0.2.3", - "@backstage/plugin-catalog-backend": "^0.2.2", + "@backstage/plugin-api-docs": "^0.3.0", + "@backstage/plugin-app-backend": "^0.3.1", + "@backstage/plugin-auth-backend": "^0.2.4", + "@backstage/plugin-catalog": "^0.2.4", + "@backstage/plugin-catalog-backend": "^0.2.3", "@backstage/plugin-circleci": "^0.2.2", "@backstage/plugin-explore": "^0.2.1", "@backstage/plugin-github-actions": "^0.2.2", "@backstage/plugin-lighthouse": "^0.2.3", "@backstage/plugin-proxy-backend": "^0.2.1", "@backstage/plugin-register-component": "^0.2.2", - "@backstage/plugin-rollbar-backend": "^0.1.3", + "@backstage/plugin-rollbar-backend": "^0.1.4", "@backstage/plugin-scaffolder": "^0.3.1", "@backstage/plugin-scaffolder-backend": "^0.3.2", "@backstage/plugin-tech-radar": "^0.3.0", - "@backstage/plugin-techdocs": "^0.2.3", - "@backstage/plugin-techdocs-backend": "^0.2.2", + "@backstage/plugin-techdocs": "^0.3.0", + "@backstage/plugin-techdocs-backend": "^0.3.0", "@backstage/plugin-user-settings": "^0.2.2", "@backstage/test-utils": "^0.1.3", "@backstage/theme": "^0.2.1", diff --git a/packages/integration/CHANGELOG.md b/packages/integration/CHANGELOG.md index fde914fac7..12a776d448 100644 --- a/packages/integration/CHANGELOG.md +++ b/packages/integration/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/integration +## 0.1.2 + +### Patch Changes + +- b3d4e4e57: Move the frontend visibility declarations of integrations config from @backstage/backend-common to @backstage/integration + ## 0.1.1 ### Patch Changes diff --git a/packages/integration/package.json b/packages/integration/package.json index 119ce51370..fd4bd0bd3a 100644 --- a/packages/integration/package.json +++ b/packages/integration/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/integration", - "version": "0.1.1", + "version": "0.1.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -24,7 +24,7 @@ "git-url-parse": "^11.4.0" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.2", "@types/jest": "^26.0.7" }, "files": [ diff --git a/plugins/api-docs/CHANGELOG.md b/plugins/api-docs/CHANGELOG.md index 60a5caf55b..efa94fb46f 100644 --- a/plugins/api-docs/CHANGELOG.md +++ b/plugins/api-docs/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-api-docs +## 0.3.0 + +### Minor Changes + +- f3bb55ee3: APIs now have real entity pages that are customizable in the app. + Therefore the old entity page from this plugin is removed. + See the `packages/app` on how to create and customize the API entity page. + +### Patch Changes + +- 6f70ed7a9: Replace usage of implementsApis with relations +- Updated dependencies [6f70ed7a9] +- Updated dependencies [ab94c9542] +- Updated dependencies [2daf18e80] +- Updated dependencies [069cda35f] + - @backstage/plugin-catalog@0.2.4 + - @backstage/catalog-model@0.3.1 + ## 0.2.2 ### Patch Changes diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index f364475bee..99f03f65ab 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-api-docs", - "version": "0.2.2", + "version": "0.3.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,9 +20,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.3.0", + "@backstage/catalog-model": "^0.3.1", "@backstage/core": "^0.3.2", - "@backstage/plugin-catalog": "^0.2.3", + "@backstage/plugin-catalog": "^0.2.4", "@backstage/theme": "^0.2.1", "@kyma-project/asyncapi-react": "^0.14.2", "@material-icons/font": "^1.0.2", @@ -40,7 +40,7 @@ "swagger-ui-react": "^3.31.1" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/app-backend/CHANGELOG.md b/plugins/app-backend/CHANGELOG.md index 3555da1959..1f2b3f008a 100644 --- a/plugins/app-backend/CHANGELOG.md +++ b/plugins/app-backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-app-backend +## 0.3.1 + +### Patch Changes + +- ff1301d28: Warn if the app-backend can't start-up because the static directory that should be served is unavailable. +- Updated dependencies [3aa7efb3f] +- Updated dependencies [b3d4e4e57] + - @backstage/backend-common@0.3.2 + ## 0.3.0 ### Minor Changes diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index bf6167d46a..ac350a14b4 100644 --- a/plugins/app-backend/package.json +++ b/plugins/app-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-app-backend", - "version": "0.3.0", + "version": "0.3.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,7 +20,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.3.0", + "@backstage/backend-common": "^0.3.2", "@backstage/config-loader": "^0.3.0", "@backstage/config": "^0.1.1", "@types/express": "^4.17.6", @@ -31,7 +31,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.2", "@types/supertest": "^2.0.8", "msw": "^0.20.5", "supertest": "^4.0.2" diff --git a/plugins/auth-backend/CHANGELOG.md b/plugins/auth-backend/CHANGELOG.md index 4a2161ad71..257c38a3e5 100644 --- a/plugins/auth-backend/CHANGELOG.md +++ b/plugins/auth-backend/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-auth-backend +## 0.2.4 + +### Patch Changes + +- 50eff1d00: Allow the backend to register custom AuthProviderFactories +- 700a212b4: bug fix: issue 3223 - detect mismatching origin and indicate it in the message at auth failure +- Updated dependencies [3aa7efb3f] +- Updated dependencies [ab94c9542] +- Updated dependencies [2daf18e80] +- Updated dependencies [069cda35f] +- Updated dependencies [b3d4e4e57] + - @backstage/backend-common@0.3.2 + - @backstage/catalog-model@0.3.1 + ## 0.2.3 ### Patch Changes diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 43e2c59f88..814c8c5258 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend", - "version": "0.2.3", + "version": "0.2.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,9 +20,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.3.1", + "@backstage/backend-common": "^0.3.2", "@backstage/catalog-client": "^0.3.1", - "@backstage/catalog-model": "^0.3.0", + "@backstage/catalog-model": "^0.3.1", "@backstage/config": "^0.1.1", "@types/express": "^4.17.6", "compression": "^1.7.4", @@ -55,7 +55,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@types/body-parser": "^1.19.0", "@types/cookie-parser": "^1.4.2", "@types/express-session": "^1.17.2", diff --git a/plugins/catalog-backend/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md index 3bf25a494b..64f1a7ba87 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,34 @@ # @backstage/plugin-catalog-backend +## 0.2.3 + +### Patch Changes + +- 1ec19a3f4: Ignore empty YAML documents. Having a YAML file like this is now ingested without an error: + + ```yaml + apiVersion: backstage.io/v1alpha1 + kind: Component + metadata: + name: web + spec: + type: website + --- + + ``` + + This behaves now the same way as Kubernetes handles multiple documents in a single YAML file. + +- ab94c9542: Add `providesApis` and `consumesApis` to the component entity spec. +- 2daf18e80: Start emitting all known relation types from the core entity kinds, based on their spec data. +- Updated dependencies [3aa7efb3f] +- Updated dependencies [ab94c9542] +- Updated dependencies [2daf18e80] +- Updated dependencies [069cda35f] +- Updated dependencies [b3d4e4e57] + - @backstage/backend-common@0.3.2 + - @backstage/catalog-model@0.3.1 + ## 0.2.2 ### Patch Changes diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 26f7180d43..9a4933ea6e 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend", - "version": "0.2.2", + "version": "0.2.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,8 +21,8 @@ }, "dependencies": { "@azure/msal-node": "^1.0.0-alpha.8", - "@backstage/backend-common": "^0.3.1", - "@backstage/catalog-model": "^0.3.0", + "@backstage/backend-common": "^0.3.2", + "@backstage/catalog-model": "^0.3.1", "@backstage/config": "^0.1.1", "@octokit/graphql": "^4.5.6", "@types/express": "^4.17.6", @@ -48,7 +48,7 @@ "yup": "^0.29.3" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/test-utils": "^0.1.3", "@types/core-js": "^2.5.4", "@types/git-url-parse": "^9.0.0", diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index 9137b7e948..b1c3cefa51 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-catalog +## 0.2.4 + +### Patch Changes + +- 6f70ed7a9: Replace usage of implementsApis with relations +- Updated dependencies [4b53294a6] +- Updated dependencies [ab94c9542] +- Updated dependencies [2daf18e80] +- Updated dependencies [069cda35f] + - @backstage/plugin-techdocs@0.3.0 + - @backstage/catalog-model@0.3.1 + ## 0.2.3 ### Patch Changes diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 7b58358734..f039749f64 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog", - "version": "0.2.3", + "version": "0.2.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -22,10 +22,10 @@ }, "dependencies": { "@backstage/catalog-client": "^0.3.1", - "@backstage/catalog-model": "^0.3.0", + "@backstage/catalog-model": "^0.3.1", "@backstage/core": "^0.3.2", "@backstage/plugin-scaffolder": "^0.3.1", - "@backstage/plugin-techdocs": "^0.2.3", + "@backstage/plugin-techdocs": "^0.3.0", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -43,7 +43,7 @@ "swr": "^0.3.0" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@microsoft/microsoft-graph-types": "^1.25.0", diff --git a/plugins/rollbar-backend/CHANGELOG.md b/plugins/rollbar-backend/CHANGELOG.md index ec042b9ae1..8aba667366 100644 --- a/plugins/rollbar-backend/CHANGELOG.md +++ b/plugins/rollbar-backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-rollbar-backend +## 0.1.4 + +### Patch Changes + +- 3a201c5d5: Add config schema for the rollbar & rollbar-backend plugins +- Updated dependencies [3aa7efb3f] +- Updated dependencies [b3d4e4e57] + - @backstage/backend-common@0.3.2 + ## 0.1.3 ### Patch Changes diff --git a/plugins/rollbar-backend/package.json b/plugins/rollbar-backend/package.json index 0f0b8bc366..c91bf0cb9e 100644 --- a/plugins/rollbar-backend/package.json +++ b/plugins/rollbar-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-rollbar-backend", - "version": "0.1.3", + "version": "0.1.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,7 +20,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.3.0", + "@backstage/backend-common": "^0.3.2", "@backstage/config": "^0.1.1", "@types/express": "^4.17.6", "axios": "^0.20.0", @@ -37,7 +37,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.2", "@types/supertest": "^2.0.8", "supertest": "^4.0.2" }, diff --git a/plugins/rollbar/CHANGELOG.md b/plugins/rollbar/CHANGELOG.md index 1ccfe16c41..095f04104a 100644 --- a/plugins/rollbar/CHANGELOG.md +++ b/plugins/rollbar/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-rollbar +## 0.2.4 + +### Patch Changes + +- 3a201c5d5: Add config schema for the rollbar & rollbar-backend plugins +- Updated dependencies [6f70ed7a9] +- Updated dependencies [ab94c9542] +- Updated dependencies [2daf18e80] +- Updated dependencies [069cda35f] + - @backstage/plugin-catalog@0.2.4 + - @backstage/catalog-model@0.3.1 + ## 0.2.3 ### Patch Changes diff --git a/plugins/rollbar/package.json b/plugins/rollbar/package.json index 64850c0dee..35b057c054 100644 --- a/plugins/rollbar/package.json +++ b/plugins/rollbar/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-rollbar", - "version": "0.2.3", + "version": "0.2.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,9 +21,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.3.0", + "@backstage/catalog-model": "^0.3.1", "@backstage/core": "^0.3.2", - "@backstage/plugin-catalog": "^0.2.3", + "@backstage/plugin-catalog": "^0.2.4", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -37,7 +37,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/techdocs-backend/CHANGELOG.md b/plugins/techdocs-backend/CHANGELOG.md index d9054230e2..0a396efd41 100644 --- a/plugins/techdocs-backend/CHANGELOG.md +++ b/plugins/techdocs-backend/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-techdocs-backend +## 0.3.0 + +### Minor Changes + +- 4b53294a6: - Use techdocs annotation to add repo_url if missing in mkdocs.yml. Having repo_url creates a Edit button on techdocs pages. + - techdocs-backend: API endpoint `/metadata/mkdocs/*` renamed to `/metadata/techdocs/*` + +### Patch Changes + +- Updated dependencies [3aa7efb3f] +- Updated dependencies [ab94c9542] +- Updated dependencies [2daf18e80] +- Updated dependencies [069cda35f] +- Updated dependencies [b3d4e4e57] + - @backstage/backend-common@0.3.2 + - @backstage/catalog-model@0.3.1 + ## 0.2.2 ### Patch Changes diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index a09adb7380..bf67108457 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-backend", - "version": "0.2.2", + "version": "0.3.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,8 +20,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.3.1", - "@backstage/catalog-model": "^0.3.0", + "@backstage/backend-common": "^0.3.2", + "@backstage/catalog-model": "^0.3.1", "@backstage/config": "^0.1.1", "@types/dockerode": "^2.5.34", "@types/express": "^4.17.6", @@ -39,7 +39,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "supertest": "^4.0.2" }, "files": [ diff --git a/plugins/techdocs/CHANGELOG.md b/plugins/techdocs/CHANGELOG.md index 3f46e1358b..5076362774 100644 --- a/plugins/techdocs/CHANGELOG.md +++ b/plugins/techdocs/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-techdocs +## 0.3.0 + +### Minor Changes + +- 4b53294a6: - Use techdocs annotation to add repo_url if missing in mkdocs.yml. Having repo_url creates a Edit button on techdocs pages. + - techdocs-backend: API endpoint `/metadata/mkdocs/*` renamed to `/metadata/techdocs/*` + +### Patch Changes + +- Updated dependencies [6f70ed7a9] +- Updated dependencies [ab94c9542] +- Updated dependencies [2daf18e80] +- Updated dependencies [069cda35f] +- Updated dependencies [700a212b4] + - @backstage/plugin-catalog@0.2.4 + - @backstage/catalog-model@0.3.1 + - @backstage/core-api@0.2.3 + ## 0.2.3 ### Patch Changes diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index 23d7a25b57..c8bca7d07e 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs", - "version": "0.2.3", + "version": "0.3.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,10 +21,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.3.0", + "@backstage/catalog-model": "^0.3.1", "@backstage/core": "^0.3.2", - "@backstage/core-api": "^0.2.1", - "@backstage/plugin-catalog": "^0.2.3", + "@backstage/core-api": "^0.2.3", + "@backstage/plugin-catalog": "^0.2.4", "@backstage/test-utils": "^0.1.3", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", @@ -39,7 +39,7 @@ "sanitize-html": "^1.27.0" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", From 1162318d063521423d1a21ab959d3482c9a3cc0c Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Fri, 27 Nov 2020 11:55:00 +0100 Subject: [PATCH 291/430] Fix remaining test issues on Windows #3459 was on the right way but missed some places. --- .../config-loader/src/lib/schema/collect.test.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/config-loader/src/lib/schema/collect.test.ts b/packages/config-loader/src/lib/schema/collect.test.ts index 2427ab994d..94fe2154c1 100644 --- a/packages/config-loader/src/lib/schema/collect.test.ts +++ b/packages/config-loader/src/lib/schema/collect.test.ts @@ -115,15 +115,15 @@ describe('collectConfigSchemas', () => { await expect(collectConfigSchemas(['a'])).resolves.toEqual([ { - path: 'node_modules/b/package.json', + path: path.join('node_modules', 'b', 'package.json'), value: { ...mockSchema, title: 'b' }, }, { - path: 'node_modules/c1/package.json', + path: path.join('node_modules', 'c1', 'package.json'), value: { ...mockSchema, title: 'c1' }, }, { - path: 'node_modules/d1/package.json', + path: path.join('node_modules', 'd1', 'package.json'), value: { ...mockSchema, title: 'd1' }, }, ]); @@ -224,7 +224,11 @@ describe('collectConfigSchemas', () => { }); await expect(collectConfigSchemas(['a'])).rejects.toThrow( - 'Invalid schema in node_modules/a/schema.d.ts, missing Config export', + `Invalid schema in ${path.join( + 'node_modules', + 'a', + 'schema.d.ts', + )}, missing Config export`, ); }); }); From b08351ebb427387fefc341212a14c5f82e0ac407 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 27 Nov 2020 14:15:08 +0100 Subject: [PATCH 292/430] plugins: keep plugin package.json in sync --- plugins/circleci/package.json | 2 +- plugins/cloudbuild/package.json | 2 +- plugins/cost-insights/package.json | 2 +- plugins/explore/package.json | 2 +- plugins/gcp-projects/package.json | 2 +- plugins/github-actions/package.json | 2 +- plugins/gitops-profiles/package.json | 2 +- plugins/graphiql/package.json | 2 +- plugins/jenkins/package.json | 2 +- plugins/kubernetes/package.json | 2 +- plugins/lighthouse/package.json | 2 +- plugins/newrelic/package.json | 2 +- plugins/register-component/package.json | 2 +- plugins/scaffolder/package.json | 2 +- plugins/search/package.json | 2 +- plugins/sentry/package.json | 2 +- plugins/sonarqube/package.json | 2 +- plugins/tech-radar/package.json | 2 +- plugins/user-settings/package.json | 2 +- plugins/welcome/package.json | 2 +- 20 files changed, 20 insertions(+), 20 deletions(-) diff --git a/plugins/circleci/package.json b/plugins/circleci/package.json index fd32ff9891..fda28733af 100644 --- a/plugins/circleci/package.json +++ b/plugins/circleci/package.json @@ -40,7 +40,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/cloudbuild/package.json b/plugins/cloudbuild/package.json index 3e7f0c9175..c36669ccb9 100644 --- a/plugins/cloudbuild/package.json +++ b/plugins/cloudbuild/package.json @@ -39,7 +39,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/cost-insights/package.json b/plugins/cost-insights/package.json index 06083b855b..bc649e50ab 100644 --- a/plugins/cost-insights/package.json +++ b/plugins/cost-insights/package.json @@ -46,7 +46,7 @@ "yup": "^0.29.3" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/explore/package.json b/plugins/explore/package.json index f5f73b4faa..3c3619f2ce 100644 --- a/plugins/explore/package.json +++ b/plugins/explore/package.json @@ -33,7 +33,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/gcp-projects/package.json b/plugins/gcp-projects/package.json index 59bdcc547c..f07cd1b0e6 100644 --- a/plugins/gcp-projects/package.json +++ b/plugins/gcp-projects/package.json @@ -31,7 +31,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/github-actions/package.json b/plugins/github-actions/package.json index b1924034f1..83ba62be95 100644 --- a/plugins/github-actions/package.json +++ b/plugins/github-actions/package.json @@ -40,7 +40,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/gitops-profiles/package.json b/plugins/gitops-profiles/package.json index 67edcca51e..78af131bac 100644 --- a/plugins/gitops-profiles/package.json +++ b/plugins/gitops-profiles/package.json @@ -32,7 +32,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/graphiql/package.json b/plugins/graphiql/package.json index bde5dea9a3..d3dc673c01 100644 --- a/plugins/graphiql/package.json +++ b/plugins/graphiql/package.json @@ -43,7 +43,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/jenkins/package.json b/plugins/jenkins/package.json index 961f551dfa..e47ec9edda 100644 --- a/plugins/jenkins/package.json +++ b/plugins/jenkins/package.json @@ -36,7 +36,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index 66929077f3..75b9ee098c 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -36,7 +36,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json index ad1264cfbb..29b5a81cb9 100644 --- a/plugins/lighthouse/package.json +++ b/plugins/lighthouse/package.json @@ -38,7 +38,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/newrelic/package.json b/plugins/newrelic/package.json index 1c2b951bc6..691b9ab862 100644 --- a/plugins/newrelic/package.json +++ b/plugins/newrelic/package.json @@ -31,7 +31,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/register-component/package.json b/plugins/register-component/package.json index 1564faae0e..ad4432be83 100644 --- a/plugins/register-component/package.json +++ b/plugins/register-component/package.json @@ -36,7 +36,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 791221bc62..5a6a970851 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -41,7 +41,7 @@ "swr": "^0.3.0" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/search/package.json b/plugins/search/package.json index 021e3f71f3..ddc7a2d687 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -34,7 +34,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/sentry/package.json b/plugins/sentry/package.json index 4c10936148..4d8473c509 100644 --- a/plugins/sentry/package.json +++ b/plugins/sentry/package.json @@ -36,7 +36,7 @@ "timeago.js": "^4.0.2" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/sonarqube/package.json b/plugins/sonarqube/package.json index 6a9d0fe0d4..d5635c2770 100644 --- a/plugins/sonarqube/package.json +++ b/plugins/sonarqube/package.json @@ -35,7 +35,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/tech-radar/package.json b/plugins/tech-radar/package.json index 849ce3bc4d..532fc26e9c 100644 --- a/plugins/tech-radar/package.json +++ b/plugins/tech-radar/package.json @@ -35,7 +35,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index 80bcdeffdb..da4c2cd1cd 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -32,7 +32,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/welcome/package.json b/plugins/welcome/package.json index 5d81e569b8..167bb07565 100644 --- a/plugins/welcome/package.json +++ b/plugins/welcome/package.json @@ -32,7 +32,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", From ac6e65aa1ae4bcb280e5a8d76f8cf00f76430bd4 Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Fri, 27 Nov 2020 14:10:17 +0100 Subject: [PATCH 293/430] create MissingTokenError and flatten error --- .../src/components/MissingTokenError.tsx | 35 +++++++++++++++++++ .../src/components/PagerdutyCard.tsx | 28 ++++----------- 2 files changed, 42 insertions(+), 21 deletions(-) create mode 100644 plugins/pagerduty/src/components/MissingTokenError.tsx diff --git a/plugins/pagerduty/src/components/MissingTokenError.tsx b/plugins/pagerduty/src/components/MissingTokenError.tsx new file mode 100644 index 0000000000..c22552b7c6 --- /dev/null +++ b/plugins/pagerduty/src/components/MissingTokenError.tsx @@ -0,0 +1,35 @@ +/* + * 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 { EmptyState } from '@backstage/core'; +import { Button } from '@material-ui/core'; + +export const MissingTokenError = () => ( + + Read More + + } + /> +); diff --git a/plugins/pagerduty/src/components/PagerdutyCard.tsx b/plugins/pagerduty/src/components/PagerdutyCard.tsx index f24e91a9e0..22a471c031 100644 --- a/plugins/pagerduty/src/components/PagerdutyCard.tsx +++ b/plugins/pagerduty/src/components/PagerdutyCard.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ import React, { useState } from 'react'; -import { useApi, EmptyState } from '@backstage/core'; +import { useApi } from '@backstage/core'; import { Entity } from '@backstage/catalog-model'; import { Button, @@ -31,9 +31,10 @@ import { useAsync } from 'react-use'; import { Alert } from '@material-ui/lab'; import { pagerDutyApiRef, UnauthorizedError } from '../api'; import { IconLinkVertical } from '@backstage/plugin-catalog'; -import PagerDutyIcon from './PagerDutyIcon'; +import { PagerDutyIcon } from './PagerDutyIcon'; import AlarmAddIcon from '@material-ui/icons/AlarmAdd'; import { TriggerDialog } from './TriggerDialog'; +import { MissingTokenError } from './MissingTokenError'; const useStyles = makeStyles(theme => ({ links: { @@ -93,26 +94,11 @@ export const PagerDutyCard = ({ entity }: Props) => { setShowDialog(!showDialog); }; - if (error) { - if (error instanceof UnauthorizedError) { - return ( - - Read More - - } - /> - ); - } + if (error instanceof UnauthorizedError) { + return ; + } + if (error) { return ( Error encountered while fetching information. {error.message} From a57ed028f85b4beda64830a2f17b14254be9b7e2 Mon Sep 17 00:00:00 2001 From: samiramkr Date: Fri, 27 Nov 2020 14:49:51 +0100 Subject: [PATCH 294/430] Update plugins/pagerduty/README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw --- plugins/pagerduty/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/pagerduty/README.md b/plugins/pagerduty/README.md index da37282aa7..b3e0b9f4e6 100644 --- a/plugins/pagerduty/README.md +++ b/plugins/pagerduty/README.md @@ -2,7 +2,7 @@ ## Overview -This plugin displays PagerDuty information about an entity such as if there are any active incidents and how the escalation policy looks like. +This plugin displays PagerDuty information about an entity such as if there are any active incidents and what the escalation policy is. There is also an easy way to trigger an alarm directly to the person who is currently on-call. From 993e039c846c035c6e19f68b1d191df7fb336752 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Fri, 27 Nov 2020 15:23:19 +0100 Subject: [PATCH 295/430] chore: run yarn to update yarn.lock --- yarn.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yarn.lock b/yarn.lock index 64c636790a..9307f7aeed 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1308,7 +1308,7 @@ to-fast-properties "^2.0.0" "@backstage/catalog-model@^0.2.0": - version "0.3.0" + version "0.3.1" dependencies: "@backstage/config" "^0.1.1" "@types/json-schema" "^7.0.5" @@ -21172,7 +21172,7 @@ rollup-plugin-dts@1.4.13: optionalDependencies: "@babel/code-frame" "^7.10.4" -rollup-plugin-esbuild@^2.0.0: +rollup-plugin-esbuild@2.3.x: version "2.3.0" resolved "https://registry.npmjs.org/rollup-plugin-esbuild/-/rollup-plugin-esbuild-2.3.0.tgz#7c107d4508af80d30966a148b306cb7d5b36b258" integrity sha512-lNbWDRaClwXauaIybuaiOBRWgxcp3GkZu0UUix9yH/OHFgVohKmzlU0NtNbvfbxAcfEooyuBRAv6YCaJl1Mbpw== From 43d0967db89e2cf32808d0ae4a02ed442a27c755 Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Fri, 27 Nov 2020 16:10:55 +0100 Subject: [PATCH 296/430] refactor code --- .../AboutCard/IconLinkVertical/IconLinkVertical.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/catalog/src/components/AboutCard/IconLinkVertical/IconLinkVertical.tsx b/plugins/catalog/src/components/AboutCard/IconLinkVertical/IconLinkVertical.tsx index 7e1dc61fa7..4a8bf2a0ab 100644 --- a/plugins/catalog/src/components/AboutCard/IconLinkVertical/IconLinkVertical.tsx +++ b/plugins/catalog/src/components/AboutCard/IconLinkVertical/IconLinkVertical.tsx @@ -75,11 +75,11 @@ export function IconLinkVertical({ if (action) { return ( From b2013cc40b33ec4d43138c4d02e553c5b142b981 Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Fri, 27 Nov 2020 16:35:53 +0100 Subject: [PATCH 297/430] refactor type --- plugins/pagerduty/src/api/client.ts | 14 +++++++------- plugins/pagerduty/src/api/types.ts | 14 ++++++++------ 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/plugins/pagerduty/src/api/client.ts b/plugins/pagerduty/src/api/client.ts index 4ed994eec2..7b453b7298 100644 --- a/plugins/pagerduty/src/api/client.ts +++ b/plugins/pagerduty/src/api/client.ts @@ -25,7 +25,7 @@ import { OnCallsResponse, OnCall, } from '../components/types'; -import { PagerDutyClient } from './types'; +import { PagerDutyClient, TriggerAlarmRequest } from './types'; export class UnauthorizedError extends Error {} @@ -67,12 +67,12 @@ export class PagerDutyClientApi implements PagerDutyClient { return oncalls; } - triggerAlarm( - integrationKey: string, - source: string, - description: string, - userName: string, - ) { + triggerAlarm({ + integrationKey, + source, + description, + userName, + }: TriggerAlarmRequest): Promise { const body = JSON.stringify({ event_action: 'trigger', routing_key: integrationKey, diff --git a/plugins/pagerduty/src/api/types.ts b/plugins/pagerduty/src/api/types.ts index c07834a213..17c2067818 100644 --- a/plugins/pagerduty/src/api/types.ts +++ b/plugins/pagerduty/src/api/types.ts @@ -16,6 +16,13 @@ import { Incident, OnCall, Service } from '../components/types'; +export type TriggerAlarmRequest = { + integrationKey: string; + source: string; + description: string; + userName: string; +}; + export interface PagerDutyClient { /** * Fetches a list of services, filtered by the provided integration key. @@ -38,10 +45,5 @@ export interface PagerDutyClient { /** * Triggers an incident to whoever is on-call. */ - triggerAlarm( - integrationKey: string, - source: string, - description: string, - userName: string, - ): Promise; + triggerAlarm(request: TriggerAlarmRequest): Promise; } From b26e3dacd9b4084e812f73f27afe59bede8b0e69 Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Fri, 27 Nov 2020 16:37:48 +0100 Subject: [PATCH 298/430] Fixdependency version --- plugins/catalog-import/package.json | 2 +- yarn.lock | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index 8efa765224..eb3ce1bd2c 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -21,7 +21,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.2.0", + "@backstage/catalog-model": "^0.3.0", "@backstage/core": "^0.3.2", "@backstage/plugin-catalog": "^0.2.0", "@backstage/plugin-catalog-backend": "^0.2.2", diff --git a/yarn.lock b/yarn.lock index b8093d125d..b37657b34a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1308,9 +1308,7 @@ to-fast-properties "^2.0.0" "@backstage/catalog-model@^0.2.0": - version "0.2.0" - resolved "https://registry.npmjs.org/@backstage/catalog-model/-/catalog-model-0.2.0.tgz#e3fe2a4ddeb6a9b6ec480c80cb2b9c39cb245576" - integrity sha512-Y1ocdRpBlxK/VrJQjHlQd0bgADECd1B2NRjwd8ss46ibT5hwLvMOfD80+Fa7oPLu0ktJrH4lq0pNIIJIml48zA== + version "0.3.0" dependencies: "@backstage/config" "^0.1.1" "@types/json-schema" "^7.0.5" From 6a02802d728031f19550b3cf26203e29b3e7008b Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Fri, 27 Nov 2020 16:59:16 +0100 Subject: [PATCH 299/430] use Progress --- .../src/components/Escalation/EscalationPolicy.tsx | 14 ++++++++------ .../src/components/Incident/Incidents.tsx | 14 ++++++++------ plugins/pagerduty/src/components/PagerdutyCard.tsx | 4 ++-- plugins/pagerduty/src/setupTests.ts | 2 -- 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/plugins/pagerduty/src/components/Escalation/EscalationPolicy.tsx b/plugins/pagerduty/src/components/Escalation/EscalationPolicy.tsx index ccf70abb0b..5e67d52172 100644 --- a/plugins/pagerduty/src/components/Escalation/EscalationPolicy.tsx +++ b/plugins/pagerduty/src/components/Escalation/EscalationPolicy.tsx @@ -15,12 +15,12 @@ */ import React from 'react'; -import { List, ListSubheader, LinearProgress } from '@material-ui/core'; +import { List, ListSubheader } from '@material-ui/core'; import { EscalationUsersEmptyState } from './EscalationUsersEmptyState'; import { EscalationUser } from './EscalationUser'; import { useAsync } from 'react-use'; import { pagerDutyApiRef } from '../../api'; -import { useApi } from '@backstage/core'; +import { useApi, Progress } from '@backstage/core'; import { Alert } from '@material-ui/lab'; type Props = { @@ -46,16 +46,18 @@ export const EscalationPolicy = ({ policyId }: Props) => { } if (loading) { - return ; + return ; } - return users!.length ? ( + if (!users?.length) { + return ; + } + + return ( ON CALL}> {users!.map((user, index) => ( ))} - ) : ( - ); }; diff --git a/plugins/pagerduty/src/components/Incident/Incidents.tsx b/plugins/pagerduty/src/components/Incident/Incidents.tsx index 2d2e5b01fd..1d4ec3a1a6 100644 --- a/plugins/pagerduty/src/components/Incident/Incidents.tsx +++ b/plugins/pagerduty/src/components/Incident/Incidents.tsx @@ -15,12 +15,12 @@ */ import React, { useEffect } from 'react'; -import { List, ListSubheader, LinearProgress } from '@material-ui/core'; +import { List, ListSubheader } from '@material-ui/core'; import { IncidentListItem } from './IncidentListItem'; import { IncidentsEmptyState } from './IncidentEmptyState'; import { useAsyncFn } from 'react-use'; import { pagerDutyApiRef } from '../../api'; -import { useApi } from '@backstage/core'; +import { useApi, Progress } from '@backstage/core'; import { Alert } from '@material-ui/lab'; type Props = { @@ -56,16 +56,18 @@ export const Incidents = ({ } if (loading) { - return ; + return ; } - return incidents?.length ? ( + if (!incidents?.length) { + return ; + } + + return ( INCIDENTS}> {incidents!.map((incident, index) => ( ))} - ) : ( - ); }; diff --git a/plugins/pagerduty/src/components/PagerdutyCard.tsx b/plugins/pagerduty/src/components/PagerdutyCard.tsx index 22a471c031..333e042f82 100644 --- a/plugins/pagerduty/src/components/PagerdutyCard.tsx +++ b/plugins/pagerduty/src/components/PagerdutyCard.tsx @@ -22,7 +22,6 @@ import { CardContent, CardHeader, Divider, - LinearProgress, makeStyles, } from '@material-ui/core'; import { Incidents } from './Incident'; @@ -35,6 +34,7 @@ import { PagerDutyIcon } from './PagerDutyIcon'; import AlarmAddIcon from '@material-ui/icons/AlarmAdd'; import { TriggerDialog } from './TriggerDialog'; import { MissingTokenError } from './MissingTokenError'; +import { Progress } from '@backstage/core'; const useStyles = makeStyles(theme => ({ links: { @@ -107,7 +107,7 @@ export const PagerDutyCard = ({ entity }: Props) => { } if (loading) { - return ; + return ; } const pagerdutyLink = { diff --git a/plugins/pagerduty/src/setupTests.ts b/plugins/pagerduty/src/setupTests.ts index d7857386de..0bfa67b49a 100644 --- a/plugins/pagerduty/src/setupTests.ts +++ b/plugins/pagerduty/src/setupTests.ts @@ -14,5 +14,3 @@ * limitations under the License. */ import '@testing-library/jest-dom'; - -global.fetch = require('node-fetch'); From 3f947b70d18d536f9c054889513f1b4ca7542b80 Mon Sep 17 00:00:00 2001 From: Peter Colapietro Date: Fri, 27 Nov 2020 12:08:37 -0500 Subject: [PATCH 300/430] fix(react): use fragment (#3478) The empty state's description is already wrapped in a Typography component so the one in MissingImplementsApisEmptyState is redundant. https://github.com/backstage/backstage/blob/bbaadb63f88d0f7d42acb9fa1b1644f85bf1043e/packages/core/src/components/EmptyState/EmptyState.tsx#L57 By wrapping the EmptyState's description in a Typography the following warning is output to the console ``` react-dom.development.js?1930:89 Warning: validateDOMNesting(...):

cannot appear as a descendant of

. in p (created by ForwardRef(Typography)) in ForwardRef(Typography) (created by WithStyles(ForwardRef(Typography))) in WithStyles(ForwardRef(Typography)) (created by MissingImplementsApisEmptyState) in p (created by ForwardRef(Typography)) in ForwardRef(Typography) (created by WithStyles(ForwardRef(Typography))) in WithStyles(ForwardRef(Typography)) (created by EmptyState) in div (created by ForwardRef(Grid)) in ForwardRef(Grid) (created by WithStyles(ForwardRef(Grid))) in WithStyles(ForwardRef(Grid)) (created by EmptyState) in div (created by ForwardRef(Grid)) in ForwardRef(Grid) (created by WithStyles(ForwardRef(Grid))) in WithStyles(ForwardRef(Grid)) (created by EmptyState) in div (created by ForwardRef(Grid)) in ForwardRef(Grid) (created by WithStyles(ForwardRef(Grid))) in WithStyles(ForwardRef(Grid)) (created by EmptyState) in EmptyState (created by MissingImplementsApisEmptyState) in MissingImplementsApisEmptyState (created by Router) in Router (at EntityPage.tsx:78) in article (created by Content) in Content (created by Layout) in Layout (created by EntityPageLayout) in div (created by Page) in ThemeProvider (created by Page) in Page (created by EntityPageLayout) in EntityPageLayout (at EntityPage.tsx:64) in ServiceEntityPage (at EntityPage.tsx:127) in EntityPage (created by EntityPageSwitch) in EntityPageSwitch (created by Router) in EntityProvider (created by Router) in Route (created by Router) in Routes (created by Router) in Router (at App.tsx:46) in Route (at App.tsx:44) in Routes (at App.tsx:42) in div (created by SidebarPage) in SidebarPage (at App.tsx:40) in Route (created by AppRouter) in Routes (created by AppRouter) in Router (created by BrowserRouter) in BrowserRouter (created by AppRouter) in AppRouter (at App.tsx:39) in CssBaseline (created by WithStyles(CssBaseline)) in WithStyles(CssBaseline) (created by AppThemeProvider) in ThemeProvider (created by AppThemeProvider) in AppThemeProvider (created by Provider) in AppContextProvider (created by Provider) in ApiProvider (created by Provider) in Provider (at App.tsx:36) in App (at src/index.tsx:6) ``` Co-authored-by: Peter Colapietro --- .../MissingImplementsApisEmptyState.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/api-docs/src/catalog/MissingImplementsApisEmptyState/MissingImplementsApisEmptyState.tsx b/plugins/api-docs/src/catalog/MissingImplementsApisEmptyState/MissingImplementsApisEmptyState.tsx index f13848bd19..fbb8810088 100644 --- a/plugins/api-docs/src/catalog/MissingImplementsApisEmptyState/MissingImplementsApisEmptyState.tsx +++ b/plugins/api-docs/src/catalog/MissingImplementsApisEmptyState/MissingImplementsApisEmptyState.tsx @@ -47,10 +47,10 @@ export const MissingImplementsApisEmptyState = () => { missing="field" title="No APIs implemented by this entity" description={ - + <> Components can implement APIs that are displayed on this page. You need to fill the providesApis field to enable this tool. - + } action={ <> From b9d1dc9f58adf7f59a39c0d0b63f874bab2ec13e Mon Sep 17 00:00:00 2001 From: Peter Colapietro Date: Fri, 27 Nov 2020 11:15:04 -0500 Subject: [PATCH 301/430] fix(react): add key prop to NotFoundErrorPage route Avoids: ``` Warning: Each child in a list should have a unique "key" prop. Check the render method of `App`. See https://fb.me/react-warning-keys for more information. in Route in App (at src/index.tsx:6) ``` --- packages/core-api/src/app/App.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/core-api/src/app/App.tsx b/packages/core-api/src/app/App.tsx index 9c38c20b44..3d32259337 100644 --- a/packages/core-api/src/app/App.tsx +++ b/packages/core-api/src/app/App.tsx @@ -184,7 +184,13 @@ export class PrivateAppImpl implements BackstageApp { } } - routes.push(} />); + routes.push( + } + />, + ); return routes; } From 4a655c89dbb196fd013a7e877214d4f5c1b40ebd Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 28 Nov 2020 17:02:02 +0100 Subject: [PATCH 302/430] cli,create-app: bump esbuild + remove resolutions override in create-app --- .changeset/light-nails-crash.md | 5 +++ .changeset/perfect-dryers-sell.md | 13 ++++++++ packages/cli/package.json | 4 +-- packages/cli/src/lib/builder/packager.ts | 17 ++++++++++ .../templates/default-app/package.json.hbs | 3 -- yarn.lock | 33 ++++++++++++++----- 6 files changed, 61 insertions(+), 14 deletions(-) create mode 100644 .changeset/light-nails-crash.md create mode 100644 .changeset/perfect-dryers-sell.md diff --git a/.changeset/light-nails-crash.md b/.changeset/light-nails-crash.md new file mode 100644 index 0000000000..c7791d1027 --- /dev/null +++ b/.changeset/light-nails-crash.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Bump versions of `esbuild` and `rollup-plugin-esbuild` diff --git a/.changeset/perfect-dryers-sell.md b/.changeset/perfect-dryers-sell.md new file mode 100644 index 0000000000..e1c5592162 --- /dev/null +++ b/.changeset/perfect-dryers-sell.md @@ -0,0 +1,13 @@ +--- +'@backstage/create-app': patch +--- + +Removed `"resolutions"` entry for `esbuild` in the root `package.json` in order to use the version specified by `@backstage/cli`. + +To apply this change to an existing app, remove the following from your root `package.json`: + +```json +"resolutions": { + "esbuild": "0.6.3" +}, +``` diff --git a/packages/cli/package.json b/packages/cli/package.json index 5d8826703c..eff546168e 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -59,7 +59,7 @@ "css-loader": "^3.5.3", "dashify": "^2.0.0", "diff": "^4.0.2", - "esbuild": "^0.7.7", + "esbuild": "^0.8.16", "eslint": "^7.1.0", "eslint-config-prettier": "^6.0.0", "eslint-formatter-friendly": "^7.0.0", @@ -88,7 +88,7 @@ "replace-in-file": "^6.0.0", "rollup": "2.33.x", "rollup-plugin-dts": "1.4.13", - "rollup-plugin-esbuild": "2.3.x", + "rollup-plugin-esbuild": "2.6.x", "rollup-plugin-peer-deps-external": "^2.2.2", "rollup-plugin-postcss": "^3.1.1", "rollup-plugin-typescript2": "^0.27.3", diff --git a/packages/cli/src/lib/builder/packager.ts b/packages/cli/src/lib/builder/packager.ts index 65e33bcb7a..ed76e23df3 100644 --- a/packages/cli/src/lib/builder/packager.ts +++ b/packages/cli/src/lib/builder/packager.ts @@ -83,6 +83,23 @@ async function build(config: RollupOptions) { } export const buildPackage = async (options: BuildOptions) => { + try { + const { resolutions } = await fs.readJson( + paths.resolveTargetRoot('package.json'), + ); + if (resolutions?.esbuild) { + console.warn( + chalk.red( + 'Your root package.json contains a "resolutions" entry for "esbuild". This was ' + + 'included in older @backstage/create-app templates in order to work around build ' + + 'issues that have since been fixed. Please remove the entry and run `yarn install`', + ), + ); + } + } catch { + /* Errors ignored, this is just a warning */ + } + const configs = await makeConfigs(options); await fs.remove(paths.resolveTarget('dist')); await Promise.all(configs.map(build)); diff --git a/packages/create-app/templates/default-app/package.json.hbs b/packages/create-app/templates/default-app/package.json.hbs index 0116287a46..975a5a2ed2 100644 --- a/packages/create-app/templates/default-app/package.json.hbs +++ b/packages/create-app/templates/default-app/package.json.hbs @@ -32,9 +32,6 @@ "lerna": "^3.20.2", "prettier": "^1.19.1" }, - "resolutions": { - "esbuild": "0.6.3" - }, "prettier": "@spotify/prettier-config", "lint-staged": { "*.{js,jsx,ts,tsx}": [ diff --git a/yarn.lock b/yarn.lock index 9307f7aeed..cf67cf7aa2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3836,6 +3836,14 @@ estree-walker "^1.0.1" picomatch "^2.2.2" +"@rollup/pluginutils@^4.1.0": + version "4.1.0" + resolved "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.1.0.tgz#0dcc61c780e39257554feb7f77207dceca13c838" + integrity sha512-TrBhfJkFxA+ER+ew2U2/fHbebhLT/l/2pRk0hfj9KusXUuRXd2v0R58AfaZK9VXDQ4TogOSEmICVrQAA3zFnHQ== + dependencies: + 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" @@ -11153,10 +11161,10 @@ es6-weak-map@^2.0.2: es6-iterator "^2.0.3" es6-symbol "^3.1.1" -esbuild@^0.7.7: - version "0.7.7" - resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.7.7.tgz#fd86332d1c0a047231bd6da930666028c40c14bf" - integrity sha512-1Ub4BBsWwPdxkwjyuXdKrgMIZROZ82ULIRFclOzXXVrqKOv9rMDoShRf23WEbPfeEA94z/BScKyUOyooyQ3XnQ== +esbuild@^0.8.16: + version "0.8.16" + resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.8.16.tgz#8ae34b15d938e8b8b5ac2459414fe3e7fd7dd6b2" + integrity sha512-HMvPNxDIhEGO/YUh8oO8oxQ1g+ttWz2anUF7NJmQglj2XfJS8zd8mP0Sb2y+jE1SVk3UjD/rYhdsEOFULN9/xw== escalade@^3.0.1: version "3.0.2" @@ -15106,6 +15114,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" @@ -21172,12 +21185,14 @@ rollup-plugin-dts@1.4.13: optionalDependencies: "@babel/code-frame" "^7.10.4" -rollup-plugin-esbuild@2.3.x: - version "2.3.0" - resolved "https://registry.npmjs.org/rollup-plugin-esbuild/-/rollup-plugin-esbuild-2.3.0.tgz#7c107d4508af80d30966a148b306cb7d5b36b258" - integrity sha512-lNbWDRaClwXauaIybuaiOBRWgxcp3GkZu0UUix9yH/OHFgVohKmzlU0NtNbvfbxAcfEooyuBRAv6YCaJl1Mbpw== +rollup-plugin-esbuild@2.6.x: + version "2.6.0" + resolved "https://registry.npmjs.org/rollup-plugin-esbuild/-/rollup-plugin-esbuild-2.6.0.tgz#80336399b113a179ccb2af5bdf7c03f061f37146" + integrity sha512-wtyDAX8kNABrBfwkrrG2xLRKRGSWUyMBURS067IRvRMpgJlLRo14WcFl95uGc4iYWfdAx436/z1LMzYqdlY4ig== dependencies: - "@rollup/pluginutils" "^3.1.0" + "@rollup/pluginutils" "^4.1.0" + joycon "^2.2.5" + strip-json-comments "^3.1.1" rollup-plugin-peer-deps-external@^2.2.2: version "2.2.3" From 85af34ffd58901bb625328f8e1e5511098f3b98f Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Sat, 28 Nov 2020 20:00:18 +0100 Subject: [PATCH 303/430] techdocs: Use EntityName from catalog-model as Entity type --- plugins/techdocs/dev/api.ts | 12 ++----- plugins/techdocs/src/api.ts | 32 +++++++------------ .../techdocs/src/reader/components/Reader.tsx | 13 ++++---- .../reader/components/TechDocsPageHeader.tsx | 10 +++--- .../src/reader/transformers/addBaseUrl.ts | 5 ++- plugins/techdocs/src/types.ts | 27 ---------------- 6 files changed, 28 insertions(+), 71 deletions(-) delete mode 100644 plugins/techdocs/src/types.ts diff --git a/plugins/techdocs/dev/api.ts b/plugins/techdocs/dev/api.ts index 60a17c3dc3..ae2b8c58ae 100644 --- a/plugins/techdocs/dev/api.ts +++ b/plugins/techdocs/dev/api.ts @@ -13,9 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import { ParsedEntityId } from '../src/types'; - +import { EntityName } from '@backstage/catalog-model'; import { TechDocsStorage } from '../src/api'; export class TechDocsDevStorageApi implements TechDocsStorage { @@ -25,7 +23,7 @@ export class TechDocsDevStorageApi implements TechDocsStorage { this.apiOrigin = apiOrigin; } - async getEntityDocs(entityId: ParsedEntityId, path: string) { + async getEntityDocs(entityId: EntityName, path: string) { const { name } = entityId; const url = `${this.apiOrigin}/${name}/${path}`; @@ -41,11 +39,7 @@ export class TechDocsDevStorageApi implements TechDocsStorage { return request.text(); } - getBaseUrl( - oldBaseUrl: string, - entityId: ParsedEntityId, - path: string, - ): string { + getBaseUrl(oldBaseUrl: string, entityId: EntityName, path: string): string { const { name } = entityId; return new URL(oldBaseUrl, `${this.apiOrigin}/${name}/${path}`).toString(); } diff --git a/plugins/techdocs/src/api.ts b/plugins/techdocs/src/api.ts index 368705fa22..c8581ec5d3 100644 --- a/plugins/techdocs/src/api.ts +++ b/plugins/techdocs/src/api.ts @@ -15,7 +15,7 @@ */ import { createApiRef } from '@backstage/core'; -import { ParsedEntityId } from './types'; +import { EntityName } from '@backstage/catalog-model'; export const techdocsStorageApiRef = createApiRef({ id: 'plugin.techdocs.storageservice', @@ -28,17 +28,13 @@ export const techdocsApiRef = createApiRef({ }); export interface TechDocsStorage { - getEntityDocs(entityId: ParsedEntityId, path: string): Promise; - getBaseUrl( - oldBaseUrl: string, - entityId: ParsedEntityId, - path: string, - ): string; + getEntityDocs(entityId: EntityName, path: string): Promise; + getBaseUrl(oldBaseUrl: string, entityId: EntityName, path: string): string; } export interface TechDocs { - getTechDocsMetadata(entityId: ParsedEntityId): Promise; - getEntityMetadata(entityId: ParsedEntityId): Promise; + getTechDocsMetadata(entityId: EntityName): Promise; + getEntityMetadata(entityId: EntityName): Promise; } /** @@ -60,9 +56,9 @@ export class TechDocsApi implements TechDocs { * static files. It includes necessary data about the docs site. This method requests techdocs-backend * which retrieves the TechDocs metadata. * - * @param {ParsedEntityId} entityId Object containing entity data like name, namespace, etc. + * @param {EntityName} entityId Object containing entity data like name, namespace, etc. */ - async getTechDocsMetadata(entityId: ParsedEntityId) { + async getTechDocsMetadata(entityId: EntityName) { const { kind, namespace, name } = entityId; const requestUrl = `${this.apiOrigin}/metadata/techdocs/${namespace}/${kind}/${name}`; @@ -79,9 +75,9 @@ export class TechDocsApi implements TechDocs { * This method requests techdocs-backend which uses the catalog APIs to respond with filtered * information required here. * - * @param {ParsedEntityId} entityId Object containing entity data like name, namespace, etc. + * @param {EntityName} entityId Object containing entity data like name, namespace, etc. */ - async getEntityMetadata(entityId: ParsedEntityId) { + async getEntityMetadata(entityId: EntityName) { const { kind, namespace, name } = entityId; const requestUrl = `${this.apiOrigin}/metadata/entity/${namespace}/${kind}/${name}`; @@ -108,12 +104,12 @@ export class TechDocsStorageApi implements TechDocsStorage { /** * Fetch HTML content as text for an individual docs page in an entity's docs site. * - * @param {ParsedEntityId} entityId Object containing entity data like name, namespace, etc. + * @param {EntityName} entityId Object containing entity data like name, namespace, etc. * @param {string} path The unique path to an individual docs page e.g. overview/what-is-new * @returns {string} HTML content of the docs page as string * @throws {Error} Throws error when the page is not found. */ - async getEntityDocs(entityId: ParsedEntityId, path: string) { + async getEntityDocs(entityId: EntityName, path: string) { const { kind, namespace, name } = entityId; const url = `${this.apiOrigin}/docs/${namespace}/${kind}/${name}/${path}`; @@ -135,11 +131,7 @@ export class TechDocsStorageApi implements TechDocsStorage { return request.text(); } - getBaseUrl( - oldBaseUrl: string, - entityId: ParsedEntityId, - path: string, - ): string { + getBaseUrl(oldBaseUrl: string, entityId: EntityName, path: string): string { const { kind, namespace, name } = entityId; return new URL( diff --git a/plugins/techdocs/src/reader/components/Reader.tsx b/plugins/techdocs/src/reader/components/Reader.tsx index fe3a34564c..bdf91936ba 100644 --- a/plugins/techdocs/src/reader/components/Reader.tsx +++ b/plugins/techdocs/src/reader/components/Reader.tsx @@ -13,16 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - import React from 'react'; -import { useApi } from '@backstage/core'; -import { useShadowDom } from '..'; import { useAsync } from 'react-use'; -import { techdocsStorageApiRef } from '../../api'; -import { useParams, useNavigate } from 'react-router-dom'; -import { ParsedEntityId } from '../../types'; import { useTheme } from '@material-ui/core'; +import { useParams, useNavigate } from 'react-router-dom'; +import { EntityName } from '@backstage/catalog-model'; +import { useApi } from '@backstage/core'; import { BackstageTheme } from '@backstage/theme'; +import { useShadowDom } from '..'; +import { techdocsStorageApiRef } from '../../api'; import TechDocsProgressBar from './TechDocsProgressBar'; import transformer, { @@ -39,7 +38,7 @@ import transformer, { import { TechDocsNotFound } from './TechDocsNotFound'; type Props = { - entityId: ParsedEntityId; + entityId: EntityName; onReady?: () => void; }; diff --git a/plugins/techdocs/src/reader/components/TechDocsPageHeader.tsx b/plugins/techdocs/src/reader/components/TechDocsPageHeader.tsx index 475b87da6f..d7a5a3c48f 100644 --- a/plugins/techdocs/src/reader/components/TechDocsPageHeader.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsPageHeader.tsx @@ -15,14 +15,14 @@ */ import React from 'react'; -import CodeIcon from '@material-ui/icons/Code'; -import { Header, HeaderLabel, Link } from '@backstage/core'; -import { CircularProgress } from '@material-ui/core'; -import { ParsedEntityId } from '../../types'; import { AsyncState } from 'react-use/lib/useAsync'; +import { CircularProgress } from '@material-ui/core'; +import CodeIcon from '@material-ui/icons/Code'; +import { EntityName } from '@backstage/catalog-model'; +import { Header, HeaderLabel, Link } from '@backstage/core'; type TechDocsPageHeaderProps = { - entityId: ParsedEntityId; + entityId: EntityName; metadataRequest: { entity: AsyncState; techdocs: AsyncState; diff --git a/plugins/techdocs/src/reader/transformers/addBaseUrl.ts b/plugins/techdocs/src/reader/transformers/addBaseUrl.ts index 7346ca7ce2..8d9d5a7294 100644 --- a/plugins/techdocs/src/reader/transformers/addBaseUrl.ts +++ b/plugins/techdocs/src/reader/transformers/addBaseUrl.ts @@ -13,14 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - +import { EntityName } from '@backstage/catalog-model'; import type { Transformer } from './index'; import { TechDocsStorage } from '../../api'; -import { ParsedEntityId } from '../../types'; type AddBaseUrlOptions = { techdocsStorageApi: TechDocsStorage; - entityId: ParsedEntityId; + entityId: EntityName; path: string; }; diff --git a/plugins/techdocs/src/types.ts b/plugins/techdocs/src/types.ts deleted file mode 100644 index 341d3f0c92..0000000000 --- a/plugins/techdocs/src/types.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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. - */ - -/** - * To uniquely identify an entity in Backstage. - * @property {string} kind - * @property {string} namespace - * @property {string} name - */ -export type ParsedEntityId = { - kind: string; - namespace?: string; - name: string; -}; From da2ad65cb5a0079cfd67a911e6e2ad3c6d0a12bc Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Sat, 28 Nov 2020 21:09:35 +0100 Subject: [PATCH 304/430] techdocs: Add changeset for entity type change --- .changeset/tidy-actors-repair.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tidy-actors-repair.md diff --git a/.changeset/tidy-actors-repair.md b/.changeset/tidy-actors-repair.md new file mode 100644 index 0000000000..4869400556 --- /dev/null +++ b/.changeset/tidy-actors-repair.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Use type EntityName from catalog-model for entities From d40eddac9d2515cf84e5eb03c3c08f114a1f8b45 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Sun, 29 Nov 2020 00:22:10 -0500 Subject: [PATCH 305/430] Refactor visibility typo --- packages/cli/src/commands/config/print.ts | 12 ++++++------ packages/cli/src/lib/config.ts | 2 +- .../config-loader/src/lib/schema/compile.test.ts | 2 +- .../config-loader/src/lib/schema/filtering.test.ts | 4 ++-- packages/config-loader/src/lib/schema/load.test.ts | 6 +++--- packages/config-loader/src/lib/schema/load.ts | 6 +++--- packages/config-loader/src/lib/schema/types.ts | 2 +- plugins/app-backend/src/lib/config.ts | 2 +- 8 files changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/cli/src/commands/config/print.ts b/packages/cli/src/commands/config/print.ts index 8148bfc9f8..8be88adce7 100644 --- a/packages/cli/src/commands/config/print.ts +++ b/packages/cli/src/commands/config/print.ts @@ -25,7 +25,7 @@ export default async (cmd: Command) => { args: cmd.config, fromPackage: cmd.package, }); - const visibility = getVisiblityOption(cmd); + const visibility = getVisibilityOption(cmd); const data = serializeConfigData(appConfigs, schema, visibility); if (cmd.format === 'json') { @@ -35,7 +35,7 @@ export default async (cmd: Command) => { } }; -function getVisiblityOption(cmd: Command): ConfigVisibility { +function getVisibilityOption(cmd: Command): ConfigVisibility { if (cmd.frontend && cmd.withSecrets) { throw new Error('Not allowed to combine frontend and secret config'); } @@ -50,14 +50,14 @@ function getVisiblityOption(cmd: Command): ConfigVisibility { function serializeConfigData( appConfigs: AppConfig[], schema: ConfigSchema, - visiblity: ConfigVisibility, + visibility: ConfigVisibility, ) { - if (visiblity === 'frontend') { + if (visibility === 'frontend') { const frontendConfigs = schema.process(appConfigs, { - visiblity: ['frontend'], + visibility: ['frontend'], }); return ConfigReader.fromConfigs(frontendConfigs).get(); - } else if (visiblity === 'secret') { + } else if (visibility === 'secret') { return ConfigReader.fromConfigs(appConfigs).get(); } diff --git a/packages/cli/src/lib/config.ts b/packages/cli/src/lib/config.ts index f6cc9c4269..88aac1e33c 100644 --- a/packages/cli/src/lib/config.ts +++ b/packages/cli/src/lib/config.ts @@ -51,7 +51,7 @@ export async function loadCliConfig(options: Options) { try { const frontendAppConfigs = schema.process(appConfigs, { - visiblity: ['frontend'], + visibility: ['frontend'], }); const frontendConfig = ConfigReader.fromConfigs(frontendAppConfigs); diff --git a/packages/config-loader/src/lib/schema/compile.test.ts b/packages/config-loader/src/lib/schema/compile.test.ts index 91e7aa687e..f03d7e6d10 100644 --- a/packages/config-loader/src/lib/schema/compile.test.ts +++ b/packages/config-loader/src/lib/schema/compile.test.ts @@ -89,7 +89,7 @@ describe('compileConfigSchemas', () => { }); }); - it('should reject visiblity conflicts', () => { + it('should reject visibility conflicts', () => { expect(() => compileConfigSchemas([ { diff --git a/packages/config-loader/src/lib/schema/filtering.test.ts b/packages/config-loader/src/lib/schema/filtering.test.ts index bfad2ca95a..f6926e3560 100644 --- a/packages/config-loader/src/lib/schema/filtering.test.ts +++ b/packages/config-loader/src/lib/schema/filtering.test.ts @@ -38,7 +38,7 @@ const data = { objS: { never: 'here' }, }; -const visiblity = new Map( +const visibility = new Map( Object.entries({ '.arr.0': 'frontend', '.arr.1': 'backend', @@ -100,6 +100,6 @@ describe('filterByVisibility', () => { ], [['frontend', 'backend', 'secret'], data], ])('should filter correctly with %p', (filter, expected) => { - expect(filterByVisibility(data, filter, visiblity)).toEqual(expected); + expect(filterByVisibility(data, filter, visibility)).toEqual(expected); }); }); diff --git a/packages/config-loader/src/lib/schema/load.test.ts b/packages/config-loader/src/lib/schema/load.test.ts index a13f36be20..baf63525bb 100644 --- a/packages/config-loader/src/lib/schema/load.test.ts +++ b/packages/config-loader/src/lib/schema/load.test.ts @@ -61,12 +61,12 @@ describe('loadConfigSchema', () => { const configs = [{ data: { key1: 'a', key2: 2 }, context: 'test' }]; expect(schema.process(configs)).toEqual(configs); - expect(schema.process(configs, { visiblity: ['frontend'] })).toEqual([ + expect(schema.process(configs, { visibility: ['frontend'] })).toEqual([ { data: { key1: 'a' }, context: 'test' }, ]); expect( schema.process(configs, { - visiblity: ['frontend'], + visibility: ['frontend'], valueTransform: () => 'X', }), ).toEqual([{ data: { key1: 'X' }, context: 'test' }]); @@ -79,7 +79,7 @@ describe('loadConfigSchema', () => { const serialized = schema.serialize(); const schema2 = await loadConfigSchema({ serialized }); - expect(schema2.process(configs, { visiblity: ['frontend'] })).toEqual([ + expect(schema2.process(configs, { visibility: ['frontend'] })).toEqual([ { data: { key1: 'a' }, context: 'test' }, ]); expect(() => diff --git a/packages/config-loader/src/lib/schema/load.ts b/packages/config-loader/src/lib/schema/load.ts index 01a9499983..67b9762f51 100644 --- a/packages/config-loader/src/lib/schema/load.ts +++ b/packages/config-loader/src/lib/schema/load.ts @@ -57,7 +57,7 @@ export async function loadConfigSchema( return { process( configs: AppConfig[], - { visiblity, valueTransform } = {}, + { visibility, valueTransform } = {}, ): AppConfig[] { const result = validate(configs); if (result.errors) { @@ -70,12 +70,12 @@ export async function loadConfigSchema( let processedConfigs = configs; - if (visiblity) { + if (visibility) { processedConfigs = processedConfigs.map(({ data, context }) => ({ context, data: filterByVisibility( data, - visiblity, + visibility, result.visibilityByPath, valueTransform, ), diff --git a/packages/config-loader/src/lib/schema/types.ts b/packages/config-loader/src/lib/schema/types.ts index 7705242b31..db3d964aa0 100644 --- a/packages/config-loader/src/lib/schema/types.ts +++ b/packages/config-loader/src/lib/schema/types.ts @@ -87,7 +87,7 @@ type ConfigProcessingOptions = { * The visibilities that should be included in the output data. * If omitted, the data will not be filtered by visibility. */ - visiblity?: ConfigVisibility[]; + visibility?: ConfigVisibility[]; /** * A transform function that can be used to transform primitive configuration values diff --git a/plugins/app-backend/src/lib/config.ts b/plugins/app-backend/src/lib/config.ts index f076967280..99590c1e2d 100644 --- a/plugins/app-backend/src/lib/config.ts +++ b/plugins/app-backend/src/lib/config.ts @@ -88,7 +88,7 @@ export async function readConfigs(options: ReadOptions): Promise { const frontendConfigs = await schema.process( [{ data: config.get() as JsonObject, context: 'app' }], - { visiblity: ['frontend'] }, + { visibility: ['frontend'] }, ); appConfigs.push(...frontendConfigs); } From 4e70917597a65e86742b93372868f1f46f4d30b3 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Sun, 29 Nov 2020 00:28:48 -0500 Subject: [PATCH 306/430] Reference changeset --- .changeset/grumpy-crews-build.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/grumpy-crews-build.md diff --git a/.changeset/grumpy-crews-build.md b/.changeset/grumpy-crews-build.md new file mode 100644 index 0000000000..2b7eece4e7 --- /dev/null +++ b/.changeset/grumpy-crews-build.md @@ -0,0 +1,7 @@ +--- +'example-app': patch +'@backstage/cli': patch +'@backstage/config-loader': patch +--- + +Fix typo of "visibility" in multiple references From 74175b988e4fca1647fd0a0523bfd1a3045fa647 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Sun, 29 Nov 2020 02:50:33 -0500 Subject: [PATCH 307/430] Refactor typo in variable name (#3486) --- .../src/components/ApiExplorerTable/ApiExplorerTable.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/api-docs/src/components/ApiExplorerTable/ApiExplorerTable.test.tsx b/plugins/api-docs/src/components/ApiExplorerTable/ApiExplorerTable.test.tsx index 408dc44151..494695e277 100644 --- a/plugins/api-docs/src/components/ApiExplorerTable/ApiExplorerTable.test.tsx +++ b/plugins/api-docs/src/components/ApiExplorerTable/ApiExplorerTable.test.tsx @@ -22,7 +22,7 @@ import * as React from 'react'; import { apiDocsConfigRef } from '../../config'; import { ApiExplorerTable } from './ApiExplorerTable'; -const entites: Entity[] = [ +const entities: Entity[] = [ { apiVersion: 'backstage.io/v1alpha1', kind: 'API', @@ -70,7 +70,7 @@ describe('ApiCatalogTable component', () => { const rendered = render( wrapInTestApp( - + , ), ); From 4122b3031fb01ef7b5e780799deea91c857ce717 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Sun, 29 Nov 2020 13:35:38 +0100 Subject: [PATCH 308/430] feat(backstage.io): Allow to zoom images like Medium --- .github/styles/vocab.txt | 1 + docs/features/techdocs/architecture.md | 4 ++-- microsite/README.md | 5 +++++ microsite/siteConfig.js | 6 +++++- microsite/static/css/custom.css | 5 +++++ microsite/static/js/medium-zoom.js | 11 +++++++++++ 6 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 microsite/static/js/medium-zoom.js diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt index a0841fe7d0..7f889392ac 100644 --- a/.github/styles/vocab.txt +++ b/.github/styles/vocab.txt @@ -230,3 +230,4 @@ yaml Zalando Zhou Zolotusky +zoomable diff --git a/docs/features/techdocs/architecture.md b/docs/features/techdocs/architecture.md index 01497b31bc..f502b3836e 100644 --- a/docs/features/techdocs/architecture.md +++ b/docs/features/techdocs/architecture.md @@ -9,7 +9,7 @@ description: Documentation on TechDocs Architecture When you deploy Backstage (with TechDocs enabled by default), you get a basic out-of-the box experience. -![TechDocs Architecture diagram](../../assets/techdocs/architecture-basic.drawio.svg) +TechDocs Architecture diagram > Note: See below for our recommended deployment architecture which takes care > of stability, scalability and speed. @@ -43,7 +43,7 @@ channel to talk about it. This is how we recommend deploying TechDocs in production environment. -![TechDocs Architecture diagram](../../assets/techdocs/architecture-recommended.drawio.svg) +TechDocs Architecture diagram The key difference in the recommended deployment approach is where the docs are built. diff --git a/microsite/README.md b/microsite/README.md index d244caa22a..9589def3e3 100644 --- a/microsite/README.md +++ b/microsite/README.md @@ -204,3 +204,8 @@ For more information about custom pages, click [here](https://docusaurus.io/docs # Full Documentation Full documentation can be found on the [website](https://docusaurus.io/). + +## Additional notes + +- If you want to make images zoomable on click, add the `data-zoomable` attribute to your `img` element. + - In a docs or blog `.md` file, convert `![This is image](/microsite/static/img/code.png)` syntax to `This is image` diff --git a/microsite/siteConfig.js b/microsite/siteConfig.js index 6e8f8edddf..517cac2496 100644 --- a/microsite/siteConfig.js +++ b/microsite/siteConfig.js @@ -86,7 +86,11 @@ const siteConfig = { }, // Add custom scripts here that would be placed in