[Synonyms UI] Synonyms UI base plugin (#203284)

## Summary

Creates a plugin for Synonyms UI implementation. It is hidden under the
UI flag and config option which is off by default.
```
POST kbn:/internal/kibana/settings/searchSynonyms:synonymsEnabled
{"value": true}
```

Serverless Search:
<img width="379" alt="Screenshot 2024-12-17 at 13 18 02"
src="https://github.com/user-attachments/assets/8c2cb6f0-ce2a-4be6-8605-4f994adeefd7"
/>

Stack Search
<img width="293" alt="Screenshot 2024-12-17 at 13 21 43"
src="https://github.com/user-attachments/assets/0d61de0e-2cd3-46a6-990f-1f1a70843324"
/>



### Checklist

Check the PR satisfies following conditions. 

Reviewers should verify this PR satisfies this list as well.

- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [ ] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] This was checked for breaking HTTP API changes, and any breaking
changes have been approved by the breaking-change committee. The
`release_note:breaking` label should be applied in these situations.
- [ ] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed
- [x] The PR description includes the appropriate Release Notes section,
and the correct `release_note:*` label is applied per the
[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

---------

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Efe Gürkan YALAMAN 2025-01-06 20:15:19 +01:00 committed by GitHub
parent 00a846f3b3
commit e542fd2370
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 432 additions and 1 deletions

1
.github/CODEOWNERS vendored
View file

@ -963,6 +963,7 @@ x-pack/solutions/search/plugins/search_inference_endpoints @elastic/search-kiban
x-pack/solutions/search/plugins/search_notebooks @elastic/search-kibana
x-pack/solutions/search/plugins/search_playground @elastic/search-kibana
x-pack/solutions/search/plugins/search_solution/search_navigation @elastic/search-kibana
x-pack/solutions/search/plugins/search_synonyms @elastic/search-kibana
x-pack/solutions/search/plugins/serverless_search @elastic/search-kibana
x-pack/solutions/security/packages/data_table @elastic/security-threat-hunting-investigations
x-pack/solutions/security/packages/data-stream-adapter @elastic/security-threat-hunting

View file

@ -8,6 +8,7 @@ xpack.fleet.internal.activeAgentsSoftLimit: 25000
xpack.fleet.internal.onlyAllowAgentUpgradeToKnownVersions: true
xpack.fleet.internal.retrySetupOnBoot: true
xpack.fleet.internal.useMeteringApi: true
xpack.searchSynonyms.enabled: false
## Fine-tune the feature privileges.
xpack.features.overrides:

View file

@ -856,6 +856,10 @@ detailed information on how Elasticsearch executed the search request. People us
to understand why a search request might be slow.
|{kib-repo}blob/{branch}/x-pack/solutions/search/plugins/search_synonyms/README.md[searchSynonyms]
|A plugin to manage synonyms in Elasticsearch through Synonyms APIs through Kibana.
|{kib-repo}blob/{branch}/x-pack/platform/plugins/shared/security/README.md[security]
|See Configuring security in
Kibana.

View file

@ -815,6 +815,7 @@
"@kbn/search-playground": "link:x-pack/solutions/search/plugins/search_playground",
"@kbn/search-response-warnings": "link:src/platform/packages/shared/kbn-search-response-warnings",
"@kbn/search-shared-ui": "link:x-pack/solutions/search/packages/search/shared_ui",
"@kbn/search-synonyms": "link:x-pack/solutions/search/plugins/search_synonyms",
"@kbn/search-types": "link:src/platform/packages/shared/kbn-search-types",
"@kbn/searchprofiler-plugin": "link:x-pack/platform/plugins/shared/searchprofiler",
"@kbn/security-api-key-management": "link:x-pack/platform/packages/shared/security/api_key_management",

View file

@ -148,6 +148,7 @@ pageLoadAssetSize:
searchNotebooks: 18942
searchPlayground: 19325
searchprofiler: 67080
searchSynonyms: 20262
security: 81771
securitySolution: 98429
securitySolutionEss: 31781

View file

@ -18,6 +18,7 @@ export const SERVERLESS_ES_CONNECTORS_ID = 'serverlessConnectors';
export const SERVERLESS_ES_WEB_CRAWLERS_ID = 'serverlessWebCrawlers';
export const ES_SEARCH_PLAYGROUND_ID = 'searchPlayground';
export const SERVERLESS_ES_SEARCH_INFERENCE_ENDPOINTS_ID = 'searchInferenceEndpoints';
export const ES_SEARCH_SYNONYMS_ID = 'searchSynonyms';
export const SEARCH_HOMEPAGE = 'searchHomepage';
export const SEARCH_INDICES_START = 'elasticsearchStart';
export const SEARCH_INDICES = 'elasticsearchIndices';

View file

@ -27,6 +27,7 @@ import {
SEARCH_VECTOR_SEARCH,
SEARCH_SEMANTIC_SEARCH,
SEARCH_AI_SEARCH,
ES_SEARCH_SYNONYMS_ID,
} from './constants';
export type EnterpriseSearchApp = typeof ENTERPRISE_SEARCH_APP_ID;
@ -40,6 +41,7 @@ export type ConnectorsId = typeof SERVERLESS_ES_CONNECTORS_ID;
export type ServerlessWebCrawlers = typeof SERVERLESS_ES_WEB_CRAWLERS_ID;
export type SearchPlaygroundId = typeof ES_SEARCH_PLAYGROUND_ID;
export type SearchInferenceEndpointsId = typeof SERVERLESS_ES_SEARCH_INFERENCE_ENDPOINTS_ID;
export type SearchSynonymsId = typeof ES_SEARCH_SYNONYMS_ID;
export type SearchHomepage = typeof SEARCH_HOMEPAGE;
export type SearchStart = typeof SEARCH_INDICES_START;
export type SearchIndices = typeof SEARCH_INDICES;
@ -56,6 +58,8 @@ export type AppsearchLinkId = 'engines';
export type SearchInferenceEndpointsLinkId = 'inferenceEndpoints';
export type SynonymsLinkId = 'synonyms';
export type SearchIndicesLinkId = typeof SEARCH_INDICES_CREATE_INDEX;
export type DeepLinkId =
@ -70,11 +74,13 @@ export type DeepLinkId =
| ServerlessWebCrawlers
| SearchPlaygroundId
| SearchInferenceEndpointsId
| SearchSynonymsId
| SearchHomepage
| `${EnterpriseSearchContentApp}:${ContentLinkId}`
| `${EnterpriseSearchApplicationsApp}:${ApplicationsLinkId}`
| `${EnterpriseSearchAppsearchApp}:${AppsearchLinkId}`
| `${SearchInferenceEndpointsId}:${SearchInferenceEndpointsLinkId}`
| `${SearchSynonymsId}:${SynonymsLinkId}`
| SearchStart
| SearchIndices
| SearchElasticsearch

View file

@ -1606,6 +1606,8 @@
"@kbn/search-response-warnings/*": ["src/platform/packages/shared/kbn-search-response-warnings/*"],
"@kbn/search-shared-ui": ["x-pack/solutions/search/packages/search/shared_ui"],
"@kbn/search-shared-ui/*": ["x-pack/solutions/search/packages/search/shared_ui/*"],
"@kbn/search-synonyms": ["x-pack/solutions/search/plugins/search_synonyms"],
"@kbn/search-synonyms/*": ["x-pack/solutions/search/plugins/search_synonyms/*"],
"@kbn/search-types": ["src/platform/packages/shared/kbn-search-types"],
"@kbn/search-types/*": ["src/platform/packages/shared/kbn-search-types/*"],
"@kbn/searchprofiler-plugin": ["x-pack/platform/plugins/shared/searchprofiler"],

View file

@ -121,6 +121,14 @@ export const buildBaseClassicNavItems = (): ClassicNavItem[] => {
},
id: 'inference_endpoints',
},
{
'data-test-subj': 'searchSideNav-Synonyms',
deepLink: {
link: 'searchSynonyms:synonyms',
shouldShowActiveForSubroutes: true,
},
id: 'synonyms',
},
],
name: i18n.translate('xpack.enterpriseSearch.nav.relevanceTitle', {
defaultMessage: 'Relevance',

View file

@ -207,7 +207,10 @@ export const getNavigationTreeDefinition = ({
}),
},
{
children: [{ link: 'searchInferenceEndpoints:inferenceEndpoints' }],
children: [
{ link: 'searchInferenceEndpoints:inferenceEndpoints' },
{ link: 'searchSynonyms:synonyms' },
],
id: 'relevance',
title: i18n.translate('xpack.enterpriseSearch.searchNav.relevance', {
defaultMessage: 'Relevance',

View file

@ -0,0 +1,3 @@
# Search Synonyms
A plugin to manage synonyms in Elasticsearch through Synonyms APIs through Kibana.

View file

@ -0,0 +1,15 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { i18n } from '@kbn/i18n';
export const PLUGIN_ID = 'searchSynonyms';
export const PLUGIN_NAME = 'Synonyms';
export const PLUGIN_TITLE = i18n.translate('xpack.searchSynonyms.pluginTitle', {
defaultMessage: 'Synonyms',
});

View file

@ -0,0 +1,19 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface SearchSynonymsPluginSetup {}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface SearchSynonymsPluginStart {}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface AppPluginSetupDependencies {}
export interface SearchSynonymsConfigType {
enabled: boolean;
}

View file

@ -0,0 +1,8 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export const SYNONYMS_UI_FLAG = 'searchSynonyms:synonymsEnabled';

View file

@ -0,0 +1,17 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
module.exports = {
preset: '@kbn/test',
rootDir: '../../../../..',
roots: ['<rootDir>/x-pack/solutions/search/plugins/search_synonyms'],
coverageDirectory: '<rootDir>/target/kibana-coverage/jest/x-pack/plugins/search_synonyms',
coverageReporters: ['text', 'html'],
collectCoverageFrom: [
'<rootDir>/x-pack/solutions/search/plugins/search_synonyms/{public,server}/**/*.{ts,tsx}',
],
};

View file

@ -0,0 +1,26 @@
{
"type": "plugin",
"id": "@kbn/search-synonyms",
"owner": "@elastic/search-kibana",
"group": "search",
"visibility": "private",
"plugin": {
"id": "searchSynonyms",
"server": true,
"browser": true,
"configPath": [
"xpack",
"searchSynonyms"
],
"requiredPlugins": [
"features",
],
"optionalPlugins": [
"console",
"searchNavigation",
],
"requiredBundles": [
"kibanaReact",
]
}
}

View file

@ -0,0 +1,35 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import ReactDOM from 'react-dom';
import { CoreStart } from '@kbn/core/public';
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import { I18nProvider } from '@kbn/i18n-react';
import { Router } from '@kbn/shared-ux-router';
import { AppPluginStartDependencies } from './types';
export const renderApp = async (
core: CoreStart,
services: AppPluginStartDependencies,
element: HTMLElement
) => {
ReactDOM.render(
<KibanaRenderContextProvider {...core}>
<KibanaContextProvider services={{ ...core, ...services }}>
<I18nProvider>
<Router history={services.history}>
<div>Synonyms</div>
</Router>
</I18nProvider>
</KibanaContextProvider>
</KibanaRenderContextProvider>,
element
);
return () => ReactDOM.unmountComponentAtNode(element);
};

View file

@ -0,0 +1,12 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { SearchSynonymsPlugin } from './plugin';
export function plugin() {
return new SearchSynonymsPlugin();
}

View file

@ -0,0 +1,68 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { CoreSetup, Plugin, AppMountParameters } from '@kbn/core/public';
import { PLUGIN_ID, PLUGIN_NAME, PLUGIN_TITLE } from '../common';
import {
AppPluginSetupDependencies,
AppPluginStartDependencies,
SearchSynonymsPluginSetup,
SearchSynonymsPluginStart,
} from './types';
import { SYNONYMS_UI_FLAG } from '../common/ui_flags';
export class SearchSynonymsPlugin
implements Plugin<SearchSynonymsPluginSetup, SearchSynonymsPluginStart>
{
constructor() {}
public setup(
core: CoreSetup<AppPluginStartDependencies, SearchSynonymsPluginStart>,
_: AppPluginSetupDependencies
): SearchSynonymsPluginSetup {
if (!core.settings.client.get<boolean>(SYNONYMS_UI_FLAG, false)) {
return {};
}
core.application.register({
id: PLUGIN_ID,
appRoute: '/app/elasticsearch/synonyms',
title: PLUGIN_TITLE,
deepLinks: [
{
id: 'synonyms',
path: '/',
title: PLUGIN_TITLE,
visibleIn: ['globalSearch'],
},
],
async mount({ element, history }: AppMountParameters) {
const { renderApp } = await import('./application');
const [coreStart, depsStart] = await core.getStartServices();
coreStart.chrome.docTitle.change(PLUGIN_NAME);
const startDeps: AppPluginStartDependencies = {
...depsStart,
history,
};
depsStart.searchNavigation?.handleOnAppMount();
return renderApp(coreStart, startDeps, element);
},
visibleIn: [],
});
return {};
}
public start(): SearchSynonymsPluginStart {
return {};
}
public stop() {}
}

View file

@ -0,0 +1,17 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { SearchNavigationPluginStart } from '@kbn/search-navigation/public';
import { AppMountParameters } from '@kbn/core/public';
import type { ConsolePluginStart } from '@kbn/console-plugin/public';
export * from '../common/types';
export interface AppPluginStartDependencies {
history: AppMountParameters['history'];
console?: ConsolePluginStart;
searchNavigation?: SearchNavigationPluginStart;
}

View file

@ -0,0 +1,19 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { schema, TypeOf } from '@kbn/config-schema';
import { PluginConfigDescriptor } from '@kbn/core/server';
const configSchema = schema.object({
enabled: schema.boolean({ defaultValue: false }),
});
export type SearchPlaygroundConfig = TypeOf<typeof configSchema>;
export const config: PluginConfigDescriptor<SearchPlaygroundConfig> = {
schema: configSchema,
};

View file

@ -0,0 +1,17 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { PluginInitializerContext } from '@kbn/core/server';
export { config } from './config';
export async function plugin(initializerContext: PluginInitializerContext) {
const { SearchSynonymsPlugin } = await import('./plugin');
return new SearchSynonymsPlugin(initializerContext);
}
export type { SearchSynonymsPluginSetup, SearchSynonymsPluginStart } from './types';

View file

@ -0,0 +1,80 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import {
PluginInitializerContext,
CoreSetup,
CoreStart,
Plugin,
Logger,
DEFAULT_APP_CATEGORIES,
} from '@kbn/core/server';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
import {
SearchSynonymsPluginSetup,
SearchSynonymsPluginSetupDependencies,
SearchSynonymsPluginStart,
} from './types';
import { defineRoutes } from './routes';
import { PLUGIN_ID, PLUGIN_TITLE } from '../common';
export class SearchSynonymsPlugin
implements Plugin<SearchSynonymsPluginSetup, SearchSynonymsPluginStart, {}, {}>
{
private readonly logger: Logger;
constructor(initializerContext: PluginInitializerContext) {
this.logger = initializerContext.logger.get();
}
public setup(core: CoreSetup, plugins: SearchSynonymsPluginSetupDependencies) {
const router = core.http.createRouter();
defineRoutes({ router, logger: this.logger });
plugins.features.registerKibanaFeature({
id: PLUGIN_ID,
name: PLUGIN_TITLE,
order: 0,
category: DEFAULT_APP_CATEGORIES.enterpriseSearch,
app: ['kibana', PLUGIN_ID],
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
catalogue: [PLUGIN_ID],
privileges: {
all: {
app: ['kibana', PLUGIN_ID],
api: ['synonyms:manage', 'synonyms:read'],
catalogue: [PLUGIN_ID],
savedObject: {
all: [],
read: [],
},
ui: ['read', 'save'],
},
read: {
app: ['kibana', PLUGIN_ID],
api: ['synonyms:read'],
savedObject: {
all: [],
read: [],
},
ui: ['read'],
},
},
});
return {};
}
public start(core: CoreStart) {
return {};
}
public stop() {}
}

View file

@ -0,0 +1,10 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { IRouter, Logger } from '@kbn/core/server';
export function defineRoutes({ logger, router }: { logger: Logger; router: IRouter }) {}

View file

@ -0,0 +1,13 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FeaturesPluginSetup } from '@kbn/features-plugin/server';
export * from '../common/types';
export interface SearchSynonymsPluginSetupDependencies {
features: FeaturesPluginSetup;
}

View file

@ -0,0 +1,27 @@
{
"extends": "../../../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "target/types",
},
"include": [
"__mocks__/**/*",
"common/**/*",
"public/**/*",
"server/**/*",
],
"kbn_references": [
"@kbn/config-schema",
"@kbn/core",
"@kbn/i18n",
"@kbn/i18n-react",
"@kbn/kibana-react-plugin",
"@kbn/shared-ux-router",
"@kbn/react-kibana-context-render",
"@kbn/console-plugin",
"@kbn/features-plugin",
"@kbn/search-navigation",
],
"exclude": [
"target/**/*",
]
}

View file

@ -103,6 +103,13 @@ export const navigationTree = ({ isAppRegistered }: ApplicationStart): Navigatio
),
link: 'searchInferenceEndpoints',
},
{
id: 'searchSynonyms',
title: i18n.translate('xpack.serverlessSearch.nav.relevance.searchSynonyms', {
defaultMessage: 'Synonyms',
}),
link: 'searchSynonyms',
},
],
},
{

View file

@ -185,6 +185,7 @@ export default function ({ getService }: FtrProviderContext) {
'rulesSettings',
'uptime',
'searchInferenceEndpoints',
'searchSynonyms',
'searchPlayground',
'siem',
'slo',

View file

@ -89,6 +89,7 @@ export default function catalogueTests({ getService }: FtrProviderContext) {
'enterpriseSearchElasticsearch',
'searchPlayground',
'searchInferenceEndpoints',
'searchSynonyms',
'appSearch',
'observabilityAIAssistant',
'workplaceSearch',

View file

@ -63,6 +63,7 @@ export default function navLinksTests({ getService }: FtrProviderContext) {
'enterpriseSearchAnalytics',
'searchPlayground',
'searchInferenceEndpoints',
'searchSynonyms',
'guidedOnboardingFeature',
'securitySolutionAssistant',
'securitySolutionAttackDiscovery',

View file

@ -37,6 +37,9 @@ export default createTestConfig({
searchPlayground: {
pathname: '/app/search_playground',
},
searchSynonyms: {
pathname: '/app/elasticsearch/search_synonyms',
},
elasticsearchStart: {
pathname: '/app/elasticsearch/start',
},

View file

@ -7065,6 +7065,10 @@
version "0.0.0"
uid ""
"@kbn/search-synonyms@link:x-pack/solutions/search/plugins/search_synonyms":
version "0.0.0"
uid ""
"@kbn/search-types@link:src/platform/packages/shared/kbn-search-types":
version "0.0.0"
uid ""