Merge pull request #14463 from Bonial-International-GmbH/pjungermann/splunk/use-routing-key
feat(splunk-on-call): use routing key instead of team name for triggered incidents
This commit is contained in:
@@ -13,11 +13,17 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { act, fireEvent, render, waitFor } from '@testing-library/react';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { ApiProvider, ConfigReader } from '@backstage/core-app-api';
|
||||
import {
|
||||
alertApiRef,
|
||||
ConfigApi,
|
||||
configApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { EntityProvider } from '@backstage/plugin-catalog-react';
|
||||
import { TestApiRegistry, wrapInTestApp } from '@backstage/test-utils';
|
||||
import { act, fireEvent, render, waitFor } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import {
|
||||
splunkOnCallApiRef,
|
||||
SplunkOnCallClient,
|
||||
@@ -33,13 +39,7 @@ import {
|
||||
MOCK_TEAM_NO_INCIDENTS,
|
||||
} from '../api/mocks';
|
||||
import { EntitySplunkOnCallCard } from './EntitySplunkOnCallCard';
|
||||
|
||||
import {
|
||||
alertApiRef,
|
||||
ConfigApi,
|
||||
configApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { ApiProvider, ConfigReader } from '@backstage/core-app-api';
|
||||
import { expectTriggeredIncident } from './TriggerDialog/testUtils';
|
||||
|
||||
const mockSplunkOnCallApi: Partial<SplunkOnCallClient> = {
|
||||
getUsers: async () => [],
|
||||
@@ -167,8 +167,10 @@ describe('SplunkOnCallCard', () => {
|
||||
mockSplunkOnCallApi.getTeams = jest
|
||||
.fn()
|
||||
.mockImplementation(async () => [MOCK_TEAM]);
|
||||
const mockTriggerAlarmFn = jest.fn();
|
||||
mockSplunkOnCallApi.incidentAction = mockTriggerAlarmFn;
|
||||
|
||||
const { getByText, queryByTestId } = render(
|
||||
const { getByRole, getByTestId, getByText, queryByTestId } = render(
|
||||
wrapInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<EntityProvider entity={mockEntityWithRoutingKeyAnnotation}>
|
||||
@@ -179,10 +181,23 @@ describe('SplunkOnCallCard', () => {
|
||||
);
|
||||
await waitFor(() => !queryByTestId('progress'));
|
||||
expect(getByText(`Team: ${MOCK_TEAM.name}`)).toBeInTheDocument();
|
||||
expect(getByText('Create Incident')).toBeInTheDocument();
|
||||
await waitFor(
|
||||
() => expect(getByText('test-incident')).toBeInTheDocument(),
|
||||
{ timeout: 2000 },
|
||||
);
|
||||
|
||||
const createIncidentButton = await getByText('Create Incident');
|
||||
await act(async () => {
|
||||
fireEvent.click(createIncidentButton);
|
||||
});
|
||||
expect(getByRole('dialog')).toBeInTheDocument();
|
||||
|
||||
await expectTriggeredIncident(
|
||||
'test-routing-key',
|
||||
getByTestId,
|
||||
mockTriggerAlarmFn,
|
||||
);
|
||||
});
|
||||
|
||||
it('Handles custom error for missing token', async () => {
|
||||
|
||||
@@ -29,11 +29,11 @@ import AlarmAddIcon from '@material-ui/icons/AlarmAdd';
|
||||
import WebIcon from '@material-ui/icons/Web';
|
||||
import { Alert } from '@material-ui/lab';
|
||||
import { splunkOnCallApiRef, UnauthorizedError } from '../api';
|
||||
import { MissingApiKeyOrApiIdError } from './Errors/MissingApiKeyOrApiIdError';
|
||||
import { MissingApiKeyOrApiIdError } from './Errors';
|
||||
import { EscalationPolicy } from './Escalation';
|
||||
import { Incidents } from './Incident';
|
||||
import { TriggerDialog } from './TriggerDialog';
|
||||
import { Team, User } from './types';
|
||||
import { RoutingKey, Team, User } from './types';
|
||||
import { configApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
|
||||
import {
|
||||
@@ -143,7 +143,7 @@ export const EntitySplunkOnCallCard = (props: EntitySplunkOnCallCardProps) => {
|
||||
}, []);
|
||||
|
||||
const {
|
||||
value: usersAndTeams,
|
||||
value: entityData,
|
||||
loading,
|
||||
error,
|
||||
} = useAsync(async () => {
|
||||
@@ -162,11 +162,15 @@ export const EntitySplunkOnCallCard = (props: EntitySplunkOnCallCardProps) => {
|
||||
teams.find(teamValue => teamValue.name === teamAnnotation),
|
||||
].filter(team => team !== undefined);
|
||||
|
||||
if (!foundTeams.length && routingKeyAnnotation) {
|
||||
let foundRoutingKey: RoutingKey | undefined;
|
||||
if (routingKeyAnnotation) {
|
||||
const routingKeys = await api.getRoutingKeys();
|
||||
const foundRoutingKey = routingKeys.find(
|
||||
foundRoutingKey = routingKeys.find(
|
||||
key => key.routingKey === routingKeyAnnotation,
|
||||
);
|
||||
}
|
||||
|
||||
if (!foundTeams.length) {
|
||||
foundTeams = foundRoutingKey
|
||||
? foundRoutingKey.targets
|
||||
.map(target => {
|
||||
@@ -179,7 +183,7 @@ export const EntitySplunkOnCallCard = (props: EntitySplunkOnCallCardProps) => {
|
||||
: [];
|
||||
}
|
||||
|
||||
return { usersHashMap, foundTeams };
|
||||
return { usersHashMap, foundTeams, foundRoutingKey };
|
||||
});
|
||||
|
||||
if (!teamAnnotation && !routingKeyAnnotation) {
|
||||
@@ -206,7 +210,7 @@ export const EntitySplunkOnCallCard = (props: EntitySplunkOnCallCardProps) => {
|
||||
return <Progress />;
|
||||
}
|
||||
|
||||
if (!usersAndTeams?.foundTeams || !usersAndTeams?.foundTeams.length) {
|
||||
if (!entityData?.foundTeams || !entityData?.foundTeams.length) {
|
||||
return (
|
||||
<InvalidAnnotation
|
||||
teamName={teamAnnotation}
|
||||
@@ -217,9 +221,11 @@ export const EntitySplunkOnCallCard = (props: EntitySplunkOnCallCardProps) => {
|
||||
|
||||
const Content = ({
|
||||
team,
|
||||
routingKey,
|
||||
usersHashMap,
|
||||
}: {
|
||||
team: Team | undefined;
|
||||
routingKey: RoutingKey | undefined;
|
||||
usersHashMap: any;
|
||||
}) => {
|
||||
const teamName = team?.name ?? '';
|
||||
@@ -235,7 +241,7 @@ export const EntitySplunkOnCallCard = (props: EntitySplunkOnCallCardProps) => {
|
||||
<EscalationPolicy team={teamName} users={usersHashMap} />
|
||||
)}
|
||||
<TriggerDialog
|
||||
team={teamName}
|
||||
routingKey={routingKey?.routingKey ?? teamName}
|
||||
showDialog={showDialog}
|
||||
handleDialog={handleDialog}
|
||||
onIncidentCreated={handleRefresh}
|
||||
@@ -257,7 +263,7 @@ export const EntitySplunkOnCallCard = (props: EntitySplunkOnCallCardProps) => {
|
||||
icon: <WebIcon />,
|
||||
};
|
||||
|
||||
const teams = usersAndTeams?.foundTeams || [];
|
||||
const teams = entityData?.foundTeams || [];
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -277,7 +283,11 @@ export const EntitySplunkOnCallCard = (props: EntitySplunkOnCallCardProps) => {
|
||||
/>
|
||||
<Divider />
|
||||
<CardContent>
|
||||
<Content team={team} usersHashMap={usersAndTeams?.usersHashMap} />
|
||||
<Content
|
||||
team={team}
|
||||
routingKey={entityData?.foundRoutingKey}
|
||||
usersHashMap={entityData?.usersHashMap}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { render, fireEvent, act } from '@testing-library/react';
|
||||
import { TestApiRegistry, wrapInTestApp } from '@backstage/test-utils';
|
||||
import { splunkOnCallApiRef } from '../../api';
|
||||
import { TriggerDialog } from './TriggerDialog';
|
||||
|
||||
import { ApiProvider } from '@backstage/core-app-api';
|
||||
import { alertApiRef } from '@backstage/core-plugin-api';
|
||||
import { TestApiRegistry, wrapInTestApp } from '@backstage/test-utils';
|
||||
import { render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { splunkOnCallApiRef } from '../../api';
|
||||
import { TriggerDialog } from './TriggerDialog';
|
||||
import { expectTriggeredIncident } from './testUtils';
|
||||
|
||||
describe('TriggerDialog', () => {
|
||||
const mockTriggerAlarmFn = jest.fn();
|
||||
@@ -38,7 +38,7 @@ describe('TriggerDialog', () => {
|
||||
wrapInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<TriggerDialog
|
||||
team="Example"
|
||||
routingKey="Example"
|
||||
showDialog
|
||||
handleDialog={() => {}}
|
||||
onIncidentCreated={() => {}}
|
||||
@@ -53,34 +53,6 @@ describe('TriggerDialog', () => {
|
||||
exact: false,
|
||||
}),
|
||||
).toBeInTheDocument();
|
||||
const incidentType = getByTestId('trigger-incident-type');
|
||||
const incidentId = getByTestId('trigger-incident-id');
|
||||
const incidentDisplayName = getByTestId('trigger-incident-displayName');
|
||||
const incidentMessage = getByTestId('trigger-incident-message');
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.change(incidentType, { target: { value: 'CRITICAL' } });
|
||||
fireEvent.change(incidentId, { target: { value: 'incident-id' } });
|
||||
fireEvent.change(incidentDisplayName, {
|
||||
target: { value: 'incident-display-name' },
|
||||
});
|
||||
fireEvent.change(incidentMessage, {
|
||||
target: { value: 'incident-message' },
|
||||
});
|
||||
});
|
||||
|
||||
// Trigger incident creation button
|
||||
const triggerButton = getByTestId('trigger-button');
|
||||
await act(async () => {
|
||||
fireEvent.click(triggerButton);
|
||||
});
|
||||
expect(mockTriggerAlarmFn).toHaveBeenCalled();
|
||||
expect(mockTriggerAlarmFn).toHaveBeenCalledWith({
|
||||
incidentType: 'CRITICAL',
|
||||
incidentId: 'incident-id',
|
||||
routingKey: 'Example',
|
||||
incidentDisplayName: 'incident-display-name',
|
||||
incidentMessage: 'incident-message',
|
||||
});
|
||||
await expectTriggeredIncident('Example', getByTestId, mockTriggerAlarmFn);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -35,11 +35,11 @@ import {
|
||||
import useAsyncFn from 'react-use/lib/useAsyncFn';
|
||||
import { splunkOnCallApiRef } from '../../api';
|
||||
import { Alert } from '@material-ui/lab';
|
||||
import { TriggerAlarmRequest } from '../../api/types';
|
||||
import { TriggerAlarmRequest } from '../../api';
|
||||
import { useApi, alertApiRef } from '@backstage/core-plugin-api';
|
||||
|
||||
type Props = {
|
||||
team: string;
|
||||
routingKey: string;
|
||||
showDialog: boolean;
|
||||
handleDialog: () => void;
|
||||
onIncidentCreated: () => void;
|
||||
@@ -76,7 +76,7 @@ const useStyles = makeStyles((theme: Theme) =>
|
||||
);
|
||||
|
||||
export const TriggerDialog = ({
|
||||
team,
|
||||
routingKey,
|
||||
showDialog,
|
||||
handleDialog,
|
||||
onIncidentCreated: onIncidentCreated,
|
||||
@@ -221,7 +221,7 @@ export const TriggerDialog = ({
|
||||
id="details"
|
||||
multiline
|
||||
fullWidth
|
||||
rows="2"
|
||||
minRows="2"
|
||||
margin="normal"
|
||||
label="Incident message"
|
||||
variant="outlined"
|
||||
@@ -242,7 +242,7 @@ export const TriggerDialog = ({
|
||||
variant="contained"
|
||||
onClick={() =>
|
||||
handleTriggerAlarm({
|
||||
routingKey: team,
|
||||
routingKey,
|
||||
incidentType,
|
||||
incidentDisplayName,
|
||||
incidentMessage,
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import {
|
||||
act,
|
||||
fireEvent,
|
||||
Matcher,
|
||||
MatcherOptions,
|
||||
} from '@testing-library/react';
|
||||
|
||||
export async function expectTriggeredIncident(
|
||||
routingKey: string,
|
||||
getByTestId: (
|
||||
id: Matcher,
|
||||
options?: MatcherOptions | undefined,
|
||||
) => HTMLElement,
|
||||
mockTriggerAlarmFn: any,
|
||||
): Promise<void> {
|
||||
const incidentType = getByTestId('trigger-incident-type');
|
||||
const incidentId = getByTestId('trigger-incident-id');
|
||||
const incidentDisplayName = getByTestId('trigger-incident-displayName');
|
||||
const incidentMessage = getByTestId('trigger-incident-message');
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.change(incidentType, { target: { value: 'CRITICAL' } });
|
||||
fireEvent.change(incidentId, { target: { value: 'incident-id' } });
|
||||
fireEvent.change(incidentDisplayName, {
|
||||
target: { value: 'incident-display-name' },
|
||||
});
|
||||
fireEvent.change(incidentMessage, {
|
||||
target: { value: 'incident-message' },
|
||||
});
|
||||
});
|
||||
|
||||
// Trigger incident creation button
|
||||
const triggerButton = getByTestId('trigger-button');
|
||||
await act(async () => {
|
||||
fireEvent.click(triggerButton);
|
||||
});
|
||||
|
||||
expect(mockTriggerAlarmFn).toHaveBeenCalled();
|
||||
expect(mockTriggerAlarmFn).toHaveBeenCalledWith({
|
||||
incidentType: 'CRITICAL',
|
||||
incidentId: 'incident-id',
|
||||
routingKey: routingKey,
|
||||
incidentDisplayName: 'incident-display-name',
|
||||
incidentMessage: 'incident-message',
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user