mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
parent
a524c001f5
commit
80a7fc3d71
92 changed files with 1245 additions and 1495 deletions
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfigOptions](./kibana-plugin-plugins-data-public.aggconfigoptions.md) > [enabled](./kibana-plugin-plugins-data-public.aggconfigoptions.enabled.md)
|
||||
|
||||
## AggConfigOptions.enabled property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
enabled?: boolean;
|
||||
```
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfigOptions](./kibana-plugin-plugins-data-public.aggconfigoptions.md) > [id](./kibana-plugin-plugins-data-public.aggconfigoptions.id.md)
|
||||
|
||||
## AggConfigOptions.id property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
id?: string;
|
||||
```
|
|
@ -0,0 +1,22 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfigOptions](./kibana-plugin-plugins-data-public.aggconfigoptions.md)
|
||||
|
||||
## AggConfigOptions interface
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface AggConfigOptions
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [enabled](./kibana-plugin-plugins-data-public.aggconfigoptions.enabled.md) | <code>boolean</code> | |
|
||||
| [id](./kibana-plugin-plugins-data-public.aggconfigoptions.id.md) | <code>string</code> | |
|
||||
| [params](./kibana-plugin-plugins-data-public.aggconfigoptions.params.md) | <code>Record<string, any></code> | |
|
||||
| [schema](./kibana-plugin-plugins-data-public.aggconfigoptions.schema.md) | <code>string</code> | |
|
||||
| [type](./kibana-plugin-plugins-data-public.aggconfigoptions.type.md) | <code>IAggType</code> | |
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfigOptions](./kibana-plugin-plugins-data-public.aggconfigoptions.md) > [params](./kibana-plugin-plugins-data-public.aggconfigoptions.params.md)
|
||||
|
||||
## AggConfigOptions.params property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
params?: Record<string, any>;
|
||||
```
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfigOptions](./kibana-plugin-plugins-data-public.aggconfigoptions.md) > [schema](./kibana-plugin-plugins-data-public.aggconfigoptions.schema.md)
|
||||
|
||||
## AggConfigOptions.schema property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
schema?: string;
|
||||
```
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfigOptions](./kibana-plugin-plugins-data-public.aggconfigoptions.md) > [type](./kibana-plugin-plugins-data-public.aggconfigoptions.type.md)
|
||||
|
||||
## AggConfigOptions.type property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
type: IAggType;
|
||||
```
|
|
@ -48,6 +48,7 @@
|
|||
|
||||
| Interface | Description |
|
||||
| --- | --- |
|
||||
| [AggConfigOptions](./kibana-plugin-plugins-data-public.aggconfigoptions.md) | |
|
||||
| [AggParamOption](./kibana-plugin-plugins-data-public.aggparamoption.md) | |
|
||||
| [DataPublicPluginSetup](./kibana-plugin-plugins-data-public.datapublicpluginsetup.md) | |
|
||||
| [DataPublicPluginStart](./kibana-plugin-plugins-data-public.datapublicpluginstart.md) | |
|
||||
|
|
|
@ -22,7 +22,6 @@ import { i18n } from '@kbn/i18n';
|
|||
import { createInputControlVisController } from './vis_controller';
|
||||
import { getControlsTab } from './components/editor/controls_tab';
|
||||
import { OptionsTab } from './components/editor/options_tab';
|
||||
import { Status } from '../../visualizations/public';
|
||||
import { InputControlVisDependencies } from './plugin';
|
||||
import { defaultFeedbackMessage } from '../../../../plugins/kibana_utils/common';
|
||||
|
||||
|
@ -40,7 +39,6 @@ export function createInputControlVisTypeDefinition(deps: InputControlVisDepende
|
|||
defaultMessage: 'Create interactive controls for easy dashboard manipulation.',
|
||||
}),
|
||||
stage: 'experimental',
|
||||
requiresUpdateStatus: [Status.PARAMS, Status.TIME],
|
||||
feedbackMessage: defaultFeedbackMessage,
|
||||
visualization: InputControlVisController,
|
||||
visConfig: {
|
||||
|
|
|
@ -54,7 +54,7 @@ export const createInputControlVisController = (deps: InputControlVisDependencie
|
|||
.subscribe(this.queryBarUpdateHandler);
|
||||
}
|
||||
|
||||
async render(visData: any, visParams: VisParams, status: any) {
|
||||
async render(visData: any, visParams: VisParams) {
|
||||
this.visParams = visParams;
|
||||
this.controls = [];
|
||||
this.controls = await this.initControls();
|
||||
|
|
|
@ -672,7 +672,7 @@ function discoverController(
|
|||
// no timefield, no vis, nothing to update
|
||||
if (!getTimeField() || !$scope.vis) return;
|
||||
|
||||
const buckets = $scope.vis.getAggConfig().byTypeName('buckets');
|
||||
const buckets = $scope.vis.data.aggs.byTypeName('buckets');
|
||||
|
||||
if (buckets && buckets.length === 1) {
|
||||
$scope.bucketInterval = buckets[0].buckets.getInterval();
|
||||
|
@ -876,11 +876,11 @@ function discoverController(
|
|||
inspectorRequest.stats(getResponseInspectorStats($scope.searchSource, resp)).ok({ json: resp });
|
||||
|
||||
if (getTimeField()) {
|
||||
const tabifiedData = tabifyAggResponse($scope.vis.aggs, resp);
|
||||
const tabifiedData = tabifyAggResponse($scope.vis.data.aggs, resp);
|
||||
$scope.searchSource.rawResponse = resp;
|
||||
$scope.histogramData = discoverResponseHandler(
|
||||
tabifiedData,
|
||||
getDimensions($scope.vis.aggs.aggs, $scope.timeRange)
|
||||
getDimensions($scope.vis.data.aggs.aggs, $scope.timeRange)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1023,41 +1023,27 @@ function discoverController(
|
|||
},
|
||||
];
|
||||
|
||||
if ($scope.vis) {
|
||||
const visState = $scope.vis.getEnabledState();
|
||||
visState.aggs = visStateAggs;
|
||||
|
||||
$scope.vis.setState(visState);
|
||||
return;
|
||||
}
|
||||
|
||||
const visSavedObject = {
|
||||
indexPattern: $scope.indexPattern.id,
|
||||
visState: {
|
||||
type: 'histogram',
|
||||
title: savedSearch.title,
|
||||
params: {
|
||||
addLegend: false,
|
||||
addTimeMarker: true,
|
||||
},
|
||||
aggs: visStateAggs,
|
||||
$scope.vis = visualizations.createVis('histogram', {
|
||||
title: savedSearch.title,
|
||||
params: {
|
||||
addLegend: false,
|
||||
addTimeMarker: true,
|
||||
},
|
||||
};
|
||||
|
||||
$scope.vis = visualizations.createVis(
|
||||
$scope.searchSource.getField('index'),
|
||||
visSavedObject.visState
|
||||
);
|
||||
visSavedObject.vis = $scope.vis;
|
||||
data: {
|
||||
aggs: visStateAggs,
|
||||
indexPattern: $scope.searchSource.getField('index').id,
|
||||
searchSource: $scope.searchSource,
|
||||
},
|
||||
});
|
||||
|
||||
$scope.searchSource.onRequestStart((searchSource, options) => {
|
||||
if (!$scope.vis) return;
|
||||
return $scope.vis.getAggConfig().onSearchRequestStart(searchSource, options);
|
||||
return $scope.vis.data.aggs.onSearchRequestStart(searchSource, options);
|
||||
});
|
||||
|
||||
$scope.searchSource.setField('aggs', function() {
|
||||
if (!$scope.vis) return;
|
||||
return $scope.vis.getAggConfig().toDsl();
|
||||
return $scope.vis.data.aggs.toDsl();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -69,7 +69,7 @@ export function getEditBreadcrumbs($route: any) {
|
|||
return [
|
||||
...getLandingBreadcrumbs(),
|
||||
{
|
||||
text: $route.current.locals.savedVis.title,
|
||||
text: $route.current.locals.resolved.savedVis.title,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
|
@ -69,7 +69,8 @@
|
|||
<visualization-embedded
|
||||
ng-if="!isVisible"
|
||||
class="visualize"
|
||||
saved-obj="savedVis"
|
||||
vis="vis"
|
||||
embeddable-handler="embeddableHandler"
|
||||
ui-state="uiState"
|
||||
time-range="timeRange"
|
||||
filters="filters"
|
||||
|
@ -89,13 +90,15 @@
|
|||
</h1>
|
||||
<visualization-editor
|
||||
ng-if="isVisible"
|
||||
saved-obj="savedVis"
|
||||
vis="vis"
|
||||
saved-search="savedSearch"
|
||||
embeddable-handler="embeddableHandler"
|
||||
event-emitter="eventEmitter"
|
||||
ui-state="uiState"
|
||||
time-range="timeRange"
|
||||
filters="filters"
|
||||
query="query"
|
||||
class="visEditor__content"
|
||||
app-state="appState"
|
||||
/>
|
||||
|
||||
</visualize-app>
|
||||
|
|
|
@ -22,6 +22,7 @@ import _ from 'lodash';
|
|||
import { Subscription } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
@ -84,6 +85,7 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState
|
|||
uiSettings,
|
||||
I18nContext,
|
||||
setActiveUrl,
|
||||
visualizations,
|
||||
} = getServices();
|
||||
|
||||
const {
|
||||
|
@ -98,27 +100,63 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState
|
|||
);
|
||||
|
||||
// Retrieve the resolved SavedVis instance.
|
||||
const savedVis = $route.current.locals.savedVis;
|
||||
const { vis, savedVis, savedSearch, embeddableHandler } = $route.current.locals.resolved;
|
||||
$scope.eventEmitter = new EventEmitter();
|
||||
const _applyVis = () => {
|
||||
$scope.$apply();
|
||||
};
|
||||
// This will trigger a digest cycle. This is needed when vis is updated from a global angular like in visualize_embeddable.js.
|
||||
savedVis.vis.on('apply', _applyVis);
|
||||
$scope.eventEmitter.on('apply', _applyVis);
|
||||
// vis is instance of src/legacy/ui/public/vis/vis.js.
|
||||
// SearchSource is a promise-based stream of search results that can inherit from other search sources.
|
||||
const { vis, searchSource, savedSearch } = savedVis;
|
||||
const searchSource = vis.data.searchSource;
|
||||
|
||||
$scope.vis = vis;
|
||||
$scope.savedSearch = savedSearch;
|
||||
|
||||
const $appStatus = {
|
||||
dirty: !savedVis.id,
|
||||
};
|
||||
|
||||
vis.on('dirtyStateChange', ({ isDirty }) => {
|
||||
vis.dirty = isDirty;
|
||||
$scope.$digest();
|
||||
const defaultQuery = {
|
||||
query: '',
|
||||
language:
|
||||
localStorage.get('kibana.userQueryLanguage') || uiSettings.get('search:queryLanguage'),
|
||||
};
|
||||
|
||||
const visStateToEditorState = () => {
|
||||
const savedVisState = visualizations.convertFromSerializedVis(vis.serialize());
|
||||
return {
|
||||
uiState: vis.uiState.toJSON(),
|
||||
query: vis.data.searchSource.getOwnField('query') || defaultQuery,
|
||||
filters: vis.data.searchSource.getOwnField('filter') || [],
|
||||
vis: { ...savedVisState.visState, title: vis.title },
|
||||
linked: !!savedVis.savedSearchId,
|
||||
};
|
||||
};
|
||||
|
||||
const stateDefaults = visStateToEditorState();
|
||||
|
||||
const { stateContainer, stopStateSync } = useVisualizeAppState({
|
||||
stateDefaults,
|
||||
kbnUrlStateStorage,
|
||||
});
|
||||
|
||||
$scope.eventEmitter.on('dirtyStateChange', ({ isDirty }) => {
|
||||
if (!isDirty) {
|
||||
stateContainer.transitions.updateVisState(visStateToEditorState().vis);
|
||||
}
|
||||
$timeout(() => {
|
||||
$scope.dirty = isDirty;
|
||||
});
|
||||
});
|
||||
|
||||
$scope.eventEmitter.on('updateVis', () => {
|
||||
embeddableHandler.reload();
|
||||
});
|
||||
|
||||
$scope.embeddableHandler = embeddableHandler;
|
||||
|
||||
$scope.topNavMenu = [
|
||||
...(visualizeCapabilities.save
|
||||
? [
|
||||
|
@ -135,10 +173,10 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState
|
|||
),
|
||||
testId: 'visualizeSaveButton',
|
||||
disableButton() {
|
||||
return Boolean(vis.dirty);
|
||||
return Boolean($scope.dirty);
|
||||
},
|
||||
tooltip() {
|
||||
if (vis.dirty) {
|
||||
if ($scope.dirty) {
|
||||
return i18n.translate(
|
||||
'kbn.visualize.topNavMenu.saveVisualizationDisabledButtonTooltip',
|
||||
{
|
||||
|
@ -207,7 +245,7 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState
|
|||
}),
|
||||
testId: 'shareTopNavButton',
|
||||
run: anchorElement => {
|
||||
const hasUnappliedChanges = vis.dirty;
|
||||
const hasUnappliedChanges = $scope.dirty;
|
||||
const hasUnsavedChanges = $appStatus.dirty;
|
||||
share.toggleShareContextMenu({
|
||||
anchorElement,
|
||||
|
@ -233,17 +271,17 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState
|
|||
}),
|
||||
testId: 'openInspectorButton',
|
||||
disableButton() {
|
||||
return !vis.hasInspector || !vis.hasInspector();
|
||||
return !embeddableHandler.hasInspector || !embeddableHandler.hasInspector();
|
||||
},
|
||||
run() {
|
||||
const inspectorSession = vis.openInspector();
|
||||
const inspectorSession = embeddableHandler.openInspector();
|
||||
// Close the inspector if this scope is destroyed (e.g. because the user navigates away).
|
||||
const removeWatch = $scope.$on('$destroy', () => inspectorSession.close());
|
||||
// Remove that watch in case the user closes the inspector session herself.
|
||||
inspectorSession.onClose.finally(removeWatch);
|
||||
},
|
||||
tooltip() {
|
||||
if (!vis.hasInspector || !vis.hasInspector()) {
|
||||
if (!embeddableHandler.hasInspector || !embeddableHandler.hasInspector()) {
|
||||
return i18n.translate('kbn.visualize.topNavMenu.openInspectorDisabledButtonTooltip', {
|
||||
defaultMessage: `This visualization doesn't support any inspectors.`,
|
||||
});
|
||||
|
@ -257,7 +295,7 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState
|
|||
defaultMessage: 'Refresh',
|
||||
}),
|
||||
run: function() {
|
||||
vis.forceReload();
|
||||
embeddableHandler.reload();
|
||||
},
|
||||
testId: 'visualizeRefreshButton',
|
||||
},
|
||||
|
@ -267,28 +305,6 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState
|
|||
chrome.docTitle.change(savedVis.title);
|
||||
}
|
||||
|
||||
const defaultQuery = {
|
||||
query: '',
|
||||
language:
|
||||
localStorage.get('kibana.userQueryLanguage') || uiSettings.get('search:queryLanguage'),
|
||||
};
|
||||
|
||||
// Extract visualization state with filtered aggs. You can see these filtered aggs in the URL.
|
||||
// Consists of things like aggs, params, listeners, title, type, etc.
|
||||
const savedVisState = vis.getState();
|
||||
const stateDefaults = {
|
||||
uiState: savedVis.uiStateJSON ? JSON.parse(savedVis.uiStateJSON) : {},
|
||||
query: searchSource.getOwnField('query') || defaultQuery,
|
||||
filters: searchSource.getOwnField('filter') || [],
|
||||
vis: savedVisState,
|
||||
linked: !!savedVis.savedSearchId,
|
||||
};
|
||||
|
||||
const { stateContainer, stopStateSync } = useVisualizeAppState({
|
||||
stateDefaults,
|
||||
kbnUrlStateStorage,
|
||||
});
|
||||
|
||||
// sync initial app filters from state to filterManager
|
||||
filterManager.setAppFilters(_.cloneDeep(stateContainer.getState().filters));
|
||||
// setup syncing of app filters between appState and filterManager
|
||||
|
@ -315,7 +331,8 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState
|
|||
// appState then they won't be equal.
|
||||
if (!_.isEqual(stateContainer.getState().vis, stateDefaults.vis)) {
|
||||
try {
|
||||
vis.setState(stateContainer.getState().vis);
|
||||
const { aggs, ...visState } = stateContainer.getState().vis;
|
||||
vis.setState({ ...visState, data: { aggs } });
|
||||
} catch (error) {
|
||||
// stop syncing url updtes with the state to prevent extra syncing
|
||||
stopAllSyncing();
|
||||
|
@ -369,8 +386,8 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState
|
|||
};
|
||||
|
||||
function init() {
|
||||
if (vis.indexPattern) {
|
||||
$scope.indexPattern = vis.indexPattern;
|
||||
if (vis.data.indexPattern) {
|
||||
$scope.indexPattern = vis.data.indexPattern;
|
||||
} else {
|
||||
indexPatterns.getDefault().then(defaultIndexPattern => {
|
||||
$scope.indexPattern = defaultIndexPattern;
|
||||
|
@ -379,22 +396,14 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState
|
|||
|
||||
const initialState = stateContainer.getState();
|
||||
|
||||
$scope.appState = {
|
||||
// mock implementation of the legacy appState.save()
|
||||
// this could be even replaced by passing only "updateAppState" callback
|
||||
save() {
|
||||
stateContainer.transitions.updateVisState(vis.getState());
|
||||
},
|
||||
};
|
||||
|
||||
const handleLinkedSearch = linked => {
|
||||
if (linked && !savedVis.savedSearchId && savedSearch) {
|
||||
savedVis.savedSearchId = savedSearch.id;
|
||||
vis.savedSearchId = savedSearch.id;
|
||||
vis.data.savedSearchId = savedSearch.id;
|
||||
searchSource.setParent(savedSearch.searchSource);
|
||||
} else if (!linked && savedVis.savedSearchId) {
|
||||
delete savedVis.savedSearchId;
|
||||
delete vis.savedSearchId;
|
||||
delete vis.data.savedSearchId;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -403,6 +412,7 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState
|
|||
'uiState',
|
||||
stateContainer
|
||||
);
|
||||
vis.uiState = persistedState;
|
||||
$scope.uiState = persistedState;
|
||||
$scope.savedVis = savedVis;
|
||||
$scope.query = initialState.query;
|
||||
|
@ -427,7 +437,7 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState
|
|||
$scope.showQueryBarTimePicker = () => {
|
||||
// tsvb loads without an indexPattern initially (TODO investigate).
|
||||
// hide timefilter only if timeFieldName is explicitly undefined.
|
||||
const hasTimeField = vis.indexPattern ? !!vis.indexPattern.timeFieldName : true;
|
||||
const hasTimeField = vis.data.indexPattern ? !!vis.data.indexPattern.timeFieldName : true;
|
||||
return vis.type.options.showTimePicker && hasTimeField;
|
||||
};
|
||||
|
||||
|
@ -442,10 +452,24 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState
|
|||
updateSavedQueryFromUrl(state.savedQuery);
|
||||
|
||||
// if the browser history was changed manually we need to reflect changes in the editor
|
||||
if (!_.isEqual(vis.getState(), state.vis)) {
|
||||
vis.setState(state.vis);
|
||||
vis.forceReload();
|
||||
vis.emit('updateEditor');
|
||||
if (
|
||||
!_.isEqual(
|
||||
{
|
||||
...visualizations.convertFromSerializedVis(vis.serialize()).visState,
|
||||
title: vis.title,
|
||||
},
|
||||
state.vis
|
||||
)
|
||||
) {
|
||||
const { aggs, ...visState } = state.vis;
|
||||
vis.setState({
|
||||
...visState,
|
||||
data: {
|
||||
aggs,
|
||||
},
|
||||
});
|
||||
embeddableHandler.reload();
|
||||
$scope.eventEmitter.emit('updateEditor');
|
||||
}
|
||||
|
||||
$appStatus.dirty = true;
|
||||
|
@ -498,8 +522,8 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState
|
|||
const { query, linked, filters } = stateContainer.getState();
|
||||
$scope.query = query;
|
||||
handleLinkedSearch(linked);
|
||||
savedVis.searchSource.setField('query', query);
|
||||
savedVis.searchSource.setField('filter', filters);
|
||||
vis.data.searchSource.setField('query', query);
|
||||
vis.data.searchSource.setField('filter', filters);
|
||||
$scope.$broadcast('render');
|
||||
};
|
||||
|
||||
|
@ -533,7 +557,7 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState
|
|||
}
|
||||
savedVis.destroy();
|
||||
subscriptions.unsubscribe();
|
||||
$scope.vis.off('apply', _applyVis);
|
||||
$scope.eventEmitter.off('apply', _applyVis);
|
||||
|
||||
unsubscribePersisted();
|
||||
unsubscribeStateUpdates();
|
||||
|
@ -556,7 +580,7 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState
|
|||
|
||||
// If nothing has changed, trigger the fetch manually, otherwise it will happen as a result of the changes
|
||||
if (!isUpdate) {
|
||||
$scope.vis.forceReload();
|
||||
embeddableHandler.reload();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -605,8 +629,10 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState
|
|||
title: savedVis.title,
|
||||
type: savedVis.type || stateContainer.getState().vis.type,
|
||||
});
|
||||
savedVis.searchSource.setField('query', stateContainer.getState().query);
|
||||
savedVis.searchSource.setField('filter', stateContainer.getState().filters);
|
||||
savedVis.visState = stateContainer.getState().vis;
|
||||
savedVis.uiStateJSON = angular.toJson($scope.uiState.getChanges());
|
||||
savedVis.uiStateJSON = angular.toJson($scope.uiState.toJSON());
|
||||
$appStatus.dirty = false;
|
||||
|
||||
return savedVis.save(saveOptions).then(
|
||||
|
@ -720,7 +746,7 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState
|
|||
);
|
||||
};
|
||||
|
||||
vis.on('unlinkFromSavedSearch', unlinkFromSavedSearch);
|
||||
$scope.eventEmitter.on('unlinkFromSavedSearch', unlinkFromSavedSearch);
|
||||
|
||||
addHelpMenuToAppChrome(chrome, docLinks);
|
||||
|
||||
|
|
|
@ -17,12 +17,12 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export function initVisualizationDirective(app, deps) {
|
||||
export function initVisualizationDirective(app) {
|
||||
app.directive('visualizationEmbedded', function($timeout) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
savedObj: '=',
|
||||
embeddableHandler: '=',
|
||||
uiState: '=?',
|
||||
timeRange: '=',
|
||||
filters: '=',
|
||||
|
@ -31,24 +31,16 @@ export function initVisualizationDirective(app, deps) {
|
|||
},
|
||||
link: function($scope, element) {
|
||||
$scope.renderFunction = async () => {
|
||||
if (!$scope._handler) {
|
||||
$scope._handler = await deps.embeddable
|
||||
.getEmbeddableFactory('visualization')
|
||||
.createFromObject($scope.savedObj, {
|
||||
timeRange: $scope.timeRange,
|
||||
filters: $scope.filters || [],
|
||||
query: $scope.query,
|
||||
appState: $scope.appState,
|
||||
uiState: $scope.uiState,
|
||||
});
|
||||
$scope._handler.render(element[0]);
|
||||
} else {
|
||||
$scope._handler.updateInput({
|
||||
timeRange: $scope.timeRange,
|
||||
filters: $scope.filters || [],
|
||||
query: $scope.query,
|
||||
});
|
||||
if (!$scope.rendered) {
|
||||
$scope.embeddableHandler.render(element[0]);
|
||||
$scope.rendered = true;
|
||||
}
|
||||
|
||||
$scope.embeddableHandler.updateInput({
|
||||
timeRange: $scope.timeRange,
|
||||
filters: $scope.filters || [],
|
||||
query: $scope.query,
|
||||
});
|
||||
};
|
||||
|
||||
$scope.$on('render', event => {
|
||||
|
@ -59,8 +51,8 @@ export function initVisualizationDirective(app, deps) {
|
|||
});
|
||||
|
||||
$scope.$on('$destroy', () => {
|
||||
if ($scope._handler) {
|
||||
$scope._handler.destroy();
|
||||
if ($scope.embeddableHandler) {
|
||||
$scope.embeddableHandler.destroy();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
|
|
@ -22,16 +22,23 @@ export function initVisEditorDirective(app, deps) {
|
|||
return {
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
savedObj: '=',
|
||||
vis: '=',
|
||||
uiState: '=?',
|
||||
timeRange: '=',
|
||||
filters: '=',
|
||||
query: '=',
|
||||
appState: '=',
|
||||
savedSearch: '=',
|
||||
embeddableHandler: '=',
|
||||
eventEmitter: '=',
|
||||
},
|
||||
link: function($scope, element) {
|
||||
const Editor = $scope.savedObj.vis.type.editor || deps.DefaultVisualizationEditor;
|
||||
const editor = new Editor(element[0], $scope.savedObj);
|
||||
const Editor = $scope.vis.type.editor || deps.DefaultVisualizationEditor;
|
||||
const editor = new Editor(
|
||||
element[0],
|
||||
$scope.vis,
|
||||
$scope.eventEmitter,
|
||||
$scope.embeddableHandler
|
||||
);
|
||||
|
||||
$scope.renderFunction = () => {
|
||||
editor.render({
|
||||
|
@ -42,8 +49,8 @@ export function initVisEditorDirective(app, deps) {
|
|||
timeRange: $scope.timeRange,
|
||||
filters: $scope.filters,
|
||||
query: $scope.query,
|
||||
appState: $scope.appState,
|
||||
linked: !!$scope.savedObj.savedSearchId,
|
||||
linked: !!$scope.vis.data.savedSearchId,
|
||||
savedSearch: $scope.savedSearch,
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -40,6 +40,50 @@ import {
|
|||
getCreateBreadcrumbs,
|
||||
getEditBreadcrumbs,
|
||||
} from './breadcrumbs';
|
||||
import { createSavedSearchesLoader } from '../../../../../../plugins/discover/public';
|
||||
|
||||
const getResolvedResults = deps => {
|
||||
const { core, data, visualizations } = deps;
|
||||
|
||||
const results = {};
|
||||
|
||||
return savedVis => {
|
||||
results.savedVis = savedVis;
|
||||
return visualizations
|
||||
.convertToSerializedVis(savedVis)
|
||||
.then(serializedVis => visualizations.createVis(serializedVis.type, serializedVis))
|
||||
.then(vis => {
|
||||
if (vis.type.setup) {
|
||||
return vis.type.setup(vis).catch(() => vis);
|
||||
}
|
||||
return vis;
|
||||
})
|
||||
.then(vis => {
|
||||
results.vis = vis;
|
||||
return deps.embeddable.getEmbeddableFactory('visualization').createFromObject(results.vis, {
|
||||
timeRange: data.query.timefilter.timefilter.getTime(),
|
||||
filters: data.query.filterManager.getFilters(),
|
||||
});
|
||||
})
|
||||
.then(embeddableHandler => {
|
||||
results.embeddableHandler = embeddableHandler;
|
||||
if (results.vis.data.savedSearchId) {
|
||||
return createSavedSearchesLoader({
|
||||
savedObjectsClient: core.savedObjects.client,
|
||||
indexPatterns: data.indexPatterns,
|
||||
chrome: core.chrome,
|
||||
overlays: core.overlays,
|
||||
}).get(results.vis.data.savedSearchId);
|
||||
}
|
||||
})
|
||||
.then(savedSearch => {
|
||||
if (savedSearch) {
|
||||
results.savedSearch = savedSearch;
|
||||
}
|
||||
return results;
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
export function initVisualizeApp(app, deps) {
|
||||
initVisualizeAppDirective(app, deps);
|
||||
|
@ -101,7 +145,7 @@ export function initVisualizeApp(app, deps) {
|
|||
template: editorTemplate,
|
||||
k7Breadcrumbs: getCreateBreadcrumbs,
|
||||
resolve: {
|
||||
savedVis: function($route, history) {
|
||||
resolved: function($route, history) {
|
||||
const { core, data, savedVisualizations, visualizations, toastNotifications } = deps;
|
||||
const visTypes = visualizations.all();
|
||||
const visType = find(visTypes, { name: $route.current.params.type });
|
||||
|
@ -121,12 +165,7 @@ export function initVisualizeApp(app, deps) {
|
|||
|
||||
return ensureDefaultIndexPattern(core, data, history)
|
||||
.then(() => savedVisualizations.get($route.current.params))
|
||||
.then(savedVis => {
|
||||
if (savedVis.vis.type.setup) {
|
||||
return savedVis.vis.type.setup(savedVis).catch(() => savedVis);
|
||||
}
|
||||
return savedVis;
|
||||
})
|
||||
.then(getResolvedResults(deps))
|
||||
.catch(
|
||||
redirectWhenMissing({
|
||||
history,
|
||||
|
@ -142,20 +181,16 @@ export function initVisualizeApp(app, deps) {
|
|||
template: editorTemplate,
|
||||
k7Breadcrumbs: getEditBreadcrumbs,
|
||||
resolve: {
|
||||
savedVis: function($route, history) {
|
||||
resolved: function($route, history) {
|
||||
const { chrome, core, data, savedVisualizations, toastNotifications } = deps;
|
||||
|
||||
return ensureDefaultIndexPattern(core, data, history)
|
||||
.then(() => savedVisualizations.get($route.current.params.id))
|
||||
.then(savedVis => {
|
||||
chrome.recentlyAccessed.add(savedVis.getFullPath(), savedVis.title, savedVis.id);
|
||||
return savedVis;
|
||||
})
|
||||
.then(savedVis => {
|
||||
if (savedVis.vis.type.setup) {
|
||||
return savedVis.vis.type.setup(savedVis).catch(() => savedVis);
|
||||
}
|
||||
return savedVis;
|
||||
})
|
||||
.then(getResolvedResults(deps))
|
||||
.catch(
|
||||
redirectWhenMissing({
|
||||
history,
|
||||
|
|
|
@ -29,8 +29,10 @@ import { PersistedState } from 'src/plugins/visualizations/public';
|
|||
import { LegacyCoreStart } from 'kibana/public';
|
||||
import { Vis } from 'src/legacy/core_plugins/visualizations/public';
|
||||
import { VisSavedObject } from '../legacy_imports';
|
||||
import { SavedVisState } from '../../../../visualizations/public/np_ready/public/types';
|
||||
import { SavedSearch } from '../../../../../../plugins/discover/public';
|
||||
|
||||
export type PureVisState = ReturnType<Vis['getCurrentState']>;
|
||||
export type PureVisState = SavedVisState;
|
||||
|
||||
export interface VisualizeAppState {
|
||||
filters: Filter[];
|
||||
|
@ -58,14 +60,13 @@ export interface VisualizeAppStateTransitions {
|
|||
}
|
||||
|
||||
export interface EditorRenderProps {
|
||||
appState: { save(): void };
|
||||
core: LegacyCoreStart;
|
||||
data: DataPublicPluginStart;
|
||||
embeddable: EmbeddableStart;
|
||||
filters: Filter[];
|
||||
uiState: PersistedState;
|
||||
timeRange: TimeRange;
|
||||
query?: Query;
|
||||
savedSearch?: SavedSearch;
|
||||
uiState: PersistedState;
|
||||
/**
|
||||
* Flag to determine if visualiztion is linked to the saved search
|
||||
*/
|
||||
|
|
|
@ -21,7 +21,6 @@ import expect from '@kbn/expect';
|
|||
import ngMock from 'ng_mock';
|
||||
import _ from 'lodash';
|
||||
import ChoroplethLayer from '../choropleth_layer';
|
||||
import LogstashIndexPatternStubProvider from 'fixtures/stubbed_logstash_index_pattern';
|
||||
import { ImageComparator } from 'test_utils/image_comparator';
|
||||
import worldJson from './world.json';
|
||||
import EMS_CATALOGUE from '../../../../ui/public/vis/__tests__/map/ems_mocks/sample_manifest.json';
|
||||
|
@ -38,13 +37,11 @@ import afterdatachangePng from './afterdatachange.png';
|
|||
import afterdatachangeandresizePng from './afterdatachangeandresize.png';
|
||||
import aftercolorchangePng from './aftercolorchange.png';
|
||||
import changestartupPng from './changestartup.png';
|
||||
import {
|
||||
setup as visualizationsSetup,
|
||||
start as visualizationsStart,
|
||||
} from '../../../visualizations/public/np_ready/public/legacy';
|
||||
import { setup as visualizationsSetup } from '../../../visualizations/public/np_ready/public/legacy';
|
||||
|
||||
import { createRegionMapVisualization } from '../region_map_visualization';
|
||||
import { createRegionMapTypeDefinition } from '../region_map_type';
|
||||
import { ExprVis } from '../../../visualizations/public/np_ready/public/expressions/vis';
|
||||
|
||||
const THRESHOLD = 0.45;
|
||||
const PIXEL_DIFF = 96;
|
||||
|
@ -52,7 +49,6 @@ const PIXEL_DIFF = 96;
|
|||
describe('RegionMapsVisualizationTests', function() {
|
||||
let domNode;
|
||||
let RegionMapsVisualization;
|
||||
let indexPattern;
|
||||
let vis;
|
||||
let dependencies;
|
||||
|
||||
|
@ -115,7 +111,6 @@ describe('RegionMapsVisualizationTests', function() {
|
|||
}
|
||||
|
||||
RegionMapsVisualization = createRegionMapVisualization(dependencies);
|
||||
indexPattern = Private(LogstashIndexPatternStubProvider);
|
||||
|
||||
ChoroplethLayer.prototype._makeJsonAjaxCall = async function() {
|
||||
//simulate network call
|
||||
|
@ -158,7 +153,7 @@ describe('RegionMapsVisualizationTests', function() {
|
|||
|
||||
imageComparator = new ImageComparator();
|
||||
|
||||
vis = visualizationsStart.createVis(indexPattern, {
|
||||
vis = new ExprVis({
|
||||
type: 'region_map',
|
||||
});
|
||||
|
||||
|
|
|
@ -20,7 +20,6 @@ import React from 'react';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { mapToLayerWithId } from './util';
|
||||
import { createRegionMapVisualization } from './region_map_visualization';
|
||||
import { Status } from '../../visualizations/public';
|
||||
import { RegionMapOptions } from './components/region_map_options';
|
||||
import { truncatedColorSchemas } from '../../../../plugins/charts/public';
|
||||
import { Schemas } from '../../vis_default_editor/public';
|
||||
|
@ -55,7 +54,6 @@ provided base maps, or add your own. Darker colors represent higher values.',
|
|||
showAllShapes: true, //still under consideration
|
||||
},
|
||||
},
|
||||
requiresUpdateStatus: [Status.AGGS, Status.PARAMS, Status.RESIZE, Status.DATA, Status.UI_STATE],
|
||||
visualization,
|
||||
editorConfig: {
|
||||
optionsTemplate: props => <RegionMapOptions {...props} serviceSettings={serviceSettings} />,
|
||||
|
@ -100,9 +98,7 @@ provided base maps, or add your own. Darker colors represent higher values.',
|
|||
},
|
||||
]),
|
||||
},
|
||||
setup: async savedVis => {
|
||||
const vis = savedVis.vis;
|
||||
|
||||
setup: async vis => {
|
||||
const tmsLayers = await serviceSettings.getTMSServices();
|
||||
vis.type.editorConfig.collections.tmsLayers = tmsLayers;
|
||||
if (!vis.params.wms.selectedTmsLayer && tmsLayers.length) {
|
||||
|
@ -146,7 +142,7 @@ provided base maps, or add your own. Darker colors represent higher values.',
|
|||
vis.params.selectedJoinField = selectedJoinField;
|
||||
}
|
||||
|
||||
return savedVis;
|
||||
return vis;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -39,8 +39,8 @@ export function createRegionMapVisualization({ serviceSettings, $injector, uiSet
|
|||
this._choroplethLayer = null;
|
||||
}
|
||||
|
||||
async render(esResponse, visParams, status) {
|
||||
await super.render(esResponse, visParams, status);
|
||||
async render(esResponse, visParams) {
|
||||
await super.render(esResponse, visParams);
|
||||
if (this._choroplethLayer) {
|
||||
await this._choroplethLayer.whenDataLoaded();
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
|
||||
import expect from '@kbn/expect';
|
||||
import ngMock from 'ng_mock';
|
||||
import LogstashIndexPatternStubProvider from 'fixtures/stubbed_logstash_index_pattern';
|
||||
import { ImageComparator } from 'test_utils/image_comparator';
|
||||
import dummyESResponse from './dummy_es_response.json';
|
||||
import initial from './initial.png';
|
||||
|
@ -32,13 +31,11 @@ import EMS_TILES from '../../../../ui/public/vis/__tests__/map/ems_mocks/sample_
|
|||
import EMS_STYLE_ROAD_MAP_BRIGHT from '../../../../ui/public/vis/__tests__/map/ems_mocks/sample_style_bright';
|
||||
import EMS_STYLE_ROAD_MAP_DESATURATED from '../../../../ui/public/vis/__tests__/map/ems_mocks/sample_style_desaturated';
|
||||
import EMS_STYLE_DARK_MAP from '../../../../ui/public/vis/__tests__/map/ems_mocks/sample_style_dark';
|
||||
import {
|
||||
setup as visualizationsSetup,
|
||||
start as visualizationsStart,
|
||||
} from '../../../visualizations/public/np_ready/public/legacy';
|
||||
import { setup as visualizationsSetup } from '../../../visualizations/public/np_ready/public/legacy';
|
||||
|
||||
import { createTileMapVisualization } from '../tile_map_visualization';
|
||||
import { createTileMapTypeDefinition } from '../tile_map_type';
|
||||
import { ExprVis } from '../../../visualizations/public/np_ready/public/expressions/vis';
|
||||
|
||||
function mockRawData() {
|
||||
const stack = [dummyESResponse];
|
||||
|
@ -67,7 +64,6 @@ let visRegComplete = false;
|
|||
describe('CoordinateMapsVisualizationTest', function() {
|
||||
let domNode;
|
||||
let CoordinateMapsVisualization;
|
||||
let indexPattern;
|
||||
let vis;
|
||||
let dependencies;
|
||||
|
||||
|
@ -92,7 +88,6 @@ describe('CoordinateMapsVisualizationTest', function() {
|
|||
}
|
||||
|
||||
CoordinateMapsVisualization = createTileMapVisualization(dependencies);
|
||||
indexPattern = Private(LogstashIndexPatternStubProvider);
|
||||
|
||||
getManifestStub = serviceSettings.__debugStubManifestCalls(async url => {
|
||||
//simulate network calls
|
||||
|
@ -124,7 +119,7 @@ describe('CoordinateMapsVisualizationTest', function() {
|
|||
setupDOM('512px', '512px');
|
||||
|
||||
imageComparator = new ImageComparator();
|
||||
vis = visualizationsStart.createVis(indexPattern, {
|
||||
vis = new ExprVis({
|
||||
type: 'tile_map',
|
||||
});
|
||||
vis.params = {
|
||||
|
|
|
@ -63,28 +63,21 @@ export function BaseMapsVisualizationProvider(serviceSettings) {
|
|||
* @param status
|
||||
* @return {Promise}
|
||||
*/
|
||||
async render(esResponse, visParams, status) {
|
||||
async render(esResponse, visParams) {
|
||||
if (!this._kibanaMap) {
|
||||
//the visualization has been destroyed;
|
||||
return;
|
||||
}
|
||||
|
||||
await this._mapIsLoaded;
|
||||
|
||||
if (status.resize) {
|
||||
this._kibanaMap.resize();
|
||||
}
|
||||
if (status.params || status.aggs) {
|
||||
this._params = visParams;
|
||||
await this._updateParams();
|
||||
}
|
||||
this._kibanaMap.resize();
|
||||
this._params = visParams;
|
||||
await this._updateParams();
|
||||
|
||||
if (this._hasESResponseChanged(esResponse)) {
|
||||
await this._updateData(esResponse);
|
||||
}
|
||||
if (status.uiState) {
|
||||
this._kibanaMap.useUiStateFromVisualization(this.vis);
|
||||
}
|
||||
this._kibanaMap.useUiStateFromVisualization(this.vis);
|
||||
|
||||
await this._whenBaseLayerIsLoaded();
|
||||
}
|
||||
|
|
|
@ -23,7 +23,6 @@ import { i18n } from '@kbn/i18n';
|
|||
import { convertToGeoJson } from 'ui/vis/map/convert_to_geojson';
|
||||
|
||||
import { Schemas } from '../../vis_default_editor/public';
|
||||
import { Status } from '../../visualizations/public';
|
||||
import { createTileMapVisualization } from './tile_map_visualization';
|
||||
import { TileMapOptions } from './components/tile_map_options';
|
||||
import { MapTypes } from './map_types';
|
||||
|
@ -57,7 +56,6 @@ export function createTileMapTypeDefinition(dependencies) {
|
|||
wms: uiSettings.get('visualization:tileMap:WMSdefaults'),
|
||||
},
|
||||
},
|
||||
requiresUpdateStatus: [Status.AGGS, Status.PARAMS, Status.RESIZE, Status.UI_STATE],
|
||||
requiresPartialRows: true,
|
||||
visualization: CoordinateMapsVisualization,
|
||||
responseHandler: convertToGeoJson,
|
||||
|
@ -143,21 +141,20 @@ export function createTileMapTypeDefinition(dependencies) {
|
|||
},
|
||||
]),
|
||||
},
|
||||
setup: async savedVis => {
|
||||
const vis = savedVis.vis;
|
||||
setup: async vis => {
|
||||
let tmsLayers;
|
||||
|
||||
try {
|
||||
tmsLayers = await serviceSettings.getTMSServices();
|
||||
} catch (e) {
|
||||
return savedVis;
|
||||
return vis;
|
||||
}
|
||||
|
||||
vis.type.editorConfig.collections.tmsLayers = tmsLayers;
|
||||
if (!vis.params.wms.selectedTmsLayer && tmsLayers.length) {
|
||||
vis.params.wms.selectedTmsLayer = tmsLayers[0];
|
||||
}
|
||||
return savedVis;
|
||||
return vis;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -22,12 +22,12 @@ import { mount, shallow } from 'enzyme';
|
|||
import { act } from 'react-dom/test-utils';
|
||||
|
||||
import { IndexPattern, IAggType, AggGroupNames } from 'src/plugins/data/public';
|
||||
import { VisState } from 'src/legacy/core_plugins/visualizations/public';
|
||||
|
||||
import { DefaultEditorAgg, DefaultEditorAggProps } from './agg';
|
||||
import { DefaultEditorAggParams } from './agg_params';
|
||||
import { AGGS_ACTION_KEYS } from './agg_group_state';
|
||||
import { Schema } from '../schemas';
|
||||
import { EditorVisState } from './sidebar/state/reducers';
|
||||
|
||||
jest.mock('./agg_params', () => ({
|
||||
DefaultEditorAggParams: () => null,
|
||||
|
@ -67,7 +67,7 @@ describe('DefaultEditorAgg component', () => {
|
|||
isLastBucket: false,
|
||||
isRemovable: false,
|
||||
metricAggs: [],
|
||||
state: { params: {} } as VisState,
|
||||
state: { params: {} } as EditorVisState,
|
||||
setAggParamValue,
|
||||
setStateParamValue,
|
||||
onAggTypeChange: () => {},
|
||||
|
|
|
@ -17,9 +17,10 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { VisState, VisParams } from 'src/legacy/core_plugins/visualizations/public';
|
||||
import { VisParams } from 'src/legacy/core_plugins/visualizations/public';
|
||||
import { IAggType, IAggConfig, IAggGroupNames } from 'src/plugins/data/public';
|
||||
import { Schema } from '../schemas';
|
||||
import { EditorVisState } from './sidebar/state/reducers';
|
||||
|
||||
type AggId = IAggConfig['id'];
|
||||
type AggParams = IAggConfig['params'];
|
||||
|
@ -31,7 +32,7 @@ export interface DefaultEditorCommonProps {
|
|||
formIsTouched: boolean;
|
||||
groupName: IAggGroupNames;
|
||||
metricAggs: IAggConfig[];
|
||||
state: VisState;
|
||||
state: EditorVisState;
|
||||
setAggParamValue: <T extends keyof AggParams>(
|
||||
aggId: AggId,
|
||||
paramName: T,
|
||||
|
|
|
@ -20,12 +20,12 @@
|
|||
import React from 'react';
|
||||
import { mount, shallow } from 'enzyme';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { VisState } from 'src/legacy/core_plugins/visualizations/public';
|
||||
import { IAggConfigs, IAggConfig } from 'src/plugins/data/public';
|
||||
import { DefaultEditorAggGroup, DefaultEditorAggGroupProps } from './agg_group';
|
||||
import { DefaultEditorAgg } from './agg';
|
||||
import { DefaultEditorAggAdd } from './agg_add';
|
||||
import { Schema } from '../schemas';
|
||||
import { EditorVisState } from './sidebar/state/reducers';
|
||||
|
||||
jest.mock('@elastic/eui', () => ({
|
||||
EuiTitle: 'eui-title',
|
||||
|
@ -93,8 +93,8 @@ describe('DefaultEditorAgg component', () => {
|
|||
metricAggs: [],
|
||||
groupName: 'metrics',
|
||||
state: {
|
||||
aggs,
|
||||
} as VisState,
|
||||
data: { aggs },
|
||||
} as EditorVisState,
|
||||
schemas: [
|
||||
{
|
||||
name: 'metrics',
|
||||
|
@ -147,8 +147,8 @@ describe('DefaultEditorAgg component', () => {
|
|||
});
|
||||
|
||||
expect(reorderAggs).toHaveBeenCalledWith(
|
||||
defaultProps.state.aggs.aggs[0],
|
||||
defaultProps.state.aggs.aggs[1]
|
||||
defaultProps.state.data.aggs!.aggs[0],
|
||||
defaultProps.state.data.aggs!.aggs[1]
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -73,9 +73,10 @@ function DefaultEditorAggGroup({
|
|||
const schemaNames = getSchemasByGroup(schemas, groupName).map(s => s.name);
|
||||
const group: IAggConfig[] = useMemo(
|
||||
() =>
|
||||
state.aggs.aggs.filter((agg: IAggConfig) => agg.schema && schemaNames.includes(agg.schema)) ||
|
||||
[],
|
||||
[state.aggs.aggs, schemaNames]
|
||||
state.data.aggs!.aggs.filter(
|
||||
(agg: IAggConfig) => agg.schema && schemaNames.includes(agg.schema)
|
||||
) || [],
|
||||
[state.data.aggs, schemaNames]
|
||||
);
|
||||
|
||||
const stats = {
|
||||
|
|
|
@ -18,10 +18,10 @@
|
|||
*/
|
||||
|
||||
import { IAggConfig, AggParam, IndexPatternField } from 'src/plugins/data/public';
|
||||
import { VisState } from 'src/legacy/core_plugins/visualizations/public';
|
||||
import { ComboBoxGroupedOptions } from '../utils';
|
||||
import { EditorConfig } from './utils';
|
||||
import { Schema } from '../schemas';
|
||||
import { EditorVisState } from './sidebar/state/reducers';
|
||||
|
||||
// NOTE: we cannot export the interface with export { InterfaceName }
|
||||
// as there is currently a bug on babel typescript transform plugin for it
|
||||
|
@ -35,7 +35,7 @@ export interface AggParamCommonProps<T, P = AggParam> {
|
|||
formIsTouched: boolean;
|
||||
indexedFields?: ComboBoxGroupedOptions<IndexPatternField>;
|
||||
showValidation: boolean;
|
||||
state: VisState;
|
||||
state: EditorVisState;
|
||||
value?: T;
|
||||
metricAggs: IAggConfig[];
|
||||
schemas: Schema[];
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
|
||||
import { VisState } from 'src/legacy/core_plugins/visualizations/public';
|
||||
import { IndexPattern, IAggConfig, AggGroupNames } from 'src/plugins/data/public';
|
||||
import {
|
||||
DefaultEditorAggParams as PureDefaultEditorAggParams,
|
||||
|
@ -28,6 +27,7 @@ import {
|
|||
} from './agg_params';
|
||||
import { KibanaContextProvider } from '../../../../../plugins/kibana_react/public';
|
||||
import { dataPluginMock } from '../../../../../plugins/data/public/mocks';
|
||||
import { EditorVisState } from './sidebar/state/reducers';
|
||||
|
||||
const mockEditorConfig = {
|
||||
useNormalizedEsInterval: { hidden: false, fixedValue: false },
|
||||
|
@ -108,7 +108,7 @@ describe('DefaultEditorAggParams component', () => {
|
|||
formIsTouched: false,
|
||||
indexPattern: {} as IndexPattern,
|
||||
metricAggs: [],
|
||||
state: {} as VisState,
|
||||
state: {} as EditorVisState,
|
||||
setAggParamValue,
|
||||
onAggTypeChange,
|
||||
setTouched,
|
||||
|
|
|
@ -25,7 +25,6 @@ import {
|
|||
IndexPattern,
|
||||
IndexPatternField,
|
||||
} from 'src/plugins/data/public';
|
||||
import { VisState } from 'src/legacy/core_plugins/visualizations/public';
|
||||
import {
|
||||
getAggParamsToRender,
|
||||
getAggTypeOptions,
|
||||
|
@ -34,6 +33,7 @@ import {
|
|||
import { FieldParamEditor, OrderByParamEditor } from './controls';
|
||||
import { EditorConfig } from './utils';
|
||||
import { Schema } from '../schemas';
|
||||
import { EditorVisState } from './sidebar/state/reducers';
|
||||
|
||||
jest.mock('../utils', () => ({
|
||||
groupAndSortBy: jest.fn(() => ['indexedFields']),
|
||||
|
@ -58,7 +58,7 @@ describe('DefaultEditorAggParams helpers', () => {
|
|||
hideCustomLabel: true,
|
||||
} as Schema,
|
||||
];
|
||||
const state = {} as VisState;
|
||||
const state = {} as EditorVisState;
|
||||
const metricAggs: IAggConfig[] = [];
|
||||
const emptyParams = {
|
||||
basic: [],
|
||||
|
|
|
@ -28,7 +28,6 @@ import {
|
|||
IndexPattern,
|
||||
IndexPatternField,
|
||||
} from 'src/plugins/data/public';
|
||||
import { VisState } from 'src/legacy/core_plugins/visualizations/public';
|
||||
import { groupAndSortBy, ComboBoxGroupedOptions } from '../utils';
|
||||
import { AggTypeState, AggParamsState } from './agg_params_state';
|
||||
import { AggParamEditorProps } from './agg_param_props';
|
||||
|
@ -36,12 +35,13 @@ import { aggParamsMap } from './agg_params_map';
|
|||
import { EditorConfig } from './utils';
|
||||
import { Schema, getSchemaByName } from '../schemas';
|
||||
import { search } from '../../../../../plugins/data/public';
|
||||
import { EditorVisState } from './sidebar/state/reducers';
|
||||
|
||||
interface ParamInstanceBase {
|
||||
agg: IAggConfig;
|
||||
editorConfig: EditorConfig;
|
||||
metricAggs: IAggConfig[];
|
||||
state: VisState;
|
||||
state: EditorVisState;
|
||||
schemas: Schema[];
|
||||
hideCustomLabel?: boolean;
|
||||
}
|
||||
|
|
|
@ -23,9 +23,9 @@ import { mount, shallow, ReactWrapper } from 'enzyme';
|
|||
import { EuiComboBoxProps, EuiComboBox } from '@elastic/eui';
|
||||
|
||||
import { IAggConfig, IndexPatternField } from 'src/plugins/data/public';
|
||||
import { VisState } from 'src/legacy/core_plugins/visualizations/public';
|
||||
import { ComboBoxGroupedOptions } from '../../utils';
|
||||
import { FieldParamEditor, FieldParamEditorProps } from './field';
|
||||
import { EditorVisState } from '../sidebar/state/reducers';
|
||||
|
||||
function callComboBoxOnChange(comp: ReactWrapper, value: any = []) {
|
||||
const comboBoxProps = comp.find(EuiComboBox).props() as EuiComboBoxProps<any>;
|
||||
|
@ -78,7 +78,7 @@ describe('FieldParamEditor component', () => {
|
|||
setValue,
|
||||
setValidity,
|
||||
setTouched,
|
||||
state: {} as VisState,
|
||||
state: {} as EditorVisState,
|
||||
metricAggs: [] as IAggConfig[],
|
||||
schemas: [],
|
||||
};
|
||||
|
|
|
@ -20,9 +20,9 @@
|
|||
import React from 'react';
|
||||
import { AggParamEditorProps } from '../agg_param_props';
|
||||
import { IAggConfig } from 'src/plugins/data/public';
|
||||
import { VisState } from 'src/legacy/core_plugins/visualizations/public';
|
||||
import { mount } from 'enzyme';
|
||||
import { PercentilesEditor } from './percentiles';
|
||||
import { EditorVisState } from '../sidebar/state/reducers';
|
||||
|
||||
describe('PercentilesEditor component', () => {
|
||||
let setValue: jest.Mock;
|
||||
|
@ -45,7 +45,7 @@ describe('PercentilesEditor component', () => {
|
|||
setValue,
|
||||
setValidity,
|
||||
setTouched,
|
||||
state: {} as VisState,
|
||||
state: {} as EditorVisState,
|
||||
metricAggs: [] as IAggConfig[],
|
||||
schemas: [],
|
||||
};
|
||||
|
|
|
@ -17,9 +17,9 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { VisState } from 'src/legacy/core_plugins/visualizations/public';
|
||||
import { IAggConfig, AggParam } from 'src/plugins/data/public';
|
||||
import { EditorConfig } from '../utils';
|
||||
import { EditorVisState } from '../sidebar/state/reducers';
|
||||
|
||||
export const aggParamCommonPropsMock = {
|
||||
agg: {} as IAggConfig,
|
||||
|
@ -27,7 +27,7 @@ export const aggParamCommonPropsMock = {
|
|||
editorConfig: {} as EditorConfig,
|
||||
formIsTouched: false,
|
||||
metricAggs: [] as IAggConfig[],
|
||||
state: {} as VisState,
|
||||
state: {} as EditorVisState,
|
||||
showValidation: false,
|
||||
schemas: [],
|
||||
};
|
||||
|
|
|
@ -21,7 +21,6 @@ import React, { useMemo, useCallback } from 'react';
|
|||
import { findLast } from 'lodash';
|
||||
import { EuiSpacer } from '@elastic/eui';
|
||||
|
||||
import { VisState } from 'src/legacy/core_plugins/visualizations/public';
|
||||
import {
|
||||
AggGroupNames,
|
||||
IAggConfig,
|
||||
|
@ -40,6 +39,7 @@ import {
|
|||
} from './state';
|
||||
import { AddSchema, ReorderAggs, DefaultEditorAggCommonProps } from '../agg_common_props';
|
||||
import { ISchemas } from '../../schemas';
|
||||
import { EditorVisState } from './state/reducers';
|
||||
|
||||
export interface DefaultEditorDataTabProps {
|
||||
dispatch: React.Dispatch<EditorAction>;
|
||||
|
@ -47,7 +47,7 @@ export interface DefaultEditorDataTabProps {
|
|||
isTabSelected: boolean;
|
||||
metricAggs: IAggConfig[];
|
||||
schemas: ISchemas;
|
||||
state: VisState;
|
||||
state: EditorVisState;
|
||||
setTouched(isTouched: boolean): void;
|
||||
setValidity(modelName: string, value: boolean): void;
|
||||
setStateValue: DefaultEditorAggCommonProps['setStateParamValue'];
|
||||
|
|
|
@ -21,6 +21,7 @@ import React, { useMemo, useState, useCallback, KeyboardEventHandler, useEffect
|
|||
import { get, isEqual } from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { keyCodes, EuiButtonIcon, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
import { Vis } from 'src/legacy/core_plugins/visualizations/public';
|
||||
import { DefaultEditorNavBar, OptionTab } from './navbar';
|
||||
|
@ -40,6 +41,7 @@ interface DefaultEditorSideBarProps {
|
|||
uiState: PersistedState;
|
||||
vis: Vis;
|
||||
isLinkedSearch: boolean;
|
||||
eventEmitter: EventEmitter;
|
||||
savedSearch?: SavedSearch;
|
||||
}
|
||||
|
||||
|
@ -50,14 +52,17 @@ function DefaultEditorSideBar({
|
|||
uiState,
|
||||
vis,
|
||||
isLinkedSearch,
|
||||
eventEmitter,
|
||||
savedSearch,
|
||||
}: DefaultEditorSideBarProps) {
|
||||
const [selectedTab, setSelectedTab] = useState(optionTabs[0].name);
|
||||
const [isDirty, setDirty] = useState(false);
|
||||
const [state, dispatch] = useEditorReducer(vis);
|
||||
const [state, dispatch] = useEditorReducer(vis, eventEmitter);
|
||||
const { formState, setTouched, setValidity, resetValidity } = useEditorFormState();
|
||||
|
||||
const responseAggs = useMemo(() => state.aggs.getResponseAggs(), [state.aggs]);
|
||||
const responseAggs = useMemo(() => (state.data.aggs ? state.data.aggs.getResponseAggs() : []), [
|
||||
state.data.aggs,
|
||||
]);
|
||||
const metricSchemas = getSchemasByGroup(vis.type.schemas.all || [], AggGroupNames.Metrics).map(
|
||||
s => s.name
|
||||
);
|
||||
|
@ -90,17 +95,20 @@ function DefaultEditorSideBar({
|
|||
const applyChanges = useCallback(() => {
|
||||
if (formState.invalid || !isDirty) {
|
||||
setTouched(true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
vis.setCurrentState(state);
|
||||
vis.updateState();
|
||||
vis.emit('dirtyStateChange', {
|
||||
vis.setState({
|
||||
...vis.serialize(),
|
||||
params: state.params,
|
||||
data: { aggs: state.data.aggs ? (state.data.aggs.aggs.map(agg => agg.toJSON()) as any) : [] },
|
||||
});
|
||||
eventEmitter.emit('updateVis');
|
||||
eventEmitter.emit('dirtyStateChange', {
|
||||
isDirty: false,
|
||||
});
|
||||
setTouched(false);
|
||||
}, [vis, state, formState.invalid, setTouched, isDirty]);
|
||||
}, [vis, state, formState.invalid, setTouched, isDirty, eventEmitter]);
|
||||
|
||||
const onSubmit: KeyboardEventHandler<HTMLFormElement> = useCallback(
|
||||
event => {
|
||||
|
@ -122,18 +130,22 @@ function DefaultEditorSideBar({
|
|||
resetValidity();
|
||||
}
|
||||
};
|
||||
vis.on('dirtyStateChange', changeHandler);
|
||||
eventEmitter.on('dirtyStateChange', changeHandler);
|
||||
|
||||
return () => vis.off('dirtyStateChange', changeHandler);
|
||||
}, [resetValidity, vis]);
|
||||
return () => {
|
||||
eventEmitter.off('dirtyStateChange', changeHandler);
|
||||
};
|
||||
}, [resetValidity, eventEmitter]);
|
||||
|
||||
// subscribe on external vis changes using browser history, for example press back button
|
||||
useEffect(() => {
|
||||
const resetHandler = () => dispatch(discardChanges(vis));
|
||||
vis.on('updateEditor', resetHandler);
|
||||
eventEmitter.on('updateEditor', resetHandler);
|
||||
|
||||
return () => vis.off('updateEditor', resetHandler);
|
||||
}, [dispatch, vis]);
|
||||
return () => {
|
||||
eventEmitter.off('updateEditor', resetHandler);
|
||||
};
|
||||
}, [dispatch, vis, eventEmitter]);
|
||||
|
||||
const dataTabProps = {
|
||||
dispatch,
|
||||
|
@ -147,7 +159,7 @@ function DefaultEditorSideBar({
|
|||
};
|
||||
|
||||
const optionTabProps = {
|
||||
aggs: state.aggs,
|
||||
aggs: state.data.aggs!,
|
||||
hasHistogramAgg,
|
||||
stateParams: state.params,
|
||||
vis,
|
||||
|
@ -173,7 +185,12 @@ function DefaultEditorSideBar({
|
|||
onKeyDownCapture={onSubmit}
|
||||
>
|
||||
{vis.type.requiresSearch && (
|
||||
<SidebarTitle isLinkedSearch={isLinkedSearch} savedSearch={savedSearch} vis={vis} />
|
||||
<SidebarTitle
|
||||
isLinkedSearch={isLinkedSearch}
|
||||
savedSearch={savedSearch}
|
||||
vis={vis}
|
||||
eventEmitter={eventEmitter}
|
||||
/>
|
||||
)}
|
||||
|
||||
{optionTabs.length > 1 && (
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { EventEmitter } from 'events';
|
||||
import {
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
|
@ -39,23 +40,24 @@ import { SavedSearch } from '../../../../../../plugins/discover/public';
|
|||
|
||||
interface LinkedSearchProps {
|
||||
savedSearch: SavedSearch;
|
||||
vis: Vis;
|
||||
eventEmitter: EventEmitter;
|
||||
}
|
||||
|
||||
interface SidebarTitleProps {
|
||||
isLinkedSearch: boolean;
|
||||
savedSearch?: SavedSearch;
|
||||
vis: Vis;
|
||||
eventEmitter: EventEmitter;
|
||||
}
|
||||
|
||||
export function LinkedSearch({ savedSearch, vis }: LinkedSearchProps) {
|
||||
export function LinkedSearch({ savedSearch, eventEmitter }: LinkedSearchProps) {
|
||||
const [showPopover, setShowPopover] = useState(false);
|
||||
const closePopover = useCallback(() => setShowPopover(false), []);
|
||||
const onClickButtonLink = useCallback(() => setShowPopover(v => !v), []);
|
||||
const onClickUnlikFromSavedSearch = useCallback(() => {
|
||||
setShowPopover(false);
|
||||
vis.emit('unlinkFromSavedSearch');
|
||||
}, [vis]);
|
||||
eventEmitter.emit('unlinkFromSavedSearch');
|
||||
}, [eventEmitter]);
|
||||
|
||||
const linkButtonAriaLabel = i18n.translate(
|
||||
'visDefaultEditor.sidebar.savedSearch.linkButtonAriaLabel',
|
||||
|
@ -151,20 +153,20 @@ export function LinkedSearch({ savedSearch, vis }: LinkedSearchProps) {
|
|||
);
|
||||
}
|
||||
|
||||
function SidebarTitle({ savedSearch, vis, isLinkedSearch }: SidebarTitleProps) {
|
||||
function SidebarTitle({ savedSearch, vis, isLinkedSearch, eventEmitter }: SidebarTitleProps) {
|
||||
return isLinkedSearch && savedSearch ? (
|
||||
<LinkedSearch savedSearch={savedSearch} vis={vis} />
|
||||
<LinkedSearch savedSearch={savedSearch} eventEmitter={eventEmitter} />
|
||||
) : vis.type.options.showIndexSelection ? (
|
||||
<EuiTitle size="xs" className="visEditorSidebar__titleContainer eui-textTruncate">
|
||||
<h2
|
||||
title={i18n.translate('visDefaultEditor.sidebar.indexPatternAriaLabel', {
|
||||
defaultMessage: 'Index pattern: {title}',
|
||||
values: {
|
||||
title: vis.indexPattern.title,
|
||||
title: vis.data.indexPattern!.title,
|
||||
},
|
||||
})}
|
||||
>
|
||||
{vis.indexPattern.title}
|
||||
{vis.data.indexPattern!.title}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
) : (
|
||||
|
|
|
@ -17,20 +17,23 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { useEffect, useReducer, useCallback } from 'react';
|
||||
import { isEqual } from 'lodash';
|
||||
import { useReducer, useCallback } from 'react';
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
import { Vis, VisState, VisParams } from 'src/legacy/core_plugins/visualizations/public';
|
||||
import { createEditorStateReducer, initEditorState } from './reducers';
|
||||
import { Vis } from 'src/legacy/core_plugins/visualizations/public';
|
||||
import { createEditorStateReducer, initEditorState, EditorVisState } from './reducers';
|
||||
import { EditorStateActionTypes } from './constants';
|
||||
import { EditorAction, updateStateParams } from './actions';
|
||||
import { EditorAction } from './actions';
|
||||
import { useKibana } from '../../../../../../../plugins/kibana_react/public';
|
||||
import { VisDefaultEditorKibanaServices } from '../../../types';
|
||||
|
||||
export * from './editor_form_state';
|
||||
export * from './actions';
|
||||
|
||||
export function useEditorReducer(vis: Vis): [VisState, React.Dispatch<EditorAction>] {
|
||||
export function useEditorReducer(
|
||||
vis: Vis,
|
||||
eventEmitter: EventEmitter
|
||||
): [EditorVisState, React.Dispatch<EditorAction>] {
|
||||
const { services } = useKibana<VisDefaultEditorKibanaServices>();
|
||||
const [state, dispatch] = useReducer(
|
||||
createEditorStateReducer(services.data.search),
|
||||
|
@ -38,28 +41,15 @@ export function useEditorReducer(vis: Vis): [VisState, React.Dispatch<EditorActi
|
|||
initEditorState
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const handleVisUpdate = (params: VisParams) => {
|
||||
if (!isEqual(params, state.params)) {
|
||||
dispatch(updateStateParams(params));
|
||||
}
|
||||
};
|
||||
|
||||
// fires when visualization state changes, and we need to copy changes to editorState
|
||||
vis.on('updateEditorStateParams', handleVisUpdate);
|
||||
|
||||
return () => vis.off('updateEditorStateParams', handleVisUpdate);
|
||||
}, [vis, state.params]);
|
||||
|
||||
const wrappedDispatch = useCallback(
|
||||
(action: EditorAction) => {
|
||||
dispatch(action);
|
||||
|
||||
vis.emit('dirtyStateChange', {
|
||||
eventEmitter.emit('dirtyStateChange', {
|
||||
isDirty: action.type !== EditorStateActionTypes.DISCARD_CHANGES,
|
||||
});
|
||||
},
|
||||
[vis]
|
||||
[eventEmitter]
|
||||
);
|
||||
|
||||
return [state, wrappedDispatch];
|
||||
|
|
|
@ -19,35 +19,45 @@
|
|||
|
||||
import { cloneDeep } from 'lodash';
|
||||
|
||||
import { Vis, VisState } from 'src/legacy/core_plugins/visualizations/public';
|
||||
import { Vis } from 'src/legacy/core_plugins/visualizations/public';
|
||||
import { AggGroupNames, DataPublicPluginStart } from '../../../../../../../plugins/data/public';
|
||||
import { EditorStateActionTypes } from './constants';
|
||||
import { getEnabledMetricAggsCount } from '../../agg_group_helper';
|
||||
import { EditorAction } from './actions';
|
||||
|
||||
function initEditorState(vis: Vis) {
|
||||
return vis.copyCurrentState(true);
|
||||
return {
|
||||
...vis.clone(),
|
||||
};
|
||||
}
|
||||
|
||||
export type EditorVisState = Pick<Vis, 'title' | 'description' | 'type' | 'params' | 'data'>;
|
||||
|
||||
const createEditorStateReducer = ({
|
||||
aggs: { createAggConfigs },
|
||||
}: DataPublicPluginStart['search']) => (state: VisState, action: EditorAction): VisState => {
|
||||
}: DataPublicPluginStart['search']) => (
|
||||
state: EditorVisState,
|
||||
action: EditorAction
|
||||
): EditorVisState => {
|
||||
switch (action.type) {
|
||||
case EditorStateActionTypes.ADD_NEW_AGG: {
|
||||
const { schema } = action.payload;
|
||||
const defaultConfig =
|
||||
!state.aggs.aggs.find(agg => agg.schema === schema.name) && schema.defaults
|
||||
!state.data.aggs!.aggs.find(agg => agg.schema === schema.name) && schema.defaults
|
||||
? (schema as any).defaults.slice(0, schema.max)
|
||||
: { schema: schema.name };
|
||||
const aggConfig = state.aggs.createAggConfig(defaultConfig, {
|
||||
const aggConfig = state.data.aggs!.createAggConfig(defaultConfig, {
|
||||
addToAggConfigs: false,
|
||||
});
|
||||
aggConfig.brandNew = true;
|
||||
const newAggs = [...state.aggs.aggs, aggConfig];
|
||||
const newAggs = [...state.data.aggs!.aggs, aggConfig];
|
||||
|
||||
return {
|
||||
...state,
|
||||
aggs: createAggConfigs(state.aggs.indexPattern, newAggs),
|
||||
data: {
|
||||
...state.data,
|
||||
aggs: createAggConfigs(state.data.indexPattern!, newAggs),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -58,7 +68,7 @@ const createEditorStateReducer = ({
|
|||
case EditorStateActionTypes.CHANGE_AGG_TYPE: {
|
||||
const { aggId, value } = action.payload;
|
||||
|
||||
const newAggs = state.aggs.aggs.map(agg => {
|
||||
const newAggs = state.data.aggs!.aggs.map(agg => {
|
||||
if (agg.id === aggId) {
|
||||
agg.type = value;
|
||||
|
||||
|
@ -70,14 +80,17 @@ const createEditorStateReducer = ({
|
|||
|
||||
return {
|
||||
...state,
|
||||
aggs: createAggConfigs(state.aggs.indexPattern, newAggs),
|
||||
data: {
|
||||
...state.data,
|
||||
aggs: createAggConfigs(state.data.indexPattern!, newAggs),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
case EditorStateActionTypes.SET_AGG_PARAM_VALUE: {
|
||||
const { aggId, paramName, value } = action.payload;
|
||||
|
||||
const newAggs = state.aggs.aggs.map(agg => {
|
||||
const newAggs = state.data.aggs!.aggs.map(agg => {
|
||||
if (agg.id === aggId) {
|
||||
const parsedAgg = agg.toJSON();
|
||||
|
||||
|
@ -95,7 +108,10 @@ const createEditorStateReducer = ({
|
|||
|
||||
return {
|
||||
...state,
|
||||
aggs: createAggConfigs(state.aggs.indexPattern, newAggs),
|
||||
data: {
|
||||
...state.data,
|
||||
aggs: createAggConfigs(state.data.indexPattern!, newAggs),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -113,7 +129,7 @@ const createEditorStateReducer = ({
|
|||
|
||||
case EditorStateActionTypes.REMOVE_AGG: {
|
||||
let isMetric = false;
|
||||
const newAggs = state.aggs.aggs.filter(({ id, schema }) => {
|
||||
const newAggs = state.data.aggs!.aggs.filter(({ id, schema }) => {
|
||||
if (id === action.payload.aggId) {
|
||||
const schemaDef = action.payload.schemas.find(s => s.name === schema);
|
||||
if (schemaDef && schemaDef.group === AggGroupNames.Metrics) {
|
||||
|
@ -136,26 +152,36 @@ const createEditorStateReducer = ({
|
|||
|
||||
return {
|
||||
...state,
|
||||
aggs: createAggConfigs(state.aggs.indexPattern, newAggs),
|
||||
data: {
|
||||
...state.data,
|
||||
aggs: createAggConfigs(state.data.indexPattern!, newAggs),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
case EditorStateActionTypes.REORDER_AGGS: {
|
||||
const { sourceAgg, destinationAgg } = action.payload;
|
||||
const destinationIndex = state.aggs.aggs.indexOf(destinationAgg);
|
||||
const newAggs = [...state.aggs.aggs];
|
||||
newAggs.splice(destinationIndex, 0, newAggs.splice(state.aggs.aggs.indexOf(sourceAgg), 1)[0]);
|
||||
const destinationIndex = state.data.aggs!.aggs.indexOf(destinationAgg);
|
||||
const newAggs = [...state.data.aggs!.aggs];
|
||||
newAggs.splice(
|
||||
destinationIndex,
|
||||
0,
|
||||
newAggs.splice(state.data.aggs!.aggs.indexOf(sourceAgg), 1)[0]
|
||||
);
|
||||
|
||||
return {
|
||||
...state,
|
||||
aggs: createAggConfigs(state.aggs.indexPattern, newAggs),
|
||||
data: {
|
||||
...state.data,
|
||||
aggs: createAggConfigs(state.data.indexPattern!, newAggs),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
case EditorStateActionTypes.TOGGLE_ENABLED_AGG: {
|
||||
const { aggId, enabled } = action.payload;
|
||||
|
||||
const newAggs = state.aggs.aggs.map(agg => {
|
||||
const newAggs = state.data.aggs!.aggs.map(agg => {
|
||||
if (agg.id === aggId) {
|
||||
const parsedAgg = agg.toJSON();
|
||||
|
||||
|
@ -170,7 +196,10 @@ const createEditorStateReducer = ({
|
|||
|
||||
return {
|
||||
...state,
|
||||
aggs: createAggConfigs(state.aggs.indexPattern, newAggs),
|
||||
data: {
|
||||
...state.data,
|
||||
aggs: createAggConfigs(state.data.indexPattern!, newAggs),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -20,10 +20,6 @@
|
|||
import React, { useEffect, useRef, useState, useCallback } from 'react';
|
||||
|
||||
import { EditorRenderProps } from '../../kibana/public/visualize/np_ready/types';
|
||||
import {
|
||||
VisualizeEmbeddableContract as VisualizeEmbeddable,
|
||||
VisualizeEmbeddableFactoryContract as VisualizeEmbeddableFactory,
|
||||
} from '../../visualizations/public/';
|
||||
import { PanelsContainer, Panel } from '../../../../plugins/kibana_react/public';
|
||||
|
||||
import './vis_type_agg_filter';
|
||||
|
@ -32,68 +28,44 @@ import { DefaultEditorControllerState } from './default_editor_controller';
|
|||
import { getInitialWidth } from './editor_size';
|
||||
|
||||
function DefaultEditor({
|
||||
embeddable,
|
||||
savedObj,
|
||||
vis,
|
||||
uiState,
|
||||
timeRange,
|
||||
filters,
|
||||
appState,
|
||||
optionTabs,
|
||||
query,
|
||||
embeddableHandler,
|
||||
eventEmitter,
|
||||
linked,
|
||||
savedSearch,
|
||||
}: DefaultEditorControllerState & Omit<EditorRenderProps, 'data' | 'core'>) {
|
||||
const visRef = useRef<HTMLDivElement>(null);
|
||||
const visHandler = useRef<VisualizeEmbeddable | null>(null);
|
||||
const [isCollapsed, setIsCollapsed] = useState(false);
|
||||
const [factory, setFactory] = useState<VisualizeEmbeddableFactory | null>(null);
|
||||
const { vis, savedSearch } = savedObj;
|
||||
|
||||
const onClickCollapse = useCallback(() => {
|
||||
setIsCollapsed(value => !value);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
async function visualize() {
|
||||
if (!visRef.current || (!visHandler.current && factory)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!visHandler.current) {
|
||||
const embeddableFactory = embeddable.getEmbeddableFactory(
|
||||
'visualization'
|
||||
) as VisualizeEmbeddableFactory;
|
||||
setFactory(embeddableFactory);
|
||||
|
||||
visHandler.current = (await embeddableFactory.createFromObject(savedObj, {
|
||||
// should be look through createFromObject interface again because of "id" param
|
||||
id: '',
|
||||
uiState,
|
||||
appState,
|
||||
timeRange,
|
||||
filters,
|
||||
query,
|
||||
})) as VisualizeEmbeddable;
|
||||
|
||||
visHandler.current.render(visRef.current);
|
||||
} else {
|
||||
visHandler.current.updateInput({
|
||||
timeRange,
|
||||
filters,
|
||||
query,
|
||||
});
|
||||
}
|
||||
if (!visRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
visualize();
|
||||
}, [uiState, savedObj, timeRange, filters, appState, query, factory, embeddable]);
|
||||
embeddableHandler.render(visRef.current);
|
||||
setTimeout(() => {
|
||||
eventEmitter.emit('apply');
|
||||
});
|
||||
|
||||
return () => embeddableHandler.destroy();
|
||||
}, [embeddableHandler, eventEmitter]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (visHandler.current) {
|
||||
visHandler.current.destroy();
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
embeddableHandler.updateInput({
|
||||
timeRange,
|
||||
filters,
|
||||
query,
|
||||
});
|
||||
}, [embeddableHandler, timeRange, filters, query]);
|
||||
|
||||
const editorInitialWidth = getInitialWidth(vis.type.editorConfig.defaultSize);
|
||||
|
||||
|
@ -120,6 +92,7 @@ function DefaultEditor({
|
|||
uiState={uiState}
|
||||
isLinkedSearch={linked}
|
||||
savedSearch={savedSearch}
|
||||
eventEmitter={eventEmitter}
|
||||
/>
|
||||
</Panel>
|
||||
</PanelsContainer>
|
||||
|
|
|
@ -21,18 +21,22 @@ import React from 'react';
|
|||
import { render, unmountComponentAtNode } from 'react-dom';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { I18nProvider } from '@kbn/i18n/react';
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
import { EditorRenderProps } from 'src/legacy/core_plugins/kibana/public/visualize/np_ready/types';
|
||||
import { VisSavedObject } from 'src/legacy/core_plugins/visualizations/public/';
|
||||
import { Vis } from 'src/legacy/core_plugins/visualizations/public/';
|
||||
import { Storage } from '../../../../plugins/kibana_utils/public';
|
||||
import { KibanaContextProvider } from '../../../../plugins/kibana_react/public';
|
||||
import { DefaultEditor } from './default_editor';
|
||||
import { DefaultEditorDataTab, OptionTab } from './components/sidebar';
|
||||
import { VisualizeEmbeddable } from '../../visualizations/public/np_ready/public/embeddable';
|
||||
|
||||
const localStorage = new Storage(window.localStorage);
|
||||
|
||||
export interface DefaultEditorControllerState {
|
||||
savedObj: VisSavedObject;
|
||||
vis: Vis;
|
||||
eventEmitter: EventEmitter;
|
||||
embeddableHandler: VisualizeEmbeddable;
|
||||
optionTabs: OptionTab[];
|
||||
}
|
||||
|
||||
|
@ -40,9 +44,9 @@ class DefaultEditorController {
|
|||
private el: HTMLElement;
|
||||
private state: DefaultEditorControllerState;
|
||||
|
||||
constructor(el: HTMLElement, savedObj: VisSavedObject) {
|
||||
constructor(el: HTMLElement, vis: Vis, eventEmitter: EventEmitter, embeddableHandler: any) {
|
||||
this.el = el;
|
||||
const { type: visType } = savedObj.vis;
|
||||
const { type: visType } = vis;
|
||||
|
||||
const optionTabs = [
|
||||
...(visType.schemas.buckets || visType.schemas.metrics
|
||||
|
@ -71,8 +75,10 @@ class DefaultEditorController {
|
|||
];
|
||||
|
||||
this.state = {
|
||||
savedObj,
|
||||
vis,
|
||||
optionTabs,
|
||||
eventEmitter,
|
||||
embeddableHandler,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { PersistedState } from 'src/plugins/visualizations/public';
|
||||
import { IAggConfigs } from 'src/plugins/data/public';
|
||||
import { PersistedState } from '../../../../plugins/visualizations/public';
|
||||
import { Vis } from '../../visualizations/public';
|
||||
|
||||
export interface VisOptionsProps<VisParamType = unknown> {
|
||||
|
|
|
@ -20,12 +20,12 @@
|
|||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { Vis } from 'src/legacy/core_plugins/visualizations/public';
|
||||
import { MetricVisComponent, MetricVisComponentProps } from './metric_vis_component';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { npStart } from 'ui/new_platform';
|
||||
import { fieldFormats } from '../../../../../plugins/data/public';
|
||||
import { identity } from 'lodash';
|
||||
import { ExprVis } from '../../../visualizations/public/np_ready/public/expressions/vis';
|
||||
|
||||
jest.mock('ui/new_platform');
|
||||
|
||||
|
@ -37,7 +37,7 @@ const baseVisData = {
|
|||
} as any;
|
||||
|
||||
describe('MetricVisComponent', function() {
|
||||
const vis: Vis = {
|
||||
const vis: ExprVis = {
|
||||
params: {
|
||||
metric: {
|
||||
colorSchema: 'Green to Red',
|
||||
|
@ -57,7 +57,7 @@ describe('MetricVisComponent', function() {
|
|||
const getComponent = (propOverrides: Partial<Props> = {} as Partial<Props>) => {
|
||||
const props: Props = {
|
||||
vis,
|
||||
visParams: vis.params,
|
||||
visParams: vis.params as any,
|
||||
visData: baseVisData,
|
||||
renderComplete: jest.fn(),
|
||||
...propOverrides,
|
||||
|
|
|
@ -27,12 +27,13 @@ import { FieldFormatsContentType, IFieldFormat } from '../../../../../plugins/da
|
|||
import { KibanaDatatable } from '../../../../../plugins/expressions/public';
|
||||
import { getHeatmapColors } from '../../../../../plugins/charts/public';
|
||||
import { VisParams, MetricVisMetric } from '../types';
|
||||
import { SchemaConfig, Vis } from '../../../visualizations/public';
|
||||
import { SchemaConfig } from '../../../visualizations/public';
|
||||
import { ExprVis } from '../../../visualizations/public/np_ready/public/expressions/vis';
|
||||
|
||||
export interface MetricVisComponentProps {
|
||||
visParams: VisParams;
|
||||
visData: Input;
|
||||
vis: Vis;
|
||||
vis: ExprVis;
|
||||
renderComplete: () => void;
|
||||
}
|
||||
|
||||
|
|
|
@ -61,11 +61,22 @@ describe('metric_vis - createMetricVisTypeDefinition', () => {
|
|||
labelTemplate: 'ip[{{value}}]',
|
||||
});
|
||||
|
||||
const searchSource = {
|
||||
getField: (name: string) => {
|
||||
if (name === 'index') {
|
||||
return stubIndexPattern;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// TODO: remove when Vis is converted to typescript. Only importing Vis as type
|
||||
// @ts-ignore
|
||||
vis = visualizationsStart.createVis(stubIndexPattern, {
|
||||
vis = visualizationsStart.createVis('metric', {
|
||||
type: 'metric',
|
||||
aggs: [{ id: '1', type: 'top_hits', schema: 'metric', params: { field: 'ip' } }],
|
||||
data: {
|
||||
searchSource,
|
||||
aggs: [{ id: '1', type: 'top_hits', schema: 'metric', params: { field: 'ip' } }],
|
||||
},
|
||||
});
|
||||
|
||||
vis.params.dimensions = {
|
||||
|
|
|
@ -50,57 +50,73 @@ describe('Table Vis - AggTable Directive', function() {
|
|||
const tabifiedData = {};
|
||||
|
||||
const init = () => {
|
||||
const vis1 = visualizationsStart.createVis(indexPattern, 'table');
|
||||
tabifiedData.metricOnly = tabifyAggResponse(vis1.aggs, metricOnly);
|
||||
const searchSource = {
|
||||
getField: name => {
|
||||
if (name === 'index') {
|
||||
return indexPattern;
|
||||
}
|
||||
},
|
||||
};
|
||||
const vis1 = visualizationsStart.createVis('table', {
|
||||
type: 'table',
|
||||
data: { searchSource, aggs: [] },
|
||||
});
|
||||
tabifiedData.metricOnly = tabifyAggResponse(vis1.data.aggs, metricOnly);
|
||||
|
||||
const vis2 = visualizationsStart.createVis(indexPattern, {
|
||||
const vis2 = visualizationsStart.createVis('table', {
|
||||
type: 'table',
|
||||
params: {
|
||||
showMetricsAtAllLevels: true,
|
||||
},
|
||||
aggs: [
|
||||
{ type: 'avg', schema: 'metric', params: { field: 'bytes' } },
|
||||
{ type: 'terms', schema: 'bucket', params: { field: 'extension' } },
|
||||
{ type: 'terms', schema: 'bucket', params: { field: 'geo.src' } },
|
||||
{ type: 'terms', schema: 'bucket', params: { field: 'machine.os' } },
|
||||
],
|
||||
data: {
|
||||
aggs: [
|
||||
{ type: 'avg', schema: 'metric', params: { field: 'bytes' } },
|
||||
{ type: 'terms', schema: 'bucket', params: { field: 'extension' } },
|
||||
{ type: 'terms', schema: 'bucket', params: { field: 'geo.src' } },
|
||||
{ type: 'terms', schema: 'bucket', params: { field: 'machine.os' } },
|
||||
],
|
||||
searchSource,
|
||||
},
|
||||
});
|
||||
vis2.aggs.aggs.forEach(function(agg, i) {
|
||||
vis2.data.aggs.aggs.forEach(function(agg, i) {
|
||||
agg.id = 'agg_' + (i + 1);
|
||||
});
|
||||
tabifiedData.threeTermBuckets = tabifyAggResponse(vis2.aggs, threeTermBuckets, {
|
||||
tabifiedData.threeTermBuckets = tabifyAggResponse(vis2.data.aggs, threeTermBuckets, {
|
||||
metricsAtAllLevels: true,
|
||||
});
|
||||
|
||||
const vis3 = visualizationsStart.createVis(indexPattern, {
|
||||
const vis3 = visualizationsStart.createVis('table', {
|
||||
type: 'table',
|
||||
aggs: [
|
||||
{ type: 'avg', schema: 'metric', params: { field: 'bytes' } },
|
||||
{ type: 'min', schema: 'metric', params: { field: '@timestamp' } },
|
||||
{ type: 'terms', schema: 'bucket', params: { field: 'extension' } },
|
||||
{
|
||||
type: 'date_histogram',
|
||||
schema: 'bucket',
|
||||
params: { field: '@timestamp', interval: 'd' },
|
||||
},
|
||||
{
|
||||
type: 'derivative',
|
||||
schema: 'metric',
|
||||
params: { metricAgg: 'custom', customMetric: { id: '5-orderAgg', type: 'count' } },
|
||||
},
|
||||
{
|
||||
type: 'top_hits',
|
||||
schema: 'metric',
|
||||
params: { field: 'bytes', aggregate: { val: 'min' }, size: 1 },
|
||||
},
|
||||
],
|
||||
data: {
|
||||
aggs: [
|
||||
{ type: 'avg', schema: 'metric', params: { field: 'bytes' } },
|
||||
{ type: 'min', schema: 'metric', params: { field: '@timestamp' } },
|
||||
{ type: 'terms', schema: 'bucket', params: { field: 'extension' } },
|
||||
{
|
||||
type: 'date_histogram',
|
||||
schema: 'bucket',
|
||||
params: { field: '@timestamp', interval: 'd' },
|
||||
},
|
||||
{
|
||||
type: 'derivative',
|
||||
schema: 'metric',
|
||||
params: { metricAgg: 'custom', customMetric: { id: '5-orderAgg', type: 'count' } },
|
||||
},
|
||||
{
|
||||
type: 'top_hits',
|
||||
schema: 'metric',
|
||||
params: { field: 'bytes', aggregate: { val: 'min' }, size: 1 },
|
||||
},
|
||||
],
|
||||
searchSource,
|
||||
},
|
||||
});
|
||||
vis3.aggs.aggs.forEach(function(agg, i) {
|
||||
vis3.data.aggs.aggs.forEach(function(agg, i) {
|
||||
agg.id = 'agg_' + (i + 1);
|
||||
});
|
||||
|
||||
tabifiedData.oneTermOneHistogramBucketWithTwoMetricsOneTopHitOneDerivative = tabifyAggResponse(
|
||||
vis3.aggs,
|
||||
vis3.data.aggs,
|
||||
oneTermOneHistogramBucketWithTwoMetricsOneTopHitOneDerivative
|
||||
);
|
||||
};
|
||||
|
|
|
@ -38,22 +38,35 @@ describe('Table Vis - AggTableGroup Directive', function() {
|
|||
const tabifiedData = {};
|
||||
|
||||
const init = () => {
|
||||
const vis1 = visualizationsStart.createVis(indexPattern, 'table');
|
||||
tabifiedData.metricOnly = tabifyAggResponse(vis1.aggs, metricOnly);
|
||||
|
||||
const vis2 = visualizationsStart.createVis(indexPattern, {
|
||||
type: 'pie',
|
||||
aggs: [
|
||||
{ type: 'avg', schema: 'metric', params: { field: 'bytes' } },
|
||||
{ type: 'terms', schema: 'split', params: { field: 'extension' } },
|
||||
{ type: 'terms', schema: 'segment', params: { field: 'geo.src' } },
|
||||
{ type: 'terms', schema: 'segment', params: { field: 'machine.os' } },
|
||||
],
|
||||
const searchSource = {
|
||||
getField: name => {
|
||||
if (name === 'index') {
|
||||
return indexPattern;
|
||||
}
|
||||
},
|
||||
};
|
||||
const vis1 = visualizationsStart.createVis('table', {
|
||||
type: 'table',
|
||||
data: { searchSource, aggs: [] },
|
||||
});
|
||||
vis2.aggs.aggs.forEach(function(agg, i) {
|
||||
tabifiedData.metricOnly = tabifyAggResponse(vis1.data.aggs, metricOnly);
|
||||
|
||||
const vis2 = visualizationsStart.createVis('pie', {
|
||||
type: 'pie',
|
||||
data: {
|
||||
aggs: [
|
||||
{ type: 'avg', schema: 'metric', params: { field: 'bytes' } },
|
||||
{ type: 'terms', schema: 'split', params: { field: 'extension' } },
|
||||
{ type: 'terms', schema: 'segment', params: { field: 'geo.src' } },
|
||||
{ type: 'terms', schema: 'segment', params: { field: 'machine.os' } },
|
||||
],
|
||||
searchSource,
|
||||
},
|
||||
});
|
||||
vis2.data.aggs.aggs.forEach(function(agg, i) {
|
||||
agg.id = 'agg_' + (i + 1);
|
||||
});
|
||||
tabifiedData.threeTermBuckets = tabifyAggResponse(vis2.aggs, threeTermBuckets);
|
||||
tabifiedData.threeTermBuckets = tabifyAggResponse(vis2.data.aggs, threeTermBuckets);
|
||||
};
|
||||
|
||||
const initLocalAngular = () => {
|
||||
|
|
|
@ -118,20 +118,22 @@ describe('Table Vis - Controller', () => {
|
|||
return ({
|
||||
type: tableVisTypeDefinition,
|
||||
params: Object.assign({}, tableVisTypeDefinition.visConfig.defaults, params),
|
||||
aggs: createAggConfigs(stubIndexPattern, [
|
||||
{ type: 'count', schema: 'metric' },
|
||||
{
|
||||
type: 'range',
|
||||
schema: 'bucket',
|
||||
params: {
|
||||
field: 'bytes',
|
||||
ranges: [
|
||||
{ from: 0, to: 1000 },
|
||||
{ from: 1000, to: 2000 },
|
||||
],
|
||||
data: {
|
||||
aggs: createAggConfigs(stubIndexPattern, [
|
||||
{ type: 'count', schema: 'metric' },
|
||||
{
|
||||
type: 'range',
|
||||
schema: 'bucket',
|
||||
params: {
|
||||
field: 'bytes',
|
||||
ranges: [
|
||||
{ from: 0, to: 1000 },
|
||||
{ from: 1000, to: 2000 },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
]),
|
||||
]),
|
||||
},
|
||||
} as unknown) as Vis;
|
||||
}
|
||||
|
||||
|
@ -151,11 +153,11 @@ describe('Table Vis - Controller', () => {
|
|||
|
||||
// basically a parameterized beforeEach
|
||||
function initController(vis: Vis) {
|
||||
vis.aggs.aggs.forEach((agg: IAggConfig, i: number) => {
|
||||
vis.data.aggs!.aggs.forEach((agg: IAggConfig, i: number) => {
|
||||
agg.id = 'agg_' + (i + 1);
|
||||
});
|
||||
|
||||
tabifiedResponse = tabifyAggResponse(vis.aggs, oneRangeBucket);
|
||||
tabifiedResponse = tabifyAggResponse(vis.data.aggs!, oneRangeBucket);
|
||||
$rootScope.vis = vis;
|
||||
$rootScope.visParams = vis.params;
|
||||
$rootScope.uiState = {
|
||||
|
|
|
@ -19,12 +19,12 @@
|
|||
|
||||
import angular, { IModule, auto, IRootScopeService, IScope, ICompileService } from 'angular';
|
||||
import $ from 'jquery';
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
import { Vis, VisParams } from '../../visualizations/public';
|
||||
import { VisParams } from '../../visualizations/public';
|
||||
import { npStart } from './legacy_imports';
|
||||
import { getAngularModule } from './get_inner_angular';
|
||||
import { initTableVisLegacyModule } from './table_vis_legacy_module';
|
||||
import { ExprVis } from '../../visualizations/public/np_ready/public/expressions/vis';
|
||||
|
||||
const innerAngularName = 'kibana/table_vis';
|
||||
|
||||
|
@ -32,12 +32,12 @@ export class TableVisualizationController {
|
|||
private tableVisModule: IModule | undefined;
|
||||
private injector: auto.IInjectorService | undefined;
|
||||
el: JQuery<Element>;
|
||||
vis: Vis;
|
||||
vis: ExprVis;
|
||||
$rootScope: IRootScopeService | null = null;
|
||||
$scope: (IScope & { [key: string]: any }) | undefined;
|
||||
$compile: ICompileService | undefined;
|
||||
|
||||
constructor(domeElement: Element, vis: Vis) {
|
||||
constructor(domeElement: Element, vis: ExprVis) {
|
||||
this.el = $(domeElement);
|
||||
this.vis = vis;
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ export class TableVisualizationController {
|
|||
}
|
||||
}
|
||||
|
||||
async render(esResponse: object, visParams: VisParams, status: { [key: string]: boolean }) {
|
||||
async render(esResponse: object, visParams: VisParams) {
|
||||
this.initLocalAngular();
|
||||
|
||||
return new Promise(async (resolve, reject) => {
|
||||
|
@ -77,15 +77,10 @@ export class TableVisualizationController {
|
|||
this.$scope.visState = { params: visParams };
|
||||
this.$scope.esResponse = esResponse;
|
||||
|
||||
if (!isEqual(this.$scope.visParams, visParams)) {
|
||||
this.vis.emit('updateEditorStateParams', visParams);
|
||||
}
|
||||
|
||||
this.$scope.visParams = visParams;
|
||||
this.$scope.renderComplete = resolve;
|
||||
this.$scope.renderFailed = reject;
|
||||
this.$scope.resize = Date.now();
|
||||
this.$scope.updateStatus = status;
|
||||
this.$scope.$apply();
|
||||
};
|
||||
|
||||
|
@ -93,7 +88,7 @@ export class TableVisualizationController {
|
|||
this.$scope = this.$rootScope.$new();
|
||||
this.$scope.uiState = this.vis.getUiState();
|
||||
updateScope();
|
||||
this.el.find('div').append(this.$compile(this.vis.type.visConfig.template)(this.$scope));
|
||||
this.el.find('div').append(this.$compile(this.vis.type!.visConfig.template)(this.$scope));
|
||||
this.$scope.$apply();
|
||||
} else {
|
||||
updateScope();
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
|
||||
import expect from '@kbn/expect';
|
||||
import ngMock from 'ng_mock';
|
||||
import LogstashIndexPatternStubProvider from 'fixtures/stubbed_logstash_index_pattern';
|
||||
import { start as visualizationsStart } from '../../../../../core_plugins/visualizations/public/np_ready/public/legacy';
|
||||
import { ImageComparator } from 'test_utils/image_comparator';
|
||||
import { createTagCloudVisualization } from '../tag_cloud_visualization';
|
||||
|
@ -36,7 +35,6 @@ const PIXEL_DIFF = 64;
|
|||
|
||||
describe('TagCloudVisualizationTest', function() {
|
||||
let domNode;
|
||||
let indexPattern;
|
||||
let vis;
|
||||
let imageComparator;
|
||||
|
||||
|
@ -66,22 +64,18 @@ describe('TagCloudVisualizationTest', function() {
|
|||
});
|
||||
|
||||
beforeEach(ngMock.module('kibana'));
|
||||
beforeEach(
|
||||
ngMock.inject(Private => {
|
||||
indexPattern = Private(LogstashIndexPatternStubProvider);
|
||||
})
|
||||
);
|
||||
|
||||
describe('TagCloudVisualization - basics', function() {
|
||||
beforeEach(async function() {
|
||||
setupDOM('512px', '512px');
|
||||
imageComparator = new ImageComparator();
|
||||
vis = visualizationsStart.createVis(indexPattern, {
|
||||
vis = visualizationsStart.createVis('tagcloud', {
|
||||
type: 'tagcloud',
|
||||
params: {
|
||||
bucket: { accessor: 0, format: {} },
|
||||
metric: { accessor: 0, format: {} },
|
||||
},
|
||||
data: {},
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -79,17 +79,10 @@ export function createTagCloudVisualization({ colors }) {
|
|||
render(<Label ref={this._label} />, this._labelNode);
|
||||
}
|
||||
|
||||
async render(data, visParams, status) {
|
||||
if (!(status.resize || status.data || status.params)) return;
|
||||
|
||||
if (status.params || status.data) {
|
||||
this._updateParams(visParams);
|
||||
this._updateData(data);
|
||||
}
|
||||
|
||||
if (status.resize) {
|
||||
this._resize();
|
||||
}
|
||||
async render(data, visParams) {
|
||||
this._updateParams(visParams);
|
||||
this._updateData(data);
|
||||
this._resize();
|
||||
|
||||
await this._renderComplete$.pipe(take(1)).toPromise();
|
||||
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { Schemas } from '../../vis_default_editor/public';
|
||||
import { Status } from '../../visualizations/public';
|
||||
|
||||
import { TagCloudOptions } from './components/tag_cloud_options';
|
||||
|
||||
|
@ -44,7 +43,6 @@ export const createTagCloudVisTypeDefinition = (deps: TagCloudVisDependencies) =
|
|||
showLabel: true,
|
||||
},
|
||||
},
|
||||
requiresUpdateStatus: [Status.PARAMS, Status.RESIZE, Status.DATA],
|
||||
visualization: createTagCloudVisualization(deps),
|
||||
editorConfig: {
|
||||
collections: {
|
||||
|
|
|
@ -20,16 +20,16 @@
|
|||
import React from 'react';
|
||||
|
||||
import { IUiSettingsClient } from 'kibana/public';
|
||||
import { Vis } from 'src/legacy/core_plugins/visualizations/public';
|
||||
import { ChartComponent } from './chart';
|
||||
import { VisParams } from '../timelion_vis_fn';
|
||||
import { TimelionSuccessResponse } from '../helpers/timelion_request_handler';
|
||||
import { ExprVis } from '../../../visualizations/public/np_ready/public/expressions/vis';
|
||||
|
||||
export interface TimelionVisComponentProp {
|
||||
config: IUiSettingsClient;
|
||||
renderComplete(): void;
|
||||
updateStatus: object;
|
||||
vis: Vis;
|
||||
vis: ExprVis;
|
||||
visData: TimelionSuccessResponse;
|
||||
visParams: VisParams;
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@ export class VisEditor extends Component {
|
|||
}
|
||||
|
||||
get uiState() {
|
||||
return this.props.vis.getUiState();
|
||||
return this.props.vis.uiState;
|
||||
}
|
||||
|
||||
getConfig = (...args) => {
|
||||
|
@ -73,17 +73,15 @@ export class VisEditor extends Component {
|
|||
};
|
||||
|
||||
handleUiState = (field, value) => {
|
||||
this.props.vis.uiStateVal(field, value);
|
||||
this.props.vis.uiState.set(field, value);
|
||||
};
|
||||
|
||||
updateVisState = debounce(() => {
|
||||
this.props.vis.params = this.state.model;
|
||||
this.props.vis.updateState();
|
||||
// This check should be redundant, since this method should only be called when we're in editor
|
||||
// mode where there's also an appState passed into us.
|
||||
if (this.props.appState) {
|
||||
this.props.appState.save();
|
||||
}
|
||||
this.props.eventEmitter.emit('updateVis');
|
||||
this.props.eventEmitter.emit('dirtyStateChange', {
|
||||
isDirty: false,
|
||||
});
|
||||
}, VIS_STATE_DEBOUNCE_DELAY);
|
||||
|
||||
isValidKueryQuery = filterQuery => {
|
||||
|
@ -184,7 +182,8 @@ export class VisEditor extends Component {
|
|||
dirty={this.state.dirty}
|
||||
autoApply={this.state.autoApply}
|
||||
model={model}
|
||||
savedObj={this.props.savedObj}
|
||||
embeddableHandler={this.props.embeddableHandler}
|
||||
vis={this.props.vis}
|
||||
timeRange={this.props.timeRange}
|
||||
uiState={this.uiState}
|
||||
onCommit={this.handleCommit}
|
||||
|
|
|
@ -29,7 +29,6 @@ import {
|
|||
AUTO_INTERVAL,
|
||||
} from './lib/get_interval';
|
||||
import { PANEL_TYPES } from '../../../../../plugins/vis_type_timeseries/common/panel_types';
|
||||
import { start as embeddables } from '../../../embeddable_api/public/np_ready/public/legacy';
|
||||
|
||||
const MIN_CHART_HEIGHT = 300;
|
||||
|
||||
|
@ -70,15 +69,9 @@ class VisEditorVisualizationUI extends Component {
|
|||
return;
|
||||
}
|
||||
|
||||
const { timeRange, savedObj, onDataChange } = this.props;
|
||||
const { onDataChange, embeddableHandler } = this.props;
|
||||
|
||||
this._handler = await embeddables
|
||||
.getEmbeddableFactory('visualization')
|
||||
.createFromObject(savedObj, {
|
||||
vis: {},
|
||||
timeRange: timeRange,
|
||||
filters: [],
|
||||
});
|
||||
this._handler = embeddableHandler;
|
||||
await this._handler.render(this._visEl.current);
|
||||
|
||||
this._subscription = this._handler.handler.data$.subscribe(data => {
|
||||
|
@ -285,7 +278,7 @@ VisEditorVisualizationUI.propTypes = {
|
|||
onCommit: PropTypes.func,
|
||||
uiState: PropTypes.object,
|
||||
onToggleAutoApply: PropTypes.func,
|
||||
savedObj: PropTypes.object,
|
||||
embeddableHandler: PropTypes.object,
|
||||
timeRange: PropTypes.object,
|
||||
dirty: PropTypes.bool,
|
||||
autoApply: PropTypes.bool,
|
||||
|
|
|
@ -23,12 +23,15 @@ import { fetchIndexPatternFields } from './lib/fetch_fields';
|
|||
import { getSavedObjectsClient, getUISettings, getI18n } from './services';
|
||||
|
||||
export class EditorController {
|
||||
constructor(el, savedObj) {
|
||||
constructor(el, vis, eventEmitter, embeddableHandler) {
|
||||
this.el = el;
|
||||
|
||||
this.embeddableHandler = embeddableHandler;
|
||||
this.eventEmitter = eventEmitter;
|
||||
|
||||
this.state = {
|
||||
savedObj: savedObj,
|
||||
vis: savedObj.vis,
|
||||
fields: [],
|
||||
vis: vis,
|
||||
isLoaded: false,
|
||||
};
|
||||
}
|
||||
|
@ -47,7 +50,7 @@ export class EditorController {
|
|||
|
||||
this.state.vis.params.default_index_pattern = title;
|
||||
this.state.vis.params.default_timefield = timeFieldName;
|
||||
this.state.vis.fields = await fetchIndexPatternFields(this.state.vis);
|
||||
this.state.fields = await fetchIndexPatternFields(this.state.vis);
|
||||
|
||||
this.state.isLoaded = true;
|
||||
};
|
||||
|
@ -67,13 +70,14 @@ export class EditorController {
|
|||
<Component
|
||||
config={getUISettings()}
|
||||
vis={this.state.vis}
|
||||
visFields={this.state.vis.fields}
|
||||
visFields={this.state.fields}
|
||||
visParams={this.state.vis.params}
|
||||
savedObj={this.state.savedObj}
|
||||
timeRange={params.timeRange}
|
||||
renderComplete={() => {}}
|
||||
isEditorMode={true}
|
||||
appState={params.appState}
|
||||
embeddableHandler={this.embeddableHandler}
|
||||
eventEmitter={this.eventEmitter}
|
||||
/>
|
||||
</I18nContext>,
|
||||
this.el
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
// @ts-ignore
|
||||
import colorJS from 'color';
|
||||
import { Theme, LIGHT_THEME, DARK_THEME } from '@elastic/charts';
|
||||
|
||||
|
|
|
@ -22,7 +22,6 @@ import expect from '@kbn/expect';
|
|||
import ngMock from 'ng_mock';
|
||||
import $ from 'jquery';
|
||||
import { createVegaVisualization } from '../vega_visualization';
|
||||
import LogstashIndexPatternStubProvider from 'fixtures/stubbed_logstash_index_pattern';
|
||||
import { ImageComparator } from 'test_utils/image_comparator';
|
||||
|
||||
import vegaliteGraph from '!!raw-loader!./vegalite_graph.hjson';
|
||||
|
@ -57,7 +56,6 @@ const PIXEL_DIFF = 30;
|
|||
describe('VegaVisualizations', () => {
|
||||
let domNode;
|
||||
let VegaVisualization;
|
||||
let indexPattern;
|
||||
let vis;
|
||||
let imageComparator;
|
||||
let vegaVisualizationDependencies;
|
||||
|
@ -71,7 +69,7 @@ describe('VegaVisualizations', () => {
|
|||
|
||||
beforeEach(ngMock.module('kibana'));
|
||||
beforeEach(
|
||||
ngMock.inject((Private, $injector) => {
|
||||
ngMock.inject($injector => {
|
||||
vegaVisualizationDependencies = {
|
||||
serviceSettings: $injector.get('serviceSettings'),
|
||||
core: {
|
||||
|
@ -99,7 +97,6 @@ describe('VegaVisualizations', () => {
|
|||
}
|
||||
|
||||
VegaVisualization = createVegaVisualization(vegaVisualizationDependencies);
|
||||
indexPattern = Private(LogstashIndexPatternStubProvider);
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -108,7 +105,7 @@ describe('VegaVisualizations', () => {
|
|||
setupDOM('512px', '512px');
|
||||
imageComparator = new ImageComparator();
|
||||
|
||||
vis = visualizationsStart.createVis(indexPattern, { type: 'vega' });
|
||||
vis = visualizationsStart.createVis('vega', { type: 'vega' });
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
// @ts-ignore
|
||||
import { Status } from '../../visualizations/public';
|
||||
import { DefaultEditorSize } from '../../vis_default_editor/public';
|
||||
import { VegaVisualizationDependencies } from './plugin';
|
||||
import { VegaVisEditor } from './components';
|
||||
|
@ -51,7 +50,6 @@ export const createVegaTypeDefinition = (dependencies: VegaVisualizationDependen
|
|||
},
|
||||
visualization,
|
||||
requestHandler,
|
||||
requiresUpdateStatus: [Status.DATA, Status.RESIZE],
|
||||
responseHandler: 'none',
|
||||
options: {
|
||||
showIndexSelection: false,
|
||||
|
|
|
@ -69,7 +69,7 @@ export const createVegaVisualization = ({ serviceSettings }) =>
|
|||
* @param {*} status
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async render(visData, visParams, status) {
|
||||
async render(visData) {
|
||||
const { toasts } = getNotifications();
|
||||
|
||||
if (!visData && !this._vegaView) {
|
||||
|
@ -82,7 +82,7 @@ export const createVegaVisualization = ({ serviceSettings }) =>
|
|||
}
|
||||
|
||||
try {
|
||||
await this._render(visData, status);
|
||||
await this._render(visData);
|
||||
} catch (error) {
|
||||
if (this._vegaView) {
|
||||
this._vegaView.onError(error);
|
||||
|
@ -96,8 +96,8 @@ export const createVegaVisualization = ({ serviceSettings }) =>
|
|||
}
|
||||
}
|
||||
|
||||
async _render(vegaParser, status) {
|
||||
if (vegaParser && (status.data || !this._vegaView)) {
|
||||
async _render(vegaParser) {
|
||||
if (vegaParser) {
|
||||
// New data received, rebuild the graph
|
||||
if (this._vegaView) {
|
||||
await this._vegaView.destroy();
|
||||
|
@ -121,9 +121,6 @@ export const createVegaVisualization = ({ serviceSettings }) =>
|
|||
this._vegaView = new VegaView(vegaViewParams);
|
||||
}
|
||||
await this._vegaView.init();
|
||||
} else if (status.resize) {
|
||||
// the graph has been resized
|
||||
await this._vegaView.resize();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ exports[`MetricsAxisOptions component should init with the default set of props
|
|||
}
|
||||
vis={
|
||||
Object {
|
||||
"setVisType": [MockFunction],
|
||||
"setState": [MockFunction],
|
||||
"type": Object {
|
||||
"schemas": Object {
|
||||
"metrics": Array [
|
||||
|
@ -126,7 +126,7 @@ exports[`MetricsAxisOptions component should init with the default set of props
|
|||
}
|
||||
vis={
|
||||
Object {
|
||||
"setVisType": [MockFunction],
|
||||
"setState": [MockFunction],
|
||||
"type": Object {
|
||||
"schemas": Object {
|
||||
"metrics": Array [
|
||||
|
@ -169,7 +169,7 @@ exports[`MetricsAxisOptions component should init with the default set of props
|
|||
setCategoryAxis={[Function]}
|
||||
vis={
|
||||
Object {
|
||||
"setVisType": [MockFunction],
|
||||
"setState": [MockFunction],
|
||||
"type": Object {
|
||||
"schemas": Object {
|
||||
"metrics": Array [
|
||||
|
|
|
@ -94,7 +94,7 @@ describe('MetricsAxisOptions component', () => {
|
|||
type: ChartTypes.AREA,
|
||||
schemas: { metrics: [{ name: 'metric' }] },
|
||||
},
|
||||
setVisType: jest.fn(),
|
||||
setState: jest.fn(),
|
||||
},
|
||||
stateParams: {
|
||||
valueAxes: [axis],
|
||||
|
@ -145,7 +145,7 @@ describe('MetricsAxisOptions component', () => {
|
|||
},
|
||||
});
|
||||
|
||||
expect(defaultProps.vis.setVisType).toHaveBeenLastCalledWith(ChartTypes.LINE);
|
||||
expect(defaultProps.vis.setState).toHaveBeenLastCalledWith({ type: ChartTypes.LINE });
|
||||
});
|
||||
|
||||
it('should set histogram visType when multiple seriesParam', () => {
|
||||
|
@ -159,7 +159,7 @@ describe('MetricsAxisOptions component', () => {
|
|||
},
|
||||
});
|
||||
|
||||
expect(defaultProps.vis.setVisType).toHaveBeenLastCalledWith(ChartTypes.HISTOGRAM);
|
||||
expect(defaultProps.vis.setState).toHaveBeenLastCalledWith({ type: ChartTypes.HISTOGRAM });
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -299,7 +299,7 @@ function MetricsAxisOptions(props: ValidationVisOptionsProps<BasicVislibParams>)
|
|||
}, [stateParams.seriesParams]);
|
||||
|
||||
useEffect(() => {
|
||||
vis.setVisType(visType);
|
||||
vis.setState({ type: visType } as any);
|
||||
}, [vis, visType]);
|
||||
|
||||
return isTabSelected ? (
|
||||
|
|
|
@ -44,7 +44,9 @@ function PointSeriesOptions(props: ValidationVisOptionsProps<BasicVislibParams>)
|
|||
|
||||
<BasicOptions {...props} />
|
||||
|
||||
{vis.hasSchemaAgg('segment', 'date_histogram') ? (
|
||||
{vis.data.aggs!.aggs.some(
|
||||
agg => agg.schema === 'segment' && agg.type.name === 'date_histogram'
|
||||
) ? (
|
||||
<SwitchOption
|
||||
label={i18n.translate('visTypeVislib.editors.pointSeries.currentTimeMarkerLabel', {
|
||||
defaultMessage: 'Current time marker',
|
||||
|
|
|
@ -26,7 +26,8 @@ import { Positions } from './utils/collections';
|
|||
import { VisTypeVislibDependencies } from './plugin';
|
||||
import { mountReactNode } from '../../../../core/public/utils';
|
||||
import { VisLegend, CUSTOM_LEGEND_VIS_TYPES } from './vislib/components/legend';
|
||||
import { VisParams, Vis } from '../../visualizations/public';
|
||||
import { VisParams } from '../../visualizations/public';
|
||||
import { ExprVis } from '../../visualizations/public/np_ready/public/expressions/vis';
|
||||
|
||||
const legendClassName = {
|
||||
top: 'visLib--legend-top',
|
||||
|
@ -45,7 +46,7 @@ export const createVislibVisController = (deps: VisTypeVislibDependencies) => {
|
|||
legendEl: HTMLDivElement;
|
||||
vislibVis: any;
|
||||
|
||||
constructor(public el: Element, public vis: Vis) {
|
||||
constructor(public el: Element, public vis: ExprVis) {
|
||||
this.el = el;
|
||||
this.vis = vis;
|
||||
this.unmount = null;
|
||||
|
|
|
@ -133,21 +133,30 @@ describe('No global chart settings', function() {
|
|||
responseHandler = vislibSlicesResponseHandler;
|
||||
|
||||
let id1 = 1;
|
||||
stubVis1 = visualizationsStart.createVis(indexPattern, {
|
||||
stubVis1 = visualizationsStart.createVis('pie', {
|
||||
type: 'pie',
|
||||
aggs: rowAgg,
|
||||
data: {
|
||||
aggs: rowAgg,
|
||||
searchSource: {
|
||||
getField: name => {
|
||||
if (name === 'index') {
|
||||
return indexPattern;
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
stubVis1.isHierarchical = () => true;
|
||||
|
||||
// We need to set the aggs to a known value.
|
||||
_.each(stubVis1.aggs.aggs, function(agg) {
|
||||
_.each(stubVis1.data.aggs.aggs, function(agg) {
|
||||
agg.id = 'agg_' + id1++;
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
const table1 = tabifyAggResponse(stubVis1.aggs, threeTermBuckets, {
|
||||
const table1 = tabifyAggResponse(stubVis1.data.aggs, threeTermBuckets, {
|
||||
metricsAtAllLevels: true,
|
||||
});
|
||||
data1 = await responseHandler(table1, rowAggDimensions);
|
||||
|
@ -222,19 +231,28 @@ describe('Vislib PieChart Class Test Suite', function() {
|
|||
responseHandler = vislibSlicesResponseHandler;
|
||||
|
||||
let id = 1;
|
||||
stubVis = visualizationsStart.createVis(indexPattern, {
|
||||
stubVis = visualizationsStart.createVis('pie', {
|
||||
type: 'pie',
|
||||
aggs: dataAgg,
|
||||
data: {
|
||||
aggs: dataAgg,
|
||||
searchSource: {
|
||||
getField: name => {
|
||||
if (name === 'index') {
|
||||
return indexPattern;
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// We need to set the aggs to a known value.
|
||||
_.each(stubVis.aggs.aggs, function(agg) {
|
||||
_.each(stubVis.data.aggs.aggs, function(agg) {
|
||||
agg.id = 'agg_' + id++;
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
const table = tabifyAggResponse(stubVis.aggs, threeTermBuckets, {
|
||||
const table = tabifyAggResponse(stubVis.data.aggs, threeTermBuckets, {
|
||||
metricsAtAllLevels: true,
|
||||
});
|
||||
data = await responseHandler(table, dataDimensions);
|
||||
|
|
|
@ -23,22 +23,11 @@ import { compact, uniq, map, every, isUndefined } from 'lodash';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiPopoverProps, EuiIcon, keyCodes, htmlIdGenerator } from '@elastic/eui';
|
||||
|
||||
import { IAggConfig } from '../../../../../../../plugins/data/public';
|
||||
import { createFiltersFromEvent } from '../../../legacy_imports';
|
||||
import { CUSTOM_LEGEND_VIS_TYPES, LegendItem } from './models';
|
||||
import { VisLegendItem } from './legend_item';
|
||||
import { getPieNames } from './pie_utils';
|
||||
|
||||
import { Vis } from '../../../../../visualizations/public';
|
||||
import { createFiltersFromEvent, tabifyGetColumns } from '../../../legacy_imports';
|
||||
|
||||
const getTableAggs = (vis: Vis): IAggConfig[] => {
|
||||
if (!vis.aggs || !vis.aggs.getResponseAggs) {
|
||||
return [];
|
||||
}
|
||||
const columns = tabifyGetColumns(vis.aggs.getResponseAggs(), !vis.isHierarchical());
|
||||
return columns.map(c => c.aggConfig);
|
||||
};
|
||||
|
||||
export interface VisLegendProps {
|
||||
vis: any;
|
||||
vislibVis: any;
|
||||
|
@ -50,7 +39,6 @@ export interface VisLegendProps {
|
|||
export interface VisLegendState {
|
||||
open: boolean;
|
||||
labels: any[];
|
||||
tableAggs: any[];
|
||||
filterableLabels: Set<string>;
|
||||
selectedLabel: string | null;
|
||||
}
|
||||
|
@ -66,7 +54,6 @@ export class VisLegend extends PureComponent<VisLegendProps, VisLegendState> {
|
|||
this.state = {
|
||||
open,
|
||||
labels: [],
|
||||
tableAggs: [],
|
||||
filterableLabels: new Set(),
|
||||
selectedLabel: null,
|
||||
};
|
||||
|
@ -200,7 +187,6 @@ export class VisLegend extends PureComponent<VisLegendProps, VisLegendState> {
|
|||
this.getColor = this.props.vislibVis.visConfig.data.getColorFunc();
|
||||
}
|
||||
|
||||
this.setState({ tableAggs: getTableAggs(this.props.vis) });
|
||||
this.setLabels(this.props.visData, vislibVis.visConfigArgs.type);
|
||||
};
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@ describe('<Visualization/>', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
vis = {
|
||||
_setUiState: function(uiState) {
|
||||
setUiState: function(uiState) {
|
||||
this.uiState = uiState;
|
||||
},
|
||||
getUiState: function() {
|
||||
|
@ -79,15 +79,6 @@ describe('<Visualization/>', () => {
|
|||
expect(wrapper.text()).toBe('No results found');
|
||||
});
|
||||
|
||||
it('should display error message when there is a request error that should be shown and no data', () => {
|
||||
const errorVis = { ...vis, requestError: { message: 'Request error' }, showRequestError: true };
|
||||
const data = null;
|
||||
const wrapper = render(
|
||||
<Visualization vis={errorVis} visData={data} listenOnChange={true} uiState={uiState} />
|
||||
);
|
||||
expect(wrapper.text()).toBe('Request error');
|
||||
});
|
||||
|
||||
it('should render chart when data is present', () => {
|
||||
const wrapper = render(
|
||||
<Visualization vis={vis} visData={visData} uiState={uiState} listenOnChange={true} />
|
||||
|
|
|
@ -23,10 +23,9 @@ import { PersistedState } from '../../../../../../../plugins/visualizations/publ
|
|||
import { memoizeLast } from '../legacy/memoize';
|
||||
import { VisualizationChart } from './visualization_chart';
|
||||
import { VisualizationNoResults } from './visualization_noresults';
|
||||
import { VisualizationRequestError } from './visualization_requesterror';
|
||||
import { Vis } from '..';
|
||||
import { ExprVis } from '../expressions/vis';
|
||||
|
||||
function shouldShowNoResultsMessage(vis: Vis, visData: any): boolean {
|
||||
function shouldShowNoResultsMessage(vis: ExprVis, visData: any): boolean {
|
||||
const requiresSearch = get(vis, 'type.requiresSearch');
|
||||
const rows: object[] | undefined = get(visData, 'rows');
|
||||
const isZeroHits = get(visData, 'hits') === 0 || (rows && !rows.length);
|
||||
|
@ -35,17 +34,11 @@ function shouldShowNoResultsMessage(vis: Vis, visData: any): boolean {
|
|||
return Boolean(requiresSearch && isZeroHits && shouldShowMessage);
|
||||
}
|
||||
|
||||
function shouldShowRequestErrorMessage(vis: Vis, visData: any): boolean {
|
||||
const requestError = get(vis, 'requestError');
|
||||
const showRequestError = get(vis, 'showRequestError');
|
||||
return Boolean(!visData && requestError && showRequestError);
|
||||
}
|
||||
|
||||
interface VisualizationProps {
|
||||
listenOnChange: boolean;
|
||||
onInit?: () => void;
|
||||
uiState: PersistedState;
|
||||
vis: Vis;
|
||||
vis: ExprVis;
|
||||
visData: any;
|
||||
visParams: any;
|
||||
}
|
||||
|
@ -56,20 +49,17 @@ export class Visualization extends React.Component<VisualizationProps> {
|
|||
constructor(props: VisualizationProps) {
|
||||
super(props);
|
||||
|
||||
props.vis._setUiState(props.uiState);
|
||||
props.vis.setUiState(props.uiState);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { vis, visData, visParams, onInit, uiState, listenOnChange } = this.props;
|
||||
|
||||
const noResults = this.showNoResultsMessage(vis, visData);
|
||||
const requestError = shouldShowRequestErrorMessage(vis, visData);
|
||||
|
||||
return (
|
||||
<div className="visualization">
|
||||
{requestError ? (
|
||||
<VisualizationRequestError onInit={onInit} error={vis.requestError} />
|
||||
) : noResults ? (
|
||||
{noResults ? (
|
||||
<VisualizationNoResults onInit={onInit} />
|
||||
) : (
|
||||
<VisualizationChart
|
||||
|
|
|
@ -21,14 +21,14 @@ import React from 'react';
|
|||
import * as Rx from 'rxjs';
|
||||
import { debounceTime, filter, share, switchMap } from 'rxjs/operators';
|
||||
import { PersistedState } from '../../../../../../../plugins/visualizations/public';
|
||||
import { Vis, VisualizationController } from '../vis';
|
||||
import { getUpdateStatus } from '../legacy/update_status';
|
||||
import { VisualizationController } from '../types';
|
||||
import { ResizeChecker } from '../../../../../../../plugins/kibana_utils/public';
|
||||
import { ExprVis } from '../expressions/vis';
|
||||
|
||||
interface VisualizationChartProps {
|
||||
onInit?: () => void;
|
||||
uiState: PersistedState;
|
||||
vis: Vis;
|
||||
vis: ExprVis;
|
||||
visData: any;
|
||||
visParams: any;
|
||||
listenOnChange: boolean;
|
||||
|
@ -40,10 +40,9 @@ class VisualizationChart extends React.Component<VisualizationChartProps> {
|
|||
private chartDiv = React.createRef<HTMLDivElement>();
|
||||
private containerDiv = React.createRef<HTMLDivElement>();
|
||||
private renderSubject: Rx.Subject<{
|
||||
vis: Vis;
|
||||
vis: ExprVis;
|
||||
visParams: any;
|
||||
visData: any;
|
||||
container: HTMLElement;
|
||||
}>;
|
||||
private renderSubscription: Rx.Subscription;
|
||||
|
||||
|
@ -54,11 +53,9 @@ class VisualizationChart extends React.Component<VisualizationChartProps> {
|
|||
const render$ = this.renderSubject.asObservable().pipe(share());
|
||||
|
||||
const success$ = render$.pipe(
|
||||
filter(
|
||||
({ vis, visData, container }) => vis && container && (!vis.type.requiresSearch || visData)
|
||||
),
|
||||
filter(({ vis, visData }) => vis && (!vis.type.requiresSearch || visData)),
|
||||
debounceTime(100),
|
||||
switchMap(async ({ vis, visData, visParams, container }) => {
|
||||
switchMap(async ({ vis, visData, visParams }) => {
|
||||
if (!this.visualization) {
|
||||
// This should never happen, since we only should trigger another rendering
|
||||
// after this component has mounted and thus the visualization implementation
|
||||
|
@ -66,15 +63,11 @@ class VisualizationChart extends React.Component<VisualizationChartProps> {
|
|||
throw new Error('Visualization implementation was not initialized on first render.');
|
||||
}
|
||||
|
||||
vis.size = [container.clientWidth, container.clientHeight];
|
||||
const status = getUpdateStatus(vis.type.requiresUpdateStatus, this, this.props);
|
||||
return this.visualization.render(visData, visParams, status);
|
||||
return this.visualization.render(visData, visParams);
|
||||
})
|
||||
);
|
||||
|
||||
const requestError$ = render$.pipe(filter(({ vis }) => vis.requestError));
|
||||
|
||||
this.renderSubscription = Rx.merge(success$, requestError$).subscribe(() => {
|
||||
this.renderSubscription = success$.subscribe(() => {
|
||||
if (this.props.onInit) {
|
||||
this.props.onInit();
|
||||
}
|
||||
|
@ -145,7 +138,6 @@ class VisualizationChart extends React.Component<VisualizationChartProps> {
|
|||
vis: this.props.vis,
|
||||
visData: this.props.visData,
|
||||
visParams: this.props.visParams,
|
||||
container: this.containerDiv.current,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,18 +28,18 @@ import { getUISettings, getSavedObjects } from '../services';
|
|||
export async function getIndexPattern(
|
||||
savedVis: VisSavedObject
|
||||
): Promise<IIndexPattern | undefined> {
|
||||
if (savedVis.vis.type.name !== 'metrics') {
|
||||
return savedVis.vis.indexPattern;
|
||||
if (savedVis.visState.type !== 'metrics') {
|
||||
return savedVis.searchSource!.getField('index');
|
||||
}
|
||||
|
||||
const savedObjectsClient = getSavedObjects().client;
|
||||
const defaultIndex = getUISettings().get('defaultIndex');
|
||||
|
||||
if (savedVis.vis.params.index_pattern) {
|
||||
if (savedVis.visState.params.index_pattern) {
|
||||
const indexPatternObjects = await savedObjectsClient.find<IndexPatternAttributes>({
|
||||
type: 'index-pattern',
|
||||
fields: ['title', 'fields'],
|
||||
search: `"${savedVis.vis.params.index_pattern}"`,
|
||||
search: `"${savedVis.visState.params.index_pattern}"`,
|
||||
searchFields: ['title'],
|
||||
});
|
||||
const [indexPattern] = indexPatternObjects.savedObjects.map(indexPatterns.getFromSavedObject);
|
||||
|
|
|
@ -45,13 +45,12 @@ import { PersistedState } from '../../../../../../../plugins/visualizations/publ
|
|||
import { buildPipeline } from '../legacy/build_pipeline';
|
||||
import { Vis } from '../vis';
|
||||
import { getExpressions, getUiActions } from '../services';
|
||||
import { VisSavedObject } from '../types';
|
||||
import { VIS_EVENT_TO_TRIGGER } from './events';
|
||||
|
||||
const getKeys = <T extends {}>(o: T): Array<keyof T> => Object.keys(o) as Array<keyof T>;
|
||||
|
||||
export interface VisualizeEmbeddableConfiguration {
|
||||
savedVisualization: VisSavedObject;
|
||||
vis: Vis;
|
||||
indexPatterns?: IIndexPattern[];
|
||||
editUrl: string;
|
||||
editable: boolean;
|
||||
|
@ -73,7 +72,6 @@ export interface VisualizeInput extends EmbeddableInput {
|
|||
export interface VisualizeOutput extends EmbeddableOutput {
|
||||
editUrl: string;
|
||||
indexPatterns?: IIndexPattern[];
|
||||
savedObjectId: string;
|
||||
visTypeName: string;
|
||||
}
|
||||
|
||||
|
@ -81,9 +79,6 @@ type ExpressionLoader = InstanceType<ExpressionsStart['ExpressionLoader']>;
|
|||
|
||||
export class VisualizeEmbeddable extends Embeddable<VisualizeInput, VisualizeOutput> {
|
||||
private handler?: ExpressionLoader;
|
||||
private savedVisualization: VisSavedObject;
|
||||
private appState: { save(): void } | undefined;
|
||||
private uiState: PersistedState;
|
||||
private timefilter: TimefilterContract;
|
||||
private timeRange?: TimeRange;
|
||||
private query?: Query;
|
||||
|
@ -99,49 +94,24 @@ export class VisualizeEmbeddable extends Embeddable<VisualizeInput, VisualizeOut
|
|||
|
||||
constructor(
|
||||
timefilter: TimefilterContract,
|
||||
{
|
||||
savedVisualization,
|
||||
editUrl,
|
||||
indexPatterns,
|
||||
editable,
|
||||
appState,
|
||||
uiState,
|
||||
}: VisualizeEmbeddableConfiguration,
|
||||
{ vis, editUrl, indexPatterns, editable }: VisualizeEmbeddableConfiguration,
|
||||
initialInput: VisualizeInput,
|
||||
parent?: Container
|
||||
) {
|
||||
super(
|
||||
initialInput,
|
||||
{
|
||||
defaultTitle: savedVisualization.title,
|
||||
defaultTitle: vis.title,
|
||||
editUrl,
|
||||
indexPatterns,
|
||||
editable,
|
||||
savedObjectId: savedVisualization.id!,
|
||||
visTypeName: savedVisualization.vis.type.name,
|
||||
visTypeName: vis.type.name,
|
||||
},
|
||||
parent
|
||||
);
|
||||
this.timefilter = timefilter;
|
||||
this.appState = appState;
|
||||
this.savedVisualization = savedVisualization;
|
||||
this.vis = this.savedVisualization.vis;
|
||||
|
||||
this.vis.on('update', this.handleVisUpdate);
|
||||
this.vis.on('reload', this.reload);
|
||||
|
||||
if (uiState) {
|
||||
this.uiState = uiState;
|
||||
} else {
|
||||
const parsedUiState = savedVisualization.uiStateJSON
|
||||
? JSON.parse(savedVisualization.uiStateJSON)
|
||||
: {};
|
||||
this.uiState = new PersistedState(parsedUiState);
|
||||
|
||||
this.uiState.on('change', this.uiStateChangeHandler);
|
||||
}
|
||||
|
||||
this.vis._setUiState(this.uiState);
|
||||
this.vis = vis;
|
||||
this.vis.uiState.on('change', this.uiStateChangeHandler);
|
||||
|
||||
this.autoRefreshFetchSubscription = timefilter
|
||||
.getAutoRefreshFetch$()
|
||||
|
@ -155,7 +125,7 @@ export class VisualizeEmbeddable extends Embeddable<VisualizeInput, VisualizeOut
|
|||
}
|
||||
|
||||
public getVisualizationDescription() {
|
||||
return this.savedVisualization.description;
|
||||
return this.vis.description;
|
||||
}
|
||||
|
||||
public getInspectorAdapters = () => {
|
||||
|
@ -184,16 +154,16 @@ export class VisualizeEmbeddable extends Embeddable<VisualizeInput, VisualizeOut
|
|||
if (!_.isEqual(visCustomizations, this.visCustomizations)) {
|
||||
this.visCustomizations = visCustomizations;
|
||||
// Turn this off or the uiStateChangeHandler will fire for every modification.
|
||||
this.uiState.off('change', this.uiStateChangeHandler);
|
||||
this.uiState.clearAllKeys();
|
||||
this.uiState.set('vis', visCustomizations);
|
||||
this.vis.uiState.off('change', this.uiStateChangeHandler);
|
||||
this.vis.uiState.clearAllKeys();
|
||||
this.vis.uiState.set('vis', visCustomizations);
|
||||
getKeys(visCustomizations).forEach(key => {
|
||||
this.uiState.set(key, visCustomizations[key]);
|
||||
this.vis.uiState.set(key, visCustomizations[key]);
|
||||
});
|
||||
this.uiState.on('change', this.uiStateChangeHandler);
|
||||
this.vis.uiState.on('change', this.uiStateChangeHandler);
|
||||
}
|
||||
} else if (!this.appState) {
|
||||
this.uiState.clearAllKeys();
|
||||
} else if (this.parent) {
|
||||
this.vis.uiState.clearAllKeys();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -227,8 +197,8 @@ export class VisualizeEmbeddable extends Embeddable<VisualizeInput, VisualizeOut
|
|||
}
|
||||
}
|
||||
|
||||
if (this.savedVisualization.description && this.domNode) {
|
||||
this.domNode.setAttribute('data-description', this.savedVisualization.description);
|
||||
if (this.vis.description && this.domNode) {
|
||||
this.domNode.setAttribute('data-description', this.vis.description);
|
||||
}
|
||||
|
||||
if (this.handler && dirty) {
|
||||
|
@ -236,6 +206,22 @@ export class VisualizeEmbeddable extends Embeddable<VisualizeInput, VisualizeOut
|
|||
}
|
||||
}
|
||||
|
||||
// this is a hack to make editor still work, will be removed once we clean up editor
|
||||
// @ts-ignore
|
||||
hasInspector = () => {
|
||||
const visTypesWithoutInspector = [
|
||||
'markdown',
|
||||
'input_control_vis',
|
||||
'metrics',
|
||||
'vega',
|
||||
'timelion',
|
||||
];
|
||||
if (visTypesWithoutInspector.includes(this.vis.type.name)) {
|
||||
return false;
|
||||
}
|
||||
return this.getInspectorAdapters();
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Element} domNode
|
||||
|
@ -245,26 +231,6 @@ export class VisualizeEmbeddable extends Embeddable<VisualizeInput, VisualizeOut
|
|||
|
||||
this.transferCustomizationsToUiState();
|
||||
|
||||
this.savedVisualization.vis._setUiState(this.uiState);
|
||||
this.uiState = this.savedVisualization.vis.getUiState();
|
||||
|
||||
// this is a hack to make editor still work, will be removed once we clean up editor
|
||||
this.vis.hasInspector = () => {
|
||||
const visTypesWithoutInspector = [
|
||||
'markdown',
|
||||
'input_control_vis',
|
||||
'metrics',
|
||||
'vega',
|
||||
'timelion',
|
||||
];
|
||||
if (visTypesWithoutInspector.includes(this.vis.type.name)) {
|
||||
return false;
|
||||
}
|
||||
return this.getInspectorAdapters();
|
||||
};
|
||||
|
||||
this.vis.openInspector = this.openInspector;
|
||||
|
||||
const div = document.createElement('div');
|
||||
div.className = `visualize panel-content panel-content--fullWidth`;
|
||||
domNode.appendChild(div);
|
||||
|
@ -277,12 +243,12 @@ export class VisualizeEmbeddable extends Embeddable<VisualizeInput, VisualizeOut
|
|||
this.handler.events$.subscribe(async event => {
|
||||
// maps hack, remove once esaggs function is cleaned up and ready to accept variables
|
||||
if (event.name === 'bounds') {
|
||||
const agg = this.vis.getAggConfig().aggs.find((a: any) => {
|
||||
const agg = this.vis.data.aggs!.aggs.find((a: any) => {
|
||||
return get(a, 'type.dslName') === 'geohash_grid';
|
||||
});
|
||||
if (
|
||||
agg.params.precision !== event.data.precision ||
|
||||
!_.isEqual(agg.params.boundingBox, event.data.boundingBox)
|
||||
(agg && agg.params.precision !== event.data.precision) ||
|
||||
(agg && !_.isEqual(agg.params.boundingBox, event.data.boundingBox))
|
||||
) {
|
||||
agg.params.boundingBox = event.data.boundingBox;
|
||||
agg.params.precision = event.data.precision;
|
||||
|
@ -296,7 +262,7 @@ export class VisualizeEmbeddable extends Embeddable<VisualizeInput, VisualizeOut
|
|||
event.name === 'brush' ? VIS_EVENT_TO_TRIGGER.brush : VIS_EVENT_TO_TRIGGER.filter;
|
||||
const context: EmbeddableVisTriggerContext = {
|
||||
embeddable: this,
|
||||
timeFieldName: this.vis.indexPattern.timeFieldName,
|
||||
timeFieldName: this.vis.data.indexPattern!.timeFieldName!,
|
||||
data: event.data,
|
||||
};
|
||||
getUiActions()
|
||||
|
@ -308,8 +274,8 @@ export class VisualizeEmbeddable extends Embeddable<VisualizeInput, VisualizeOut
|
|||
|
||||
div.setAttribute('data-title', this.output.title || '');
|
||||
|
||||
if (this.savedVisualization.description) {
|
||||
div.setAttribute('data-description', this.savedVisualization.description);
|
||||
if (this.vis.description) {
|
||||
div.setAttribute('data-description', this.vis.description);
|
||||
}
|
||||
|
||||
div.setAttribute('data-test-subj', 'visualizationLoader');
|
||||
|
@ -339,10 +305,8 @@ export class VisualizeEmbeddable extends Embeddable<VisualizeInput, VisualizeOut
|
|||
public destroy() {
|
||||
super.destroy();
|
||||
this.subscriptions.forEach(s => s.unsubscribe());
|
||||
this.uiState.off('change', this.uiStateChangeHandler);
|
||||
this.savedVisualization.vis.removeListener('reload', this.reload);
|
||||
this.savedVisualization.vis.removeListener('update', this.handleVisUpdate);
|
||||
this.savedVisualization.destroy();
|
||||
this.vis.uiState.off('change', this.uiStateChangeHandler);
|
||||
|
||||
if (this.handler) {
|
||||
this.handler.destroy();
|
||||
this.handler.getElement().remove();
|
||||
|
@ -361,35 +325,25 @@ export class VisualizeEmbeddable extends Embeddable<VisualizeInput, VisualizeOut
|
|||
query: this.input.query,
|
||||
filters: this.input.filters,
|
||||
},
|
||||
uiState: this.uiState,
|
||||
uiState: this.vis.uiState,
|
||||
};
|
||||
this.expression = await buildPipeline(this.vis, {
|
||||
searchSource: this.savedVisualization.searchSource,
|
||||
timefilter: this.timefilter,
|
||||
timeRange: this.timeRange,
|
||||
savedObjectId: this.savedVisualization.id,
|
||||
});
|
||||
|
||||
this.vis.filters = { timeRange: this.timeRange };
|
||||
|
||||
if (this.handler) {
|
||||
this.handler.update(this.expression, expressionParams);
|
||||
}
|
||||
|
||||
this.vis.emit('apply');
|
||||
}
|
||||
|
||||
private handleVisUpdate = async () => {
|
||||
if (this.appState) {
|
||||
this.appState.save();
|
||||
}
|
||||
|
||||
this.updateHandler();
|
||||
};
|
||||
|
||||
private uiStateChangeHandler = () => {
|
||||
this.updateInput({
|
||||
...this.uiState.toJSON(),
|
||||
...this.vis.uiState.toJSON(),
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -26,9 +26,8 @@ import {
|
|||
ErrorEmbeddable,
|
||||
} from '../../../../../../../plugins/embeddable/public';
|
||||
import { DisabledLabEmbeddable } from './disabled_lab_embeddable';
|
||||
import { getIndexPattern } from './get_index_pattern';
|
||||
import { VisualizeEmbeddable, VisualizeInput, VisualizeOutput } from './visualize_embeddable';
|
||||
import { VisSavedObject } from '../types';
|
||||
import { Vis } from '../types';
|
||||
import { VISUALIZE_EMBEDDABLE_TYPE } from './constants';
|
||||
import {
|
||||
getCapabilities,
|
||||
|
@ -39,6 +38,7 @@ import {
|
|||
getTimeFilter,
|
||||
} from '../services';
|
||||
import { showNewVisModal } from '../wizard';
|
||||
import { convertToSerializedVis } from '../saved_visualizations/_saved_vis';
|
||||
|
||||
interface VisualizationAttributes extends SavedObjectAttributes {
|
||||
visState: string;
|
||||
|
@ -94,31 +94,31 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory<
|
|||
}
|
||||
|
||||
public async createFromObject(
|
||||
savedObject: VisSavedObject,
|
||||
vis: Vis,
|
||||
input: Partial<VisualizeInput> & { id: string },
|
||||
parent?: Container
|
||||
): Promise<VisualizeEmbeddable | ErrorEmbeddable | DisabledLabEmbeddable> {
|
||||
const savedVisualizations = getSavedVisualizationsLoader();
|
||||
|
||||
try {
|
||||
const visId = savedObject.id as string;
|
||||
const visId = vis.id as string;
|
||||
|
||||
const editUrl = visId
|
||||
? getHttp().basePath.prepend(`/app/kibana${savedVisualizations.urlFor(visId)}`)
|
||||
: '';
|
||||
const isLabsEnabled = getUISettings().get<boolean>('visualize:enableLabs');
|
||||
|
||||
if (!isLabsEnabled && savedObject.vis.type.stage === 'experimental') {
|
||||
return new DisabledLabEmbeddable(savedObject.title, input);
|
||||
if (!isLabsEnabled && vis.type.stage === 'experimental') {
|
||||
return new DisabledLabEmbeddable(vis.title, input);
|
||||
}
|
||||
|
||||
const indexPattern = await getIndexPattern(savedObject);
|
||||
const indexPattern = vis.data.indexPattern;
|
||||
const indexPatterns = indexPattern ? [indexPattern] : [];
|
||||
const editable = await this.isEditable();
|
||||
return new VisualizeEmbeddable(
|
||||
getTimeFilter(),
|
||||
{
|
||||
savedVisualization: savedObject,
|
||||
vis,
|
||||
indexPatterns,
|
||||
editUrl,
|
||||
editable,
|
||||
|
@ -143,7 +143,8 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory<
|
|||
|
||||
try {
|
||||
const savedObject = await savedVisualizations.get(savedObjectId);
|
||||
return this.createFromObject(savedObject, input, parent);
|
||||
const vis = new Vis(savedObject.visState.type, await convertToSerializedVis(savedObject));
|
||||
return this.createFromObject(vis, input, parent);
|
||||
} catch (e) {
|
||||
console.error(e); // eslint-disable-line no-console
|
||||
return new ErrorEmbeddable(e, input, parent);
|
||||
|
|
|
@ -32,25 +32,47 @@ import _ from 'lodash';
|
|||
import { PersistedState } from '../../../../../../../plugins/visualizations/public';
|
||||
|
||||
import { getTypes } from '../services';
|
||||
import { VisType } from '../vis_types';
|
||||
import { VisParams } from '../types';
|
||||
|
||||
export class Vis extends EventEmitter {
|
||||
constructor(visState = { type: 'histogram' }) {
|
||||
export interface ExprVisState {
|
||||
title?: string;
|
||||
type: VisType | string;
|
||||
params?: VisParams;
|
||||
}
|
||||
|
||||
export interface ExprVisAPIEvents {
|
||||
filter: (data: any) => void;
|
||||
brush: (data: any) => void;
|
||||
}
|
||||
|
||||
export interface ExprVisAPI {
|
||||
events: ExprVisAPIEvents;
|
||||
}
|
||||
|
||||
export class ExprVis extends EventEmitter {
|
||||
public title: string = '';
|
||||
public type: VisType;
|
||||
public params: VisParams = {};
|
||||
public sessionState: Record<string, any> = {};
|
||||
public API: ExprVisAPI;
|
||||
public eventsSubject: any;
|
||||
private uiState: PersistedState;
|
||||
|
||||
constructor(visState: ExprVisState = { type: 'histogram' }) {
|
||||
super();
|
||||
|
||||
this._setUiState(new PersistedState());
|
||||
this.type = this.getType(visState.type);
|
||||
this.uiState = new PersistedState();
|
||||
this.setState(visState);
|
||||
|
||||
// Session state is for storing information that is transitory, and will not be saved with the visualization.
|
||||
// For instance, map bounds, which depends on the view port, browser window size, etc.
|
||||
this.sessionState = {};
|
||||
|
||||
this.API = {
|
||||
events: {
|
||||
filter: data => {
|
||||
filter: (data: any) => {
|
||||
if (!this.eventsSubject) return;
|
||||
this.eventsSubject.next({ name: 'filterBucket', data });
|
||||
},
|
||||
brush: data => {
|
||||
brush: (data: any) => {
|
||||
if (!this.eventsSubject) return;
|
||||
this.eventsSubject.next({ name: 'brush', data });
|
||||
},
|
||||
|
@ -58,18 +80,22 @@ export class Vis extends EventEmitter {
|
|||
};
|
||||
}
|
||||
|
||||
setState(state) {
|
||||
this.title = state.title || '';
|
||||
const type = state.type || this.type;
|
||||
private getType(type: string | VisType) {
|
||||
if (_.isString(type)) {
|
||||
this.type = getTypes().get(type);
|
||||
return getTypes().get(type);
|
||||
if (!this.type) {
|
||||
throw new Error(`Invalid type "${type}"`);
|
||||
}
|
||||
} else {
|
||||
this.type = type;
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
setState(state: ExprVisState) {
|
||||
this.title = state.title || '';
|
||||
if (state.type) {
|
||||
this.type = this.getType(state.type);
|
||||
}
|
||||
this.params = _.defaultsDeep(
|
||||
{},
|
||||
_.cloneDeep(state.params || {}),
|
||||
|
@ -77,10 +103,6 @@ export class Vis extends EventEmitter {
|
|||
);
|
||||
}
|
||||
|
||||
setCurrentState(state) {
|
||||
this.setState(state);
|
||||
}
|
||||
|
||||
getState() {
|
||||
return {
|
||||
title: this.title,
|
||||
|
@ -106,34 +128,27 @@ export class Vis extends EventEmitter {
|
|||
}
|
||||
|
||||
hasUiState() {
|
||||
return !!this.__uiState;
|
||||
}
|
||||
|
||||
/***
|
||||
* this should not be used outside of visualize
|
||||
* @param uiState
|
||||
* @private
|
||||
*/
|
||||
_setUiState(uiState) {
|
||||
if (uiState instanceof PersistedState) {
|
||||
this.__uiState = uiState;
|
||||
}
|
||||
return !!this.uiState;
|
||||
}
|
||||
|
||||
getUiState() {
|
||||
return this.__uiState;
|
||||
return this.uiState;
|
||||
}
|
||||
|
||||
setUiState(state: PersistedState) {
|
||||
this.uiState = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently this is only used to extract map-specific information
|
||||
* (e.g. mapZoom, mapCenter).
|
||||
*/
|
||||
uiStateVal(key, val) {
|
||||
uiStateVal(key: string, val: any) {
|
||||
if (this.hasUiState()) {
|
||||
if (_.isUndefined(val)) {
|
||||
return this.__uiState.get(key);
|
||||
return this.uiState.get(key);
|
||||
}
|
||||
return this.__uiState.set(key, val);
|
||||
return this.uiState.set(key, val);
|
||||
}
|
||||
return val;
|
||||
}
|
|
@ -20,8 +20,9 @@
|
|||
import React from 'react';
|
||||
import { render, unmountComponentAtNode } from 'react-dom';
|
||||
// @ts-ignore
|
||||
import { Vis } from './vis';
|
||||
import { ExprVis } from './vis';
|
||||
import { Visualization } from '../components';
|
||||
import { VisParams } from '../types';
|
||||
|
||||
export const visualization = () => ({
|
||||
name: 'visualization',
|
||||
|
@ -31,9 +32,9 @@ export const visualization = () => ({
|
|||
const { visData, visConfig, params } = config;
|
||||
const visType = config.visType || visConfig.type;
|
||||
|
||||
const vis = new Vis({
|
||||
type: visType,
|
||||
params: visConfig,
|
||||
const vis = new ExprVis({
|
||||
type: visType as string,
|
||||
params: visConfig as VisParams,
|
||||
});
|
||||
|
||||
vis.eventsSubject = { next: handlers.event };
|
||||
|
|
|
@ -39,12 +39,11 @@ export { VisualizationsSetup, VisualizationsStart };
|
|||
/** @public types */
|
||||
export { VisTypeAlias, VisType } from './vis_types';
|
||||
export { VisSavedObject } from './types';
|
||||
export { Vis, VisParams, VisState } from './vis';
|
||||
export { Vis, VisParams, SerializedVis, SerializedVisData, VisData } from './vis';
|
||||
import { VisualizeEmbeddableFactory, VisualizeEmbeddable } from './embeddable';
|
||||
export type VisualizeEmbeddableFactoryContract = PublicContract<VisualizeEmbeddableFactory>;
|
||||
export type VisualizeEmbeddableContract = PublicContract<VisualizeEmbeddable>;
|
||||
export { TypesService } from './vis_types/types_service';
|
||||
export { Status } from './legacy/update_status'; // should remove
|
||||
export { VISUALIZE_EMBEDDABLE_TYPE, VisualizeInput } from './embeddable';
|
||||
export { SchemaConfig } from './legacy/build_pipeline';
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ import {
|
|||
Schemas,
|
||||
} from './build_pipeline';
|
||||
import { Vis } from '..';
|
||||
import { searchSourceMock, dataPluginMock } from '../../../../../../../plugins/data/public/mocks';
|
||||
import { dataPluginMock } from '../../../../../../../plugins/data/public/mocks';
|
||||
import { IAggConfig } from '../../../../../../../plugins/data/public';
|
||||
|
||||
jest.mock('ui/new_platform');
|
||||
|
@ -78,19 +78,11 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
|
|||
});
|
||||
|
||||
describe('buildPipelineVisFunction', () => {
|
||||
let visStateDef: ReturnType<Vis['getCurrentState']>;
|
||||
let schemaConfig: SchemaConfig;
|
||||
let schemasDef: Schemas;
|
||||
let uiState: any;
|
||||
|
||||
beforeEach(() => {
|
||||
visStateDef = {
|
||||
title: 'title',
|
||||
// @ts-ignore
|
||||
type: 'type',
|
||||
params: {},
|
||||
} as ReturnType<Vis['getCurrentState']>;
|
||||
|
||||
schemaConfig = {
|
||||
accessor: 0,
|
||||
label: '',
|
||||
|
@ -105,66 +97,53 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
|
|||
|
||||
it('handles vega function', () => {
|
||||
const vis = {
|
||||
...visStateDef,
|
||||
params: { spec: 'this is a test' },
|
||||
};
|
||||
const actual = buildPipelineVisFunction.vega(vis, schemasDef, uiState);
|
||||
const actual = buildPipelineVisFunction.vega(vis.params, schemasDef, uiState);
|
||||
expect(actual).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('handles input_control_vis function', () => {
|
||||
const visState = {
|
||||
...visStateDef,
|
||||
params: {
|
||||
some: 'nested',
|
||||
data: { here: true },
|
||||
},
|
||||
const params = {
|
||||
some: 'nested',
|
||||
data: { here: true },
|
||||
};
|
||||
const actual = buildPipelineVisFunction.input_control_vis(visState, schemasDef, uiState);
|
||||
const actual = buildPipelineVisFunction.input_control_vis(params, schemasDef, uiState);
|
||||
expect(actual).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('handles metrics/tsvb function', () => {
|
||||
const visState = { ...visStateDef, params: { foo: 'bar' } };
|
||||
const actual = buildPipelineVisFunction.metrics(visState, schemasDef, uiState);
|
||||
const params = { foo: 'bar' };
|
||||
const actual = buildPipelineVisFunction.metrics(params, schemasDef, uiState);
|
||||
expect(actual).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('handles timelion function', () => {
|
||||
const visState = {
|
||||
...visStateDef,
|
||||
params: { expression: 'foo', interval: 'bar' },
|
||||
};
|
||||
const actual = buildPipelineVisFunction.timelion(visState, schemasDef, uiState);
|
||||
const params = { expression: 'foo', interval: 'bar' };
|
||||
const actual = buildPipelineVisFunction.timelion(params, schemasDef, uiState);
|
||||
expect(actual).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('handles markdown function', () => {
|
||||
const visState = {
|
||||
...visStateDef,
|
||||
params: {
|
||||
markdown: '## hello _markdown_',
|
||||
fontSize: 12,
|
||||
openLinksInNewTab: true,
|
||||
foo: 'bar',
|
||||
},
|
||||
const params = {
|
||||
markdown: '## hello _markdown_',
|
||||
fontSize: 12,
|
||||
openLinksInNewTab: true,
|
||||
foo: 'bar',
|
||||
};
|
||||
const actual = buildPipelineVisFunction.markdown(visState, schemasDef, uiState);
|
||||
const actual = buildPipelineVisFunction.markdown(params, schemasDef, uiState);
|
||||
expect(actual).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('handles undefined markdown function', () => {
|
||||
const visState = {
|
||||
...visStateDef,
|
||||
params: { fontSize: 12, openLinksInNewTab: true, foo: 'bar' },
|
||||
};
|
||||
const actual = buildPipelineVisFunction.markdown(visState, schemasDef, uiState);
|
||||
const params = { fontSize: 12, openLinksInNewTab: true, foo: 'bar' };
|
||||
const actual = buildPipelineVisFunction.markdown(params, schemasDef, uiState);
|
||||
expect(actual).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe('handles table function', () => {
|
||||
it('without splits or buckets', () => {
|
||||
const visState = { ...visStateDef, params: { foo: 'bar' } };
|
||||
const params = { foo: 'bar' };
|
||||
const schemas = {
|
||||
...schemasDef,
|
||||
metric: [
|
||||
|
@ -172,22 +151,22 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
|
|||
{ ...schemaConfig, accessor: 1 },
|
||||
],
|
||||
};
|
||||
const actual = buildPipelineVisFunction.table(visState, schemas, uiState);
|
||||
const actual = buildPipelineVisFunction.table(params, schemas, uiState);
|
||||
expect(actual).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('with splits', () => {
|
||||
const visState = { ...visStateDef, params: { foo: 'bar' } };
|
||||
const params = { foo: 'bar' };
|
||||
const schemas = {
|
||||
...schemasDef,
|
||||
split_row: [1, 2],
|
||||
};
|
||||
const actual = buildPipelineVisFunction.table(visState, schemas, uiState);
|
||||
const actual = buildPipelineVisFunction.table(params, schemas, uiState);
|
||||
expect(actual).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('with splits and buckets', () => {
|
||||
const visState = { ...visStateDef, params: { foo: 'bar' } };
|
||||
const params = { foo: 'bar' };
|
||||
const schemas = {
|
||||
...schemasDef,
|
||||
metric: [
|
||||
|
@ -197,17 +176,14 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
|
|||
split_row: [2, 4],
|
||||
bucket: [3],
|
||||
};
|
||||
const actual = buildPipelineVisFunction.table(visState, schemas, uiState);
|
||||
const actual = buildPipelineVisFunction.table(params, schemas, uiState);
|
||||
expect(actual).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('with showPartialRows=true and showMetricsAtAllLevels=true', () => {
|
||||
const visState = {
|
||||
...visStateDef,
|
||||
params: {
|
||||
showMetricsAtAllLevels: true,
|
||||
showPartialRows: true,
|
||||
},
|
||||
const params = {
|
||||
showMetricsAtAllLevels: true,
|
||||
showPartialRows: true,
|
||||
};
|
||||
const schemas = {
|
||||
...schemasDef,
|
||||
|
@ -219,17 +195,14 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
|
|||
],
|
||||
bucket: [0, 3],
|
||||
};
|
||||
const actual = buildPipelineVisFunction.table(visState, schemas, uiState);
|
||||
const actual = buildPipelineVisFunction.table(params, schemas, uiState);
|
||||
expect(actual).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('with showPartialRows=true and showMetricsAtAllLevels=false', () => {
|
||||
const visState = {
|
||||
...visStateDef,
|
||||
params: {
|
||||
showMetricsAtAllLevels: false,
|
||||
showPartialRows: true,
|
||||
},
|
||||
const params = {
|
||||
showMetricsAtAllLevels: false,
|
||||
showPartialRows: true,
|
||||
};
|
||||
const schemas = {
|
||||
...schemasDef,
|
||||
|
@ -241,14 +214,14 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
|
|||
],
|
||||
bucket: [0, 3],
|
||||
};
|
||||
const actual = buildPipelineVisFunction.table(visState, schemas, uiState);
|
||||
const actual = buildPipelineVisFunction.table(params, schemas, uiState);
|
||||
expect(actual).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('handles metric function', () => {
|
||||
it('without buckets', () => {
|
||||
const visState = { ...visStateDef, params: { metric: {} } };
|
||||
const params = { metric: {} };
|
||||
const schemas = {
|
||||
...schemasDef,
|
||||
metric: [
|
||||
|
@ -256,12 +229,12 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
|
|||
{ ...schemaConfig, accessor: 1 },
|
||||
],
|
||||
};
|
||||
const actual = buildPipelineVisFunction.metric(visState, schemas, uiState);
|
||||
const actual = buildPipelineVisFunction.metric(params, schemas, uiState);
|
||||
expect(actual).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('with buckets', () => {
|
||||
const visState = { ...visStateDef, params: { metric: {} } };
|
||||
const params = { metric: {} };
|
||||
const schemas = {
|
||||
...schemasDef,
|
||||
metric: [
|
||||
|
@ -270,21 +243,21 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
|
|||
],
|
||||
group: [{ accessor: 2 }],
|
||||
};
|
||||
const actual = buildPipelineVisFunction.metric(visState, schemas, uiState);
|
||||
const actual = buildPipelineVisFunction.metric(params, schemas, uiState);
|
||||
expect(actual).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('with percentage mode should have percentage format', () => {
|
||||
const visState = { ...visStateDef, params: { metric: { percentageMode: true } } };
|
||||
const params = { metric: { percentageMode: true } };
|
||||
const schemas = { ...schemasDef };
|
||||
const actual = buildPipelineVisFunction.metric(visState, schemas, uiState);
|
||||
const actual = buildPipelineVisFunction.metric(params, schemas, uiState);
|
||||
expect(actual).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('handles tagcloud function', () => {
|
||||
it('without buckets', () => {
|
||||
const actual = buildPipelineVisFunction.tagcloud(visStateDef, schemasDef, uiState);
|
||||
const actual = buildPipelineVisFunction.tagcloud({}, schemasDef, uiState);
|
||||
expect(actual).toMatchSnapshot();
|
||||
});
|
||||
|
||||
|
@ -293,21 +266,21 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
|
|||
...schemasDef,
|
||||
segment: [{ accessor: 1 }],
|
||||
};
|
||||
const actual = buildPipelineVisFunction.tagcloud(visStateDef, schemas, uiState);
|
||||
const actual = buildPipelineVisFunction.tagcloud({}, schemas, uiState);
|
||||
expect(actual).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('with boolean param showLabel', () => {
|
||||
const visState = { ...visStateDef, params: { showLabel: false } };
|
||||
const actual = buildPipelineVisFunction.tagcloud(visState, schemasDef, uiState);
|
||||
const params = { showLabel: false };
|
||||
const actual = buildPipelineVisFunction.tagcloud(params, schemasDef, uiState);
|
||||
expect(actual).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('handles region_map function', () => {
|
||||
it('without buckets', () => {
|
||||
const visState = { ...visStateDef, params: { metric: {} } };
|
||||
const actual = buildPipelineVisFunction.region_map(visState, schemasDef, uiState);
|
||||
const params = { metric: {} };
|
||||
const actual = buildPipelineVisFunction.region_map(params, schemasDef, uiState);
|
||||
expect(actual).toMatchSnapshot();
|
||||
});
|
||||
|
||||
|
@ -316,19 +289,19 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
|
|||
...schemasDef,
|
||||
segment: [1, 2],
|
||||
};
|
||||
const actual = buildPipelineVisFunction.region_map(visStateDef, schemas, uiState);
|
||||
const actual = buildPipelineVisFunction.region_map({}, schemas, uiState);
|
||||
expect(actual).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
it('handles tile_map function', () => {
|
||||
const visState = { ...visStateDef, params: { metric: {} } };
|
||||
const params = { metric: {} };
|
||||
const schemas = {
|
||||
...schemasDef,
|
||||
segment: [1, 2],
|
||||
geo_centroid: [3, 4],
|
||||
};
|
||||
const actual = buildPipelineVisFunction.tile_map(visState, schemas, uiState);
|
||||
const actual = buildPipelineVisFunction.tile_map(params, schemas, uiState);
|
||||
expect(actual).toMatchSnapshot();
|
||||
});
|
||||
|
||||
|
@ -337,7 +310,7 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
|
|||
...schemasDef,
|
||||
segment: [1, 2],
|
||||
};
|
||||
const actual = buildPipelineVisFunction.pie(visStateDef, schemas, uiState);
|
||||
const actual = buildPipelineVisFunction.pie({}, schemas, uiState);
|
||||
expect(actual).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
@ -347,11 +320,16 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
|
|||
|
||||
it('calls toExpression on vis_type if it exists', async () => {
|
||||
const vis = ({
|
||||
getCurrentState: () => {},
|
||||
getUiState: () => null,
|
||||
getState: () => {},
|
||||
isHierarchical: () => false,
|
||||
aggs: {
|
||||
getResponseAggs: () => [],
|
||||
data: {
|
||||
aggs: {
|
||||
getResponseAggs: () => [],
|
||||
},
|
||||
searchSource: {
|
||||
getField: jest.fn(),
|
||||
getParent: jest.fn(),
|
||||
},
|
||||
},
|
||||
// @ts-ignore
|
||||
type: {
|
||||
|
@ -359,7 +337,6 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
|
|||
},
|
||||
} as unknown) as Vis;
|
||||
const expression = await buildPipeline(vis, {
|
||||
searchSource: searchSourceMock,
|
||||
timefilter: dataStart.query.timefilter.timefilter,
|
||||
});
|
||||
expect(expression).toMatchSnapshot();
|
||||
|
@ -370,7 +347,6 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
|
|||
const dataStart = dataPluginMock.createStartContract();
|
||||
|
||||
let aggs: IAggConfig[];
|
||||
let visState: any;
|
||||
let vis: Vis;
|
||||
let params: any;
|
||||
|
||||
|
@ -397,7 +373,11 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
|
|||
|
||||
describe('test y dimension format for histogram chart', () => {
|
||||
beforeEach(() => {
|
||||
visState = {
|
||||
vis = {
|
||||
// @ts-ignore
|
||||
type: {
|
||||
name: 'histogram',
|
||||
},
|
||||
params: {
|
||||
seriesParams: [
|
||||
{
|
||||
|
@ -414,24 +394,16 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
|
|||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
vis = {
|
||||
// @ts-ignore
|
||||
type: {
|
||||
name: 'histogram',
|
||||
},
|
||||
aggs: {
|
||||
getResponseAggs: () => {
|
||||
return aggs;
|
||||
},
|
||||
data: {
|
||||
aggs: {
|
||||
getResponseAggs: () => {
|
||||
return aggs;
|
||||
},
|
||||
} as any,
|
||||
},
|
||||
isHierarchical: () => {
|
||||
return false;
|
||||
},
|
||||
getCurrentState: () => {
|
||||
return visState;
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -443,7 +415,7 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
|
|||
});
|
||||
|
||||
it('with one numeric metric in percentage mode', async () => {
|
||||
visState.params.valueAxes[0].scale.mode = 'percentage';
|
||||
vis.params.valueAxes[0].scale.mode = 'percentage';
|
||||
const dimensions = await buildVislibDimensions(vis, params);
|
||||
const expected = { id: 'percent' };
|
||||
const actual = dimensions.y[0].format;
|
||||
|
@ -454,33 +426,31 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
|
|||
const aggConfig = aggs[0];
|
||||
aggs = [{ ...aggConfig } as IAggConfig, { ...aggConfig, id: '5' } as IAggConfig];
|
||||
|
||||
visState = {
|
||||
params: {
|
||||
seriesParams: [
|
||||
{
|
||||
data: { id: '0' },
|
||||
valueAxis: 'axis-y-1',
|
||||
vis.params = {
|
||||
seriesParams: [
|
||||
{
|
||||
data: { id: '0' },
|
||||
valueAxis: 'axis-y-1',
|
||||
},
|
||||
{
|
||||
data: { id: '5' },
|
||||
valueAxis: 'axis-y-2',
|
||||
},
|
||||
],
|
||||
valueAxes: [
|
||||
{
|
||||
id: 'axis-y-1',
|
||||
scale: {
|
||||
mode: 'normal',
|
||||
},
|
||||
{
|
||||
data: { id: '5' },
|
||||
valueAxis: 'axis-y-2',
|
||||
},
|
||||
{
|
||||
id: 'axis-y-2',
|
||||
scale: {
|
||||
mode: 'percentage',
|
||||
},
|
||||
],
|
||||
valueAxes: [
|
||||
{
|
||||
id: 'axis-y-1',
|
||||
scale: {
|
||||
mode: 'normal',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'axis-y-2',
|
||||
scale: {
|
||||
mode: 'percentage',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const dimensions = await buildVislibDimensions(vis, params);
|
||||
|
@ -493,29 +463,27 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
|
|||
|
||||
describe('test y dimension format for gauge chart', () => {
|
||||
beforeEach(() => {
|
||||
visState = { params: { gauge: {} } };
|
||||
|
||||
vis = {
|
||||
// @ts-ignore
|
||||
type: {
|
||||
name: 'gauge',
|
||||
},
|
||||
aggs: {
|
||||
getResponseAggs: () => {
|
||||
return aggs;
|
||||
},
|
||||
params: { gauge: {} },
|
||||
data: {
|
||||
aggs: {
|
||||
getResponseAggs: () => {
|
||||
return aggs;
|
||||
},
|
||||
} as any,
|
||||
},
|
||||
isHierarchical: () => {
|
||||
return false;
|
||||
},
|
||||
getCurrentState: () => {
|
||||
return visState;
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
it('with percentageMode = false', async () => {
|
||||
visState.params.gauge.percentageMode = false;
|
||||
vis.params.gauge.percentageMode = false;
|
||||
const dimensions = await buildVislibDimensions(vis, params);
|
||||
const expected = { id: 'number' };
|
||||
const actual = dimensions.y[0].format;
|
||||
|
@ -523,7 +491,7 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
|
|||
});
|
||||
|
||||
it('with percentageMode = true', async () => {
|
||||
visState.params.gauge.percentageMode = true;
|
||||
vis.params.gauge.percentageMode = true;
|
||||
const dimensions = await buildVislibDimensions(vis, params);
|
||||
const expected = { id: 'percent' };
|
||||
const actual = dimensions.y[0].format;
|
||||
|
|
|
@ -21,14 +21,13 @@ import { get } from 'lodash';
|
|||
import moment from 'moment';
|
||||
import { SerializedFieldFormat } from '../../../../../../../plugins/expressions/public';
|
||||
import {
|
||||
fieldFormats,
|
||||
IAggConfig,
|
||||
ISearchSource,
|
||||
fieldFormats,
|
||||
search,
|
||||
TimefilterContract,
|
||||
} from '../../../../../../../plugins/data/public';
|
||||
const { isDateHistogramBucketAggConfig } = search.aggs;
|
||||
import { Vis, VisParams } from '../types';
|
||||
const { isDateHistogramBucketAggConfig } = search.aggs;
|
||||
|
||||
interface SchemaConfigParams {
|
||||
precision?: number;
|
||||
|
@ -59,7 +58,7 @@ export interface Schemas {
|
|||
}
|
||||
|
||||
type buildVisFunction = (
|
||||
visState: ReturnType<Vis['getCurrentState']>,
|
||||
params: VisParams,
|
||||
schemas: Schemas,
|
||||
uiState: any,
|
||||
meta?: { savedObjectId?: string }
|
||||
|
@ -139,7 +138,12 @@ const getSchemas = (
|
|||
const schemas: Schemas = {
|
||||
metric: [],
|
||||
};
|
||||
const responseAggs = vis.aggs.getResponseAggs().filter((agg: IAggConfig) => agg.enabled);
|
||||
|
||||
if (!vis.data.aggs) {
|
||||
return schemas;
|
||||
}
|
||||
|
||||
const responseAggs = vis.data.aggs.getResponseAggs().filter((agg: IAggConfig) => agg.enabled);
|
||||
const isHierarchical = vis.isHierarchical();
|
||||
const metrics = responseAggs.filter((agg: IAggConfig) => agg.type.type === 'metrics');
|
||||
responseAggs.forEach((agg: IAggConfig) => {
|
||||
|
@ -228,9 +232,8 @@ export const prepareDimension = (variable: string, data: any) => {
|
|||
};
|
||||
|
||||
const adjustVislibDimensionFormmaters = (vis: Vis, dimensions: { y: any[] }): void => {
|
||||
const visState = vis.getCurrentState();
|
||||
const visConfig = visState.params;
|
||||
const responseAggs = vis.aggs.getResponseAggs().filter((agg: IAggConfig) => agg.enabled);
|
||||
const visConfig = vis.params;
|
||||
const responseAggs = vis.data.aggs!.getResponseAggs().filter((agg: IAggConfig) => agg.enabled);
|
||||
|
||||
(dimensions.y || []).forEach(yDimension => {
|
||||
const yAgg = responseAggs[yDimension.accessor];
|
||||
|
@ -252,27 +255,26 @@ const adjustVislibDimensionFormmaters = (vis: Vis, dimensions: { y: any[] }): vo
|
|||
};
|
||||
|
||||
export const buildPipelineVisFunction: BuildPipelineVisFunction = {
|
||||
vega: visState => {
|
||||
return `vega ${prepareString('spec', visState.params.spec)}`;
|
||||
vega: params => {
|
||||
return `vega ${prepareString('spec', params.spec)}`;
|
||||
},
|
||||
input_control_vis: visState => {
|
||||
return `input_control_vis ${prepareJson('visConfig', visState.params)}`;
|
||||
input_control_vis: params => {
|
||||
return `input_control_vis ${prepareJson('visConfig', params)}`;
|
||||
},
|
||||
metrics: (visState, schemas, uiState = {}, meta) => {
|
||||
const paramsJson = prepareJson('params', visState.params);
|
||||
metrics: (params, schemas, uiState = {}) => {
|
||||
const paramsJson = prepareJson('params', params);
|
||||
const uiStateJson = prepareJson('uiState', uiState);
|
||||
const savedObjectIdParam = prepareString('savedObjectId', meta?.savedObjectId);
|
||||
|
||||
const params = [paramsJson, uiStateJson, savedObjectIdParam].filter(param => Boolean(param));
|
||||
return `tsvb ${params.join(' ')}`;
|
||||
const paramsArray = [paramsJson, uiStateJson].filter(param => Boolean(param));
|
||||
return `tsvb ${paramsArray.join(' ')}`;
|
||||
},
|
||||
timelion: visState => {
|
||||
const expression = prepareString('expression', visState.params.expression);
|
||||
const interval = prepareString('interval', visState.params.interval);
|
||||
timelion: params => {
|
||||
const expression = prepareString('expression', params.expression);
|
||||
const interval = prepareString('interval', params.interval);
|
||||
return `timelion_vis ${expression}${interval}`;
|
||||
},
|
||||
markdown: visState => {
|
||||
const { markdown, fontSize, openLinksInNewTab } = visState.params;
|
||||
markdown: params => {
|
||||
const { markdown, fontSize, openLinksInNewTab } = params;
|
||||
let escapedMarkdown = '';
|
||||
if (typeof markdown === 'string' || markdown instanceof String) {
|
||||
escapedMarkdown = escapeString(markdown.toString());
|
||||
|
@ -282,14 +284,14 @@ export const buildPipelineVisFunction: BuildPipelineVisFunction = {
|
|||
expr += prepareValue('openLinksInNewTab', openLinksInNewTab);
|
||||
return expr;
|
||||
},
|
||||
table: (visState, schemas) => {
|
||||
table: (params, schemas) => {
|
||||
const visConfig = {
|
||||
...visState.params,
|
||||
...buildVisConfig.table(schemas, visState.params),
|
||||
...params,
|
||||
...buildVisConfig.table(schemas, params),
|
||||
};
|
||||
return `kibana_table ${prepareJson('visConfig', visConfig)}`;
|
||||
},
|
||||
metric: (visState, schemas) => {
|
||||
metric: (params, schemas) => {
|
||||
const {
|
||||
percentageMode,
|
||||
useRanges,
|
||||
|
@ -299,11 +301,11 @@ export const buildPipelineVisFunction: BuildPipelineVisFunction = {
|
|||
labels,
|
||||
invertColors,
|
||||
style,
|
||||
} = visState.params.metric;
|
||||
} = params.metric;
|
||||
const { metrics, bucket } = buildVisConfig.metric(schemas).dimensions;
|
||||
|
||||
// fix formatter for percentage mode
|
||||
if (get(visState.params, 'metric.percentageMode') === true) {
|
||||
if (get(params, 'metric.percentageMode') === true) {
|
||||
metrics.forEach((metric: SchemaConfig) => {
|
||||
metric.format = { id: 'percent' };
|
||||
});
|
||||
|
@ -335,8 +337,8 @@ export const buildPipelineVisFunction: BuildPipelineVisFunction = {
|
|||
|
||||
return expr;
|
||||
},
|
||||
tagcloud: (visState, schemas) => {
|
||||
const { scale, orientation, minFontSize, maxFontSize, showLabel } = visState.params;
|
||||
tagcloud: (params, schemas) => {
|
||||
const { scale, orientation, minFontSize, maxFontSize, showLabel } = params;
|
||||
const { metric, bucket } = buildVisConfig.tagcloud(schemas);
|
||||
let expr = `tagcloud metric={visdimension ${metric.accessor}} `;
|
||||
expr += prepareValue('scale', scale);
|
||||
|
@ -348,23 +350,23 @@ export const buildPipelineVisFunction: BuildPipelineVisFunction = {
|
|||
|
||||
return expr;
|
||||
},
|
||||
region_map: (visState, schemas) => {
|
||||
region_map: (params, schemas) => {
|
||||
const visConfig = {
|
||||
...visState.params,
|
||||
...params,
|
||||
...buildVisConfig.region_map(schemas),
|
||||
};
|
||||
return `regionmap ${prepareJson('visConfig', visConfig)}`;
|
||||
},
|
||||
tile_map: (visState, schemas) => {
|
||||
tile_map: (params, schemas) => {
|
||||
const visConfig = {
|
||||
...visState.params,
|
||||
...params,
|
||||
...buildVisConfig.tile_map(schemas),
|
||||
};
|
||||
return `tilemap ${prepareJson('visConfig', visConfig)}`;
|
||||
},
|
||||
pie: (visState, schemas) => {
|
||||
pie: (params, schemas) => {
|
||||
const visConfig = {
|
||||
...visState.params,
|
||||
...params,
|
||||
...buildVisConfig.pie(schemas),
|
||||
};
|
||||
return `kibana_pie ${prepareJson('visConfig', visConfig)}`;
|
||||
|
@ -440,7 +442,6 @@ const buildVisConfig: BuildVisConfigFunction = {
|
|||
export const buildVislibDimensions = async (
|
||||
vis: any,
|
||||
params: {
|
||||
searchSource: any;
|
||||
timefilter: TimefilterContract;
|
||||
timeRange?: any;
|
||||
abortSignal?: AbortSignal;
|
||||
|
@ -460,7 +461,7 @@ export const buildVislibDimensions = async (
|
|||
splitColumn: schemas.split_column,
|
||||
};
|
||||
if (schemas.segment) {
|
||||
const xAgg = vis.aggs.getResponseAggs()[dimensions.x.accessor];
|
||||
const xAgg = vis.data.aggs.getResponseAggs()[dimensions.x.accessor];
|
||||
if (xAgg.type.name === 'date_histogram') {
|
||||
dimensions.x.params.date = true;
|
||||
const { esUnit, esValue } = xAgg.buckets.getInterval();
|
||||
|
@ -472,7 +473,7 @@ export const buildVislibDimensions = async (
|
|||
} else if (xAgg.type.name === 'histogram') {
|
||||
const intervalParam = xAgg.type.paramByName('interval');
|
||||
const output = { params: {} as any };
|
||||
await intervalParam.modifyAggConfigOnSearchRequestStart(xAgg, params.searchSource, {
|
||||
await intervalParam.modifyAggConfigOnSearchRequestStart(xAgg, vis.data.searchSource, {
|
||||
abortSignal: params.abortSignal,
|
||||
});
|
||||
intervalParam.write(xAgg, output);
|
||||
|
@ -487,18 +488,14 @@ export const buildVislibDimensions = async (
|
|||
export const buildPipeline = async (
|
||||
vis: Vis,
|
||||
params: {
|
||||
searchSource: ISearchSource;
|
||||
timefilter: TimefilterContract;
|
||||
timeRange?: any;
|
||||
savedObjectId?: string;
|
||||
}
|
||||
) => {
|
||||
const { searchSource } = params;
|
||||
const { indexPattern } = vis;
|
||||
const query = searchSource.getField('query');
|
||||
const filters = searchSource.getField('filter');
|
||||
const visState = vis.getCurrentState();
|
||||
const uiState = vis.getUiState();
|
||||
const { indexPattern, searchSource } = vis.data;
|
||||
const query = searchSource!.getField('query');
|
||||
const filters = searchSource!.getField('filter');
|
||||
const { uiState } = vis;
|
||||
|
||||
// context
|
||||
let pipeline = `kibana | kibana_context `;
|
||||
|
@ -508,18 +505,18 @@ export const buildPipeline = async (
|
|||
if (filters) {
|
||||
pipeline += prepareJson('filters', filters);
|
||||
}
|
||||
if (vis.savedSearchId) {
|
||||
pipeline += prepareString('savedSearchId', vis.savedSearchId);
|
||||
if (vis.data.savedSearchId) {
|
||||
pipeline += prepareString('savedSearchId', vis.data.savedSearchId);
|
||||
}
|
||||
pipeline += '| ';
|
||||
|
||||
// request handler
|
||||
if (vis.type.requestHandler === 'courier') {
|
||||
pipeline += `esaggs
|
||||
${prepareString('index', indexPattern.id)}
|
||||
${prepareString('index', indexPattern!.id)}
|
||||
metricsAtAllLevels=${vis.isHierarchical()}
|
||||
partialRows=${vis.type.requiresPartialRows || vis.params.showPartialRows || false}
|
||||
${prepareJson('aggConfigs', visState.aggs)} | `;
|
||||
${prepareJson('aggConfigs', vis.data.aggs!.aggs)} | `;
|
||||
}
|
||||
|
||||
const schemas = getSchemas(vis, {
|
||||
|
@ -527,18 +524,16 @@ export const buildPipeline = async (
|
|||
timefilter: params.timefilter,
|
||||
});
|
||||
if (buildPipelineVisFunction[vis.type.name]) {
|
||||
pipeline += buildPipelineVisFunction[vis.type.name](visState, schemas, uiState, {
|
||||
savedObjectId: params.savedObjectId,
|
||||
});
|
||||
pipeline += buildPipelineVisFunction[vis.type.name](vis.params, schemas, uiState);
|
||||
} else if (vislibCharts.includes(vis.type.name)) {
|
||||
const visConfig = visState.params;
|
||||
const visConfig = { ...vis.params };
|
||||
visConfig.dimensions = await buildVislibDimensions(vis, params);
|
||||
|
||||
pipeline += `vislib type='${vis.type.name}' ${prepareJson('visConfig', visState.params)}`;
|
||||
pipeline += `vislib type='${vis.type.name}' ${prepareJson('visConfig', visConfig)}`;
|
||||
} else if (vis.type.toExpression) {
|
||||
pipeline += await vis.type.toExpression(vis, params);
|
||||
} else {
|
||||
const visConfig = visState.params;
|
||||
const visConfig = { ...vis.params };
|
||||
visConfig.dimensions = schemas;
|
||||
pipeline += `visualization type='${vis.type.name}'
|
||||
${prepareJson('visConfig', visConfig)}
|
||||
|
|
|
@ -1,102 +0,0 @@
|
|||
/*
|
||||
* 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 { getUpdateStatus, Status } from './update_status';
|
||||
|
||||
// Parts of the tests in this file are generated more dynamically, based on the
|
||||
// values inside the Status object.Make sure this object has one function per entry
|
||||
// in Status, that actually change on the passed $scope, what needs to be changed
|
||||
// so that we expect the getUpdateStatus function to actually detect a change.
|
||||
const changeFunctions = {
|
||||
[Status.AGGS]: $scope => ($scope.vis.aggs = { foo: 'new' }),
|
||||
[Status.DATA]: $scope => ($scope.visData = { foo: 'new' }),
|
||||
[Status.PARAMS]: $scope => ($scope.vis.params = { foo: 'new' }),
|
||||
[Status.RESIZE]: $scope => ($scope.vis.size = [50, 50]),
|
||||
[Status.TIME]: $scope => ($scope.vis.filters.timeRange = { from: 'now-7d', to: 'now' }),
|
||||
[Status.UI_STATE]: $scope => ($scope.uiState = { foo: 'new' }),
|
||||
};
|
||||
|
||||
describe('getUpdateStatus', () => {
|
||||
function getScope() {
|
||||
return {
|
||||
vis: {
|
||||
aggs: {},
|
||||
size: [100, 100],
|
||||
params: {},
|
||||
filters: {},
|
||||
},
|
||||
uiState: {},
|
||||
visData: {},
|
||||
};
|
||||
}
|
||||
|
||||
function initStatusCheckerAndChangeProperty(type, requiresUpdateStatus) {
|
||||
const $scope = getScope();
|
||||
// Call the getUpdateStatus function initially, so it can store it's current state
|
||||
getUpdateStatus(requiresUpdateStatus, $scope, $scope);
|
||||
|
||||
// Get the change function for that specific change type
|
||||
const changeFn = changeFunctions[type];
|
||||
if (!changeFn) {
|
||||
throw new Error(`Please implement the test change function for ${type}.`);
|
||||
}
|
||||
|
||||
// Call that change function to manipulate the scope so it changed.
|
||||
changeFn($scope);
|
||||
|
||||
return getUpdateStatus(requiresUpdateStatus, $scope, $scope);
|
||||
}
|
||||
|
||||
it('should be a function', () => {
|
||||
expect(typeof getUpdateStatus).toBe('function');
|
||||
});
|
||||
|
||||
Object.entries(Status).forEach(([typeKey, typeValue]) => {
|
||||
// This block automatically creates very simple tests for each of the Status
|
||||
// keys, so we have simple tests per changed property.
|
||||
// If it makes sense to test more specific behavior of a specific change detection
|
||||
// please add additional tests for that.
|
||||
|
||||
it(`should detect changes for Status.${typeKey}`, () => {
|
||||
// Check whether the required change type is not correctly determined
|
||||
const status = initStatusCheckerAndChangeProperty(typeValue, [typeValue]);
|
||||
expect(status[typeValue]).toBe(true);
|
||||
});
|
||||
|
||||
it(`should not detect changes in other properties when changing Status.${typeKey}`, () => {
|
||||
// Only change typeKey, but track changes for all status changes
|
||||
const status = initStatusCheckerAndChangeProperty(typeValue, Object.values(Status));
|
||||
Object.values(Status)
|
||||
// Filter out the actual changed property so we only test for all other properties
|
||||
.filter(stat => stat !== typeValue)
|
||||
.forEach(otherProp => {
|
||||
expect(status[otherProp]).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
it(`should not detect changes if not requested for Status.${typeKey}`, () => {
|
||||
const allOtherStatusProperties = Object.values(Status).filter(stat => stat !== typeValue);
|
||||
// Change only the typeKey property, but do not listen for changes on it
|
||||
// listen on all other status changes instead.
|
||||
const status = initStatusCheckerAndChangeProperty(typeValue, allOtherStatusProperties);
|
||||
// The typeValue check should be falsy, since we did not request tracking it.
|
||||
expect(status[typeValue]).toBeFalsy();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,117 +0,0 @@
|
|||
/*
|
||||
* 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 { PersistedState } from '../../../../../../../plugins/visualizations/public';
|
||||
import { calculateObjectHash } from '../../../../../../../plugins/kibana_utils/common';
|
||||
import { Vis } from '../vis';
|
||||
|
||||
enum Status {
|
||||
AGGS = 'aggs',
|
||||
DATA = 'data',
|
||||
PARAMS = 'params',
|
||||
RESIZE = 'resize',
|
||||
TIME = 'time',
|
||||
UI_STATE = 'uiState',
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the hash of a specific key in the given oldStatus has changed
|
||||
* compared to the new valueHash passed.
|
||||
*/
|
||||
function hasHashChanged<T extends string>(
|
||||
valueHash: string,
|
||||
oldStatus: { [key in T]?: string },
|
||||
name: T
|
||||
): boolean {
|
||||
const oldHash = oldStatus[name];
|
||||
return oldHash !== valueHash;
|
||||
}
|
||||
|
||||
interface Size {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
function hasSizeChanged(size: Size, oldSize?: Size): boolean {
|
||||
if (!oldSize) {
|
||||
return true;
|
||||
}
|
||||
return oldSize.width !== size.width || oldSize.height !== size.height;
|
||||
}
|
||||
|
||||
function getUpdateStatus<T extends Status>(
|
||||
requiresUpdateStatus: T[] = [],
|
||||
obj: any,
|
||||
param: { vis: Vis; visData: any; uiState: PersistedState }
|
||||
): { [reqStats in T]: boolean } {
|
||||
const status = {} as { [reqStats in Status]: boolean };
|
||||
|
||||
// If the vis type doesn't need update status, skip all calculations
|
||||
if (requiresUpdateStatus.length === 0) {
|
||||
return status;
|
||||
}
|
||||
|
||||
if (!obj._oldStatus) {
|
||||
obj._oldStatus = {};
|
||||
}
|
||||
|
||||
for (const requiredStatus of requiresUpdateStatus) {
|
||||
let hash;
|
||||
// Calculate all required status updates for this visualization
|
||||
switch (requiredStatus) {
|
||||
case Status.AGGS:
|
||||
hash = calculateObjectHash(param.vis.aggs);
|
||||
status.aggs = hasHashChanged(hash, obj._oldStatus, 'aggs');
|
||||
obj._oldStatus.aggs = hash;
|
||||
break;
|
||||
case Status.DATA:
|
||||
hash = calculateObjectHash(param.visData);
|
||||
status.data = hasHashChanged(hash, obj._oldStatus, 'data');
|
||||
obj._oldStatus.data = hash;
|
||||
break;
|
||||
case Status.PARAMS:
|
||||
hash = calculateObjectHash(param.vis.params);
|
||||
status.params = hasHashChanged(hash, obj._oldStatus, 'param');
|
||||
obj._oldStatus.param = hash;
|
||||
break;
|
||||
case Status.RESIZE:
|
||||
const width: number = param.vis.size ? param.vis.size[0] : 0;
|
||||
const height: number = param.vis.size ? param.vis.size[1] : 0;
|
||||
const size = { width, height };
|
||||
status.resize = hasSizeChanged(size, obj._oldStatus.resize);
|
||||
obj._oldStatus.resize = size;
|
||||
break;
|
||||
case Status.TIME:
|
||||
const timeRange = param.vis.filters && param.vis.filters.timeRange;
|
||||
hash = calculateObjectHash(timeRange);
|
||||
status.time = hasHashChanged(hash, obj._oldStatus, 'time');
|
||||
obj._oldStatus.time = hash;
|
||||
break;
|
||||
case Status.UI_STATE:
|
||||
hash = calculateObjectHash(param.uiState);
|
||||
status.uiState = hasHashChanged(hash, obj._oldStatus, 'uiState');
|
||||
obj._oldStatus.uiState = hash;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
export { getUpdateStatus, Status };
|
|
@ -41,6 +41,8 @@ const createStartContract = (): VisualizationsStart => ({
|
|||
savedVisualizationsLoader: {} as any,
|
||||
showNewVisModal: jest.fn(),
|
||||
createVis: jest.fn(),
|
||||
convertFromSerializedVis: jest.fn(),
|
||||
convertToSerializedVis: jest.fn(),
|
||||
});
|
||||
|
||||
const createInstance = async () => {
|
||||
|
|
|
@ -39,6 +39,8 @@ import {
|
|||
setSavedVisualizationsLoader,
|
||||
setTimeFilter,
|
||||
setAggs,
|
||||
setChrome,
|
||||
setOverlays,
|
||||
} from './services';
|
||||
import { VISUALIZE_EMBEDDABLE_TYPE, VisualizeEmbeddableFactory } from './embeddable';
|
||||
import { ExpressionsSetup, ExpressionsStart } from '../../../../../../plugins/expressions/public';
|
||||
|
@ -48,14 +50,16 @@ import { visualization as visualizationRenderer } from './expressions/visualizat
|
|||
import {
|
||||
DataPublicPluginSetup,
|
||||
DataPublicPluginStart,
|
||||
IIndexPattern,
|
||||
} from '../../../../../../plugins/data/public';
|
||||
import { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/public';
|
||||
import { createSavedVisLoader, SavedVisualizationsLoader } from './saved_visualizations';
|
||||
import { VisImpl } from './vis_impl';
|
||||
import { SerializedVis, Vis } from './vis';
|
||||
import { showNewVisModal } from './wizard';
|
||||
import { UiActionsStart } from '../../../../../../plugins/ui_actions/public';
|
||||
import { VisState } from './types';
|
||||
import {
|
||||
convertFromSerializedVis,
|
||||
convertToSerializedVis,
|
||||
} from './saved_visualizations/_saved_vis';
|
||||
|
||||
/**
|
||||
* Interface for this plugin's returned setup/start contracts.
|
||||
|
@ -67,7 +71,9 @@ export type VisualizationsSetup = TypesSetup;
|
|||
|
||||
export interface VisualizationsStart extends TypesStart {
|
||||
savedVisualizationsLoader: SavedVisualizationsLoader;
|
||||
createVis: (indexPattern: IIndexPattern, visState?: VisState) => VisImpl;
|
||||
createVis: (visType: string, visState?: SerializedVis) => Vis;
|
||||
convertToSerializedVis: typeof convertToSerializedVis;
|
||||
convertFromSerializedVis: typeof convertFromSerializedVis;
|
||||
showNewVisModal: typeof showNewVisModal;
|
||||
}
|
||||
|
||||
|
@ -138,6 +144,8 @@ export class VisualizationsPlugin
|
|||
setUiActions(uiActions);
|
||||
setTimeFilter(data.query.timefilter.timefilter);
|
||||
setAggs(data.search.aggs);
|
||||
setOverlays(core.overlays);
|
||||
setChrome(core.chrome);
|
||||
const savedVisualizationsLoader = createSavedVisLoader({
|
||||
savedObjectsClient: core.savedObjects.client,
|
||||
indexPatterns: data.indexPatterns,
|
||||
|
@ -155,8 +163,9 @@ export class VisualizationsPlugin
|
|||
* @param {IIndexPattern} indexPattern - index pattern to use
|
||||
* @param {VisState} visState - visualization configuration
|
||||
*/
|
||||
createVis: (indexPattern: IIndexPattern, visState?: VisState) =>
|
||||
new VisImpl(indexPattern, visState),
|
||||
createVis: (visType: string, visState?: SerializedVis) => new Vis(visType, visState),
|
||||
convertToSerializedVis,
|
||||
convertFromSerializedVis,
|
||||
savedVisualizationsLoader,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -32,65 +32,74 @@ import {
|
|||
// @ts-ignore
|
||||
import { updateOldState } from '../legacy/vis_update_state';
|
||||
import { extractReferences, injectReferences } from './saved_visualization_references';
|
||||
import { IIndexPattern } from '../../../../../../../plugins/data/public';
|
||||
import { VisSavedObject } from '../types';
|
||||
import { VisImpl } from '../vis_impl';
|
||||
import {
|
||||
IIndexPattern,
|
||||
ISearchSource,
|
||||
SearchSource,
|
||||
} from '../../../../../../../plugins/data/public';
|
||||
import { ISavedVis, SerializedVis } from '../types';
|
||||
import { createSavedSearchesLoader } from '../../../../../../../plugins/discover/public';
|
||||
import { getChrome, getOverlays, getIndexPatterns, getSavedObjects } from '../services';
|
||||
|
||||
async function _afterEsResp(savedVis: VisSavedObject, services: any) {
|
||||
await _getLinkedSavedSearch(savedVis, services);
|
||||
savedVis.searchSource!.setField('size', 0);
|
||||
savedVis.vis = savedVis.vis ? _updateVis(savedVis) : await _createVis(savedVis);
|
||||
return savedVis;
|
||||
}
|
||||
export const convertToSerializedVis = async (savedVis: ISavedVis): Promise<SerializedVis> => {
|
||||
const { visState } = savedVis;
|
||||
const searchSource =
|
||||
savedVis.searchSource && (await getSearchSource(savedVis.searchSource, savedVis.savedSearchId));
|
||||
|
||||
async function _getLinkedSavedSearch(savedVis: VisSavedObject, services: any) {
|
||||
const linkedSearch = !!savedVis.savedSearchId;
|
||||
const current = savedVis.savedSearch;
|
||||
const indexPattern =
|
||||
searchSource && searchSource.getField('index') ? searchSource.getField('index')!.id : undefined;
|
||||
|
||||
if (linkedSearch && current && current.id === savedVis.savedSearchId) {
|
||||
return;
|
||||
const aggs = indexPattern ? visState.aggs || [] : visState.aggs;
|
||||
|
||||
return {
|
||||
id: savedVis.id,
|
||||
title: savedVis.title,
|
||||
type: visState.type,
|
||||
description: savedVis.description,
|
||||
params: visState.params,
|
||||
uiState: JSON.parse(savedVis.uiStateJSON || '{}'),
|
||||
data: {
|
||||
indexPattern,
|
||||
aggs,
|
||||
searchSource,
|
||||
savedSearchId: savedVis.savedSearchId,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const convertFromSerializedVis = (vis: SerializedVis): ISavedVis => {
|
||||
return {
|
||||
id: vis.id,
|
||||
title: vis.title,
|
||||
description: vis.description,
|
||||
visState: {
|
||||
type: vis.type,
|
||||
aggs: vis.data.aggs,
|
||||
params: vis.params,
|
||||
},
|
||||
uiStateJSON: JSON.stringify(vis.uiState),
|
||||
searchSource: vis.data.searchSource!,
|
||||
savedSearchId: vis.data.savedSearchId,
|
||||
};
|
||||
};
|
||||
|
||||
const getSearchSource = async (inputSearchSource: ISearchSource, savedSearchId?: string) => {
|
||||
const searchSource = inputSearchSource.createCopy
|
||||
? inputSearchSource.createCopy()
|
||||
: new SearchSource({ ...(inputSearchSource as any).fields });
|
||||
if (savedSearchId) {
|
||||
const savedSearch = await createSavedSearchesLoader({
|
||||
savedObjectsClient: getSavedObjects().client,
|
||||
indexPatterns: getIndexPatterns(),
|
||||
chrome: getChrome(),
|
||||
overlays: getOverlays(),
|
||||
}).get(savedSearchId);
|
||||
|
||||
searchSource.setParent(savedSearch.searchSource);
|
||||
}
|
||||
|
||||
if (savedVis.savedSearch) {
|
||||
savedVis.searchSource!.setParent(savedVis.savedSearch.searchSource.getParent());
|
||||
savedVis.savedSearch.destroy();
|
||||
delete savedVis.savedSearch;
|
||||
}
|
||||
const savedSearches = createSavedSearchesLoader(services);
|
||||
|
||||
if (linkedSearch) {
|
||||
savedVis.savedSearch = await savedSearches.get(savedVis.savedSearchId!);
|
||||
savedVis.searchSource!.setParent(savedVis.savedSearch!.searchSource);
|
||||
}
|
||||
}
|
||||
|
||||
async function _createVis(savedVis: VisSavedObject) {
|
||||
savedVis.visState = updateOldState(savedVis.visState);
|
||||
|
||||
// visState doesn't yet exist when importing a visualization, so we can't
|
||||
// assume that exists at this point. If it does exist, then we're not
|
||||
// importing a visualization, so we want to sync the title.
|
||||
if (savedVis.visState) {
|
||||
savedVis.visState.title = savedVis.title;
|
||||
}
|
||||
|
||||
savedVis.vis = new VisImpl(savedVis.searchSource!.getField('index')!, savedVis.visState);
|
||||
|
||||
savedVis.vis!.savedSearchId = savedVis.savedSearchId;
|
||||
|
||||
return savedVis.vis;
|
||||
}
|
||||
|
||||
function _updateVis(savedVis: VisSavedObject) {
|
||||
if (savedVis.vis && savedVis.searchSource) {
|
||||
savedVis.vis.indexPattern = savedVis.searchSource.getField('index');
|
||||
savedVis.visState.title = savedVis.title;
|
||||
savedVis.vis.setState(savedVis.visState);
|
||||
savedVis.vis.savedSearchId = savedVis.savedSearchId;
|
||||
}
|
||||
return savedVis.vis;
|
||||
}
|
||||
searchSource!.setField('size', 0);
|
||||
return searchSource;
|
||||
};
|
||||
|
||||
export function createSavedVisClass(services: SavedObjectKibanaServices) {
|
||||
const SavedObjectClass = createSavedObjectClass(services);
|
||||
|
@ -131,8 +140,16 @@ export function createSavedVisClass(services: SavedObjectKibanaServices) {
|
|||
savedSearchId: opts.savedSearchId,
|
||||
version: 1,
|
||||
},
|
||||
afterESResp: (savedObject: SavedObject) => {
|
||||
return _afterEsResp(savedObject as VisSavedObject, services) as Promise<SavedObject>;
|
||||
afterESResp: async (savedObject: SavedObject) => {
|
||||
const savedVis = (savedObject as any) as ISavedVis;
|
||||
savedVis.visState = await updateOldState(savedVis.visState);
|
||||
if (savedVis.savedSearchId && savedVis.searchSource) {
|
||||
savedObject.searchSource = await getSearchSource(
|
||||
savedVis.searchSource,
|
||||
savedVis.savedSearchId
|
||||
);
|
||||
}
|
||||
return (savedVis as any) as SavedObject;
|
||||
},
|
||||
});
|
||||
this.showInRecentlyAccessed = true;
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
*/
|
||||
|
||||
import { extractReferences, injectReferences } from './saved_visualization_references';
|
||||
import { VisSavedObject, VisState } from '../types';
|
||||
import { VisSavedObject, SavedVisState } from '../types';
|
||||
|
||||
describe('extractReferences', () => {
|
||||
test('extracts nothing if savedSearchId is empty', () => {
|
||||
|
@ -140,7 +140,7 @@ Object {
|
|||
},
|
||||
],
|
||||
},
|
||||
} as unknown) as VisState,
|
||||
} as unknown) as SavedVisState,
|
||||
} as VisSavedObject;
|
||||
const references = [
|
||||
{
|
||||
|
@ -201,7 +201,7 @@ Object {
|
|||
},
|
||||
],
|
||||
},
|
||||
} as unknown) as VisState,
|
||||
} as unknown) as SavedVisState,
|
||||
} as VisSavedObject;
|
||||
expect(() => injectReferences(context, [])).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Could not find index pattern reference \\"control_0_index_pattern\\""`
|
||||
|
|
|
@ -18,10 +18,13 @@
|
|||
*/
|
||||
|
||||
import {
|
||||
ApplicationStart,
|
||||
Capabilities,
|
||||
ChromeStart,
|
||||
HttpStart,
|
||||
I18nStart,
|
||||
IUiSettingsClient,
|
||||
OverlayStart,
|
||||
SavedObjectsStart,
|
||||
} from '../../../../../../core/public';
|
||||
import { TypesStart } from './vis_types';
|
||||
|
@ -76,3 +79,9 @@ export const [getSavedVisualizationsLoader, setSavedVisualizationsLoader] = crea
|
|||
export const [getAggs, setAggs] = createGetterSetter<DataPublicPluginStart['search']['aggs']>(
|
||||
'AggConfigs'
|
||||
);
|
||||
|
||||
export const [getOverlays, setOverlays] = createGetterSetter<OverlayStart>('Overlays');
|
||||
|
||||
export const [getChrome, setChrome] = createGetterSetter<ChromeStart>('Chrome');
|
||||
|
||||
export const [getApplication, setApplication] = createGetterSetter<ApplicationStart>('Application');
|
||||
|
|
|
@ -18,21 +18,33 @@
|
|||
*/
|
||||
|
||||
import { SavedObject } from '../../../../../../plugins/saved_objects/public';
|
||||
import { Vis, VisState, VisParams, VisualizationController } from './vis';
|
||||
import { ISearchSource } from '../../../../../../plugins/data/public/';
|
||||
import { SavedSearch } from '../../../../../../plugins/discover/public';
|
||||
import { ISearchSource, AggConfigOptions } from '../../../../../../plugins/data/public';
|
||||
import { SerializedVis, Vis, VisParams } from './vis';
|
||||
|
||||
export { Vis, VisState, VisParams, VisualizationController };
|
||||
export { Vis, SerializedVis, VisParams };
|
||||
|
||||
export interface VisSavedObject extends SavedObject {
|
||||
vis: Vis;
|
||||
description?: string;
|
||||
searchSource: ISearchSource;
|
||||
export interface VisualizationController {
|
||||
render(visData: any, visParams: any): Promise<void>;
|
||||
destroy(): void;
|
||||
isLoaded?(): Promise<void> | void;
|
||||
}
|
||||
|
||||
export interface SavedVisState {
|
||||
type: string;
|
||||
params: VisParams;
|
||||
aggs: AggConfigOptions[];
|
||||
}
|
||||
|
||||
export interface ISavedVis {
|
||||
id: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
visState: SavedVisState;
|
||||
searchSource?: ISearchSource;
|
||||
uiStateJSON?: string;
|
||||
destroy: () => void;
|
||||
savedSearchRefName?: string;
|
||||
savedSearchId?: string;
|
||||
savedSearch?: SavedSearch;
|
||||
visState: VisState;
|
||||
}
|
||||
|
||||
// @ts-ignore-next-line
|
||||
export interface VisSavedObject extends SavedObject, ISavedVis {}
|
||||
|
|
|
@ -17,47 +17,182 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @name Vis
|
||||
*
|
||||
* @description This class consists of aggs, params, listeners, title, and type.
|
||||
* - Aggs: Instances of IAggConfig.
|
||||
* - Params: The settings in the Options tab.
|
||||
*
|
||||
* Not to be confused with vislib/vis.js.
|
||||
*/
|
||||
|
||||
import { isFunction, defaults, cloneDeep } from 'lodash';
|
||||
import { PersistedState } from '../../../../../../../src/plugins/visualizations/public';
|
||||
// @ts-ignore
|
||||
import { updateVisualizationConfig } from './legacy/vis_update';
|
||||
import { getTypes, getAggs } from './services';
|
||||
import { VisType } from './vis_types';
|
||||
import { Status } from './legacy/update_status';
|
||||
import { IAggConfigs } from '../../../../../../plugins/data/public';
|
||||
import {
|
||||
IAggConfigs,
|
||||
IndexPattern,
|
||||
ISearchSource,
|
||||
AggConfigOptions,
|
||||
} from '../../../../../../plugins/data/public';
|
||||
|
||||
export interface Vis {
|
||||
type: VisType;
|
||||
getCurrentState: (
|
||||
includeDisabled?: boolean
|
||||
) => {
|
||||
title: string;
|
||||
type: string;
|
||||
params: VisParams;
|
||||
aggs: Array<{ [key: string]: any }>;
|
||||
};
|
||||
|
||||
/**
|
||||
* If a visualization based on the saved search,
|
||||
* the id is necessary for building an expression function in src/plugins/expressions/common/expression_functions/specs/kibana_context.ts
|
||||
*/
|
||||
export interface SerializedVisData {
|
||||
expression?: string;
|
||||
aggs: AggConfigOptions[];
|
||||
indexPattern?: string;
|
||||
searchSource?: ISearchSource;
|
||||
savedSearchId?: string;
|
||||
}
|
||||
|
||||
// Since we haven't typed everything here yet, we basically "any" the rest
|
||||
// of that interface. This should be removed as soon as this type definition
|
||||
// has been completed. But that way we at least have typing for a couple of
|
||||
// properties on that type.
|
||||
[key: string]: any;
|
||||
export interface SerializedVis {
|
||||
id: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
type: string;
|
||||
params: VisParams;
|
||||
uiState?: any;
|
||||
data: SerializedVisData;
|
||||
}
|
||||
|
||||
export interface VisData {
|
||||
ast?: string;
|
||||
aggs?: IAggConfigs;
|
||||
indexPattern?: IndexPattern;
|
||||
searchSource?: ISearchSource;
|
||||
savedSearchId?: string;
|
||||
}
|
||||
|
||||
export interface VisParams {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface VisState {
|
||||
title: string;
|
||||
type: VisType;
|
||||
params: VisParams;
|
||||
aggs: IAggConfigs;
|
||||
}
|
||||
export class Vis {
|
||||
public readonly type: VisType;
|
||||
public readonly id: string;
|
||||
public title: string = '';
|
||||
public description: string = '';
|
||||
public params: VisParams = {};
|
||||
// Session state is for storing information that is transitory, and will not be saved with the visualization.
|
||||
// For instance, map bounds, which depends on the view port, browser window size, etc.
|
||||
public sessionState: Record<string, any> = {};
|
||||
public data: VisData = {};
|
||||
|
||||
export interface VisualizationController {
|
||||
render(visData: any, visParams: any, update: { [key in Status]: boolean }): Promise<void>;
|
||||
destroy(): void;
|
||||
isLoaded?(): Promise<void> | void;
|
||||
public readonly uiState: PersistedState;
|
||||
|
||||
constructor(visType: string, visState: SerializedVis = {} as any) {
|
||||
this.type = this.getType(visType);
|
||||
this.params = this.getParams(visState.params);
|
||||
this.uiState = new PersistedState(visState.uiState);
|
||||
this.id = visState.id;
|
||||
|
||||
this.setState(visState || {});
|
||||
}
|
||||
|
||||
private getType(visType: string) {
|
||||
const type = getTypes().get(visType);
|
||||
if (!type) {
|
||||
throw new Error(`Invalid type "${visType}"`);
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
private getParams(params: VisParams) {
|
||||
return defaults({}, cloneDeep(params || {}), cloneDeep(this.type.visConfig.defaults || {}));
|
||||
}
|
||||
|
||||
setState(state: SerializedVis) {
|
||||
let typeChanged = false;
|
||||
if (state.type && this.type.name !== state.type) {
|
||||
// @ts-ignore
|
||||
this.type = this.getType(state.type);
|
||||
typeChanged = true;
|
||||
}
|
||||
if (state.title !== undefined) {
|
||||
this.title = state.title;
|
||||
}
|
||||
if (state.description !== undefined) {
|
||||
this.description = state.description;
|
||||
}
|
||||
if (state.params || typeChanged) {
|
||||
this.params = this.getParams(state.params);
|
||||
}
|
||||
|
||||
// move to migration script
|
||||
updateVisualizationConfig(state.params, this.params);
|
||||
|
||||
if (state.data && state.data.searchSource) {
|
||||
this.data.searchSource = state.data.searchSource!;
|
||||
this.data.indexPattern = this.data.searchSource.getField('index');
|
||||
}
|
||||
if (state.data && state.data.savedSearchId) {
|
||||
this.data.savedSearchId = state.data.savedSearchId;
|
||||
}
|
||||
if (state.data && state.data.aggs) {
|
||||
let configStates = state.data.aggs;
|
||||
configStates = this.initializeDefaultsFromSchemas(configStates, this.type.schemas.all || []);
|
||||
if (!this.data.indexPattern) {
|
||||
if (state.data.aggs.length) {
|
||||
throw new Error('trying to initialize aggs without index pattern');
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.data.aggs = getAggs().createAggConfigs(this.data.indexPattern, configStates);
|
||||
}
|
||||
}
|
||||
|
||||
clone() {
|
||||
return new Vis(this.type.name, this.serialize());
|
||||
}
|
||||
|
||||
serialize(): SerializedVis {
|
||||
const aggs = this.data.aggs ? this.data.aggs.aggs.map(agg => agg.toJSON()) : [];
|
||||
const indexPattern = this.data.searchSource && this.data.searchSource.getField('index');
|
||||
return {
|
||||
id: this.id,
|
||||
title: this.title,
|
||||
type: this.type.name,
|
||||
params: cloneDeep(this.params) as any,
|
||||
uiState: this.uiState.toJSON(),
|
||||
data: {
|
||||
aggs: aggs as any,
|
||||
indexPattern: indexPattern ? indexPattern.id : undefined,
|
||||
searchSource: this.data.searchSource!.createCopy(),
|
||||
savedSearchId: this.data.savedSearchId,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
toAST() {
|
||||
return this.type.toAST(this.params);
|
||||
}
|
||||
|
||||
// deprecated
|
||||
isHierarchical() {
|
||||
if (isFunction(this.type.hierarchicalData)) {
|
||||
return !!this.type.hierarchicalData(this);
|
||||
} else {
|
||||
return !!this.type.hierarchicalData;
|
||||
}
|
||||
}
|
||||
|
||||
private initializeDefaultsFromSchemas(configStates: AggConfigOptions[], schemas: any) {
|
||||
// Set the defaults for any schema which has them. If the defaults
|
||||
// for some reason has more then the max only set the max number
|
||||
// of defaults (not sure why a someone define more...
|
||||
// but whatever). Also if a schema.name is already set then don't
|
||||
// set anything.
|
||||
const newConfigs = [...configStates];
|
||||
schemas
|
||||
.filter((schema: any) => Array.isArray(schema.defaults) && schema.defaults.length > 0)
|
||||
.filter((schema: any) => !configStates.find(agg => agg.schema && agg.schema === schema.name))
|
||||
.forEach((schema: any) => {
|
||||
const defaultSchemaConfig = schema.defaults.slice(0, schema.max);
|
||||
defaultSchemaConfig.forEach((d: any) => newConfigs.push(d));
|
||||
});
|
||||
return newConfigs;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
/*
|
||||
* 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 { Vis, VisState, VisParams } from './vis';
|
||||
import { VisType } from './vis_types';
|
||||
import { IAggConfig, IIndexPattern } from '../../../../../../plugins/data/public';
|
||||
import { Schema } from '../../../../vis_default_editor/public';
|
||||
|
||||
type InitVisStateType =
|
||||
| Partial<VisState>
|
||||
| Partial<Omit<VisState, 'type'> & { type: string }>
|
||||
| string;
|
||||
|
||||
export type VisImplConstructor = new (
|
||||
indexPattern: IIndexPattern,
|
||||
visState?: InitVisStateType
|
||||
) => VisImpl;
|
||||
|
||||
export declare class VisImpl implements Vis {
|
||||
constructor(indexPattern: IIndexPattern, visState?: InitVisStateType);
|
||||
|
||||
type: VisType;
|
||||
getCurrentState: (
|
||||
includeDisabled?: boolean
|
||||
) => {
|
||||
title: string;
|
||||
type: string;
|
||||
params: VisParams;
|
||||
aggs: Array<{ [key: string]: any }>;
|
||||
};
|
||||
|
||||
private initializeDefaultsFromSchemas(configStates: IAggConfig[], schemas: Schema[]);
|
||||
|
||||
// Since we haven't typed everything here yet, we basically "any" the rest
|
||||
// of that interface. This should be removed as soon as this type definition
|
||||
// has been completed. But that way we at least have typing for a couple of
|
||||
// properties on that type.
|
||||
[key: string]: any;
|
||||
}
|
|
@ -1,223 +0,0 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @name Vis
|
||||
*
|
||||
* @description This class consists of aggs, params, listeners, title, and type.
|
||||
* - Aggs: Instances of IAggConfig.
|
||||
* - Params: The settings in the Options tab.
|
||||
*
|
||||
* Not to be confused with vislib/vis.js.
|
||||
*/
|
||||
|
||||
import { EventEmitter } from 'events';
|
||||
import _ from 'lodash';
|
||||
import { PersistedState } from '../../../../../../../src/plugins/visualizations/public';
|
||||
import { updateVisualizationConfig } from './legacy/vis_update';
|
||||
import { getTypes, getAggs } from './services';
|
||||
|
||||
class VisImpl extends EventEmitter {
|
||||
constructor(indexPattern, visState) {
|
||||
super();
|
||||
visState = visState || {};
|
||||
|
||||
if (_.isString(visState)) {
|
||||
visState = {
|
||||
type: visState,
|
||||
};
|
||||
}
|
||||
|
||||
this.indexPattern = indexPattern;
|
||||
this._setUiState(new PersistedState());
|
||||
this.setCurrentState(visState);
|
||||
this.setState(this.getCurrentState(), false);
|
||||
|
||||
// Session state is for storing information that is transitory, and will not be saved with the visualization.
|
||||
// For instance, map bounds, which depends on the view port, browser window size, etc.
|
||||
this.sessionState = {};
|
||||
|
||||
this.API = {
|
||||
events: {
|
||||
filter: data => this.eventsSubject.next({ name: 'filterBucket', data }),
|
||||
brush: data => this.eventsSubject.next({ name: 'brush', data }),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
initializeDefaultsFromSchemas(configStates, schemas) {
|
||||
// Set the defaults for any schema which has them. If the defaults
|
||||
// for some reason has more then the max only set the max number
|
||||
// of defaults (not sure why a someone define more...
|
||||
// but whatever). Also if a schema.name is already set then don't
|
||||
// set anything.
|
||||
const newConfigs = [...configStates];
|
||||
schemas
|
||||
.filter(schema => Array.isArray(schema.defaults) && schema.defaults.length > 0)
|
||||
.filter(schema => !configStates.find(agg => agg.schema && agg.schema === schema.name))
|
||||
.forEach(schema => {
|
||||
const defaults = schema.defaults.slice(0, schema.max);
|
||||
defaults.forEach(d => newConfigs.push(d));
|
||||
});
|
||||
return newConfigs;
|
||||
}
|
||||
|
||||
setCurrentState(state) {
|
||||
this.title = state.title || '';
|
||||
const type = state.type || this.type;
|
||||
if (_.isString(type)) {
|
||||
this.type = getTypes().get(type);
|
||||
if (!this.type) {
|
||||
throw new Error(`Invalid type "${type}"`);
|
||||
}
|
||||
} else {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
this.params = _.defaults(
|
||||
{},
|
||||
_.cloneDeep(state.params || {}),
|
||||
_.cloneDeep(this.type.visConfig.defaults || {})
|
||||
);
|
||||
|
||||
updateVisualizationConfig(state.params, this.params);
|
||||
|
||||
if (state.aggs || !this.aggs) {
|
||||
let configStates = state.aggs ? state.aggs.aggs || state.aggs : [];
|
||||
configStates = this.initializeDefaultsFromSchemas(configStates, this.type.schemas.all || []);
|
||||
this.aggs = getAggs().createAggConfigs(this.indexPattern, configStates);
|
||||
}
|
||||
}
|
||||
|
||||
setState(state, updateCurrentState = true) {
|
||||
this._state = _.cloneDeep(state);
|
||||
if (updateCurrentState) {
|
||||
this.setCurrentState(this._state);
|
||||
}
|
||||
}
|
||||
|
||||
setVisType(type) {
|
||||
this.type.type = type;
|
||||
}
|
||||
|
||||
updateState() {
|
||||
this.setState(this.getCurrentState(true));
|
||||
this.emit('update');
|
||||
}
|
||||
|
||||
forceReload() {
|
||||
this.emit('reload');
|
||||
}
|
||||
|
||||
getCurrentState(includeDisabled) {
|
||||
return {
|
||||
title: this.title,
|
||||
type: this.type.name,
|
||||
params: _.cloneDeep(this.params),
|
||||
aggs: this.aggs.aggs
|
||||
.map(agg => agg.toJSON())
|
||||
.filter(agg => includeDisabled || agg.enabled)
|
||||
.filter(Boolean),
|
||||
};
|
||||
}
|
||||
|
||||
copyCurrentState(includeDisabled = false) {
|
||||
const state = this.getCurrentState(includeDisabled);
|
||||
state.aggs = getAggs().createAggConfigs(
|
||||
this.indexPattern,
|
||||
state.aggs.aggs || state.aggs,
|
||||
this.type.schemas.all
|
||||
);
|
||||
return state;
|
||||
}
|
||||
|
||||
getStateInternal(includeDisabled) {
|
||||
return {
|
||||
title: this._state.title,
|
||||
type: this._state.type,
|
||||
params: this._state.params,
|
||||
aggs: this._state.aggs.filter(agg => includeDisabled || agg.enabled),
|
||||
};
|
||||
}
|
||||
|
||||
getEnabledState() {
|
||||
return this.getStateInternal(false);
|
||||
}
|
||||
|
||||
getAggConfig() {
|
||||
return this.aggs.clone({ enabledOnly: true });
|
||||
}
|
||||
|
||||
getState() {
|
||||
return this.getStateInternal(true);
|
||||
}
|
||||
|
||||
isHierarchical() {
|
||||
if (_.isFunction(this.type.hierarchicalData)) {
|
||||
return !!this.type.hierarchicalData(this);
|
||||
} else {
|
||||
return !!this.type.hierarchicalData;
|
||||
}
|
||||
}
|
||||
|
||||
hasSchemaAgg(schemaName, aggTypeName) {
|
||||
const aggs = this.aggs.bySchemaName(schemaName) || [];
|
||||
return aggs.some(function(agg) {
|
||||
if (!agg.type || !agg.type.name) return false;
|
||||
return agg.type.name === aggTypeName;
|
||||
});
|
||||
}
|
||||
|
||||
hasUiState() {
|
||||
return !!this.__uiState;
|
||||
}
|
||||
|
||||
/***
|
||||
* this should not be used outside of visualize
|
||||
* @param uiState
|
||||
* @private
|
||||
*/
|
||||
_setUiState(uiState) {
|
||||
if (uiState instanceof PersistedState) {
|
||||
this.__uiState = uiState;
|
||||
}
|
||||
}
|
||||
|
||||
getUiState() {
|
||||
return this.__uiState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently this is only used to extract map-specific information
|
||||
* (e.g. mapZoom, mapCenter).
|
||||
*/
|
||||
uiStateVal(key, val) {
|
||||
if (this.hasUiState()) {
|
||||
if (_.isUndefined(val)) {
|
||||
return this.__uiState.get(key);
|
||||
}
|
||||
return this.__uiState.set(key, val);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
VisImpl.prototype.type = 'histogram';
|
||||
|
||||
export { VisImpl };
|
|
@ -328,6 +328,7 @@ export {
|
|||
AggParamType,
|
||||
AggTypeFieldFilters, // TODO convert to interface
|
||||
AggTypeFilters, // TODO convert to interface
|
||||
AggConfigOptions,
|
||||
BUCKET_TYPES,
|
||||
DateRangeKey, // only used in field formatter deserialization, which will live in data
|
||||
IAggConfig,
|
||||
|
|
|
@ -54,6 +54,22 @@ import { Unit } from '@elastic/datemath';
|
|||
import { UnregisterCallback } from 'history';
|
||||
import { UserProvidedValues } from 'src/core/server/types';
|
||||
|
||||
// Warning: (ae-missing-release-tag) "AggConfigOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export interface AggConfigOptions {
|
||||
// (undocumented)
|
||||
enabled?: boolean;
|
||||
// (undocumented)
|
||||
id?: string;
|
||||
// (undocumented)
|
||||
params?: Record<string, any>;
|
||||
// (undocumented)
|
||||
schema?: string;
|
||||
// (undocumented)
|
||||
type: IAggType;
|
||||
}
|
||||
|
||||
// Warning: (ae-missing-release-tag) "AggGroupNames" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
|
@ -1838,21 +1854,21 @@ export type TSearchStrategyProvider<T extends TStrategyTypes> = (context: ISearc
|
|||
// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "getRoutes" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:379:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:379:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:379:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:379:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:384:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:385:1 - (ae-forgotten-export) The symbol "convertDateRangeToString" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:387:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:396:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:397:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:398:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:401:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:402:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:405:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:406:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:409:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:380:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:380:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:380:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:380:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:385:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:386:1 - (ae-forgotten-export) The symbol "convertDateRangeToString" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:388:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:397:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:398:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:399:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:402:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:403:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:406:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:407:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:410:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/query/state_sync/connect_to_query_state.ts:33:33 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/query/state_sync/connect_to_query_state.ts:37:1 - (ae-forgotten-export) The symbol "QueryStateChange" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/types.ts:52:5 - (ae-forgotten-export) The symbol "createFiltersFromEvent" needs to be exported by the entry point index.d.ts
|
||||
|
|
|
@ -85,7 +85,7 @@ export class PersistedState extends EventEmitter {
|
|||
setSilent(key: PersistedStateKey | any, value?: any) {
|
||||
const params = prepSetParams(key, value, this._path);
|
||||
|
||||
if (params.key) {
|
||||
if (params.key || params.value) {
|
||||
return this.setValue(params.key, params.value, true);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue