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]Essential/Analytics(All) use case overview page #7673

Merged
merged 29 commits into from
Aug 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
d9c4845
navigates to use case overview page
Hailong-am Aug 7, 2024
07348e1
Essential use case overviea page
Hailong-am Aug 9, 2024
e166b50
set breadcrumb for overview page
Hailong-am Aug 17, 2024
dcd72ef
remove width
Hailong-am Aug 20, 2024
d951e73
Analytics use case overview
Hailong-am Aug 20, 2024
ca271ff
add unit test
Hailong-am Aug 20, 2024
a427120
Changeset file for PR #7673 created/updated
opensearch-changeset-bot[bot] Aug 20, 2024
1a0fa6e
Merge branch 'main' into use_case_overview
Hailong-am Aug 21, 2024
e227863
add cardProps for card input
Hailong-am Aug 21, 2024
7a55afb
Merge remote-tracking branch 'upstream/main' into use_case_overview
Hailong-am Aug 21, 2024
c8d124e
remove repeat license header
Hailong-am Aug 21, 2024
c8b2ddb
navigates to use case overvie page instead of workspace detail
Hailong-am Aug 21, 2024
2de0ed3
Merge branch 'main' into use_case_overview
Hailong-am Aug 21, 2024
2cda491
Update src/plugins/content_management/public/components/card_containe…
Hailong-am Aug 21, 2024
5b0dea7
update page id to match pattern usecaseId_overview
Hailong-am Aug 21, 2024
9bbccfa
Merge branch 'main' into use_case_overview
Hailong-am Aug 22, 2024
6c57a4d
fix import sample data target area
Hailong-am Aug 22, 2024
5196017
workspace icon
Hailong-am Aug 22, 2024
3effa25
Merge remote-tracking branch 'upstream/main' into use_case_overview
Hailong-am Aug 23, 2024
ea292a9
move all content ids to content management plugin
Hailong-am Aug 23, 2024
a81d269
Merge branch 'main' into use_case_overview
Hailong-am Aug 23, 2024
a57a522
refactor whats_new and learn opensearch
Hailong-am Aug 23, 2024
531cf00
fix merge issue
Hailong-am Aug 23, 2024
3b3c534
Merge remote-tracking branch 'upstream/main' into use_case_overview
Hailong-am Aug 24, 2024
34b3442
fix merge issue
Hailong-am Aug 24, 2024
7bad053
Merge branch 'main' into use_case_overview
Hailong-am Aug 26, 2024
c5ca867
unify use case page content id
Hailong-am Aug 26, 2024
9c16d09
add unit test
Hailong-am Aug 26, 2024
9255b79
Merge branch 'main' into use_case_overview
Hailong-am Aug 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions changelogs/fragments/7673.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
feat:
- [Workspace]Essential/Analytics(All) use case overview page ([#7673](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7673))
4 changes: 4 additions & 0 deletions src/core/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ export {
cleanWorkspaceId,
DEFAULT_NAV_GROUPS,
ALL_USE_CASE_ID,
SEARCH_USE_CASE_ID,
ESSENTIAL_USE_CASE_ID,
OBSERVABILITY_USE_CASE_ID,
SECURITY_ANALYTICS_USE_CASE_ID,
} from '../utils';
export {
AppCategory,
Expand Down
17 changes: 13 additions & 4 deletions src/core/utils/default_nav_groups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import { i18n } from '@osd/i18n';
import { ChromeNavGroup, NavGroupType } from '../types';

export const ALL_USE_CASE_ID = 'all';
export const OBSERVABILITY_USE_CASE_ID = 'observability';
export const SECURITY_ANALYTICS_USE_CASE_ID = 'security-analytics';
export const ESSENTIAL_USE_CASE_ID = 'analytics';
export const SEARCH_USE_CASE_ID = 'search';

const defaultNavGroups = {
dataAdministration: {
Expand Down Expand Up @@ -40,9 +44,10 @@ const defaultNavGroups = {
defaultMessage: 'This is a use case contains all the features.',
}),
order: 3000,
icon: 'wsAnalytics',
},
observability: {
id: 'observability',
id: OBSERVABILITY_USE_CASE_ID,
title: i18n.translate('core.ui.group.observability.title', {
defaultMessage: 'Observability',
}),
Expand All @@ -51,9 +56,10 @@ const defaultNavGroups = {
'Gain visibility into system health, performance, and reliability through monitoring and analysis of logs, metrics, and traces.',
}),
order: 4000,
icon: 'wsObservability',
},
'security-analytics': {
id: 'security-analytics',
id: SECURITY_ANALYTICS_USE_CASE_ID,
title: i18n.translate('core.ui.group.security.analytics.title', {
defaultMessage: 'Security Analytics',
}),
Expand All @@ -62,9 +68,10 @@ const defaultNavGroups = {
'Detect and investigate potential security threats and vulnerabilities across your systems and data.',
}),
order: 5000,
icon: 'wsSecurityAnalytics',
},
essentials: {
id: 'analytics',
id: ESSENTIAL_USE_CASE_ID,
title: i18n.translate('core.ui.group.essential.title', {
defaultMessage: 'Essentials',
}),
Expand All @@ -73,9 +80,10 @@ const defaultNavGroups = {
'Analyze data to derive insights, identify patterns and trends, and make data-driven decisions.',
}),
order: 7000,
icon: 'wsEssentials',
},
search: {
id: 'search',
id: SEARCH_USE_CASE_ID,
title: i18n.translate('core.ui.group.search.title', {
defaultMessage: 'Search',
}),
Expand All @@ -84,6 +92,7 @@ const defaultNavGroups = {
"Quickly find and explore relevant information across your organization's data sources.",
}),
order: 6000,
icon: 'wsSearch',
},
} as const;

Expand Down
9 changes: 8 additions & 1 deletion src/core/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,11 @@ export {
export { DEFAULT_APP_CATEGORIES } from './default_app_categories';
export { WORKSPACE_PATH_PREFIX, WORKSPACE_TYPE } from './constants';
export { getWorkspaceIdFromUrl, formatUrlWithWorkspaceId, cleanWorkspaceId } from './workspace';
export { DEFAULT_NAV_GROUPS, ALL_USE_CASE_ID } from './default_nav_groups';
export {
DEFAULT_NAV_GROUPS,
ALL_USE_CASE_ID,
SEARCH_USE_CASE_ID,
ESSENTIAL_USE_CASE_ID,
OBSERVABILITY_USE_CASE_ID,
SECURITY_ANALYTICS_USE_CASE_ID,
} from './default_nav_groups';
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,34 @@ test('CardEmbeddable should render a card with the title', () => {
Array.from(node.querySelectorAll('*')).find((ele) => ele.textContent?.trim() === 'card title')
).toBeFalsy();
});

test('CardEmbeddable should render a card with the cardProps', () => {
const embeddable = new CardEmbeddable({
id: 'card-id',
title: 'card title',
description: '',
cardProps: {
selectable: {
children: 'selectable line',
onSelect: () => {},
},
},
});

const node = document.createElement('div');
embeddable.render(node);

// it should render the card with title specified
expect(
Array.from(node.querySelectorAll('*')).find(
(ele) => ele.textContent?.trim() === 'selectable line'
)
).toBeTruthy();

embeddable.destroy();
expect(
Array.from(node.querySelectorAll('*')).find(
(ele) => ele.textContent?.trim() === 'selectable line'
)
).toBeFalsy();
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import React from 'react';
import ReactDOM from 'react-dom';
import { EuiCard } from '@elastic/eui';
import { EuiCard, EuiCardProps } from '@elastic/eui';

import { Embeddable, EmbeddableInput, IContainer } from '../../../../embeddable/public';

Expand All @@ -15,6 +15,7 @@ export type CardEmbeddableInput = EmbeddableInput & {
onClick?: () => void;
getIcon?: () => React.ReactElement;
getFooter?: () => React.ReactElement;
cardProps?: Omit<EuiCardProps, 'title' | 'description'>;
};

export class CardEmbeddable extends Embeddable<CardEmbeddableInput> {
Expand All @@ -30,18 +31,21 @@ export class CardEmbeddable extends Embeddable<CardEmbeddableInput> {
ReactDOM.unmountComponentAtNode(this.node);
}
this.node = node;
ReactDOM.render(
<EuiCard
textAlign="left"
title={this.input.title ?? ''}
description={this.input.description}
display="plain"
onClick={this.input.onClick}
icon={this.input?.getIcon?.()}
footer={this.input?.getFooter?.()}
/>,
node
);

const cardProps: EuiCardProps = {
...this.input.cardProps,
title: this.input.title ?? '',
description: this.input.description,
onClick: this.input.onClick,
icon: this.input?.getIcon?.(),
};

if (!cardProps.layout || cardProps.layout === 'vertical') {
cardProps.textAlign = 'left';
cardProps.footer = this.input?.getFooter?.();
}

ReactDOM.render(<EuiCard {...cardProps} />, node);
}

public destroy() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,12 @@ const CardListInner = ({ embeddable, input, embeddableServices }: Props) => {
const child = embeddable.getChild(panel.explicitInput.id);
return (
<EuiFlexItem key={panel.explicitInput.id}>
<embeddableServices.EmbeddablePanel embeddable={child} />
<embeddableServices.EmbeddablePanel
embeddable={child}
hideHeader
hasBorder={false}
hasShadow={false}
/>
</EuiFlexItem>
);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { EuiCardProps } from '@elastic/eui';
import { ContainerInput } from '../../../../embeddable/public';

export interface CardExplicitInput {
Expand All @@ -11,6 +12,7 @@ export interface CardExplicitInput {
onClick?: () => void;
getIcon?: () => React.ReactElement;
getFooter?: () => React.ReactElement;
cardProps?: Omit<EuiCardProps, 'title' | 'description'>;
}

export type CardContainerInput = ContainerInput<CardExplicitInput> & {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export const createCardInput = (
onClick: content.onClick,
getIcon: content?.getIcon,
getFooter: content?.getFooter,
cardProps: content.cardProps,
},
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,12 @@ const DashboardSection = ({ section, embeddable, contents$, savedObjectsClient }

if (section.kind === 'dashboard' && factory && input) {
// const input = createDashboardSection(section, contents ?? []);
return <EmbeddableRenderer factory={factory} input={input} />;
return (
// to make dashboard section align with others add margin left and right -8px
<div style={{ margin: '0 -8px' }}>
<EmbeddableRenderer factory={factory} input={input} />
</div>
);
}

return null;
Expand All @@ -61,18 +66,20 @@ const CardSection = ({ section, embeddable, contents$ }: Props) => {

if (section.kind === 'card' && factory && input) {
return (
<EuiPanel>
<EuiTitle size="s">
<h2>
<EuiButtonIcon
iconType={isCardVisible ? 'arrowDown' : 'arrowUp'}
onClick={toggleCardVisibility}
color="text"
aria-label={isCardVisible ? 'Show panel' : 'Hide panel'}
/>
{section.title}
</h2>
</EuiTitle>
<EuiPanel paddingSize="none" hasBorder={false} hasShadow={false} color="transparent">
{section.title ? (
<EuiTitle size="s">
<h2>
<EuiButtonIcon
iconType={isCardVisible ? 'arrowDown' : 'arrowUp'}
onClick={toggleCardVisibility}
color="text"
aria-label={isCardVisible ? 'Show panel' : 'Hide panel'}
/>
{section.title}
</h2>
</EuiTitle>
) : null}
{isCardVisible && (
<>
<EuiSpacer size="m" /> <EmbeddableRenderer factory={factory} input={input} />
Expand Down
55 changes: 55 additions & 0 deletions src/plugins/content_management/public/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import {
ESSENTIAL_USE_CASE_ID,
ALL_USE_CASE_ID,
SEARCH_USE_CASE_ID,
OBSERVABILITY_USE_CASE_ID,
SECURITY_ANALYTICS_USE_CASE_ID,
} from '../../../core/public';

// central place for all content ids rendered by content management

// page ids
export const ANALYTICS_ALL_OVERVIEW_PAGE_ID = `${ALL_USE_CASE_ID}_overview`;
export const ESSENTIAL_OVERVIEW_PAGE_ID = `${ESSENTIAL_USE_CASE_ID}_overview`;
export const SEARCH_OVERVIEW_PAGE_ID = `${SEARCH_USE_CASE_ID}_overview`;
export const OBSERVABILITY_OVERVIEW_PAGE_ID = `${OBSERVABILITY_USE_CASE_ID}_overview`;
export const SECURITY_ANALYTICS_OVERVIEW_PAGE_ID = `${SECURITY_ANALYTICS_USE_CASE_ID}_overview`;
export const HOME_PAGE_ID = 'osd_homepage';

// section ids
export enum SECTIONS {
GET_STARTED = `get_started`,
SERVICE_CARDS = `service_cards`,
RECENTLY_VIEWED = `recently_viewed`,
DIFFERENT_SEARCH_TYPES = 'different_search_types',
CONFIG_EVALUATE_SEARCH = 'config_evaluate_search',
}

export enum HOME_CONTENT_AREAS {
GET_STARTED = `${HOME_PAGE_ID}/${SECTIONS.GET_STARTED}`,
SERVICE_CARDS = `${HOME_PAGE_ID}/${SECTIONS.SERVICE_CARDS}`,
RECENTLY_VIEWED = `${HOME_PAGE_ID}/${SECTIONS.RECENTLY_VIEWED}`,
}

export enum ESSENTIAL_OVERVIEW_CONTENT_AREAS {
GET_STARTED = `${ESSENTIAL_OVERVIEW_PAGE_ID}/${SECTIONS.GET_STARTED}`,
SERVICE_CARDS = `${ESSENTIAL_OVERVIEW_PAGE_ID}/${SECTIONS.SERVICE_CARDS}`,
RECENTLY_VIEWED = `${ESSENTIAL_OVERVIEW_PAGE_ID}/${SECTIONS.RECENTLY_VIEWED}`,
}

export enum ANALYTICS_ALL_OVERVIEW_CONTENT_AREAS {
GET_STARTED = `${ANALYTICS_ALL_OVERVIEW_PAGE_ID}/${SECTIONS.GET_STARTED}`,
SERVICE_CARDS = `${ANALYTICS_ALL_OVERVIEW_PAGE_ID}/${SECTIONS.SERVICE_CARDS}`,
RECENTLY_VIEWED = `${ANALYTICS_ALL_OVERVIEW_PAGE_ID}/${SECTIONS.RECENTLY_VIEWED}`,
}

export enum SEARCH_OVERVIEW_CONTENT_AREAS {
DIFFERENT_SEARCH_TYPES = `${SEARCH_OVERVIEW_PAGE_ID}/${SECTIONS.DIFFERENT_SEARCH_TYPES}`,
CONFIG_EVALUATE_SEARCH = `${SEARCH_OVERVIEW_PAGE_ID}/${SECTIONS.CONFIG_EVALUATE_SEARCH}`,
GET_STARTED = `${SEARCH_OVERVIEW_PAGE_ID}/${SECTIONS.GET_STARTED}`,
}
2 changes: 2 additions & 0 deletions src/plugins/content_management/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ export const plugin = (initializerContext: PluginInitializerContext) =>

export * from './components';
export * from './mocks';
export * from './services/content_management';
export * from './constants';
1 change: 1 addition & 0 deletions src/plugins/content_management/public/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const createStartContract = (): ContentManagementPluginStart => {
return {
registerContentProvider: jest.fn(),
renderPage: jest.fn(),
updatePageSection: jest.fn(),
};
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,29 @@ test('it register content provider', () => {
expect(cms.getPage('page1')?.getContents('section1')).toHaveLength(1);
});

test('it register content provider to multiple destination', () => {
const cms = new ContentManagementService();
cms.registerPage({ id: 'page1', sections: [{ id: 'section1', kind: 'card', order: 0 }] });
cms.registerPage({ id: 'page2', sections: [{ id: 'section1', kind: 'card', order: 0 }] });
cms.registerContentProvider({
id: 'content_provider1',
getTargetArea() {
return ['page1/section1', 'page2/section1'];
},
getContent() {
return {
kind: 'card',
id: 'content1',
title: 'card',
description: 'descriptions',
order: 0,
};
},
});
expect(cms.getPage('page1')?.getContents('section1')).toHaveLength(1);
expect(cms.getPage('page2')?.getContents('section1')).toHaveLength(1);
});

test('it should throw error when register content provider with invalid target area', () => {
const cms = new ContentManagementService();
cms.registerPage({ id: 'page1', sections: [{ id: 'section1', kind: 'card', order: 0 }] });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,19 @@ export class ContentManagementService {
registerContentProvider = (provider: ContentProvider) => {
this.contentProviders.set(provider.id, provider);

const targetArea = provider.getTargetArea();
const [pageId, sectionId] = targetArea.split('/');

if (!pageId || !sectionId) {
throw new Error('getTargetArea() should return a string in format {pageId}/{sectionId}');
}

const page = this.getPage(pageId);
if (page) {
page.addContent(sectionId, provider.getContent());
const area = provider.getTargetArea();
const targetAreas: string[] = Array.isArray(area) ? [...area] : [area];
for (const targetArea of targetAreas) {
const [pageId, sectionId] = targetArea.split('/');

if (!pageId || !sectionId) {
throw new Error('getTargetArea() should return a string in format {pageId}/{sectionId}');
}

const page = this.getPage(pageId);
if (page) {
page.addContent(sectionId, provider.getContent());
}
}
};

Expand Down
Loading
Loading