changing vis.API.events (#25280)

This commit is contained in:
Peter Pisljar 2018-11-16 14:58:36 +01:00 committed by GitHub
parent 33f61a8fa2
commit 6dd2c1c0da
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 211 additions and 184 deletions

View file

@ -80,6 +80,9 @@ export default function GaugeVisType(Private, i18n) {
}
},
},
events: {
brush: { disabled: true },
},
editorConfig: {
collections: {
gaugeTypes: ['Arc', 'Circle'],

View file

@ -76,6 +76,9 @@ export default function GoalVisType(Private, i18n) {
}
},
},
events: {
brush: { disabled: true },
},
editorConfig: {
collections: {
gaugeTypes: ['Arc', 'Circle'],

View file

@ -46,6 +46,9 @@ export default function HistogramVisType(Private, i18n) {
}
},
},
events: {
brush: { disabled: true },
},
editorConfig: {
collections: {
legendPositions: [{

View file

@ -159,7 +159,7 @@ export class MetricVisComponent extends Component {
return;
}
const table = this.props.visData;
this.props.vis.API.events.addFilter(table, metric.columnIndex, metric.rowIndex);
this.props.vis.API.events.filter({ table, column: metric.columnIndex, row: metric.rowIndex });
};
_renderMetric = (metric, index) => {

View file

@ -154,7 +154,7 @@ export function RegionMapsVisualizationProvider(Private, config, i18n) {
}
const rowIndex = this._chartData.rows.findIndex(row => row[0] === event);
this._vis.API.events.addFilter(this._chartData, 0, rowIndex, event);
this._vis.API.events.filter({ table: this._chartData, column: 0, row: rowIndex, value: event });
});
this._choroplethLayer.on('styleChanged', (event) => {

View file

@ -47,9 +47,9 @@ export class TagCloudVisualization {
if (!this._bucketAgg) {
return;
}
this._vis.API.events.addFilter(
event.meta.data, 0, event.meta.rowIndex
);
this._vis.API.events.filter({
table: event.meta.data, column: 0, row: event.meta.rowIndex
});
});
this._renderComplete$ = Rx.fromEvent(this._tagCloud, 'renderComplete');

View file

@ -19,7 +19,6 @@
import _ from 'lodash';
import ngMock from 'ng_mock';
import sinon from 'sinon';
import expect from 'expect.js';
import { VisProvider } from '..';
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
@ -42,15 +41,6 @@ describe('Vis Class', function () {
listeners: { click: _.noop }
};
// Wrap the given vis type definition in a state, that can be passed to vis
const state = (type) => ({
type: {
visConfig: { defaults: {} },
schemas: {},
...type,
}
});
beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject(function (Private) {
Vis = Private(VisProvider);
@ -116,41 +106,4 @@ describe('Vis Class', function () {
});
});
describe('vis addFilter method', () => {
let aggConfig;
let data;
beforeEach(() => {
aggConfig = {
type: { name: 'terms' },
params: {},
createFilter: sinon.stub()
};
data = {
columns: [{
id: 'col-0',
title: 'test',
aggConfig
}],
rows: [{ 'col-0': 'US' }]
};
});
it('adds a simple filter', () => {
const vis = new Vis(indexPattern, state({ requestHandler: 'none' }));
vis.API.events.addFilter(data, 0, 0);
expect(aggConfig.createFilter.callCount).to.be(1);
expect(aggConfig.createFilter.getCall(0).args[0]).to.be('US');
});
it('adds a filter if value is provided instead of row index', () => {
const vis = new Vis(indexPattern, state({ requestHandler: 'none' }));
vis.API.events.addFilter(data, 0, -1, 'UK');
expect(aggConfig.createFilter.callCount).to.be(1);
expect(aggConfig.createFilter.getCall(0).args[0]).to.be('UK');
});
});
});

View file

@ -18,9 +18,16 @@
*/
import expect from 'expect.js';
import { BaseVisType } from '../../vis_types/base_vis_type';
import ngMock from 'ng_mock';
import { BaseVisTypeProvider } from '../../vis_types/base_vis_type';
describe('Base Vis Type', function () {
let BaseVisType;
beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject(function (Private) {
BaseVisType = Private(BaseVisTypeProvider);
}));
describe('initialization', () => {
it('should throw if mandatory properties are missing', () => {

View file

@ -18,10 +18,13 @@
*/
import expect from 'expect.js';
import { ReactVisType } from '../../vis_types/react_vis_type';
import ngMock from 'ng_mock';
import { ReactVisTypeProvider } from '../../vis_types/react_vis_type';
describe('React Vis Type', function () {
let ReactVisType;
const visConfig = {
name: 'test',
title: 'test',
@ -31,6 +34,11 @@ describe('React Vis Type', function () {
type: { visConfig: { component: 'test' } }
};
beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject(function (Private) {
ReactVisType = Private(ReactVisTypeProvider);
}));
describe('initialization', () => {
it('should throw if component is not set', () => {
expect(() => {

View file

@ -32,20 +32,17 @@ import _ from 'lodash';
import { VisTypesRegistryProvider } from '../registry/vis_types';
import { AggConfigs } from './agg_configs';
import { PersistedState } from '../persisted_state';
import { onBrushEvent } from '../utils/brush_event';
import { FilterBarQueryFilterProvider } from '../filter_bar/query_filter';
import { updateVisualizationConfig } from './vis_update';
import { SearchSourceProvider } from '../courier/search_source';
import { SavedObjectsClientProvider } from '../saved_objects';
import { timefilter } from 'ui/timefilter';
import { VisFiltersProvider } from './vis_filters';
export function VisProvider(Private, indexPatterns, getAppState) {
const visTypes = Private(VisTypesRegistryProvider);
const queryFilter = Private(FilterBarQueryFilterProvider);
const SearchSource = Private(SearchSourceProvider);
const savedObjectsClient = Private(SavedObjectsClientProvider);
const visFilter = Private(VisFiltersProvider);
class Vis extends EventEmitter {
constructor(indexPattern, visState) {
@ -73,14 +70,8 @@ export function VisProvider(Private, indexPatterns, getAppState) {
timeFilter: timefilter,
queryFilter: queryFilter,
events: {
// the filter method will be removed in the near feature
// you should rather use addFilter method below
filter: visFilter.filter,
createFilter: visFilter.createFilter,
addFilter: visFilter.addFilter,
brush: (event) => {
onBrushEvent(event, getAppState());
}
filter: data => this.eventsSubject.next({ name: 'filterBucket', data }),
brush: data => this.eventsSubject.next({ name: 'brush', data }),
},
getAppState,
};

View file

@ -17,11 +17,13 @@
* under the License.
*/
import { BaseVisType, AngularVisTypeProvider, ReactVisType, VislibVisTypeProvider } from './vis_types';
import { BaseVisTypeProvider, AngularVisTypeProvider, ReactVisTypeProvider, VislibVisTypeProvider } from './vis_types';
export const VisFactoryProvider = (Private) => {
const AngularVisType = Private(AngularVisTypeProvider);
const VislibVisType = Private(VislibVisTypeProvider);
const BaseVisType = Private(BaseVisTypeProvider);
const ReactVisType = Private(ReactVisTypeProvider);
return {
createBaseVisualization: (config) => {

View file

@ -20,6 +20,7 @@
import _ from 'lodash';
import { FilterBarPushFiltersProvider } from '../filter_bar/push_filters';
import { FilterBarQueryFilterProvider } from '../filter_bar/query_filter';
import { onBrushEvent } from '../utils/brush_event';
const getTerms = (table, columnIndex, rowIndex) => {
if (rowIndex === -1) {
@ -41,26 +42,26 @@ const getTerms = (table, columnIndex, rowIndex) => {
}))];
};
export function VisFiltersProvider(Private, getAppState) {
const createFilter = (data, columnIndex, rowIndex, cellValue) => {
const { aggConfig, id: columnId } = data.columns[columnIndex];
let filter = [];
const value = rowIndex > -1 ? data.rows[rowIndex][columnId] : cellValue;
if (value === null || value === undefined) {
return;
}
if (aggConfig.type.name === 'terms' && aggConfig.params.otherBucket) {
const terms = getTerms(data, columnIndex, rowIndex);
filter = aggConfig.createFilter(value, { terms });
} else {
filter = aggConfig.createFilter(value);
}
return filter;
};
const VisFiltersProvider = (Private, getAppState) => {
const filterBarPushFilters = Private(FilterBarPushFiltersProvider);
const queryFilter = Private(FilterBarQueryFilterProvider);
const createFilter = (data, columnIndex, rowIndex, cellValue) => {
const { aggConfig, id: columnId } = data.columns[columnIndex];
let filter = [];
const value = rowIndex > -1 ? data.rows[rowIndex][columnId] : cellValue;
if (value === null || value === undefined) {
return;
}
if (aggConfig.type.name === 'terms' && aggConfig.params.otherBucket) {
const terms = getTerms(data, columnIndex, rowIndex);
filter = aggConfig.createFilter(value, { terms });
} else {
filter = aggConfig.createFilter(value);
}
return filter;
};
const filter = (event, { simulate } = {}) => {
let data = event.datum.aggConfigResult;
const filters = [];
@ -87,14 +88,18 @@ export function VisFiltersProvider(Private, getAppState) {
return filters;
};
const addFilter = (data, columnIndex, rowIndex, cellValue) => {
const filter = createFilter(data, columnIndex, rowIndex, cellValue);
const addFilter = (event) => {
const filter = createFilter(event.table, event.column, event.row, event.value);
queryFilter.addFilters(filter);
};
return {
createFilter,
addFilter,
filter
filter,
brush: (event) => {
onBrushEvent(event, getAppState());
},
};
}
};
export { VisFiltersProvider, createFilter };

View file

@ -17,11 +17,12 @@
* under the License.
*/
import { BaseVisType } from './base_vis_type';
import { BaseVisTypeProvider } from './base_vis_type';
import $ from 'jquery';
export function AngularVisTypeProvider($compile, $rootScope) {
export function AngularVisTypeProvider(Private, $compile, $rootScope) {
const BaseVisType = Private(BaseVisTypeProvider);
class AngularVisController {
constructor(domeElement, vis) {

View file

@ -19,65 +19,77 @@
import { CATEGORY } from '../vis_category';
import _ from 'lodash';
import { VisFiltersProvider } from '../vis_filters';
export class BaseVisType {
constructor(opts = {}) {
export function BaseVisTypeProvider(Private) {
const visFilters = Private(VisFiltersProvider);
if (!opts.name) {
throw('vis_type must define its name');
}
if (!opts.title) {
throw('vis_type must define its title');
}
if (!opts.description) {
throw('vis_type must define its description');
}
if (!opts.icon && !opts.image && !opts.legacyIcon) {
throw('vis_type must define its icon or image');
}
if (!opts.visualization) {
throw('vis_type must define visualization controller');
class BaseVisType {
constructor(opts = {}) {
if (!opts.name) {
throw('vis_type must define its name');
}
if (!opts.title) {
throw('vis_type must define its title');
}
if (!opts.description) {
throw('vis_type must define its description');
}
if (!opts.icon && !opts.image && !opts.legacyIcon) {
throw('vis_type must define its icon or image');
}
if (!opts.visualization) {
throw('vis_type must define visualization controller');
}
const _defaults = {
// name, title, description, icon, image
category: CATEGORY.OTHER,
visualization: null, // must be a class with render/resize/destroy methods
visConfig: {
defaults: {}, // default configuration
},
requestHandler: 'courier', // select one from registry or pass a function
responseHandler: 'none',
editor: 'default',
editorConfig: {
collections: {}, // collections used for configuration (list of positions, ...)
},
options: { // controls the visualize editor
showTimePicker: true,
showQueryBar: true,
showFilterBar: true,
showIndexSelection: true,
hierarchicalData: false // we should get rid of this i guess ?
},
events: {
filterBucket: {
defaultAction: visFilters.addFilter,
}
},
stage: 'production',
feedbackMessage: ''
};
_.defaultsDeep(this, opts, _defaults);
this.requiresSearch = this.requestHandler !== 'none';
}
const _defaults = {
// name, title, description, icon, image
category: CATEGORY.OTHER,
visualization: null, // must be a class with render/resize/destroy methods
visConfig: {
defaults: {}, // default configuration
},
requestHandler: 'courier', // select one from registry or pass a function
responseHandler: 'none',
editor: 'default',
editorConfig: {
collections: {}, // collections used for configuration (list of positions, ...)
},
options: { // controls the visualize editor
showTimePicker: true,
showQueryBar: true,
showFilterBar: true,
showIndexSelection: true,
hierarchicalData: false // we should get rid of this i guess ?
},
stage: 'production',
feedbackMessage: ''
};
shouldMarkAsExperimentalInUI() {
//we are not making a distinction in the UI if a plugin is experimental and/or labs.
//we just want to indicate it is special. the current flask icon is sufficient for that.
return this.stage === 'experimental' || this.stage === 'lab';
}
_.defaultsDeep(this, opts, _defaults);
this.requiresSearch = this.requestHandler !== 'none';
get schemas() {
if (this.editorConfig && this.editorConfig.schemas) {
return this.editorConfig.schemas;
}
return [];
}
}
shouldMarkAsExperimentalInUI() {
//we are not making a distinction in the UI if a plugin is experimental and/or labs.
//we just want to indicate it is special. the current flask icon is sufficient for that.
return this.stage === 'experimental' || this.stage === 'lab';
}
get schemas() {
if (this.editorConfig && this.editorConfig.schemas) {
return this.editorConfig.schemas;
}
return [];
}
return BaseVisType;
}

View file

@ -17,9 +17,9 @@
* under the License.
*/
import { BaseVisType } from './base_vis_type';
import { BaseVisTypeProvider } from './base_vis_type';
import { AngularVisTypeProvider } from './angular_vis_type';
import { VislibVisTypeProvider } from './vislib_vis_type';
import { ReactVisType } from './react_vis_type';
import { ReactVisTypeProvider } from './react_vis_type';
export { BaseVisType, AngularVisTypeProvider, VislibVisTypeProvider, ReactVisType };
export { BaseVisTypeProvider, AngularVisTypeProvider, VislibVisTypeProvider, ReactVisTypeProvider };

View file

@ -20,44 +20,50 @@
import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import chrome from '../../chrome';
import { BaseVisType } from './base_vis_type';
import { BaseVisTypeProvider } from './base_vis_type';
class ReactVisController {
constructor(element, vis) {
this.el = element;
this.vis = vis;
}
export function ReactVisTypeProvider(Private) {
const BaseVisType = Private(BaseVisTypeProvider);
render(visData, updateStatus) {
this.visData = visData;
class ReactVisController {
constructor(element, vis) {
this.el = element;
this.vis = vis;
}
return new Promise((resolve) => {
const Component = this.vis.type.visConfig.component;
const config = chrome.getUiSettingsClient();
render(<Component
config={config}
vis={this.vis}
visData={visData}
renderComplete={resolve}
updateStatus={updateStatus}
/>, this.el);
});
}
render(visData, updateStatus) {
this.visData = visData;
destroy() {
unmountComponentAtNode(this.el);
}
}
return new Promise((resolve) => {
const Component = this.vis.type.visConfig.component;
const config = chrome.getUiSettingsClient();
render(<Component
config={config}
vis={this.vis}
visData={visData}
renderComplete={resolve}
updateStatus={updateStatus}
/>, this.el);
});
}
export class ReactVisType extends BaseVisType {
constructor(opts) {
super({
...opts,
visualization: ReactVisController
});
if (!this.visConfig.component) {
throw new Error('Missing component for ReactVisType');
destroy() {
unmountComponentAtNode(this.el);
}
}
class ReactVisType extends BaseVisType {
constructor(opts) {
super({
...opts,
visualization: ReactVisController
});
if (!this.visConfig.component) {
throw new Error('Missing component for ReactVisType');
}
}
}
return ReactVisType;
}

View file

@ -21,12 +21,14 @@ import _ from 'lodash';
import html from './vislib_vis_legend.html';
import { VislibLibDataProvider } from '../../vislib/lib/data';
import { uiModules } from '../../modules';
import { VisFiltersProvider } from '../vis_filters';
import { htmlIdGenerator, keyCodes } from '@elastic/eui';
uiModules.get('kibana')
.directive('vislibLegend', function (Private, $timeout, i18n) {
const Data = Private(VislibLibDataProvider);
const visFilters = Private(VisFiltersProvider);
return {
restrict: 'E',
@ -104,7 +106,7 @@ uiModules.get('kibana')
};
$scope.canFilter = function (legendData) {
const filters = $scope.vis.API.events.filter({ datum: legendData.values }, { simulate: true });
const filters = visFilters.filter({ datum: legendData.values }, { simulate: true });
return filters.length;
};

View file

@ -24,14 +24,18 @@ import 'plugins/kbn_vislib_vis_types/controls/heatmap_options';
import 'plugins/kbn_vislib_vis_types/controls/gauge_options';
import 'plugins/kbn_vislib_vis_types/controls/point_series';
import './vislib_vis_legend';
import { BaseVisType } from './base_vis_type';
import { BaseVisTypeProvider } from './base_vis_type';
import { AggResponsePointSeriesProvider } from '../../agg_response/point_series/point_series';
import VislibProvider from '../../vislib';
import { VisFiltersProvider } from '../vis_filters';
import $ from 'jquery';
import { defaultsDeep } from 'lodash';
export function VislibVisTypeProvider(Private, $rootScope, $timeout, $compile) {
const pointSeries = Private(AggResponsePointSeriesProvider);
const vislib = Private(VislibProvider);
const visFilters = Private(VisFiltersProvider);
const BaseVisType = Private(BaseVisTypeProvider);
const legendClassName = {
top: 'vislib-container--legend-top',
@ -118,6 +122,14 @@ export function VislibVisTypeProvider(Private, $rootScope, $timeout, $compile) {
if (!opts.responseConverter) {
opts.responseConverter = pointSeries;
}
opts.events = defaultsDeep({}, opts.events, {
filterBucket: {
defaultAction: visFilters.filter,
},
brush: {
defaultAction: visFilters.brush,
}
});
opts.visualization = VislibVisController;
super(opts);
this.refreshLegend = 0;

View file

@ -18,7 +18,7 @@
*/
import { EventEmitter } from 'events';
import { debounce } from 'lodash';
import { debounce, forEach } from 'lodash';
import * as Rx from 'rxjs';
import { share } from 'rxjs/operators';
import { Inspector } from '../../inspector';
@ -80,7 +80,9 @@ export class EmbeddedVisualizeHandler {
private uiState: PersistedState;
private dataLoader: VisualizeDataLoader;
private dataSubject: Rx.Subject<any>;
private inspectorAdapters: Adapters = {};
private readonly inspectorAdapters: Adapters = {};
private actions: any = {};
private events$: Rx.Observable<any>;
constructor(
private readonly element: HTMLElement,
@ -128,6 +130,23 @@ export class EmbeddedVisualizeHandler {
this.vis.openInspector = this.openInspector;
this.vis.hasInspector = this.hasInspector;
// init default actions
forEach(this.vis.type.events, (event, eventName) => {
if (event.disabled || !eventName) {
return;
} else {
this.actions[eventName] = event.defaultAction;
}
});
this.vis.eventsSubject = new Rx.Subject();
this.events$ = this.vis.eventsSubject.asObservable().pipe(share());
this.events$.subscribe(event => {
if (this.actions[event.name]) {
this.actions[event.name](event.data);
}
});
this.dataSubject = new Rx.Subject();
this.data$ = this.dataSubject.asObservable().pipe(share());