|
@@ -1,168 +1,177 @@
|
|
|
import {Fragment} from 'react';
|
|
|
-import {RouteComponentProps} from 'react-router';
|
|
|
+import type {RouteComponentProps} from 'react-router';
|
|
|
|
|
|
import AlertLink from 'sentry/components/alertLink';
|
|
|
-import {Button} from 'sentry/components/button';
|
|
|
+import {LinkButton} from 'sentry/components/button';
|
|
|
import Form from 'sentry/components/forms/form';
|
|
|
import JsonForm from 'sentry/components/forms/jsonForm';
|
|
|
+import LoadingError from 'sentry/components/loadingError';
|
|
|
+import LoadingIndicator from 'sentry/components/loadingIndicator';
|
|
|
import PanelAlert from 'sentry/components/panels/panelAlert';
|
|
|
import PluginList from 'sentry/components/pluginList';
|
|
|
+import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
|
|
|
import {fields} from 'sentry/data/forms/projectAlerts';
|
|
|
import {IconMail} from 'sentry/icons';
|
|
|
import {t} from 'sentry/locale';
|
|
|
-import {Organization, Plugin, Project} from 'sentry/types';
|
|
|
+import type {Plugin, Project} from 'sentry/types';
|
|
|
+import {
|
|
|
+ ApiQueryKey,
|
|
|
+ setApiQueryData,
|
|
|
+ useApiQuery,
|
|
|
+ useQueryClient,
|
|
|
+} from 'sentry/utils/queryClient';
|
|
|
import routeTitleGen from 'sentry/utils/routeTitle';
|
|
|
-import DeprecatedAsyncView from 'sentry/views/deprecatedAsyncView';
|
|
|
+import useOrganization from 'sentry/utils/useOrganization';
|
|
|
import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader';
|
|
|
import PermissionAlert from 'sentry/views/settings/project/permissionAlert';
|
|
|
|
|
|
-type RouteParams = {projectId: string};
|
|
|
-
|
|
|
-type Props = RouteComponentProps<RouteParams, {}> &
|
|
|
- DeprecatedAsyncView['props'] & {
|
|
|
- canEditRule: boolean;
|
|
|
- organization: Organization;
|
|
|
- };
|
|
|
-
|
|
|
-type State = DeprecatedAsyncView['state'] & {
|
|
|
- pluginList: Array<Plugin> | null;
|
|
|
- project: Project | null;
|
|
|
-};
|
|
|
-
|
|
|
-class Settings extends DeprecatedAsyncView<Props, State> {
|
|
|
- getDefaultState() {
|
|
|
- return {
|
|
|
- ...super.getDefaultState(),
|
|
|
- project: null,
|
|
|
- pluginList: [],
|
|
|
- };
|
|
|
- }
|
|
|
+interface ProjectAlertSettingsProps extends RouteComponentProps<{projectId: string}, {}> {
|
|
|
+ canEditRule: boolean;
|
|
|
+}
|
|
|
|
|
|
- getProjectEndpoint() {
|
|
|
- const {organization, params} = this.props;
|
|
|
- return `/projects/${organization.slug}/${params.projectId}/`;
|
|
|
- }
|
|
|
+function makeFetchProjectPluginsQueryKey(
|
|
|
+ organizationSlug: string,
|
|
|
+ projectSlug: string
|
|
|
+): ApiQueryKey {
|
|
|
+ return [`/projects/${organizationSlug}/${projectSlug}/plugins/`];
|
|
|
+}
|
|
|
|
|
|
- getEndpoints(): ReturnType<DeprecatedAsyncView['getEndpoints']> {
|
|
|
- const {organization, params} = this.props;
|
|
|
- const projectEndpoint = this.getProjectEndpoint();
|
|
|
- return [
|
|
|
- ['project', projectEndpoint],
|
|
|
- ['pluginList', `/projects/${organization.slug}/${params.projectId}/plugins/`],
|
|
|
- ];
|
|
|
+function ProjectAlertSettings({canEditRule, params}: ProjectAlertSettingsProps) {
|
|
|
+ const organization = useOrganization();
|
|
|
+ const queryClient = useQueryClient();
|
|
|
+
|
|
|
+ const projectSlug = params.projectId;
|
|
|
+ const {
|
|
|
+ data: project,
|
|
|
+ isLoading: isProjectLoading,
|
|
|
+ isError: isProjectError,
|
|
|
+ refetch: refetchProject,
|
|
|
+ } = useApiQuery<Project>([`/projects/${organization.slug}/${projectSlug}/`], {
|
|
|
+ staleTime: 0,
|
|
|
+ cacheTime: 0,
|
|
|
+ });
|
|
|
+ const {
|
|
|
+ data: pluginList = [],
|
|
|
+ isLoading: isPluginListLoading,
|
|
|
+ isError: isPluginListError,
|
|
|
+ refetch: refetchPluginList,
|
|
|
+ } = useApiQuery<Plugin[]>(
|
|
|
+ makeFetchProjectPluginsQueryKey(organization.slug, projectSlug),
|
|
|
+ {staleTime: 0, cacheTime: 0}
|
|
|
+ );
|
|
|
+
|
|
|
+ if ((!isProjectLoading && !project) || isPluginListError || isProjectError) {
|
|
|
+ return (
|
|
|
+ <LoadingError
|
|
|
+ onRetry={() => {
|
|
|
+ isProjectError && refetchProject();
|
|
|
+ isPluginListError && refetchPluginList();
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
- handleEnablePlugin = (plugin: Plugin) => {
|
|
|
- this.setState(prevState => ({
|
|
|
- pluginList: (prevState.pluginList ?? []).map(p => {
|
|
|
- if (p.id !== plugin.id) {
|
|
|
- return p;
|
|
|
- }
|
|
|
- return {
|
|
|
- ...plugin,
|
|
|
- enabled: true,
|
|
|
- };
|
|
|
- }),
|
|
|
- }));
|
|
|
+ const updatePlugin = (plugin: Plugin, enabled: boolean) => {
|
|
|
+ setApiQueryData<Plugin[]>(
|
|
|
+ queryClient,
|
|
|
+ makeFetchProjectPluginsQueryKey(organization.slug, projectSlug),
|
|
|
+ oldState =>
|
|
|
+ oldState.map(p => {
|
|
|
+ if (p.id !== plugin.id) {
|
|
|
+ return p;
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ ...plugin,
|
|
|
+ enabled,
|
|
|
+ };
|
|
|
+ })
|
|
|
+ );
|
|
|
};
|
|
|
|
|
|
- handleDisablePlugin = (plugin: Plugin) => {
|
|
|
- this.setState(prevState => ({
|
|
|
- pluginList: (prevState.pluginList ?? []).map(p => {
|
|
|
- if (p.id !== plugin.id) {
|
|
|
- return p;
|
|
|
- }
|
|
|
- return {
|
|
|
- ...plugin,
|
|
|
- enabled: false,
|
|
|
- };
|
|
|
- }),
|
|
|
- }));
|
|
|
+ const handleEnablePlugin = (plugin: Plugin) => {
|
|
|
+ updatePlugin(plugin, true);
|
|
|
};
|
|
|
|
|
|
- getTitle() {
|
|
|
- const {projectId} = this.props.params;
|
|
|
- return routeTitleGen(t('Alerts Settings'), projectId, false);
|
|
|
- }
|
|
|
-
|
|
|
- renderBody() {
|
|
|
- const {canEditRule, organization} = this.props;
|
|
|
- const {project, pluginList} = this.state;
|
|
|
-
|
|
|
- if (!project) {
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- const projectEndpoint = this.getProjectEndpoint();
|
|
|
-
|
|
|
- return (
|
|
|
- <Fragment>
|
|
|
- <SettingsPageHeader
|
|
|
- title={t('Alerts Settings')}
|
|
|
- action={
|
|
|
- <Button
|
|
|
- to={{
|
|
|
- pathname: `/organizations/${organization.slug}/alerts/rules/`,
|
|
|
- query: {project: project.id},
|
|
|
- }}
|
|
|
- size="sm"
|
|
|
- >
|
|
|
- {t('View Alert Rules')}
|
|
|
- </Button>
|
|
|
- }
|
|
|
- />
|
|
|
- <PermissionAlert project={project} />
|
|
|
- <AlertLink to="/settings/account/notifications/" icon={<IconMail />}>
|
|
|
- {t(
|
|
|
- 'Looking to fine-tune your personal notification preferences? Visit your Account Settings'
|
|
|
- )}
|
|
|
- </AlertLink>
|
|
|
-
|
|
|
- <Form
|
|
|
- saveOnBlur
|
|
|
- allowUndo
|
|
|
- initialData={{
|
|
|
- subjectTemplate: project.subjectTemplate,
|
|
|
- digestsMinDelay: project.digestsMinDelay,
|
|
|
- digestsMaxDelay: project.digestsMaxDelay,
|
|
|
- }}
|
|
|
- apiMethod="PUT"
|
|
|
- apiEndpoint={projectEndpoint}
|
|
|
- >
|
|
|
- <JsonForm
|
|
|
- disabled={!canEditRule}
|
|
|
- title={t('Email Settings')}
|
|
|
- fields={[fields.subjectTemplate]}
|
|
|
- />
|
|
|
-
|
|
|
- <JsonForm
|
|
|
- title={t('Digests')}
|
|
|
- disabled={!canEditRule}
|
|
|
- fields={[fields.digestsMinDelay, fields.digestsMaxDelay]}
|
|
|
- renderHeader={() => (
|
|
|
- <PanelAlert type="info">
|
|
|
- {t(
|
|
|
- '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.'
|
|
|
- )}
|
|
|
- </PanelAlert>
|
|
|
- )}
|
|
|
- />
|
|
|
- </Form>
|
|
|
+ const handleDisablePlugin = (plugin: Plugin) => {
|
|
|
+ updatePlugin(plugin, false);
|
|
|
+ };
|
|
|
|
|
|
- {canEditRule && (
|
|
|
- <PluginList
|
|
|
- organization={organization}
|
|
|
- project={project}
|
|
|
- pluginList={(pluginList ?? []).filter(
|
|
|
- p => p.type === 'notification' && p.hasConfiguration
|
|
|
- )}
|
|
|
- onEnablePlugin={this.handleEnablePlugin}
|
|
|
- onDisablePlugin={this.handleDisablePlugin}
|
|
|
- />
|
|
|
+ return (
|
|
|
+ <Fragment>
|
|
|
+ <SentryDocumentTitle
|
|
|
+ title={routeTitleGen(t('Alerts Settings'), projectSlug, false)}
|
|
|
+ />
|
|
|
+ <SettingsPageHeader
|
|
|
+ title={t('Alerts Settings')}
|
|
|
+ action={
|
|
|
+ <LinkButton
|
|
|
+ to={{
|
|
|
+ pathname: `/organizations/${organization.slug}/alerts/rules/`,
|
|
|
+ query: {project: project?.id},
|
|
|
+ }}
|
|
|
+ size="sm"
|
|
|
+ >
|
|
|
+ {t('View Alert Rules')}
|
|
|
+ </LinkButton>
|
|
|
+ }
|
|
|
+ />
|
|
|
+ <PermissionAlert project={project} />
|
|
|
+ <AlertLink to="/settings/account/notifications/" icon={<IconMail />}>
|
|
|
+ {t(
|
|
|
+ 'Looking to fine-tune your personal notification preferences? Visit your Account Settings'
|
|
|
)}
|
|
|
- </Fragment>
|
|
|
- );
|
|
|
- }
|
|
|
+ </AlertLink>
|
|
|
+
|
|
|
+ {isProjectLoading || isPluginListLoading ? (
|
|
|
+ <LoadingIndicator />
|
|
|
+ ) : (
|
|
|
+ <Fragment>
|
|
|
+ <Form
|
|
|
+ saveOnBlur
|
|
|
+ allowUndo
|
|
|
+ initialData={{
|
|
|
+ subjectTemplate: project.subjectTemplate,
|
|
|
+ digestsMinDelay: project.digestsMinDelay,
|
|
|
+ digestsMaxDelay: project.digestsMaxDelay,
|
|
|
+ }}
|
|
|
+ apiMethod="PUT"
|
|
|
+ apiEndpoint={`/projects/${organization.slug}/${project.slug}/`}
|
|
|
+ >
|
|
|
+ <JsonForm
|
|
|
+ disabled={!canEditRule}
|
|
|
+ title={t('Email Settings')}
|
|
|
+ fields={[fields.subjectTemplate]}
|
|
|
+ />
|
|
|
+
|
|
|
+ <JsonForm
|
|
|
+ title={t('Digests')}
|
|
|
+ disabled={!canEditRule}
|
|
|
+ fields={[fields.digestsMinDelay, fields.digestsMaxDelay]}
|
|
|
+ renderHeader={() => (
|
|
|
+ <PanelAlert type="info">
|
|
|
+ {t(
|
|
|
+ '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.'
|
|
|
+ )}
|
|
|
+ </PanelAlert>
|
|
|
+ )}
|
|
|
+ />
|
|
|
+ </Form>
|
|
|
+
|
|
|
+ {canEditRule && (
|
|
|
+ <PluginList
|
|
|
+ organization={organization}
|
|
|
+ project={project}
|
|
|
+ pluginList={(pluginList ?? []).filter(
|
|
|
+ p => p.type === 'notification' && p.hasConfiguration
|
|
|
+ )}
|
|
|
+ onEnablePlugin={handleEnablePlugin}
|
|
|
+ onDisablePlugin={handleDisablePlugin}
|
|
|
+ />
|
|
|
+ )}
|
|
|
+ </Fragment>
|
|
|
+ )}
|
|
|
+ </Fragment>
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
-export default Settings;
|
|
|
+export default ProjectAlertSettings;
|