details.tsx 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. import styled from '@emotion/styled';
  2. import {updateUptimeRule} from 'sentry/actionCreators/uptime';
  3. import ActorAvatar from 'sentry/components/avatar/actorAvatar';
  4. import Breadcrumbs from 'sentry/components/breadcrumbs';
  5. import {LinkButton} from 'sentry/components/button';
  6. import ButtonBar from 'sentry/components/buttonBar';
  7. import {SectionHeading} from 'sentry/components/charts/styles';
  8. import {CodeSnippet} from 'sentry/components/codeSnippet';
  9. import IdBadge from 'sentry/components/idBadge';
  10. import {KeyValueTable, KeyValueTableRow} from 'sentry/components/keyValueTable';
  11. import * as Layout from 'sentry/components/layouts/thirds';
  12. import LoadingError from 'sentry/components/loadingError';
  13. import LoadingIndicator from 'sentry/components/loadingIndicator';
  14. import {DatePageFilter} from 'sentry/components/organizations/datePageFilter';
  15. import {EnvironmentPageFilter} from 'sentry/components/organizations/environmentPageFilter';
  16. import PageFilterBar from 'sentry/components/organizations/pageFilterBar';
  17. import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
  18. import {IconEdit} from 'sentry/icons';
  19. import {t} from 'sentry/locale';
  20. import {space} from 'sentry/styles/space';
  21. import type {RouteComponentProps} from 'sentry/types/legacyReactRouter';
  22. import getDuration from 'sentry/utils/duration/getDuration';
  23. import {
  24. type ApiQueryKey,
  25. setApiQueryData,
  26. useApiQuery,
  27. useQueryClient,
  28. } from 'sentry/utils/queryClient';
  29. import useApi from 'sentry/utils/useApi';
  30. import useOrganization from 'sentry/utils/useOrganization';
  31. import useProjects from 'sentry/utils/useProjects';
  32. import type {UptimeRule} from 'sentry/views/alerts/rules/uptime/types';
  33. import {StatusToggleButton} from './statusToggleButton';
  34. import {UptimeIssues} from './uptimeIssues';
  35. interface UptimeAlertDetailsProps
  36. extends RouteComponentProps<{projectId: string; uptimeRuleId: string}, {}> {}
  37. export default function UptimeAlertDetails({params}: UptimeAlertDetailsProps) {
  38. const api = useApi();
  39. const organization = useOrganization();
  40. const queryClient = useQueryClient();
  41. const {projectId, uptimeRuleId} = params;
  42. const {projects, fetching: loadingProject} = useProjects({slugs: [projectId]});
  43. const project = projects.find(({slug}) => slug === projectId);
  44. const queryKey: ApiQueryKey = [
  45. `/projects/${organization.slug}/${projectId}/uptime/${uptimeRuleId}/`,
  46. ];
  47. const {
  48. data: uptimeRule,
  49. isPending,
  50. isError,
  51. } = useApiQuery<UptimeRule>(queryKey, {staleTime: 0});
  52. if (isError) {
  53. return (
  54. <LoadingError
  55. message={t('The uptime alert rule you were looking for was not found.')}
  56. />
  57. );
  58. }
  59. if (isPending || loadingProject) {
  60. return (
  61. <Layout.Body>
  62. <Layout.Main fullWidth>
  63. <LoadingIndicator />
  64. </Layout.Main>
  65. </Layout.Body>
  66. );
  67. }
  68. if (!project) {
  69. return (
  70. <LoadingError message={t('The project you were looking for was not found.')} />
  71. );
  72. }
  73. const handleUpdate = async (data: Partial<UptimeRule>) => {
  74. const resp = await updateUptimeRule(api, organization.slug, uptimeRule, data);
  75. if (resp !== null) {
  76. setApiQueryData(queryClient, queryKey, resp);
  77. }
  78. };
  79. return (
  80. <Layout.Page>
  81. <SentryDocumentTitle title={`${uptimeRule.name} — Alerts`} />
  82. <Layout.Header>
  83. <Layout.HeaderContent>
  84. <Breadcrumbs
  85. crumbs={[
  86. {
  87. label: t('Alerts'),
  88. to: `/organizations/${organization.slug}/alerts/rules/`,
  89. },
  90. {
  91. label: t('Uptime Monitor'),
  92. },
  93. ]}
  94. />
  95. <Layout.Title>
  96. <IdBadge
  97. project={project}
  98. avatarSize={28}
  99. hideName
  100. avatarProps={{hasTooltip: true, tooltip: project.slug}}
  101. />
  102. {uptimeRule.name}
  103. </Layout.Title>
  104. </Layout.HeaderContent>
  105. <Layout.HeaderActions>
  106. <ButtonBar gap={1}>
  107. <StatusToggleButton
  108. uptimeRule={uptimeRule}
  109. onToggleStatus={status => handleUpdate({status})}
  110. size="sm"
  111. />
  112. <LinkButton
  113. size="sm"
  114. icon={<IconEdit />}
  115. to={`/organizations/${organization.slug}/alerts/uptime-rules/${project.slug}/${uptimeRuleId}/`}
  116. >
  117. {t('Edit Rule')}
  118. </LinkButton>
  119. </ButtonBar>
  120. </Layout.HeaderActions>
  121. </Layout.Header>
  122. <Layout.Body>
  123. <Layout.Main>
  124. <StyledPageFilterBar condensed>
  125. <DatePageFilter />
  126. <EnvironmentPageFilter />
  127. </StyledPageFilterBar>
  128. <UptimeIssues project={project} ruleId={uptimeRuleId} />
  129. </Layout.Main>
  130. <Layout.Side>
  131. <SectionHeading>{t('Checked URL')}</SectionHeading>
  132. <CodeSnippet
  133. hideCopyButton
  134. >{`${uptimeRule.method} ${uptimeRule.url}`}</CodeSnippet>
  135. <SectionHeading>{t('Configuration')}</SectionHeading>
  136. <KeyValueTable>
  137. <KeyValueTableRow
  138. keyName={t('Check Interval')}
  139. value={t('Every %s', getDuration(uptimeRule.intervalSeconds))}
  140. />
  141. <KeyValueTableRow
  142. keyName={t('Timeout')}
  143. value={t('After %s', getDuration(uptimeRule.timeoutMs / 1000, 2))}
  144. />
  145. <KeyValueTableRow keyName={t('Environment')} value={uptimeRule.environment} />
  146. <KeyValueTableRow
  147. keyName={t('Owner')}
  148. value={
  149. uptimeRule.owner ? (
  150. <ActorAvatar actor={uptimeRule.owner} />
  151. ) : (
  152. t('Unassigned')
  153. )
  154. }
  155. />
  156. </KeyValueTable>
  157. </Layout.Side>
  158. </Layout.Body>
  159. </Layout.Page>
  160. );
  161. }
  162. const StyledPageFilterBar = styled(PageFilterBar)`
  163. margin-bottom: ${space(2)};
  164. `;