generate types, create http client and add exemplary usage (#3384)
# What this PR does https://github.com/grafana/oncall/issues/3330 - add a script that generates TS type definitions based on OnCall API OpenAPI schemas - support adding custom properties on the frontend if needed - add simple example of usage (for `'/labels/keys/'` endpoint) ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [x] Documentation added (or `pr:no public docs` PR label added if not required) - [x] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not required)
This commit is contained in:
parent
64488f2705
commit
2c4b34d3af
26 changed files with 1912 additions and 72 deletions
|
|
@ -53,9 +53,9 @@ repos:
|
|||
rev: v8.25.0
|
||||
hooks:
|
||||
- id: eslint
|
||||
entry: bash -c 'cd grafana-plugin && eslint --max-warnings=0 --fix ${@/grafana-plugin\//}' --
|
||||
entry: bash -c "cd grafana-plugin && eslint --max-warnings=0 --fix ${@/grafana-plugin\//}" --
|
||||
types: [file]
|
||||
files: ^grafana-plugin/src/.*\.(js|jsx|ts|tsx)$
|
||||
files: ^grafana-plugin/src/(?:(?!autogenerated).)*\.(js|jsx|ts|tsx)$
|
||||
additional_dependencies:
|
||||
- eslint@^8.25.0
|
||||
- eslint-plugin-import@^2.25.4
|
||||
|
|
@ -81,7 +81,7 @@ repos:
|
|||
rev: v13.13.1
|
||||
hooks:
|
||||
- id: stylelint
|
||||
entry: bash -c 'cd grafana-plugin && stylelint --fix ${@/grafana-plugin\//}' --
|
||||
entry: bash -c "cd grafana-plugin && stylelint --fix ${@/grafana-plugin\//}" --
|
||||
types: [file]
|
||||
files: ^grafana-plugin/src/.*\.css$
|
||||
additional_dependencies:
|
||||
|
|
|
|||
|
|
@ -41,3 +41,5 @@ RABBITMQ_PORT=5672
|
|||
RABBITMQ_DEFAULT_VHOST="/"
|
||||
|
||||
REDIS_URI=redis://redis:6379/0
|
||||
|
||||
DRF_SPECTACULAR_ENABLED=True
|
||||
|
|
|
|||
|
|
@ -43,15 +43,23 @@ Related: [How to develop integrations](/engine/config_integrations/README.md)
|
|||
|
||||
1. Create local k8s cluster:
|
||||
|
||||
```bash
|
||||
make cluster/up
|
||||
```
|
||||
```bash
|
||||
make cluster/up
|
||||
```
|
||||
|
||||
2. Deploy the project:
|
||||
|
||||
```bash
|
||||
tilt up
|
||||
```
|
||||
```bash
|
||||
tilt up
|
||||
```
|
||||
|
||||
You can set local environment variables using `dev/helm-local.dev.yml` file, e.g.:
|
||||
|
||||
```yaml
|
||||
env:
|
||||
- name: FEATURE_LABELS_ENABLED_FOR_ALL
|
||||
value: 'True'
|
||||
```
|
||||
|
||||
3. Wait until all resources are green and open <http://localhost:3000/a/grafana-oncall-app> (user: oncall, password: oncall)
|
||||
|
||||
|
|
@ -59,9 +67,9 @@ Related: [How to develop integrations](/engine/config_integrations/README.md)
|
|||
|
||||
5. Clean up the project by deleting the local k8s cluster:
|
||||
|
||||
```bash
|
||||
make cluster/down
|
||||
```
|
||||
```bash
|
||||
make cluster/down
|
||||
```
|
||||
|
||||
## Running the project with docker-compose
|
||||
|
||||
|
|
|
|||
|
|
@ -84,9 +84,9 @@ if settings.SILK_PROFILER_ENABLED:
|
|||
]
|
||||
|
||||
if settings.DRF_SPECTACULAR_ENABLED:
|
||||
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
|
||||
from drf_spectacular.views import SpectacularSwaggerView, SpectacularYAMLAPIView
|
||||
|
||||
urlpatterns += [
|
||||
path("internal/schema/", SpectacularAPIView.as_view(api_version="internal/v1"), name="schema"),
|
||||
path("internal/schema/", SpectacularYAMLAPIView.as_view(api_version="internal/v1"), name="schema"),
|
||||
path("internal/schema/swagger-ui/", SpectacularSwaggerView.as_view(url_name="schema"), name="swagger-ui"),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -290,7 +290,7 @@ REST_FRAMEWORK = {
|
|||
}
|
||||
|
||||
|
||||
DRF_SPECTACULAR_ENABLED = getenv_boolean("DRF_SPECTACULAR_ENABLED", default=False)
|
||||
DRF_SPECTACULAR_ENABLED = getenv_boolean("DRF_SPECTACULAR_ENABLED", default=True)
|
||||
|
||||
SPECTACULAR_SETTINGS = {
|
||||
"TITLE": "Grafana OnCall Private API",
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
/src/assets
|
||||
/src/assets
|
||||
/src/network/oncall-api/autogenerated-api.types.d.ts
|
||||
89
grafana-plugin/README.md
Normal file
89
grafana-plugin/README.md
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
# Grafana OnCall
|
||||
|
||||
Developer-Friendly
|
||||
Alert Management
|
||||
with Brilliant Slack Integration
|
||||
|
||||
- Connect monitoring systems
|
||||
- Collect and analyze data
|
||||
- On-call rotation
|
||||
- Automatic escalation
|
||||
- Never miss alerts with calls and SMS
|
||||
|
||||
## Documentation
|
||||
|
||||
- [On Github](http://github.com/grafana/oncall)
|
||||
- [Grafana OnCall](https://grafana.com/docs/oncall/latest/)
|
||||
|
||||
## Development
|
||||
|
||||
### Autogenerating TS types based on OpenAPI schema
|
||||
|
||||
| :warning: WARNING |
|
||||
| :------------------------------------------------------------------------------------------ |
|
||||
| Transition to this approach is [in progress](https://github.com/grafana/oncall/issues/3338) |
|
||||
|
||||
#### Overview
|
||||
|
||||
In order to automate types creation and prevent API usage pitfalls, OnCall project is using the following approach:
|
||||
|
||||
1. OnCall Engine (backend) exposes OpenAPI schema
|
||||
2. OnCall Grafana Plugin (frontend) autogenerates TS type definitions based on it
|
||||
3. OnCall Grafana Plugin (frontend) uses autogenerated types as a single source of truth for
|
||||
any backend-related interactions (url paths, request bodies, params, response payloads)
|
||||
|
||||
#### Instruction
|
||||
|
||||
1. Whenever API contract changes, run `yarn generate-types` from `grafana-plugin` directory
|
||||
2. Then you can start consuming types and you can use fully typed http client:
|
||||
|
||||
```ts
|
||||
import { ApiSchemas } from 'network/oncall-api/api.types';
|
||||
import onCallApi from 'network/oncall-api/http-client';
|
||||
|
||||
const {
|
||||
data: { results },
|
||||
} = await onCallApi.GET('/alertgroups/');
|
||||
const alertGroups: Array<ApiSchemas['AlertGroup']> = results;
|
||||
```
|
||||
|
||||
3. [Optional] If there is any property that is not yet exposed in OpenAPI schema and you already want to use it,
|
||||
you can append missing properties to particular schemas by editing
|
||||
`grafana-plugin/src/network/oncall-api/types-generator/custom-schemas.ts` file:
|
||||
|
||||
```ts
|
||||
export type CustomApiSchemas = {
|
||||
Alert: {
|
||||
propertyMissingInOpenAPI: string;
|
||||
};
|
||||
AlertGroup: {
|
||||
anotherPropertyMissingInOpenAPI: number[];
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
Then add their names to `CUSTOMIZED_SCHEMAS` array in `grafana-plugin/src/network/oncall-api/types-generator/generate-types.ts`:
|
||||
|
||||
```ts
|
||||
const CUSTOMIZED_SCHEMAS = ['Alert', 'AlertGroup'];
|
||||
```
|
||||
|
||||
The outcome is that autogenerated schemas will be modified as follows:
|
||||
|
||||
```ts
|
||||
import type { CustomApiSchemas } from './types-generator/custom-schemas';
|
||||
|
||||
export interface components {
|
||||
schemas: {
|
||||
Alert: CustomApiSchemas['Alert'] & {
|
||||
readonly id: string;
|
||||
...
|
||||
};
|
||||
AlertGroup: CustomApiSchemas['AlertGroup'] & {
|
||||
readonly pk: string;
|
||||
...
|
||||
},
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -1,15 +1,16 @@
|
|||
const esModules = ['@grafana', 'uplot', 'ol', 'd3', 'react-colorful', 'uuid'].join('|');
|
||||
const esModules = ['@grafana', 'uplot', 'ol', 'd3', 'react-colorful', 'uuid', 'openapi-fetch'].join('|');
|
||||
|
||||
module.exports = {
|
||||
testEnvironment: 'jsdom',
|
||||
|
||||
moduleDirectories: ['node_modules', 'src'],
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js'],
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'd.ts', 'cjs'],
|
||||
|
||||
transformIgnorePatterns: [`/node_modules/(?!${esModules})`],
|
||||
|
||||
moduleNameMapper: {
|
||||
'grafana/app/(.*)': '<rootDir>/src/jest/grafanaMock.ts',
|
||||
'openapi-fetch': '<rootDir>/src/jest/openapiFetchMock.ts',
|
||||
'jest/matchMedia': '<rootDir>/src/jest/matchMedia.ts',
|
||||
'^jest$': '<rootDir>/src/jest',
|
||||
'^.+\\.(css|scss)$': '<rootDir>/src/jest/styleMock.ts',
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@
|
|||
"test:e2e-expensive": "yarn playwright test --grep @expensive",
|
||||
"test:e2e:watch": "yarn test:e2e --ui",
|
||||
"test:e2e:gen": "yarn playwright codegen http://localhost:3000",
|
||||
"cleanup-e2e-results": "rm -rf playwright-report && rm -rf test-results",
|
||||
"e2e-show-report": "yarn playwright show-report",
|
||||
"generate-types": "cd ./src/network/oncall-api/types-generator && yarn generate",
|
||||
"dev": "grafana-toolkit plugin:dev",
|
||||
"watch": "grafana-toolkit plugin:dev --watch",
|
||||
"sign": "grafana-toolkit plugin:sign",
|
||||
|
|
@ -96,6 +96,7 @@
|
|||
"lodash-es": "^4.17.21",
|
||||
"mailslurp-client": "^15.14.1",
|
||||
"moment-timezone": "^0.5.35",
|
||||
"openapi-typescript": "^7.0.0-next.4",
|
||||
"plop": "^2.7.4",
|
||||
"postcss-loader": "^7.0.1",
|
||||
"react": "17.0.2",
|
||||
|
|
@ -105,6 +106,7 @@
|
|||
"stylelint-prettier": "^2.0.0",
|
||||
"ts-jest": "29.0.3",
|
||||
"ts-loader": "^9.3.1",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "4.6.4",
|
||||
"webpack-bundle-analyzer": "^4.6.1"
|
||||
},
|
||||
|
|
@ -127,6 +129,7 @@
|
|||
"mobx": "5.13.0",
|
||||
"mobx-react": "6.1.1",
|
||||
"object-hash": "^3.0.0",
|
||||
"openapi-fetch": "^0.8.1",
|
||||
"prettier": "^2.8.2",
|
||||
"qrcode.react": "^3.1.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
# Grafana OnCall
|
||||
|
||||
Developer-Friendly
|
||||
Alert Management
|
||||
with Brilliant Slack Integration
|
||||
|
||||
- Connect monitoring systems
|
||||
- Collect and analyze data
|
||||
- On-call rotation
|
||||
- Automatic escalation
|
||||
- Never miss alerts with calls and SMS
|
||||
|
||||
## Documentation
|
||||
|
||||
- [On Github](http://github.com/grafana/oncall)
|
||||
- [Grafana OnCall](https://grafana.com/docs/oncall/latest/)
|
||||
|
|
@ -22,7 +22,7 @@ import MonacoEditor, { MONACO_LANGUAGE } from 'components/MonacoEditor/MonacoEdi
|
|||
import Text from 'components/Text/Text';
|
||||
import IntegrationTemplate from 'containers/IntegrationTemplate/IntegrationTemplate';
|
||||
import { AlertReceiveChannel } from 'models/alert_receive_channel/alert_receive_channel.types';
|
||||
import { LabelKey } from 'models/label/label.types';
|
||||
import { ApiSchemas } from 'network/oncall-api/api.types';
|
||||
import { useStore } from 'state/useStore';
|
||||
import { openErrorNotification } from 'utils';
|
||||
|
||||
|
|
@ -68,7 +68,7 @@ const IntegrationLabelsForm = observer((props: IntegrationLabelsFormProps) => {
|
|||
onOpenIntegraionSettings(id);
|
||||
};
|
||||
|
||||
const getInheritanceChangeHandler = (keyId: LabelKey['id']) => {
|
||||
const getInheritanceChangeHandler = (keyId: ApiSchemas['LabelKey']['id']) => {
|
||||
return (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setAlertGroupLabels((alertGroupLabels) => ({
|
||||
...alertGroupLabels,
|
||||
|
|
|
|||
1
grafana-plugin/src/jest/openapiFetchMock.ts
Normal file
1
grafana-plugin/src/jest/openapiFetchMock.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export default () => ({});
|
||||
|
|
@ -3,9 +3,9 @@ import qs from 'query-string';
|
|||
|
||||
import { AlertReceiveChannel } from 'models/alert_receive_channel/alert_receive_channel.types';
|
||||
import BaseStore from 'models/base_store';
|
||||
import { LabelKey } from 'models/label/label.types';
|
||||
import { User } from 'models/user/user.types';
|
||||
import { makeRequest } from 'network';
|
||||
import { ApiSchemas } from 'network/oncall-api/api.types';
|
||||
import { Mixpanel } from 'services/mixpanel';
|
||||
import { RootStore } from 'state';
|
||||
import { SelectOption } from 'state/types';
|
||||
|
|
@ -63,9 +63,6 @@ export class AlertGroupStore extends BaseStore {
|
|||
@observable
|
||||
silencedIncidents: any = {};
|
||||
|
||||
@observable
|
||||
alertGroupStats: any = {};
|
||||
|
||||
@observable
|
||||
liveUpdatesEnabled = false;
|
||||
|
||||
|
|
@ -358,11 +355,6 @@ export class AlertGroupStore extends BaseStore {
|
|||
this.silencedIncidents = result;
|
||||
}
|
||||
|
||||
@action
|
||||
async getAlertGroupsStats() {
|
||||
this.alertGroupStats = await makeRequest('/alertgroups/stats/', {});
|
||||
}
|
||||
|
||||
@action
|
||||
async doIncidentAction(alertId: Alert['pk'], action: AlertAction, isUndo = false, data?: any) {
|
||||
this.updateAlert(alertId, { loading: true });
|
||||
|
|
@ -442,7 +434,7 @@ export class AlertGroupStore extends BaseStore {
|
|||
}
|
||||
|
||||
@action
|
||||
public async loadValuesForLabelKey(key: LabelKey['id'], search = '') {
|
||||
public async loadValuesForLabelKey(key: ApiSchemas['LabelKey']['id'], search = '') {
|
||||
if (!key) {
|
||||
return [];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
import { action, observable } from 'mobx';
|
||||
import { action, observable, runInAction } from 'mobx';
|
||||
|
||||
import BaseStore from 'models/base_store';
|
||||
import { makeRequest } from 'network';
|
||||
import { ApiSchemas } from 'network/oncall-api/api.types';
|
||||
import onCallApi from 'network/oncall-api/http-client';
|
||||
import { RootStore } from 'state';
|
||||
import { openNotification } from 'utils';
|
||||
|
||||
import { LabelKey, LabelValue } from './label.types';
|
||||
|
||||
export class LabelStore extends BaseStore {
|
||||
@observable.shallow
|
||||
public keys: LabelKey[] = [];
|
||||
public keys: Array<ApiSchemas['LabelKey']> = [];
|
||||
|
||||
@observable.shallow
|
||||
public values: { [key: string]: LabelValue[] } = {};
|
||||
public values: { [key: string]: Array<ApiSchemas['LabelValue']> } = {};
|
||||
|
||||
constructor(rootStore: RootStore) {
|
||||
super(rootStore);
|
||||
|
|
@ -22,15 +22,17 @@ export class LabelStore extends BaseStore {
|
|||
|
||||
@action
|
||||
public async loadKeys() {
|
||||
const result = await makeRequest(`${this.path}keys/`, {});
|
||||
const { data } = await onCallApi.GET('/labels/keys/', undefined);
|
||||
|
||||
this.keys = result;
|
||||
runInAction(() => {
|
||||
this.keys = data;
|
||||
});
|
||||
|
||||
return result;
|
||||
return data;
|
||||
}
|
||||
|
||||
@action
|
||||
public async loadValuesForKey(key: LabelKey['id'], search = '') {
|
||||
public async loadValuesForKey(key: ApiSchemas['LabelKey']['id'], search = '') {
|
||||
if (!key) {
|
||||
return [];
|
||||
}
|
||||
|
|
@ -62,7 +64,7 @@ export class LabelStore extends BaseStore {
|
|||
return key;
|
||||
}
|
||||
|
||||
public async createValue(keyId: LabelKey['id'], value: string) {
|
||||
public async createValue(keyId: ApiSchemas['LabelKey']['id'], value: string) {
|
||||
const result = await makeRequest(`${this.path}id/${keyId}/values`, {
|
||||
method: 'POST',
|
||||
data: { name: value },
|
||||
|
|
@ -76,7 +78,7 @@ export class LabelStore extends BaseStore {
|
|||
}
|
||||
|
||||
@action
|
||||
public async updateKey(keyId: LabelKey['id'], name: string) {
|
||||
public async updateKey(keyId: ApiSchemas['LabelKey']['id'], name: string) {
|
||||
const result = await makeRequest(`${this.path}id/${keyId}`, {
|
||||
method: 'PUT',
|
||||
data: { name },
|
||||
|
|
@ -90,7 +92,11 @@ export class LabelStore extends BaseStore {
|
|||
}
|
||||
|
||||
@action
|
||||
public async updateKeyValue(keyId: LabelKey['id'], valueId: LabelValue['id'], name: string) {
|
||||
public async updateKeyValue(
|
||||
keyId: ApiSchemas['LabelKey']['id'],
|
||||
valueId: ApiSchemas['LabelValue']['id'],
|
||||
name: string
|
||||
) {
|
||||
const result = await makeRequest(`${this.path}id/${keyId}/values/${valueId}`, {
|
||||
method: 'PUT',
|
||||
data: { name },
|
||||
|
|
|
|||
|
|
@ -1,12 +1,6 @@
|
|||
export interface Label {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface LabelKey extends Label {}
|
||||
export interface LabelValue extends Label {}
|
||||
import { ApiSchemas } from 'network/oncall-api/api.types';
|
||||
|
||||
export interface LabelKeyValue {
|
||||
key: LabelKey;
|
||||
value: LabelValue;
|
||||
key: ApiSchemas['LabelKey'];
|
||||
value: ApiSchemas['LabelValue'];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import qs from 'query-string';
|
|||
|
||||
import FaroHelper from 'utils/faro';
|
||||
|
||||
export const API_HOST = `${window.location.protocol}//${window.location.host}/`;
|
||||
export const API_PROXY_PREFIX = 'api/plugin-proxy/grafana-oncall-app';
|
||||
export const API_PATH_PREFIX = '/api/internal/v1';
|
||||
|
||||
|
|
|
|||
3
grafana-plugin/src/network/oncall-api/api.types.d.ts
vendored
Normal file
3
grafana-plugin/src/network/oncall-api/api.types.d.ts
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import { components } from './autogenerated-api.types';
|
||||
|
||||
export type ApiSchemas = components['schemas'];
|
||||
1318
grafana-plugin/src/network/oncall-api/autogenerated-api.types.d.ts
vendored
Normal file
1318
grafana-plugin/src/network/oncall-api/autogenerated-api.types.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load diff
126
grafana-plugin/src/network/oncall-api/http-client.test.ts
Normal file
126
grafana-plugin/src/network/oncall-api/http-client.test.ts
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
import { SpanStatusCode } from '@opentelemetry/api';
|
||||
|
||||
import FaroHelper from 'utils/faro';
|
||||
|
||||
import { customFetch } from './http-client';
|
||||
|
||||
jest.mock('utils/faro', () => ({
|
||||
__esModule: true,
|
||||
default: {
|
||||
faro: {
|
||||
api: {
|
||||
getOTEL: jest.fn(() => undefined),
|
||||
pushEvent: jest.fn(),
|
||||
pushError: jest.fn(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
jest.mock('openapi-fetch', () => ({
|
||||
__esModule: true,
|
||||
default: () => {},
|
||||
}));
|
||||
|
||||
const fetchMock = jest.fn().mockResolvedValue(true);
|
||||
|
||||
const REQUEST_CONFIG = {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
};
|
||||
const URL = 'https://someurl.com';
|
||||
const SUCCESSFUL_RESPONSE_MOCK = { ok: true };
|
||||
const ERROR_MOCK = 'error';
|
||||
|
||||
describe('customFetch', () => {
|
||||
beforeAll(() => {
|
||||
Object.defineProperty(global, 'fetch', {
|
||||
writable: true,
|
||||
value: fetchMock,
|
||||
});
|
||||
});
|
||||
afterEach(jest.clearAllMocks);
|
||||
|
||||
describe('if there is no otel', () => {
|
||||
describe('if response is successful', () => {
|
||||
it('should push event to faro and return response', async () => {
|
||||
fetchMock.mockResolvedValue(SUCCESSFUL_RESPONSE_MOCK);
|
||||
const response = await customFetch(URL, REQUEST_CONFIG);
|
||||
expect(FaroHelper.faro.api.pushEvent).toHaveBeenCalledWith('Request completed', { url: URL });
|
||||
expect(response).toEqual(SUCCESSFUL_RESPONSE_MOCK);
|
||||
});
|
||||
});
|
||||
|
||||
describe('if response is not successful', () => {
|
||||
it('should push event and error to faro', async () => {
|
||||
(FaroHelper.faro.api.getOTEL as unknown as jest.Mock).mockReturnValueOnce(undefined);
|
||||
fetchMock.mockRejectedValueOnce(ERROR_MOCK);
|
||||
await expect(customFetch(URL, REQUEST_CONFIG)).rejects.toEqual(Error(ERROR_MOCK));
|
||||
expect(FaroHelper.faro.api.pushEvent).toHaveBeenCalledWith('Request failed', { url: URL });
|
||||
expect(FaroHelper.faro.api.pushError).toHaveBeenCalledWith(ERROR_MOCK);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('if there is otel', () => {
|
||||
const spanEndMock = jest.fn();
|
||||
const setStatusMock = jest.fn();
|
||||
const setAttributeMock = jest.fn();
|
||||
const spanStartMock = jest.fn(() => ({
|
||||
setAttribute: setAttributeMock,
|
||||
end: spanEndMock,
|
||||
setStatus: setStatusMock,
|
||||
}));
|
||||
const otel = {
|
||||
trace: {
|
||||
getTracer: () => ({
|
||||
startSpan: spanStartMock,
|
||||
}),
|
||||
getActiveSpan: jest.fn(),
|
||||
setSpan: jest.fn(),
|
||||
},
|
||||
context: {
|
||||
active: jest.fn(),
|
||||
with: (_ctx, fn) => {
|
||||
fn();
|
||||
},
|
||||
},
|
||||
};
|
||||
(FaroHelper.faro.api.getOTEL as unknown as jest.Mock).mockReturnValue(otel);
|
||||
|
||||
it(`starts span if it doesn't exist`, async () => {
|
||||
otel.trace.getActiveSpan.mockReturnValueOnce(undefined);
|
||||
await customFetch(URL, REQUEST_CONFIG);
|
||||
expect(spanStartMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it(`adds 'X-Idempotency-Key' header`, async () => {
|
||||
await customFetch(URL, REQUEST_CONFIG);
|
||||
expect(fetchMock).toHaveBeenCalledWith(expect.any(String), {
|
||||
headers: { ...REQUEST_CONFIG.headers, 'X-Idempotency-Key': expect.any(String) },
|
||||
});
|
||||
});
|
||||
|
||||
describe('if response is successful', () => {
|
||||
it('should push event to faro, end span and return response', async () => {
|
||||
fetchMock.mockResolvedValue(SUCCESSFUL_RESPONSE_MOCK);
|
||||
const response = await customFetch(URL, REQUEST_CONFIG);
|
||||
expect(FaroHelper.faro.api.pushEvent).toHaveBeenCalledWith('Request completed', { url: URL });
|
||||
expect(spanEndMock).toHaveBeenCalledTimes(1);
|
||||
expect(response).toEqual(SUCCESSFUL_RESPONSE_MOCK);
|
||||
});
|
||||
});
|
||||
|
||||
describe('if response is not successful', () => {
|
||||
it('should reject Promise, push event to faro, set span status to error and end span', async () => {
|
||||
fetchMock.mockRejectedValueOnce(ERROR_MOCK);
|
||||
await expect(customFetch(URL, REQUEST_CONFIG)).rejects.toEqual(ERROR_MOCK);
|
||||
expect(FaroHelper.faro.api.pushEvent).toHaveBeenCalledWith('Request failed', { url: URL });
|
||||
expect(FaroHelper.faro.api.pushError).toHaveBeenCalledWith(ERROR_MOCK);
|
||||
expect(setStatusMock).toHaveBeenCalledTimes(1);
|
||||
expect(setStatusMock).toHaveBeenCalledWith({ code: SpanStatusCode.ERROR });
|
||||
expect(spanEndMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
78
grafana-plugin/src/network/oncall-api/http-client.ts
Normal file
78
grafana-plugin/src/network/oncall-api/http-client.ts
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import { SpanStatusCode } from '@opentelemetry/api';
|
||||
import { SemanticAttributes } from '@opentelemetry/semantic-conventions';
|
||||
import createClient from 'openapi-fetch';
|
||||
import qs from 'query-string';
|
||||
|
||||
import FaroHelper from 'utils/faro';
|
||||
|
||||
import { paths } from './autogenerated-api.types';
|
||||
|
||||
export const API_PROXY_PREFIX = 'api/plugin-proxy/grafana-oncall-app';
|
||||
export const API_PATH_PREFIX = '/api/internal/v1';
|
||||
|
||||
export const customFetch = async (url: string, requestConfig: Parameters<typeof fetch>[1] = {}): Promise<Response> => {
|
||||
const { faro } = FaroHelper;
|
||||
const otel = faro?.api?.getOTEL();
|
||||
|
||||
if (faro && otel) {
|
||||
const tracer = otel.trace.getTracer('default');
|
||||
let span = otel.trace.getActiveSpan();
|
||||
|
||||
if (!span) {
|
||||
span = tracer.startSpan('http-request');
|
||||
span.setAttribute('page_url', document.URL.split('//')[1]);
|
||||
span.setAttribute(SemanticAttributes.HTTP_URL, url);
|
||||
span.setAttribute(SemanticAttributes.HTTP_METHOD, requestConfig.method);
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
otel.context.with(otel.trace.setSpan(otel.context.active(), span), async () => {
|
||||
faro.api.pushEvent('Sending request', { url });
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
...requestConfig,
|
||||
headers: {
|
||||
...requestConfig.headers,
|
||||
/**
|
||||
* In short, this header will tell the Grafana plugin proxy, a Go service which use Go's HTTP Transport,
|
||||
* to retry POST requests (and other non-idempotent requests). This doesn't necessarily make these requests
|
||||
* idempotent, but it will make them retry-able from Go's (read: net/http) perspective.
|
||||
*
|
||||
* https://stackoverflow.com/questions/42847294/how-to-catch-http-server-closed-idle-connection-error/62292758#62292758
|
||||
* https://raintank-corp.slack.com/archives/C01C4K8DETW/p1692280544382739?thread_ts=1692279329.797149&cid=C01C4K8DETW
|
||||
*/ 'X-Idempotency-Key': `${Date.now()}-${Math.random()}`,
|
||||
},
|
||||
});
|
||||
faro.api.pushEvent('Request completed', { url });
|
||||
span.end();
|
||||
resolve(response);
|
||||
} catch (error) {
|
||||
faro.api.pushEvent('Request failed', { url });
|
||||
faro.api.pushError(error);
|
||||
span.setStatus({ code: SpanStatusCode.ERROR });
|
||||
span.end();
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
try {
|
||||
const response = await fetch(url, requestConfig);
|
||||
faro?.api.pushEvent('Request completed', { url });
|
||||
return response;
|
||||
} catch (error) {
|
||||
faro?.api.pushEvent('Request failed', { url });
|
||||
faro?.api.pushError(error);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onCallApi = createClient<paths>({
|
||||
baseUrl: `${API_PROXY_PREFIX}${API_PATH_PREFIX}`,
|
||||
querySerializer: (params: unknown) => qs.stringify(params, { arrayFormat: 'none' }),
|
||||
fetch: customFetch,
|
||||
});
|
||||
|
||||
export default onCallApi;
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
// Custom properties not exposed by OpenAPI schema should be defined here
|
||||
|
||||
export type CustomApiSchemas = {};
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
import openapiTS, { astToString } from 'openapi-typescript';
|
||||
|
||||
import fs from 'fs';
|
||||
|
||||
// They need to match with any custom schema added to CustomApiSchemas keys
|
||||
const CUSTOMIZED_SCHEMAS = [];
|
||||
|
||||
const addCustomSchemasToAutogeneratedOutput = (
|
||||
originalOutput: string,
|
||||
customizedSchemas: string[] = CUSTOMIZED_SCHEMAS
|
||||
) => {
|
||||
const REGEX = new RegExp(`\\s(${customizedSchemas.join('|')})\\s*:`, 'g');
|
||||
|
||||
let newOutput = `
|
||||
import type { CustomApiSchemas } from './types-generator/custom-schemas';
|
||||
|
||||
${originalOutput}
|
||||
`;
|
||||
newOutput = newOutput.replace(REGEX, (match) =>
|
||||
match.concat(` CustomApiSchemas["${match.split(':')[0].trim()}"] & `)
|
||||
);
|
||||
|
||||
return newOutput;
|
||||
};
|
||||
|
||||
(async () => {
|
||||
const ast = await openapiTS(new URL('http://localhost:8080/internal/schema/'));
|
||||
const output = astToString(ast);
|
||||
|
||||
fs.writeFileSync(
|
||||
'../autogenerated-api.types.d.ts',
|
||||
addCustomSchemasToAutogeneratedOutput(output, CUSTOMIZED_SCHEMAS)
|
||||
);
|
||||
})();
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"generate": "npx ts-node-esm -P ./types-generator.tsconfig.json ./generate-types.ts && prettier --write ../autogenerated-api.types.d.ts"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@
|
|||
"noUnusedParameters": true,
|
||||
"strict": false,
|
||||
"resolveJsonModule": true,
|
||||
"noImplicitAny": false
|
||||
"noImplicitAny": false,
|
||||
"skipLibCheck": true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1310,6 +1310,13 @@
|
|||
resolved "https://registry.yarnpkg.com/@braintree/sanitize-url/-/sanitize-url-6.0.2.tgz#6110f918d273fe2af8ea1c4398a88774bb9fc12f"
|
||||
integrity sha512-Tbsj02wXCbqGmzdnXNk0SOF19ChhRU70BsroIi4Pm6Ehp56in6vch94mfbdQ17DozxkL3BAVjbZ4Qc1a0HFRAg==
|
||||
|
||||
"@cspotcode/source-map-support@^0.8.0":
|
||||
version "0.8.1"
|
||||
resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1"
|
||||
integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==
|
||||
dependencies:
|
||||
"@jridgewell/trace-mapping" "0.3.9"
|
||||
|
||||
"@csstools/postcss-color-function@^1.0.3":
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@csstools/postcss-color-function/-/postcss-color-function-1.1.1.tgz#2bd36ab34f82d0497cfacdc9b18d34b5e6f64b6b"
|
||||
|
|
@ -2726,6 +2733,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78"
|
||||
integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==
|
||||
|
||||
"@jridgewell/resolve-uri@^3.0.3":
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721"
|
||||
integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==
|
||||
|
||||
"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72"
|
||||
|
|
@ -2744,6 +2756,14 @@
|
|||
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
|
||||
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
|
||||
|
||||
"@jridgewell/trace-mapping@0.3.9":
|
||||
version "0.3.9"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9"
|
||||
integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==
|
||||
dependencies:
|
||||
"@jridgewell/resolve-uri" "^3.0.3"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.10"
|
||||
|
||||
"@jridgewell/trace-mapping@^0.3.14", "@jridgewell/trace-mapping@^0.3.9":
|
||||
version "0.3.17"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz#793041277af9073b0951a7fe0f0d8c4c98c36985"
|
||||
|
|
@ -3833,6 +3853,32 @@
|
|||
resolved "https://registry.yarnpkg.com/@react-types/shared/-/shared-3.21.0.tgz#1af41fdf7dfbdbd33bbc1210617c43ed0d4ef20c"
|
||||
integrity sha512-wJA2cUF8dP4LkuNUt9Vh2kkfiQb2NLnV2pPXxVnKJZ7d4x2/7VPccN+LYPnH8m0X3+rt50cxWuPKQmjxSsCFOg==
|
||||
|
||||
"@redocly/ajv@^8.11.0":
|
||||
version "8.11.0"
|
||||
resolved "https://registry.yarnpkg.com/@redocly/ajv/-/ajv-8.11.0.tgz#2fad322888dc0113af026e08fceb3e71aae495ae"
|
||||
integrity sha512-9GWx27t7xWhDIR02PA18nzBdLcKQRgc46xNQvjFkrYk4UOmvKhJ/dawwiX0cCOeetN5LcaaiqQbVOWYK62SGHw==
|
||||
dependencies:
|
||||
fast-deep-equal "^3.1.1"
|
||||
json-schema-traverse "^1.0.0"
|
||||
require-from-string "^2.0.2"
|
||||
uri-js "^4.2.2"
|
||||
|
||||
"@redocly/openapi-core@^1.4.1":
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.4.1.tgz#0620a5e204159626a1d99b88f758e23ef0cb5740"
|
||||
integrity sha512-oAhnG8MKocM9LuP++NGFxdniNKWSLA7hzHPQoOK92LIP/DdvXx8pEeZ68UTNxIXhKonoUcO6s86I3L0zj143zg==
|
||||
dependencies:
|
||||
"@redocly/ajv" "^8.11.0"
|
||||
"@types/node" "^14.11.8"
|
||||
colorette "^1.2.0"
|
||||
js-levenshtein "^1.1.6"
|
||||
js-yaml "^4.1.0"
|
||||
lodash.isequal "^4.5.0"
|
||||
minimatch "^5.0.1"
|
||||
node-fetch "^2.6.1"
|
||||
pluralize "^8.0.0"
|
||||
yaml-ast-parser "0.0.43"
|
||||
|
||||
"@sentry/browser@6.19.7":
|
||||
version "6.19.7"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-6.19.7.tgz#a40b6b72d911b5f1ed70ed3b4e7d4d4e625c0b5f"
|
||||
|
|
@ -3979,6 +4025,26 @@
|
|||
resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad"
|
||||
integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==
|
||||
|
||||
"@tsconfig/node10@^1.0.7":
|
||||
version "1.0.9"
|
||||
resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2"
|
||||
integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==
|
||||
|
||||
"@tsconfig/node12@^1.0.7":
|
||||
version "1.0.11"
|
||||
resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d"
|
||||
integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==
|
||||
|
||||
"@tsconfig/node14@^1.0.0":
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1"
|
||||
integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==
|
||||
|
||||
"@tsconfig/node16@^1.0.2":
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9"
|
||||
integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==
|
||||
|
||||
"@types/aria-query@^4.2.0":
|
||||
version "4.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.2.tgz#ed4e0ad92306a704f9fb132a0cfcf77486dbe2bc"
|
||||
|
|
@ -4360,6 +4426,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.26.tgz#63d204d136c9916fb4dcd1b50f9740fe86884e47"
|
||||
integrity sha512-GZ7bu5A6+4DtG7q9GsoHXy3ALcgeIHP4NnL0Vv2wu0uUB/yQex26v0tf6/na1mm0+bS9Uw+0DFex7aaKr2qawQ==
|
||||
|
||||
"@types/node@^14.11.8":
|
||||
version "14.18.63"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.63.tgz#1788fa8da838dbb5f9ea994b834278205db6ca2b"
|
||||
integrity sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==
|
||||
|
||||
"@types/normalize-package-data@^2.4.0":
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301"
|
||||
|
|
@ -5064,6 +5135,11 @@ acorn-walk@^8.0.0:
|
|||
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1"
|
||||
integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==
|
||||
|
||||
acorn-walk@^8.1.1:
|
||||
version "8.3.0"
|
||||
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.0.tgz#2097665af50fd0cf7a2dfccd2b9368964e66540f"
|
||||
integrity sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==
|
||||
|
||||
acorn@^7.1.1:
|
||||
version "7.4.1"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
|
||||
|
|
@ -5074,6 +5150,11 @@ acorn@^8.0.4, acorn@^8.2.4, acorn@^8.5.0, acorn@^8.7.1, acorn@^8.8.0:
|
|||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.1.tgz#0a3f9cbecc4ec3bea6f0a80b66ae8dd2da250b73"
|
||||
integrity sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==
|
||||
|
||||
acorn@^8.4.1:
|
||||
version "8.11.2"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.2.tgz#ca0d78b51895be5390a5903c5b3bdcdaf78ae40b"
|
||||
integrity sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==
|
||||
|
||||
add-dom-event-listener@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/add-dom-event-listener/-/add-dom-event-listener-1.1.0.tgz#6a92db3a0dd0abc254e095c0f1dc14acbbaae310"
|
||||
|
|
@ -5145,7 +5226,7 @@ ajv@^8.0.0, ajv@^8.0.1, ajv@^8.8.0:
|
|||
require-from-string "^2.0.2"
|
||||
uri-js "^4.2.2"
|
||||
|
||||
ansi-colors@^4.1.1:
|
||||
ansi-colors@^4.1.1, ansi-colors@^4.1.3:
|
||||
version "4.1.3"
|
||||
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b"
|
||||
integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==
|
||||
|
|
@ -5602,6 +5683,13 @@ brace-expansion@^1.1.7:
|
|||
balanced-match "^1.0.0"
|
||||
concat-map "0.0.1"
|
||||
|
||||
brace-expansion@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae"
|
||||
integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==
|
||||
dependencies:
|
||||
balanced-match "^1.0.0"
|
||||
|
||||
braces@^2.3.1:
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729"
|
||||
|
|
@ -6034,6 +6122,11 @@ colord@^2.9.1:
|
|||
resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43"
|
||||
integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==
|
||||
|
||||
colorette@^1.2.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.4.0.tgz#5190fbb87276259a86ad700bff2c6d6faa3fca40"
|
||||
integrity sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==
|
||||
|
||||
colorette@^2.0.16:
|
||||
version "2.0.19"
|
||||
resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798"
|
||||
|
|
@ -10146,6 +10239,11 @@ js-cookie@^2.2.1:
|
|||
resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8"
|
||||
integrity sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==
|
||||
|
||||
js-levenshtein@^1.1.6:
|
||||
version "1.1.6"
|
||||
resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d"
|
||||
integrity sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==
|
||||
|
||||
js-sdsl@^4.1.4:
|
||||
version "4.1.5"
|
||||
resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.1.5.tgz#1ff1645e6b4d1b028cd3f862db88c9d887f26e2a"
|
||||
|
|
@ -10487,6 +10585,11 @@ lodash.get@^4.4.2:
|
|||
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
|
||||
integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==
|
||||
|
||||
lodash.isequal@^4.5.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
|
||||
integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==
|
||||
|
||||
lodash.isstring@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
|
||||
|
|
@ -10874,6 +10977,13 @@ minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
|
|||
dependencies:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
minimatch@^5.0.1:
|
||||
version "5.1.6"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96"
|
||||
integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==
|
||||
dependencies:
|
||||
brace-expansion "^2.0.1"
|
||||
|
||||
minimist-options@4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619"
|
||||
|
|
@ -11085,6 +11195,13 @@ node-fetch@2.6.7:
|
|||
dependencies:
|
||||
whatwg-url "^5.0.0"
|
||||
|
||||
node-fetch@^2.6.1:
|
||||
version "2.7.0"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d"
|
||||
integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==
|
||||
dependencies:
|
||||
whatwg-url "^5.0.0"
|
||||
|
||||
node-int64@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
|
||||
|
|
@ -11407,6 +11524,29 @@ open@^8.4.0:
|
|||
is-docker "^2.1.1"
|
||||
is-wsl "^2.2.0"
|
||||
|
||||
openapi-fetch@^0.8.1:
|
||||
version "0.8.1"
|
||||
resolved "https://registry.yarnpkg.com/openapi-fetch/-/openapi-fetch-0.8.1.tgz#a2bda1f72a8311e92cc789d1c8fec7b2d8ca28b6"
|
||||
integrity sha512-xmzMaBCydPTMd0TKy4P2DYx/JOe9yjXtPIky1n1GV7nJJdZ3IZgSHvAWVbe06WsPD8EreR7E97IAiskPr6sa2g==
|
||||
dependencies:
|
||||
openapi-typescript-helpers "^0.0.4"
|
||||
|
||||
openapi-typescript-helpers@^0.0.4:
|
||||
version "0.0.4"
|
||||
resolved "https://registry.yarnpkg.com/openapi-typescript-helpers/-/openapi-typescript-helpers-0.0.4.tgz#ffe7c4868f094fcc8502dbdcddc6c32ce8011aee"
|
||||
integrity sha512-Q0MTapapFAG993+dx8lNw33X6P/6EbFr31yNymJHq56fNc6dODyRm8tWyRnGxuC74lyl1iCRMV6nQCGQsfVNKg==
|
||||
|
||||
openapi-typescript@^7.0.0-next.4:
|
||||
version "7.0.0-next.4"
|
||||
resolved "https://registry.yarnpkg.com/openapi-typescript/-/openapi-typescript-7.0.0-next.4.tgz#62117ed4c5dd3b09e344ec10838a576bc60acad9"
|
||||
integrity sha512-mm6cpWnvYZgaTTfMnA/z+NkbmjzWNX5FeoW7+ZBAfI9tBhb8JlNf/HZfXueV1Eq6ZOWKQ1doyjTxJNgmsF6mNQ==
|
||||
dependencies:
|
||||
"@redocly/openapi-core" "^1.4.1"
|
||||
ansi-colors "^4.1.3"
|
||||
supports-color "^9.4.0"
|
||||
typescript "^5.3.2"
|
||||
yargs-parser "^21.1.1"
|
||||
|
||||
opener@^1.5.2:
|
||||
version "1.5.2"
|
||||
resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598"
|
||||
|
|
@ -11789,6 +11929,11 @@ plop@^2.7.4:
|
|||
ora "^3.4.0"
|
||||
v8flags "^2.0.10"
|
||||
|
||||
pluralize@^8.0.0:
|
||||
version "8.0.0"
|
||||
resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1"
|
||||
integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==
|
||||
|
||||
pngjs@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-6.0.0.tgz#ca9e5d2aa48db0228a52c419c3308e87720da821"
|
||||
|
|
@ -14712,6 +14857,11 @@ supports-color@^8.0.0:
|
|||
dependencies:
|
||||
has-flag "^4.0.0"
|
||||
|
||||
supports-color@^9.4.0:
|
||||
version "9.4.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-9.4.0.tgz#17bfcf686288f531db3dea3215510621ccb55954"
|
||||
integrity sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==
|
||||
|
||||
supports-hyperlinks@^2.0.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz#3943544347c1ff90b15effb03fc14ae45ec10624"
|
||||
|
|
@ -15052,6 +15202,25 @@ ts-loader@^9.3.1:
|
|||
micromatch "^4.0.0"
|
||||
semver "^7.3.4"
|
||||
|
||||
ts-node@^10.9.1:
|
||||
version "10.9.1"
|
||||
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b"
|
||||
integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==
|
||||
dependencies:
|
||||
"@cspotcode/source-map-support" "^0.8.0"
|
||||
"@tsconfig/node10" "^1.0.7"
|
||||
"@tsconfig/node12" "^1.0.7"
|
||||
"@tsconfig/node14" "^1.0.0"
|
||||
"@tsconfig/node16" "^1.0.2"
|
||||
acorn "^8.4.1"
|
||||
acorn-walk "^8.1.1"
|
||||
arg "^4.1.0"
|
||||
create-require "^1.1.0"
|
||||
diff "^4.0.1"
|
||||
make-error "^1.1.1"
|
||||
v8-compile-cache-lib "^3.0.1"
|
||||
yn "3.1.1"
|
||||
|
||||
ts-node@^9.1.0:
|
||||
version "9.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-9.1.1.tgz#51a9a450a3e959401bda5f004a72d54b936d376d"
|
||||
|
|
@ -15177,6 +15346,11 @@ typescript@4.8.4:
|
|||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6"
|
||||
integrity sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==
|
||||
|
||||
typescript@^5.3.2:
|
||||
version "5.3.2"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.2.tgz#00d1c7c1c46928c5845c1ee8d0cc2791031d4c43"
|
||||
integrity sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==
|
||||
|
||||
ua-parser-js@^1.0.32:
|
||||
version "1.0.33"
|
||||
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.33.tgz#f21f01233e90e7ed0f059ceab46eb190ff17f8f4"
|
||||
|
|
@ -15396,6 +15570,11 @@ uuid@^8.3.2:
|
|||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
|
||||
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
|
||||
|
||||
v8-compile-cache-lib@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"
|
||||
integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==
|
||||
|
||||
v8-compile-cache@^2.0.3, v8-compile-cache@^2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"
|
||||
|
|
@ -15746,6 +15925,11 @@ yallist@^4.0.0:
|
|||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
|
||||
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
|
||||
|
||||
yaml-ast-parser@0.0.43:
|
||||
version "0.0.43"
|
||||
resolved "https://registry.yarnpkg.com/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz#e8a23e6fb4c38076ab92995c5dca33f3d3d7c9bb"
|
||||
integrity sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==
|
||||
|
||||
yaml@^1.10.0, yaml@^1.10.2, yaml@^1.7.2:
|
||||
version "1.10.2"
|
||||
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
|
||||
|
|
@ -15756,7 +15940,7 @@ yargs-parser@20.x, yargs-parser@^20.2.2, yargs-parser@^20.2.3:
|
|||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
|
||||
integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
|
||||
|
||||
yargs-parser@^21.0.1:
|
||||
yargs-parser@^21.0.1, yargs-parser@^21.1.1:
|
||||
version "21.1.1"
|
||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35"
|
||||
integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue