mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[Discover] Deangularize discover controller (#96766)
Co-authored-by: Tim Roes <mail@timroes.de>
This commit is contained in:
parent
4fbb6f0baa
commit
65251051fd
169 changed files with 2299 additions and 2048 deletions
|
@ -45,6 +45,10 @@ export const searchSourceCommonMock: jest.Mocked<ISearchStartSearchSource> = {
|
|||
export const createSearchSourceMock = (fields?: SearchSourceFields) =>
|
||||
new SearchSource(fields, {
|
||||
getConfig: uiSettingsServiceMock.createStartContract().get,
|
||||
search: jest.fn(),
|
||||
search: jest
|
||||
.fn()
|
||||
.mockReturnValue(
|
||||
of({ rawResponse: { hits: { hits: [], total: 0 } }, isPartial: false, isRunning: false })
|
||||
),
|
||||
onResponse: jest.fn().mockImplementation((req, res) => res),
|
||||
});
|
||||
|
|
|
@ -699,7 +699,7 @@ export class SearchSource {
|
|||
searchRequest.body = searchRequest.body || {};
|
||||
const { body, index, query, filters, highlightAll } = searchRequest;
|
||||
searchRequest.indexType = this.getIndexType(index);
|
||||
const metaFields = getConfig(UI_SETTINGS.META_FIELDS);
|
||||
const metaFields = getConfig(UI_SETTINGS.META_FIELDS) ?? [];
|
||||
|
||||
// get some special field types from the index pattern
|
||||
const { docvalueFields, scriptFields, storedFields, runtimeFields } = index
|
||||
|
|
|
@ -7,12 +7,13 @@
|
|||
*/
|
||||
|
||||
import { QueryStringContract } from '.';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
const createSetupContractMock = () => {
|
||||
const queryStringManagerMock: jest.Mocked<QueryStringContract> = {
|
||||
getQuery: jest.fn(),
|
||||
setQuery: jest.fn(),
|
||||
getUpdates$: jest.fn(),
|
||||
getUpdates$: jest.fn(() => new Observable()),
|
||||
getDefaultQuery: jest.fn(),
|
||||
formatQuery: jest.fn(),
|
||||
clearQuery: jest.fn(),
|
||||
|
|
|
@ -21,7 +21,7 @@ const createSetupContractMock = () => {
|
|||
getTimeUpdate$: jest.fn(),
|
||||
getRefreshIntervalUpdate$: jest.fn(),
|
||||
getAutoRefreshFetch$: jest.fn(() => new Observable<() => void>()),
|
||||
getFetch$: jest.fn(),
|
||||
getFetch$: jest.fn(() => new Observable<() => void>()),
|
||||
getTime: jest.fn(),
|
||||
setTime: jest.fn(),
|
||||
setRefreshInterval: jest.fn(),
|
||||
|
|
|
@ -65,6 +65,10 @@ fields.getByName = (name: string) => {
|
|||
return fields.find((field) => field.name === name);
|
||||
};
|
||||
|
||||
fields.getAll = () => {
|
||||
return fields;
|
||||
};
|
||||
|
||||
const indexPattern = ({
|
||||
id: 'the-index-pattern-id',
|
||||
title: 'the-index-pattern-title',
|
||||
|
|
|
@ -56,6 +56,9 @@ const fields = [
|
|||
fields.getByName = (name: string) => {
|
||||
return fields.find((field) => field.name === name);
|
||||
};
|
||||
fields.getAll = () => {
|
||||
return fields;
|
||||
};
|
||||
|
||||
const indexPattern = ({
|
||||
id: 'index-pattern-with-timefield-id',
|
||||
|
@ -72,5 +75,8 @@ const indexPattern = ({
|
|||
|
||||
indexPattern.flattenHit = indexPatterns.flattenHitWrapper(indexPattern, indexPattern.metaFields);
|
||||
indexPattern.isTimeBased = () => !!indexPattern.timeFieldName;
|
||||
indexPattern.formatField = (hit: Record<string, unknown>, fieldName: string) => {
|
||||
return fieldName === '_source' ? hit._source : indexPattern.flattenHit(hit)[fieldName];
|
||||
};
|
||||
|
||||
export const indexPatternWithTimefieldMock = indexPattern;
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
*/
|
||||
|
||||
import { SavedSearch } from '../saved_searches';
|
||||
import { createSearchSourceMock } from '../../../data/public/mocks';
|
||||
import { indexPatternMock } from './index_pattern';
|
||||
|
||||
export const savedSearchMock = ({
|
||||
id: 'the-saved-search-id',
|
||||
|
@ -27,4 +29,5 @@ export const savedSearchMock = ({
|
|||
],
|
||||
migrationVersion: { search: '7.5.0' },
|
||||
error: undefined,
|
||||
searchSource: createSearchSourceMock({ index: indexPatternMock }),
|
||||
} as unknown) as SavedSearch;
|
||||
|
|
27
src/plugins/discover/public/__mocks__/search_session.ts
Normal file
27
src/plugins/discover/public/__mocks__/search_session.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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 { createMemoryHistory } from 'history';
|
||||
import { dataPluginMock } from '../../../data/public/mocks';
|
||||
import { DataPublicPluginStart } from '../../../data/public';
|
||||
import { DiscoverSearchSessionManager } from '../application/apps/main/services/discover_search_session';
|
||||
|
||||
export function createSearchSessionMock() {
|
||||
const history = createMemoryHistory();
|
||||
const session = dataPluginMock.createStartContract().search.session as jest.Mocked<
|
||||
DataPublicPluginStart['search']['session']
|
||||
>;
|
||||
const searchSessionManager = new DiscoverSearchSessionManager({
|
||||
history,
|
||||
session,
|
||||
});
|
||||
return {
|
||||
history,
|
||||
session,
|
||||
searchSessionManager,
|
||||
};
|
||||
}
|
63
src/plugins/discover/public/__mocks__/services.ts
Normal file
63
src/plugins/discover/public/__mocks__/services.ts
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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 { DiscoverServices } from '../build_services';
|
||||
import { dataPluginMock } from '../../../data/public/mocks';
|
||||
import { chromeServiceMock, coreMock, docLinksServiceMock } from '../../../../core/public/mocks';
|
||||
import { DEFAULT_COLUMNS_SETTING } from '../../common';
|
||||
import { savedSearchMock } from './saved_search';
|
||||
import { UI_SETTINGS } from '../../../data/common';
|
||||
import { TopNavMenu } from '../../../navigation/public';
|
||||
const dataPlugin = dataPluginMock.createStartContract();
|
||||
|
||||
export const discoverServiceMock = ({
|
||||
core: coreMock.createStart(),
|
||||
chrome: chromeServiceMock.createStartContract(),
|
||||
history: () => ({
|
||||
location: {
|
||||
search: '',
|
||||
},
|
||||
}),
|
||||
data: dataPlugin,
|
||||
docLinks: docLinksServiceMock.createStartContract(),
|
||||
capabilities: {
|
||||
visualize: {
|
||||
show: true,
|
||||
},
|
||||
discover: {
|
||||
save: false,
|
||||
},
|
||||
advancedSettings: {
|
||||
save: true,
|
||||
},
|
||||
},
|
||||
filterManager: dataPlugin.query.filterManager,
|
||||
uiSettings: {
|
||||
get: (key: string) => {
|
||||
if (key === 'fields:popularLimit') {
|
||||
return 5;
|
||||
} else if (key === DEFAULT_COLUMNS_SETTING) {
|
||||
return [];
|
||||
} else if (key === UI_SETTINGS.META_FIELDS) {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
},
|
||||
indexPatternFieldEditor: {
|
||||
openEditor: jest.fn(),
|
||||
userPermissions: {
|
||||
editIndexPattern: jest.fn(),
|
||||
},
|
||||
},
|
||||
getSavedSearchById: (id?: string) => Promise.resolve(savedSearchMock),
|
||||
navigation: {
|
||||
ui: { TopNavMenu },
|
||||
},
|
||||
metadata: {
|
||||
branch: 'test',
|
||||
},
|
||||
} as unknown) as DiscoverServices;
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* 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 { DiscoverMainApp } from '../apps/main';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function createDiscoverDirective(reactDirective: any) {
|
||||
return reactDirective(DiscoverMainApp, [
|
||||
['indexPattern', { watchDepth: 'reference' }],
|
||||
['opts', { watchDepth: 'reference' }],
|
||||
]);
|
||||
}
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
import React, { useState } from 'react';
|
||||
import { I18nProvider } from '@kbn/i18n/react';
|
||||
import { DiscoverGrid, DiscoverGridProps } from './discover_grid/discover_grid';
|
||||
import { DiscoverGrid, DiscoverGridProps } from '../components/discover_grid/discover_grid';
|
||||
import { getServices } from '../../kibana_services';
|
||||
import { ElasticSearchHit } from '../doc_views/doc_views_types';
|
||||
|
|
@ -1,57 +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
|
||||
* 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 _ from 'lodash';
|
||||
// Debounce service, angularized version of lodash debounce
|
||||
// borrowed heavily from https://github.com/shahata/angular-debounce
|
||||
|
||||
export function createDebounceProviderTimeout($timeout) {
|
||||
return function (func, wait, options) {
|
||||
let timeout;
|
||||
let args;
|
||||
let self;
|
||||
let result;
|
||||
options = _.defaults(options || {}, {
|
||||
leading: false,
|
||||
trailing: true,
|
||||
invokeApply: true,
|
||||
});
|
||||
|
||||
function debounce() {
|
||||
self = this;
|
||||
args = arguments;
|
||||
|
||||
const later = function () {
|
||||
timeout = null;
|
||||
if (!options.leading || options.trailing) {
|
||||
result = func.apply(self, args);
|
||||
}
|
||||
};
|
||||
|
||||
const callNow = options.leading && !timeout;
|
||||
|
||||
if (timeout) {
|
||||
$timeout.cancel(timeout);
|
||||
}
|
||||
timeout = $timeout(later, wait, options.invokeApply);
|
||||
|
||||
if (callNow) {
|
||||
result = func.apply(self, args);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
debounce.cancel = function () {
|
||||
$timeout.cancel(timeout);
|
||||
timeout = null;
|
||||
};
|
||||
|
||||
return debounce;
|
||||
};
|
||||
}
|
|
@ -1,127 +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
|
||||
* 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 sinon, { SinonSpy } from 'sinon';
|
||||
import angular, { auto, ITimeoutService } from 'angular';
|
||||
import 'angular-mocks';
|
||||
import 'angular-sanitize';
|
||||
import 'angular-route';
|
||||
|
||||
// @ts-expect-error
|
||||
import { createDebounceProviderTimeout } from './debounce';
|
||||
import { coreMock } from '../../../../../../../core/public/mocks';
|
||||
import { initializeInnerAngularModule } from '../../../../get_inner_angular';
|
||||
import { navigationPluginMock } from '../../../../../../navigation/public/mocks';
|
||||
import { dataPluginMock } from '../../../../../../data/public/mocks';
|
||||
import { initAngularBootstrap } from '../../../../../../kibana_legacy/public';
|
||||
|
||||
describe('debounce service', function () {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let debounce: (fn: () => void, timeout: number, options?: any) => any;
|
||||
let $timeout: ITimeoutService;
|
||||
let spy: SinonSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
spy = sinon.spy();
|
||||
|
||||
initAngularBootstrap();
|
||||
|
||||
initializeInnerAngularModule(
|
||||
'app/discover',
|
||||
coreMock.createStart(),
|
||||
navigationPluginMock.createStartContract(),
|
||||
dataPluginMock.createStartContract()
|
||||
);
|
||||
|
||||
angular.mock.module('app/discover');
|
||||
|
||||
angular.mock.inject(($injector: auto.IInjectorService, _$timeout_: ITimeoutService) => {
|
||||
$timeout = _$timeout_;
|
||||
|
||||
debounce = createDebounceProviderTimeout($timeout);
|
||||
});
|
||||
});
|
||||
|
||||
it('should have a cancel method', function () {
|
||||
const bouncer = debounce(() => {}, 100);
|
||||
|
||||
expect(bouncer).toHaveProperty('cancel');
|
||||
});
|
||||
|
||||
describe('delayed execution', function () {
|
||||
const sandbox = sinon.createSandbox();
|
||||
|
||||
beforeEach(() => sandbox.useFakeTimers());
|
||||
afterEach(() => sandbox.restore());
|
||||
|
||||
it('should delay execution', function () {
|
||||
const bouncer = debounce(spy, 100);
|
||||
|
||||
bouncer();
|
||||
sinon.assert.notCalled(spy);
|
||||
$timeout.flush();
|
||||
sinon.assert.calledOnce(spy);
|
||||
|
||||
spy.resetHistory();
|
||||
});
|
||||
|
||||
it('should fire on leading edge', function () {
|
||||
const bouncer = debounce(spy, 100, { leading: true });
|
||||
|
||||
bouncer();
|
||||
sinon.assert.calledOnce(spy);
|
||||
$timeout.flush();
|
||||
sinon.assert.calledTwice(spy);
|
||||
|
||||
spy.resetHistory();
|
||||
});
|
||||
|
||||
it('should only fire on leading edge', function () {
|
||||
const bouncer = debounce(spy, 100, { leading: true, trailing: false });
|
||||
|
||||
bouncer();
|
||||
sinon.assert.calledOnce(spy);
|
||||
$timeout.flush();
|
||||
sinon.assert.calledOnce(spy);
|
||||
|
||||
spy.resetHistory();
|
||||
});
|
||||
|
||||
it('should reset delayed execution', function () {
|
||||
const cancelSpy = sinon.spy($timeout, 'cancel');
|
||||
const bouncer = debounce(spy, 100);
|
||||
|
||||
bouncer();
|
||||
sandbox.clock.tick(1);
|
||||
|
||||
bouncer();
|
||||
sinon.assert.notCalled(spy);
|
||||
$timeout.flush();
|
||||
sinon.assert.calledOnce(spy);
|
||||
sinon.assert.calledOnce(cancelSpy);
|
||||
|
||||
spy.resetHistory();
|
||||
cancelSpy.resetHistory();
|
||||
});
|
||||
});
|
||||
|
||||
describe('cancel', function () {
|
||||
it('should cancel the $timeout', function () {
|
||||
const cancelSpy = sinon.spy($timeout, 'cancel');
|
||||
const bouncer = debounce(spy, 100);
|
||||
|
||||
bouncer();
|
||||
bouncer.cancel();
|
||||
sinon.assert.calledOnce(cancelSpy);
|
||||
// throws if pending timeouts
|
||||
$timeout.verifyNoPendingTasks();
|
||||
|
||||
cancelSpy.resetHistory();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -6,47 +6,17 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { merge, Subject, Subscription } from 'rxjs';
|
||||
import { debounceTime, tap, filter } from 'rxjs/operators';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { createSearchSessionRestorationDataProvider, getState, splitState } from './discover_state';
|
||||
import { RequestAdapter } from '../../../../inspector/public';
|
||||
import {
|
||||
connectToQueryState,
|
||||
esFilters,
|
||||
indexPatterns as indexPatternsUtils,
|
||||
noSearchSessionStorageCapabilityMessage,
|
||||
syncQueryStateWithUrl,
|
||||
} from '../../../../data/public';
|
||||
import { getSortArray } from './doc_table';
|
||||
import { getState } from '../apps/main/services/discover_state';
|
||||
import indexTemplateLegacy from './discover_legacy.html';
|
||||
import { discoverResponseHandler } from './response_handler';
|
||||
import {
|
||||
getAngularModule,
|
||||
getHeaderActionMenuMounter,
|
||||
getServices,
|
||||
getUrlTracker,
|
||||
redirectWhenMissing,
|
||||
subscribeWithScope,
|
||||
tabifyAggResponse,
|
||||
} from '../../kibana_services';
|
||||
import { getRootBreadcrumbs, getSavedSearchBreadcrumbs } from '../helpers/breadcrumbs';
|
||||
import { getStateDefaults } from '../helpers/get_state_defaults';
|
||||
import { getResultState } from '../helpers/get_result_state';
|
||||
import { validateTimeRange } from '../helpers/validate_time_range';
|
||||
import { addFatalError } from '../../../../kibana_legacy/public';
|
||||
import {
|
||||
SAMPLE_SIZE_SETTING,
|
||||
SEARCH_FIELDS_FROM_SOURCE,
|
||||
SEARCH_ON_PAGE_LOAD_SETTING,
|
||||
} from '../../../common';
|
||||
import { loadIndexPattern, resolveIndexPattern } from '../helpers/resolve_index_pattern';
|
||||
import { updateSearchSource } from '../helpers/update_search_source';
|
||||
import { calcFieldCounts } from '../helpers/calc_field_counts';
|
||||
import { DiscoverSearchSessionManager } from './discover_search_session';
|
||||
import { applyAggsToSearchSource, getDimensions } from '../components/histogram';
|
||||
import { fetchStatuses } from '../components/constants';
|
||||
import { loadIndexPattern, resolveIndexPattern } from '../apps/main/utils/resolve_index_pattern';
|
||||
|
||||
const services = getServices();
|
||||
|
||||
|
@ -56,8 +26,6 @@ const {
|
|||
chrome,
|
||||
data,
|
||||
history: getHistory,
|
||||
filterManager,
|
||||
timefilter,
|
||||
toastNotifications,
|
||||
uiSettings: config,
|
||||
} = getServices();
|
||||
|
@ -147,390 +115,29 @@ app.directive('discoverApp', function () {
|
|||
});
|
||||
|
||||
function discoverController($route, $scope) {
|
||||
const { isDefault: isDefaultType } = indexPatternsUtils;
|
||||
const subscriptions = new Subscription();
|
||||
const refetch$ = new Subject();
|
||||
|
||||
let isChangingIndexPattern = false;
|
||||
const savedSearch = $route.current.locals.savedObjects.savedSearch;
|
||||
const persistentSearchSource = savedSearch.searchSource;
|
||||
$scope.indexPattern = resolveIndexPattern(
|
||||
$route.current.locals.savedObjects.ip,
|
||||
persistentSearchSource,
|
||||
savedSearch.searchSource,
|
||||
toastNotifications
|
||||
);
|
||||
$scope.useNewFieldsApi = !config.get(SEARCH_FIELDS_FROM_SOURCE);
|
||||
|
||||
//used for functional testing
|
||||
$scope.fetchCounter = 0;
|
||||
|
||||
const getTimeField = () => {
|
||||
return isDefaultType($scope.indexPattern) ? $scope.indexPattern.timeFieldName : undefined;
|
||||
};
|
||||
|
||||
const history = getHistory();
|
||||
const searchSessionManager = new DiscoverSearchSessionManager({
|
||||
history,
|
||||
session: data.search.session,
|
||||
});
|
||||
|
||||
const stateContainer = getState({
|
||||
getStateDefaults: () =>
|
||||
getStateDefaults({
|
||||
config,
|
||||
data,
|
||||
indexPattern: $scope.indexPattern,
|
||||
savedSearch,
|
||||
searchSource: persistentSearchSource,
|
||||
}),
|
||||
storeInSessionStorage: config.get('state:storeInSessionStorage'),
|
||||
history,
|
||||
toasts: core.notifications.toasts,
|
||||
uiSettings: config,
|
||||
});
|
||||
|
||||
const {
|
||||
appStateContainer,
|
||||
startSync: startStateSync,
|
||||
stopSync: stopStateSync,
|
||||
setAppState,
|
||||
replaceUrlAppState,
|
||||
kbnUrlStateStorage,
|
||||
getPreviousAppState,
|
||||
} = stateContainer;
|
||||
|
||||
if (appStateContainer.getState().index !== $scope.indexPattern.id) {
|
||||
//used index pattern is different than the given by url/state which is invalid
|
||||
setAppState({ index: $scope.indexPattern.id });
|
||||
}
|
||||
$scope.state = { ...appStateContainer.getState() };
|
||||
|
||||
// syncs `_g` portion of url with query services
|
||||
const { stop: stopSyncingGlobalStateWithUrl } = syncQueryStateWithUrl(
|
||||
data.query,
|
||||
kbnUrlStateStorage
|
||||
);
|
||||
|
||||
// sync initial app filters from state to filterManager
|
||||
filterManager.setAppFilters(_.cloneDeep(appStateContainer.getState().filters));
|
||||
data.query.queryString.setQuery(appStateContainer.getState().query);
|
||||
|
||||
const stopSyncingQueryAppStateWithStateContainer = connectToQueryState(
|
||||
data.query,
|
||||
appStateContainer,
|
||||
{
|
||||
filters: esFilters.FilterStateStore.APP_STATE,
|
||||
query: true,
|
||||
}
|
||||
);
|
||||
const showUnmappedFields = $scope.useNewFieldsApi;
|
||||
const updateSearchSourceHelper = () => {
|
||||
const { indexPattern, useNewFieldsApi } = $scope;
|
||||
const { columns, sort } = $scope.state;
|
||||
updateSearchSource({
|
||||
persistentSearchSource,
|
||||
volatileSearchSource: $scope.volatileSearchSource,
|
||||
indexPattern,
|
||||
services,
|
||||
sort,
|
||||
columns,
|
||||
useNewFieldsApi,
|
||||
showUnmappedFields,
|
||||
});
|
||||
};
|
||||
|
||||
const appStateUnsubscribe = appStateContainer.subscribe(async (newState) => {
|
||||
const { state: newStatePartial } = splitState(newState);
|
||||
const { state: oldStatePartial } = splitState(getPreviousAppState());
|
||||
|
||||
if (!_.isEqual(newStatePartial, oldStatePartial)) {
|
||||
$scope.$evalAsync(async () => {
|
||||
// NOTE: this is also called when navigating from discover app to context app
|
||||
if (newStatePartial.index && oldStatePartial.index !== newStatePartial.index) {
|
||||
//in case of index pattern switch the route has currently to be reloaded, legacy
|
||||
isChangingIndexPattern = true;
|
||||
$route.reload();
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.state = { ...newState };
|
||||
|
||||
// detect changes that should trigger fetching of new data
|
||||
const changes = ['interval', 'sort'].filter(
|
||||
(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();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// this listener is waiting for such a path http://localhost:5601/app/discover#/
|
||||
// which could be set through pressing "New" button in top nav or go to "Discover" plugin from the sidebar
|
||||
// to reload the page in a right way
|
||||
const unlistenHistoryBasePath = history.listen(({ pathname, search, hash }) => {
|
||||
if (!search && !hash && pathname === '/') {
|
||||
$route.reload();
|
||||
}
|
||||
});
|
||||
|
||||
data.search.session.enableStorage(
|
||||
createSearchSessionRestorationDataProvider({
|
||||
appStateContainer,
|
||||
data,
|
||||
getSavedSearch: () => savedSearch,
|
||||
}),
|
||||
{
|
||||
isDisabled: () =>
|
||||
capabilities.discover.storeSearchSession
|
||||
? { disabled: false }
|
||||
: {
|
||||
disabled: true,
|
||||
reasonText: noSearchSessionStorageCapabilityMessage,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
$scope.opts = {
|
||||
// number of records to fetch, then paginate through
|
||||
sampleSize: config.get(SAMPLE_SIZE_SETTING),
|
||||
timefield: getTimeField(),
|
||||
savedSearch: savedSearch,
|
||||
savedSearch,
|
||||
history,
|
||||
services,
|
||||
indexPatternList: $route.current.locals.savedObjects.ip.list,
|
||||
config: config,
|
||||
setHeaderActionMenu: getHeaderActionMenuMounter(),
|
||||
filterManager,
|
||||
setAppState,
|
||||
data,
|
||||
stateContainer,
|
||||
searchSessionManager,
|
||||
refetch$,
|
||||
navigateTo: (path) => {
|
||||
$scope.$evalAsync(() => {
|
||||
history.push(path);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
const inspectorAdapters = ($scope.opts.inspectorAdapters = {
|
||||
requests: new RequestAdapter(),
|
||||
});
|
||||
|
||||
const shouldSearchOnPageLoad = () => {
|
||||
// A saved search is created on every page load, so we check the ID to see if we're loading a
|
||||
// previously saved search or if it is just transient
|
||||
return (
|
||||
config.get(SEARCH_ON_PAGE_LOAD_SETTING) ||
|
||||
savedSearch.id !== undefined ||
|
||||
timefilter.getRefreshInterval().pause === false ||
|
||||
searchSessionManager.hasSearchSessionIdInURL()
|
||||
);
|
||||
};
|
||||
|
||||
$scope.fetchStatus = fetchStatuses.UNINITIALIZED;
|
||||
$scope.resultState = shouldSearchOnPageLoad() ? 'loading' : 'uninitialized';
|
||||
|
||||
let abortController;
|
||||
$scope.$on('$destroy', () => {
|
||||
if (abortController) abortController.abort();
|
||||
savedSearch.destroy();
|
||||
subscriptions.unsubscribe();
|
||||
if (!isChangingIndexPattern) {
|
||||
// HACK:
|
||||
// do not clear session when changing index pattern due to how state management around it is setup
|
||||
// it will be cleared by searchSessionManager on controller reload instead
|
||||
data.search.session.clear();
|
||||
}
|
||||
appStateUnsubscribe();
|
||||
stopStateSync();
|
||||
stopSyncingGlobalStateWithUrl();
|
||||
stopSyncingQueryAppStateWithStateContainer();
|
||||
unlistenHistoryBasePath();
|
||||
});
|
||||
|
||||
$scope.opts.getFieldCounts = async () => {
|
||||
// the field counts aren't set until we have the data back,
|
||||
// so we wait for the fetch to be done before proceeding
|
||||
if ($scope.fetchStatus === fetchStatuses.COMPLETE) {
|
||||
return $scope.fieldCounts;
|
||||
}
|
||||
|
||||
return await new Promise((resolve) => {
|
||||
const unwatch = $scope.$watch('fetchStatus', (newValue) => {
|
||||
if (newValue === fetchStatuses.COMPLETE) {
|
||||
unwatch();
|
||||
resolve($scope.fieldCounts);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
$scope.opts.navigateTo = (path) => {
|
||||
$scope.$evalAsync(() => {
|
||||
history.push(path);
|
||||
});
|
||||
};
|
||||
|
||||
persistentSearchSource.setField('index', $scope.indexPattern);
|
||||
|
||||
// searchSource which applies time range
|
||||
const volatileSearchSource = savedSearch.searchSource.create();
|
||||
|
||||
if (isDefaultType($scope.indexPattern)) {
|
||||
volatileSearchSource.setField('filter', () => {
|
||||
return timefilter.createFilter($scope.indexPattern);
|
||||
});
|
||||
}
|
||||
|
||||
volatileSearchSource.setParent(persistentSearchSource);
|
||||
$scope.volatileSearchSource = volatileSearchSource;
|
||||
$scope.state.index = $scope.indexPattern.id;
|
||||
$scope.state.sort = getSortArray($scope.state.sort, $scope.indexPattern);
|
||||
|
||||
$scope.opts.fetch = $scope.fetch = async function () {
|
||||
$scope.fetchCounter++;
|
||||
$scope.fetchError = undefined;
|
||||
if (!validateTimeRange(timefilter.getTime(), toastNotifications)) {
|
||||
$scope.resultState = 'none';
|
||||
}
|
||||
|
||||
// Abort any in-progress requests before fetching again
|
||||
if (abortController) abortController.abort();
|
||||
abortController = new AbortController();
|
||||
|
||||
const searchSessionId = searchSessionManager.getNextSearchSessionId();
|
||||
updateSearchSourceHelper();
|
||||
|
||||
$scope.opts.chartAggConfigs = applyAggsToSearchSource(
|
||||
getTimeField() && !$scope.state.hideChart,
|
||||
volatileSearchSource,
|
||||
$scope.state.interval,
|
||||
$scope.indexPattern,
|
||||
data
|
||||
);
|
||||
|
||||
$scope.fetchStatus = fetchStatuses.LOADING;
|
||||
$scope.resultState = getResultState($scope.fetchStatus, $scope.rows);
|
||||
|
||||
inspectorAdapters.requests.reset();
|
||||
return $scope.volatileSearchSource
|
||||
.fetch$({
|
||||
abortSignal: abortController.signal,
|
||||
sessionId: searchSessionId,
|
||||
inspector: {
|
||||
adapter: inspectorAdapters.requests,
|
||||
title: i18n.translate('discover.inspectorRequestDataTitle', {
|
||||
defaultMessage: 'data',
|
||||
}),
|
||||
description: i18n.translate('discover.inspectorRequestDescription', {
|
||||
defaultMessage: 'This request queries Elasticsearch to fetch the data for the search.',
|
||||
}),
|
||||
},
|
||||
})
|
||||
.toPromise()
|
||||
.then(({ rawResponse }) => onResults(rawResponse))
|
||||
.catch((error) => {
|
||||
// If the request was aborted then no need to surface this error in the UI
|
||||
if (error instanceof Error && error.name === 'AbortError') return;
|
||||
|
||||
$scope.fetchStatus = fetchStatuses.NO_RESULTS;
|
||||
$scope.fetchError = error;
|
||||
data.search.showError(error);
|
||||
})
|
||||
.finally(() => {
|
||||
$scope.resultState = getResultState($scope.fetchStatus, $scope.rows);
|
||||
$scope.$apply();
|
||||
});
|
||||
};
|
||||
|
||||
function onResults(resp) {
|
||||
if (getTimeField() && !$scope.state.hideChart) {
|
||||
const tabifiedData = tabifyAggResponse($scope.opts.chartAggConfigs, resp);
|
||||
$scope.volatileSearchSource.rawResponse = resp;
|
||||
const dimensions = getDimensions($scope.opts.chartAggConfigs, data);
|
||||
if (dimensions) {
|
||||
$scope.histogramData = discoverResponseHandler(tabifiedData, dimensions);
|
||||
}
|
||||
}
|
||||
|
||||
$scope.hits = resp.hits.total;
|
||||
$scope.rows = resp.hits.hits;
|
||||
|
||||
$scope.fieldCounts = calcFieldCounts(
|
||||
$scope.fieldCounts || {},
|
||||
resp.hits.hits,
|
||||
$scope.indexPattern
|
||||
);
|
||||
$scope.fetchStatus = fetchStatuses.COMPLETE;
|
||||
}
|
||||
|
||||
$scope.refreshAppState = async () => {
|
||||
$scope.hits = [];
|
||||
$scope.rows = [];
|
||||
$scope.fieldCounts = {};
|
||||
await refetch$.next();
|
||||
};
|
||||
|
||||
$scope.resetQuery = function () {
|
||||
history.push(
|
||||
$route.current.params.id ? `/view/${encodeURIComponent($route.current.params.id)}` : '/'
|
||||
);
|
||||
$route.reload();
|
||||
};
|
||||
|
||||
$scope.newQuery = function () {
|
||||
history.push('/');
|
||||
};
|
||||
|
||||
$scope.unmappedFieldsConfig = {
|
||||
showUnmappedFields,
|
||||
};
|
||||
|
||||
// handler emitted by `timefilter.getAutoRefreshFetch$()`
|
||||
// to notify when data completed loading and to start a new autorefresh loop
|
||||
let autoRefreshDoneCb;
|
||||
const fetch$ = merge(
|
||||
refetch$,
|
||||
filterManager.getFetches$(),
|
||||
timefilter.getFetch$(),
|
||||
timefilter.getAutoRefreshFetch$().pipe(
|
||||
tap((done) => {
|
||||
autoRefreshDoneCb = done;
|
||||
}),
|
||||
filter(() => $scope.fetchStatus !== fetchStatuses.LOADING)
|
||||
),
|
||||
data.query.queryString.getUpdates$(),
|
||||
searchSessionManager.newSearchSessionIdFromURL$
|
||||
).pipe(debounceTime(100));
|
||||
|
||||
subscriptions.add(
|
||||
subscribeWithScope(
|
||||
$scope,
|
||||
fetch$,
|
||||
{
|
||||
next: async () => {
|
||||
try {
|
||||
await $scope.fetch();
|
||||
} finally {
|
||||
// if there is a saved `autoRefreshDoneCb`, notify auto refresh service that
|
||||
// the last fetch is completed so it starts the next auto refresh loop if needed
|
||||
autoRefreshDoneCb?.();
|
||||
autoRefreshDoneCb = undefined;
|
||||
}
|
||||
},
|
||||
},
|
||||
(error) => addFatalError(core.fatalErrors, error)
|
||||
)
|
||||
);
|
||||
|
||||
// Propagate current app state to url, then start syncing and fetching
|
||||
replaceUrlAppState().then(() => {
|
||||
startStateSync();
|
||||
if (shouldSearchOnPageLoad()) {
|
||||
refetch$.next();
|
||||
}
|
||||
data.search.session.clear();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,31 +1,11 @@
|
|||
<discover-app>
|
||||
<discover
|
||||
fetch="fetch"
|
||||
fetch-counter="fetchCounter"
|
||||
fetch-error="fetchError"
|
||||
field-counts="fieldCounts"
|
||||
histogram-data="histogramData"
|
||||
hits="hits"
|
||||
angularRoute="angularRoute"
|
||||
index-pattern="indexPattern"
|
||||
on-add-column="addColumn"
|
||||
on-add-filter="filterQuery"
|
||||
on-change-interval="changeInterval"
|
||||
on-remove-column="removeColumn"
|
||||
on-set-columns="setColumns"
|
||||
on-sort="setSortOrder"
|
||||
opts="opts"
|
||||
reset-query="resetQuery"
|
||||
result-state="resultState"
|
||||
rows="rows"
|
||||
search-source="volatileSearchSource"
|
||||
set-index-pattern="setIndexPattern"
|
||||
show-save-query="showSaveQuery"
|
||||
state="state"
|
||||
time-filter-update-handler="timefilterUpdateHandler"
|
||||
time-range="timeRange"
|
||||
top-nav-menu="topNavMenu"
|
||||
update-query="updateQuery"
|
||||
update-saved-query-id="updateSavedQueryId"
|
||||
>
|
||||
</discover>
|
||||
</discover-app>
|
||||
|
|
|
@ -1,22 +1,7 @@
|
|||
<discover-app>
|
||||
<discover
|
||||
fetch="fetch"
|
||||
fetch-counter="fetchCounter"
|
||||
fetch-error="fetchError"
|
||||
field-counts="fieldCounts"
|
||||
histogram-data="histogramData"
|
||||
hits="hits"
|
||||
index-pattern="indexPattern"
|
||||
opts="opts"
|
||||
reset-query="resetQuery"
|
||||
result-state="resultState"
|
||||
fetch-status="fetchStatus"
|
||||
rows="rows"
|
||||
search-source="volatileSearchSource"
|
||||
state="state"
|
||||
top-nav-menu="topNavMenu"
|
||||
use-new-fields-api="useNewFieldsApi"
|
||||
unmapped-fields-config="unmappedFieldsConfig"
|
||||
refresh-app-state="refreshAppState">
|
||||
>
|
||||
</discover>
|
||||
</discover-app>
|
||||
</discover-app>
|
||||
|
|
|
@ -11,7 +11,7 @@ import { configMock } from '../../../../__mocks__/config';
|
|||
import { indexPatternMock } from '../../../../__mocks__/index_pattern';
|
||||
import { indexPatternsMock } from '../../../../__mocks__/index_patterns';
|
||||
import { Capabilities } from '../../../../../../../core/types';
|
||||
import { AppState } from '../../discover_state';
|
||||
import { AppState } from '../../../apps/main/services/discover_state';
|
||||
|
||||
function getStateColumnAction(state: {}, setAppState: (state: Partial<AppState>) => void) {
|
||||
return getStateColumnActions({
|
||||
|
|
|
@ -11,7 +11,7 @@ import { IndexPattern, IndexPatternsContract } from '../../../../kibana_services
|
|||
import {
|
||||
AppState as DiscoverState,
|
||||
GetStateReturn as DiscoverGetStateReturn,
|
||||
} from '../../discover_state';
|
||||
} from '../../../apps/main/services/discover_state';
|
||||
import {
|
||||
AppState as ContextState,
|
||||
GetStateReturn as ContextGetStateReturn,
|
||||
|
|
|
@ -19,7 +19,7 @@ import { setScopedHistory, setServices, setDocViewsRegistry } from '../../../../
|
|||
import { coreMock } from '../../../../../../../core/public/mocks';
|
||||
import { dataPluginMock } from '../../../../../../data/public/mocks';
|
||||
import { navigationPluginMock } from '../../../../../../navigation/public/mocks';
|
||||
import { getInnerAngularModule } from '../../../../get_inner_angular';
|
||||
import { getInnerAngularModule } from '../../get_inner_angular';
|
||||
import { createBrowserHistory } from 'history';
|
||||
|
||||
const fakeRowVals = {
|
||||
|
|
|
@ -13,8 +13,8 @@ import type { estypes } from '@elastic/elasticsearch';
|
|||
import { EuiButtonEmpty } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { getServices, IIndexPattern } from '../../../kibana_services';
|
||||
import { IndexPatternField } from '../../../../../data/common/index_patterns';
|
||||
import { SkipBottomButton } from '../../components/skip_bottom_button';
|
||||
import { IndexPatternField } from '../../../../../data/common';
|
||||
import { SkipBottomButton } from '../../apps/main/components/skip_bottom_button';
|
||||
|
||||
export interface DocTableLegacyProps {
|
||||
columns: string[];
|
||||
|
@ -23,7 +23,7 @@ export interface DocTableLegacyProps {
|
|||
onFilter: (field: IndexPatternField | string, value: string, type: '+' | '-') => void;
|
||||
rows: estypes.Hit[];
|
||||
indexPattern: IIndexPattern;
|
||||
minimumVisibleRows: number;
|
||||
minimumVisibleRows?: number;
|
||||
onAddColumn?: (column: string) => void;
|
||||
onBackToTop: () => void;
|
||||
onSort?: (sort: string[][]) => void;
|
||||
|
@ -119,11 +119,9 @@ export function DocTableLegacy(renderProps: DocTableLegacyProps) {
|
|||
}, [setMinimumVisibleRows, renderProps.rows]);
|
||||
|
||||
useEffect(() => {
|
||||
if (minimumVisibleRows > 50) {
|
||||
setMinimumVisibleRows(50);
|
||||
}
|
||||
setMinimumVisibleRows(50);
|
||||
setRows(renderProps.rows);
|
||||
}, [renderProps.rows, minimumVisibleRows, setMinimumVisibleRows]);
|
||||
}, [renderProps.rows, setMinimumVisibleRows]);
|
||||
|
||||
useEffect(() => {
|
||||
if (ref && ref.current && !scope.current) {
|
||||
|
@ -133,7 +131,7 @@ export function DocTableLegacy(renderProps: DocTableLegacyProps) {
|
|||
});
|
||||
} else if (scope && scope.current) {
|
||||
scope.current.renderProps = { ...renderProps, rows, minimumVisibleRows };
|
||||
scope.current.$apply();
|
||||
scope.current.$applyAsync();
|
||||
}
|
||||
}, [renderProps, minimumVisibleRows, rows]);
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ import { coreMock } from '../../../../../../core/public/mocks';
|
|||
import { dataPluginMock } from '../../../../../data/public/mocks';
|
||||
import { navigationPluginMock } from '../../../../../navigation/public/mocks';
|
||||
import { setScopedHistory, setServices } from '../../../kibana_services';
|
||||
import { getInnerAngularModule } from '../../../get_inner_angular';
|
||||
import { getInnerAngularModule } from '../get_inner_angular';
|
||||
|
||||
let $parentScope;
|
||||
|
||||
|
|
|
@ -9,29 +9,29 @@
|
|||
// inner angular imports
|
||||
// these are necessary to bootstrap the local angular.
|
||||
// They can stay even after NP cutover
|
||||
import './application/index.scss';
|
||||
import '../index.scss';
|
||||
import angular from 'angular';
|
||||
// required for `ngSanitize` angular module
|
||||
import 'angular-sanitize';
|
||||
import { EuiIcon } from '@elastic/eui';
|
||||
import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular';
|
||||
import { CoreStart, PluginInitializerContext } from 'kibana/public';
|
||||
import { DataPublicPluginStart } from '../../data/public';
|
||||
import { Storage } from '../../kibana_utils/public';
|
||||
import { NavigationPublicPluginStart as NavigationStart } from '../../navigation/public';
|
||||
import { createDocTableDirective } from './application/angular/doc_table';
|
||||
import { createTableHeaderDirective } from './application/angular/doc_table/components/table_header';
|
||||
import { DataPublicPluginStart } from '../../../../data/public';
|
||||
import { Storage } from '../../../../kibana_utils/public';
|
||||
import { NavigationPublicPluginStart as NavigationStart } from '../../../../navigation/public';
|
||||
import { createDocTableDirective } from './doc_table';
|
||||
import { createTableHeaderDirective } from './doc_table/components/table_header';
|
||||
import {
|
||||
createToolBarPagerButtonsDirective,
|
||||
createToolBarPagerTextDirective,
|
||||
} from './application/angular/doc_table/components/pager';
|
||||
import { createContextAppLegacy } from './application/components/context_app/context_app_legacy_directive';
|
||||
import { createTableRowDirective } from './application/angular/doc_table/components/table_row';
|
||||
import { createPagerFactory } from './application/angular/doc_table/lib/pager/pager_factory';
|
||||
import { createInfiniteScrollDirective } from './application/angular/doc_table/infinite_scroll';
|
||||
import { createDocViewerDirective } from './application/angular/doc_viewer';
|
||||
import { createDiscoverGridDirective } from './application/components/create_discover_grid_directive';
|
||||
import { createRenderCompleteDirective } from './application/angular/directives/render_complete';
|
||||
} from './doc_table/components/pager';
|
||||
import { createContextAppLegacy } from '../components/context_app/context_app_legacy_directive';
|
||||
import { createTableRowDirective } from './doc_table/components/table_row';
|
||||
import { createPagerFactory } from './doc_table/lib/pager/pager_factory';
|
||||
import { createInfiniteScrollDirective } from './doc_table/infinite_scroll';
|
||||
import { createDocViewerDirective } from './doc_viewer';
|
||||
import { createDiscoverGridDirective } from './create_discover_grid_directive';
|
||||
import { createRenderCompleteDirective } from './directives/render_complete';
|
||||
import {
|
||||
initAngularBootstrap,
|
||||
configureAppAngularModule,
|
||||
|
@ -39,10 +39,10 @@ import {
|
|||
PromiseServiceCreator,
|
||||
registerListenEventListener,
|
||||
watchMultiDecorator,
|
||||
} from '../../kibana_legacy/public';
|
||||
import { DiscoverStartPlugins } from './plugin';
|
||||
import { getScopedHistory } from './kibana_services';
|
||||
import { createDiscoverDirective } from './application/components/create_discover_directive';
|
||||
} from '../../../../kibana_legacy/public';
|
||||
import { DiscoverStartPlugins } from '../../plugin';
|
||||
import { getScopedHistory } from '../../kibana_services';
|
||||
import { createDiscoverDirective } from './create_discover_directive';
|
||||
|
||||
/**
|
||||
* returns the main inner angular module, it contains all the parts of Angular Discover
|
|
@ -6,6 +6,5 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { buildPointSeriesData } from './point_series';
|
||||
export { formatRow, formatTopLevelObject } from './row_formatter';
|
||||
export { handleSourceColumnState } from './state_helpers';
|
||||
|
|
|
@ -16,4 +16,3 @@ import './doc';
|
|||
import './context';
|
||||
import './doc_viewer';
|
||||
import './redirect';
|
||||
import './directives';
|
||||
|
|
|
@ -1,109 +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
|
||||
* 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 { getServices } from '../../kibana_services';
|
||||
import { buildPointSeriesData } from './helpers';
|
||||
|
||||
function tableResponseHandler(table, dimensions) {
|
||||
const converted = { tables: [] };
|
||||
const split = dimensions.splitColumn || dimensions.splitRow;
|
||||
|
||||
if (split) {
|
||||
converted.direction = dimensions.splitRow ? 'row' : 'column';
|
||||
const splitColumnIndex = split[0].accessor;
|
||||
const splitColumnFormatter = getServices().data.fieldFormats.deserialize(split[0].format);
|
||||
const splitColumn = table.columns[splitColumnIndex];
|
||||
const splitMap = {};
|
||||
let splitIndex = 0;
|
||||
|
||||
table.rows.forEach((row, rowIndex) => {
|
||||
const splitValue = row[splitColumn.id];
|
||||
|
||||
if (!splitMap.hasOwnProperty(splitValue)) {
|
||||
splitMap[splitValue] = splitIndex++;
|
||||
const tableGroup = {
|
||||
$parent: converted,
|
||||
title: `${splitColumnFormatter.convert(splitValue)}: ${splitColumn.name}`,
|
||||
name: splitColumn.name,
|
||||
key: splitValue,
|
||||
column: splitColumnIndex,
|
||||
row: rowIndex,
|
||||
table,
|
||||
tables: [],
|
||||
};
|
||||
tableGroup.tables.push({
|
||||
$parent: tableGroup,
|
||||
columns: table.columns,
|
||||
rows: [],
|
||||
});
|
||||
|
||||
converted.tables.push(tableGroup);
|
||||
}
|
||||
|
||||
const tableIndex = splitMap[splitValue];
|
||||
converted.tables[tableIndex].tables[0].rows.push(row);
|
||||
});
|
||||
} else {
|
||||
converted.tables.push({
|
||||
columns: table.columns,
|
||||
rows: table.rows,
|
||||
});
|
||||
}
|
||||
|
||||
return converted;
|
||||
}
|
||||
|
||||
function convertTableGroup(tableGroup, convertTable) {
|
||||
const tables = tableGroup.tables;
|
||||
|
||||
if (!tables.length) return;
|
||||
|
||||
const firstChild = tables[0];
|
||||
if (firstChild.columns) {
|
||||
const chart = convertTable(firstChild);
|
||||
// if chart is within a split, assign group title to its label
|
||||
if (tableGroup.$parent) {
|
||||
chart.label = tableGroup.title;
|
||||
}
|
||||
return chart;
|
||||
}
|
||||
|
||||
const out = {};
|
||||
let outList;
|
||||
|
||||
tables.forEach(function (table) {
|
||||
if (!outList) {
|
||||
const direction = tableGroup.direction === 'row' ? 'rows' : 'columns';
|
||||
outList = out[direction] = [];
|
||||
}
|
||||
|
||||
let output;
|
||||
if ((output = convertTableGroup(table, convertTable))) {
|
||||
outList.push(output);
|
||||
}
|
||||
});
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
export const discoverResponseHandler = (response, dimensions) => {
|
||||
const tableGroup = tableResponseHandler(response, dimensions);
|
||||
|
||||
let converted = convertTableGroup(tableGroup, (table) => {
|
||||
return buildPointSeriesData(table, dimensions);
|
||||
});
|
||||
if (!converted) {
|
||||
// mimic a row of tables that doesn't have any tables
|
||||
// https://github.com/elastic/kibana/blob/7bfb68cd24ed42b1b257682f93c50cd8d73e2520/src/kibana/components/vislib/components/zero_injection/inject_zeros.js#L32
|
||||
converted = { rows: [] };
|
||||
}
|
||||
|
||||
converted.hits = response.rows.length;
|
||||
|
||||
return converted;
|
||||
};
|
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
* 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, { useCallback } from 'react';
|
||||
import moment from 'moment';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiButtonEmpty, EuiSpacer } from '@elastic/eui';
|
||||
import { IUiSettingsClient } from 'kibana/public';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { HitsCounter } from '../hits_counter';
|
||||
import { DataPublicPluginStart, IndexPattern, search } from '../../../../../../../data/public';
|
||||
import { TimechartHeader } from '../timechart_header';
|
||||
import { SavedSearch } from '../../../../../saved_searches';
|
||||
import { AppState, GetStateReturn } from '../../services/discover_state';
|
||||
import { TimechartBucketInterval } from '../timechart_header/timechart_header';
|
||||
import { Chart as IChart } from './point_series';
|
||||
import { DiscoverHistogram } from './histogram';
|
||||
|
||||
const TimechartHeaderMemoized = React.memo(TimechartHeader);
|
||||
const DiscoverHistogramMemoized = React.memo(DiscoverHistogram);
|
||||
export function DiscoverChart({
|
||||
config,
|
||||
data,
|
||||
bucketInterval,
|
||||
chartData,
|
||||
hits,
|
||||
isLegacy,
|
||||
resetQuery,
|
||||
savedSearch,
|
||||
state,
|
||||
stateContainer,
|
||||
timefield,
|
||||
}: {
|
||||
config: IUiSettingsClient;
|
||||
data: DataPublicPluginStart;
|
||||
bucketInterval?: TimechartBucketInterval;
|
||||
chartData?: IChart;
|
||||
hits?: number;
|
||||
indexPattern: IndexPattern;
|
||||
isLegacy: boolean;
|
||||
resetQuery: () => void;
|
||||
savedSearch: SavedSearch;
|
||||
state: AppState;
|
||||
stateContainer: GetStateReturn;
|
||||
timefield?: string;
|
||||
}) {
|
||||
const toggleHideChart = useCallback(() => {
|
||||
stateContainer.setAppState({ hideChart: !state.hideChart });
|
||||
}, [state, stateContainer]);
|
||||
|
||||
const onChangeInterval = useCallback(
|
||||
(interval: string) => {
|
||||
if (interval) {
|
||||
stateContainer.setAppState({ interval });
|
||||
}
|
||||
},
|
||||
[stateContainer]
|
||||
);
|
||||
|
||||
const timefilterUpdateHandler = useCallback(
|
||||
(ranges: { from: number; to: number }) => {
|
||||
data.query.timefilter.timefilter.setTime({
|
||||
from: moment(ranges.from).toISOString(),
|
||||
to: moment(ranges.to).toISOString(),
|
||||
mode: 'absolute',
|
||||
});
|
||||
},
|
||||
[data]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup 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}
|
||||
showResetButton={!!(savedSearch && savedSearch.id)}
|
||||
onResetQuery={resetQuery}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
{!state.hideChart && (
|
||||
<EuiFlexItem className="dscResultCount__actions">
|
||||
<TimechartHeaderMemoized
|
||||
data={data}
|
||||
dateFormat={config.get('dateFormat')}
|
||||
options={search.aggs.intervalOptions}
|
||||
onChangeInterval={onChangeInterval}
|
||||
stateInterval={state.interval || ''}
|
||||
bucketInterval={bucketInterval}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
{timefield && (
|
||||
<EuiFlexItem className="dscResultCount__toggle" grow={false}>
|
||||
<EuiButtonEmpty
|
||||
size="xs"
|
||||
iconType={!state.hideChart ? 'eyeClosed' : 'eye'}
|
||||
onClick={() => {
|
||||
toggleHideChart();
|
||||
}}
|
||||
data-test-subj="discoverChartToggle"
|
||||
>
|
||||
{!state.hideChart
|
||||
? i18n.translate('discover.hideChart', {
|
||||
defaultMessage: 'Hide chart',
|
||||
})
|
||||
: i18n.translate('discover.showChart', {
|
||||
defaultMessage: 'Show chart',
|
||||
})}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
{!state.hideChart && chartData && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<section
|
||||
aria-label={i18n.translate('discover.histogramOfFoundDocumentsAriaLabel', {
|
||||
defaultMessage: 'Histogram of found documents',
|
||||
})}
|
||||
className="dscTimechart"
|
||||
>
|
||||
<div
|
||||
className={isLegacy ? 'dscHistogram' : 'dscHistogramGrid'}
|
||||
data-test-subj="discoverChart"
|
||||
>
|
||||
<DiscoverHistogramMemoized
|
||||
chartData={chartData}
|
||||
timefilterUpdateHandler={timefilterUpdateHandler}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
<EuiSpacer size="s" />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
|
@ -27,14 +27,14 @@ import {
|
|||
import { IUiSettingsClient } from 'kibana/public';
|
||||
import { EuiChartThemeType } from '@elastic/eui/dist/eui_charts_theme';
|
||||
import { Subscription, combineLatest } from 'rxjs';
|
||||
import { getServices } from '../../../kibana_services';
|
||||
import { Chart as IChart } from '../helpers/point_series';
|
||||
import { getServices } from '../../../../../kibana_services';
|
||||
import { Chart as IChart } from './point_series';
|
||||
import {
|
||||
CurrentTime,
|
||||
Endzones,
|
||||
getAdjustedInterval,
|
||||
renderEndzoneTooltip,
|
||||
} from '../../../../../charts/public';
|
||||
} from '../../../../../../../charts/public';
|
||||
|
||||
export interface DiscoverHistogramProps {
|
||||
chartData: IChart;
|
|
@ -6,4 +6,4 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { createDebounceProviderTimeout } from './debounce';
|
||||
export { DiscoverChart } from './discover_chart';
|
|
@ -9,8 +9,7 @@
|
|||
import { uniq } from 'lodash';
|
||||
import { Duration, Moment } from 'moment';
|
||||
import { Unit } from '@elastic/datemath';
|
||||
|
||||
import { SerializedFieldFormat } from '../../../../../expressions/common/types';
|
||||
import { SerializedFieldFormat } from '../../../../../../../expressions/common';
|
||||
|
||||
export interface Column {
|
||||
id: string;
|
||||
|
@ -26,20 +25,23 @@ export interface Table {
|
|||
rows: Row[];
|
||||
}
|
||||
|
||||
export interface HistogramParamsBounds {
|
||||
min: Moment;
|
||||
max: Moment;
|
||||
}
|
||||
|
||||
interface HistogramParams {
|
||||
date: true;
|
||||
interval: Duration;
|
||||
intervalESValue: number;
|
||||
intervalESUnit: Unit;
|
||||
format: string;
|
||||
bounds: {
|
||||
min: Moment;
|
||||
max: Moment;
|
||||
};
|
||||
bounds: HistogramParamsBounds;
|
||||
}
|
||||
export interface Dimension {
|
||||
accessor: 0 | 1;
|
||||
format: SerializedFieldFormat<{ pattern: string }>;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export interface Dimensions {
|
||||
|
@ -68,7 +70,7 @@ export interface Chart {
|
|||
ordered: Ordered;
|
||||
}
|
||||
|
||||
export const buildPointSeriesData = (table: Table, dimensions: Dimensions) => {
|
||||
export const buildPointSeriesData = (table: Table, dimensions: Dimensions): Chart => {
|
||||
const { x, y } = dimensions;
|
||||
const xAccessor = table.columns[x.accessor].id;
|
||||
const yAccessor = table.columns[y.accessor].id;
|
|
@ -12,13 +12,13 @@ import React from 'react';
|
|||
import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
|
||||
import { FormattedMessage, I18nProvider } from '@kbn/i18n/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { formatNumWithCommas } from '../../helpers';
|
||||
import { formatNumWithCommas } from '../../../../helpers';
|
||||
|
||||
export interface HitsCounterProps {
|
||||
/**
|
||||
* the number of query hits
|
||||
*/
|
||||
hits: number;
|
||||
hits?: number;
|
||||
/**
|
||||
* displays the reset button
|
||||
*/
|
||||
|
@ -30,6 +30,9 @@ export interface HitsCounterProps {
|
|||
}
|
||||
|
||||
export function HitsCounter({ hits, showResetButton, onResetQuery }: HitsCounterProps) {
|
||||
if (typeof hits === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<I18nProvider>
|
||||
<EuiFlexGroup
|
|
@ -1,4 +1,4 @@
|
|||
@import '../../../../../core/public/mixins';
|
||||
@import 'src/core/public/mixins';
|
||||
|
||||
discover-app {
|
||||
flex-grow: 1;
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* 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 } from 'rxjs';
|
||||
import { mountWithIntl } from '@kbn/test/jest';
|
||||
import { setHeaderActionMenuMounter } from '../../../../../kibana_services';
|
||||
import { DiscoverLayout } from './discover_layout';
|
||||
import { esHits } from '../../../../../__mocks__/es_hits';
|
||||
import { indexPatternMock } from '../../../../../__mocks__/index_pattern';
|
||||
import { savedSearchMock } from '../../../../../__mocks__/saved_search';
|
||||
import { createSearchSourceMock } from '../../../../../../../data/common/search/search_source/mocks';
|
||||
import { IndexPattern, IndexPatternAttributes } from '../../../../../../../data/common';
|
||||
import { SavedObject } from '../../../../../../../../core/types';
|
||||
import { indexPatternWithTimefieldMock } from '../../../../../__mocks__/index_pattern_with_timefield';
|
||||
import { DiscoverSearchSessionManager } from '../../services/discover_search_session';
|
||||
import { GetStateReturn } from '../../services/discover_state';
|
||||
import { DiscoverLayoutProps } from './types';
|
||||
import { SavedSearchDataSubject } from '../../services/use_saved_search';
|
||||
import { discoverServiceMock } from '../../../../../__mocks__/services';
|
||||
import { FetchStatus } from '../../../../types';
|
||||
|
||||
setHeaderActionMenuMounter(jest.fn());
|
||||
|
||||
function getProps(indexPattern: IndexPattern): DiscoverLayoutProps {
|
||||
const searchSourceMock = createSearchSourceMock({});
|
||||
const services = discoverServiceMock;
|
||||
services.data.query.timefilter.timefilter.getTime = () => {
|
||||
return { from: '2020-05-14T11:05:13.590', to: '2020-05-14T11:20:13.590' };
|
||||
};
|
||||
|
||||
const indexPatternList = ([indexPattern].map((ip) => {
|
||||
return { ...ip, ...{ attributes: { title: ip.title } } };
|
||||
}) as unknown) as Array<SavedObject<IndexPatternAttributes>>;
|
||||
|
||||
const savedSearch$ = new BehaviorSubject({
|
||||
state: FetchStatus.COMPLETE,
|
||||
rows: esHits,
|
||||
fetchCounter: 1,
|
||||
fieldCounts: {},
|
||||
hits: Number(esHits.length),
|
||||
}) as SavedSearchDataSubject;
|
||||
|
||||
return {
|
||||
indexPattern,
|
||||
indexPatternList,
|
||||
navigateTo: jest.fn(),
|
||||
resetQuery: jest.fn(),
|
||||
savedSearch: savedSearchMock,
|
||||
savedSearchData$: savedSearch$,
|
||||
savedSearchRefetch$: new Subject(),
|
||||
searchSessionManager: {} as DiscoverSearchSessionManager,
|
||||
searchSource: searchSourceMock,
|
||||
services,
|
||||
state: { columns: [] },
|
||||
stateContainer: {} as GetStateReturn,
|
||||
};
|
||||
}
|
||||
|
||||
describe('Discover component', () => {
|
||||
test('selected index pattern without time field displays no chart toggle', () => {
|
||||
const component = mountWithIntl(<DiscoverLayout {...getProps(indexPatternMock)} />);
|
||||
expect(component.find('[data-test-subj="discoverChartToggle"]').exists()).toBeFalsy();
|
||||
});
|
||||
test('selected index pattern with time field displays chart toggle', () => {
|
||||
const component = mountWithIntl(
|
||||
<DiscoverLayout {...getProps(indexPatternWithTimefieldMock)} />
|
||||
);
|
||||
expect(component.find('[data-test-subj="discoverChartToggle"]').exists()).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -5,10 +5,10 @@
|
|||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import './discover.scss';
|
||||
import './discover_layout.scss';
|
||||
import React, { useState, useRef, useMemo, useCallback, useEffect } from 'react';
|
||||
import {
|
||||
EuiButtonEmpty,
|
||||
EuiSpacer,
|
||||
EuiButtonIcon,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
|
@ -17,99 +17,141 @@ import {
|
|||
EuiPage,
|
||||
EuiPageBody,
|
||||
EuiPageContent,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import moment from 'moment';
|
||||
import { METRIC_TYPE } from '@kbn/analytics';
|
||||
import { FormattedMessage, I18nProvider } from '@kbn/i18n/react';
|
||||
import classNames from 'classnames';
|
||||
import { HitsCounter } from './hits_counter';
|
||||
import { TimechartHeader } from './timechart_header';
|
||||
import { DiscoverHistogram, DiscoverUninitialized } from '../angular/directives';
|
||||
import { DiscoverNoResults } from './no_results';
|
||||
import { LoadingSpinner } from './loading_spinner/loading_spinner';
|
||||
import { DocTableLegacy } from '../angular/doc_table/create_doc_table_react';
|
||||
import { esFilters, IndexPatternField, search } from '../../../../data/public';
|
||||
import { DiscoverSidebarResponsive } from './sidebar';
|
||||
import { DiscoverProps } from './types';
|
||||
import { SortPairArr } from '../angular/doc_table/lib/get_sort';
|
||||
import { DiscoverNoResults } from '../no_results';
|
||||
import { LoadingSpinner } from '../loading_spinner/loading_spinner';
|
||||
import { DocTableLegacy } from '../../../../angular/doc_table/create_doc_table_react';
|
||||
import {
|
||||
esFilters,
|
||||
IndexPatternField,
|
||||
indexPatterns as indexPatternsUtils,
|
||||
} from '../../../../../../../data/public';
|
||||
import { DiscoverSidebarResponsive } from '../sidebar';
|
||||
import { DiscoverLayoutProps } from './types';
|
||||
import { SortPairArr } from '../../../../angular/doc_table/lib/get_sort';
|
||||
import {
|
||||
DOC_HIDE_TIME_COLUMN_SETTING,
|
||||
DOC_TABLE_LEGACY,
|
||||
MODIFY_COLUMNS_ON_SWITCH,
|
||||
SAMPLE_SIZE_SETTING,
|
||||
SEARCH_FIELDS_FROM_SOURCE,
|
||||
} from '../../../common';
|
||||
import { popularizeField } from '../helpers/popularize_field';
|
||||
import { DocViewFilterFn } from '../doc_views/doc_views_types';
|
||||
import { DiscoverGrid } from './discover_grid/discover_grid';
|
||||
import { DiscoverTopNav } from './discover_topnav';
|
||||
import { ElasticSearchHit } from '../doc_views/doc_views_types';
|
||||
import { setBreadcrumbsTitle } from '../helpers/breadcrumbs';
|
||||
import { addHelpMenuToAppChrome } from './help_menu/help_menu_util';
|
||||
import { InspectorSession } from '../../../../inspector/public';
|
||||
import { useDataGridColumns } from '../helpers/use_data_grid_columns';
|
||||
SORT_DEFAULT_ORDER_SETTING,
|
||||
} from '../../../../../../common';
|
||||
import { popularizeField } from '../../../../helpers/popularize_field';
|
||||
import { DocViewFilterFn } from '../../../../doc_views/doc_views_types';
|
||||
import { DiscoverGrid } from '../../../../components/discover_grid/discover_grid';
|
||||
import { DiscoverTopNav } from '../top_nav/discover_topnav';
|
||||
import { ElasticSearchHit } from '../../../../doc_views/doc_views_types';
|
||||
import { DiscoverChart } from '../chart';
|
||||
import { getResultState } from '../../utils/get_result_state';
|
||||
import { InspectorSession } from '../../../../../../../inspector/public';
|
||||
import { DiscoverUninitialized } from '../uninitialized/uninitialized';
|
||||
import { SavedSearchDataMessage } from '../../services/use_saved_search';
|
||||
import { useDataGridColumns } from '../../../../helpers/use_data_grid_columns';
|
||||
import { getSwitchIndexPatternAppState } from '../../utils/get_switch_index_pattern_app_state';
|
||||
import { FetchStatus } from '../../../../types';
|
||||
|
||||
const DocTableLegacyMemoized = React.memo(DocTableLegacy);
|
||||
const SidebarMemoized = React.memo(DiscoverSidebarResponsive);
|
||||
const DataGridMemoized = React.memo(DiscoverGrid);
|
||||
const TopNavMemoized = React.memo(DiscoverTopNav);
|
||||
const TimechartHeaderMemoized = React.memo(TimechartHeader);
|
||||
const DiscoverHistogramMemoized = React.memo(DiscoverHistogram);
|
||||
const DiscoverChartMemoized = React.memo(DiscoverChart);
|
||||
|
||||
export function Discover({
|
||||
fetch,
|
||||
fetchCounter,
|
||||
fetchError,
|
||||
fieldCounts,
|
||||
fetchStatus,
|
||||
histogramData,
|
||||
hits,
|
||||
interface DiscoverLayoutFetchState extends SavedSearchDataMessage {
|
||||
state: FetchStatus;
|
||||
fetchCounter: number;
|
||||
fieldCounts: Record<string, number>;
|
||||
rows: ElasticSearchHit[];
|
||||
}
|
||||
|
||||
export function DiscoverLayout({
|
||||
indexPattern,
|
||||
minimumVisibleRows,
|
||||
opts,
|
||||
indexPatternList,
|
||||
navigateTo,
|
||||
savedSearchRefetch$,
|
||||
resetQuery,
|
||||
resultState,
|
||||
rows,
|
||||
savedSearchData$,
|
||||
savedSearch,
|
||||
searchSessionManager,
|
||||
searchSource,
|
||||
services,
|
||||
state,
|
||||
unmappedFieldsConfig,
|
||||
refreshAppState,
|
||||
}: DiscoverProps) {
|
||||
stateContainer,
|
||||
}: DiscoverLayoutProps) {
|
||||
const {
|
||||
trackUiMetric,
|
||||
capabilities,
|
||||
indexPatterns,
|
||||
data,
|
||||
uiSettings: config,
|
||||
filterManager,
|
||||
} = services;
|
||||
|
||||
const sampleSize = useMemo(() => config.get(SAMPLE_SIZE_SETTING), [config]);
|
||||
const [expandedDoc, setExpandedDoc] = useState<ElasticSearchHit | undefined>(undefined);
|
||||
const [inspectorSession, setInspectorSession] = useState<InspectorSession | undefined>(undefined);
|
||||
const scrollableDesktop = useRef<HTMLDivElement>(null);
|
||||
const collapseIcon = useRef<HTMLButtonElement>(null);
|
||||
|
||||
const [fetchState, setFetchState] = useState<DiscoverLayoutFetchState>({
|
||||
state: savedSearchData$.getValue().state,
|
||||
fetchCounter: 0,
|
||||
fieldCounts: {},
|
||||
rows: [],
|
||||
});
|
||||
const { state: fetchStatus, fetchCounter, inspectorAdapters, rows } = fetchState;
|
||||
|
||||
useEffect(() => {
|
||||
const subscription = savedSearchData$.subscribe((next) => {
|
||||
if (
|
||||
(next.state && next.state !== fetchState.state) ||
|
||||
(next.fetchCounter && next.fetchCounter !== fetchState.fetchCounter) ||
|
||||
(next.rows && next.rows !== fetchState.rows) ||
|
||||
(next.chartData && next.chartData !== fetchState.chartData)
|
||||
) {
|
||||
setFetchState({ ...fetchState, ...next });
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
subscription.unsubscribe();
|
||||
};
|
||||
}, [savedSearchData$, fetchState]);
|
||||
|
||||
const isMobile = () => {
|
||||
// collapse icon isn't displayed in mobile view, use it to detect which view is displayed
|
||||
return collapseIcon && !collapseIcon.current;
|
||||
};
|
||||
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, chrome, docLinks } = services;
|
||||
const timeField = useMemo(() => {
|
||||
return indexPatternsUtils.isDefault(indexPattern) ? indexPattern.timeFieldName : undefined;
|
||||
}, [indexPattern]);
|
||||
|
||||
const [isSidebarClosed, setIsSidebarClosed] = useState(false);
|
||||
const bucketInterval = useMemo(() => {
|
||||
const bucketAggConfig = opts.chartAggConfigs?.aggs[1];
|
||||
return bucketAggConfig && search.aggs.isDateHistogramBucketAggConfig(bucketAggConfig)
|
||||
? bucketAggConfig.buckets?.getInterval()
|
||||
: undefined;
|
||||
}, [opts.chartAggConfigs]);
|
||||
const isLegacy = useMemo(() => services.uiSettings.get(DOC_TABLE_LEGACY), [services]);
|
||||
const useNewFieldsApi = useMemo(() => !services.uiSettings.get(SEARCH_FIELDS_FROM_SOURCE), [
|
||||
services,
|
||||
]);
|
||||
|
||||
const unmappedFieldsConfig = useMemo(
|
||||
() => ({
|
||||
showUnmappedFields: useNewFieldsApi,
|
||||
}),
|
||||
[useNewFieldsApi]
|
||||
);
|
||||
|
||||
const resultState = useMemo(() => getResultState(fetchStatus, rows!), [fetchStatus, rows]);
|
||||
|
||||
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();
|
||||
searchSessionManager.removeSearchSessionIdFromURL({ replace: false });
|
||||
savedSearchRefetch$.next();
|
||||
}
|
||||
},
|
||||
[opts]
|
||||
[savedSearchRefetch$, searchSessionManager]
|
||||
);
|
||||
|
||||
const { columns, onAddColumn, onRemoveColumn, onMoveColumn, onSetColumns } = useDataGridColumns({
|
||||
|
@ -117,27 +159,21 @@ export function Discover({
|
|||
config,
|
||||
indexPattern,
|
||||
indexPatterns,
|
||||
setAppState,
|
||||
setAppState: stateContainer.setAppState,
|
||||
state,
|
||||
useNewFieldsApi,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const pageTitleSuffix = savedSearch.id && savedSearch.title ? `: ${savedSearch.title}` : '';
|
||||
chrome.docTitle.change(`Discover${pageTitleSuffix}`);
|
||||
|
||||
setBreadcrumbsTitle(savedSearch, chrome);
|
||||
addHelpMenuToAppChrome(chrome, docLinks);
|
||||
}, [savedSearch, chrome, docLinks]);
|
||||
|
||||
const onOpenInspector = useCallback(() => {
|
||||
// prevent overlapping
|
||||
setExpandedDoc(undefined);
|
||||
const session = services.inspector.open(opts.inspectorAdapters, {
|
||||
title: savedSearch.title,
|
||||
});
|
||||
setInspectorSession(session);
|
||||
}, [setExpandedDoc, opts.inspectorAdapters, savedSearch, services.inspector]);
|
||||
if (inspectorAdapters) {
|
||||
setExpandedDoc(undefined);
|
||||
const session = services.inspector.open(inspectorAdapters, {
|
||||
title: savedSearch.title,
|
||||
});
|
||||
setInspectorSession(session);
|
||||
}
|
||||
}, [setExpandedDoc, inspectorAdapters, savedSearch, services.inspector]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
|
@ -150,9 +186,9 @@ export function Discover({
|
|||
|
||||
const onSort = useCallback(
|
||||
(sort: string[][]) => {
|
||||
setAppState({ sort });
|
||||
stateContainer.setAppState({ sort });
|
||||
},
|
||||
[setAppState]
|
||||
[stateContainer]
|
||||
);
|
||||
|
||||
const onAddFilter = useCallback(
|
||||
|
@ -160,7 +196,7 @@ export function Discover({
|
|||
const fieldName = typeof field === 'string' ? field : field.name;
|
||||
popularizeField(indexPattern, fieldName, indexPatterns);
|
||||
const newFilters = esFilters.generateFilters(
|
||||
opts.filterManager,
|
||||
filterManager,
|
||||
field,
|
||||
values,
|
||||
operation,
|
||||
|
@ -169,31 +205,13 @@ export function Discover({
|
|||
if (trackUiMetric) {
|
||||
trackUiMetric(METRIC_TYPE.CLICK, 'filter_added');
|
||||
}
|
||||
return opts.filterManager.addFilters(newFilters);
|
||||
return filterManager.addFilters(newFilters);
|
||||
},
|
||||
[opts, indexPattern, indexPatterns, trackUiMetric]
|
||||
[filterManager, indexPattern, indexPatterns, trackUiMetric]
|
||||
);
|
||||
|
||||
const onChangeInterval = useCallback(
|
||||
(interval: string) => {
|
||||
if (interval) {
|
||||
setAppState({ interval });
|
||||
}
|
||||
},
|
||||
[setAppState]
|
||||
);
|
||||
|
||||
const timefilterUpdateHandler = useCallback(
|
||||
(ranges: { from: number; to: number }) => {
|
||||
data.query.timefilter.timefilter.setTime({
|
||||
from: moment(ranges.from).toISOString(),
|
||||
to: moment(ranges.to).toISOString(),
|
||||
mode: 'absolute',
|
||||
});
|
||||
},
|
||||
[data]
|
||||
);
|
||||
|
||||
/**
|
||||
* Legacy function, remove once legacy grid is removed
|
||||
*/
|
||||
const onBackToTop = useCallback(() => {
|
||||
if (scrollableDesktop && scrollableDesktop.current) {
|
||||
scrollableDesktop.current.focus();
|
||||
|
@ -214,28 +232,69 @@ export function Discover({
|
|||
width: colSettings.width,
|
||||
};
|
||||
const newGrid = { ...grid, columns: newColumns };
|
||||
opts.setAppState({ grid: newGrid });
|
||||
stateContainer.setAppState({ grid: newGrid });
|
||||
},
|
||||
[opts, state]
|
||||
[stateContainer, state]
|
||||
);
|
||||
|
||||
const onEditRuntimeField = () => {
|
||||
if (refreshAppState) {
|
||||
refreshAppState();
|
||||
}
|
||||
};
|
||||
const onEditRuntimeField = useCallback(() => {
|
||||
savedSearchRefetch$.next('reset');
|
||||
}, [savedSearchRefetch$]);
|
||||
|
||||
const contentCentered = resultState === 'uninitialized';
|
||||
const showTimeCol = useMemo(
|
||||
() => !config.get(DOC_HIDE_TIME_COLUMN_SETTING, false) && !!indexPattern.timeFieldName,
|
||||
[config, indexPattern.timeFieldName]
|
||||
);
|
||||
|
||||
const onChangeIndexPattern = useCallback(
|
||||
async (id: string) => {
|
||||
const nextIndexPattern = await indexPatterns.get(id);
|
||||
if (nextIndexPattern && indexPattern) {
|
||||
/**
|
||||
* Without resetting the fetch state, e.g. a time column would be displayed when switching
|
||||
* from a index pattern without to a index pattern with time filter for a brief moment
|
||||
* That's because appState is updated before savedSearchData$
|
||||
* The following line of code catches this, but should be improved
|
||||
*/
|
||||
savedSearchData$.next({ rows: [], state: FetchStatus.LOADING, fieldCounts: {} });
|
||||
|
||||
const nextAppState = getSwitchIndexPatternAppState(
|
||||
indexPattern,
|
||||
nextIndexPattern,
|
||||
state.columns || [],
|
||||
(state.sort || []) as SortPairArr[],
|
||||
config.get(MODIFY_COLUMNS_ON_SWITCH),
|
||||
config.get(SORT_DEFAULT_ORDER_SETTING)
|
||||
);
|
||||
stateContainer.setAppState(nextAppState);
|
||||
}
|
||||
},
|
||||
[
|
||||
config,
|
||||
indexPattern,
|
||||
indexPatterns,
|
||||
savedSearchData$,
|
||||
state.columns,
|
||||
state.sort,
|
||||
stateContainer,
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
<I18nProvider>
|
||||
<EuiPage className="dscPage" data-fetch-counter={fetchCounter}>
|
||||
<TopNavMemoized
|
||||
indexPattern={indexPattern}
|
||||
opts={opts}
|
||||
onOpenInspector={onOpenInspector}
|
||||
query={state.query}
|
||||
navigateTo={navigateTo}
|
||||
savedQuery={state.savedQuery}
|
||||
updateQuery={updateQuery}
|
||||
savedSearch={savedSearch}
|
||||
searchSource={searchSource}
|
||||
services={services}
|
||||
stateContainer={stateContainer}
|
||||
updateQuery={updateQuery}
|
||||
/>
|
||||
<EuiPageBody className="dscPageBody" aria-describedby="savedSearchTitle">
|
||||
<h1 id="savedSearchTitle" className="euiScreenReaderOnly">
|
||||
|
@ -244,18 +303,16 @@ export function Discover({
|
|||
<EuiFlexGroup className="dscPageBody__contents" gutterSize="none">
|
||||
<EuiFlexItem grow={false}>
|
||||
<SidebarMemoized
|
||||
config={config}
|
||||
columns={columns}
|
||||
fieldCounts={fieldCounts}
|
||||
fieldCounts={fetchState.fieldCounts}
|
||||
hits={rows}
|
||||
indexPatternList={indexPatternList}
|
||||
indexPatterns={indexPatterns}
|
||||
onAddField={onAddColumn}
|
||||
onAddFilter={onAddFilter}
|
||||
onRemoveField={onRemoveColumn}
|
||||
selectedIndexPattern={searchSource && searchSource.getField('index')}
|
||||
onChangeIndexPattern={onChangeIndexPattern}
|
||||
selectedIndexPattern={indexPattern}
|
||||
services={services}
|
||||
setAppState={setAppState}
|
||||
state={state}
|
||||
isClosed={isSidebarClosed}
|
||||
trackUiMetric={trackUiMetric}
|
||||
|
@ -295,13 +352,15 @@ export function Discover({
|
|||
>
|
||||
{resultState === 'none' && (
|
||||
<DiscoverNoResults
|
||||
timeFieldName={opts.timefield}
|
||||
queryLanguage={state.query?.language || ''}
|
||||
data={opts.data}
|
||||
error={fetchError}
|
||||
timeFieldName={timeField}
|
||||
queryLanguage={state.query?.language ?? ''}
|
||||
data={data}
|
||||
error={fetchState.fetchError}
|
||||
/>
|
||||
)}
|
||||
{resultState === 'uninitialized' && <DiscoverUninitialized onRefresh={fetch} />}
|
||||
{resultState === 'uninitialized' && (
|
||||
<DiscoverUninitialized onRefresh={() => savedSearchRefetch$.next()} />
|
||||
)}
|
||||
{resultState === 'loading' && <LoadingSpinner />}
|
||||
{resultState === 'ready' && (
|
||||
<EuiFlexGroup
|
||||
|
@ -311,79 +370,22 @@ export function Discover({
|
|||
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>
|
||||
{!hideChart && (
|
||||
<EuiFlexItem className="dscResultCount__actions">
|
||||
<TimechartHeaderMemoized
|
||||
data={opts.data}
|
||||
dateFormat={opts.config.get('dateFormat')}
|
||||
options={search.aggs.intervalOptions}
|
||||
onChangeInterval={onChangeInterval}
|
||||
stateInterval={state.interval || ''}
|
||||
bucketInterval={bucketInterval}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
{opts.timefield && (
|
||||
<EuiFlexItem className="dscResultCount__toggle" grow={false}>
|
||||
<EuiButtonEmpty
|
||||
size="xs"
|
||||
iconType={!hideChart ? 'eyeClosed' : 'eye'}
|
||||
onClick={() => {
|
||||
toggleHideChart();
|
||||
}}
|
||||
data-test-subj="discoverChartToggle"
|
||||
>
|
||||
{!hideChart
|
||||
? i18n.translate('discover.hideChart', {
|
||||
defaultMessage: 'Hide chart',
|
||||
})
|
||||
: i18n.translate('discover.showChart', {
|
||||
defaultMessage: 'Show chart',
|
||||
})}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>
|
||||
<DiscoverChartMemoized
|
||||
config={config}
|
||||
chartData={fetchState.chartData}
|
||||
bucketInterval={fetchState.bucketInterval}
|
||||
data={data}
|
||||
hits={fetchState.hits}
|
||||
indexPattern={indexPattern}
|
||||
isLegacy={isLegacy}
|
||||
state={state}
|
||||
resetQuery={resetQuery}
|
||||
savedSearch={savedSearch}
|
||||
stateContainer={stateContainer}
|
||||
timefield={timeField}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
{!hideChart && opts.timefield && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<section
|
||||
aria-label={i18n.translate(
|
||||
'discover.histogramOfFoundDocumentsAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Histogram of found documents',
|
||||
}
|
||||
)}
|
||||
className="dscTimechart"
|
||||
>
|
||||
{opts.chartAggConfigs && histogramData && rows.length !== 0 && (
|
||||
<div
|
||||
className={isLegacy ? 'dscHistogram' : 'dscHistogramGrid'}
|
||||
data-test-subj="discoverChart"
|
||||
>
|
||||
<DiscoverHistogramMemoized
|
||||
chartData={histogramData}
|
||||
timefilterUpdateHandler={timefilterUpdateHandler}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
<EuiSpacer size="s" />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
|
||||
<EuiHorizontalRule margin="none" />
|
||||
|
||||
<EuiFlexItem className="eui-yScroll">
|
||||
|
@ -403,18 +405,17 @@ export function Discover({
|
|||
<DocTableLegacyMemoized
|
||||
columns={columns}
|
||||
indexPattern={indexPattern}
|
||||
minimumVisibleRows={minimumVisibleRows}
|
||||
rows={rows}
|
||||
sort={state.sort || []}
|
||||
searchDescription={opts.savedSearch.description}
|
||||
searchTitle={opts.savedSearch.lastSavedTitle}
|
||||
searchDescription={savedSearch.description}
|
||||
searchTitle={savedSearch.lastSavedTitle}
|
||||
onAddColumn={onAddColumn}
|
||||
onBackToTop={onBackToTop}
|
||||
onFilter={onAddFilter}
|
||||
onMoveColumn={onMoveColumn}
|
||||
onRemoveColumn={onRemoveColumn}
|
||||
onSort={onSort}
|
||||
sampleSize={opts.sampleSize}
|
||||
sampleSize={sampleSize}
|
||||
useNewFieldsApi={useNewFieldsApi}
|
||||
/>
|
||||
)}
|
||||
|
@ -428,18 +429,15 @@ export function Discover({
|
|||
isLoading={fetchStatus === 'loading'}
|
||||
rows={rows}
|
||||
sort={(state.sort as SortPairArr[]) || []}
|
||||
sampleSize={opts.sampleSize}
|
||||
searchDescription={opts.savedSearch.description}
|
||||
searchTitle={opts.savedSearch.lastSavedTitle}
|
||||
sampleSize={sampleSize}
|
||||
searchDescription={savedSearch.description}
|
||||
searchTitle={savedSearch.lastSavedTitle}
|
||||
setExpandedDoc={setExpandedDoc}
|
||||
showTimeCol={
|
||||
!config.get(DOC_HIDE_TIME_COLUMN_SETTING, false) &&
|
||||
!!indexPattern.timeFieldName
|
||||
}
|
||||
showTimeCol={showTimeCol}
|
||||
services={services}
|
||||
settings={state.grid}
|
||||
onFilter={onAddFilter as DocViewFilterFn}
|
||||
onAddColumn={onAddColumn}
|
||||
onFilter={onAddFilter as DocViewFilterFn}
|
||||
onRemoveColumn={onRemoveColumn}
|
||||
onSetColumns={onSetColumns}
|
||||
onSort={onSort}
|
|
@ -6,5 +6,4 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { DiscoverUninitialized } from './uninitialized';
|
||||
export { DiscoverHistogram } from './histogram';
|
||||
export { DiscoverLayout } from './discover_layout';
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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 {
|
||||
IndexPattern,
|
||||
IndexPatternAttributes,
|
||||
SavedObject,
|
||||
} from '../../../../../../../data/common';
|
||||
import { ISearchSource } from '../../../../../../../data/public';
|
||||
import { DiscoverSearchSessionManager } from '../../services/discover_search_session';
|
||||
import { AppState, GetStateReturn } from '../../services/discover_state';
|
||||
import { SavedSearchRefetchSubject, SavedSearchDataSubject } from '../../services/use_saved_search';
|
||||
import { DiscoverServices } from '../../../../../build_services';
|
||||
import { SavedSearch } from '../../../../../saved_searches';
|
||||
|
||||
export interface DiscoverLayoutProps {
|
||||
indexPattern: IndexPattern;
|
||||
indexPatternList: Array<SavedObject<IndexPatternAttributes>>;
|
||||
resetQuery: () => void;
|
||||
navigateTo: (url: string) => void;
|
||||
savedSearch: SavedSearch;
|
||||
savedSearchData$: SavedSearchDataSubject;
|
||||
savedSearchRefetch$: SavedSearchRefetchSubject;
|
||||
searchSessionManager: DiscoverSearchSessionManager;
|
||||
searchSource: ISearchSource;
|
||||
services: DiscoverServices;
|
||||
state: AppState;
|
||||
stateContainer: GetStateReturn;
|
||||
}
|
|
@ -12,7 +12,7 @@ import { findTestSubject } from '@elastic/eui/lib/test';
|
|||
|
||||
import { DiscoverNoResults, DiscoverNoResultsProps } from './no_results';
|
||||
|
||||
jest.mock('../../../kibana_services', () => {
|
||||
jest.mock('../../../../../kibana_services', () => {
|
||||
return {
|
||||
getServices: () => ({
|
||||
docLinks: {
|
|
@ -9,8 +9,8 @@
|
|||
import React, { Fragment } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiButton, EuiCallOut, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { getServices } from '../../../kibana_services';
|
||||
import { DataPublicPluginStart } from '../../../../../data/public';
|
||||
import { getServices } from '../../../../../kibana_services';
|
||||
import { DataPublicPluginStart } from '../../../../../../../data/public';
|
||||
import { getLuceneQueryMessage, getTimeFieldMessage } from './no_results_helper';
|
||||
import './_no_results.scss';
|
||||
|
|
@ -8,15 +8,16 @@
|
|||
|
||||
import React from 'react';
|
||||
import { findTestSubject } from '@elastic/eui/lib/test';
|
||||
// @ts-expect-error
|
||||
import stubbedLogstashFields from '../../../__fixtures__/logstash_fields';
|
||||
import { mountWithIntl } from '@kbn/test/jest';
|
||||
import { DiscoverField } from './discover_field';
|
||||
import { coreMock } from '../../../../../../core/public/mocks';
|
||||
import { IndexPatternField } from '../../../../../data/public';
|
||||
import { getStubIndexPattern } from '../../../../../data/public/test_utils';
|
||||
|
||||
jest.mock('../../../kibana_services', () => ({
|
||||
// @ts-expect-error
|
||||
import stubbedLogstashFields from '../../../../../__fixtures__/logstash_fields';
|
||||
import { DiscoverField } from './discover_field';
|
||||
import { coreMock } from '../../../../../../../../core/public/mocks';
|
||||
import { IndexPatternField } from '../../../../../../../data/public';
|
||||
import { getStubIndexPattern } from '../../../../../../../data/public/test_utils';
|
||||
|
||||
jest.mock('../../../../../kibana_services', () => ({
|
||||
getServices: () => ({
|
||||
history: () => ({
|
||||
location: {
|
|
@ -23,9 +23,9 @@ import { i18n } from '@kbn/i18n';
|
|||
import { UiCounterMetricType } from '@kbn/analytics';
|
||||
import classNames from 'classnames';
|
||||
import { DiscoverFieldDetails } from './discover_field_details';
|
||||
import { FieldIcon, FieldButton } from '../../../../../kibana_react/public';
|
||||
import { FieldIcon, FieldButton } from '../../../../../../../kibana_react/public';
|
||||
import { FieldDetails } from './types';
|
||||
import { IndexPatternField, IndexPattern } from '../../../../../data/public';
|
||||
import { IndexPatternField, IndexPattern } from '../../../../../../../data/public';
|
||||
import { getFieldTypeName } from './lib/get_field_type_name';
|
||||
import { DiscoverFieldDetailsFooter } from './discover_field_details_footer';
|
||||
|
|
@ -11,7 +11,7 @@ import { EuiText, EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@e
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { StringFieldProgressBar } from './string_progress_bar';
|
||||
import { Bucket } from './types';
|
||||
import { IndexPatternField } from '../../../../../data/public';
|
||||
import { IndexPatternField } from '../../../../../../../data/public';
|
||||
import './discover_field_bucket.scss';
|
||||
|
||||
interface Props {
|
|
@ -8,13 +8,13 @@
|
|||
|
||||
import React from 'react';
|
||||
import { findTestSubject } from '@elastic/eui/lib/test';
|
||||
// @ts-expect-error
|
||||
import stubbedLogstashFields from '../../../__fixtures__/logstash_fields';
|
||||
import { mountWithIntl } from '@kbn/test/jest';
|
||||
// @ts-expect-error
|
||||
import stubbedLogstashFields from '../../../../../__fixtures__/logstash_fields';
|
||||
import { DiscoverFieldDetails } from './discover_field_details';
|
||||
import { coreMock } from '../../../../../../core/public/mocks';
|
||||
import { IndexPatternField } from '../../../../../data/public';
|
||||
import { getStubIndexPattern } from '../../../../../data/public/test_utils';
|
||||
import { coreMock } from '../../../../../../../../core/public/mocks';
|
||||
import { IndexPatternField } from '../../../../../../../data/public';
|
||||
import { getStubIndexPattern } from '../../../../../../../data/public/test_utils';
|
||||
|
||||
const indexPattern = getStubIndexPattern(
|
||||
'logstash-*',
|
|
@ -18,7 +18,7 @@ import {
|
|||
getVisualizeHref,
|
||||
} from './lib/visualize_trigger_utils';
|
||||
import { Bucket, FieldDetails } from './types';
|
||||
import { IndexPatternField, IndexPattern } from '../../../../../data/public';
|
||||
import { IndexPatternField, IndexPattern } from '../../../../../../../data/public';
|
||||
import './discover_field_details.scss';
|
||||
import { DiscoverFieldDetailsFooter } from './discover_field_details_footer';
|
||||
|
|
@ -8,12 +8,12 @@
|
|||
|
||||
import React from 'react';
|
||||
import { findTestSubject } from '@elastic/eui/lib/test';
|
||||
// @ts-expect-error
|
||||
import stubbedLogstashFields from '../../../__fixtures__/logstash_fields';
|
||||
import { mountWithIntl } from '@kbn/test/jest';
|
||||
import { coreMock } from '../../../../../../core/public/mocks';
|
||||
import { IndexPatternField } from '../../../../../data/public';
|
||||
import { getStubIndexPattern } from '../../../../../data/public/test_utils';
|
||||
// @ts-expect-error
|
||||
import stubbedLogstashFields from '../../../../../__fixtures__/logstash_fields';
|
||||
import { coreMock } from '../../../../../../../../core/public/mocks';
|
||||
import { IndexPatternField } from '../../../../../../../data/public';
|
||||
import { getStubIndexPattern } from '../../../../../../../data/public/test_utils';
|
||||
import { DiscoverFieldDetailsFooter } from './discover_field_details_footer';
|
||||
|
||||
const indexPattern = getStubIndexPattern(
|
|
@ -9,8 +9,8 @@
|
|||
import React from 'react';
|
||||
import { EuiLink, EuiPopoverFooter, EuiText } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { IndexPatternField } from '../../../../../data/common/index_patterns/fields';
|
||||
import { IndexPattern } from '../../../../../data/common/index_patterns/index_patterns';
|
||||
import { IndexPatternField } from '../../../../../../../data/common/index_patterns/fields';
|
||||
import { IndexPattern } from '../../../../../../../data/common/index_patterns/index_patterns';
|
||||
import { FieldDetails } from './types';
|
||||
|
||||
interface DiscoverFieldDetailsFooterProps {
|
|
@ -15,8 +15,7 @@ import { SavedObject } from 'kibana/server';
|
|||
import { DiscoverIndexPattern, DiscoverIndexPatternProps } from './discover_index_pattern';
|
||||
import { EuiSelectable } from '@elastic/eui';
|
||||
import { IndexPattern, IndexPatternAttributes } from 'src/plugins/data/public';
|
||||
import { configMock } from '../../../__mocks__/config';
|
||||
import { indexPatternsMock } from '../../../__mocks__/index_patterns';
|
||||
import { indexPatternsMock } from '../../../../../__mocks__/index_patterns';
|
||||
|
||||
const indexPattern = {
|
||||
id: 'the-index-pattern-id-first',
|
||||
|
@ -38,13 +37,11 @@ const indexPattern2 = {
|
|||
} as SavedObject<IndexPatternAttributes>;
|
||||
|
||||
const defaultProps = {
|
||||
config: configMock,
|
||||
indexPatternList: [indexPattern1, indexPattern2],
|
||||
selectedIndexPattern: indexPattern,
|
||||
state: {},
|
||||
setAppState: jest.fn(),
|
||||
useNewFieldsApi: true,
|
||||
indexPatterns: indexPatternsMock,
|
||||
onChangeIndexPattern: jest.fn(),
|
||||
};
|
||||
|
||||
function getIndexPatternPickerList(instance: ShallowWrapper) {
|
||||
|
@ -72,7 +69,7 @@ describe('DiscoverIndexPattern', () => {
|
|||
const props = ({
|
||||
indexPatternList: null,
|
||||
selectedIndexPattern: null,
|
||||
setIndexPattern: jest.fn(),
|
||||
onChangeIndexPattern: jest.fn(),
|
||||
} as unknown) as DiscoverIndexPatternProps;
|
||||
|
||||
expect(shallow(<DiscoverIndexPattern {...props} />)).toMatchSnapshot(`""`);
|
||||
|
@ -92,10 +89,6 @@ describe('DiscoverIndexPattern', () => {
|
|||
await act(async () => {
|
||||
selectIndexPatternPickerOption(instance, 'test2 title');
|
||||
});
|
||||
expect(defaultProps.setAppState).toHaveBeenCalledWith({
|
||||
index: 'the-index-pattern-id',
|
||||
columns: [],
|
||||
sort: [],
|
||||
});
|
||||
expect(defaultProps.onChangeIndexPattern).toHaveBeenCalledWith('the-index-pattern-id');
|
||||
});
|
||||
});
|
|
@ -6,58 +6,35 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { IUiSettingsClient, SavedObject } from 'kibana/public';
|
||||
import {
|
||||
IndexPattern,
|
||||
IndexPatternAttributes,
|
||||
IndexPatternsContract,
|
||||
} from 'src/plugins/data/public';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { I18nProvider } from '@kbn/i18n/react';
|
||||
import { SavedObject } from 'kibana/public';
|
||||
import { IndexPattern, IndexPatternAttributes } from 'src/plugins/data/public';
|
||||
|
||||
import { IndexPatternRef } from './types';
|
||||
import { ChangeIndexPattern } from './change_indexpattern';
|
||||
import { getSwitchIndexPatternAppState } from '../../helpers/get_switch_index_pattern_app_state';
|
||||
import { SortPairArr } from '../../angular/doc_table/lib/get_sort';
|
||||
import { MODIFY_COLUMNS_ON_SWITCH, SORT_DEFAULT_ORDER_SETTING } from '../../../../common';
|
||||
import { AppState } from '../../angular/discover_state';
|
||||
export interface DiscoverIndexPatternProps {
|
||||
/**
|
||||
* Client of uiSettings
|
||||
*/
|
||||
config: IUiSettingsClient;
|
||||
/**
|
||||
* list of available index patterns, if length > 1, component offers a "change" link
|
||||
*/
|
||||
indexPatternList: Array<SavedObject<IndexPatternAttributes>>;
|
||||
/**
|
||||
* Index patterns service
|
||||
* Callback function when changing an index pattern
|
||||
*/
|
||||
indexPatterns: IndexPatternsContract;
|
||||
onChangeIndexPattern: (id: string) => void;
|
||||
/**
|
||||
* currently selected index pattern, due to angular issues it's undefined at first rendering
|
||||
*/
|
||||
selectedIndexPattern: IndexPattern;
|
||||
/**
|
||||
* Function to set the current state
|
||||
*/
|
||||
setAppState: (state: Partial<AppState>) => void;
|
||||
/**
|
||||
* Discover App state
|
||||
*/
|
||||
state: AppState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Component allows you to select an index pattern in discovers side bar
|
||||
*/
|
||||
export function DiscoverIndexPattern({
|
||||
config,
|
||||
indexPatternList,
|
||||
onChangeIndexPattern,
|
||||
selectedIndexPattern,
|
||||
indexPatterns,
|
||||
state,
|
||||
setAppState,
|
||||
}: DiscoverIndexPatternProps) {
|
||||
const options: IndexPatternRef[] = (indexPatternList || []).map((entity) => ({
|
||||
id: entity.id,
|
||||
|
@ -65,24 +42,6 @@ export function DiscoverIndexPattern({
|
|||
}));
|
||||
const { id: selectedId, title: selectedTitle } = selectedIndexPattern || {};
|
||||
|
||||
const setIndexPattern = useCallback(
|
||||
async (id: string) => {
|
||||
const nextIndexPattern = await indexPatterns.get(id);
|
||||
if (nextIndexPattern && selectedIndexPattern) {
|
||||
const nextAppState = getSwitchIndexPatternAppState(
|
||||
selectedIndexPattern,
|
||||
nextIndexPattern,
|
||||
state.columns || [],
|
||||
(state.sort || []) as SortPairArr[],
|
||||
config.get(MODIFY_COLUMNS_ON_SWITCH),
|
||||
config.get(SORT_DEFAULT_ORDER_SETTING)
|
||||
);
|
||||
setAppState(nextAppState);
|
||||
}
|
||||
},
|
||||
[selectedIndexPattern, state, config, indexPatterns, setAppState]
|
||||
);
|
||||
|
||||
const [selected, setSelected] = useState({
|
||||
id: selectedId,
|
||||
title: selectedTitle || '',
|
||||
|
@ -108,7 +67,7 @@ export function DiscoverIndexPattern({
|
|||
onChangeIndexPattern={(id) => {
|
||||
const indexPattern = options.find((pattern) => pattern.id === id);
|
||||
if (indexPattern) {
|
||||
setIndexPattern(id);
|
||||
onChangeIndexPattern(id);
|
||||
setSelected(indexPattern);
|
||||
}
|
||||
}}
|
|
@ -6,16 +6,16 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { getStubIndexPattern } from '../../../../../data/public/index_patterns/index_pattern.stub';
|
||||
import { coreMock } from '../../../../../../core/public/mocks';
|
||||
import { DiscoverServices } from '../../../build_services';
|
||||
// @ts-expect-error
|
||||
import stubbedLogstashFields from '../../../__fixtures__/logstash_fields';
|
||||
import { mountWithIntl } from '@kbn/test/jest';
|
||||
import React from 'react';
|
||||
import { DiscoverIndexPatternManagement } from './discover_index_pattern_management';
|
||||
import { mountWithIntl } from '@kbn/test/jest';
|
||||
import { EuiContextMenuPanel, EuiPopover, EuiContextMenuItem } from '@elastic/eui';
|
||||
import { findTestSubject } from '@kbn/test/jest';
|
||||
import { getStubIndexPattern } from '../../../../../../../data/public/index_patterns/index_pattern.stub';
|
||||
import { coreMock } from '../../../../../../../../core/public/mocks';
|
||||
import { DiscoverServices } from '../../../../../build_services';
|
||||
// @ts-expect-error
|
||||
import stubbedLogstashFields from '../../../../../__fixtures__/logstash_fields';
|
||||
import { DiscoverIndexPatternManagement } from './discover_index_pattern_management';
|
||||
|
||||
const mockServices = ({
|
||||
history: () => ({
|
|
@ -9,8 +9,8 @@
|
|||
import React, { useState } from 'react';
|
||||
import { EuiButtonIcon, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { DiscoverServices } from '../../../build_services';
|
||||
import { IndexPattern } from '../../../../../data/common/index_patterns/index_patterns';
|
||||
import { DiscoverServices } from '../../../../../build_services';
|
||||
import { IndexPattern } from '../../../../../../../data/common/index_patterns/index_patterns';
|
||||
|
||||
export interface DiscoverIndexPatternManagementProps {
|
||||
/**
|
|
@ -10,54 +10,23 @@ import { each, cloneDeep } from 'lodash';
|
|||
import { ReactWrapper } from 'enzyme';
|
||||
import { findTestSubject } from '@elastic/eui/lib/test';
|
||||
// @ts-expect-error
|
||||
import realHits from '../../../__fixtures__/real_hits.js';
|
||||
import realHits from '../../../../../__fixtures__/real_hits.js';
|
||||
// @ts-expect-error
|
||||
import stubbedLogstashFields from '../../../__fixtures__/logstash_fields';
|
||||
import stubbedLogstashFields from '../../../../../__fixtures__/logstash_fields';
|
||||
import { mountWithIntl } from '@kbn/test/jest';
|
||||
import React from 'react';
|
||||
import { DiscoverSidebarProps } from './discover_sidebar';
|
||||
import { coreMock } from '../../../../../../core/public/mocks';
|
||||
import { IndexPatternAttributes } from '../../../../../data/common';
|
||||
import { getStubIndexPattern } from '../../../../../data/public/test_utils';
|
||||
import { SavedObject } from '../../../../../../core/types';
|
||||
import { coreMock } from '../../../../../../../../core/public/mocks';
|
||||
import { IndexPatternAttributes } from '../../../../../../../data/common';
|
||||
import { getStubIndexPattern } from '../../../../../../../data/public/test_utils';
|
||||
import { SavedObject } from '../../../../../../../../core/types';
|
||||
import { getDefaultFieldFilter } from './lib/field_filter';
|
||||
import { DiscoverSidebar } from './discover_sidebar';
|
||||
import { DiscoverServices } from '../../../build_services';
|
||||
import { ElasticSearchHit } from '../../doc_views/doc_views_types';
|
||||
import { configMock } from '../../../__mocks__/config';
|
||||
import { indexPatternsMock } from '../../../__mocks__/index_patterns';
|
||||
import { ElasticSearchHit } from '../../../../doc_views/doc_views_types';
|
||||
import { discoverServiceMock as mockDiscoverServices } from '../../../../../__mocks__/services';
|
||||
|
||||
const mockServices = ({
|
||||
history: () => ({
|
||||
location: {
|
||||
search: '',
|
||||
},
|
||||
}),
|
||||
capabilities: {
|
||||
visualize: {
|
||||
show: true,
|
||||
},
|
||||
discover: {
|
||||
save: false,
|
||||
},
|
||||
},
|
||||
uiSettings: {
|
||||
get: (key: string) => {
|
||||
if (key === 'fields:popularLimit') {
|
||||
return 5;
|
||||
}
|
||||
},
|
||||
},
|
||||
indexPatternFieldEditor: {
|
||||
openEditor: jest.fn(),
|
||||
userPermissions: {
|
||||
editIndexPattern: jest.fn(),
|
||||
},
|
||||
},
|
||||
} as unknown) as DiscoverServices;
|
||||
|
||||
jest.mock('../../../kibana_services', () => ({
|
||||
getServices: () => mockServices,
|
||||
jest.mock('../../../../../kibana_services', () => ({
|
||||
getServices: () => mockDiscoverServices,
|
||||
}));
|
||||
|
||||
jest.mock('./lib/get_index_pattern_field_list', () => ({
|
||||
|
@ -92,22 +61,20 @@ function getCompProps(): DiscoverSidebarProps {
|
|||
}
|
||||
}
|
||||
return {
|
||||
config: configMock,
|
||||
columns: ['extension'],
|
||||
fieldCounts,
|
||||
hits,
|
||||
indexPatternList,
|
||||
indexPatterns: indexPatternsMock,
|
||||
onChangeIndexPattern: jest.fn(),
|
||||
onAddFilter: jest.fn(),
|
||||
onAddField: jest.fn(),
|
||||
onRemoveField: jest.fn(),
|
||||
selectedIndexPattern: indexPattern,
|
||||
services: mockServices,
|
||||
services: mockDiscoverServices,
|
||||
state: {},
|
||||
trackUiMetric: jest.fn(),
|
||||
fieldFilter: getDefaultFieldFilter(),
|
||||
setFieldFilter: jest.fn(),
|
||||
setAppState: jest.fn(),
|
||||
onEditRuntimeField: jest.fn(),
|
||||
editField: jest.fn(),
|
||||
};
|
|
@ -27,9 +27,9 @@ import { FormattedMessage } from '@kbn/i18n/react';
|
|||
import { DiscoverField } from './discover_field';
|
||||
import { DiscoverIndexPattern } from './discover_index_pattern';
|
||||
import { DiscoverFieldSearch } from './discover_field_search';
|
||||
import { FIELDS_LIMIT_SETTING } from '../../../../common';
|
||||
import { FIELDS_LIMIT_SETTING } from '../../../../../../common';
|
||||
import { groupFields } from './lib/group_fields';
|
||||
import { IndexPatternField } from '../../../../../data/public';
|
||||
import { IndexPatternField } from '../../../../../../../data/public';
|
||||
import { getDetails } from './lib/get_details';
|
||||
import { FieldFilterState, getDefaultFieldFilter, setFieldFilterProp } from './lib/field_filter';
|
||||
import { getIndexPatternFieldList } from './lib/get_index_pattern_field_list';
|
||||
|
@ -68,30 +68,28 @@ export interface DiscoverSidebarProps extends DiscoverSidebarResponsiveProps {
|
|||
export function DiscoverSidebar({
|
||||
alwaysShowActionButtons = false,
|
||||
columns,
|
||||
config,
|
||||
fieldCounts,
|
||||
fieldFilter,
|
||||
hits,
|
||||
indexPatternList,
|
||||
indexPatterns,
|
||||
onAddField,
|
||||
onAddFilter,
|
||||
onRemoveField,
|
||||
selectedIndexPattern,
|
||||
services,
|
||||
setAppState,
|
||||
setFieldFilter,
|
||||
state,
|
||||
trackUiMetric,
|
||||
useNewFieldsApi = false,
|
||||
useFlyout = false,
|
||||
unmappedFieldsConfig,
|
||||
onEditRuntimeField,
|
||||
onChangeIndexPattern,
|
||||
setFieldEditorRef,
|
||||
closeFlyout,
|
||||
editField,
|
||||
}: DiscoverSidebarProps) {
|
||||
const [fields, setFields] = useState<IndexPatternField[] | null>(null);
|
||||
|
||||
const { indexPatternFieldEditor } = services;
|
||||
const indexPatternFieldEditPermission = indexPatternFieldEditor?.userPermissions.editIndexPattern();
|
||||
const canEditIndexPatternField = !!indexPatternFieldEditPermission && useNewFieldsApi;
|
||||
|
@ -282,12 +280,9 @@ export function DiscoverSidebar({
|
|||
<EuiFlexGroup direction="row" gutterSize="s" alignItems="center" responsive={false}>
|
||||
<EuiFlexItem grow={true}>
|
||||
<DiscoverIndexPattern
|
||||
config={config}
|
||||
selectedIndexPattern={selectedIndexPattern}
|
||||
indexPatternList={sortBy(indexPatternList, (o) => o.attributes.title)}
|
||||
indexPatterns={indexPatterns}
|
||||
state={state}
|
||||
setAppState={setAppState}
|
||||
onChangeIndexPattern={onChangeIndexPattern}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
|
@ -323,12 +318,9 @@ export function DiscoverSidebar({
|
|||
<EuiFlexGroup direction="row" alignItems="center" gutterSize="s">
|
||||
<EuiFlexItem grow={true} className="dscSidebar__indexPatternSwitcher">
|
||||
<DiscoverIndexPattern
|
||||
config={config}
|
||||
selectedIndexPattern={selectedIndexPattern}
|
||||
indexPatternList={sortBy(indexPatternList, (o) => o.attributes.title)}
|
||||
indexPatterns={indexPatterns}
|
||||
state={state}
|
||||
setAppState={setAppState}
|
||||
onChangeIndexPattern={onChangeIndexPattern}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
|
@ -10,23 +10,21 @@ import { each, cloneDeep } from 'lodash';
|
|||
import { ReactWrapper } from 'enzyme';
|
||||
import { findTestSubject } from '@elastic/eui/lib/test';
|
||||
// @ts-expect-error
|
||||
import realHits from '../../../__fixtures__/real_hits.js';
|
||||
import realHits from '../../../../../__fixtures__/real_hits.js';
|
||||
// @ts-expect-error
|
||||
import stubbedLogstashFields from '../../../__fixtures__/logstash_fields';
|
||||
import stubbedLogstashFields from '../../../../../__fixtures__/logstash_fields';
|
||||
import { mountWithIntl } from '@kbn/test/jest';
|
||||
import React from 'react';
|
||||
import { coreMock } from '../../../../../../core/public/mocks';
|
||||
import { IndexPatternAttributes } from '../../../../../data/common';
|
||||
import { getStubIndexPattern } from '../../../../../data/public/test_utils';
|
||||
import { SavedObject } from '../../../../../../core/types';
|
||||
import { coreMock } from '../../../../../../../../core/public/mocks';
|
||||
import { IndexPatternAttributes } from '../../../../../../../data/common';
|
||||
import { getStubIndexPattern } from '../../../../../../../data/public/test_utils';
|
||||
import { SavedObject } from '../../../../../../../../core/types';
|
||||
import {
|
||||
DiscoverSidebarResponsive,
|
||||
DiscoverSidebarResponsiveProps,
|
||||
} from './discover_sidebar_responsive';
|
||||
import { DiscoverServices } from '../../../build_services';
|
||||
import { ElasticSearchHit } from '../../doc_views/doc_views_types';
|
||||
import { configMock } from '../../../__mocks__/config';
|
||||
import { indexPatternsMock } from '../../../__mocks__/index_patterns';
|
||||
import { DiscoverServices } from '../../../../../build_services';
|
||||
import { ElasticSearchHit } from '../../../../doc_views/doc_views_types';
|
||||
import { DiscoverSidebar } from './discover_sidebar';
|
||||
|
||||
const mockServices = ({
|
||||
|
@ -52,7 +50,7 @@ const mockServices = ({
|
|||
},
|
||||
} as unknown) as DiscoverServices;
|
||||
|
||||
jest.mock('../../../kibana_services', () => ({
|
||||
jest.mock('../../../../../kibana_services', () => ({
|
||||
getServices: () => mockServices,
|
||||
}));
|
||||
|
||||
|
@ -89,17 +87,15 @@ function getCompProps(): DiscoverSidebarResponsiveProps {
|
|||
}
|
||||
return {
|
||||
columns: ['extension'],
|
||||
config: configMock,
|
||||
fieldCounts,
|
||||
hits,
|
||||
indexPatternList,
|
||||
indexPatterns: indexPatternsMock,
|
||||
onChangeIndexPattern: jest.fn(),
|
||||
onAddFilter: jest.fn(),
|
||||
onAddField: jest.fn(),
|
||||
onRemoveField: jest.fn(),
|
||||
selectedIndexPattern: indexPattern,
|
||||
services: mockServices,
|
||||
setAppState: jest.fn(),
|
||||
state: {},
|
||||
trackUiMetric: jest.fn(),
|
||||
onEditRuntimeField: jest.fn(),
|
|
@ -11,7 +11,6 @@ import { sortBy } from 'lodash';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { UiCounterMetricType } from '@kbn/analytics';
|
||||
import { IUiSettingsClient } from 'kibana/public';
|
||||
import {
|
||||
EuiTitle,
|
||||
EuiHideFor,
|
||||
|
@ -28,14 +27,14 @@ import {
|
|||
EuiFlexItem,
|
||||
} from '@elastic/eui';
|
||||
import { DiscoverIndexPattern } from './discover_index_pattern';
|
||||
import { IndexPatternAttributes, IndexPatternsContract } from '../../../../../data/common';
|
||||
import { SavedObject } from '../../../../../../core/types';
|
||||
import { IndexPatternField, IndexPattern } from '../../../../../data/public';
|
||||
import { IndexPatternAttributes } from '../../../../../../../data/common';
|
||||
import { SavedObject } from '../../../../../../../../core/types';
|
||||
import { IndexPatternField, IndexPattern } from '../../../../../../../data/public';
|
||||
import { getDefaultFieldFilter } from './lib/field_filter';
|
||||
import { DiscoverSidebar } from './discover_sidebar';
|
||||
import { DiscoverServices } from '../../../build_services';
|
||||
import { ElasticSearchHit } from '../../doc_views/doc_views_types';
|
||||
import { AppState } from '../../angular/discover_state';
|
||||
import { DiscoverServices } from '../../../../../build_services';
|
||||
import { ElasticSearchHit } from '../../../../doc_views/doc_views_types';
|
||||
import { AppState } from '../../services/discover_state';
|
||||
import { DiscoverIndexPatternManagement } from './discover_index_pattern_management';
|
||||
|
||||
export interface DiscoverSidebarResponsiveProps {
|
||||
|
@ -47,10 +46,6 @@ export interface DiscoverSidebarResponsiveProps {
|
|||
* the selected columns displayed in the doc table in discover
|
||||
*/
|
||||
columns: string[];
|
||||
/**
|
||||
* Client of uiSettings
|
||||
*/
|
||||
config: IUiSettingsClient;
|
||||
/**
|
||||
* a statistics of the distribution of fields in the given hits
|
||||
*/
|
||||
|
@ -63,10 +58,6 @@ export interface DiscoverSidebarResponsiveProps {
|
|||
* List of available index patterns
|
||||
*/
|
||||
indexPatternList: Array<SavedObject<IndexPatternAttributes>>;
|
||||
/**
|
||||
* Index patterns service
|
||||
*/
|
||||
indexPatterns: IndexPatternsContract;
|
||||
/**
|
||||
* Has been toggled closed
|
||||
*/
|
||||
|
@ -79,6 +70,10 @@ export interface DiscoverSidebarResponsiveProps {
|
|||
* Callback function when adding a filter from sidebar
|
||||
*/
|
||||
onAddFilter: (field: IndexPatternField | string, value: string, type: '+' | '-') => void;
|
||||
/**
|
||||
* Callback function when changing an index pattern
|
||||
*/
|
||||
onChangeIndexPattern: (id: string) => void;
|
||||
/**
|
||||
* Callback function when removing a field
|
||||
* @param fieldName
|
||||
|
@ -92,10 +87,6 @@ export interface DiscoverSidebarResponsiveProps {
|
|||
* Discover plugin services;
|
||||
*/
|
||||
services: DiscoverServices;
|
||||
/**
|
||||
* Function to set the current state
|
||||
*/
|
||||
setAppState: (state: Partial<AppState>) => void;
|
||||
/**
|
||||
* Discover App state
|
||||
*/
|
||||
|
@ -114,7 +105,6 @@ export interface DiscoverSidebarResponsiveProps {
|
|||
* Read from the Fields API
|
||||
*/
|
||||
useNewFieldsApi?: boolean;
|
||||
|
||||
/**
|
||||
* an object containing properties for proper handling of unmapped fields
|
||||
*/
|
||||
|
@ -215,12 +205,9 @@ export function DiscoverSidebarResponsive(props: DiscoverSidebarResponsiveProps)
|
|||
<EuiFlexGroup direction="row" gutterSize="s" alignItems="center" responsive={false}>
|
||||
<EuiFlexItem grow={true}>
|
||||
<DiscoverIndexPattern
|
||||
config={props.config}
|
||||
onChangeIndexPattern={props.onChangeIndexPattern}
|
||||
selectedIndexPattern={props.selectedIndexPattern}
|
||||
indexPatternList={sortBy(props.indexPatternList, (o) => o.attributes.title)}
|
||||
indexPatterns={props.indexPatterns}
|
||||
state={props.state}
|
||||
setAppState={props.setAppState}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
|
@ -10,12 +10,12 @@
|
|||
|
||||
import _ from 'lodash';
|
||||
// @ts-expect-error
|
||||
import realHits from '../../../../__fixtures__/real_hits.js';
|
||||
import realHits from '../../../../../../__fixtures__/real_hits.js';
|
||||
// @ts-expect-error
|
||||
import stubbedLogstashFields from '../../../../__fixtures__/logstash_fields';
|
||||
import { coreMock } from '../../../../../../../core/public/mocks';
|
||||
import { IndexPattern } from '../../../../../../data/public';
|
||||
import { getStubIndexPattern } from '../../../../../../data/public/test_utils';
|
||||
import stubbedLogstashFields from '../../../../../../__fixtures__/logstash_fields';
|
||||
import { coreMock } from '../../../../../../../../../core/public/mocks';
|
||||
import { IndexPattern } from '../../../../../../../../data/public';
|
||||
import { getStubIndexPattern } from '../../../../../../../../data/public/test_utils';
|
||||
// @ts-expect-error
|
||||
import { fieldCalculator } from './field_calculator';
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import { getDefaultFieldFilter, setFieldFilterProp, isFieldFiltered } from './field_filter';
|
||||
import { IndexPatternField } from '../../../../../../data/public';
|
||||
import { IndexPatternField } from '../../../../../../../../data/public';
|
||||
|
||||
describe('field_filter', function () {
|
||||
it('getDefaultFieldFilter should return default filter state', function () {
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { IndexPatternField } from '../../../../../../data/public';
|
||||
import { IndexPatternField } from '../../../../../../../../data/public';
|
||||
|
||||
export interface FieldFilterState {
|
||||
missing: boolean;
|
|
@ -8,8 +8,8 @@
|
|||
|
||||
// @ts-expect-error
|
||||
import { fieldCalculator } from './field_calculator';
|
||||
import { IndexPattern, IndexPatternField } from '../../../../../../data/public';
|
||||
import { ElasticSearchHit } from '../../../doc_views/doc_views_types';
|
||||
import { IndexPattern, IndexPatternField } from '../../../../../../../../data/public';
|
||||
import { ElasticSearchHit } from '../../../../../doc_views/doc_views_types';
|
||||
|
||||
export function getDetails(
|
||||
field: IndexPatternField,
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import { difference } from 'lodash';
|
||||
import { IndexPattern, IndexPatternField } from 'src/plugins/data/public';
|
||||
import { isNestedFieldParent } from '../../../helpers/nested_fields';
|
||||
import { isNestedFieldParent } from '../../../utils/nested_fields';
|
||||
|
||||
export function getIndexPatternFieldList(
|
||||
indexPattern?: IndexPattern,
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { IndexPatternField } from '../../../../../../data/public';
|
||||
import { IndexPatternField } from '../../../../../../../../data/public';
|
||||
|
||||
export function getWarnings(field: IndexPatternField) {
|
||||
let warnings = [];
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import { groupFields } from './group_fields';
|
||||
import { getDefaultFieldFilter } from './field_filter';
|
||||
import { IndexPatternField } from '../../../../../../data/common/index_patterns/fields';
|
||||
import { IndexPatternField } from '../../../../../../../../data/common/index_patterns/fields';
|
||||
|
||||
const fields = [
|
||||
{
|
|
@ -11,9 +11,9 @@ import {
|
|||
VISUALIZE_GEO_FIELD_TRIGGER,
|
||||
visualizeFieldTrigger,
|
||||
visualizeGeoFieldTrigger,
|
||||
} from '../../../../../../ui_actions/public';
|
||||
import { getUiActions } from '../../../../kibana_services';
|
||||
import { IndexPatternField, KBN_FIELD_TYPES } from '../../../../../../data/public';
|
||||
} from '../../../../../../../../ui_actions/public';
|
||||
import { getUiActions } from '../../../../../../kibana_services';
|
||||
import { IndexPatternField, KBN_FIELD_TYPES } from '../../../../../../../../data/public';
|
||||
|
||||
function getTriggerConstant(type: string) {
|
||||
return type === KBN_FIELD_TYPES.GEO_POINT || type === KBN_FIELD_TYPES.GEO_SHAPE
|
|
@ -12,7 +12,7 @@ import { ReactWrapper } from 'enzyme';
|
|||
import { TimechartHeader, TimechartHeaderProps } from './timechart_header';
|
||||
import { EuiIconTip } from '@elastic/eui';
|
||||
import { findTestSubject } from '@elastic/eui/lib/test';
|
||||
import { DataPublicPluginStart } from '../../../../../data/public';
|
||||
import { DataPublicPluginStart } from '../../../../../../../data/public';
|
||||
|
||||
describe('timechart header', function () {
|
||||
let props: TimechartHeaderProps;
|
|
@ -18,8 +18,14 @@ import {
|
|||
import moment from 'moment';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import dateMath from '@elastic/datemath';
|
||||
import { DataPublicPluginStart } from '../../../../../data/public';
|
||||
import './timechart_header.scss';
|
||||
import { DataPublicPluginStart } from '../../../../../../../data/public';
|
||||
|
||||
export interface TimechartBucketInterval {
|
||||
scaled?: boolean;
|
||||
description?: string;
|
||||
scale?: number;
|
||||
}
|
||||
|
||||
export interface TimechartHeaderProps {
|
||||
/**
|
||||
|
@ -29,11 +35,7 @@ export interface TimechartHeaderProps {
|
|||
/**
|
||||
* Interval for the buckets of the recent request
|
||||
*/
|
||||
bucketInterval?: {
|
||||
scaled?: boolean;
|
||||
description?: string;
|
||||
scale?: number;
|
||||
};
|
||||
bucketInterval?: TimechartBucketInterval;
|
||||
data: DataPublicPluginStart;
|
||||
/**
|
||||
* Interval Options
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* 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 { shallowWithIntl } from '@kbn/test/jest';
|
||||
import { indexPatternMock } from '../../../../../__mocks__/index_pattern';
|
||||
import { savedSearchMock } from '../../../../../__mocks__/saved_search';
|
||||
import { DiscoverTopNav, DiscoverTopNavProps } from './discover_topnav';
|
||||
import { TopNavMenuData } from '../../../../../../../navigation/public';
|
||||
import { ISearchSource, Query } from '../../../../../../../data/common';
|
||||
import { GetStateReturn } from '../../services/discover_state';
|
||||
import { setHeaderActionMenuMounter } from '../../../../../kibana_services';
|
||||
import { discoverServiceMock } from '../../../../../__mocks__/services';
|
||||
|
||||
setHeaderActionMenuMounter(jest.fn());
|
||||
|
||||
function getProps(savePermissions = true): DiscoverTopNavProps {
|
||||
discoverServiceMock.capabilities.discover!.save = savePermissions;
|
||||
|
||||
return {
|
||||
stateContainer: {} as GetStateReturn,
|
||||
indexPattern: indexPatternMock,
|
||||
savedSearch: savedSearchMock,
|
||||
navigateTo: jest.fn(),
|
||||
services: discoverServiceMock,
|
||||
query: {} as Query,
|
||||
savedQuery: '',
|
||||
updateQuery: jest.fn(),
|
||||
onOpenInspector: jest.fn(),
|
||||
searchSource: {} as ISearchSource,
|
||||
};
|
||||
}
|
||||
|
||||
describe('Discover topnav component', () => {
|
||||
test('generated config of TopNavMenu config is correct when discover save permissions are assigned', () => {
|
||||
const props = getProps(true);
|
||||
const component = shallowWithIntl(<DiscoverTopNav {...props} />);
|
||||
const topMenuConfig = component.props().config.map((obj: TopNavMenuData) => obj.id);
|
||||
expect(topMenuConfig).toEqual(['options', 'new', 'save', 'open', 'share', 'inspect']);
|
||||
});
|
||||
|
||||
test('generated config of TopNavMenu config is correct when no discover save permissions are assigned', () => {
|
||||
const props = getProps(false);
|
||||
const component = shallowWithIntl(<DiscoverTopNav {...props} />);
|
||||
const topMenuConfig = component.props().config.map((obj: TopNavMenuData) => obj.id);
|
||||
expect(topMenuConfig).toEqual(['options', 'new', 'open', 'share', 'inspect']);
|
||||
});
|
||||
});
|
|
@ -6,45 +6,53 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
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';
|
||||
import { DiscoverLayoutProps } from '../layout/types';
|
||||
import { getTopNavLinks } from './get_top_nav_links';
|
||||
import { Query, TimeRange } from '../../../../../../../data/common/query';
|
||||
import { getHeaderActionMenuMounter } from '../../../../../kibana_services';
|
||||
import { GetStateReturn } from '../../services/discover_state';
|
||||
|
||||
export type DiscoverTopNavProps = Pick<DiscoverProps, 'indexPattern' | 'opts' | 'searchSource'> & {
|
||||
export type DiscoverTopNavProps = Pick<
|
||||
DiscoverLayoutProps,
|
||||
'indexPattern' | 'navigateTo' | 'savedSearch' | 'services' | 'searchSource'
|
||||
> & {
|
||||
onOpenInspector: () => void;
|
||||
query?: Query;
|
||||
savedQuery?: string;
|
||||
updateQuery: (payload: { dateRange: TimeRange; query?: Query }, isUpdate?: boolean) => void;
|
||||
stateContainer: GetStateReturn;
|
||||
};
|
||||
|
||||
export const DiscoverTopNav = ({
|
||||
indexPattern,
|
||||
opts,
|
||||
onOpenInspector,
|
||||
query,
|
||||
savedQuery,
|
||||
stateContainer,
|
||||
updateQuery,
|
||||
searchSource,
|
||||
navigateTo,
|
||||
savedSearch,
|
||||
services,
|
||||
}: DiscoverTopNavProps) => {
|
||||
const showDatePicker = useMemo(() => indexPattern.isTimeBased(), [indexPattern]);
|
||||
const { TopNavMenu } = opts.services.navigation.ui;
|
||||
const { TopNavMenu } = services.navigation.ui;
|
||||
const topNavMenu = useMemo(
|
||||
() =>
|
||||
getTopNavLinks({
|
||||
getFieldCounts: opts.getFieldCounts,
|
||||
indexPattern,
|
||||
navigateTo: opts.navigateTo,
|
||||
savedSearch: opts.savedSearch,
|
||||
services: opts.services,
|
||||
state: opts.stateContainer,
|
||||
navigateTo,
|
||||
savedSearch,
|
||||
services,
|
||||
state: stateContainer,
|
||||
onOpenInspector,
|
||||
searchSource,
|
||||
}),
|
||||
[indexPattern, opts, onOpenInspector, searchSource]
|
||||
[indexPattern, navigateTo, onOpenInspector, searchSource, stateContainer, savedSearch, services]
|
||||
);
|
||||
|
||||
const updateSavedQueryId = (newSavedQueryId: string | undefined) => {
|
||||
const { appStateContainer, setAppState } = opts.stateContainer;
|
||||
const { appStateContainer, setAppState } = stateContainer;
|
||||
if (newSavedQueryId) {
|
||||
setAppState({ savedQuery: newSavedQueryId });
|
||||
} else {
|
||||
|
@ -56,6 +64,10 @@ export const DiscoverTopNav = ({
|
|||
appStateContainer.set(newState);
|
||||
}
|
||||
};
|
||||
const setMenuMountPoint = useMemo(() => {
|
||||
return getHeaderActionMenuMounter();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<TopNavMenu
|
||||
appName="discover"
|
||||
|
@ -64,11 +76,11 @@ export const DiscoverTopNav = ({
|
|||
onQuerySubmit={updateQuery}
|
||||
onSavedQueryIdChange={updateSavedQueryId}
|
||||
query={query}
|
||||
setMenuMountPoint={opts.setHeaderActionMenu}
|
||||
setMenuMountPoint={setMenuMountPoint}
|
||||
savedQueryId={savedQuery}
|
||||
screenTitle={opts.savedSearch.title}
|
||||
screenTitle={savedSearch.title}
|
||||
showDatePicker={showDatePicker}
|
||||
showSaveQuery={!!opts.services.capabilities.discover.saveQuery}
|
||||
showSaveQuery={!!services.capabilities.discover.saveQuery}
|
||||
showSearchBar={true}
|
||||
useDefaultBehaviors={true}
|
||||
/>
|
|
@ -8,10 +8,10 @@
|
|||
|
||||
import { ISearchSource } from 'src/plugins/data/public';
|
||||
import { getTopNavLinks } from './get_top_nav_links';
|
||||
import { indexPatternMock } from '../../../__mocks__/index_pattern';
|
||||
import { savedSearchMock } from '../../../__mocks__/saved_search';
|
||||
import { DiscoverServices } from '../../../build_services';
|
||||
import { GetStateReturn } from '../../angular/discover_state';
|
||||
import { indexPatternMock } from '../../../../../__mocks__/index_pattern';
|
||||
import { savedSearchMock } from '../../../../../__mocks__/saved_search';
|
||||
import { DiscoverServices } from '../../../../../build_services';
|
||||
import { GetStateReturn } from '../../services/discover_state';
|
||||
|
||||
const services = ({
|
||||
capabilities: {
|
||||
|
@ -28,7 +28,6 @@ const state = ({} as unknown) as GetStateReturn;
|
|||
|
||||
test('getTopNavLinks result', () => {
|
||||
const topNavLinks = getTopNavLinks({
|
||||
getFieldCounts: jest.fn(),
|
||||
indexPattern: indexPatternMock,
|
||||
navigateTo: jest.fn(),
|
||||
onOpenInspector: jest.fn(),
|
|
@ -9,20 +9,19 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import moment from 'moment';
|
||||
import { showOpenSearchPanel } from './show_open_search_panel';
|
||||
import { getSharingData, showPublicUrlSwitch } from '../../helpers/get_sharing_data';
|
||||
import { unhashUrl } from '../../../../../kibana_utils/public';
|
||||
import { DiscoverServices } from '../../../build_services';
|
||||
import { SavedSearch } from '../../../saved_searches';
|
||||
import { getSharingData, showPublicUrlSwitch } from '../../utils/get_sharing_data';
|
||||
import { unhashUrl } from '../../../../../../../kibana_utils/public';
|
||||
import { DiscoverServices } from '../../../../../build_services';
|
||||
import { SavedSearch } from '../../../../../saved_searches';
|
||||
import { onSaveSearch } from './on_save_search';
|
||||
import { GetStateReturn } from '../../angular/discover_state';
|
||||
import { IndexPattern, ISearchSource } from '../../../kibana_services';
|
||||
import { GetStateReturn } from '../../services/discover_state';
|
||||
import { IndexPattern, ISearchSource } from '../../../../../kibana_services';
|
||||
import { openOptionsPopover } from './open_options_popover';
|
||||
|
||||
/**
|
||||
* Helper function to build the top nav links
|
||||
*/
|
||||
export const getTopNavLinks = ({
|
||||
getFieldCounts,
|
||||
indexPattern,
|
||||
navigateTo,
|
||||
savedSearch,
|
||||
|
@ -31,7 +30,6 @@ export const getTopNavLinks = ({
|
|||
onOpenInspector,
|
||||
searchSource,
|
||||
}: {
|
||||
getFieldCounts: () => Promise<Record<string, number>>;
|
||||
indexPattern: IndexPattern;
|
||||
navigateTo: (url: string) => void;
|
||||
savedSearch: SavedSearch;
|
|
@ -6,15 +6,15 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { showSaveModal } from '../../../../../saved_objects/public';
|
||||
jest.mock('../../../../../saved_objects/public');
|
||||
import { showSaveModal } from '../../../../../../../saved_objects/public';
|
||||
jest.mock('../../../../../../../saved_objects/public');
|
||||
|
||||
import { onSaveSearch } from './on_save_search';
|
||||
import { indexPatternMock } from '../../../__mocks__/index_pattern';
|
||||
import { savedSearchMock } from '../../../__mocks__/saved_search';
|
||||
import { DiscoverServices } from '../../../build_services';
|
||||
import { GetStateReturn } from '../../angular/discover_state';
|
||||
import { i18nServiceMock } from '../../../../../../core/public/mocks';
|
||||
import { indexPatternMock } from '../../../../../__mocks__/index_pattern';
|
||||
import { savedSearchMock } from '../../../../../__mocks__/saved_search';
|
||||
import { DiscoverServices } from '../../../../../build_services';
|
||||
import { GetStateReturn } from '../../services/discover_state';
|
||||
import { i18nServiceMock } from '../../../../../../../../core/public/mocks';
|
||||
|
||||
test('onSaveSearch', async () => {
|
||||
const serviceMock = ({
|
|
@ -8,13 +8,13 @@
|
|||
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { SavedObjectSaveModal, showSaveModal } from '../../../../../saved_objects/public';
|
||||
import { SavedSearch } from '../../../saved_searches';
|
||||
import { IndexPattern } from '../../../../../data/common/index_patterns/index_patterns';
|
||||
import { DiscoverServices } from '../../../build_services';
|
||||
import { GetStateReturn } from '../../angular/discover_state';
|
||||
import { setBreadcrumbsTitle } from '../../helpers/breadcrumbs';
|
||||
import { persistSavedSearch } from '../../helpers/persist_saved_search';
|
||||
import { SavedObjectSaveModal, showSaveModal } from '../../../../../../../saved_objects/public';
|
||||
import { SavedSearch } from '../../../../../saved_searches';
|
||||
import { IndexPattern } from '../../../../../../../data/common/index_patterns/index_patterns';
|
||||
import { DiscoverServices } from '../../../../../build_services';
|
||||
import { GetStateReturn } from '../../services/discover_state';
|
||||
import { setBreadcrumbsTitle } from '../../../../helpers/breadcrumbs';
|
||||
import { persistSavedSearch } from '../../utils/persist_saved_search';
|
||||
|
||||
async function saveDataSource({
|
||||
indexPattern,
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue