mirror of
https://github.com/grafana/grafana.git
synced 2025-08-01 04:51:49 +08:00
Alerting: Add versioning to the API client (#104944)
This commit is contained in:
@ -37,7 +37,7 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"typecheck": "tsc --emitDeclarationOnly false --noEmit",
|
"typecheck": "tsc --emitDeclarationOnly false --noEmit",
|
||||||
"codegen": "yarn run rtk-query-codegen-openapi ./scripts/codegen.ts"
|
"codegen": "rtk-query-codegen-openapi ./scripts/codegen.ts"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@grafana/tsconfig": "^2.0.0",
|
"@grafana/tsconfig": "^2.0.0",
|
||||||
@ -57,7 +57,7 @@
|
|||||||
"react-dom": "^18.0.0"
|
"react-dom": "^18.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@reduxjs/toolkit": "^2.7.0",
|
"@reduxjs/toolkit": "^2.8.0",
|
||||||
"lodash": "^4.17.21"
|
"lodash": "^4.17.21"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
4
packages/grafana-alerting/scripts/README.md
Normal file
4
packages/grafana-alerting/scripts/README.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
These files are built using the `yarn run codegen` command.
|
||||||
|
API clients will be written to `src/grafana/api/<version>/api.gen.ts`.
|
||||||
|
|
||||||
|
Make sure to create a versioned API client for each API version – see `src/grafana/api/v0alpha1/api.ts` as an example.
|
@ -1,21 +1,47 @@
|
|||||||
/**
|
/**
|
||||||
* This script will generate TypeScript type definitions and a RTKQ client for the alerting k8s APIs.
|
* This script will generate TypeScript type definitions and a RTKQ clients for the alerting k8s APIs.
|
||||||
* It downloads the OpenAPI schema from a running Grafana instance and generates the types.
|
|
||||||
*
|
*
|
||||||
* Run `yarn run codegen` from the "grafana-alerting" package to invoke this script.
|
* Run `yarn run codegen` from the "grafana-alerting" package to invoke this script.
|
||||||
|
*
|
||||||
|
* API clients will be placed in "src/grafana/api/<version>/api.gen.ts"
|
||||||
*/
|
*/
|
||||||
import { type ConfigFile } from '@rtk-query/codegen-openapi';
|
import type { ConfigFile } from '@rtk-query/codegen-openapi';
|
||||||
import { resolve } from 'node:path';
|
|
||||||
|
|
||||||
// these snapshots are generated by running "go test pkg/tests/apis/openapi_test.go", see the README in the "openapi_snapshots" directory
|
// ℹ️ append versions here to generate additional API clients
|
||||||
const OPENAPI_SCHEMA_LOCATION = resolve(
|
const VERSIONS = ['v0alpha1'] as const;
|
||||||
'../../../pkg/tests/apis/openapi_snapshots/notifications.alerting.grafana.app-v0alpha1.json'
|
|
||||||
);
|
type OutputFile = Omit<ConfigFile, 'outputFile'>;
|
||||||
|
type OutputFiles = Record<string, OutputFile>;
|
||||||
|
|
||||||
|
const outputFiles = VERSIONS.reduce<OutputFiles>((acc, version) => {
|
||||||
|
// we append the version here so we export versioned API clients from this package without having to re-export with an alias
|
||||||
|
const exportName = 'alertingAPI';
|
||||||
|
|
||||||
|
// ℹ️ these snapshots are generated by running "go test pkg/tests/apis/openapi_test.go" and "scripts/process-specs.ts",
|
||||||
|
// see the README in the "openapi_snapshots" directory
|
||||||
|
const schemaFile = `../../../data/openapi/notifications.alerting.grafana.app-${version}.json`;
|
||||||
|
|
||||||
|
// ℹ️ make sure there is a API file in each versioned directory
|
||||||
|
const apiFile = `../src/grafana/api/${version}/api.ts`;
|
||||||
|
|
||||||
|
// output each api client into a versioned directory
|
||||||
|
const outputPath = `../src/grafana/api/${version}/api.gen.ts`;
|
||||||
|
|
||||||
|
acc[outputPath] = {
|
||||||
|
exportName,
|
||||||
|
schemaFile,
|
||||||
|
apiFile,
|
||||||
|
tag: true, // generate tags for cache invalidation
|
||||||
|
} satisfies OutputFile;
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
exportName: 'alertingAPI',
|
// these are intentionally empty but will be set for each versioned config file
|
||||||
schemaFile: OPENAPI_SCHEMA_LOCATION,
|
exportName: '',
|
||||||
apiFile: '../src/grafana/api.ts',
|
schemaFile: '',
|
||||||
outputFile: resolve('../src/grafana/api.gen.ts'),
|
apiFile: '',
|
||||||
tag: true,
|
|
||||||
|
outputFiles,
|
||||||
} satisfies ConfigFile;
|
} satisfies ConfigFile;
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
|
|
||||||
|
|
||||||
const BASE_URL = '/';
|
|
||||||
|
|
||||||
export const api = createApi({
|
|
||||||
reducerPath: 'grafanaAlertingAPI',
|
|
||||||
baseQuery: fetchBaseQuery({
|
|
||||||
baseUrl: BASE_URL,
|
|
||||||
}),
|
|
||||||
endpoints: () => ({}),
|
|
||||||
});
|
|
6
packages/grafana-alerting/src/grafana/api/README.md
Normal file
6
packages/grafana-alerting/src/grafana/api/README.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# Versioned Alerting API clients
|
||||||
|
|
||||||
|
1. create a new folder for your new API version
|
||||||
|
2. create a `api.ts` file in the new folder, see existing ones
|
||||||
|
3. run `yarn codegen` to generate the `api.get.ts` file, which is your new RTKQ client
|
||||||
|
4. (optional) create a `types.ts` file in the new folder, see existing ones to enhance the types that are auto-generated.
|
13
packages/grafana-alerting/src/grafana/api/util.ts
Normal file
13
packages/grafana-alerting/src/grafana/api/util.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* @TODO move this to some shared package, currently copied from Grafana core (app/api/utils)
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { config } from '@grafana/runtime';
|
||||||
|
|
||||||
|
export const getAPINamespace = () => config.namespace;
|
||||||
|
|
||||||
|
export const getAPIBaseURL = (group: string, version: string) =>
|
||||||
|
`/apis/${group}/${version}/namespaces/${getAPINamespace()}` as const;
|
||||||
|
|
||||||
|
// By including the version in the reducer path we can prevent cache bugs when different versions of the API are used for the same entities
|
||||||
|
export const getAPIReducerPath = (group: string, version: string) => `${group}/${version}` as const;
|
File diff suppressed because it is too large
Load Diff
17
packages/grafana-alerting/src/grafana/api/v0alpha1/api.ts
Normal file
17
packages/grafana-alerting/src/grafana/api/v0alpha1/api.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
|
||||||
|
|
||||||
|
import { getAPIBaseURL, getAPIReducerPath } from '../util';
|
||||||
|
|
||||||
|
const VERSION = 'v0alpha1';
|
||||||
|
const GROUP = 'notifications.alerting.grafana.app';
|
||||||
|
|
||||||
|
const baseUrl = getAPIBaseURL(GROUP, VERSION);
|
||||||
|
const reducerPath = getAPIReducerPath(GROUP, VERSION);
|
||||||
|
|
||||||
|
export const api = createApi({
|
||||||
|
reducerPath,
|
||||||
|
baseQuery: fetchBaseQuery({
|
||||||
|
baseUrl,
|
||||||
|
}),
|
||||||
|
endpoints: () => ({}),
|
||||||
|
});
|
@ -3,14 +3,10 @@
|
|||||||
*/
|
*/
|
||||||
import { MergeDeep, MergeExclusive, OverrideProperties } from 'type-fest';
|
import { MergeDeep, MergeExclusive, OverrideProperties } from 'type-fest';
|
||||||
|
|
||||||
import {
|
import type { Receiver, Integration as ReceiverIntegration, ListReceiverApiResponse } from './api.gen';
|
||||||
ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Receiver as ContactPointV0Alpha1,
|
|
||||||
ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Integration as IntegrationV0Alpha1,
|
|
||||||
ListReceiverApiResponse,
|
|
||||||
} from '../api.gen';
|
|
||||||
|
|
||||||
type GenericIntegration = OverrideProperties<
|
type GenericIntegration = OverrideProperties<
|
||||||
IntegrationV0Alpha1,
|
ReceiverIntegration,
|
||||||
{
|
{
|
||||||
settings: Record<string, unknown>;
|
settings: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
@ -60,7 +56,7 @@ export type Integration = EmailIntegration | SlackIntegration | GenericIntegrati
|
|||||||
// Enhanced version of ContactPoint with typed integrations
|
// Enhanced version of ContactPoint with typed integrations
|
||||||
// ⚠️ MergeDeep does not check if the property you are overriding exists in the base type and there is no "DeepOverrideProperties" helper
|
// ⚠️ MergeDeep does not check if the property you are overriding exists in the base type and there is no "DeepOverrideProperties" helper
|
||||||
export type ContactPoint = MergeDeep<
|
export type ContactPoint = MergeDeep<
|
||||||
ContactPointV0Alpha1,
|
Receiver,
|
||||||
{
|
{
|
||||||
spec: {
|
spec: {
|
||||||
integrations: Integration[];
|
integrations: Integration[];
|
||||||
@ -68,7 +64,7 @@ export type ContactPoint = MergeDeep<
|
|||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export type EnhancedListReceiverResponse = OverrideProperties<
|
export type EnhancedListReceiverApiResponse = OverrideProperties<
|
||||||
ListReceiverApiResponse,
|
ListReceiverApiResponse,
|
||||||
{
|
{
|
||||||
items: ContactPoint[];
|
items: ContactPoint[];
|
@ -2,8 +2,8 @@ import { chain } from 'lodash';
|
|||||||
|
|
||||||
import { Combobox, ComboboxOption } from '@grafana/ui';
|
import { Combobox, ComboboxOption } from '@grafana/ui';
|
||||||
|
|
||||||
import { useListContactPoints } from '../hooks/useContactPoints';
|
import { ContactPoint } from '../../api/v0alpha1/types';
|
||||||
import { ContactPoint } from '../types';
|
import { useListContactPointsv0alpha1 } from '../hooks/useContactPoints';
|
||||||
import { getContactPointDescription } from '../utils';
|
import { getContactPointDescription } from '../utils';
|
||||||
|
|
||||||
const collator = new Intl.Collator('en', { sensitivity: 'accent' });
|
const collator = new Intl.Collator('en', { sensitivity: 'accent' });
|
||||||
@ -17,7 +17,7 @@ type ContactPointSelectorProps = {
|
|||||||
* @TODO make ComboBox accept a ReactNode so we can use icons and such
|
* @TODO make ComboBox accept a ReactNode so we can use icons and such
|
||||||
*/
|
*/
|
||||||
function ContactPointSelector({ onChange }: ContactPointSelectorProps) {
|
function ContactPointSelector({ onChange }: ContactPointSelectorProps) {
|
||||||
const { currentData: contactPoints, isLoading } = useListContactPoints();
|
const { currentData: contactPoints, isLoading } = useListContactPointsv0alpha1();
|
||||||
|
|
||||||
// Create a mapping of options with their corresponding contact points
|
// Create a mapping of options with their corresponding contact points
|
||||||
const contactPointOptions = chain(contactPoints?.items)
|
const contactPointOptions = chain(contactPoints?.items)
|
||||||
@ -27,7 +27,7 @@ function ContactPointSelector({ onChange }: ContactPointSelectorProps) {
|
|||||||
label: contactPoint.spec.title,
|
label: contactPoint.spec.title,
|
||||||
value: contactPoint.metadata.uid ?? contactPoint.spec.title,
|
value: contactPoint.metadata.uid ?? contactPoint.spec.title,
|
||||||
description: getContactPointDescription(contactPoint),
|
description: getContactPointDescription(contactPoint),
|
||||||
},
|
} satisfies ComboboxOption<string>,
|
||||||
contactPoint,
|
contactPoint,
|
||||||
}))
|
}))
|
||||||
.value()
|
.value()
|
||||||
|
@ -1,15 +1,11 @@
|
|||||||
import { fetchBaseQuery, TypedUseQueryHookResult } from '@reduxjs/toolkit/query/react';
|
import { fetchBaseQuery, TypedUseQueryHookResult } from '@reduxjs/toolkit/query/react';
|
||||||
|
|
||||||
import { config } from '@grafana/runtime';
|
import { alertingAPI, type ListReceiverApiArg } from '../../api/v0alpha1/api.gen';
|
||||||
|
import type { EnhancedListReceiverApiResponse } from '../../api/v0alpha1/types';
|
||||||
import { alertingAPI, ListReceiverApiArg } from '../../api.gen';
|
|
||||||
import { EnhancedListReceiverResponse } from '../types';
|
|
||||||
|
|
||||||
const { namespace } = config;
|
|
||||||
|
|
||||||
// this is a workaround for the fact that the generated types are not narrow enough
|
// this is a workaround for the fact that the generated types are not narrow enough
|
||||||
type EnhancedHookResult = TypedUseQueryHookResult<
|
type EnhancedHookResult = TypedUseQueryHookResult<
|
||||||
EnhancedListReceiverResponse,
|
EnhancedListReceiverApiResponse,
|
||||||
ListReceiverApiArg,
|
ListReceiverApiArg,
|
||||||
ReturnType<typeof fetchBaseQuery>
|
ReturnType<typeof fetchBaseQuery>
|
||||||
>;
|
>;
|
||||||
@ -22,8 +18,8 @@ type EnhancedHookResult = TypedUseQueryHookResult<
|
|||||||
*
|
*
|
||||||
* It automatically uses the configured namespace for the query.
|
* It automatically uses the configured namespace for the query.
|
||||||
*/
|
*/
|
||||||
function useListContactPoints() {
|
function useListContactPointsv0alpha1() {
|
||||||
return alertingAPI.useListReceiverQuery<EnhancedHookResult>({ namespace });
|
return alertingAPI.useListReceiverQuery<EnhancedHookResult>({});
|
||||||
}
|
}
|
||||||
|
|
||||||
export { useListContactPoints };
|
export { useListContactPointsv0alpha1 };
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { countBy, isEmpty } from 'lodash';
|
import { countBy, isEmpty } from 'lodash';
|
||||||
|
|
||||||
import { ContactPoint } from './types';
|
import { ContactPoint } from '../api/v0alpha1/types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a human-readable description of a ContactPoint by summarizing its integrations.
|
* Generates a human-readable description of a ContactPoint by summarizing its integrations.
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
/**
|
/**
|
||||||
* Export things here that you want to be available under @grafana/alerting/internal
|
* Export things here that you want to be available under @grafana/alerting/internal
|
||||||
*/
|
*/
|
||||||
export { alertingAPI } from './grafana/api.gen';
|
|
||||||
|
|
||||||
export default {};
|
export default {};
|
||||||
|
@ -3,6 +3,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// Contact Points
|
// Contact Points
|
||||||
export * from './grafana/contactPoints/types';
|
export * from './grafana/api/v0alpha1/types';
|
||||||
export { useListContactPoints } from './grafana/contactPoints/hooks/useContactPoints';
|
export { useListContactPointsv0alpha1 } from './grafana/contactPoints/hooks/useContactPoints';
|
||||||
export { ContactPointSelector } from './grafana/contactPoints/components/ContactPointSelector';
|
export { ContactPointSelector } from './grafana/contactPoints/components/ContactPointSelector';
|
||||||
|
|
||||||
|
// Low-level API hooks
|
||||||
|
export { alertingAPI as alertingAPIv0alpha1 } from './grafana/api/v0alpha1/api.gen';
|
||||||
|
10
yarn.lock
10
yarn.lock
@ -2915,7 +2915,7 @@ __metadata:
|
|||||||
resolution: "@grafana/alerting@workspace:packages/grafana-alerting"
|
resolution: "@grafana/alerting@workspace:packages/grafana-alerting"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@grafana/tsconfig": "npm:^2.0.0"
|
"@grafana/tsconfig": "npm:^2.0.0"
|
||||||
"@reduxjs/toolkit": "npm:^2.7.0"
|
"@reduxjs/toolkit": "npm:^2.8.0"
|
||||||
"@rtk-query/codegen-openapi": "npm:^2.0.0"
|
"@rtk-query/codegen-openapi": "npm:^2.0.0"
|
||||||
"@types/lodash": "npm:^4"
|
"@types/lodash": "npm:^4"
|
||||||
"@types/react": "npm:18.3.18"
|
"@types/react": "npm:18.3.18"
|
||||||
@ -6517,9 +6517,9 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@reduxjs/toolkit@npm:^2.7.0":
|
"@reduxjs/toolkit@npm:^2.8.0":
|
||||||
version: 2.7.0
|
version: 2.8.0
|
||||||
resolution: "@reduxjs/toolkit@npm:2.7.0"
|
resolution: "@reduxjs/toolkit@npm:2.8.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@standard-schema/spec": "npm:^1.0.0"
|
"@standard-schema/spec": "npm:^1.0.0"
|
||||||
"@standard-schema/utils": "npm:^0.3.0"
|
"@standard-schema/utils": "npm:^0.3.0"
|
||||||
@ -6535,7 +6535,7 @@ __metadata:
|
|||||||
optional: true
|
optional: true
|
||||||
react-redux:
|
react-redux:
|
||||||
optional: true
|
optional: true
|
||||||
checksum: 10/cc264efc95f9ebeafa469bf1040d106a33768a802e6f46aa678bf9f26822d049c18b5f10864aa8badb2e62febe58e242860256174528e62b09e8f897d32cd182
|
checksum: 10/22a97393e6d8688edacea748efeff2e5c8165c61aa05239192cca8856dbbf175c49e8dd9fcf954e0c09014acaefcf56dcd61303b905e4e0eb47e77ad09f230d8
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user