import {useEffect, useState} from 'react'; import styled from '@emotion/styled'; import {autorun} from 'mobx'; import {Observer} from 'mobx-react'; import {Button} from 'sentry/components/button'; import Confirm from 'sentry/components/confirm'; import FieldWrapper from 'sentry/components/forms/fieldGroup/fieldWrapper'; import BooleanField from 'sentry/components/forms/fields/booleanField'; import HiddenField from 'sentry/components/forms/fields/hiddenField'; import RangeField from 'sentry/components/forms/fields/rangeField'; import SelectField from 'sentry/components/forms/fields/selectField'; import SentryMemberTeamSelectorField from 'sentry/components/forms/fields/sentryMemberTeamSelectorField'; import SentryProjectSelectorField from 'sentry/components/forms/fields/sentryProjectSelectorField'; import TextareaField from 'sentry/components/forms/fields/textareaField'; import TextField from 'sentry/components/forms/fields/textField'; import Form from 'sentry/components/forms/form'; import FormModel from 'sentry/components/forms/model'; import List from 'sentry/components/list'; import ListItem from 'sentry/components/list/listItem'; import Panel from 'sentry/components/panels/panel'; import Text from 'sentry/components/text'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import type {Organization} from 'sentry/types/organization'; import type {Project} from 'sentry/types/project'; import getDuration from 'sentry/utils/duration/getDuration'; import normalizeUrl from 'sentry/utils/url/normalizeUrl'; import {useNavigate} from 'sentry/utils/useNavigate'; import useOrganization from 'sentry/utils/useOrganization'; import useProjects from 'sentry/utils/useProjects'; import type {UptimeRule} from 'sentry/views/alerts/rules/uptime/types'; import {HTTPSnippet} from './httpSnippet'; import {UptimeHeadersField} from './uptimeHeadersField'; interface Props { organization: Organization; project: Project; handleDelete?: () => void; rule?: UptimeRule; } const HTTP_METHOD_OPTIONS = ['GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'PATCH', 'OPTIONS']; const MINUTE = 60; const VALID_INTERVALS_SEC = [ MINUTE * 1, MINUTE * 5, MINUTE * 10, MINUTE * 20, MINUTE * 30, MINUTE * 60, ]; function getFormDataFromRule(rule: UptimeRule) { return { name: rule.name, environment: rule.environment, url: rule.url, projectSlug: rule.projectSlug, method: rule.method, body: rule.body, headers: rule.headers, intervalSeconds: rule.intervalSeconds, timeoutMs: rule.timeoutMs, traceSampling: rule.traceSampling, owner: rule.owner ? `${rule.owner.type}:${rule.owner.id}` : null, }; } export function UptimeAlertForm({project, handleDelete, rule}: Props) { const navigate = useNavigate(); const organization = useOrganization(); const {projects} = useProjects(); const initialData = rule ? getFormDataFromRule(rule) : {projectSlug: project.slug, method: 'GET', headers: []}; const [formModel] = useState(() => new FormModel()); const [knownEnvironments, setEnvironments] = useState([]); const [newEnvironment, setNewEnvironment] = useState(undefined); const environments = [newEnvironment, ...knownEnvironments].filter(Boolean); // XXX(epurkhiser): The forms API endpoint is derived from the selcted // project. We don't have an easy way to interpolate this into the
// components `apiEndpoint` prop, so instead we setup a mobx observer on // value of the project slug and use that to update the endpoint of the form // model useEffect( () => autorun(() => { const projectSlug = formModel.getValue('projectSlug'); const selectedProject = projects.find(p => p.slug === projectSlug); const apiEndpoint = rule ? `/projects/${organization.slug}/${projectSlug}/uptime/${rule.id}/` : `/projects/${organization.slug}/${projectSlug}/uptime/`; function onSubmitSuccess(response: any) { navigate( normalizeUrl( `/organizations/${organization.slug}/alerts/rules/uptime/${projectSlug}/${response.id}/details/` ) ); } formModel.setFormOptions({apiEndpoint, onSubmitSuccess}); if (selectedProject) { setEnvironments(selectedProject.environments); } }), [formModel, navigate, organization.slug, projects, rule] ); return ( {t('Delete Uptime Rule?')}} priority="danger" confirmText={t('Delete Rule')} onConfirm={handleDelete} > ) : undefined } > {t('Select a project and environment')} {t( 'The selected project and environment is where Uptime Issues will be created.' )} { setNewEnvironment(env); formModel.setValue('environment', env); }} creatable options={environments.map(e => ({value: e, label: e}))} inline={false} flexibleControlStateSize stacked required /> {t('Configure Request')} {t('Configure the HTTP request made for uptime checks.')} ({ value, label: t('Every %s', getDuration(value)), }))} name="intervalSeconds" label={t('Interval')} defaultValue={60} flexibleControlStateSize required /> getDuration((value || 0) / 1000, 2, true)} flexibleControlStateSize required /> ({ value: option, label: option, }))} flexibleControlStateSize required /> !['GET', 'HEAD'].includes(model.getValue('method'))} rows={4} maxRows={15} autosize monospace placeholder='{"key": "value"}' flexibleControlStateSize /> {() => ( )} {t('Establish ownership')} {t( 'Choose a team or member as the rule owner. Issues created will be automatically assigned to the owner.' )}
); } const AlertListItem = styled(ListItem)` font-size: ${p => p.theme.fontSizeExtraLarge}; font-weight: ${p => p.theme.fontWeightBold}; line-height: 1.3; `; const ListItemSubText = styled(Text)` padding-left: ${space(4)}; color: ${p => p.theme.subText}; `; const FormRow = styled('div')` display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); align-items: center; gap: ${space(2)}; margin-top: ${space(1)}; margin-bottom: ${space(4)}; margin-left: ${space(4)}; ${FieldWrapper} { padding: 0; } `; const Configuration = styled('div')` margin-top: ${space(1)}; margin-bottom: ${space(4)}; margin-left: ${space(4)}; `; const ConfigurationPanel = styled(Panel)` display: grid; gap: 0 ${space(2)}; grid-template-columns: max-content 1fr; align-items: center; ${FieldWrapper} { display: grid; grid-template-columns: subgrid; grid-column: 1 / -1; label { width: auto; } } `;