Add AggTypeFilters to filter out aggs in editor (#19913)

* Add AggTypeFilters to filter out aggs in editor

* Add documentation

* Implement CJ's feedback

* Add link to missing vis variable

* Fix for RxJS 6

* Add babel-core types and fix tests

* Pass index pattern instead of vis

* Fix documentation
This commit is contained in:
Tim Roes 2018-06-25 10:34:18 +02:00 committed by GitHub
parent 8fc0fcacc0
commit a5810bad98
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 388 additions and 13 deletions

View file

@ -226,6 +226,7 @@
"@kbn/eslint-plugin-license-header": "link:packages/kbn-eslint-plugin-license-header",
"@kbn/plugin-generator": "link:packages/kbn-plugin-generator",
"@kbn/test": "link:packages/kbn-test",
"@types/babel-core": "^6.25.5",
"@types/angular": "^1.6.45",
"@types/babel-core": "^6.25.5",
"@types/bluebird": "^3.1.1",

View file

@ -30,6 +30,7 @@ import 'uiExports/visTypes';
import 'uiExports/visResponseHandlers';
import 'uiExports/visRequestHandlers';
import 'uiExports/visEditorTypes';
import 'uiExports/visualize';
import 'uiExports/savedObjectTypes';
import 'uiExports/fieldFormats';
import 'uiExports/fieldFormatEditors';

View file

@ -20,7 +20,6 @@
import _ from 'lodash';
import '../saved_visualizations/saved_visualizations';
import 'ui/vis/editors/default/sidebar';
import './agg_filter';
import 'ui/visualize';
import 'ui/collapsible_sidebar';
import 'ui/share';

View file

@ -20,7 +20,6 @@
import './styles/main.less';
import './editor/editor';
import './wizard/wizard';
import './editor/agg_filter';
import 'ui/draggable/draggable_container';
import 'ui/draggable/draggable_item';
import 'ui/draggable/draggable_handle';

View file

@ -58,11 +58,12 @@ describe('editor', function () {
]
});
const $el = $('<vis-editor-agg-params agg="agg" group-name="groupName"></vis-editor-agg-params>');
const $el = $('<vis-editor-agg-params agg="agg" index-pattern="vis.indexPattern" group-name="groupName"></vis-editor-agg-params>');
const $parentScope = $injector.get('$rootScope').$new();
agg = $parentScope.agg = vis.aggs.bySchemaName.segment[0];
$parentScope.groupName = 'buckets';
$parentScope.vis = vis;
$compile($el)($parentScope);
$scope = $el.scope();

20
src/ui/public/agg_types/agg_type.d.ts vendored Normal file
View file

@ -0,0 +1,20 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export type AggType = any;

View file

@ -24,6 +24,7 @@
</div>
<div ng-show="agg.params.orderAgg" class="vis-editor-agg-order-agg">
<vis-editor-agg-params
index-pattern="vis.indexPattern"
agg="agg.params.orderAgg"
ng-if="agg.params.orderAgg"
group-name="'metrics'">

View file

@ -25,6 +25,7 @@
<div ng-if="agg.params.metricAgg === 'custom'" class="vis-editor-agg-order-agg">
<ng-form name="customMetricForm">
<vis-editor-agg-params
index-pattern="vis.indexPattern"
agg="agg.params.customMetric"
group-name="'metrics'">
</vis-editor-agg-params>

View file

@ -5,6 +5,7 @@
<ng-form name="{{aggType}}Form">
<vis-editor-agg-params
agg="agg.params[aggType]"
index-pattern="vis.indexPattern"
group-name="'{{aggGroup}}'">
</vis-editor-agg-params>
</ng-form>

View file

@ -0,0 +1,76 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { first } from 'rxjs/operators';
import { AggTypeFilters } from './agg_type_filters';
describe('AggTypeFilters', () => {
let registry: AggTypeFilters;
const indexPattern = {};
const aggConfig = {};
beforeEach(() => {
registry = new AggTypeFilters();
});
it('should filter nothing without registered filters', async () => {
const aggTypes = [{ name: 'count' }, { name: 'sum' }];
const observable = registry.filter$(aggTypes, indexPattern, aggConfig);
const filtered = await observable.pipe(first()).toPromise();
expect(filtered).toEqual(aggTypes);
});
it('should emit a new filtered list when registering a new filter', async () => {
const aggTypes = [{ name: 'count' }, { name: 'sum' }];
const observable = registry.filter$(aggTypes, indexPattern, aggConfig);
const spy = jest.fn();
observable.subscribe(spy);
expect(spy).toHaveBeenCalledTimes(1);
registry.addFilter(() => true);
expect(spy).toHaveBeenCalledTimes(2);
});
it('should pass all aggTypes to the registered filter', async () => {
const aggTypes = [{ name: 'count' }, { name: 'sum' }];
const filter = jest.fn();
registry.addFilter(filter);
await registry
.filter$(aggTypes, indexPattern, aggConfig)
.pipe(first())
.toPromise();
expect(filter).toHaveBeenCalledWith(aggTypes[0], indexPattern, aggConfig);
expect(filter).toHaveBeenCalledWith(aggTypes[1], indexPattern, aggConfig);
});
it('should allow registered filters to filter out aggTypes', async () => {
const aggTypes = [{ name: 'count' }, { name: 'sum' }, { name: 'avg' }];
const observable = registry.filter$(aggTypes, indexPattern, aggConfig);
let filtered = await observable.pipe(first()).toPromise();
expect(filtered).toEqual(aggTypes);
registry.addFilter(() => true);
registry.addFilter(aggType => aggType.name !== 'count');
filtered = await observable.pipe(first()).toPromise();
expect(filtered).toEqual([aggTypes[1], aggTypes[2]]);
registry.addFilter(aggType => aggType.name !== 'avg');
filtered = await observable.pipe(first()).toPromise();
expect(filtered).toEqual([aggTypes[1]]);
});
});

View file

@ -0,0 +1,83 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { BehaviorSubject } from 'rxjs';
import { map } from 'rxjs/operators';
import { AggType } from '..';
import { IndexPattern } from '../../index_patterns';
import { AggConfig } from '../../vis';
type AggTypeFilter = (
aggType: AggType,
indexPattern: IndexPattern,
aggConfig: AggConfig
) => boolean;
/**
* A registry to store {@link AggTypeFilter} which are used to filter down
* available aggregations for a specific visualization and {@link AggConfig}.
*/
class AggTypeFilters {
private filters = new Set<AggTypeFilter>();
private subject = new BehaviorSubject<Set<AggTypeFilter>>(this.filters);
/**
* Register a new {@link AggTypeFilter} with this registry.
* This will emit a new set of filtered aggTypes on every Observer returned
* by the {@link #filter$|filter method}.
*
* @param filter The filter to register.
*/
public addFilter(filter: AggTypeFilter): void {
this.filters.add(filter);
this.subject.next(this.filters);
}
/**
* Returns an Observable that will emit a filtered list of the passed {@link AggType|aggTypes}.
* A new filtered list will always be emitted when the {@link AggTypeFilter}
* registered with this registry will change.
*
* @param aggTypes A list of aggTypes that will be filtered down by this registry.
* @param indexPattern The indexPattern for which this list should be filtered down.
* @param aggConfig The aggConfig for which the returning list will be used.
* @return A filtered list of the passed aggTypes.
*/
public filter$(
aggTypes: AggType[],
indexPattern: IndexPattern,
aggConfig: AggConfig
) {
return this.subject.pipe(
map(filters => {
const allFilters = Array.from(filters);
const allowedAggTypes = aggTypes.filter(aggType => {
const isAggTypeAllowed = allFilters.every(filter =>
filter(aggType, indexPattern, aggConfig)
);
return isAggTypeAllowed;
});
return allowedAggTypes;
})
);
}
}
const aggTypeFilters = new AggTypeFilters();
export { aggTypeFilters, AggTypeFilters };

View file

@ -0,0 +1,20 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export { aggTypeFilters } from './agg_type_filters';

20
src/ui/public/agg_types/index.d.ts vendored Normal file
View file

@ -0,0 +1,20 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export { AggType } from './agg_type';

View file

@ -17,11 +17,8 @@
* under the License.
*/
import { propFilter } from 'ui/filters/_prop_filter';
import { uiModules } from 'ui/modules';
type FilterFunc = <I>(item: I) => boolean;
uiModules
.get('kibana')
.filter('aggFilter', function () {
return propFilter('name');
});
export const propFilter: (
prop: string
) => <T>(list: T[], filters: string[] | string | FilterFunc) => T[];

View file

@ -0,0 +1,20 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export type IndexPattern = any;

20
src/ui/public/index_patterns/index.d.ts vendored Normal file
View file

@ -0,0 +1,20 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export { IndexPattern } from './_index_pattern';

20
src/ui/public/vis/agg_config.d.ts vendored Normal file
View file

@ -0,0 +1,20 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export type AggConfig = any;

View file

@ -77,10 +77,11 @@ describe('Vis-Editor-Agg-Params plugin directive', function () {
});
$parentScope.agg = new AggConfig(vis, state);
$parentScope.vis = vis;
// make the element
$elem = angular.element(
`<vis-editor-agg-params agg="agg" group-name="groupName"></vis-editor-agg-params>`
`<vis-editor-agg-params index-pattern="vis.indexPattern" agg="agg" group-name="groupName"></vis-editor-agg-params>`
);
// compile the html

View file

@ -93,6 +93,7 @@
agg="agg"
group-name="groupName"
ng-show="editorOpen"
index-pattern="vis.indexPattern"
class="vis-editor-agg-editor">
</vis-editor-agg-params>

View file

@ -27,6 +27,7 @@ import { aggTypes } from '../../../agg_types';
import { uiModules } from '../../../modules';
import { documentationLinks } from '../../../documentation_links/documentation_links';
import aggParamsTemplate from './agg_params.html';
import { aggTypeFilters } from '../../../agg_types/filter';
uiModules
.get('app/visualize')
@ -39,8 +40,16 @@ uiModules
link: function ($scope, $el, attr) {
$scope.$bind('agg', attr.agg);
$scope.$bind('groupName', attr.groupName);
$scope.$bind('indexPattern', attr.indexPattern);
const aggTypeSubscription = aggTypeFilters
.filter$(aggTypes.byType[$scope.groupName], $scope.indexPattern, $scope.agg)
.subscribe(aggTypes => $scope.aggTypeOptions = aggTypes);
$scope.$on('$destroy', () => {
aggTypeSubscription.unsubscribe();
});
$scope.aggTypeOptions = aggTypes.byType[$scope.groupName];
$scope.advancedToggled = false;
// We set up this watch prior to adding the controls below, because when the controls are added,

View file

@ -25,7 +25,6 @@
</ui-select-match>
<ui-select-choices
repeat="agg in aggTypeOptions
| aggFilter:agg.schema.aggFilter
| filter: { title: $select.search }
| orderBy:'title'
| sortPrefixFirst:$select.search:'title'"

View file

@ -20,6 +20,7 @@
import './sidebar';
import './vis_options';
import './vis_editor_resizer';
import './vis_type_agg_filter';
import $ from 'jquery';
import _ from 'lodash';

View file

@ -0,0 +1,38 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { AggType } from '../../../agg_types';
import { aggTypeFilters } from '../../../agg_types/filter';
import { IndexPattern } from '../../../index_patterns';
import { AggConfig } from '../../../vis';
import { propFilter } from '../../../filters/_prop_filter';
const filterByName = propFilter('name');
/**
* This filter checks the defined aggFilter in the schemas of that visualization
* and limits available aggregations based on that.
*/
aggTypeFilters.addFilter(
(aggType: AggType, indexPatterns: IndexPattern, aggConfig: AggConfig) => {
const doesSchemaAllowAggType =
filterByName([aggType], aggConfig.schema.aggFilter).length !== 0;
return doesSchemaAllowAggType;
}
);

21
src/ui/public/vis/index.d.ts vendored Normal file
View file

@ -0,0 +1,21 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export { AggConfig } from './agg_config';
export { Vis, VisProvider } from './vis';

22
src/ui/public/vis/vis.d.ts vendored Normal file
View file

@ -0,0 +1,22 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export type Vis = any;
export type VisProvider = (...dependencies: any[]) => Vis;

View file

@ -51,6 +51,7 @@ export {
home,
visTypeEnhancers,
aliases,
visualize
} from './ui_app_extensions';
export {

View file

@ -50,6 +50,8 @@ export const docViews = appExtension;
export const hacks = appExtension;
export const home = appExtension;
export const inspectorViews = appExtension;
// Add a visualize app extension that should be used for visualize specific stuff
export const visualize = appExtension;
// aliases visTypeEnhancers to the visTypes group
export const visTypeEnhancers = wrap(alias('visTypes'), appExtension);