mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[Console] Set up a folder for manual definitions files (#162652)
## Summary Fixes https://github.com/elastic/kibana/issues/162564 This PR adds a new folder to Console server side where manually created definitions for endpoint will be stored. This is important before we switch to the new script. The logic in the new script is to clear the folder with generated definitions before re-generating them. That is not the case in the current script. The downside of that is when an endpoint is removed from the specifications, it won't be removed from autocomplete definitions automatically. Displaying autocomplete suggestions for unavailable endpoints might be confusing for users. Currently, the `manual` folder is empty because there were no definitions present in the folder `generated` that would not be re-generated if the script would clear the folder and create all endpoints defined in ES json specs. I first suspected that endpoints from these 2 PRs (https://github.com/elastic/kibana/pull/162503, https://github.com/elastic/kibana/pull/158674) would need to be moved to the manual folder, but the definitions added there manually can be re-generated using the script. I also removed several deprecated/deleted/renamed endpoints from the `generated` folder. Several files in the `overrides` folder needed renaming and one was deleted as deprecated. There are also smaller renaming changes in this PR because I think the code is more difficult to understand when it's using "spec/specification" and "definition" interchangeably or even together as "SpecDefinitions". I believe we should use "specification" for the ES specifications (i.e. the source) and "definitions" or even better "autocomplete definitions" for the files that are used for Console autocomplete engine. The renaming is to be continued in follow up PRs. I also added a unit test file for the SpecDefinitionsService since it contains a lot of important logic for endpoints such as loading generated definitions, overrides and manual ones and filtering out endpoints not available in the current context. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Alison Goryachev <alisonmllr20@gmail.com>
This commit is contained in:
parent
c07aac5ff4
commit
fbfd3ed0dd
48 changed files with 481 additions and 484 deletions
|
@ -54,7 +54,14 @@ Autocomplete definitions are all created in the form of javascript objects loade
|
|||
### Creating definitions
|
||||
The [`generated`](https://github.com/elastic/kibana/blob/main/src/plugins/console/server/lib/spec_definitions/json/generated) folder contains definitions created automatically from Elasticsearch REST API specifications. See this [README](https://github.com/elastic/kibana/blob/main/packages/kbn-spec-to-console/README.md) file for more information on the `spec-to-console` script.
|
||||
|
||||
Manually created override files in the [`overrides`](https://github.com/elastic/kibana/blob/main/src/plugins/console/server/lib/spec_definitions/json/overrides) folder contain fixes for generated files and additions for request body parameters.
|
||||
Manually created override files in the [`overrides`](https://github.com/elastic/kibana/blob/main/src/plugins/console/server/lib/spec_definitions/json/overrides) folder contain additions for request body parameters since those
|
||||
are not created by the script. Any other fixes such as documentation links, request methods and patterns and url parameters
|
||||
should be addressed at the source. That means this should be fixed in Elasticsearch REST API specifications and then
|
||||
autocomplete definitions can be re-generated with the script.
|
||||
|
||||
If there are any endpoints missing completely from the `generated` folder, this should also be addressed at the source, i.e.
|
||||
Elasticsearch REST API specifications. If for some reason, that is not possible, then additional definitions files
|
||||
can be placed in the folder [`manual`]((https://github.com/elastic/kibana/blob/main/src/plugins/console/server/lib/spec_definitions/json/manual)).
|
||||
|
||||
### Top level keys
|
||||
Use following top level keys in the definitions objects.
|
||||
|
|
|
@ -12,3 +12,7 @@ export const AUTOCOMPLETE_DEFINITIONS_FOLDER = resolve(
|
|||
__dirname,
|
||||
'../../server/lib/spec_definitions/json'
|
||||
);
|
||||
|
||||
export const GENERATED_SUBFOLDER = 'generated';
|
||||
export const OVERRIDES_SUBFOLDER = 'overrides';
|
||||
export const MANUAL_SUBFOLDER = 'manual';
|
||||
|
|
|
@ -9,4 +9,9 @@
|
|||
export { MAJOR_VERSION } from './plugin';
|
||||
export { API_BASE_PATH, KIBANA_API_PREFIX } from './api';
|
||||
export { DEFAULT_VARIABLES } from './variables';
|
||||
export { AUTOCOMPLETE_DEFINITIONS_FOLDER } from './autocomplete_definitions';
|
||||
export {
|
||||
AUTOCOMPLETE_DEFINITIONS_FOLDER,
|
||||
GENERATED_SUBFOLDER,
|
||||
OVERRIDES_SUBFOLDER,
|
||||
MANUAL_SUBFOLDER,
|
||||
} from './autocomplete_definitions';
|
||||
|
|
23
src/plugins/console/common/types/autocomplete_definitions.ts
Normal file
23
src/plugins/console/common/types/autocomplete_definitions.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export type EndpointsAvailability = 'stack' | 'serverless';
|
||||
|
||||
export interface EndpointDescription {
|
||||
methods?: string[];
|
||||
patterns?: string | string[];
|
||||
url_params?: Record<string, unknown>;
|
||||
data_autocomplete_rules?: Record<string, unknown>;
|
||||
url_components?: Record<string, unknown>;
|
||||
priority?: number;
|
||||
availability?: Record<EndpointsAvailability, boolean>;
|
||||
}
|
||||
|
||||
export interface EndpointDefinition {
|
||||
[endpointName: string]: EndpointDescription;
|
||||
}
|
|
@ -8,3 +8,4 @@
|
|||
|
||||
export * from './models';
|
||||
export * from './plugin_config';
|
||||
export * from './autocomplete_definitions';
|
||||
|
|
|
@ -26,8 +26,9 @@ const schemaLatest = schema.object(
|
|||
}),
|
||||
autocompleteDefinitions: schema.object({
|
||||
// Only displays the endpoints that are available in the specified environment
|
||||
// Current supported values are 'stack' and 'serverless'
|
||||
endpointsAvailability: schema.string({ defaultValue: 'stack' }),
|
||||
endpointsAvailability: schema.oneOf([schema.literal('stack'), schema.literal('serverless')], {
|
||||
defaultValue: 'stack',
|
||||
}),
|
||||
}),
|
||||
},
|
||||
{ defaultValue: undefined }
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Please refer to this [README](https://github.com/elastic/kibana/blob/main/src/plugins/console/README.md#creating-definitions) file before adding/editing definitions files in this folder.
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"autoscaling.get_autoscaling_decision": {
|
||||
"methods": [
|
||||
"GET"
|
||||
],
|
||||
"patterns": [
|
||||
"_autoscaling/decision"
|
||||
],
|
||||
"documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/autoscaling-get-autoscaling-decision.html"
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"data_frame_transform_deprecated.delete_transform": {
|
||||
"url_params": {
|
||||
"force": "__flag__"
|
||||
},
|
||||
"methods": [],
|
||||
"patterns": [],
|
||||
"documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/delete-transform.html"
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
{
|
||||
"data_frame_transform_deprecated.get_transform": {
|
||||
"url_params": {
|
||||
"from": 0,
|
||||
"size": 0,
|
||||
"allow_no_match": "__flag__",
|
||||
"exclude_generated": "__flag__"
|
||||
},
|
||||
"methods": [],
|
||||
"patterns": [],
|
||||
"documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/get-transform.html"
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"data_frame_transform_deprecated.get_transform_stats": {
|
||||
"url_params": {
|
||||
"from": "",
|
||||
"size": "",
|
||||
"allow_no_match": "__flag__"
|
||||
},
|
||||
"methods": [],
|
||||
"patterns": [],
|
||||
"documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/get-transform-stats.html"
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"data_frame_transform_deprecated.preview_transform": {
|
||||
"methods": [],
|
||||
"patterns": [],
|
||||
"documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/preview-transform.html"
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"data_frame_transform_deprecated.put_transform": {
|
||||
"url_params": {
|
||||
"defer_validation": "__flag__"
|
||||
},
|
||||
"methods": [],
|
||||
"patterns": [],
|
||||
"documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/put-transform.html"
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"data_frame_transform_deprecated.start_transform": {
|
||||
"url_params": {
|
||||
"timeout": ""
|
||||
},
|
||||
"methods": [],
|
||||
"patterns": [],
|
||||
"documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/start-transform.html"
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"data_frame_transform_deprecated.stop_transform": {
|
||||
"url_params": {
|
||||
"wait_for_completion": "__flag__",
|
||||
"timeout": "",
|
||||
"allow_no_match": "__flag__"
|
||||
},
|
||||
"methods": [],
|
||||
"patterns": [],
|
||||
"documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/stop-transform.html"
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"data_frame_transform_deprecated.update_transform": {
|
||||
"url_params": {
|
||||
"defer_validation": "__flag__"
|
||||
},
|
||||
"methods": [],
|
||||
"patterns": [],
|
||||
"documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/update-transform.html"
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"ilm.set_policy": {
|
||||
"methods": [
|
||||
"PUT"
|
||||
],
|
||||
"patterns": [
|
||||
"{indices}/_ilm/{new_policy}"
|
||||
],
|
||||
"documentation": "http://www.elastic.co/guide/en/index_lifecycle/current/index_lifecycle.html"
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
{
|
||||
"indices.exists_type": {
|
||||
"url_params": {
|
||||
"ignore_unavailable": "__flag__",
|
||||
"allow_no_indices": "__flag__",
|
||||
"expand_wildcards": [
|
||||
"open",
|
||||
"closed",
|
||||
"hidden",
|
||||
"none",
|
||||
"all"
|
||||
],
|
||||
"local": "__flag__"
|
||||
},
|
||||
"methods": [
|
||||
"HEAD"
|
||||
],
|
||||
"patterns": [
|
||||
"{indices}/_mapping/{type}"
|
||||
],
|
||||
"documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-types-exists.html"
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
{
|
||||
"indices.flush_synced": {
|
||||
"url_params": {
|
||||
"ignore_unavailable": "__flag__",
|
||||
"allow_no_indices": "__flag__",
|
||||
"expand_wildcards": [
|
||||
"open",
|
||||
"closed",
|
||||
"none",
|
||||
"all"
|
||||
]
|
||||
},
|
||||
"methods": [
|
||||
"POST",
|
||||
"GET"
|
||||
],
|
||||
"patterns": [
|
||||
"_flush/synced",
|
||||
"{indices}/_flush/synced"
|
||||
],
|
||||
"documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-synced-flush-api.html"
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
{
|
||||
"indices.freeze": {
|
||||
"url_params": {
|
||||
"timeout": "",
|
||||
"master_timeout": "",
|
||||
"ignore_unavailable": "__flag__",
|
||||
"allow_no_indices": "__flag__",
|
||||
"expand_wildcards": [
|
||||
"open",
|
||||
"closed",
|
||||
"hidden",
|
||||
"none",
|
||||
"all"
|
||||
],
|
||||
"wait_for_active_shards": ""
|
||||
},
|
||||
"methods": [],
|
||||
"patterns": [],
|
||||
"documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/freeze-index-api.html"
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
{
|
||||
"indices.get_upgrade": {
|
||||
"url_params": {
|
||||
"ignore_unavailable": "__flag__",
|
||||
"allow_no_indices": "__flag__",
|
||||
"expand_wildcards": [
|
||||
"open",
|
||||
"closed",
|
||||
"hidden",
|
||||
"none",
|
||||
"all"
|
||||
]
|
||||
},
|
||||
"methods": [
|
||||
"GET"
|
||||
],
|
||||
"patterns": [
|
||||
"_upgrade",
|
||||
"{indices}/_upgrade"
|
||||
],
|
||||
"documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-upgrade.html"
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
{
|
||||
"indices.upgrade": {
|
||||
"url_params": {
|
||||
"allow_no_indices": "__flag__",
|
||||
"expand_wildcards": [
|
||||
"open",
|
||||
"closed",
|
||||
"hidden",
|
||||
"none",
|
||||
"all"
|
||||
],
|
||||
"ignore_unavailable": "__flag__",
|
||||
"wait_for_completion": "__flag__",
|
||||
"only_ancient_segments": "__flag__"
|
||||
},
|
||||
"methods": [
|
||||
"POST"
|
||||
],
|
||||
"patterns": [
|
||||
"_upgrade",
|
||||
"{indices}/_upgrade"
|
||||
],
|
||||
"documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-upgrade.html"
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
{
|
||||
"xpack.migration.get_assistance": {
|
||||
"url_params": {
|
||||
"allow_no_indices": "__flag__",
|
||||
"expand_wildcards": [
|
||||
"open",
|
||||
"closed",
|
||||
"none",
|
||||
"all"
|
||||
],
|
||||
"ignore_unavailable": "__flag__"
|
||||
},
|
||||
"methods": [
|
||||
"GET"
|
||||
],
|
||||
"patterns": [
|
||||
"_migration/assistance",
|
||||
"_migration/assistance/{indices}"
|
||||
],
|
||||
"documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/migration-api-assistance.html"
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
{
|
||||
"xpack.migration.upgrade": {
|
||||
"url_params": {
|
||||
"wait_for_completion": "__flag__"
|
||||
},
|
||||
"methods": [
|
||||
"POST"
|
||||
],
|
||||
"patterns": [
|
||||
"_migration/upgrade/{indices}"
|
||||
],
|
||||
"documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/migration-api-upgrade.html"
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
{
|
||||
"ml.find_file_structure": {
|
||||
"url_params": {
|
||||
"lines_to_sample": 0,
|
||||
"line_merge_size_limit": 0,
|
||||
"timeout": "",
|
||||
"charset": "",
|
||||
"format": [
|
||||
"ndjson",
|
||||
"xml",
|
||||
"delimited",
|
||||
"semi_structured_text"
|
||||
],
|
||||
"has_header_row": "__flag__",
|
||||
"column_names": [],
|
||||
"delimiter": "",
|
||||
"quote": "",
|
||||
"should_trim_fields": "__flag__",
|
||||
"grok_pattern": "",
|
||||
"timestamp_field": "",
|
||||
"timestamp_format": "",
|
||||
"explain": "__flag__"
|
||||
},
|
||||
"methods": [
|
||||
"POST"
|
||||
],
|
||||
"patterns": [
|
||||
"_text_structure/find_structure"
|
||||
],
|
||||
"documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/find-structure.html"
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
{
|
||||
"ml.upgrade": {
|
||||
"url_params": {
|
||||
"wait_for_completion": "__flag__"
|
||||
},
|
||||
"methods": [
|
||||
"POST"
|
||||
],
|
||||
"patterns": [
|
||||
"_ml/_upgrade"
|
||||
],
|
||||
"documentation": "TODO"
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"rollup.rollup": {
|
||||
"methods": [
|
||||
"POST"
|
||||
],
|
||||
"patterns": [
|
||||
"{indices}/_rollup/{rollup_index}"
|
||||
],
|
||||
"documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/xpack-rollup.html"
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
{
|
||||
"transform.cat_transform": {
|
||||
"url_params": {
|
||||
"from": 0,
|
||||
"size": 0,
|
||||
"allow_no_match": "__flag__",
|
||||
"format": "",
|
||||
"h": [],
|
||||
"help": "__flag__",
|
||||
"s": [],
|
||||
"time": [
|
||||
"d",
|
||||
"h",
|
||||
"m",
|
||||
"s",
|
||||
"ms",
|
||||
"micros",
|
||||
"nanos"
|
||||
],
|
||||
"v": "__flag__"
|
||||
},
|
||||
"methods": [
|
||||
"GET"
|
||||
],
|
||||
"patterns": [
|
||||
"_cat/transforms",
|
||||
"_cat/transforms/{transform_id}"
|
||||
],
|
||||
"documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/cat-transforms.html"
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"xpack.ssl.certificates": {
|
||||
"methods": [
|
||||
"GET"
|
||||
],
|
||||
"patterns": [
|
||||
"_xpack/ssl/certificates"
|
||||
],
|
||||
"documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-ssl.html"
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
{
|
||||
"indices.analyze": {
|
||||
"data_autocomplete_rules": {
|
||||
"text": [],
|
||||
"field": "{field}",
|
||||
"analyzer": "",
|
||||
"tokenizer": "",
|
||||
"char_filter": [],
|
||||
"filter": [],
|
||||
"explain": { "__one_of": [false, true] },
|
||||
"attributes": []
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"xpack.security.authenticate": {
|
||||
"data_autocomplete_rules": {
|
||||
"password": ""
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"xpack.security.change_password": {
|
||||
"data_autocomplete_rules": {
|
||||
"password": ""
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"xpack.security.get_token": {
|
||||
"data_autocomplete_rules": {
|
||||
"grant_type": "",
|
||||
"password": "",
|
||||
"scope": "",
|
||||
"username": ""
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"xpack.security.invalidate_token": {
|
||||
"data_autocomplete_rules": {
|
||||
"token": ""
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
{
|
||||
"xpack.security.put_role": {
|
||||
"data_autocomplete_rules": {
|
||||
"cluster": [],
|
||||
"indices": [
|
||||
{
|
||||
"field_security": {},
|
||||
"names": [],
|
||||
"privileges": [],
|
||||
"query": ""
|
||||
}
|
||||
],
|
||||
"run_as": [],
|
||||
"metadata": {}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"xpack.security.put_role_mapping": {
|
||||
"data_autocomplete_rules": {
|
||||
"enabled": true,
|
||||
"metadata": {},
|
||||
"roles": [],
|
||||
"rules": {}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"xpack.security.put_user": {
|
||||
"data_autocomplete_rules": {
|
||||
"metadata": {},
|
||||
"password": "",
|
||||
"full_name": "",
|
||||
"roles": []
|
||||
},
|
||||
"documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-put-user.html"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,366 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import globby from 'globby';
|
||||
import fs from 'fs';
|
||||
import { SpecDefinitionsService } from '.';
|
||||
import { EndpointDefinition, EndpointsAvailability } from '../../common/types';
|
||||
|
||||
const mockReadFilySync = jest.spyOn(fs, 'readFileSync');
|
||||
const mockGlobbySync = jest.spyOn(globby, 'sync');
|
||||
const mockJsLoadersGetter = jest.fn();
|
||||
|
||||
jest.mock('../lib', () => {
|
||||
return {
|
||||
...jest.requireActual('../lib'),
|
||||
get jsSpecLoaders() {
|
||||
return mockJsLoadersGetter();
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const getMockEndpoint = ({
|
||||
endpointName,
|
||||
methods,
|
||||
patterns,
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
data_autocomplete_rules,
|
||||
availability,
|
||||
}: {
|
||||
endpointName: string;
|
||||
methods?: string[];
|
||||
patterns?: string[];
|
||||
data_autocomplete_rules?: Record<string, unknown>;
|
||||
availability?: Record<EndpointsAvailability, boolean>;
|
||||
}): EndpointDefinition => ({
|
||||
[endpointName]: {
|
||||
methods: methods ?? ['GET'],
|
||||
patterns: patterns ?? ['/endpoint'],
|
||||
data_autocomplete_rules: data_autocomplete_rules ?? undefined,
|
||||
availability: availability ?? undefined,
|
||||
},
|
||||
});
|
||||
|
||||
describe('SpecDefinitionsService', () => {
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers().setSystemTime(new Date(1577836800000));
|
||||
});
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
beforeEach(() => {
|
||||
// mock the function that lists files in the definitions folders
|
||||
mockGlobbySync.mockImplementation(() => []);
|
||||
// mock the function that reads files
|
||||
mockReadFilySync.mockImplementation(() => '');
|
||||
// mock the function that returns the list of js definitions loaders
|
||||
mockJsLoadersGetter.mockImplementation(() => []);
|
||||
});
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('initializes with empty definitions when folders and global rules are empty', () => {
|
||||
const specDefinitionsService = new SpecDefinitionsService();
|
||||
specDefinitionsService.start({
|
||||
endpointsAvailability: 'stack',
|
||||
});
|
||||
const definitions = specDefinitionsService.asJson();
|
||||
expect(definitions).toEqual({
|
||||
endpoints: {},
|
||||
globals: {},
|
||||
name: 'es',
|
||||
});
|
||||
});
|
||||
|
||||
it('loads globals rules', () => {
|
||||
const loadMockAliasRule = (service: SpecDefinitionsService) => {
|
||||
service.addGlobalAutocompleteRules('alias', {
|
||||
param1: 1,
|
||||
param2: 'test',
|
||||
});
|
||||
};
|
||||
const loadMockIndicesRule = (service: SpecDefinitionsService) => {
|
||||
service.addGlobalAutocompleteRules('indices', {
|
||||
test1: 'param1',
|
||||
test2: 'param2',
|
||||
});
|
||||
};
|
||||
mockJsLoadersGetter.mockImplementation(() => [loadMockAliasRule, loadMockIndicesRule]);
|
||||
const specDefinitionsService = new SpecDefinitionsService();
|
||||
specDefinitionsService.start({
|
||||
endpointsAvailability: 'stack',
|
||||
});
|
||||
const globals = specDefinitionsService.asJson().globals;
|
||||
expect(globals).toEqual({
|
||||
alias: {
|
||||
param1: 1,
|
||||
param2: 'test',
|
||||
},
|
||||
indices: {
|
||||
test1: 'param1',
|
||||
test2: 'param2',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('loads generated endpoints definition', () => {
|
||||
mockGlobbySync.mockImplementation((pattern) => {
|
||||
if (pattern.includes('generated')) {
|
||||
return ['/generated/endpoint1.json', '/generated/endpoint2.json'];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
mockReadFilySync.mockImplementation((path) => {
|
||||
if (path.toString() === '/generated/endpoint1.json') {
|
||||
return JSON.stringify(getMockEndpoint({ endpointName: 'endpoint1' }));
|
||||
}
|
||||
if (path.toString() === '/generated/endpoint2.json') {
|
||||
return JSON.stringify(
|
||||
getMockEndpoint({
|
||||
endpointName: 'endpoint2',
|
||||
methods: ['POST'],
|
||||
patterns: ['/endpoint2'],
|
||||
})
|
||||
);
|
||||
}
|
||||
return '';
|
||||
});
|
||||
const specDefinitionsService = new SpecDefinitionsService();
|
||||
specDefinitionsService.start({
|
||||
endpointsAvailability: 'stack',
|
||||
});
|
||||
const endpoints = specDefinitionsService.asJson().endpoints;
|
||||
expect(endpoints).toEqual({
|
||||
endpoint1: {
|
||||
id: 'endpoint1',
|
||||
methods: ['GET'],
|
||||
patterns: ['/endpoint'],
|
||||
},
|
||||
endpoint2: {
|
||||
id: 'endpoint2',
|
||||
methods: ['POST'],
|
||||
patterns: ['/endpoint2'],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('overrides an endpoint if override file is present', () => {
|
||||
mockGlobbySync.mockImplementation((pattern) => {
|
||||
if (pattern.includes('generated')) {
|
||||
return ['/generated/endpoint1.json', '/generated/endpoint2.json'];
|
||||
}
|
||||
if (pattern.includes('overrides')) {
|
||||
return ['/overrides/endpoint1.json'];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
mockReadFilySync.mockImplementation((path) => {
|
||||
if (path.toString() === '/generated/endpoint1.json') {
|
||||
return JSON.stringify(getMockEndpoint({ endpointName: 'endpoint1' }));
|
||||
}
|
||||
if (path.toString() === '/generated/endpoint2.json') {
|
||||
return JSON.stringify(
|
||||
getMockEndpoint({
|
||||
endpointName: 'endpoint2',
|
||||
methods: ['POST'],
|
||||
patterns: ['/endpoint2'],
|
||||
})
|
||||
);
|
||||
}
|
||||
if (path.toString() === '/overrides/endpoint1.json') {
|
||||
return JSON.stringify(
|
||||
getMockEndpoint({
|
||||
endpointName: 'endpoint1',
|
||||
data_autocomplete_rules: {
|
||||
param1: 'test',
|
||||
param2: 2,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
return '';
|
||||
});
|
||||
const specDefinitionsService = new SpecDefinitionsService();
|
||||
specDefinitionsService.start({
|
||||
endpointsAvailability: 'stack',
|
||||
});
|
||||
const endpoints = specDefinitionsService.asJson().endpoints;
|
||||
expect(endpoints).toEqual({
|
||||
endpoint1: {
|
||||
data_autocomplete_rules: {
|
||||
param1: 'test',
|
||||
param2: 2,
|
||||
},
|
||||
id: 'endpoint1',
|
||||
methods: ['GET'],
|
||||
patterns: ['/endpoint'],
|
||||
},
|
||||
endpoint2: {
|
||||
id: 'endpoint2',
|
||||
methods: ['POST'],
|
||||
patterns: ['/endpoint2'],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('loads manual definitions if any', () => {
|
||||
mockGlobbySync.mockImplementation((pattern) => {
|
||||
if (pattern.includes('manual')) {
|
||||
return ['manual_endpoint.json'];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
mockReadFilySync.mockImplementation((path) => {
|
||||
if (path.toString() === 'manual_endpoint.json') {
|
||||
return JSON.stringify(getMockEndpoint({ endpointName: 'manual_endpoint' }));
|
||||
}
|
||||
return '';
|
||||
});
|
||||
const specDefinitionsService = new SpecDefinitionsService();
|
||||
specDefinitionsService.start({
|
||||
endpointsAvailability: 'stack',
|
||||
});
|
||||
const endpoints = specDefinitionsService.asJson().endpoints;
|
||||
expect(endpoints).toEqual({
|
||||
manual_endpoint: {
|
||||
id: 'manual_endpoint',
|
||||
methods: ['GET'],
|
||||
patterns: ['/endpoint'],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("manual definitions don't override generated files even when the same endpoint name is used", () => {
|
||||
mockGlobbySync.mockImplementation((pattern) => {
|
||||
if (pattern.includes('generated')) {
|
||||
return ['generated_endpoint.json'];
|
||||
}
|
||||
if (pattern.includes('manual')) {
|
||||
return ['manual_endpoint.json'];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
mockReadFilySync.mockImplementation((path) => {
|
||||
if (path.toString() === 'generated_endpoint.json') {
|
||||
return JSON.stringify(getMockEndpoint({ endpointName: 'test', methods: ['GET'] }));
|
||||
}
|
||||
if (path.toString() === 'manual_endpoint.json') {
|
||||
return JSON.stringify(
|
||||
getMockEndpoint({ endpointName: 'test', methods: ['POST'], patterns: ['/manual_test'] })
|
||||
);
|
||||
}
|
||||
return '';
|
||||
});
|
||||
const specDefinitionsService = new SpecDefinitionsService();
|
||||
specDefinitionsService.start({
|
||||
endpointsAvailability: 'stack',
|
||||
});
|
||||
const endpoints = specDefinitionsService.asJson().endpoints;
|
||||
expect(endpoints).toEqual({
|
||||
test: {
|
||||
id: 'test',
|
||||
methods: ['GET'],
|
||||
patterns: ['/endpoint'],
|
||||
},
|
||||
test1577836800000: {
|
||||
id: 'test1577836800000',
|
||||
methods: ['POST'],
|
||||
patterns: ['/manual_test'],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('filters out endpoints not available in stack', () => {
|
||||
mockGlobbySync.mockImplementation((pattern) => {
|
||||
if (pattern.includes('generated')) {
|
||||
return ['/generated/endpoint1.json', '/generated/endpoint2.json'];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
mockReadFilySync.mockImplementation((path) => {
|
||||
if (path.toString() === '/generated/endpoint1.json') {
|
||||
return JSON.stringify(
|
||||
getMockEndpoint({
|
||||
endpointName: 'endpoint1',
|
||||
availability: { stack: false, serverless: true },
|
||||
})
|
||||
);
|
||||
}
|
||||
if (path.toString() === '/generated/endpoint2.json') {
|
||||
return JSON.stringify(
|
||||
getMockEndpoint({
|
||||
endpointName: 'endpoint2',
|
||||
methods: ['POST'],
|
||||
patterns: ['/endpoint2'],
|
||||
})
|
||||
);
|
||||
}
|
||||
return '';
|
||||
});
|
||||
const specDefinitionsService = new SpecDefinitionsService();
|
||||
specDefinitionsService.start({
|
||||
endpointsAvailability: 'stack',
|
||||
});
|
||||
const endpoints = specDefinitionsService.asJson().endpoints;
|
||||
expect(endpoints).toEqual({
|
||||
endpoint2: {
|
||||
id: 'endpoint2',
|
||||
methods: ['POST'],
|
||||
patterns: ['/endpoint2'],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('filters out endpoints not available in serverless', () => {
|
||||
mockGlobbySync.mockImplementation((pattern) => {
|
||||
if (pattern.includes('generated')) {
|
||||
return ['/generated/endpoint1.json', '/generated/endpoint2.json'];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
mockReadFilySync.mockImplementation((path) => {
|
||||
if (path.toString() === '/generated/endpoint1.json') {
|
||||
return JSON.stringify(
|
||||
getMockEndpoint({
|
||||
endpointName: 'endpoint1',
|
||||
availability: { stack: true, serverless: false },
|
||||
})
|
||||
);
|
||||
}
|
||||
if (path.toString() === '/generated/endpoint2.json') {
|
||||
return JSON.stringify(
|
||||
getMockEndpoint({
|
||||
endpointName: 'endpoint2',
|
||||
methods: ['POST'],
|
||||
patterns: ['/endpoint2'],
|
||||
})
|
||||
);
|
||||
}
|
||||
return '';
|
||||
});
|
||||
const specDefinitionsService = new SpecDefinitionsService();
|
||||
specDefinitionsService.start({
|
||||
endpointsAvailability: 'serverless',
|
||||
});
|
||||
const endpoints = specDefinitionsService.asJson().endpoints;
|
||||
expect(endpoints).toEqual({
|
||||
endpoint2: {
|
||||
id: 'endpoint2',
|
||||
methods: ['POST'],
|
||||
patterns: ['/endpoint2'],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
|
@ -12,37 +12,37 @@ import { basename, join } from 'path';
|
|||
import normalizePath from 'normalize-path';
|
||||
import { readFileSync } from 'fs';
|
||||
|
||||
import { AUTOCOMPLETE_DEFINITIONS_FOLDER } from '../../common/constants';
|
||||
import {
|
||||
AUTOCOMPLETE_DEFINITIONS_FOLDER,
|
||||
GENERATED_SUBFOLDER,
|
||||
MANUAL_SUBFOLDER,
|
||||
OVERRIDES_SUBFOLDER,
|
||||
} from '../../common/constants';
|
||||
import { jsSpecLoaders } from '../lib';
|
||||
|
||||
interface EndpointDescription {
|
||||
methods?: string[];
|
||||
patterns?: string | string[];
|
||||
url_params?: Record<string, unknown>;
|
||||
data_autocomplete_rules?: Record<string, unknown>;
|
||||
url_components?: Record<string, unknown>;
|
||||
priority?: number;
|
||||
availability?: Record<string, boolean>;
|
||||
}
|
||||
import type {
|
||||
EndpointsAvailability,
|
||||
EndpointDescription,
|
||||
EndpointDefinition,
|
||||
} from '../../common/types';
|
||||
|
||||
export interface SpecDefinitionsDependencies {
|
||||
endpointsAvailability: string;
|
||||
endpointsAvailability: EndpointsAvailability;
|
||||
}
|
||||
|
||||
export class SpecDefinitionsService {
|
||||
private readonly name = 'es';
|
||||
|
||||
private readonly globalRules: Record<string, any> = {};
|
||||
private readonly endpoints: Record<string, any> = {};
|
||||
private readonly endpoints: Record<string, EndpointDescription> = {};
|
||||
|
||||
private hasLoadedSpec = false;
|
||||
private hasLoadedDefinitions = false;
|
||||
|
||||
public addGlobalAutocompleteRules(parentNode: string, rules: unknown) {
|
||||
this.globalRules[parentNode] = rules;
|
||||
}
|
||||
|
||||
public addEndpointDescription(endpoint: string, description: EndpointDescription = {}) {
|
||||
let copiedDescription: { patterns?: string; url_params?: Record<string, unknown> } = {};
|
||||
let copiedDescription: EndpointDescription = {};
|
||||
if (this.endpoints[endpoint]) {
|
||||
copiedDescription = { ...this.endpoints[endpoint] };
|
||||
}
|
||||
|
@ -87,42 +87,70 @@ export class SpecDefinitionsService {
|
|||
}
|
||||
|
||||
public start({ endpointsAvailability }: SpecDefinitionsDependencies) {
|
||||
if (!this.hasLoadedSpec) {
|
||||
this.loadJsonSpec(endpointsAvailability);
|
||||
this.loadJSSpec();
|
||||
this.hasLoadedSpec = true;
|
||||
if (!this.hasLoadedDefinitions) {
|
||||
this.loadJsonDefinitions(endpointsAvailability);
|
||||
this.loadJSDefinitions();
|
||||
this.hasLoadedDefinitions = true;
|
||||
} else {
|
||||
throw new Error('Service has already started!');
|
||||
}
|
||||
}
|
||||
|
||||
private loadJSONSpecInDir(dirname: string) {
|
||||
private loadJSONDefinitionsFiles() {
|
||||
// we need to normalize paths otherwise they don't work on windows, see https://github.com/elastic/kibana/issues/151032
|
||||
const generatedFiles = globby.sync(normalizePath(join(dirname, 'generated', '*.json')));
|
||||
const overrideFiles = globby.sync(normalizePath(join(dirname, 'overrides', '*.json')));
|
||||
const generatedFiles = globby.sync(
|
||||
normalizePath(join(AUTOCOMPLETE_DEFINITIONS_FOLDER, GENERATED_SUBFOLDER, '*.json'))
|
||||
);
|
||||
const overrideFiles = globby.sync(
|
||||
normalizePath(join(AUTOCOMPLETE_DEFINITIONS_FOLDER, OVERRIDES_SUBFOLDER, '*.json'))
|
||||
);
|
||||
const manualFiles = globby.sync(
|
||||
normalizePath(join(AUTOCOMPLETE_DEFINITIONS_FOLDER, MANUAL_SUBFOLDER, '*.json'))
|
||||
);
|
||||
|
||||
return generatedFiles.reduce((acc, file) => {
|
||||
// definitions files contain only 1 definition per endpoint name { "endpointName": { endpointDescription }}
|
||||
// all endpoints need to be merged into 1 object with endpoint names as keys and endpoint definitions as values
|
||||
const jsonDefinitions = {} as Record<string, EndpointDescription>;
|
||||
generatedFiles.forEach((file) => {
|
||||
const overrideFile = overrideFiles.find((f) => basename(f) === basename(file));
|
||||
const loadedSpec: Record<string, EndpointDescription> = JSON.parse(
|
||||
readFileSync(file, 'utf8')
|
||||
);
|
||||
const loadedDefinition: EndpointDefinition = JSON.parse(readFileSync(file, 'utf8'));
|
||||
if (overrideFile) {
|
||||
merge(loadedSpec, JSON.parse(readFileSync(overrideFile, 'utf8')));
|
||||
merge(loadedDefinition, JSON.parse(readFileSync(overrideFile, 'utf8')));
|
||||
}
|
||||
Object.entries(loadedSpec).forEach(([key, value]) => {
|
||||
if (acc[key]) {
|
||||
// add time to remove key collision
|
||||
acc[`${key}${Date.now()}`] = value;
|
||||
} else {
|
||||
acc[key] = value;
|
||||
}
|
||||
});
|
||||
return acc;
|
||||
}, {} as Record<string, EndpointDescription>);
|
||||
this.addToJsonDefinitions({ loadedDefinition, jsonDefinitions });
|
||||
});
|
||||
|
||||
// add manual definitions
|
||||
manualFiles.forEach((file) => {
|
||||
const loadedDefinition: EndpointDefinition = JSON.parse(readFileSync(file, 'utf8'));
|
||||
this.addToJsonDefinitions({ loadedDefinition, jsonDefinitions });
|
||||
});
|
||||
return jsonDefinitions;
|
||||
}
|
||||
|
||||
private loadJsonSpec(endpointsAvailability: string) {
|
||||
const result = this.loadJSONSpecInDir(AUTOCOMPLETE_DEFINITIONS_FOLDER);
|
||||
private addToJsonDefinitions({
|
||||
loadedDefinition,
|
||||
jsonDefinitions,
|
||||
}: {
|
||||
loadedDefinition: EndpointDefinition;
|
||||
jsonDefinitions: Record<string, EndpointDescription>;
|
||||
}) {
|
||||
// iterate over EndpointDefinition for a safe and easy access to the only property in this object
|
||||
Object.entries(loadedDefinition).forEach(([endpointName, endpointDescription]) => {
|
||||
// endpoints should all have unique names, but in case that happens unintentionally
|
||||
// don't silently overwrite the definition but create a new unique endpoint name
|
||||
if (jsonDefinitions[endpointName]) {
|
||||
// add time to create a unique key
|
||||
jsonDefinitions[`${endpointName}${Date.now()}`] = endpointDescription;
|
||||
} else {
|
||||
jsonDefinitions[endpointName] = endpointDescription;
|
||||
}
|
||||
});
|
||||
return jsonDefinitions;
|
||||
}
|
||||
|
||||
private loadJsonDefinitions(endpointsAvailability: string) {
|
||||
const result = this.loadJSONDefinitionsFiles();
|
||||
|
||||
Object.keys(result).forEach((endpoint) => {
|
||||
const description = result[endpoint];
|
||||
|
@ -137,7 +165,7 @@ export class SpecDefinitionsService {
|
|||
});
|
||||
}
|
||||
|
||||
private loadJSSpec() {
|
||||
private loadJSDefinitions() {
|
||||
jsSpecLoaders.forEach((addJsSpec) => addJsSpec(this));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -83,7 +83,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
|
|||
// what types of config settings can be exposed to the browser.
|
||||
// When plugin owners make a change that exposes additional config values, the changes will be reflected in this test assertion.
|
||||
// Ensure that your change does not unintentionally expose any sensitive values!
|
||||
'console.autocompleteDefinitions.endpointsAvailability (string)',
|
||||
'console.autocompleteDefinitions.endpointsAvailability (alternatives)',
|
||||
'console.ui.enabled (boolean)',
|
||||
'dashboard.allowByValueEmbeddables (boolean)',
|
||||
'unifiedSearch.autocomplete.querySuggestions.enabled (boolean)',
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue