Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Workspace] search use case overview page #7877

Merged
merged 14 commits into from
Sep 3, 2024
2 changes: 2 additions & 0 deletions changelogs/fragments/7877.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
feat:
- [Workspace] Add search use case overview page ([#7877](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7877))
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ test('render list of cards', () => {
jest
.spyOn(embeddableStart, 'EmbeddablePanel')
.mockImplementation(() => <span>CardEmbeddablePanel</span>);
render(
const { container, queryAllByText } = render(
<CardList
embeddableServices={embeddableStart}
embeddable={
Expand All @@ -37,5 +37,37 @@ test('render list of cards', () => {
}
/>
);
expect(screen.queryAllByText('CardEmbeddablePanel')).toHaveLength(2);
expect(queryAllByText('CardEmbeddablePanel')).toHaveLength(2);
// verify container has div with class euiFlexGroup
expect(container.querySelector('.euiFlexGroup')).toBeInTheDocument();
});

test('render list of cards with grid', () => {
const embeddableStart = embeddablePluginMock.createStartContract();
jest
.spyOn(embeddableStart, 'EmbeddablePanel')
.mockImplementation(() => <span>CardEmbeddablePanel</span>);
const { container } = render(
<CardList
embeddableServices={embeddableStart}
embeddable={
new CardContainer(
{
id: 'card',
panels: {
'card-id-1': { type: CARD_EMBEDDABLE, explicitInput: { id: 'card-id-1' } },
'card-id-2': { type: CARD_EMBEDDABLE, explicitInput: { id: 'card-id-2' } },
'card-id-3': { type: CARD_EMBEDDABLE, explicitInput: { id: 'card-id-3' } },
'card-id-4': { type: CARD_EMBEDDABLE, explicitInput: { id: 'card-id-4' } },
},
grid: true,
columns: 2,
},
embeddableStart
)
}
/>
);
// verify container has div with class euiFlexGrid
expect(container.querySelector('.euiFlexGrid')).toBeInTheDocument();
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
*/

import React from 'react';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { EuiFlexGrid, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';

import { FlexGridColumns } from '@elastic/eui/src/components/flex/flex_grid';
import {
IContainer,
withEmbeddableSubscription,
Expand All @@ -22,13 +23,18 @@ interface Props {
}

const CardListInner = ({ embeddable, input, embeddableServices }: Props) => {
if (input.columns) {
if (input.columns && !input.grid) {
const width = `${(1 / input.columns) * 100}%`;
const cards = Object.values(input.panels).map((panel) => {
const child = embeddable.getChild(panel.explicitInput.id);
return (
<EuiFlexItem key={panel.explicitInput.id} style={{ minWidth: `calc(${width} - 8px)` }}>
<embeddableServices.EmbeddablePanel embeddable={child} />
<embeddableServices.EmbeddablePanel
embeddable={child}
hideHeader
hasBorder={false}
hasShadow={false}
/>
</EuiFlexItem>
);
});
Expand Down Expand Up @@ -57,6 +63,14 @@ const CardListInner = ({ embeddable, input, embeddableServices }: Props) => {
);
});

if (input.grid && input.columns) {
return (
<EuiFlexGrid columns={input.columns as FlexGridColumns} gutterSize="s">
{cards}
</EuiFlexGrid>
);
}

return (
<EuiFlexGroup wrap={input.wrap} gutterSize="s">
{cards}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface CardExplicitInput {
export type CardContainerInput = ContainerInput<CardExplicitInput> & {
columns?: number;
wrap?: boolean;
grid?: boolean;
};

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export const createCardInput = (
viewMode: ViewMode.VIEW,
columns: section.columns,
wrap: section.wrap,
grid: section.grid,
panels,
...section.input,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export type Section =
input?: CardContainerExplicitInput;
columns?: number;
wrap?: boolean;
grid?: boolean;
};

export type Content =
Expand Down
19 changes: 19 additions & 0 deletions src/plugins/home/public/application/application.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
import { getServices } from './opensearch_dashboards_services';

import './index.scss';
import { ContentManagementPluginStart } from '../../../../plugins/content_management/public';
import { SearchUseCaseOverviewApp } from './components/usecase_overview/search_use_case_app';

export const renderApp = async (
element: HTMLElement,
Expand Down Expand Up @@ -90,3 +92,20 @@
unmountComponentAtNode(element);
};
};

export const renderSearchUseCaseOverviewApp = async (
element: HTMLElement,
coreStart: CoreStart,
contentManagementStart: ContentManagementPluginStart
) => {
render(

Check warning on line 101 in src/plugins/home/public/application/application.tsx

View check run for this annotation

Codecov / codecov/patch

src/plugins/home/public/application/application.tsx#L101

Added line #L101 was not covered by tests
<OpenSearchDashboardsContextProvider services={{ ...coreStart }}>
<SearchUseCaseOverviewApp contentManagement={contentManagementStart} />
</OpenSearchDashboardsContextProvider>,
element
);

return () => {
unmountComponentAtNode(element);

Check warning on line 109 in src/plugins/home/public/application/application.tsx

View check run for this annotation

Codecov / codecov/patch

src/plugins/home/public/application/application.tsx#L108-L109

Added lines #L108 - L109 were not covered by tests
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ describe('Sample data card', () => {
it('should call the getTargetArea function with the correct arguments', () => {
registerSampleDataCard(contentManagement, coreStart);
const call = registerContentProviderMock.mock.calls[0];
expect(call[0].getTargetArea()).toEqual(['essentials_overview/get_started']);
expect(call[0].getTargetArea()).toEqual('essentials_overview/get_started');
expect(call[0].getContent()).toMatchInlineSnapshot(`
Object {
"cardProps": Object {
Expand All @@ -39,5 +39,10 @@ describe('Sample data card', () => {
"title": "Try openSearch",
}
`);

// search use case overview
expect(registerContentProviderMock.mock.calls[1][0].getTargetArea()).toEqual(
'search_overview/get_started'
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,23 @@ import { EuiI18n } from '@elastic/eui';
import { i18n } from '@osd/i18n';
import {
ContentManagementPluginStart,
ContentProvider,
ESSENTIAL_OVERVIEW_CONTENT_AREAS,
SEARCH_OVERVIEW_CONTENT_AREAS,
} from '../../../../../content_management/public';
import { IMPORT_SAMPLE_DATA_APP_ID } from '../../../../common/constants';

export const registerSampleDataCard = (
contentManagement: ContentManagementPluginStart,
core: CoreStart
) => {
contentManagement.registerContentProvider({
id: `get_start_sample_data`,
getTargetArea: () => [ESSENTIAL_OVERVIEW_CONTENT_AREAS.GET_STARTED],
const sampleDataCard = (order: number, targetArea: string): ContentProvider => ({
id: `get_start_sample_data_${targetArea}`,
getTargetArea: () => targetArea,
getContent: () => ({
id: 'sample_data',
kind: 'card',
order: 0,
order,
description: i18n.translate('home.sampleData.card.description', {
defaultMessage: 'You can install sample data to experiment with OpenSearch Dashboards.',
}),
Expand All @@ -42,4 +44,11 @@ export const registerSampleDataCard = (
},
}),
});

contentManagement.registerContentProvider(
sampleDataCard(0, ESSENTIAL_OVERVIEW_CONTENT_AREAS.GET_STARTED)
);
contentManagement.registerContentProvider(
sampleDataCard(30, SEARCH_OVERVIEW_CONTENT_AREAS.GET_STARTED)
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { render } from '@testing-library/react';
import React from 'react';
import { coreMock } from '../../../../../../core/public/mocks';
import { OpenSearchDashboardsContextProvider } from '../../../../../opensearch_dashboards_react/public';
import { contentManagementPluginMocks } from '../../../../../content_management/public/mocks';
import { SearchUseCaseOverviewApp } from './search_use_case_app';
import { ContentManagementPluginStart } from '../../../../../content_management/public';
import { BehaviorSubject } from 'rxjs';
import { WorkspaceObject } from 'opensearch-dashboards/public';

describe('<SearchUseCaseOverviewApp />', () => {
const renderPageMock = jest.fn();
renderPageMock.mockReturnValue('dummy page');
const mock = {
...contentManagementPluginMocks.createStartContract(),
renderPage: renderPageMock,
};
const coreStartMocks = coreMock.createStart();

function renderSearchUseCaseOverviewApp(
contentManagement: ContentManagementPluginStart,
services = { ...coreStartMocks }
) {
return (
<OpenSearchDashboardsContextProvider services={services}>
<SearchUseCaseOverviewApp contentManagement={contentManagement} />
</OpenSearchDashboardsContextProvider>
);
}

beforeEach(() => {
jest.clearAllMocks();
});

it('render for workspace disabled case', () => {
const { container } = render(renderSearchUseCaseOverviewApp(mock, coreStartMocks));

expect(container).toMatchInlineSnapshot(`
<div>
dummy page
</div>
`);

expect(coreStartMocks.chrome.setBreadcrumbs).toHaveBeenCalledWith([
{ text: 'Search overview' },
]);
expect(mock.renderPage).toBeCalledWith('search_overview');
});

it('render for workspace enabled case', () => {
const coreStartMocksWithWorkspace = {
...coreStartMocks,
workspaces: {
...coreStartMocks.workspaces,
currentWorkspace$: new BehaviorSubject({
id: 'foo',
name: 'foo ws',
}),
},
};

const { container } = render(renderSearchUseCaseOverviewApp(mock, coreStartMocksWithWorkspace));

expect(container).toMatchInlineSnapshot(`
<div>
dummy page
</div>
`);

expect(coreStartMocks.chrome.setBreadcrumbs).toHaveBeenCalledWith([{ text: 'foo ws' }]);
expect(mock.renderPage).toBeCalledWith('search_overview');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React, { useEffect } from 'react';
import { useObservable } from 'react-use';
import { CoreStart } from 'opensearch-dashboards/public';
import { EuiBreadcrumb } from '@elastic/eui';
import { I18nProvider } from '@osd/i18n/react';
import { i18n } from '@osd/i18n';
import { useOpenSearchDashboards } from '../../../../../opensearch_dashboards_react/public';
import {
ContentManagementPluginStart,
SEARCH_OVERVIEW_PAGE_ID,
} from '../../../../../content_management/public';

interface Props {
contentManagement: ContentManagementPluginStart;
}

export const SearchUseCaseOverviewApp = ({ contentManagement }: Props) => {
const {
services: { workspaces, chrome },
} = useOpenSearchDashboards<CoreStart>();

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 (
<I18nProvider>
{contentManagement ? contentManagement.renderPage(SEARCH_OVERVIEW_PAGE_ID) : null}
</I18nProvider>
);
};
Loading
Loading