feat(splunk-on-call-plugin): add components

This commit is contained in:
Remi
2021-02-04 23:44:29 +01:00
parent 76aa8e56c7
commit 398a90a592
5 changed files with 421 additions and 0 deletions
@@ -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 = () => (
<EmptyState
missing="info"
title="Missing or invalid SplunkOnCall Token"
description="The request to fetch data needs a valid token. See README for more details."
action={
<Button
color="primary"
variant="contained"
href="https://github.com/backstage/backstage/blob/master/plugins/splunk-on-call/README.md"
>
Read More
</Button>
}
/>
);
@@ -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';
@@ -0,0 +1,142 @@
/*
* 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, useCallback } from 'react';
import { useApi, Progress, HeaderIconLinkRow } from '@backstage/core';
import { Entity } from '@backstage/catalog-model';
import {
Button,
makeStyles,
Card,
CardHeader,
Divider,
CardContent,
} from '@material-ui/core';
import { Incidents } from './Incident';
import { EscalationPolicy } from './Escalation';
import { useAsync } from 'react-use';
import { Alert } from '@material-ui/lab';
import { splunkOnCallApiRef, UnauthorizedError } from '../api';
import AlarmAddIcon from '@material-ui/icons/AlarmAdd';
import { TriggerDialog } from './TriggerDialog';
import { MissingTokenError } from './Errors/MissingTokenError';
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_INTEGRATION_KEY = '';
export const isPluginApplicableToEntity = (entity: Entity) =>
Boolean(entity.metadata.annotations?.[SPLUNK_ON_CALL_INTEGRATION_KEY]);
type Props = {
entity: Entity;
};
export const SplunkOnCallCard = () => {
const classes = useStyles();
const api = useApi(splunkOnCallApiRef);
const [showDialog, setShowDialog] = useState<boolean>(false);
const [refreshIncidents, setRefreshIncidents] = useState<boolean>(false);
const handleRefresh = useCallback(() => {
setRefreshIncidents(x => !x);
}, []);
const handleDialog = useCallback(() => {
setShowDialog(x => !x);
}, []);
const { value: users, loading, error } = useAsync(async () => {
const users = await api.getUsers();
const usersHashMap = users.reduce(
(map: Record<string, User>, obj: User) => {
if (obj.username) {
map[obj.username] = obj;
}
return map;
},
{},
);
return { usersHashMap, userList: users };
});
if (error instanceof UnauthorizedError) {
return <MissingTokenError />;
}
if (error) {
return (
<Alert severity="error">
Error encountered while fetching information. {error.message}
</Alert>
);
}
if (loading) {
return <Progress />;
}
const triggerLink = {
label: 'Create Incident',
action: (
<Button
data-testid="trigger-button"
color="secondary"
onClick={handleDialog}
className={classes.triggerAlarm}
>
Create Incident
</Button>
),
icon: <AlarmAddIcon onClick={handleDialog} />,
};
return (
<Card>
<CardHeader
title="Incidents"
subheader={<HeaderIconLinkRow links={[triggerLink]} />}
/>
<Divider />
<CardContent>
<Incidents refreshIncidents={refreshIncidents} />
{users?.usersHashMap && <EscalationPolicy users={users.usersHashMap} />}
{users && (
<TriggerDialog
users={users?.userList}
showDialog={showDialog}
handleDialog={handleDialog}
onIncidentCreated={handleRefresh}
/>
)}
</CardContent>
</Card>
);
};
@@ -0,0 +1,72 @@
/*
* Copyright 2021 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { Grid, makeStyles } from '@material-ui/core';
import {
Content,
ContentHeader,
Page,
Header,
SupportButton,
} from '@backstage/core';
import { SplunkOnCallCard } from '..';
const useStyles = makeStyles(() => ({
overflowXScroll: {
overflowX: 'scroll',
},
}));
export type SplunkOnCallProps = {
title?: string;
subtitle?: string;
pageTitle?: string;
};
export const SplunkOnCallPage = ({
title,
subtitle,
pageTitle,
...props
}: SplunkOnCallProps): JSX.Element => {
const classes = useStyles();
return (
<Page themeId="tool">
<Header title={title} subtitle={subtitle} />
<Content className={classes.overflowXScroll}>
<ContentHeader title={pageTitle}>
<SupportButton>
This is used for visualizing the official guidelines of different
areas of software development such as languages, frameworks,
infrastructure and processes.
</SupportButton>
</ContentHeader>
<Grid container spacing={3} direction="row">
<Grid item xs={12}>
<SplunkOnCallCard {...props} />
</Grid>
</Grid>
</Content>
</Page>
);
};
SplunkOnCallPage.defaultProps = {
title: 'Splunk On-Call',
subtitle: 'Offers a simple way to identify incidents and escalation policies',
pageTitle: 'Welcome to Splunk On-Call in Backstage!',
};
@@ -0,0 +1,155 @@
/*
* 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 { IncidentTarget } from '../api/types';
export type Service = {
id: string;
name: string;
html_url: string;
integrationKey: string;
escalation_policy: {
id: string;
user: User;
};
};
export type Assignee = {
id: string;
summary: string;
html_url: string;
};
export type SubHeaderLink = {
title: string;
href?: string;
icon: React.ReactNode;
action?: React.ReactNode;
};
// GET Escalation Policies
// export type EscalationPolicyInfo = {
// policy: EscalationPolicySummary;
// team: Team;
// }
// export type EscalationPolicySummary = {
// name: string;
// slug: string;
// _selfUrl: string;
// }
// export type Team = {
// name: string;
// slug: string;
// }
// GET oncall
export type OnCall = {
team?: OnCallTeamResource;
oncallNow?: OnCallNowResource[];
};
export type OnCallTeamResource = {
name?: string;
slug?: string;
};
export type OnCallNowResource = {
escalationPolicy?: OnCallEscalationPolicyResource;
users?: OnCallUsersResource[];
};
export type OnCallEscalationPolicyResource = {
name?: string;
slug?: string;
};
export type OnCallUsersResource = {
onCalluser?: OnCallUser;
};
export type OnCallUser = {
username?: string;
};
// GET /api-public/v2/user
export type User = {
firstName?: string;
lastName?: string;
username?: string;
email?: string;
createdAt?: string;
passwordLastUpdated?: string;
verified?: boolean;
_selfUrl?: string;
};
// Incident creation
export type CreateIncidentRequest = {
summary: string;
details: string;
userName: string;
targets: IncidentTarget;
isMultiResponder: boolean;
};
// GET incidents
export type Incident = {
incidentNumber?: string;
startTime?: string;
currentPhase?: string;
alertCount?: number;
lastAlertTime?: string;
lastAlertId?: string;
entityId?: string;
host?: string;
service?: string;
pagedUsers?: string[];
pagedTeams?: string[];
entityDisplayName?: string;
pagedPolicies?: EscalationPolicyInfo[];
transitions?: IncidentTransition[];
firstAlertUuid?: string;
monitorName?: string;
monitorType?: string;
incidentLink?: string;
};
export type EscalationPolicyInfo = {
policy: EscalationPolicySummary;
team: Team;
};
export type IncidentTransition = {
name?: string;
at?: string;
by?: string;
message?: string;
manually?: boolean;
alertId?: string;
alertUrl?: string;
};
export type EscalationPolicySummary = {
name: string;
slug: string;
_selfUrl: string;
};
export type Team = {
name: string;
slug: string;
};