();
+
+ const currentWorkspace = useObservable(workspaces.currentWorkspace$);
+
+ const title = i18n.translate('home.usecase.search.title', { defaultMessage: 'Search overview' });
+
+ useEffect(() => {
+ const breadcrumbs: EuiBreadcrumb[] = [
+ {
+ text: currentWorkspace?.name || title,
+ },
+ ];
+ chrome.setBreadcrumbs(breadcrumbs);
+ }, [chrome, currentWorkspace, title]);
+
+ return (
+
+ {contentManagement ? contentManagement.renderPage(SEARCH_OVERVIEW_PAGE_ID) : null}
+
+ );
+};
diff --git a/src/plugins/home/public/application/components/usecase_overview/search_use_case_setup.test.tsx b/src/plugins/home/public/application/components/usecase_overview/search_use_case_setup.test.tsx
new file mode 100644
index 000000000000..3d622b44d046
--- /dev/null
+++ b/src/plugins/home/public/application/components/usecase_overview/search_use_case_setup.test.tsx
@@ -0,0 +1,115 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { coreMock } from '../../../../../../core/public/mocks';
+import { contentManagementPluginMocks } from '../../../../../content_management/public/mocks';
+import { registerContentToSearchUseCasePage, setupSearchUseCase } from './search_use_case_setup';
+
+describe('Search use case setup', () => {
+ const coreStart = coreMock.createStart();
+ const registerContentProviderMock = jest.fn();
+ const registerPageMock = jest.fn();
+
+ const contentManagementSetupMock = {
+ ...contentManagementPluginMocks.createSetupContract(),
+ registerPage: registerPageMock,
+ };
+
+ const contentManagementStartMock = {
+ ...contentManagementPluginMocks.createStartContract(),
+ registerContentProvider: registerContentProviderMock,
+ };
+
+ it('setupSearchUseCase', () => {
+ setupSearchUseCase(contentManagementSetupMock);
+ expect(registerPageMock).toHaveBeenCalledTimes(1);
+
+ const call = registerPageMock.mock.calls[0];
+ expect(call[0]).toMatchInlineSnapshot(`
+ Object {
+ "id": "search_overview",
+ "sections": Array [
+ Object {
+ "id": "get_started",
+ "kind": "card",
+ "order": 1000,
+ "title": "Set up search",
+ },
+ Object {
+ "columns": 2,
+ "grid": true,
+ "id": "different_search_types",
+ "kind": "card",
+ "order": 2000,
+ "title": "Try out different search techniques",
+ },
+ Object {
+ "columns": 2,
+ "grid": true,
+ "id": "config_evaluate_search",
+ "kind": "card",
+ "order": 3000,
+ "title": "Configure and evaluate search",
+ },
+ ],
+ "title": "Overview",
+ }
+ `);
+ });
+
+ it('registerContentToSearchUseCasePage', () => {
+ registerContentToSearchUseCasePage(contentManagementStartMock, coreStart);
+
+ const call = registerContentProviderMock.mock.calls[0];
+ expect(call[0].getTargetArea()).toEqual('search_overview/get_started');
+ expect(call[0].getContent()).toMatchInlineSnapshot(`
+ Object {
+ "cardProps": Object {
+ "selectable": Object {
+ "children": ,
+ "isSelected": false,
+ "onClick": [Function],
+ },
+ },
+ "description": "You can run a search using REST API or language client. For experimentation, you can also run queries interactively.",
+ "id": "access_search_functionality",
+ "kind": "card",
+ "order": 10,
+ "title": "Access search functionality",
+ }
+ `);
+
+ // search type section
+ const searchTypesCall = registerContentProviderMock.mock.calls[2];
+ expect(searchTypesCall[0].getTargetArea()).toEqual('search_overview/different_search_types');
+ expect(searchTypesCall[0].getContent()).toMatchInlineSnapshot(`
+ Object {
+ "cardProps": Object {
+ "children":
+
+ View Documentation
+
+
,
+ "layout": "horizontal",
+ },
+ "description": "Lexical or keyword search matches documents based on exact words or phrases. Search the text using human-friendly query string query syntax or create complex, customizable queries using Query DSL—the OpenSearch query language.",
+ "getIcon": [Function],
+ "id": "text_search",
+ "kind": "card",
+ "order": 10,
+ "title": "Text search",
+ }
+ `);
+ });
+});
diff --git a/src/plugins/home/public/application/components/usecase_overview/search_use_case_setup.tsx b/src/plugins/home/public/application/components/usecase_overview/search_use_case_setup.tsx
new file mode 100644
index 000000000000..d3a7abf8986c
--- /dev/null
+++ b/src/plugins/home/public/application/components/usecase_overview/search_use_case_setup.tsx
@@ -0,0 +1,252 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import React from 'react';
+import { CoreStart } from 'opensearch-dashboards/public';
+import { EuiI18n, EuiIcon, EuiLink } from '@elastic/eui';
+import { i18n } from '@osd/i18n';
+import {
+ ContentManagementPluginSetup,
+ ContentManagementPluginStart,
+ SEARCH_OVERVIEW_PAGE_ID,
+ SECTIONS,
+ SEARCH_OVERVIEW_CONTENT_AREAS,
+} from '../../../../../content_management/public';
+
+export const setupSearchUseCase = (contentManagement: ContentManagementPluginSetup) => {
+ contentManagement.registerPage({
+ id: SEARCH_OVERVIEW_PAGE_ID,
+ title: 'Overview',
+ sections: [
+ {
+ id: SECTIONS.GET_STARTED,
+ order: 1000,
+ title: i18n.translate('home.searchOverview.setupSection.title', {
+ defaultMessage: 'Set up search',
+ }),
+ kind: 'card',
+ },
+ {
+ id: SECTIONS.DIFFERENT_SEARCH_TYPES,
+ order: 2000,
+ title: i18n.translate('home.searchOverview.differentSearchTypes.title', {
+ defaultMessage: 'Try out different search techniques',
+ }),
+ kind: 'card',
+ grid: true,
+ columns: 2,
+ },
+ {
+ id: SECTIONS.CONFIG_EVALUATE_SEARCH,
+ order: 3000,
+ title: i18n.translate('home.searchOverview.configEvaluate.title', {
+ defaultMessage: 'Configure and evaluate search',
+ }),
+ kind: 'card',
+ grid: true,
+ columns: 2,
+ },
+ ],
+ });
+};
+
+export const registerContentToSearchUseCasePage = (
+ contentManagement: ContentManagementPluginStart,
+ core: CoreStart
+) => {
+ const getStartedCards = [
+ {
+ id: 'access_search_functionality',
+ order: 10,
+ title: i18n.translate('home.searchOverview.setup.accessSearch.title', {
+ defaultMessage: 'Access search functionality',
+ }),
+ description: i18n.translate('home.searchOverview.setup.accessSearch.description', {
+ defaultMessage:
+ 'You can run a search using REST API or language client. For experimentation, you can also run queries interactively.',
+ }),
+ footer: (
+
+ ),
+ documentURL: 'https://opensearch.org/docs/latest/search-plugins/',
+ },
+ {
+ id: 'create_document_index',
+ order: 20,
+ title: i18n.translate('home.searchOverview.setup.createDocumentIndex.title', {
+ defaultMessage: 'Create a document index',
+ }),
+ description: i18n.translate('home.searchOverview.setup.createDocumentIndex.description', {
+ defaultMessage:
+ 'You can create a document collection (an index) by adding documents to a new index.',
+ }),
+ footer: (
+
+ ),
+ documentURL: 'https://opensearch.org/docs/latest/getting-started/intro/',
+ },
+ ];
+
+ getStartedCards.forEach((card) => {
+ contentManagement.registerContentProvider({
+ id: card.id,
+ getContent: () => ({
+ id: card.id,
+ kind: 'card',
+ order: card.order,
+ title: card.title,
+ description: card.description,
+ cardProps: {
+ selectable: {
+ onClick: () => {
+ window.open(card.documentURL, '_blank');
+ },
+ children: card.footer,
+ isSelected: false,
+ },
+ },
+ }),
+ getTargetArea: () => SEARCH_OVERVIEW_CONTENT_AREAS.GET_STARTED,
+ });
+ });
+
+ const searchIcon = ;
+
+ const searchTypeCards = [
+ {
+ id: 'text_search',
+ order: 10,
+ title: i18n.translate('home.searchOverview.searchTypes.textSearch.title', {
+ defaultMessage: 'Text search',
+ }),
+ description: i18n.translate('home.searchOverview.searchTypes.textSearch.description', {
+ defaultMessage:
+ 'Lexical or keyword search matches documents based on exact words or phrases. Search the text using human-friendly query string query syntax or create complex, customizable queries using Query DSL—the OpenSearch query language.',
+ }),
+ icon: searchIcon,
+ footer: (
+
+ View Documentation
+
+ ),
+ },
+ {
+ id: 'analyzer',
+ order: 10,
+ title: i18n.translate('home.searchOverview.searchTypes.analyzer.title', {
+ defaultMessage: 'Analyzers',
+ }),
+ description: i18n.translate('home.searchOverview.searchTypes.analyzer.description', {
+ defaultMessage:
+ 'Analyzers prepare text for indexing. For example, HTML text typically has its tags removed. English text typically treats paint, painted, and painting as equivalent by mapping them all to the token paint. You can use OpenSearch’s extensive library of standard analyzers or create your own.',
+ }),
+ icon: searchIcon,
+ footer: (
+
+ View Documentation
+
+ ),
+ },
+ {
+ id: 'semantic_vector_search',
+ order: 10,
+ title: i18n.translate('home.searchOverview.searchTypes.semanticVectorSearch.title', {
+ defaultMessage: 'Semantic vector search',
+ }),
+ description: i18n.translate(
+ 'home.searchOverview.searchTypes.semanticVectorSearch.description',
+ {
+ defaultMessage:
+ 'Using semantic vector search, you can search for documents similar to your query in a vector space. With OpenSearch’s neural search capability, you specify an embedding model, and OpenSearch manages the operational complexity for you.',
+ }
+ ),
+ icon: searchIcon,
+ footer: (
+
+ View Documentation
+
+ ),
+ },
+ {
+ id: 'neural_sparse_search',
+ order: 10,
+ title: i18n.translate('home.searchOverview.searchTypes.neuralSparseSearch.title', {
+ defaultMessage: 'Neural sparse search',
+ }),
+ description: i18n.translate(
+ 'home.searchOverview.searchTypes.neuralSparseSearch.description',
+ {
+ defaultMessage:
+ 'Neural sparse search combines many of the advantages of Lexical and semantic search.',
+ }
+ ),
+ icon: searchIcon,
+ footer: (
+
+ View Documentation
+
+ ),
+ },
+ {
+ id: 'hybrid_search',
+ order: 10,
+ title: i18n.translate('home.searchOverview.searchTypes.hybridSearch.title', {
+ defaultMessage: 'Hybrid search',
+ }),
+ description: i18n.translate('home.searchOverview.searchTypes.hybridSearch.description', {
+ defaultMessage:
+ 'For many uses, lexical and semantic search are complementary: Lexical search performs better on highly specific queries, while semantic search performs better on broader queries. Hybrid search runs both search types and combines the results, generally producing better results than either one separately.',
+ }),
+ icon: searchIcon,
+ footer: (
+
+ View Documentation
+
+ ),
+ },
+ ];
+
+ searchTypeCards.forEach((card) => {
+ contentManagement.registerContentProvider({
+ id: card.id,
+ getContent: () => ({
+ id: card.id,
+ kind: 'card',
+ order: card.order,
+ title: card.title,
+ description: card.description,
+ getIcon: () => card.icon,
+ cardProps: {
+ children: {card.footer}
,
+ layout: 'horizontal',
+ },
+ }),
+ getTargetArea: () => SEARCH_OVERVIEW_CONTENT_AREAS.DIFFERENT_SEARCH_TYPES,
+ });
+ });
+};
diff --git a/src/plugins/home/public/application/index.ts b/src/plugins/home/public/application/index.ts
index 5bb49c2993d9..c61276a75f14 100644
--- a/src/plugins/home/public/application/index.ts
+++ b/src/plugins/home/public/application/index.ts
@@ -28,4 +28,8 @@
* under the License.
*/
-export { renderApp, renderImportSampleDataApp } from './application';
+export {
+ renderApp,
+ renderImportSampleDataApp,
+ renderSearchUseCaseOverviewApp,
+} from './application';
diff --git a/src/plugins/home/public/plugin.ts b/src/plugins/home/public/plugin.ts
index ac5f4c508821..59be17d70c1c 100644
--- a/src/plugins/home/public/plugin.ts
+++ b/src/plugins/home/public/plugin.ts
@@ -68,12 +68,18 @@ import { learnBasicsSection } from './application/components/homepage/sections/l
import {
ContentManagementPluginSetup,
ContentManagementPluginStart,
+ SEARCH_OVERVIEW_PAGE_ID,
} from '../../content_management/public';
import { initHome, setupHome } from './application/home_render';
import { registerSampleDataCard } from './application/components/sample_data/sample_data_card';
import { registerHomeListCardToPage } from './application/components/home_list_card';
import { toMountPoint } from '../../opensearch_dashboards_react/public';
import { HomeIcon } from './application/components/home_icon';
+import {
+ registerContentToSearchUseCasePage,
+ setupSearchUseCase,
+} from './application/components/usecase_overview/search_use_case_setup';
+import { DEFAULT_NAV_GROUPS } from '../../../core/public';
export interface HomePluginStartDependencies {
data: DataPublicPluginStart;
@@ -168,6 +174,36 @@ export class HomePublicPlugin
workspaceAvailability: WorkspaceAvailability.outsideWorkspace,
});
+ if (core.chrome.navGroup.getNavGroupEnabled()) {
+ // register search use case overview page
+ core.application.register({
+ id: SEARCH_OVERVIEW_PAGE_ID,
+ title: 'Overview',
+ mount: async (params: AppMountParameters) => {
+ const [
+ coreStart,
+ { contentManagement: contentManagementStart },
+ ] = await core.getStartServices();
+ setCommonService();
+
+ const { renderSearchUseCaseOverviewApp } = await import('./application');
+ return await renderSearchUseCaseOverviewApp(
+ params.element,
+ coreStart,
+ contentManagementStart
+ );
+ },
+ });
+
+ // add to search group
+ core.chrome.navGroup.addNavLinksToGroup(DEFAULT_NAV_GROUPS.search, [
+ {
+ id: SEARCH_OVERVIEW_PAGE_ID,
+ order: -1,
+ },
+ ]);
+ }
+
// Register import sample data as a standalone app so that it is available inside workspace.
core.application.register({
id: IMPORT_SAMPLE_DATA_APP_ID,
@@ -211,6 +247,7 @@ export class HomePublicPlugin
sectionTypes.registerSection(workWithDataSection);
sectionTypes.registerSection(learnBasicsSection);
setupHome(contentManagement);
+ setupSearchUseCase(contentManagement);
return {
featureCatalogue,
@@ -234,6 +271,7 @@ export class HomePublicPlugin
// register sample data card to use case overview page
registerSampleDataCard(contentManagement, core);
+ registerContentToSearchUseCasePage(contentManagement, core);
// register what's new learn opensearch card to use case overview page
registerHomeListCardToPage(contentManagement);
diff --git a/src/plugins/workspace/public/utils.test.ts b/src/plugins/workspace/public/utils.test.ts
index d744218a1aba..f58273822282 100644
--- a/src/plugins/workspace/public/utils.test.ts
+++ b/src/plugins/workspace/public/utils.test.ts
@@ -618,7 +618,7 @@ describe('workspace utils: prependWorkspaceToBreadcrumbs', () => {
expect(coreStart.chrome.setBreadcrumbsEnricher).not.toHaveBeenCalled();
});
- it('should enrich breadcrumbs when in a workspace and use workspace use case as current nav group', async () => {
+ it('should enrich breadcrumbs when in a workspace and add workspace name into breadcrumbs', async () => {
const navGroupSearch = {
id: 'search',
title: 'Search',
@@ -646,7 +646,7 @@ describe('workspace utils: prependWorkspaceToBreadcrumbs', () => {
const breadcrumbs = [{ text: 'test app' }];
let enrichedBreadcrumbs = enricher?.(breadcrumbs);
expect(enrichedBreadcrumbs).toHaveLength(3);
- expect(enrichedBreadcrumbs?.[1].text).toEqual('Search');
+ expect(enrichedBreadcrumbs?.[1].text).toEqual('test workspace 1');
// ignore current nav group
prependWorkspaceToBreadcrumbs(coreStart, workspace, 'app1', navGroupDashboards, {
@@ -661,7 +661,7 @@ describe('workspace utils: prependWorkspaceToBreadcrumbs', () => {
enrichedBreadcrumbs = enricher?.(breadcrumbs);
expect(enrichedBreadcrumbs).toHaveLength(3);
- expect(enrichedBreadcrumbs?.[1].text).toEqual('Search');
+ expect(enrichedBreadcrumbs?.[1].text).toEqual('test workspace 1');
});
it('should enrich breadcrumbs when in a workspace with all use case and use selected nav group', async () => {
diff --git a/src/plugins/workspace/public/utils.ts b/src/plugins/workspace/public/utils.ts
index 4d0ab311d7de..559e81c9408b 100644
--- a/src/plugins/workspace/public/utils.ts
+++ b/src/plugins/workspace/public/utils.ts
@@ -24,6 +24,7 @@ import {
WorkspaceObject,
WorkspaceAvailability,
} from '../../../core/public';
+
import { WORKSPACE_DETAIL_APP_ID, USE_CASE_PREFIX } from '../common/constants';
import { getUseCaseFeatureConfig } from '../common/utils';
import { WorkspaceUseCase, WorkspaceUseCaseFeature } from './types';
@@ -32,6 +33,9 @@ import { SigV4ServiceName } from '../../../plugins/data_source/common/data_sourc
import {
ANALYTICS_ALL_OVERVIEW_PAGE_ID,
ESSENTIAL_OVERVIEW_PAGE_ID,
+ OBSERVABILITY_OVERVIEW_PAGE_ID,
+ SEARCH_OVERVIEW_PAGE_ID,
+ SECURITY_ANALYTICS_OVERVIEW_PAGE_ID,
} from '../../../plugins/content_management/public';
export const isUseCaseFeatureConfig = (featureConfig: string) =>
@@ -327,15 +331,18 @@ export function prependWorkspaceToBreadcrumbs(
currentNavGroup: NavGroupItemInMap | undefined,
navGroupsMap: Record
) {
- if (
- appId === WORKSPACE_DETAIL_APP_ID ||
- appId === ESSENTIAL_OVERVIEW_PAGE_ID ||
- appId === ANALYTICS_ALL_OVERVIEW_PAGE_ID
- ) {
+ if (appId === WORKSPACE_DETAIL_APP_ID) {
core.chrome.setBreadcrumbsEnricher(undefined);
return;
}
+ const homeBreadcrumb: ChromeBreadcrumb = {
+ text: 'Home',
+ onClick: () => {
+ core.application.navigateToApp('home');
+ },
+ };
+
/**
* There has 3 cases
* nav group is enable + workspace enable + in a workspace -> workspace enricher
@@ -346,6 +353,20 @@ export function prependWorkspaceToBreadcrumbs(
* so we don't need to have reset logic for workspace
*/
if (currentWorkspace) {
+ // use case overview page only show workspace name
+ if (
+ appId === SEARCH_OVERVIEW_PAGE_ID ||
+ appId === OBSERVABILITY_OVERVIEW_PAGE_ID ||
+ appId === SECURITY_ANALYTICS_OVERVIEW_PAGE_ID ||
+ appId === ESSENTIAL_OVERVIEW_PAGE_ID ||
+ appId === ANALYTICS_ALL_OVERVIEW_PAGE_ID
+ ) {
+ core.chrome.setBreadcrumbsEnricher((breadcrumbs) => [
+ homeBreadcrumb,
+ { text: currentWorkspace.name },
+ ]);
+ return;
+ }
const useCase = getFirstUseCaseOfFeatureConfigs(currentWorkspace?.features || []);
// get workspace the only use case
if (useCase && useCase !== ALL_USE_CASE_ID) {
@@ -360,22 +381,20 @@ export function prependWorkspaceToBreadcrumbs(
}
},
};
- const homeBreadcrumb: ChromeBreadcrumb = {
- text: 'Home',
+
+ const workspaceBreadcrumb: ChromeBreadcrumb = {
+ text: currentWorkspace.name,
onClick: () => {
- core.application.navigateToApp('home');
+ if (useCase) {
+ const allNavGroups = navGroupsMap[useCase];
+ core.application.navigateToApp(allNavGroups?.navLinks[0].id);
+ }
},
};
core.chrome.setBreadcrumbsEnricher((breadcrumbs) => {
if (!breadcrumbs || !breadcrumbs.length) return breadcrumbs;
- const workspaceBreadcrumb: ChromeBreadcrumb = {
- text: currentWorkspace.name,
- onClick: () => {
- core.application.navigateToApp(WORKSPACE_DETAIL_APP_ID);
- },
- };
if (useCase === ALL_USE_CASE_ID) {
if (currentNavGroup && currentNavGroup.id !== DEFAULT_NAV_GROUPS.all.id) {
return [homeBreadcrumb, workspaceBreadcrumb, navGroupBreadcrumb, ...breadcrumbs];
@@ -383,7 +402,7 @@ export function prependWorkspaceToBreadcrumbs(
return [homeBreadcrumb, workspaceBreadcrumb, ...breadcrumbs];
}
} else {
- return [homeBreadcrumb, navGroupBreadcrumb, ...breadcrumbs];
+ return [homeBreadcrumb, workspaceBreadcrumb, ...breadcrumbs];
}
});
}