[Discover] Merge discover.tsx and discover_legacy.tsx (#88465) (#89446)

This commit is contained in:
Matthias Wilhelm 2021-01-28 15:37:24 +01:00 committed by GitHub
parent 34269bef14
commit 925f12a237
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 387 additions and 633 deletions

View file

@ -24,7 +24,6 @@ import {
import { getSortArray } from './doc_table';
import * as columnActions from './doc_table/actions/columns';
import indexTemplateLegacy from './discover_legacy.html';
import indexTemplateGrid from './discover_datagrid.html';
import { addHelpMenuToAppChrome } from '../components/help_menu/help_menu_util';
import { discoverResponseHandler } from './response_handler';
import {
@ -112,9 +111,7 @@ app.config(($routeProvider) => {
};
const discoverRoute = {
...defaults,
template: getServices().uiSettings.get('doc_table:legacy', true)
? indexTemplateLegacy
: indexTemplateGrid,
template: indexTemplateLegacy,
reloadOnSearch: false,
resolve: {
savedObjects: function ($route, Promise) {

View file

@ -1,5 +1,5 @@
<discover-app>
<discover-legacy
<discover
fetch="fetch"
fetch-counter="fetchCounter"
fetch-error="fetchError"
@ -31,5 +31,5 @@
update-saved-query-id="updateSavedQueryId"
use-new-fields-api="useNewFieldsApi"
>
</discover-legacy>
</discover>
</discover-app>

View file

@ -9,8 +9,11 @@
import angular, { auto, ICompileService, IScope } from 'angular';
import { render } from 'react-dom';
import React, { useRef, useEffect } from 'react';
import { EuiButtonEmpty } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { getServices, IIndexPattern } from '../../../kibana_services';
import { IndexPatternField } from '../../../../../data/common/index_patterns';
export type AngularScope = IScope;
export interface AngularDirective {
@ -83,9 +86,11 @@ export interface DocTableLegacyProps {
indexPattern: IIndexPattern;
minimumVisibleRows: number;
onAddColumn?: (column: string) => void;
onBackToTop: () => void;
onSort?: (sort: string[][]) => void;
onMoveColumn?: (columns: string, newIdx: number) => void;
onRemoveColumn?: (column: string) => void;
sampleSize: number;
sort?: string[][];
useNewFieldsApi?: boolean;
}
@ -120,5 +125,31 @@ export function DocTableLegacy(renderProps: DocTableLegacyProps) {
return renderFn(ref.current, renderProps);
}
}, [renderFn, renderProps]);
return <div ref={ref} />;
return (
<div>
<div ref={ref} />
{renderProps.rows.length === renderProps.sampleSize ? (
<div
className="dscTable__footer"
data-test-subj="discoverDocTableFooter"
tabIndex={-1}
id="discoverBottomMarker"
>
<FormattedMessage
id="discover.howToSeeOtherMatchingDocumentsDescription"
defaultMessage="These are the first {sampleSize} documents matching
your search, refine your search to see others."
values={{ sampleSize: renderProps.sampleSize }}
/>
<EuiButtonEmpty onClick={renderProps.onBackToTop}>
<FormattedMessage id="discover.backToTopLinkText" defaultMessage="Back to top." />
</EuiButtonEmpty>
</div>
) : (
<span tabIndex={-1} id="discoverBottomMarker">
&#8203;
</span>
)}
</div>
);
}

View file

@ -17,18 +17,21 @@ export function createDiscoverDirective(reactDirective: any) {
['histogramData', { watchDepth: 'reference' }],
['hits', { watchDepth: 'reference' }],
['indexPattern', { watchDepth: 'reference' }],
['minimumVisibleRows', { watchDepth: 'reference' }],
['onAddColumn', { watchDepth: 'reference' }],
['onAddFilter', { watchDepth: 'reference' }],
['onChangeInterval', { watchDepth: 'reference' }],
['onMoveColumn', { watchDepth: 'reference' }],
['onRemoveColumn', { watchDepth: 'reference' }],
['onSetColumns', { watchDepth: 'reference' }],
['onSkipBottomButtonClick', { watchDepth: 'reference' }],
['onSort', { watchDepth: 'reference' }],
['opts', { watchDepth: 'reference' }],
['resetQuery', { watchDepth: 'reference' }],
['resultState', { watchDepth: 'reference' }],
['rows', { watchDepth: 'reference' }],
['savedSearch', { watchDepth: 'reference' }],
['searchSource', { watchDepth: 'reference' }],
['setColumns', { watchDepth: 'reference' }],
['setIndexPattern', { watchDepth: 'reference' }],
['showSaveQuery', { watchDepth: 'reference' }],
['state', { watchDepth: 'reference' }],

View file

@ -1,45 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* and the Server Side Public License, v 1; you may not use this file except in
* compliance with, at your election, the Elastic License or the Server Side
* Public License, v 1.
*/
import { DiscoverLegacy } from './discover_legacy';
export function createDiscoverLegacyDirective(reactDirective: any) {
return reactDirective(DiscoverLegacy, [
['fetch', { watchDepth: 'reference' }],
['fetchCounter', { watchDepth: 'reference' }],
['fetchError', { watchDepth: 'reference' }],
['fieldCounts', { watchDepth: 'reference' }],
['histogramData', { watchDepth: 'reference' }],
['hits', { watchDepth: 'reference' }],
['indexPattern', { watchDepth: 'reference' }],
['minimumVisibleRows', { watchDepth: 'reference' }],
['onAddColumn', { watchDepth: 'reference' }],
['onAddFilter', { watchDepth: 'reference' }],
['onChangeInterval', { watchDepth: 'reference' }],
['onMoveColumn', { watchDepth: 'reference' }],
['onRemoveColumn', { watchDepth: 'reference' }],
['onSetColumns', { watchDepth: 'reference' }],
['onSkipBottomButtonClick', { watchDepth: 'reference' }],
['onSort', { watchDepth: 'reference' }],
['opts', { watchDepth: 'reference' }],
['resetQuery', { watchDepth: 'reference' }],
['resultState', { watchDepth: 'reference' }],
['rows', { watchDepth: 'reference' }],
['savedSearch', { watchDepth: 'reference' }],
['searchSource', { watchDepth: 'reference' }],
['setIndexPattern', { watchDepth: 'reference' }],
['showSaveQuery', { watchDepth: 'reference' }],
['state', { watchDepth: 'reference' }],
['timefilterUpdateHandler', { watchDepth: 'reference' }],
['timeRange', { watchDepth: 'reference' }],
['topNavMenu', { watchDepth: 'reference' }],
['updateQuery', { watchDepth: 'reference' }],
['updateSavedQueryId', { watchDepth: 'reference' }],
['useNewFieldsApi', { watchDepth: 'reference' }],
]);
}

View file

@ -8,7 +8,7 @@
import React from 'react';
import { shallowWithIntl } from '@kbn/test/jest';
import { DiscoverLegacy } from './discover_legacy';
import { Discover } from './discover';
import { inspectorPluginMock } from '../../../../inspector/public/mocks';
import { esHits } from '../../__mocks__/es_hits';
import { indexPatternMock } from '../../__mocks__/index_pattern';
@ -19,7 +19,7 @@ import { savedSearchMock } from '../../__mocks__/saved_search';
import { createSearchSourceMock } from '../../../../data/common/search/search_source/mocks';
import { dataPluginMock } from '../../../../data/public/mocks';
import { createFilterManagerMock } from '../../../../data/public/query/filter_manager/filter_manager.mock';
import { uiSettingsMock } from '../../__mocks__/ui_settings';
import { uiSettingsMock as mockUiSettings } from '../../__mocks__/ui_settings';
import { IndexPattern, IndexPatternAttributes } from '../../../../data/common/index_patterns';
import { SavedObject } from '../../../../../core/types';
import { navigationPluginMock } from '../../../../navigation/public/mocks';
@ -40,6 +40,7 @@ jest.mock('../../kibana_services', () => {
},
},
navigation: mockNavigation,
uiSettings: mockUiSettings,
}),
};
});
@ -53,6 +54,7 @@ function getProps(indexPattern: IndexPattern) {
save: true,
},
},
uiSettings: mockUiSettings,
} as unknown) as DiscoverServices;
return {
@ -72,7 +74,7 @@ function getProps(indexPattern: IndexPattern) {
onSkipBottomButtonClick: jest.fn(),
onSort: jest.fn(),
opts: {
config: uiSettingsMock,
config: mockUiSettings,
data: dataPluginMock.createStartContract(),
fixedScroll: jest.fn(),
filterManager: createFilterManagerMock(),
@ -105,15 +107,13 @@ function getProps(indexPattern: IndexPattern) {
};
}
describe('Descover legacy component', () => {
describe('Discover component', () => {
test('selected index pattern without time field displays no chart toggle', () => {
const component = shallowWithIntl(<DiscoverLegacy {...getProps(indexPatternMock)} />);
const component = shallowWithIntl(<Discover {...getProps(indexPatternMock)} />);
expect(component.find('[data-test-subj="discoverChartToggle"]').length).toBe(0);
});
test('selected index pattern with time field displays chart toggle', () => {
const component = shallowWithIntl(
<DiscoverLegacy {...getProps(indexPatternWithTimefieldMock)} />
);
const component = shallowWithIntl(<Discover {...getProps(indexPatternWithTimefieldMock)} />);
expect(component.find('[data-test-subj="discoverChartToggle"]').length).toBe(1);
});
});

View file

@ -26,25 +26,30 @@ import classNames from 'classnames';
import { HitsCounter } from './hits_counter';
import { TimechartHeader } from './timechart_header';
import { getServices } from '../../kibana_services';
import { DiscoverUninitialized, DiscoverHistogram } from '../angular/directives';
import { DiscoverHistogram, DiscoverUninitialized } from '../angular/directives';
import { DiscoverNoResults } from './no_results';
import { LoadingSpinner } from './loading_spinner/loading_spinner';
import { DocTableLegacy, DocTableLegacyProps } from '../angular/doc_table/create_doc_table_react';
import { SkipBottomButton } from './skip_bottom_button';
import { search } from '../../../../data/public';
import {
DiscoverSidebarResponsive,
DiscoverSidebarResponsiveProps,
} from './sidebar/discover_sidebar_responsive';
import { DiscoverProps } from './discover_legacy';
import { DiscoverProps } from './types';
import { getDisplayedColumns } from '../helpers/columns';
import { SortPairArr } from '../angular/doc_table/lib/get_sort';
import { DiscoverGrid, DiscoverGridProps } from './discover_grid/discover_grid';
import { SEARCH_FIELDS_FROM_SOURCE } from '../../../common';
export const SidebarMemoized = React.memo((props: DiscoverSidebarResponsiveProps) => (
const DocTableLegacyMemoized = React.memo((props: DocTableLegacyProps) => (
<DocTableLegacy {...props} />
));
const SidebarMemoized = React.memo((props: DiscoverSidebarResponsiveProps) => (
<DiscoverSidebarResponsive {...props} />
));
export const DataGridMemoized = React.memo((props: DiscoverGridProps) => (
<DiscoverGrid {...props} />
));
const DataGridMemoized = React.memo((props: DiscoverGridProps) => <DiscoverGrid {...props} />);
export function Discover({
fetch,
@ -54,11 +59,14 @@ export function Discover({
histogramData,
hits,
indexPattern,
minimumVisibleRows,
onAddColumn,
onAddFilter,
onChangeInterval,
onMoveColumn,
onRemoveColumn,
onSetColumns,
onSkipBottomButtonClick,
onSort,
opts,
resetQuery,
@ -66,7 +74,6 @@ export function Discover({
rows,
searchSource,
setIndexPattern,
showSaveQuery,
state,
timefilterUpdateHandler,
timeRange,
@ -76,6 +83,11 @@ export function Discover({
}: DiscoverProps) {
const scrollableDesktop = useRef<HTMLDivElement>(null);
const collapseIcon = useRef<HTMLButtonElement>(null);
const isMobile = () => {
// collapse icon isn't displayed in mobile view, use it to detect which view is displayed
return collapseIcon && !collapseIcon.current;
};
const [toggleOn, toggleChart] = useState(true);
const [isSidebarClosed, setIsSidebarClosed] = useState(false);
const services = getServices();
@ -88,18 +100,8 @@ export function Discover({
? bucketAggConfig.buckets?.getInterval()
: undefined;
const contentCentered = resultState === 'uninitialized';
const showTimeCol = !config.get('doc_table:hideTimeColumn', false) && indexPattern.timeFieldName;
const columns =
state.columns &&
state.columns.length > 0 &&
// check if all columns where removed except the configured timeField (this can't be removed)
!(state.columns.length === 1 && state.columns[0] === indexPattern.timeFieldName)
? state.columns
: ['_source'];
// if columns include _source this is considered as default view, so you can't remove columns
// until you add a column using Discover's sidebar
const defaultColumns = columns.includes('_source');
const isLegacy = services.uiSettings.get('doc_table:legacy');
const useNewFieldsApi = !services.uiSettings.get(SEARCH_FIELDS_FROM_SOURCE);
return (
<I18nProvider>
<EuiPage className="dscPage" data-fetch-counter={fetchCounter}>
@ -114,7 +116,7 @@ export function Discover({
savedQueryId={state.savedQuery}
screenTitle={savedSearch.title}
showDatePicker={indexPattern.isTimeBased()}
showSaveQuery={showSaveQuery}
showSaveQuery={!!services.capabilities.discover.saveQuery}
showSearchBar={true}
useDefaultBehaviors={true}
/>
@ -137,6 +139,7 @@ export function Discover({
setIndexPattern={setIndexPattern}
isClosed={isSidebarClosed}
trackUiMetric={trackUiMetric}
useNewFieldsApi={useNewFieldsApi}
/>
</EuiFlexItem>
<EuiHideFor sizes={['xs', 's']}>
@ -207,24 +210,28 @@ export function Discover({
/>
</EuiFlexItem>
)}
<EuiFlexItem className="dscResultCount__toggle" grow={false}>
<EuiButtonEmpty
size="xs"
iconType={toggleOn ? 'eyeClosed' : 'eye'}
onClick={() => {
toggleChart(!toggleOn);
}}
>
{toggleOn
? i18n.translate('discover.hideChart', {
defaultMessage: 'Hide chart',
})
: i18n.translate('discover.showChart', {
defaultMessage: 'Show chart',
})}
</EuiButtonEmpty>
</EuiFlexItem>
{opts.timefield && (
<EuiFlexItem className="dscResultCount__toggle" grow={false}>
<EuiButtonEmpty
size="xs"
iconType={toggleOn ? 'eyeClosed' : 'eye'}
onClick={() => {
toggleChart(!toggleOn);
}}
data-test-subj="discoverChartToggle"
>
{toggleOn
? i18n.translate('discover.hideChart', {
defaultMessage: 'Hide chart',
})
: i18n.translate('discover.showChart', {
defaultMessage: 'Show chart',
})}
</EuiButtonEmpty>
</EuiFlexItem>
)}
</EuiFlexGroup>
{isLegacy && <SkipBottomButton onClick={onSkipBottomButtonClick} />}
</EuiFlexItem>
{toggleOn && opts.timefield && (
<EuiFlexItem grow={false}>
@ -238,7 +245,10 @@ export function Discover({
className="dscTimechart"
>
{opts.chartAggConfigs && histogramData && rows.length !== 0 && (
<div className="dscHistogramGrid" data-test-subj="discoverChart">
<div
className={isLegacy ? 'dscHistogram' : 'dscHistogramGrid'}
data-test-subj="discoverChart"
>
<DiscoverHistogram
chartData={histogramData}
timefilterUpdateHandler={timefilterUpdateHandler}
@ -265,19 +275,50 @@ export function Discover({
defaultMessage="Documents"
/>
</h2>
{rows && rows.length && (
{isLegacy && rows && rows.length && (
<DocTableLegacyMemoized
columns={state.columns || []}
indexPattern={indexPattern}
minimumVisibleRows={minimumVisibleRows}
rows={rows}
sort={state.sort || []}
searchDescription={opts.savedSearch.description}
searchTitle={opts.savedSearch.lastSavedTitle}
onAddColumn={onAddColumn}
onBackToTop={() => {
if (scrollableDesktop && scrollableDesktop.current) {
scrollableDesktop.current.focus();
}
// Only the desktop one needs to target a specific container
if (!isMobile() && scrollableDesktop.current) {
scrollableDesktop.current.scrollTo(0, 0);
} else if (window) {
window.scrollTo(0, 0);
}
}}
onFilter={onAddFilter}
onMoveColumn={onMoveColumn}
onRemoveColumn={onRemoveColumn}
onSort={onSort}
sampleSize={opts.sampleSize}
useNewFieldsApi={useNewFieldsApi}
/>
)}
{!isLegacy && rows && rows.length && (
<div className="dscDiscoverGrid">
<DataGridMemoized
ariaLabelledBy="documentsAriaLabel"
columns={columns}
defaultColumns={defaultColumns}
columns={getDisplayedColumns(state.columns, indexPattern)}
indexPattern={indexPattern}
rows={rows}
sort={(state.sort as SortPairArr[]) || []}
sampleSize={opts.sampleSize}
searchDescription={opts.savedSearch.description}
searchTitle={opts.savedSearch.lastSavedTitle}
showTimeCol={Boolean(showTimeCol)}
showTimeCol={
!config.get('doc_table:hideTimeColumn', false) &&
!!indexPattern.timeFieldName
}
services={services}
settings={state.grid}
onAddColumn={onAddColumn}

View file

@ -50,12 +50,6 @@ export interface DiscoverGridProps {
* Determines which columns are displayed
*/
columns: string[];
/**
* Determines whether the given columns are the default ones, so parts of the document
* are displayed (_source) with limited actions (cannor move, remove columns)
* Implemented for matching with legacy behavior
*/
defaultColumns: boolean;
/**
* The used index pattern
*/
@ -126,7 +120,6 @@ export const EuiDataGridMemoized = React.memo((props: EuiDataGridProps) => {
export const DiscoverGrid = ({
ariaLabelledBy,
columns,
defaultColumns,
indexPattern,
onAddColumn,
onFilter,
@ -144,6 +137,7 @@ export const DiscoverGrid = ({
sort,
}: DiscoverGridProps) => {
const [expanded, setExpanded] = useState<ElasticSearchHit | undefined>(undefined);
const defaultColumns = columns.includes('_source');
/**
* Pagination

View file

@ -48,7 +48,12 @@ export function buildEuiGridColumn(
id: columnName,
schema: getSchemaByKbnType(indexPatternField?.type),
isSortable: indexPatternField?.sortable,
display: indexPatternField?.displayName,
display:
columnName === '_source'
? i18n.translate('discover.grid.documentHeader', {
defaultMessage: 'Document',
})
: indexPatternField?.displayName,
actions: {
showHide:
defaultColumns || columnName === indexPattern.timeFieldName

View file

@ -1,517 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* and the Server Side Public License, v 1; you may not use this file except in
* compliance with, at your election, the Elastic License or the Server Side
* Public License, v 1.
*/
import './discover.scss';
import React, { useState, useRef } from 'react';
import {
EuiButtonEmpty,
EuiButtonIcon,
EuiFlexGroup,
EuiFlexItem,
EuiHideFor,
EuiPage,
EuiPageBody,
EuiPageContent,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage, I18nProvider } from '@kbn/i18n/react';
import { IUiSettingsClient, MountPoint } from 'kibana/public';
import classNames from 'classnames';
import { HitsCounter } from './hits_counter';
import { TimechartHeader } from './timechart_header';
import { getServices, IndexPattern } from '../../kibana_services';
import { DiscoverUninitialized, DiscoverHistogram } from '../angular/directives';
import { DiscoverNoResults } from './no_results';
import { LoadingSpinner } from './loading_spinner/loading_spinner';
import { DocTableLegacy, DocTableLegacyProps } from '../angular/doc_table/create_doc_table_react';
import { SkipBottomButton } from './skip_bottom_button';
import {
search,
ISearchSource,
TimeRange,
Query,
IndexPatternAttributes,
DataPublicPluginStart,
AggConfigs,
FilterManager,
} from '../../../../data/public';
import { Chart } from '../angular/helpers/point_series';
import { AppState } from '../angular/discover_state';
import { SavedSearch } from '../../saved_searches';
import { SavedObject } from '../../../../../core/types';
import { TopNavMenuData } from '../../../../navigation/public';
import {
DiscoverSidebarResponsive,
DiscoverSidebarResponsiveProps,
} from './sidebar/discover_sidebar_responsive';
import { DocViewFilterFn, ElasticSearchHit } from '../doc_views/doc_views_types';
export interface DiscoverProps {
/**
* Function to fetch documents from Elasticsearch
*/
fetch: () => void;
/**
* Counter how often data was fetched (used for testing)
*/
fetchCounter: number;
/**
* Error in case of a failing document fetch
*/
fetchError?: Error;
/**
* Statistics by fields calculated using the fetched documents
*/
fieldCounts: Record<string, number>;
/**
* Histogram aggregation data
*/
histogramData?: Chart;
/**
* Number of documents found by recent fetch
*/
hits: number;
/**
* Current IndexPattern
*/
indexPattern: IndexPattern;
/**
* Value needed for legacy "infinite" loading functionality
* Determins how much records are rendered using the legacy table
* Increased when scrolling down
*/
minimumVisibleRows: number;
/**
* Function to add a column to state
*/
onAddColumn: (column: string) => void;
/**
* Function to add a filter to state
*/
onAddFilter: DocViewFilterFn;
/**
* Function to change the used time interval of the date histogram
*/
onChangeInterval: (interval: string) => void;
/**
* Function to move a given column to a given index, used in legacy table
*/
onMoveColumn: (columns: string, newIdx: number) => void;
/**
* Function to remove a given column from state
*/
onRemoveColumn: (column: string) => void;
/**
* Function to replace columns in state
*/
onSetColumns: (columns: string[]) => void;
/**
* Function to scroll down the legacy table to the bottom
*/
onSkipBottomButtonClick: () => void;
/**
* Function to change sorting of the table, triggers a fetch
*/
onSort: (sort: string[][]) => void;
opts: {
/**
* Date histogram aggregation config
*/
chartAggConfigs?: AggConfigs;
/**
* Client of uiSettings
*/
config: IUiSettingsClient;
/**
* Data plugin
*/
data: DataPublicPluginStart;
/**
* Data plugin filter manager
*/
filterManager: FilterManager;
/**
* List of available index patterns
*/
indexPatternList: Array<SavedObject<IndexPatternAttributes>>;
/**
* The number of documents that can be displayed in the table/grid
*/
sampleSize: number;
/**
* Current instance of SavedSearch
*/
savedSearch: SavedSearch;
/**
* Function to set the header menu
*/
setHeaderActionMenu: (menuMount: MountPoint | undefined) => void;
/**
* Timefield of the currently used index pattern
*/
timefield: string;
/**
* Function to set the current state
*/
setAppState: (state: Partial<AppState>) => void;
};
/**
* Function to reset the current query
*/
resetQuery: () => void;
/**
* Current state of the actual query, one of 'uninitialized', 'loading' ,'ready', 'none'
*/
resultState: string;
/**
* Array of document of the recent successful search request
*/
rows: ElasticSearchHit[];
/**
* Instance of SearchSource, the high level search API
*/
searchSource: ISearchSource;
/**
* Function to change the current index pattern
*/
setIndexPattern: (id: string) => void;
/**
* Determines whether the user should be able to use the save query feature
*/
showSaveQuery: boolean;
/**
* Current app state of URL
*/
state: AppState;
/**
* Function to update the time filter
*/
timefilterUpdateHandler: (ranges: { from: number; to: number }) => void;
/**
* Currently selected time range
*/
timeRange?: { from: string; to: string };
/**
* Menu data of top navigation (New, save ...)
*/
topNavMenu: TopNavMenuData[];
/**
* Function to update the actual query
*/
updateQuery: (payload: { dateRange: TimeRange; query?: Query }, isUpdate?: boolean) => void;
/**
* Function to update the actual savedQuery id
*/
updateSavedQueryId: (savedQueryId?: string) => void;
useNewFieldsApi?: boolean;
}
export const DocTableLegacyMemoized = React.memo((props: DocTableLegacyProps) => (
<DocTableLegacy {...props} />
));
export const SidebarMemoized = React.memo((props: DiscoverSidebarResponsiveProps) => (
<DiscoverSidebarResponsive {...props} />
));
export function DiscoverLegacy({
fetch,
fetchCounter,
fieldCounts,
fetchError,
histogramData,
hits,
indexPattern,
minimumVisibleRows,
onAddColumn,
onAddFilter,
onChangeInterval,
onMoveColumn,
onRemoveColumn,
onSkipBottomButtonClick,
onSort,
opts,
resetQuery,
resultState,
rows,
searchSource,
setIndexPattern,
showSaveQuery,
state,
timefilterUpdateHandler,
timeRange,
topNavMenu,
updateQuery,
updateSavedQueryId,
useNewFieldsApi,
}: DiscoverProps) {
const scrollableDesktop = useRef<HTMLDivElement>(null);
const collapseIcon = useRef<HTMLButtonElement>(null);
const isMobile = () => {
// collapse icon isn't displayed in mobile view, use it to detect which view is displayed
return collapseIcon && !collapseIcon.current;
};
const [toggleOn, toggleChart] = useState(true);
const [isSidebarClosed, setIsSidebarClosed] = useState(false);
const services = getServices();
const { TopNavMenu } = services.navigation.ui;
const { trackUiMetric } = services;
const { savedSearch, indexPatternList } = opts;
const bucketAggConfig = opts.chartAggConfigs?.aggs[1];
const bucketInterval =
bucketAggConfig && search.aggs.isDateHistogramBucketAggConfig(bucketAggConfig)
? bucketAggConfig.buckets?.getInterval()
: undefined;
const contentCentered = resultState === 'uninitialized';
const getDisplayColumns = () => {
if (!state.columns) {
return [];
}
const columns = [...state.columns];
if (useNewFieldsApi) {
return columns.filter((column) => column !== '_source');
}
return columns.length === 0 ? ['_source'] : columns;
};
return (
<I18nProvider>
<EuiPage className="dscPage" data-fetch-counter={fetchCounter}>
<TopNavMenu
appName="discover"
config={topNavMenu}
indexPatterns={[indexPattern]}
onQuerySubmit={updateQuery}
onSavedQueryIdChange={updateSavedQueryId}
query={state.query}
setMenuMountPoint={opts.setHeaderActionMenu}
savedQueryId={state.savedQuery}
screenTitle={savedSearch.title}
showDatePicker={indexPattern.isTimeBased()}
showSaveQuery={showSaveQuery}
showSearchBar={true}
useDefaultBehaviors={true}
/>
<EuiPageBody className="dscPageBody" aria-describedby="savedSearchTitle">
<h1 id="savedSearchTitle" className="euiScreenReaderOnly">
{savedSearch.title}
</h1>
<EuiFlexGroup className="dscPageBody__contents" gutterSize="none">
<EuiFlexItem grow={false}>
<SidebarMemoized
columns={state.columns || []}
fieldCounts={fieldCounts}
hits={rows}
indexPatternList={indexPatternList}
onAddField={onAddColumn}
onAddFilter={onAddFilter}
onRemoveField={onRemoveColumn}
selectedIndexPattern={searchSource && searchSource.getField('index')}
services={services}
setIndexPattern={setIndexPattern}
isClosed={isSidebarClosed}
trackUiMetric={trackUiMetric}
useNewFieldsApi={useNewFieldsApi}
/>
</EuiFlexItem>
<EuiHideFor sizes={['xs', 's']}>
<EuiFlexItem grow={false}>
<EuiButtonIcon
iconType={isSidebarClosed ? 'menuRight' : 'menuLeft'}
iconSize="m"
size="s"
onClick={() => setIsSidebarClosed(!isSidebarClosed)}
data-test-subj="collapseSideBarButton"
aria-controls="discover-sidebar"
aria-expanded={isSidebarClosed ? 'false' : 'true'}
aria-label="Toggle sidebar"
buttonRef={collapseIcon}
/>
</EuiFlexItem>
</EuiHideFor>
<EuiFlexItem className="dscPageContent__wrapper">
<EuiPageContent
verticalPosition={contentCentered ? 'center' : undefined}
horizontalPosition={contentCentered ? 'center' : undefined}
paddingSize="none"
className={classNames('dscPageContent', {
'dscPageContent--centered': contentCentered,
})}
>
{resultState === 'none' && (
<DiscoverNoResults
timeFieldName={opts.timefield}
queryLanguage={state.query ? state.query.language : ''}
data={opts.data}
error={fetchError}
/>
)}
{resultState === 'uninitialized' && <DiscoverUninitialized onRefresh={fetch} />}
{resultState === 'loading' && <LoadingSpinner />}
{resultState === 'ready' && (
<EuiFlexGroup
className="dscPageContent__inner"
direction="column"
alignItems="stretch"
gutterSize="none"
responsive={false}
>
<EuiFlexItem grow={false} className="dscResultCount">
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween">
<EuiFlexItem
grow={false}
className="dscResuntCount__title eui-textTruncate eui-textNoWrap"
>
<HitsCounter
hits={hits > 0 ? hits : 0}
showResetButton={!!(savedSearch && savedSearch.id)}
onResetQuery={resetQuery}
/>
</EuiFlexItem>
{toggleOn && (
<EuiFlexItem className="dscResultCount__actions">
<TimechartHeader
dateFormat={opts.config.get('dateFormat')}
timeRange={timeRange}
options={search.aggs.intervalOptions}
onChangeInterval={onChangeInterval}
stateInterval={state.interval || ''}
bucketInterval={bucketInterval}
/>
</EuiFlexItem>
)}
{opts.timefield && (
<EuiFlexItem className="dscResultCount__toggle" grow={false}>
<EuiButtonEmpty
size="xs"
iconType={toggleOn ? 'eyeClosed' : 'eye'}
onClick={() => {
toggleChart(!toggleOn);
}}
data-test-subj="discoverChartToggle"
>
{toggleOn
? i18n.translate('discover.hideChart', {
defaultMessage: 'Hide chart',
})
: i18n.translate('discover.showChart', {
defaultMessage: 'Show chart',
})}
</EuiButtonEmpty>
</EuiFlexItem>
)}
</EuiFlexGroup>
<SkipBottomButton onClick={onSkipBottomButtonClick} />
</EuiFlexItem>
{toggleOn && opts.timefield && (
<EuiFlexItem grow={false}>
<section
aria-label={i18n.translate(
'discover.histogramOfFoundDocumentsAriaLabel',
{
defaultMessage: 'Histogram of found documents',
}
)}
className="dscTimechart"
>
{opts.chartAggConfigs && rows.length !== 0 && histogramData && (
<div className="dscHistogram" data-test-subj="discoverChart">
<DiscoverHistogram
chartData={histogramData}
timefilterUpdateHandler={timefilterUpdateHandler}
/>
</div>
)}
</section>
</EuiFlexItem>
)}
<EuiFlexItem className="eui-yScroll">
<section
className="dscTable eui-yScroll"
aria-labelledby="documentsAriaLabel"
ref={scrollableDesktop}
tabIndex={-1}
>
<h2 className="euiScreenReaderOnly" id="documentsAriaLabel">
<FormattedMessage
id="discover.documentsAriaLabel"
defaultMessage="Documents"
/>
</h2>
{rows && rows.length && (
<div>
<DocTableLegacyMemoized
columns={getDisplayColumns()}
indexPattern={indexPattern}
minimumVisibleRows={minimumVisibleRows}
rows={rows}
sort={state.sort || []}
searchDescription={opts.savedSearch.description}
searchTitle={opts.savedSearch.lastSavedTitle}
onAddColumn={onAddColumn}
onFilter={onAddFilter}
onMoveColumn={onMoveColumn}
onRemoveColumn={onRemoveColumn}
onSort={onSort}
useNewFieldsApi={useNewFieldsApi}
/>
{rows.length === opts.sampleSize ? (
<div
className="dscTable__footer"
data-test-subj="discoverDocTableFooter"
tabIndex={-1}
id="discoverBottomMarker"
>
<FormattedMessage
id="discover.howToSeeOtherMatchingDocumentsDescription"
defaultMessage="These are the first {sampleSize} documents matching
your search, refine your search to see others."
values={{ sampleSize: opts.sampleSize }}
/>
<EuiButtonEmpty
onClick={() => {
if (scrollableDesktop && scrollableDesktop.current) {
scrollableDesktop.current.focus();
}
// Only the desktop one needs to target a specific container
if (!isMobile() && scrollableDesktop.current) {
scrollableDesktop.current.scrollTo(0, 0);
} else if (window) {
window.scrollTo(0, 0);
}
}}
>
<FormattedMessage
id="discover.backToTopLinkText"
defaultMessage="Back to top."
/>
</EuiButtonEmpty>
</div>
) : (
<span tabIndex={-1} id="discoverBottomMarker">
&#8203;
</span>
)}
</div>
)}
</section>
</EuiFlexItem>
</EuiFlexGroup>
)}
</EuiPageContent>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPageBody>
</EuiPage>
</I18nProvider>
);
}

View file

@ -0,0 +1,179 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* and the Server Side Public License, v 1; you may not use this file except in
* compliance with, at your election, the Elastic License or the Server Side
* Public License, v 1.
*/
import { IUiSettingsClient, MountPoint, SavedObject } from 'kibana/public';
import { Chart } from '../angular/helpers/point_series';
import { IndexPattern } from '../../../../data/common/index_patterns/index_patterns';
import { DocViewFilterFn, ElasticSearchHit } from '../doc_views/doc_views_types';
import { AggConfigs } from '../../../../data/common/search/aggs';
import {
DataPublicPluginStart,
FilterManager,
IndexPatternAttributes,
ISearchSource,
Query,
TimeRange,
} from '../../../../data/public';
import { SavedSearch } from '../../saved_searches';
import { AppState } from '../angular/discover_state';
import { TopNavMenuData } from '../../../../navigation/public';
export interface DiscoverProps {
/**
* Function to fetch documents from Elasticsearch
*/
fetch: () => void;
/**
* Counter how often data was fetched (used for testing)
*/
fetchCounter: number;
/**
* Error in case of a failing document fetch
*/
fetchError?: Error;
/**
* Statistics by fields calculated using the fetched documents
*/
fieldCounts: Record<string, number>;
/**
* Histogram aggregation data
*/
histogramData?: Chart;
/**
* Number of documents found by recent fetch
*/
hits: number;
/**
* Current IndexPattern
*/
indexPattern: IndexPattern;
/**
* Value needed for legacy "infinite" loading functionality
* Determins how much records are rendered using the legacy table
* Increased when scrolling down
*/
minimumVisibleRows: number;
/**
* Function to add a column to state
*/
onAddColumn: (column: string) => void;
/**
* Function to add a filter to state
*/
onAddFilter: DocViewFilterFn;
/**
* Function to change the used time interval of the date histogram
*/
onChangeInterval: (interval: string) => void;
/**
* Function to move a given column to a given index, used in legacy table
*/
onMoveColumn: (columns: string, newIdx: number) => void;
/**
* Function to remove a given column from state
*/
onRemoveColumn: (column: string) => void;
/**
* Function to replace columns in state
*/
onSetColumns: (columns: string[]) => void;
/**
* Function to scroll down the legacy table to the bottom
*/
onSkipBottomButtonClick: () => void;
/**
* Function to change sorting of the table, triggers a fetch
*/
onSort: (sort: string[][]) => void;
opts: {
/**
* Date histogram aggregation config
*/
chartAggConfigs?: AggConfigs;
/**
* Client of uiSettings
*/
config: IUiSettingsClient;
/**
* Data plugin
*/
data: DataPublicPluginStart;
/**
* Data plugin filter manager
*/
filterManager: FilterManager;
/**
* List of available index patterns
*/
indexPatternList: Array<SavedObject<IndexPatternAttributes>>;
/**
* The number of documents that can be displayed in the table/grid
*/
sampleSize: number;
/**
* Current instance of SavedSearch
*/
savedSearch: SavedSearch;
/**
* Function to set the header menu
*/
setHeaderActionMenu: (menuMount: MountPoint | undefined) => void;
/**
* Timefield of the currently used index pattern
*/
timefield: string;
/**
* Function to set the current state
*/
setAppState: (state: Partial<AppState>) => void;
};
/**
* Function to reset the current query
*/
resetQuery: () => void;
/**
* Current state of the actual query, one of 'uninitialized', 'loading' ,'ready', 'none'
*/
resultState: string;
/**
* Array of document of the recent successful search request
*/
rows: ElasticSearchHit[];
/**
* Instance of SearchSource, the high level search API
*/
searchSource: ISearchSource;
/**
* Function to change the current index pattern
*/
setIndexPattern: (id: string) => void;
/**
* Current app state of URL
*/
state: AppState;
/**
* Function to update the time filter
*/
timefilterUpdateHandler: (ranges: { from: number; to: number }) => void;
/**
* Currently selected time range
*/
timeRange?: { from: string; to: string };
/**
* Menu data of top navigation (New, save ...)
*/
topNavMenu: TopNavMenuData[];
/**
* Function to update the actual query
*/
updateQuery: (payload: { dateRange: TimeRange; query?: Query }, isUpdate?: boolean) => void;
/**
* Function to update the actual savedQuery id
*/
updateSavedQueryId: (savedQueryId?: string) => void;
}

View file

@ -0,0 +1,46 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* and the Server Side Public License, v 1; you may not use this file except in
* compliance with, at your election, the Elastic License or the Server Side
* Public License, v 1.
*/
import { getDisplayedColumns } from './columns';
import { indexPatternWithTimefieldMock } from '../../__mocks__/index_pattern_with_timefield';
import { indexPatternMock } from '../../__mocks__/index_pattern';
describe('getDisplayedColumns', () => {
test('returns default columns given a index pattern without timefield', async () => {
const result = getDisplayedColumns([], indexPatternMock);
expect(result).toMatchInlineSnapshot(`
Array [
"_source",
]
`);
});
test('returns default columns given a index pattern with timefield', async () => {
const result = getDisplayedColumns([], indexPatternWithTimefieldMock);
expect(result).toMatchInlineSnapshot(`
Array [
"_source",
]
`);
});
test('returns default columns when just timefield is in state', async () => {
const result = getDisplayedColumns(['timestamp'], indexPatternWithTimefieldMock);
expect(result).toMatchInlineSnapshot(`
Array [
"_source",
]
`);
});
test('returns columns given by argument, no fallback ', async () => {
const result = getDisplayedColumns(['test'], indexPatternWithTimefieldMock);
expect(result).toMatchInlineSnapshot(`
Array [
"test",
]
`);
});
});

View file

@ -0,0 +1,22 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* and the Server Side Public License, v 1; you may not use this file except in
* compliance with, at your election, the Elastic License or the Server Side
* Public License, v 1.
*/
import { IndexPattern } from '../../../../data/common';
/**
* Function to provide fallback when
* 1) no columns are given
* 2) Just one column is given, which is the configured timefields
*/
export function getDisplayedColumns(stateColumns: string[] = [], indexPattern: IndexPattern) {
return stateColumns &&
stateColumns.length > 0 &&
// check if all columns where removed except the configured timeField (this can't be removed)
!(stateColumns.length === 1 && stateColumns[0] === indexPattern.timeFieldName)
? stateColumns
: ['_source'];
}

View file

@ -42,7 +42,6 @@ import {
} from '../../kibana_legacy/public';
import { DiscoverStartPlugins } from './plugin';
import { getScopedHistory } from './kibana_services';
import { createDiscoverLegacyDirective } from './application/components/create_discover_legacy_directive';
import { createDiscoverDirective } from './application/components/create_discover_directive';
/**
@ -124,7 +123,6 @@ export function initializeInnerAngularModule(
.config(watchMultiDecorator)
.run(registerListenEventListener)
.directive('renderComplete', createRenderCompleteDirective)
.directive('discoverLegacy', createDiscoverLegacyDirective)
.directive('discover', createDiscoverDirective);
}

View file

@ -38,7 +38,7 @@ export default function ({
const getTitles = async () =>
(await testSubjects.getVisibleText('dataGridHeader')).replace(/\s|\r?\n|\r/g, ' ');
expect(await getTitles()).to.be('Time (@timestamp) _source');
expect(await getTitles()).to.be('Time (@timestamp) Document');
await PageObjects.discover.clickFieldListItemAdd('bytes');
expect(await getTitles()).to.be('Time (@timestamp) bytes');
@ -50,7 +50,7 @@ export default function ({
expect(await getTitles()).to.be('Time (@timestamp) agent');
await PageObjects.discover.clickFieldListItemAdd('agent');
expect(await getTitles()).to.be('Time (@timestamp) _source');
expect(await getTitles()).to.be('Time (@timestamp) Document');
});
});
}

View file

@ -61,7 +61,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
});
it('doc view should show Time and _source columns', async function () {
const expectedHeader = 'Time (@timestamp) _source';
const expectedHeader = 'Time (@timestamp) Document';
const DocHeader = await dataGrid.getHeaderFields();
expect(DocHeader.join(' ')).to.be(expectedHeader);
});