diff --git a/.changeset/sour-insects-marry.md b/.changeset/sour-insects-marry.md new file mode 100644 index 0000000000..27e72977d5 --- /dev/null +++ b/.changeset/sour-insects-marry.md @@ -0,0 +1,31 @@ +--- +'@backstage/core': minor +'@backstage/plugin-pagerduty': patch +--- + +- Adds onClick and other props to IconLinkVertical; +- Allows TriggerButton component to render when pager duty key is missing; +- Refactors TriggerButton and PagerDutyCard not to have shared state; +- Removes the `action` prop of the IconLinkVertical component while adding `onClick`. + + Instead of having an action including a button with onClick, now the whole component can be clickable making it easier to implement and having a better UX. + + Before: + + ```ts + const myLink: IconLinkVerticalProps = { + label: 'Click me', + action: - + {integrationKey && ( + + )} ); } diff --git a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx index 8073ab44b5..c710aae0fa 100644 --- a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx +++ b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx @@ -14,8 +14,8 @@ * limitations under the License. */ import React from 'react'; -import { render, fireEvent, act } from '@testing-library/react'; -import { wrapInTestApp } from '@backstage/test-utils'; +import { fireEvent, act } from '@testing-library/react'; +import { renderInTestApp } from '@backstage/test-utils'; import { ApiRegistry, alertApiRef, @@ -26,6 +26,7 @@ import { } from '@backstage/core'; import { pagerDutyApiRef } from '../../api'; import { Entity } from '@backstage/catalog-model'; +import { EntityProvider } from '@backstage/plugin-catalog-react'; import { TriggerDialog } from './TriggerDialog'; describe('TriggerDialog', () => { @@ -62,18 +63,16 @@ describe('TriggerDialog', () => { }, }; - const { getByText, getByRole, getByTestId } = render( - wrapInTestApp( - + const { getByText, getByRole, getByTestId } = await renderInTestApp( + + {}} - name={entity.metadata.name} - integrationKey="abc123" onIncidentCreated={() => {}} /> - , - ), + + , ); expect(getByRole('dialog')).toBeInTheDocument(); diff --git a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx index c2084a41ed..3d7d4a19b4 100644 --- a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx +++ b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx @@ -29,22 +29,20 @@ import { useApi, alertApiRef, identityApiRef } from '@backstage/core'; import { useAsyncFn } from 'react-use'; import { pagerDutyApiRef } from '../../api'; import { Alert } from '@material-ui/lab'; +import { usePagerdutyEntity } from '../../hooks'; type Props = { - name: string; - integrationKey: string; showDialog: boolean; handleDialog: () => void; onIncidentCreated?: () => void; }; export const TriggerDialog = ({ - name, - integrationKey, showDialog, handleDialog, onIncidentCreated: onIncidentCreated, }: Props) => { + const { name, integrationKey } = usePagerdutyEntity(); const alertApi = useApi(alertApiRef); const identityApi = useApi(identityApiRef); const userName = identityApi.getUserId(); @@ -54,7 +52,7 @@ export const TriggerDialog = ({ const [{ value, loading, error }, handleTriggerAlarm] = useAsyncFn( async (descriptions: string) => await api.triggerAlarm({ - integrationKey, + integrationKey: integrationKey as string, source: window.location.toString(), description: descriptions, userName, diff --git a/plugins/pagerduty/src/hooks/index.ts b/plugins/pagerduty/src/hooks/index.ts new file mode 100644 index 0000000000..6ba9c09699 --- /dev/null +++ b/plugins/pagerduty/src/hooks/index.ts @@ -0,0 +1,28 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useEntity } from '@backstage/plugin-catalog-react'; + +import { PAGERDUTY_INTEGRATION_KEY } from '../components/constants'; + +export function usePagerdutyEntity() { + const { entity } = useEntity(); + const integrationKey = + entity.metadata.annotations?.[PAGERDUTY_INTEGRATION_KEY]; + const name = entity.metadata.name; + + return { integrationKey, name }; +} diff --git a/plugins/splunk-on-call/src/components/SplunkOnCallCard.test.tsx b/plugins/splunk-on-call/src/components/SplunkOnCallCard.test.tsx index b1280ab21c..0cbaab649e 100644 --- a/plugins/splunk-on-call/src/components/SplunkOnCallCard.test.tsx +++ b/plugins/splunk-on-call/src/components/SplunkOnCallCard.test.tsx @@ -132,12 +132,13 @@ describe('SplunkOnCallCard', () => { ), ).toBeInTheDocument(); }); + it('opens the dialog when trigger button is clicked', async () => { mockSplunkOnCallApi.getUsers = jest .fn() .mockImplementationOnce(async () => [MOCKED_USER]); - const { getByText, queryByTestId, getByTestId, getByRole } = render( + const { getByText, queryByTestId, getByRole } = render( wrapInTestApp( @@ -146,7 +147,7 @@ describe('SplunkOnCallCard', () => { ); await waitFor(() => !queryByTestId('progress')); expect(getByText('Create Incident')).toBeInTheDocument(); - const triggerButton = getByTestId('trigger-button'); + const triggerButton = getByText('Create Incident'); await act(async () => { fireEvent.click(triggerButton); }); diff --git a/plugins/splunk-on-call/src/components/SplunkOnCallCard.tsx b/plugins/splunk-on-call/src/components/SplunkOnCallCard.tsx index 84d94d60d3..1e28be1483 100644 --- a/plugins/splunk-on-call/src/components/SplunkOnCallCard.tsx +++ b/plugins/splunk-on-call/src/components/SplunkOnCallCard.tsx @@ -21,11 +21,10 @@ import { MissingAnnotationEmptyState, configApiRef, EmptyState, + IconLinkVerticalProps, } from '@backstage/core'; import { Entity } from '@backstage/catalog-model'; import { - Button, - makeStyles, Card, CardHeader, Divider, @@ -42,22 +41,6 @@ import { TriggerDialog } from './TriggerDialog'; import { MissingApiKeyOrApiIdError } from './Errors/MissingApiKeyOrApiIdError'; import { User } from './types'; -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', - }, - }, -}); - export const SPLUNK_ON_CALL_TEAM = 'splunk.com/on-call-team'; export const MissingTeamAnnotation = () => ( @@ -82,7 +65,6 @@ type Props = { }; export const SplunkOnCallCard = ({ entity }: Props) => { - const classes = useStyles(); const config = useApi(configApiRef); const api = useApi(splunkOnCallApiRef); const [showDialog, setShowDialog] = useState(false); @@ -159,19 +141,11 @@ export const SplunkOnCallCard = ({ entity }: Props) => { ); }; - const triggerLink = { + const triggerLink: IconLinkVerticalProps = { label: 'Create Incident', - action: ( - - ), - icon: , + onClick: handleDialog, + color: 'secondary', + icon: , }; return (