Skip to content

Commit

Permalink
Merge branch 'main' into usecase
Browse files Browse the repository at this point in the history
  • Loading branch information
raintygao authored Oct 1, 2024
2 parents 395bf46 + a7f3e9d commit 8d7d9eb
Show file tree
Hide file tree
Showing 19 changed files with 924 additions and 299 deletions.
2 changes: 2 additions & 0 deletions changelogs/fragments/8214.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
feat:
- Add last updated time and cache refresh button to Discover Advanced Dataset Selector ([#8214](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/8214))
2 changes: 2 additions & 0 deletions changelogs/fragments/8382.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
feat:
- Refactor collaborators panel at workspace create ([#8382](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/8382))
15 changes: 3 additions & 12 deletions src/core/public/chrome/ui/header/collapsible_nav_group_enabled.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
EuiFlyout,
EuiPanel,
EuiHorizontalRule,
EuiSpacer,
EuiHideFor,
EuiFlyoutProps,
EuiShowFor,
Expand Down Expand Up @@ -79,6 +78,7 @@ export function CollapsibleNavGroupEnabled({
id,
isNavOpen,
storage = window.localStorage,
currentWorkspace$,
closeNav,
navigateToApp,
navigateToUrl,
Expand All @@ -94,7 +94,6 @@ export function CollapsibleNavGroupEnabled({
const appId = useObservable(observables.appId$, '');
const navGroupsMap = useObservable(observables.navGroupsMap$, {});
const currentNavGroup = useObservable(observables.currentNavGroup$, undefined);

const visibleUseCases = useMemo(() => getVisibleUseCases(navGroupsMap), [navGroupsMap]);

const currentNavGroupId = useMemo(() => {
Expand All @@ -115,9 +114,6 @@ export function CollapsibleNavGroupEnabled({
? !currentNavGroupId
: currentNavGroupId === ALL_USE_CASE_ID;

const shouldShowCollapsedNavHeaderContent =
isNavOpen && !!collapsibleNavHeaderRender && !currentNavGroupId;

const navLinksForRender: ChromeNavLink[] = useMemo(() => {
const getSystemNavGroups = () => {
const result: ChromeNavLink[] = [];
Expand Down Expand Up @@ -300,6 +296,7 @@ export function CollapsibleNavGroupEnabled({
<CollapsibleNavTop
homeLink={homeLink}
navGroupsMap={navGroupsMap}
collapsibleNavHeaderRender={collapsibleNavHeaderRender}
navLinks={navLinks}
navigateToApp={navigateToApp}
logos={logos}
Expand All @@ -308,7 +305,7 @@ export function CollapsibleNavGroupEnabled({
shouldShrinkNavigation={!isNavOpen}
onClickShrink={closeNav}
visibleUseCases={visibleUseCases}
currentWorkspace$={observables.currentWorkspace$}
currentWorkspace$={currentWorkspace$}
/>
</EuiPanel>
)}
Expand All @@ -320,12 +317,6 @@ export function CollapsibleNavGroupEnabled({
hasShadow={false}
className="eui-yScroll flex-1-container"
>
{shouldShowCollapsedNavHeaderContent && collapsibleNavHeaderRender ? (
<>
{collapsibleNavHeaderRender()}
<EuiSpacer />
</>
) : null}
<NavGroups
navLinks={navLinksForRender}
navigateToApp={navigateToApp}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ import {
EuiButtonIcon,
EuiFlexGroup,
EuiFlexItem,
EuiText,
EuiIcon,
EuiPanel,
EuiSpacer,
EuiText,
} from '@elastic/eui';
import { InternalApplicationStart } from 'src/core/public/application';
import { createEuiListItem } from './nav_link';
Expand All @@ -25,6 +25,7 @@ import { fulfillRegistrationLinksToChromeNavLinks } from '../../utils';
import './collapsible_nav_group_enabled_top.scss';

export interface CollapsibleNavTopProps {
collapsibleNavHeaderRender?: () => JSX.Element | null;
homeLink?: ChromeNavLink;
navGroupsMap: Record<string, NavGroupItemInMap>;
currentNavGroup?: NavGroupItemInMap;
Expand All @@ -39,6 +40,7 @@ export interface CollapsibleNavTopProps {
}

export const CollapsibleNavTop = ({
collapsibleNavHeaderRender,
currentNavGroup,
navigateToApp,
logos,
Expand All @@ -52,7 +54,6 @@ export const CollapsibleNavTop = ({
navLinks,
}: CollapsibleNavTopProps) => {
const currentWorkspace = useObservable(currentWorkspace$);

const firstVisibleNavLinkInFirstVisibleUseCase = useMemo(
() =>
fulfillRegistrationLinksToChromeNavLinks(
Expand Down Expand Up @@ -148,12 +149,19 @@ export const CollapsibleNavTop = ({
/>
</EuiFlexItem>
</EuiFlexGroup>
{currentNavGroup?.title && (
<>
<EuiSpacer />
<EuiText>{currentNavGroup?.title}</EuiText>
</>
)}
{
// Nav groups with type are system(global) nav group and we should show title for those nav groups
(currentNavGroup?.type || collapsibleNavHeaderRender) && (
<>
<EuiSpacer />
{currentNavGroup?.type ? (
<EuiText>{currentNavGroup?.title}</EuiText>
) : (
collapsibleNavHeaderRender?.()
)}
</>
)
}
</EuiPanel>
);
};
4 changes: 4 additions & 0 deletions src/plugins/data/common/storage/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ export class DataStorage {
if (ourKey != null) ours.push(ourKey);
});
}

clear(): void {
this.engine.clear();
}
}

export function createStorage(deps: { engine: IStorageEngine; prefix: string }) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,26 @@ describe('DatasetService', () => {
service = new DatasetService(uiSettings, sessionStorage);
});

test('registerType and getType', () => {
const mockType = {
id: 'test-type',
title: 'Test Type',
meta: { icon: { type: 'test' } },
toDataset: jest.fn(),
fetch: jest.fn(),
fetchFields: jest.fn(),
supportedLanguages: jest.fn(),
};
const mockResult = {
id: 'test-structure',
title: 'Test Structure',
type: 'test-type',
children: [{ id: 'child1', title: 'Child 1', type: 'test-type' }],
};

const mockPath: DataStructure[] = [{ id: 'root', title: 'Root', type: 'root' }];

const mockType = {
id: 'test-type',
title: 'Test Type',
meta: { icon: { type: 'test' } },
toDataset: jest.fn(),
fetch: jest.fn().mockResolvedValue(mockResult),
fetchFields: jest.fn(),
supportedLanguages: jest.fn(),
};

test('registerType and getType', () => {
service.registerType(mockType);
expect(service.getType('test-type')).toBe(mockType);
});
Expand All @@ -52,25 +61,9 @@ describe('DatasetService', () => {
});

test('fetchOptions caches and returns data structures', async () => {
const mockType = {
id: 'test-type',
title: 'Test Type',
meta: { icon: { type: 'test' } },
toDataset: jest.fn(),
fetch: jest.fn().mockResolvedValue({
id: 'test-structure',
title: 'Test Structure',
type: 'test-type',
children: [{ id: 'child1', title: 'Child 1', type: 'test-type' }],
}),
fetchFields: jest.fn(),
supportedLanguages: jest.fn(),
};

service.registerType(mockType);

const path: DataStructure[] = [{ id: 'root', title: 'Root', type: 'root' }];
const result = await service.fetchOptions(mockDataPluginServices, path, 'test-type');
const result = await service.fetchOptions(mockDataPluginServices, mockPath, 'test-type');

expect(result).toEqual({
id: 'test-structure',
Expand All @@ -79,8 +72,30 @@ describe('DatasetService', () => {
children: [{ id: 'child1', title: 'Child 1', type: 'test-type' }],
});

const cachedResult = await service.fetchOptions(mockDataPluginServices, path, 'test-type');
const cachedResult = await service.fetchOptions(mockDataPluginServices, mockPath, 'test-type');
expect(cachedResult).toEqual(result);
expect(mockType.fetch).toHaveBeenCalledTimes(2);
});

test('clear cache', async () => {
service.registerType(mockType);

await service.fetchOptions(mockDataPluginServices, mockPath, 'test-type');
expect(sessionStorage.keys().length === 1);

service.clearCache();
expect(sessionStorage.keys().length === 0);
});

test('caching object correctly sets last cache time', async () => {
service.registerType(mockType);

const time = Date.now();

Date.now = jest.fn(() => time);

await service.fetchOptions(mockDataPluginServices, mockPath, 'test-type');

expect(service.getLastCacheTime()).toEqual(time);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ export class DatasetService {
}

private cacheDataStructure(dataType: string, dataStructure: DataStructure) {
this.setLastCacheTime(Date.now());
const cachedDataStructure: CachedDataStructure = {
id: dataStructure.id,
title: dataStructure.title,
Expand All @@ -164,6 +165,18 @@ export class DatasetService {
});
}

public clearCache(): void {
this.sessionStorage.clear();
}

public getLastCacheTime(): number | undefined {
return Number(this.sessionStorage.get('lastCacheTime')) || undefined;
}

private setLastCacheTime(time: number): void {
this.sessionStorage.set('lastCacheTime', time);
}

private async fetchDefaultDataset(): Promise<Dataset | undefined> {
const defaultIndexPatternId = this.uiSettings.get('defaultIndex');
if (!defaultIndexPatternId) {
Expand Down
91 changes: 68 additions & 23 deletions src/plugins/data/public/ui/dataset_selector/dataset_explorer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import React, { useState } from 'react';
import {
EuiButton,
EuiButtonEmpty,
EuiFlexGroup,
EuiFlexItem,
EuiIcon,
EuiLink,
EuiModalBody,
Expand All @@ -19,6 +21,7 @@ import {
EuiToolTip,
} from '@elastic/eui';
import { FormattedMessage } from '@osd/i18n/react';
import moment from 'moment';
import { BaseDataset, DATA_STRUCTURE_META_TYPES, DataStructure } from '../../../common';
import { QueryStringContract } from '../../query';
import { IDataPluginServices } from '../../types';
Expand All @@ -38,6 +41,7 @@ export const DatasetExplorer = ({
onNext: (dataset: BaseDataset) => void;
onCancel: () => void;
}) => {
const uiSettings = services.uiSettings;
const [explorerDataset, setExplorerDataset] = useState<BaseDataset | undefined>(undefined);
const [loading, setLoading] = useState<boolean>(false);

Expand Down Expand Up @@ -68,31 +72,72 @@ export const DatasetExplorer = ({
return (
<>
<EuiModalHeader>
<EuiModalHeaderTitle>
<h1>
<FormattedMessage
id="data.explorer.datasetSelector.advancedSelector.title.step1"
defaultMessage="Step 1: Select data"
/>
</h1>
<EuiText>
<p>
<FormattedMessage
id="data.explorer.datasetSelector.advancedSelector.description"
defaultMessage="Select from those available to you. "
/>
<EuiLink
href={`${services.http.basePath.get()}/app/management/opensearch-dashboards/dataSources`}
target="_blank"
>
<EuiFlexGroup alignItems="center" justifyContent="flexEnd" gutterSize="s">
<EuiFlexItem grow={true}>
<EuiModalHeaderTitle>
<h1>
<FormattedMessage
id="data.explorer.datasetSelector.advancedSelector.dataSourceManagement.title"
defaultMessage="Manage data sources"
id="data.explorer.datasetSelector.advancedSelector.title.step1"
defaultMessage="Step 1: Select data"
/>
</EuiLink>
</p>
</EuiText>
</EuiModalHeaderTitle>
</h1>
<EuiText>
<p>
<FormattedMessage
id="data.explorer.datasetSelector.advancedSelector.description"
defaultMessage="Select from those available to you. "
/>
<EuiLink
href={`${services.http.basePath.get()}/app/management/opensearch-dashboards/dataSources`}
target="_blank"
>
<FormattedMessage
id="data.explorer.datasetSelector.advancedSelector.dataSourceManagement.title"
defaultMessage="Manage data sources"
/>
</EuiLink>
</p>
</EuiText>
</EuiModalHeaderTitle>
</EuiFlexItem>
{queryString.getDatasetService().getLastCacheTime() && (
<EuiFlexItem grow={false}>
<EuiFlexGroup gutterSize="xs">
<EuiFlexItem grow={false}>
<EuiText size="s">
<FormattedMessage
id="data.explorer.datasetSelector.advancedSelector.lastUpdatedTime"
defaultMessage={'Last updated at: {timestamp}. '}
values={{
timestamp: moment(queryString.getDatasetService().getLastCacheTime())
.format(uiSettings.get('dateFormat'))
.toString(),
}}
/>
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonEmpty
onClick={() => {
queryString.getDatasetService().clearCache();
onCancel();
}}
size="xs"
iconSide="left"
iconType="refresh"
iconGap="s"
flush="both"
>
<FormattedMessage
id="data.explorer.datasetSelector.advancedSelector.refreshCacheButton"
defaultMessage="Refresh Cache"
/>
</EuiButtonEmpty>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
)}
</EuiFlexGroup>
</EuiModalHeader>
<EuiModalBody>
<div
Expand Down
Loading

0 comments on commit 8d7d9eb

Please sign in to comment.