Skip to content

Commit

Permalink
Merge branch 'main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
Hailong-am authored Aug 27, 2024
2 parents 9395322 + 6931a41 commit 6563e77
Show file tree
Hide file tree
Showing 39 changed files with 1,109 additions and 138 deletions.
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
33 changes: 20 additions & 13 deletions src/plugins/content_management/public/components/section_render.tsx
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

0 comments on commit 6563e77

Please sign in to comment.