API clients: Add generator (#104093)
* Add API client generator * Extract config entry template * Fix index file * Fix message and file pattern * Fix generate-rtk template * Match generated-api * Format * Split helpers * Cleanup * Remove unused helpers * Simplify group name handling * Run generate-apis * Prettier * Format + lint * improve lint/format * Optional filterEndpoints * Format * Update readme * More updates * Move the helpers out * Switch to TS * Cleanup types * Add support for Enterprise * Add comments * Refactor endpoint handling and update README * Simplify checks * Do not register reducers and middleware for enterprise * More docs updates * Remove redundant sections * Format gitignored files * Add limitations * Simplify types * Simplify path logic * Do not format OSS paths for enterprise * dedupe * format * Simplify instructions * Update lockfile * Add comments * Remove custom types
This commit is contained in:
+3
-1
@@ -64,7 +64,8 @@
|
||||
"plugin:build:commit": "nx run-many -t build:commit --projects='tag:scope:plugin'",
|
||||
"plugin:build:dev": "nx run-many -t dev --projects='tag:scope:plugin' --maxParallel=100",
|
||||
"process-specs": "node --experimental-strip-types scripts/process-specs.ts",
|
||||
"generate-apis": "yarn process-specs && rtk-query-codegen-openapi ./scripts/generate-rtk-apis.ts"
|
||||
"generate-apis": "yarn process-specs && rtk-query-codegen-openapi ./scripts/generate-rtk-apis.ts",
|
||||
"generate:api-client": "NODE_OPTIONS='--experimental-strip-types' plop --plopfile public/app/api/generator/plopfile.ts"
|
||||
},
|
||||
"grafana": {
|
||||
"whatsNewUrl": "https://grafana.com/docs/grafana/next/whatsnew/whats-new-in-v%[1]s-%[2]s/",
|
||||
@@ -216,6 +217,7 @@
|
||||
"nx": "20.7.1",
|
||||
"openapi-types": "^12.1.3",
|
||||
"pdf-parse": "^1.1.1",
|
||||
"plop": "^4.0.1",
|
||||
"postcss": "8.5.1",
|
||||
"postcss-loader": "8.1.1",
|
||||
"postcss-reporter": "7.1.0",
|
||||
|
||||
+3
-133
@@ -7,7 +7,7 @@ To show the steps to follow, we are going to work on adding an API client to cre
|
||||
First, check if the `group` and the `version` are already present in [openapi_test.go](/pkg/tests/apis/openapi_test.go). If so, move on to the next step.
|
||||
<br/> If you need to add a new block, you can check for the right `group` and `version` in the backend API call that you want to replicate in the frontend.
|
||||
|
||||
```jsx
|
||||
```go
|
||||
{
|
||||
Group: "dashboard.grafana.app",
|
||||
Version: "v0alpha1",
|
||||
@@ -20,136 +20,6 @@ Afterwards, you need to run the `TestIntegrationOpenAPIs` test. Note that it wil
|
||||
|
||||
> Note: You don’t need to follow these two steps if the `group` you’re working with is already in the `openapi_test.go` file.
|
||||
|
||||
<br/>
|
||||
### 2. Run the API generator script
|
||||
|
||||
### 2. Create the API definition
|
||||
|
||||
In the [`/public/app/api/clients`](/public/app/api/clients) folder, create a new folder and `baseAPI.ts` file for your group. This file should have the following content:
|
||||
|
||||
```jsx
|
||||
import { createApi } from '@reduxjs/toolkit/query/react';
|
||||
|
||||
import { createBaseQuery } from 'app/api/createBaseQuery';
|
||||
import { getAPIBaseURL } from 'app/api/utils';
|
||||
|
||||
export const BASE_URL = getAPIBaseURL('dashboard.grafana.app', 'v0alpha1');
|
||||
|
||||
export const api = createApi({
|
||||
reducerPath: 'dashboardAPI',
|
||||
baseQuery: createBaseQuery({
|
||||
baseURL: BASE_URL,
|
||||
}),
|
||||
endpoints: () => ({}),
|
||||
});
|
||||
```
|
||||
|
||||
This is the API definition for the specific group you're working with, where `getAPIBaseURL` should have the proper `group` and `version` as parameters. The `reducerPath` needs to be unique. The convention is to use `<group>API`: `dashboard` will be `dashboardAPI`, `iam` will be `iamAPI` and so on.
|
||||
|
||||
### 3. Add your new client to the generation script
|
||||
|
||||
Open [generate-rtk-apis.ts](/scripts/generate-rtk-apis.ts) and add the following information:
|
||||
|
||||
| Data | Descritpion |
|
||||
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| outputFile name | File that will be created after running the API Client Generation script. It is the key of the object. |
|
||||
| apiFile | File with the group's API definition. |
|
||||
| schemaFile | File with the schema that was automatically created in the second step. Although it is in openapi_snapshots, you should link the one saved in `data/openapi`. |
|
||||
| filterEndpoints | The `operationId` of the particular route you want to work with. You can check the available operationIds in the specific group's spec file. As seen in the `migrate-to-cloud` one, it is an array |
|
||||
| tag | Must be set to `true`, to automatically attach tags to endpoints. This is needed for proper cache invalidation. See more info in the [official documentation](https://redux-toolkit.js.org/rtk-query/usage/automated-refetching#:~:text=RTK%20Query%20uses,an%20active%20subscription.). |
|
||||
|
||||
<br/>
|
||||
|
||||
> More info in [Redux Toolkit](https://redux-toolkit.js.org/rtk-query/usage/code-generation#simple-usage)
|
||||
|
||||
In our example, the information added will be:
|
||||
|
||||
```jsx
|
||||
'../public/app/api/clients/dashboard/endpoints.gen.ts': {
|
||||
apiFile: '../public/app/api/clients/dashboard/baseAPI.ts',
|
||||
schemaFile: '../data/openapi/dashboard.grafana.app-v0alpha1.json',
|
||||
filterEndpoints: ['createDashboard', 'updateDashboard'],
|
||||
tag: true,
|
||||
},
|
||||
```
|
||||
|
||||
### 4. Run the API client generation script
|
||||
|
||||
Then, we are ready to run the script to create the API client:
|
||||
|
||||
```jsx
|
||||
yarn generate-apis
|
||||
```
|
||||
|
||||
This will create an `endpoints.gen.ts` file in the path specified in the previous step.
|
||||
|
||||
### 5. Create the index file for your hooks
|
||||
|
||||
In the same `api` folder where the `endpoints.gen.ts` file has been saved, you have to create an index file from which you can import the types and hooks needed. By doing this, we selectively export hooks/types from `endpoints.gen.ts`.
|
||||
|
||||
In our case, the dashboard index will be like:
|
||||
|
||||
```jsx
|
||||
import { generatedAPI } from './endpoints.gen';
|
||||
|
||||
export const dashboardAPI = generatedAPI;
|
||||
export const { useCreateDashboardMutation, useUpdateDashboardMutation} = dashboardAPI;
|
||||
// eslint-disable-next-line no-barrel-files/no-barrel-files
|
||||
export { type Dashboard } from './endpoints.gen';
|
||||
|
||||
```
|
||||
|
||||
There are some use cases where the hook will not work out of the box, and that is a clue to see if it needs to be modified. The hooks can be tweaked by using `enhanceEndpoints`.
|
||||
|
||||
```jsx
|
||||
export const dashboardsAPI = generatedApi.enhanceEndpoints({
|
||||
endpoints: {
|
||||
// Need to mutate the generated query to set the Content-Type header correctly
|
||||
updateDashboard: (endpointDefinition) => {
|
||||
const originalQuery = endpointDefinition.query;
|
||||
if (originalQuery) {
|
||||
endpointDefinition.query = (requestOptions) => ({
|
||||
...originalQuery(requestOptions),
|
||||
headers: {
|
||||
'Content-Type': 'application/merge-patch+json',
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### 6. Add reducers and middleware to the Redux store
|
||||
|
||||
Last but not least, you need to add the middleware and reducers to the store.
|
||||
|
||||
In Grafana, the reducers are added to [`root.ts`](/public/app/core/reducers/root.ts):
|
||||
|
||||
```jsx
|
||||
import { dashboardAPI } from '<pathToYourAPI>';
|
||||
const rootReducers = {
|
||||
...,
|
||||
[dashboardAPI.reducerPath]: dashboardAPI.reducer,
|
||||
};
|
||||
```
|
||||
|
||||
And the middleware is added to [`configureStore.ts`](/public/app/store/configureStore.ts):
|
||||
|
||||
```jsx
|
||||
import { dashboardAPI } from '<pathToYourAPI>';
|
||||
export function configureStore(initialState?: Partial<StoreState>) {
|
||||
const store = reduxConfigureStore({
|
||||
reducer: createRootReducer(),
|
||||
middleware: (getDefaultMiddleware) =>
|
||||
getDefaultMiddleware({ thunk: true, serializableCheck: false, immutableCheck: false }).concat(
|
||||
...,
|
||||
dashboardAPI.middleware
|
||||
),
|
||||
...,
|
||||
});
|
||||
```
|
||||
|
||||
You have available the official documentation in [RTK Query](https://redux-toolkit.js.org/tutorials/rtk-query#add-the-service-to-your-store)
|
||||
|
||||
After this step is done, it is time to use your hooks across Grafana.
|
||||
Enjoy coding!
|
||||
Run `yarn generate:api-client` and follow the prompts. See [API Client Generator](./generator/README.md) for details.
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
# RTK Query API Client Generator
|
||||
|
||||
This generator automates the process of creating RTK Query API clients for Grafana's API groups. It replaces the manual steps outlined in the [main API documentation](../README.md).
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
yarn generate:api-client
|
||||
```
|
||||
|
||||
The CLI will prompt for:
|
||||
|
||||
1. **Enterprise or OSS API** - Whether this is an Enterprise or OSS API. This affects paths and build commands.
|
||||
2. **API group name** - The basic name for the API (e.g., `dashboard`)
|
||||
3. **API group** - The full API group name (defaults to `<group-name>.grafana.app`)
|
||||
4. **API version** - The API version (e.g., `v0alpha1`)
|
||||
5. **Reducer path** - The Redux reducer path (defaults to `<group-name>API`). This will also be used as the API's named export.
|
||||
6. **Endpoints** - Optional comma-separated list of endpoints to include (e.g., `createDashboard,updateDashboard`). If not provided, all endpoints will be included.
|
||||
|
||||
## What It Does
|
||||
|
||||
The generator automates the following:
|
||||
|
||||
1. Creates the `baseAPI.ts` file for the API group
|
||||
2. Updates the appropriate generate script to include the API client
|
||||
- `scripts/generate-rtk-apis.ts` for OSS APIs
|
||||
- `local/generate-enterprise-apis.ts` for Enterprise APIs
|
||||
3. Creates the `index.ts` file with proper exports
|
||||
4. For OSS APIs only: Registers Redux reducers and middleware in the store. For Enterprise this needs to be done manually
|
||||
5. Formats all generated files using Prettier and ESLint
|
||||
6. Automatically runs the appropriate command to generate endpoints from the OpenAPI schema
|
||||
|
||||
## Limitations
|
||||
|
||||
- The generator is optimized for Kubernetes-style APIs, as it requires Kubernetes resource details. For legacy APIs, manual adjustments may be needed.
|
||||
- It expects processed OpenAPI specifications to exist in the `openapi_snapshots` directory
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Missing OpenAPI Schema
|
||||
|
||||
If an error about a missing OpenAPI schema appears, check that:
|
||||
|
||||
1. The API group and version exist in the backend
|
||||
2. The `TestIntegrationOpenAPIs` test has been run to generate the schema (step 1 in the [main API documentation](../README.md)).
|
||||
3. The schema file exists at `data/openapi/<group>-<version>.json`
|
||||
|
||||
### Validation Errors
|
||||
|
||||
- API group must include `.grafana.app`
|
||||
- Version must be in format `v0alpha1`, `v1beta2`, etc.
|
||||
- Reducer path must end with `API`
|
||||
@@ -0,0 +1,115 @@
|
||||
import { execSync } from 'child_process';
|
||||
import path from 'path';
|
||||
|
||||
type PlopActionFunction = (
|
||||
answers: Record<string, unknown>,
|
||||
config?: Record<string, unknown>
|
||||
) => string | Promise<string>;
|
||||
|
||||
// Helper to remove quotes from operation IDs
|
||||
export const removeQuotes = (str: string | unknown) => {
|
||||
if (typeof str !== 'string') {
|
||||
return str;
|
||||
}
|
||||
return str.replace(/^['"](.*)['"]$/, '$1');
|
||||
};
|
||||
|
||||
export const formatEndpoints = () => (endpointsInput: string | string[]) => {
|
||||
if (Array.isArray(endpointsInput)) {
|
||||
return endpointsInput.map((op) => `'${removeQuotes(op)}'`).join(', ');
|
||||
}
|
||||
|
||||
// Handle string input (comma-separated)
|
||||
if (typeof endpointsInput === 'string') {
|
||||
const endpointsArray = endpointsInput
|
||||
.split(',')
|
||||
.map((id) => id.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
return endpointsArray.map((op) => `'${removeQuotes(op)}'`).join(', ');
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
// List of created or modified files
|
||||
export const getFilesToFormat = (groupName: string, isEnterprise = false) => {
|
||||
const apiClientBasePath = isEnterprise ? 'public/app/extensions/api/clients' : 'public/app/api/clients';
|
||||
const generateScriptPath = isEnterprise ? 'local/generate-enterprise-apis.ts' : 'scripts/generate-rtk-apis.ts';
|
||||
|
||||
return [
|
||||
`${apiClientBasePath}/${groupName}/baseAPI.ts`,
|
||||
`${apiClientBasePath}/${groupName}/index.ts`,
|
||||
generateScriptPath,
|
||||
...(isEnterprise ? [] : [`public/app/core/reducers/root.ts`, `public/app/store/configureStore.ts`]),
|
||||
];
|
||||
};
|
||||
|
||||
export const runGenerateApis =
|
||||
(basePath: string): PlopActionFunction =>
|
||||
(answers, config) => {
|
||||
try {
|
||||
const isEnterprise = answers.isEnterprise || (config && config.isEnterprise);
|
||||
|
||||
let command;
|
||||
if (isEnterprise) {
|
||||
command = 'yarn process-specs && npx rtk-query-codegen-openapi ./local/generate-enterprise-apis.ts';
|
||||
} else {
|
||||
command = 'yarn generate-apis';
|
||||
}
|
||||
|
||||
console.log(`⏳ Running ${command} to generate endpoints...`);
|
||||
execSync(command, { stdio: 'inherit', cwd: basePath });
|
||||
return '✅ API endpoints generated successfully!';
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
console.error('❌ Failed to generate API endpoints:', errorMessage);
|
||||
return '❌ Failed to generate API endpoints. See error above.';
|
||||
}
|
||||
};
|
||||
|
||||
export const formatFiles =
|
||||
(basePath: string): PlopActionFunction =>
|
||||
(_, config) => {
|
||||
if (!config || !Array.isArray(config.files)) {
|
||||
console.error('Invalid config passed to formatFiles action');
|
||||
return '❌ Formatting failed: Invalid configuration';
|
||||
}
|
||||
|
||||
const filesToFormat = config.files.map((file: string) => path.join(basePath, file));
|
||||
|
||||
try {
|
||||
const filesList = filesToFormat.map((file: string) => `"${file}"`).join(' ');
|
||||
|
||||
console.log('🧹 Running ESLint on generated/modified files...');
|
||||
try {
|
||||
execSync(`yarn eslint --fix ${filesList}`, { cwd: basePath });
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
console.warn(`⚠️ Warning: ESLint encountered issues: ${errorMessage}`);
|
||||
}
|
||||
|
||||
console.log('🧹 Running Prettier on generated/modified files...');
|
||||
try {
|
||||
// '--ignore-path' is necessary so the gitignored files ('local/' folder) can still be formatted
|
||||
execSync(`yarn prettier --write ${filesList} --ignore-path=./.prettierignore`, { cwd: basePath });
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
console.warn(`⚠️ Warning: Prettier encountered issues: ${errorMessage}`);
|
||||
}
|
||||
|
||||
return '✅ Files linted and formatted successfully!';
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
console.error('⚠️ Warning: Formatting operations failed:', errorMessage);
|
||||
return '⚠️ Warning: Formatting operations failed.';
|
||||
}
|
||||
};
|
||||
|
||||
export const validateGroup = (group: string) => {
|
||||
return group && group.includes('.grafana.app') ? true : 'Group should be in format: name.grafana.app';
|
||||
};
|
||||
|
||||
export const validateVersion = (version: string) => {
|
||||
return version && /^v\d+[a-z]*\d+$/.test(version) ? true : 'Version should be in format: v0alpha1, v1beta2, etc.';
|
||||
};
|
||||
@@ -0,0 +1,165 @@
|
||||
import path from 'path';
|
||||
import type { NodePlopAPI, PlopGeneratorConfig } from 'plop';
|
||||
|
||||
import {
|
||||
formatEndpoints,
|
||||
validateGroup,
|
||||
validateVersion,
|
||||
getFilesToFormat,
|
||||
runGenerateApis,
|
||||
formatFiles,
|
||||
// The file extension is necessary to make the imports
|
||||
// work with the '--experimental-strip-types' flag
|
||||
// @ts-ignore
|
||||
} from './helpers.ts';
|
||||
// @ts-ignore
|
||||
import { type ActionConfig, type PlopData, isPlopData } from './types.ts';
|
||||
|
||||
export default function plopGenerator(plop: NodePlopAPI) {
|
||||
// Grafana root path
|
||||
const basePath = path.resolve(import.meta.dirname, '../../../..');
|
||||
|
||||
// Register custom action types
|
||||
plop.setActionType('runGenerateApis', runGenerateApis(basePath));
|
||||
plop.setActionType('formatFiles', formatFiles(basePath));
|
||||
|
||||
// Used in templates to format endpoints
|
||||
plop.setHelper('formatEndpoints', formatEndpoints());
|
||||
|
||||
const generateRtkApiActions = (data: PlopData) => {
|
||||
const { reducerPath, groupName, isEnterprise } = data;
|
||||
|
||||
const apiClientBasePath = isEnterprise ? 'public/app/extensions/api/clients' : 'public/app/api/clients';
|
||||
const generateScriptPath = isEnterprise ? 'local/generate-enterprise-apis.ts' : 'scripts/generate-rtk-apis.ts';
|
||||
|
||||
// Using app path, so the imports work on any file level
|
||||
const clientImportPath = isEnterprise ? '../extensions/api/clients' : 'app/api/clients';
|
||||
|
||||
const apiPathPrefix = isEnterprise ? '../public/app/extensions/api/clients' : '../public/app/api/clients';
|
||||
|
||||
const templateData = {
|
||||
...data,
|
||||
apiPathPrefix,
|
||||
};
|
||||
|
||||
// Base actions that are always added
|
||||
const actions: ActionConfig[] = [
|
||||
{
|
||||
type: 'add',
|
||||
path: path.join(basePath, `${apiClientBasePath}/${groupName}/baseAPI.ts`),
|
||||
templateFile: './templates/baseAPI.ts.hbs',
|
||||
},
|
||||
{
|
||||
type: 'modify',
|
||||
path: path.join(basePath, generateScriptPath),
|
||||
pattern: '// PLOP_INJECT_API_CLIENT - Used by the API client generator',
|
||||
templateFile: './templates/config-entry.hbs',
|
||||
data: templateData,
|
||||
},
|
||||
{
|
||||
type: 'add',
|
||||
path: path.join(basePath, `${apiClientBasePath}/${groupName}/index.ts`),
|
||||
templateFile: './templates/index.ts.hbs',
|
||||
},
|
||||
];
|
||||
|
||||
// Only add redux reducer and middleware for OSS clients
|
||||
if (!isEnterprise) {
|
||||
actions.push(
|
||||
{
|
||||
type: 'modify',
|
||||
path: path.join(basePath, 'public/app/core/reducers/root.ts'),
|
||||
pattern: '// PLOP_INJECT_IMPORT',
|
||||
template: `import { ${reducerPath} } from '${clientImportPath}/${groupName}';\n// PLOP_INJECT_IMPORT`,
|
||||
},
|
||||
{
|
||||
type: 'modify',
|
||||
path: path.join(basePath, 'public/app/core/reducers/root.ts'),
|
||||
pattern: '// PLOP_INJECT_REDUCER',
|
||||
template: `[${reducerPath}.reducerPath]: ${reducerPath}.reducer,\n // PLOP_INJECT_REDUCER`,
|
||||
},
|
||||
{
|
||||
type: 'modify',
|
||||
path: path.join(basePath, 'public/app/store/configureStore.ts'),
|
||||
pattern: '// PLOP_INJECT_IMPORT',
|
||||
template: `import { ${reducerPath} } from '${clientImportPath}/${groupName}';\n// PLOP_INJECT_IMPORT`,
|
||||
},
|
||||
{
|
||||
type: 'modify',
|
||||
path: path.join(basePath, 'public/app/store/configureStore.ts'),
|
||||
pattern: '// PLOP_INJECT_MIDDLEWARE',
|
||||
template: `${reducerPath}.middleware,\n // PLOP_INJECT_MIDDLEWARE`,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Add formatting and generation actions
|
||||
actions.push(
|
||||
{
|
||||
type: 'formatFiles',
|
||||
files: getFilesToFormat(groupName, isEnterprise),
|
||||
},
|
||||
{
|
||||
type: 'runGenerateApis',
|
||||
isEnterprise,
|
||||
}
|
||||
);
|
||||
|
||||
return actions;
|
||||
};
|
||||
|
||||
const generator: PlopGeneratorConfig = {
|
||||
description: 'Generate RTK Query API client for a Grafana API group',
|
||||
prompts: [
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'isEnterprise',
|
||||
message: 'Is this a Grafana Enterprise API?',
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'groupName',
|
||||
message: 'API group name (e.g. dashboard):',
|
||||
validate: (input: string) => (input?.trim() ? true : 'Group name is required'),
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'group',
|
||||
message: 'API group (e.g. dashboard.grafana.app):',
|
||||
default: (answers: { groupName?: string }) => `${answers.groupName}.grafana.app`,
|
||||
validate: validateGroup,
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'version',
|
||||
message: 'API version (e.g. v0alpha1):',
|
||||
default: 'v0alpha1',
|
||||
validate: validateVersion,
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'reducerPath',
|
||||
message: 'Reducer path (e.g. dashboardAPI):',
|
||||
default: (answers: { groupName?: string }) => `${answers.groupName}API`,
|
||||
validate: (input: string) =>
|
||||
input?.endsWith('API') ? true : 'Reducer path should end with "API" (e.g. dashboardAPI)',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'endpoints',
|
||||
message: 'Endpoints to include (comma-separated, optional):',
|
||||
validate: () => true,
|
||||
},
|
||||
],
|
||||
actions: function (data) {
|
||||
if (!isPlopData(data)) {
|
||||
throw new Error('Invalid data format received from prompts');
|
||||
}
|
||||
|
||||
return generateRtkApiActions(data);
|
||||
},
|
||||
};
|
||||
|
||||
plop.setGenerator('rtk-api-client', generator);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { createApi } from '@reduxjs/toolkit/query/react';
|
||||
|
||||
import { createBaseQuery } from 'app/api/createBaseQuery';
|
||||
import { getAPIBaseURL } from 'app/api/utils';
|
||||
|
||||
export const BASE_URL = getAPIBaseURL('{{group}}', '{{version}}');
|
||||
|
||||
export const api = createApi({
|
||||
reducerPath: '{{reducerPath}}',
|
||||
baseQuery: createBaseQuery({
|
||||
baseURL: BASE_URL,
|
||||
}),
|
||||
endpoints: () => ({}),
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
'{{apiPathPrefix}}/{{groupName}}/endpoints.gen.ts': {
|
||||
apiFile: '{{apiPathPrefix}}/{{groupName}}/baseAPI.ts',
|
||||
schemaFile: '../data/openapi/{{group}}-{{version}}.json',
|
||||
{{#if endpoints}}
|
||||
filterEndpoints: [{{{formatEndpoints endpoints}}}],
|
||||
{{/if}}
|
||||
tag: true,
|
||||
},
|
||||
// PLOP_INJECT_API_CLIENT - Used by the API client generator
|
||||
@@ -0,0 +1,3 @@
|
||||
import { generatedAPI } from './endpoints.gen';
|
||||
|
||||
export const {{reducerPath}} = generatedAPI.enhanceEndpoints({});
|
||||
@@ -0,0 +1,27 @@
|
||||
import type { AddActionConfig, ModifyActionConfig } from 'plop';
|
||||
|
||||
export interface FormatFilesActionConfig {
|
||||
type: 'formatFiles';
|
||||
files: string[];
|
||||
}
|
||||
|
||||
export interface RunGenerateApisActionConfig {
|
||||
type: 'runGenerateApis';
|
||||
isEnterprise: boolean;
|
||||
}
|
||||
|
||||
// Union type of all possible action configs
|
||||
export type ActionConfig = AddActionConfig | ModifyActionConfig | FormatFilesActionConfig | RunGenerateApisActionConfig;
|
||||
|
||||
export interface PlopData {
|
||||
groupName: string;
|
||||
group: string;
|
||||
version: string;
|
||||
reducerPath: string;
|
||||
endpoints: string;
|
||||
isEnterprise: boolean;
|
||||
}
|
||||
|
||||
export function isPlopData(data: unknown): data is PlopData {
|
||||
return typeof data === 'object' && data !== null;
|
||||
}
|
||||
@@ -35,6 +35,8 @@ import { provisioningAPI } from '../../api/clients/provisioning';
|
||||
import { alertingApi } from '../../features/alerting/unified/api/alertingApi';
|
||||
import { userPreferencesAPI } from '../../features/preferences/api';
|
||||
import { cleanUpAction } from '../actions/cleanUp';
|
||||
// Used by the API client generator
|
||||
// PLOP_INJECT_IMPORT
|
||||
|
||||
const rootReducers = {
|
||||
...sharedReducers,
|
||||
@@ -69,6 +71,8 @@ const rootReducers = {
|
||||
[provisioningAPI.reducerPath]: provisioningAPI.reducer,
|
||||
[folderAPI.reducerPath]: folderAPI.reducer,
|
||||
[advisorAPI.reducerPath]: advisorAPI.reducer,
|
||||
// PLOP_INJECT_REDUCER
|
||||
// Used by the API client generator
|
||||
};
|
||||
|
||||
const addedReducers = {};
|
||||
|
||||
@@ -13,6 +13,8 @@ import { folderAPI } from '../api/clients/folder';
|
||||
import { iamAPI } from '../api/clients/iam';
|
||||
import { playlistAPI } from '../api/clients/playlist';
|
||||
import { provisioningAPI } from '../api/clients/provisioning';
|
||||
// Used by the API client generator
|
||||
// PLOP_INJECT_IMPORT
|
||||
import { buildInitialState } from '../core/reducers/navModel';
|
||||
import { addReducer, createRootReducer } from '../core/reducers/root';
|
||||
import { alertingApi } from '../features/alerting/unified/api/alertingApi';
|
||||
@@ -49,6 +51,8 @@ export function configureStore(initialState?: Partial<StoreState>) {
|
||||
provisioningAPI.middleware,
|
||||
folderAPI.middleware,
|
||||
advisorAPI.middleware,
|
||||
// PLOP_INJECT_MIDDLEWARE
|
||||
// Used by the API client generator
|
||||
...extraMiddleware
|
||||
),
|
||||
devTools: process.env.NODE_ENV !== 'production',
|
||||
|
||||
@@ -72,6 +72,7 @@ const config: ConfigFile = {
|
||||
filterEndpoints: ['listPlaylist', 'getPlaylist', 'createPlaylist', 'deletePlaylist', 'replacePlaylist'],
|
||||
tag: true,
|
||||
},
|
||||
// PLOP_INJECT_API_CLIENT - Used by the API client generator
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user