import type {RouteComponentProps} from 'react-router'; import styled from '@emotion/styled'; import pick from 'lodash/pick'; import moment from 'moment'; import AsyncComponent from 'sentry/components/asyncComponent'; import Breadcrumbs from 'sentry/components/breadcrumbs'; import Button from 'sentry/components/button'; import ButtonBar from 'sentry/components/buttonBar'; import type {DateTimeObject} from 'sentry/components/charts/utils'; import IdBadge from 'sentry/components/idBadge'; import * as Layout from 'sentry/components/layouts/thirds'; import LoadingError from 'sentry/components/loadingError'; import LoadingIndicator from 'sentry/components/loadingIndicator'; import PageFiltersContainer from 'sentry/components/organizations/pageFilters/container'; import {normalizeDateTimeParams} from 'sentry/components/organizations/pageFilters/parse'; import {ChangeData} from 'sentry/components/organizations/timeRangeSelector'; import PageTimeRangeSelector from 'sentry/components/pageTimeRangeSelector'; import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle'; import {IconCopy, IconEdit} from 'sentry/icons'; import {t} from 'sentry/locale'; import space from 'sentry/styles/space'; import {DateString, Member, Organization, Project} from 'sentry/types'; import {IssueAlertRule} from 'sentry/types/alerts'; import trackAdvancedAnalyticsEvent from 'sentry/utils/analytics/trackAdvancedAnalyticsEvent'; import {ALERT_DEFAULT_CHART_PERIOD} from 'sentry/views/alerts/rules/metric/details/constants'; import AlertChart from './alertChart'; import AlertRuleIssuesList from './issuesList'; import Sidebar from './sidebar'; type Props = AsyncComponent['props'] & { organization: Organization; project: Project; } & RouteComponentProps<{orgId: string; projectId: string; ruleId: string}, {}>; type State = AsyncComponent['state'] & { memberList: Member[]; rule: IssueAlertRule | null; }; const PAGE_QUERY_PARAMS = [ 'pageStatsPeriod', 'pageStart', 'pageEnd', 'pageUtc', 'cursor', ]; class AlertRuleDetails extends AsyncComponent { shouldRenderBadRequests = true; componentDidMount() { const {organization, params} = this.props; trackAdvancedAnalyticsEvent('issue_alert_rule_details.viewed', { organization, rule_id: parseInt(params.ruleId, 10), }); } componentDidUpdate(prevProps: Props) { const {params: prevParams} = prevProps; const {params: currParams} = this.props; if ( prevParams.ruleId !== currParams.ruleId || prevParams.orgId !== currParams.orgId || prevParams.projectId !== currParams.projectId ) { this.reloadData(); } } getDefaultState(): State { return { ...super.getDefaultState(), rule: null, memberList: [], }; } getEndpoints(): ReturnType { const {orgId, ruleId, projectId} = this.props.params; return [ [ 'rule', `/projects/${orgId}/${projectId}/rules/${ruleId}/`, {query: {expand: 'lastTriggered'}}, {allowError: error => error.status === 404}, ], ['memberList', `/organizations/${orgId}/users/`, {query: {projectSlug: projectId}}], ]; } getDataDatetime(): DateTimeObject { const query = this.props.location?.query ?? {}; const { start, end, statsPeriod, utc: utcString, } = normalizeDateTimeParams(query, { allowEmptyPeriod: true, allowAbsoluteDatetime: true, allowAbsolutePageDatetime: true, }); if (!statsPeriod && !start && !end) { return {period: ALERT_DEFAULT_CHART_PERIOD}; } // Following getParams, statsPeriod will take priority over start/end if (statsPeriod) { return {period: statsPeriod}; } const utc = utcString === 'true'; if (start && end) { return utc ? { start: moment.utc(start).format(), end: moment.utc(end).format(), utc, } : { start: moment(start).utc().format(), end: moment(end).utc().format(), utc, }; } return {period: ALERT_DEFAULT_CHART_PERIOD}; } setStateOnUrl(nextState: { cursor?: string; pageEnd?: DateString; pageStart?: DateString; pageStatsPeriod?: string | null; pageUtc?: boolean | null; team?: string; }) { return this.props.router.push({ ...this.props.location, query: { ...this.props.location.query, ...pick(nextState, PAGE_QUERY_PARAMS), }, }); } handleUpdateDatetime = (datetime: ChangeData) => { const {start, end, relative, utc} = datetime; if (start && end) { const parser = utc ? moment.utc : moment; return this.setStateOnUrl({ pageStatsPeriod: undefined, pageStart: parser(start).format(), pageEnd: parser(end).format(), pageUtc: utc ?? undefined, cursor: undefined, }); } return this.setStateOnUrl({ pageStatsPeriod: relative || undefined, pageStart: undefined, pageEnd: undefined, pageUtc: undefined, cursor: undefined, }); }; renderLoading() { return ( ); } renderBody() { const {params, location, organization, project} = this.props; const {orgId, ruleId, projectId} = params; const {cursor} = location.query; const {period, start, end, utc} = this.getDataDatetime(); const {rule, memberList} = this.state; if (!rule) { return ( ); } if (!project) { return ( ); } const duplicateLink = { pathname: `/organizations/${orgId}/alerts/new/issue/`, query: { project: project.slug, duplicateRuleId: rule.id, createFromDuplicate: true, referrer: 'issue_rule_details', }, }; return ( {rule.name} ); } } export default AlertRuleDetails; const StyledPageTimeRangeSelector = styled(PageTimeRangeSelector)` margin-bottom: ${space(2)}; `; const RuleName = styled('div')` display: grid; grid-template-columns: max-content 1fr; grid-column-gap: ${space(1)}; align-items: center; `; const StyledLoadingError = styled(LoadingError)` margin: ${space(2)}; `;