settings.tsx 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. import {Fragment} from 'react';
  2. import {LinkButton} from 'sentry/components/button';
  3. import {AlertLink} from 'sentry/components/core/alert/alertLink';
  4. import Form from 'sentry/components/forms/form';
  5. import JsonForm from 'sentry/components/forms/jsonForm';
  6. import LoadingError from 'sentry/components/loadingError';
  7. import LoadingIndicator from 'sentry/components/loadingIndicator';
  8. import PanelAlert from 'sentry/components/panels/panelAlert';
  9. import PluginList from 'sentry/components/pluginList';
  10. import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
  11. import {fields} from 'sentry/data/forms/projectAlerts';
  12. import {IconMail} from 'sentry/icons';
  13. import {t} from 'sentry/locale';
  14. import type {Plugin} from 'sentry/types/integrations';
  15. import type {RouteComponentProps} from 'sentry/types/legacyReactRouter';
  16. import type {Project} from 'sentry/types/project';
  17. import type {ApiQueryKey} from 'sentry/utils/queryClient';
  18. import {setApiQueryData, useApiQuery, useQueryClient} from 'sentry/utils/queryClient';
  19. import routeTitleGen from 'sentry/utils/routeTitle';
  20. import useOrganization from 'sentry/utils/useOrganization';
  21. import {makeAlertsPathname} from 'sentry/views/alerts/pathnames';
  22. import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader';
  23. import {ProjectPermissionAlert} from 'sentry/views/settings/project/projectPermissionAlert';
  24. interface ProjectAlertSettingsProps extends RouteComponentProps<{projectId: string}> {
  25. canEditRule: boolean;
  26. }
  27. function makeFetchProjectPluginsQueryKey(
  28. organizationSlug: string,
  29. projectSlug: string
  30. ): ApiQueryKey {
  31. return [`/projects/${organizationSlug}/${projectSlug}/plugins/`];
  32. }
  33. function ProjectAlertSettings({canEditRule, params}: ProjectAlertSettingsProps) {
  34. const organization = useOrganization();
  35. const queryClient = useQueryClient();
  36. const projectSlug = params.projectId;
  37. const {
  38. data: project,
  39. isPending: isProjectLoading,
  40. isError: isProjectError,
  41. refetch: refetchProject,
  42. } = useApiQuery<Project>([`/projects/${organization.slug}/${projectSlug}/`], {
  43. staleTime: 0,
  44. gcTime: 0,
  45. });
  46. const {
  47. data: pluginList = [],
  48. isPending: isPluginListLoading,
  49. isError: isPluginListError,
  50. refetch: refetchPluginList,
  51. } = useApiQuery<Plugin[]>(
  52. makeFetchProjectPluginsQueryKey(organization.slug, projectSlug),
  53. {staleTime: 0, gcTime: 0}
  54. );
  55. if ((!isProjectLoading && !project) || isPluginListError || isProjectError) {
  56. return (
  57. <LoadingError
  58. onRetry={() => {
  59. if (isProjectError) {
  60. refetchProject();
  61. }
  62. if (isPluginListError) {
  63. refetchPluginList();
  64. }
  65. }}
  66. />
  67. );
  68. }
  69. const updatePlugin = (plugin: Plugin, enabled: boolean) => {
  70. setApiQueryData<Plugin[]>(
  71. queryClient,
  72. makeFetchProjectPluginsQueryKey(organization.slug, projectSlug),
  73. oldState =>
  74. oldState.map(p => {
  75. if (p.id !== plugin.id) {
  76. return p;
  77. }
  78. return {
  79. ...plugin,
  80. enabled,
  81. };
  82. })
  83. );
  84. };
  85. const handleEnablePlugin = (plugin: Plugin) => {
  86. updatePlugin(plugin, true);
  87. };
  88. const handleDisablePlugin = (plugin: Plugin) => {
  89. updatePlugin(plugin, false);
  90. };
  91. return (
  92. <Fragment>
  93. <SentryDocumentTitle
  94. title={routeTitleGen(t('Alerts Settings'), projectSlug, false)}
  95. />
  96. <SettingsPageHeader
  97. title={t('Alerts Settings')}
  98. action={
  99. <LinkButton
  100. to={{
  101. pathname: makeAlertsPathname({
  102. path: `/rules/`,
  103. organization,
  104. }),
  105. query: {project: project?.id},
  106. }}
  107. size="sm"
  108. >
  109. {t('View Alert Rules')}
  110. </LinkButton>
  111. }
  112. />
  113. <ProjectPermissionAlert project={project} />
  114. <AlertLink.Container>
  115. <AlertLink
  116. to="/settings/account/notifications/"
  117. trailingItems={<IconMail />}
  118. type="info"
  119. >
  120. {t(
  121. 'Looking to fine-tune your personal notification preferences? Visit your Account Settings'
  122. )}
  123. </AlertLink>
  124. </AlertLink.Container>
  125. {isProjectLoading || isPluginListLoading ? (
  126. <LoadingIndicator />
  127. ) : (
  128. <Fragment>
  129. <Form
  130. saveOnBlur
  131. allowUndo
  132. initialData={{
  133. subjectTemplate: project.subjectTemplate,
  134. digestsMinDelay: project.digestsMinDelay,
  135. digestsMaxDelay: project.digestsMaxDelay,
  136. }}
  137. apiMethod="PUT"
  138. apiEndpoint={`/projects/${organization.slug}/${project.slug}/`}
  139. >
  140. <JsonForm
  141. disabled={!canEditRule}
  142. title={t('Email Settings')}
  143. fields={[fields.subjectTemplate!]}
  144. />
  145. <JsonForm
  146. title={t('Digests')}
  147. disabled={!canEditRule}
  148. fields={[fields.digestsMinDelay!, fields.digestsMaxDelay!]}
  149. renderHeader={() => (
  150. <PanelAlert type="info">
  151. {t(
  152. 'Sentry will automatically digest alerts sent by some services to avoid flooding your inbox with individual issue notifications. To control how frequently notifications are delivered, use the sliders below.'
  153. )}
  154. </PanelAlert>
  155. )}
  156. />
  157. </Form>
  158. {canEditRule && (
  159. <PluginList
  160. organization={organization}
  161. project={project}
  162. pluginList={(pluginList ?? []).filter(
  163. p => p.type === 'notification' && p.hasConfiguration
  164. )}
  165. onEnablePlugin={handleEnablePlugin}
  166. onDisablePlugin={handleDisablePlugin}
  167. />
  168. )}
  169. </Fragment>
  170. )}
  171. </Fragment>
  172. );
  173. }
  174. export default ProjectAlertSettings;