feat(splunk-on-call-plugin): add components
This commit is contained in:
@@ -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;
|
||||
};
|
||||
Reference in New Issue
Block a user