Browse Source

feat(alerts): Add control for filtering by alert type (#58323)

Add a control tor filtering by alert types on the alert list.

- requires https://github.com/getsentry/sentry/pull/58327
- relates to https://github.com/getsentry/sentry/issues/58139
ArthurKnaus 1 year ago
parent
commit
64d35c6a94

+ 34 - 4
static/app/views/alerts/filterBar.tsx

@@ -5,17 +5,19 @@ import ButtonBar from 'sentry/components/buttonBar';
 import {CompactSelect} from 'sentry/components/compactSelect';
 import {ProjectPageFilter} from 'sentry/components/organizations/projectPageFilter';
 import SearchBar from 'sentry/components/searchBar';
+import {SegmentedControl} from 'sentry/components/segmentedControl';
 import {t} from 'sentry/locale';
 import {space} from 'sentry/styles/space';
 
 import TeamFilter from './list/rules/teamFilter';
-import {getQueryStatus, getTeamParams} from './utils';
+import {DatasetOption, getQueryDataset, getQueryStatus, getTeamParams} from './utils';
 
 interface Props {
   location: Location<any>;
   onChangeFilter: (activeFilters: string[]) => void;
   onChangeSearch: (query: string) => void;
   hasStatusFilters?: boolean;
+  onChangeDataset?: (dataset: DatasetOption) => void;
   onChangeStatus?: (status: string) => void;
 }
 
@@ -24,10 +26,12 @@ function FilterBar({
   onChangeSearch,
   onChangeFilter,
   onChangeStatus,
+  onChangeDataset,
   hasStatusFilters,
 }: Props) {
   const selectedTeams = getTeamParams(location.query.team);
   const selectedStatus = getQueryStatus(location.query.status);
+  const selectedDataset = getQueryDataset(location.query.dataset);
 
   return (
     <Wrapper>
@@ -57,6 +61,28 @@ function FilterBar({
             onChange={({value}) => onChangeStatus(value)}
           />
         )}
+        {onChangeDataset && (
+          <SegmentedControlWrapper>
+            <SegmentedControl<DatasetOption>
+              aria-label={t('Alert type')}
+              value={selectedDataset}
+              onChange={onChangeDataset}
+            >
+              <SegmentedControl.Item key={DatasetOption.ALL}>
+                {t('All')}
+              </SegmentedControl.Item>
+              <SegmentedControl.Item key={DatasetOption.ERRORS}>
+                {t('Errors')}
+              </SegmentedControl.Item>
+              <SegmentedControl.Item key={DatasetOption.SESSIONS}>
+                {t('Sessions')}
+              </SegmentedControl.Item>
+              <SegmentedControl.Item key={DatasetOption.PERFORMANCE}>
+                {t('Performance')}
+              </SegmentedControl.Item>
+            </SegmentedControl>
+          </SegmentedControlWrapper>
+        )}
       </FilterButtons>
       <SearchBar
         placeholder={t('Search by name')}
@@ -74,20 +100,24 @@ const Wrapper = styled('div')`
   gap: ${space(1.5)};
   margin-bottom: ${space(2)};
 
-  @media (min-width: ${p => p.theme.breakpoints.small}) {
+  @media (min-width: ${p => p.theme.breakpoints.large}) {
     grid-template-columns: min-content 1fr;
   }
 `;
 
 const FilterButtons = styled(ButtonBar)`
-  @media (max-width: ${p => p.theme.breakpoints.small}) {
+  @media (max-width: ${p => p.theme.breakpoints.large}) {
     display: flex;
     align-items: flex-start;
     gap: ${space(1.5)};
   }
 
-  @media (min-width: ${p => p.theme.breakpoints.small}) {
+  @media (min-width: ${p => p.theme.breakpoints.large}) {
     display: grid;
     grid-auto-columns: minmax(auto, 300px);
   }
 `;
+
+const SegmentedControlWrapper = styled('div')`
+  width: max-content;
+`;

+ 38 - 0
static/app/views/alerts/list/rules/alertRulesList.spec.tsx

@@ -17,6 +17,7 @@ import OrganizationStore from 'sentry/stores/organizationStore';
 import ProjectsStore from 'sentry/stores/projectsStore';
 import TeamStore from 'sentry/stores/teamStore';
 import {IncidentStatus} from 'sentry/views/alerts/types';
+import {DatasetOption} from 'sentry/views/alerts/utils';
 
 import AlertRulesList from './alertRulesList';
 
@@ -292,6 +293,43 @@ describe('AlertRulesList', () => {
     );
   });
 
+  it('searches by alert type', async () => {
+    const {routerContext, organization, router} = initializeOrg();
+    render(<AlertRulesList />, {context: routerContext, organization});
+
+    const performanceControl = await screen.getByRole('radio', {name: 'Performance'});
+    expect(performanceControl).toBeInTheDocument();
+    await userEvent.click(performanceControl);
+
+    expect(router.push).toHaveBeenCalledWith(
+      expect.objectContaining({
+        query: {
+          dataset: DatasetOption.PERFORMANCE,
+        },
+      })
+    );
+  });
+
+  it('calls api with correct query params when searching by alert type', () => {
+    const {routerContext, organization} = initializeOrg({
+      router: {
+        location: {
+          query: {
+            dataset: DatasetOption.PERFORMANCE,
+          },
+        },
+      },
+    });
+    render(<AlertRulesList />, {context: routerContext, organization});
+
+    expect(rulesMock).toHaveBeenCalledWith(
+      '/organizations/org-slug/combined-rules/',
+      expect.objectContaining({
+        query: expect.objectContaining({dataset: ['generic_metrics', 'transactions']}),
+      })
+    );
+  });
+
   it('uses empty team query parameter when removing all teams', async () => {
     const {routerContext, organization, router} = initializeOrg({
       router: {

+ 20 - 1
static/app/views/alerts/list/rules/alertRulesList.tsx

@@ -38,7 +38,13 @@ import useRouter from 'sentry/utils/useRouter';
 
 import FilterBar from '../../filterBar';
 import {AlertRuleType, CombinedMetricIssueAlerts} from '../../types';
-import {getTeamParams, isIssueAlert} from '../../utils';
+import {
+  DatasetOption,
+  datasetToQueryParam,
+  getQueryDataset,
+  getTeamParams,
+  isIssueAlert,
+} from '../../utils';
 import AlertHeader from '../header';
 
 import RuleListRow from './row';
@@ -50,6 +56,7 @@ function getAlertListQueryKey(orgSlug: string, query: Location['query']): ApiQue
   const queryParams = {...query};
   queryParams.expand = ['latestIncident', 'lastTriggered'];
   queryParams.team = getTeamParams(queryParams.team!);
+  queryParams.dataset = datasetToQueryParam[getQueryDataset(queryParams.dataset!)];
 
   if (!queryParams.sort) {
     queryParams.sort = defaultSort;
@@ -107,6 +114,17 @@ function AlertRulesList() {
     });
   };
 
+  const handleChangeDataset = (value: DatasetOption): void => {
+    const {cursor: _cursor, page: _page, ...currentQuery} = location.query;
+    router.push({
+      pathname: location.pathname,
+      query: {
+        ...currentQuery,
+        dataset: value === DatasetOption.ALL ? undefined : value,
+      },
+    });
+  };
+
   const handleOwnerChange = (
     projectId: string,
     rule: CombinedMetricIssueAlerts,
@@ -179,6 +197,7 @@ function AlertRulesList() {
           <Layout.Main fullWidth>
             <FilterBar
               location={location}
+              onChangeDataset={handleChangeDataset}
               onChangeFilter={handleChangeFilter}
               onChangeSearch={handleChangeSearch}
             />

+ 26 - 0
static/app/views/alerts/utils/index.tsx

@@ -37,6 +37,13 @@ export function isIssueAlert(
   return !data.hasOwnProperty('triggers');
 }
 
+export enum DatasetOption {
+  ALL = 'all',
+  ERRORS = 'errors',
+  SESSIONS = 'sessions',
+  PERFORMANCE = 'performance',
+}
+
 export const DATA_SOURCE_LABELS = {
   [Dataset.ERRORS]: t('Errors'),
   [Dataset.TRANSACTIONS]: t('Transactions'),
@@ -195,3 +202,22 @@ export function getTeamParams(team?: string | string[]): string[] {
 
   return toArray(team);
 }
+
+const datasetValues = new Set(Object.values(DatasetOption));
+export function getQueryDataset(dataset: any): DatasetOption {
+  if ((datasetValues as Set<any>).has(dataset)) {
+    return dataset as DatasetOption;
+  }
+  return DatasetOption.ALL;
+}
+
+export const datasetToQueryParam: Record<DatasetOption, Dataset[] | undefined> = {
+  [DatasetOption.ALL]: undefined,
+  [DatasetOption.ERRORS]: [Dataset.ERRORS],
+  [DatasetOption.SESSIONS]: [Dataset.METRICS],
+  [DatasetOption.PERFORMANCE]: [
+    Dataset.GENERIC_METRICS,
+    // TODO(telemetry-experience): remove this once we migrated all performance alerts to generic metrics
+    Dataset.TRANSACTIONS,
+  ],
+};