diff --git a/src/plugins/discover/public/application/angular/discover.js b/src/plugins/discover/public/application/angular/discover.js index af63485507d05..3733e86698958 100644 --- a/src/plugins/discover/public/application/angular/discover.js +++ b/src/plugins/discover/public/application/angular/discover.js @@ -252,6 +252,12 @@ function discoverController($route, $scope, Promise) { (prop) => !_.isEqual(newStatePartial[prop], oldStatePartial[prop]) ); + if (oldStatePartial.hideChart && !newStatePartial.hideChart) { + // in case the histogram is hidden, no data is requested + // so when changing this state data needs to be fetched + changes.push(true); + } + if (changes.length) { refetch$.next(); } @@ -313,6 +319,8 @@ function discoverController($route, $scope, Promise) { setAppState, data, stateContainer, + searchSessionManager, + refetch$, }; const inspectorAdapters = ($scope.opts.inspectorAdapters = { @@ -412,6 +420,9 @@ function discoverController($route, $scope, Promise) { if (savedSearch.grid) { defaultState.grid = savedSearch.grid; } + if (savedSearch.hideChart) { + defaultState.hideChart = savedSearch.hideChart; + } return defaultState; } @@ -562,13 +573,6 @@ function discoverController($route, $scope, Promise) { }); }; - $scope.handleRefresh = function (_payload, isUpdate) { - if (isUpdate === false) { - searchSessionManager.removeSearchSessionIdFromURL({ replace: false }); - refetch$.next(); - } - }; - function getDimensions(aggs, timeRange) { const [metric, agg] = aggs; agg.params.timeRange = timeRange; @@ -601,7 +605,7 @@ function discoverController($route, $scope, Promise) { function onResults(resp) { inspectorRequest.stats(getResponseInspectorStats(resp, $scope.searchSource)).ok({ json: resp }); - if (getTimeField()) { + if (getTimeField() && !$scope.state.hideChart) { const tabifiedData = tabifyAggResponse($scope.opts.chartAggConfigs, resp); $scope.searchSource.rawResponse = resp; $scope.histogramData = discoverResponseHandler( @@ -704,7 +708,7 @@ function discoverController($route, $scope, Promise) { async function setupVisualization() { // If no timefield has been specified we don't create a histogram of messages - if (!getTimeField()) return; + if (!getTimeField() || $scope.state.hideChart) return; const { interval: histogramInterval } = $scope.state; const visStateAggs = [ diff --git a/src/plugins/discover/public/application/angular/discover_legacy.html b/src/plugins/discover/public/application/angular/discover_legacy.html index dc18b7929318b..501496494106a 100644 --- a/src/plugins/discover/public/application/angular/discover_legacy.html +++ b/src/plugins/discover/public/application/angular/discover_legacy.html @@ -17,7 +17,6 @@ state="state" time-range="timeRange" top-nav-menu="topNavMenu" - update-query="handleRefresh" use-new-fields-api="useNewFieldsApi" unmapped-fields-config="unmappedFieldsConfig" > diff --git a/src/plugins/discover/public/application/angular/discover_state.ts b/src/plugins/discover/public/application/angular/discover_state.ts index 5e93966d78d9f..93fc49b65cbc9 100644 --- a/src/plugins/discover/public/application/angular/discover_state.ts +++ b/src/plugins/discover/public/application/angular/discover_state.ts @@ -44,6 +44,10 @@ export interface AppState { * Data Grid related state */ grid?: DiscoverGridSettings; + /** + * Hide chart + */ + hideChart?: boolean; /** * id of the used index pattern */ diff --git a/src/plugins/discover/public/application/components/discover.test.tsx b/src/plugins/discover/public/application/components/discover.test.tsx index f0f11558abd65..00554196e11fd 100644 --- a/src/plugins/discover/public/application/components/discover.test.tsx +++ b/src/plugins/discover/public/application/components/discover.test.tsx @@ -25,6 +25,8 @@ import { indexPatternWithTimefieldMock } from '../../__mocks__/index_pattern_wit import { calcFieldCounts } from '../helpers/calc_field_counts'; import { DiscoverProps } from './types'; import { RequestAdapter } from '../../../../inspector/common'; +import { Subject } from 'rxjs'; +import { DiscoverSearchSessionManager } from '../angular/discover_search_session'; const mockNavigation = navigationPluginMock.createStartContract(); @@ -73,8 +75,10 @@ function getProps(indexPattern: IndexPattern): DiscoverProps { indexPatternList: (indexPattern as unknown) as Array>, inspectorAdapters: { requests: {} as RequestAdapter }, navigateTo: jest.fn(), + refetch$: {} as Subject, sampleSize: 10, savedSearch: savedSearchMock, + searchSessionManager: {} as DiscoverSearchSessionManager, setHeaderActionMenu: jest.fn(), timefield: indexPattern.timeFieldName || '', setAppState: jest.fn(), @@ -86,7 +90,6 @@ function getProps(indexPattern: IndexPattern): DiscoverProps { rows: esHits, searchSource: searchSourceMock, state: { columns: [] }, - updateQuery: jest.fn(), }; } diff --git a/src/plugins/discover/public/application/components/discover.tsx b/src/plugins/discover/public/application/components/discover.tsx index 99baa30e18c7a..71650a4a38472 100644 --- a/src/plugins/discover/public/application/components/discover.tsx +++ b/src/plugins/discover/public/application/components/discover.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ import './discover.scss'; -import React, { useCallback, useMemo, useRef, useState } from 'react'; +import React, { useState, useRef, useMemo, useCallback } from 'react'; import { EuiButtonEmpty, EuiButtonIcon, @@ -66,7 +66,6 @@ export function Discover({ searchSource, state, timeRange, - updateQuery, unmappedFieldsConfig, }: DiscoverProps) { const [expandedDoc, setExpandedDoc] = useState(undefined); @@ -76,8 +75,11 @@ export function Discover({ // 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 toggleHideChart = useCallback(() => { + const newState = { ...state, hideChart: !state.hideChart }; + opts.stateContainer.setAppState(newState); + }, [state, opts]); + const hideChart = useMemo(() => state.hideChart, [state]); const { savedSearch, indexPatternList, config, services, data, setAppState } = opts; const { trackUiMetric, capabilities, indexPatterns } = services; const [isSidebarClosed, setIsSidebarClosed] = useState(false); @@ -89,6 +91,15 @@ export function Discover({ const contentCentered = resultState === 'uninitialized'; const isLegacy = services.uiSettings.get('doc_table:legacy'); const useNewFieldsApi = !services.uiSettings.get(SEARCH_FIELDS_FROM_SOURCE); + const updateQuery = useCallback( + (_payload, isUpdate?: boolean) => { + if (isUpdate === false) { + opts.searchSessionManager.removeSearchSessionIdFromURL({ replace: false }); + opts.refetch$.next(); + } + }, + [opts] + ); const { onAddColumn, onRemoveColumn, onMoveColumn, onSetColumns } = useMemo( () => @@ -192,7 +203,8 @@ export function Discover({ indexPattern={indexPattern} opts={opts} onOpenInspector={onOpenInspector} - state={state} + query={state.query} + savedQuery={state.savedQuery} updateQuery={updateQuery} /> @@ -277,7 +289,7 @@ export function Discover({ onResetQuery={resetQuery} /> - {toggleOn && ( + {!hideChart && ( { - toggleChart(!toggleOn); + toggleHideChart(); }} data-test-subj="discoverChartToggle" > - {toggleOn + {!hideChart ? i18n.translate('discover.hideChart', { defaultMessage: 'Hide chart', }) @@ -312,7 +324,7 @@ export function Discover({ {isLegacy && } - {toggleOn && opts.timefield && ( + {!hideChart && opts.timefield && (
>, inspectorAdapters: { requests: {} as RequestAdapter }, navigateTo: jest.fn(), + refetch$: {} as Subject, sampleSize: 10, savedSearch: savedSearchMock, + searchSessionManager: {} as DiscoverSearchSessionManager, services, setAppState: jest.fn(), setHeaderActionMenu: jest.fn(), stateContainer: {} as GetStateReturn, timefield: indexPattern.timeFieldName || '', }, - state, + query: {} as Query, + savedQuery: '', updateQuery: jest.fn(), onOpenInspector: jest.fn(), }; diff --git a/src/plugins/discover/public/application/components/discover_topnav.tsx b/src/plugins/discover/public/application/components/discover_topnav.tsx index 69a1433b6505c..fd2aba22aa41d 100644 --- a/src/plugins/discover/public/application/components/discover_topnav.tsx +++ b/src/plugins/discover/public/application/components/discover_topnav.tsx @@ -8,17 +8,21 @@ import React, { useMemo } from 'react'; import { DiscoverProps } from './types'; import { getTopNavLinks } from './top_nav/get_top_nav_links'; +import { Query, TimeRange } from '../../../../data/common/query'; -export type DiscoverTopNavProps = Pick< - DiscoverProps, - 'indexPattern' | 'updateQuery' | 'state' | 'opts' -> & { onOpenInspector: () => void }; +export type DiscoverTopNavProps = Pick & { + onOpenInspector: () => void; + query?: Query; + savedQuery?: string; + updateQuery: (payload: { dateRange: TimeRange; query?: Query }, isUpdate?: boolean) => void; +}; export const DiscoverTopNav = ({ indexPattern, opts, onOpenInspector, - state, + query, + savedQuery, updateQuery, }: DiscoverTopNavProps) => { const showDatePicker = useMemo(() => indexPattern.isTimeBased(), [indexPattern]); @@ -58,9 +62,9 @@ export const DiscoverTopNav = ({ indexPatterns={[indexPattern]} onQuerySubmit={updateQuery} onSavedQueryIdChange={updateSavedQueryId} - query={state.query} + query={query} setMenuMountPoint={opts.setHeaderActionMenu} - savedQueryId={state.savedQuery} + savedQueryId={savedQuery} screenTitle={opts.savedSearch.title} showDatePicker={showDatePicker} showSaveQuery={!!opts.services.capabilities.discover.saveQuery} diff --git a/src/plugins/discover/public/application/components/types.ts b/src/plugins/discover/public/application/components/types.ts index ee06bcab6528b..e276795f9ed7f 100644 --- a/src/plugins/discover/public/application/components/types.ts +++ b/src/plugins/discover/public/application/components/types.ts @@ -7,6 +7,7 @@ */ import { IUiSettingsClient, MountPoint, SavedObject } from 'kibana/public'; +import { Subject } from 'rxjs'; import { Chart } from '../angular/helpers/point_series'; import { IndexPattern } from '../../../../data/common/index_patterns/index_patterns'; import { ElasticSearchHit } from '../doc_views/doc_views_types'; @@ -17,13 +18,12 @@ import { FilterManager, IndexPatternAttributes, ISearchSource, - Query, - TimeRange, } from '../../../../data/public'; import { SavedSearch } from '../../saved_searches'; import { AppState, GetStateReturn } from '../angular/discover_state'; import { RequestAdapter } from '../../../../inspector/common'; import { DiscoverServices } from '../../build_services'; +import { DiscoverSearchSessionManager } from '../angular/discover_search_session'; export interface DiscoverProps { /** @@ -97,10 +97,18 @@ export interface DiscoverProps { * List of available index patterns */ indexPatternList: Array>; + /** + * Refetch observable + */ + refetch$: Subject; /** * Kibana core services used by discover */ services: DiscoverServices; + /** + * Helps with state management of search session + */ + searchSessionManager: DiscoverSearchSessionManager; /** * The number of documents that can be displayed in the table/grid */ @@ -113,10 +121,6 @@ export interface DiscoverProps { * Function to set the header menu */ setHeaderActionMenu: (menuMount: MountPoint | undefined) => void; - /** - * Functions for retrieving/mutating state - */ - stateContainer: GetStateReturn; /** * Timefield of the currently used index pattern */ @@ -125,6 +129,10 @@ export interface DiscoverProps { * Function to set the current state */ setAppState: (state: Partial) => void; + /** + * State container providing globalState, appState and functions + */ + stateContainer: GetStateReturn; }; /** * Function to reset the current query @@ -150,10 +158,6 @@ export interface DiscoverProps { * Currently selected time range */ timeRange?: { from: string; to: string }; - /** - * Function to update the actual query - */ - updateQuery: (payload: { dateRange: TimeRange; query?: Query }, isUpdate?: boolean) => void; /** * An object containing properties for proper handling of unmapped fields in the UI */ diff --git a/src/plugins/discover/public/application/helpers/persist_saved_search.ts b/src/plugins/discover/public/application/helpers/persist_saved_search.ts index 1bebf60c0a805..06e90c93bc77c 100644 --- a/src/plugins/discover/public/application/helpers/persist_saved_search.ts +++ b/src/plugins/discover/public/application/helpers/persist_saved_search.ts @@ -48,6 +48,9 @@ export async function persistSavedSearch( if (state.grid) { savedSearch.grid = state.grid; } + if (state.hideChart) { + savedSearch.hideChart = state.hideChart; + } try { const id = await savedSearch.save(saveOptions); diff --git a/src/plugins/discover/public/saved_searches/_saved_search.ts b/src/plugins/discover/public/saved_searches/_saved_search.ts index d5bd3ea4011bb..a7b6ef49cacd2 100644 --- a/src/plugins/discover/public/saved_searches/_saved_search.ts +++ b/src/plugins/discover/public/saved_searches/_saved_search.ts @@ -14,6 +14,7 @@ export function createSavedSearchClass(savedObjects: SavedObjectsStart) { public static mapping = { title: 'text', description: 'text', + hideChart: 'boolean', hits: 'integer', columns: 'keyword', grid: 'object', @@ -35,6 +36,7 @@ export function createSavedSearchClass(savedObjects: SavedObjectsStart) { mapping: { title: 'text', description: 'text', + hideChart: 'boolean', hits: 'integer', columns: 'keyword', grid: 'object', diff --git a/src/plugins/discover/public/saved_searches/types.ts b/src/plugins/discover/public/saved_searches/types.ts index 24fbbcb61cb48..4646744ee0ef3 100644 --- a/src/plugins/discover/public/saved_searches/types.ts +++ b/src/plugins/discover/public/saved_searches/types.ts @@ -24,6 +24,7 @@ export interface SavedSearch { lastSavedTitle?: string; copyOnSave?: boolean; pre712?: boolean; + hideChart?: boolean; } export interface SavedSearchLoader { get: (id: string) => Promise; diff --git a/src/plugins/discover/server/saved_objects/search.ts b/src/plugins/discover/server/saved_objects/search.ts index 43f107399ac36..de3a2197fe0ac 100644 --- a/src/plugins/discover/server/saved_objects/search.ts +++ b/src/plugins/discover/server/saved_objects/search.ts @@ -34,6 +34,7 @@ export const searchSavedObjectType: SavedObjectsType = { properties: { columns: { type: 'keyword', index: false, doc_values: false }, description: { type: 'text' }, + hideChart: { type: 'boolean', index: false, doc_values: false }, hits: { type: 'integer', index: false, doc_values: false }, kibanaSavedObjectMeta: { properties: { diff --git a/test/functional/apps/discover/_discover_histogram.ts b/test/functional/apps/discover/_discover_histogram.ts index 56dc784eac70b..9a6692dc793d6 100644 --- a/test/functional/apps/discover/_discover_histogram.ts +++ b/test/functional/apps/discover/_discover_histogram.ts @@ -19,6 +19,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { defaultIndex: 'long-window-logstash-*', 'dateFormat:tz': 'Europe/Berlin', }; + const testSubjects = getService('testSubjects'); + const browser = getService('browser'); describe('discover histogram', function describeIndexTests() { before(async () => { @@ -35,11 +37,13 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await security.testUser.restoreDefaults(); }); - async function prepareTest(fromTime: string, toTime: string, interval: string) { + async function prepareTest(fromTime: string, toTime: string, interval?: string) { await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); await PageObjects.discover.waitUntilSearchingHasFinished(); - await PageObjects.discover.setChartInterval(interval); - await PageObjects.header.waitUntilLoadingHasFinished(); + if (interval) { + await PageObjects.discover.setChartInterval(interval); + await PageObjects.header.waitUntilLoadingHasFinished(); + } } it('should visualize monthly data with different day intervals', async () => { @@ -65,5 +69,43 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const chartIntervalIconTip = await PageObjects.discover.getChartIntervalWarningIcon(); expect(chartIntervalIconTip).to.be(true); }); + it('should allow hide/show histogram, persisted in url state', async () => { + const fromTime = 'Jan 01, 2010 @ 00:00:00.000'; + const toTime = 'Mar 21, 2019 @ 00:00:00.000'; + await prepareTest(fromTime, toTime); + let canvasExists = await elasticChart.canvasExists(); + expect(canvasExists).to.be(true); + await testSubjects.click('discoverChartToggle'); + canvasExists = await elasticChart.canvasExists(); + expect(canvasExists).to.be(false); + // histogram is hidden, when reloading the page it should remain hidden + await browser.refresh(); + canvasExists = await elasticChart.canvasExists(); + expect(canvasExists).to.be(false); + await testSubjects.click('discoverChartToggle'); + await PageObjects.header.waitUntilLoadingHasFinished(); + canvasExists = await elasticChart.canvasExists(); + expect(canvasExists).to.be(true); + }); + it('should allow hiding the histogram, persisted in saved search', async () => { + const fromTime = 'Jan 01, 2010 @ 00:00:00.000'; + const toTime = 'Mar 21, 2019 @ 00:00:00.000'; + const savedSearch = 'persisted hidden histogram'; + await prepareTest(fromTime, toTime); + await testSubjects.click('discoverChartToggle'); + let canvasExists = await elasticChart.canvasExists(); + expect(canvasExists).to.be(false); + await PageObjects.discover.saveSearch(savedSearch); + await PageObjects.header.waitUntilLoadingHasFinished(); + canvasExists = await elasticChart.canvasExists(); + expect(canvasExists).to.be(false); + await testSubjects.click('discoverChartToggle'); + canvasExists = await elasticChart.canvasExists(); + expect(canvasExists).to.be(true); + await PageObjects.discover.clickResetSavedSearchButton(); + await PageObjects.header.waitUntilLoadingHasFinished(); + canvasExists = await elasticChart.canvasExists(); + expect(canvasExists).to.be(false); + }); }); }