Files
2024-02-15 17:01:50 +00:00

297 lines
8.9 KiB
TypeScript

import {useState} from 'react';
import type {RouteComponentProps} from 'react-router';
import styled from '@emotion/styled';
import Feature from 'sentry/components/acl/feature';
import FeatureDisabled from 'sentry/components/acl/featureDisabled';
import CreateAlertButton from 'sentry/components/createAlertButton';
import {Hovercard} from 'sentry/components/hovercard';
import * as Layout from 'sentry/components/layouts/thirds';
import ExternalLink from 'sentry/components/links/externalLink';
import List from 'sentry/components/list';
import ListItem from 'sentry/components/list/listItem';
import Panel from 'sentry/components/panels/panel';
import PanelBody from 'sentry/components/panels/panelBody';
import PanelHeader from 'sentry/components/panels/panelHeader';
import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
import {t} from 'sentry/locale';
import {space} from 'sentry/styles/space';
import type {Organization} from 'sentry/types';
import {trackAnalytics} from 'sentry/utils/analytics';
import BuilderBreadCrumbs from 'sentry/views/alerts/builder/builderBreadCrumbs';
import {Dataset} from 'sentry/views/alerts/rules/metric/types';
import {AlertRuleType} from 'sentry/views/alerts/types';
import type {AlertType, WizardRuleTemplate} from './options';
import {
AlertWizardAlertNames,
AlertWizardRuleTemplates,
getAlertWizardCategories,
} from './options';
import {AlertWizardPanelContent} from './panelContent';
import RadioPanelGroup from './radioPanelGroup';
type RouteParams = {
projectId?: string;
};
type AlertWizardProps = RouteComponentProps<RouteParams, {}> & {
organization: Organization;
projectId: string;
};
const DEFAULT_ALERT_OPTION = 'issues';
function AlertWizard({organization, params, location, projectId}: AlertWizardProps) {
const [alertOption, setAlertOption] = useState<AlertType>(
location.query.alert_option in AlertWizardAlertNames
? location.query.alert_option
: DEFAULT_ALERT_OPTION
);
const projectSlug = params.projectId ?? projectId;
const handleChangeAlertOption = (option: AlertType) => {
setAlertOption(option);
};
function renderCreateAlertButton() {
let metricRuleTemplate: Readonly<WizardRuleTemplate> | undefined =
AlertWizardRuleTemplates[alertOption];
const isMetricAlert = !!metricRuleTemplate;
const isTransactionDataset = metricRuleTemplate?.dataset === Dataset.TRANSACTIONS;
if (
organization.features.includes('alert-crash-free-metrics') &&
metricRuleTemplate?.dataset === Dataset.SESSIONS
) {
metricRuleTemplate = {...metricRuleTemplate, dataset: Dataset.METRICS};
}
if (
organization.features.includes('metric-alert-ignore-archived') &&
metricRuleTemplate?.dataset === Dataset.ERRORS
) {
// Pre-fill is:unresolved for error metric alerts
// Filters out events in issues that are archived or resolved
metricRuleTemplate = {...metricRuleTemplate, query: 'is:unresolved'};
}
const renderNoAccess = p => (
<Hovercard
body={
<FeatureDisabled
features={p.features}
hideHelpToggle
featureName={t('Metric Alerts')}
/>
}
>
{p.children(p)}
</Hovercard>
);
return (
<Feature
features={
isTransactionDataset
? ['organizations:incidents', 'organizations:performance-view']
: isMetricAlert
? ['organizations:incidents']
: []
}
requireAll
organization={organization}
hookName="feature-disabled:alert-wizard-performance"
renderDisabled={renderNoAccess}
>
{({hasFeature}) => (
<WizardButtonContainer
onClick={() =>
trackAnalytics('alert_wizard.option_selected', {
organization,
alert_type: alertOption,
})
}
>
<CreateAlertButton
organization={organization}
projectSlug={projectSlug}
disabled={!hasFeature}
priority="primary"
to={{
pathname: `/organizations/${organization.slug}/alerts/new/${
isMetricAlert ? AlertRuleType.METRIC : AlertRuleType.ISSUE
}/`,
query: {
...(metricRuleTemplate ? metricRuleTemplate : {}),
project: projectSlug,
referrer: location?.query?.referrer,
},
}}
hideIcon
>
{t('Set Conditions')}
</CreateAlertButton>
</WizardButtonContainer>
)}
</Feature>
);
}
const panelContent = AlertWizardPanelContent[alertOption];
return (
<Layout.Page>
<SentryDocumentTitle title={t('Alert Creation Wizard')} projectSlug={projectSlug} />
<Layout.Header>
<StyledHeaderContent>
<BuilderBreadCrumbs
organization={organization}
projectSlug={projectSlug}
title={t('Select Alert')}
/>
<Layout.Title>{t('Select Alert')}</Layout.Title>
</StyledHeaderContent>
</Layout.Header>
<Layout.Body>
<Layout.Main fullWidth>
<WizardBody>
<WizardOptions>
{getAlertWizardCategories(organization).map(
({categoryHeading, options}) => (
<div key={categoryHeading}>
<CategoryTitle>{categoryHeading} </CategoryTitle>
<RadioPanelGroup
choices={options.map(alertType => {
return [alertType, AlertWizardAlertNames[alertType]];
})}
onChange={handleChangeAlertOption}
value={alertOption}
label="alert-option"
/>
</div>
)
)}
</WizardOptions>
<WizardPanel visible={!!panelContent && !!alertOption}>
<WizardPanelBody>
<div>
<PanelHeader>{AlertWizardAlertNames[alertOption]}</PanelHeader>
<PanelBody withPadding>
<PanelDescription>
{panelContent.description}{' '}
{panelContent.docsLink && (
<ExternalLink href={panelContent.docsLink}>
{t('Learn more')}
</ExternalLink>
)}
</PanelDescription>
<WizardImage src={panelContent.illustration} />
<ExampleHeader>{t('Examples')}</ExampleHeader>
<ExampleList symbol="bullet">
{panelContent.examples.map((example, i) => (
<ExampleItem key={i}>{example}</ExampleItem>
))}
</ExampleList>
</PanelBody>
</div>
<WizardFooter>{renderCreateAlertButton()}</WizardFooter>
</WizardPanelBody>
</WizardPanel>
</WizardBody>
</Layout.Main>
</Layout.Body>
</Layout.Page>
);
}
const StyledHeaderContent = styled(Layout.HeaderContent)`
overflow: visible;
`;
const CategoryTitle = styled('h2')`
font-weight: normal;
font-size: ${p => p.theme.fontSizeExtraLarge};
margin-bottom: ${space(1)} !important;
`;
const WizardBody = styled('div')`
display: flex;
padding-top: ${space(1)};
`;
const WizardOptions = styled('div')`
display: flex;
flex-direction: column;
gap: ${space(4)};
flex: 3;
margin-right: ${space(3)};
padding-right: ${space(3)};
max-width: 300px;
`;
const WizardImage = styled('img')`
max-height: 300px;
`;
const WizardPanel = styled(Panel)<{visible?: boolean}>`
max-width: 700px;
position: sticky;
top: 20px;
flex: 5;
display: flex;
${p => !p.visible && 'visibility: hidden'};
flex-direction: column;
align-items: start;
align-self: flex-start;
${p => p.visible && 'animation: 0.6s pop ease forwards'};
@keyframes pop {
0% {
transform: translateY(30px);
opacity: 0;
}
100% {
transform: translateY(0);
opacity: 1;
}
}
`;
const ExampleList = styled(List)`
margin-bottom: ${space(2)} !important;
`;
const WizardPanelBody = styled(PanelBody)`
flex: 1;
min-width: 100%;
`;
const PanelDescription = styled('p')`
margin-bottom: ${space(2)};
`;
const ExampleHeader = styled('div')`
margin: 0 0 ${space(1)} 0;
font-size: ${p => p.theme.fontSizeLarge};
`;
const ExampleItem = styled(ListItem)`
font-size: ${p => p.theme.fontSizeMedium};
`;
const WizardFooter = styled('div')`
border-top: 1px solid ${p => p.theme.border};
padding: ${space(1.5)} ${space(1.5)} ${space(1.5)} ${space(1.5)};
`;
const WizardButtonContainer = styled('div')`
display: flex;
justify-content: flex-end;
a:not(:last-child) {
margin-right: ${space(1)};
}
`;
export default AlertWizard;