Search synonyms empty prompt (#205723)

## Summary

Implement empty prompt for the Synonyms page.

<img width="1044" alt="Screenshot 2025-01-07 at 13 56 09"
src="https://github.com/user-attachments/assets/d61c5251-afdb-4e34-bf18-a20ab1044800"
/>

### 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/src/platform/packages/shared/kbn-i18n/README.md)
- [x] [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
- [ ] [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: Liam Thompson <32779855+leemthompo@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-08 18:06:29 +01:00 committed by GitHub
parent efe44b2f2d
commit 5323067906
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 329 additions and 5 deletions

View file

@ -1020,5 +1020,8 @@ export const getDocLinks = ({ kibanaBranch, buildFlavor }: GetDocLinkOptions): D
cases: {
legacyApiDeprecations: `${KIBANA_DOCS}breaking-changes-summary.html#breaking-201004`,
},
synonyms: {
synonymsAPIDocumentation: `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/current/synonyms-apis.html`,
},
});
};

View file

@ -688,6 +688,9 @@ export interface DocLinks {
readonly cases: {
readonly legacyApiDeprecations: string;
};
readonly synonyms: {
readonly synonymsAPIDocumentation: string;
};
}
export type BuildFlavor = 'serverless' | 'traditional';

View file

@ -0,0 +1,20 @@
/*
* 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 { DocLinks } from '@kbn/doc-links';
class SynonymsDocLinks {
public synonymsApi: string = '';
constructor() {}
setDocLinks(newDocLinks: DocLinks) {
this.synonymsApi = newDocLinks.synonyms.synonymsAPIDocumentation;
}
}
export const docLinks = new SynonymsDocLinks();

View file

@ -11,8 +11,9 @@ 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 { Route, Router, Routes } from '@kbn/shared-ux-router';
import { AppPluginStartDependencies } from './types';
import { SearchSynonymsOverview } from './components/overview/overview';
export const renderApp = async (
core: CoreStart,
@ -24,7 +25,11 @@ export const renderApp = async (
<KibanaContextProvider services={{ ...core, ...services }}>
<I18nProvider>
<Router history={services.history}>
<div>Synonyms</div>
<Routes>
<Route path="/">
<SearchSynonymsOverview />
</Route>
</Routes>
</Router>
</I18nProvider>
</KibanaContextProvider>

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 { EmptyPrompt } from './empty_prompt';
import { render, screen } from '@testing-library/react';
import { I18nProvider } from '@kbn/i18n-react';
jest.mock('../../../common/doc_links', () => ({
docLinks: {
synonymsApi: 'documentation-url',
},
}));
const Wrapper = ({ children }: { children?: React.ReactNode }) => (
<I18nProvider>{children}</I18nProvider>
);
describe('Synonyms Overview Empty Prompt', () => {
it('renders', () => {
render(
<Wrapper>
<EmptyPrompt />
</Wrapper>
);
expect(screen.getByTestId('searchSynonymsEmptyPromptGetStartedButton')).toBeInTheDocument();
expect(screen.getByTestId('searchSynonymsEmptyPromptFooterLink').getAttribute('href')).toBe(
'documentation-url'
);
});
});

View file

@ -0,0 +1,206 @@
/*
* 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 {
EuiButton,
EuiFlexGrid,
EuiFlexGroup,
EuiFlexItem,
EuiHorizontalRule,
EuiIcon,
EuiLink,
EuiSpacer,
EuiSplitPanel,
EuiText,
EuiTitle,
useEuiTheme,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { css } from '@emotion/react';
import { docLinks } from '../../../common/doc_links';
export const EmptyPrompt: React.FC = () => {
const { euiTheme } = useEuiTheme();
return (
<EuiFlexGroup direction="row" gutterSize="l" alignItems="center" justifyContent="center">
<EuiFlexItem grow={false}>
<div
css={css`
max-width: calc(${euiTheme.size.base} * 50);
`}
>
<EuiSplitPanel.Outer grow={false}>
<EuiSplitPanel.Inner paddingSize="l">
<EuiSpacer size="m" />
<EuiTitle size="l">
<h2>
<FormattedMessage
id="xpack.searchSynonyms.emptyPrompt.title"
defaultMessage="Search with synonyms"
/>
</h2>
</EuiTitle>
<EuiSpacer size="m" />
<EuiFlexGroup direction="column" gutterSize="m">
<EuiFlexItem grow={false}>
<EuiText size="m">
<p>
<FormattedMessage
id="xpack.searchSynonyms.emptyPrompt.subtitle"
defaultMessage="Improve search relevance by matching terms that express the same concept."
/>
</p>
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<div>
<EuiButton
data-test-subj="searchSynonymsEmptyPromptGetStartedButton"
color="primary"
fill
>
<FormattedMessage
id="xpack.searchSynonyms.emptyPrompt.getStartedButton"
defaultMessage="Get started"
/>
</EuiButton>
</div>
</EuiFlexItem>
<EuiHorizontalRule margin="m" />
<EuiFlexItem grow={false}>
<EuiFlexGrid columns={3} direction="row">
<EuiFlexItem grow={false}>
<EuiFlexGroup responsive={false} gutterSize="xs" direction="column">
<EuiFlexItem grow={false}>
<EuiFlexGroup responsive={false} gutterSize="s">
<EuiFlexItem grow={false}>
<EuiIcon type="check" />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiTitle size="xxs">
<h5>
<FormattedMessage
id="xpack.searchSynonyms.emptyPrompt.improveSearchRelevance.title"
defaultMessage="Expand search coverage"
/>
</h5>
</EuiTitle>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem>
<EuiText size="s" color="subdued">
<p>
<FormattedMessage
id="xpack.searchSynonyms.emptyPrompt.improveSearchRelevance.description"
defaultMessage="Include related terms your users actually use."
/>
</p>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFlexGroup responsive={false} gutterSize="xs" direction="column">
<EuiFlexItem grow={false}>
<EuiFlexGroup responsive={false} gutterSize="s">
<EuiFlexItem grow={false}>
<EuiIcon type="check" />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiTitle size="xxs">
<h5>
<FormattedMessage
id="xpack.searchSynonyms.emptyPrompt.domainSpecific.title"
defaultMessage="Match domain-specific terms"
/>
</h5>
</EuiTitle>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem>
<EuiText size="s" color="subdued">
<p>
<FormattedMessage
id="xpack.searchSynonyms.emptyPrompt.domainSpecific.description"
defaultMessage="Connect industry terms with common search phrases."
/>
</p>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFlexGroup responsive={false} gutterSize="xs" direction="column">
<EuiFlexItem grow={false}>
<EuiFlexGroup responsive={false} gutterSize="s">
<EuiFlexItem grow={false}>
<EuiIcon type="check" />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiTitle size="xxs">
<h5>
<FormattedMessage
id="xpack.searchSynonyms.emptyPrompt.improvePerformance.title"
defaultMessage="Optimize performance"
/>
</h5>
</EuiTitle>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem>
<EuiText size="s" color="subdued">
<p>
<FormattedMessage
id="xpack.searchSynonyms.emptyPrompt.improvedPerformance.description"
defaultMessage="Uses the built-in synonyms API for improved performance and flexibility."
/>
</p>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGrid>
</EuiFlexItem>
</EuiFlexGroup>
</EuiSplitPanel.Inner>
<EuiSplitPanel.Inner color="subdued" paddingSize="l">
<>
<EuiTitle size="xxs">
<span>
<FormattedMessage
id="xpack.searchSynonyms.emptyPrompt.footer"
defaultMessage="Prefer to use the APIs?"
/>
</span>
</EuiTitle>
&nbsp;
<EuiLink
data-test-subj="searchSynonymsEmptyPromptFooterLink"
href={docLinks.synonymsApi}
target="_blank"
external
>
<FormattedMessage
id="xpack.searchSynonyms.emptyPrompt.footerLink"
defaultMessage="Synonyms API documentation"
/>
</EuiLink>
</>
</EuiSplitPanel.Inner>
</EuiSplitPanel.Outer>
</div>
</EuiFlexItem>
</EuiFlexGroup>
);
};

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, { useMemo } from 'react';
import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template';
import { useKibana } from '../../hooks/use_kibana';
import { EmptyPrompt } from '../empty_prompt/empty_prompt';
export const SearchSynonymsOverview = () => {
const {
services: { console: consolePlugin, history, searchNavigation },
} = useKibana();
const embeddableConsole = useMemo(
() => (consolePlugin?.EmbeddableConsole ? <consolePlugin.EmbeddableConsole /> : null),
[consolePlugin]
);
return (
<KibanaPageTemplate
offset={0}
restrictWidth={false}
grow={false}
data-test-subj="searchSynonymsOverviewPage"
solutionNav={searchNavigation?.useClassicNavigation(history)}
>
<EmptyPrompt />
{embeddableConsole}
</KibanaPageTemplate>
);
};

View file

@ -0,0 +1,11 @@
/*
* 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 { useKibana as _useKibana } from '@kbn/kibana-react-plugin/public';
import { AppServicesContext } from '../types';
export const useKibana = () => _useKibana<AppServicesContext>();

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import type { CoreSetup, Plugin, AppMountParameters } from '@kbn/core/public';
import type { CoreSetup, Plugin, AppMountParameters, CoreStart } from '@kbn/core/public';
import { PLUGIN_ID, PLUGIN_NAME, PLUGIN_TITLE } from '../common';
import {
AppPluginSetupDependencies,
@ -14,6 +14,7 @@ import {
SearchSynonymsPluginStart,
} from './types';
import { SYNONYMS_UI_FLAG } from '../common/ui_flags';
import { docLinks } from '../common/doc_links';
export class SearchSynonymsPlugin
implements Plugin<SearchSynonymsPluginSetup, SearchSynonymsPluginStart>
@ -60,7 +61,8 @@ export class SearchSynonymsPlugin
return {};
}
public start(): SearchSynonymsPluginStart {
public start(core: CoreStart): SearchSynonymsPluginStart {
docLinks.setDocLinks(core.docLinks.links);
return {};
}

View file

@ -6,7 +6,7 @@
*/
import { SearchNavigationPluginStart } from '@kbn/search-navigation/public';
import { AppMountParameters } from '@kbn/core/public';
import { AppMountParameters, CoreStart } from '@kbn/core/public';
import type { ConsolePluginStart } from '@kbn/console-plugin/public';
export * from '../common/types';
@ -15,3 +15,5 @@ export interface AppPluginStartDependencies {
console?: ConsolePluginStart;
searchNavigation?: SearchNavigationPluginStart;
}
export type AppServicesContext = CoreStart & AppPluginStartDependencies;

View file

@ -20,6 +20,8 @@
"@kbn/console-plugin",
"@kbn/features-plugin",
"@kbn/search-navigation",
"@kbn/doc-links",
"@kbn/shared-ux-page-kibana-template",
],
"exclude": [
"target/**/*",