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

[Discover] Enhance breakdown by functionality #4

Original file line number Diff line number Diff line change
@@ -118,7 +118,7 @@ describe('checking migration metadata changes on all registered SO types', () =>
"osquery-saved-query": "7b213b4b7a3e59350e99c50e8df9948662ed493a",
"query": "4640ef356321500a678869f24117b7091a911cb6",
"sample-data-telemetry": "8b10336d9efae6f3d5593c4cc89fb4abcdf84e04",
"search": "e7ba25ea37cb36b622db42c9590c6d8dfc838801",
"search": "963f2eb44b401e48c8c7cbe980e85572c2f52506",
"search-session": "ba383309da68a15be3765977f7a44c84f0ec7964",
"search-telemetry": "beb3fc25488c753f2a6dcff1845d667558712b66",
"security-rule": "e0dfdba5d66139d0300723b2e6672993cd4a11f3",
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import React from 'react';
import { Subject, BehaviorSubject, of } from 'rxjs';
import { mountWithIntl } from '@kbn/test-jest-helpers';
import { esHits } from '../../../../__mocks__/es_hits';
import { dataViewMock } from '../../../../__mocks__/data_view';
import { GetStateReturn } from '../../services/discover_state';
import { savedSearchMock } from '../../../../__mocks__/saved_search';
import {
AvailableFields$,
DataDocuments$,
DataMain$,
DataTotalHits$,
RecordRawType,
} from '../../hooks/use_saved_search';
import { discoverServiceMock } from '../../../../__mocks__/services';
import { FetchStatus } from '../../../types';
import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { buildDataTableRecord } from '../../../../utils/build_data_record';
import {
DiscoverHistogramContent,
DiscoverHistogramContentProps,
} from './discover_histogram_content';
import { VIEW_MODE } from '@kbn/saved-search-plugin/public';
import { CoreTheme } from '@kbn/core/public';
import { act } from 'react-dom/test-utils';
import { setTimeout } from 'timers/promises';
import { DocumentViewModeToggle } from '../../../../components/view_mode_toggle';
import { searchSourceInstanceMock } from '@kbn/data-plugin/common/search/search_source/mocks';

const mountComponent = async ({
isPlainRecord = false,
}: {
isPlainRecord?: boolean;
} = {}) => {
const services = discoverServiceMock;
services.data.query.timefilter.timefilter.getAbsoluteTime = () => {
return { from: '2020-05-14T11:05:13.590', to: '2020-05-14T11:20:13.590' };
};

(services.data.query.queryString.getDefaultQuery as jest.Mock).mockReturnValue({
language: 'kuery',
query: '',
});
(searchSourceInstanceMock.fetch$ as jest.Mock).mockImplementation(
jest.fn().mockReturnValue(of({ rawResponse: { hits: { total: 2 } } }))
);

const main$ = new BehaviorSubject({
fetchStatus: FetchStatus.COMPLETE,
recordRawType: isPlainRecord ? RecordRawType.PLAIN : RecordRawType.DOCUMENT,
foundDocuments: true,
}) as DataMain$;

const documents$ = new BehaviorSubject({
fetchStatus: FetchStatus.COMPLETE,
result: esHits.map((esHit) => buildDataTableRecord(esHit, dataViewMock)),
}) as DataDocuments$;

const availableFields$ = new BehaviorSubject({
fetchStatus: FetchStatus.COMPLETE,
fields: [] as string[],
}) as AvailableFields$;

const totalHits$ = new BehaviorSubject({
fetchStatus: FetchStatus.COMPLETE,
result: Number(esHits.length),
}) as DataTotalHits$;

const savedSearchData$ = {
main$,
documents$,
totalHits$,
availableFields$,
};

const props: DiscoverHistogramContentProps = {
isPlainRecord,
dataView: dataViewMock,
navigateTo: jest.fn(),
setExpandedDoc: jest.fn(),
savedSearch: savedSearchMock,
savedSearchData$,
savedSearchRefetch$: new Subject(),
state: { columns: [], hideChart: false },
stateContainer: {
setAppState: () => {},
appStateContainer: {
getState: () => ({
interval: 'auto',
}),
},
} as unknown as GetStateReturn,
onFieldEdited: jest.fn(),
columns: [],
viewMode: VIEW_MODE.DOCUMENT_LEVEL,
onAddFilter: jest.fn(),
};

const coreTheme$ = new BehaviorSubject<CoreTheme>({ darkMode: false });

const component = mountWithIntl(
<KibanaContextProvider services={services}>
<KibanaThemeProvider theme$={coreTheme$}>
<DiscoverHistogramContent {...props} />
</KibanaThemeProvider>
</KibanaContextProvider>
);

// DiscoverMainContent uses UnifiedHistogramLayout which
// is lazy loaded, so we need to wait for it to be loaded
await act(() => setTimeout(0));
component.update();

return component;
};

describe('Discover histogram content component', () => {
describe('DocumentViewModeToggle', () => {
it('should show DocumentViewModeToggle when isPlainRecord is false', async () => {
const component = await mountComponent();
expect(component.find(DocumentViewModeToggle).exists()).toBe(true);
});

it('should not show DocumentViewModeToggle when isPlainRecord is true', async () => {
const component = await mountComponent({ isPlainRecord: true });
expect(component.find(DocumentViewModeToggle).exists()).toBe(false);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule } from '@elastic/eui';
import { SavedSearch } from '@kbn/saved-search-plugin/public';
import React, { useCallback } from 'react';
import { DataView } from '@kbn/data-views-plugin/common';
import { METRIC_TYPE } from '@kbn/analytics';
import { useDiscoverServices } from '../../../../hooks/use_discover_services';
import { DataTableRecord } from '../../../../types';
import { DocumentViewModeToggle, VIEW_MODE } from '../../../../components/view_mode_toggle';
import { DocViewFilterFn } from '../../../../services/doc_views/doc_views_types';
import { DataRefetch$, SavedSearchData } from '../../hooks/use_saved_search';
import { AppState, GetStateReturn } from '../../services/discover_state';
import { FieldStatisticsTable } from '../field_stats_table';
import { DiscoverDocuments } from './discover_documents';
import { DOCUMENTS_VIEW_CLICK, FIELD_STATISTICS_VIEW_CLICK } from '../field_stats_table/constants';

const FieldStatisticsTableMemoized = React.memo(FieldStatisticsTable);

export interface CommonDiscoverHistogramProps {
dataView: DataView;
savedSearch: SavedSearch;
isPlainRecord: boolean;
navigateTo: (url: string) => void;
savedSearchData$: SavedSearchData;
savedSearchRefetch$: DataRefetch$;
expandedDoc?: DataTableRecord;
setExpandedDoc: (doc?: DataTableRecord) => void;
viewMode: VIEW_MODE;
onAddFilter: DocViewFilterFn | undefined;
onFieldEdited: () => Promise<void>;
columns: string[];
state: AppState;
stateContainer: GetStateReturn;
}

export interface DiscoverHistogramContentProps extends CommonDiscoverHistogramProps {
chartHidden?: boolean;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might be able to drop this prop and just use the key on this component instead, then we can just use the common props.

}

export const DiscoverHistogramContent = ({
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I may rename this back to DiscoverMainContent since it doesn't contain the histogram. But DiscoverHistogramLayout for the other component seems good to me.

dataView,
isPlainRecord,
navigateTo,
savedSearchData$,
savedSearchRefetch$,
expandedDoc,
setExpandedDoc,
viewMode,
onAddFilter,
onFieldEdited,
columns,
state,
stateContainer,
savedSearch,
chartHidden,
}: DiscoverHistogramContentProps) => {
const { trackUiMetric } = useDiscoverServices();

const setDiscoverViewMode = useCallback(
(mode: VIEW_MODE) => {
stateContainer.setAppState({ viewMode: mode });

if (trackUiMetric) {
if (mode === VIEW_MODE.AGGREGATED_LEVEL) {
trackUiMetric(METRIC_TYPE.CLICK, FIELD_STATISTICS_VIEW_CLICK);
} else {
trackUiMetric(METRIC_TYPE.CLICK, DOCUMENTS_VIEW_CLICK);
}
}
},
[trackUiMetric, stateContainer]
);

return (
<EuiFlexGroup
className="eui-fullHeight"
direction="column"
gutterSize="none"
responsive={false}
>
{!isPlainRecord && (
<EuiFlexItem grow={false}>
<EuiHorizontalRule margin="none" />
<DocumentViewModeToggle viewMode={viewMode} setDiscoverViewMode={setDiscoverViewMode} />
</EuiFlexItem>
)}
{viewMode === VIEW_MODE.DOCUMENT_LEVEL ? (
<DiscoverDocuments
// The documents grid doesn't rerender when the chart visibility changes
// which causes it to render blank space, so we need to force a rerender
key={`docKey${chartHidden}`}
documents$={savedSearchData$.documents$}
expandedDoc={expandedDoc}
dataView={dataView}
navigateTo={navigateTo}
onAddFilter={!isPlainRecord ? onAddFilter : undefined}
savedSearch={savedSearch}
setExpandedDoc={setExpandedDoc}
state={state}
stateContainer={stateContainer}
onFieldEdited={!isPlainRecord ? onFieldEdited : undefined}
/>
) : (
<FieldStatisticsTableMemoized
availableFields$={savedSearchData$.availableFields$}
savedSearch={savedSearch}
dataView={dataView}
query={state.query}
filters={state.filters}
columns={columns}
stateContainer={stateContainer}
onAddFilter={!isPlainRecord ? onAddFilter : undefined}
trackUiMetric={trackUiMetric}
savedSearchRefetch$={savedSearchRefetch$}
savedSearchDataTotalHits$={savedSearchData$.totalHits$}
/>
)}
</EuiFlexGroup>
);
};
Loading