{
return false;
}
- const filters = await getDataActions().createFiltersFromEvent(item.values);
+ const filters = await getDataActions().createFiltersFromValueClickAction({ data: item.values });
return Boolean(filters.length);
};
diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/handler.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/handler.js
index ecf67ee3e017c..f33ce0395af1f 100644
--- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/handler.js
+++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/handler.js
@@ -83,10 +83,21 @@ export class Handler {
// memoize so that the same function is returned every time,
// allowing us to remove/re-add the same function
- this.getProxyHandler = _.memoize(function(event) {
+ this.getProxyHandler = _.memoize(function(eventType) {
const self = this;
- return function(e) {
- self.vis.emit(event, e);
+ return function(eventPayload) {
+ switch (eventType) {
+ case 'brush':
+ const xRaw = _.get(eventPayload.data, 'series[0].values[0].xRaw');
+ if (!xRaw) return; // not sure if this is possible?
+ return self.vis.emit(eventType, {
+ table: xRaw.table,
+ range: eventPayload.range,
+ column: xRaw.column,
+ });
+ case 'click':
+ return self.vis.emit(eventType, eventPayload);
+ }
};
});
diff --git a/src/legacy/ui/public/i18n/index.tsx b/src/legacy/ui/public/i18n/index.tsx
index 4d0f5d3a5bd56..c918554563fcb 100644
--- a/src/legacy/ui/public/i18n/index.tsx
+++ b/src/legacy/ui/public/i18n/index.tsx
@@ -44,7 +44,7 @@ export function wrapInI18nContext(ComponentToWrap: React.ComponentType
) {
}
uiModules
- .get('i18n')
+ .get('i18n', ['ngSanitize'])
.provider('i18n', I18nProvider)
.filter('i18n', i18nFilter)
.directive('i18nId', i18nDirective);
diff --git a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js
index f14f26613ef01..271586bb8c582 100644
--- a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js
+++ b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js
@@ -377,7 +377,8 @@ export const npStart = {
},
data: {
actions: {
- createFiltersFromEvent: Promise.resolve(['yes']),
+ createFiltersFromValueClickAction: Promise.resolve(['yes']),
+ createFiltersFromRangeSelectAction: sinon.fake(),
},
autocomplete: {
getProvider: sinon.fake(),
diff --git a/src/plugins/dashboard/public/application/application.ts b/src/plugins/dashboard/public/application/application.ts
index 3134a5bfe2c67..a1696298117b0 100644
--- a/src/plugins/dashboard/public/application/application.ts
+++ b/src/plugins/dashboard/public/application/application.ts
@@ -38,12 +38,7 @@ import { EmbeddableStart } from '../../../embeddable/public';
import { NavigationPublicPluginStart as NavigationStart } from '../../../navigation/public';
import { DataPublicPluginStart } from '../../../data/public';
import { SharePluginStart } from '../../../share/public';
-import {
- KibanaLegacyStart,
- configureAppAngularModule,
- createTopNavDirective,
- createTopNavHelper,
-} from '../../../kibana_legacy/public';
+import { KibanaLegacyStart, configureAppAngularModule } from '../../../kibana_legacy/public';
import { SavedObjectLoader } from '../../../saved_objects/public';
export interface RenderDeps {
@@ -114,13 +109,11 @@ function mountDashboardApp(appBasePath: string, element: HTMLElement) {
function createLocalAngularModule(core: AppMountContext['core'], navigation: NavigationStart) {
createLocalI18nModule();
- createLocalTopNavModule(navigation);
createLocalIconModule();
const dashboardAngularModule = angular.module(moduleName, [
...thirdPartyAngularDependencies,
'app/dashboard/I18n',
- 'app/dashboard/TopNav',
'app/dashboard/icon',
]);
return dashboardAngularModule;
@@ -132,13 +125,6 @@ function createLocalIconModule() {
.directive('icon', reactDirective => reactDirective(EuiIcon));
}
-function createLocalTopNavModule(navigation: NavigationStart) {
- angular
- .module('app/dashboard/TopNav', ['react'])
- .directive('kbnTopNav', createTopNavDirective)
- .directive('kbnTopNavHelper', createTopNavHelper(navigation.ui));
-}
-
function createLocalI18nModule() {
angular
.module('app/dashboard/I18n', [])
diff --git a/src/plugins/dashboard/public/application/dashboard_app.html b/src/plugins/dashboard/public/application/dashboard_app.html
index 3cf8932958b6d..87a5728ac2059 100644
--- a/src/plugins/dashboard/public/application/dashboard_app.html
+++ b/src/plugins/dashboard/public/application/dashboard_app.html
@@ -2,52 +2,7 @@
class="app-container dshAppContainer"
ng-class="{'dshAppContainer--withMargins': model.useMargins}"
>
-
-
-
-
-
-
-
-
+
{{screenTitle}}
diff --git a/src/plugins/dashboard/public/application/dashboard_app.tsx b/src/plugins/dashboard/public/application/dashboard_app.tsx
index 150cd8f8fcbb5..f101935b9288d 100644
--- a/src/plugins/dashboard/public/application/dashboard_app.tsx
+++ b/src/plugins/dashboard/public/application/dashboard_app.tsx
@@ -33,7 +33,6 @@ import { SavedObjectDashboard } from '../saved_dashboards';
export interface DashboardAppScope extends ng.IScope {
dash: SavedObjectDashboard;
appState: DashboardAppState;
- screenTitle: string;
model: {
query: Query;
filters: Filter[];
@@ -54,21 +53,7 @@ export interface DashboardAppScope extends ng.IScope {
getShouldShowEditHelp: () => boolean;
getShouldShowViewHelp: () => boolean;
updateQueryAndFetch: ({ query, dateRange }: { query: Query; dateRange?: TimeRange }) => void;
- onRefreshChange: ({
- isPaused,
- refreshInterval,
- }: {
- isPaused: boolean;
- refreshInterval: any;
- }) => void;
- onFiltersUpdated: (filters: Filter[]) => void;
- onCancelApplyFilters: () => void;
- onApplyFilters: (filters: Filter[]) => void;
- onQuerySaved: (savedQuery: SavedQuery) => void;
- onSavedQueryUpdated: (savedQuery: SavedQuery) => void;
- onClearSavedQuery: () => void;
topNavMenu: any;
- showFilterBar: () => boolean;
showAddPanel: any;
showSaveQuery: boolean;
kbnTopNav: any;
diff --git a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx
index 283fe9f0a83a4..b4a53234bffac 100644
--- a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx
+++ b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx
@@ -21,12 +21,15 @@ import _, { uniq } from 'lodash';
import { i18n } from '@kbn/i18n';
import { EUI_MODAL_CANCEL_BUTTON } from '@elastic/eui';
import React from 'react';
+import ReactDOM from 'react-dom';
import angular from 'angular';
import { Subscription } from 'rxjs';
import { map } from 'rxjs/operators';
import { History } from 'history';
import { SavedObjectSaveOpts } from 'src/plugins/saved_objects/public';
+import { NavigationPublicPluginStart as NavigationStart } from 'src/plugins/navigation/public';
+import { TimeRange } from 'src/plugins/data/public';
import { DashboardEmptyScreen, DashboardEmptyScreenProps } from './dashboard_empty_screen';
import {
@@ -87,6 +90,7 @@ export interface DashboardAppControllerDependencies extends RenderDeps {
dashboardConfig: KibanaLegacyStart['dashboardConfig'];
history: History;
kbnUrlStateStorage: IKbnUrlStateStorage;
+ navigation: NavigationStart;
}
export class DashboardAppController {
@@ -123,10 +127,13 @@ export class DashboardAppController {
history,
kbnUrlStateStorage,
usageCollection,
+ navigation,
}: DashboardAppControllerDependencies) {
const filterManager = queryService.filterManager;
const queryFilter = filterManager;
const timefilter = queryService.timefilter.timefilter;
+ let showSearchBar = true;
+ let showQueryBar = true;
let lastReloadRequestTime = 0;
const dash = ($scope.dash = $route.current.locals.dash);
@@ -243,6 +250,9 @@ export class DashboardAppController {
}
};
+ const showFilterBar = () =>
+ $scope.model.filters.length > 0 || !dashboardStateManager.getFullScreenMode();
+
const getEmptyScreenProps = (
shouldShowEditHelp: boolean,
isEmptyInReadOnlyMode: boolean
@@ -310,7 +320,6 @@ export class DashboardAppController {
refreshInterval: timefilter.getRefreshInterval(),
};
$scope.panels = dashboardStateManager.getPanels();
- $scope.screenTitle = dashboardStateManager.getTitle();
};
updateState();
@@ -515,49 +524,8 @@ export class DashboardAppController {
}
};
- $scope.onRefreshChange = function({ isPaused, refreshInterval }) {
- timefilter.setRefreshInterval({
- pause: isPaused,
- value: refreshInterval ? refreshInterval : $scope.model.refreshInterval.value,
- });
- };
-
- $scope.onFiltersUpdated = filters => {
- // The filters will automatically be set when the queryFilter emits an update event (see below)
- queryFilter.setFilters(filters);
- };
-
- $scope.onQuerySaved = savedQuery => {
- $scope.savedQuery = savedQuery;
- };
-
- $scope.onSavedQueryUpdated = savedQuery => {
- $scope.savedQuery = { ...savedQuery };
- };
-
- $scope.onClearSavedQuery = () => {
- delete $scope.savedQuery;
- dashboardStateManager.setSavedQueryId(undefined);
- dashboardStateManager.applyFilters(
- {
- query: '',
- language:
- localStorage.get('kibana.userQueryLanguage') || uiSettings.get('search:queryLanguage'),
- },
- queryFilter.getGlobalFilters()
- );
- // Making this method sync broke the updates.
- // Temporary fix, until we fix the complex state in this file.
- setTimeout(() => {
- queryFilter.setFilters(queryFilter.getGlobalFilters());
- }, 0);
- };
-
const updateStateFromSavedQuery = (savedQuery: SavedQuery) => {
- const savedQueryFilters = savedQuery.attributes.filters || [];
- const globalFilters = queryFilter.getGlobalFilters();
- const allFilters = [...globalFilters, ...savedQueryFilters];
-
+ const allFilters = filterManager.getFilters();
dashboardStateManager.applyFilters(savedQuery.attributes.query, allFilters);
if (savedQuery.attributes.timefilter) {
timefilter.setTime({
@@ -616,6 +584,42 @@ export class DashboardAppController {
}
);
+ const onSavedQueryIdChange = (savedQueryId?: string) => {
+ dashboardStateManager.setSavedQueryId(savedQueryId);
+ };
+
+ const getNavBarProps = () => {
+ const isFullScreenMode = dashboardStateManager.getFullScreenMode();
+ const screenTitle = dashboardStateManager.getTitle();
+ return {
+ appName: 'dashboard',
+ config: $scope.isVisible ? $scope.topNavMenu : undefined,
+ className: isFullScreenMode ? 'kbnTopNavMenu-isFullScreen' : undefined,
+ screenTitle,
+ showSearchBar,
+ showQueryBar,
+ showFilterBar: showFilterBar(),
+ indexPatterns: $scope.indexPatterns,
+ showSaveQuery: $scope.showSaveQuery,
+ query: $scope.model.query,
+ savedQuery: $scope.savedQuery,
+ onSavedQueryIdChange,
+ savedQueryId: dashboardStateManager.getSavedQueryId(),
+ useDefaultBehaviors: true,
+ onQuerySubmit: (payload: { dateRange: TimeRange; query?: Query }): void => {
+ if (!payload.query) {
+ $scope.updateQueryAndFetch({ query: $scope.model.query, dateRange: payload.dateRange });
+ } else {
+ $scope.updateQueryAndFetch({ query: payload.query, dateRange: payload.dateRange });
+ }
+ },
+ };
+ };
+ const dashboardNavBar = document.getElementById('dashboardChrome');
+ const updateNavBar = () => {
+ ReactDOM.render(, dashboardNavBar);
+ };
+
$scope.timefilterSubscriptions$ = new Subscription();
$scope.timefilterSubscriptions$.add(
@@ -707,6 +711,8 @@ export class DashboardAppController {
revertChangesAndExitEditMode();
}
});
+
+ updateNavBar();
};
/**
@@ -761,9 +767,6 @@ export class DashboardAppController {
});
}
- $scope.showFilterBar = () =>
- $scope.model.filters.length > 0 || !dashboardStateManager.getFullScreenMode();
-
$scope.showAddPanel = () => {
dashboardStateManager.setFullScreenMode(false);
/*
@@ -785,7 +788,11 @@ export class DashboardAppController {
const navActions: {
[key: string]: NavAction;
} = {};
- navActions[TopNavIds.FULL_SCREEN] = () => dashboardStateManager.setFullScreenMode(true);
+ navActions[TopNavIds.FULL_SCREEN] = () => {
+ dashboardStateManager.setFullScreenMode(true);
+ showQueryBar = false;
+ updateNavBar();
+ };
navActions[TopNavIds.EXIT_EDIT_MODE] = () => onChangeViewMode(ViewMode.VIEW);
navActions[TopNavIds.ENTER_EDIT_MODE] = () => onChangeViewMode(ViewMode.EDIT);
navActions[TopNavIds.SAVE] = () => {
@@ -858,6 +865,7 @@ export class DashboardAppController {
if ((response as { error: Error }).error) {
dashboardStateManager.setTitle(currentTitle);
}
+ updateNavBar();
return response;
});
};
@@ -939,6 +947,9 @@ export class DashboardAppController {
const visibleSubscription = chrome.getIsVisible$().subscribe(isVisible => {
$scope.$evalAsync(() => {
$scope.isVisible = isVisible;
+ showSearchBar = isVisible || showFilterBar();
+ showQueryBar = !dashboardStateManager.getFullScreenMode() && isVisible;
+ updateNavBar();
});
});
@@ -949,6 +960,11 @@ export class DashboardAppController {
navActions,
dashboardConfig.getHideWriteControls()
);
+ updateNavBar();
+ });
+
+ $scope.$watch('indexPatterns', () => {
+ updateNavBar();
});
$scope.$on('$destroy', () => {
@@ -965,9 +981,6 @@ export class DashboardAppController {
if (outputSubscription) {
outputSubscription.unsubscribe();
}
- if (dashboardContainer) {
- dashboardContainer.destroy();
- }
});
}
}
diff --git a/src/plugins/dashboard/public/application/embeddable/placeholder/placeholder_embeddable_factory.ts b/src/plugins/dashboard/public/application/embeddable/placeholder/placeholder_embeddable_factory.ts
index 30a93989649a7..b3ce2f1e57d5f 100644
--- a/src/plugins/dashboard/public/application/embeddable/placeholder/placeholder_embeddable_factory.ts
+++ b/src/plugins/dashboard/public/application/embeddable/placeholder/placeholder_embeddable_factory.ts
@@ -33,6 +33,10 @@ export class PlaceholderEmbeddableFactory implements EmbeddableFactoryDefinition
return false;
}
+ public canCreateNew() {
+ return false;
+ }
+
public async create(initialInput: EmbeddableInput, parent?: IContainer) {
return new PlaceholderEmbeddable(initialInput, parent);
}
diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx
index 203c784d9df4e..5f6b67ee6ad20 100644
--- a/src/plugins/dashboard/public/plugin.tsx
+++ b/src/plugins/dashboard/public/plugin.tsx
@@ -251,6 +251,8 @@ export class DashboardPlugin
localStorage: new Storage(localStorage),
usageCollection,
};
+ // make sure the index pattern list is up to date
+ await dataStart.indexPatterns.clearCache();
const { renderApp } = await import('./application/application');
const unmount = renderApp(params.element, params.appBasePath, deps);
return () => {
diff --git a/src/plugins/data/public/actions/filters/brush_event.test.ts b/src/plugins/data/public/actions/filters/create_filters_from_range_select.test.ts
similarity index 58%
rename from src/plugins/data/public/actions/filters/brush_event.test.ts
rename to src/plugins/data/public/actions/filters/create_filters_from_range_select.test.ts
index 60244354f06e4..5d21b395b994f 100644
--- a/src/plugins/data/public/actions/filters/brush_event.test.ts
+++ b/src/plugins/data/public/actions/filters/create_filters_from_range_select.test.ts
@@ -19,30 +19,34 @@
import moment from 'moment';
-import { onBrushEvent, BrushEvent } from './brush_event';
+import { createFiltersFromRangeSelectAction } from './create_filters_from_range_select';
-import { IndexPatternsContract } from '../../../public';
+import { IndexPatternsContract, RangeFilter } from '../../../public';
import { dataPluginMock } from '../../../public/mocks';
import { setIndexPatterns } from '../../../public/services';
import { mockDataServices } from '../../../public/search/aggs/test_helpers';
+import { TriggerContextMapping } from '../../../../ui_actions/public';
describe('brushEvent', () => {
const DAY_IN_MS = 24 * 60 * 60 * 1000;
const JAN_01_2014 = 1388559600000;
- let baseEvent: BrushEvent;
+ let baseEvent: TriggerContextMapping['SELECT_RANGE_TRIGGER']['data'];
+
+ const indexPattern = {
+ id: 'indexPatternId',
+ timeFieldName: 'time',
+ fields: {
+ getByName: () => undefined,
+ filter: () => [],
+ },
+ };
const aggConfigs = [
{
params: {
field: {},
},
- getIndexPattern: () => ({
- timeFieldName: 'time',
- fields: {
- getByName: () => undefined,
- filter: () => [],
- },
- }),
+ getIndexPattern: () => indexPattern,
},
];
@@ -50,56 +54,37 @@ describe('brushEvent', () => {
mockDataServices();
setIndexPatterns(({
...dataPluginMock.createStartContract().indexPatterns,
- get: async () => ({
- id: 'indexPatternId',
- timeFieldName: 'time',
- fields: {
- getByName: () => undefined,
- filter: () => [],
- },
- }),
+ get: async () => indexPattern,
} as unknown) as IndexPatternsContract);
baseEvent = {
- data: {
- ordered: {
- date: false,
- },
- series: [
+ column: 0,
+ table: {
+ type: 'kibana_datatable',
+ columns: [
{
- values: [
- {
- xRaw: {
- column: 0,
- table: {
- columns: [
- {
- id: '1',
- meta: {
- type: 'histogram',
- indexPatternId: 'indexPatternId',
- aggConfigParams: aggConfigs[0].params,
- },
- },
- ],
- },
- },
- },
- ],
+ id: '1',
+ name: '1',
+ meta: {
+ type: 'histogram',
+ indexPatternId: 'indexPatternId',
+ aggConfigParams: aggConfigs[0].params,
+ },
},
],
+ rows: [],
},
range: [],
};
});
test('should be a function', () => {
- expect(typeof onBrushEvent).toBe('function');
+ expect(typeof createFiltersFromRangeSelectAction).toBe('function');
});
test('ignores event when data.xAxisField not provided', async () => {
- const filter = await onBrushEvent(baseEvent);
- expect(filter).toBeUndefined();
+ const filter = await createFiltersFromRangeSelectAction(baseEvent);
+ expect(filter).toEqual([]);
});
describe('handles an event when the x-axis field is a date field', () => {
@@ -109,29 +94,29 @@ describe('brushEvent', () => {
name: 'time',
type: 'date',
};
- baseEvent.data.ordered = { date: true };
});
afterAll(() => {
baseEvent.range = [];
- baseEvent.data.ordered = { date: false };
+ aggConfigs[0].params.field = {};
});
test('by ignoring the event when range spans zero time', async () => {
baseEvent.range = [JAN_01_2014, JAN_01_2014];
- const filter = await onBrushEvent(baseEvent);
- expect(filter).toBeUndefined();
+ const filter = await createFiltersFromRangeSelectAction(baseEvent);
+ expect(filter).toEqual([]);
});
test('by updating the timefilter', async () => {
baseEvent.range = [JAN_01_2014, JAN_01_2014 + DAY_IN_MS];
- const filter = await onBrushEvent(baseEvent);
+ const filter = await createFiltersFromRangeSelectAction(baseEvent);
expect(filter).toBeDefined();
- if (filter) {
- expect(filter.range.time.gte).toBe(new Date(JAN_01_2014).toISOString());
+ if (filter.length) {
+ const rangeFilter = filter[0] as RangeFilter;
+ expect(rangeFilter.range.time.gte).toBe(new Date(JAN_01_2014).toISOString());
// Set to a baseline timezone for comparison.
- expect(filter.range.time.lt).toBe(new Date(JAN_01_2014 + DAY_IN_MS).toISOString());
+ expect(rangeFilter.range.time.lt).toBe(new Date(JAN_01_2014 + DAY_IN_MS).toISOString());
}
});
});
@@ -142,26 +127,26 @@ describe('brushEvent', () => {
name: 'anotherTimeField',
type: 'date',
};
- baseEvent.data.ordered = { date: true };
});
afterAll(() => {
baseEvent.range = [];
- baseEvent.data.ordered = { date: false };
+ aggConfigs[0].params.field = {};
});
test('creates a new range filter', async () => {
const rangeBegin = JAN_01_2014;
const rangeEnd = rangeBegin + DAY_IN_MS;
baseEvent.range = [rangeBegin, rangeEnd];
- const filter = await onBrushEvent(baseEvent);
+ const filter = await createFiltersFromRangeSelectAction(baseEvent);
expect(filter).toBeDefined();
- if (filter) {
- expect(filter.range.anotherTimeField.gte).toBe(moment(rangeBegin).toISOString());
- expect(filter.range.anotherTimeField.lt).toBe(moment(rangeEnd).toISOString());
- expect(filter.range.anotherTimeField).toHaveProperty(
+ if (filter.length) {
+ const rangeFilter = filter[0] as RangeFilter;
+ expect(rangeFilter.range.anotherTimeField.gte).toBe(moment(rangeBegin).toISOString());
+ expect(rangeFilter.range.anotherTimeField.lt).toBe(moment(rangeEnd).toISOString());
+ expect(rangeFilter.range.anotherTimeField).toHaveProperty(
'format',
'strict_date_optional_time'
);
@@ -184,20 +169,21 @@ describe('brushEvent', () => {
test('by ignoring the event when range does not span at least 2 values', async () => {
baseEvent.range = [1];
- const filter = await onBrushEvent(baseEvent);
- expect(filter).toBeUndefined();
+ const filter = await createFiltersFromRangeSelectAction(baseEvent);
+ expect(filter).toEqual([]);
});
test('by creating a new filter', async () => {
baseEvent.range = [1, 2, 3, 4];
- const filter = await onBrushEvent(baseEvent);
+ const filter = await createFiltersFromRangeSelectAction(baseEvent);
expect(filter).toBeDefined();
- if (filter) {
- expect(filter.range.numberField.gte).toBe(1);
- expect(filter.range.numberField.lt).toBe(4);
- expect(filter.range.numberField).not.toHaveProperty('format');
+ if (filter.length) {
+ const rangeFilter = filter[0] as RangeFilter;
+ expect(rangeFilter.range.numberField.gte).toBe(1);
+ expect(rangeFilter.range.numberField.lt).toBe(4);
+ expect(rangeFilter.range.numberField).not.toHaveProperty('format');
}
});
});
diff --git a/src/plugins/data/public/actions/filters/brush_event.ts b/src/plugins/data/public/actions/filters/create_filters_from_range_select.ts
similarity index 74%
rename from src/plugins/data/public/actions/filters/brush_event.ts
rename to src/plugins/data/public/actions/filters/create_filters_from_range_select.ts
index 714f005fbeb6d..409614ca9c380 100644
--- a/src/plugins/data/public/actions/filters/brush_event.ts
+++ b/src/plugins/data/public/actions/filters/create_filters_from_range_select.ts
@@ -17,34 +17,18 @@
* under the License.
*/
-import { get, last } from 'lodash';
+import { last } from 'lodash';
import moment from 'moment';
import { esFilters, IFieldType, RangeFilterParams } from '../../../public';
import { getIndexPatterns } from '../../../public/services';
import { deserializeAggConfig } from '../../search/expressions/utils';
+import { RangeSelectTriggerContext } from '../../../../embeddable/public';
-export interface BrushEvent {
- data: {
- ordered: {
- date: boolean;
- };
- series: Array>;
- };
- range: number[];
-}
-
-export async function onBrushEvent(event: BrushEvent) {
- const isDate = get(event.data, 'ordered.date');
- const xRaw: Record = get(event.data, 'series[0].values[0].xRaw');
-
- if (!xRaw) {
- return;
- }
-
- const column: Record = xRaw.table.columns[xRaw.column];
+export async function createFiltersFromRangeSelectAction(event: RangeSelectTriggerContext['data']) {
+ const column: Record = event.table.columns[event.column];
if (!column || !column.meta) {
- return;
+ return [];
}
const indexPattern = await getIndexPatterns().get(column.meta.indexPatternId);
@@ -55,16 +39,18 @@ export async function onBrushEvent(event: BrushEvent) {
const field: IFieldType = aggConfig.params.field;
if (!field || event.range.length <= 1) {
- return;
+ return [];
}
const min = event.range[0];
const max = last(event.range);
if (min === max) {
- return;
+ return [];
}
+ const isDate = field.type === 'date';
+
const range: RangeFilterParams = {
gte: isDate ? moment(min).toISOString() : min,
lt: isDate ? moment(max).toISOString() : max,
@@ -74,5 +60,5 @@ export async function onBrushEvent(event: BrushEvent) {
range.format = 'strict_date_optional_time';
}
- return esFilters.buildRangeFilter(field, range, indexPattern);
+ return esFilters.mapAndFlattenFilters([esFilters.buildRangeFilter(field, range, indexPattern)]);
}
diff --git a/src/plugins/data/public/actions/filters/create_filters_from_event.test.ts b/src/plugins/data/public/actions/filters/create_filters_from_value_click.test.ts
similarity index 85%
rename from src/plugins/data/public/actions/filters/create_filters_from_event.test.ts
rename to src/plugins/data/public/actions/filters/create_filters_from_value_click.test.ts
index 1ed09002816d1..a0e285c20d776 100644
--- a/src/plugins/data/public/actions/filters/create_filters_from_event.test.ts
+++ b/src/plugins/data/public/actions/filters/create_filters_from_value_click.test.ts
@@ -26,7 +26,8 @@ import {
import { dataPluginMock } from '../../../public/mocks';
import { setIndexPatterns } from '../../../public/services';
import { mockDataServices } from '../../../public/search/aggs/test_helpers';
-import { createFiltersFromEvent, EventData } from './create_filters_from_event';
+import { createFiltersFromValueClickAction } from './create_filters_from_value_click';
+import { ValueClickTriggerContext } from '../../../../embeddable/public';
const mockField = {
name: 'bytes',
@@ -37,8 +38,8 @@ const mockField = {
format: new fieldFormats.BytesFormat({}, (() => {}) as FieldFormatsGetConfigFn),
};
-describe('createFiltersFromEvent', () => {
- let dataPoints: EventData[];
+describe('createFiltersFromValueClick', () => {
+ let dataPoints: ValueClickTriggerContext['data']['data'];
beforeEach(() => {
dataPoints = [
@@ -86,7 +87,7 @@ describe('createFiltersFromEvent', () => {
test('ignores event when value for rows is not provided', async () => {
dataPoints[0].table.rows[0]['1-1'] = null;
- const filters = await createFiltersFromEvent(dataPoints);
+ const filters = await createFiltersFromValueClickAction({ data: dataPoints });
expect(filters.length).toEqual(0);
});
@@ -95,14 +96,14 @@ describe('createFiltersFromEvent', () => {
if (dataPoints[0].table.columns[0].meta) {
dataPoints[0].table.columns[0].meta.type = 'terms';
}
- const filters = await createFiltersFromEvent(dataPoints);
+ const filters = await createFiltersFromValueClickAction({ data: dataPoints });
expect(filters.length).toEqual(1);
expect(filters[0].query.match_phrase.bytes).toEqual('2048');
});
test('handles an event when aggregations type is not terms', async () => {
- const filters = await createFiltersFromEvent(dataPoints);
+ const filters = await createFiltersFromValueClickAction({ data: dataPoints });
expect(filters.length).toEqual(1);
diff --git a/src/plugins/data/public/actions/filters/create_filters_from_event.ts b/src/plugins/data/public/actions/filters/create_filters_from_value_click.ts
similarity index 90%
rename from src/plugins/data/public/actions/filters/create_filters_from_event.ts
rename to src/plugins/data/public/actions/filters/create_filters_from_value_click.ts
index e62945a592072..2b426813a98a4 100644
--- a/src/plugins/data/public/actions/filters/create_filters_from_event.ts
+++ b/src/plugins/data/public/actions/filters/create_filters_from_value_click.ts
@@ -21,13 +21,7 @@ import { KibanaDatatable } from '../../../../../plugins/expressions/public';
import { deserializeAggConfig } from '../../search/expressions';
import { esFilters, Filter } from '../../../public';
import { getIndexPatterns } from '../../../public/services';
-
-export interface EventData {
- table: Pick;
- column: number;
- row: number;
- value: any;
-}
+import { ValueClickTriggerContext } from '../../../../embeddable/public';
/**
* For terms aggregations on `__other__` buckets, this assembles a list of applicable filter
@@ -39,7 +33,7 @@ export interface EventData {
* @return {array} - array of terms to filter against
*/
const getOtherBucketFilterTerms = (
- table: EventData['table'],
+ table: Pick,
columnIndex: number,
rowIndex: number
) => {
@@ -76,7 +70,11 @@ const getOtherBucketFilterTerms = (
* @param {string} cellValue - value of the current cell
* @return {Filter[]|undefined} - list of filters to provide to queryFilter.addFilters()
*/
-const createFilter = async (table: EventData['table'], columnIndex: number, rowIndex: number) => {
+const createFilter = async (
+ table: Pick,
+ columnIndex: number,
+ rowIndex: number
+) => {
if (!table || !table.columns || !table.columns[columnIndex]) {
return;
}
@@ -113,11 +111,14 @@ const createFilter = async (table: EventData['table'], columnIndex: number, rowI
};
/** @public */
-export const createFiltersFromEvent = async (dataPoints: EventData[], negate?: boolean) => {
+export const createFiltersFromValueClickAction = async ({
+ data,
+ negate,
+}: ValueClickTriggerContext['data']) => {
const filters: Filter[] = [];
await Promise.all(
- dataPoints
+ data
.filter(point => point)
.map(async val => {
const { table, column, row } = val;
@@ -133,5 +134,5 @@ export const createFiltersFromEvent = async (dataPoints: EventData[], negate?: b
})
);
- return filters;
+ return esFilters.mapAndFlattenFilters(filters);
};
diff --git a/src/plugins/data/public/actions/index.ts b/src/plugins/data/public/actions/index.ts
index cdb84ff13f25e..ef9014aafe82d 100644
--- a/src/plugins/data/public/actions/index.ts
+++ b/src/plugins/data/public/actions/index.ts
@@ -18,6 +18,7 @@
*/
export { ACTION_GLOBAL_APPLY_FILTER, createFilterAction } from './apply_filter_action';
-export { createFiltersFromEvent } from './filters/create_filters_from_event';
+export { createFiltersFromValueClickAction } from './filters/create_filters_from_value_click';
+export { createFiltersFromRangeSelectAction } from './filters/create_filters_from_range_select';
export { selectRangeAction } from './select_range_action';
export { valueClickAction } from './value_click_action';
diff --git a/src/plugins/data/public/actions/select_range_action.ts b/src/plugins/data/public/actions/select_range_action.ts
index 6e1f16a09e803..70a018e3c2bda 100644
--- a/src/plugins/data/public/actions/select_range_action.ts
+++ b/src/plugins/data/public/actions/select_range_action.ts
@@ -23,19 +23,17 @@ import {
IncompatibleActionError,
ActionByType,
} from '../../../../plugins/ui_actions/public';
-import { onBrushEvent } from './filters/brush_event';
+import { createFiltersFromRangeSelectAction } from './filters/create_filters_from_range_select';
+import { RangeSelectTriggerContext } from '../../../embeddable/public';
import { FilterManager, TimefilterContract, esFilters } from '..';
export const ACTION_SELECT_RANGE = 'ACTION_SELECT_RANGE';
-export interface SelectRangeActionContext {
- data: any;
- timeFieldName: string;
-}
+export type SelectRangeActionContext = RangeSelectTriggerContext;
async function isCompatible(context: SelectRangeActionContext) {
try {
- return Boolean(await onBrushEvent(context.data));
+ return Boolean(await createFiltersFromRangeSelectAction(context.data));
} catch {
return false;
}
@@ -59,13 +57,7 @@ export function selectRangeAction(
throw new IncompatibleActionError();
}
- const filter = await onBrushEvent(data);
-
- if (!filter) {
- return;
- }
-
- const selectedFilters = esFilters.mapAndFlattenFilters([filter]);
+ const selectedFilters = await createFiltersFromRangeSelectAction(data);
if (timeFieldName) {
const { timeRangeFilter, restOfFilters } = esFilters.extractTimeFilter(
diff --git a/src/plugins/data/public/actions/value_click_action.ts b/src/plugins/data/public/actions/value_click_action.ts
index 01c32e27da07d..1141e485309cf 100644
--- a/src/plugins/data/public/actions/value_click_action.ts
+++ b/src/plugins/data/public/actions/value_click_action.ts
@@ -26,21 +26,17 @@ import {
} from '../../../../plugins/ui_actions/public';
import { getOverlays, getIndexPatterns } from '../services';
import { applyFiltersPopover } from '../ui/apply_filters';
-import { createFiltersFromEvent } from './filters/create_filters_from_event';
+import { createFiltersFromValueClickAction } from './filters/create_filters_from_value_click';
+import { ValueClickTriggerContext } from '../../../embeddable/public';
import { Filter, FilterManager, TimefilterContract, esFilters } from '..';
export const ACTION_VALUE_CLICK = 'ACTION_VALUE_CLICK';
-export interface ValueClickActionContext {
- data: any;
- timeFieldName: string;
-}
+export type ValueClickActionContext = ValueClickTriggerContext;
async function isCompatible(context: ValueClickActionContext) {
try {
- const filters: Filter[] =
- (await createFiltersFromEvent(context.data.data || [context.data], context.data.negate)) ||
- [];
+ const filters: Filter[] = await createFiltersFromValueClickAction(context.data);
return filters.length > 0;
} catch {
return false;
@@ -60,17 +56,16 @@ export function valueClickAction(
});
},
isCompatible,
- execute: async ({ timeFieldName, data }: ValueClickActionContext) => {
- if (!(await isCompatible({ timeFieldName, data }))) {
+ execute: async (context: ValueClickActionContext) => {
+ if (!(await isCompatible(context))) {
throw new IncompatibleActionError();
}
- const filters: Filter[] =
- (await createFiltersFromEvent(data.data || [data], data.negate)) || [];
+ const filters: Filter[] = await createFiltersFromValueClickAction(context.data);
- let selectedFilters: Filter[] = esFilters.mapAndFlattenFilters(filters);
+ let selectedFilters = filters;
- if (selectedFilters.length > 1) {
+ if (filters.length > 1) {
const indexPatterns = await Promise.all(
filters.map(filter => {
return getIndexPatterns().get(filter.meta.index!);
@@ -102,9 +97,9 @@ export function valueClickAction(
selectedFilters = await filterSelectionPromise;
}
- if (timeFieldName) {
+ if (context.timeFieldName) {
const { timeRangeFilter, restOfFilters } = esFilters.extractTimeFilter(
- timeFieldName,
+ context.timeFieldName,
selectedFilters
);
filterManager.addFilters(restOfFilters);
diff --git a/src/plugins/data/public/mocks.ts b/src/plugins/data/public/mocks.ts
index 2d43cae79ac98..1f604b9eb6baa 100644
--- a/src/plugins/data/public/mocks.ts
+++ b/src/plugins/data/public/mocks.ts
@@ -45,7 +45,8 @@ const createStartContract = (): Start => {
const queryStartMock = queryServiceMock.createStartContract();
return {
actions: {
- createFiltersFromEvent: jest.fn().mockResolvedValue(['yes']),
+ createFiltersFromValueClickAction: jest.fn().mockResolvedValue(['yes']),
+ createFiltersFromRangeSelectAction: jest.fn(),
},
autocomplete: autocompleteMock,
search: searchStartMock,
diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts
index 1723545b32522..ccf94171235fe 100644
--- a/src/plugins/data/public/plugin.ts
+++ b/src/plugins/data/public/plugin.ts
@@ -58,7 +58,12 @@ import {
VALUE_CLICK_TRIGGER,
APPLY_FILTER_TRIGGER,
} from '../../ui_actions/public';
-import { ACTION_GLOBAL_APPLY_FILTER, createFilterAction, createFiltersFromEvent } from './actions';
+import {
+ ACTION_GLOBAL_APPLY_FILTER,
+ createFilterAction,
+ createFiltersFromValueClickAction,
+ createFiltersFromRangeSelectAction,
+} from './actions';
import { ApplyGlobalFilterActionContext } from './actions/apply_filter_action';
import {
selectRangeAction,
@@ -162,7 +167,8 @@ export class DataPublicPlugin implements Plugin import("../../expressions/common").SerializedFieldFormat