diff --git a/pkg/services/ngalert/notifier/channels_config/available_channels.go b/pkg/services/ngalert/notifier/channels_config/available_channels.go index 7adfb84c095..6d3429a7359 100644 --- a/pkg/services/ngalert/notifier/channels_config/available_channels.go +++ b/pkg/services/ngalert/notifier/channels_config/available_channels.go @@ -425,9 +425,9 @@ func GetAvailableNotifiers() []*NotifierPlugin { }, { Type: "oncall", - Name: "Grafana OnCall", - Description: "Sends alerts to Grafana OnCall", - Heading: "Grafana OnCall settings", + Name: "Grafana IRM", + Description: "Sends alerts to Grafana IRM", + Heading: "Grafana IRM settings", Options: []NotifierOption{ { Label: "URL", diff --git a/public/app/features/alerting/unified/components/receivers/form/ChannelSubForm.tsx b/public/app/features/alerting/unified/components/receivers/form/ChannelSubForm.tsx index 6f4952a41d2..1cbef2199a6 100644 --- a/public/app/features/alerting/unified/components/receivers/form/ChannelSubForm.tsx +++ b/public/app/features/alerting/unified/components/receivers/form/ChannelSubForm.tsx @@ -5,7 +5,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { Controller, FieldErrors, FieldValues, useFormContext } from 'react-hook-form'; import { GrafanaTheme2, SelectableValue } from '@grafana/data'; -import { Alert, Button, Field, Select, Text, useStyles2 } from '@grafana/ui'; +import { Alert, Button, Field, Select, Stack, Text, useStyles2 } from '@grafana/ui'; import { Trans, t } from 'app/core/internationalization'; import { useUnifiedAlertingSelector } from '../../../hooks/useUnifiedAlertingSelector'; @@ -106,11 +106,16 @@ export function ChannelSubForm({ sortBy(notifiers, ({ dto, meta }) => [meta?.order ?? 0, dto.name]) // .notifiers.sort((a, b) => a.dto.name.localeCompare(b.dto.name)) .map(({ dto: { name, type }, meta }) => ({ - label: name, + // @ts-expect-error ReactNode is supported + label: ( + + {name} + {meta?.badge} + + ), value: type, description: meta?.description, isDisabled: meta ? !meta.enabled : false, - imgUrl: meta?.iconUrl, })), [notifiers] ); diff --git a/public/app/features/alerting/unified/components/receivers/form/GrafanaReceiverForm.test.tsx b/public/app/features/alerting/unified/components/receivers/form/GrafanaReceiverForm.test.tsx index 3cc376b1e86..bd2616efca8 100644 --- a/public/app/features/alerting/unified/components/receivers/form/GrafanaReceiverForm.test.tsx +++ b/public/app/features/alerting/unified/components/receivers/form/GrafanaReceiverForm.test.tsx @@ -42,9 +42,9 @@ const ui = { onCallIntegrationType: byRole('radiogroup'), integrationOption: { new: byRole('radio', { - name: 'A new OnCall integration without escalation chains will be automatically created', + name: 'A new IRM integration without escalation chains will be automatically created', }), - existing: byRole('radio', { name: 'Use an existing OnCall integration' }), + existing: byRole('radio', { name: 'Use an existing IRM integration' }), }, newOnCallIntegrationName: byRole('textbox', { name: /Integration name/ }), existingOnCallIntegrationSelect: (index: number) => byTestId(`items.${index}.settings.url`), diff --git a/public/app/features/alerting/unified/components/receivers/form/notifiers.ts b/public/app/features/alerting/unified/components/receivers/form/notifiers.ts index 77e6cbb4309..15baf45aabe 100644 --- a/public/app/features/alerting/unified/components/receivers/form/notifiers.ts +++ b/public/app/features/alerting/unified/components/receivers/form/notifiers.ts @@ -1,10 +1,12 @@ +import { ReactElement } from 'react'; + import { NotifierDTO } from '../../../../../../types'; export interface NotifierMetadata { enabled: boolean; order: number; description?: string; - iconUrl?: string; + badge?: ReactElement; } export interface Notifier { diff --git a/public/app/features/alerting/unified/components/receivers/grafanaAppReceivers/onCall/useOnCallIntegration.test.ts b/public/app/features/alerting/unified/components/receivers/grafanaAppReceivers/onCall/useOnCallIntegration.test.ts index 8a227514f31..5f0318317af 100644 --- a/public/app/features/alerting/unified/components/receivers/grafanaAppReceivers/onCall/useOnCallIntegration.test.ts +++ b/public/app/features/alerting/unified/components/receivers/grafanaAppReceivers/onCall/useOnCallIntegration.test.ts @@ -70,16 +70,16 @@ describe('useOnCallIntegration', () => { const { onCallFormValidators } = result.current; const gfValidationResult = await waitFor(() => onCallFormValidators.integration_name('grafana-integration')); - expect(gfValidationResult).toBe('Integration of this name already exists in OnCall'); + expect(gfValidationResult).toBe('Integration of this name already exists in IRM'); const amValidationResult = await waitFor(() => onCallFormValidators.integration_name('alertmanager-integration')); - expect(amValidationResult).toBe('Integration of this name already exists in OnCall'); + expect(amValidationResult).toBe('Integration of this name already exists in IRM'); // ULR validator should check if the provided URL already exists expect(onCallFormValidators.url('https://oncall.com/grafana-integration')).toBe(true); expect(onCallFormValidators.url('https://oncall.com/alertmanager-integration')).toBe( - 'Selection of existing OnCall integration is required' + 'Selection of existing IRM integration is required' ); }); diff --git a/public/app/features/alerting/unified/components/receivers/grafanaAppReceivers/onCall/useOnCallIntegration.ts b/public/app/features/alerting/unified/components/receivers/grafanaAppReceivers/onCall/useOnCallIntegration.tsx similarity index 75% rename from public/app/features/alerting/unified/components/receivers/grafanaAppReceivers/onCall/useOnCallIntegration.ts rename to public/app/features/alerting/unified/components/receivers/grafanaAppReceivers/onCall/useOnCallIntegration.tsx index 1a22aa3de9b..bcd879b42d4 100644 --- a/public/app/features/alerting/unified/components/receivers/grafanaAppReceivers/onCall/useOnCallIntegration.ts +++ b/public/app/features/alerting/unified/components/receivers/grafanaAppReceivers/onCall/useOnCallIntegration.tsx @@ -3,6 +3,8 @@ import { useCallback, useMemo } from 'react'; import { SelectableValue } from '@grafana/data'; import { isFetchError } from '@grafana/runtime'; +import { Badge } from '@grafana/ui'; +import { t } from 'app/core/internationalization'; import { getIrmIfPresentOrOnCallPluginId } from 'app/features/alerting/unified/utils/config'; import { useAppNotification } from '../../../../../../../core/copy/appNotification'; @@ -11,7 +13,6 @@ import { NotifierDTO } from '../../../../../../../types'; import { ONCALL_INTEGRATION_V2_FEATURE, onCallApi } from '../../../../api/onCallApi'; import { usePluginBridge } from '../../../../hooks/usePluginBridge'; import { option } from '../../../../utils/notifier-types'; -import { GRAFANA_APP_RECEIVERS_SOURCE_IMAGE } from '../types'; import { GRAFANA_ONCALL_INTEGRATION_TYPE, ReceiverTypes } from './onCall'; @@ -100,10 +101,18 @@ export function useOnCallIntegration() { return true; } catch (error) { if (isFetchError(error) && error.status === 409) { - return 'Integration of this name already exists in OnCall'; + return t( + 'alerting.irm-integration.integration-name-exists', + 'Integration of this name already exists in IRM' + ); } - notifyApp.error('Failed to validate OnCall integration name. Is the OnCall API available?'); + notifyApp.error( + t( + 'alerting.irm-integration.validation-failed', + 'Failed to validate IRM integration name. Is the OnCall API available?' + ) + ); throw error; } }, @@ -114,7 +123,7 @@ export function useOnCallIntegration() { return grafanaOnCallIntegrations.map((i) => i.integration_url).includes(value) ? true - : 'Selection of existing OnCall integration is required'; + : t('alerting.irm-integration.integration-required', 'Selection of existing IRM integration is required'); }, }; }, [grafanaOnCallIntegrations, validateIntegrationNameQuery, isAlertingV2IntegrationEnabled, notifyApp]); @@ -187,41 +196,57 @@ export function useOnCallIntegration() { const newIntegrationOption: SelectableValue = { value: OnCallIntegrationType.NewIntegration, - label: 'New OnCall integration', - description: 'A new OnCall integration without escalation chains will be automatically created', + label: t('alerting.irm-integration.new-integration', 'New IRM integration'), + description: t( + 'alerting.irm-integration.new-integration-description', + 'A new IRM integration without escalation chains will be automatically created' + ), }; const existingIntegrationOption: SelectableValue = { value: OnCallIntegrationType.ExistingIntegration, - label: 'Existing OnCall integration', - description: 'Use an existing OnCall integration', + label: t('alerting.irm-integration.existing-integration', 'Existing IRM integration'), + description: t( + 'alerting.irm-integration.existing-integration-description', + 'Use an existing IRM integration' + ), }; options.unshift( - option(OnCallIntegrationSetting.IntegrationType, 'How to connect to OnCall', '', { - required: true, - element: 'radio', - defaultValue: newIntegrationOption, - selectOptions: [newIntegrationOption, existingIntegrationOption], - }), + option( + OnCallIntegrationSetting.IntegrationType, + t('alerting.irm-integration.connection-method', 'How to connect to IRM'), + '', + { + required: true, + element: 'radio', + defaultValue: newIntegrationOption, + selectOptions: [newIntegrationOption, existingIntegrationOption], + } + ), option( OnCallIntegrationSetting.IntegrationName, - 'Integration name', - 'The name of the new OnCall integration', + t('alerting.irm-integration.integration-name', 'Integration name'), + t('alerting.irm-integration.integration-name-description', 'The name of the new IRM integration'), { required: true, showWhen: { field: 'integration_type', is: OnCallIntegrationType.NewIntegration }, } ), - option('url', 'OnCall Integration', 'The OnCall integration to send alerts to', { - element: 'select', - required: true, - showWhen: { field: 'integration_type', is: OnCallIntegrationType.ExistingIntegration }, - selectOptions: grafanaOnCallIntegrations.map((i) => ({ - label: i.display_name, - description: i.integration_url, - value: i.integration_url, - })), - }) + option( + 'url', + t('alerting.irm-integration.integration', 'IRM Integration'), + t('alerting.irm-integration.integration-description', 'The IRM integration to send alerts to'), + { + element: 'select', + required: true, + showWhen: { field: 'integration_type', is: OnCallIntegrationType.ExistingIntegration }, + selectOptions: grafanaOnCallIntegrations.map((i) => ({ + label: i.display_name, + description: i.integration_url, + value: i.integration_url, + })), + } + ) ); return { ...notifier, options }; @@ -238,9 +263,9 @@ export function useOnCallIntegration() { enabled: !!isOnCallEnabled, order: -1, // The default is 0. We want OnCall to be the first on the list description: isOnCallEnabled - ? 'Connect effortlessly to Grafana OnCall' - : 'Enable Grafana OnCall plugin to use this integration', - iconUrl: GRAFANA_APP_RECEIVERS_SOURCE_IMAGE[getIrmIfPresentOrOnCallPluginId()], + ? t('alerting.irm-integration.enabled-description', 'Seamless way to handle alerts and manage incidents') + : t('alerting.irm-integration.disabled-description', 'Enable Grafana IRM to use this integration'), + badge: , }, extendOnCallNotifierFeatures, extendOnCallReceivers, diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 14183eb6419..910818911ef 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -376,6 +376,23 @@ "export": "Export", "reorder": "Re-order rules" }, + "irm-integration": { + "connection-method": "How to connect to IRM", + "disabled-description": "Enable Grafana IRM to use this integration", + "enabled-description": "Seamless way to handle alerts and manage incidents", + "existing-integration": "Existing IRM integration", + "existing-integration-description": "Use an existing IRM integration", + "integration": "IRM Integration", + "integration-description": "The IRM integration to send alerts to", + "integration-name": "Integration name", + "integration-name-description": "The name of the new IRM integration", + "integration-name-exists": "Integration of this name already exists in IRM", + "integration-required": "Selection of existing IRM integration is required", + "new-integration": "New IRM integration", + "new-integration-description": "A new IRM integration without escalation chains will be automatically created", + "recommended": "Recommended", + "validation-failed": "Failed to validate IRM integration name. Is the OnCall API available?" + }, "list-view": { "empty": { "new-alert-rule": "New alert rule",