Configure knip and remove dead code (#3999)

# What this PR does
 - provide a way to detect dead code and remove it

## Checklist

- [x] Unit, integration, and e2e (if applicable) tests updated
- [x] Documentation added (or `pr:no public docs` PR label added if not
required)
- [x] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not
required)

---------

Co-authored-by: Joey Orlando <joseph.t.orlando@gmail.com>
This commit is contained in:
Dominik Broj 2024-03-06 09:06:03 +01:00 committed by GitHub
parent 5326d945e0
commit 3eaeabdddf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 1212 additions and 977 deletions

4
grafana-plugin/knip.json Normal file
View file

@ -0,0 +1,4 @@
{
"entry": ["src/PluginPage.tsx", "src/module.ts"],
"project": ["**/*.{js,ts,jsx,tsx}"]
}

View file

@ -25,7 +25,8 @@
"plop": "plop",
"setversion": "setversion",
"typecheck": "tsc --noEmit",
"typecheck:watch": "yarn typecheck --watch --preserveWatchOutput false"
"typecheck:watch": "yarn typecheck --watch --preserveWatchOutput false",
"find-dead-code": "knip"
},
"repository": {
"type": "git",
@ -89,6 +90,7 @@
"identity-obj-proxy": "3.0.0",
"jest": "^29.5.0",
"jest-environment-jsdom": "^29.5.0",
"knip": "^5.0.3",
"lint-staged": "^10.2.11",
"lodash-es": "^4.17.21",
"mailslurp-client": "^15.14.1",
@ -141,7 +143,7 @@
"change-case": "^4.1.1",
"circular-dependency-plugin": "^5.2.2",
"dayjs": "^1.11.5",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-import": "^2.29.1",
"immutability-helper": "^3.1.1",
"mobx": "6.12.0",
"mobx-react": "9.1.0",

View file

@ -1,4 +0,0 @@
export const getBackendSrv = () => ({
get: jest.fn(),
post: jest.fn(),
});

View file

@ -1,12 +0,0 @@
import { capitalCase } from 'change-case';
export function getLabelFromTemplateName(templateName: string, group: any) {
let arrayFromName = capitalCase(templateName).split(' ', 4);
let arrayWithNeededValues;
if (group === 'alert behaviour') {
arrayWithNeededValues = arrayFromName.slice(0, arrayFromName.lastIndexOf('Template'));
} else {
arrayWithNeededValues = arrayFromName.slice(1, arrayFromName.lastIndexOf('Template'));
}
return arrayWithNeededValues.join(' ');
}

View file

@ -1,85 +0,0 @@
import React from 'react';
import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { HorizontalGroup, getTagColorsFromName, useStyles2 } from '@grafana/ui';
import tinycolor2 from 'tinycolor2';
export interface LabelTagProps {
label: string;
value: string;
size?: LabelTagSize;
}
export type LabelTagSize = 'md' | 'sm';
export const LabelTag: React.FC<LabelTagProps> = (props: LabelTagProps) => {
const { label, value, size = 'sm' } = props;
const color = getLabelColor(label);
const styles = useStyles2((theme) => getStyles(theme, color, size));
return (
<div className={styles.wrapper} role="listitem">
<HorizontalGroup spacing="none">
<div className={styles.label}>{label ?? ''}</div>
<div className={styles.value}>{value}</div>
</HorizontalGroup>
</div>
);
};
function getLabelColor(input: string): string {
return getTagColorsFromName(input).color;
}
const getStyles = (theme: GrafanaTheme2, color?: string, size?: string) => {
const backgroundColor = color ?? theme.colors.secondary.main;
const borderColor = theme.isDark
? tinycolor2(backgroundColor).lighten(5).toString()
: tinycolor2(backgroundColor).darken(5).toString();
const valueBackgroundColor = theme.isDark
? tinycolor2(backgroundColor).darken(5).toString()
: tinycolor2(backgroundColor).lighten(5).toString();
const fontColor = color
? tinycolor2.mostReadable(backgroundColor, ['#000', '#fff']).toString()
: theme.colors.text.primary;
const padding =
size === 'md' ? `${theme.spacing(0.33)} ${theme.spacing(1)}` : `${theme.spacing(0.2)} ${theme.spacing(0.6)}`;
return {
wrapper: css`
color: ${fontColor};
font-size: ${theme.typography.bodySmall.fontSize};
border-radius: ${theme.shape.radius.default};
`,
label: css`
display: flex;
align-items: center;
color: inherit;
padding: ${padding};
background: ${backgroundColor};
border: solid 1px ${borderColor};
border-top-left-radius: ${theme.shape.radius.default};
border-bottom-left-radius: ${theme.shape.radius.default};
`,
value: css`
color: inherit;
padding: ${padding};
background: ${valueBackgroundColor};
border: solid 1px ${borderColor};
border-left: none;
border-top-right-radius: ${theme.shape.radius.default};
border-bottom-right-radius: ${theme.shape.radius.default};
`,
};
};

View file

@ -1,29 +0,0 @@
import React from 'react';
import { PENDING_COLOR, Tooltip, Icon } from '@grafana/ui';
import { Schedule } from 'models/schedule/schedule.types';
interface ScheduleWarningProps {
item: Schedule;
}
export const ScheduleWarning = (props: ScheduleWarningProps) => {
const { item } = props;
if (item.warnings.length > 0) {
const tooltipContent = (
<div>
{item.warnings.map((warning: string, key: number) => (
<p key={key}>{warning}</p>
))}
</div>
);
return (
<Tooltip placement="top" content={tooltipContent}>
<Icon style={{ color: PENDING_COLOR }} name="exclamation-triangle" />
</Tooltip>
);
}
return null;
};

View file

@ -1,119 +0,0 @@
import React, { ChangeEvent, useCallback } from 'react';
import { Field, Icon, Input, RadioButtonGroup } from '@grafana/ui';
import cn from 'classnames/bind';
import { ScheduleType } from 'models/schedule/schedule.types';
import styles from './SchedulesFilters.module.scss';
import { SchedulesFiltersType } from './SchedulesFilters.types';
const cx = cn.bind(styles);
interface SchedulesFiltersProps {
value: SchedulesFiltersType;
onChange: (filters: SchedulesFiltersType) => void;
}
export const SchedulesFilters = (props: SchedulesFiltersProps) => {
const { value, onChange } = props;
const onSearchTermChangeCallback = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
onChange({ ...value, searchTerm: e.currentTarget.value });
},
[value]
);
const handleMineChange = useCallback(
(mine) => {
onChange({ ...value, mine });
},
[value]
);
const handleStatusChange = useCallback(
(used) => {
onChange({ ...value, used });
},
[value]
);
const handleTypeChange = useCallback(
(type) => {
onChange({ ...value, type });
},
[value]
);
return (
<>
<div className={cx('left')}>
<Field label="Search by name">
<Input
autoFocus
className={cx('search')}
prefix={<Icon name="search" />}
placeholder="Search..."
value={value.searchTerm}
onChange={onSearchTermChangeCallback}
/>
</Field>
</div>
<div className={cx('right')}>
<Field label="Mine">
<RadioButtonGroup
options={[
{ label: 'All', value: undefined },
{
label: 'Mine',
value: true,
},
{
label: 'Not mine',
value: false,
},
]}
value={value.mine}
onChange={handleMineChange}
/>
</Field>
<Field label="Status">
<RadioButtonGroup
options={[
{ label: 'All', value: undefined },
{
label: 'Used in escalations',
value: true,
},
{ label: 'Unused', value: false },
]}
value={value.used}
onChange={handleStatusChange}
/>
</Field>
<Field label="Type">
<RadioButtonGroup
options={[
{ label: 'All', value: undefined },
{
label: 'Web',
value: ScheduleType.API,
},
{
label: 'ICal',
value: ScheduleType.Ical,
},
{
label: 'API',
value: ScheduleType.Calendar,
},
]}
value={value?.type}
onChange={handleTypeChange}
/>
</Field>
</div>
</>
);
};

View file

@ -1,8 +0,0 @@
import { ScheduleType } from 'models/schedule/schedule.types';
export interface SchedulesFiltersType {
searchTerm: string;
type: ScheduleType;
used: boolean | undefined;
mine: boolean | undefined;
}

View file

@ -1,8 +0,0 @@
.root {
display: inline-flex;
align-items: center;
& .search {
width: 320px;
}
}

View file

@ -1,42 +0,0 @@
import React, { ChangeEvent, useCallback } from 'react';
import { Icon, Input } from '@grafana/ui';
import cn from 'classnames/bind';
import styles from './SearchInput.module.scss';
const cx = cn.bind(styles);
interface SearchInputProps {
value: any;
onChange: (filters: any) => void;
className?: string;
}
export const SearchInput = (props: SearchInputProps) => {
const { value = { searchTerm: '' }, onChange, className } = props;
const onSearchTermChangeCallback = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
const filters = {
...value,
searchTerm: e.currentTarget.value,
};
onChange(filters);
},
[onChange, value]
);
return (
<div className={cx('root', className)}>
<Input
className={cx('search', 'control')}
placeholder="Search"
value={value.searchTerm}
onChange={onSearchTermChangeCallback}
suffix={<Icon name="search" />}
/>
</div>
);
};

View file

@ -1,48 +0,0 @@
// TODO: Refactor to reuse these tag styles across multiple pages
$score-primary: rgba(27, 133, 94, 0.15);
$score-warning: rgba(245, 183, 61, 0.18);
$score-danger: rgba(209, 14, 92, 0.15);
.root {
display: block;
}
.heartbeat {
width: 16px;
height: 16px;
}
.heartbeat-icon {
cursor: pointer;
}
.alertsInfoText {
font-size: 12px;
}
.tag {
font-size: 12px;
padding: 4px 10px 3px 10px;
&--danger {
background-color: $score-danger;
color: var(--tag-text-danger);
border: 1px solid var(--tag-border-danger);
}
&--warning {
background-color: $score-warning;
color: var(--tag-text-warning);
border: 1px solid var(--tag-border-warning);
}
}
.tag__icon {
&--danger {
color: var(--error-text-color);
}
&--warning {
color: var(--warning-text-color);
}
}

View file

@ -1,117 +0,0 @@
import React from 'react';
import { Badge, HorizontalGroup, IconButton, Tooltip, VerticalGroup } from '@grafana/ui';
import cn from 'classnames/bind';
import { observer } from 'mobx-react';
import CopyToClipboard from 'react-copy-to-clipboard';
import Emoji from 'react-emoji-render';
import { IntegrationLogo } from 'components/IntegrationLogo/IntegrationLogo';
import { PluginLink } from 'components/PluginLink/PluginLink';
import { Text } from 'components/Text/Text';
import { TeamName } from 'containers/TeamName/TeamName';
import { HeartGreenIcon, HeartRedIcon } from 'icons/Icons';
import { AlertReceiveChannelHelper } from 'models/alert_receive_channel/alert_receive_channel.helpers';
import { ApiSchemas } from 'network/oncall-api/api.types';
import { useStore } from 'state/useStore';
import styles from './AlertReceiveChannelCard.module.scss';
const cx = cn.bind(styles);
interface AlertReceiveChannelCardProps {
id: ApiSchemas['AlertReceiveChannel']['id'];
onShowHeartbeatModal: () => void;
}
export const AlertReceiveChannelCard = observer((props: AlertReceiveChannelCardProps) => {
const { id, onShowHeartbeatModal } = props;
const store = useStore();
const { alertReceiveChannelStore, heartbeatStore, grafanaTeamStore } = store;
const alertReceiveChannel = alertReceiveChannelStore.items[id];
const alertReceiveChannelCounter = alertReceiveChannelStore.counters[id];
const heartbeatId = alertReceiveChannelStore.alertReceiveChannelToHeartbeat[alertReceiveChannel.id];
const heartbeat = heartbeatStore.items[heartbeatId];
const heartbeatStatus = Boolean(heartbeat?.status);
const integration = AlertReceiveChannelHelper.getIntegrationSelectOption(
alertReceiveChannelStore,
alertReceiveChannel
);
return (
<div className={cx('root')}>
<HorizontalGroup align="flex-start">
<div className={cx('heartbeat')}>
{alertReceiveChannel.is_available_for_integration_heartbeat && (
<Tooltip
placement="top"
content={
heartbeat
? `Last heartbeat: ${heartbeat.last_heartbeat_time_verbal || 'never'}`
: 'Click to setup heartbeat'
}
>
<div className={cx('heartbeat-icon')} onClick={onShowHeartbeatModal}>
{heartbeatStatus ? <HeartGreenIcon /> : <HeartRedIcon />}
</div>
</Tooltip>
)}
</div>
<VerticalGroup spacing="xs">
<HorizontalGroup>
<Text type="primary" size="medium">
<Emoji className={cx('title')} text={alertReceiveChannel.verbal_name} />
</Text>
<CopyToClipboard text={alertReceiveChannel.id}>
<IconButton
variant="primary"
tooltip={
<div>
ID {alertReceiveChannel.id}
<br />
(click to copy ID to clipboard)
</div>
}
tooltipPlacement="top"
name="info-circle"
/>
</CopyToClipboard>
{alertReceiveChannelCounter && (
<PluginLink
query={{ page: 'alert-groups', integration: alertReceiveChannel.id }}
className={cx('alertsInfoText')}
>
<Badge
text={alertReceiveChannelCounter?.alerts_count + '/' + alertReceiveChannelCounter?.alert_groups_count}
color={'blue'}
tooltip={
alertReceiveChannelCounter?.alerts_count +
' alert' +
(alertReceiveChannelCounter?.alerts_count === 1 ? '' : 's') +
' in ' +
alertReceiveChannelCounter?.alert_groups_count +
' alert group' +
(alertReceiveChannelCounter?.alert_groups_count === 1 ? '' : 's')
}
/>
</PluginLink>
)}
</HorizontalGroup>
<HorizontalGroup spacing="xs">
<IntegrationLogo scale={0.08} integration={integration} />
<Text type="secondary" size="small">
{integration?.display_name}
</Text>
</HorizontalGroup>
<TeamName team={grafanaTeamStore.items[alertReceiveChannel.team]} size="small" />
</VerticalGroup>
</HorizontalGroup>
</div>
);
});

View file

@ -1,84 +0,0 @@
import React from 'react';
import { Icon, Label, Tooltip } from '@grafana/ui';
import { FormItemType } from 'components/GForm/GForm.types';
import { GrafanaTeamStore } from 'models/grafana_team/grafana_team';
import { generateAssignToTeamInputDescription } from 'utils/consts';
export const getForm = (grafanaTeamStore: GrafanaTeamStore) => ({
name: 'Integration',
fields: [
{
label: 'Name',
name: 'verbal_name',
type: FormItemType.Input,
placeholder: 'Integration Name',
validation: { required: true },
},
{
label: 'Description',
name: 'description_short',
type: FormItemType.TextArea,
placeholder: 'Integration Description',
},
{
name: 'team',
label: (
<Label>
<span>Assign to team</span>&nbsp;
<Tooltip content={generateAssignToTeamInputDescription('Integrations')} placement="right">
<Icon name="info-circle" />
</Tooltip>
</Label>
),
type: FormItemType.GSelect,
extra: {
items: grafanaTeamStore.items,
fetchItemsFn: grafanaTeamStore.updateItems,
fetchItemFn: grafanaTeamStore.fetchItemById,
getSearchResult: grafanaTeamStore.getSearchResult,
displayField: 'name',
valueField: 'id',
showSearch: true,
allowClear: true,
},
},
{
name: '', // this will skip field it in the submitted form
label: 'Bi-directional Integration',
type: FormItemType.PlainLabel,
isHidden: (data) => data.integration !== 'servicenow',
},
{
name: 'servicenow_url',
label: 'Service Now URL',
type: FormItemType.Input,
isHidden: (data) => data.integration !== 'servicenow',
},
{
name: 'authorization_header',
label: 'Authorization Header',
type: FormItemType.Input,
isHidden: (data) => data.integration !== 'servicenow',
},
{
name: 'alert_manager',
type: FormItemType.Other,
},
{
name: 'contact_point',
type: FormItemType.Other,
},
{
name: 'is_existing',
type: FormItemType.Other,
},
{
name: 'alerting',
type: FormItemType.Other,
render: true,
},
],
});

View file

@ -1,82 +0,0 @@
import React, { useCallback, useMemo } from 'react';
import { DateTime, dateTime, SelectableValue } from '@grafana/data';
import { Select, TimeOfDayPicker, VerticalGroup } from '@grafana/ui';
import cn from 'classnames/bind';
import dayjs from 'dayjs';
import { Text } from 'components/Text/Text';
import { toDate } from 'containers/RotationForm/RotationForm.helpers';
import { Timezone } from 'models/timezone/timezone.types';
import { useStore } from 'state/useStore';
import styles from 'containers/RotationForm/RotationForm.module.css';
const cx = cn.bind(styles);
interface WeekdayTimePickerProps {
value: dayjs.Dayjs;
timezone: Timezone;
onWeekDayChange: (value: number) => void;
onTimeChange: (hh: number, mm: number, ss: number) => void;
disabled?: boolean;
hideWeekday?: boolean;
weekStart: string;
error?: string[];
}
const weekdays = ['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'];
export const WeekdayTimePicker = (props: WeekdayTimePickerProps) => {
const { value: propValue, timezone, hideWeekday, disabled, weekStart, onWeekDayChange, onTimeChange, error } = props;
const { scheduleStore } = useStore();
const value = useMemo(() => toDate(propValue, timezone), [propValue, timezone]);
const options = useMemo(() => {
const index = scheduleStore.byDayOptions.findIndex(
({ display_name }) => display_name.toLowerCase() === weekStart.toLowerCase()
);
return [...scheduleStore.byDayOptions.slice(index), ...scheduleStore.byDayOptions.slice(0, index)].map(
({ display_name, value }) => ({
label: display_name.substring(0, 3),
value: weekdays.findIndex((val) => val === value),
})
);
}, [weekStart]);
const handleWeekDayChange = useCallback(
({ value: newValue }: SelectableValue) => {
const oldIndex = options.findIndex(({ value: optionValue }) => optionValue === value.getDay());
const newIndex = options.findIndex(({ value: optionValue }) => optionValue === newValue);
onWeekDayChange(newIndex - oldIndex);
},
[options, value]
);
const handleTimeChange = useCallback(
(newMoment: DateTime) => {
// @ts-ignore actually new newMoment has second method
onTimeChange(newMoment.hour(), newMoment.minute(), newMoment.second());
},
[value]
);
return (
<VerticalGroup>
<div style={{ display: 'flex', flexWrap: 'nowrap', gap: '8px' }}>
{!hideWeekday && (
<div style={{ width: '58%' }} className={cx({ 'control--error': Boolean(error) })}>
<Select options={options} onChange={handleWeekDayChange} value={value.getDay()} />
</div>
)}
<div style={{ width: hideWeekday ? '100%' : '42%' }} className={cx({ 'control--error': Boolean(error) })}>
<TimeOfDayPicker disabled={disabled} value={dateTime(value)} onChange={handleTimeChange} />
</div>
</div>
{error && <Text type="danger">{error}</Text>}
</VerticalGroup>
);
};

View file

@ -1,11 +0,0 @@
.slack-infoblock {
width: 725px;
}
.slack-infoblock input {
color: var(--primary-text-link);
}
.slack-icon {
width: 60px;
}

View file

@ -1,70 +0,0 @@
import React, { FC } from 'react';
import { Button, VerticalGroup, Icon, Field, Input } from '@grafana/ui';
import cn from 'classnames/bind';
import { observer } from 'mobx-react';
import { Block } from 'components/GBlock/Block';
import { Text } from 'components/Text/Text';
import { SlackNewIcon } from 'icons/Icons';
import { DOCS_SLACK_SETUP } from 'utils/consts';
import styles from './SlackInstructions.module.css';
const cx = cn.bind(styles);
interface SlackInstructionsProps {}
/* This component will be used when we will work on moving ENV variables to chat-ops, but we need to do work on backend side first */
export const SlackInstructions: FC<SlackInstructionsProps> = observer(() => {
return (
<div>
<VerticalGroup spacing="lg">
<Text.Title level={2}>Connect Slack workspace</Text.Title>
<Block bordered withBackground className={cx('slack-infoblock')}>
<VerticalGroup align="center" spacing="lg">
<SlackNewIcon />
<Text>You can manage alert groups in your Slack workspace. </Text>
<Text>Before start you need to connect your Slack bot to Grafana OnCall.</Text>
<Text type="secondary">
For bot creating instructions and additional information please read{' '}
<a href={DOCS_SLACK_SETUP} target="_blank" rel="noreferrer">
<Text type="link">our documentation</Text>
</a>
</Text>{' '}
</VerticalGroup>
</Block>
<Text>Setup environment</Text>
<Text>
Create OnCall Slack bot using{' '}
<a href={DOCS_SLACK_SETUP} target="_blank" rel="noreferrer">
<Text type="link">our instructions</Text>
</a>{' '}
and fill out app credentials below.
</Text>
<div className={cx('slack-infoblock')}>
<Field label="App ID">
<Input id="appId" onChange={() => {}} defaultValue={'appId'} />
</Field>
<Field label="Client secret">
<Input id="clientsecret" onChange={() => {}} defaultValue={'clientsecret'} />
</Field>
<Field label="Signing secret">
<Input id="signingsecret" onChange={() => {}} defaultValue={'signingsecret'} />
</Field>
<Field label="Redirect host">
<Input id="host" onChange={() => {}} defaultValue={'https://'} />
</Field>
</div>
<Block bordered withBackground className={cx('slack-infoblock')}>
<Text type="secondary">
<Icon name="info-circle" /> Your host to Slack must start with https://” and be publicly available (meaning
that it can be reached by Slack servers). If your host is private or local, you can use redirecting services
like Ngrok.
</Text>
</Block>
<Button onClick={() => {}}>Save environment</Button>
</VerticalGroup>
</div>
);
});

View file

@ -1,61 +0,0 @@
export const WebhooksDefaultAlertGroup = {
pk: '0',
event: {
type: 'resolve',
time: '2023-04-19T21:59:21.714058+00:00',
},
user: {
id: 'UVMX6YI9VY9PV',
username: 'admin',
email: 'admin@localhost',
},
alert_group: {
id: 'I6HNZGUFG4K11',
integration_id: 'CZ7URAT4V3QF2',
route_id: 'RKHXJKVZYYVST',
alerts_count: 1,
state: 'resolved',
created_at: '2023-04-19T21:53:48.231148Z',
resolved_at: '2023-04-19T21:59:21.714058Z',
acknowledged_at: '2023-04-19T21:54:39.029347Z',
title: 'Incident',
permalinks: {
slack: null,
telegram: null,
web: 'https://**********.grafana.net/a/grafana-oncall-app/alert-groups/I6HNZGUFG4K11',
},
},
alert_group_id: 'I6HNZGUFG4K11',
alert_payload: {
endsAt: '0001-01-01T00:00:00Z',
labels: {
region: 'eu-1',
alertname: 'TestAlert',
},
status: 'firing',
startsAt: '2018-12-25T15:47:47.377363608Z',
annotations: {
description: 'This alert was sent by user for the demonstration purposes',
},
generatorURL: '',
},
integration: {
id: 'CZ7URAT4V3QF2',
type: 'webhook',
name: 'Main Integration - Webhook',
team: 'Webhooks Demo',
},
notified_users: [],
users_to_be_notified: [],
responses: {
WHP936BM1GPVHQ: {
id: '7Qw7TbPmzppRnhLvK3AdkQ',
created_at: '15:53:50',
status: 'new',
content: {
message: 'Ticket created!',
region: 'eu',
},
},
},
};

View file

@ -1,29 +0,0 @@
import { useEffect } from 'react';
import { observer } from 'mobx-react';
import { getUserNotificationsSummary } from 'models/user/user.helpers';
import { User } from 'models/user/user.types';
import { useStore } from 'state/useStore';
interface UserSummaryProps {
id: User['pk'];
}
export const UserSummary = observer((props: UserSummaryProps) => {
const { id } = props;
const store = useStore();
const { userStore } = store;
useEffect(() => {
if (!userStore.items[id]) {
userStore.loadUser(id);
}
});
const user = userStore.items[id];
return getUserNotificationsSummary(user);
});

View file

@ -1,81 +0,0 @@
import { EscalationChainsPage } from 'pages/escalation-chains/EscalationChains';
import { IncidentPage } from 'pages/incident/Incident';
import { IncidentsPage } from 'pages/incidents/Incidents';
import { Insights } from 'pages/insights/Insights';
import { OutgoingWebhooks } from 'pages/outgoing_webhooks/OutgoingWebhooks';
import { SchedulePage } from 'pages/schedule/Schedule';
import { SchedulesPage } from 'pages/schedules/Schedules';
import { SettingsPage } from 'pages/settings/SettingsPage';
import { ChatOpsPage } from 'pages/settings/tabs/ChatOps/ChatOps';
import { CloudPage } from 'pages/settings/tabs/Cloud/CloudPage';
import LiveSettingsPage from 'pages/settings/tabs/LiveSettings/LiveSettingsPage';
import { UsersPage } from 'pages/users/Users';
import { IntegrationsPage } from './integrations/Integrations';
export interface NavRoute {
id: string;
component: (props?: any) => JSX.Element;
}
export const routes: { [id: string]: NavRoute } = [
{
component: IncidentsPage,
id: 'incidents',
},
{
component: IncidentPage,
id: 'incident',
},
{
component: UsersPage,
id: 'users',
},
{
component: IntegrationsPage,
id: 'integrations',
},
{
component: EscalationChainsPage,
id: 'escalations',
},
{
component: SchedulesPage,
id: 'schedules',
},
{
component: SchedulePage,
id: 'schedule',
},
{
component: ChatOpsPage,
id: 'chat-ops',
},
{
component: OutgoingWebhooks,
id: 'outgoing_webhooks',
},
{
component: SettingsPage,
id: 'settings',
},
{
component: LiveSettingsPage,
id: 'live-settings',
},
{
component: CloudPage,
id: 'cloud',
},
{
component: Insights,
id: 'insights',
},
].reduce((prev, current) => {
prev[current.id] = {
id: current.id,
component: current.component,
};
return prev;
}, {});

File diff suppressed because it is too large Load diff