From eeff2b12febc9925f66a5c1f38d487c4dcf963eb Mon Sep 17 00:00:00 2001 From: David Wang Date: Wed, 3 Nov 2021 18:03:43 -0700 Subject: [PATCH] use teams for alert list --- static/app/views/alerts/list/index.tsx | 9 +- static/app/views/alerts/rules/index.tsx | 203 +++++++++--------- static/app/views/alerts/rules/teamFilter.tsx | 49 +++-- .../js/spec/views/alerts/rules/index.spec.jsx | 2 + 4 files changed, 142 insertions(+), 121 deletions(-) diff --git a/static/app/views/alerts/list/index.tsx b/static/app/views/alerts/list/index.tsx index 6c3fc53828c09f..835fc65cfdbf7d 100644 --- a/static/app/views/alerts/list/index.tsx +++ b/static/app/views/alerts/list/index.tsx @@ -19,11 +19,10 @@ import SentryDocumentTitle from 'app/components/sentryDocumentTitle'; import {IconInfo} from 'app/icons'; import {t, tct} from 'app/locale'; import space from 'app/styles/space'; -import {Organization, Project, Team} from 'app/types'; +import {Organization, Project} from 'app/types'; import {trackAnalyticsEvent} from 'app/utils/analytics'; import Projects from 'app/utils/projects'; import withOrganization from 'app/utils/withOrganization'; -import withTeams from 'app/utils/withTeams'; import TeamFilter, {getTeamParams} from '../rules/teamFilter'; import {Incident} from '../types'; @@ -37,7 +36,6 @@ const DOCS_URL = type Props = RouteComponentProps<{orgId: string}, {}> & { organization: Organization; - teams: Team[]; }; type State = { @@ -176,7 +174,7 @@ class IncidentsList extends AsyncComponent p.theme.fontSizeLarge}; `; -export default withOrganization(withTeams(IncidentsListContainer)); +export default withOrganization(IncidentsListContainer); diff --git a/static/app/views/alerts/rules/index.tsx b/static/app/views/alerts/rules/index.tsx index 1599b548266540..d5d3c1e4f10088 100644 --- a/static/app/views/alerts/rules/index.tsx +++ b/static/app/views/alerts/rules/index.tsx @@ -16,11 +16,11 @@ import SentryDocumentTitle from 'app/components/sentryDocumentTitle'; import {IconArrow} from 'app/icons'; import {t, tct} from 'app/locale'; import space from 'app/styles/space'; -import {GlobalSelection, Organization, Project, Team} from 'app/types'; +import {GlobalSelection, Organization, Project} from 'app/types'; import {trackAnalyticsEvent} from 'app/utils/analytics'; import Projects from 'app/utils/projects'; +import Teams from 'app/utils/teams'; import withGlobalSelection from 'app/utils/withGlobalSelection'; -import withTeams from 'app/utils/withTeams'; import AlertHeader from '../list/header'; import {CombinedMetricIssueAlerts} from '../types'; @@ -34,7 +34,6 @@ const DOCS_URL = 'https://docs.sentry.io/product/alerts-notifications/metric-ale type Props = RouteComponentProps<{orgId: string}, {}> & { organization: Organization; selection: GlobalSelection; - teams: Team[]; }; type State = { @@ -113,13 +112,12 @@ class AlertRulesList extends AsyncComponent @@ -137,7 +135,6 @@ class AlertRulesList extends AsyncComponent ); - const userTeams = new Set(teams.filter(({isMember}) => isMember).map(({id}) => id)); - return ( {this.renderFilterBar()} - - {t('Alert Rule')} {sort.field === 'name' && sortArrow} - , - - + {({initiallyLoaded: loadedTeams, teams}) => ( + + {t('Alert Rule')} {sort.field === 'name' && sortArrow} + , + + + {t('Status')} {isAlertRuleSort && sortArrow} + , + + t('Project'), + t('Team'), + + {t('Created')} {sort.field === 'date_added' && sortArrow} + , + t('Actions'), + ]} + isLoading={loading || !loadedTeams} + isEmpty={ruleList?.length === 0} + emptyMessage={t('No alert rules found for the current query.')} + emptyAction={ + + {tct('Learn more about [link:Alerts]', { + link: , + })} + } - to={{ - pathname: location.pathname, - query: { - ...currentQuery, - asc: isAlertRuleSort && !sort.asc ? '1' : undefined, - sort: ['incident_status', 'date_triggered'], - }, - }} > - {t('Status')} {isAlertRuleSort && sortArrow} - , - - t('Project'), - t('Team'), - - {t('Created')} {sort.field === 'date_added' && sortArrow} - , - t('Actions'), - ]} - isLoading={loading} - isEmpty={ruleList?.length === 0} - emptyMessage={t('No alert rules found for the current query.')} - emptyAction={ - - {tct('Learn more about [link:Alerts]', { - link: , - })} - - } - > - - {({initiallyLoaded, projects}) => - ruleList.map(rule => ( - - )) - } - - + + {({initiallyLoaded, projects}) => + ruleList.map(rule => ( + team.id))} + /> + )) + } + + + )} + { @@ -328,7 +331,7 @@ class AlertRulesListContainer extends Component { } } -export default withGlobalSelection(withTeams(AlertRulesListContainer)); +export default withGlobalSelection(AlertRulesListContainer); const StyledLayoutBody = styled(Layout.Body)` margin-bottom: -20px; diff --git a/static/app/views/alerts/rules/teamFilter.tsx b/static/app/views/alerts/rules/teamFilter.tsx index 789851388c45e1..1e531fa9669364 100644 --- a/static/app/views/alerts/rules/teamFilter.tsx +++ b/static/app/views/alerts/rules/teamFilter.tsx @@ -1,16 +1,19 @@ import {useState} from 'react'; import styled from '@emotion/styled'; +import debounce from 'lodash/debounce'; import Input from 'app/components/forms/input'; +import LoadingIndicator from 'app/components/loadingIndicator'; +import {DEFAULT_DEBOUNCE_DURATION} from 'app/constants'; import {t} from 'app/locale'; -import {Team} from 'app/types'; +import space from 'app/styles/space'; +import useTeams from 'app/utils/useTeams'; import Filter from './filter'; const ALERT_LIST_QUERY_DEFAULT_TEAMS = ['myteams', 'unassigned']; type Props = { - teams: Team[]; selectedTeams: Set; handleChangeFilter: (sectionId: string, activeFilters: Set) => void; showStatus?: boolean; @@ -34,12 +37,13 @@ export function getTeamParams(team?: string | string[]): string[] { } function TeamFilter({ - teams, selectedTeams, showStatus = false, selectedStatus = new Set(), handleChangeFilter, }: Props) { + const {teams, onSearch, fetching} = useTeams(); + const debouncedSearch = debounce(onSearch, DEFAULT_DEBOUNCE_DURATION); const [teamFilterSearch, setTeamFilterSearch] = useState(); const statusOptions = [ @@ -83,17 +87,22 @@ function TeamFilter({ return ( { - event.stopPropagation(); - }} - onChange={(event: React.ChangeEvent) => { - setTeamFilterSearch(event.target.value); - }} - value={teamFilterSearch || ''} - /> + + { + event.stopPropagation(); + }} + onChange={(event: React.ChangeEvent) => { + const search = event.target.value; + setTeamFilterSearch(search); + debouncedSearch(search); + }} + value={teamFilterSearch || ''} + /> + {fetching && } + } onFilterChange={handleChangeFilter} dropdownSections={[ @@ -118,8 +127,18 @@ function TeamFilter({ export default TeamFilter; +const InputWrapper = styled('div')` + position: relative; +`; + const StyledInput = styled(Input)` border: none; - border-bottom: 1px solid ${p => p.theme.gray200}; + border-bottom: 1px solid transparent; border-radius: 0; `; + +const StyledLoadingIndicator = styled(LoadingIndicator)` + position: absolute; + right: 0; + top: ${space(0.75)}; +`; diff --git a/tests/js/spec/views/alerts/rules/index.spec.jsx b/tests/js/spec/views/alerts/rules/index.spec.jsx index 122a2528508e36..48e172cefcd949 100644 --- a/tests/js/spec/views/alerts/rules/index.spec.jsx +++ b/tests/js/spec/views/alerts/rules/index.spec.jsx @@ -3,6 +3,7 @@ import {act, fireEvent, mountWithTheme, screen} from 'sentry-test/reactTestingLi import OrganizationStore from 'app/stores/organizationStore'; import ProjectsStore from 'app/stores/projectsStore'; +import TeamStore from 'app/stores/teamStore'; import {trackAnalyticsEvent} from 'app/utils/analytics'; import AlertRulesList from 'app/views/alerts/rules'; import {IncidentStatus} from 'app/views/alerts/types'; @@ -11,6 +12,7 @@ jest.mock('app/utils/analytics'); describe('OrganizationRuleList', () => { const {routerContext, organization, router} = initializeOrg(); + TeamStore.loadInitialData([]); let rulesMock; let projectMock; const pageLinks =