Skip to content

Commit

Permalink
[Log Explorer] Implement Data Views tab into selector (elastic#166938)
Browse files Browse the repository at this point in the history
## 📓 Summary

Closes elastic#166848 

This work adds a new tab to navigate Data View from the Log Explorer
selector.
In this first iteration, when the user selects a data view, we move into
discovering preselecting and loading the data view of choice.

**N.B.**: this recording is made on a setup where I have no installed
integrations, so having the no integrations panel is the expected
behaviour.


https://github.com/elastic/kibana/assets/34506779/e8d1f622-86fb-4841-b4cc-4a913067d2cc

## Updated selector state machine

<img width="1492" alt="Screenshot 2023-09-22 at 12 15 44"
src="https://github.com/elastic/kibana/assets/34506779/c563b765-6c6c-41e8-b8cd-769c518932c3">

## New DataViews state machine

<img width="995" alt="Screenshot 2023-09-22 at 12 39 09"
src="https://github.com/elastic/kibana/assets/34506779/e4e43343-c915-42d8-8660-a2ee89f8d595">

---------

Co-authored-by: Marco Antonio Ghiani <[email protected]>
Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
3 people authored Sep 28, 2023
1 parent f8dae67 commit 3be21c9
Show file tree
Hide file tree
Showing 28 changed files with 1,161 additions and 92 deletions.
2 changes: 2 additions & 0 deletions x-pack/plugins/log_explorer/common/dataset_selection/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* 2.0.
*/

import { DataViewListItem } from '@kbn/data-views-plugin/common';
import { AllDatasetSelection } from './all_dataset_selection';
import { SingleDatasetSelection } from './single_dataset_selection';
import { UnresolvedDatasetSelection } from './unresolved_dataset_selection';
Expand All @@ -14,6 +15,7 @@ export type DatasetSelection =
| SingleDatasetSelection
| UnresolvedDatasetSelection;
export type DatasetSelectionChange = (datasetSelection: DatasetSelection) => void;
export type DataViewSelection = (dataView: DataViewListItem) => void;

export const isDatasetSelection = (input: any): input is DatasetSelection => {
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ export const INTEGRATIONS_PANEL_ID = 'dataset-selector-integrations-panel';
export const INTEGRATIONS_TAB_ID = 'dataset-selector-integrations-tab';
export const UNCATEGORIZED_PANEL_ID = 'dataset-selector-uncategorized-panel';
export const UNCATEGORIZED_TAB_ID = 'dataset-selector-uncategorized-tab';
export const DATA_VIEWS_PANEL_ID = 'dataset-selector-data-views-panel';
export const DATA_VIEWS_TAB_ID = 'dataset-selector-data-views-tab';

export const DATA_VIEW_POPOVER_CONTENT_WIDTH = 300;
export const DATA_VIEW_POPOVER_CONTENT_WIDTH = 400;

export const showAllLogsLabel = i18n.translate('xpack.logExplorer.datasetSelector.showAllLogs', {
defaultMessage: 'Show all logs',
Expand All @@ -28,6 +30,14 @@ export const uncategorizedLabel = i18n.translate(
{ defaultMessage: 'Uncategorized' }
);

export const dataViewsLabel = i18n.translate('xpack.logExplorer.datasetSelector.dataViews', {
defaultMessage: 'Data Views',
});

export const openDiscoverLabel = i18n.translate('xpack.logExplorer.datasetSelector.openDiscover', {
defaultMessage: 'Opens in Discover',
});

export const sortOrdersLabel = i18n.translate('xpack.logExplorer.datasetSelector.sortOrders', {
defaultMessage: 'Sort directions',
});
Expand All @@ -43,6 +53,17 @@ export const noDatasetsDescriptionLabel = i18n.translate(
}
);

export const noDataViewsLabel = i18n.translate('xpack.logExplorer.datasetSelector.noDataViews', {
defaultMessage: 'No data views found',
});

export const noDataViewsDescriptionLabel = i18n.translate(
'xpack.logExplorer.datasetSelector.noDataViewsDescription',
{
defaultMessage: 'No data views or search results found.',
}
);

export const noIntegrationsLabel = i18n.translate(
'xpack.logExplorer.datasetSelector.noIntegrations',
{ defaultMessage: 'No integrations found' }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import React, { useState } from 'react';
import { I18nProvider } from '@kbn/i18n-react';
import type { Meta, Story } from '@storybook/react';
import { IndexPattern } from '@kbn/io-ts-utils';
import { DataViewListItem } from '@kbn/data-views-plugin/common';
import {
AllDatasetSelection,
DatasetSelection,
Expand All @@ -29,6 +30,10 @@ const meta: Meta<typeof DatasetSelector> = {
options: [null, { message: 'Failed to fetch data streams' }],
control: { type: 'radio' },
},
dataViewsError: {
options: [null, { message: 'Failed to fetch data data views' }],
control: { type: 'radio' },
},
integrationsError: {
options: [null, { message: 'Failed to fetch data integrations' }],
control: { type: 'radio' },
Expand Down Expand Up @@ -71,33 +76,58 @@ const DatasetSelectorTemplate: Story<DatasetSelectorProps> = (args) => {

const sortedDatasets = search.sortOrder === 'asc' ? filteredDatasets : filteredDatasets.reverse();

const filteredDataViews = mockDataViews.filter((dataView) =>
dataView.name?.includes(search.name as string)
);

const sortedDataViews =
search.sortOrder === 'asc' ? filteredDataViews : filteredDataViews.reverse();

const {
datasetsError,
dataViewsError,
integrationsError,
isLoadingDataViews,
isLoadingIntegrations,
isLoadingUncategorized,
} = args;

return (
<DatasetSelector
{...args}
datasets={sortedDatasets}
datasets={Boolean(datasetsError || isLoadingUncategorized) ? [] : sortedDatasets}
dataViews={Boolean(dataViewsError || isLoadingDataViews) ? [] : sortedDataViews}
datasetSelection={datasetSelection}
integrations={sortedIntegrations}
integrations={Boolean(integrationsError || isLoadingIntegrations) ? [] : sortedIntegrations}
onDataViewsSearch={setSearch}
onDataViewsSort={setSearch}
onIntegrationsLoadMore={onIntegrationsLoadMore}
onIntegrationsSearch={setSearch}
onIntegrationsSort={setSearch}
onIntegrationsStreamsSearch={setSearch}
onIntegrationsStreamsSort={setSearch}
onSelectionChange={onSelectionChange}
onUnmanagedStreamsSearch={setSearch}
onUnmanagedStreamsSort={setSearch}
onUncategorizedSearch={setSearch}
onUncategorizedSort={setSearch}
/>
);
};

export const Basic = DatasetSelectorTemplate.bind({});
Basic.args = {
datasetsError: null,
dataViewsError: null,
integrationsError: null,
isLoadingDataViews: false,
isLoadingIntegrations: false,
isLoadingStreams: false,
isLoadingUncategorized: false,
isSearchingIntegrations: false,
onDataViewsReload: () => alert('Reload data views...'),
onDataViewSelection: (dataView) => alert(`Navigate to data view "${dataView.name}"`),
onDataViewsTabClick: () => console.log('Load data views...'),
onIntegrationsReload: () => alert('Reload integrations...'),
onStreamsEntryClick: () => console.log('Load uncategorized streams...'),
onUnmanagedStreamsReload: () => alert('Reloading streams...'),
onUncategorizedTabClick: () => console.log('Load uncategorized streams...'),
onUncategorizedReload: () => alert('Reloading streams...'),
};

const mockIntegrations: Integration[] = [
Expand Down Expand Up @@ -477,3 +507,25 @@ const mockDatasets: Dataset[] = [
{ name: 'data-load-balancing-logs-*' as IndexPattern },
{ name: 'data-scaling-logs-*' as IndexPattern },
].map((dataset) => Dataset.create(dataset));

const mockDataViews: DataViewListItem[] = [
{
id: 'logs-*',
namespaces: ['default'],
title: 'logs-*',
name: 'logs-*',
},
{
id: 'metrics-*',
namespaces: ['default'],
title: 'metrics-*',
name: 'metrics-*',
},
{
id: '7258d186-6430-4b51-bb67-2603cdfb4652',
namespaces: ['default'],
title: 'synthetics-*',
typeMeta: {},
name: 'synthetics-dashboard',
},
];
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import styled from '@emotion/styled';
import { EuiContextMenu, EuiHorizontalRule, EuiTab, EuiTabs } from '@elastic/eui';
import { useIntersectionRef } from '../../hooks/use_intersection_ref';
import {
dataViewsLabel,
DATA_VIEWS_PANEL_ID,
DATA_VIEWS_TAB_ID,
DATA_VIEW_POPOVER_CONTENT_WIDTH,
integrationsLabel,
INTEGRATIONS_PANEL_ID,
Expand All @@ -25,30 +28,41 @@ import { SelectorActions } from './sub_components/selector_actions';
import { DatasetSelectorProps } from './types';
import {
buildIntegrationsTree,
createDataViewsStatusItem,
createIntegrationStatusItem,
createUncategorizedStatusItem,
} from './utils';
import { getDataViewTestSubj } from '../../utils/get_data_view_test_subj';
import { DataViewsPanelTitle } from './sub_components/data_views_panel_title';

export function DatasetSelector({
datasets,
datasetsError,
datasetSelection,
datasetsError,
dataViews,
dataViewsError,
integrations,
integrationsError,
isLoadingDataViews,
isLoadingIntegrations,
isLoadingStreams,
isLoadingUncategorized,
isSearchingIntegrations,
onDataViewSelection,
onDataViewsReload,
onDataViewsSearch,
onDataViewsSort,
onDataViewsTabClick,
onIntegrationsLoadMore,
onIntegrationsReload,
onIntegrationsSearch,
onIntegrationsSort,
onIntegrationsStreamsSearch,
onIntegrationsStreamsSort,
onSelectionChange,
onStreamsEntryClick,
onUnmanagedStreamsReload,
onUnmanagedStreamsSearch,
onUnmanagedStreamsSort,
onUncategorizedReload,
onUncategorizedSearch,
onUncategorizedSort,
onUncategorizedTabClick,
}: DatasetSelectorProps) {
const {
panelId,
Expand All @@ -62,21 +76,26 @@ export function DatasetSelector({
searchByName,
selectAllLogDataset,
selectDataset,
selectDataView,
sortByOrder,
switchToIntegrationsTab,
switchToUncategorizedTab,
switchToDataViewsTab,
togglePopover,
} = useDatasetSelector({
initialContext: { selection: datasetSelection },
onDataViewSelection,
onDataViewsSearch,
onDataViewsSort,
onIntegrationsLoadMore,
onIntegrationsReload,
onIntegrationsSearch,
onIntegrationsSort,
onIntegrationsStreamsSearch,
onIntegrationsStreamsSort,
onUnmanagedStreamsSearch,
onUnmanagedStreamsSort,
onUnmanagedStreamsReload,
onUncategorizedSearch,
onUncategorizedSort,
onUncategorizedReload,
onSelectionChange,
});

Expand Down Expand Up @@ -117,8 +136,8 @@ export function DatasetSelector({
createUncategorizedStatusItem({
data: datasets,
error: datasetsError,
isLoading: isLoadingStreams,
onRetry: onUnmanagedStreamsReload,
isLoading: isLoadingUncategorized,
onRetry: onUncategorizedReload,
}),
];
}
Expand All @@ -127,7 +146,26 @@ export function DatasetSelector({
name: dataset.title,
onClick: () => selectDataset(dataset),
}));
}, [datasets, datasetsError, isLoadingStreams, selectDataset, onUnmanagedStreamsReload]);
}, [datasets, datasetsError, isLoadingUncategorized, selectDataset, onUncategorizedReload]);

const dataViewsItems = useMemo(() => {
if (!dataViews || dataViews.length === 0) {
return [
createDataViewsStatusItem({
data: dataViews,
error: dataViewsError,
isLoading: isLoadingDataViews,
onRetry: onDataViewsReload,
}),
];
}

return dataViews.map((dataView) => ({
'data-test-subj': getDataViewTestSubj(dataView.title),
name: dataView.name,
onClick: () => selectDataView(dataView),
}));
}, [dataViews, dataViewsError, isLoadingDataViews, selectDataView, onDataViewsReload]);

const tabs = [
{
Expand All @@ -140,11 +178,20 @@ export function DatasetSelector({
id: UNCATEGORIZED_TAB_ID,
name: uncategorizedLabel,
onClick: () => {
onStreamsEntryClick(); // Lazy-load uncategorized datasets only when accessing the Uncategorized tab
onUncategorizedTabClick(); // Lazy-load uncategorized datasets only when accessing the Uncategorized tab
switchToUncategorizedTab();
},
'data-test-subj': 'datasetSelectorUncategorizedTab',
},
{
id: DATA_VIEWS_TAB_ID,
name: dataViewsLabel,
onClick: () => {
onDataViewsTabClick(); // Lazy-load data views only when accessing the Data Views tab
switchToDataViewsTab();
},
'data-test-subj': 'datasetSelectorDataViewsTab',
},
];

const tabEntries = tabs.map((tab) => (
Expand Down Expand Up @@ -175,7 +222,7 @@ export function DatasetSelector({
search={search}
onSearch={searchByName}
onSort={sortByOrder}
isLoading={isSearchingIntegrations || isLoadingStreams}
isLoading={isSearchingIntegrations || isLoadingUncategorized}
/>
<EuiHorizontalRule margin="none" />
{/* For a smoother user experience, we keep each tab content mount and we only show the select one
Expand Down Expand Up @@ -215,6 +262,22 @@ export function DatasetSelector({
data-test-subj="uncategorizedContextMenu"
size="s"
/>
{/* Data views tab content */}
<ContextMenu
hidden={tabId !== DATA_VIEWS_TAB_ID}
initialPanelId={DATA_VIEWS_PANEL_ID}
panels={[
{
id: DATA_VIEWS_PANEL_ID,
title: <DataViewsPanelTitle />,
width: DATA_VIEW_POPOVER_CONTENT_WIDTH,
items: dataViewsItems,
},
]}
className="eui-yScroll"
data-test-subj="dataViewsContextMenu"
size="s"
/>
</DatasetsPopover>
);
}
Expand Down
Loading

0 comments on commit 3be21c9

Please sign in to comment.