enable 'read only' mode Splunk OnCall option

This exposes a `splunkOnCall.readOnly` configuration
option to address issue #9634

Signed-off-by: Mike Ball <mikedball@gmail.com>
This commit is contained in:
Mike Ball
2022-02-17 16:31:47 -05:00
parent 209fd128e6
commit 15884c25f6
5 changed files with 104 additions and 10 deletions
@@ -53,6 +53,14 @@ const mockSplunkOnCallApi: Partial<SplunkOnCallClient> = {
const configApi: ConfigApi = new ConfigReader({
splunkOnCall: {
eventsRestEndpoint: 'EXAMPLE_REST_ENDPOINT',
readOnly: false,
},
});
const readOnlyConfigApi: ConfigApi = new ConfigReader({
splunkOnCall: {
eventsRestEndpoint: 'EXAMPLE_REST_ENDPOINT',
readOnly: true,
},
});
@@ -62,6 +70,12 @@ const apis = TestApiRegistry.from(
[alertApiRef, {}],
);
const readOnlyApis = TestApiRegistry.from(
[splunkOnCallApiRef, mockSplunkOnCallApi],
[configApiRef, readOnlyConfigApi],
[alertApiRef, {}],
);
const mockEntity = {
apiVersion: 'backstage.io/v1alpha1',
kind: 'Component',
@@ -131,6 +145,32 @@ describe('SplunkOnCallCard', () => {
expect(getByText('Empty escalation policy')).toBeInTheDocument();
});
it('does not render a "Create incident" link in read only mode', async () => {
mockSplunkOnCallApi.getUsers = jest
.fn()
.mockImplementationOnce(async () => [MOCKED_USER]);
mockSplunkOnCallApi.getTeams = jest
.fn()
.mockImplementation(async () => [MOCK_TEAM_NO_INCIDENTS]);
const { getByText, queryByTestId } = render(
wrapInTestApp(
<ApiProvider apis={readOnlyApis}>
<EntityProvider entity={mockEntityNoIncidents}>
<EntitySplunkOnCallCard />
</EntityProvider>
</ApiProvider>,
),
);
await waitFor(() => !queryByTestId('progress'));
expect(() => getByText('Create Incident')).toThrow();
await waitFor(
() => expect(getByText('Nice! No incidents found!')).toBeInTheDocument(),
{ timeout: 2000 },
);
expect(getByText('Empty escalation policy')).toBeInTheDocument();
});
it('handles a "splunk.com/on-call-routing-key" annotation', async () => {
mockSplunkOnCallApi.getUsers = jest
.fn()
@@ -124,7 +124,9 @@ export const EntitySplunkOnCallCard = () => {
: undefined;
const eventsRestEndpoint =
config.getOptionalString('splunkOnCall.eventsRestEndpoint') || null;
config.getOptionalString('splunkOnCall.eventsRestEndpoint') || true;
const readOnly = config.getOptionalBoolean('splunkOnCall.readOnly') || false;
const handleRefresh = useCallback(() => {
setRefreshIncidents(x => !x);
@@ -218,7 +220,11 @@ export const EntitySplunkOnCallCard = () => {
return (
<>
<Incidents team={teamName} refreshIncidents={refreshIncidents} />
<Incidents
readOnly={readOnly}
team={teamName}
refreshIncidents={refreshIncidents}
/>
{usersHashMap && team && (
<EscalationPolicy team={teamName} users={usersHashMap} />
)}
@@ -259,7 +265,7 @@ export const EntitySplunkOnCallCard = () => {
</Typography>,
<HeaderIconLinkRow
key="incident_trigger"
links={[serviceLink, triggerLink]}
links={!readOnly ? [serviceLink, triggerLink] : [serviceLink]}
/>,
]}
/>
@@ -64,6 +64,7 @@ type Props = {
team: string;
incident: Incident;
onIncidentAction: () => void;
readOnly: boolean;
};
const IncidentPhaseStatus = ({
@@ -135,6 +136,7 @@ const IncidentAction = ({
export const IncidentListItem = ({
incident,
readOnly,
onIncidentAction,
team,
}: Props) => {
@@ -241,12 +243,16 @@ export const IncidentListItem = ({
{incident.incidentLink && incident.incidentNumber && (
<ListItemSecondaryAction>
<IncidentAction
currentPhase={incident.currentPhase || ''}
incidentId={incident.entityId}
resolveAction={handleResolveIncident}
acknowledgeAction={handleAcknowledgeIncident}
/>
{!readOnly ? (
<IncidentAction
currentPhase={incident.currentPhase || ''}
incidentId={incident.entityId}
resolveAction={handleResolveIncident}
acknowledgeAction={handleAcknowledgeIncident}
/>
) : (
''
)}
<Tooltip title="View in Splunk On-Call" placement="top">
<IconButton
href={incident.incidentLink}
@@ -90,6 +90,46 @@ describe('Incidents', () => {
expect(getAllByTitle('View in Splunk On-Call').length).toEqual(1);
});
it('does not render incident action buttons in read only mode', async () => {
mockSplunkOnCallApi.getIncidents.mockResolvedValue([MOCK_INCIDENT]);
mockSplunkOnCallApi.getTeams.mockResolvedValue([MOCK_TEAM]);
const {
getByText,
getByTitle,
getAllByTitle,
getByLabelText,
queryByTestId,
} = render(
wrapInTestApp(
<ApiProvider apis={apis}>
<Incidents readOnly team="test" refreshIncidents={false} />
</ApiProvider>,
),
);
await waitFor(() => !queryByTestId('progress'));
await waitFor(
() =>
expect(
getByText('user', {
exact: false,
}),
).toBeInTheDocument(),
{ timeout: 2000 },
);
expect(getByText('test-incident')).toBeInTheDocument();
expect(getByLabelText('Status warning')).toBeInTheDocument();
expect(() => getAllByTitle('Acknowledge')).toThrow(
'Unable to find an element with the title: Acknowledge.',
);
expect(() => getAllByTitle('Resolve')).toThrow(
'Unable to find an element with the title: Resolve.',
);
// assert links, mailto and hrefs, date calculation
expect(getAllByTitle('View in Splunk On-Call').length).toEqual(1);
});
it('Handle errors', async () => {
mockSplunkOnCallApi.getIncidents.mockRejectedValueOnce(
new Error('Error occurred'),
@@ -49,9 +49,10 @@ const useStyles = makeStyles((theme: Theme) =>
type Props = {
refreshIncidents: boolean;
team: string;
readOnly: boolean;
};
export const Incidents = ({ refreshIncidents, team }: Props) => {
export const Incidents = ({ readOnly, refreshIncidents, team }: Props) => {
const classes = useStyles();
const api = useApi(splunkOnCallApiRef);
@@ -108,6 +109,7 @@ export const Incidents = ({ refreshIncidents, team }: Props) => {
key={index}
team={team}
incident={incident}
readOnly={readOnly}
/>
))
)}