From 5539625f645704e97fabc72eadc9fcf5094cf6b4 Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Mon, 19 Oct 2020 14:15:46 +0200 Subject: [PATCH 01/77] 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 65ba6140b2836..487e4ad0c568a 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 bdc38513186b7..3851556882379 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 0000000000000..13573efa9c466 --- /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 0000000000000..0335cb5a9bbba --- /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 0000000000000..264d6f801f5a2 --- /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 0000000000000..33054c6163aff --- /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 0000000000000..224e29389089d --- /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 0000000000000..8d4545ac12fd4 --- /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 0000000000000..8f8523b78bae1 --- /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 0000000000000..d7857386de33a --- /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 02/77] 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 0000000000000..3dadacadfbf7e --- /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 0000000000000..237ffa2011254 --- /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 224e29389089d..b24018887a8d2 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 03/77] 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 f30e7ff34fc3b..51467fc2bbecc 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 04/77] 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 0000000000000..217b93a400527 --- /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 0000000000000..6f6d194a77c8c --- /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 3dadacadfbf7e..cd51c4bcee994 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 0000000000000..b3b94f02007ff --- /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 4dba9ea97ae30e1e5b83ee6dcf251c141b41519c Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Wed, 21 Oct 2020 10:04:05 +0200 Subject: [PATCH 05/77] 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 cd51c4bcee994..e60575ee9beb0 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 0000000000000..578b86d50b282 --- /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 0000000000000..29378cce63fa5 --- /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 06/77] 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 33054c6163aff..e5d0264be3dda 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 0000000000000..d5db842dab999 --- /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 217b93a400527..74e253d51613e 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 6f6d194a77c8c..98a26c66dbaec 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 e60575ee9beb0..e0564d76ce183 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 578b86d50b282..eb051e68fa0d6 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 07/77] 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 51467fc2bbecc..7629c86a1e70a 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 d5db842dab999..fa42c82f9eb54 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 e0564d76ce183..917ae27090b03 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 08/77] 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 ef55b007a7b45..c31629e82e0e4 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 18fd44f0891b3..0ca17c7d7fb01 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 0000000000000..6dc3d8c9ba1cc --- /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 0000000000000..16a033dbc6027 --- /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 0000000000000..099a94a85f500 --- /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 0000000000000..cad4ba268ea59 --- /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 0000000000000..7612c392a2c33 --- /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 0000000000000..b96989e4b85ab --- /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 0000000000000..0aaeafa379667 --- /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 0000000000000..eb7cc734c2536 --- /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 0000000000000..73f928bef4dee --- /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 0000000000000..a5907fd52ff07 --- /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 09/77] 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 eb7cc734c2536..192a22019a783 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 10/77] added error handling --- .../pagerduty-backend/src/service/router.ts | 100 ++++++++++++------ plugins/pagerduty/src/api/pagerDutyClient.ts | 20 ---- .../src/components/PagerDutyServiceCard.tsx | 10 +- 3 files changed, 72 insertions(+), 58 deletions(-) diff --git a/plugins/pagerduty-backend/src/service/router.ts b/plugins/pagerduty-backend/src/service/router.ts index 192a22019a783..1fbab9b55169f 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); - - 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); + 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); } - - 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 fa42c82f9eb54..dc2176680df65 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 917ae27090b03..b7fde8eae3445 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 361049d9c2c7e8209b383db73c28bf6caee6b79a Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Tue, 3 Nov 2020 11:26:23 +0100 Subject: [PATCH 11/77] Refactor component to use pagerduty restApi directly --- .../app/src/components/catalog/EntityPage.tsx | 10 +- plugins/pagerduty/src/api/pagerDutyClient.ts | 44 +++++++++ .../pagerduty/src/components/Escalation.tsx | 4 +- .../pagerduty/src/components/Incidents.tsx | 5 +- .../src/components/PagerDutyServiceCard.tsx | 99 +++++++++---------- 5 files changed, 102 insertions(+), 60 deletions(-) diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index 7629c86a1e70a..ddc8ed4b1302c 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 dc2176680df65..dbdc6622f4d76 100644 --- a/plugins/pagerduty/src/api/pagerDutyClient.ts +++ b/plugins/pagerduty/src/api/pagerDutyClient.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +const API_URL = 'https://api.pagerduty.com'; const EVENTS_API_URL = 'https://events.pagerduty.com/v2'; type Options = { @@ -43,6 +44,49 @@ const request = async ( return await response.json(); }; +const getByUrl = async (url: string, token: string) => { + const options = { + method: 'GET', + headers: { + Authorization: `Token token=${token}`, + Accept: 'application/vnd.pagerduty+json;version=2', + 'Content-Type': 'application/json', + }, + }; + return await request(url, options); +}; + +export const getServiceByIntegrationKey = async ( + integrationKey: string, + token: string, +) => { + const response = await getByUrl( + `${API_URL}/services?include[]=integrations&include[]=escalation_policies&query=${integrationKey}`, + token, + ); + if (response.services.length > 1) { + throw new Error('More than one service in response'); + } + return response.services[0]; +}; + +export const getIncidentsByServiceId = async ( + serviceId: string, + token: string, +) => { + return await getByUrl( + `${API_URL}/incidents?service_ids[]=${serviceId}`, + token, + ); +}; + +export const getOncallByPolicyId = async (policyId: string, token: string) => { + return await getByUrl( + `${API_URL}/oncalls?include[]=users&escalation_policy_ids[]=${policyId}`, + token, + ); +}; + export function triggerPagerDutyAlarm( integrationKey: string, source: string, diff --git a/plugins/pagerduty/src/components/Escalation.tsx b/plugins/pagerduty/src/components/Escalation.tsx index 74e253d51613e..69a4543c4bbaf 100644 --- a/plugins/pagerduty/src/components/Escalation.tsx +++ b/plugins/pagerduty/src/components/Escalation.tsx @@ -102,8 +102,8 @@ type EscalationPolicyProps = { export const EscalationPolicy = ({ escalation }: EscalationPolicyProps) => ( Escalation Policy}> {escalation.length ? ( - escalation.map((user, index) => ( - + escalation.map((item, index) => ( + )) ) : ( diff --git a/plugins/pagerduty/src/components/Incidents.tsx b/plugins/pagerduty/src/components/Incidents.tsx index 98a26c66dbaec..d2f6fc7128b7c 100644 --- a/plugins/pagerduty/src/components/Incidents.tsx +++ b/plugins/pagerduty/src/components/Incidents.tsx @@ -80,8 +80,9 @@ const IncidentList = ({ incidents }: IncidentListProps) => { primary={incident.title} secondary={ - Created {moment(incident.createdAt).fromNow()}, assigned to{' '} - {(incident.assignees.length && incident.assignees[0].name) || + Created {moment(incident.created_at).fromNow()}, assigned to{' '} + {(incident?.assignments[0]?.assignee?.summary && + incident.assignments[0].assignee.summary) || 'nobody'} } diff --git a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx index b7fde8eae3445..e17e35a99660a 100644 --- a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx +++ b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx @@ -14,19 +14,18 @@ * limitations under the License. */ import React from 'react'; -import { - InfoCard, - MissingAnnotationEmptyState, - useApi, - configApiRef, -} from '@backstage/core'; +import { InfoCard, useApi, configApiRef } from '@backstage/core'; import { Entity } from '@backstage/catalog-model'; -import { Grid } from '@material-ui/core'; +import { Grid, LinearProgress } 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 { + getServiceByIntegrationKey, + getIncidentsByServiceId, + getOncallByPolicyId, +} from '../api/pagerDutyClient'; export const PAGERDUTY_INTEGRATION_KEY = 'pagerduty.com/integration-key'; @@ -40,65 +39,63 @@ type Props = { export const PagerDutyServiceCard = ({ entity }: Props) => { const configApi = useApi(configApiRef); + // TODO: handle missing token + const token = configApi.getOptionalString('pagerduty.api.token') ?? undefined; + console.log({ token }); + const { value, loading, error } = useAsync(async () => { - const response = await fetch( - `${configApi.getString('backend.baseUrl')}/api/pagerduty/services`, + const integrationKey = entity.metadata.annotations![ + PAGERDUTY_INTEGRATION_KEY + ]; + + const service = await getServiceByIntegrationKey(integrationKey, token); + const incidents = await getIncidentsByServiceId( + (service as any).id /*// TODO: fix type */, + token, ); - return await response.json(); + const oncalls = await getOncallByPolicyId( + (service as any).escalation_policy.id, // TODO: fix type + token, + ); + + return { + pagerDutyServices: [ + { + activeIncidents: incidents, + escalationPolicy: oncalls, + id: service.id, + name: service.name, + homepageUrl: service.html_url, + }, + ], + }; }); - if (value) console.log(value); - if (error) throw new Error(`Error in getting services, ${error}`); - // 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', - }, - ], - }; + if (error) { + // TODO: use the errorApi + console.log(error); + throw new Error(`Error in getting data: ${error.message}`); + } + + if (loading) { + return ; + } const { activeIncidents, escalationPolicy, homepageUrl, - } = mockData.pagerDutyServices[0]; + } = value!.pagerDutyServices[0]!; const link = { title: 'View in PagerDuty', link: homepageUrl, }; - return !isPluginApplicableToEntity(entity) ? ( - - ) : ( + return ( - - + + From 122410f1ab388d0bd48480d9d486c2b4ecd89212 Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Thu, 5 Nov 2020 10:49:11 +0100 Subject: [PATCH 12/77] fix typings --- .../pagerduty/src/components/Escalation.tsx | 12 +-- .../pagerduty/src/components/Incidents.tsx | 15 ++-- plugins/pagerduty/src/components/types.tsx | 84 ++++++++++++++++--- 3 files changed, 84 insertions(+), 27 deletions(-) diff --git a/plugins/pagerduty/src/components/Escalation.tsx b/plugins/pagerduty/src/components/Escalation.tsx index 69a4543c4bbaf..19c6a15096d9c 100644 --- a/plugins/pagerduty/src/components/Escalation.tsx +++ b/plugins/pagerduty/src/components/Escalation.tsx @@ -30,7 +30,7 @@ import UserIcon from '@material-ui/icons/Person'; import EmailIcon from '@material-ui/icons/Email'; import { StatusWarning } from '@backstage/core'; import Pagerduty from '../assets/pd.svg'; -import { PagerDutyUserData } from './types'; +import { Oncall, PagerDutyEscalationPolicy } from './types'; const useStyles = makeStyles({ denseListIcon: { @@ -45,11 +45,7 @@ const useStyles = makeStyles({ }, }); -type EscalationUserProps = { - user: PagerDutyUserData; -}; - -const EscalationUser = ({ user }: EscalationUserProps) => { +const EscalationUser = ({ user }: PagerDutyEscalationPolicy) => { const classes = useStyles(); return ( @@ -65,7 +61,7 @@ const EscalationUser = ({ user }: EscalationUserProps) => { @@ -96,7 +92,7 @@ const EscalationUsersEmptyState = () => { }; type EscalationPolicyProps = { - escalation: PagerDutyUserData[]; + escalation: Oncall[]; }; export const EscalationPolicy = ({ escalation }: EscalationPolicyProps) => ( diff --git a/plugins/pagerduty/src/components/Incidents.tsx b/plugins/pagerduty/src/components/Incidents.tsx index d2f6fc7128b7c..0117281a0ed20 100644 --- a/plugins/pagerduty/src/components/Incidents.tsx +++ b/plugins/pagerduty/src/components/Incidents.tsx @@ -29,6 +29,7 @@ import { import { StatusError, StatusWarning, StatusOK } from '@backstage/core'; import Pagerduty from '../assets/pd.svg'; import moment from 'moment'; +import { Incident } from '../components/types'; const useStyles = makeStyles({ denseListIcon: { @@ -57,13 +58,13 @@ const IncidentsEmptyState = () => { ); }; -type IncidentListProps = { - incidents: any; +type IncidentListItemProps = { + incident: Incident; }; -const IncidentList = ({ incidents }: IncidentListProps) => { +const IncidentListItem = ({ incident }: IncidentListItemProps) => { const classes = useStyles(); - return incidents.map((incident: any) => ( + return ( @@ -103,17 +104,17 @@ const IncidentList = ({ incidents }: IncidentListProps) => { - )); + ); }; type IncidentsProps = { - incidents: Array; + incidents: Incident[]; }; export const Incidents = ({ incidents }: IncidentsProps) => ( Incidents}> {incidents.length ? ( - + incidents.map(incident => ) ) : ( )} diff --git a/plugins/pagerduty/src/components/types.tsx b/plugins/pagerduty/src/components/types.tsx index b3b94f02007ff..377ae6d0eaded 100644 --- a/plugins/pagerduty/src/components/types.tsx +++ b/plugins/pagerduty/src/components/types.tsx @@ -1,27 +1,87 @@ -export type PagerDutyIncident = { +/* + * 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 Incident = { id: string; title: string; status: string; - createdAt: string; homepageUrl: string; - assignees: Partial[]; + assignments: [ + { + assignee: User; + }, + ]; + serviceId: string; + created_at: string; }; -export type PagerDutyUserData = { - email: string; - name: string; - homepageUrl: string; +export type Service = { id: string; + name: string; + html_url: string; + integrationKey: string; + escalation_policy: PagerDutyEscalationPolicy; +}; + +export type ResponseService = { + services: Service[]; + more: boolean; }; -export type PagerDutyServicesData = { - activeIncidents: PagerDutyIncident[]; - escalationPolicy: any; // PagerDutyUserData[]; +export type ResponseOncall = { id: string; name: string; + email: string; homepageUrl: string; }; -export type PagerDutyData = { - pagerDutyServices: PagerDutyServicesData[]; +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 PagerDutyEscalationPolicy = { + user: User; +}; + +export type User = { + id: string; + summary: string; + email: string; + html_url: string; + name: string; +}; + +export type Oncall = { + escalation_policy: PagerDutyEscalationPolicy; + user: User; }; From f7614ee6fce099c4e50403f979fdcbf38956193a Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Thu, 5 Nov 2020 11:17:19 +0100 Subject: [PATCH 13/77] add utility api and use it in pluign --- plugins/pagerduty/src/api/pagerDutyClient.ts | 196 ++++++++++-------- .../pagerduty/src/components/Incidents.tsx | 4 +- .../src/components/PagerDutyServiceCard.tsx | 66 +++--- .../src/components/TriggerDialog.tsx | 5 +- plugins/pagerduty/src/plugin.ts | 18 +- 5 files changed, 158 insertions(+), 131 deletions(-) diff --git a/plugins/pagerduty/src/api/pagerDutyClient.ts b/plugins/pagerduty/src/api/pagerDutyClient.ts index dbdc6622f4d76..09abaf856c68b 100644 --- a/plugins/pagerduty/src/api/pagerDutyClient.ts +++ b/plugins/pagerduty/src/api/pagerDutyClient.ts @@ -14,92 +14,78 @@ * 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; -}; - -const request = async ( - url: string, - options: any, //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); +import { createApiRef } from '@backstage/core'; +import { + Service, + Incident, + Options, + PagerDutyClientConfig, + ServicesResponse, + IncidentResponse, + OncallsResponse, + Oncall, +} from '../components/types'; + +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?: PagerDutyClientConfig) {} + + 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 { 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 { incidents } = await this.getByUrl(url); + + return incidents; } - return await response.json(); -}; - -const getByUrl = async (url: string, token: string) => { - const options = { - method: 'GET', - headers: { - Authorization: `Token token=${token}`, - Accept: 'application/vnd.pagerduty+json;version=2', - 'Content-Type': 'application/json', - }, - }; - return await request(url, options); -}; - -export const getServiceByIntegrationKey = async ( - integrationKey: string, - token: string, -) => { - const response = await getByUrl( - `${API_URL}/services?include[]=integrations&include[]=escalation_policies&query=${integrationKey}`, - token, - ); - if (response.services.length > 1) { - throw new Error('More than one service in response'); + 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 { oncalls } = await this.getByUrl(url); + + return oncalls; } - return response.services[0]; -}; - -export const getIncidentsByServiceId = async ( - serviceId: string, - token: string, -) => { - return await getByUrl( - `${API_URL}/incidents?service_ids[]=${serviceId}`, - token, - ); -}; - -export const getOncallByPolicyId = async (policyId: string, token: string) => { - return await getByUrl( - `${API_URL}/oncalls?include[]=users&escalation_policy_ids[]=${policyId}`, - token, - ); -}; - -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({ + + triggerPagerDutyAlarm( + integrationKey: string, + source: string, + description: string, + userName: string, + ) { + const body = JSON.stringify({ event_action: 'trigger', routing_key: integrationKey, client: 'Backstage', @@ -113,8 +99,46 @@ export function triggerPagerDutyAlarm( user: userName, }, }, - }), - }; + }); + + const options = { + method: 'POST', + headers: { + 'Content-Type': 'application/json; charset=UTF-8', + Accept: 'application/json, text/plain, */*', + }, + body, + }; + + return this.request(`${this.EVENTS_API_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', + }, + }; + const response = await this.request(url, options); - return request(`${EVENTS_API_URL}/enqueue`, options); + return response.json(); + } + + private async request(url: string, options: Options): 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); + } + } } diff --git a/plugins/pagerduty/src/components/Incidents.tsx b/plugins/pagerduty/src/components/Incidents.tsx index 0117281a0ed20..dd3301904db20 100644 --- a/plugins/pagerduty/src/components/Incidents.tsx +++ b/plugins/pagerduty/src/components/Incidents.tsx @@ -114,7 +114,9 @@ type IncidentsProps = { export const Incidents = ({ incidents }: IncidentsProps) => ( Incidents}> {incidents.length ? ( - incidents.map(incident => ) + incidents.map((incident, index) => ( + + )) ) : ( )} diff --git a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx index e17e35a99660a..54713b8ecd818 100644 --- a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx +++ b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx @@ -14,18 +14,15 @@ * limitations under the License. */ import React from 'react'; -import { InfoCard, useApi, configApiRef } from '@backstage/core'; +import { InfoCard, useApi } from '@backstage/core'; import { Entity } from '@backstage/catalog-model'; import { Grid, LinearProgress } from '@material-ui/core'; import { Incidents } from './Incidents'; import { EscalationPolicy } from './Escalation'; import { TriggerButton } from './TriggerButton'; import { useAsync } from 'react-use'; -import { - getServiceByIntegrationKey, - getIncidentsByServiceId, - getOncallByPolicyId, -} from '../api/pagerDutyClient'; +import { Alert } from '@material-ui/lab'; +import { pagerDutyApiRef } from '../api/pagerDutyClient'; export const PAGERDUTY_INTEGRATION_KEY = 'pagerduty.com/integration-key'; @@ -37,65 +34,52 @@ type Props = { }; export const PagerDutyServiceCard = ({ entity }: Props) => { - const configApi = useApi(configApiRef); - - // TODO: handle missing token - const token = configApi.getOptionalString('pagerduty.api.token') ?? undefined; - console.log({ token }); + const api = useApi(pagerDutyApiRef); const { value, loading, error } = useAsync(async () => { const integrationKey = entity.metadata.annotations![ PAGERDUTY_INTEGRATION_KEY ]; - const service = await getServiceByIntegrationKey(integrationKey, token); - const incidents = await getIncidentsByServiceId( - (service as any).id /*// TODO: fix type */, - token, - ); - const oncalls = await getOncallByPolicyId( - (service as any).escalation_policy.id, // TODO: fix type - token, - ); + 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); return { - pagerDutyServices: [ - { - activeIncidents: incidents, - escalationPolicy: oncalls, - id: service.id, - name: service.name, - homepageUrl: service.html_url, - }, - ], + incidents, + oncalls, + id: service.id, + name: service.name, + homepageUrl: service.html_url, }; }); if (error) { - // TODO: use the errorApi - console.log(error); - throw new Error(`Error in getting data: ${error.message}`); + return ( +
+ + Error encountered while fetching information. {error.message} + +
+ ); } if (loading) { return ; } - const { - activeIncidents, - escalationPolicy, - homepageUrl, - } = value!.pagerDutyServices[0]!; - const link = { title: 'View in PagerDuty', - link: homepageUrl, + link: value!.homepageUrl, }; return ( - - + + diff --git a/plugins/pagerduty/src/components/TriggerDialog.tsx b/plugins/pagerduty/src/components/TriggerDialog.tsx index 7dbb0be5f0d6c..13c2ba6aad819 100644 --- a/plugins/pagerduty/src/components/TriggerDialog.tsx +++ b/plugins/pagerduty/src/components/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 { triggerPagerDutyAlarm } from '../api/pagerDutyClient'; +import { pagerDutyApiRef } from '../api/pagerDutyClient'; const useStyles = makeStyles({ warningText: { @@ -48,6 +48,7 @@ export const TriggerDialog = ({ name, integrationKey, onClose }: 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); @@ -55,7 +56,7 @@ export const TriggerDialog = ({ name, integrationKey, onClose }: Props) => { const [{ value, loading, error }, triggerAlarm] = useAsyncFn( async (desc: string) => { - return await triggerPagerDutyAlarm( + return await api.triggerPagerDutyAlarm( integrationKey, window.location.toString(), desc, diff --git a/plugins/pagerduty/src/plugin.ts b/plugins/pagerduty/src/plugin.ts index 8f8523b78bae1..4073551dc8a06 100644 --- a/plugins/pagerduty/src/plugin.ts +++ b/plugins/pagerduty/src/plugin.ts @@ -13,7 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { createPlugin, createRouteRef } from '@backstage/core'; +import { + createApiFactory, + createPlugin, + createRouteRef, + configApiRef, +} from '@backstage/core'; +import { pagerDutyApiRef, PagerDutyClientApi } from './api/pagerDutyClient'; export const rootRouteRef = createRouteRef({ path: '/pagerduty', @@ -22,4 +28,14 @@ export const rootRouteRef = createRouteRef({ export const plugin = createPlugin({ id: 'pagerduty', + apis: [ + createApiFactory({ + api: pagerDutyApiRef, + deps: { configApi: configApiRef }, + factory: ({ configApi }) => + new PagerDutyClientApi({ + token: configApi.getOptionalString('pagerduty.api.token'), + }), + }), + ], }); From 2aed296e459dde1703a8376c6118b3c499c53686 Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Wed, 11 Nov 2020 14:24:40 +0100 Subject: [PATCH 14/77] change UI bsed on UX --- .../pagerduty/src/components/Escalation.tsx | 15 +- .../pagerduty/src/components/Incidents.tsx | 18 +-- .../src/components/PagerDutyServiceCard.tsx | 32 +++-- .../src/components/PagerdutyCard.tsx | 132 ++++++++++++++++++ plugins/pagerduty/src/components/Pd.tsx | 27 ++++ .../src/components/TriggerButton.tsx | 24 +++- .../src/components/TriggerDialog.tsx | 2 + plugins/pagerduty/src/components/types.tsx | 7 +- 8 files changed, 217 insertions(+), 40 deletions(-) create mode 100644 plugins/pagerduty/src/components/PagerdutyCard.tsx create mode 100644 plugins/pagerduty/src/components/Pd.tsx diff --git a/plugins/pagerduty/src/components/Escalation.tsx b/plugins/pagerduty/src/components/Escalation.tsx index 19c6a15096d9c..f53f79af41a6d 100644 --- a/plugins/pagerduty/src/components/Escalation.tsx +++ b/plugins/pagerduty/src/components/Escalation.tsx @@ -29,8 +29,8 @@ import { import UserIcon from '@material-ui/icons/Person'; import EmailIcon from '@material-ui/icons/Email'; import { StatusWarning } from '@backstage/core'; -import Pagerduty from '../assets/pd.svg'; -import { Oncall, PagerDutyEscalationPolicy } from './types'; +import { Oncall } from './types'; +import PagerdutyIcon from './Pd'; const useStyles = makeStyles({ denseListIcon: { @@ -45,7 +45,7 @@ const useStyles = makeStyles({ }, }); -const EscalationUser = ({ user }: PagerDutyEscalationPolicy) => { +const EscalationUser = ({ user }: Oncall) => { const classes = useStyles(); return ( @@ -56,7 +56,7 @@ const EscalationUser = ({ user }: PagerDutyEscalationPolicy) => { - + @@ -64,12 +64,9 @@ const EscalationUser = ({ user }: PagerDutyEscalationPolicy) => { href={user.html_url} target="_blank" rel="noopener noreferrer" + color="primary" > - View in PagerDuty +
diff --git a/plugins/pagerduty/src/components/Incidents.tsx b/plugins/pagerduty/src/components/Incidents.tsx index dd3301904db20..512836422c455 100644 --- a/plugins/pagerduty/src/components/Incidents.tsx +++ b/plugins/pagerduty/src/components/Incidents.tsx @@ -25,11 +25,12 @@ import { makeStyles, IconButton, ListSubheader, + Link, } from '@material-ui/core'; import { StatusError, StatusWarning, StatusOK } from '@backstage/core'; -import Pagerduty from '../assets/pd.svg'; import moment from 'moment'; import { Incident } from '../components/types'; +import PagerdutyIcon from './Pd'; const useStyles = makeStyles({ denseListIcon: { @@ -64,6 +65,7 @@ type IncidentListItemProps = { const IncidentListItem = ({ incident }: IncidentListItemProps) => { const classes = useStyles(); + const user = incident.assignments[0].assignee; return ( @@ -82,8 +84,9 @@ const IncidentListItem = ({ incident }: IncidentListItemProps) => { secondary={ Created {moment(incident.created_at).fromNow()}, assigned to{' '} - {(incident?.assignments[0]?.assignee?.summary && - incident.assignments[0].assignee.summary) || + {(incident?.assignments[0]?.assignee?.summary && ( + {user.summary} + )) || 'nobody'} } @@ -91,15 +94,12 @@ const IncidentListItem = ({ incident }: IncidentListItemProps) => { - View in PagerDuty + diff --git a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx index 54713b8ecd818..4fd4691458903 100644 --- a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx +++ b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx @@ -14,15 +14,16 @@ * limitations under the License. */ import React from 'react'; -import { InfoCard, useApi } from '@backstage/core'; +import { useApi } from '@backstage/core'; import { Entity } from '@backstage/catalog-model'; -import { Grid, LinearProgress } from '@material-ui/core'; +import { LinearProgress } from '@material-ui/core'; import { Incidents } from './Incidents'; import { EscalationPolicy } from './Escalation'; 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'; @@ -71,19 +72,26 @@ export const PagerDutyServiceCard = ({ entity }: Props) => { return ; } - const link = { + const pagerdutyLink = { title: 'View in PagerDuty', - link: value!.homepageUrl, + href: value!.homepageUrl, + }; + + const triggerAlarm = { + title: 'Trigger Alarm', + action: , }; return ( - - - - - - - {/* todo show something when we dont have token */} - + + + + + } + > ); }; diff --git a/plugins/pagerduty/src/components/PagerdutyCard.tsx b/plugins/pagerduty/src/components/PagerdutyCard.tsx new file mode 100644 index 0000000000000..2af4d2e4bc0e6 --- /dev/null +++ b/plugins/pagerduty/src/components/PagerdutyCard.tsx @@ -0,0 +1,132 @@ +import React from 'react'; +import { + Card, + CardHeader, + CardContent, + Divider, + Link, + makeStyles, +} from '@material-ui/core'; +import LinkIcon from '@material-ui/icons/Link'; +import ReportProblemIcon from '@material-ui/icons/ReportProblem'; +import classnames from 'classnames'; +import Pagerduty from '../assets/pd.svg'; +import PagerdutyIcon from './Pd'; + +// TODO: create a general component to use it in pagerduty and aboutCard + +const useStyles = makeStyles(theme => ({ + links: { + margin: theme.spacing(2, 0), + display: 'grid', + gridAutoFlow: 'column', + 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 }; +}; + +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) => { + const classes = useStyles(); + if (action) { + return ( + <> + + {icon} + {action} + + + ); + } + return ( + <> + + {icon} + {label} + + + ); +}; +export const PagerdutyCard = ({ + title, + subheader, + content, +}: PagerdutyCardProps) => { + const classes = useStyles(); + const getSubheader = ({ ViewPagerduty, TriggerAlarm }: SubHeaderProps) => { + return ( + + ); + }; + + return ( + + + + {content} + + ); +}; diff --git a/plugins/pagerduty/src/components/Pd.tsx b/plugins/pagerduty/src/components/Pd.tsx new file mode 100644 index 0000000000000..3698d0a1c497b --- /dev/null +++ b/plugins/pagerduty/src/components/Pd.tsx @@ -0,0 +1,27 @@ +// import React from 'react'; +// import SvgIcon, { SvgIconProps } from '@material-ui/core/SvgIcon'; + +// const SvgPd = (props: SvgIconProps) => +// React.createElement( +// SvgIcon, +// props, +// +// +// , +// ); + +// export default SvgPd; + +import React from 'react'; +import SvgIcon, { SvgIconProps } from '@material-ui/core/SvgIcon'; + +const SvgPd = (props: SvgIconProps) => + React.createElement( + SvgIcon, + props, + + + , + ); + +export default SvgPd; diff --git a/plugins/pagerduty/src/components/TriggerButton.tsx b/plugins/pagerduty/src/components/TriggerButton.tsx index eb051e68fa0d6..48bed68e2e9de 100644 --- a/plugins/pagerduty/src/components/TriggerButton.tsx +++ b/plugins/pagerduty/src/components/TriggerButton.tsx @@ -15,17 +15,34 @@ */ import React, { useState } from 'react'; -import { Button } from '@material-ui/core'; +import { Button, makeStyles } from '@material-ui/core'; import { TriggerDialog } from './TriggerDialog'; import { Entity } from '@backstage/catalog-model'; import { PAGERDUTY_INTEGRATION_KEY } from './PagerDutyServiceCard'; +const useStyles = makeStyles({ + triggerAlarm: { + paddingTop: 0, + paddingBottom: 0, + fontSize: '0.7rem', + textTransform: 'uppercase', + fontWeight: 600, + letterSpacing: 1.2, + lineHeight: 1.1, + '&: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); @@ -34,12 +51,11 @@ export const TriggerButton = ({ entity }: Props) => { return ( <> {showDialog && ( { the issue and reach out to you if necessary.

{ - - {loading && } ); }; From bc161fba4ebeedac9b39774ee4aacf8fab488de4 Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Fri, 20 Nov 2020 11:33:53 +0100 Subject: [PATCH 26/77] 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 0335cb5a9bbba..9c5c0b24857e4 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 d14f125ed1769b2c8fee7c51f572eb304b4af43d Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Fri, 20 Nov 2020 12:03:27 +0100 Subject: [PATCH 27/77] 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 cbce0ce4a3fb3..ccf70abb0b777 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 adc8d4e6d7196..642c9ecd96e93 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 6e22ccbbac106..7a97dad281ac7 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 6b6d46685082d..f24e91a9e05a9 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 a4f012ac47bd7..0000000000000 --- 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 6a58a4f43f5e1..0000000000000 --- 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 4156074460066..e6bb6d107d6e3 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 9c9e29cb019d9..e968656e65d6f 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 58dc39932dd9c..1e8e549ac92f2 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 28/77] 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 4fa65eb999852..15ff3278a0cad 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 fbe3783d3032e..6baa41002104f 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 0000000000000..bca6f9f940fd6 --- /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 7e26985136e9b..440621dcf6f3c 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 29/77] 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 0000000000000..0952612d846fe --- /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 60b290cd0bcbe4e1fccf04897041836c3f051f0d Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Fri, 27 Nov 2020 11:10:04 +0100 Subject: [PATCH 30/77] 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 9c5c0b24857e4..da37282aa7aef 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 c9d40a16941cd..4ed994eec24be 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 aa88559488d9070f93471a716b235ab07c55eb3e Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Fri, 27 Nov 2020 11:24:19 +0100 Subject: [PATCH 31/77] 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 e968656e65d6f..5e5ee0a61be47 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 2e07b5c430a18..02b11ed773106 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 ac6e65aa1ae4bcb280e5a8d76f8cf00f76430bd4 Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Fri, 27 Nov 2020 14:10:17 +0100 Subject: [PATCH 32/77] 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 0000000000000..c22552b7c6b7c --- /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 f24e91a9e05a9..22a471c031db9 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 33/77] 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 da37282aa7aef..b3e0b9f4e642b 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 43d0967db89e2cf32808d0ae4a02ed442a27c755 Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Fri, 27 Nov 2020 16:10:55 +0100 Subject: [PATCH 34/77] 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 7e1dc61fa72e4..4a8bf2a0abc6c 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 35/77] 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 4ed994eec24be..7b453b7298bc4 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 c07834a213701..17c2067818c38 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 6a02802d728031f19550b3cf26203e29b3e7008b Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Fri, 27 Nov 2020 16:59:16 +0100 Subject: [PATCH 36/77] 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 ccf70abb0b777..5e67d5217221b 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 2d2e5b01fd6bd..1d4ec3a1a638f 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 22a471c031db9..333e042f82f6e 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 d7857386de33a..0bfa67b49a755 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 d2f716d64083ad4769153bb6dd833d5e9b093604 Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Mon, 30 Nov 2020 10:57:16 +0100 Subject: [PATCH 37/77] fix changeset --- .changeset/great-spiders-repair.md | 1 - 1 file changed, 1 deletion(-) diff --git a/.changeset/great-spiders-repair.md b/.changeset/great-spiders-repair.md index 0952612d846fe..2cf1ddd8e8551 100644 --- a/.changeset/great-spiders-repair.md +++ b/.changeset/great-spiders-repair.md @@ -1,5 +1,4 @@ --- -'example-app': patch '@backstage/plugin-catalog': patch '@backstage/plugin-pagerduty': patch --- From 49938595bcc8b54a0074bc8d34a8c8b5b4f94382 Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Mon, 30 Nov 2020 18:31:35 +0100 Subject: [PATCH 38/77] improve updating incident list, refactor code, update tests, move types, rename types file --- plugins/pagerduty/README.md | 3 ++- plugins/pagerduty/src/api/client.ts | 13 +++++----- plugins/pagerduty/src/api/types.ts | 24 ++++++++++++++++++ .../components/Escalation/EscalationUser.tsx | 4 +-- .../components/Incident/IncidentListItem.tsx | 4 +-- .../components/Incident/Incidents.test.tsx | 6 ++--- .../src/components/Incident/Incidents.tsx | 12 +++------ .../src/components/PagerDutyIcon.tsx | 4 +-- .../src/components/PagerdutyCard.tsx | 16 ++++++------ .../TriggerDialog/TriggerDialog.test.tsx | 14 ++++++----- .../TriggerDialog/TriggerDialog.tsx | 24 +++++++++--------- .../src/components/{types.tsx => types.ts} | 25 ------------------- 12 files changed, 71 insertions(+), 78 deletions(-) rename plugins/pagerduty/src/components/{types.tsx => types.ts} (74%) diff --git a/plugins/pagerduty/README.md b/plugins/pagerduty/README.md index b3e0b9f4e642b..7a886627e307f 100644 --- a/plugins/pagerduty/README.md +++ b/plugins/pagerduty/README.md @@ -57,7 +57,8 @@ createApiFactory({ deps: { discoveryApi: discoveryApiRef }, factory: ({ discoveryApi }) => new PagerDutyClientApi({ - discoveryApi: discoveryApi + discoveryApi: discoveryApi, + eventUrl: "https://events.pagerduty.com/v2" //to override the default value }), }), ``` diff --git a/plugins/pagerduty/src/api/client.ts b/plugins/pagerduty/src/api/client.ts index 7b453b7298bc4..ab65514466cae 100644 --- a/plugins/pagerduty/src/api/client.ts +++ b/plugins/pagerduty/src/api/client.ts @@ -15,17 +15,16 @@ */ import { createApiRef } from '@backstage/core'; +import { Service, Incident, OnCall } from '../components/types'; import { - Service, - Incident, - RequestOptions, - ClientApiConfig, + PagerDutyClient, + TriggerAlarmRequest, ServicesResponse, IncidentsResponse, OnCallsResponse, - OnCall, -} from '../components/types'; -import { PagerDutyClient, TriggerAlarmRequest } from './types'; + ClientApiConfig, + RequestOptions, +} from './types'; export class UnauthorizedError extends Error {} diff --git a/plugins/pagerduty/src/api/types.ts b/plugins/pagerduty/src/api/types.ts index 17c2067818c38..2231cc6991e9d 100644 --- a/plugins/pagerduty/src/api/types.ts +++ b/plugins/pagerduty/src/api/types.ts @@ -15,6 +15,7 @@ */ import { Incident, OnCall, Service } from '../components/types'; +import { DiscoveryApi } from '@backstage/core'; export type TriggerAlarmRequest = { integrationKey: string; @@ -47,3 +48,26 @@ export interface PagerDutyClient { */ triggerAlarm(request: TriggerAlarmRequest): Promise; } + +export type ServicesResponse = { + services: Service[]; +}; + +export type IncidentsResponse = { + incidents: Incident[]; +}; + +export type OnCallsResponse = { + oncalls: OnCall[]; +}; + +export type ClientApiConfig = { + eventsUrl?: string; + discoveryApi: DiscoveryApi; +}; + +export type RequestOptions = { + method: string; + headers: HeadersInit; + body?: BodyInit; +}; diff --git a/plugins/pagerduty/src/components/Escalation/EscalationUser.tsx b/plugins/pagerduty/src/components/Escalation/EscalationUser.tsx index 642c9ecd96e93..3847896e89fe7 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 '../PagerDutyIcon'; +import { PagerDutyIcon } from '../PagerDutyIcon'; import { User } from '../types'; const useStyles = makeStyles({ @@ -69,7 +69,7 @@ export const EscalationUser = ({ user }: Props) => { rel="noopener noreferrer" color="primary" > - + diff --git a/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx b/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx index 7a97dad281ac7..488631b834418 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 '../PagerDutyIcon'; +import { PagerDutyIcon } from '../PagerDutyIcon'; const useStyles = makeStyles({ denseListIcon: { @@ -96,7 +96,7 @@ export const IncidentListItem = ({ incident }: Props) => { rel="noopener noreferrer" color="primary" > - + diff --git a/plugins/pagerduty/src/components/Incident/Incidents.test.tsx b/plugins/pagerduty/src/components/Incident/Incidents.test.tsx index 6baa41002104f..3eef24cc6ae8c 100644 --- a/plugins/pagerduty/src/components/Incident/Incidents.test.tsx +++ b/plugins/pagerduty/src/components/Incident/Incidents.test.tsx @@ -37,7 +37,7 @@ describe('Incidents', () => { {}} /> , @@ -101,7 +101,7 @@ describe('Incidents', () => { {}} /> , @@ -131,7 +131,7 @@ describe('Incidents', () => { {}} /> , diff --git a/plugins/pagerduty/src/components/Incident/Incidents.tsx b/plugins/pagerduty/src/components/Incident/Incidents.tsx index 1d4ec3a1a638f..4b229fb4bc699 100644 --- a/plugins/pagerduty/src/components/Incident/Incidents.tsx +++ b/plugins/pagerduty/src/components/Incident/Incidents.tsx @@ -25,27 +25,21 @@ import { Alert } from '@material-ui/lab'; type Props = { serviceId: string; - shouldRefreshIncidents: boolean; - setShouldRefreshIncidents: (shouldRefreshIncidents: boolean) => void; + refreshIncidents: Boolean; }; -export const Incidents = ({ - serviceId, - shouldRefreshIncidents, - setShouldRefreshIncidents, -}: Props) => { +export const Incidents = ({ serviceId, refreshIncidents }: Props) => { const api = useApi(pagerDutyApiRef); const [{ value: incidents, loading, error }, getIncidents] = useAsyncFn( async () => { - setShouldRefreshIncidents(false); return await api.getIncidentsByServiceId(serviceId); }, ); useEffect(() => { getIncidents(); - }, [shouldRefreshIncidents, getIncidents]); + }, [refreshIncidents, getIncidents]); if (error) { return ( diff --git a/plugins/pagerduty/src/components/PagerDutyIcon.tsx b/plugins/pagerduty/src/components/PagerDutyIcon.tsx index 36f6e1989d4ff..67cfc0036ae73 100644 --- a/plugins/pagerduty/src/components/PagerDutyIcon.tsx +++ b/plugins/pagerduty/src/components/PagerDutyIcon.tsx @@ -17,7 +17,7 @@ import React from 'react'; import SvgIcon, { SvgIconProps } from '@material-ui/core/SvgIcon'; -const SvgPd = (props: SvgIconProps) => +export const PagerDutyIcon = (props: SvgIconProps) => React.createElement( SvgIcon, props, @@ -25,5 +25,3 @@ const SvgPd = (props: SvgIconProps) => , ); - -export default SvgPd; diff --git a/plugins/pagerduty/src/components/PagerdutyCard.tsx b/plugins/pagerduty/src/components/PagerdutyCard.tsx index 333e042f82f6e..ac309e8a95de2 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 } from '@backstage/core'; +import { useApi, Progress } from '@backstage/core'; import { Entity } from '@backstage/catalog-model'; import { Button, @@ -34,7 +34,6 @@ 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: { @@ -72,9 +71,7 @@ export const PagerDutyCard = ({ entity }: Props) => { const classes = useStyles(); const api = useApi(pagerDutyApiRef); const [showDialog, setShowDialog] = useState(false); - const [shouldRefreshIncidents, setShouldRefreshIncidents] = useState( - false, - ); + const [refreshIncidents, setRefreshIncidents] = useState(false); const integrationKey = entity.metadata.annotations![ PAGERDUTY_INTEGRATION_KEY ]; @@ -129,6 +126,10 @@ export const PagerDutyCard = ({ entity }: Props) => { ), }; + const onTriggerRefresh = () => { + setRefreshIncidents(true); + }; + return ( { { handleDialog={handleDialog} name={entity.metadata.name} integrationKey={integrationKey} - setShouldRefreshIncidents={setShouldRefreshIncidents} + onTriggerRefresh={onTriggerRefresh} /> diff --git a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx index 440621dcf6f3c..3d2a48b419021 100644 --- a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx +++ b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx @@ -71,7 +71,7 @@ describe('TriggerDialog', () => { handleDialog={() => {}} name={entity.metadata.name} integrationKey="abc123" - setShouldRefreshIncidents={() => {}} + onTriggerRefresh={() => {}} /> , ), @@ -93,11 +93,13 @@ describe('TriggerDialog', () => { fireEvent.click(triggerButton); }); expect(mockTriggerAlarmFn).toHaveBeenCalled(); - expect(mockTriggerAlarmFn).toHaveBeenCalledWith( - entity!.metadata!.annotations!['pagerduty.com/integration-key'], - window.location.toString(), + expect(mockTriggerAlarmFn).toHaveBeenCalledWith({ + integrationKey: entity!.metadata!.annotations![ + 'pagerduty.com/integration-key' + ], + source: window.location.toString(), description, - 'guest@example.com', - ); + userName: 'guest@example.com', + }); }); }); diff --git a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx index e6bb6d107d6e3..4717b8fece059 100644 --- a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx +++ b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx @@ -35,7 +35,7 @@ type Props = { integrationKey: string; showDialog: boolean; handleDialog: () => void; - setShouldRefreshIncidents: (shouldRefreshIncidents: boolean) => void; + onTriggerRefresh: () => void; }; export const TriggerDialog = ({ @@ -43,22 +43,22 @@ export const TriggerDialog = ({ integrationKey, showDialog, handleDialog, - setShouldRefreshIncidents, + onTriggerRefresh, }: Props) => { const alertApi = useApi(alertApiRef); const identityApi = useApi(identityApiRef); - const userId = identityApi.getUserId(); + const userName = identityApi.getUserId(); const api = useApi(pagerDutyApiRef); const [description, setDescription] = useState(''); const [{ value, loading, error }, handleTriggerAlarm] = useAsyncFn( - async (desc: string) => - await api.triggerAlarm( + async (description: string) => + await api.triggerAlarm({ integrationKey, - window.location.toString(), - desc, - userId, - ), + source: window.location.toString(), + description, + userName, + }), ); const descriptionChanged = ( @@ -70,9 +70,9 @@ export const TriggerDialog = ({ useEffect(() => { if (value) { alertApi.post({ - message: `Alarm successfully triggered by ${userId}`, + message: `Alarm successfully triggered by ${userName}`, }); - setShouldRefreshIncidents(true); + onTriggerRefresh(); handleDialog(); } @@ -82,7 +82,7 @@ export const TriggerDialog = ({ severity: 'error', }); } - }, [value, error, alertApi, handleDialog, userId, setShouldRefreshIncidents]); + }, [value, error, alertApi, handleDialog, userName, onTriggerRefresh]); if (!showDialog) { return null; diff --git a/plugins/pagerduty/src/components/types.tsx b/plugins/pagerduty/src/components/types.ts similarity index 74% rename from plugins/pagerduty/src/components/types.tsx rename to plugins/pagerduty/src/components/types.ts index 5e5ee0a61be47..a93791fe8e050 100644 --- a/plugins/pagerduty/src/components/types.tsx +++ b/plugins/pagerduty/src/components/types.ts @@ -14,8 +14,6 @@ * limitations under the License. */ -import { DiscoveryApi } from '@backstage/core'; - export type Incident = { id: string; title: string; @@ -58,26 +56,3 @@ export type User = { html_url: string; name: 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; -}; - -export type ClientApiConfig = { - eventsUrl?: string; - discoveryApi: DiscoveryApi; -}; From 04a53de52fdd9424d35bd398904ff21989853153 Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Tue, 1 Dec 2020 13:46:46 +0100 Subject: [PATCH 39/77] rename type and class, fix test --- plugins/pagerduty/README.md | 2 +- plugins/pagerduty/src/api/client.ts | 6 +++--- plugins/pagerduty/src/api/index.ts | 8 ++------ plugins/pagerduty/src/api/types.ts | 2 +- .../src/components/Incident/Incidents.test.tsx | 18 +++--------------- .../src/components/PagerDutyCard.test.tsx | 2 +- plugins/pagerduty/src/index.ts | 4 ++-- plugins/pagerduty/src/plugin.ts | 4 ++-- 8 files changed, 15 insertions(+), 31 deletions(-) diff --git a/plugins/pagerduty/README.md b/plugins/pagerduty/README.md index 7a886627e307f..b925e9d7a767f 100644 --- a/plugins/pagerduty/README.md +++ b/plugins/pagerduty/README.md @@ -56,7 +56,7 @@ createApiFactory({ api: pagerDutyApiRef, deps: { discoveryApi: discoveryApiRef }, factory: ({ discoveryApi }) => - new PagerDutyClientApi({ + new PagerDutyClient({ discoveryApi: discoveryApi, eventUrl: "https://events.pagerduty.com/v2" //to override the default value }), diff --git a/plugins/pagerduty/src/api/client.ts b/plugins/pagerduty/src/api/client.ts index ab65514466cae..214e7d57a74ef 100644 --- a/plugins/pagerduty/src/api/client.ts +++ b/plugins/pagerduty/src/api/client.ts @@ -17,7 +17,7 @@ import { createApiRef } from '@backstage/core'; import { Service, Incident, OnCall } from '../components/types'; import { - PagerDutyClient, + PagerDutyApi, TriggerAlarmRequest, ServicesResponse, IncidentsResponse, @@ -28,12 +28,12 @@ import { 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', }); -export class PagerDutyClientApi implements PagerDutyClient { +export class PagerDutyClient implements PagerDutyApi { constructor(private readonly config: ClientApiConfig) {} async getServiceByIntegrationKey(integrationKey: string): Promise { diff --git a/plugins/pagerduty/src/api/index.ts b/plugins/pagerduty/src/api/index.ts index 5ac7470475b49..90604c40126be 100644 --- a/plugins/pagerduty/src/api/index.ts +++ b/plugins/pagerduty/src/api/index.ts @@ -14,9 +14,5 @@ * limitations under the License. */ -export { - PagerDutyClientApi, - pagerDutyApiRef, - UnauthorizedError, -} from './client'; -export type { PagerDutyClient } from './types'; +export { PagerDutyClient, pagerDutyApiRef, UnauthorizedError } from './client'; +export type { PagerDutyApi } from './types'; diff --git a/plugins/pagerduty/src/api/types.ts b/plugins/pagerduty/src/api/types.ts index 2231cc6991e9d..4b3ae74cc18e8 100644 --- a/plugins/pagerduty/src/api/types.ts +++ b/plugins/pagerduty/src/api/types.ts @@ -24,7 +24,7 @@ export type TriggerAlarmRequest = { userName: string; }; -export interface PagerDutyClient { +export interface PagerDutyApi { /** * Fetches a list of services, filtered by the provided integration key. * diff --git a/plugins/pagerduty/src/components/Incident/Incidents.test.tsx b/plugins/pagerduty/src/components/Incident/Incidents.test.tsx index 3eef24cc6ae8c..3c5e30e0ccfa8 100644 --- a/plugins/pagerduty/src/components/Incident/Incidents.test.tsx +++ b/plugins/pagerduty/src/components/Incident/Incidents.test.tsx @@ -35,11 +35,7 @@ describe('Incidents', () => { const { getByText, queryByTestId } = render( wrapInTestApp( - {}} - /> + , ), ); @@ -99,11 +95,7 @@ describe('Incidents', () => { } = render( wrapInTestApp( - {}} - /> + , ), ); @@ -129,11 +121,7 @@ describe('Incidents', () => { const { getByText, queryByTestId } = render( wrapInTestApp( - {}} - /> + , ), ); diff --git a/plugins/pagerduty/src/components/PagerDutyCard.test.tsx b/plugins/pagerduty/src/components/PagerDutyCard.test.tsx index bca6f9f940fd6..26d67a55e4065 100644 --- a/plugins/pagerduty/src/components/PagerDutyCard.test.tsx +++ b/plugins/pagerduty/src/components/PagerDutyCard.test.tsx @@ -15,7 +15,7 @@ */ import React from 'react'; import { render, waitFor, fireEvent } from '@testing-library/react'; -import { PagerDutyCard } from './PagerdutyCard'; +import { PagerDutyCard } from './PagerDutyCard'; import { Entity } from '@backstage/catalog-model'; import { wrapInTestApp } from '@backstage/test-utils'; import { diff --git a/plugins/pagerduty/src/index.ts b/plugins/pagerduty/src/index.ts index 1e8e549ac92f2..4ecd4edcc63a2 100644 --- a/plugins/pagerduty/src/index.ts +++ b/plugins/pagerduty/src/index.ts @@ -17,9 +17,9 @@ export { plugin } from './plugin'; export { isPluginApplicableToEntity, PagerDutyCard, -} from './components/PagerdutyCard'; +} from './components/PagerDutyCard'; export { - PagerDutyClientApi, + PagerDutyClient, pagerDutyApiRef, UnauthorizedError, } from './api/client'; diff --git a/plugins/pagerduty/src/plugin.ts b/plugins/pagerduty/src/plugin.ts index 02b11ed773106..4e8f429b059d0 100644 --- a/plugins/pagerduty/src/plugin.ts +++ b/plugins/pagerduty/src/plugin.ts @@ -19,7 +19,7 @@ import { createRouteRef, discoveryApiRef, } from '@backstage/core'; -import { pagerDutyApiRef, PagerDutyClientApi } from './api'; +import { pagerDutyApiRef, PagerDutyClient } from './api'; export const rootRouteRef = createRouteRef({ path: '/pagerduty', @@ -33,7 +33,7 @@ export const plugin = createPlugin({ api: pagerDutyApiRef, deps: { discoveryApi: discoveryApiRef }, factory: ({ discoveryApi }) => - new PagerDutyClientApi({ + new PagerDutyClient({ discoveryApi: discoveryApi, }), }), From 3d392a0b6acfe0852d8c7eb83200dc8d276e5a48 Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Tue, 1 Dec 2020 16:38:40 +0100 Subject: [PATCH 40/77] address comments --- app-config.yaml | 2 ++ packages/app/package.json | 6 +++--- packages/app/src/plugins.ts | 2 +- plugins/pagerduty/README.md | 17 +++++------------ plugins/pagerduty/src/api/client.ts | 14 ++++++++++++-- plugins/pagerduty/src/api/types.ts | 2 +- .../src/components/PagerDutyCard.test.tsx | 3 +-- .../TriggerDialog/TriggerDialog.test.tsx | 3 +-- plugins/pagerduty/src/plugin.ts | 9 ++++----- 9 files changed, 30 insertions(+), 28 deletions(-) diff --git a/app-config.yaml b/app-config.yaml index 18eeb24400845..e80b5df0213ac 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -312,3 +312,5 @@ homepage: timezone: 'Europe/Stockholm' - label: TYO timezone: 'Asia/Tokyo' +pagerduty: + eventsBaseUrl: 'https://events.pagerduty.com/v2' diff --git a/packages/app/package.json b/packages/app/package.json index b0499c8431ab9..f78a3474ff07b 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -21,25 +21,25 @@ "@backstage/plugin-kubernetes": "^0.3.0", "@backstage/plugin-lighthouse": "^0.2.3", "@backstage/plugin-newrelic": "^0.2.1", + "@backstage/plugin-pagerduty": "0.2.1", "@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-sentry": "^0.2.3", "@backstage/plugin-tech-radar": "^0.3.0", "@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", - "@backstage/plugin-pagerduty": "^0.2.1", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@octokit/rest": "^18.0.0", + "@roadiehq/backstage-plugin-buildkite": "^0.1.3", "@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/plugins.ts b/packages/app/src/plugins.ts index 3fdf236e8d686..d70694969ee3b 100644 --- a/packages/app/src/plugins.ts +++ b/packages/app/src/plugins.ts @@ -38,6 +38,6 @@ 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'; +export { plugin as PagerDuty } from '@backstage/plugin-pagerduty'; export { plugin as Buildkite } from '@roadiehq/backstage-plugin-buildkite'; export { plugin as Search } from '@backstage/plugin-search'; diff --git a/plugins/pagerduty/README.md b/plugins/pagerduty/README.md index b925e9d7a767f..1c22053b889b5 100644 --- a/plugins/pagerduty/README.md +++ b/plugins/pagerduty/README.md @@ -47,20 +47,13 @@ import { ## Client configuration -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". +If you want to override the default URL for events, you can add it to `app-config.yaml`. -In `apis.ts`: +In `app-config.yaml`: -```ts -createApiFactory({ - api: pagerDutyApiRef, - deps: { discoveryApi: discoveryApiRef }, - factory: ({ discoveryApi }) => - new PagerDutyClient({ - discoveryApi: discoveryApi, - eventUrl: "https://events.pagerduty.com/v2" //to override the default value - }), -}), +```yaml +pagerduty: + eventsBaseUrl: 'https://events.pagerduty.com/v2' ``` ## Providing the API Token diff --git a/plugins/pagerduty/src/api/client.ts b/plugins/pagerduty/src/api/client.ts index 214e7d57a74ef..99e21ef6d366a 100644 --- a/plugins/pagerduty/src/api/client.ts +++ b/plugins/pagerduty/src/api/client.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { createApiRef } from '@backstage/core'; +import { createApiRef, DiscoveryApi, ConfigApi } from '@backstage/core'; import { Service, Incident, OnCall } from '../components/types'; import { PagerDutyApi, @@ -34,6 +34,15 @@ export const pagerDutyApiRef = createApiRef({ }); export class PagerDutyClient implements PagerDutyApi { + static fromConfig(configApi: ConfigApi, discoveryApi: DiscoveryApi) { + const eventsBaseUrl: string = + configApi.getOptionalString('pagerDuty.eventsBaseUrl') ?? + 'https://events.pagerduty.com/v2'; + return new PagerDutyClient({ + eventsBaseUrl, + discoveryApi, + }); + } constructor(private readonly config: ClientApiConfig) {} async getServiceByIntegrationKey(integrationKey: string): Promise { @@ -98,7 +107,8 @@ export class PagerDutyClient implements PagerDutyApi { }; return this.request( - `${this.config.eventsUrl ?? 'https://events.pagerduty.com/v2'}/enqueue`, + `${this.config.eventsBaseUrl ?? + 'https://events.pagerduty.com/v2'}/enqueue`, options, ); } diff --git a/plugins/pagerduty/src/api/types.ts b/plugins/pagerduty/src/api/types.ts index 4b3ae74cc18e8..733f171489bef 100644 --- a/plugins/pagerduty/src/api/types.ts +++ b/plugins/pagerduty/src/api/types.ts @@ -62,7 +62,7 @@ export type OnCallsResponse = { }; export type ClientApiConfig = { - eventsUrl?: string; + eventsBaseUrl?: string; discoveryApi: DiscoveryApi; }; diff --git a/plugins/pagerduty/src/components/PagerDutyCard.test.tsx b/plugins/pagerduty/src/components/PagerDutyCard.test.tsx index 26d67a55e4065..34ddb63f71386 100644 --- a/plugins/pagerduty/src/components/PagerDutyCard.test.tsx +++ b/plugins/pagerduty/src/components/PagerDutyCard.test.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ import React from 'react'; -import { render, waitFor, fireEvent } from '@testing-library/react'; +import { render, waitFor, fireEvent, act } from '@testing-library/react'; import { PagerDutyCard } from './PagerDutyCard'; import { Entity } from '@backstage/catalog-model'; import { wrapInTestApp } from '@backstage/test-utils'; @@ -26,7 +26,6 @@ import { } 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 () => [], diff --git a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx index 3d2a48b419021..7ef57d67b474c 100644 --- a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx +++ b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ import React from 'react'; -import { render, fireEvent } from '@testing-library/react'; +import { render, fireEvent, act } from '@testing-library/react'; import { wrapInTestApp } from '@backstage/test-utils'; import { ApiRegistry, @@ -26,7 +26,6 @@ import { } from '@backstage/core'; import { pagerDutyApiRef } from '../../api'; import { Entity } from '@backstage/catalog-model'; -import { act } from 'react-dom/test-utils'; import { TriggerDialog } from './TriggerDialog'; describe('TriggerDialog', () => { diff --git a/plugins/pagerduty/src/plugin.ts b/plugins/pagerduty/src/plugin.ts index 4e8f429b059d0..34796f491ec0c 100644 --- a/plugins/pagerduty/src/plugin.ts +++ b/plugins/pagerduty/src/plugin.ts @@ -18,6 +18,7 @@ import { createPlugin, createRouteRef, discoveryApiRef, + configApiRef, } from '@backstage/core'; import { pagerDutyApiRef, PagerDutyClient } from './api'; @@ -31,11 +32,9 @@ export const plugin = createPlugin({ apis: [ createApiFactory({ api: pagerDutyApiRef, - deps: { discoveryApi: discoveryApiRef }, - factory: ({ discoveryApi }) => - new PagerDutyClient({ - discoveryApi: discoveryApi, - }), + deps: { discoveryApi: discoveryApiRef, configApi: configApiRef }, + factory: ({ configApi, discoveryApi }) => + PagerDutyClient.fromConfig(configApi, discoveryApi), }), ], }); From fa25d9f448934593c32ee43c80fe41f9252ed1c0 Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Tue, 1 Dec 2020 16:58:35 +0100 Subject: [PATCH 41/77] rename file --- .../src/components/PagerDutyCard.tsx | 169 ++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 plugins/pagerduty/src/components/PagerDutyCard.tsx diff --git a/plugins/pagerduty/src/components/PagerDutyCard.tsx b/plugins/pagerduty/src/components/PagerDutyCard.tsx new file mode 100644 index 0000000000000..ac309e8a95de2 --- /dev/null +++ b/plugins/pagerduty/src/components/PagerDutyCard.tsx @@ -0,0 +1,169 @@ +/* + * 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 { useApi, Progress } from '@backstage/core'; +import { Entity } from '@backstage/catalog-model'; +import { + Button, + Card, + CardContent, + CardHeader, + Divider, + makeStyles, +} from '@material-ui/core'; +import { Incidents } from './Incident'; +import { EscalationPolicy } from './Escalation'; +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 AlarmAddIcon from '@material-ui/icons/AlarmAdd'; +import { TriggerDialog } from './TriggerDialog'; +import { MissingTokenError } from './MissingTokenError'; + +const useStyles = makeStyles(theme => ({ + links: { + margin: theme.spacing(2, 0), + display: 'grid', + gridAutoFlow: 'column', + 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'; + +export const isPluginApplicableToEntity = (entity: Entity) => + Boolean(entity.metadata.annotations?.[PAGERDUTY_INTEGRATION_KEY]); + +type Props = { + entity: Entity; +}; + +export const PagerDutyCard = ({ entity }: Props) => { + const classes = useStyles(); + const api = useApi(pagerDutyApiRef); + const [showDialog, setShowDialog] = useState(false); + const [refreshIncidents, setRefreshIncidents] = useState(false); + const integrationKey = entity.metadata.annotations![ + PAGERDUTY_INTEGRATION_KEY + ]; + + const { value: service, loading, error } = useAsync(async () => { + const services = await api.getServiceByIntegrationKey(integrationKey); + + return { + id: services[0].id, + name: services[0].name, + url: services[0].html_url, + policyId: services[0].escalation_policy.id, + }; + }); + + const handleDialog = () => { + setShowDialog(!showDialog); + }; + + if (error instanceof UnauthorizedError) { + return ; + } + + if (error) { + return ( + + Error encountered while fetching information. {error.message} + + ); + } + + if (loading) { + return ; + } + + const pagerdutyLink = { + title: 'View in PagerDuty', + href: service!.url, + }; + + const triggerAlarm = { + title: 'Trigger Alarm', + action: ( + + ), + }; + + const onTriggerRefresh = () => { + setRefreshIncidents(true); + }; + + return ( + + + } + /> + } + action={triggerAlarm.action} + /> + + } + /> + + + + + + + + ); +}; From e7807c4c79522491ef3705aa26845aad3dc20444 Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Tue, 1 Dec 2020 18:23:39 +0100 Subject: [PATCH 42/77] remove moment --- plugins/pagerduty/package.json | 12 ++++++------ .../src/components/Incident/IncidentListItem.tsx | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json index 2fce2ebef321a..64cc5a089362a 100644 --- a/plugins/pagerduty/package.json +++ b/plugins/pagerduty/package.json @@ -20,21 +20,21 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.3.0", - "@backstage/theme": "^0.2.1", "@backstage/catalog-model": "^0.2.0", - "@backstage/test-utils": "^0.1.2", + "@backstage/core": "^0.3.0", "@backstage/plugin-catalog": "^0.2.1", + "@backstage/test-utils": "^0.1.2", + "@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", + "date-fns": "^2.16.1", "react": "^16.13.1", "react-dom": "^16.13.1", - "react-use": "^15.3.3", - "moment": "^2.27.0" + "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.2.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx b/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx index 488631b834418..7a34501325d36 100644 --- a/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx +++ b/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx @@ -27,7 +27,7 @@ import { Typography, } from '@material-ui/core'; import { StatusError, StatusWarning } from '@backstage/core'; -import moment from 'moment'; +import { formatDistanceToNowStrict } from 'date-fns'; import { Incident } from '../types'; import { PagerDutyIcon } from '../PagerDutyIcon'; @@ -54,7 +54,7 @@ type Props = { export const IncidentListItem = ({ incident }: Props) => { const classes = useStyles(); const user = incident.assignments[0]?.assignee; - const createdAt = moment(incident.created_at).fromNow(); + const createdAt = formatDistanceToNowStrict(new Date(incident.created_at)); return ( @@ -77,7 +77,7 @@ export const IncidentListItem = ({ incident }: Props) => { }} secondary={ - Created {createdAt} and assigned to{' '} + Created {createdAt} ago and assigned to{' '} Date: Wed, 2 Dec 2020 21:04:42 -0500 Subject: [PATCH 43/77] Align plugin ID --- plugins/catalog-import/src/api/CatalogImportApi.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/catalog-import/src/api/CatalogImportApi.ts b/plugins/catalog-import/src/api/CatalogImportApi.ts index 49012764e2e29..0ce569171ff0a 100644 --- a/plugins/catalog-import/src/api/CatalogImportApi.ts +++ b/plugins/catalog-import/src/api/CatalogImportApi.ts @@ -18,7 +18,7 @@ import { createApiRef } from '@backstage/core'; import { PartialEntity } from '../util/types'; export const catalogImportApiRef = createApiRef({ - id: 'plugin.catalogimport.service', + id: 'plugin.catalog-import.service', description: 'Used by the catalog import plugin to make requests', }); From c02db852a2fd577d5f21458b696ea1e8ead46f87 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Wed, 2 Dec 2020 21:05:16 -0500 Subject: [PATCH 44/77] Fix variable typo --- plugins/catalog-import/src/api/CatalogImportClient.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/catalog-import/src/api/CatalogImportClient.ts b/plugins/catalog-import/src/api/CatalogImportClient.ts index 94ba260d5573e..aaa110329ea29 100644 --- a/plugins/catalog-import/src/api/CatalogImportClient.ts +++ b/plugins/catalog-import/src/api/CatalogImportClient.ts @@ -160,7 +160,7 @@ export class CatalogImportClient implements CatalogImportApi { ); }); - const pullRequestRespone = await octo.pulls + const pullRequestResponse = await octo.pulls .create({ owner, repo, @@ -178,7 +178,7 @@ export class CatalogImportClient implements CatalogImportApi { }); return { - link: pullRequestRespone.data.html_url, + link: pullRequestResponse.data.html_url, location: `https://github.com/${owner}/${repo}/blob/${repoData.data.default_branch}/${fileName}`, }; } From 79418ddb6295a469e1bf4c1c65e5f6bad28cea0a Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Wed, 2 Dec 2020 21:07:40 -0500 Subject: [PATCH 45/77] Add changeset --- .changeset/grumpy-meals-wink.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/grumpy-meals-wink.md diff --git a/.changeset/grumpy-meals-wink.md b/.changeset/grumpy-meals-wink.md new file mode 100644 index 0000000000000..8fb346b831845 --- /dev/null +++ b/.changeset/grumpy-meals-wink.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-import': patch +--- + +Align plugin ID and fix variable typo From 9e11dfca4021ef93c03ea95661624ccdd0077392 Mon Sep 17 00:00:00 2001 From: keshan Date: Wed, 2 Dec 2020 11:23:30 +0530 Subject: [PATCH 46/77] Introducing env prop to have configurable authentication env --- app-config.yaml | 5 +- packages/core/config.d.ts | 7 ++ packages/core/src/api-wrappers/defaultApis.ts | 78 +++++++++++++++---- 3 files changed, 71 insertions(+), 19 deletions(-) diff --git a/app-config.yaml b/app-config.yaml index 54d564de7dbf3..2850145eefbf9 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -190,6 +190,7 @@ scaffolder: token: $env: AZURE_TOKEN auth: + environment: development ### Providing an auth.session.secret will enable session support in the auth-backend # session: # secret: custom session secret @@ -203,9 +204,9 @@ auth: github: development: clientId: - $env: AUTH_GITHUB_CLIENT_ID + $env: 688ff0b3ac05066bbfbd clientSecret: - $env: AUTH_GITHUB_CLIENT_SECRET + $env: e054385169884b9618fd7ffa3e4d97b168708ca1 enterpriseInstanceUrl: $env: AUTH_GITHUB_ENTERPRISE_INSTANCE_URL gitlab: diff --git a/packages/core/config.d.ts b/packages/core/config.d.ts index 014e4ac934521..e13c00e561a04 100644 --- a/packages/core/config.d.ts +++ b/packages/core/config.d.ts @@ -62,4 +62,11 @@ export interface Config { timezone: string; }[]; }; + auth?: { + /** + * The environment config added to be able to change the authentication environment. + * @visibility frontend + */ + environment?: string; + }; } diff --git a/packages/core/src/api-wrappers/defaultApis.ts b/packages/core/src/api-wrappers/defaultApis.ts index 1f18f54d2a7fa..58cf50fca0929 100644 --- a/packages/core/src/api-wrappers/defaultApis.ts +++ b/packages/core/src/api-wrappers/defaultApis.ts @@ -78,30 +78,42 @@ export const defaultApis = [ deps: { discoveryApi: discoveryApiRef, oauthRequestApi: oauthRequestApiRef, + configApi: configApiRef, }, - factory: ({ discoveryApi, oauthRequestApi }) => - GoogleAuth.create({ discoveryApi, oauthRequestApi }), + factory: ({ discoveryApi, oauthRequestApi, configApi }) => + GoogleAuth.create({ + discoveryApi, + oauthRequestApi, + environment: configApi.getString('auth.environment'), + }), }), createApiFactory({ api: microsoftAuthApiRef, deps: { discoveryApi: discoveryApiRef, oauthRequestApi: oauthRequestApiRef, + configApi: configApiRef, }, - factory: ({ discoveryApi, oauthRequestApi }) => - MicrosoftAuth.create({ discoveryApi, oauthRequestApi }), + factory: ({ discoveryApi, oauthRequestApi, configApi }) => + MicrosoftAuth.create({ + discoveryApi, + oauthRequestApi, + environment: configApi.getString('auth.environment'), + }), }), createApiFactory({ api: githubAuthApiRef, deps: { discoveryApi: discoveryApiRef, oauthRequestApi: oauthRequestApiRef, + configApi: configApiRef, }, - factory: ({ discoveryApi, oauthRequestApi }) => + factory: ({ discoveryApi, oauthRequestApi, configApi }) => GithubAuth.create({ discoveryApi, oauthRequestApi, defaultScopes: ['read:user'], + environment: configApi.getString('auth.environment'), }), }), createApiFactory({ @@ -109,60 +121,91 @@ export const defaultApis = [ deps: { discoveryApi: discoveryApiRef, oauthRequestApi: oauthRequestApiRef, + configApi: configApiRef, }, - factory: ({ discoveryApi, oauthRequestApi }) => - OktaAuth.create({ discoveryApi, oauthRequestApi }), + factory: ({ discoveryApi, oauthRequestApi, configApi }) => + OktaAuth.create({ + discoveryApi, + oauthRequestApi, + environment: configApi.getString('auth.environment'), + }), }), createApiFactory({ api: gitlabAuthApiRef, deps: { discoveryApi: discoveryApiRef, oauthRequestApi: oauthRequestApiRef, + configApi: configApiRef, }, - factory: ({ discoveryApi, oauthRequestApi }) => - GitlabAuth.create({ discoveryApi, oauthRequestApi }), + factory: ({ discoveryApi, oauthRequestApi, configApi }) => + GitlabAuth.create({ + discoveryApi, + oauthRequestApi, + environment: configApi.getString('auth.environment'), + }), }), createApiFactory({ api: auth0AuthApiRef, deps: { discoveryApi: discoveryApiRef, oauthRequestApi: oauthRequestApiRef, + configApi: configApiRef, }, - factory: ({ discoveryApi, oauthRequestApi }) => - Auth0Auth.create({ discoveryApi, oauthRequestApi }), + factory: ({ discoveryApi, oauthRequestApi, configApi }) => + Auth0Auth.create({ + discoveryApi, + oauthRequestApi, + environment: configApi.getString('auth.environment'), + }), }), createApiFactory({ api: oauth2ApiRef, deps: { discoveryApi: discoveryApiRef, oauthRequestApi: oauthRequestApiRef, + configApi: configApiRef, }, - factory: ({ discoveryApi, oauthRequestApi }) => - OAuth2.create({ discoveryApi, oauthRequestApi }), + factory: ({ discoveryApi, oauthRequestApi, configApi }) => + OAuth2.create({ + discoveryApi, + oauthRequestApi, + environment: configApi.getString('auth.environment'), + }), }), createApiFactory({ api: samlAuthApiRef, deps: { discoveryApi: discoveryApiRef, + configApi: configApiRef, }, - factory: ({ discoveryApi }) => SamlAuth.create({ discoveryApi }), + factory: ({ discoveryApi, configApi }) => + SamlAuth.create({ + discoveryApi, + environment: configApi.getString('auth.environment'), + }), }), createApiFactory({ api: oneloginAuthApiRef, deps: { discoveryApi: discoveryApiRef, oauthRequestApi: oauthRequestApiRef, + configApi: configApiRef, }, - factory: ({ discoveryApi, oauthRequestApi }) => - OneLoginAuth.create({ discoveryApi, oauthRequestApi }), + factory: ({ discoveryApi, oauthRequestApi, configApi }) => + OneLoginAuth.create({ + discoveryApi, + oauthRequestApi, + environment: configApi.getString('auth.environment'), + }), }), createApiFactory({ api: oidcAuthApiRef, deps: { discoveryApi: discoveryApiRef, oauthRequestApi: oauthRequestApiRef, + configApi: configApiRef, }, - factory: ({ discoveryApi, oauthRequestApi }) => + factory: ({ discoveryApi, oauthRequestApi, configApi }) => OAuth2.create({ discoveryApi, oauthRequestApi, @@ -171,6 +214,7 @@ export const defaultApis = [ title: 'Your Identity Provider', icon: OAuth2Icon, }, + environment: configApi.getString('auth.environment'), }), }), ]; From 1f6a0a2803028f15f1cf951a9f2f48f2747f8102 Mon Sep 17 00:00:00 2001 From: keshan Date: Wed, 2 Dec 2020 11:35:11 +0530 Subject: [PATCH 47/77] reverted back the github config value --- app-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app-config.yaml b/app-config.yaml index 2850145eefbf9..7fc041bf13932 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -204,9 +204,9 @@ auth: github: development: clientId: - $env: 688ff0b3ac05066bbfbd + $env: AUTH_GITHUB_CLIENT_ID clientSecret: - $env: e054385169884b9618fd7ffa3e4d97b168708ca1 + $env: AUTH_GITHUB_CLIENT_SECRET enterpriseInstanceUrl: $env: AUTH_GITHUB_ENTERPRISE_INSTANCE_URL gitlab: From ff243ce96a539500bbdfa8ed4cf73802606de8ff Mon Sep 17 00:00:00 2001 From: keshan Date: Wed, 2 Dec 2020 11:35:49 +0530 Subject: [PATCH 48/77] changeset added with minor impact --- .changeset/unlucky-kiwis-rescue.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/unlucky-kiwis-rescue.md diff --git a/.changeset/unlucky-kiwis-rescue.md b/.changeset/unlucky-kiwis-rescue.md new file mode 100644 index 0000000000000..0ac7d8f299c83 --- /dev/null +++ b/.changeset/unlucky-kiwis-rescue.md @@ -0,0 +1,5 @@ +--- +'@backstage/core': minor +--- + +Introducing env prop to have configurable authentication env From 6a5e3e204f0d9877a969140114458e021b7e6b0b Mon Sep 17 00:00:00 2001 From: keshan Date: Thu, 3 Dec 2020 09:07:04 +0530 Subject: [PATCH 49/77] Updated comments and code to reflect feedbacks --- .changeset/unlucky-kiwis-rescue.md | 11 +++++++++- packages/core/config.d.ts | 8 +++++++- packages/core/src/api-wrappers/defaultApis.ts | 20 +++++++++---------- 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/.changeset/unlucky-kiwis-rescue.md b/.changeset/unlucky-kiwis-rescue.md index 0ac7d8f299c83..71afaeb44ac5d 100644 --- a/.changeset/unlucky-kiwis-rescue.md +++ b/.changeset/unlucky-kiwis-rescue.md @@ -2,4 +2,13 @@ '@backstage/core': minor --- -Introducing env prop to have configurable authentication env +Introducing a new optional property within `app-config.yaml` called `auth.environment` to have configurable environment value for `auth.providers` + +**Default Value:** 'development' + +**Optional Values:** 'production' | 'development' + +**Migration-steps:** + +- To override the default value, one could simply introduce the new property `environment` within the `auth` section of the `config.yaml` +- re-run the build to reflect the changed configs diff --git a/packages/core/config.d.ts b/packages/core/config.d.ts index e13c00e561a04..a6f95ae71c279 100644 --- a/packages/core/config.d.ts +++ b/packages/core/config.d.ts @@ -62,9 +62,15 @@ export interface Config { timezone: string; }[]; }; + + /** + * Configuration that provides information on available authentication providers configured for app + */ auth?: { /** - * The environment config added to be able to change the authentication environment. + * The 'environment' attribute added as an optional parameter to have configurable environment value for `auth.providers`. + * default value: 'development' + * optional values: 'development' | 'production' * @visibility frontend */ environment?: string; diff --git a/packages/core/src/api-wrappers/defaultApis.ts b/packages/core/src/api-wrappers/defaultApis.ts index 58cf50fca0929..d044b50b7aaf9 100644 --- a/packages/core/src/api-wrappers/defaultApis.ts +++ b/packages/core/src/api-wrappers/defaultApis.ts @@ -84,7 +84,7 @@ export const defaultApis = [ GoogleAuth.create({ discoveryApi, oauthRequestApi, - environment: configApi.getString('auth.environment'), + environment: configApi.getOptionalString('auth.environment'), }), }), createApiFactory({ @@ -98,7 +98,7 @@ export const defaultApis = [ MicrosoftAuth.create({ discoveryApi, oauthRequestApi, - environment: configApi.getString('auth.environment'), + environment: configApi.getOptionalString('auth.environment'), }), }), createApiFactory({ @@ -113,7 +113,7 @@ export const defaultApis = [ discoveryApi, oauthRequestApi, defaultScopes: ['read:user'], - environment: configApi.getString('auth.environment'), + environment: configApi.getOptionalString('auth.environment'), }), }), createApiFactory({ @@ -127,7 +127,7 @@ export const defaultApis = [ OktaAuth.create({ discoveryApi, oauthRequestApi, - environment: configApi.getString('auth.environment'), + environment: configApi.getOptionalString('auth.environment'), }), }), createApiFactory({ @@ -141,7 +141,7 @@ export const defaultApis = [ GitlabAuth.create({ discoveryApi, oauthRequestApi, - environment: configApi.getString('auth.environment'), + environment: configApi.getOptionalString('auth.environment'), }), }), createApiFactory({ @@ -155,7 +155,7 @@ export const defaultApis = [ Auth0Auth.create({ discoveryApi, oauthRequestApi, - environment: configApi.getString('auth.environment'), + environment: configApi.getOptionalString('auth.environment'), }), }), createApiFactory({ @@ -169,7 +169,7 @@ export const defaultApis = [ OAuth2.create({ discoveryApi, oauthRequestApi, - environment: configApi.getString('auth.environment'), + environment: configApi.getOptionalString('auth.environment'), }), }), createApiFactory({ @@ -181,7 +181,7 @@ export const defaultApis = [ factory: ({ discoveryApi, configApi }) => SamlAuth.create({ discoveryApi, - environment: configApi.getString('auth.environment'), + environment: configApi.getOptionalString('auth.environment'), }), }), createApiFactory({ @@ -195,7 +195,7 @@ export const defaultApis = [ OneLoginAuth.create({ discoveryApi, oauthRequestApi, - environment: configApi.getString('auth.environment'), + environment: configApi.getOptionalString('auth.environment'), }), }), createApiFactory({ @@ -214,7 +214,7 @@ export const defaultApis = [ title: 'Your Identity Provider', icon: OAuth2Icon, }, - environment: configApi.getString('auth.environment'), + environment: configApi.getOptionalString('auth.environment'), }), }), ]; From e5ff181467f391c0b33e2c5a1658a0804e9dd6b3 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Wed, 2 Dec 2020 22:51:24 -0500 Subject: [PATCH 50/77] Add register instructions --- .../src/components/ImportComponentPage.tsx | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/plugins/catalog-import/src/components/ImportComponentPage.tsx b/plugins/catalog-import/src/components/ImportComponentPage.tsx index c2eb935e54ffe..8f494458fd1e0 100644 --- a/plugins/catalog-import/src/components/ImportComponentPage.tsx +++ b/plugins/catalog-import/src/components/ImportComponentPage.tsx @@ -15,7 +15,7 @@ */ import React, { useState } from 'react'; -import { Grid } from '@material-ui/core'; +import { Grid, Typography } from '@material-ui/core'; import { InfoCard, Page, @@ -57,15 +57,23 @@ export const ImportComponentPage = ({
- + - Start tracking your component in Backstage. TODO: Add more - information about what this is. + Start tracking your component in Backstage by adding it to the + software catalog. + + There are two ways to register an existing component. If you + already have a GitHub repository, enter the full URL to your + repo below and a new pull request with a sample metadata Entity + File (catalog-info.yaml) will be opened. Or, if + you've already created a Backstage metadata file and put it in + your repo, you can enter the full URL to that Entity File. + Date: Wed, 2 Dec 2020 22:52:16 -0500 Subject: [PATCH 51/77] Add changeset --- .changeset/grumpy-toys-live.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/grumpy-toys-live.md diff --git a/.changeset/grumpy-toys-live.md b/.changeset/grumpy-toys-live.md new file mode 100644 index 0000000000000..bb1100160113b --- /dev/null +++ b/.changeset/grumpy-toys-live.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-import': patch +--- + +Add register existing component instructions From 2b027c913893aa256e67aa34756776018af78395 Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Thu, 3 Dec 2020 14:25:58 +0100 Subject: [PATCH 52/77] revert changes --- .../IconLinkVertical/IconLinkVertical.tsx | 26 ++----------------- 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/plugins/catalog/src/components/AboutCard/IconLinkVertical/IconLinkVertical.tsx b/plugins/catalog/src/components/AboutCard/IconLinkVertical/IconLinkVertical.tsx index 4a8bf2a0abc6c..dd267dde35353 100644 --- a/plugins/catalog/src/components/AboutCard/IconLinkVertical/IconLinkVertical.tsx +++ b/plugins/catalog/src/components/AboutCard/IconLinkVertical/IconLinkVertical.tsx @@ -25,10 +25,9 @@ export type IconLinkVerticalProps = { disabled?: boolean; title?: string; label: string; - action?: React.ReactNode; }; -const useIconStyles = makeStyles(theme => ({ +const useIconStyles = makeStyles({ link: { display: 'grid', justifyItems: 'center', @@ -44,16 +43,12 @@ const useIconStyles = makeStyles(theme => ({ fontWeight: 600, letterSpacing: 1.2, }, - linkStyle: { - color: theme.palette.secondary.main, - }, -})); +}); export function IconLinkVertical({ icon = , href = '#', disabled = false, - action, ...props }: IconLinkVerticalProps) { const classes = useIconStyles(); @@ -72,23 +67,6 @@ export function IconLinkVertical({ ); } - if (action) { - return ( - - {icon} - {action} - - ); - } - // Absolute links should not be using RouterLink if (href?.startsWith('//') || href?.includes('://')) { return ( From 6c91854391c8b6d391b02338db44c14cc3f1a0ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20=C3=96stberg?= Date: Thu, 3 Dec 2020 20:41:31 +0100 Subject: [PATCH 53/77] Fix missing authentication in GitHubUrlReader An oversight that probably stems from that most repositories for tech-docs are public anyway. Ours are not, and are inside of a GitHub Enterprise, which is how i found this tiny bug. I wish i knew how to mock the cross-fetch in order to inspect the Authorization header of the request to test this fix. I can only confirm that it works on our backstage that targets GHE. --- packages/backend-common/src/reading/GithubUrlReader.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/backend-common/src/reading/GithubUrlReader.ts b/packages/backend-common/src/reading/GithubUrlReader.ts index 692a3c6dd8804..278fb70eb3f9d 100644 --- a/packages/backend-common/src/reading/GithubUrlReader.ts +++ b/packages/backend-common/src/reading/GithubUrlReader.ts @@ -196,6 +196,7 @@ export class GithubUrlReader implements UrlReader { new URL( `${protocol}://${resource}/${full_name}/archive/${ref}.tar.gz`, ).toString(), + getApiRequestOptions(this.config) ); if (!response.ok) { const message = `Failed to read tree from ${url}, ${response.status} ${response.statusText}`; From c950ee81f03769e3f722b1e7fb17d19f9905d27c Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Thu, 3 Dec 2020 15:45:21 -0500 Subject: [PATCH 54/77] Move instructions card to right side --- .../src/components/ImportComponentPage.tsx | 40 ++++++++++++++----- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/plugins/catalog-import/src/components/ImportComponentPage.tsx b/plugins/catalog-import/src/components/ImportComponentPage.tsx index 8f494458fd1e0..339e7c8dd63a5 100644 --- a/plugins/catalog-import/src/components/ImportComponentPage.tsx +++ b/plugins/catalog-import/src/components/ImportComponentPage.tsx @@ -63,17 +63,39 @@ export const ImportComponentPage = ({ software catalog. - - - + + + - There are two ways to register an existing component. If you - already have a GitHub repository, enter the full URL to your - repo below and a new pull request with a sample metadata Entity - File (catalog-info.yaml) will be opened. Or, if - you've already created a Backstage metadata file and put it in - your repo, you can enter the full URL to that Entity File. + There are two ways to register an existing component. +
    +
  1. + GitHub Repo +
    + If you already have code in a GitHub repository, enter the + full URL to your repo and a new pull request with a sample + Backstage metadata Entity File ( + catalog-info.yaml) will be opened for you. +
  2. +
  3. + GitHub Repo & Entity File +
    + If you've already created a Backstage metadata file and put + it in your repo, you can enter the full URL to that Entity + File. +
  4. +
+
+
+ + Date: Thu, 3 Dec 2020 15:45:43 -0500 Subject: [PATCH 55/77] Update doco screen cap --- .../software-catalog/bsc-register-2.png | Bin 175353 -> 131597 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/assets/software-catalog/bsc-register-2.png b/docs/assets/software-catalog/bsc-register-2.png index de71141ba032cc58c9f1d0d43ac072512831197a..18a6c5a4f0b5241f0a785afb269e43e9d286c56e 100644 GIT binary patch literal 131597 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°uv?n{|RK(%MfD)CnS?h?F-LT(O_(G7AaMqX{ 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 GEc<>E86L1wkPs@wge)g$j{ zlvs()iS(EHY>fwck!!ulIy(c^l)Mmud0v|%7bkGzMxji!3P0jRG8#R-x@vVN@{Jl@ zEc!7GW-DLO-&%$h>~IYTKE9G%ig$q-+EKV8)u*E*_;vKjg`zyB6pPpWJ)ZJ{F8nDy z@wvgH7ALN{VFbO({Ue{3Dda;Bt0ZUzw`sl(P;OgwxPZCe+Go(Uth!n#X!)*2f$T%I zN|1_Y{XFku>6UjU)Dyi&EUZ#Y7d%I?d{qN2Za4%YQaW-eG3Re5Fks<GZ+ZOz=h>DxbX3gANm&lb=vBCADf0 zPSc9FL%L2wb~XD9;_&JlyO6pjHBW$XXO|$0{m8TjvjN1D9H*3QO@Qh*GqNgo|4k`X|3kh zHqB6#YQh^=Qr5p&&V^5%QI6MrDu2RIZ_nn6`Z4?Z8W-p{W4=;R&#)?$yJex`QM`t> z{p(cn6Han$$-Uaq4F^rh%Z8*&2V5@Ksby$*u@Z&5qNs+Z??}m>0&$9h$x(VfbNkBr zh{Al+b0}?|lW;3#v<13CK(kM{s4t0gE$G_8f=14LpJ~cNgtgg;SdYdFZhE9CIQo?S zCu718m~JYQ==`#&cfvdGNk#C)jrR_l4*MZ+eUZ>NL0#(}ooR-NaDKb`&KAh^2fDEM z%V}BTMAbSo<%xNNKJTY637+8;H`&Td1|hhrlR@rgv^1SaX|*g`nNhCX_~}}nCDxcd z6{4~S&%%V7V6(NchTQW#FRU%!#3~Y-K2v9wX}yDAYc|O}-!67^^u;pPH#U59r$RM~ z7!LCZl67FVy;8IaB+mPzkAWH%^L$w~JD8pD@h&))a)qkuCBueE#qom9)YtZs z3WW6=Fd<-#p+}f15A_Wn|6sfgB%y)H=-HHYykHAk(?PYfeil68fxWSeuW#}_dpL)6 z<95N8eB+M+{nB+*IPUwpkJ!d3@|^XiKt3~_VsJ>nJ>Dw^)0kJ!d-*dnPwf^E=0sd9 zF7W5-B2>s0;OEx-DXhE&5nVKI)J-t%(C5z_$8QDhiM5!*>@AZ(rkEr?&d<25?wYnn zP%zI7)VS|)9#kG-!5FvJY39V_U@V3;Rhh4>FxjsY%rA$3OmQ`L#}Bl;5P=$+XFGqP z&`qKv)ZwJMB&XXKd>OqaMhX^DUs!FAh#+aS3dSxGTCEy*} zspj%sAlvBy8|Fc_uY?DD(9e*n$}JurJ#t_?Wp{gmI$8hG)N7T4AJ*jHl+^(B4Y3drI!?PxiXxXM`0;!bb9 z$S*w;N@VUelD8(yll26+tT&-jmtDQb1dHezD<>=@k}sSzqMz;8EP+j zt|+=WdiF(Yc3)f1iXoOW1)M^IHFI;cDS8bLp2(HbXjBiNl+I+`xkVscYB75-}B7AXsGF3*6|*A6xF;g$~0% zPgC@6j2D3U_Io=H&le8BvIET!+^(QgE#}zH( zpbpZFQ)~Bzj(4oR9o`%>%QC-I0ilEHxqX+{F1&pRNO+kZV|fomvq3Fo=%S|x)G*JL zm%d()Azq=$q$RB+y>mU;$dZ%Ao+A&`NN1KELPBa&~UdGAteaZKVQ z()@|HjD4=0TM8`x;t)aEF)grx@12qtk^Z2@VREwX)n{*A@2Fv@ zANM2Ql4hp)yQ){2#k#J#rtKtgVpFi7A$ptm9sMfHeee9#yexgLbDY9XenDVXZ6zlY zZL__RgyB({>vU{lf?N)S_7t1tN+`vVa(d_4A-L&&y92P~p2!{(C_V31crdz$DH3S3 z*x;`{GLU1=IY=nZ!(|>O^q|`7JG=oLr8x|UtlWbOWodxBw!q4RAS-;e+mQjv)7&Rf zyZf;rOU zZTZ+sT5|}?>~{ux5L;(@sAdCR&}*J*0|;h)z|usJJg%%cXNtpd5#6y;@;n$-itW>o zyy$;z;$dDJyf|o3=POd$Gkk(h@(UbPrFYq9wZ=^~wOg!7!CfJXS1mha;dzy(1aQGa zzJ3c6x|Q=LCz8iuV*@XbP&6afHl5a*E*~hz$7ZKO5Z`BK9M<~Xgcrp|>R)sRw2jDu z-#3$99I^7!o?-!4pY$fbW196+;{3ST2JnqyXjr0_GkNvdQ0GRP=E-;NoucKTl;t)r zV+DiJRPuThi#H1Ee3?z14GmVk4DrAwE{k53iyC^%>^RW1(x*=CLuZ+Y;x^GuL$VS6 zGIqfUF7JxL5HC$B8yWqZGx&OAf_vdsajv&U!Zyv(aEG)p&VaWrbA>!>QJ`8bUNhlS z)o=C`pBq)XuXPyj$nuc9wQUCZ+?u{T6|HiC3(p~}d9^pF#=a)lfyR;b$))w;SgkFe zp-B(EAw=I*I_9d{5$9pmW3hD5tIEmMrJhfEwdpj|G4K7^3R(K~Gf6#Omo?WLDehd_ zA=NXwZaAN&-Z5y$8ZMOjVF*Z=<#LYs81};)F zC`Q(JbN-Go7v9`y*72KYY^=v9H91-RUZ#cankCUbp<<-Lrm&;U;nR4-USn=NcE)N% z&S^vxtS%Uipp{l$yK*O8Zb)C3e|G>f1uPnfe~B)tiBb>PM5UL7FJ|mS->i9%j{{O{ z^)Qa`NfJ&j)-w6kU=OFfbLt+%EY1QfKVUeDM0wuMO)2F8||3(-w(qp$CF4MwK%6~|{~FHpvE7gYxSnu-H*{uO&z;##O|=$)(z4{?%Q zkRFVaO8$YBkwlVwnn%9)OFF=u5vPh?Sf{=ph-%$Cw)DX+X_WV!;}LKV^5C0m@~JV3 zpX7WGXV>>Z6KjtV*y(OV?Z)c-2NMm{;4_bb?sw-Ca=2$YoImUs#XWD(X!+)K zAFN0CQqO4BkEt;4*u##Uv$;kXhb+z<41LdvciY{cUi}r*%^%?L^6I&G#Se>ZS-j0F zUy@5Tyk`TIRe~cFif3mXtg2@fm>n0udCO=pzx`Je5ougRu z;)9hPWW0lR>&=Mh=K-}nJ`L+Y1SrrxB+A>S(|Qyc>v?<0ufIlOsH`~lSW4Mu_R$K9 zP|8Jlq*hlAbolg@M$|zZB_`6}oQUzIc_hqkh_$Olh5apH*s~JKx$Rsfm-Q%Ir%~;i zmH?M>rNJ)eBL+u#XL{(^$Ma$=>0ltw`xKPApZQAFroLL@L0W6t8`4>bFrP+arA%_%A8 zY*?0S)6jficSX+*yZOW6w0W?;>?Po2^6AQ`X`$#v;;@=TehMv&E*{MzVSkiGhX?ZR0 zvN6Q##9?UJXHS>VCl9#mitw6}6ZF&r2HH@jwLvvRV^#cH{|AR_1ZlVD<;x?sLS`*f z95^fFk#ESNvj+kYsV2P7B9%Y26wn)DGx2JT!4t<6%3Rmu%D=8nNXF@W7FB+Ke2D+1 zRj*H{YP_3H;m{rGu;VoT7*j|m81y1|vq#H$ez7D-%@67^3l@H9y2w|z(Nrdka6G93 z8XMxl@b?7nF9f2)r8OQrBrW3|=>bvSEgnwqNODuEpxwfcX7OB_xQ?mC;OTY>LVPhb zmd`f=CnB_tlgp-q5*`c_O={Xp68vw|=x+VKA^=+QXOi@X<_ADN&H--Ad*KO(PxG_mmB{YwYoQ=-u?D-{6zpX9Or>LrKym_T5<#O%?j^;?7jPJK5MBMN`8*b#L{$`x{W6#rKE0%%WMR z%#-;RJrvJY3f?nim?_&VjFl7*IbHSiMWCSjs}diMxG2c7>m?!Ju=@6vI9nfb4wcz zNv_VHCUdfMZVb#wd7?mxLJ5~{M5&^^qZ1y5M;uG^KRz8!E85Y>_@rtf6Hs}a(=P3M zz)^jsUi-K6cP~*z&#LmJPB8IRLcx|3Qq0V$4}(?5mCK*gA6KGV=Ogf%APc#iI@Po@ z7!p7#MTOk)jpd_w2}I_hH0=2#Dy+_N|2pZnP*MQF{f;+_{z*#oltDh<~)1c;-uLk`oPq(v|v>6cZS@p0*LvKmT|s@q&{F0NWoH!AV$HsTH)U#8)E z(Z{oA-e@SkP2qUi%D9uoZ$p|z)uH^0$K1R;6KPNv5L^&^y#GcFb;aT4=h`@|T`lH!FY zo>TraAox4T;#2MDLS4r;2I8+|>F34?rz=l2i;N zObI6}LRo$<>bFw7?6m&@IV!7Akus4|K7xY-*%Qe`daK@7uhtr&lhbeI=78WBmF!+co99GUBSdZ9|<#AS$8pE?hiIH1eLSO_dIDIutOwrurTY3hsfo4uvJ5=L1*U3<*g>X}j77KV-Ikh`cP$6F@KuE0WM443)V` zFVS@y1mYTM4x%VAp+8=7R}21YACR8(5@X+T5)|<()5_$kd`A1t0O%B_=1Gl3b#oKL zJp>N*f2JFG!|(4;4<3$w&1$_7*U4fVvjgp`uYnN&MhZZt`)g8j3wi~h_+0_SD`#GR zN5{_hJUVs5g*i~2)s;pTvTA%o&m-gHE+@5b>O}hy6wY0I`CzmKUk^BG{0X!D!t+@k z@`OMm2FzkhrbIuCfyG|vs&e$XPkatNDU$Z8so~8W_#UceUpn*)Werjzr|O4+uA$mk z4xD}-A{WfdzeN@JOS1Kkb~%5U`qY|aWlxD~-K@ZZ#ojvggVN%j_}b4HwVP1zJEs-q z88K3MbR`DmB-_V_uebtM{VR6;^4x!X+U^Y$m>}9$A?e2+;cJda4Xa7arY5sSEZb2E%u@eEOQ#4O=;IRB*6deJ7q!=OJc z>GfBmi73JEG|O?Nq3^Uix1$0#UX&Gp-D@TV%kdEHC0`BcZCh^df5zv(3c<3lh;Fxh zsC9IA3WD!2?eoyHqT2(;S4aIF^%@$4Jwxp=0Ig4^FsP0_l5I zRf$;1kFFxG`gq4h5uz(Jx(MT*by|mQK=M|qTJQpCK#IKveTY*^q58(D@6ZYQYhlDe zh$U=12(I?7947!YD=e6YWvg86zmCUkY_B(-gc#2O6t$gR7qib$+hk$H?&LYn=H&Gt z5fgxzGeBEy`*6;38BE1ps@c1;{lPzQgg;&=d(@FrcTv@c4E!5EAG09@iD+(f%*1a2 zNs=!GgcF!Oi`~_(nxcu14im?EGsiZ@yg6r%IdK03rsTcsB?jztgkXA2KOpr2+2)AJ zY^z|I;YxA)8i6?H$CApXFFd}FfZyL6!dGZ#0_cy)Lfm`t6SXJy&FyqChc*YeZn`v^ zxV5rNHq=`y=_ihyR+ZpP$j#83twPJ5AUTF-Xy}wFzOd-HIlMW+X9yl2{iGh~GCPpr z=)Bl&N>=QZdv{8P18L%6?PbB^L~`yy$vCd7Ww@QRAn$Z(U)aVGpbKhCL`ac_ z^hV*ZNM+0i-}{$n;duN<3IR?WPt}5-swi*a#P-)$u3PCIDJOP&O@I z>%AsW<$|i(qjwbi17bkDFZ%&8jC$0xTIziclm0lMT%xd9<{tjpVLnr2jR8GWNGi=7nrY9n@c%EocHfAn5 zB3j+sTDI}Y?j)J`f`=p(YESH$gY3itiTN}DdH7x-6=v-M0`SJj#E#hc({QDe7XtZ~ zw7c6s<_8uB-#3%2*;iKPtCrdTn{{!;FZo3_;1H3zO@k3uv3YQhvI8eM5%9CFLwEKJ0=CN0Lh@adaF(E8SVoL%4$&ASNz6foxA zaW*wsIVGQ&aG!w>zpZY<*d3{>;a;;WPL)xTWO)gYR{xb61@^fRQujv0xUBx6u!pyk zj!>gvO7u~l(^xH=ebl?+pTs#HphtGIX0%P|+-THD1kNTRzs@F=hH_e8GS6g%sTuTa zXP4R7c>gj`oa05(y}Kop3ckCL{LG;;X07rpNG@8j5k==wEvyzR760gb-;6srbMA(8 zZVS|zz9*;AbLp|FF+;^{&)8#wE^lxDaud(G)x?;u#QD%iQ~@E-ddh^QO2d>IH%>Ve$f}K1ECkT z72~EpCI?DoT(MpJ{K|^?UdZs`HcalL)lqL&JX}|G`jOVxXKT4kjeZqs3UYs-3!hvE z7t-(SW$Q+q=l3T%UG|JO{gLd%V3=XM+GP277AQ8b&sjRGwYK~^<2mADJrsY44P95v zB@7db`)ZW8s{k4TZ3hY`rAZE9vd^ozoY@afNPNU}4utye4xl&H707-2d==M6yL+QF z!qxKV@?CCQJk8}k@CKqiWvM8<_V&v8 ztYMYWTiWKD>&GvMTq+|5LbPbc2AP+<8sFw?&-#>XuXe2)$ypqWE60U?jQ*KSyPw&t z?(bn}C)v96sqS>v`HLTNb0;~}QzbNAhzH%i5;DzG$HUsF)!CREY>6RISH|;l;7Kjb zQjM}ohEY6K=KN0}?C7Sro(7xdMez60W0Rs^133dSI{ZGuy)4u&kPNruhdmwV=QD?W zWK>3`jEzNs@Yja<( zdK>{+zi~wTI6eIgv|*c(CM*7X_SlS_z!P)zGC4(Cr`xb+A<(xQgE2W-8T9>d2*7J- z{?5E?}30h{CjiU z#;Va8@wpq$HQ{Ow`m?vhXtnf-oMHjAkyI7%;|2iyP;&`LY8O5)6nOES?eki$=#}4YP)r2 z=5z~&Qu06@Y^2mzQ|vbdy-kt&GiU909@*%DDW_>n)1T>1#kDJcSRJjH(;SJ(qc)+f z;EiEoD<>@=15QmYh1H^Do5yExpW$L1H!0f^d@1&Y8WJkyGe_O97Ne%sGD1O&aVvTu zBflp{9$z>#&Q=XP@Aq{(^Pc9lxee^@HRs^Md~x=);&x9w;3H_DCPl&ZU3nZKezDzl zLG@o~#U`ez*3&LHwkE^PJ* zrEFNrGoibvdulfKOoNbzO&`1z0S1sW&RG?Y`xcUoncZWqB8DU|`K(}NjJJ0WUwPJy{*V5X=G~63 zlQ?m5uoP99WG$;jVkv|dW+4d}EqnzVa!N0N{y9?P5dx-T6BZz&mBB^`C+ z%&HKD$E>EKjPQ!D_d_g;5DpF`1^o~#=JMjwOC7ShG&@Y~L&+HFfT~0L{9QL0-HuC? zd+I?KGRZHux6RMk&H9o0gRkzvT^TVDVUJ*lB`@OXPG*K94B>al6W-|}!R)PGLKfQ1 zx4U_|VwV`-ztO{(`Ea2ZDJ?l!@3P`!7YCeV0g8{i;~Y0&G+?<1&++&oLbz9g^J_SL zlv%FN6J$Q~0+HjM#*Z3VM=!J+*fACwCDU@1Rx2P+HP|u1OGTq^qUSK`I_w`=|GN7d z+!=LFz0!C7`f%denIDwn8-N-%ES&%T-K{WneQ9Gwy>!YPu29@roOWC%^!c4iKR+Od z44D;`_X5~XxCc`uK&lnTf!d0O=ZX`lgPA_mdskA|l1=(0Uc>V)s(MgF$CPd&+nevu zHy4>epc!L5j08w3`Av2!y$*CMxjxM%chkCwsSi676QAPm2*m{+`WQ@_YMY+vKC-KM zw|7ZAc)#Xl+;-VlTYS{!U!7v9pM>%p_i4&F(_@{vyMFo|L0%}U)=gQ0AeXa&o zR39OPk?_^Dn6ne{8Efy%x|)YVw)%$U2G75xTMU3awxgqQPz562CHBBd{x@DrhpR*> zL3HwtYRu#$NQNDomeB9)T^XW@JS>;W+pMwqu9{{?U0pj+`Q|v`@&YCk63movPMo*( zt(VQ>{gDGbr~0dC8$gKpGiRl*z8zmX32JH~l9h}@C<+VRiZCo!Fzxx-a+jURu?6MH z<`;x8wP0yE)IA7%;d!;g&;d;uv&{c!{d0fX+^frCzDIi3jj?Qd*?%W)X7{Y1-Hs*c z0aieAEz7-10D+Jd&o!$GiUaB4L;@l+{Vd`^=(y8;p3*<%Ftvq$>OyjS7^L!+9(_$w zr;S`l*we6#UkM|?_t&zzSR865uSG6ipPnT4D=|3&u=-1~DGB_RRRZX8*yLmB^}cfD zeJekno(q6>esnk>MR^Xxc^QGJioyK|tuJ*qf8n83JJ7s*d1|#aZ;z3gj=ni$Pkg zGL;*)^{cRpK`ZC4n`m|gu5Nyv{19M}UtPGbzkGep1X=CE@k9T?@&riuY_;^`^H;n9 zvGnCCacF!shTomq`%@BCRM(L(yxYxjVd4G@+T^WZH&>#6KH&x;upO z<;#WHXCr{h^|AiK2q5YoL;6Qdb=V~uC%D+tPBWZr58q$@-PZD2W%V-8wC#9j=P`f( zgtWk`BP+hQHh3pLD91st|IwKe!YfI&N*rzxTRN+k5fRx~p*FRW2o&#IxpAwHztjgz zV2IIr+hrVqlU$&-g`c;oD3xsz&f+suAlEEBLVNZr{a4TPv-P*XUSUJqU7PyeWU=@? z-#5kxtJmWKKTC3Mo^D)frzcN8`sOPe=v7b^Od-Dxk01BYAI`|7j1$CSj%Gpfna<8O z$f$U+7*w;L2;^6aINNqqSU{Ily}vT7mR@Ff=CS9Qix2Eqh2@wx;=_tNYF3h}9c3t2 zehPg~&HbJ!FKtHs*H`AfPYqrlfIHBx(v&GneTbQ_YHaAY*kfSr?Ut^(EQHAD;lUHyn%Xy!NU7i?x+6du>(VEJ(z#8pbv-^LlpvnaXR-L z7*y-4)vF~2uHzjRuL!NZ?I+}e44kfJ|0ota_od5w+CPO-W8{N(08KfDG2`%Pe56rauMI!tiC>-(qt{d z^#=&>9~;Z1!&F>}ebmdSs1}0e=BoEJ3>c+Zh$1`@{$a3Tq%rPQu7WLGO~;(3gvwz@ zryT0teF9tORNTtrQPydIZRdgH?NL0S1U{JBt?H#psl{d^TpNqnN4PgK>~qY5Qx0I_?1M^852vQiWdI#~na9+*m*fnbrp^yq9(V zNYnpw_kRwkAR!$oV1BlT;XI<(WXkJDPvWvdbp`DzkM&rf70OO2fzx#zcjjc%`x;un z;%*!v1tn({AA5kGlH~JB_YKTqy*08TV{2hHe`wzHTx0@V*c zAQTx%@~vvhXGaqK1d#Nc>+YtxS(d2Lz9GzRBQWDQc3FX{X!6z>=_2#3tlSx6tJ#!W zj^_>lGlm-VM!`=1$O61pt#w%43;@^4q<2tu(C@OFIHt)Hs~X#0NzQ}@4bx6BZoc9qIiWE^22 zDa*LZ!F#z~JO(9*o2>o4-ji(T+1M(*J)(MFhl83izV-#oC1`bCfBpl@H-76AhpD36 z|6@kTZ_NmOhB?~KWXj^aWeG(7b;+^%xDSx`Vk=f5d%Jd#T^hK zS*V-3q?jZH;}umq0>cNE12Z!`NfU0{WPrL-)LUvD6m+?qaRc zoHx~&1p4w}p738w`(H9bTRpHvWd1X5`wQ0iLqrVFKL0|F{XcQ(96)#~DhhcD{7Fdu z@#8zt8h?Ow$OAuOtJ>+m9bNwT`puw)cOAy?Lc_M$#>;L=>(XI7RUz7RZT8R$!;9hp^pUc^gc!?!l`u9AN5L}6j zh-g|_sUQGBJch@YpPyelw!XneFae7XY}rD3ZC0IcM!GOgHLaDp$Wbq=aM7_uhpTmCw&h6-cec zoSWu6r2>@ixw_r*3vR1d-FjK^5fR*uWhNdQ>#I}{G)kRz7kI74cKwNvpZyKkEY5xd z&>mEI6A#l{liHUy=Y^Y(MH_xhmUTX-y)Hj}Ft;2Rq-kSes(#umq#y+7)l+~AZ7i50 zfTFrLp=2p~?oK~!_yn)aS8Qs)J-W3|t2_^( znPx3gDP8r{-5@~Gp?y6cpIgOB6>x|s{&TAf`uYoMX^y-IvC1DM5QUc1R|oMR}d8h z=_*P@IsqaCLQ`qdqyz{dD4h_3lq8V$?nw~6miPC2%g6uuln*C+c4ud2XP$X>wv9W` zYz7$UFb+#qRPi2$%YdU_bjw5st)3rc%zbp zTmn^qy57uh=>0G?Y=f9;K#YXGGs&mrm1kzi+tU04+Ng%qPX;-*)IHc+w81`t%&O|7fh*|h4#)_NmGq> zIx(>D3JyyH@q_Ef^?Lw~*o zNSG}X(7eOUbz7|DiHYyqeeJ>>CbeUcS@hJZQDFnTMh|Q`9OaQY^9AQ2q@VD<63rwP zwRo#k#f#H59VDX(DMmIiMc8!eyMWek(k_(;2~?CfdHQfp->{qWK>CXM(K9r)?t%55 zj4f$o6l!jkEPM+Kpu-^7BN5rkU+G`_QfDx%TdtT;FBMw#J^IuqQ<@I`)ne{Vybb@!ccc8tQ1i#1&ze7>9+an5~EDz1uOdL>=e3{SF+T=U8NxwUL)Ho9Kq{$yg*nGAHa_llrT<)@Gt zK$!LxWaXUtgRD67Z(p!|9 zB@AjWnzIsDzliQTDQ_@?!fD+}pR#X@DH2-hlyqH;Jm9qv&#Wt;llsbHTV`Y`gZGgpIC)`(M?L?xi2Rm`DqCztl83z6Z}%l(dx& zPHFM>QghF|mww{_AtjxSijobmjh2jye9|c)r(tc%8XoG&dX#zb0P-8MwpFD{h8M}V>p<(ENEka9fU!7>H}FMrw?n?r zr2<%`ksF)O$iF+8T@hMswYW4LIq)gyB>I|+JO1NJoit_4-pv-~g4&5r{>$H{D}C@9 zttUA;!w})7E2@eT5J?~1W8C!^o|K8$hB6n+u7>?X)@VP(MM?w#kr42rh}=jXsjCdk zt*zd;Z0fkP2ptCu;CxvARTPLZ_n zSmvIqT~+Z}_wZmr%%WE&u3nZ${X`G5nob%n3)xr{*^-JIb$rE2aF2=)VM}F6*~~}{ z+9%en0%=oXnEEHrZLCKK!YJ!eiGe;x#h|HxvFT8Xm+xS)Dn7rn!E+#?l0A*mNTdg} z->#^7glim8qIco)*d@(b905d9F?lNMxmcG+{qYMhm~*u2wlTricOHqs^*S)1LhH4J z4TIEG)b-w={vef16|;84lLfG;``TPj^z2_lXh)pKy&)cu>>bo&Y$WALhFC+httyOl zbacFW*z!(FrqwJckaIvoBZDD3y5Jl1AY4*s zUBuy|EA|yVNsv@syIeyw27gH?(xQa5{IEN2&3n!85B(S3*h?%QeC)~6=pl2JppaaK zHY?E-tUcQm38Y{TN^9YS90}eA`Hl8IR$b)SCx%45+5)QlOi`DRNl8qfak1W@qAexv zdcvg{R7tlL(z<1iCL>?gMyU!#W5qRGK>}a=D!3;m~m_}v@$WM>p?XSt};# zFQRb(T`#)Be}!|aXUu@`w2zpApPC#di$JAud|Ij^jlC&BHiP?zrjhOK#df<-BSQ}l z&FM;8+I&hWo=VJ-V^}m&N{tvtBkb}UykE4j$=0U6&K-eT{^WX+vSUCN(LaDO*4Glg z*Y0EOhGmhSUZ93JGAnI*Ci~zL|3zNWVDw=w!)p8YKk7oB>~QR!DZ?dvIplqMXp-ML z08tX!!*6YwR(y>!ru0t$gsY@GLebuHA=I1e9So9Vg1zh0O?g#KxYo{PgrhhEL{H=i zByymiSS&5i^GiEmI=d$76~zvXZIV}BnOC`Fw?Gcf;P%#8+jEzERy~xwIXk_cxmv{e zKs4Ug=+f++&|Vq&l^#vn&pi&cnOLxhhNT#Bc)PI?eJaemQkRCx2#>kVO4Un1MH?bUKT*&*0}Ps}I|M(e8{9SWU# zd^J2k)Z8u#=K6u^#aS~aSBR^mZXOft2++}-P%L`x`VX)M9nodVRXg&GD|AUE1qrEP z8RW5nH9}ag7-~U+TYiyh-FZe8xqj&lLX#yyUqvT99CFz`m%}YHqSn`5Wlu+=nb(v> zgIsImvqZcD}BEuM!-GFNBd za4L5`PTILOY6opful99H+JMPIO*bAfsIq+hVEXzj?unPA*^Y8+AAK@*2Vf_xE=if% z;a0*J>Jgg-1@5LYXP9jF(VXy|2eU;xsGhHqFoDyk=O?X^b4X<0lIphwerd}7W5exI zwC+o7YHD!-8cxC$RyWFTlqoAY`VGApx9BC^!Z=4qbuAk5T5PR$Qj9z~olcF+luJL8bY>T3I?Z?gr-+$MUAO^54jGs6))R^|%$reDpv8w*hLHT}~#6bUap88yc z+Si*lNxSRZoaSD~tJA~_|4Bh$C&kU#4d-Gsi?&5;?X!gMA|X`vws06PUiP)G7?`Bp z95BGjB|SCJCnRB6@I$w!b11Nfby>m#*g$hRIK?cxzW9RA?xg|BiOWZ42dDnYm)X^_ z{3+sVvAQbjwL0fM9b7F5o>rm7#AC8$HB0dui=S-~HBR63@|u~^9v|{rzl$z$e^(FQ zQrfKlaP4cMqL=~vj?JkS4njSJp>sLnx!YNz)O>f*LeI78Ju7KGFZS)#g z6#ow`SbWPRoa9-s|5Z_vPLgBCdxibxsIxe~Ku)Kv<5g=+X`Xv2nY1eyJB=#W=;=uB zeLHde*lS!=uDZW2D#{*?9CLq+PS^}{^>CTW3hCK2Cb$k{Q};$Rp>bfx^AE~8uk+T$ zfif`@d~s^p32@FXkDel&oDs29o?c!{VIOlnk)i9VhO9S}hM+srdOCHQZS{U4)p|C+ z_<4F=E{;YY6vQS4X3I%PsjW}f+AhJ-G=D2spWNd1W+u(&NCm^~Yir&{R+{Jj<}oBL z&dp=)qJvhYt6W{o$Q2dD-leWBo>dHv{c0iaeV?y4-7!4p!yL(<8lm1X5ZBr@g;?t( zE?jem#u=5EvS#s6!A=n;)VXg63}M0-wo zWDOWJ#01ElJoMRZqbS~haK}^9xzG%@|eXoN1$ouSa$Ln!D zMVn3_k_R84M{1T`S9c9~2Sp+WGmrZMzN4&{uyzU=FIduj5SRg&CZor|jI%mYS z`qQ!v4J`!({x2gV+Y2Xc^PCT{-25$u4&4e%;uDMR2%=a@#JgRH@Sgr)c!*&b#@fsfP8#oQsynUygw$IlpKlU>LJ@CJ+(6ryl4V)G<*T$T!*_=9iqNOA&ljhp(v)C4VaJ z*{Qb-E?>MRqi(CHZza-KD{?n6De9o3%=y3*iJZZ_JtdR2>g1^v;L^zqA*d@iEV$Sv zCX9w9HYMl{i1G#3fLjdE1g|)$f2l{N#;r3Nu8!#vx&68Otv@_Fkb;l@(Af_5p3X(% z$lR}E@a*x|drG=vqEwa|-q+OFa620*S0dfklIW(g#h6C4j*K``NL}3&bL+_DsY3dF zPZcd_9WO&Q6_dOzQj*NW6ZaJK4Dp<}8Nic<`uFCamy50Y-o3je9Xfv2FvUb-iwTrn zwuQOA;F|k4R?Ju3OR1fwIvf=|sYl1fF=(a!iX^?d>E`W)kJH16^=hn=y01p{B9sur zz0&?;FPOWm{_)|OZ~5xmHiYEH5^?#klQzb4DqP%0rYDw$Zi<jR z=Zf!PJxNjN85uLHnLoorKfhagucxD93DQ9m!G^CJ)ueXVJ?C-+913{;9^X7GLF@-M z4{x+LUi>l%)p`otA@Z#TK-7P~1Zqg}8vG7yE=bZ%{6`G(XXX*F#s7C)_It5WUW=ek z?5{+n|I48Tuqhl?>DMG)%VoTZ?LW*3uYwj&03L5d;aao(5!sm_7iJpBz!N|0h5mQr zTp5$m$W&`$`ZqB#525on=RtG^$Oa2%f)W647C{d|Jf!vb8z?C&_pOEgShmB4&2nH% zUS?~Ygh*^@P-ZJAYavzC;39xU(7X|{AQ|5H-@kC*&-m$medD04=15NS3F(?F1k16| ziyX@wbI`hP$?d=A2PUTRTSv2NvvE)x0bC5a47YG17dcvnOKhKg6>atSOtjo>1bYnC z_+>5Bz+uUK9PB|(!$D5ZLC!cnVYqqVi2?# z1c&^gr9=lP|5zLp6#SS6GZx0`7#gf%#(KYvk>(3ak%XEsTM>ci1ApFr#MPM7{4&sQ|1!rOLu1s88qv*AKu;Ztt34VbHWio351@yNT5lKl%M7pP;Rz>rycg& zk%0*Tj~TcV;jbK5$oE=BpIhhxNJoO_AA)2@8I6i}jfQwnZXol1J60<^JUk7*g~tFo z*-QH2_~3o0K~L{zi-`bqt$wZ@a?#hCaSpWEcd84YN+wL^sj^S29&%3 zV~g0GpQmu)Pv9!`BhEe3^~XULerIqKNN_;W@sHXAifBL@SA+rAh{gP?LaMRq1L%4F zzJ9%eQgQ*lA0V-#1v-XnThz9WXa4a!dnfAs94KGD`~#RFG6I?adN0b_X^2O9(`vQP z1kTE0Upiafv{wDA)%)mpb9;Wt3H@!(YS+Vav06RuTQcoNHg~3_H_o`Ps5x&CAR%dJ z&k7CT7WVj^*fIsMGEI6%jZD?X5}rPNC@kf(Nz|=Ai9FB+Jc@1CatQ^Zl_n))O{*%6 z`ylM!Gt8_P?|vvW`1K!@g#Z(QqY}pz4V7f^PoLpnu2G;~VMN)uU*Cho$^4^L>e1LD zQ)1}~F^gWzR=^I?|H*s-h8qy!v2+Y(75$Q4jgxQ@akjy`c!tQPqV3^u^;Tvl%=Lnm zm3~{>eKOKKO#g0|$UpogNZ1o_-NQ;HV;v`1^>*Z=hQM4C5qLtAf~UHHe2sR}ndnEv z#OjZ-@PupKtwXol1o6rF$tWw51jp>!zMqZ2w`sKW?RD%aj}xP)b{WtI_3+(#h9NMA z{TQwN8-$`@HyiU3kRCH<4<1-*&Hxvudo45HN^UY;4#(*EXMmd`n!P%~l@3XHxGHja z5SK~#n+b3k2%^Be39oK>OOmtOi^FN-7>Y>}wZ5g*7H58P=!%i&1_QN+c_}sp!43OD z^9xWF{SD$-7W+A!g!5%Z=K%rvGV{ zYWa!aYN#wp>Xq1?G5KX)PELEel^s86!GVyaV@l}?T@ zGw;O*o;McRO7I+2s1AIM&ni<65ItRHk*(nyV&3_dG&6H z3EZO6tg*H`;OC8mX9sh}O{D0t-qS_puyY{q+A22`BrJ6ny}ygHX#9O^%S-*?I58cJ zV60%UC21%AA_wfWk*ED97h9v#^*P`ys~)K>n4e2Sok#z;J`UGDfqBl_Ptqusd8cTb z;Tvme>8e{>-Sr{T8P^tJGW^s*b~rCdJ-^}vCVpS2afP`9=H?!cKUjM z5s+o3BG1ASG{2N!gL#IUhq-_M#5h>XgQp1Fs!{n#y=^@$j06fCS)xgTm3jS*Kd|P) zE{hh)5Q`)BJq07Ow_vE7Er-$A0+3<6kFHt)=RY`i`AUsQnm@Sru}?(NHdFJ7yQNzC zhoG|%8;R$r@j)5vc_VGgHPWva^#E?`Te;2#_4kg6bBdeC3Eo6WEC#nnnWN!87;5ps z8BJ*KK`=&-*s12_85iIxPVU`ET?|9-*)6JHh$_Pouo*2 zj3wW0N$o3}Uo|{e{XZYKwre-U%2elTTbqhgHJKBYqgb`TA_?!867(y#CEPEqImz)|+w!!%wh)In)VZyUakoN>g zJ5YsSXfq_hmchdYPeGK~d~Hk9sl=X(mg4NI_;q@J`x*$^A)F1S;%%v|I5Tgaj`2Jd z$f4zJm)>BovCou9r-WIGWv{iqSshd^dLpYO!Z3B{I!j+@856zq8d4PF0DpbP%TgVVi?ZuiF2aQH4-oaH1gFW&RPQrTw*xizFpsByx^;u4VBT_Gb|z5yynJ$ zPm6fnAN|#B@)U#P+S^-i6o~4{AaHmiK8Zh4`NrWWR}ans1x%$K2C z;5(vAC0eIS>dNQi1o$`8wBW;~yZ7QjwP~#<)?F(`*n`XMXIz*9uT5mBAjdg%_p1v zb=0^rblcU`=jo&Gt!wuJNYGXuLv@Ux*BX%RVGtDZwG8K5LCaXy99TN{=ppaEPlI3R&T<8^hL3>^S~47fW+v5A-OtIxn@>rH>i~#@TH74d`@Lf)}gC zX7?nMraR$p6pOpZi!yW;Jd*DU0#H#vsK+V4dskATKhjO|hkLK3>(?bWI=xIRYUA(e zci%WD#OQ#}&8_Gk-dg82%RjZV=h9r{1t#N~=Q||nlR|m+{Hh2A$GCnSSio3LRqI(L zA{~a^T%tkXqD-r+V{aI@h~1niL(fhP+r|0J-bTL&A1R|5nsd^U4+Km{UhrX=PTrf# zcG*ZzEbIN=QK6I1C7gzY5bi-vTti zxbw8yG;m7Cztf;HdX`F+vIhEMw4ToP(oWuxx`@sU|X_tJ0-`v3e42g@V zUD^L=^O-yO=4%1AaM^Em9Y)|s%$Oh92=n7n@bDKbj z&a=EfD$;r{wiNLT7Zl46%K9wsJM?&Fyoa}7%w3Uh=k4}G5UF$bpYOrgyj8gdSrw># zK0eQH(U2T?nF{f)p|tJqz%1T|@uM-rT_blXD!wEmQ^dG!-8ctb4htUA_r^fR|Bgx8 zE1`T=QzQ?K`Hf*bv=j`Za46A`AD?i!mdlmpTetleKuBlNrz&Dq-z5l`LKm8M) z^;6f*ErTxy1AUyIpPzWs8kNymHO7N<_r3W=sQ^JLtNz0WokJQ^vr|KrXK5f~uuKu5 zRQFL8H4ie)|A)08p+ln!F5NFe2Ivb{WZ#{H@00YKZC8@6ud54%xBbAoe28}~g%Q4; ziTb4>LeO%Mv}a7W7$CPzAK&Qod!IsB352uMq96+3{vloF_wdaN9fr!ZKodD$CWJ-I zArYg3Sj6-R#LNHPn}XQVDYeq%J0wU?{k=;RBm(D*Xt9u!Apm%? zeeIao&nh7@N+udZR!F-TP?!+z6%Fw&J}1@Z=H%?p-*c>6^7ljW2KQ-zz@$>vQ&vW% zqLDLG$~K+7UrJv#Yt11cYD!sXj?x8cm@8ac%5V5NRa0hIk?~ZE2)KT^Z{O~ytE;m$ zm!T_CXo@7?M)@z}zjp;eT*oNmUvbM|xQHFinY8Z0GebBN(iQgLH@l9Si+923H6*Cv z#O{U3j49Tcn3$Zmk5PT>yfR1i_r2Z?X6dq{FzOy>kjRXqDshITSzA>C=JJ?m4wZPG zvA(_qcT_4d*(2mh|Y z^p%}GS+WOjp2fe)8gHZRB?Jm!P5w4lEeET5t?_Std-UYa1Q7j1E|nmUM04Jk(j`70 z@b9{0ifSmv0Fg}9j@A43-!j%OI5XKal*g(A*I_AZvuf;*r@p zR)Nf6g(Ddy%!$vH2b7hgEmC}$^y|L#&mo3&?A{gZO?z5&zdFfN-W#nOy&X5Q?~re; zH^nsii@j`rqI|;TEQ&yi#ohSC^{Q)Lci9W1B*}mRBE`=6Af`t~myE<24tO4Q{$mfI z=txI=6T}K7HzqNLRqS9`=O z6PnN@Lo|8fEyLrZ#oM?&DE#Q{l6&oeHP^vWt8>5C2Vt2BRuX3lQTy?u?$tQ;`j&DC zzQ8AWZ=_$sPAL%q?M)ZeZRH^Rs@*W}ltyZqY24{TR-Uu!1Uau|X;cL3N#D-3-E%EE z;(z|cmI;;tSamhEwf{0v2x?<7Iai^499)psMPclfpQ!hFqa+5dgK$OD=H>?IL9Tvp z9Mr1A5)1NcuT_}JM{b{Z5Bp=hK}0aaIXQdw?yZT&PH*?bs8cW$O#rHjY4-#QVPh!u z`7?{HmDB4xl?(h=lI~;$u8Vsc0WOWFY(#PL_We<_ym9_=b#%oYS9>X!Yp|>J!=pbfxH4MPbM4e;+0sFj=HwHG z_OYsYQ#{AaxUT-l|;LhO$lyTxj{i8C8Fdl@++kCzUcA(f7 zIBKRAvb8XosF`o{}gMhp8JSt4OE2Y9h_L{zMDhu4d zdGIFr)po|)WVZe>^HWdH{mE(2Y~HKnm@={dQ>F!foZt z;D4dHM{j=F!XP{H70Vcp=&K43;V35Qb%5KQRc5K^(!FS6AO>ARqA(jM)k*A;q}q`d z1ls;qQn!c&{a%CkCBbo`Yro$-7rfzLQK5wZ^H<+v!HN7obCwo7#?9v3~eJAd)0FwFy@*2K6tMd%}VPW^u%i|mBS;>Xr+j!WU>gjLM_CDZ( zf0kw+fvP2Sc6W!m4)Aa?KGmm;KWma}|_FqywfOG3RilQGoVP(-=ra(Y>VRdse|XHs80cw!ARa7{t2VY-kkK5PQil zGuq1UlpL&RHG3A-sCnO&!t0$)Xy7`s=R~$K0haj|a#FwPZvb<377cQ23g*R!4#$}7-9 m1~r?z+Q8p&X=8i_SyTcbF%&m06Cc@5@o{5 z0AvZJ;;0~BT0&wH4fhn_0RgB1YhcV`OGJ#w$S{D|^!#^we;Ys(^G%zHT>t+2e>GJO zSHcDWN=7)9BVlJD+=h&AY>`+B019DXv{)*d;HL=CQ#QSaDKbcWad6ZWLw7T> zL**LcjrBAO@)*8)O10paFj%01_ZVzOevuoTS_( z(uwB$vPDb90v=yVV<0z>OJfSzw$?NUI|n-eChqgp5qV3Uyr&TL@{BL14FI1ZTIgr! zICKWO1a(9Apgw3AdINoiQ7{QsgLPq3cs5Lj8E_~Z122Gia1NXY7r-0gGPnvp2sgoJ z;45$s+zY>eM^ON!fTE%dP&Oz!$`2KRNIQ zGzCpV+n_zr!RS~t8=Zq*iQa%NN7ta6(C5%M(S7I<^fwF{ql2-+xMP@@1Pl+e7*mKT z#T>vK!<@(TUYld~j24fSk>DXo14cJ}SMr<3l8{3Z^#o=&TI4hhdE&|8G zNpXd^ow$Rz)3_VB0o+Ht0$v~QfDgnc;f44$_)>fw{uKTOeh@!KP$rlV+zAl`4q+)_ z6JbB0m2jOfKo}#c5Y335#28{aaW%1w*g!l_d`Nt&prBx=;HEH7L72ep0=cilUlQnbb_`Ch8IDZR%$YT@8i?S7W_KgT@Vw51O+yeKfh6 zMVgJ8J({1jXj%bU8CqMknzbHjqqQxy=V>q2-mQIJ`{gXPSst_4vx;UNnRQUit$4ZThG6pVQQ73|c0w zjCPJTVxVIXY#=k(YtU&hW@u&@Yq-X+(eRNG(THx8X0*+y-Dt#E-#FZOxpBSmeG`HS z-Gpzl!=%IHgQ=Nmyy-gAX44@vO*5uho>{%wLvymZw|S0vmH90TtOea7!(x|3mnCfJ zY{|E*u)J;sTe(;Xtae#-TVt%r&+rNL#`)z~$~wbJztU5y?|-$1|UhH>+CTjh4b?UTEUyVU)N`yU=Q z9vL2W9z%1?=5XiipVRMY=*jZj>)Go?^Gfmh(W}?nz?v8&DqbIM6VV7g!%S6674TET}CQ6C4`6 zCHM|gm&s<G3nhg{hn9yv39}3nhqZ>o;UVE$!ynEynJb*z904Mj5nCf3 zMVdwCME*JtJ1=rx#k^-x_EF2DI-)7ji=ykI-^cjIY>IghYaT0$ZI4rmOOC6J`w;IR zUlQM&FgsyI!j(krL_uOp5-uq&=|Ixx{DAq}=MN-1Cl@B)USPIh$%0D@wHIbCJh@0| z5o^)W6iiBdN?pnqRs^exHJZvytxO$Z`>}VhUvPXl+c?8qPi_f!FwHZqByEW2$=k{s z=6mx?`7Z^2f^xy@^x*V8=^rv8GO9DiGvhNGv+!9dS;vK{!VKZLY`yFy+1(;5QK6_e z$316z&WJcvd_V$87D!s8YKU;UDl?aTC+o}g%B{%#xHxX{(IqNNgiEe2wOqP>>0n-9 z-cQTW%h=1>mm4f!y}WM)W5wQ;(8|=6?W>Gd6|5S_56Z7uOEsrG>Of#L&StA*9QHBmL^YF%q<>vZZi)x-6&`r(7g2fGgW zA8Ki^Y1rFHZ7e&A(ZTQ9Hm&#+5V?|9+)6%AqW`1*TOJYm+ z@sQ)~tsbq%ezpCz`h>xWij(RmH=QD$DmXQMI`8yoo2c#O8Qz)x_C@Uv&L*6_b#C6d z&hw$?FJ1_|aJGZdar&a?#S@p@FCD*3zubJq0>C)MB-Sv7)ms?kB z_nhuiH@t70x#@rN!Y$^lD?M|2x^KtczI!M6PVZgL-JyG#_g>%6y+8IK{~_jK(Ie$Y zrH^$USN&%3TVt7f2#?U2LJiQ&NEuIKZg z54;e)7<*axyUOpCugqWl@`ukKS4R>?240I_kH6XQR_kr`JEwQ;qmiSJ-)Fra`%v^z z>toF)`lpW1@t+6B7JtEgDf?>iwe?%*w}<0dL^~i_ znmB(-Z%-OD^^Pz};6L${CZ9AH0IGceh|LEe4ACgNkzs-`37IEkQj8CP9UbN4UnWU& zbePITLewVax;c0DKjXkB{vdAOE@^DfS)!Z8=jqf5NcH+fRv^h9i8fv&TOl z{KKZ+0shkCKW8veffM-W{3mfP?@5`Tzg`fam}K zbua(`>RI+y?e7jT@qQ9J+u00v@9M??Vs0RI60puMM)000b`dQ@0+Qek%>aB^>E zX>4U6ba`-PAZcS`003BprI~AzAyGy68Dm9fPRZyOU;@em*qL3rc32? z>wMgJ_Q$7OjEpZgDvqJCJy-CsZSe~;SNVVAJ514upC_V7O+e~F|2V<22R zYWaOB=@qV_R+Css*N+=-R?l-v=_nVx_$GliHG>K>Xe>8bzeP z543bJ^LXo5by4wmi>inhc^`=2N-6pGeo(%_&->C8A7zQ}1CbbSPCWw^?WIV3cZC*l z8E*sCrUrKFTR%%HD(kI3GeQ2~qcaeuzQ2%aDd|HHimpS(?@K#t)(_#I^&6IcEX`Tk zdbdc<(!}0g$b|Vr5N?ZNDIcR&>_hl56TEy}T492Bi=a_S?@K#r{Z&>Pho z+8A}TPY>2-y~}Qo?2oTv_4VBCBaM&gy06n1eV9NW^@vJs|&H%j>P_I83^4H44 z@%EX0KNLqi%440OPU)dhpE84HCeHo5$UrC5!b^uDPGkx%l@pcxh|($NCo-pIRn};@ z!Wh8%t!#qxzBdMP257w0c&YJH=cUd|otH*@l+(dWr$|m-n*2UFz4*QQZ8XA$fp*8; z=daQxF3&WlZNx{Rai3+xY_!En=>y@B&_=9}dOQE(f7}FQ5woK)2qs78A#WizCTKj| zId{%j81ZzRoG0hSd2!wVGVTTrGwsMeq0Nv$bV?iF8mcbRdLTh)+@t!YN?+wYqt8}I zZdXXeI5h_8yv&RVe~8DcIW|H`^g^8 zIKC6X5kX+W*#j&Lgf}WE48S#74dbL4$Q=wi&mRDta#F2@iC-qqO%Oi^_b0zMfL!2V zfxbobP`F{$9hP_UQNzLqYc2B9!)Ey#ly8_}gX)d1cF$q6?7hZyJOrExfn>sj11dH< z$<^tf^}HELG~)V{!G0k&MqU8J2AB!)1a-4t+YgA5|k_i zKx<%n6o{aZnUow74~2BGDQHowQ-db#u$UBfh$&LssKgqn8L&lSL_tv9=Y#R;;^5gGWg8NL`I)En3^rCck;_W*jM3bnVeu zs&}wEXC%X7OD#5B(`to|Rs2`tn8`t9EeDV+D7(^tsFw3r8H)RG)J2F zWmZj-ndbtRpaub-FboQ>iIhn!5GrqJdrX^^?7c3)i!fBOuaUj8lL$ar2p=wro%2rl z*p@Ws8Ypf#hY zt~^-MSUFE3OtxfznxUAy(%B-@s7Npg@i}UT1~T?y^PAYvsvL?u+;ibtP^hGI8^Cm7 z52Xs*Q!jWFQ%HC0XSO2>GZGouYfVV-!x>aEG%9LD%3PuewXev-)&)r^ct>jO;EobU z!7}MW9*aSbBnz?-mo^kX&$e8p)p-eqP}zsvTNBv6V>bCGL{&y_0;FULXD1euy&NK7 zq4t5oqgJwkz?GNmc?vy*U1NdSp?bK2S_mB!GP3}Jk}7EpsY7dpFj81sIr8vGd$CQB z`$d+q@nq*is0%Dyl+vn=E!9RGvS+BI5>>ny5VSgrjb0p*akgFFPsOXH6}?JpTG}q9 znn z(&R&tNw^j;dDTp5krVW@yfY4% z0Sem;-$;b2or!zny!VBn6`j7(u59iQ8MCALsVPglt^peG_(mygC>=FMp+~kXLId8T zmx@FnzMU;Z!w?i?#uFqU{4bAhR)a9XHYoRsbI!V|LhQP&vQq|TnfgMV&{b`Wi=edX zhHO~SmS`oih#aX9eC;F>#9D{ZPEldELJHL*Ul$CxxDPoXVC1TMp|Yf+Bn&BzuF!6A zOh_cabP(_sD4`lzO8J}y)J+z-&O4=b)X{Nny3VfG?lt)~Ml?3NkijyCwB50FFC#Cl zjb@a3!-dxOu%QqJeG}%Is9KwQ+q#3ZY-j4GBb?8M4dMxt(t`So&7AT9X*Z{opJZ{k z1ja#98A-S00)pGi@H-EQuOp!xZ#s@}Hpy-s!vK||y?hTqVBRL6hU$CUP8IZFej&!H zPasAWatfcXwt&lIq22dJ<0S`~Juxw3nqRT`C2NRgA~WT3Uum<+9g2O6LO zW~*UUGYW|fX%H+=IAUhBM$xCDv`(7!wSg3zf)=5eFKWl4>>mJQQ9qkIauU_m zIx?ikNxc#({($H+zHf^alBPZ`xFYvr4Sj~;)jPxn>a1f8xlMjgwlkDf=Ve&Bw_X@& z@#DZO_Tl{qo~uu^Eh~V>;j~TVrrNLyjN;AbAe!V^BYs%n>LyaK?ZwM(;WpX+TMXf;A>*3eJ4sm?0|@5%`)*;DN`pb&Um=tI-gHayMR*>#KfI&&9*zf zNBy!38PqUW+oaWDl|so)2@K zufp)f0sxdmWI!KiETBJ$C!E?=xhMumYWxkgE`;xzK9C8v2<+OFzC94qh5f`TJE9lj z-e?&-5`9BSTiE$gb}rZ&@||m|&aEeCC{5!b19eCpa-eRQWjB<3uXPhn62)+!?@5j< z>3hv_ZMcy+HXE<=Av}KjobdV-1`X@|gIYaqo_vA4u-A+%KX}bN+A+FN^L2 zC4~aZ-#^bpwqB3xvhDbhNIjfV`~CT6&!hS{(398wW47nNbIjX`1SbCYM}pV!jD;hq zDLXkJJO?;G8}s1zrjxpxy*Y0vo&N<0R-w`AbAqfdh2%!tD3Y1Vp}VTl)w5y08mj8fryF>h?%Nj z%s*rP4+N^Jq5wpM{z?5ORRj{>!1FqAHj_D$o$pM>Ty-uqUmXTiOiTdiytWqCU*_*swRow3n#mZV zCIVG~U-J3hR0AOkW)D-i*&HsjosOpgULIF|p zOA4r%Dh5QMs{S6w{2)~^=8Xgq83K#X7lQd|sfvH1eDBR~QyBwlHg8Y$?@_uwIsltL zCi9opr|WmBia_J4$1fNE490+|iD3ThT&F%viQgADsQJC55U4*Hb8U_LM`QiCK58OB zeMUMzOUe7kUkvL0l=zs}{3-PA>3PCY{;e4p?zj^;VfBPNF z7;&X^-e6;hsfzkz&3lAjSbrn_?5O(RKw$G7%-8Q1D=(aX8UoDcXb7+G_ou|h5&r-c z!@P$@EUtIFX^8lp>=(J#ms}=Z!CYSedNC5~$0e5eHsZmJwkn0<=R40AF`XON>hsmuouRQm(g&opK3&rMSBYmH@xJG$>DR42{#y5^)_e8)-~8P{ zOcfF@MDj<6siHr>{?GGSQU83!6eAYbTwnd;9WYb)J|iMB#&X~N_uqHleK&93fAjvE zalT9K53xth>o`obE~?i0^D+!EQD|P}d3Ck+&-no6rImP`{3T=fNh^6n&Ud03*JrNp z4~_q$?|1WqkLw>bM(jv_{WX4J-g}p>53O5qE}I8O?w_%~)Q%U=FX$rX^$`r1C`|kM z5-*$ThuqBbjL2#P`TG0y_uEEA=`N4$_kpf|lrHfOV*kuDGp>*CUcNpgeb|x^P*)iK>dq;o;$rnF?lY*3PdXV?O=0{``xo zS;PwgKd%(ZN2kn}y_xGv{IlQezP~|W(D{66o{#W?ySA*kwQB8+h%IKLBGA#Vc01o8 zB7U*_UYNgFFbh=wky?MA?6Z^{8_Dy%znFPEd0m7mTDd!^-ofiYAl8q=W3$L>}L-D+~V&1n78GgsjV%>{qDCTZm52endqCa~B{(R}Y!gT*bhN`XBrw4HMX};?s zT9MfSq?ttDKim0ku z6iL30s(DpjN9+7mCR{aJ{-75H{_?%1kKgX!Hu02sWNy52nb^!+Ux$k2Ugk%)ZLMq#FG*U7rbJc~L-HxwNt~dh>ka5H<=7BNhocW5Rx!izJ6bGwixD@+CM%j zQzcJ9M}O2mUx@i8Uhga+F!*OWcXB^QX6fJ^SDs_>*(qei`m=wYx7di2citZJLQr)w zp`lv8iX*|ua#*4tdsoQ&lb&V zN)X&Mfj_S|W+k9evBW*7kfCB;bBp>%s)*BBs{w$+4idGESItm(@0IJ%gIy<}scQ4O z$DQG=&)j8HrD`@`e?E7PeQCAOv$38~=EPNOZi&7>2tW|17dW2Y+ed(Bj@Kh z3!i!a!)q4An@cP})n-`=Lex#fU%!d@rPH`S=d(8<2>31pMQ-UGLQ}PQ)6xD0Uw`&R z@zGSZ>ifJ35QVy;piGF+`J!4#V>E@9*jnJ!)FXI-<`6D*YE=jF`4YehB)6a0o7L&0 z($9iHO9ZI^&K$x^Ggblm3ZQOeN&nxAnpZ?`rZ#&U|4)uW_omRbb5Ar z{ra`**B)|yes*wpXsXL@@r}tGxj{C48m*Q$78+o@(&y{vi{lvTWkUQ(>NM4Ox$(Hn zb}iP|EGrNXaIGKDm-vjp|1|fpTEDg)$rqHbeQ*63)l7IRk8UN%-d7TZifL6e+Sm7Q zwLm&lb>dfni6Hu**rk*Y41Y_3oIy0+Tw5QOJ!fA3_4+k?@;VJsEybDQ&5(-548f}~ zEwHMtzw-4L&7<4bk8({D#|lI~uG*}DRBL^S*N=^D9`)$QYz__)_{H3R;5AJk4+i!7 zGb%)_56q!o-(d-M3n&eOc$^U*b%E>* z&E;fF;_?$ZFVfeq`-RT0+`|09ug_QFuFY9hZon~Ii2Hx;=d-@>M=8btbBUp{5gqD? zz2>ln_C9}M>YM*O^5Xm&egkCn6X(mk1Y?d(^Tvv~MwD9Fq==Z%FIAC6U{F|m;PdAO z#sVzMZrLrn?RL9eFveKCrZa=KR~^7X6bOEGmGIXnUq)*@`1vh#BF9o*6e~mnDe*^p zo~2NRnnnHoG)4yVnndL?1PPl?+%E97LNa0(h;C1_`tT9#1TP#hQ{-oFfm`~{k&_P< z^*bQYqD|GYbA6-lG$D*;J{t4+rGe|57pc~RKU6k(K$?oTU5@m{J65}ew%q!9aHB0B z{H#0;yp*c;$1BKc4@OOJbAtxeDft|EbE@0*T7Wbr?b zy_!JL|5eNCDVn&E2z8NiM)a%RP2_uzDgvgeef|0N=7>)4W@)T0Y_^* z9tTXIBqOUD&4I~mmEqeLO-EvVf92k8$NE7SigttBT8#r=irax4GX{8{>mznxL|Ipq z1HWrPr>qig7N4cokt_{T@M?2b0mHd6F$%kM{Uij{m}&%`kRMBR)f3bUh$+hMymROE zw;IW1ot(;#E^2vd>L=NR4in5?sC`}qA)}A+Sv`uk&5F?E!;^LRq zQ=3nv<^-yG38LlX&FD3iDBV_wCfH7n508$Hx65u|(Lw^Vs?B0M&^{sPsCpkr6-HWr zjAAoCPvzl`m(ZXtyssa_mb~7zey&RP;QKMr>?zz$CLp68Y5flumIzOOnB$(*MU5u1{N(dio6_gl%V-6t(@HCsb0WruN z=);II5%5?MA00TJn9KYP@ghX#`tu$cPP>WE7csXrny(?=&3rO3N0?b_YGIN7iI@bm z5)7BG}VydXuQ*q6*LAJ17#hzt-cG&)2-=M%*E$ zQCJ7TE04e!_~lbC`CtlF%OHjpQTF1AR>|(G_D`XaV*M0E&EcHteg#8J<>2t(5iD>o4o;PxXU#c1~`|i9n$} zU&(cdc1bk%PZ5EdJ@QeHJ~}zpM1TC({!Zr$?X`Jk#_^CNyy%&gC?o6hMH!n>?dMm4 z5RMCq4oBT+2Pon|GPQ{zp+(tN(NS%%pW3wx#y*C?>>!t{PiRPAF#Fizx@MHX z04FKx7TR*_>%oq;jLFV~24@5UbE+X~h6oUDtJ$*q&f=%-NGvAWhoYQIV8wt;D%}K3 zlkX5?FG%vt{TZkA6VdgZSaQNA*^W$Np9^rz*B6~^>tf;R@Jgo!?Sn>*DPtNPsnP6o zWi{}C)ytC_s45XxlM@t!ZqpnnOO1YGt`}OO*q93m(OQ|;&n7S`m>41Y>`?;*h~p4J zUHQT4|CKFnvKQ?($A!6 zIM3G+TQlNa)I)4J&z&zaXmQr zuOEtpvcP>U-8^)uvK>-ppup8iDP0ggGPIa_`=1G8k%g?S!EF|vpGIKzH)x$ z`i&b`uUtDmJ~}wqU|^t;_Oym4$ot=qoYWaay&sJ|YDmDshk7D})dbPp&%4@Ga{ZJh zXi!$GyT~>&uM6#{aqUL+35i(L>nA<{-JhBrk<$KN4!>eokfb^24B{jU-^?^>+0RD??^ zr^G-G1595M6=0YY$m;t~4V9AC2C0&6b_fL{AVhWm7L2hh+hy6_yx5K1Znt0rJZ1CK zI*60PD=*hcn--|aFEJUGKE3d2c&g@{>7-$Jkn4*GY}BVKF(VrZnX_3?!*OFt~gVj0rJj z&aDkV2xC;l*H_eVy$aQQpJR%YgajBhvDJifB+kB^R0g%g-_9tah7h(3r&Lyv0xxtlQjDvTZ6in)#FGeF%{dS=8E-aNv0)$ts}0ar4F$vc^}B$ z`Wo+4Bw6R7^YRis)FsYORFlB0ys=az!Mzf2rmPLFsZLmE1eBmSYZ z(f4`eSWfu7V@>ojxi|AQYfPV1hihirg~qd z7PzrlHMr0d^bmi;het=pCnvk@ZY(3@%k^x#OO1avHA({Y-K}d@_P@~9XJ%>5ZDZcZ zUhx6@>kBc#iiMKR2K`J5;EK^QS-oC|`wlQmhJh+zrOJ-N<51oJE^Jd_6d;-g%?_E? zCsH_b0|N{Ljgqrk4*K8}(}5s@pyDF-66~)pf#C+T05!0x_2I8rlLs@2U&!n4LV)=Y zbLBrVl&rx0kxBd=1x09FLW77|)S}aqMiLwIozHSBcm98rfw8``yb7_2>U|QbHMO!R z(I_C6s?w@0@W& zZ`n+g`F9!~9(?_izZi2@wGz#^qDjkoi2-cno@pl~C74xZb@lZf0rwTW?333rIYURzPvf?HC zWC=!hZ#LSre!YGKh>&=;2}7xx$P!3ZrP8M5j&>IWC)(G~>*LSB9S^ZW!VMw)N|Lw(oDL9}sE0Z$j-rqLq+l zODVEatSq!?)r>OuM@IJuVxjAYWYepavFW|Q6sLX7xO-Qhe{j0L-8x4YOa zyNm5^w_C6bSp;f2%Pps11QMd79@T^`%!f&FhCrZZ$l?t}*l_SiYa~gakWR&MuJ63R zWLAYy7yNuLp&J_S1k#_?Ixa+qh7lvCVi?2=@`5Cep}hX(C0i;T>o043TaJCksI5D&ItulW-8%SSQEmc zs>S-z^dp(YRX}Hd50dDmx<2~8T3_p#Ex$o;WKiz2ImAOaOvLeHu@9SbE2qvg*WU<+ z7?Nxs!~kfuc{!5E-!Rc!8*Z2oLJy!g+|G-hR7Y4`A9XEiwhY<$AV%~EY0u=DIE6uN zny)!ODfG&7=bDoFVNB$3Iwn$dRHmeuh_x2)d-gv_v1W=~EZvGzj1zp{q^lPAqUrB} zG}tYI@q9HFuU}{J9yXrgY-@q`m^MPb|2$1>Y|WKMdf`k9gETlHhEO;lP)#So>kF;V zcD~Wpm$z~rJ7hegn9y4%0wtxUu~<+cX}c^z>x2*=#SikfBf}aFeMj7{e;KO`@d$!B<83E**IYDU>Mdh#Vp)>OzQD`kHsto)4!>iEI*67>tbA`%55yKFVG;?v7xQQn#h*g z?*R)?o@_g-rZ$4;)3i6|W?2BUHx;VJW2&E&Vv?tbq>4{ARX4{h7<(-cD;Na9i8Fdp zu=nVZfxJX8w0?=exq%JyXVny}bU%#sPl{-f<-<8`S2=~I8>$A^)n9c=l zsJu7wW{wd9HJZfA%=elGdDMYBEM&3%JRkzrBV~v7`fYvUl-Q|QG~v&Y=VM&|P^{(^ zUahZ<^UV7{U4NnLpE5vJ#NN1mSZk#)LT+bmxj(e*(f%1{wP@dYV-v z)iVv(X=uVot*_78vCyQ}y|3)08lEi$Xw(d}-T!&NIRY+g8y}@xP}W-hB3Y5R$e`S@ z=eUhNUplWG$s%H^fanu74)j5)rmVIq*SLSysu->H$E4B>jAgf5mfgj6yW8z{W5^h3 z%1iqj+KbE%$wzPG-(R!hAl=ZEIE>+t7!|Fr+C+j0EUW5t`&$VHh3vA5N~|G90&Aiv zJ0d?HQ_bbI&M#^EbBKOx{pJ+`c8_2!8K^W0`=e&gKvhRa6{;ma#+eG#)X`idGHsoZy_POU$PlFsW-gOJfa zDz6^Qv0$%_lUMHIA90rVOpO_duL%33ImR$@3ZJr9h)pgIJTeGQL-IXzI*q#{;V46a zDEpFRqvZ^&c>$nMn~lSSjeJ5sNJt*Ij+%@S87aWD6->{?%;wOr*N~9D2G2e^Aj$E+ zCa)k1i0P9D1fpJUT}Jb&G(^xUIfrLYv?4zQOw-vkC=7(9q_2fp5nscfgj+KaS@WFSP#Z-N+r6-_3T>|@eCfW%0XgkaM7BBZXNL#It` zW#vGUj=*92BQ=*cl0vW_fF}A1damL)2|d5CZ&ermWRoP_*;g{3E_WPbzpd(Hl1S19 z!Dvmr5RImNAInme*YBoiQxp+<-oe4<w>cA^4?w zZGN`X^D_I0q+{xeX(N1nisnz(N5Va)Vi}`ALaBvZBVE?MMUF}I2nxN=GH;~!O2rQ=jIKy@ewfhBr_j^0(}z9$?qqzHi5Zw#sN_jffU1fPE0Y|ebAV$m zQ8V_UR8AsG8N8IT&<`wgDlbyjfS|EZiCO1a=(OpMP@zD9Q1Hbx(6sbOPG@bYZ~N;z z&d!2W4V8BkNHw+y{MBtlBKIlq8KIXNDA_blu_6i<*JZ*;L1`>z9D>5EEN;?_6_C0G z;T$8|s~2iK#GAf0xv)w zvfJ%;+l$?Hx9k?Gx6^El`mnJ_!-R1QMs)IBRp^x6Rad6M<-mnrW&Zxxmu+_2Qt9Yo6xyFy_1zo@Zg@R1c{I zG+y_ov)#=lM`Y@93vIde^`J*v{NZVu!ysurxngFa%jvG{luM*FYss29_al?mRU{7}czx*Aev!$s5?{`@Ox5(9Lv1=rlIMCl zKs0J`CM&i)4d$uVfdNScCRe!_Vn%vb?I+3Ts4<%d(BGR!gm^MXHTSW88r7)MEIJLi zRuwv5V4lRGj<0eV$n|r$IhVzcCP+bv22Q8hqv!4j_f@bC{0SQH$1Wgj?GoP>4 zR%~I!`cgrgo&^j^JvL{FAow%M@Byg@tdDdCg!=6qY}E+NP>>(x6r-gZwBxFhk0CnT z3RQ+fGFstN^vb8|gxk4%{53Nv(cMiTX2*v|M~8>o-7=QZ?@#TBL01HUo-a1IrVxb& zF>uSsS`}G4W48#9Hu#V|_JCB#R_;H#w|>VwN^!G#XB_9b+Mn2zxVdRqAIz~IvWTs> zXk7zEbbT~Ow#;(3YOUGYzp8!RdvG=7ZG<4B*H1!uj?!CSYNS*Nz0lIqn36NK5|cw3 zVkgW_*A??52#?yDS83^K?3794XHa2Y>_uw9q^kicXjj{*RHl*w*)wU^7D~~4s~cH` zND`u}Q?Cv+A=(~ zJG*x6+O_M~kB?3^2L~dU(H|7*19F}SWmwum#x)f11Q%Y>Oxmqp8||mbTjArnR2R| zQVW>&^Q1Mbi3P*hMebX~I%N3QJ;RR&ZGO|eBW!HbYO{d?EB%=$^0h~wZA7VdPpybu z2AT0p%T~~v35jtHHMN~%u6DC#M?ggLB0nd9Cy@61mNl=e#yv$dwmu*&tzx`W21@xP z;VMR@?{BNZn)1oY`vkHGrtj|jT9##uF{a#{afTvZP6Wil+T6TW1B*ka;Ld2mkX5n! zAA6N;nndlCwW+D5kV)CCEGEmAS#aU|Jh|l)9p9wTy12f<*NxQ2@a9pIuD*=nZ%W>* za^NPF%L^8|j@5QFL@zo{Et)SoMQrZ8^Uhmn%dM{m?bq$kd0%o)Bl}tHV7m75y~?X3 z3v;|sPK48yv?zt;=0{m*)foFRJ9-VP3|^!MHN^*@C6!P_BaH<(D|aY(X3GG5n5sjeWD7|yPg|djJ#mTQX9^|ahq|nl=YQxb+cG2z?xeJkFeBl{y#JMi zFRoo-pBZ0|?e64w>a>9D5UP{6Y_r$bgLRD>YP zBt`81YfzYmW;K~j%9s^=gT`ed{a&_`quV;f2j-{TY$`#ppmHctzi-jF;K?^jq9Qr% z)*Vp1A2e3FD$iw^kI`X7tb`gJwNZMFXs=&%O<7unqEL(})!S=8kEFEwsJ+s0C&UNf z2eZ!fjoRVS;n~^QwX4^z-MD&sdUkkts4BZe?dKo+bjE10*e8@h2%fjuSf zXsBMCTkJka{(#!A^GY#ij4fXa67N>!vEKW2}3)FY1$ z4>QgNX>p;QJ;Gd}EL4-h#`ZiJBopSwTIuTLsJ6Gh1{bmp2`!5tE!~2URQnHqFOTd_J} zDGq2$_Nx6;(qe`*M@d<7z;g^>ndF!4vc1?Xiwr*j&6HDh!pFGZJ=2mWbE!1Eso3y) ztYcnjSK{0^n(k$`w%_!Gw9=LF-!qungpAml+0kk-`bc%PP`8M6r_}V^V6z@wAGS(L z@-Eco4L;j~&BGq{uv=)$t*-~|*Bu}9%uEa&0a?skP;DdT+Q-mY#vtxVI?yUsr)nGK z+04aEq5{ehL!od^B@K)bg8gu9Rn(MIFfza=&`-znX^KW{^Rk7skmo=#6*S4=$e^G+ zneu=#tpphw8dq@zPe! z&nh2^7f(V3EuY+M5$pAr`alZ`NbgPx87#rCu`H*4RRZ6hsQ=p4}uOiou0CsLBawRrQDfXiM2l@Qzx zA%wl128=l9)wwkgdPEZo_BCVG-SVM=G9fu05~+B2>Q9sUCaMQVho>he%Wk*ZEvb>w zG#b=+uEkJcxL~_vp!Ij~xtElcL*rSLtEm)8mUt;V`eX?ZxOmG1=cci_ga)Sk;i1M} zA=Yrl5tDn}>5~TUXgq|NQw8#3`&uath=CD3LIjM(Wsy%qNoeIHpk-m`@40d$+Qf*E zENggHd2^E&WbO|}b|?t~7Cu{gG^(~tjQ(gSOUB;w=lb(Rpr(Y^N3XoaBU#eASJ_4# z9g_|FQI5mqxJt}Mh9cciS+%pgG^K=n z{WPr3!8yq9p~3h_fxLiBF-d8g23)m)W6ks`SD&Q z*7{`d0%m?#eGiz1#On(q*CJ}#_-m}-?hGyWgQk#`G7LAmK20bk>ARV`Pm~Fs=75f=uIfZH=U`npcb^gt#}*&)j%r0Y>%qN(;-)>Hd!Nlg4L<-o7e4GzTS`uPAusgcWxX=V&%DLBX~NTI{R)+xSs zWIU^;lWy^r1h*2PZ+$(uzixZZGj*W5*9*}my+0fKrw4(*9JDU7H`yi{Ls1(d8AZsg zPMBe-+9#K~$8m1cPwGa>2ewK6iNRN3Y$Bz);v=FH%K00zxoL`2pKCf$)tEEF&Z)|N z^gv4>sMlDc*>XfckbDL)5I_UXp*8!2oAD%a5l*Eo$0zcblN0aj`z{)}B>nvb-zPt6wW9r+EVK4L{G|DdgG^ zSFBZ`Y~zn2>B)2UY+5>x6;^yK8^@bF-_y#Rv%(mUsTl^|_e zqxYn%nh+(69!c%Oi-c5~UM#SM70j3&2m2N`Hds?(umz&DL3maWHJnXiN6^aKhLQJ; zyWQ%LmD?wUCr4fsvV5xq$-P{3ddP;{%rb^`hBF|7GVu!^jR zdf|$Uz>ERbw}z3iN_YRu$~(I}beT!o&F8Eb*%B6tk`IBlVRcxM)34Q{V4(amJQOWH zX);M>#*(UN^D+)~KA58@jZeWLcX6kd^g}toK0}V{$&Y*yg^F$tHb+Ot=V#|Pu3x`; z?ds9-$-!nLDq}2#Q09FoY2hN|F-*2#f#U@aKYcPh3VjuzLzD2}M42W4PyL+ctrqK3 z?;y?pR_PQ=49+`ovOuZp!<@Z&c}>}!1>p6Av0oaj`8`J0hlc;0wMUL}WNySKh(oZu+V?cCa}oV#mR)+Xo+y+%b`_VY_JS-i~d6TL9a zW>p)x(MOgliSw+3VVWnelq)*zIiQ3a>ABz(Qf&=80_gme%0nRMxttxLWfr#xa&o^> z*}p$KzcueVo{#B@@S+`1Z<{5X`PBW{SfZ3_7|~QrYLuiP$r{hlNMJyN?CE>iPDliT zfn^!H?RLA{ZZCGrSY#P8pw>(TYT}VAho!S-&1TxF;cT?b?KKO21`s!k3ljiJY_GNk z8FH1xIw_e}`{ednk~DLTylgiCF3@%6O&A5M*LZUx@5MM`yE>Lo!b>~5g|^)KdQg8o z^f}M0*2B-%v5t9*dI8iidXl@#A=*R|{`_}COQ6{^RApb_2GBJpATBTgJ6IVQfW02s zvppt%F<{LuFEg&!fsHw=Y>$2*E{M6mdbW@@w~|H%M;iK4Lt`r*+$89fb{Ng_E88j1 zAvhJZz?|Rm_?r#uecR6iD(m0)JcbJ~Gx~CM8IXKeXA4Eg?V1S9+}sk0I{F3*ilu}I znr&0Stk6_GslGZrc9GfSLPe132$-*w5^fhif0t#7`KY*|7%+IbAAii~TXz$4u%ygF z1KAl4O^KF)W`as|p9(>RQCT*#ki-Fe{gDWh64??76=^xY(jX#&vX%1=LFjfw^*sTrvi36EdVmU?$vc~Jrmq>~k^1v>p7DuY>h%kB9MNl|x0x{3!*CqJ7+zqh7pzwsbHl_joQOq z(Pd7|yFpzP@Xl9{MwmB4sxRHB9UUH?oSk34dhP1<>nA5CM~8^vE znJH{$F<<`#>XbVWG?c!sPve;+;garqT0dqW7r*TIB}r_biwGkwt1WgcUNeNs|NAh%bzhNG}LFUl=Gxy0~zRvddt!hK{nL z^6>u5*Wb`jIxq-S>$BcTi(E)860}?1Cp~KnEX!`Y+ifqlyJZ=Rk4xRpsJZmI(M=?G zW|UBzzeu%fp&#+Ho%il`;98(Qih5=-jL8&KAGl*^PnDlg# zbd#jkAV99p)DJFJTIqvis8Py~8add?@6_4obBHwHiI`oXlI6b^Ilz<;3KbbdnGdB; zyd!fS=7<@ZDfcbYKE4}omQJEd%Pdn%d8GxNm4w;f1R959D39#43Oq6*QJSlH(V0^F zqod>F5;Keh_f0`8;>meXV*KvJ((tkyT`4oOl_=@zKGhD$3P zFQhPLb&QPkKnkb?t@3XC73|p#UQ&im!cAF1cp7;EfMrfW0drUzi-6j6<>(2AdpHFW zp&d*28!grgfl#XJ^|ky{rNy3>m%3K?v98ldIpV!FNtcnG9FnT`PAPmFLPp-4F-EfX zRewe{SL8^+%1W(B8&0&<6|w?yK~~Q7D5_tZN!Dc$o2Pc1zt%k7>qROl*V>a)>!VF~ zlX<5JW_rrh5HYpG!-M0K(<@ibZ``r*s;O^Rb? zcW8U-X~NsA@}+xO6=6y%ily94)9)5YKNT&jXkA&zRzh3?Rpn8p=_6F?+_-LaetFMq z0at6LAEr-H+_p;nXGIg0ov(zAY^ILnlxI#z_FumwkBej>m#&}JKT0k!Jv=&k#NCfj zvrfU##!Fl6KMhimHSWhz;~j`{*ZGD5=w<5*>7iEJB|0p=7XHxsc8Pok#M#i5S3e5f zn;8x(!*5P|NV);jaL59@I8eoMDbBEh-J_`EU0wfnT{1GzKVPk9SBsYD`eUs4?DUBU zvoWQ3ivm_(f|9dP3MT?ww6;d&Q!#lLTDr6F*-H`--R7i^Zsb;B+@!lwn{>x15vJjw z#sRhGC@TQ>4ev`H+wJf%Fvi4QocDrdiHFTn3LG^|3A!Q+S}EL}xi;!`%m^4Q)Tr4X zt)kn*IoAyy-FVt^|E#3?^pKlVx$-!IXKpsf8C`F$@U~+z$AWzmc}8Tbq*-k!&9rWz zEw{cN)L*xM@cR~(dynHFL}<*pM$%9q$gaK_jRF(=Y?^`TT+X#99YGQ?#7KKDmkc6` z2bzCNc&C7jEnMf4p)Rh9P4F6FSU=nDp}xL~s96mpq|xB2+!5caM|aVyhBPuWD&j6^ z6Y7SmrqmQA(Hl9JqO3pmWpkthwYL=DGqOC&-kt)~-03Bp!^@3~90-&%Zg>(}x!|gb zq2W7L)&+?Jxbf&rcA(@(O_o!NW3_?}lLH&_<+!5g5fMVX?Jz~mQ^oS-07l=Rj>&~E z7|n{Z3ypNC2zAsvDybKB$j`#5Oxzb55sTeRzyjxU;2wUomWLySlt)QwrO^slsyqm} zS(!q!kw1eLH2|M^T0hoT5`nm@sP3R4x6Pbtcqo`cM{;1I9F9OMr7Q&zv5lUdo^EV& zvAe(+$%fYq^3*mwYMEXt-CBu4qdZZXV{ZXO-2q)CP;02%m~^4!xY07yaxa2$aU6BD zMuod_RBk!GI#T_u#GptBpo$hqDOD^7z;S_5U9VPNGbE)RG2O1wwUqmn23c5PQmzKl z1F5U$C(V_zkdwIRTrI}~IIXk>;volh?98N=J_FX`9u?cBVu&=>moZe-A-hb<>|r3j zKMxYNCt(1Z@y{u`aI($>Y=(o<;UQJYb0A;KIR=#o2TG}p@K);Sn#lbj4$8>-P~{9Z z#5B=C=0SowThngJ>pgu5lI8)5zm+C-^0q@(p+=su)+XxZQR3%QLQ+nqA$w(J-&YtR zi0fj?o=h!e6_H-;7qE_j1m#Ev5l_-)vpGCEK0iCXcJ1obE7wj=j_qJGhrscPB#;DS z8jMunhALa}2UY_1yY~IT%AZ8ZSykzh=KbH)L&NZi0tnJ2wkRQW{mD*<^1{d<3SG0IsvDEL z?|$Ur(V=i=&h%fEc5x{Nam9zkUPQU>(c!ckenfUW+hDCQW!~Q)`cYXSke| zN5>mWT~0TE7a@(Lc20+~3~7~hp1w&e@8Ce9;xswK`vvgVgIz zr)xl9Eup}WmHR0<^4s2DG8(=5oSDt4O_b6gB@%bmMGt$U7V5x!3SF(gXEde-_eg*J z&5VZL+**mM``0SZyfMZycH3pUyVx$F(9D`HQk&p+Nc%pfz zTiO)X{+u?pv5z?~$qW8Suui8VP3c@xYBD1r!zxhBcWt6c$>?`YUq233AqzwTA#b5A zx4s_SU$;H`eS@Jfi_mg4NuY!Dt>vt41J3*R;0%fQw@7HzO!bWsHm@I~OQ(gdjAl)A zpkK+el)ru zuR$H?y(Of@cZ!H?l%p*6*9X}auK1Qhu8Qn!K&d<$eyHs=u(t&`u3FoXm=mhD{&d^Q zv(=Qck|m-kiuTmfPmEI-=wCyyhU9W2#W~ks+n%NpwRzboa=xAv9xos>4SCM#y-Bc2 zN*t1yBE;b{G&7GX*7@!!pE<}7H50cMn@p9w?MHTF0udXM4t`M&`kn0{#p3J@8}r#1 z6^c31O+yXT>QBgLdOeTA90T)8^h4`$OyImw_$HzL#MZzICGBNOez+us$Q9saVuj+SeJnWv+K-sst;`u$7r9-P<515hHr&7I2NObXdxk;g-#~i&ME#D*6C7}d}Sjhs-{Cv@o zt$j4OSY3ZtG_HxLc__OK6rnGHb%aF-Ak9n#$@=HEeC{_xtegiq zaW|aZr95NI?R7@i&aML4B{QQE8}c`4?z^3K4c+f+T(^gtkY;0qYzxQgw$XxWo8?JHZ|xFgSH4l8gZ=5 z4%1|hQomcMqI8?!LI<^{qsmIm!!Lzoi`uMwu)lr>#B}d`zl_<5{m)#$MfQr1CJPt8`DPtMSvMjsZ#dfghD?GXgVjOy9>5=`o9p| zo`FT2l@iRQavrnXFHvfQa9n|beH$zoayh~>?ejm5N}xcd`MH}Bh0s#9nsJY}xGlH7 z9`xLn4|-p*?krv&S7DT$5oEFXBC<6c5gC$(FFBQO27L6uYVrJPpP5eXr65?v$CghS zdCwQ@;FxX+WHjaIDBGO>_=6YjVBHDpg;f z+1+SIt%7?&?-Mi$nnjVMWy|Q_b2xH}N=C-Qgb9j&t>~NiaT;|><&a0ItQGPYatW6h zON)OYBVKkRR!iHAmK-{_ZjQJp=0!?B(viOe5Tz2BTBKsY18vY1Ol&LlF9TR%{HUA` zEK+(EEo~BdW<=?ABpm$8e^Z+QehzcV2oOybuY}?exiUR{q!0JgT7>5 z3DC4unKOwjP|6t*Ac!gl9r?3qN;Fv3YZ*j1{64e8qobqaqh-4rW0V){%v@2B{SRu3 zK=?pJOs2C0+|}Pw@&*KTh*F9oU^47VC7gz8&x$OgS*k0g%gm%RIFSGdaXC=518q(p z0LVzA418ukZAc+yVxr#Drw$HzBl55dr07b*piDY0l8j~*QzkLBk_Kf9*%nvQlXjMS zC2nc54+CgKrfm?duMsdr3iyiTaqNX)Xa=MtB%xuU6c=?Q?u$N1ru{G42NT80O#$RN zkSd~h{hEkM;{CNjkgcP#m0?1*l$0iv$E^wes94cUcn>1`-ZaInuD9%qN^+>#9|Q#L z>nB7r4M1w%4Uj8irblVohJ~q35$XE+66;rF4`}x4>nm|^v~034yUE!Xo^>wD&otldN;-R`73r@|%K%FIL8f%5PiJ~~YZ{YkJfjER*8I@*%4 ztJm&+pfk&)l4hzU4!Ed7ka&GoNcyMEh4Z(mz_u0<%?qv)0H zTyn0(e#(FfTIO)*ZYWxA<<`UlDBYtSG)uPXvV|}o>BE;Y0GB;4ZH_=9i{bXn2(Pcf zm(ex&ub0_WG1RkWSP|f2?S8ssWwy6jSwCGu%V-><;-;#!r0ji@lodAefa@FS!&LWp zDnyOxBUa7i^9iNX^;w@U#VA>?&sQJofOmpcYhbu{|5zJ6aH3mpM(7@<>yuAL*$)yy zd0@dX+$Zp2x~>$BWD}a7ORl!SMB24;4QS}*4Kj^ZFze3?y7yem&8yaXtic6 zxmAzs1~M#J^YI2~;E*XblwP2PAt2fJsNvkkMs;bsH)3eV^Q4qLctmUQ(#kg9 z+s+tvDQTpcgbJ!;+xlTD*GM8-5?wZ#2q=S$<}9X>JRaaOmzdgxy)%n9r0YaH0fOZ1 z1y5VwXpIDQ&@u$I6$Vghs?iDM<~m)Zy-ll0BFZgXenHq+I`R*#lOJFbkIeE`{PP7H zbZR+QPOFj7DFK7z#PP3{B#v@)lWElwT|Gia-@<83WnoL3@b1m^n{@zy3?>M}5QIOi zCUB7n%&2B9S}6?DPbtmY^8TS70+}D7g|OB7F8-UJVd4ViF$d)U>v&88cuj@ zgqoA6rv}Z)p{m%Zot&H<9Bj7RWz2sy%^YzJ7MMR@R#l8vN{W=u%3&f+y@3{N#WtDY zp17g)O}SYtAv=T+52%Q46fN|)>9A68tOGu-S-<|+3yc~$?i)=xxLnw(v3Hof5eb>F zcs=$&>#s+7^+Tliqy{0q791!D_M6+pQ$oCIcZXTA z8c6pN1PC_M4Im;gb~R|0+SvxQ8EE9o9c(s-M@Q#pXIHLWyMFcB$?5TCb7-n#*-6EC zD4HRQ+FDDYl}SaLobR6ugIMgJ%7Kivk+=uKswrYgN1;Zf?9o;ONU?tk1gqbQw*R6C zD4Ac9DNyOitrQ!U+5n+Lm4Sqi25ZGKk>O^B*!aX{S~q!VVZ~+T%3`j6e02Qqhu=*p zRZNcP8#e!Hl6Uz!rQmZ(&j4+H%xmTc7l>>#0IFrNM$cAq8)0*1E$_X3 zbVK$(zA6&q*O{zhDckxI(lnn`bWRJ_frI76$eWC=U(AYo#QHn19tKPZq9J2Ob+$KH zkz>TCWCBjCFFbA~8D!LnV_BAExwzQwwl{ZUV2mj}hL$dV4U{yv@T?QXd&um zfU)&28ce(TSKfbm5|^N_Puwun?WRpZrYpp-LpNrIuQ_ z_R#sVjr`!|rs2|2j4-oJZZ?Kite*&3(5lG50ILz)-pmql7=D~v?#0GCiTe7SSkf6jxU>{(gQ5wY)&A;3f8HK3*q9z#9=LEG| zf(+;>lCdlXvohaokhyd`dNP$Uq?D%=K?qs1O%Z`Y)p?hz>qRiTYb7Zu z5u3Hl>?7Ve3)1ANl;T^E#eGKF*w)}Ucx1ue%VtX}ot;PC9^7$Uo631Uq;Fhu5(izS31+%PD%od{&IComcU6VnP+s9bu; z<&enU-+>x49`|!>G_-gO1!?q2ICTQrG?cWffyb&!S;V7-Uc`isnRsJTu7N*L9C* z;Zghn_qzCSOYW2EM>ys?utU)g}fbE%Z%%s|Rkkup_V#B187)cK;7?UuF31gFzSZJvJ?#4kFS z>9>+|iXuL(8bs56P>5qVrdY~In?I>1!KDMQ<1$*+UL6>e$=%|tB}Kxd{;O6lI=AN#{lqiWW;xeF>1S zdWq0#6&6%XkUatppC+nnI0j(hnPEw#B}gqBk>Li{q%7A) z?=0$O&6zN!K2W1y2Deuw2Z$g98IhXCCyz;S=5x4X<*!2NYu>ZKOO&vkw*cU?Q_G{E zf|Z3g;5s%CIUknBtGT{^zn4SJWG$eKVlVPpX!IxSALjrjHK|DH8EsEZmIGI|I_`CH z31qJdtz);eG?Yu>cu-mK$NI@tN_c(4q@|)6m%KEp*)%aO&k=HAV5p_`T`c;|;Co&L#rtg!*ak>g?+*QB5b{qC!RZJkAFh5rW!0CWLcd1k~@JY4S4S zRzaC5XRd4{f@uw5=kseT%Y_wUnja+%%Lj9Gl{{0RCP#rV!b~h_2AXz8u z)7XHL96^g^lYHDtsP2<{5sGHW{sn8AfszkYu8%wedZDS(f1@_cBCp|Kat^%!hcH0G z0O^L8G}UcN2ZlgXO>pTfbe+)2!90Xta7@tJ{HI_n;oFkd2dB9fCfo8V`TkT6f?rwJ zb|(iS%h0Aw!MGs1Q1HJ>iDsbdzO{GER&xk7dh zqtDGz2I7~jp8`w%k7PP(6LD;8b9{1gc6xU8+O?}!t{fj9AK0OajIksmP>V!c*~hG8 z(sl(i;VmfcT9VmFJ5=>`RPE#;C5}C{SSrpjQ?omh%yOC+zojNo_^>}-V&~|8& zp>;M&pxx|jl0lM`2TiEA?w#)xwYFw&HV~hgqM4Y=76_bo&yGT>D>zK};(`3bR(eue zUoKlehDHkXnKZ}}rl+Ac5@c)`=&&0EkY$Wz*={d(+ue3qmN+>qz2|E_!oy0{BB^PN zNZft}E@sHBVaku^qwTGPa$u2aShtC`WcYx=qr_HK^61U~HRs$#G*Lp7xP`Xd`g+i# zErdB!6EK9vzTiREUIb=6=p0Q*`N^!qWxo;S2B&abb~?l~43+Y}&51?)L!IU$*01z~ z%I0bEoeXM(s$mSNM>ug4E&qe!K#-1Bv+)7FbuAHkDy3r|HY5>VGe3q+1OWF#%{gEr z5+MWlggWDz1lg3`luk-k0&Vw2RIAs-e_aCbJRNgD#=S9lr|{Sy_$>)mNd#$y|9t>eY1hNJ+kgBIAy`c80Bq_kyU#e~-q~xbsk+r&gP3%VB3wtTHN~T!V zZkJQr`}$!%t@||SnMS?8evyXuho36@jwzL;(%v>BHlmEoC4B;yt$)dv?}fbouiEg$ zi^Zh!)xpY>6)wqmaKODy|Iib&a(xxbsg`Okuy+-k>4YWrR8U&;RE{HW#kWV40+O->Jrzb~;N1KgdU<@QUAQV^JAm5Tiv*@b`ZjpVL zf}iPUL>8@D|Hi7Iwmw=mYBt2xtt%>UD?d!FY~N|jQ0$J*>qYrqxI7`*AYqL)e}_>v zlW=yhRJ3i&@1h|HT_4MqQLkUW`jFdhc}&CNQt}agAv_31E})o z_TtTvTUO3lTKB&Sbryuy3fb%fV~l0HY`5F(Zac;RrqU>;l;NYPT>q#F#Ek5jLY}S7 z5-O^5=qd+MLFX=VUe|2NZFNjY%X#;*ADKR1kJT--<<{4O`s=o5KO@NYNFr&CVBqpS zoB8S-CRC3G(mf$3Ltqv&aza3QS&op?%1Vj98k~MsTB7n;uk=%m! z5m2R#@%njF&v5RT?NdW-(@)l%E@2rSlhNctHBh;@Oo>fKkGltDgoS~)<%Y>c)9Lk4 zePWAY5FxWMx0nswhwqha@*>H_OniN}o2~FtWCT(A1;v)2fNl;B&Q8umWV_q8ae7qo z85JdrOJ1r7pY{F-p3$mrV<;h1X3dy5W?W%o-DAVZEhGYjbgdfzt>3*qh3Np+Dcop@ z?X*!Tg`VoF6Bel-q!8!5fXw~!jC%+!3+(|81|_kes>%b1>I=N0&3$FffUxoF|4p0}C{@-dOTCpn7SqVgVT&zDo52HA1vZxNu z7iS_Px9#SQ&9YAQK6QrkAiaK6iYG5E0F#bNX1)RK4cD|<(ilAmdW46?`rTAr*m^BY zUh{^B7AlYwR=@t$*3|kmwI|Vk#sO+MOew{cCeJC`6f_ zo}FF2cJ0cQD<>x>2ZtLI#qc12etn9}f>M`KFJjr#8{gqkn2c!`sl_Ew)gdq+t5{D; zt4VNSIjQPfY1Af=UD_a4D-^jT^QXzfSwr|eO8dn1N!VFi=@japX;K6g(|8_o*Wl}; z$EU}4J?w6?lnQEj`p4wT_i+k&lH{pstlx;8Ys^whwaLUbWw0oHuA_D%ip!qPNB2}Z z#4b){t9Isg`U}F{fs|xn9OoQVNoD}G@ZyTCE=d!;{<{8Y&|h~6)zg=)uQ|{x9T-7- zw7>E5OY4`3M@h6Q%0g=gD3~H**bu=9VJlbOYXJ%xRc@S%PL&;(%Q|KW?rzazu0JHz z!(LA!CU>NhN%z-0P)2PPLHQr0H44A`dAkl_J0WP}>#{yBtl&J%bN+wJb+;^tTuj0K{WQ$Hn&OZu-w1u~SQ z;8+Z+kzARw#g&Us9~Wr~q8KkFzS^F*kM_di7TR*_>%q=#QS+W1aoWa?lF80lZqJif zM=>EJ3<7h&G50?V$1bNBqEPLb4gz1Ff?voZcciu5WUgt6o@X?x`Fw4tt1%`aVs4xR z!kv6#1|cAsXRO6!7cH|*$eV0rXuJ$H%Y0o#l4}$|)6m7aHf~O6IR;Ie?n=X8;76xf ztR`P3{jv%wAljvdeW=dpmk77eND1=wGwdY$6wHzcIkBJ-Rq~NFtPWLW3{i8ElWCo* z6M-8!`{BNj7$eZ~W05@6Vtt8-8H7?3o^j;I*FeSfCZ!ds=&qY_J*N>)^r{xoS=>2? zEl6fgMd73p<>qRhfE*#umS9M+96KXp9)u%9Wuwb6qLYH6KS|s{(MgrJtWB$;EV?07 zAocyt!y|26Dg%Lk#B}9boETx1s44Ey8nlPQ@M?V|K0_`u+RQh+h8o3V7KqBx(b37t z>254z?4;%&h;oW2QbJxqyQr*lgG&7Nt z2r>jsV-u>kVoZp#X$))Rv@J-OtTQB%A&xz3+h*4R<@81&?lhteX8-V&vLrR*)zA=q zK@p0Z=1u@Gji-^$0IKf6O+MBEt(qVUQWUXTpV$}?{SfB?&C~pqwyiZseY5V9Rm%_6 zH7|+9qo_KO`Cpk&h1@@L%z7!~g0xzD#*l6Ln(LEIl4e3)bk@mPTk}VJZbIe;eRrnR z^|^j>Q6sw5Xk zw9^JhN}n%Uz1fbYQUS6D(}S*-jqR zjz7Tq9HAx6C4}Io+D(;YpR*aLmxHMSCq%tRWEOp?9xPe0IpgvSp|A?Exyj7+K}0b> zOKcW86%2$?7q7qm`AwEIdss%PG55;HOitOImt?`%Ez54Z-Co?hSQdedNp)G5OZ75J zrcg<4u|cadMynRJ+EYgJn-bwUF^|OJu_@-0mAOxTc(>4&TVD_AuZMi#GXPPHX^d%3 z0Z;>hC6KJjknug7_z)Z05Dukq|1%URc}_(p46G^Lt92b3Kj=J+J@MWAu3~FKK3KmpUrewe@rwdX)wbLwnwOBoHsC7v7my8icVjO zkv13_{~fPz_%?(o0GijQLLmXMX%?!2kwRGr6+#vYJRudadc`2kkOEcRdW@+;^sm-2!Sj%Qbl3J&~agIjX`qir^ z$st^g#2+73Y&7prr#l&;OVwzixMD0)%ehvl_q|0&&Qg>zKa3?wN(56fqFBXwqR$+v zyeRj(npr6oZ7m#DWbJ;pXAx1BYNmF2e0*?lu-h#IqiDOqR#3!-MxfcpOL z3pgek)pH6pQa>|M*(b-FuY=j^+Eo|*@8!sx`hMnfo+Zs56{ld&aYm(e)ZbP$;sj2p>1?o zWX8tP^$9)?&B~RaVr8e>f``i=CVbwEoi0&o;A(5p^0SBvpd_2)^F@g(rP{LMV#)UU zLknBjN<8ZcD^!+IU*BhPqeKjecbLk<9`^8)laopwu^AMje1B@mrt|(Wt|fvf;r8ie zLZX!HSovS21yYgW+${uVWr;eff#44d)QlVpgZELqSj!Br`GW&ujY;B`X(r_&CaS$m zKntT#ZT?c^n67mF5zvC^jA$UnY(~Fp$K>hr6IVViHX(vx?Ov^) z6Dtb2EWKHo;AjmTsIF0MCU?YMjmK;>O&lkWO1nP`QB@XB)#k<{Swsg0WkcWxL(pyjYgqi0&|~B=fctjp;#E}*}D4V(GtWhjQA_vzz|K0GW|0PBHw6j?si^!n*l^EPM7jQ-g-6zgiInJ+7oHAuO^FhKMySJDlCn2 zhlH_*!a=0S0#hG@Swa>(AT3_9=5e>MTXVX>6?%V-YRxri0)-nFy3*g ze+I43!SY37sc5w!+886W2KwBQ=!K!8ap&x8SasD)gwkKxqqYcytMySP!A3&EO2iYB z^vY7gZ^*2)$Y?2lP3KED?Nmt%FfDpVIg=)o;R*0MiR3I>oio)$B%FgO6ozc^o<|sQ zte8u4leSzR-Oec1pJ$p8y4lx{T|DRTasE!rBP$`o+S1MU&+q}|P&%Fa>X05f8hwKl zzrlQ4QM9CB-_xQkhh-oU%cT7It-C67ZSqdARg2rO$gEE`hX=<;N9X6~*REYXKRZ7@ zIypEvP{kM^<9ELQrC7d&O_UByyOB3oGZXs*Y2-|cn@7VGL>6^rFViim6*~g5m>`BM zYCFpMBm?rK)<}|V6Gxcx&PDPnP3&<#|F_@%(5u(3s^4ej?=&@Xh{SYD5AEn(W7LFl zCkpJxv~$^XrO@-w&oq*8x?!qS|D60ONNwo@4 zKuuhyx$Q`?=fa*BMT#7Vc3F#jmjn!N_05VD>EEmgG3IlpXm-Ifb;|3ZL3`YY4tf6M z7~w@EgqJ8$-n_K5bWTlb0WDcw&0 zO1~K&t0ZqZ<-@L(?h#Htf4b!SicQ2g4@60wsx*@8>r>DbDAuaGv&SZD+I{^CS!{uT zL78pY)LM9E)t}8W&igxe2JrH8j0wDKw-?K9@th#rkcL<(eLd$vO0jX8&giC`%#c8< zlG9Myl7)UNK1xd;2smsy)7fsJEw{cN+}xHAczSwe6k8A#bjGHvnq1!+N;QqX|8wNC z7UoPmy|9rF^ZYU)NwzcnxKMORo_xhGqdJ0g9RU}RXruN5Gz$LO$yIo^Q-11{R);*{ zi}Ti||8atYaH>ioiTQL*8R;C(=$bOg5Fyw7k?UjH;pPiNSl@<#(y&l=4@sn4m0ANA zVQPq^`NkNTxJ1L3oY+p3Wo-}M%ICcL)2+>9jA&I*eq(C$1RRyCH&jGylk|0Q1HqwYK5>~^ zbjvZRe;7(!b4i1sGD(tFy(T46A!e?JP0fj_+RCSmS;a|&uw+;$r!9DYlXp|39M#0j zvVYtseB$3WWEG1<&wk;> zO^(H`9khN849mqAlY3ZN}L&Gea_*s>2l%z+W}LT?M4m!Rr)xuyaXK)_O%_w%X-%xcc)r$;?U%G zvfCE4?>3Zo1JQjjXGcoLLG)4YDg!z*d#SSeRA=E0T#^xCznw?Fiu zsQr_S@6z`UXtq+sOwsJ%DSEw(1Cu($so=R}MF;}gd|ttnicEt@PfuM(`bxS$Wj?uF zw!V}@h1H`(;vp#!8a3Ud{9#+^glb1~knV}n+RN+`arO1f7M7#-Xav(j*!_7KWK=~V z?^>r*#69{Mk@i`kPadM?LEBcsaBcjNWT9HdXk;;-xiZWQ3g_Zb;#Q_T z((=w+uon+J>h-oDs5s%ur1w?=^sTQ4`PXe9`1Fdx6wwh*7L$DGECeYF2@`k6NW>xk zN$5dd1RB1RT(lsaGO{Jgjba}8@bVQDUa$PK)0;)1!*mseiy+xokQtrMf|@Bno2b4f zIh~UjlvSnF=qjHh?U6_)e1KF7))ELARbGoA5t{4rJ+ZWLqqzr_K-^N1(iIU>(Y=7n zIi*}nDYhUG4+xc5@Z=meql;{#nq;B1d*OoxU`vt-Ap#;GW8c^? zof}DQ(x&TDsUZy{nMGmG0oq@`=)P%Un8b4HqjaS}35b|`LiuF6k8O%IH4#~Egv(X7 z;A_4<`Oz6@eIe|Ij67<6bm}5d35^_LUEqmY-m)MjKYHSaKWJ85@D|@XRf~He^uE6T z$^mke;)LkTfh&K6a954>rzkwrR+=oyvoUD>RlzJ$mb*zQtY@xV9kfK4a2J#mnIIDK zRsqjZWQQJvvp*X`;v_bj3yfea&Tkuu$MO0#vWWGWy2S4DBPPT7P>xZ~)R9UE21Mwy zcFa1Qq{_z*~A;U%(;ohJ7I5yf!ZJ?YLe9Sh5~ zdT#VAoSd~>*;I#3noe8AaQn9K$5JHB;PASe9D>p(GEvlPMHk0R_GwZ%tn6ZAeM4E# zX-e0LU#V0hr^nNPB|uXl8GecV6hq<51hQbV!mJ6B2`&ruy{!!CgGVSCKOfAsMof%7 z)F2RyF?M6wUR+%4cFVGx>u+M*SWxIRRWNeaNh8-tuj^ z_4S}fTb`EwE75+IddGJVOW;&v{_6e-IsLT+c55IIvsmT{sx|{nayuyGHOvoJ*4EUa zmTj933L8=8k@PfGG^LW;^CEY*8pIM$u)3rueo#CYB~+yXN2tTk$NMLZJt{m*Pz1D4noVz z)3Of>$(mDAHkFmKMzH~I*Hn8#hSDUXyqB;g7tRmmVnmKCiW1>i~%)`_fj zJC%Kmn{^?RQ{cdARh zY^8ddz8Wd1^i@W&8LCeK#h($fSYyoaDKWZMb$+e1_L&o*E!3J*WXWF0O;MEGXS1Qv zQgQO&nVM<{N665dr_VRn=O|a@rbsK$PJ~TF^EF3Pz9;Y1Fl_;o*Celh0J@b%oF<>r z*UuBx@)9zBg^Hj{9%&7*rCCoCu}~OM~}5Bf{= z9Jc}=p{551o1>%Sv$M0SSFW62IX^i*J~%iqwXuvLNedIrJD}cs%}z3UFZf)^0|8Bf zYm!nX-sSROL~p7in=;d)erR#=AQxPxrmCCd3(9^tHJ7X$5GBc(2#yYq9{$L?O*d7| zkmf_Hn&HQ}IsXx6oM~9)9PNBJFs$F#PtWiqw1B9w!rMZ*zoJwgu>i_IHNOY7LIfC{ z`~b$%i@8L(QbMIm;o8OOltxg%SY7`*tSw#AN~zV-&(>;C{`f%z6=_TvP5hU!s!A86 z(kMNN=Lu(Yps&2+q?w+kMYvS1fC6oX!EtiZ-2FA^4wPd`huAut2zdton;o z)#!b~K0IU6(j_=HKi`+LSU?6JNeH_@7IC7J9Zmp=pG>3~?AIqTNllee-k;6a zKaeOL^)zYvpcnBe)B1YJkX?!e&Y}c?iccha?D-V*3}6|%?QVNoB!nA z{LmigIc9$`W-T(Jkar8R8v2FhZsIAkpB#}R3AAPck)VRfwJxsW1j0T1-Cx%bc)lP| z1Lrw2B71HH?bz-i4~$TPl((S{z$|P~h)F&$G%Tlrk&+y}F>55CgxBipYqsnojRfaL zJf?L^<yo?k2oexYVk0nF5S zAU&3%kF;k3F%y1h{n+F!!xB?@HN376A2YNb(FZ-{W8%T#`tm zStWxTa28db#>mjX=&w;(B_eJq>8{P_@x6gt=ty@E$VNPD%sk*4Kx4oJPBYz54~`Db zPEW;Tcd_l%n93*8a8D^{EP8c{eJN$Ks+7G@BcZkXU&xJ8DUo&x#!wC2G`3SAqhlmq zsf^mDodB($toii8qs;6OxLMgS1sk^rPMnOT${%WzFU{9#J75k7+sX(ar;^@Z1Ous}tC7G{yt z)dKdZ$FD4@44VM~uOLQvn3rboDT5zqg9&gX8lae?L*LG|%H7b7NhLspcL_Zi%sKQ{ zC3`|Yzwhte~RFJgPBHVmeGf% zx;Z>NIX*o-KfQMC>iPNE@$u2-;9yn|Qwtm|SiXXa9+Zlptpw~j`h`kkxhk53LO2?` z1zKq(x) z`J`&*Be9f{0CdX9D*$>5o*|tbZSjr>j8*q2Qz7J(Exgj8d6bv_loDUEe)ZD{;KshD z(t?30Ldd^!fRtX%7o9vvR_piAyG0}m^G-W2BzZroJPr~iwqC{vUABIMrR!YhXz{*2 zKbDXN2KDtb^^EHkRV39}dRfw-8T2*gU%um(6`ZM7kA| z79~WC2J@67TykHs@9cV-l+W}DX`3_p`rt!6%?wi6UM_M{vt{0NG$CQOc9C*G+7vi>w7zX79f5rtDVnza(_MN;mKx;+mx)bkwNy;T zr)dgZU5~yS+#XkhI_4`HE0#p9UycxSOP<%?s{EMA2O2g7l)D%jX$tXS)rE~7I01a0Av!U<5Cv0kkdjfZ%e@YboX zB?9)RJ)e8&88aj*M%mHua5SFjK?mim%!~~AEutGxA2LC>bMzy}2gfI;Cm3V5TQc90 zGL(qgUlzkk1$S|z*6TCmQXWaFT~wlLn)crMRIy<-HbgJOsJz}=Uy0eyrW+)2(971> z6)|MxqtSg!m3U0rWNjJhU4$hRJ)%>Ro4cV>okgS%cJ(5w2J%B+uOCYMxGpARkVb_` zIl7WHYIzQU-?8q6bXGW$y8EM+p@r1vDT%{2LKeygHM<0rl@XYR-Xaq#OhI5M;zmU)+cI{kRNrik;~^|#IHBCVq2|pr87;eX8%}=49kF=!&TCAwOW7B`p&Hjz{!Un)UBif&UHF%dgqs6NXC+>7PJvoHu-!P`9A%h2&=bl zR5P*ou`CxiFD|whV;Let%(8H>D7__5243GoU4^V*r0BZo0H`X0TWHIzuLn2U@&WIY zVrR7RSGPl3s7wZP+}!ik<7^{*QD>O(%zySh+XibTrI-pcZv|T3YMKQ2^2E=>vb-r$ z+M}wWB1CaErE-3yTxLwU+LCmpL2-~pwE9AgbTmU1ps~J%sS}WehDQ}kY8$I%%J6J; zcc(!0ji{0pscS)`a}(|iw5Tb*Ur`U-@dIP7-_Rz@ssS}8OXJd5Uloz+Qpaa`3i?0g zCQsgnhE= zrko_E;Jf09mLxgn4!z%yz4McpDYHd))Y&wRfA z6n~({x>fAjUIitVSFj|PEND+M#yC0Zqd3CaML zT>Fv+kapz!0<^}?`MGh;jK(69WR`R6wqr4vqZIisAMC*^!HYf0VZReEFu`TYs&%TT!XB z2?U{Lo5SOylau4~^D9@cUO72CJ3cxx+sHJ~NvIVi8Ua1=%7kGpARVQ+AuR^j0o|w7 zHDYrDv74^zdVMaWSTB;3a-O$~ncaEUou_B#no%T9*D42(a_I5waYd^)d|xz+M7(H! zGcXrjKbKi|Mw;@F%EoBJ=CB3n^r4ivSI)py^a+UrYkldqF-YTE6`S%6H_z9ZC|9_& zLz4t!=S3>r6&`~Tjs#Kv zxM3uPrD-x;tfJ`3v5X?GJYWbhoy%Z{SU=c+4;8-T1&~6#0zOZ?24!d#bc9mJ zEQqTLO`PNsgU)n6Nxxcc3717H;C^$oAObLEQbZDI9Vtk^3lc~oV)wW*4jQ)~u4&1Y zo^_KEV_HUq8oUci)R|YxY8epAJkPIycEMWktjl?>%T`@{$RvD2(h4DEOCSALMZ7vp z1>8;3?{g~O2xU0Un(BL`8SM6!i>5bFQ8qZKSqfRX^3Nkl6JbAGjOSQ?P$#p(3@SaZ zWYklurOGV$5c9Y`T_Uq#%qdA!fVPKA)L%xnbmA0@J7_iF!Ht1Vb+F}ZBz#FxbsXRS z61p<QPA1o{vKd02z~u?aRP6LB;-QER z5|#{6*=#naCnpC7o84}QrMKnO!>r$2MSAr8C&l@E|DY4VR{NVs7(Nj>TzT@oP7s%& z&CeL@%5+(H^B^}5CW4pyQosK5{s+SwXx9P##z2dWq!tW9Jh|e{`})-KDB2FI&FjK< zJ03u`ASPZvJycbaj}8_N5mTc;NlnA^#(iHwqT0fqiPwidCDpQ`t>>j4>}*MSXCw~^ zwB(Qcd=*>@_h4id^7NrhAhd@xw^G_B;%|`PD1ePEJpc4v#jQjff5mA%oINOH)yu zS@ji;TrA$83@bH|WDhLUyo_vl$$y8=cT>u!?DtO?%7%#$L`5F@(1%{RdMzoE*}`e{ zbqR2klUfarc(2GAQ_?Q8I8#zWgvfXXETFtsBgd{jq9JYnE96hh9f$5x%IWdO6QEQG zsn^emH0ALZI_^r5XH-r_hbw}do@c`DfXXv}O+&T{3LSH-HY~!?v~sH5c_uRE)tInS zk!ERHQUPz?B-5=v{o0M_Ode#I>(9`^+9*@Ds|05eUyhaEfs2zyJCmg$N?bS3RiYii zGm-ih;GQHD%tJXB9HOeSH$_OCb|wzd=Ue?zVk4+iW6qv~&tjUh&7uuMySgWDd>(A|Bl=U~rXl&H6L6&N> z%NomWYPTQGf8wx3}=lgGRzTJh!uKbkzR)Dv3VrC%*UnKlcT8uzBr^ zUUCaZxb^kBpV^8-q3j@k#_RVwTX056tQtG0K zLr<#=uMW&$3?qMpA?;Ndh3r&qT7#9SE7DaG++`7_;NVX4Z4q&MfYkA87*H7!$&U zI6}BHgQ|4EK($I9OcdP%ythr)0r>i}aZ&U21I!PL5F;y5n^TFAsG;g8{gofVaL1Q1 z{0hwfw!rp0D0tGzMy&6El4p8oxa8YJ3kO^%<<2886%44B2+YsKA!*s7f_Xn`GpiPj zUnF9lQ8uJS4Z963WZmAh`FNVYW>;o21S~MA{KTEtad2>OdU9$uwimnJ`B!dC>*^uv zz6iZd)n;2PG=`*v73qjDDbA$NI-9kU=N1rbs>MPRZQ1GQIT8wTVkFJB)3``JWGa-! zQ|oi95D^IRNd|q$*s}ov>a`Gp-~v{=3sp6`GEL1-sFP=yXftA+A1e(s+x{=*eY{R$ z0-EQdg5^QvE1Zy5^mb<9=55NKFUSx}gnSW7TSuX+U5`~Cy0nO1YBRd93~e;ZKnb){ zA=o_#q!R%nf|_z62xYP|;)a#Ng#{hWbI17o=k*!%7|kO)P23w-Zf~-u5g0+nVFhGn zsPMePL%R6{947^V6;4-C_5(AF;Y?`w}2tgF*bF8@sZdvQ`HkKARWD^Otq3i z&F}PtF{p?RHop`tNDCuvYfQ)FsDE1iJ1Tt)z?!qzvoz7Hra;6%r z6?3JLQ^@ZK6(H!foY8#7Y1iUYH212pm{66O`O2`cxI$pLpA)k*RZh@iBITB9VVM^= zWat2ukWeMnSb8&Nz$ran#B|a!_-Ud&(1%QDTRp#4=R27;IAu=xe2w)fTVd>$-EMpH z=EbsG24J2hZ4$l#!P7Eu3j~;0z8n1a^lf>;D}VgpcmIczRhl1)zrzwSPKd&t3lhWy>d;ACJ?Tr^q^+T50MeGqdxw<|EmIxVuO5C)Npfi{~$InU1^R$^v* zgRN;BUYK3;`HP2 zlQ$4WXP)IlGIb$C3_1DqoqOR;PCMD?$TfZ(mXzyB|FWRw)VE#p*f>OfS>}2w> zivg)M+DZ|7Dkcz8c4J7Aq{C|6^|PNZLzOArY>tkOPEJm)oSk2}dgbK!^yuhlvoR6H7-+tp zG)eZp2YLOqU;mo9t16qO4?oW;ZNY}iRc<9KgY`OhKZ;M)Gn`c00OpJ zAH^W%j4YwB#kuKm6<&@U$%ddG7t((EW@4U(;qxmM6Im>39~A4d+2MPiLZ(d!OBaUW z&?*f?eB9EU^(pKOshLCh#Q6^!$=veD%jETt6rt=QmqFhBy5FcFd|T_@x)`|Mfdw^SW>O$WLA_ zE^c88x4s_e>#=|7)24IP^l6*fwG+plkMWorU9c%JQks#4FLYuwqGNX9sC4F;jB2K1 z5ak_Cnh})+Gip|~GU0%t63#$2T|8tJs)m8oZCK^kOje@%2@_OWCLB&Dpy!-j= zisnDZoV9)ycc@>s_xgn_<$l-e2NG9ym1tTPP*Xvcd-VB|fUnVzGS;%%rw3SH%JWjw zvbpT_Qbz(+%a2Cbvz|TdGCo|I%V;vDK;u0Ukk|dQTXy&S_HS>uTk>a62|<3A+ix&L^ipvUb;TYi`JuieG6QF zzH@ROcz{=jlWi`sYgu>FK~#ho`LubO?WBVtG6TX{e3o^VIEmX=XGnZiMO!rx&EH7PV5hrC7%{LGr|sOMUm9J5kctg%I!Kyl!#O=Ss(fOYlGwU z>)8YL@VufwjJ9Fs@KD$F>Vg<(|Qm<%_*dqmXY}A2Xh1wZAnQ51%iEZ_~E8 zUVpgXMtq<}6G)w$pR~}SvT@7S_4%kVR|ugf97h|@fmc4?`T3o&SnLlLEtWs_**-bP zaeH&O-CkU5FD|ybWqW~keUt>u548X1`8BH9vcA+UZ>gdDR&G@EAB>+7RL!4Ik;=^68Opa;EzH~RID?cjl~KbLIM$`O^8 zeDDC*$Ddi0i;C{8-(8a8To0K&GMvHpgv=Zqv3^|u?8&Q}kh>Kvx!Z=XO2tH4JYP1a z7Y3F*ulSNmgBtqx5Y(1mIlHCTwH&OkYa-ZrAr1}>4~`BG4i22)y_wYjrf?8L)PO~G z^ZUT8B~b~j zEd$45e64O7OF|ZJkGxUN@+>5p0l=*228@XXQ zjE}0;avbdWx`P75;9h#A*M)Z>3L!p2rL9bR6|2O*$qw{9?9j&~fPyicYe6=3OU2~G zgx}W(hy8kr=p;fQ)T$LNnpqA&;r<9U6e2sSTns!Ru2>?V=m6AQlEe&td8m}oQY4FJ zUP4`eNv_fje58xgPROw1Y5jp9$lnY&&;jhCT0Z=BopK%w*1MI$Kf z)#b+rIAz}R$V&R`{4BiqQnC*9QahZ+$P^|fwW==GqR1oSrenizO&GE?l1<8sk}#C~uuInu zp?F(A>C2K&UOB8*W>$2R4t#J=OH>xfYL8(JUDilo>%Q5Rd`y?Mn7t@a$XzCO+Q z*Xx(Shj3~&0qMT9W@njCM)aIIU$prmaeAhdmu#hsM#6CnBcwEV)MD}V5ovOq)%uYjsl3b{0C_t-5$@EVe z<^+}2BFRk>4`?rhJQvd0Zml2+`WhR~*a z1e#SAO=rliPn(H5;}0qsdL9oOpC_vvXjSwGJGM{m$-d-*7l$We{ZS#F(jA7I>#NfK z$pp*9C zw+23`8N!iQaU^MTC~xH5y^v}?hzhL=2iLleRbSCN&+K7l(#u{50Eau#{i*9%Bxd6i zlD2ZY;!Dx4fB7Cr*C^Kg!`FYs7ZZLBbgT4nT6-lRW>w2S$k{{_#kTd=_rI)07@emm zq*n_Rn}DG)u-k1fZr;D!4vNSMGsj5!5XkQ@w`Fs9_-B9qN9TWU@45FMz4vqTpWgp- zU+}cg{FC{|zyI#!S_COQBB2V3rPRG5<4}{2!%pJZuWX1BVXlw}B9L6;U`^7Km;n7)Oc)x(ROMf+;bX z|EeO(6p@Z#LB?!jjG^k8YYsh7!g3IVL(^cB@RV=|5?`oIUI#K^1g`usBKwO@nt&|H z5B9;7S4-&knn=I?d~nS~WsZVLHH0>q_JCTtC>Jx7*X%K@WNnHTQQQRCrNM}qLUWx) z17pf9cjZ&SY0I)2r{qx4H11mTX+)q>sg|JGJlJP}s#1WiPjnf?J;H!)TAuNMumf;g zzlOrq1FOmyY8GQARaqb#(V;NG5)qGRP~;Q1c6O+W6LTmUi-uOP2B*WluN)s3C;-gDXIs;?KiOQtwLl8Ro7QCr zW;J7D(!x;y$L2?{jc!?!kkwMdd84%t^Nwi9rj2Jj~bLmoiXs{pS z`87!!Ps+vQ_2VpvC^)8Jl|+@DxzmN7j0RBfN$zh-Jal*86_FKDCNRX-h9%)}O7PO4 zNq7ofF+aY3kd0PEoM580(6Kn*>&%xxjeI;q*M}dg-WqmS6lv38ilR$4RhSUh`ovR- zv%C0!J2AcbeK|Ae(&oH{TBN0(>BYl20* z{E;GwFkB9r9%LR`3JPh0La^QLcDsvEz@?yNsjT%l*$l$Ch%8V|Wi4f$(A-#Wq-O+Xn72CH;e-gP97+0)#h+*cz3 zp|CVG2#1iIV2pVTVLGxkuOL>ea!egL2SHkW>r7zZ-?S{{CMN;KxnnJca6m!HTC&8JI?@#5wrv3;8E#>*%$+5LQ$!OQ>@0EtNelet5+SiH?Oh!|M%d#OI z^J1FoPzOEs12o7qaga**U9XQw1|Q{Sv;Vw$J)Lvx0Y2Xw`BFL)took0&PF$z!-J!v zgN+@G^cNKUeMDOhPES7bsG7qSwXxLI1U zVZc<{Wa(QVTk}6KI^kF^iB%So_oxX5r%44KDcl;Dtr`aUZHV&v&~;a}3gO{yNJ-w& zp5H8s(F@oy?z#8A`|i6Jp;1B=F9wlyE+wkJ55q#H4Z}rrCYS9Gv3`OFabvBpV}why zc!YW;JjEko(v6R#wRBksM>Cf3>$Rs4T~F0@&b5BG0j2`5v}VCHKu_VWJqu(QGzely zw169udX$P)tZ1Gl@~+k|qbLm{?;g4x4_&|&w7#qdym9@_v17OYn%Cb%07H-GZK@;! z4%WWFS|2UHejVDbKT0U?{Blt6r$>`OTc)`TiaqsEjzIHu$e={o5d9`RK{3HT5#2zC zj!D84X(=8Vzu}O6) z4;q0e@qV&xk68c-)^zA}s!RcuYjVZJ>iG(?Ov^S@i#;) znkO=m&$H0?XXr3DNUv%FWu>}4u-pBaY)f#fr_@iTAY`}fw%hH+cDrmZZeHxh*tN7H zNDs8jq+9Lt27e#t=jV6ab*DDJ)x82kFWcW56N4Ub*3PZ!E$tdtk;~RcUtZS=8ptK< z^L*p7BJz^Ls_CqPwbJ(=f)+``iGDZ=iCbhpk-exjwZE;#p8lq+bb8I|>UUbdi6-|% ztjrl}*I(D4uW*UGDrn&_51Ii-!J-f}PDWF7$x0%&u8Z?Jnl^)v6eY&`3~1+?ZYyJZ zL*#Yok7));h_gDp+38}d*=mSp*OwhV{BbHdA87p=I_nw;TffvHZHC=vj%fG4_9(6N zr-8}}QeW2(P3I=Evanm7kC(2$c59c*@6Xu(W#yXgF;#YWcyMrVcz9qC`~6Z99bLV` zNXkEc-;db;p0VBj#8-d)Ghg`W^N+9nv7h+u*S-E0zHsY{zizz$`&45!jr21G1r#-$ zkvKChS4r?4WpzC;qcX;%D78rQkC?D)kj>DU*x=!ecOyeWgc^~({TPZ~4VR!KVU-#q zk-fv%x`;NuSsS^L9K>rk_w47sPxSFDku52ta}dQ8m(&Ez+Y}DG*}Fa{VyrX3k`aZJlKG$t8Q~Z0O=Ku~ zVhkO>T1t{IObZ&5R{wxBRt`5Hq$_-acm4jYd6T2Vqm$zkVC;6g;Gro$7($jvQ2JCP^Rc4ioK!3g zYtkX??;27M_r~)iQElcr$RQo_^YCA217+_x6%Hf3pN1eMawEJ#Ni^nd@@R>e6hs46 zu~AN=nRV@LZIIaKy{KiGhqmX(9TKC@)*V;~6Z8HI%T2dM;i#U9O3_PTY>4hr8Vi7} zANOpk@RXz|g|?q1(%0?W+vbz zHM-o;iJ4fJc$KfN9Cw}fv&97q9UFT;tF|+a+%dG-n{c?P5GIpuUsK1Uk-DLkJ5#w` zq-O=oFiXRl6+)EJNh2X0Z9Qs`6@Z`EFDNQ6pMZ2)D#)XvSCIF>ko7!*9;5j0z4a;I zkk%h9$KGjSkT#c!{Uvm|^do3&o;AEO%K59XK*(5`g%mw0j-Gx{&$H#==Rs{Yz9B3D**)_GbS#K81ay=;A^F0t1xdgQ$J z1P6pvS$p@}iX0wiX1>$AT%QGHJuRsOsY>!67N4x@D@sC^JQ6!EV=Z&i_KeEP{8-nb zsPGTh)qFQz%INdO{q+esL$|_D*1fceVK0S;R8D3Q7n?u_Bv+e@80{W@zD$~@1u~M` z@(`sE^nhcE&K6ne74T7l{y2X%vpvb=A>7@%k_Cuci2ma_ET6L%x%f&tSzT-Du z_v-Kap*O$cmA@Zvk%+#bAfihM#MYPhOQK_3O z*^FTF=6SxbgG>lYktK~IK{O5DvRyefK<4x_;5O{e&9`RXdj?@5 z(}R7CPGI#upZMe>Mb(iMm35`L5G?Vjf-E?*nQOXzieglkgUinx49t3&Ok5Rv1l`eT z?HTMH8M*}_smSnORQ6~R_ZHTbjk4>)-keI}ret1~fb1bU?pBaIzaGiUyoPvE?FFhZ z*?wY)*FXQ5BV{Ca+G>P9@+nN@CTQcRX;wv{M=kF>cnFbFH2g=b3ezDmpNuz-{2dBV zWO_DS^KL7BMb!D8`%cDstgUnsYUXg#G-1lxO{27JCJI6 z5TirbJ3&+dGrM~A#_8$BJKy=vi`@lcn+ae@gYGgc{U|wxcpwb=YvAVe?Yd(XlP93j zUX0=DCRgYfFw%s7aPEj3&F0PX{CW^mAZTEQ*i0N8~$2 z{V2mgC~}&D0KWc&4oUaFfNIqs!{aL4v|=FAGC1Owdr;{%$@5i(QPu4ELmrbxp`N`I zC9r!i0mBf=Sw!mT+$d9lki=-^`1c^nqcV*NBrge?c|aN>x6XYnHS84!jCd>nTtG_u zr=ikgNn@h~43s}h-k1K?2bTx)os>E;YjkLugORsXs}xXCEF%m|J+9t(eGs}d*-Qou z3gaWcAuy-1qGSevaC0+20}#t*TmTPeRA%yUIyWbwq10*Ri>8Dp`R>~+>ur`tvby~W zb|a(TTEC2MEY=Un>!2140?Vf%881>%ZE3>`HM5hW6FEEy>)Em0ZZEdG-S*~gx7#i| zWYBK<>p-A-es*@(U3W+m|Bxa^xoK~H#ontbO}`+N$W99OI^WS`smLGX=0Cm}!F#)W zeQk0lG@y$pOVX^pvtseO&RT$P8_}uOGuW@rKBXm$<28fe z>khOud}K0yViLY6~#3owzKV=ONYYNEiHDmj7rXH`qnB9m2e>nTHXFMelnx&9+XiJ6M zmi2K{1|lj-Y{a5g&geFyHxhlyoMR2h#l_9vd$i@s!|wc(Km4EOfB)it_>Ozt_@<|Q z`twEPqrT?<5Rq?qzmL4{EpKC7WyLfZJnU&tee8!k`=#ITE%K}XP3wHr*ZdzxSI+;hKmEdW-*xNjclYJvJ#J|+ zJn9v)js-Ado7NiFPT7Kn*1s}{Y9}A1d>Fy5tZmmzIJ4Ah&ry7xm1UkX+;V_uj3bMp z>2y5{@2oj}3mTOx9~y&XgU768zJALg+kCe3aU> zDQ3ACmGUW_J)&6>3Xle$rX+gdNwjp50F8E}m1MymMcW!sP9IOX56&0Z5V>yG190JN= zN|~2>1LQkf!!t+8D9dOJkIPDzPpPmmfKkqo3CvTqJh+v!`3@Xw~ zB<*VxO2F_S?r_h}-ZEq|l@R6q?%Y_6#pFYW=DyPJk&)YO_+Z^c6q)NW(KrB= z-X8Pr>Klv)`Exrnkule+RBRN*CGWD~Ps7DOt#sFnoRtzM(8&Cg`{`}5!G>n6#Tsc4u$@H05uOEaGaAGG` zi{}>}EEe_!PV@x@*youaG_N5;O?~}QmZW9Pt7TIMcRUM%9X6R^RA5AAht2hMsM*oc z@!|2Y%K0n7SX${Fn>peTYul{43GZU@~XYqk!<%Apy&e)kn}e8OU!DQkJo zB|?8FV+yIYC$uE)Pah|!_y8;lO7fm1S3jY0Cp5#*_DYC2mgh@4vn3>q6`P+W@OaHcryyR=Y;ZtAqT?Z$}BJ!y(`mW{X#Sj1Wzq7ml z=7)d97v~S&_tv-F|69NHt`~pZkN?#7ecoToKW>hWt~~6{Kk&(a?e`tS`wFy>Hyh;1tDN! zKT)!y_Q)Y-fz@PB9Crh=x``@Mqw_2U>$uKxDn7f!F+c_*F;Na^s+0~A)WKOaP7zJo zj>9z%f*Jgz9%ao#s^&a5q)c;7G)55>9uyZSgcfywjN$c;1rcL985);1gL#!R=#oYt zX;v^ig@nybu*@4cq!#HBnvK_B+Y<56F3+pT$|UU|lgZ`kquG9|wX_&WixtT0KSIos zv)2f3^3TZ9@;$&dB>Oa3UADSEYi1~yt{_5M~1^|SxAKG4-F>4@eGlX zT9!<%6dFQ<^m<>YDB`fR844mbk9PefHBnTZXzgY7Bnp5Mf+m8k=`N9jVTv{~$IvbOS*S6)W@S@DeH)V^k_`uJ3#2 zDE7LOaxA;xNI}*DjiY9dW&)01))*+Xe1Y>8kd`(}Idc&jwENSdH4Y-1w8~$T>+2)|WIe zS-?nmr5spMR$W4Re9k#%@>Yy1s#a%HXRO&qPKp^TI!19gNW9T zL;*s?KZ^AmEXTq#sZo`#1WOMgZbavd+z;*@AW~Cc)zEA{6G8qBC;C(iG<{qAQz?Fk?Gp$}TL<<{4K z1FL0pN}yxE@R|u)5&%d?p38@H3@u}bi!T~*A`h;&>XEf%JrfK?V`TQ-!)xRKEz%wQpcqY(Ku8UW@a%#Y2g zdYNxB6Gt)9aYIwF(vARc{GbWVqmq?}4x4gMxL(Q=mplqQ@EWaKH2OQA4A&<&(%~&z z9St*#ytF|ga+T=-a&i)(EM6wV20Qgm^=O4qq(Wd~cErYv&(AdDdi|J~H#X*swZb{$ zZ3RSo)o*bVX9^Z5G-uv#k zH+UbY+@jg65Rawyw}{_bSuaxlD1#3suSIsbM#0 zK2W@TwLi=okN&^9tQ28Z92}ww9s~lmHiG14;xkC{VCBz6gtALweI*tPZC$bc=w`ol zNtBzA@@;n49ppx@t%FjYGpC%Z*-92jMMFZuW+nwnM)^yz9jOq8=oi2e-btrmUO(p) zA_5DyLZ(N%+O7#yXxL^-Nj z_w}nANYDK=VNtDV^L)N6j=#`usUH%qMy%fx*!FP5U?NOo8uLP{O^@eyh{%|hB+_30 zgn=Mqaz9se_YtU7u+}6^p_2!Z4>}Cm0LXj?ZdVuKd;Vr7cUahWmLjatwX!N0lnho#EjHrYRC^1@KnqbOK1Zpdv<1{mq9vHjT`cS77OEU^mD7uWz6^WW zKQRQb3f2^2FGR#kyGd3Xa%lpEg-B@=g>qQV)2~o|tKK_HprZLAeBwZacxI!}Bj{(| zAR$<{TTa$6h=tG432$j&c@2s7h%lMM-}(TYw>Ccr7IcdSM5JEQ zI?@r2XT%}ZSI_>;`+t;?Kc2t!tZ+?ggi&~7#jCeWnNjoi+y zMOdh-YJLA-d0A!-!Yb-DO89!$qt2IA3`vezf6tUecRuw!KIz~7)A`^3^~=BNr~mOk zUwy-CU-Xk-`=n>S;FUklfBA-&{p>INoB#COFa7+-Jof{?{WJg4|0D7Pe)FeZe)lt< zcJh!ldE}0#y!##R@ouLNy?uA@eQ*7hUw!S1esb(~tM55DJ$dN6Kk1IAyt|tH)~~)-h6Km4ML-+t%( z=DYsjd)mRq4h}@*;ZOgA0=(^2zjpuI-tH66gM&wX;4`m1`jN+1&+mWxJAU)0UiOw( z{PHEg@$QG+@suZDf6Sxa^z*;?hM)e~?fv&Z{Ao|!-h1Dhf8m$-+fE*G1Ncl++CvTwUKxvw$Y^XSgHG|uj7{3)UJUO-iSmQYaZZ_&83dc1zs_*;Q zC*@Xn&&AE3eeJKi5etR}$tAVr8U9R-gOlzoBpY*oF?W3!-fMsgXMp^nYWGtL*U8s8 zo#$QS#1NU%_(*yE^jeRXot2$wVJRTf9L-Y!h5wfwTh1}();cOK$utWzRFpWxwP@bY znCi10^Tfox@42}D=U(^goZ^=A;v|stOzrL;mLtg0+&cJCtWXtIeb!^26jTbl^XAQ; zeci8TTfRyFL}7*1#ZIFMkbQIXK#n9>UdAFQRV=4uo})>lrx*FV9A@@2foZj!df^+S}B!P28T4;09BPyjKXqtiPqP;v3kp3K3)FN`k!h|RJ_4i#1HF?c*PJ2>1_vKZssEw>L zsjZ^wb5f7G{w=pW&M(g6P|lOe_hcD*ln1WwSwo6}FmVG-Y$|#mWTtr999?x>Q-2pG zL_kzP5Ksi9Te?%a8v%)dAR!&nFqF>G9U>iqfPmzryE{iW8x4cSyWji&-QBt0dp{?h z^PJ~MM;2+DFnkZiKHj5BhJF$^Y|VaBk_igY5O3u7;9xeI`PyCJuTK4g?~sG-rJIJE z;6#WhOua)Puf8?oW>}1WGGl6$7ry#*&LQMUUmdXrNw=o)h_qL*R~^_e;X`Jv(M`J} zVIg;lG*d>QM4sztHqhAU!|~?k zo{rXz%{n$lVQ4A-Gp6tGd5N2$2s^3b2kzRPb$NvZH6J7U#lLpfKYpE9hjhF0--pe! z&w~BU?+AsDn@Oi}Wthu0B&qVh`h+Q2Q7Q5-Arj{fQ{I1;kE;=s{`dE#P*_E;w|nLU z=eHyT`R>x^(+P*S{KL;!R4oLtqQjjy;Eo!zn$ThV$_NhmSa@E`H#y^&^v|7Cq0*aW z)x|nf3W*=pWMftar43DM0S8svZNJbzu9*#kppVO<$&}kH;#gnwtBmXp=RI*?(!(h% zmlAr_Q4{(`@>=H|K|2p`TA|Hf#$Iz(EZa%_#H}Vnm)Xg^F0M}2vfN+3#VLQDSfzX| zxm>eKr9X@Sgi32|grw)wU|Zt#fhss^r1lZMvDcUKW$i~S%cF7U)vq+|Cc1_$t!UnN zOB27_P+(c6L%{E)}rzEh)2rSw`tC7@{$*WUb5cEAZwU^^Y#OS0W|fU{N45aK zBV@q?DR83#JOV(@2du-ljKCLFV6?=#$Ap9cl|ras;xE;G@JnO}?{}>ba;S(x-S-5> zq~^>;6cXS2&aePccDWBYzh6KNx!z~blM3(oKMhf$raz9myUwmf8V z8-(14S&|b=cv|NqQG?%{JMU9u8qIfr_l>2JU!y;|he+wh;r~6sFSw`eV3b#oUi}66<$$EA7m~m6F9&HbuKHA18vWvY5k{-i>nh zfB)`_#bIMD7#my2FxM>B#=3S?M*&MXY;ry>} zU;E?jsehJwspaf9YH5en_h`Y^ueh_(#e^6^XD$Qr5+?>p>KxlDxU=y0zdpgi7$sZ8 zcmS-$&mH`@xmNT|MK=`_y(MU17X2)dMKWGkfc$SSJ~4G>W4OMj}^}gZ2XLsCd=1UEM@z^Eq*QLw@V)GKD@h&?CEuT0F3o{I}nE(>#B28_N z==M{F7py(ucfPHZzB+C_Zy{-4=wAm&hQInE*GVge94Td(o>v%dWzuCVS|}(`+!cMe z5_kL{16IlfY|Or_u&MHj66BAlj(j_w!LO-mRT5?~#=aM@0Q}jsQT>A9UntGHG|k-+ z0*PVZ?<}dMMWw>Fj|H;qzv=?5>M9sEZ*a(8By-fVJ(iX9`kbnJaQ~b2ADfW6IdJQj zVM;Q+ht(QKdEy$=NP1WC2Mf^3F%KVbY-5XF^@!Rf?ft8+&%-bGHQ7f0ta2RRw+VL* zNP2aa-MoD5EcQz#P&@JcE%5=Wc%nJiZ~Kx4XF+5i6Ctke;~g<-meXERVI^;!UNHD$ zB!ib^cNW)D)7R?b>Q0(Qs7744h%N%PpK4{-*|GwPs{KMkh60rf6Z^?OaLfpl{N;G_ zE4}3#)00o55%D>Fd>sru^x%RaMsXdA-Seo8yKhycIzT-xB;vB7Dd(+Ov#8*aZ z3bj;StKZTNxyS#sDAUF+?IUq%QWrg*uV1scl4*g63S~9O3tq!G+tXxZ7Gs5u3w&DZT3+r;mcoSQMPsX70=h zX5SE1#U)A*w!cF@G|8+rav3quLWQ4OKX?_zm>#PrF@}fu7&cCXh4GWTf1Y52Gh_Ql zvga&-Yu4q9Z0x79AntGJUdcl(rj)DfA|YTq`w<@01G*gpnCYVyS)&FUz#E?VNW6ke zp8Q)?7{l;BqDEpI=J5kl6l_rj^+J%-K-4CB1|5_aEq!QRaQheJ3+-?XKIXxc#cth$ zwwIBpAxPW#-+}h~2jI?sOkfCt2<+wm;0!}LppVxkd||}!dLD!ia1^%;vYmn+j6}rq zn7LTApHPE$zsj7?c)OkbB}Q+s29HL{z+gd3z`GQlzvQ=mvke8;e1O}RKkxH^s7v5= z0ML!!UwX?G-MG88HGsNVK<#06J=lWo|Jij;*h!+70B2&ZC^*3XG!}R<06sb<)=DUV zFBJs4`CnKA&V{hnFNkW%lO)!+hbA5ZfHZBpyAZ@LFg%6l;Xj7N4XeyK617zjuo@|| z-+}67?YJsH#KNJ{_s6LDqI_gE zdy4>|%9@eGRlp;|uLNv?MGWl9j~!n$S}XFW?pR+HD1I3FzPGNV?;u!KU5m5^?$uO1C%_On zQPHjljx5?Xh9i1s&x=f7{oM=tASHgM2;)}zw>Ny6Cs2+`bvz%2ieprS^ziq3D+uMuqI*1j7%RPO)cgdoXH>$MNt1;KU} zJEwhRfjvf5y9P5uY ztb#MaEDN5Q2>wEi1>|&m9X=k=)E3L@?DnM^q!s^f(U+G+fiT&q-`UwpLmx}_^(*#E z!SBrl^chhD75R|CnylM{=D_)V>63fsOIBFh-@8TdCG*c68Iqa`CxM>L#U!~Lqd$B| z+%y9dE9g!_O|gGs>VskFix(kdNpAoJT&V;~x*j67Wm8F6YJZqjg>9jI5`d&uQ~%;C z%W(fEpQ{I8udcO*{05#gbKXo=j!yZU7C5|*@0}Bp!irFSRbpW16{qX1 zl+YRnZ!vCCD@hxj@Cx2mVLekTy@X;vDPPRA`8LGSY`ItQ=(mP$4V5$p1(``{ZQ!Gp z$7rmVpZ57FG_J?Y`23z^!>??r3ifLoTrnc(2)-@v0%F>wM4R8BI#Xfonv(CSY1}xo zVUuY(2^F3;F&{SCsc!U*rc1+Lf6RPC=O6Wl`qk%)pYL&P8foCSPkgv-!gswD%QrYm zhGepPD);O^LkHy>Na+XG5md7<+xTM9->hm+eAoI#plYgH39??7iPR!m6WaTam-1R< z-7dVQWvNc12~I4nZ9TkvUzBWNgdaa+MiRe&UV^G=|19z5JgiBj1G1ls3RtMNP`1%k zTF=InDo-ro?Dx5)tTfOhFBAt>meEYcxN|o)_E%^zQnEAFegs!^%xIS{b^VCqiY_*o z)_yb6HBa(9GU0%*wCDVON3OA1no!jAuYbTiPJ`EUsjlP?!PKad+FdS{0Xf@e$)Wty z^~|b;J6%wR$3uxH^G&;z-GeWRmq;myV{%<%yk|29)#|I&B8%_pyd)K@ma49~6}84j zWoK5NF|>Eu)~IyePknE-CSa+s4b5?4-~IRHr9;>LXp4i%qDmdeAXC4xG1wQkdO@35 zg@gOw7-^x4KnCC2%7Pd2MCwwggIl9$cg)0ncKLD`&sl| z1&W&qfW|9|fe#YCS=J5-5AW$+b#2iWE0B^o7zo|QqA!bNHyq6;vSWBJ!&Kr!--F?= zo(p%n`}ZLKn}+rC>;>eCHcH^Ou7|$F|SCk|Bz-dZO<}_i~+{H)NJG*guz_H88A; zGR`Ca#1Y#-An?t@LleJXpopp#YxE{=4R`Jt*?1=F_%gA;4h6ql-xo0+f-tj}iON^8 zk{54Gd21&+0aF_0s{5aAEf-a;+`HVOQ7?&N4o>BA-qJ0_obnGT?qm=!6Zk7a zWIL(p`?i<7|KpP{O9HR64)NXa)blVIYAy=}udFmjdiK}nc~5yda-C(xv{aw!mn^6Z z5K_iD>E<<} zr3TqA-)!w)yVosFL0h5ruGtUd{O8R~jMh;m!v9KN6moE6l?sriPFDQlbou1gMrJe* zN#aZM{%KWkO2}ss|2&m3!KCPfJlqLdfDObqRk22Hu&3iz`HYUc#%=KG)4W{z>Cn%~ z&@++2mKUiI)3g^xU%D1`O<$VkB;pyFgWO}w?u`q;k?AH4v?dkZDIJp65Ex!7Vn7(@ zxb!~eZ_1X}iwi{Jz3VMESLZtLHzn$Iyy1v_6#rH{dGu!t{a87YTUN{GIhyf97ypDmgm#iHeE^}UPz|2^`Sy8|2uK88h6a-7h+e*W;;rmm!3g5 z0juu78a^&sXPB>UG`6ZFAH#b>nQIf7S(GiAbskRs3I?#-xnoj^?`wL-XXIaU_nGI> zB6YLPlk#$>=g)=lMbqV7$NLxGjTrZ1ma5cqSsU4Ee?B-3dSI*70E6ScMGskW5%Hhr z2uB)ar@&XASFnZbMOD4YV5%#2Gl^d&wjbXgj$p3{|AHMIb5=$)^*MKH+DGAYMz&z! zG&!7{qdW-d;1xz*)DhHxw{PFQzqU&PJUEZQTdbQ8^y(3IAXn!`@jBD zV80#m5aWcM01=b_b2a(DO0~)_gQBnjU?( z4Y?YQDo zqf3B0MT$G0A$QzlU(UPkR@PDVuJ;%K{rkiaI4kgH!Cf}6n%G%}K{)ZzqhV&}ZXq7^Lv^6QeJ9Z0s(wo59SV zM)_?ca1?Ou5-JYO0Gxzd*uTrzSu(~#DzS}!z886kM`1(GygRC0$D{N?wXzi}{ps18 z4QBF3x;ZorM%dJ2zj1ym|4p~2AmnYaV5*tSGz>WEoncKlD4@psfvcY=U9w1Pqb58; z)DJ=o^As2F2)PD~An-a-nGrN0-P{ItIZUn1(h(k?00Vl$3?nKWyHq2@=wY56woM_| zAR-paeH2nh_YK=)FN5!*TrVDYhj_@dept9T?kxD3pXl4LT1PJZEGH0-!TyDqxkyn; z{ioWW;c%3Rt-fOf1MO0wY)Yv|l=|Iz*3=@r_OPXnFX`F%zGKmn=_}D+{?ry)E+#AQ zvWep}UD?Eq9y91wpnR!z6OHwpcqaToAy&ZLrik&2jk|!0P*3j%sZOS;zQRw8Uv2j2 zGAoC1d|u>IiftL{=~5(`JXRj#x;dz@Gp>rmFr|BrBSl;-Ry|=-)+S7zIJ_#4^bOaZ z`W93|9dyY+p!nQ3Y9?Uz{aAYwY(CChE`CYbV750lw0|G{m=#>dUGa6x`y#Kz_`_G@ zeARnT(=J~|0>cxXGR2W!aAmVwh|i8ITrJs?@#_G5U|{$p&Q>^@$ImtuyL(DZcNT( zC7W3jQ{ya8ewu!Cnpd(I*RysZsJd9a_!^{GeQ4IXdtnMcc#*#@6NH zF2)YvX-wO!+$vOr2Ioi)`pt)3>+Li|8e<9d`=D1|l$N-+ldZ0@J=alnWg&YR) z-QpWJm3Z#eSpfYTnZs&J85t-F=LjD=6@DQze*Lb$f{WWDQvGxPA%uc@mHdc z_HI^OCB((mIC)qey&XG^_4rf#%v)7~yu_NDS;r~3RM|VE=xSo}KQsg*h{<(@u|0(((z93E z{uM;1OtB9+qv97!h0T0fX#gC4x>Z_C5HDL%9HX*HVS#6?rlDjYb!qYf=gypUlsv}6 zvkJjcMocm0Rs(p)f(rPNWS#h2rI6%RpoXHlC;$Lqj-r6yIOWmS zvTdt1Ec0cIv3tfceHyznOQ`Q*Z1ej1THG_mc3kdtFug|cd|4zlA29BE3HpI>o*f4G zbx&`?Anz%WAuGc2%_AFC@{@K={Nq93)xDCT@*=^m{X#8hH+|LNxTqSG=qWFrHMmQ} zz#kdLr>v%)9nIDDn|~+iP(;A2TSW6|n%+Owp9aSwxO)%@NV;9%d;zK!cwAM0vO~|F zH&)SIh;*JALXf!D%>i2jfp>_7|A1F`4)~S_cs>w>H0nHR18!_i+=cSouK*sf3vRpZ zI?ra%UHbXR8epF*;vg2hjaQ(|!64P*jwGG%zZXDv+XesI?>I{+lZ3a)?8hqXoPzI< zUD0y@%B0Yg?Ue#VF=nd=Uo&`xtl*B1&IKQTiC%kkUnF>^&oN*&06NB2g761v-}k^! z(R5|Dx8T#}byOu_ALzf^44jye-iC|N-X6%HXdw4F7|OV##Z@vUmmje}&&XW9V=_Ql zY#9}-qY^Ry)B9g5P6Bc%b>VRyix|sjce-w+PdY zZ-2HQQ0aJD=US?!>txe?29YX}n3P1i9z^9hX>}_7<(D{Jmw`Nb z_!1QIh%XACI!mtld|8n4y`TDAeL0$dz@}Irst8xSzE)aVT3lMX6}J8W>wwXM)Emqg zNE0@@_k7)yl&XlL>1b&+AKqETKmN{Job1Bpk&zx z{Wcgx3PDeO77!7mnQK!%j=It_G}+?jpl)ZvnYq7g{cW`plWnipJ|8tOZ;l9gB7)t= z`o3qJUn)M=Tbf7(7>p@TW8UM-m-b3|wEnEnrVqKV<}+*h^CG*u;v?=tmKQh%H0{R? zuQ|`0^K#2Qmro%eH(yWfWH}JnUiXrHR(*JeKe6GY!ddU{;4B#(zv`uir~m5UGM%v* zCz^QO{Cnf*M;tXZG#AlS*)Y>y*iRn2XDR~sq+2PZ-7nmmwg_lOs6KhI=nqKMx#YpJ zPRQ%;bVL>ujg?fZwQYq%y8*xL=wD^mF$|U;7^~><-;!5OEn2@XY(!7~FvW*Y5*c7} z-)5<+o1U@4owe*j$Xe)lxXyfm4ChjKiHVIatovCO!E8ndGas zHL6AKw$CQ%b(UGCu@t1TV)Zp#4F)s#gI&U@F7bK-y0&uUM|%9!%=oo(S?!`{G9~Y& zv%?L~t?&boZJ&g!2EBYK3TjGT@s2JONC!NcX%jMnHixz+vDyiH&~4YK%-TW=F13YW zA0R9#9i+JgnFnV`&E)RXqOdNTjtfdPu|_~GX^kT`g2}yqf%qviLT8(Vu5YepN=;H>(od;=H zZO~!{dTu}l)wjsYsOp<&fU!)T*guZ6YKhvf2ck?*8NK}@RQ-Jzy=Kw`u$N%BHi2sgMaHAbcJ(fTH~nw=c8FD6>NfyNy$BhX^Rx1i(6E(7({AJ^-A! z`F8C#BtpPe8@^p|H>(ZbiIha~{S;5%Z4N#au|VR=T!euTlZbpy;x=^!XwZo1LR{RW zhS=`@y%4D%2o2xnK`NFt%j^K%sy{zZ4vng$k5?#tor(>8<{ zUf48z5EM-uX@m``MHdTpoImRxHSjvWg6zE~`-aWb<}NE@v_(yGr|=vQYXEA!sdX=(S>I}qZ0#i@|kjqHwyv_(dQJlN^e+Z?~+ zvq*h%UtLIEc8Ft}!{^QLnA5UY(vVI6cj|W&ARK+a0ezz2;Zr zArns&jn$kpwY-N|{rZn=`u@tg8Jp3c&JC8a{P4h}^h3*}2*| zz)b&wZ&3jQ3`lI+<2#!{CBwNUst>4`ipHu1*Y0*v!;5;iK~fvv-l?(-`e<^!z3U%% zW%kL~)BIt2b&W15PJbBIm&y8>bGrUy$6YI%`+bXiqT@g#_-O$%vF*;$_T>%Li~K~o zv5h-l9*dN+^ee?Ihw53vpsOdnqBFYs$TBC_PPEcF*> zqh8QK=m@qjSMXCV)5~c!ez%Mf6rG-Y+o4YLr7%%*sQ=O74+>3n+1eTbf>|bMP+{2J z=PgyM_-xu)eujwtrGPOjez`El10HEAkq_DB(LJKe<{B5B3a<+}4QV_BoL}&2;*Tso z&A@5zHk8<>`xZ11HE*h;Uef^_{TMNI$lY^nB+p>(n|oXonB5>bTL!vya=A^x0ZUBMd?@gBVHBQ{tAj=T1tj zpse;hY)N_30TzBQQD?O@x%H4_nhHntq1SU=qJ&I2PPLzEqz%4$`r3b&7xM^kvoz_i zH|51JE#T<-+jVzu^Eu?f$A?u3MaHqXm^rScbnd14o%ML4jRekq%TT_tKUjqHk5Uq4 z2@-Lbn69!dW4c1v-J9J-npShTL`=qxI(){=kCAmoEBgtAL#!~V{o5Jj?zxlSpZ!(O zo27tS*nK($pZ-V^ds=b!dFVnrRojq#r|siP8#)f+Ja0)3X~0wW%izGxe}8^ozj~%? zHBrkS{HF(fqz9#7`SRtlah&VE@t;sm2}8r@8SEnqi=v(9LcWW}eE>$3K*ENn=0%V7n&+(zz$J*0P<{Go@!pIpoNS)coeG8u{w8rlosi zun6CdK0T{m1}OC+iSJVEDf6K95R+hhaJm*NIP{54_R^oxNOEcqo6RT2_vs`!FDkhG zQBuWwz<%1W9|Cdv_&EuWXpgpgUGN;b_@C-9=%$#my|%5|O{jKNd6Kt;h&x;?lxDER z`#yCGc%*DD`Hw$t!s2$YDd$PMIpmi7#>Zs^!Uu(^`NrukqCM?LVA8h}_yBKA^ytt+ zc0;&=u7(;L_G!Hi70Zw_keJ+nx$ODzyq=pk&9K8-3idInTfiCPH*Od79VGvz4~(2+ zhQVRTqehY|+!{G=BIe84Ayb^_OO;cZPWxFJDWVpEe8 zEI1(vR`Te?bILK_yFFqvzCdu$yVkvjNbs%mrE44O7tf|zQ<-fdK;C(H?F~%8eKjfa z)N@!hMN0}k>0`bOhDnRG?=$jz&7zYf$S@(w^pOp)^nLZo^- zt(E8Q?y@L%fSs<;OM$&{;c{_1u6(i6$R*#AI^B^V+ICi%*`zgjNKiMs(!yUPyo?SY=o$!#@_a76^_%eFP5ZtD z)?dr9;g+foh`owPyLTUd5PW;@{z|S}p5^2^n|+3O-Ln>VKx?tpb3kB(Vhr>8vIl5a z|2mHZXiEm&CDQvL@EnbhTT`z>=KFshTsw8TW0P8A`OfWBC~K_x3?I1T$GwtiFSDB~ zjmMmh-r`^HUSI52TkJ5I# z7ZUjeVm`YbGQx>orB$4)+0zIu5mdwQu)QJb0_h^KhrCpOEzj zy>r&FSA#Kh?qUUFC~%Jdmx^pLzAR(pwK=MCVOjSsfigeXX%lhY(Xsyq(@UJ>-1}v5 zp2JG#=ds!DY})L+)}&cQMFj1ytQ?oRDraQTl(wJfC_8O2vMXxwzwBoPWZzaP+pUUU z@;$x5TRQgzS@ocI;yj;aw>j(;5s&*>bw^39`(B+ZYc8Xsf^Kj_#5gm?ESh$X@L|DD zC^MC0qx;T<{6;&p3KkVD#ZZFMr2wTxdcio&#Ay3IyzE5sr(e4Z5_f85#VOni5s3Aq z{G~jL?lFYoxkjS*evJ@bgx}k!%#~&d(icq6G<EdnVzZ>ft;N%deO0x5`?zQXYj7ESi}h753}cG>PRhx7`InP~SZPI#`&sRT ze$M|Jax+aK=|9(ea&VJ@6PB~WJ&$w{b(<7xA&b%To3+d3HF)2mr1_(-!PuulJH1@* zY19i_pm(|I=DdLQ@Vxm8Lhf+7q^xM$6UM^jC3|S#hpC^1)r3335e&-D*+*Ss692uY z4u6N~KX@gQ&YNj55n3(a_{4&APay3JOIraylF@Ir2sdc;M#-NZLPIqn>5DhVhj+ z2Z;l+8r)y_reqyM9+%H~+|e`P!k(4<@Ne#petvWZvVWpo zzdBgGp|DH#Qcq@~_w8dIso3|nn$Kc~QYa)|reseB#?w#8eS$d}4;D57NTfKIt%?{d z2QpQ0-ZAOazx;bQsPWxeqqS3UPtN`p;{SMFY}zpAjf0{qm7Bd0@8gx<8#cwa!+IcN zzY^hKoG-ijT4(7=mpM~D=G@toc+5^6j8ho;e5Aemz-~?Y+MI_MVwFozAd#cOUuej)CJcha#-PEkFqUuZ z@Sgi8PZxtD?h18sb%Y9qbmW#4al89NQ&dy+T76bZr~KeQUiZ85iCmxa!T_lGUxH_+ zL8qu0KpXnXw7~De)%AY2X8xT{1B?3`g~#8UX+5V4@-JG8yGLtmFN=rp^9AAmVxP-E zLyds9C!q{y%lEu}$)T8fw@=|$VwA*(2VNE1e+g&$HANqO^nl$bxMhORXh-lG zkr(ybTK8v;fZ%$XKwq&XDCiFrNa!iTa(m{=`x(6VcwI-`kWQ-F_3C#gy!bsz$XQd3t-ei82h!UAK|9C)>_JQNnhXp- zjPv7}JyrWS2@%+dnVF!w3h1~A30?O?IM%kKYhV!IJ{JCqH=kTrM)=#?_Fcz2d{vZ{ zeJ9Dv;&bj=50wMAp{6zd2kSLZ=r7YICrD@BT$FV|QW1K${`O7F#E#sT#FYI2|kb({Cgd~ol5;(dVsy8BkeP0!_rUsO{w4->9_KH6p4zvWrA#J#qPi9r5y zGHZ8Lu+^{X^QO50i&K#rxdsqfeg6Sf{^e{5ou(I@ihg z+@jz9mA|6j8Duu8xl~TAT(Fm={mxz}7;fJO^0wWJT~BL1GjQ_|;_UBE5+sD0nBKH@ zk@+$yv9%Z!h{e(QXcJ67+<@35uZGGqnbHhiMFwGSZZA~GF zWj#0OlX9j844zw(QS{p#e$gO!!MWs{%)ZZs1!^Q9`yli2pgt?u?Xcodczy?+J8>M1 zl~S6&o7P5=JIv5CW^VCx8{){NS;9nFqfs*9_N4 z7$)%xX5K;MYHpe_6D0+aT}Cu~YwKU3HV^A=ABhc_wvOj=RDIGZ909N+7R7NnVCVaG z5&j%kAo4sO{l>X`(md!dNy&1pL3rioAMm12#5-i?maAZ#{np>9%8a`Njx4Xl2PrZf zRPA18u^)4nM;3CN#5*R}@_Dq))o{8~$uR7FQ%rT36J7{c`*dSlE%si`nvh?hg#O#o zIZhQvhLkP8IG=rgk_X2aWz3uovxB!{KKHLU@mMW3|1yP_pX<-(CX$gM$;jwulc@j( z-}WKjmmV3`axC*qjn4X4njc?i90DsjEG{OkvD}s^(cWwAc%YzTH7wCXec<{Wr1@xx z68i7}%nL&P@^ooM?hywC_EUnJCfHd|1Nt3nS`KLJf=}r{;4M&dg(bIUn6qs~x;nfnI6hX8DtX!XZ@65Y4pqsI*^#wW4ZNaCtxIb;Jg~MViZg~>sLkokQ%;!l+e_fm7 zeofUYX8e>|F#=WLiT;s;$f#{2*#N%~9(R5sqQ1WMJwYgFwRa*9olelGK(gOcD}^aI z@OK6cCNuU@>;P9u`WD{xLFe7gir+z*tsy50PW<7y4)O$IdE_Ki7-#Ey9e}QW+(;hc zTI4>Y%Nn?ch`pptig=ETODh>%>$rYf_nJvpcG~c|Gerx0bRk2tE~QYNH}*+7@PL2-@8?Wnk z!|_N)`{foRXu+P*_7jV=??m)MJ8S}-0{eR5VAS!@)6*7-wfiL{>X>Ku>YPkEperXb zRU%5}K!04?WjZzWx1Ie&6e(k?xCF^vJ7Yoz26nD^&wcLMMLK{w38kZ35)cfSOY~5P zF}JP_0(&eyFwDM4O4ZK2Ej6O!eU|jsW~uWHr|zYnajYVYeD{adX?pVP`@w&yI=%em zCbP@OjHW*uuKHrDbAh|ttbE;PkH0W5vM`lorCi{O`d<;CbJXac{0@;gb6m#^C7KG^F%R*&xM)G($TuDe}*&qDu!H>*O{!ldz;rWLW(VXwLcgPNdN zoqOOvSDRoXRRnuxp!Imq(VZ=XhdDjJR<>s`iE4~szVBt&DBHq>N*LUY1;$gy^!UJR z5EOng^+HbW==k{fl&+uZwSbd=IaC|Dr_x zn~bmw#Kg2Zi2K+q^8D55+_RFlw8HXYs*6B7;U9|)i&Kn`cb5iFEM?H4+#lkm&Lc;^ zs?Km3dPlSP?O7VLqFDd+Ci5%8RX>dnk%-1|eac~cW_<8VtWYgKKYKIwv$pNU5cHd_ ziZzR3jQ(CI^`5mmj-{6BCqAhLj)QcQ!+3*7cDv7pnx68a&HI>*ZkW#Sxk+Q#+XTC;ncgh)-?Bgd_G#-O_DnHz(z!%c6Cxn%;w2dMKBDXG zx2-#h+R~sOy`!>WU6VAU^BJNnNtrelw|aH=mvKZbxoAF@k^BK#zX3qt-Yid`|6S+9 z8~8ULsk;(aT5ouXdC$Xbrgw<0$|#;UCOX_ya-wW5>tE(|^w6TAAX@l6xN!ih5r`>7 zq^_Us|J268?B^cnB?Hj}EWXEbL(2rWG&)jZRcA^0^ri-Z;Aa8NH6{%X=k@D^+qV+O zsXdU(CP^RVljezgyx92(;Ng;EO^)|D9;O@L3`Jg1rb}M=Le|Mx3WAp3XILoU;t~UQ z+d?g59S6oGP!|o9$P>u(fQ6Dvm$m@-w!r?n|81~cp8pmgDAn}OS^g^tVOLsl@6ke- zzxu)%>Y9kc*{t1QPKmnp{^5D&iRSU%C_o@!yp(yw$9ZX)&*{LO?qNgU$U;-X=UG8L zY!lY$G+n`p{qOhrws=6~>EhqRZ~(-2aOEAX-@hHA;Ja&9F!1``kLX79?jIxiAWu*y zDs&HZQ?si*{u(%IwU6F7#@H)SRgkyVd>e)diSVdfu=T_JGXWri=njCIa=|D3p_%kn z?fvJP1DVf?I>}Ap4j__O?mCjnzqs3FE}Es+WI|6PBAXf=K$h(}%<;tFC`*a?&*w+2c*5XWqYBzx|cg(M9WJg77fW2ko4s9zV z8D7G#X9l#n-fhIYf)UCPCsN$*Z0;NsY7c;%QG`w)F({@VBzK>p7J$t1TsOe8zld!% zZ?;+csFgmUyJ~5ef0?=7d9T^IKM$`mf+o3MbOopIh@UJLNFmz%96v~j!otHHjc}XR zcI*mTMr?VbDt{%t;2_Qt3amPEW{1ge@|BzKBd0NDu8YysEob`E?xzWfQI#5<-Mf-^ z_1`13oY{QX&s6Wp68kLEVotWN~s;`tP7ul6IK zP@s1$b*t*LhHdmZg|73*ILJiA$q{;m#msHK z?KV^ql25vEVjCx$f*mYfBo;C?72b()s^!0vBgw3v`|^QtC@IJDbVTRRw1yehr+4K~ z?cG17=G*^%H;{^aFX)rX)+WqX$A9|EoJGo4*E$L;SU-X$G;?-YF095mOElemkrW>J zLw<$IoJO`gTu}cb^T9?mP`WcmW@q4icYvK^$iv$x5>TrLxpx74vR%o>URA6+PHWA9 z&)_2Nv$B1-TCZkw-@pJA$Xhruri>*g#uM;GQEObFP3PHTa!Pg^rJ*$aYK8_4<5!xt zg7JhZP#F&mF2RB~>U#|Ca$8|kPJt>K^dun$#AVXFd@R8!X@uRQm{e_Lq&*b_u5K1Cy^^MQv zhl{y?+e_bEWIQ@DI5sZga0`B;bBACXo07sxGF-1&@BM;P9`{@zbjP9(~OUYKH^nxm# zKaEh#Moj)U{Ojv{CI64B?!PxxBL+A<2@LaW+0BM_am{Q&)Gs-ASL7%rc4oCM%ax-D z-%zas2&O_Us+X=~iPBem`_D@}tv8RAT>n@O`aZhE%*!3$<@4M4n+clo67x*mG0uf{ zql?v{6@WWT=}KL2w+>3c6iJ|mOJbaVJP==aA*|hIxW#UH;$-y?ypRX@P^=x~wC)PK zUuwxfW%Scwf`>l%Oym^U={d6pL9T%%(Z|%6U};Rq)e=RO*D1fb*gFPth}0+&qmG;V zX@k#-^|gaX#$?ca;jRFoV|akfgSK%AE{{0uD+IwhpzjTBJ+4J}maek4O16^|(YM9w6Z16Za`7 zBzN9;`|~py;Q6I+_v~k{ed^)g{L#m)$b9ww)90^U`OSN8%^?yueDD+9eNv?D7#R4g zYkwNgqk!;H+-<1w(4?fbU7Jqx*C66q4aq{S~c2#%2FzfoK9P|&+J9(Q_vRhj@(>?<&6#bJ?I$~T4dknQ@5!gX z|D-e8kVkDbXs{G!U4go2)aW3(S@r&(e0%k#O^n=`nrBWw^wZ~EV*m9G zhadaRyKimLKt61~=jEtboIZj-MTqDMob?+@#~koyH}lVUU$&p*Ux

@xlOL&6Z6cy83%Bu3U}`qkDV5b?N)} z9=V54%dvajH0ue@^>EwW-q{-X_^*C^-<)Ujxny*2|FzaF*kl%w~Z^2q$z zrLBp7maSj=p&xzovH5d=Ibr(L>)&$ut9_E3JoTX7uC7hnxBkG! z+}`!!AAdU}|4P%)tobBqOU{J>_Y$sqR{toG2^CTi!9RI&Nj6`@*qhQ+V$bzq4~Wi@ z1K}_$J_Jd_NX;-a7aEp>EMs7&`g{mp_gW?65W1NaO?7E>b212!`gUC@fY51pz zPqevKbGFrj)(ssLF$k3Hz~A(cm~j0pXJ!`#m?+B2Hx5xy8eOIBJ20m~$@j`fT-5uI zsP=(u3IQz>gjbKH+R7c7SuN1jW+FzJhE!DjQksy(Y9|VTk{{SJ99%mfI3k0F%aI`e zGm;pzu!wtP{S434!+20BQl1c}{`Wa0CWSkf)1^`ol9dxvk&zR-SB!PegMX?9FhZ8ORf`S@{fV<= z5jrp}dwlf53Q30KOOpb?7DHy^>oXAWPTU6AvSwl|a7ca!B~01)0#g`_rgx0Yj~vYS zIjPZiDw3{h$@2ciEMlz&Fn~K^M?g_C4n-}C*+kx(Pu4(48XoH7hrj~yC5Dg^nv8iF z@-F0_q8Sfomuj5{_8Oo{x>%$)PKL`+5{p*QQ&<(Qwv4H~-6zlu8;YoJQ|>aP0=J`1>{b%E%B|kaijZ3jXvnWA$yEm{^Zjuo_c23xUm2*uw$o)-X^ zJ&!$NkLgoKOxbtS(&a0joVDVqXST230RQVhYO#+9>S8T!ht}TGb6pWX!I6f@%K0lk zhwQocSKjol-~a2k?%et1JFdU;`I#uaB0OQg{ZH6$s{O#f+Hk1`5_a#)#= z>z#K8PU*lgd&d8A(D=RXcy^|(1dG?K`PSXH_6_ODYtV+RTcHIUx{t-EWEO>qnNHkq zYVl=nyW!p!pEpM8_MNT2yYW{??X|BFOx|_uYd-wa_j5yspsrqN5&g>@H(dSjeVK8Y z)!3%d&%IJTtxLPRb^%|y{f2v9c*eR9J9h2-yPJP~)ZY8~1swO955II}R?`E(7jM1x z$i4O**Ed}6%*2DnPqfiiZ{GO1n|^5llQ*uxtUtYz`}KFzIo4Y z`@6gI7uv9GYmkofAUWF}U4qE;GWt1g=e_*nEx&$h@d9B5B>tB>Z#Z-Mp(A>RIVwGL z*rpxZn0eaxiRENlbH%TJ%+l=!2K>(NZksrKk7BZ^=v$4YRRpYN6nPKp>FR!8zy!DN z+Vw|Qe|Od94Y^p}PP?vu`q6U^IqImr_cKw?f#dglddW+PX;@G9ey$NEd0ndSAPHPAF`kM$f@VN<=_4l!Rh|-^Z8qT{qX!b`9!~V(}sV({n~H5 z`MvQ6{{E(4%~`&bF(J_MjT^sw#|{5_vA^|`MvX@6dA2?Nf4J#a_sxD*9iwpNhV}2e z>O0qd@GlJ7IQNjFzWck|R&LlZtf%K)weR)Q?`+(<&4Lb|UAE{Wzqs=I@9^*Sr>CCt z;J>|y!S6qQzHrm8XD)t8>OP`>z4Q9ogC5n}ziG`1MmTZ5sV7YKcjTXcd+oimpV1~B zY}>Kp6Fd~3()$8 zZ`!_1l+%O0FW@~X4#zCxaa$Zb)(}Ut|Io~Jt#-8nus)huuBygTqcPF_USz@6bR&`yeAw`@IP^~E;WY=p-toBV21Ahg*?{BPtX!M+vhonL# zS+kOQGoajiTO>)T00C2A58sLuFaoym(fwql8kF5ie0YO>jMe}5&6;A3^ z6wYsIL!w?KY%LcVxVyW%ufJcr?r0PIdSdb%h9s`lDPgKQ8pM$Go(#Y_ zcbZxQM3n#0X1?BFbF{^9d~};#kEbr8+3fxD@N#vk)%@c!fzv%tJn_iPWKa;lh)r1} zq2VhLO*B8+us;~pAtMir5WQCbLiCkeB&lyMsx6Xd&$+zD-amc4?T2mG;SddGlt^7o z$x{N6J*5Dc)PGftpuu7R&6h{=*u=8f$G43sfo&dW@H{CpoKy&#g2}S(Rpj-N<<+^r zt2vHw;dpL*kKFDy@9%sAFobyp+*bQ>#5ReoPmwP+8MJK>cT(HzE%=ezUEaS0aKEACSN*b+i-?=+j+;L!;=c(l1I3xVgLUi zw`I%9Ro|F$%>L(}b^4b+d&K+S0RUN8RMQXvz>bZZp8WT#F2451-=22DA2|4`XJ1(L z>8T(7toDZ+URrej=fBdS!JWSw1mfAgW9E+vdCtbgOCS68 z4}Kp%gJ|)LM;6a`r0BT5FlWsRb80{N(%tvJboc%FpZoTn*uT%jb@N`T{p4OJ9}fVl z%d~7JTKvGHiywFt{tx@4+``P}^U%@-w*}r7GWjHl+5q#}m6*&HTH_({zx2yDZr}Fq zAAfW1$`x$sx(SZhYrj}$UN`emQeW3>7q)fhj@zG~X_+`DOrAQrZ}{>J>k)asJ;ufH zfA;A8l$oSnS<^2cd!SYTXSAN$C=fYMg8=8RDa@GPcI?TMMvlI2<|B_Rn6qK~HUj>) z8F$Fp#tuWag|@jZI6oKQh`lBiU-s!oR0N*a7}~aL$L-HO{pO?n%Z?k?zii_ML;!Bp zG1&+1y87XJ(M`q?6hF43a%S3_-tvgO_6--X^MZ*X@cCtni@jiA*Ni#OWhyu^B&4Sn&5u7Ywzr>jtQw%XVcXVQpLtyM zu>Y{NA^3d}{KjG4XrdGVAe;TedhQ1DFYBE?V&X^2Jlf zPO$IQ*+|<51`z9!t!uYz{@t8s*_i1GTLZ0!=g&DSJgiYYJ@Z!sFdtsNBR4(!L|}Owpq(92hy?t|6ERkb*8qDiR(o)$gQVfy?2Sl7z?Z74$PL-K-jNZ8gTROCHB+aY< zOmdJJrR@Hw2l>R<@}PH?Olr&?F5U$sb4JX+22-*aE~B_Gv@Clj^d$+x=@n(;N}zms zui4PUWTQ>4Xb&)Zc%Vx&2mmUJBG#By?N`dOozUw~G|h!&>&j7+-NPeSb-M5-3ln1J zpC=4$NMygo@~l0zoP$`tCmFt`^&DE+<6e|rih&(d5F+@d<2z~= zls0)#P2@(DQvTu~89qDYpn*{gs7dNyjJqiyF=Y!K?z=;Z>EH1Y>Mn(m4bOp@qkZJRB-bs!{vr$S(E3 zG=n0tvdaFt%&t^bsRDow{Zc@!7PMLeOf=9U;uf_y1gwyzvxQRF;&F^nF8){%Nrn*0 zlq|1OX$nvuUtzC|H5i$|^Mp*E1ZGg{APh)Ku)Q)N{TNNF=`t0zJQy-l+@xp8Y?6XS zSQ-hX)p%QOR40as!El$|Fj^oMFi*91lmL@wrw2|X8~@O}Z^a-%beB@| zBx_oT#+*o+bSVMl2h@^eLOB&B%u5Sor=^5kfRd09$@0xC+ME-6;2bhT%I0RG^37h= zx}=~`qSS?*we?_MTvORF#P{g>EX#wm7z9ueZ%xZkQsp}O{x1K&gxVJ-wMrLYl-&LX zf(tTh*&^eUwlaW3VK_i4ko3QK8x=!!TViQ&}_+8NXDNcJs~vyXG`PR*WL2s z4ZpqMN8i}<*dsI4^3a#RQ+0GqeZx6bv-!fcH!pkiiPwGepN{>r4_x=oKY`t!-}~9) zps8nm`R|uL`s8)*_~X{jod&@i{+`Q#`Ppl(`@i8PP)lA>Z@Zf^uo9<4sg$#*77QcJ zZ#)D^C;peLOLI^D2XFt-Ki={mH$L;Y9a*9dg_yqZhPN6K+N}4-_w@&S702}rU$$`_ zFzhpGk2wDG%NBcfo^Gf!m%fx&%d+TiGo7)c9>4>0XK&rPBRV^rIC<)clczH9g4L^L zE_vyp`7b^&fA)s0Tg_$7^BC&2QexlbcyfS;i0KO#UKV8C^4EtD8EEob{4!p9cQ zx$2>N-hSL`YG3r{kIa}c_j&ZPPgTV2@Dl?AJTU*o+D$%T^3)ULCa;{i_@#&Dzxcqs z+3UA$Lm)+?5V8cD2c@>yalBnil>68wMpTyHhIK76%Q5B_LgLk%HpQPA+SyIah#d7Y z0Px(3#bVl!q{cwzrnOPoFMvz?p;cC?s{RCw`zo5kF%C8dS>ZD5`L*s zIdR_-1G<0i3w9nwXVT*RbDuY~B_U24Ir@baOG7Kp2|GJ>?h4x~ZrZUU{=n+Zn|8HY z8tBIiylLn5cHAqUUu#mLM_!t>P6?TL z#@rVSZAplGj~M;JilyAfVOqLjExF@`EPAWAY>YoMth*;egl+H0W&W>}#r`xQxW*vj zgyEx>u3OXo_AgqqxxP%GNk#mvp;TgeJpecOWlu%}p0D+ihqNDQ2Z9fx$wNFpS`K zsAM<@6L4zAQ0{|68QKMYNM1E0%=R4QNzW1nZo!Qlo4DK*}M|qFTR^Y}BUe+qPA4%5#ygpD1Q?{AUnX|d-G!Ijb)=H{^lhTP(!v6jd zU0q$kp;OJWe{sCgtyr)Z1(Do0a`~CFMIeR{p@?i4nZo!j^e{1Nqqti{NK7-}CYR&! zx67oK@nst3&S6V0DV}MvOHKl41&czHR;dL|C|ZsBM>?ahLZ~{Lz}RW{VvC8W#Vs0W zwU{yu#w2OruNwOS(Ue&(k3^#@xT=$i)1)ykg3g}<(~q2vq%<;F-ym|hb$mxk41DAO zmsz5>rVksRN20Pd?W`DIpl8nZJ>2r(E|nrE%z!ZwlFw@3rUogQj3NOP%0cWgRIj# z*Jss!L8SG06=B=B8=8qtoR6;{lA6CjNkwD=jOn++kCLv9{+Gu8czMi3|8dy|dMEDr zwmW|V0LOmh{q~;@yZq9_F2582p8Mr>i|&7DH=gqQKl5+DXX(R_k3Z&!H{bA+IXB<2 zZOz&dQ})~MtW&%Cd!PH|b*oBl%m1;T;4Pjl-Yt>=TB$YqjIdoVd?%+YmEwXB@*u4HDzqsSi`X0CgHam&sfwUIN?D}G-9SiNzh9GG%2Lyp#@`eehf zRoM=QB2CUc7o0Srz;TvuSg!(cafefzh@?K&>cYodGl5>cX(Ng0Xm-?)@^;n60$JF1 z)Yuo6En&b~jNaz$+m*75EXQstm4G`)UvDCY1iY&~+e=b0YFw{X8#h{e-ihDH%QqGZ z&GG#snR(yxd#&2Ei77DeSfVOclz(@>J;uJUVi~vLZEH4fqLv(NEH!yc2_)q+4KYj} zJ?_8!j`a45*uO?2-LxJHjw}=N=Iz^SO9Yd|(GDWLL&e9XX+~sS%dB@HRmmd@{X%SA z^=K)lR&vfj(uQJAItxoSyZN6aO(se%Q9+GzAeHrH2W}PY*by>}uWmC+jyFq>+^|Yo zxsib;eR=%>xbR=2~ zIh+{T>d{Qbc3@0u3bfWI#qoVn(fc7bCJC*hoQ=0Kw&7%N+q?Dp(NS681SuBU+HDTw zLvUeGJr@c+I}y~zH`0osJjqwF(9H(5w=o90#)7g+*h2fH6d1%nChA=vqB6HIVJ0tTWHC^(0xJwHR#xp)69i8r-wvJag$$esNPEiMs2cqv zM|LzC_ObV;EqB280*|+GT$zhXqk`WKEUYhZK{CMPii&EI$8* zVXR9@t|!@2go^Rq`6PwA3=XRUa!Nn|(9-Rn?fue>aU%}u_=#N>mhUqsFC=CDri6%1 ztgu6ueZm&Es6|8rLV#(JCH!(Yl$v-ewAmQk#RHXi6Ks3Qu?kbKxDizdv4%j6U_)<{ zAvP#C+IYZ>!CjYKofKldgAA{R|Mb(^vGe+O|LKu`^v(k=Jm;tnUM}SJg^M5j z(l?&_zk}G4*KG6T*O$pRN{+jsBbsR1QU^y(A{+6nH7mY(&&`ly=mNYtW%Rgzd)o(0 zE77c~w;g-(U;gGN`JI&3?tkSoV#v^Z5ZkzYTR}tU?t*omv0>+dMpdDFCcFajZlHx^GWPw~FJSnPoS2?$2JJUy?AYES&%1v}&nZ(6zGc=E ztcIO~>Z<%c17@maE_#(63J)6UjF zh|4oCy+IgCfLxz!o!#JVNwj^neX@CkM(&O#jp#)=ACB(rD-ewx+l>#7?2TBpl5~S0 zMCj}8j(>Oit{v!JL12EN+Xr^WAM5Mt&e?uAh_enkuRSrAuQ_}7Nzc3O95Hr}f%w_B zYiB;>VlZNDM?FUL3K&Rhl7eOtUGbkej7L$?_m=&XX zm|7UR#2L7Q0Lj{JJw{`cqwu1|^o!*{ED5v#Nj)gU>XD5c;>IdgKC0)X)VxLX0JH*| zMjjRdVoOQtS`HJ|#^XFvq0<|k4j=neIJR1!M zrP$I(2Cl2o`b_0cd3Q-+e0?d=ziIHzbajP|ueF=iYU&weDW$A}r(lv;)_uCY%m0|c zCg^ys4nfGfJ7L`sY(@a zLol3#)FP%95mAd;kZ~9mLmXK@vj)wmY>w-OFsg)Jh6f{}=mrZY-hF1a3qJp4$1`xm zc$s8kZLQ7GyoiC_T8S~CneaG2&Iwsq#0iBp4cXk#-3PTWAXYUgTX-{=Ka^!zgQ=FZ zATef)Bre5}oghdP@ZUVGA;z4^@@i*_{M}RWJ+nIB`UJ_?mgt-=buiDF*%o?c4=g0t z8Mvo(9#gL}gw!C8`Z@tDlc>{t|5AaD+#llzA{Q+lyA>h+$NnCuMY5ewu+8wt4kBP{ z5nJhKTVSXUnYo~|w*ZucLxB~Ep6M*^l-Vf7p zboKW&hjd2++TEW&YX=D8o9?&`%FH3;-sVZp1en}x6CNR7v6B$CExoJgqC8hTJmlL^Ssq7rWIfI zhS*s~Bhnht6ZpC1i$RdBqA=dPV;fNJXttc}AP2}6GU~w&qrY110=|6b4G4ploNnFr z?21Jev&on`Q{(S0cyo+=&Yw@5b>R~W=Plc`J{LUMoSoW5J+jP;KjE6qTfTPpO<%j~ zrhR_zP5%5FF1zI4|3w;~RJUmMgA1tVV!M1vnosrTqq6t_8gSbMBLHK4vW*-$YuUn_ zS!;M&Ur$dBCLpa7jFcnK#f~J%i`EuNXCsI8E?BclWy1+vx(x3ADEtPPx4Q5c8ZoqY z!Rl3Js;I|LjCQa9FmLq=>BBe6YlZ^(xnV*w-%!H@1*cR$kF=}r1rHUHn<_8vCHlG zA**~eCmnOh{?HC+D5?!V6cQmy>`-pyg0X%WLZT8ThLABWqO5#0g_`C-$r;icMC4ja z#a2%+sLI9{kIC@^%WCq|Ha>zk z-$AplBZ4VWm0Ox-`|8*=8d2LWSPg3*kaz*jD!IoW#24?X;F20LQ+C8((Uc*9ojP#H2qxo zeWu7EghcuNpsx=E&Wo@h7#DIkAN6CXM0JEvwGWf3s_fFUO*WM8oz^n+{*8v_XfNlgSGK}f5hlJ z1Io$?!QwT?U-!X@;|H^1_VYAYd=$#TilgxW+5SS{0qd4-=hq7{KuoaOFuuzObTe}y zRfLVujI+vv7zVK+IEHMihS}OjNx7r%ZlxS_L0lwP1y!ao-6DXZ9Uk`at?S~n&bWuL zXT$lF^T(c}vH~-T3(b+?-?i%x=jf0GSq*0n3!9T*lej!0F`?#o4bqE6aiyR@gk-pD_A?P}>1uY2>+N5%ek}mZ zUA5e5n_*quH$MI7P9pTwGv`8PEei>e764)C`l0}S-Soq6ne{{g8ZexH=+T37E4ZB< zfl338wKgtTvvR?jRlj^}1~N|HbFa%!IOVkm9ArPS|CsT;-8~z(Z)F-pIAc=(TxiT& zwR~FfWgpwsYDF1o-3=!sJk(p?rdsEO%q)g?Tzot4=*kLTyMUW!KDLWm%y~6znXkKh zuv@-cu5g+E?CRxQLlcg7sSeoP#cg?B=t5^Q=(HQ{g zpz#wgKe2w3`#QQeY~7M;#~OPjC)F~M1fZ1lKMSQRVLNSwEn~rK_p#mdgOV+kc zwx^%SH8$!&>HI^EZm*)t@xe@W)UXj|FE3)+_8&d&(FJorwAIo$n=oS3Ctr7wO^=(O ze&X7v9tMWFE0@K;J9*TY#}>>1%av6yb;{_%PkPR(Wga|O-`1S*?cL@!umysD-yqnQ zTUmr_8$HgVX^htZ2>XsMrqiNzs~KTV`MvfVJ?_y3bF43GN8c#T1ArG-F5{>JFD8k} z{RaKsVCtm$iZtu7T?4JSniT=701U8w+ja@Q;lc22w~rhAJ=P;#yG1`>I5R%WP>SNEE;p57h{9 z6b>qLNF$|q+H!9a%RkMuF3(=bV0a#1|5#ev9Q-6au!&t+>aE>e)!EUPQlLB-`puL= zUJ@4QuIJNoc+Zp4N5L##q~Q1dJ$jUN*75r*g=SbRLVU!OWvBwys7?*3v!Th#CW!$RLKY? z>Bs_Sot2s7#_S1m5%2;LHRxGW-LbM`RRdmz5v~LuOO_i4RjUGMpy~G=gwQmpm%m zl!`bYGh&N;0n>V$c+qyGq~lZ6dqmY!2rOd&VLL$)J|&Mmm9BtT(1)XH&Vsu1%~ER> zc1R*p9@IdK-Pj4N6H~+j#rSJ5LZul)>Y9$V9aq74Zlh4!P7_&Q0Xk(5n2)j8;50pE z2m85fbE)>g!u>o_+Dus7M_Ye)vas>6vX%1_3ua4U4DVL(A%2Pr%2|=X3~o1x91ZcT zRw%}|X%XY6f@%poKivKV;}w;YHviHF?EdWjyn;V0v^iu{`5Oj=AOR6q08A3yNfOK; zub@a3#SXDvvS=-xH@8F#NmD0RtY5!+^QPE{9zCpo@w&ADIA_)Js2n+T!oH6#n46br zBFbClh@N5R9CWx*Tz>WVLpyjEF+Z|k?ygp=soISbCr^FN0n_h(;aQ2F0GzzXxXWL4 zO09lDPL>>3^y8(Y&tJIcZ9_Y|@^^cD;k+yFzawu!ka*^jmp*#U55D)d4_F*808AV{ z@|h*upiM{6NR`FW)O?1PT5d-}`AAKWppbC906pE8B` z!U~(K!8^FYf_75tcYET#k1Ti*gPA1(jOZSE_Q6NkD)-CB9@sgsE9+Q}+I#X_k3A{y zYqqc7x@E#}F*G@5(v-Ixd(uxI{+%8y>b-h38UqIU!W-V+)7h0D=;I6Le*gYENzXpB zmM#3qFRuLew|_9+k>=&o=Fc0MA}kpe^Sfa^%$y{f;G}R*q+v9eeVz`|fW)^Zonp z%CFg+GTQ0qcfB`D!|Fm&aP9YI&`&R+txLkH#uLXj!bzI4lT;GP67+*P&_D6 zgAwbaA2@Epl+j~fT)xbsCm9%^22m|svkD02*1p%FNB#Wa`@99sj0io=u4t;VVcXU< zn>WU;T>T*;NSBfZ`w=~2&0Sf4NgsV_j%ADHAMGE~bN0bUnjGnuk3G=U*|B}cb{v%5 zVr3V2rfQx3#vxBxKh3Jj9chgYqL5aLp}$Wqwx@XFXOB|J9d&liT`Mz8#ECWmFemoK z1i^ujDCRex;{0EeWt%7uJHz49qPKH$B4AAHR1U={vhAlOP`k@T<_aWWN*L1HwI8jF zFQvT@s8Mcy*V*H7Ab6aTloA}$F)U-xY!i@K!$7tAj?tuC1U`r0P7uO00~hHVcDSsM zpDZj@c1@dg1i`=y(HP}f8MpevuEVrOgA^vERTZ^%hmWn#j zkXoNR{Z;t#N#RzJI7qZ-c4tO+VUhU>2;5?*P|8#SN~VL6To6`Y8H})fNk1QWV%-AhRVY6ib-H z-B}@)>`Q8>jXQAa3#`brR^7?b;SBp_K?9p)!%!sm4^JHPhw9^VV1b*+3!O;AgehsD zH)cbglnKL}FbTAaoUM47xJ4~$af@3-)Mol(M1}yz6~yqeRtLyhF_-2*H13V{t$S^! zg5Oa)W#wq9;y+_}>{xUku{|S_0a5|M2zSZ8mo;z5b&K{+D*qxX|AHwt%34?-KN%?K zWO^pZlSvWCqGyC5|G^UDFZv|uEg9=4lubw9BBmrqsbWV7%dR>~ zF(nA%4xm+=3!l!Rot^p2cJnijZwr;FbH(bLX3Ve)~;>@)d&C!Y4hcl~kyke)X22*cp! z9O59>Y&1@oJT>PKU3}E5nvJF~zgw~*MyH9Lt%2Fgmr)yhlZ&OjM~)Uf4bx%jv=gxm~{F-07 z?Yh6Y_GhuE{_rX1Od2&>j%#fm6>RAWfE|s-iIb-~MN(o&T!IQ;*tv@_SA? z{fC!-xP7u^`Z{q-TBmHrD8MZI;rXjp&RVk2{>HR%6W@B=$^NSW`n!8B2o$X?JGMW% zU~b+D7pz|SM95&CHg2NAVfkoH6~f<~`=-u@`#t~W$%h1ZB5n_j0$_p`5OE9GNiyMf zQ7dqC2VuC8<;S#HJALx(H}-UPGPHo?u3mh^s}7s!vFJDkt0|?T|H2j4iuU1{9LLgPr2OraW$n}#N7GYE*WLU!s4D#e$K@~H$#RxjS#TH1^0U>GHFe1aSVZ9?pj>LpMK6?>P zz?6X>mO~!sx5yPc5wXMOOaQGs7fEf#(HLsPWPote`6B`iWE3qiXT=R$%^De!Bt*|F zK+i}6x>cvXeD?m5EHmaGFEL*NLpg~<*Ef_oDUY36$}Z9nUgEsX1^gP2DL+@??O0Ti zDPv?tuxBq4+4_Js8Fh-wvsI&E%u&4>XD(|RNS3@lTTYl7jjE&3?CRO1 zLx&9=Hl$}rS65d@N3&5?sjBS$gwQ2)W~!}th|#M$uwPZKQ9J)|39WEx!fig<>>?CT zANO`sDIXbx0a~hbQt6W##eA5Qz7|w52ti{RShb2w)CuD$o2NtRm6n?hNm)M5_XxfU z3Owo?jBfAEPTNUf9wqr@w}>%F39ztN2d3$R{SI0iuqo;gAZmhn|%{K#Ld= z9-RBaDJo+b+uQ%o7rp%-ZvOSMjq4DgO8EN2jy-YmRQrpwmMq%Y+KIWz%e-py#_|20 zl=S^~eDIM4b9N34%$WPaEzdr&Z2h|0m;KELKK{cQcgKvwp5Oj?akoGIgBf?tTeYH5r4!mN;NO3D zM?Og;z$?zUAbzzOb6@z);}0>z)eqfwsS;i~8;!re;L^AM`&V~z3mAw7K`xMinID)p z`?cXFf9Xwc|NPCrUb0~w05+<0!68SNH+kWXfq?`fZO0FGjPlirZul) zi>5a7RvgT%1i|3G;&aG^eSYw+Ke_IyM`tctFs{G<`2D8Vgnr-q-R--mmFXLwz5W-! z_@j^6_xkAR=N~X;&l&SxShQyKwDA*8pLW=hdl%p9n|J?~h|GtfuGXPISY_ZrG*m={ zkZAda_20hxmXDrsfxRQY{=mn7IOFa)E0;GaJaO{0*BnrLM{dt&bqLA00n0PrP9nxZ z#6pr%jQJv0Y*-)TrGNFOpPVuKxt#+8cRlyib&o!nU2zo|Fcah~lw*qQXctTgZ3dD> zsoTjBnbrI@-aZ=JMR=yuGro?1i%y7sJxv?l&&@Qq$sE8E@y1l9rbc~~*KBh@ULj;;W1OkDf%1jKtVh=AuXN#~v zi5B|9O8#Pogeg1WT96P)L|EoNL5nd{YrF;M8cJZ-hGB@D5&te$Pl?2qJY@!+30%)* zKIQ1CS3+0``7-Q)jOjlkSwvy$JQI_JGiQAg_~i=+#M9Abd`y`QSaC;HsY7xG@`wjo zOwi&MF}GSo#)iTapN|U5Ch@O*p1vBGrv}O0_ zm98ZU`qC`(Wuxj5pthsg-K9eiLtzKc?Ln)o2Gwe!o|kD(fV`yiSQ#^Hm@LP{S3P|1 zxrZFJ{}{2B7}wYTmuFo#=+)o!%o7jJo1L3T`9QvK-Rcki^m|{p=xzO_JE?IZ{^D0Z z{Fqj)5$X48&s6NitAHmJn>lZWrs zjwv(3GfNj8I$iOkMe)zy$m!EK|jdsCd$6RpOG3~$QsYUa@eA{*T!V)4AFIc_e%KPtl-^pj# z&zy7ck>?zIC3E0Pe1n1{MjnhPqihn(P&hNEzF(nJjK~UjygI%Wfed^T4eWqAyV35K=6 zg~m+e;v^4yHEu{kY0hU8(d;;eL0X|9FeWwfTp2(hA{2-MzzrTmj{?96#heO5IBBj! zP=zg+8K+SPpw~}1HHB=s0?pvaf5r)cQemTD$v{apF~%4qv;7E}M8WbAkOoB#2ga$W-^^hah>pOOkNqTKd^VWGC!Y4xqujx z2%)QB#=wz1r0{t;KKk|BiGvvu%6DaU{eR@(5Ej|prgBq@s+=>WFH{!h2GzT+Z=jA3 z4DM}fdMZONWbqUGEVWh7n>;y(s2IOxCjAVW79M=gK_cG@VL2JsHb7mZzF8PGWI-6f z4)mFHC_|~N>$6Iib}NZ?e_r8WOFllKk&m_gkV%aq0toU{ry)9HCE=jP2%vqjB>6s^ z^+~xVe}3uW_@f7m+0%Le1Jt_wN8gAtjX^&*JpI^T|N1Agh7bcYw*VO*od3dGzV|Or zES%q>qQG0VY2ycf^1VBspVij8Z>P5Ci{1iY-mqosyMOr2g=<$u*~s1__c&<$UJ(Ug z;LqOhi`$=j%1$bX@XJRZ*syKu%fMU!#4T=J{-baHAz${B*Z!1=)t-@GhN(naGfSUJ zS^~MhlpXJYSxE293v) zCL@rtMoMsv8L5Ma_nQ!%DJy#x=^IQ@-wDY(Z}8RIZ~FW#zuC5H=kIwavsWzr^I!aMmqZxk^h#vrZ{Bt5Kiu@IEjxGo zp1-?f{o40m^_>}Wo;L>$hC$#kDgN@@Y5`zfzotjU1enAhss8uHjiH9O-2K9=Z`^gu z%U#~13+BA@%C8RyJ0;g0>6>@m@}GaNb!*;#)wl1T`@9f$AqVr;Uq1A+Z5E^qwppX} z%3DO2|KLAgk#3y%=qG=3HE?FF45DC4DUBFDd}z;*%CZWG2V0X#iY|hy?!R;W)-8iv zn#l&hW4YA@hCJX=$VehL5xhgjH|2*c_EN3`<@H|+KSm;DOp92?$h;yC(V>`g6+{d! zE)zOBDck_etyYLP;h@rB)9>jwGB-|;EDEwc2tr(Nt~d}9Fy{6)n}M>NElo79$WJEd zXk0r8!_0LD(n?3y8DW+rfvX!Z@aWOThlFORVR5`{0f0#zCFDORTbMLYGv{R4AnYy- z_OH6L>i&y9J{FFQkZ+1yZIW0RfWrI>Lr>M@1u2u#B%_tkO2M+_3uw-xYA@@QCX0Ag zem%On4b{;`OCPpYGYU7rN67O|CcgSucJ@VK=DOULiaTa>2 z1Sy1bWbqGM9+Wr&g&&ZbgoH3htPZ*PUjYyam|HCo7749Z|2h#wMG9t}lyda*=p00w z((27GtDWDNf%}H_4Ie%-o0cY3x)iJUL?H$XjR z^QC_$S@c#x*)s%-nCuZb7ZHNJJerpaMXz7!>?m)kF!~j)nY@+7f8!z(-JfN?@?=gh z>~7|se(nn?lg%dI8$;BPW|aekt%o7T)%p7GlMPMqBs)%c5(@Xp#>py4%`FpDA0yKE z3d0Y5{sTgi9hGwUXx7Fmgsy9)iH(BdTn?5o6xZ#s*ofUuj} zvitMD0B%tVsCS46&@|ehLQ-Ik+6&~Yr`GLfPvY%(O1FahGTD+;vb)%#By*4edj z+tz%1*tBiSC$GKww&$Muv1)5q<#cFUHhmn>Mier-lqdpbL9sP)^nP==h4+AA`1GGDoA!)5>e91b*#!*8(mHEMWv#)u4hE?j!@W_I>KY#T8Hy?GP;U<6o z`b*#R?Ju=xpuJ|4fV;%2H*b9FzklU|Ly!JFH~E^!W<0)d9*CGnQ17{`m%aDL-+a%B zr=2`yT5*%j44^hGnT_NKg8@Kd8aJqr2%JEiw}!>*SHI)RuXZ(?`;Q$zZQNe#wrqNO z@k`4#uFI9hkTBUcY}?9esp(lHtj7Ci!Xx*Y+|$t+FJsw; zbzi^prkkFAltNcmdA$sXKYR54dtR7z#aR~~zwiD%on7(ouHL-q=BFR~`dv3~-?dZo zBse7`ZVSlq$YNHLH)>~zNTtwX&p0feft-2U?lyk!-dmTfTl10A&mBF?{Y_SET>sl= zp8VI_Z*0*nAri3q+93S=k^Ap?Vb;gby70Kczt`j6xbvoM13S_6*{OX7fTuDj>0%|=iQ1g8c-h%%L0LyH75cZd)G zAuLllYid%tk_>{{f?_k>(kUqCuzJXdqG;$b#3?>s*`|y8gOF)<`S%)WL0GS4+@9IR z2B1m}XvM`DqMv!3pV4Yo5DqH*ID=NjmKqEHg=nHo`mHI*B4mdV=MY`NRMTuhA@1WC zz$Q^}CE@S1RDe~4zEc?A-U~@g1mwb05ZP`&zsH9OdH!aS@cN|sK>K<@|w~X0&OP+wnF?i27!f3f}%{-udGJ1#Hw0# zYMW}`$XIj8L8n+aN}w~gFvlHQ*jP5bRk;>88`~Q3mdE4W6^^;MHZ7_2j&&j($7El- zKRK3(0{etGjPK6eU`sx^n6U`6brW@mO+BmF)ltgWV65i>#PQ4UwUPiLRK_YLzzQmp zJs~3#5x0n$snsGPB4B8-N6@iypv0nFrBfk3$0&b>(ki*gH{2RQ$h5t|YaC@kQ#M@; zQ7S@%@?D|?zQ2GSdQD7kA#v>dL%rWAq~w69-_R2Nq{K(CoGJOc6ht~U@05QbO*AxR zlvh#m_*sQy8eES-rJ2(@)F=(ZF;r1Q@RNYN2CQGohU)3)q+jUR7iPt z<@uQv?9uv5`Z`%8W#jwbqkt0S*^|_@+MIV(g`{LaReothfZ5w8f;UP5h=&|<$chy! zcGvW~Kd;Qs9v^&j;Dzgnq2S~qu!K7ldBpw?7=syu*<}9nf){8O0Z?vL;V-p~571Fn z`-~d1|LE~mN=rAaUA%71vJLA7{n@N#s8`Nmd_?GMbUgHl&)d@RgBf@J>+RR`@57_Q z&WZpGdym{>?6BdZdwaV(I@fI8xN6hJd23c|-oCv!LAs<_F0aDW+tuAWWC#Fk-LYfM zmd!#553wKs*sQ8aqsHt%dR&#NrR&!&UcYA9`nBO`%aJ@E#}Bq-_TUJj8-3*J`MDyV zbO8{WshTuu%>HBaZm(Ule8akU42%bj(siPU4~$M>@N(p*T8bdJif!+adyMTJKDxKJ zyVV>OUt=zQH<>3&n(G<2}{BixmC+{(KbZ`H{H7n<=SiWxC77umi@;$=BFFL+o!DS=# zlSIdWLD6E;T!iw=?K^7BZ$9*wHu!I!ee#NHewaICdkhWO6JtuHm0XUCU_UG{LrSC(l_di@rWK;w zlz3|!5@NQy9yNCVG2ag7?-nM=F_HElTn_Gh0 z<7q0?ag9{<_4M=&83KS?wryLzWn*iAAbG4;IlhITSTSerEm8Te+0T*vVXp>DC)jx6 zwx*kO+|zM@+vhNZ%mPgkwX+`^lH=nWE5ZffmUYEK!w$|G0M0(~_gBMNfF*7Yt6Ugl zE{n=8!iyHx9|)6IDz%ggfn+ZXioKa#KROATLo%?nB{?phu@1HIA;5#IsvmvBMT3Bn zzvCCJm4lzX6NWD)5M{#l@y)k1j1TONlL376`wtOhQ9xplKl5e9bpQ-66Au@ERhe5- zEP&C9AplP0*K(7hs3?NL#*ge<^npF*K85mE?>`pxYVgx&+rI+ZN$L=(_=`Eq8XPK4 zKhblv9@C<-lvLgy4nr{j_6+G6*4LlZTY;mLESfkKc17T{0*^(}5OofFk=)@dG+>kL zS;31I$c!PS)~+tEZ+;-L)*E=F3P{FSod34>l;cCOzOndBh3_19*S7Id_Xf#u_GejM zzZgU4vS~H55lsjs%v0A)sD%Ft6=Mr1Wh-ozcwxBz9M?~TU@BuB0pf(oJ-zMzqkeVG zpw{q7zkZI#R}b|KJP%VJ0yVwTM7b7%?3V&)W_LSh#u=l+pUR5TQ`$ zQFJbeP#C&JyGS`T3|%oW*7&ftbEjE4LmVI62PcDmIUh)zaccXEh!b^xFWzJu-wOeT zm%aa}L4FA>Fe|SUN$r&94uEv)gYh=6-#l@tU(a{h4B^1Qys5zMDzV1-0j=4`}0cw*o&QE z-BzvAN}_sgN$Q=Pu-RO=lc?a!wwRrQ!=VV3V2h?o!T^eB+Y`M>r={zTf!6HhOJ}cG z7M8G3o^nnWK|*dqbf7JNv7DM_ECWmr%ynA9`%$E6iNHkYr~M(sWR$ z*idvf5iTrTz51oqs~ElJM6=$?sx07W&z^1RT0rZzZCST%i=V^n6p3s=@UC607gsEu zy<%zjG1Rntd9R0*CqV-$1tlPS|ygYJg zcW^l8Xz(WvL|Ott93QB z!wl>H5|WtG*m`5gBN-Do`Kn`X1svvQK%sUFv}UhZJbT3wCklY%#)zvaGPC8HYDpXr z1Qp?9S7%peSLe>{JGO4$MguKyRhPN#32?BBY4xT}Yc_7eq;FJ0fZWKFL{V8kQICj> zZd-%38dVoCG#gyB1Z&2y0EQPgTa8j;&Lx1{pL(bh$=(1Ah)IDDNT}=#b(%mw^4a*3 zv7*?QK68{yILV4Th2k;x zy^|PhkoJM)`o;V=2-NaBWUeJ_-aO9W5DALVa~xBW@M4o`WvAwF|2cT-t1F5SF|l-F zP&X0VJ&iwQ)-aku8umr2X;7wgS1+@v1E{}@p3jPYHhnn!= z(WECDK8EOshB2tn8!^dZ&C!WvGC$G&C8b7RUtf1ur|x6u<`66uiN#MV@j%yZVIP>Hts0B*^(!e|4#`X4oCF>su(QG}*+UBqN+X{olQ!s? z06hdRKW{TFVt=D_9mzl7*aVtGZoag9iV7KK773n&@(aj<;kU$xmMJs9?f&>dPqL^{ z1J2N#%}j}fh)jsGq9ElYMFhkaTLwELK`U{#;4=RakNHw(4)nf)Avt1+D(4SkxnHus zh=vpyTFB<5g$aQZrqlpnXg2K$w=xPtEheDs2$M2$UA+yIE2fHfkkY%)<{N)S#3;G#VmOadhP$PpUnNQmKF~5;8KFazW3}}LJ z3Wjr;y!Wc!LCW{XIM2Eg8K`409a$Q+d#%}aR=q=s){m;p($)6Y~<4BmU>_B}UAGQ9=ml{;YSdAGoX3Lf> zyNmkWpI7Fm`>5%DEp2Y zc|v=wIJVb87^x8Qn_JA2M~^i#%VAyJz1>3|S};c<3>l8y?|@I9b5U1QVq)j7UiptV z|C*e<9cjcie#x1QJYUTnDS&!5{6wl5=SGT0oHtj`ma z1lT@)0A-4D7i*moi{o^MKC4y1lJ5c>MNZqtqWcgLsQSN}06|hcX}{WJ>*{`F{+w2e z@;h?;eh2*ZId2S;ZN=wr`c>%Tddny@;lfUhrtyyXCffIgNFFYT-B6eMU=g|)>QVVm znI%Le30&?E1~Pm(D%I6Q%54rlbBM8-KYZxWHy?eX{k6HPmj8Cvllo(|%jaU)LCI$E zO|=!p+(0q%g_1UOpgp6S>s~e1Glf!CAbHW@9A~-%%X|U%kpQ3dSQBPN!4CogHX4oY z&aTv`Xn?3i0^jP{3$3?iJ`!da7sE;-R?L2)l@q=oa_BT{$w4(OMsS}1*O$8Isl`LH z2lhHt%pA%IRxB&XTnb2@Jr1)zK1H#K+n<{5CsM`>-8;p*u8A$GH!!mbqf9_)&Zmt; z(;_7{ZHKY$i*QnHt676kN$$4(XXq;haTXdk-1jfVzZq-d8HsJRsqCB~ZF-_>9c5Wa zIo1p@ zVdscP3-yOcsMn9aXP_GKn>VfE5Vm5dUk-XCl?Dl;AWt^+j7x*&-|cvy5fUvu46T=e zT5bB#l|aTPeVz_k3vis!vo$e91tEt3a*$Z!B&=@Bhs&>Wk^`TJA;a9s{?2T(B?&b$ zbBlQS{lTHLX{d-qpGS#N3+@4(b4Sa=;&-V8&#uOHL5Blbi_X-Llq9s ztZiN>4}~%{Qg|OQ6cTRrg?>)e@e?|&RX8}wsLD>eKx>xTu~^UVIzEO~O{vad{QSbm z{15MMeSBZvWbM&pxG=tTD0P)_b#7RnF(l?e7$0nXD++@_4u=q&_QL)G;l>A*XOiEF zAz3S^2eEd3tBvnRE2sXaxWBkwNo>Sqm?XXE6#C4bn!L@>S0*TfAibntex&oib)z3QPO9@41DDOhkx) z**-AGCbK(TGDgBQBUEBOU`ThkOYv|oowrQAL1{JTv_e%Vv!*ubiVF-}NtgqNZW)IZ zI=5{LT3|DO%&-<~CcRMtD9KJtMt=?&1t|hI;s6lVNOLrI?mwzjpHSD-ocO>Y#Lk7c zcA|8>i;>tp9#6rde36Qyv#T6JS|6j8P{{4dIrpg zz!c*Ul>n;l7s|e^Pqt%Ee$!DWJhyDolJ)EM9W|yl*}iknZ#|7bTKIvYB+;bE&Vdcp zog9&bfyo*G%jQK^=ztkg%4sa+?#BR$xrLal9u?AVR`aT9VyGrrek@SYHLQ#3q<0gw zL#3h06kT5cl?i`d~asc9rw7(xthDH?=w zd?CsB*GHe_vi=~tm`8XHIhDf(4;*S{(-zn*?CR|5>gd|JbLX~gTX*f+OV z5;Mzt>=K#< zNkAH7cnF4z?&X^&fCS=V=}ExK{qUG`Jjwyc?dK>P^!q=7xgS$oF6<3g^b-QwlFy?CVRMQ zN{7SmGD$N!5rkSP+ctAd-eJpz;ouTOjB33@fmBlyc!HQ?%5*|K)|gFgB-_f%lvBWo z%xse>%umpb6yjMyjVZu_VtAKw{vE{awc|R;#>O zO0pS;!#lWKH6DvR1}evwBL-!ZpRfF{CG}PI@pp4uc7I;cpCLycjGjJVm(kjplPF7_? zWHI-zkQAds~3DxmhM?Yh3#?@xUuU!75P3)-Bwr;S8yNMQAV;Hg=165V5 z4N1vyNNz@vzcC2`U%kscP9$;8Cz4-kbRJ#%H64JxZ_XL@v#at`ygc$H>(~83la2Xn zciwdM!}m$s$zw88Xw;hCJc(mqn5$A?Y1D{l%u!=Q6_Ygeo}O1yplun|zj9ZYD4+%; zj!X?}pE#zfbIaxJF8j9mnXf;NuU~NZke)Xmb5g-=dG^VWE2G*N)pLZv6wn!(+l}VN z53Wopx-6DOY&DI-%g8mKjWE7Zg0W!D1UyE⪚124NTEQN#iag*4I!?^q5n$Re-<& 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

+ ); +}; diff --git a/plugins/pagerduty/src/components/About/VerticalIcon.tsx b/plugins/pagerduty/src/components/About/VerticalIcon.tsx new file mode 100644 index 0000000000000..5463372ac0bae --- /dev/null +++ b/plugins/pagerduty/src/components/About/VerticalIcon.tsx @@ -0,0 +1,85 @@ +/* + * 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 classnames from 'classnames'; +import { makeStyles, Link } from '@material-ui/core'; +import LinkIcon from '@material-ui/icons/Link'; +import { Link as RouterLink } from 'react-router-dom'; + +export type VerticalIconProps = { + icon?: React.ReactNode; + href?: string; + title?: string; + label: string; + action?: React.ReactNode; +}; + +const useIconStyles = makeStyles(theme => ({ + link: { + display: 'grid', + justifyItems: 'center', + gridGap: 4, + textAlign: 'center', + }, + label: { + fontSize: '0.7rem', + textTransform: 'uppercase', + fontWeight: 600, + letterSpacing: 1.2, + }, + linkStyle: { + color: theme.palette.secondary.main, + }, +})); + +export function VerticalIcon({ + icon = , + href = '#', + action, + ...props +}: VerticalIconProps) { + const classes = useIconStyles(); + + if (action) { + return ( + + {icon} + {action} + + ); + } + + // Absolute links should not be using RouterLink + if (href?.startsWith('//') || href?.includes('://')) { + return ( + + {icon} + {props.label} + + ); + } + + return ( + + {icon} + {props.label} + + ); +} diff --git a/plugins/pagerduty/src/components/About/index.ts b/plugins/pagerduty/src/components/About/index.ts new file mode 100644 index 0000000000000..4e495daa541c2 --- /dev/null +++ b/plugins/pagerduty/src/components/About/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 { AboutCard } from './AboutCard'; +// export { SubHeader } from './SubHeader'; +// export { VerticalIcon } from './VerticalIcon'; diff --git a/plugins/pagerduty/src/components/Incident/Incidents.tsx b/plugins/pagerduty/src/components/Incident/Incidents.tsx index 4b229fb4bc699..732a582cdb1f1 100644 --- a/plugins/pagerduty/src/components/Incident/Incidents.tsx +++ b/plugins/pagerduty/src/components/Incident/Incidents.tsx @@ -25,16 +25,14 @@ import { Alert } from '@material-ui/lab'; type Props = { serviceId: string; - refreshIncidents: Boolean; + refreshIncidents: boolean; }; export const Incidents = ({ serviceId, refreshIncidents }: Props) => { const api = useApi(pagerDutyApiRef); const [{ value: incidents, loading, error }, getIncidents] = useAsyncFn( - async () => { - return await api.getIncidentsByServiceId(serviceId); - }, + async () => await api.getIncidentsByServiceId(serviceId), ); useEffect(() => { diff --git a/plugins/pagerduty/src/components/PagerDutyCard.test.tsx b/plugins/pagerduty/src/components/PagerDutyCard.test.tsx index 34ddb63f71386..a6615aee82aa1 100644 --- a/plugins/pagerduty/src/components/PagerDutyCard.test.tsx +++ b/plugins/pagerduty/src/components/PagerDutyCard.test.tsx @@ -85,11 +85,9 @@ describe('PageDutyCard', () => { ), ); 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('Service Directory')).toBeInTheDocument(); + expect(getByText('Create Incident')).toBeInTheDocument(); + expect(getByText('Nice! No incidents found!')).toBeInTheDocument(); expect(getByText('Empty escalation policy')).toBeInTheDocument(); }); @@ -141,8 +139,8 @@ describe('PageDutyCard', () => { ), ); await waitFor(() => !queryByTestId('progress')); - expect(getByText('View in PagerDuty')).toBeInTheDocument(); - expect(getByText('Trigger Alarm')).toBeInTheDocument(); + expect(getByText('Service Directory')).toBeInTheDocument(); + expect(getByText('Create Incident')).toBeInTheDocument(); const triggerButton = getByTestId('trigger-button'); await act(async () => { fireEvent.click(triggerButton); diff --git a/plugins/pagerduty/src/components/PagerDutyCard.tsx b/plugins/pagerduty/src/components/PagerDutyCard.tsx index ac309e8a95de2..8fd20f37e8511 100644 --- a/plugins/pagerduty/src/components/PagerDutyCard.tsx +++ b/plugins/pagerduty/src/components/PagerDutyCard.tsx @@ -13,36 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { useState } from 'react'; +import React, { useState, useCallback } from 'react'; import { useApi, Progress } from '@backstage/core'; import { Entity } from '@backstage/catalog-model'; -import { - Button, - Card, - CardContent, - CardHeader, - Divider, - makeStyles, -} from '@material-ui/core'; +import { Button, makeStyles } from '@material-ui/core'; import { Incidents } from './Incident'; import { EscalationPolicy } from './Escalation'; 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 AlarmAddIcon from '@material-ui/icons/AlarmAdd'; import { TriggerDialog } from './TriggerDialog'; -import { MissingTokenError } from './MissingTokenError'; +import { MissingTokenError } from './Errors/MissingTokenError'; +import WebIcon from '@material-ui/icons/Web'; +import { AboutCard } from './About/AboutCard'; -const useStyles = makeStyles(theme => ({ - links: { - margin: theme.spacing(2, 0), - display: 'grid', - gridAutoFlow: 'column', - gridAutoColumns: 'min-content', - gridGap: theme.spacing(3), - }, +const useStyles = makeStyles({ triggerAlarm: { paddingTop: 0, paddingBottom: 0, @@ -56,7 +42,7 @@ const useStyles = makeStyles(theme => ({ textDecoration: 'none', }, }, -})); +}); export const PAGERDUTY_INTEGRATION_KEY = 'pagerduty.com/integration-key'; @@ -76,6 +62,14 @@ export const PagerDutyCard = ({ entity }: Props) => { PAGERDUTY_INTEGRATION_KEY ]; + const handleRefresh = useCallback(() => { + setRefreshIncidents(x => !x); + }, []); + + const handleDialog = useCallback(() => { + setShowDialog(x => !x); + }, []); + const { value: service, loading, error } = useAsync(async () => { const services = await api.getServiceByIntegrationKey(integrationKey); @@ -87,10 +81,6 @@ export const PagerDutyCard = ({ entity }: Props) => { }; }); - const handleDialog = () => { - setShowDialog(!showDialog); - }; - if (error instanceof UnauthorizedError) { return ; } @@ -107,13 +97,14 @@ export const PagerDutyCard = ({ entity }: Props) => { return ; } - const pagerdutyLink = { - title: 'View in PagerDuty', + const serviceLink = { + title: 'Service Directory', href: service!.url, + icon: , }; - const triggerAlarm = { - title: 'Trigger Alarm', + const triggerLink = { + title: 'Create Incident', action: ( ), - }; - - const onTriggerRefresh = () => { - setRefreshIncidents(true); + icon: , }; return ( - - - } - /> - } - action={triggerAlarm.action} - /> - - } - /> - - - - - - - + + + + + + } + /> ); }; diff --git a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx index 7ef57d67b474c..8073ab44b5a72 100644 --- a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx +++ b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx @@ -70,7 +70,7 @@ describe('TriggerDialog', () => { handleDialog={() => {}} name={entity.metadata.name} integrationKey="abc123" - onTriggerRefresh={() => {}} + onIncidentCreated={() => {}} /> , ), diff --git a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx index 4717b8fece059..4fa88a1b9a35c 100644 --- a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx +++ b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx @@ -35,7 +35,7 @@ type Props = { integrationKey: string; showDialog: boolean; handleDialog: () => void; - onTriggerRefresh: () => void; + onIncidentCreated: () => void; }; export const TriggerDialog = ({ @@ -43,7 +43,7 @@ export const TriggerDialog = ({ integrationKey, showDialog, handleDialog, - onTriggerRefresh, + onIncidentCreated: onIncidentCreated, }: Props) => { const alertApi = useApi(alertApiRef); const identityApi = useApi(identityApiRef); @@ -72,24 +72,20 @@ export const TriggerDialog = ({ alertApi.post({ message: `Alarm successfully triggered by ${userName}`, }); - onTriggerRefresh(); + onIncidentCreated(); handleDialog(); } + }, [value, alertApi, handleDialog, userName, onIncidentCreated]); - if (error) { - alertApi.post({ - message: `Failed to trigger alarm. ${error.message}`, - severity: 'error', - }); - } - }, [value, error, alertApi, handleDialog, userName, onTriggerRefresh]); - - if (!showDialog) { - return null; + if (error) { + alertApi.post({ + message: `Failed to trigger alarm. ${error.message}`, + severity: 'error', + }); } return ( - + This action will trigger an incident for "{name}". diff --git a/plugins/pagerduty/src/components/types.ts b/plugins/pagerduty/src/components/types.ts index a93791fe8e050..371f4eea6d0ce 100644 --- a/plugins/pagerduty/src/components/types.ts +++ b/plugins/pagerduty/src/components/types.ts @@ -56,3 +56,10 @@ export type User = { html_url: string; name: string; }; + +export type SubHeaderLink = { + title: string; + href?: string; + icon: React.ReactNode; + action?: React.ReactNode; +}; From 18c2dcb368f4dfcefdb0fcd211dbf5eb1c772c32 Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Fri, 4 Dec 2020 09:58:45 +0100 Subject: [PATCH 63/77] remove pagerduty logo --- .../components/Escalation/EscalationUser.tsx | 4 +-- .../Incident/IncidentEmptyState.tsx | 4 +-- .../components/Incident/IncidentListItem.tsx | 4 +-- .../components/Incident/Incidents.test.tsx | 4 +-- .../src/components/PagerDutyIcon.tsx | 27 ------------------- 5 files changed, 7 insertions(+), 36 deletions(-) delete mode 100644 plugins/pagerduty/src/components/PagerDutyIcon.tsx diff --git a/plugins/pagerduty/src/components/Escalation/EscalationUser.tsx b/plugins/pagerduty/src/components/Escalation/EscalationUser.tsx index 3847896e89fe7..41995c86f5835 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 '../PagerDutyIcon'; +import OpenInBrowserIcon from '@material-ui/icons/OpenInBrowser'; import { User } from '../types'; const useStyles = makeStyles({ @@ -69,7 +69,7 @@ export const EscalationUser = ({ user }: Props) => { rel="noopener noreferrer" color="primary" > - + diff --git a/plugins/pagerduty/src/components/Incident/IncidentEmptyState.tsx b/plugins/pagerduty/src/components/Incident/IncidentEmptyState.tsx index ded161261ed9c..c140620bb3fbe 100644 --- a/plugins/pagerduty/src/components/Incident/IncidentEmptyState.tsx +++ b/plugins/pagerduty/src/components/Incident/IncidentEmptyState.tsx @@ -15,14 +15,14 @@ */ import React from 'react'; -import { Grid } from '@material-ui/core'; +import { Grid, Typography } from '@material-ui/core'; import EmptyStateImage from '../../assets/emptyState.svg'; export const IncidentsEmptyState = () => { return ( -

Nice! No incidents are assigned to you!

+ Nice! No incidents found!
{ rel="noopener noreferrer" color="primary" > - + diff --git a/plugins/pagerduty/src/components/Incident/Incidents.test.tsx b/plugins/pagerduty/src/components/Incident/Incidents.test.tsx index 3c5e30e0ccfa8..9da9f9f11b4b8 100644 --- a/plugins/pagerduty/src/components/Incident/Incidents.test.tsx +++ b/plugins/pagerduty/src/components/Incident/Incidents.test.tsx @@ -40,9 +40,7 @@ describe('Incidents', () => { ), ); await waitFor(() => !queryByTestId('progress')); - expect( - getByText('Nice! No incidents are assigned to you!'), - ).toBeInTheDocument(); + expect(getByText('Nice! No incidents found!')).toBeInTheDocument(); }); it('Renders all incidents', async () => { diff --git a/plugins/pagerduty/src/components/PagerDutyIcon.tsx b/plugins/pagerduty/src/components/PagerDutyIcon.tsx deleted file mode 100644 index 67cfc0036ae73..0000000000000 --- a/plugins/pagerduty/src/components/PagerDutyIcon.tsx +++ /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 React from 'react'; -import SvgIcon, { SvgIconProps } from '@material-ui/core/SvgIcon'; - -export const PagerDutyIcon = (props: SvgIconProps) => - React.createElement( - SvgIcon, - props, - - - , - ); From 6f23e968303fb6a5ac91bd3021527d3cfcfa79d5 Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Fri, 4 Dec 2020 10:03:26 +0100 Subject: [PATCH 64/77] refactor structure --- .../{ => Errors}/MissingTokenError.tsx | 0 .../pagerduty/src/components/Errors/index.ts | 17 +++++++++++++++++ 2 files changed, 17 insertions(+) rename plugins/pagerduty/src/components/{ => Errors}/MissingTokenError.tsx (100%) create mode 100644 plugins/pagerduty/src/components/Errors/index.ts diff --git a/plugins/pagerduty/src/components/MissingTokenError.tsx b/plugins/pagerduty/src/components/Errors/MissingTokenError.tsx similarity index 100% rename from plugins/pagerduty/src/components/MissingTokenError.tsx rename to plugins/pagerduty/src/components/Errors/MissingTokenError.tsx diff --git a/plugins/pagerduty/src/components/Errors/index.ts b/plugins/pagerduty/src/components/Errors/index.ts new file mode 100644 index 0000000000000..3c2dfa65f2fcc --- /dev/null +++ b/plugins/pagerduty/src/components/Errors/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 { MissingTokenError } from './MissingTokenError'; From 1c6f8417d111e8a69cfba997f74ff6128f4d7a8f Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Fri, 4 Dec 2020 10:14:05 +0100 Subject: [PATCH 65/77] fix getIncident query --- plugins/pagerduty/src/api/client.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/plugins/pagerduty/src/api/client.ts b/plugins/pagerduty/src/api/client.ts index 99e21ef6d366a..527a56b2af4ab 100644 --- a/plugins/pagerduty/src/api/client.ts +++ b/plugins/pagerduty/src/api/client.ts @@ -56,7 +56,7 @@ export class PagerDutyClient implements PagerDutyApi { } async getIncidentsByServiceId(serviceId: string): Promise { - const params = `service_ids[]=${serviceId}`; + const params = `statuses[]=triggered&statuses[]=acknowledged&service_ids[]=${serviceId}`; const url = `${await this.config.discoveryApi.getBaseUrl( 'proxy', )}/pagerduty/incidents?${params}`; @@ -106,11 +106,9 @@ export class PagerDutyClient implements PagerDutyApi { body, }; - return this.request( - `${this.config.eventsBaseUrl ?? - 'https://events.pagerduty.com/v2'}/enqueue`, - options, - ); + const url = this.config.eventsBaseUrl ?? 'https://events.pagerduty.com/v2'; + + return this.request(`${url}/enqueue`, options); } private async getByUrl(url: string): Promise { From afb65b2f886dbce5112a862cc27b0bdbcf1e47a1 Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Fri, 4 Dec 2020 10:14:50 +0100 Subject: [PATCH 66/77] remove plugin-catalog dep --- plugins/pagerduty/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json index 64cc5a089362a..a7b5c3b58f71d 100644 --- a/plugins/pagerduty/package.json +++ b/plugins/pagerduty/package.json @@ -22,15 +22,16 @@ "dependencies": { "@backstage/catalog-model": "^0.2.0", "@backstage/core": "^0.3.0", - "@backstage/plugin-catalog": "^0.2.1", "@backstage/test-utils": "^0.1.2", "@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", + "classnames": "^2.2.6", "date-fns": "^2.16.1", "react": "^16.13.1", "react-dom": "^16.13.1", + "react-router-dom": "6.0.0-beta.0", "react-use": "^15.3.3" }, "devDependencies": { From 02be8ca4f1a7095ea88a95642a4c4ee256c8b6b6 Mon Sep 17 00:00:00 2001 From: samiramkr Date: Fri, 4 Dec 2020 11:41:30 +0100 Subject: [PATCH 67/77] Delete PagerdutyCard.tsx --- .../src/components/PagerdutyCard.tsx | 169 ------------------ 1 file changed, 169 deletions(-) delete mode 100644 plugins/pagerduty/src/components/PagerdutyCard.tsx diff --git a/plugins/pagerduty/src/components/PagerdutyCard.tsx b/plugins/pagerduty/src/components/PagerdutyCard.tsx deleted file mode 100644 index ac309e8a95de2..0000000000000 --- a/plugins/pagerduty/src/components/PagerdutyCard.tsx +++ /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 React, { useState } from 'react'; -import { useApi, Progress } from '@backstage/core'; -import { Entity } from '@backstage/catalog-model'; -import { - Button, - Card, - CardContent, - CardHeader, - Divider, - makeStyles, -} from '@material-ui/core'; -import { Incidents } from './Incident'; -import { EscalationPolicy } from './Escalation'; -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 AlarmAddIcon from '@material-ui/icons/AlarmAdd'; -import { TriggerDialog } from './TriggerDialog'; -import { MissingTokenError } from './MissingTokenError'; - -const useStyles = makeStyles(theme => ({ - links: { - margin: theme.spacing(2, 0), - display: 'grid', - gridAutoFlow: 'column', - 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'; - -export const isPluginApplicableToEntity = (entity: Entity) => - Boolean(entity.metadata.annotations?.[PAGERDUTY_INTEGRATION_KEY]); - -type Props = { - entity: Entity; -}; - -export const PagerDutyCard = ({ entity }: Props) => { - const classes = useStyles(); - const api = useApi(pagerDutyApiRef); - const [showDialog, setShowDialog] = useState(false); - const [refreshIncidents, setRefreshIncidents] = useState(false); - const integrationKey = entity.metadata.annotations![ - PAGERDUTY_INTEGRATION_KEY - ]; - - const { value: service, loading, error } = useAsync(async () => { - const services = await api.getServiceByIntegrationKey(integrationKey); - - return { - id: services[0].id, - name: services[0].name, - url: services[0].html_url, - policyId: services[0].escalation_policy.id, - }; - }); - - const handleDialog = () => { - setShowDialog(!showDialog); - }; - - if (error instanceof UnauthorizedError) { - return ; - } - - if (error) { - return ( - - Error encountered while fetching information. {error.message} - - ); - } - - if (loading) { - return ; - } - - const pagerdutyLink = { - title: 'View in PagerDuty', - href: service!.url, - }; - - const triggerAlarm = { - title: 'Trigger Alarm', - action: ( - - ), - }; - - const onTriggerRefresh = () => { - setRefreshIncidents(true); - }; - - return ( - - - } - /> - } - action={triggerAlarm.action} - /> - - } - /> - - - - - - - - ); -}; From 5d88473d9ec27423aff62b476044e1880245c4f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Fri, 4 Dec 2020 12:14:38 +0100 Subject: [PATCH 68/77] bump app template deps accordingly --- .changeset/shaggy-camels-remain.md | 1 + .../templates/default-app/packages/backend/package.json.hbs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.changeset/shaggy-camels-remain.md b/.changeset/shaggy-camels-remain.md index a519d429132f2..f1c4e1764396b 100644 --- a/.changeset/shaggy-camels-remain.md +++ b/.changeset/shaggy-camels-remain.md @@ -2,6 +2,7 @@ 'example-backend': patch '@backstage/plugin-scaffolder-backend': patch '@backstage/plugin-techdocs-backend': patch +'@backstage/create-app': patch --- Unify `dockerode` library and type dependency versions 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 07d1b0be5849a..8dccb0b683b17 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 @@ -29,7 +29,7 @@ "@backstage/plugin-techdocs-backend": "^{{version '@backstage/plugin-techdocs-backend'}}", "@octokit/rest": "^18.0.0", "@gitbeaker/node": "^25.2.0", - "dockerode": "^3.2.0", + "dockerode": "^3.2.1", "express": "^4.17.1", "express-promise-router": "^3.0.3", "knex": "^0.21.6", @@ -43,7 +43,7 @@ }, "devDependencies": { "@backstage/cli": "^{{version '@backstage/cli'}}", - "@types/dockerode": "^2.5.32", + "@types/dockerode": "^3.2.1", "@types/express": "^4.17.6", "@types/express-serve-static-core": "^4.17.5", "@types/helmet": "^0.0.47" From b5f89d1c5d0b77691b22c07501ce45b8b5c2fcdd Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 4 Dec 2020 13:00:34 +0100 Subject: [PATCH 69/77] Update CHANGELOG.md --- CHANGELOG.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ac33ca7f86f8..9df533bb88543 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,9 @@ # Backstage Changelog -This is a best-effort changelog where we manually collect breaking changes. It is not an exhaustive list of all changes or even features added. +This changelog is no longer being updated and will be removed in the future, as each package now has its own changelog instead. It was a best-effort changelog where we manually collected breaking changes during the `v0.1.1-alpha.` releases. If you encounter issues while upgrading to a newer version, don't hesitate to reach out on [Discord](https://discord.gg/EBHEGzX) or [open an issue](https://github.com/backstage/backstage/issues/new/choose)! -## Next Release - -> Collect changes for the next release below - ## v0.1.1-alpha.26 ### @backstage/cli From fa9d634350425d44287f604729a4786f1ccf7a2f Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Fri, 4 Dec 2020 13:42:20 +0100 Subject: [PATCH 70/77] update dependencies version --- plugins/pagerduty/package.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json index a7b5c3b58f71d..4d9ec49d025b4 100644 --- a/plugins/pagerduty/package.json +++ b/plugins/pagerduty/package.json @@ -20,23 +20,23 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.0", - "@backstage/test-utils": "^0.1.2", + "@backstage/catalog-model": "^0.4.0", + "@backstage/core": "^0.3.2", + "@backstage/test-utils": "^0.1.4", "@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", "classnames": "^2.2.6", - "date-fns": "^2.16.1", + "date-fns": "^2.15.0", "react": "^16.13.1", "react-dom": "^16.13.1", "react-router-dom": "6.0.0-beta.0", "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.1", - "@backstage/dev-utils": "^0.1.3", + "@backstage/cli": "^0.4.0", + "@backstage/dev-utils": "^0.1.5", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", From b6557c098811208cfb4f311422bb8110c5a26ffa Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 4 Dec 2020 13:50:37 +0100 Subject: [PATCH 71/77] core-api: update ApiFactory type to correctly infer API type and disallow mismatched implementations --- .changeset/calm-icons-jump.md | 23 +++++++++++++++++++ .../src/apis/system/ApiFactoryRegistry.ts | 10 ++++---- packages/core-api/src/apis/system/helpers.ts | 15 ++++++------ packages/core-api/src/apis/system/types.ts | 16 +++++++++---- packages/dev-utils/src/devApp/render.tsx | 8 ++++--- 5 files changed, 54 insertions(+), 18 deletions(-) create mode 100644 .changeset/calm-icons-jump.md diff --git a/.changeset/calm-icons-jump.md b/.changeset/calm-icons-jump.md new file mode 100644 index 0000000000000..16054a80e6a0e --- /dev/null +++ b/.changeset/calm-icons-jump.md @@ -0,0 +1,23 @@ +--- +'@backstage/core-api': patch +'@backstage/dev-utils': patch +--- + +Update ApiFactory type to correctly infer API type and disallow mismatched implementations. + +This fixes for example the following code: + +```ts +interface MyApi { + myMethod(): void +} + +const myApiRef = createApiRef({...}); + +createApiFactory({ + api: myApiRef, + deps: {}, + // This should've caused an error, since the empty object does not fully implement MyApi + factory: () => ({}), +}) +``` diff --git a/packages/core-api/src/apis/system/ApiFactoryRegistry.ts b/packages/core-api/src/apis/system/ApiFactoryRegistry.ts index 556f63589c478..9d9b058eac5e3 100644 --- a/packages/core-api/src/apis/system/ApiFactoryRegistry.ts +++ b/packages/core-api/src/apis/system/ApiFactoryRegistry.ts @@ -55,9 +55,9 @@ export class ApiFactoryRegistry implements ApiFactoryHolder { * A factory will not be added to the registry if there is already * an existing factory with the same or higher priority. */ - register( + register( scope: ApiFactoryScope, - factory: ApiFactory, + factory: ApiFactory, ) { const priority = ScopePriority[scope]; const existing = this.factories.get(factory.api); @@ -69,12 +69,14 @@ export class ApiFactoryRegistry implements ApiFactoryHolder { return true; } - get(api: ApiRef): ApiFactory | undefined { + get( + api: ApiRef, + ): ApiFactory | undefined { const tuple = this.factories.get(api); if (!tuple) { return undefined; } - return tuple.factory as ApiFactory; + return tuple.factory as ApiFactory; } getAllApis(): Set { diff --git a/packages/core-api/src/apis/system/helpers.ts b/packages/core-api/src/apis/system/helpers.ts index 0ccd0cdb8080f..cabff730605bc 100644 --- a/packages/core-api/src/apis/system/helpers.ts +++ b/packages/core-api/src/apis/system/helpers.ts @@ -25,18 +25,19 @@ export function createApiFactory< Api, Impl extends Api, Deps extends { [name in string]: unknown } ->(factory: ApiFactory): ApiFactory; -export function createApiFactory( +>(factory: ApiFactory): ApiFactory; +export function createApiFactory( api: ApiRef, - instance: Api, -): ApiFactory; + instance: Impl, +): ApiFactory; export function createApiFactory< Api, + Impl extends Api, Deps extends { [name in string]: unknown } >( - factory: ApiFactory | ApiRef, - instance?: Api, -): ApiFactory { + factory: ApiFactory | ApiRef, + instance?: Impl, +): ApiFactory { if ('id' in factory) { return { api: factory, diff --git a/packages/core-api/src/apis/system/types.ts b/packages/core-api/src/apis/system/types.ts index b0c19e551c1dd..6234a55c711ec 100644 --- a/packages/core-api/src/apis/system/types.ts +++ b/packages/core-api/src/apis/system/types.ts @@ -34,16 +34,24 @@ export type ApiHolder = { get(api: ApiRef): T | undefined; }; -export type ApiFactory = { +export type ApiFactory< + Api, + Impl extends Api, + Deps extends { [name in string]: unknown } +> = { api: ApiRef; deps: TypesToApiRefs; - factory(deps: Deps): Api; + factory(deps: Deps): Impl; }; -export type AnyApiFactory = ApiFactory; +export type AnyApiFactory = ApiFactory< + unknown, + unknown, + { [key in string]: unknown } +>; export type ApiFactoryHolder = { get( api: ApiRef, - ): ApiFactory | undefined; + ): ApiFactory | undefined; }; diff --git a/packages/dev-utils/src/devApp/render.tsx b/packages/dev-utils/src/devApp/render.tsx index cca3470f3f8fd..869de01024abf 100644 --- a/packages/dev-utils/src/devApp/render.tsx +++ b/packages/dev-utils/src/devApp/render.tsx @@ -56,9 +56,11 @@ class DevAppBuilder { /** * Register an API factory to add to the app */ - registerApi( - factory: ApiFactory, - ): DevAppBuilder { + registerApi< + Api, + Impl extends Api, + Deps extends { [name in string]: unknown } + >(factory: ApiFactory): DevAppBuilder { this.apis.push(factory); return this; } From 6a0d7a9fbcf97bb7883a0aaf496f40ed3c44e0e6 Mon Sep 17 00:00:00 2001 From: Peter Grauvogel <46317312+petergrau@users.noreply.github.com> Date: Fri, 4 Dec 2020 14:06:42 +0100 Subject: [PATCH 72/77] Increase pageSize for search result view (#3565) --- .changeset/purple-ghosts-attack.md | 5 +++++ plugins/search/src/components/SearchResult/SearchResult.tsx | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/purple-ghosts-attack.md diff --git a/.changeset/purple-ghosts-attack.md b/.changeset/purple-ghosts-attack.md new file mode 100644 index 0000000000000..bf13d541e2c97 --- /dev/null +++ b/.changeset/purple-ghosts-attack.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search': patch +--- + +change default size for pageSize in search result view diff --git a/plugins/search/src/components/SearchResult/SearchResult.tsx b/plugins/search/src/components/SearchResult/SearchResult.tsx index 9fd54ac06c8f6..4bbb15e229493 100644 --- a/plugins/search/src/components/SearchResult/SearchResult.tsx +++ b/plugins/search/src/components/SearchResult/SearchResult.tsx @@ -251,7 +251,7 @@ export const SearchResult = ({ searchQuery }: SearchResultProps) => { )} Date: Fri, 4 Dec 2020 14:56:53 +0100 Subject: [PATCH 73/77] fix import --- .../pagerduty/src/components/Incident/IncidentEmptyState.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/pagerduty/src/components/Incident/IncidentEmptyState.tsx b/plugins/pagerduty/src/components/Incident/IncidentEmptyState.tsx index c140620bb3fbe..b697422b0f91d 100644 --- a/plugins/pagerduty/src/components/Incident/IncidentEmptyState.tsx +++ b/plugins/pagerduty/src/components/Incident/IncidentEmptyState.tsx @@ -16,7 +16,7 @@ import React from 'react'; import { Grid, Typography } from '@material-ui/core'; -import EmptyStateImage from '../../assets/emptyState.svg'; +import EmptyStateImage from '../../assets/emptystate.svg'; export const IncidentsEmptyState = () => { return ( From 56751f06320a1cd392c059003ff58bcdf3d7fb99 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Fri, 4 Dec 2020 10:26:38 -0500 Subject: [PATCH 74/77] Convert card to full Material-UI components --- .../src/components/ImportComponentPage.tsx | 35 +++++++++---------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/plugins/catalog-import/src/components/ImportComponentPage.tsx b/plugins/catalog-import/src/components/ImportComponentPage.tsx index c294d4c8572a3..9fdb465dd1086 100644 --- a/plugins/catalog-import/src/components/ImportComponentPage.tsx +++ b/plugins/catalog-import/src/components/ImportComponentPage.tsx @@ -64,7 +64,7 @@ export const ImportComponentPage = ({ - + There are two ways to register an existing component. -
    -
  1. - GitHub Repo -
    - If you already have code in a GitHub repository, enter the - full URL to your repo and a new pull request with a sample - Backstage metadata Entity File ( - catalog-info.yaml) will be opened for you. -
  2. -
  3. - GitHub Repo & Entity File -
    - If you've already created a Backstage metadata file and put - it in your repo, you can enter the full URL to that Entity - File. -
  4. -
+
+ GitHub Repo + + If you already have code in a GitHub repository, enter the full + URL to your repo and a new pull request with a sample Backstage + metadata Entity File (catalog-info.yaml) will be + opened for you. + + + GitHub Repo & Entity File + + + If you've already created a Backstage metadata file and put it + in your repo, you can enter the full URL to that Entity File.
- + Date: Fri, 4 Dec 2020 10:42:47 -0500 Subject: [PATCH 75/77] Change sample repo URL --- plugins/catalog-import/src/components/ImportComponentForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/catalog-import/src/components/ImportComponentForm.tsx b/plugins/catalog-import/src/components/ImportComponentForm.tsx index 9a4fd38a06065..dab5ccbea24a2 100644 --- a/plugins/catalog-import/src/components/ImportComponentForm.tsx +++ b/plugins/catalog-import/src/components/ImportComponentForm.tsx @@ -99,7 +99,7 @@ export const RegisterComponentForm = ({ nextStep, saveConfig }: Props) => { variant="outlined" label="Repository URL" error={hasErrors} - placeholder="https://github.com/spotify/backstage" + placeholder="https://github.com/backstage/backstage" name="componentLocation" required margin="normal" From 7c856e33174b1c675e619d229e60817b4c94c320 Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Fri, 4 Dec 2020 16:44:01 +0100 Subject: [PATCH 76/77] update dep version --- plugins/pagerduty/package.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json index 4d9ec49d025b4..4d58a1da42ffa 100644 --- a/plugins/pagerduty/package.json +++ b/plugins/pagerduty/package.json @@ -22,7 +22,6 @@ "dependencies": { "@backstage/catalog-model": "^0.4.0", "@backstage/core": "^0.3.2", - "@backstage/test-utils": "^0.1.4", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -37,13 +36,15 @@ "devDependencies": { "@backstage/cli": "^0.4.0", "@backstage/dev-utils": "^0.1.5", + "@backstage/test-utils": "^0.1.4", "@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" + "msw": "^0.21.2", + "node-fetch": "^2.6.1", + "cross-fetch": "^3.0.6" }, "files": [ "dist" From cb124a00056ee1b74c3e0bec5762d14f09308ba6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Fri, 4 Dec 2020 17:29:12 +0100 Subject: [PATCH 77/77] Deduplicate @changesets/config in yarn.lock --- yarn.lock | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/yarn.lock b/yarn.lock index 44024b595243e..a0681a459b32b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1413,19 +1413,7 @@ term-size "^2.1.0" tty-table "^2.7.0" -"@changesets/config@^1.2.0": - version "1.3.0" - resolved "https://registry.npmjs.org/@changesets/config/-/config-1.3.0.tgz#82fcbf572b00ba16636be9ea45167983f1fc203b" - integrity sha512-IeAHmN5kI7OywBUNJXsk/v4vcXDDscwgTe/K5D3FSng5QTvzbgiMAe5K1iwBxBvuT4u/33n89kxSJdg4TTTFfA== - dependencies: - "@changesets/errors" "^0.1.4" - "@changesets/get-dependents-graph" "^1.1.3" - "@changesets/logger" "^0.0.5" - "@changesets/types" "^3.1.0" - "@manypkg/get-packages" "^1.0.1" - fs-extra "^7.0.1" - -"@changesets/config@^1.4.0": +"@changesets/config@^1.2.0", "@changesets/config@^1.4.0": version "1.4.0" resolved "https://registry.npmjs.org/@changesets/config/-/config-1.4.0.tgz#c157a4121f198b749f2bbc2e9015b6e976ece7d6" integrity sha512-eoTOcJ6py7jBDY8rUXwEGxR5UtvUX+p//0NhkVpPGcnvIeITHq+DOIsuWyGzWcb+1FaYkof3CCr32/komZTu4Q==

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 ST7Z{Ol=o{Wq*ITb`+ooy6Yp~X From 993acf186723f15b110208703d20f97020fbebbe Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Thu, 3 Dec 2020 16:04:19 -0500 Subject: [PATCH 56/77] Fix attribute definitions --- plugins/catalog-import/src/components/ImportComponentPage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/catalog-import/src/components/ImportComponentPage.tsx b/plugins/catalog-import/src/components/ImportComponentPage.tsx index 339e7c8dd63a5..c294d4c8572a3 100644 --- a/plugins/catalog-import/src/components/ImportComponentPage.tsx +++ b/plugins/catalog-import/src/components/ImportComponentPage.tsx @@ -64,7 +64,7 @@ export const ImportComponentPage = ({ - + - + Date: Fri, 4 Dec 2020 15:30:09 +0800 Subject: [PATCH 57/77] fix(cli): Fix Config Schema for `app.listen` --- .changeset/perfect-donkeys-hope.md | 5 +++++ packages/cli/package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/perfect-donkeys-hope.md diff --git a/.changeset/perfect-donkeys-hope.md b/.changeset/perfect-donkeys-hope.md new file mode 100644 index 0000000000000..80feae11a0c0d --- /dev/null +++ b/.changeset/perfect-donkeys-hope.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Fix config schema for `.app.listen` diff --git a/packages/cli/package.json b/packages/cli/package.json index fd024734539bb..0d585436bb431 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -178,7 +178,7 @@ "description": "Listening configuration for local development", "properties": { "host": { - "type": "number", + "type": "string", "visibility": "frontend", "description": "The host that the frontend should be bound to. Only used for local development." }, From b7793a2d2d9fac5d5b61246dacff3ddac8fc268c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Dec 2020 08:46:21 +0100 Subject: [PATCH 58/77] build(deps): bump chokidar from 3.4.2 to 3.4.3 (#3562) Bumps [chokidar](https://github.com/paulmillr/chokidar) from 3.4.2 to 3.4.3. - [Release notes](https://github.com/paulmillr/chokidar/releases) - [Commits](https://github.com/paulmillr/chokidar/compare/3.4.2...3.4.3) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/yarn.lock b/yarn.lock index 5c2a8253d3483..72403191d466b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1316,7 +1316,18 @@ to-fast-properties "^2.0.0" "@backstage/catalog-model@^0.2.0": - version "0.3.1" + version "0.4.0" + 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" + +"@backstage/catalog-model@^0.3.0": + version "0.4.0" dependencies: "@backstage/config" "^0.1.1" "@types/json-schema" "^7.0.5" @@ -8666,9 +8677,9 @@ chokidar@^2.1.8: fsevents "^1.2.7" chokidar@^3.2.2, chokidar@^3.3.0, chokidar@^3.3.1, chokidar@^3.4.1, chokidar@^3.4.2: - version "3.4.2" - resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz#38dc8e658dec3809741eb3ef7bb0a47fe424232d" - integrity sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A== + version "3.4.3" + resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz#c1df38231448e45ca4ac588e6c79573ba6a57d5b" + integrity sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ== dependencies: anymatch "~3.1.1" braces "~3.0.2" @@ -8676,7 +8687,7 @@ chokidar@^3.2.2, chokidar@^3.3.0, chokidar@^3.3.1, chokidar@^3.4.1, chokidar@^3. is-binary-path "~2.1.0" is-glob "~4.0.1" normalize-path "~3.0.0" - readdirp "~3.4.0" + readdirp "~3.5.0" optionalDependencies: fsevents "~2.1.2" @@ -20630,10 +20641,10 @@ readdirp@^2.2.1: micromatch "^3.1.10" readable-stream "^2.0.2" -readdirp@~3.4.0: - version "3.4.0" - resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz#9fdccdf9e9155805449221ac645e8303ab5b9ada" - integrity sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ== +readdirp@~3.5.0: + version "3.5.0" + resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e" + integrity sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ== dependencies: picomatch "^2.2.1" From fb445772d1673164e3fb943d37fb93426a163ef7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Dec 2020 08:47:36 +0100 Subject: [PATCH 59/77] build(deps): bump @gitbeaker/core from 25.2.0 to 25.6.0 (#3561) Bumps [@gitbeaker/core](https://github.com/jdalrymple/gitbeaker) from 25.2.0 to 25.6.0. - [Release notes](https://github.com/jdalrymple/gitbeaker/releases) - [Changelog](https://github.com/jdalrymple/gitbeaker/blob/master/CHANGELOG.md) - [Commits](https://github.com/jdalrymple/gitbeaker/compare/25.2.0...25.6.0) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/yarn.lock b/yarn.lock index 72403191d466b..9bd7e9f06715c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1811,11 +1811,11 @@ yaml-ast-parser "0.0.43" "@gitbeaker/core@^25.2.0": - version "25.2.0" - resolved "https://registry.npmjs.org/@gitbeaker/core/-/core-25.2.0.tgz#c2f46b65ed88aebfa69afd2e58dbf4ebe2efb2c7" - integrity sha512-dhCvZItI8FIzHtJ9EySQ43GmNg3j39MxDGMCpDHn+Qb1o54QS+J6XPVQrMRmJioZIyj+WZQPX/IP+/mRkx8vhg== + version "25.6.0" + resolved "https://registry.npmjs.org/@gitbeaker/core/-/core-25.6.0.tgz#97d5ccc5d61bab6b678bec280036d594d275931e" + integrity sha512-+CohJNsbZiPl7jPgw7PHt5t0JIIV9NngObOskY1Ww8jef7SqaKpz0NsbSDawuWFBdmXApMpK81AEfASKtVI+cw== dependencies: - "@gitbeaker/requester-utils" "^25.2.0" + "@gitbeaker/requester-utils" "^25.6.0" form-data "^3.0.0" li "^1.3.0" xcase "^2.0.1" @@ -1830,10 +1830,10 @@ got "^11.7.0" xcase "^2.0.1" -"@gitbeaker/requester-utils@^25.2.0": - version "25.2.0" - resolved "https://registry.npmjs.org/@gitbeaker/requester-utils/-/requester-utils-25.2.0.tgz#f71c44e0073617877f9875dd00c4ae0d74897b09" - integrity sha512-pjuFIVlbxSTPdN+zFT/LBP4ym8k0OBYwUpc5WkzoOtvdTGuDX05r8ufnV07kibLDJkwDmjwnH4Hsc66yevSQTw== +"@gitbeaker/requester-utils@^25.2.0", "@gitbeaker/requester-utils@^25.6.0": + version "25.6.0" + resolved "https://registry.npmjs.org/@gitbeaker/requester-utils/-/requester-utils-25.6.0.tgz#001a432a48460bb5196a02ed71763eb707a1a01e" + integrity sha512-jD8cHbAZPR6+cB3HiukQxcqIKF5VkHtqg2m+Ns6ROE1pb0oRn10D/a9J1lZOXC9Jz2tQOBMWfHlplbmFFdB6Og== dependencies: form-data "^3.0.0" query-string "^6.13.3" From be98e222eeb976f987c65765619ff4a8c838f84a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20=C3=96stberg?= Date: Fri, 4 Dec 2020 09:06:26 +0100 Subject: [PATCH 60/77] Update GithubUrlReader.ts --- packages/backend-common/src/reading/GithubUrlReader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend-common/src/reading/GithubUrlReader.ts b/packages/backend-common/src/reading/GithubUrlReader.ts index 278fb70eb3f9d..de798a70674a6 100644 --- a/packages/backend-common/src/reading/GithubUrlReader.ts +++ b/packages/backend-common/src/reading/GithubUrlReader.ts @@ -196,7 +196,7 @@ export class GithubUrlReader implements UrlReader { new URL( `${protocol}://${resource}/${full_name}/archive/${ref}.tar.gz`, ).toString(), - getApiRequestOptions(this.config) + getRawRequestOptions(this.config), ); if (!response.ok) { const message = `Failed to read tree from ${url}, ${response.status} ${response.statusText}`; From 1e22f8e0b820d4f5c1913a82ab40a6341e51dd7d Mon Sep 17 00:00:00 2001 From: Joel Low Date: Fri, 4 Dec 2020 16:02:39 +0800 Subject: [PATCH 61/77] Fix conflicting dockerode version specifications --- .changeset/shaggy-camels-remain.md | 7 +++++++ packages/backend/package.json | 4 ++-- plugins/scaffolder-backend/package.json | 4 ++-- plugins/techdocs-backend/package.json | 2 +- yarn.lock | 10 +++++----- 5 files changed, 17 insertions(+), 10 deletions(-) create mode 100644 .changeset/shaggy-camels-remain.md diff --git a/.changeset/shaggy-camels-remain.md b/.changeset/shaggy-camels-remain.md new file mode 100644 index 0000000000000..a519d429132f2 --- /dev/null +++ b/.changeset/shaggy-camels-remain.md @@ -0,0 +1,7 @@ +--- +'example-backend': patch +'@backstage/plugin-scaffolder-backend': patch +'@backstage/plugin-techdocs-backend': patch +--- + +Unify `dockerode` library and type dependency versions diff --git a/packages/backend/package.json b/packages/backend/package.json index 9e635902e9351..5937d6a6f37b3 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -34,7 +34,7 @@ "@gitbeaker/node": "^25.2.0", "@octokit/rest": "^18.0.0", "azure-devops-node-api": "^10.1.1", - "dockerode": "^3.2.0", + "dockerode": "^3.2.1", "example-app": "^0.2.5", "express": "^4.17.1", "express-promise-router": "^3.0.3", @@ -46,7 +46,7 @@ }, "devDependencies": { "@backstage/cli": "^0.4.0", - "@types/dockerode": "^2.5.32", + "@types/dockerode": "^3.2.1", "@types/express": "^4.17.6", "@types/express-serve-static-core": "^4.17.5", "@types/helmet": "^0.0.48" diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index f71d8bc36c8b7..7aa8fc873a598 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -26,13 +26,13 @@ "@gitbeaker/core": "^25.2.0", "@gitbeaker/node": "^25.2.0", "@octokit/rest": "^18.0.0", - "@types/dockerode": "^2.5.32", + "@types/dockerode": "^3.2.1", "@types/express": "^4.17.6", "azure-devops-node-api": "^10.1.1", "command-exists-promise": "^2.0.2", "compression": "^1.7.4", "cors": "^2.8.5", - "dockerode": "^3.2.0", + "dockerode": "^3.2.1", "express": "^4.17.1", "express-promise-router": "^3.0.3", "fs-extra": "^9.0.0", diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index 773b5a5d5c473..47b4a7cd9a9cf 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -23,7 +23,7 @@ "@backstage/backend-common": "^0.3.3", "@backstage/catalog-model": "^0.4.0", "@backstage/config": "^0.1.1", - "@types/dockerode": "^2.5.34", + "@types/dockerode": "^3.2.1", "@types/express": "^4.17.6", "command-exists-promise": "^2.0.2", "cross-fetch": "^3.0.6", diff --git a/yarn.lock b/yarn.lock index 9bd7e9f06715c..44024b595243e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5180,10 +5180,10 @@ resolved "https://registry.npmjs.org/@types/diff/-/diff-4.0.2.tgz#2e9bb89f9acc3ab0108f0f3dc4dbdcf2fff8a99c" integrity sha512-mIenTfsIe586/yzsyfql69KRnA75S8SVXQbTLpDejRrjH0QSJcpu3AUOi/Vjnt9IOsXKxPhJfGpQUNMueIU1fQ== -"@types/dockerode@^2.5.32", "@types/dockerode@^2.5.34": - version "2.5.34" - resolved "https://registry.npmjs.org/@types/dockerode/-/dockerode-2.5.34.tgz#9adb884f7cc6c012a6eb4b2ad794cc5d01439959" - integrity sha512-LcbLGcvcBwBAvjH9UrUI+4qotY+A5WCer5r43DR5XHv2ZIEByNXFdPLo1XxR+v/BjkGjlggW8qUiXuVEhqfkpA== +"@types/dockerode@^3.2.1": + version "3.2.1" + resolved "https://registry.npmjs.org/@types/dockerode/-/dockerode-3.2.1.tgz#f713a8f6f1017c227845ab33239383da721207b9" + integrity sha512-AeZpdQMNqM8dtrEaaP81CbjdRVKNmFIMzgz5IlKIeS5uWEmjlEmENP444AGTEEF71r5TFuY9E4SkzZAO8lOF1A== dependencies: "@types/node" "*" @@ -10640,7 +10640,7 @@ docker-modem@^2.1.0: split-ca "^1.0.1" ssh2 "^0.8.7" -dockerode@^3.2.0, dockerode@^3.2.1: +dockerode@^3.2.1: version "3.2.1" resolved "https://registry.npmjs.org/dockerode/-/dockerode-3.2.1.tgz#4a2222e3e1df536bf595e78e76d3cfbf6d4d93b9" integrity sha512-XsSVB5Wu5HWMg1aelV5hFSqFJaKS5x1aiV/+sT7YOzOq1IRl49I/UwV8Pe4x6t0iF9kiGkWu5jwfvbkcFVupBw== From 3dee3093dee7ab0eb696997a9d35ff60c5b562ea Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Fri, 4 Dec 2020 09:28:48 +0100 Subject: [PATCH 62/77] fix updating incidents and create AboutCard and use it --- .../src/components/About/AboutCard.tsx | 33 ++++++ .../src/components/About/SubHeader.tsx | 50 ++++++++ .../src/components/About/VerticalIcon.tsx | 85 ++++++++++++++ .../pagerduty/src/components/About/index.ts | 19 +++ .../src/components/Incident/Incidents.tsx | 6 +- .../src/components/PagerDutyCard.test.tsx | 12 +- .../src/components/PagerDutyCard.tsx | 110 +++++++----------- .../TriggerDialog/TriggerDialog.test.tsx | 2 +- .../TriggerDialog/TriggerDialog.tsx | 24 ++-- plugins/pagerduty/src/components/types.ts | 7 ++ 10 files changed, 254 insertions(+), 94 deletions(-) create mode 100644 plugins/pagerduty/src/components/About/AboutCard.tsx create mode 100644 plugins/pagerduty/src/components/About/SubHeader.tsx create mode 100644 plugins/pagerduty/src/components/About/VerticalIcon.tsx create mode 100644 plugins/pagerduty/src/components/About/index.ts diff --git a/plugins/pagerduty/src/components/About/AboutCard.tsx b/plugins/pagerduty/src/components/About/AboutCard.tsx new file mode 100644 index 0000000000000..7d5419908a02d --- /dev/null +++ b/plugins/pagerduty/src/components/About/AboutCard.tsx @@ -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 React from 'react'; +import { Card, CardContent, CardHeader, Divider } from '@material-ui/core'; +import { SubHeader } from './SubHeader'; +import { SubHeaderLink } from '../types'; + +type Props = { + title: string; + links: SubHeaderLink[]; + content: React.ReactNode; +}; + +export const AboutCard = ({ title, links, content }: Props) => ( + + } /> + + {content} + +); diff --git a/plugins/pagerduty/src/components/About/SubHeader.tsx b/plugins/pagerduty/src/components/About/SubHeader.tsx new file mode 100644 index 0000000000000..5e4ea96fe0b44 --- /dev/null +++ b/plugins/pagerduty/src/components/About/SubHeader.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 { VerticalIcon } from './VerticalIcon'; +import { SubHeaderLink } from '../types'; +import { makeStyles } from '@material-ui/core'; + +const useStyles = makeStyles(theme => ({ + links: { + margin: theme.spacing(2, 0), + display: 'grid', + gridAutoFlow: 'column', + gridAutoColumns: 'min-content', + gridGap: theme.spacing(3), + }, +})); + +type Props = { + links: SubHeaderLink[]; +}; + +export const SubHeader = ({ links }: Props) => { + const classes = useStyles(); + return ( +