mirror of
https://github.com/grafana/grafana.git
synced 2025-08-01 03:11:49 +08:00

* i18n: removes useTranslate hook * chore: fix duplicate imports * chore: fix import sorting and hook dependencies
219 lines
7.1 KiB
TypeScript
219 lines
7.1 KiB
TypeScript
import { css } from '@emotion/css';
|
|
import { orderBy } from 'lodash';
|
|
import { useState } from 'react';
|
|
import { useDebounce } from 'react-use';
|
|
|
|
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
|
|
import { Trans, t } from '@grafana/i18n';
|
|
import { Card, FilterInput, Icon, Pagination, Select, Stack, TagList, useStyles2 } from '@grafana/ui';
|
|
import { DEFAULT_PER_PAGE_PAGINATION } from 'app/core/constants';
|
|
import { getQueryParamValue } from 'app/core/utils/query';
|
|
import { FolderDTO } from 'app/types';
|
|
import { GrafanaRuleDefinition, RulerGrafanaRuleDTO } from 'app/types/unified-alerting-dto';
|
|
|
|
import { usePagination } from './hooks/usePagination';
|
|
import { useURLSearchParams } from './hooks/useURLSearchParams';
|
|
import { combineMatcherStrings, labelsMatchMatchers } from './utils/alertmanager';
|
|
import { GRAFANA_RULES_SOURCE_NAME } from './utils/datasource';
|
|
import { parsePromQLStyleMatcherLooseSafe } from './utils/matchers';
|
|
import { rulesNav } from './utils/navigation';
|
|
|
|
interface Props {
|
|
folder: FolderDTO;
|
|
rules: RulerGrafanaRuleDTO[];
|
|
}
|
|
|
|
enum SortOrder {
|
|
Ascending = 'alpha-asc',
|
|
Descending = 'alpha-desc',
|
|
}
|
|
|
|
const sortOptions: Array<SelectableValue<SortOrder>> = [
|
|
{ label: 'Alphabetically [A-Z]', value: SortOrder.Ascending },
|
|
{ label: 'Alphabetically [Z-A]', value: SortOrder.Descending },
|
|
];
|
|
|
|
export const AlertsFolderView = ({ folder, rules }: Props) => {
|
|
const styles = useStyles2(getStyles);
|
|
|
|
const onTagClick = (tagName: string) => {
|
|
const matchersString = combineMatcherStrings(labelFilter, tagName);
|
|
setLabelFilter(matchersString);
|
|
};
|
|
|
|
const { nameFilter, labelFilter, sortOrder, setNameFilter, setLabelFilter, setSortOrder } =
|
|
useAlertsFolderViewParams();
|
|
|
|
const filteredRules = filterAndSortRules(rules, nameFilter, labelFilter, sortOrder ?? SortOrder.Ascending);
|
|
const hasNoResults = filteredRules.length === 0;
|
|
|
|
const { page, numberOfPages, onPageChange, pageItems } = usePagination(filteredRules, 1, DEFAULT_PER_PAGE_PAGINATION);
|
|
|
|
return (
|
|
<div className={styles.container}>
|
|
<Stack direction="column" gap={3}>
|
|
<FilterInput
|
|
value={nameFilter}
|
|
onChange={setNameFilter}
|
|
placeholder={t(
|
|
'alerting.alerts-folder-view.name-filter-placeholder-search-alert-rules-by-name',
|
|
'Search alert rules by name'
|
|
)}
|
|
data-testid="name-filter"
|
|
/>
|
|
<Stack direction="row">
|
|
<Select<SortOrder>
|
|
value={sortOrder}
|
|
onChange={({ value }) => value && setSortOrder(value)}
|
|
options={sortOptions}
|
|
width={25}
|
|
aria-label={t('alerting.alerts-folder-view.aria-label-sort', 'Sort')}
|
|
placeholder={t('alerting.alerts-folder-view.placeholder-sort-default-az', 'Sort (Default A-Z)')}
|
|
prefix={<Icon name={sortOrder === SortOrder.Ascending ? 'sort-amount-up' : 'sort-amount-down'} />}
|
|
/>
|
|
<FilterInput
|
|
value={labelFilter}
|
|
onChange={setLabelFilter}
|
|
placeholder={t(
|
|
'alerting.alerts-folder-view.label-filter-placeholder-search-alerts-by-labels',
|
|
'Search alerts by labels'
|
|
)}
|
|
className={styles.filterLabelsInput}
|
|
data-testid="label-filter"
|
|
/>
|
|
</Stack>
|
|
|
|
<Stack direction="column" gap={1}>
|
|
{pageItems.map(({ grafana_alert, labels = {} }) => (
|
|
<Card
|
|
key={grafana_alert.uid}
|
|
href={createGrafanaRuleViewLink(grafana_alert)}
|
|
className={styles.card}
|
|
data-testid="alert-card-row"
|
|
>
|
|
<Card.Heading>{grafana_alert.title}</Card.Heading>
|
|
<Card.Tags>
|
|
<TagList
|
|
onClick={onTagClick}
|
|
tags={Object.entries(labels).map(([label, value]) => `${label}=${value}`)}
|
|
/>
|
|
</Card.Tags>
|
|
<Card.Meta>
|
|
<div>
|
|
<Icon name="folder" /> {folder.title}
|
|
</div>
|
|
</Card.Meta>
|
|
</Card>
|
|
))}
|
|
</Stack>
|
|
{hasNoResults && (
|
|
<div className={styles.noResults}>
|
|
<Trans i18nKey="alerting.alerts-folder-view.no-alert-rules-found">No alert rules found</Trans>
|
|
</div>
|
|
)}
|
|
<div className={styles.pagination}>
|
|
<Pagination
|
|
currentPage={page}
|
|
numberOfPages={numberOfPages}
|
|
onNavigate={onPageChange}
|
|
hideWhenSinglePage={true}
|
|
/>
|
|
</div>
|
|
</Stack>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
enum AlertFolderViewParams {
|
|
nameFilter = 'nameFilter',
|
|
labelFilter = 'labelFilter',
|
|
sortOrder = 'sort',
|
|
}
|
|
|
|
function useAlertsFolderViewParams() {
|
|
const [searchParams, setSearchParams] = useURLSearchParams();
|
|
|
|
const [nameFilter, setNameFilter] = useState(searchParams.get(AlertFolderViewParams.nameFilter) ?? '');
|
|
const [labelFilter, setLabelFilter] = useState(searchParams.get(AlertFolderViewParams.labelFilter) ?? '');
|
|
|
|
const sortParam = searchParams.get(AlertFolderViewParams.sortOrder);
|
|
const defaultSortOrder = (() => {
|
|
if (sortParam === SortOrder.Ascending) {
|
|
return SortOrder.Ascending;
|
|
}
|
|
if (sortParam === SortOrder.Descending) {
|
|
return SortOrder.Descending;
|
|
}
|
|
return undefined;
|
|
})();
|
|
const [sortOrder, setSortOrder] = useState<SortOrder | undefined>(defaultSortOrder);
|
|
|
|
useDebounce(
|
|
() =>
|
|
setSearchParams(
|
|
{
|
|
[AlertFolderViewParams.nameFilter]: getQueryParamValue(nameFilter),
|
|
[AlertFolderViewParams.labelFilter]: getQueryParamValue(labelFilter),
|
|
[AlertFolderViewParams.sortOrder]: getQueryParamValue(sortOrder),
|
|
},
|
|
true
|
|
),
|
|
400,
|
|
[nameFilter, labelFilter, sortOrder]
|
|
);
|
|
|
|
return { nameFilter, labelFilter, sortOrder, setNameFilter, setLabelFilter, setSortOrder };
|
|
}
|
|
|
|
function filterAndSortRules(
|
|
originalRules: RulerGrafanaRuleDTO[],
|
|
nameFilter: string,
|
|
labelFilter: string,
|
|
sortOrder: SortOrder
|
|
) {
|
|
const matchers = parsePromQLStyleMatcherLooseSafe(labelFilter);
|
|
const rules = originalRules.filter((rule) => {
|
|
const nameMatch = rule.grafana_alert.title.toLowerCase().includes(nameFilter.toLowerCase());
|
|
const labelMatch = labelsMatchMatchers(rule.labels ?? {}, matchers);
|
|
return nameMatch && labelMatch;
|
|
});
|
|
|
|
return orderBy(rules, (rule) => rule.grafana_alert.title.toLowerCase(), [
|
|
sortOrder === SortOrder.Ascending ? 'asc' : 'desc',
|
|
]);
|
|
}
|
|
|
|
function createGrafanaRuleViewLink(ruleDefinition: GrafanaRuleDefinition): string {
|
|
return rulesNav.detailsPageLink(
|
|
GRAFANA_RULES_SOURCE_NAME,
|
|
{
|
|
uid: ruleDefinition.uid,
|
|
ruleSourceName: GRAFANA_RULES_SOURCE_NAME,
|
|
},
|
|
undefined
|
|
);
|
|
}
|
|
|
|
export const getStyles = (theme: GrafanaTheme2) => ({
|
|
container: css({
|
|
padding: theme.spacing(1),
|
|
}),
|
|
card: css({
|
|
gridTemplateColumns: 'auto 1fr 2fr',
|
|
margin: 0,
|
|
}),
|
|
pagination: css({
|
|
alignSelf: 'center',
|
|
}),
|
|
filterLabelsInput: css({
|
|
flex: 1,
|
|
width: 'auto',
|
|
minWidth: '240px',
|
|
}),
|
|
noResults: css({
|
|
padding: theme.spacing(2),
|
|
backgroundColor: theme.colors.background.secondary,
|
|
fontStyle: 'italic',
|
|
}),
|
|
});
|