Fix ROUTES_META_SYMBOL being lost because of RTK query cache transform
This commit is contained in:
@@ -1,89 +0,0 @@
|
||||
import { MatcherOperator, Route } from 'app/plugins/datasource/alertmanager/types';
|
||||
|
||||
import { ComGithubGrafanaGrafanaPkgApisAlertingNotificationsV0Alpha1Route } from '../openapi/routesApi.gen';
|
||||
|
||||
import { k8sSubRouteToRoute, routeToK8sSubRoute } from './routesApi';
|
||||
|
||||
test('k8sSubRouteToRoute', () => {
|
||||
const input: ComGithubGrafanaGrafanaPkgApisAlertingNotificationsV0Alpha1Route = {
|
||||
continue: false,
|
||||
group_by: ['label1'],
|
||||
group_interval: '5m',
|
||||
group_wait: '30s',
|
||||
matchers: [{ label: 'label1', type: '=', value: 'value1' }],
|
||||
mute_time_intervals: ['mt-1'],
|
||||
receiver: 'my-receiver',
|
||||
repeat_interval: '4h',
|
||||
routes: [
|
||||
{
|
||||
receiver: 'receiver2',
|
||||
matchers: [{ label: 'label2', type: '!=', value: 'value2' }],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const expected: Route = {
|
||||
name: 'test-name',
|
||||
continue: false,
|
||||
group_by: ['label1'],
|
||||
group_interval: '5m',
|
||||
group_wait: '30s',
|
||||
matchers: undefined, // matchers -> object_matchers
|
||||
object_matchers: [['label1', MatcherOperator.equal, 'value1']],
|
||||
mute_time_intervals: ['mt-1'],
|
||||
receiver: 'my-receiver',
|
||||
repeat_interval: '4h',
|
||||
routes: [
|
||||
{
|
||||
name: 'test-name',
|
||||
receiver: 'receiver2',
|
||||
matchers: undefined,
|
||||
object_matchers: [['label2', MatcherOperator.notEqual, 'value2']],
|
||||
routes: undefined,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(k8sSubRouteToRoute(input, 'test-name')).toStrictEqual(expected);
|
||||
});
|
||||
|
||||
test('routeToK8sSubRoute', () => {
|
||||
const input: Route = {
|
||||
continue: false,
|
||||
group_by: ['label1'],
|
||||
group_interval: '5m',
|
||||
group_wait: '30s',
|
||||
matchers: undefined, // matchers -> object_matchers
|
||||
object_matchers: [['label1', MatcherOperator.equal, 'value1']],
|
||||
mute_time_intervals: ['mt-1'],
|
||||
receiver: 'my-receiver',
|
||||
repeat_interval: '4h',
|
||||
routes: [
|
||||
{
|
||||
receiver: 'receiver2',
|
||||
matchers: undefined,
|
||||
object_matchers: [['label2', MatcherOperator.notEqual, 'value2']],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const expected: ComGithubGrafanaGrafanaPkgApisAlertingNotificationsV0Alpha1Route = {
|
||||
continue: false,
|
||||
group_by: ['label1'],
|
||||
group_interval: '5m',
|
||||
group_wait: '30s',
|
||||
matchers: [{ label: 'label1', type: '=', value: 'value1' }],
|
||||
mute_time_intervals: ['mt-1'],
|
||||
receiver: 'my-receiver',
|
||||
repeat_interval: '4h',
|
||||
routes: [
|
||||
{
|
||||
receiver: 'receiver2',
|
||||
matchers: [{ label: 'label2', type: '!=', value: 'value2' }],
|
||||
routes: undefined,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(routeToK8sSubRoute(input)).toStrictEqual(expected);
|
||||
});
|
||||
@@ -1,101 +0,0 @@
|
||||
import {
|
||||
ComGithubGrafanaGrafanaPkgApisAlertingNotificationsV0Alpha1RoutingTree,
|
||||
ComGithubGrafanaGrafanaPkgApisAlertingNotificationsV0Alpha1Route,
|
||||
generatedRoutesApi,
|
||||
ReadNamespacedRoutingTreeApiResponse,
|
||||
ListNamespacedRoutingTreeApiResponse,
|
||||
} from 'app/features/alerting/unified/openapi/routesApi.gen';
|
||||
import { MatcherOperator, ROUTES_META_SYMBOL, Route } from 'app/plugins/datasource/alertmanager/types';
|
||||
import { ROOT_ROUTE_NAME } from '../utils/k8s/constants';
|
||||
import { isK8sEntityProvisioned } from '../utils/k8s/utils';
|
||||
import { DefinitionsFromApi, OverrideResultType, TagTypesFromApi } from '@reduxjs/toolkit/query';
|
||||
|
||||
type Definitions = DefinitionsFromApi<typeof generatedRoutesApi>;
|
||||
type TagTypes = TagTypesFromApi<typeof generatedRoutesApi>;
|
||||
|
||||
type UpdatedDefinitions = Omit<Definitions, 'readNamespacedRoutingTree' | 'listNamespacedRoutingTree'> & {
|
||||
readNamespacedRoutingTree: OverrideResultType<Definitions['readNamespacedRoutingTree'], Route>;
|
||||
listNamespacedRoutingTree: OverrideResultType<Definitions['listNamespacedRoutingTree'], Route[]>;
|
||||
};
|
||||
|
||||
export const routesApi = generatedRoutesApi.enhanceEndpoints<TagTypes, UpdatedDefinitions>({
|
||||
endpoints: {
|
||||
readNamespacedRoutingTree: (endpoint) => {
|
||||
// We transform the response here instead of in `selectFromResult` so that memoization of the transformed Route
|
||||
// is automatically handled.
|
||||
endpoint.transformResponse = (response: ReadNamespacedRoutingTreeApiResponse): Route => {
|
||||
return k8sRouteToRoute(response);
|
||||
};
|
||||
},
|
||||
listNamespacedRoutingTree: (endpoint) => {
|
||||
endpoint.transformResponse = (response: ListNamespacedRoutingTreeApiResponse): Route[] => {
|
||||
return k8sRoutesToRoutes(response.items);
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const NAMED_ROOT_LABEL_NAME = '__grafana_managed_route__';
|
||||
|
||||
function k8sRouteToRoute(route: ComGithubGrafanaGrafanaPkgApisAlertingNotificationsV0Alpha1RoutingTree): Route {
|
||||
return {
|
||||
...route.spec.defaults,
|
||||
name: route.metadata.name,
|
||||
routes: route.spec.routes?.map((subroute) => k8sSubRouteToRoute(subroute, route.metadata.name)),
|
||||
// This assumes if a `NAMED_ROOT_LABEL_NAME` label exists, it will NOT go to the default route, which is a fair but
|
||||
// not perfect assumption since we don't yet protect the label.
|
||||
object_matchers:
|
||||
route.metadata.name == ROOT_ROUTE_NAME || !route.metadata.name
|
||||
? [[NAMED_ROOT_LABEL_NAME, MatcherOperator.equal, '']]
|
||||
: [[NAMED_ROOT_LABEL_NAME, MatcherOperator.equal, route.metadata.name]],
|
||||
[ROUTES_META_SYMBOL]: {
|
||||
provisioned: isK8sEntityProvisioned(route),
|
||||
resourceVersion: route.metadata.resourceVersion,
|
||||
name: route.metadata.name,
|
||||
metadata: route.metadata,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function k8sRoutesToRoutes(routes: ComGithubGrafanaGrafanaPkgApisAlertingNotificationsV0Alpha1RoutingTree[]): Route[] {
|
||||
return routes?.map((route) => {
|
||||
return k8sRouteToRoute(route);
|
||||
});
|
||||
}
|
||||
|
||||
/** Helper to provide type safety for matcher operators from API */
|
||||
function isValidMatcherOperator(type: string): type is MatcherOperator {
|
||||
return Object.values<string>(MatcherOperator).includes(type);
|
||||
}
|
||||
|
||||
export function k8sSubRouteToRoute(
|
||||
route: ComGithubGrafanaGrafanaPkgApisAlertingNotificationsV0Alpha1Route,
|
||||
rootName?: string
|
||||
): Route {
|
||||
return {
|
||||
...route,
|
||||
name: rootName,
|
||||
routes: route.routes?.map((subroute) => k8sSubRouteToRoute(subroute, rootName)),
|
||||
matchers: undefined,
|
||||
object_matchers: route.matchers?.map(({ label, type, value }) => {
|
||||
if (!isValidMatcherOperator(type)) {
|
||||
throw new Error(`Invalid matcher operator from API: ${type}`);
|
||||
}
|
||||
return [label, type, value];
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
export function routeToK8sSubRoute(route: Route): ComGithubGrafanaGrafanaPkgApisAlertingNotificationsV0Alpha1Route {
|
||||
const { object_matchers, ...rest } = route;
|
||||
return {
|
||||
...rest,
|
||||
receiver: route.receiver ?? undefined,
|
||||
matchers: object_matchers?.map(([label, type, value]) => ({
|
||||
label,
|
||||
type,
|
||||
value,
|
||||
})),
|
||||
routes: route.routes?.map(routeToK8sSubRoute),
|
||||
};
|
||||
}
|
||||
+95
-14
@@ -3,7 +3,7 @@ import memoize from 'micro-memoize';
|
||||
|
||||
import { INHERITABLE_KEYS, type InheritableProperties } from '@grafana/alerting/internal';
|
||||
import { BaseAlertmanagerArgs, Skippable } from 'app/features/alerting/unified/types/hooks';
|
||||
import { ROUTES_META_SYMBOL, Route, RouteWithID } from 'app/plugins/datasource/alertmanager/types';
|
||||
import { ROUTES_META_SYMBOL, Route, RouteWithID, MatcherOperator } from 'app/plugins/datasource/alertmanager/types';
|
||||
|
||||
import { getAPINamespace } from '../../../../../api/utils';
|
||||
import { alertmanagerApi } from '../../api/alertmanagerApi';
|
||||
@@ -12,8 +12,9 @@ import { useProduceNewAlertmanagerConfiguration } from '../../hooks/useProduceNe
|
||||
import {
|
||||
ComGithubGrafanaGrafanaPkgApisAlertingNotificationsV0Alpha1RouteDefaults,
|
||||
ComGithubGrafanaGrafanaPkgApisAlertingNotificationsV0Alpha1RoutingTree,
|
||||
ComGithubGrafanaGrafanaPkgApisAlertingNotificationsV0Alpha1Route,
|
||||
generatedRoutesApi as routingTreeApi,
|
||||
} from '../../openapi/routesApi.gen';
|
||||
import { routesApi as routingTreeApi, routeToK8sSubRoute } from '../../api/routesApi';
|
||||
import {
|
||||
addRouteAction,
|
||||
deleteRouteAction,
|
||||
@@ -22,7 +23,7 @@ import {
|
||||
import { FormAmRoute } from '../../types/amroutes';
|
||||
import { addUniqueIdentifierToRoute } from '../../utils/amroutes';
|
||||
import { PROVENANCE_NONE, ROOT_ROUTE_NAME } from '../../utils/k8s/constants';
|
||||
import { shouldUseK8sApi } from '../../utils/k8s/utils';
|
||||
import { isK8sEntityProvisioned, shouldUseK8sApi } from '../../utils/k8s/utils';
|
||||
import { routeAdapter } from '../../utils/routeAdapter';
|
||||
import {
|
||||
InsertPosition,
|
||||
@@ -57,11 +58,21 @@ export const useNotificationPolicyRoute = (
|
||||
{
|
||||
skip: skip || !k8sApiSupported,
|
||||
selectFromResult: (result) => {
|
||||
return {
|
||||
...result,
|
||||
currentData: result.currentData,
|
||||
data: result.data,
|
||||
};
|
||||
const { data, currentData, ...rest } = result;
|
||||
|
||||
const transformed = useMemo(() => {
|
||||
return data ? k8sRouteToRoute(data) : data;
|
||||
}, [data]);
|
||||
|
||||
const transformedCurrent = useMemo(() => {
|
||||
return currentData ? k8sRouteToRoute(currentData) : currentData;
|
||||
}, [currentData]);
|
||||
|
||||
return {
|
||||
...rest,
|
||||
data: transformed,
|
||||
currentData: transformedCurrent,
|
||||
};
|
||||
},
|
||||
}
|
||||
);
|
||||
@@ -90,10 +101,20 @@ export const useListNotificationPolicyRoutes = ({ skip }: Skippable = {}) => {
|
||||
{
|
||||
skip: skip,
|
||||
selectFromResult: (result) => {
|
||||
const { data, currentData, ...rest } = result;
|
||||
|
||||
const transformed = useMemo(() => {
|
||||
return data ? data.items.map(k8sRouteToRoute) : data;
|
||||
}, [data]);
|
||||
|
||||
const transformedCurrent = useMemo(() => {
|
||||
return currentData ? currentData.items.map(k8sRouteToRoute) : currentData;
|
||||
}, [currentData]);
|
||||
|
||||
return {
|
||||
...result,
|
||||
currentData: result.currentData,
|
||||
data: result.data,
|
||||
...rest,
|
||||
data: transformed,
|
||||
currentData: transformedCurrent,
|
||||
};
|
||||
},
|
||||
}
|
||||
@@ -125,7 +146,7 @@ export function useUpdateExistingNotificationPolicy({ alertmanager }: BaseAlertm
|
||||
throw new Error(`no root route found for namespace ${namespace} and name ${name}`);
|
||||
}
|
||||
|
||||
const rootRouteWithIdentifiers = addUniqueIdentifierToRoute(rootTree);
|
||||
const rootRouteWithIdentifiers = addUniqueIdentifierToRoute(k8sRouteToRoute(rootTree));
|
||||
const newRouteTree = mergePartialAmRouteWithRouteTree(alertmanager, update, rootRouteWithIdentifiers);
|
||||
|
||||
// Create the K8s route object
|
||||
@@ -162,7 +183,7 @@ export function useDeleteNotificationPolicy({ alertmanager }: BaseAlertmanagerAr
|
||||
throw new Error(`no root route found for namespace ${namespace}`);
|
||||
}
|
||||
|
||||
const rootRouteWithIdentifiers = addUniqueIdentifierToRoute(rootTree);
|
||||
const rootRouteWithIdentifiers = addUniqueIdentifierToRoute(k8sRouteToRoute(rootTree));
|
||||
const newRouteTree = omitRouteFromRouteTree(route.id, rootRouteWithIdentifiers);
|
||||
|
||||
// Create the K8s route object
|
||||
@@ -208,7 +229,7 @@ export function useAddNotificationPolicy({ alertmanager }: BaseAlertmanagerArgs)
|
||||
throw new Error(`no root route found for namespace ${namespace}`);
|
||||
}
|
||||
|
||||
const rootRouteWithIdentifiers = addUniqueIdentifierToRoute(rootTree);
|
||||
const rootRouteWithIdentifiers = addUniqueIdentifierToRoute(k8sRouteToRoute(rootTree));
|
||||
const newRouteTree = addRouteToReferenceRoute(
|
||||
alertmanager ?? '',
|
||||
partialRoute,
|
||||
@@ -369,3 +390,63 @@ export function createKubernetesRoutingTreeSpec(
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export const NAMED_ROOT_LABEL_NAME = '__grafana_managed_route__';
|
||||
|
||||
function k8sRouteToRoute(route: ComGithubGrafanaGrafanaPkgApisAlertingNotificationsV0Alpha1RoutingTree): Route {
|
||||
return {
|
||||
...route.spec.defaults,
|
||||
name: route.metadata.name,
|
||||
routes: route.spec.routes?.map((subroute) => k8sSubRouteToRoute(subroute, route.metadata.name)),
|
||||
// This assumes if a `NAMED_ROOT_LABEL_NAME` label exists, it will NOT go to the default route, which is a fair but
|
||||
// not perfect assumption since we don't yet protect the label.
|
||||
object_matchers:
|
||||
route.metadata.name == ROOT_ROUTE_NAME || !route.metadata.name
|
||||
? [[NAMED_ROOT_LABEL_NAME, MatcherOperator.equal, '']]
|
||||
: [[NAMED_ROOT_LABEL_NAME, MatcherOperator.equal, route.metadata.name]],
|
||||
[ROUTES_META_SYMBOL]: {
|
||||
provisioned: isK8sEntityProvisioned(route),
|
||||
resourceVersion: route.metadata.resourceVersion,
|
||||
name: route.metadata.name,
|
||||
metadata: route.metadata,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** Helper to provide type safety for matcher operators from API */
|
||||
function isValidMatcherOperator(type: string): type is MatcherOperator {
|
||||
return Object.values<string>(MatcherOperator).includes(type);
|
||||
}
|
||||
|
||||
export function k8sSubRouteToRoute(
|
||||
route: ComGithubGrafanaGrafanaPkgApisAlertingNotificationsV0Alpha1Route,
|
||||
rootName?: string
|
||||
): Route {
|
||||
return {
|
||||
...route,
|
||||
name: rootName,
|
||||
routes: route.routes?.map((subroute) => k8sSubRouteToRoute(subroute, rootName)),
|
||||
matchers: undefined,
|
||||
object_matchers: route.matchers?.map(({ label, type, value }) => {
|
||||
if (!isValidMatcherOperator(type)) {
|
||||
throw new Error(`Invalid matcher operator from API: ${type}`);
|
||||
}
|
||||
return [label, type, value];
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
export function routeToK8sSubRoute(route: Route): ComGithubGrafanaGrafanaPkgApisAlertingNotificationsV0Alpha1Route {
|
||||
const { object_matchers, ...rest } = route;
|
||||
return {
|
||||
...rest,
|
||||
receiver: route.receiver ?? undefined,
|
||||
matchers: object_matchers?.map(([label, type, value]) => ({
|
||||
label,
|
||||
type,
|
||||
value,
|
||||
})),
|
||||
routes: route.routes?.map(routeToK8sSubRoute),
|
||||
};
|
||||
}
|
||||
|
||||
+4
-2
@@ -1,14 +1,16 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useAsync } from 'react-use';
|
||||
|
||||
import { useNotificationPolicyRoute } from 'app/features/alerting/unified/components/notification-policies/useNotificationPolicyRoute';
|
||||
import {
|
||||
NAMED_ROOT_LABEL_NAME,
|
||||
useNotificationPolicyRoute,
|
||||
} from 'app/features/alerting/unified/components/notification-policies/useNotificationPolicyRoute';
|
||||
|
||||
import { Labels } from '../../../../../../types/unified-alerting-dto';
|
||||
import { useRouteGroupsMatcher } from '../../../useRouteGroupsMatcher';
|
||||
import { addUniqueIdentifierToRoute } from '../../../utils/amroutes';
|
||||
import { GRAFANA_RULES_SOURCE_NAME } from '../../../utils/datasource';
|
||||
import { normalizeRoute } from '../../../utils/notification-policies';
|
||||
import { NAMED_ROOT_LABEL_NAME } from '../../../api/routesApi';
|
||||
|
||||
export const useAlertmanagerNotificationRoutingPreview = (alertmanager: string, instances: Labels[]) => {
|
||||
// if a NAMED_ROOT_LABEL_NAME label exists, then we only match to that route.
|
||||
|
||||
Reference in New Issue
Block a user