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": {
|
||||
"typecheck": "tsc --emitDeclarationOnly false --noEmit",
|
||||
"codegen": "yarn run rtk-query-codegen-openapi ./scripts/codegen.ts"
|
||||
"codegen": "rtk-query-codegen-openapi ./scripts/codegen.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@grafana/tsconfig": "^2.0.0",
|
||||
@ -57,7 +57,7 @@
|
||||
"react-dom": "^18.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@reduxjs/toolkit": "^2.7.0",
|
||||
"@reduxjs/toolkit": "^2.8.0",
|
||||
"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.
|
||||
* It downloads the OpenAPI schema from a running Grafana instance and generates the types.
|
||||
* This script will generate TypeScript type definitions and a RTKQ clients for the alerting k8s APIs.
|
||||
*
|
||||
* 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 { resolve } from 'node:path';
|
||||
import type { ConfigFile } from '@rtk-query/codegen-openapi';
|
||||
|
||||
// these snapshots are generated by running "go test pkg/tests/apis/openapi_test.go", see the README in the "openapi_snapshots" directory
|
||||
const OPENAPI_SCHEMA_LOCATION = resolve(
|
||||
'../../../pkg/tests/apis/openapi_snapshots/notifications.alerting.grafana.app-v0alpha1.json'
|
||||
);
|
||||
// ℹ️ append versions here to generate additional API clients
|
||||
const VERSIONS = ['v0alpha1'] as const;
|
||||
|
||||
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 {
|
||||
exportName: 'alertingAPI',
|
||||
schemaFile: OPENAPI_SCHEMA_LOCATION,
|
||||
apiFile: '../src/grafana/api.ts',
|
||||
outputFile: resolve('../src/grafana/api.gen.ts'),
|
||||
tag: true,
|
||||
// these are intentionally empty but will be set for each versioned config file
|
||||
exportName: '',
|
||||
schemaFile: '',
|
||||
apiFile: '',
|
||||
|
||||
outputFiles,
|
||||
} 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 {
|
||||
ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Receiver as ContactPointV0Alpha1,
|
||||
ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Integration as IntegrationV0Alpha1,
|
||||
ListReceiverApiResponse,
|
||||
} from '../api.gen';
|
||||
import type { Receiver, Integration as ReceiverIntegration, ListReceiverApiResponse } from './api.gen';
|
||||
|
||||
type GenericIntegration = OverrideProperties<
|
||||
IntegrationV0Alpha1,
|
||||
ReceiverIntegration,
|
||||
{
|
||||
settings: Record<string, unknown>;
|
||||
}
|
||||
@ -60,7 +56,7 @@ export type Integration = EmailIntegration | SlackIntegration | GenericIntegrati
|
||||
// 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
|
||||
export type ContactPoint = MergeDeep<
|
||||
ContactPointV0Alpha1,
|
||||
Receiver,
|
||||
{
|
||||
spec: {
|
||||
integrations: Integration[];
|
||||
@ -68,7 +64,7 @@ export type ContactPoint = MergeDeep<
|
||||
}
|
||||
>;
|
||||
|
||||
export type EnhancedListReceiverResponse = OverrideProperties<
|
||||
export type EnhancedListReceiverApiResponse = OverrideProperties<
|
||||
ListReceiverApiResponse,
|
||||
{
|
||||
items: ContactPoint[];
|
@ -2,8 +2,8 @@ import { chain } from 'lodash';
|
||||
|
||||
import { Combobox, ComboboxOption } from '@grafana/ui';
|
||||
|
||||
import { useListContactPoints } from '../hooks/useContactPoints';
|
||||
import { ContactPoint } from '../types';
|
||||
import { ContactPoint } from '../../api/v0alpha1/types';
|
||||
import { useListContactPointsv0alpha1 } from '../hooks/useContactPoints';
|
||||
import { getContactPointDescription } from '../utils';
|
||||
|
||||
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
|
||||
*/
|
||||
function ContactPointSelector({ onChange }: ContactPointSelectorProps) {
|
||||
const { currentData: contactPoints, isLoading } = useListContactPoints();
|
||||
const { currentData: contactPoints, isLoading } = useListContactPointsv0alpha1();
|
||||
|
||||
// Create a mapping of options with their corresponding contact points
|
||||
const contactPointOptions = chain(contactPoints?.items)
|
||||
@ -27,7 +27,7 @@ function ContactPointSelector({ onChange }: ContactPointSelectorProps) {
|
||||
label: contactPoint.spec.title,
|
||||
value: contactPoint.metadata.uid ?? contactPoint.spec.title,
|
||||
description: getContactPointDescription(contactPoint),
|
||||
},
|
||||
} satisfies ComboboxOption<string>,
|
||||
contactPoint,
|
||||
}))
|
||||
.value()
|
||||
|
@ -1,15 +1,11 @@
|
||||
import { fetchBaseQuery, TypedUseQueryHookResult } from '@reduxjs/toolkit/query/react';
|
||||
|
||||
import { config } from '@grafana/runtime';
|
||||
|
||||
import { alertingAPI, ListReceiverApiArg } from '../../api.gen';
|
||||
import { EnhancedListReceiverResponse } from '../types';
|
||||
|
||||
const { namespace } = config;
|
||||
import { alertingAPI, type ListReceiverApiArg } from '../../api/v0alpha1/api.gen';
|
||||
import type { EnhancedListReceiverApiResponse } from '../../api/v0alpha1/types';
|
||||
|
||||
// this is a workaround for the fact that the generated types are not narrow enough
|
||||
type EnhancedHookResult = TypedUseQueryHookResult<
|
||||
EnhancedListReceiverResponse,
|
||||
EnhancedListReceiverApiResponse,
|
||||
ListReceiverApiArg,
|
||||
ReturnType<typeof fetchBaseQuery>
|
||||
>;
|
||||
@ -22,8 +18,8 @@ type EnhancedHookResult = TypedUseQueryHookResult<
|
||||
*
|
||||
* It automatically uses the configured namespace for the query.
|
||||
*/
|
||||
function useListContactPoints() {
|
||||
return alertingAPI.useListReceiverQuery<EnhancedHookResult>({ namespace });
|
||||
function useListContactPointsv0alpha1() {
|
||||
return alertingAPI.useListReceiverQuery<EnhancedHookResult>({});
|
||||
}
|
||||
|
||||
export { useListContactPoints };
|
||||
export { useListContactPointsv0alpha1 };
|
||||
|
@ -1,6 +1,6 @@
|
||||
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.
|
||||
|
@ -1,6 +1,4 @@
|
||||
/**
|
||||
* Export things here that you want to be available under @grafana/alerting/internal
|
||||
*/
|
||||
export { alertingAPI } from './grafana/api.gen';
|
||||
|
||||
export default {};
|
||||
|
@ -3,6 +3,9 @@
|
||||
*/
|
||||
|
||||
// Contact Points
|
||||
export * from './grafana/contactPoints/types';
|
||||
export { useListContactPoints } from './grafana/contactPoints/hooks/useContactPoints';
|
||||
export * from './grafana/api/v0alpha1/types';
|
||||
export { useListContactPointsv0alpha1 } from './grafana/contactPoints/hooks/useContactPoints';
|
||||
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"
|
||||
dependencies:
|
||||
"@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"
|
||||
"@types/lodash": "npm:^4"
|
||||
"@types/react": "npm:18.3.18"
|
||||
@ -6517,9 +6517,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@reduxjs/toolkit@npm:^2.7.0":
|
||||
version: 2.7.0
|
||||
resolution: "@reduxjs/toolkit@npm:2.7.0"
|
||||
"@reduxjs/toolkit@npm:^2.8.0":
|
||||
version: 2.8.0
|
||||
resolution: "@reduxjs/toolkit@npm:2.8.0"
|
||||
dependencies:
|
||||
"@standard-schema/spec": "npm:^1.0.0"
|
||||
"@standard-schema/utils": "npm:^0.3.0"
|
||||
@ -6535,7 +6535,7 @@ __metadata:
|
||||
optional: true
|
||||
react-redux:
|
||||
optional: true
|
||||
checksum: 10/cc264efc95f9ebeafa469bf1040d106a33768a802e6f46aa678bf9f26822d049c18b5f10864aa8badb2e62febe58e242860256174528e62b09e8f897d32cd182
|
||||
checksum: 10/22a97393e6d8688edacea748efeff2e5c8165c61aa05239192cca8856dbbf175c49e8dd9fcf954e0c09014acaefcf56dcd61303b905e4e0eb47e77ad09f230d8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
Reference in New Issue
Block a user