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

[Backport 2.x] [WorkSpace] Refractor homepage assets list section #7709

Merged
merged 1 commit into from
Aug 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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/7702.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
feat:
- Refractor the homepage assets list section ([#7702](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7702))
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { RecentWork } from './recent_work';
import { coreMock } from '../../../../core/public/mocks';
import { ChromeRecentlyAccessedHistoryItem } from 'opensearch-dashboards/public';
import { SavedObjectWithMetadata } from '../types';
import { APP_ID } from '../plugin';

const mockedRecentItems: ChromeRecentlyAccessedHistoryItem[] = [
{
Expand Down Expand Up @@ -60,7 +61,7 @@ const getStartMockForRecentWork = () => {
describe('<RecentWork />', () => {
it('render with emty recent work', async () => {
const { findByText } = render(<RecentWork core={getStartMockForRecentWork()} />);
await findByText('No recent work');
await findByText('No assets found');
});

it('render with recent works', async () => {
Expand All @@ -85,14 +86,27 @@ describe('<RecentWork />', () => {
const allCards = await findAllByTestId('recentlyCard');
expect(allCards.length).toBe(2);
expect(allCards[0].querySelector('.euiCard__titleAnchor')?.textContent).toEqual(
mockedRecentItems[0].label
mockedRecentItems[0].label.charAt(0).toUpperCase() + mockedRecentItems[0].label.slice(1)
);

// click the filter button
fireEvent.click(getByTestId('filterButton-recently%20updated'));
fireEvent.click(getByTestId('filterButton-Recently%20updated'));
const allCardsAfterSort = await findAllByTestId('recentlyCard');
expect(allCardsAfterSort[0].querySelector('.euiCard__titleAnchor')?.textContent).toEqual(
mockedRecentItems[1].label
mockedRecentItems[1].label.charAt(0).toUpperCase() + mockedRecentItems[1].label.slice(1)
);
});

it('should be able to show view all button', () => {
const { getByText } = render(<RecentWork core={getStartMockForRecentWork()} />);
expect(getByText('View all')).toBeInTheDocument();
});

it('shoule be able to be linked to the expected page when clicking View all button', () => {
const coreStartMock = getStartMockForRecentWork();
const { getByText } = render(<RecentWork core={coreStartMock} />);
const mockedViewAllButton = getByText('View all');
fireEvent.click(mockedViewAllButton);
expect(coreStartMock.application.navigateToApp).toHaveBeenCalledWith(APP_ID);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,18 @@ import {
EuiFlexItem,
EuiCard,
EuiPanel,
EuiSpacer,
EuiFlexGrid,
EuiFlexGroup,
EuiTitle,
EuiFilterGroup,
EuiFilterButton,
EuiComboBox,
EuiIcon,
EuiLink,
EuiEmptyPrompt,
EuiToolTip,
EuiSpacer,
EuiText,
} from '@elastic/eui';
import { i18n } from '@osd/i18n';
import {
Expand All @@ -26,18 +30,19 @@ import {
} from 'opensearch-dashboards/public';
import { useObservable } from 'react-use';
import { SavedObjectWithMetadata } from 'src/plugins/saved_objects_management/common';
import { APP_ID } from '../plugin';
import { createRecentNavLink } from '../../../../core/public';

const allOption = i18n.translate('savedObjectsManagement.recentWorkSection.all.items', {
defaultMessage: 'all items',
defaultMessage: 'All items',
});

const recentlyViewed = i18n.translate('savedObjectsManagement.recentWorkSection.recentlyViewed', {
defaultMessage: 'recently viewed',
defaultMessage: 'Recently viewed',
});

const recentlyUpdated = i18n.translate('savedObjectsManagement.recentWorkSection.recentlyUpdated', {
defaultMessage: 'recently updated',
defaultMessage: 'Recently updated',
});

const sortKeyMap = {
Expand Down Expand Up @@ -102,14 +107,18 @@ export const RecentWork = (props: { core: CoreStart; workspaceEnabled?: boolean
const options: string[] = [allOption];
detailedSavedObjects
.filter((item) => !item.error)
.forEach((recentAccessItem: ChromeRecentlyAccessedHistoryItem) => {
if (recentAccessItem.meta?.type && options.indexOf(recentAccessItem.meta.type) === -1) {
options.push(recentAccessItem.meta.type);
.forEach((recentAccessItem) => {
if (recentAccessItem?.type && options.indexOf(recentAccessItem?.type) === -1) {
options.push(recentAccessItem?.type);
}
});
return options.map((option: string) => ({ label: option, value: option }));
}, [detailedSavedObjects]);

const capitalTheFirstLetter = function (recentAccessItem: DetailedRecentlyAccessedItem) {
return recentAccessItem.type.charAt(0).toUpperCase() + recentAccessItem.type.slice(1);
};

const itemsForDisplay = useMemo(() => {
const sortedResult = [...detailedSavedObjects]
.filter((item) => !item.error)
Expand Down Expand Up @@ -156,22 +165,39 @@ export const RecentWork = (props: { core: CoreStart; workspaceEnabled?: boolean
<EuiPanel>
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem>
<EuiTitle>
<h5>
{i18n.translate('savedObjectsManagement.recentWorkSection.title', {
defaultMessage: 'Assets',
})}
</h5>
</EuiTitle>
<EuiFlexGroup justifyContent="flexStart" alignItems="center" gutterSize="xs">
<EuiFlexItem grow={false}>
<EuiTitle>
<h3>
{i18n.translate('savedObjectsManagement.recentWorkSection.title', {
defaultMessage: 'Assets',
})}
</h3>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiToolTip
display="inlineBlock"
position="right"
content={i18n.translate('savedObjectsManagement.recentWorkSection.assetsInfo', {
defaultMessage:
'Dashboards, visualizations, saved queries, and other assets within your Worksapces.',
})}
data-test-subj="assetsTooltip"
>
<EuiIcon type="iInCircle" />
</EuiToolTip>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFlexGroup>
<EuiFlexItem>
<EuiFilterGroup>
{[recentlyViewed, recentlyUpdated].map((item) => (
<EuiFilterButton
hasActiveFilters={selectedSort === item}
key={item}
hasActiveFilters={item === selectedSort}
onClick={() => setSelectedSort(item)}
data-test-subj={`filterButton-${encodeURIComponent(item)}`}
>
Expand Down Expand Up @@ -208,47 +234,80 @@ export const RecentWork = (props: { core: CoreStart; workspaceEnabled?: boolean
core.http.basePath,
core.application.navigateToUrl
);

content = (
<EuiCard
title={recentAccessItem.label}
titleSize="xs"
title={
<EuiFlexGroup justifyContent="flexStart" alignItems="center" gutterSize="none">
<EuiFlexItem grow={false}>
<EuiIcon
style={{ marginRight: widthForRightMargin }}
type={recentAccessItem.meta.icon || 'apps'}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="xs" color="subdued">
{capitalTheFirstLetter(recentAccessItem)}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
}
data-test-subj="recentlyCard"
description=""
description={<h3>{recentAccessItem.label}</h3>}
textAlign="left"
href={recentNavLink.href}
footer={
<>
<div>
<EuiIcon
style={{ marginRight: widthForRightMargin }}
type={recentAccessItem.meta.icon || 'apps'}
/>
{recentAccessItem.type}
</div>
<EuiSpacer size="s" />
<div>
{selectedSort === recentlyViewed
? i18n.translate('savedObjectsManagement.recentWorkSection.viewedAt', {
defaultMessage: 'Viewed',
})
: i18n.translate('savedObjectsManagement.recentWorkSection.updatedAt', {
defaultMessage: 'Updated',
})}
:{' '}
<b>
{selectedSort === recentlyViewed
? moment(recentAccessItem?.lastAccessedTime).fromNow()
: moment(recentAccessItem?.updatedAt).fromNow()}
</b>
</div>
{workspaceEnabled && (
<div>
{i18n.translate('savedObjectsManagement.recentWorkSection.workspace', {
defaultMessage: 'Workspace',
})}
: <b>{recentAccessItem.workspaceName || 'N/A'}</b>
</div>
)}
<EuiFlexGrid columns={2} gutterSize="s">
<EuiFlexItem grow={false}>
<EuiText size="xs" color="default">
{selectedSort === recentlyViewed
? i18n.translate(
'savedObjectsManagement.recentWorkSection.viewedAt',
{
defaultMessage: 'Viewed',
}
)
: i18n.translate(
'savedObjectsManagement.recentWorkSection.updatedAt',
{
defaultMessage: 'Updated',
}
)}
:{' '}
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={1} style={{ textAlign: 'right' }}>
<EuiText size="xs" color="default">
<b>
{selectedSort === recentlyViewed
? moment(recentAccessItem?.lastAccessedTime).fromNow()
: moment(recentAccessItem?.updatedAt).fromNow()}
</b>
</EuiText>
</EuiFlexItem>

{workspaceEnabled && (
<>
<EuiFlexItem grow={false}>
<EuiText size="xs" color="default">
{i18n.translate(
'savedObjectsManagement.recentWorkSection.workspace',
{
defaultMessage: 'Workspace',
}
)}
:
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={1} style={{ textAlign: 'right' }}>
<EuiText size="xs" color="default">
<b>{recentAccessItem.workspaceName || 'N/A'} </b>
</EuiText>
</EuiFlexItem>
</>
)}
</EuiFlexGrid>
</>
}
onClick={recentNavLink.onClick}
Expand All @@ -262,15 +321,27 @@ export const RecentWork = (props: { core: CoreStart; workspaceEnabled?: boolean
</EuiFlexGroup>
) : (
<EuiEmptyPrompt
icon={<EuiIcon size="l" type="layers" />}
title={
<h2>
{i18n.translate('savedObjectsManagement.recentWorkSection.empty.title', {
defaultMessage: 'No recent work',
defaultMessage: 'No assets found',
})}
</h2>
}
body={i18n.translate('savedObjectsManagement.recentWorkSection.empty.body', {
defaultMessage: "Assets you've recently viewed or updated will appear here.",
})}
/>
)}
<EuiSpacer size="m" />
<EuiLink target="_blank" onClick={() => core.application.navigateToApp(APP_ID)}>
<EuiText size="s" className="eui-displayInline">
{i18n.translate('home.list.card.view_all', {
defaultMessage: 'View all',
})}
</EuiText>
</EuiLink>
</EuiPanel>
);
};
18 changes: 10 additions & 8 deletions src/plugins/saved_objects_management/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ import { RecentWork } from './management_section/recent_work';
import { HOME_CONTENT_AREAS } from '../../../plugins/home/public';
import { getScopedBreadcrumbs } from '../../opensearch_dashboards_react/public';

export const APP_ID = 'objects';

export interface SavedObjectsManagementPluginSetup {
actions: SavedObjectsManagementActionServiceSetup;
columns: SavedObjectsManagementColumnServiceSetup;
Expand Down Expand Up @@ -141,7 +143,7 @@ export class SavedObjectsManagementPlugin

const opensearchDashboardsSection = management.sections.section.opensearchDashboards;
opensearchDashboardsSection.registerApp({
id: 'objects',
id: APP_ID,
title: i18n.translate('savedObjectsManagement.managementSectionLabel', {
defaultMessage: 'Saved objects',
}),
Expand All @@ -160,7 +162,7 @@ export class SavedObjectsManagementPlugin

if (core.chrome.navGroup.getNavGroupEnabled()) {
core.application.register({
id: 'objects',
id: APP_ID,
title: i18n.translate('savedObjectsManagement.assets.label', {
defaultMessage: 'Assets',
}),
Expand All @@ -187,46 +189,46 @@ export class SavedObjectsManagementPlugin

core.chrome.navGroup.addNavLinksToGroup(DEFAULT_NAV_GROUPS.settingsAndSetup, [
{
id: 'objects',
id: APP_ID,
order: 300,
},
]);

core.chrome.navGroup.addNavLinksToGroup(DEFAULT_NAV_GROUPS.observability, [
{
id: 'objects',
id: APP_ID,
category: DEFAULT_APP_CATEGORIES.manage,
order: 300,
},
]);

core.chrome.navGroup.addNavLinksToGroup(DEFAULT_NAV_GROUPS.search, [
{
id: 'objects',
id: APP_ID,
category: DEFAULT_APP_CATEGORIES.manage,
order: 300,
},
]);

core.chrome.navGroup.addNavLinksToGroup(DEFAULT_NAV_GROUPS['security-analytics'], [
{
id: 'objects',
id: APP_ID,
category: DEFAULT_APP_CATEGORIES.manage,
order: 300,
},
]);

core.chrome.navGroup.addNavLinksToGroup(DEFAULT_NAV_GROUPS.essentials, [
{
id: 'objects',
id: APP_ID,
category: DEFAULT_APP_CATEGORIES.manage,
order: 300,
},
]);

core.chrome.navGroup.addNavLinksToGroup(DEFAULT_NAV_GROUPS.all, [
{
id: 'objects',
id: APP_ID,
category: DEFAULT_APP_CATEGORIES.manage,
order: 300,
},
Expand Down
Loading