[Console] Filter autocomplete endpoints by availability (#161781)

Closes https://github.com/elastic/kibana/issues/160160

## Summary

This PR adds functionality to the new autocomplete generation script for
creating an `availability` property in the spec files that is used for
filtering out endpoints that are not available in the current
environment (e.g. `serverless` or `stack`). It also adds a config
setting in the console plugin that specifies the current environment.
This setting is also configured accordingly for serverless.


**How to test**
1. Checkout the [ES specification
repo](https://github.com/elastic/elasticsearch-specification)
2. Run the command with `node scripts/generate_console_definitions.js
--source <ES_SPECIFICATION_REPO> --emptyDest` where
`<ES_SPECIFICATION_REPO>` is the absolute path to the root of the ES
specification repo
3. Start the classic Kibana and verify that Console suggests only
endpoints that are available in the `stack` environment.
4. Start Kibana in any of the serverless modes and verify that Console
suggests only endpoints that are available in the `serverless`
environment.

Here are some example endpoints that can be used for testing:
| Endpoint  | Available in Stack | Available in Serverless |
| ------------- | ------------- | ------------- |
| [POST
_bulk](https://github.com/elastic/elasticsearch-specification/blob/main/specification/_global/bulk/BulkRequest.ts)
| Yes | Yes |
| [DELETE
_security/oauth2/token](https://github.com/elastic/elasticsearch-specification/blob/main/specification/security/invalidate_token/SecurityInvalidateTokenRequest.ts)
| Yes | No |
This commit is contained in:
Elena Stoeva 2023-07-14 10:29:27 +01:00 committed by GitHub
parent efdc760a42
commit 6bc2ee2581
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 238 additions and 6 deletions

View file

@ -72,5 +72,8 @@ server.versioned.strictClientVersionCheck: false
xpack.spaces.maxSpaces: 1
xpack.spaces.allowFeatureVisibility: false
# Only display console autocomplete suggestions for ES endpoints that are available in serverless
console.autocompleteDefinitions.endpointsAvailability: serverless
# Allow authentication via the Elasticsearch JWT realm with the `shared_secret` client authentication type.
elasticsearch.requestHeadersWhitelist: ["authorization", "es-client-authentication"]

View file

@ -0,0 +1,169 @@
/*
* 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 { SpecificationTypes } from './types';
import { generateAvailability } from './generate_availability';
describe('generateAvailability', () => {
const mockEndpoint: SpecificationTypes.Endpoint = {
name: 'test-endpoint',
description: 'test-endpoint',
docUrl: 'test-endpoint',
availability: {},
request: null,
requestBodyRequired: false,
response: null,
urls: [],
};
it('converts empty availability to true', () => {
const endpoint = mockEndpoint;
const availability = generateAvailability(endpoint);
expect(availability).toEqual({
stack: true,
serverless: true,
});
});
describe('converts correctly stack visibility', function () {
it('public visibility to true', () => {
const endpoint = {
...mockEndpoint,
availability: {
stack: {
visibility: SpecificationTypes.Visibility.public,
},
},
};
const availability = generateAvailability(endpoint);
expect(availability).toEqual({
stack: true,
serverless: true,
});
});
it('private visibility to false', () => {
const endpoint = {
...mockEndpoint,
availability: {
stack: {
visibility: SpecificationTypes.Visibility.private,
},
},
};
const availability = generateAvailability(endpoint);
expect(availability).toEqual({
stack: false,
serverless: true,
});
});
it('feature_flag visibility to false', () => {
const endpoint = {
...mockEndpoint,
availability: {
stack: {
visibility: SpecificationTypes.Visibility.feature_flag,
},
},
};
const availability = generateAvailability(endpoint);
expect(availability).toEqual({
stack: false,
serverless: true,
});
});
it('missing visibility to true', () => {
const endpoint = {
...mockEndpoint,
availability: {
stack: {},
},
};
const availability = generateAvailability(endpoint);
expect(availability).toEqual({
stack: true,
serverless: true,
});
});
});
describe('converts correctly serverless visibility', function () {
it('public visibility to true', () => {
const endpoint = {
...mockEndpoint,
availability: {
serverless: {
visibility: SpecificationTypes.Visibility.public,
},
},
};
const availability = generateAvailability(endpoint);
expect(availability).toEqual({
stack: true,
serverless: true,
});
});
it('private visibility to false', () => {
const endpoint = {
...mockEndpoint,
availability: {
serverless: {
visibility: SpecificationTypes.Visibility.private,
},
},
};
const availability = generateAvailability(endpoint);
expect(availability).toEqual({
stack: true,
serverless: false,
});
});
it('feature_flag visibility to false', () => {
const endpoint = {
...mockEndpoint,
availability: {
serverless: {
visibility: SpecificationTypes.Visibility.feature_flag,
},
},
};
const availability = generateAvailability(endpoint);
expect(availability).toEqual({
stack: true,
serverless: false,
});
});
it('missing visibility to true', () => {
const endpoint = {
...mockEndpoint,
availability: {
serverless: {},
},
};
const availability = generateAvailability(endpoint);
expect(availability).toEqual({
stack: true,
serverless: true,
});
});
});
});

View file

@ -0,0 +1,28 @@
/*
* 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 { AutocompleteAvailability } from './types';
import type { SpecificationTypes } from './types';
const DEFAULT_ENDPOINT_AVAILABILITY = true;
export const generateAvailability = (
endpoint: SpecificationTypes.Endpoint
): AutocompleteAvailability => {
const availability: AutocompleteAvailability = {
stack: DEFAULT_ENDPOINT_AVAILABILITY,
serverless: DEFAULT_ENDPOINT_AVAILABILITY,
};
if (endpoint.availability.stack?.visibility) {
availability.stack = endpoint.availability.stack?.visibility === 'public';
}
if (endpoint.availability.serverless?.visibility) {
availability.serverless = endpoint.availability.serverless?.visibility === 'public';
}
return availability;
};

View file

@ -10,6 +10,7 @@ import fs from 'fs';
import Path, { join } from 'path';
import { ToolingLog } from '@kbn/tooling-log';
import { generateQueryParams } from './generate_query_params';
import { generateAvailability } from './generate_availability';
import type {
AutocompleteBodyParams,
AutocompleteDefinition,
@ -86,12 +87,13 @@ const generateDefinition = (
const methods = generateMethods(endpoint);
const patterns = generatePatterns(endpoint);
const documentation = generateDocumentation(endpoint);
const availability = generateAvailability(endpoint);
let definition: AutocompleteDefinition = {};
const params = generateParams(endpoint, schema);
if (params) {
definition = addParams(definition, params);
}
definition = { ...definition, methods, patterns, documentation };
definition = { ...definition, methods, patterns, documentation, availability };
return definition;
};

View file

@ -14,10 +14,16 @@ export interface AutocompleteBodyParams {
[key: string]: number | string;
}
export interface AutocompleteAvailability {
stack: boolean;
serverless: boolean;
}
export interface AutocompleteDefinition {
documentation?: string;
methods?: string[];
patterns?: string[];
url_params?: AutocompleteUrlParams;
data_autocomplete_rules?: AutocompleteBodyParams;
availability?: AutocompleteAvailability;
}

View file

@ -10,6 +10,7 @@ export type {
AutocompleteDefinition,
AutocompleteUrlParams,
AutocompleteBodyParams,
AutocompleteAvailability,
} from './autocomplete_definition_types';
export * as SpecificationTypes from './specification_types';

View file

@ -24,6 +24,11 @@ const schemaLatest = schema.object(
ui: schema.object({
enabled: schema.boolean({ defaultValue: true }),
}),
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' }),
}),
},
{ defaultValue: undefined }
);
@ -31,6 +36,7 @@ const schemaLatest = schema.object(
const configLatest: PluginConfigDescriptor<ConsoleConfig> = {
exposeToBrowser: {
ui: true,
autocompleteDefinitions: true,
},
schema: schemaLatest,
deprecations: () => [],

View file

@ -79,7 +79,10 @@ export class ConsoleServerPlugin implements Plugin<ConsoleSetup, ConsoleStart> {
}
start() {
this.specDefinitionsService.start();
const {
autocompleteDefinitions: { endpointsAvailability: endpointsAvailability },
} = this.ctx.config.get<ConsoleConfig>();
this.specDefinitionsService.start({ endpointsAvailability });
}
stop() {

View file

@ -22,6 +22,11 @@ interface EndpointDescription {
data_autocomplete_rules?: Record<string, unknown>;
url_components?: Record<string, unknown>;
priority?: number;
availability?: Record<string, boolean>;
}
export interface SpecDefinitionsDependencies {
endpointsAvailability: string;
}
export class SpecDefinitionsService {
@ -81,9 +86,9 @@ export class SpecDefinitionsService {
};
}
public start() {
public start({ endpointsAvailability }: SpecDefinitionsDependencies) {
if (!this.hasLoadedSpec) {
this.loadJsonSpec();
this.loadJsonSpec(endpointsAvailability);
this.loadJSSpec();
this.hasLoadedSpec = true;
} else {
@ -116,11 +121,19 @@ export class SpecDefinitionsService {
}, {} as Record<string, EndpointDescription>);
}
private loadJsonSpec() {
private loadJsonSpec(endpointsAvailability: string) {
const result = this.loadJSONSpecInDir(AUTOCOMPLETE_DEFINITIONS_FOLDER);
Object.keys(result).forEach((endpoint) => {
this.addEndpointDescription(endpoint, result[endpoint]);
const description = result[endpoint];
const addEndpoint =
// If the 'availability' property doesn't exist, display the endpoint by default
!description.availability ||
(endpointsAvailability === 'stack' && description.availability.stack) ||
(endpointsAvailability === 'serverless' && description.availability.serverless);
if (addEndpoint) {
this.addEndpointDescription(endpoint, description);
}
});
}

View file

@ -83,6 +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.ui.enabled (boolean)',
'dashboard.allowByValueEmbeddables (boolean)',
'unifiedSearch.autocomplete.querySuggestions.enabled (boolean)',