123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278 |
- import {Fragment, useCallback, useEffect, useMemo, useState} from 'react';
- import styled from '@emotion/styled';
- import MultipleCheckbox from 'sentry/components/forms/controls/multipleCheckbox';
- import {t, tct} from 'sentry/locale';
- import {space} from 'sentry/styles/space';
- import {type IntegrationAction, IssueAlertActionType} from 'sentry/types/alerts';
- import type {OrganizationIntegration} from 'sentry/types/integrations';
- import {useApiQuery} from 'sentry/utils/queryClient';
- import useRouteAnalyticsParams from 'sentry/utils/routeAnalytics/useRouteAnalyticsParams';
- import useApi from 'sentry/utils/useApi';
- import useOrganization from 'sentry/utils/useOrganization';
- import SetupMessagingIntegrationButton, {
- MessagingIntegrationAnalyticsView,
- } from 'sentry/views/alerts/rules/issue/setupMessagingIntegrationButton';
- import MessagingIntegrationAlertRule from 'sentry/views/projectInstall/messagingIntegrationAlertRule';
- export const providerDetails = {
- slack: {
- name: t('Slack'),
- action: IssueAlertActionType.SLACK,
- placeholder: t('channel, e.g. #critical'),
- makeSentence: ({providerName, integrationName, target}) =>
- tct(
- 'Send [providerName] notification to the [integrationName] workspace to [target]',
- {
- providerName,
- integrationName,
- target,
- }
- ),
- },
- discord: {
- name: t('Discord'),
- action: IssueAlertActionType.DISCORD,
- placeholder: t('channel ID or URL'),
- makeSentence: ({providerName, integrationName, target}) =>
- tct(
- 'Send [providerName] notification to the [integrationName] server in the channel [target]',
- {
- providerName,
- integrationName,
- target,
- }
- ),
- },
- msteams: {
- name: t('MS Teams'),
- action: IssueAlertActionType.MS_TEAMS,
- placeholder: t('channel ID'),
- makeSentence: ({providerName, integrationName, target}) =>
- tct('Send [providerName] notification to the [integrationName] team to [target]', {
- providerName,
- integrationName,
- target,
- }),
- },
- };
- export const enum MultipleCheckboxOptions {
- EMAIL = 'email',
- INTEGRATION = 'integration',
- }
- export type IssueAlertNotificationProps = {
- actions: MultipleCheckboxOptions[];
- channel: string | undefined;
- integration: OrganizationIntegration | undefined;
- provider: string | undefined;
- providersToIntegrations: Record<string, OrganizationIntegration[]>;
- querySuccess: boolean;
- setActions: (action: MultipleCheckboxOptions[]) => void;
- setChannel: (channel: string | undefined) => void;
- setIntegration: (integration: OrganizationIntegration | undefined) => void;
- setProvider: (provider: string | undefined) => void;
- shouldRenderSetupButton: boolean;
- };
- export function useCreateNotificationAction() {
- const api = useApi();
- const organization = useOrganization();
- const messagingIntegrationsQuery = useApiQuery<OrganizationIntegration[]>(
- [`/organizations/${organization.slug}/integrations/?integrationType=messaging`],
- {staleTime: 0, refetchOnWindowFocus: true}
- );
- const providersToIntegrations = useMemo(() => {
- const map: Record<string, OrganizationIntegration[]> = {};
- if (messagingIntegrationsQuery.data) {
- for (const i of messagingIntegrationsQuery.data) {
- if (i.status === 'active') {
- const providerSlug = i.provider.slug;
- map[providerSlug] = map[providerSlug] ?? [];
- map[providerSlug].push(i);
- }
- }
- }
- return map;
- }, [messagingIntegrationsQuery.data]);
- const [actions, setActions] = useState<MultipleCheckboxOptions[]>([
- MultipleCheckboxOptions.EMAIL,
- ]);
- const [provider, setProvider] = useState<string | undefined>(undefined);
- const [integration, setIntegration] = useState<OrganizationIntegration | undefined>(
- undefined
- );
- const [channel, setChannel] = useState<string | undefined>(undefined);
- const [shouldRenderSetupButton, setShouldRenderSetupButton] = useState<boolean>(false);
- useEffect(() => {
- if (messagingIntegrationsQuery.isSuccess) {
- const providerKeys = Object.keys(providersToIntegrations);
- const firstProvider = providerKeys[0] ?? undefined;
- const firstIntegration = providersToIntegrations[firstProvider]?.[0] ?? undefined;
- setProvider(firstProvider);
- setIntegration(firstIntegration);
- setShouldRenderSetupButton(!firstProvider);
- }
- }, [messagingIntegrationsQuery.isSuccess, providersToIntegrations]);
- type Props = {
- actionMatch: string | undefined;
- conditions: {id: string; interval: string; value: string}[] | undefined;
- frequency: number | undefined;
- name: string | undefined;
- projectSlug: string;
- shouldCreateRule: boolean | undefined;
- };
- const createNotificationAction = useCallback(
- ({
- shouldCreateRule,
- projectSlug,
- name,
- conditions,
- actionMatch,
- frequency,
- }: Props) => {
- const isCreatingIntegrationNotification = actions.find(
- action => action === MultipleCheckboxOptions.INTEGRATION
- );
- if (
- !organization.features.includes(
- 'messaging-integration-onboarding-project-creation'
- ) ||
- !shouldCreateRule ||
- !isCreatingIntegrationNotification
- ) {
- return undefined;
- }
- let integrationAction: IntegrationAction;
- switch (provider) {
- case 'slack':
- integrationAction = {
- id: IssueAlertActionType.SLACK,
- workspace: integration?.id,
- channel: channel,
- };
- break;
- case 'discord':
- integrationAction = {
- id: IssueAlertActionType.DISCORD,
- server: integration?.id,
- channel_id: channel,
- };
- break;
- case 'msteams':
- integrationAction = {
- id: IssueAlertActionType.MS_TEAMS,
- team: integration?.id,
- channel: channel,
- };
- break;
- default:
- return undefined;
- }
- return api.requestPromise(`/projects/${organization.slug}/${projectSlug}/rules/`, {
- method: 'POST',
- data: {
- name,
- conditions,
- actions: [integrationAction],
- actionMatch,
- frequency,
- },
- });
- },
- [
- actions,
- api,
- provider,
- integration,
- channel,
- organization.features,
- organization.slug,
- ]
- );
- return {
- createNotificationAction,
- notificationProps: {
- actions,
- provider,
- integration,
- channel,
- setActions,
- setProvider,
- setIntegration,
- setChannel,
- providersToIntegrations,
- querySuccess: messagingIntegrationsQuery.isSuccess,
- shouldRenderSetupButton,
- },
- };
- }
- export default function IssueAlertNotificationOptions(
- notificationProps: IssueAlertNotificationProps
- ) {
- const {actions, setActions, querySuccess, shouldRenderSetupButton} = notificationProps;
- const shouldRenderNotificationConfigs = actions.some(
- v => v !== MultipleCheckboxOptions.EMAIL
- );
- useRouteAnalyticsParams({
- setup_message_integration_button_shown: shouldRenderSetupButton,
- });
- if (!querySuccess) {
- return null;
- }
- return (
- <Fragment>
- <MultipleCheckbox
- name="notification"
- value={actions}
- onChange={values => setActions(values)}
- >
- <Wrapper>
- <MultipleCheckbox.Item value={MultipleCheckboxOptions.EMAIL} disabled>
- {t('Notify via email')}
- </MultipleCheckbox.Item>
- {!shouldRenderSetupButton && (
- <div>
- <MultipleCheckbox.Item value={MultipleCheckboxOptions.INTEGRATION}>
- {t('Notify via integration (Slack, Discord, MS Teams, etc.)')}
- </MultipleCheckbox.Item>
- {shouldRenderNotificationConfigs && (
- <MessagingIntegrationAlertRule {...notificationProps} />
- )}
- </div>
- )}
- </Wrapper>
- </MultipleCheckbox>
- {shouldRenderSetupButton && (
- <SetupMessagingIntegrationButton
- analyticsParams={{
- view: MessagingIntegrationAnalyticsView.PROJECT_CREATION,
- }}
- />
- )}
- </Fragment>
- );
- }
- const Wrapper = styled('div')`
- display: flex;
- flex-direction: column;
- gap: ${space(1)};
- `;
|