mirror of
https://github.com/grafana/grafana.git
synced 2026-02-03 20:49:50 -05:00
Alerting: Single alertmanager contact points versions (#116076)
Some checks are pending
Actionlint / Lint GitHub Actions files (push) Waiting to run
Backend Code Checks / Detect whether code changed (push) Waiting to run
Backend Code Checks / Validate Backend Configs (push) Blocked by required conditions
Backend Unit Tests / Detect whether code changed (push) Waiting to run
Backend Unit Tests / Grafana (1/8) (push) Blocked by required conditions
Backend Unit Tests / Grafana (2/8) (push) Blocked by required conditions
Backend Unit Tests / Grafana (3/8) (push) Blocked by required conditions
Backend Unit Tests / Grafana (4/8) (push) Blocked by required conditions
Backend Unit Tests / Grafana (5/8) (push) Blocked by required conditions
Backend Unit Tests / Grafana (6/8) (push) Blocked by required conditions
Backend Unit Tests / Grafana (7/8) (push) Blocked by required conditions
Backend Unit Tests / Grafana (8/8) (push) Blocked by required conditions
Backend Unit Tests / Grafana Enterprise (1/8) (push) Blocked by required conditions
Backend Unit Tests / Grafana Enterprise (2/8) (push) Blocked by required conditions
Backend Unit Tests / Grafana Enterprise (3/8) (push) Blocked by required conditions
Backend Unit Tests / Grafana Enterprise (4/8) (push) Blocked by required conditions
Backend Unit Tests / Grafana Enterprise (5/8) (push) Blocked by required conditions
Backend Unit Tests / Grafana Enterprise (6/8) (push) Blocked by required conditions
Backend Unit Tests / Grafana Enterprise (7/8) (push) Blocked by required conditions
Backend Unit Tests / Grafana Enterprise (8/8) (push) Blocked by required conditions
Backend Unit Tests / All backend unit tests complete (push) Blocked by required conditions
CodeQL checks / Detect whether code changed (push) Waiting to run
CodeQL checks / Analyze (push) Blocked by required conditions
Lint Frontend / Detect whether code changed (push) Waiting to run
Lint Frontend / Lint (push) Blocked by required conditions
Lint Frontend / Typecheck (push) Blocked by required conditions
Lint Frontend / Verify API clients (push) Waiting to run
Lint Frontend / Verify API clients (enterprise) (push) Waiting to run
Lint Frontend / Verify packed frontend packages (push) Blocked by required conditions
golangci-lint / Detect whether code changed (push) Waiting to run
golangci-lint / go-fmt (push) Blocked by required conditions
golangci-lint / lint-go (push) Blocked by required conditions
Crowdin Upload Action / upload-sources-to-crowdin (push) Waiting to run
Verify i18n / verify-i18n (push) Waiting to run
End-to-end tests / Detect whether code changed (push) Waiting to run
End-to-end tests / Build & Package Grafana (push) Blocked by required conditions
End-to-end tests / Build E2E test runner (push) Blocked by required conditions
End-to-end tests / push-docker-image (push) Blocked by required conditions
End-to-end tests / dashboards-suite (old arch) (push) Blocked by required conditions
End-to-end tests / panels-suite (old arch) (push) Blocked by required conditions
End-to-end tests / smoke-tests-suite (old arch) (push) Blocked by required conditions
End-to-end tests / various-suite (old arch) (push) Blocked by required conditions
End-to-end tests / Verify Storybook (Playwright) (push) Blocked by required conditions
End-to-end tests / Playwright E2E tests (1/8) (push) Blocked by required conditions
End-to-end tests / Playwright E2E tests (2/8) (push) Blocked by required conditions
End-to-end tests / Playwright E2E tests (3/8) (push) Blocked by required conditions
End-to-end tests / Playwright E2E tests (4/8) (push) Blocked by required conditions
End-to-end tests / Playwright E2E tests (5/8) (push) Blocked by required conditions
End-to-end tests / Playwright E2E tests (6/8) (push) Blocked by required conditions
End-to-end tests / Playwright E2E tests (7/8) (push) Blocked by required conditions
End-to-end tests / Playwright E2E tests (8/8) (push) Blocked by required conditions
End-to-end tests / run-azure-monitor-e2e (push) Blocked by required conditions
End-to-end tests / All Playwright tests complete (push) Blocked by required conditions
End-to-end tests / A11y test (push) Blocked by required conditions
End-to-end tests / Publish metrics (push) Blocked by required conditions
End-to-end tests / All E2E tests complete (push) Blocked by required conditions
Frontend tests / Detect whether code changed (push) Waiting to run
Frontend tests / Unit tests (1 / 16) (push) Blocked by required conditions
Frontend tests / Unit tests (10 / 16) (push) Blocked by required conditions
Frontend tests / Unit tests (11 / 16) (push) Blocked by required conditions
Frontend tests / Unit tests (12 / 16) (push) Blocked by required conditions
Frontend tests / Unit tests (13 / 16) (push) Blocked by required conditions
Frontend tests / Unit tests (14 / 16) (push) Blocked by required conditions
Frontend tests / Unit tests (15 / 16) (push) Blocked by required conditions
Frontend tests / Unit tests (16 / 16) (push) Blocked by required conditions
Frontend tests / Unit tests (2 / 16) (push) Blocked by required conditions
Frontend tests / Unit tests (3 / 16) (push) Blocked by required conditions
Frontend tests / Unit tests (4 / 16) (push) Blocked by required conditions
Frontend tests / Unit tests (5 / 16) (push) Blocked by required conditions
Frontend tests / Unit tests (6 / 16) (push) Blocked by required conditions
Frontend tests / Unit tests (7 / 16) (push) Blocked by required conditions
Frontend tests / Unit tests (8 / 16) (push) Blocked by required conditions
Frontend tests / Unit tests (9 / 16) (push) Blocked by required conditions
Frontend tests / Decoupled plugin tests (push) Blocked by required conditions
Frontend tests / Packages unit tests (push) Blocked by required conditions
Frontend tests / All frontend unit tests complete (push) Blocked by required conditions
Frontend tests / Devenv frontend-service build (push) Blocked by required conditions
Integration Tests / Detect whether code changed (push) Waiting to run
Integration Tests / Sqlite (1/4) (push) Blocked by required conditions
Integration Tests / Sqlite (2/4) (push) Blocked by required conditions
Integration Tests / Sqlite (3/4) (push) Blocked by required conditions
Integration Tests / Sqlite (4/4) (push) Blocked by required conditions
Integration Tests / Sqlite Without CGo (1/4) (push) Blocked by required conditions
Integration Tests / Sqlite Without CGo (2/4) (push) Blocked by required conditions
Integration Tests / Sqlite Without CGo (3/4) (push) Blocked by required conditions
Integration Tests / Sqlite Without CGo (4/4) (push) Blocked by required conditions
Integration Tests / Sqlite Without CGo (profiled) (push) Blocked by required conditions
Integration Tests / MySQL (1/16) (push) Blocked by required conditions
Integration Tests / MySQL (10/16) (push) Blocked by required conditions
Integration Tests / MySQL (11/16) (push) Blocked by required conditions
Integration Tests / MySQL (12/16) (push) Blocked by required conditions
Integration Tests / MySQL (13/16) (push) Blocked by required conditions
Integration Tests / MySQL (14/16) (push) Blocked by required conditions
Integration Tests / MySQL (15/16) (push) Blocked by required conditions
Integration Tests / MySQL (16/16) (push) Blocked by required conditions
Integration Tests / MySQL (2/16) (push) Blocked by required conditions
Integration Tests / MySQL (3/16) (push) Blocked by required conditions
Integration Tests / MySQL (4/16) (push) Blocked by required conditions
Integration Tests / MySQL (5/16) (push) Blocked by required conditions
Integration Tests / MySQL (6/16) (push) Blocked by required conditions
Integration Tests / MySQL (7/16) (push) Blocked by required conditions
Integration Tests / MySQL (8/16) (push) Blocked by required conditions
Integration Tests / MySQL (9/16) (push) Blocked by required conditions
Integration Tests / Postgres (1/16) (push) Blocked by required conditions
Integration Tests / Postgres (10/16) (push) Blocked by required conditions
Integration Tests / Postgres (11/16) (push) Blocked by required conditions
Integration Tests / Postgres (12/16) (push) Blocked by required conditions
Integration Tests / Postgres (13/16) (push) Blocked by required conditions
Integration Tests / Postgres (14/16) (push) Blocked by required conditions
Integration Tests / Postgres (15/16) (push) Blocked by required conditions
Integration Tests / Postgres (16/16) (push) Blocked by required conditions
Integration Tests / Postgres (2/16) (push) Blocked by required conditions
Integration Tests / Postgres (3/16) (push) Blocked by required conditions
Integration Tests / Postgres (4/16) (push) Blocked by required conditions
Integration Tests / Postgres (5/16) (push) Blocked by required conditions
Integration Tests / Postgres (6/16) (push) Blocked by required conditions
Integration Tests / Postgres (7/16) (push) Blocked by required conditions
Integration Tests / Postgres (8/16) (push) Blocked by required conditions
Integration Tests / Postgres (9/16) (push) Blocked by required conditions
Integration Tests / All backend integration tests complete (push) Blocked by required conditions
publish-kinds-next / main (push) Waiting to run
Reject GitHub secrets / reject-gh-secrets (push) Waiting to run
Build Release Packages / setup (push) Waiting to run
Build Release Packages / Dispatch grafana-enterprise build (push) Blocked by required conditions
Build Release Packages / / darwin-amd64 (push) Blocked by required conditions
Build Release Packages / / darwin-arm64 (push) Blocked by required conditions
Build Release Packages / / linux-amd64 (push) Blocked by required conditions
Build Release Packages / / linux-armv6 (push) Blocked by required conditions
Build Release Packages / / linux-armv7 (push) Blocked by required conditions
Build Release Packages / / linux-arm64 (push) Blocked by required conditions
Build Release Packages / / linux-s390x (push) Blocked by required conditions
Build Release Packages / / windows-amd64 (push) Blocked by required conditions
Build Release Packages / / windows-arm64 (push) Blocked by required conditions
Build Release Packages / Upload artifacts (push) Blocked by required conditions
Build Release Packages / publish-dockerhub (push) Blocked by required conditions
Build Release Packages / Dispatch publish NPM canaries (push) Blocked by required conditions
Build Release Packages / notify-pr (push) Blocked by required conditions
Run dashboard schema v2 e2e / dashboard-schema-v2-e2e (push) Waiting to run
Shellcheck / Shellcheck scripts (push) Waiting to run
Run Storybook a11y tests / Detect whether code changed (push) Waiting to run
Run Storybook a11y tests / Run Storybook a11y tests (light theme) (push) Blocked by required conditions
Run Storybook a11y tests / Run Storybook a11y tests (dark theme) (push) Blocked by required conditions
Swagger generated code / Detect whether code changed (push) Waiting to run
Swagger generated code / Verify committed API specs match (push) Blocked by required conditions
Dispatch sync to mirror / dispatch-job (push) Waiting to run
Some checks are pending
Actionlint / Lint GitHub Actions files (push) Waiting to run
Backend Code Checks / Detect whether code changed (push) Waiting to run
Backend Code Checks / Validate Backend Configs (push) Blocked by required conditions
Backend Unit Tests / Detect whether code changed (push) Waiting to run
Backend Unit Tests / Grafana (1/8) (push) Blocked by required conditions
Backend Unit Tests / Grafana (2/8) (push) Blocked by required conditions
Backend Unit Tests / Grafana (3/8) (push) Blocked by required conditions
Backend Unit Tests / Grafana (4/8) (push) Blocked by required conditions
Backend Unit Tests / Grafana (5/8) (push) Blocked by required conditions
Backend Unit Tests / Grafana (6/8) (push) Blocked by required conditions
Backend Unit Tests / Grafana (7/8) (push) Blocked by required conditions
Backend Unit Tests / Grafana (8/8) (push) Blocked by required conditions
Backend Unit Tests / Grafana Enterprise (1/8) (push) Blocked by required conditions
Backend Unit Tests / Grafana Enterprise (2/8) (push) Blocked by required conditions
Backend Unit Tests / Grafana Enterprise (3/8) (push) Blocked by required conditions
Backend Unit Tests / Grafana Enterprise (4/8) (push) Blocked by required conditions
Backend Unit Tests / Grafana Enterprise (5/8) (push) Blocked by required conditions
Backend Unit Tests / Grafana Enterprise (6/8) (push) Blocked by required conditions
Backend Unit Tests / Grafana Enterprise (7/8) (push) Blocked by required conditions
Backend Unit Tests / Grafana Enterprise (8/8) (push) Blocked by required conditions
Backend Unit Tests / All backend unit tests complete (push) Blocked by required conditions
CodeQL checks / Detect whether code changed (push) Waiting to run
CodeQL checks / Analyze (push) Blocked by required conditions
Lint Frontend / Detect whether code changed (push) Waiting to run
Lint Frontend / Lint (push) Blocked by required conditions
Lint Frontend / Typecheck (push) Blocked by required conditions
Lint Frontend / Verify API clients (push) Waiting to run
Lint Frontend / Verify API clients (enterprise) (push) Waiting to run
Lint Frontend / Verify packed frontend packages (push) Blocked by required conditions
golangci-lint / Detect whether code changed (push) Waiting to run
golangci-lint / go-fmt (push) Blocked by required conditions
golangci-lint / lint-go (push) Blocked by required conditions
Crowdin Upload Action / upload-sources-to-crowdin (push) Waiting to run
Verify i18n / verify-i18n (push) Waiting to run
End-to-end tests / Detect whether code changed (push) Waiting to run
End-to-end tests / Build & Package Grafana (push) Blocked by required conditions
End-to-end tests / Build E2E test runner (push) Blocked by required conditions
End-to-end tests / push-docker-image (push) Blocked by required conditions
End-to-end tests / dashboards-suite (old arch) (push) Blocked by required conditions
End-to-end tests / panels-suite (old arch) (push) Blocked by required conditions
End-to-end tests / smoke-tests-suite (old arch) (push) Blocked by required conditions
End-to-end tests / various-suite (old arch) (push) Blocked by required conditions
End-to-end tests / Verify Storybook (Playwright) (push) Blocked by required conditions
End-to-end tests / Playwright E2E tests (1/8) (push) Blocked by required conditions
End-to-end tests / Playwright E2E tests (2/8) (push) Blocked by required conditions
End-to-end tests / Playwright E2E tests (3/8) (push) Blocked by required conditions
End-to-end tests / Playwright E2E tests (4/8) (push) Blocked by required conditions
End-to-end tests / Playwright E2E tests (5/8) (push) Blocked by required conditions
End-to-end tests / Playwright E2E tests (6/8) (push) Blocked by required conditions
End-to-end tests / Playwright E2E tests (7/8) (push) Blocked by required conditions
End-to-end tests / Playwright E2E tests (8/8) (push) Blocked by required conditions
End-to-end tests / run-azure-monitor-e2e (push) Blocked by required conditions
End-to-end tests / All Playwright tests complete (push) Blocked by required conditions
End-to-end tests / A11y test (push) Blocked by required conditions
End-to-end tests / Publish metrics (push) Blocked by required conditions
End-to-end tests / All E2E tests complete (push) Blocked by required conditions
Frontend tests / Detect whether code changed (push) Waiting to run
Frontend tests / Unit tests (1 / 16) (push) Blocked by required conditions
Frontend tests / Unit tests (10 / 16) (push) Blocked by required conditions
Frontend tests / Unit tests (11 / 16) (push) Blocked by required conditions
Frontend tests / Unit tests (12 / 16) (push) Blocked by required conditions
Frontend tests / Unit tests (13 / 16) (push) Blocked by required conditions
Frontend tests / Unit tests (14 / 16) (push) Blocked by required conditions
Frontend tests / Unit tests (15 / 16) (push) Blocked by required conditions
Frontend tests / Unit tests (16 / 16) (push) Blocked by required conditions
Frontend tests / Unit tests (2 / 16) (push) Blocked by required conditions
Frontend tests / Unit tests (3 / 16) (push) Blocked by required conditions
Frontend tests / Unit tests (4 / 16) (push) Blocked by required conditions
Frontend tests / Unit tests (5 / 16) (push) Blocked by required conditions
Frontend tests / Unit tests (6 / 16) (push) Blocked by required conditions
Frontend tests / Unit tests (7 / 16) (push) Blocked by required conditions
Frontend tests / Unit tests (8 / 16) (push) Blocked by required conditions
Frontend tests / Unit tests (9 / 16) (push) Blocked by required conditions
Frontend tests / Decoupled plugin tests (push) Blocked by required conditions
Frontend tests / Packages unit tests (push) Blocked by required conditions
Frontend tests / All frontend unit tests complete (push) Blocked by required conditions
Frontend tests / Devenv frontend-service build (push) Blocked by required conditions
Integration Tests / Detect whether code changed (push) Waiting to run
Integration Tests / Sqlite (1/4) (push) Blocked by required conditions
Integration Tests / Sqlite (2/4) (push) Blocked by required conditions
Integration Tests / Sqlite (3/4) (push) Blocked by required conditions
Integration Tests / Sqlite (4/4) (push) Blocked by required conditions
Integration Tests / Sqlite Without CGo (1/4) (push) Blocked by required conditions
Integration Tests / Sqlite Without CGo (2/4) (push) Blocked by required conditions
Integration Tests / Sqlite Without CGo (3/4) (push) Blocked by required conditions
Integration Tests / Sqlite Without CGo (4/4) (push) Blocked by required conditions
Integration Tests / Sqlite Without CGo (profiled) (push) Blocked by required conditions
Integration Tests / MySQL (1/16) (push) Blocked by required conditions
Integration Tests / MySQL (10/16) (push) Blocked by required conditions
Integration Tests / MySQL (11/16) (push) Blocked by required conditions
Integration Tests / MySQL (12/16) (push) Blocked by required conditions
Integration Tests / MySQL (13/16) (push) Blocked by required conditions
Integration Tests / MySQL (14/16) (push) Blocked by required conditions
Integration Tests / MySQL (15/16) (push) Blocked by required conditions
Integration Tests / MySQL (16/16) (push) Blocked by required conditions
Integration Tests / MySQL (2/16) (push) Blocked by required conditions
Integration Tests / MySQL (3/16) (push) Blocked by required conditions
Integration Tests / MySQL (4/16) (push) Blocked by required conditions
Integration Tests / MySQL (5/16) (push) Blocked by required conditions
Integration Tests / MySQL (6/16) (push) Blocked by required conditions
Integration Tests / MySQL (7/16) (push) Blocked by required conditions
Integration Tests / MySQL (8/16) (push) Blocked by required conditions
Integration Tests / MySQL (9/16) (push) Blocked by required conditions
Integration Tests / Postgres (1/16) (push) Blocked by required conditions
Integration Tests / Postgres (10/16) (push) Blocked by required conditions
Integration Tests / Postgres (11/16) (push) Blocked by required conditions
Integration Tests / Postgres (12/16) (push) Blocked by required conditions
Integration Tests / Postgres (13/16) (push) Blocked by required conditions
Integration Tests / Postgres (14/16) (push) Blocked by required conditions
Integration Tests / Postgres (15/16) (push) Blocked by required conditions
Integration Tests / Postgres (16/16) (push) Blocked by required conditions
Integration Tests / Postgres (2/16) (push) Blocked by required conditions
Integration Tests / Postgres (3/16) (push) Blocked by required conditions
Integration Tests / Postgres (4/16) (push) Blocked by required conditions
Integration Tests / Postgres (5/16) (push) Blocked by required conditions
Integration Tests / Postgres (6/16) (push) Blocked by required conditions
Integration Tests / Postgres (7/16) (push) Blocked by required conditions
Integration Tests / Postgres (8/16) (push) Blocked by required conditions
Integration Tests / Postgres (9/16) (push) Blocked by required conditions
Integration Tests / All backend integration tests complete (push) Blocked by required conditions
publish-kinds-next / main (push) Waiting to run
Reject GitHub secrets / reject-gh-secrets (push) Waiting to run
Build Release Packages / setup (push) Waiting to run
Build Release Packages / Dispatch grafana-enterprise build (push) Blocked by required conditions
Build Release Packages / / darwin-amd64 (push) Blocked by required conditions
Build Release Packages / / darwin-arm64 (push) Blocked by required conditions
Build Release Packages / / linux-amd64 (push) Blocked by required conditions
Build Release Packages / / linux-armv6 (push) Blocked by required conditions
Build Release Packages / / linux-armv7 (push) Blocked by required conditions
Build Release Packages / / linux-arm64 (push) Blocked by required conditions
Build Release Packages / / linux-s390x (push) Blocked by required conditions
Build Release Packages / / windows-amd64 (push) Blocked by required conditions
Build Release Packages / / windows-arm64 (push) Blocked by required conditions
Build Release Packages / Upload artifacts (push) Blocked by required conditions
Build Release Packages / publish-dockerhub (push) Blocked by required conditions
Build Release Packages / Dispatch publish NPM canaries (push) Blocked by required conditions
Build Release Packages / notify-pr (push) Blocked by required conditions
Run dashboard schema v2 e2e / dashboard-schema-v2-e2e (push) Waiting to run
Shellcheck / Shellcheck scripts (push) Waiting to run
Run Storybook a11y tests / Detect whether code changed (push) Waiting to run
Run Storybook a11y tests / Run Storybook a11y tests (light theme) (push) Blocked by required conditions
Run Storybook a11y tests / Run Storybook a11y tests (dark theme) (push) Blocked by required conditions
Swagger generated code / Detect whether code changed (push) Waiting to run
Swagger generated code / Verify committed API specs match (push) Blocked by required conditions
Dispatch sync to mirror / dispatch-job (push) Waiting to run
* POC ssingle AM * wip * add query param ?version=2 * wip2 * wip3 * Update logic * update badges and tests * remove unsused import * fix: update NewReceiverView snapshots to include version field * update translations * fix: delegate version determination to backend for new integrations - Remove hardcoded version: 'v1' from defaultChannelValues - Reset version to undefined when integration type changes - Backend uses GetCurrentVersion() when no version is provided - Update snapshots to reflect version handling changes - Remove unused getDefaultVersionForNotifier function * update snapshot * fix(alerting): fix contact point form issues - Fix empty info alert showing when notifier.dto.info is undefined - Fix options not loading for new contact points by using default creatable version * fix(alerting): only show version badge for legacy integrations * update tests for version badge and getOptionsForVersion changes * docs: add comment explaining currentVersion field in NotifierDTO * Show user-friendly 'Legacy' label for legacy integrations - Replace technical version strings (v0mimir1, v0mimir2) with user-friendly labels - v0mimir1 -> 'Legacy', v0mimir2 -> 'Legacy v2', etc. - Technical version is still shown in tooltip for reference - Add getLegacyVersionLabel() utility function - Update tests for badge display and utility function * Add v0mimir2 to test mock for Legacy v2 badge test * hasLegacyIntegrations now uses isLegacyVersion - Accept notifiers array to properly check canCreate: false - No longer relies on version string comparison (v1 check) - Uses isLegacyVersion for consistent legacy detection - Update tests to pass notifiers and test correct behavior * update translations
This commit is contained in:
parent
cf452c167b
commit
ccb032f376
12 changed files with 952 additions and 38 deletions
|
|
@ -108,7 +108,9 @@ export const alertmanagerApi = alertingApi.injectEndpoints({
|
|||
}),
|
||||
|
||||
grafanaNotifiers: build.query<NotifierDTO[], void>({
|
||||
query: () => ({ url: '/api/alert-notifiers' }),
|
||||
// NOTE: version=2 parameter required for versioned schema (PR #109969)
|
||||
// This parameter will be removed in future when v2 becomes default
|
||||
query: () => ({ url: '/api/alert-notifiers?version=2' }),
|
||||
transformResponse: (response: NotifierDTO[]) => {
|
||||
const populateSecureFieldKey = (
|
||||
option: NotificationChannelOption,
|
||||
|
|
@ -121,11 +123,16 @@ export const alertmanagerApi = alertingApi.injectEndpoints({
|
|||
),
|
||||
});
|
||||
|
||||
// Keep versions array intact for version-specific options lookup
|
||||
// Transform options with secureFieldKey population
|
||||
return response.map((notifier) => ({
|
||||
...notifier,
|
||||
options: notifier.options.map((option) => {
|
||||
return populateSecureFieldKey(option, '');
|
||||
}),
|
||||
options: (notifier.options || []).map((option) => populateSecureFieldKey(option, '')),
|
||||
// Also transform options within each version
|
||||
versions: notifier.versions?.map((version) => ({
|
||||
...version,
|
||||
options: (version.options || []).map((option) => populateSecureFieldKey(option, '')),
|
||||
})),
|
||||
}));
|
||||
},
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -36,6 +36,24 @@ export const ProvisioningAlert = ({ resource, ...rest }: ProvisioningAlertProps)
|
|||
);
|
||||
};
|
||||
|
||||
export const ImportedContactPointAlert = (props: ExtraAlertProps) => {
|
||||
return (
|
||||
<Alert
|
||||
title={t(
|
||||
'alerting.provisioning.title-imported',
|
||||
'This contact point was imported and cannot be edited through the UI'
|
||||
)}
|
||||
severity="info"
|
||||
{...props}
|
||||
>
|
||||
<Trans i18nKey="alerting.provisioning.body-imported">
|
||||
This contact point contains integrations that were imported from an external Alertmanager and is currently
|
||||
read-only. The integrations will become editable after the migration process is complete.
|
||||
</Trans>
|
||||
</Alert>
|
||||
);
|
||||
};
|
||||
|
||||
export const ProvisioningBadge = ({
|
||||
tooltip,
|
||||
provenance,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import 'core-js/stable/structured-clone';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { clickSelectOption } from 'test/helpers/selectOptionInTest';
|
||||
import { render } from 'test/test-utils';
|
||||
import { render, screen } from 'test/test-utils';
|
||||
import { byRole, byTestId } from 'testing-library-selector';
|
||||
|
||||
import { grafanaAlertNotifiers } from 'app/features/alerting/unified/mockGrafanaNotifiers';
|
||||
import { AlertmanagerProvider } from 'app/features/alerting/unified/state/AlertmanagerContext';
|
||||
import { NotifierDTO } from 'app/features/alerting/unified/types/alerting';
|
||||
|
||||
import { ChannelSubForm } from './ChannelSubForm';
|
||||
import { GrafanaCommonChannelSettings } from './GrafanaCommonChannelSettings';
|
||||
|
|
@ -16,6 +17,7 @@ type TestChannelValues = {
|
|||
type: string;
|
||||
settings: Record<string, unknown>;
|
||||
secureFields: Record<string, boolean>;
|
||||
version?: string;
|
||||
};
|
||||
|
||||
type TestReceiverFormValues = {
|
||||
|
|
@ -246,4 +248,241 @@ describe('ChannelSubForm', () => {
|
|||
expect(slackUrl).toBeEnabled();
|
||||
expect(slackUrl).toHaveValue('');
|
||||
});
|
||||
|
||||
describe('version-specific options display', () => {
|
||||
// Create a mock notifier with different options for v0 and v1
|
||||
const legacyOptions = [
|
||||
{
|
||||
element: 'input' as const,
|
||||
inputType: 'text',
|
||||
label: 'Legacy URL',
|
||||
description: 'The legacy endpoint URL',
|
||||
placeholder: '',
|
||||
propertyName: 'legacyUrl',
|
||||
required: true,
|
||||
secure: false,
|
||||
showWhen: { field: '', is: '' },
|
||||
validationRule: '',
|
||||
dependsOn: '',
|
||||
},
|
||||
];
|
||||
|
||||
const webhookWithVersions: NotifierDTO = {
|
||||
...grafanaAlertNotifiers.webhook,
|
||||
versions: [
|
||||
{
|
||||
version: 'v0mimir1',
|
||||
label: 'Webhook (Legacy)',
|
||||
description: 'Legacy webhook from Mimir',
|
||||
canCreate: false,
|
||||
options: legacyOptions,
|
||||
},
|
||||
{
|
||||
version: 'v0mimir2',
|
||||
label: 'Webhook (Legacy v2)',
|
||||
description: 'Legacy webhook v2 from Mimir',
|
||||
canCreate: false,
|
||||
options: legacyOptions,
|
||||
},
|
||||
{
|
||||
version: 'v1',
|
||||
label: 'Webhook',
|
||||
description: 'Sends HTTP POST request',
|
||||
canCreate: true,
|
||||
options: grafanaAlertNotifiers.webhook.options,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const versionedNotifiers: Notifier[] = [
|
||||
{ dto: webhookWithVersions, meta: { enabled: true, order: 1 } },
|
||||
{ dto: grafanaAlertNotifiers.slack, meta: { enabled: true, order: 2 } },
|
||||
];
|
||||
|
||||
function VersionedTestFormWrapper({
|
||||
defaults,
|
||||
initial,
|
||||
}: {
|
||||
defaults: TestChannelValues;
|
||||
initial?: TestChannelValues;
|
||||
}) {
|
||||
const form = useForm<TestReceiverFormValues>({
|
||||
defaultValues: {
|
||||
name: 'test-contact-point',
|
||||
items: [defaults],
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<AlertmanagerProvider accessType="notification">
|
||||
<FormProvider {...form}>
|
||||
<ChannelSubForm
|
||||
defaultValues={defaults}
|
||||
initialValues={initial}
|
||||
pathPrefix={`items.0.`}
|
||||
integrationIndex={0}
|
||||
notifiers={versionedNotifiers}
|
||||
onDuplicate={jest.fn()}
|
||||
commonSettingsComponent={GrafanaCommonChannelSettings}
|
||||
isEditable={true}
|
||||
isTestable={false}
|
||||
canEditProtectedFields={true}
|
||||
/>
|
||||
</FormProvider>
|
||||
</AlertmanagerProvider>
|
||||
);
|
||||
}
|
||||
|
||||
function renderVersionedForm(defaults: TestChannelValues, initial?: TestChannelValues) {
|
||||
return render(<VersionedTestFormWrapper defaults={defaults} initial={initial} />);
|
||||
}
|
||||
|
||||
it('should display v1 options when integration has v1 version', () => {
|
||||
const webhookV1: TestChannelValues = {
|
||||
__id: 'id-0',
|
||||
type: 'webhook',
|
||||
version: 'v1',
|
||||
settings: { url: 'https://example.com' },
|
||||
secureFields: {},
|
||||
};
|
||||
|
||||
renderVersionedForm(webhookV1, webhookV1);
|
||||
|
||||
// Should show v1 URL field (from default options)
|
||||
expect(ui.settings.webhook.url.get()).toBeInTheDocument();
|
||||
// Should NOT show legacy URL field
|
||||
expect(screen.queryByRole('textbox', { name: /Legacy URL/i })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should display v0 options when integration has legacy version', () => {
|
||||
const webhookV0: TestChannelValues = {
|
||||
__id: 'id-0',
|
||||
type: 'webhook',
|
||||
version: 'v0mimir1',
|
||||
settings: { legacyUrl: 'https://legacy.example.com' },
|
||||
secureFields: {},
|
||||
};
|
||||
|
||||
renderVersionedForm(webhookV0, webhookV0);
|
||||
|
||||
// Should show legacy URL field (from v0 options)
|
||||
expect(screen.getByRole('textbox', { name: /Legacy URL/i })).toBeInTheDocument();
|
||||
// Should NOT show v1 URL field
|
||||
expect(ui.settings.webhook.url.query()).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should display "Legacy" badge for v0mimir1 integration', () => {
|
||||
const webhookV0: TestChannelValues = {
|
||||
__id: 'id-0',
|
||||
type: 'webhook',
|
||||
version: 'v0mimir1',
|
||||
settings: { legacyUrl: 'https://legacy.example.com' },
|
||||
secureFields: {},
|
||||
};
|
||||
|
||||
renderVersionedForm(webhookV0, webhookV0);
|
||||
|
||||
// Should show "Legacy" badge for v0mimir1 integrations
|
||||
expect(screen.getByText('Legacy')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should display "Legacy v2" badge for v0mimir2 integration', () => {
|
||||
const webhookV0v2: TestChannelValues = {
|
||||
__id: 'id-0',
|
||||
type: 'webhook',
|
||||
version: 'v0mimir2',
|
||||
settings: { legacyUrl: 'https://legacy.example.com' },
|
||||
secureFields: {},
|
||||
};
|
||||
|
||||
renderVersionedForm(webhookV0v2, webhookV0v2);
|
||||
|
||||
// Should show "Legacy v2" badge for v0mimir2 integrations
|
||||
expect(screen.getByText('Legacy v2')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should NOT display version badge for v1 integration', () => {
|
||||
const webhookV1: TestChannelValues = {
|
||||
__id: 'id-0',
|
||||
type: 'webhook',
|
||||
version: 'v1',
|
||||
settings: { url: 'https://example.com' },
|
||||
secureFields: {},
|
||||
};
|
||||
|
||||
renderVersionedForm(webhookV1, webhookV1);
|
||||
|
||||
// Should NOT show version badge for non-legacy v1 integrations
|
||||
expect(screen.queryByText('v1')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should filter out notifiers with canCreate: false from dropdown', () => {
|
||||
// Create a notifier that only has v0 versions (cannot be created)
|
||||
const legacyOnlyNotifier: NotifierDTO = {
|
||||
type: 'wechat',
|
||||
name: 'WeChat',
|
||||
heading: 'WeChat settings',
|
||||
description: 'Sends notifications to WeChat',
|
||||
options: [],
|
||||
versions: [
|
||||
{
|
||||
version: 'v0mimir1',
|
||||
label: 'WeChat (Legacy)',
|
||||
description: 'Legacy WeChat',
|
||||
canCreate: false,
|
||||
options: [],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const notifiersWithLegacyOnly: Notifier[] = [
|
||||
{ dto: webhookWithVersions, meta: { enabled: true, order: 1 } },
|
||||
{ dto: legacyOnlyNotifier, meta: { enabled: true, order: 2 } },
|
||||
];
|
||||
|
||||
function LegacyOnlyTestWrapper({ defaults }: { defaults: TestChannelValues }) {
|
||||
const form = useForm<TestReceiverFormValues>({
|
||||
defaultValues: {
|
||||
name: 'test-contact-point',
|
||||
items: [defaults],
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<AlertmanagerProvider accessType="notification">
|
||||
<FormProvider {...form}>
|
||||
<ChannelSubForm
|
||||
defaultValues={defaults}
|
||||
pathPrefix={`items.0.`}
|
||||
integrationIndex={0}
|
||||
notifiers={notifiersWithLegacyOnly}
|
||||
onDuplicate={jest.fn()}
|
||||
commonSettingsComponent={GrafanaCommonChannelSettings}
|
||||
isEditable={true}
|
||||
isTestable={false}
|
||||
canEditProtectedFields={true}
|
||||
/>
|
||||
</FormProvider>
|
||||
</AlertmanagerProvider>
|
||||
);
|
||||
}
|
||||
|
||||
render(
|
||||
<LegacyOnlyTestWrapper
|
||||
defaults={{
|
||||
__id: 'id-0',
|
||||
type: 'webhook',
|
||||
settings: {},
|
||||
secureFields: {},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
// Webhook should be in dropdown (has v1 with canCreate: true)
|
||||
expect(ui.typeSelector.get()).toHaveTextContent('Webhook');
|
||||
|
||||
// WeChat should NOT be in the options (only has v0 with canCreate: false)
|
||||
// We can't easily check dropdown options without opening it, but the filter should work
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { Controller, FieldErrors, useFormContext } from 'react-hook-form';
|
|||
|
||||
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
import { Alert, Button, Field, Select, Stack, Text, useStyles2 } from '@grafana/ui';
|
||||
import { Alert, Badge, Button, Field, Select, Stack, Text, useStyles2 } from '@grafana/ui';
|
||||
import { NotificationChannelOption } from 'app/features/alerting/unified/types/alerting';
|
||||
|
||||
import {
|
||||
|
|
@ -16,6 +16,12 @@ import {
|
|||
GrafanaChannelValues,
|
||||
ReceiverFormValues,
|
||||
} from '../../../types/receiver-form';
|
||||
import {
|
||||
canCreateNotifier,
|
||||
getLegacyVersionLabel,
|
||||
getOptionsForVersion,
|
||||
isLegacyVersion,
|
||||
} from '../../../utils/notifier-versions';
|
||||
import { OnCallIntegrationType } from '../grafanaAppReceivers/onCall/useOnCallIntegration';
|
||||
|
||||
import { ChannelOptions } from './ChannelOptions';
|
||||
|
|
@ -62,6 +68,7 @@ export function ChannelSubForm<R extends ChannelValues>({
|
|||
|
||||
const channelFieldPath = `items.${integrationIndex}` as const;
|
||||
const typeFieldPath = `${channelFieldPath}.type` as const;
|
||||
const versionFieldPath = `${channelFieldPath}.version` as const;
|
||||
const settingsFieldPath = `${channelFieldPath}.settings` as const;
|
||||
const secureFieldsPath = `${channelFieldPath}.secureFields` as const;
|
||||
|
||||
|
|
@ -104,6 +111,9 @@ export function ChannelSubForm<R extends ChannelValues>({
|
|||
|
||||
setValue(settingsFieldPath, defaultNotifierSettings);
|
||||
setValue(secureFieldsPath, {});
|
||||
|
||||
// Reset version when changing type - backend will use its default
|
||||
setValue(versionFieldPath, undefined);
|
||||
}
|
||||
|
||||
// Restore initial value of an existing oncall integration
|
||||
|
|
@ -123,6 +133,7 @@ export function ChannelSubForm<R extends ChannelValues>({
|
|||
setValue,
|
||||
settingsFieldPath,
|
||||
typeFieldPath,
|
||||
versionFieldPath,
|
||||
secureFieldsPath,
|
||||
getValues,
|
||||
watch,
|
||||
|
|
@ -164,24 +175,30 @@ export function ChannelSubForm<R extends ChannelValues>({
|
|||
setValue(`${settingsFieldPath}.${fieldPath}`, undefined);
|
||||
};
|
||||
|
||||
const typeOptions = useMemo(
|
||||
(): SelectableValue[] =>
|
||||
sortBy(notifiers, ({ dto, meta }) => [meta?.order ?? 0, dto.name]).map<SelectableValue>(
|
||||
({ dto: { name, type }, meta }) => ({
|
||||
// @ts-expect-error ReactNode is supported
|
||||
const typeOptions = useMemo((): SelectableValue[] => {
|
||||
// Filter out notifiers that can't be created (e.g., v0-only integrations like WeChat)
|
||||
// These are legacy integrations that only exist in Mimir and can't be created in Grafana
|
||||
const creatableNotifiers = notifiers.filter(({ dto }) => canCreateNotifier(dto));
|
||||
|
||||
return sortBy(creatableNotifiers, ({ dto, meta }) => [meta?.order ?? 0, dto.name]).map<SelectableValue>(
|
||||
({ dto: { name, type }, meta }) => {
|
||||
return {
|
||||
// ReactNode is supported in Select label, but types don't reflect it
|
||||
/* eslint-disable @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any */
|
||||
label: (
|
||||
<Stack alignItems="center" gap={1}>
|
||||
{name}
|
||||
{meta?.badge}
|
||||
</Stack>
|
||||
),
|
||||
) as any,
|
||||
/* eslint-enable @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any */
|
||||
value: type,
|
||||
description: meta?.description,
|
||||
isDisabled: meta ? !meta.enabled : false,
|
||||
})
|
||||
),
|
||||
[notifiers]
|
||||
);
|
||||
};
|
||||
}
|
||||
);
|
||||
}, [notifiers]);
|
||||
|
||||
const handleTest = async () => {
|
||||
await trigger();
|
||||
|
|
@ -198,10 +215,21 @@ export function ChannelSubForm<R extends ChannelValues>({
|
|||
// Cloud AM takes no value at all
|
||||
const isParseModeNone = parse_mode === 'None' || !parse_mode;
|
||||
const showTelegramWarning = isTelegram && !isParseModeNone;
|
||||
|
||||
// Check if current integration is a legacy version (canCreate: false)
|
||||
// Legacy integrations are read-only and cannot be edited
|
||||
// Read version from existing integration data (stored in receiver config)
|
||||
const integrationVersion = initialValues?.version || defaultValues.version;
|
||||
const isLegacy = notifier ? isLegacyVersion(notifier.dto, integrationVersion) : false;
|
||||
|
||||
// Get the correct options based on the integration's version
|
||||
// This ensures legacy (v0) integrations display the correct schema
|
||||
const versionedOptions = notifier ? getOptionsForVersion(notifier.dto, integrationVersion) : [];
|
||||
|
||||
// if there are mandatory options defined, optional options will be hidden by a collapse
|
||||
// if there aren't mandatory options, all options will be shown without collapse
|
||||
const mandatoryOptions = notifier?.dto.options.filter((o) => o.required) ?? [];
|
||||
const optionalOptions = notifier?.dto.options.filter((o) => !o.required) ?? [];
|
||||
const mandatoryOptions = versionedOptions.filter((o) => o.required);
|
||||
const optionalOptions = versionedOptions.filter((o) => !o.required);
|
||||
|
||||
const contactPointTypeInputId = `contact-point-type-${pathPrefix}`;
|
||||
return (
|
||||
|
|
@ -214,21 +242,35 @@ export function ChannelSubForm<R extends ChannelValues>({
|
|||
data-testid={`${pathPrefix}type`}
|
||||
noMargin
|
||||
>
|
||||
<Controller
|
||||
name={typeFieldPath}
|
||||
control={control}
|
||||
defaultValue={defaultValues.type}
|
||||
render={({ field: { ref, onChange, ...field } }) => (
|
||||
<Select
|
||||
disabled={!isEditable}
|
||||
inputId={contactPointTypeInputId}
|
||||
{...field}
|
||||
width={37}
|
||||
options={typeOptions}
|
||||
onChange={(value) => onChange(value?.value)}
|
||||
<Stack direction="row" alignItems="center" gap={1}>
|
||||
<Controller
|
||||
name={typeFieldPath}
|
||||
control={control}
|
||||
defaultValue={defaultValues.type}
|
||||
render={({ field: { ref, onChange, ...field } }) => (
|
||||
<Select
|
||||
disabled={!isEditable}
|
||||
inputId={contactPointTypeInputId}
|
||||
{...field}
|
||||
width={37}
|
||||
options={typeOptions}
|
||||
onChange={(value) => onChange(value?.value)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{isLegacy && integrationVersion && (
|
||||
<Badge
|
||||
text={getLegacyVersionLabel(integrationVersion)}
|
||||
color="orange"
|
||||
icon="exclamation-triangle"
|
||||
tooltip={t(
|
||||
'alerting.channel-sub-form.tooltip-legacy-version',
|
||||
'This is a legacy integration (version: {{version}}). It cannot be modified.',
|
||||
{ version: integrationVersion }
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Stack>
|
||||
</Field>
|
||||
</div>
|
||||
<div className={styles.buttons}>
|
||||
|
|
@ -292,7 +334,7 @@ export function ChannelSubForm<R extends ChannelValues>({
|
|||
name: notifier.dto.name,
|
||||
})}
|
||||
>
|
||||
{notifier.dto.info !== '' && (
|
||||
{notifier.dto.info && (
|
||||
<Alert title="" severity="info">
|
||||
{notifier.dto.info}
|
||||
</Alert>
|
||||
|
|
|
|||
|
|
@ -18,12 +18,13 @@ import {
|
|||
|
||||
import { alertmanagerApi } from '../../../api/alertmanagerApi';
|
||||
import { GrafanaChannelValues, ReceiverFormValues } from '../../../types/receiver-form';
|
||||
import { hasLegacyIntegrations } from '../../../utils/notifier-versions';
|
||||
import {
|
||||
formChannelValuesToGrafanaChannelConfig,
|
||||
formValuesToGrafanaReceiver,
|
||||
grafanaReceiverToFormValues,
|
||||
} from '../../../utils/receiver-form';
|
||||
import { ProvisionedResource, ProvisioningAlert } from '../../Provisioning';
|
||||
import { ImportedContactPointAlert, ProvisionedResource, ProvisioningAlert } from '../../Provisioning';
|
||||
import { ReceiverTypes } from '../grafanaAppReceivers/onCall/onCall';
|
||||
import { useOnCallIntegration } from '../grafanaAppReceivers/onCall/useOnCallIntegration';
|
||||
|
||||
|
|
@ -39,6 +40,8 @@ const defaultChannelValues: GrafanaChannelValues = Object.freeze({
|
|||
secureFields: {},
|
||||
disableResolveMessage: false,
|
||||
type: 'email',
|
||||
// version is intentionally not set here - it will be determined by the notifier's currentVersion
|
||||
// when the integration is created/type is changed. The backend will use its default if not provided.
|
||||
});
|
||||
|
||||
interface Props {
|
||||
|
|
@ -67,7 +70,6 @@ export const GrafanaReceiverForm = ({ contactPoint, readOnly = false, editMode }
|
|||
} = useOnCallIntegration();
|
||||
|
||||
const { data: grafanaNotifiers = [], isLoading: isLoadingNotifiers } = useGrafanaNotifiersQuery();
|
||||
|
||||
const [testReceivers, setTestReceivers] = useState<Receiver[]>();
|
||||
|
||||
// transform receiver DTO to form values
|
||||
|
|
@ -135,15 +137,20 @@ export const GrafanaReceiverForm = ({ contactPoint, readOnly = false, editMode }
|
|||
);
|
||||
}
|
||||
|
||||
// Map notifiers to Notifier[] format for ReceiverForm
|
||||
// The grafanaNotifiers include version-specific options via the versions array from the backend
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/consistent-type-assertions
|
||||
const notifiers: Notifier[] = grafanaNotifiers.map((n) => {
|
||||
if (n.type === ReceiverTypes.OnCall) {
|
||||
return {
|
||||
dto: extendOnCallNotifierFeatures(n),
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/consistent-type-assertions
|
||||
dto: extendOnCallNotifierFeatures(n as any) as any,
|
||||
meta: onCallNotifierMeta,
|
||||
};
|
||||
}
|
||||
|
||||
return { dto: n };
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/consistent-type-assertions
|
||||
return { dto: n as any };
|
||||
});
|
||||
|
||||
return (
|
||||
|
|
@ -163,7 +170,12 @@ export const GrafanaReceiverForm = ({ contactPoint, readOnly = false, editMode }
|
|||
</Alert>
|
||||
)}
|
||||
|
||||
{contactPoint?.provisioned && <ProvisioningAlert resource={ProvisionedResource.ContactPoint} />}
|
||||
{contactPoint?.provisioned && hasLegacyIntegrations(contactPoint, grafanaNotifiers) && (
|
||||
<ImportedContactPointAlert />
|
||||
)}
|
||||
{contactPoint?.provisioned && !hasLegacyIntegrations(contactPoint, grafanaNotifiers) && (
|
||||
<ProvisioningAlert resource={ProvisionedResource.ContactPoint} />
|
||||
)}
|
||||
|
||||
<ReceiverForm<GrafanaChannelValues>
|
||||
contactPointId={contactPoint?.id}
|
||||
|
|
|
|||
|
|
@ -80,6 +80,20 @@ export type CloudNotifierType =
|
|||
| 'jira';
|
||||
|
||||
export type NotifierType = GrafanaNotifierType | CloudNotifierType;
|
||||
|
||||
/**
|
||||
* Represents a specific version of a notifier integration
|
||||
* Used for integration versioning during Single Alert Manager migration
|
||||
*/
|
||||
export interface NotifierVersion {
|
||||
version: string;
|
||||
label: string;
|
||||
description: string;
|
||||
options: NotificationChannelOption[];
|
||||
/** Whether this version can be used to create new integrations */
|
||||
canCreate?: boolean;
|
||||
}
|
||||
|
||||
export interface NotifierDTO<T = NotifierType> {
|
||||
name: string;
|
||||
description: string;
|
||||
|
|
@ -88,6 +102,23 @@ export interface NotifierDTO<T = NotifierType> {
|
|||
options: NotificationChannelOption[];
|
||||
info?: string;
|
||||
secure?: boolean;
|
||||
/**
|
||||
* Available versions for this notifier from the backend
|
||||
* Each version contains version-specific options and metadata
|
||||
*/
|
||||
versions?: NotifierVersion[];
|
||||
/**
|
||||
* The default version that the backend will use when creating new integrations.
|
||||
* Returned by the backend from /api/alert-notifiers?version=2
|
||||
*
|
||||
* - "v1" for most notifiers (modern Grafana version)
|
||||
* - "v0mimir1" for legacy-only notifiers (e.g., WeChat)
|
||||
*
|
||||
* Note: Currently not used in the frontend. The backend handles version
|
||||
* selection automatically. Could be used in the future to display
|
||||
* version information or validate notifier capabilities.
|
||||
*/
|
||||
currentVersion?: string;
|
||||
}
|
||||
|
||||
export interface NotificationChannelType {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { ControlledField } from '../hooks/useControlledFieldArray';
|
|||
export interface ChannelValues {
|
||||
__id: string; // used to correlate form values to original DTOs
|
||||
type: string;
|
||||
version?: string; // Integration version (e.g. "v0" for Mimir legacy, "v1" for Grafana)
|
||||
settings: Record<string, any>;
|
||||
secureFields: Record<string, boolean | ''>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,429 @@
|
|||
import { GrafanaManagedContactPoint } from 'app/plugins/datasource/alertmanager/types';
|
||||
|
||||
import { NotificationChannelOption, NotifierDTO, NotifierVersion } from '../types/alerting';
|
||||
|
||||
import {
|
||||
canCreateNotifier,
|
||||
getLegacyVersionLabel,
|
||||
getOptionsForVersion,
|
||||
hasLegacyIntegrations,
|
||||
isLegacyVersion,
|
||||
} from './notifier-versions';
|
||||
|
||||
// Helper to create a minimal NotifierDTO for testing
|
||||
function createNotifier(overrides: Partial<NotifierDTO> = {}): NotifierDTO {
|
||||
return {
|
||||
name: 'Test Notifier',
|
||||
description: 'Test description',
|
||||
type: 'webhook',
|
||||
heading: 'Test heading',
|
||||
options: [
|
||||
{
|
||||
element: 'input',
|
||||
inputType: 'text',
|
||||
label: 'Default Option',
|
||||
description: 'Default option description',
|
||||
placeholder: '',
|
||||
propertyName: 'defaultOption',
|
||||
required: true,
|
||||
secure: false,
|
||||
showWhen: { field: '', is: '' },
|
||||
validationRule: '',
|
||||
dependsOn: '',
|
||||
},
|
||||
],
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
// Helper to create a NotifierVersion for testing
|
||||
function createVersion(overrides: Partial<NotifierVersion> = {}): NotifierVersion {
|
||||
return {
|
||||
version: 'v1',
|
||||
label: 'Test Version',
|
||||
description: 'Test version description',
|
||||
options: [
|
||||
{
|
||||
element: 'input',
|
||||
inputType: 'text',
|
||||
label: 'Version Option',
|
||||
description: 'Version option description',
|
||||
placeholder: '',
|
||||
propertyName: 'versionOption',
|
||||
required: true,
|
||||
secure: false,
|
||||
showWhen: { field: '', is: '' },
|
||||
validationRule: '',
|
||||
dependsOn: '',
|
||||
},
|
||||
],
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe('notifier-versions utilities', () => {
|
||||
describe('canCreateNotifier', () => {
|
||||
it('should return true if notifier has no versions array', () => {
|
||||
const notifier = createNotifier({ versions: undefined });
|
||||
expect(canCreateNotifier(notifier)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true if notifier has empty versions array', () => {
|
||||
const notifier = createNotifier({ versions: [] });
|
||||
expect(canCreateNotifier(notifier)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true if at least one version has canCreate: true', () => {
|
||||
const notifier = createNotifier({
|
||||
versions: [
|
||||
createVersion({ version: 'v0mimir1', canCreate: false }),
|
||||
createVersion({ version: 'v1', canCreate: true }),
|
||||
],
|
||||
});
|
||||
expect(canCreateNotifier(notifier)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true if at least one version has canCreate: undefined (defaults to true)', () => {
|
||||
const notifier = createNotifier({
|
||||
versions: [
|
||||
createVersion({ version: 'v0mimir1', canCreate: false }),
|
||||
createVersion({ version: 'v1', canCreate: undefined }),
|
||||
],
|
||||
});
|
||||
expect(canCreateNotifier(notifier)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if all versions have canCreate: false', () => {
|
||||
const notifier = createNotifier({
|
||||
versions: [
|
||||
createVersion({ version: 'v0mimir1', canCreate: false }),
|
||||
createVersion({ version: 'v0mimir2', canCreate: false }),
|
||||
],
|
||||
});
|
||||
expect(canCreateNotifier(notifier)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for notifiers like WeChat that only have legacy versions', () => {
|
||||
const wechatNotifier = createNotifier({
|
||||
name: 'WeChat',
|
||||
type: 'wechat',
|
||||
versions: [createVersion({ version: 'v0mimir1', canCreate: false })],
|
||||
});
|
||||
expect(canCreateNotifier(wechatNotifier)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isLegacyVersion', () => {
|
||||
it('should return false if no version is specified', () => {
|
||||
const notifier = createNotifier({
|
||||
versions: [createVersion({ version: 'v0mimir1', canCreate: false })],
|
||||
});
|
||||
expect(isLegacyVersion(notifier, undefined)).toBe(false);
|
||||
expect(isLegacyVersion(notifier, '')).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false if notifier has no versions array', () => {
|
||||
const notifier = createNotifier({ versions: undefined });
|
||||
expect(isLegacyVersion(notifier, 'v0mimir1')).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false if notifier has empty versions array', () => {
|
||||
const notifier = createNotifier({ versions: [] });
|
||||
expect(isLegacyVersion(notifier, 'v0mimir1')).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false if version is not found in versions array', () => {
|
||||
const notifier = createNotifier({
|
||||
versions: [createVersion({ version: 'v1', canCreate: true })],
|
||||
});
|
||||
expect(isLegacyVersion(notifier, 'v0mimir1')).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false if version has canCreate: true', () => {
|
||||
const notifier = createNotifier({
|
||||
versions: [createVersion({ version: 'v1', canCreate: true })],
|
||||
});
|
||||
expect(isLegacyVersion(notifier, 'v1')).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false if version has canCreate: undefined', () => {
|
||||
const notifier = createNotifier({
|
||||
versions: [createVersion({ version: 'v1', canCreate: undefined })],
|
||||
});
|
||||
expect(isLegacyVersion(notifier, 'v1')).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true if version has canCreate: false', () => {
|
||||
const notifier = createNotifier({
|
||||
versions: [
|
||||
createVersion({ version: 'v0mimir1', canCreate: false }),
|
||||
createVersion({ version: 'v1', canCreate: true }),
|
||||
],
|
||||
});
|
||||
expect(isLegacyVersion(notifier, 'v0mimir1')).toBe(true);
|
||||
});
|
||||
|
||||
it('should correctly identify legacy versions in a mixed notifier', () => {
|
||||
const notifier = createNotifier({
|
||||
versions: [
|
||||
createVersion({ version: 'v0mimir1', canCreate: false }),
|
||||
createVersion({ version: 'v0mimir2', canCreate: false }),
|
||||
createVersion({ version: 'v1', canCreate: true }),
|
||||
],
|
||||
});
|
||||
expect(isLegacyVersion(notifier, 'v0mimir1')).toBe(true);
|
||||
expect(isLegacyVersion(notifier, 'v0mimir2')).toBe(true);
|
||||
expect(isLegacyVersion(notifier, 'v1')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getOptionsForVersion', () => {
|
||||
const defaultOptions: NotificationChannelOption[] = [
|
||||
{
|
||||
element: 'input',
|
||||
inputType: 'text',
|
||||
label: 'Default URL',
|
||||
description: 'Default URL description',
|
||||
placeholder: '',
|
||||
propertyName: 'url',
|
||||
required: true,
|
||||
secure: false,
|
||||
showWhen: { field: '', is: '' },
|
||||
validationRule: '',
|
||||
dependsOn: '',
|
||||
},
|
||||
];
|
||||
|
||||
const v0Options: NotificationChannelOption[] = [
|
||||
{
|
||||
element: 'input',
|
||||
inputType: 'text',
|
||||
label: 'Legacy URL',
|
||||
description: 'Legacy URL description',
|
||||
placeholder: '',
|
||||
propertyName: 'legacyUrl',
|
||||
required: true,
|
||||
secure: false,
|
||||
showWhen: { field: '', is: '' },
|
||||
validationRule: '',
|
||||
dependsOn: '',
|
||||
},
|
||||
];
|
||||
|
||||
const v1Options: NotificationChannelOption[] = [
|
||||
{
|
||||
element: 'input',
|
||||
inputType: 'text',
|
||||
label: 'Modern URL',
|
||||
description: 'Modern URL description',
|
||||
placeholder: '',
|
||||
propertyName: 'modernUrl',
|
||||
required: true,
|
||||
secure: false,
|
||||
showWhen: { field: '', is: '' },
|
||||
validationRule: '',
|
||||
dependsOn: '',
|
||||
},
|
||||
];
|
||||
|
||||
it('should return options from default creatable version if no version is specified', () => {
|
||||
const notifier = createNotifier({
|
||||
options: defaultOptions,
|
||||
versions: [createVersion({ version: 'v1', options: v1Options, canCreate: true })],
|
||||
});
|
||||
// When no version specified, should use options from the default creatable version
|
||||
expect(getOptionsForVersion(notifier, undefined)).toBe(v1Options);
|
||||
});
|
||||
|
||||
it('should return default options if no version is specified and empty string is passed', () => {
|
||||
const notifier = createNotifier({
|
||||
options: defaultOptions,
|
||||
versions: [createVersion({ version: 'v1', options: v1Options, canCreate: true })],
|
||||
});
|
||||
// Empty string is still a falsy version, so should use default creatable version
|
||||
expect(getOptionsForVersion(notifier, '')).toBe(v1Options);
|
||||
});
|
||||
|
||||
it('should return default options if notifier has no versions array', () => {
|
||||
const notifier = createNotifier({
|
||||
options: defaultOptions,
|
||||
versions: undefined,
|
||||
});
|
||||
expect(getOptionsForVersion(notifier, 'v1')).toBe(defaultOptions);
|
||||
});
|
||||
|
||||
it('should return default options if notifier has empty versions array', () => {
|
||||
const notifier = createNotifier({
|
||||
options: defaultOptions,
|
||||
versions: [],
|
||||
});
|
||||
expect(getOptionsForVersion(notifier, 'v1')).toBe(defaultOptions);
|
||||
});
|
||||
|
||||
it('should return default options if version is not found', () => {
|
||||
const notifier = createNotifier({
|
||||
options: defaultOptions,
|
||||
versions: [createVersion({ version: 'v1', options: v1Options })],
|
||||
});
|
||||
expect(getOptionsForVersion(notifier, 'v0mimir1')).toBe(defaultOptions);
|
||||
});
|
||||
|
||||
it('should return version-specific options when version is found', () => {
|
||||
const notifier = createNotifier({
|
||||
options: defaultOptions,
|
||||
versions: [
|
||||
createVersion({ version: 'v0mimir1', options: v0Options }),
|
||||
createVersion({ version: 'v1', options: v1Options }),
|
||||
],
|
||||
});
|
||||
expect(getOptionsForVersion(notifier, 'v0mimir1')).toBe(v0Options);
|
||||
expect(getOptionsForVersion(notifier, 'v1')).toBe(v1Options);
|
||||
});
|
||||
|
||||
it('should return default options if version found but has no options', () => {
|
||||
const notifier = createNotifier({
|
||||
options: defaultOptions,
|
||||
versions: [
|
||||
{
|
||||
version: 'v1',
|
||||
label: 'V1',
|
||||
description: 'V1 description',
|
||||
options: undefined as unknown as NotificationChannelOption[],
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(getOptionsForVersion(notifier, 'v1')).toBe(defaultOptions);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasLegacyIntegrations', () => {
|
||||
// Helper to create a minimal contact point for testing
|
||||
function createContactPoint(overrides: Partial<GrafanaManagedContactPoint> = {}): GrafanaManagedContactPoint {
|
||||
return {
|
||||
name: 'Test Contact Point',
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
// Create notifiers with version info for testing
|
||||
const notifiersWithVersions: NotifierDTO[] = [
|
||||
createNotifier({
|
||||
type: 'slack',
|
||||
versions: [
|
||||
createVersion({ version: 'v0mimir1', canCreate: false }),
|
||||
createVersion({ version: 'v1', canCreate: true }),
|
||||
],
|
||||
}),
|
||||
createNotifier({
|
||||
type: 'webhook',
|
||||
versions: [
|
||||
createVersion({ version: 'v0mimir1', canCreate: false }),
|
||||
createVersion({ version: 'v0mimir2', canCreate: false }),
|
||||
createVersion({ version: 'v1', canCreate: true }),
|
||||
],
|
||||
}),
|
||||
];
|
||||
|
||||
it('should return false if contact point is undefined', () => {
|
||||
expect(hasLegacyIntegrations(undefined, notifiersWithVersions)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false if notifiers is undefined', () => {
|
||||
const contactPoint = createContactPoint({
|
||||
grafana_managed_receiver_configs: [{ type: 'slack', settings: {}, version: 'v0mimir1' }],
|
||||
});
|
||||
expect(hasLegacyIntegrations(contactPoint, undefined)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false if contact point has no integrations', () => {
|
||||
const contactPoint = createContactPoint({ grafana_managed_receiver_configs: undefined });
|
||||
expect(hasLegacyIntegrations(contactPoint, notifiersWithVersions)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false if contact point has empty integrations array', () => {
|
||||
const contactPoint = createContactPoint({ grafana_managed_receiver_configs: [] });
|
||||
expect(hasLegacyIntegrations(contactPoint, notifiersWithVersions)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false if all integrations have v1 version (canCreate: true)', () => {
|
||||
const contactPoint = createContactPoint({
|
||||
grafana_managed_receiver_configs: [
|
||||
{ type: 'slack', settings: {}, version: 'v1' },
|
||||
{ type: 'webhook', settings: {}, version: 'v1' },
|
||||
],
|
||||
});
|
||||
expect(hasLegacyIntegrations(contactPoint, notifiersWithVersions)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false if all integrations have no version', () => {
|
||||
const contactPoint = createContactPoint({
|
||||
grafana_managed_receiver_configs: [
|
||||
{ type: 'slack', settings: {} },
|
||||
{ type: 'webhook', settings: {} },
|
||||
],
|
||||
});
|
||||
expect(hasLegacyIntegrations(contactPoint, notifiersWithVersions)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true if any integration has a legacy version (canCreate: false)', () => {
|
||||
const contactPoint = createContactPoint({
|
||||
grafana_managed_receiver_configs: [
|
||||
{ type: 'slack', settings: {}, version: 'v0mimir1' },
|
||||
{ type: 'webhook', settings: {}, version: 'v1' },
|
||||
],
|
||||
});
|
||||
expect(hasLegacyIntegrations(contactPoint, notifiersWithVersions)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true if all integrations have legacy versions', () => {
|
||||
const contactPoint = createContactPoint({
|
||||
grafana_managed_receiver_configs: [
|
||||
{ type: 'slack', settings: {}, version: 'v0mimir1' },
|
||||
{ type: 'webhook', settings: {}, version: 'v0mimir2' },
|
||||
],
|
||||
});
|
||||
expect(hasLegacyIntegrations(contactPoint, notifiersWithVersions)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if notifier type is not found in notifiers array', () => {
|
||||
const contactPoint = createContactPoint({
|
||||
grafana_managed_receiver_configs: [{ type: 'unknown', settings: {}, version: 'v0mimir1' }],
|
||||
});
|
||||
expect(hasLegacyIntegrations(contactPoint, notifiersWithVersions)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLegacyVersionLabel', () => {
|
||||
it('should return "Legacy" for undefined version', () => {
|
||||
expect(getLegacyVersionLabel(undefined)).toBe('Legacy');
|
||||
});
|
||||
|
||||
it('should return "Legacy" for empty string version', () => {
|
||||
expect(getLegacyVersionLabel('')).toBe('Legacy');
|
||||
});
|
||||
|
||||
it('should return "Legacy" for v0mimir1', () => {
|
||||
expect(getLegacyVersionLabel('v0mimir1')).toBe('Legacy');
|
||||
});
|
||||
|
||||
it('should return "Legacy v2" for v0mimir2', () => {
|
||||
expect(getLegacyVersionLabel('v0mimir2')).toBe('Legacy v2');
|
||||
});
|
||||
|
||||
it('should return "Legacy v3" for v0mimir3', () => {
|
||||
expect(getLegacyVersionLabel('v0mimir3')).toBe('Legacy v3');
|
||||
});
|
||||
|
||||
it('should return "Legacy" for v1 (trailing 1)', () => {
|
||||
expect(getLegacyVersionLabel('v1')).toBe('Legacy');
|
||||
});
|
||||
|
||||
it('should return "Legacy v2" for v2 (trailing 2)', () => {
|
||||
expect(getLegacyVersionLabel('v2')).toBe('Legacy v2');
|
||||
});
|
||||
|
||||
it('should return "Legacy" for version strings without trailing number', () => {
|
||||
expect(getLegacyVersionLabel('legacy')).toBe('Legacy');
|
||||
});
|
||||
});
|
||||
});
|
||||
126
public/app/features/alerting/unified/utils/notifier-versions.ts
Normal file
126
public/app/features/alerting/unified/utils/notifier-versions.ts
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
/**
|
||||
* Utilities for integration versioning
|
||||
*
|
||||
* These utilities help get version-specific options from the backend response
|
||||
* (via /api/alert-notifiers?version=2)
|
||||
*/
|
||||
|
||||
import { GrafanaManagedContactPoint } from 'app/plugins/datasource/alertmanager/types';
|
||||
|
||||
import { NotificationChannelOption, NotifierDTO } from '../types/alerting';
|
||||
|
||||
/**
|
||||
* Checks if a notifier can be used to create new integrations.
|
||||
* A notifier can be created if it has at least one version with canCreate: true,
|
||||
* or if it has no versions array (legacy behavior).
|
||||
*
|
||||
* @param notifier - The notifier DTO to check
|
||||
* @returns True if the notifier can be used to create new integrations
|
||||
*/
|
||||
export function canCreateNotifier(notifier: NotifierDTO): boolean {
|
||||
// If no versions array, assume it can be created (legacy behavior)
|
||||
if (!notifier.versions || notifier.versions.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if any version has canCreate: true (or undefined, which defaults to true)
|
||||
return notifier.versions.some((v) => v.canCreate !== false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a specific version is legacy (cannot be created).
|
||||
* A version is legacy if it has canCreate: false in the notifier's versions array.
|
||||
*
|
||||
* @param notifier - The notifier DTO containing versions array
|
||||
* @param version - The version string to check (e.g., 'v0mimir1', 'v1')
|
||||
* @returns True if the version is legacy (canCreate: false)
|
||||
*/
|
||||
export function isLegacyVersion(notifier: NotifierDTO, version?: string): boolean {
|
||||
// If no version specified or no versions array, it's not legacy
|
||||
if (!version || !notifier.versions || notifier.versions.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find the matching version and check its canCreate property
|
||||
const versionData = notifier.versions.find((v) => v.version === version);
|
||||
|
||||
// A version is legacy if canCreate is explicitly false
|
||||
return versionData?.canCreate === false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the options for a specific version of a notifier.
|
||||
* Used to display the correct form fields based on integration version.
|
||||
*
|
||||
* @param notifier - The notifier DTO containing versions array
|
||||
* @param version - The version to get options for (e.g., 'v0', 'v1')
|
||||
* @returns The options for the specified version, or default options if version not found
|
||||
*/
|
||||
export function getOptionsForVersion(notifier: NotifierDTO, version?: string): NotificationChannelOption[] {
|
||||
// If no versions array, use default options
|
||||
if (!notifier.versions || notifier.versions.length === 0) {
|
||||
return notifier.options;
|
||||
}
|
||||
|
||||
// If version is specified, find the matching version
|
||||
if (version) {
|
||||
const versionData = notifier.versions.find((v) => v.version === version);
|
||||
// Return version-specific options if found, otherwise fall back to default
|
||||
return versionData?.options ?? notifier.options;
|
||||
}
|
||||
|
||||
// If no version specified, find the default creatable version (canCreate !== false)
|
||||
const defaultVersion = notifier.versions.find((v) => v.canCreate !== false);
|
||||
return defaultVersion?.options ?? notifier.options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a contact point has any legacy (imported) integrations.
|
||||
* A contact point has legacy integrations if any of its integrations uses a version
|
||||
* with canCreate: false in the corresponding notifier's versions array.
|
||||
*
|
||||
* @param contactPoint - The contact point to check
|
||||
* @param notifiers - Array of notifier DTOs to look up version info
|
||||
* @returns True if the contact point has at least one legacy/imported integration
|
||||
*/
|
||||
export function hasLegacyIntegrations(contactPoint?: GrafanaManagedContactPoint, notifiers?: NotifierDTO[]): boolean {
|
||||
if (!contactPoint?.grafana_managed_receiver_configs || !notifiers) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return contactPoint.grafana_managed_receiver_configs.some((config) => {
|
||||
const notifier = notifiers.find((n) => n.type === config.type);
|
||||
return notifier ? isLegacyVersion(notifier, config.version) : false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a user-friendly label for a legacy version.
|
||||
* Extracts the version number from the version string and formats it as:
|
||||
* - "Legacy" for version 1 (e.g., v0mimir1)
|
||||
* - "Legacy v2" for version 2 (e.g., v0mimir2)
|
||||
* - etc.
|
||||
*
|
||||
* Precondition: This function assumes the version is already known to be legacy
|
||||
* (i.e., canCreate: false). Use isLegacyVersion() to check before calling this.
|
||||
*
|
||||
* @param version - The version string (e.g., 'v0mimir1', 'v0mimir2')
|
||||
* @returns A user-friendly label like "Legacy" or "Legacy v2"
|
||||
*/
|
||||
export function getLegacyVersionLabel(version?: string): string {
|
||||
if (!version) {
|
||||
return 'Legacy';
|
||||
}
|
||||
|
||||
// Extract trailing number from version string (e.g., v0mimir1 → 1, v0mimir2 → 2)
|
||||
const match = version.match(/(\d+)$/);
|
||||
if (match) {
|
||||
const num = parseInt(match[1], 10);
|
||||
if (num === 1) {
|
||||
return 'Legacy';
|
||||
}
|
||||
return `Legacy v${num}`;
|
||||
}
|
||||
|
||||
return 'Legacy';
|
||||
}
|
||||
|
|
@ -185,6 +185,7 @@ function grafanaChannelConfigToFormChannelValues(
|
|||
const values: GrafanaChannelValues = {
|
||||
__id: id,
|
||||
type: channel.type as NotifierType,
|
||||
version: channel.version,
|
||||
provenance: channel.provenance,
|
||||
settings: { ...channel.settings },
|
||||
secureFields: { ...channel.secureFields },
|
||||
|
|
@ -239,6 +240,7 @@ export function formChannelValuesToGrafanaChannelConfig(
|
|||
}),
|
||||
secureFields: secureFieldsFromValues,
|
||||
type: values.type,
|
||||
version: values.version ?? existing?.version,
|
||||
name,
|
||||
disableResolveMessage:
|
||||
values.disableResolveMessage ?? existing?.disableResolveMessage ?? defaults.disableResolveMessage,
|
||||
|
|
|
|||
|
|
@ -85,6 +85,10 @@ export type GrafanaManagedReceiverConfig = {
|
|||
// SecureSettings?: GrafanaManagedReceiverConfigSettings<boolean>;
|
||||
settings: GrafanaManagedReceiverConfigSettings;
|
||||
type: string;
|
||||
/**
|
||||
* Version of the integration (e.g. "v0" for Mimir legacy, "v1" for Grafana)
|
||||
*/
|
||||
version?: string;
|
||||
/**
|
||||
* Name of the _receiver_, which in most cases will be the
|
||||
* same as the contact point's name. This should not be used, and is optional because the
|
||||
|
|
|
|||
|
|
@ -807,7 +807,8 @@
|
|||
"label-integration": "Integration",
|
||||
"label-notification-settings": "Notification settings",
|
||||
"label-section": "Optional {{name}} settings",
|
||||
"test": "Test"
|
||||
"test": "Test",
|
||||
"tooltip-legacy-version": "This is a legacy integration (version: {{version}}). It cannot be modified."
|
||||
},
|
||||
"classic-condition-viewer": {
|
||||
"of": "OF",
|
||||
|
|
@ -2176,7 +2177,9 @@
|
|||
"provisioning": {
|
||||
"badge-tooltip-provenance": "This resource has been provisioned via {{provenance}} and cannot be edited through the UI",
|
||||
"badge-tooltip-standard": "This resource has been provisioned and cannot be edited through the UI",
|
||||
"body-imported": "This contact point contains integrations that were imported from an external Alertmanager and is currently read-only. The integrations will become editable after the migration process is complete.",
|
||||
"body-provisioned": "This {{resource}} has been provisioned, that means it was created by config. Please contact your server admin to update this {{resource}}.",
|
||||
"title-imported": "This contact point was imported and cannot be edited through the UI",
|
||||
"title-provisioned": "This {{resource}} cannot be edited through the UI"
|
||||
},
|
||||
"provisioning-badge": {
|
||||
|
|
|
|||
Loading…
Reference in a new issue