alerting/filter-contact-points
Gilles De Mey 7 months ago
parent c4dfbec856
commit 51e1cfed69
No known key found for this signature in database
  1. 14
      public/app/features/alerting/unified/components/contact-points/ContactPoints.tsx
  2. 41
      public/app/features/alerting/unified/components/contact-points/components/ContactPointsFilter.tsx
  3. 44
      public/app/features/alerting/unified/components/contact-points/components/useContactPointsFilter.tsx
  4. 26
      public/app/features/alerting/unified/components/contact-points/useContactPointsSearch.tsx

@ -32,6 +32,7 @@ import { ContactPoint } from './ContactPoint';
import { NotificationTemplates } from './NotificationTemplates';
import { ContactPointsFilter } from './components/ContactPointsFilter';
import { GlobalConfigAlert } from './components/GlobalConfigAlert';
import { ContactPointsFilterState, useContactPointsFilter } from './components/useContactPointsFilter';
import { useContactPointsWithStatus } from './useContactPoints';
import { useContactPointsSearch } from './useContactPointsSearch';
import { ALL_CONTACT_POINTS, useExportContactPoint } from './useExportContactPoint';
@ -46,7 +47,8 @@ const DEFAULT_PAGE_SIZE = 10;
const ContactPointsTab = () => {
const { selectedAlertmanager } = useAlertmanager();
const [queryParams] = useURLSearchParams();
const { filters } = useContactPointsFilter();
console.log(filters);
// If we're using the K8S API, then we don't need to fetch the policies info within the hook,
// as we get metadata about this from the API
@ -69,8 +71,6 @@ const ContactPointsTab = () => {
const [ExportDrawer, showExportDrawer] = useExportContactPoint();
const search = queryParams.get('search');
if (isLoading) {
return <LoadingPlaceholder text="Loading..." />;
}
@ -129,7 +129,7 @@ const ContactPointsTab = () => {
</Stack>
</Stack>
{error && <Alert title="Failed to fetch contact points">{stringifyErrorLike(error)}</Alert>}
{!error && <ContactPointsList contactPoints={contactPoints} search={search} pageSize={DEFAULT_PAGE_SIZE} />}
{!error && <ContactPointsList contactPoints={contactPoints} filters={filters} pageSize={DEFAULT_PAGE_SIZE} />}
{/* Grafana manager Alertmanager does not support global config, Mimir and Cortex do */}
{!isGrafanaManagedAlertmanager && <GlobalConfigAlert alertManagerName={selectedAlertmanager!} />}
{ExportDrawer}
@ -235,12 +235,12 @@ export const ContactPointsPageContents = () => {
interface ContactPointsListProps {
contactPoints: ContactPointWithMetadata[];
search?: string | null;
filters?: ContactPointsFilterState;
pageSize?: number;
}
const ContactPointsList = ({ contactPoints, search, pageSize = DEFAULT_PAGE_SIZE }: ContactPointsListProps) => {
const searchResults = useContactPointsSearch(contactPoints, search);
const ContactPointsList = ({ contactPoints, filters, pageSize = DEFAULT_PAGE_SIZE }: ContactPointsListProps) => {
const searchResults = useContactPointsSearch(contactPoints, filters);
const { page, pageItems, numberOfPages, onPageChange } = usePagination(searchResults, 1, pageSize);
if (pageItems.length === 0) {

@ -1,54 +1,36 @@
import { css } from '@emotion/css';
import { useCallback, useDeferredValue, useEffect, useMemo, useState } from 'react';
import { useMemo } from 'react';
import { SelectableValue } from '@grafana/data';
import { Stack } from '@grafana/experimental';
import { Button, Field, Icon, Input, Select, SelectCommonProps, Text, useStyles2 } from '@grafana/ui';
import { Button, Field, Icon, Input, Select, SelectCommonProps, Stack, Text, useStyles2 } from '@grafana/ui';
import { alertmanagerApi } from '../../../api/alertmanagerApi';
import { useURLSearchParams } from '../../../hooks/useURLSearchParams';
import { useAlertmanager } from '../../../state/AlertmanagerContext';
import { stringifyErrorLike } from '../../../utils/misc';
import { useContactPointsFilter } from './useContactPointsFilter';
const useGrafanaNotifiers = alertmanagerApi.endpoints.grafanaNotifiers.useQuery;
const ContactPointsFilter = () => {
const styles = useStyles2(getStyles);
const { isGrafanaAlertmanager } = useAlertmanager();
const [searchParams, setSearchParams] = useURLSearchParams();
const [filters, setFilters] = useState({
search: searchParams.get('search') ?? undefined,
type: searchParams.get('type') ?? undefined,
});
const filtersDeferred = useDeferredValue(filters);
const clear = useCallback(() => {
setFilters({ search: '', type: '' });
}, []);
// when the filters are updated (deferred) update the search params
useEffect(() => {
setSearchParams(filtersDeferred, true);
}, [filtersDeferred, setSearchParams]);
const { filters, updateFilter, clear } = useContactPointsFilter();
const hasFilters = Object.values(filters).some(Boolean);
return (
<Stack direction="row" alignItems="end" gap={0.5}>
<Field className={styles.noBottom} label="Search by name or type">
<Field className={styles.noBottom} label="Search by name">
<Input
aria-label="search contact points"
placeholder="Search"
width={46}
prefix={<Icon name="search" />}
onChange={(event) => {
setFilters({
...filters,
search: event.currentTarget.value,
});
updateFilter({ search: event.currentTarget.value });
}}
value={filtersDeferred.search}
value={filters.search}
/>
</Field>
{/* I don't feel like doing this for non-Grafana Alertmanagers right now */}
@ -56,12 +38,9 @@ const ContactPointsFilter = () => {
<Field className={styles.noBottom} label="Filter by type">
<NotifierSelector
width={24}
value={filtersDeferred.type}
value={filters.type}
onChange={(type) => {
setFilters({
...filters,
type: type.value,
});
updateFilter({ type: type.value });
}}
/>
</Field>

@ -0,0 +1,44 @@
import { useCallback, useDeferredValue, useEffect, useState } from 'react';
import { useURLSearchParams } from '../../../hooks/useURLSearchParams';
export type ContactPointsFilterState = {
search?: string;
type?: string;
};
export function useContactPointsFilter() {
const [searchParams, setSearchParams] = useURLSearchParams();
const [filters, setFilters] = useState<ContactPointsFilterState>({
search: searchParams.get('search') ?? undefined,
type: searchParams.get('type') ?? undefined,
});
const filtersDeferred = useDeferredValue(filters);
const clear = useCallback(() => {
setFilters({ search: '', type: '' });
}, []);
const updateFilter = useCallback((update: Partial<ContactPointsFilterState>) => {
setFilters((filters) => ({ ...filters, ...update }));
}, []);
// // when the filters are updated (deferred) update the search params
useEffect(() => {
setSearchParams(filtersDeferred, true);
}, [filtersDeferred, setSearchParams]);
// When URL params change, update filters
useEffect(() => {
setFilters({
search: searchParams.get('search') ?? undefined,
type: searchParams.get('type') ?? undefined,
});
}, [searchParams]);
return {
clear,
filters: filtersDeferred,
updateFilter,
};
}

@ -5,6 +5,8 @@ import { useMemo } from 'react';
import { RECEIVER_META_KEY } from 'app/features/alerting/unified/components/contact-points/constants';
import { ContactPointWithMetadata } from 'app/features/alerting/unified/components/contact-points/utils';
import { ContactPointsFilterState } from './components/useContactPointsFilter';
const fuzzyFinder = new uFuzzy({
intraMode: 1,
intraIns: 1,
@ -16,8 +18,11 @@ const fuzzyFinder = new uFuzzy({
// let's search in two different haystacks, the name of the contact point and the type of the receiver(s)
export const useContactPointsSearch = (
contactPoints: ContactPointWithMetadata[],
search?: string | null
filters: ContactPointsFilterState = {}
): ContactPointWithMetadata[] => {
const hasFilters = Object.values(filters).some(Boolean);
const { search, type } = filters;
const nameHaystack = useMemo(() => {
return contactPoints.map((contactPoint) => contactPoint.name);
}, [contactPoints]);
@ -29,14 +34,23 @@ export const useContactPointsSearch = (
);
}, [contactPoints]);
if (!search) {
if (!hasFilters) {
return contactPoints;
}
const nameHits = fuzzyFinder.filter(nameHaystack, search) ?? [];
const typeHits = fuzzyFinder.filter(typeHaystack, search) ?? [];
const results: ContactPointWithMetadata[] = [];
const hits = [...nameHits, ...typeHits];
if (search) {
fuzzyFinder.filter(nameHaystack, search)?.forEach((id) => {
results.push(contactPoints[id]);
});
}
if (type) {
fuzzyFinder.filter(typeHaystack, type)?.forEach((id) => {
results.push(contactPoints[id]);
});
}
return uniq(hits).map((id) => contactPoints[id]) ?? [];
return uniq(results);
};

Loading…
Cancel
Save