grafana/public/app/features/alerting/unified/components/amroutes/AmSpecificRouting.tsx
Gilles De Mey 702fd0f9e8
Docs: Update wording / text and copy (#61868)
Co-authored-by: brendamuir <100768211+brendamuir@users.noreply.github.com>
2023-01-30 15:26:21 +00:00

216 lines
6.7 KiB
TypeScript

import { css } from '@emotion/css';
import React, { FC, useState } from 'react';
import { useDebounce } from 'react-use';
import { GrafanaTheme2 } from '@grafana/data';
import { Button, Icon, Input, Label, useStyles2 } from '@grafana/ui';
import { contextSrv } from 'app/core/services/context_srv';
import { Authorize } from '../../components/Authorize';
import { useURLSearchParams } from '../../hooks/useURLSearchParams';
import { FormAmRoute } from '../../types/amroutes';
import { getNotificationsPermissions } from '../../utils/access-control';
import { emptyArrayFieldMatcher, emptyRoute } from '../../utils/amroutes';
import { getNotificationPoliciesFilters } from '../../utils/misc';
import { EmptyArea } from '../EmptyArea';
import { EmptyAreaWithCTA } from '../EmptyAreaWithCTA';
import { MatcherFilter } from '../alert-groups/MatcherFilter';
import { AmRouteReceiver } from '../receivers/grafanaAppReceivers/types';
import { AmRoutesTable } from './AmRoutesTable';
export interface AmSpecificRoutingProps {
alertManagerSourceName: string;
onChange: (routes: FormAmRoute) => void;
onRootRouteEdit: () => void;
receivers: AmRouteReceiver[];
routes: FormAmRoute;
readOnly?: boolean;
}
interface Filters {
queryString?: string;
contactPoint?: string;
}
export const AmSpecificRouting: FC<AmSpecificRoutingProps> = ({
alertManagerSourceName,
onChange,
onRootRouteEdit,
receivers,
routes,
readOnly = false,
}) => {
const [actualRoutes, setActualRoutes] = useState([...routes.routes]);
const [isAddMode, setIsAddMode] = useState(false);
const permissions = getNotificationsPermissions(alertManagerSourceName);
const canCreateNotifications = contextSrv.hasPermission(permissions.create);
const [searchParams, setSearchParams] = useURLSearchParams();
const { queryString, contactPoint } = getNotificationPoliciesFilters(searchParams);
const [filters, setFilters] = useState<Filters>({ queryString, contactPoint });
useDebounce(
() => {
setSearchParams({ queryString: filters.queryString, contactPoint: filters.contactPoint });
},
400,
[filters]
);
const styles = useStyles2(getStyles);
const clearFilters = () => {
setFilters({ queryString: undefined, contactPoint: undefined });
setSearchParams({ queryString: undefined, contactPoint: undefined });
};
const addNewRoute = () => {
clearFilters();
setIsAddMode(true);
setActualRoutes(() => [
...routes.routes,
{
...emptyRoute,
matchers: [emptyArrayFieldMatcher],
},
]);
};
const onCancelAdd = () => {
setIsAddMode(false);
setActualRoutes([...routes.routes]);
};
const onTableRouteChange = (newRoutes: FormAmRoute[]): void => {
onChange({
...routes,
routes: newRoutes,
});
if (isAddMode) {
setIsAddMode(false);
}
};
return (
<div className={styles.container}>
<h5>Specific routing</h5>
<p>Send specific alerts to chosen contact points, based on matching criteria</p>
{!routes.receiver ? (
readOnly ? (
<EmptyArea>
<p>There is no default contact point configured for the root route.</p>
</EmptyArea>
) : (
<EmptyAreaWithCTA
buttonIcon="rocket"
buttonLabel="Set a default contact point"
onButtonClick={onRootRouteEdit}
text="You haven't set a default contact point for the root route yet."
showButton={canCreateNotifications}
/>
)
) : actualRoutes.length > 0 ? (
<>
<div>
{!isAddMode && (
<div className={styles.searchContainer}>
<MatcherFilter
onFilterChange={(filter) =>
setFilters((currentFilters) => ({ ...currentFilters, queryString: filter }))
}
defaultQueryString={filters.queryString ?? ''}
className={styles.filterInput}
/>
<div className={styles.filterInput}>
<Label>Search by contact point</Label>
<Input
onChange={({ currentTarget }) =>
setFilters((currentFilters) => ({ ...currentFilters, contactPoint: currentTarget.value }))
}
value={filters.contactPoint ?? ''}
placeholder="Search by contact point"
data-testid="search-query-input"
prefix={<Icon name={'search'} />}
/>
</div>
{(queryString || contactPoint) && (
<Button variant="secondary" icon="times" onClick={clearFilters} className={styles.clearFilterBtn}>
Clear filters
</Button>
)}
</div>
)}
{!isAddMode && !readOnly && (
<Authorize actions={[permissions.create]}>
<div className={styles.addMatcherBtnRow}>
<Button className={styles.addMatcherBtn} icon="plus" onClick={addNewRoute} type="button">
Add policy
</Button>
</div>
</Authorize>
)}
</div>
<AmRoutesTable
isAddMode={isAddMode}
readOnly={readOnly}
onCancelAdd={onCancelAdd}
onChange={onTableRouteChange}
receivers={receivers}
routes={actualRoutes}
filters={{ queryString, contactPoint }}
alertManagerSourceName={alertManagerSourceName}
/>
</>
) : readOnly ? (
<EmptyArea>
<p>There are no specific policies configured.</p>
</EmptyArea>
) : (
<EmptyAreaWithCTA
buttonIcon="plus"
buttonLabel="New specific policy"
onButtonClick={addNewRoute}
text="You haven't created any specific policies yet."
showButton={canCreateNotifications}
/>
)}
</div>
);
};
const getStyles = (theme: GrafanaTheme2) => {
return {
container: css`
display: flex;
flex-flow: column wrap;
`,
searchContainer: css`
display: flex;
flex-flow: row nowrap;
padding-bottom: ${theme.spacing(2)};
border-bottom: 1px solid ${theme.colors.border.strong};
`,
clearFilterBtn: css`
align-self: flex-end;
margin-left: ${theme.spacing(1)};
`,
filterInput: css`
width: 340px;
& + & {
margin-left: ${theme.spacing(1)};
}
`,
addMatcherBtnRow: css`
display: flex;
flex-flow: column nowrap;
padding: ${theme.spacing(2)} 0;
`,
addMatcherBtn: css`
align-self: flex-end;
`,
};
};