edit.tsx 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. import type {RouteComponentProps} from 'react-router';
  2. import styled from '@emotion/styled';
  3. import {addErrorMessage} from 'sentry/actionCreators/indicator';
  4. import Alert from 'sentry/components/alert';
  5. import {Button} from 'sentry/components/button';
  6. import Confirm from 'sentry/components/confirm';
  7. import FieldWrapper from 'sentry/components/forms/fieldGroup/fieldWrapper';
  8. import SelectField from 'sentry/components/forms/fields/selectField';
  9. import SentryMemberTeamSelectorField from 'sentry/components/forms/fields/sentryMemberTeamSelectorField';
  10. import SentryProjectSelectorField from 'sentry/components/forms/fields/sentryProjectSelectorField';
  11. import TextField from 'sentry/components/forms/fields/textField';
  12. import Form from 'sentry/components/forms/form';
  13. import List from 'sentry/components/list';
  14. import ListItem from 'sentry/components/list/listItem';
  15. import LoadingError from 'sentry/components/loadingError';
  16. import LoadingIndicator from 'sentry/components/loadingIndicator';
  17. import {IconLab} from 'sentry/icons';
  18. import {t} from 'sentry/locale';
  19. import {space} from 'sentry/styles/space';
  20. import type {Organization} from 'sentry/types/organization';
  21. import type {Project} from 'sentry/types/project';
  22. import {useApiQuery} from 'sentry/utils/queryClient';
  23. import normalizeUrl from 'sentry/utils/url/normalizeUrl';
  24. import useApi from 'sentry/utils/useApi';
  25. import {useNavigate} from 'sentry/utils/useNavigate';
  26. import type {UptimeAlert} from 'sentry/views/alerts/types';
  27. type RouteParams = {
  28. projectId: string;
  29. ruleId: string;
  30. };
  31. type Props = {
  32. onChangeTitle: (data: string) => void;
  33. organization: Organization;
  34. project: Project;
  35. userTeamIds: string[];
  36. } & RouteComponentProps<RouteParams, {}>;
  37. export function UptimeRulesEdit({params, onChangeTitle, organization, project}: Props) {
  38. const api = useApi();
  39. const navigate = useNavigate();
  40. const apiUrl = `/projects/${organization.slug}/${params.projectId}/uptime/${params.ruleId}/`;
  41. const {
  42. isLoading,
  43. isError,
  44. data: rule,
  45. error,
  46. } = useApiQuery<UptimeAlert>([apiUrl], {
  47. staleTime: 0,
  48. retry: false,
  49. onSuccess: data => onChangeTitle(data[0]?.name ?? ''),
  50. });
  51. if (isLoading) {
  52. return <LoadingIndicator />;
  53. }
  54. if (isError) {
  55. if (error?.status === 404) {
  56. return (
  57. <Alert type="error" showIcon>
  58. {t('This alert rule could not be found.')}
  59. </Alert>
  60. );
  61. }
  62. return <LoadingError />;
  63. }
  64. const handleDelete = async () => {
  65. try {
  66. await api.requestPromise(apiUrl, {method: 'DELETE'});
  67. navigate(normalizeUrl(`/organizations/${organization.slug}/alerts/rules/`));
  68. } catch (_err) {
  69. addErrorMessage(t('Error deleting rule'));
  70. }
  71. };
  72. const {name, url, projectSlug} = rule;
  73. const owner = rule?.owner ? `${rule.owner.type}:${rule.owner.id}` : null;
  74. return (
  75. <UptimeForm
  76. apiMethod="PUT"
  77. apiEndpoint={apiUrl}
  78. saveOnBlur={false}
  79. initialData={{projectSlug, url, name, owner}}
  80. onSubmitSuccess={() => {
  81. navigate(
  82. normalizeUrl(
  83. `/organizations/${organization.slug}/alerts/rules/uptime/${params.projectId}/${params.ruleId}/details`
  84. )
  85. );
  86. }}
  87. extraButton={
  88. <Confirm
  89. message={t(
  90. 'Are you sure you want to delete "%s"? Once deleted, this alert cannot be recreated automatically.',
  91. rule.name
  92. )}
  93. header={<h5>{t('Delete Uptime Rule?')}</h5>}
  94. priority="danger"
  95. confirmText={t('Delete Rule')}
  96. onConfirm={handleDelete}
  97. >
  98. <Button priority="danger">{t('Delete Rule')}</Button>
  99. </Confirm>
  100. }
  101. >
  102. <Alert type="info" showIcon icon={<IconLab />}>
  103. {t(
  104. 'Uptime Monitoring is currently in Early Access. Additional configuration options will be available soon.'
  105. )}
  106. </Alert>
  107. <List symbol="colored-numeric">
  108. <AlertListItem>{t('Select an environment and project')}</AlertListItem>
  109. <FormRow>
  110. <SentryProjectSelectorField
  111. disabled
  112. name="projectSlug"
  113. label={t('Project')}
  114. hideLabel
  115. projects={[project]}
  116. valueIsSlug
  117. inline={false}
  118. flexibleControlStateSize
  119. stacked
  120. />
  121. <SelectField
  122. disabled
  123. name="environment"
  124. label={t('Environment')}
  125. hideLabel
  126. placeholder={t('Production')}
  127. inline={false}
  128. flexibleControlStateSize
  129. stacked
  130. />
  131. </FormRow>
  132. <AlertListItem>{t('Set a URL to monitor')}</AlertListItem>
  133. <FormRow>
  134. <TextField
  135. disabled
  136. name="url"
  137. label={t('URL')}
  138. hideLabel
  139. placeholder={t('The URL to monitor')}
  140. inline={false}
  141. flexibleControlStateSize
  142. stacked
  143. />
  144. </FormRow>
  145. <AlertListItem>{t('Establish ownership')}</AlertListItem>
  146. <FormRow>
  147. <TextField
  148. name="name"
  149. label={t('Uptime rule name')}
  150. hideLabel
  151. placeholder={t('Uptime rule name')}
  152. inline={false}
  153. flexibleControlStateSize
  154. stacked
  155. />
  156. <SentryMemberTeamSelectorField
  157. name="owner"
  158. label={t('Owner')}
  159. hideLabel
  160. menuPlacement="auto"
  161. inline={false}
  162. flexibleControlStateSize
  163. stacked
  164. style={{
  165. padding: 0,
  166. border: 'none',
  167. }}
  168. />
  169. </FormRow>
  170. </List>
  171. </UptimeForm>
  172. );
  173. }
  174. const UptimeForm = styled(Form)`
  175. ${FieldWrapper} {
  176. padding: 0;
  177. }
  178. `;
  179. const AlertListItem = styled(ListItem)`
  180. margin: ${space(2)} 0 ${space(1)} 0;
  181. font-size: ${p => p.theme.fontSizeExtraLarge};
  182. `;
  183. const FormRow = styled('div')`
  184. display: grid;
  185. grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  186. align-items: center;
  187. gap: ${space(2)};
  188. `;