Move visualize editor out of visualize directive (#20263)

This commit is contained in:
Peter Pisljar 2018-07-03 14:33:47 +02:00 committed by GitHub
parent 59426cbfa4
commit 2d4bb6a19f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 186 additions and 307 deletions

View file

@ -67,12 +67,18 @@
</div>
<visualize
ng-if="!chrome.getVisible()"
saved-obj="savedVis"
ui-state="uiState"
time-range="timeRange"
editor-mode="chrome.getVisible()"
>
/>
</visualize>
<visualization-editor
ng-if="chrome.getVisible()"
saved-obj="savedVis"
ui-state="uiState"
time-range="timeRange"
class="vis-editor-content"
/>
</visualize-app>

View file

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

View file

@ -17,36 +17,34 @@
* under the License.
*/
import './visualize.less';
import './visualize_legend';
import { uiModules } from '../modules';
import { uiModules } from 'ui/modules';
import 'angular-sanitize';
import { VisEditorTypesRegistryProvider } from '../registry/vis_editor_types';
import { getUpdateStatus } from '../vis/update_status';
import { VisEditorTypesRegistryProvider } from 'ui/registry/vis_editor_types';
uiModules
.get('kibana/directive', ['ngSanitize'])
.directive('visualizationEditor', function (Private, $timeout) {
.directive('visualizationEditor', function (Private, $timeout, getAppState) {
const editorTypes = Private(VisEditorTypesRegistryProvider);
return {
restrict: 'E',
scope: {
vis: '=',
visData: '=',
savedObj: '=',
uiState: '=?',
searchSource: '='
timeRange: '='
},
link: function ($scope, element) {
// Clone the _vis instance.
const vis = $scope.vis;
const Editor = typeof vis.type.editor === 'function' ? vis.type.editor :
editorTypes.find(editor => editor.key === vis.type.editor);
const editor = new Editor(element[0], vis);
const editorType = $scope.savedObj.vis.type.editor;
const Editor = typeof editorType === 'function' ? editorType :
editorTypes.find(editor => editor.key === editorType);
const editor = new Editor(element[0], $scope.savedObj);
$scope.renderFunction = () => {
if (!$scope.vis) return;
editor.render($scope.visData, $scope.searchSource, getUpdateStatus(Editor.requiresUpdateStatus, $scope, $scope), $scope.uiState);
editor.render({
uiState: $scope.uiState,
timeRange: $scope.timeRange,
appState: getAppState(),
});
};
$scope.$on('render', (event) => {
@ -58,9 +56,8 @@ uiModules
editor.destroy();
});
if (!vis.initialized) {
$timeout(() => { $scope.renderFunction(); });
}
$scope.$watch('timeRange', $scope.renderFunction);
}
};
});

View file

@ -81,7 +81,7 @@ class VisEditor extends Component {
this.setState({ dirty: false });
};
if (!this.props.vis.isEditorMode()) {
if (!this.props.isEditorMode) {
if (!this.props.vis.params || !this.props.visData) return null;
const reversed = this.state.reversed;
return (
@ -110,7 +110,9 @@ class VisEditor extends Component {
dirty={this.state.dirty}
autoApply={this.state.autoApply}
model={model}
visData={this.props.visData}
appState={this.props.appState}
savedObj={this.props.savedObj}
timeRange={this.props.timeRange}
onUiState={this.handleUiState}
uiState={this.props.vis.getUiState()}
onBrush={this.onBrush}
@ -155,7 +157,10 @@ VisEditor.propTypes = {
visData: PropTypes.object,
appState: PropTypes.object,
renderComplete: PropTypes.func,
config: PropTypes.object
config: PropTypes.object,
isEditorMode: PropTypes.bool,
savedObj: PropTypes.object,
timeRange: PropTypes.object,
};
export default VisEditor;

View file

@ -20,9 +20,9 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { keyCodes } from '@elastic/eui';
import Visualization from './visualization';
import Toggle from 'react-toggle';
import 'react-toggle/style.css';
import { getVisualizeLoader } from 'ui/visualize/loader/visualize_loader';
const MIN_CHART_HEIGHT = 250;
@ -37,6 +37,8 @@ class VisEditorVisualization extends Component {
this.handleMouseUp = this.handleMouseUp.bind(this);
this.handleMouseDown = this.handleMouseDown.bind(this);
this.onSizeHandleKeyDown = this.onSizeHandleKeyDown.bind(this);
this._visEl = React.createRef();
}
handleMouseDown() {
@ -62,8 +64,37 @@ class VisEditorVisualization extends Component {
componentWillUnmount() {
window.removeEventListener('mousemove', this.handleMouseMove);
window.removeEventListener('mouseup', this.handleMouseUp);
if (this._handler) {
this._handler.destroy();
}
}
_loadVisualization() {
getVisualizeLoader().then(loader => {
if (!this._visEl.current) {
// In case the visualize loader isn't done before the component is unmounted.
return;
}
this._loader = loader;
this._handler = this._loader.embedVisualizationWithSavedObject(this._visEl.current, this.props.savedObj, {
uiState: this.props.uiState,
listenOnChange: false,
timeRange: this.props.timeRange,
appState: this.props.appState,
});
});
}
componentDidUpdate() {
this._handler.update({
timeRange: this.props.timeRange
});
}
componentDidMount() {
this._loadVisualization();
}
/**
* Resize the chart height when pressing up/down while the drag handle
* for resizing has the focus.
@ -126,7 +157,6 @@ class VisEditorVisualization extends Component {
</div>
);
const visBackgroundColor = '#FFF';
return (
<div>
<div
@ -137,19 +167,8 @@ class VisEditorVisualization extends Component {
data-title={this.props.title}
data-description={this.props.description}
data-render-complete="disabled"
>
<Visualization
backgroundColor={visBackgroundColor}
className="dashboard__visualization"
dateFormat={this.props.dateFormat}
model={this.props.model}
onBrush={this.props.onBrush}
onChange={this.handleChange}
onUiState={this.props.onUiState}
uiState={this.props.uiState}
visData={this.props.visData}
/>
</div>
ref={this._visEl}
/>
<div className="vis-editor-hide-for-reporting">
{applyButton}
<button
@ -175,10 +194,12 @@ VisEditorVisualization.propTypes = {
onUiState: PropTypes.func,
uiState: PropTypes.object,
onToggleAutoApply: PropTypes.func,
visData: PropTypes.object,
savedObj: PropTypes.object,
timeRange: PropTypes.object,
dirty: PropTypes.bool,
autoApply: PropTypes.bool,
dateFormat: PropTypes.string
dateFormat: PropTypes.string,
appState: PropTypes.object,
};
export default VisEditorVisualization;

View file

@ -22,25 +22,20 @@ import { render, unmountComponentAtNode } from 'react-dom';
import { SavedObjectsClientProvider } from 'ui/saved_objects';
import { FetchFieldsProvider } from '../lib/fetch_fields';
import { extractIndexPatterns } from '../lib/extract_index_patterns';
const AUTO_APPLY_KEY = 'metrics_autoApply';
function ReactEditorControllerProvider(Private, localStorage, config) {
function ReactEditorControllerProvider(Private, config) {
const fetchFields = Private(FetchFieldsProvider);
const savedObjectsClient = Private(SavedObjectsClientProvider);
class ReactEditorController {
constructor(el, vis) {
constructor(el, savedObj) {
this.el = el;
this.vis = vis;
this.savedObj = savedObj;
this.vis = savedObj.vis;
this.vis.fields = {};
const autoApply = localStorage.get(AUTO_APPLY_KEY);
vis.autoApply = autoApply != null ? autoApply : true;
vis.initialized = true;
}
render(visData) {
this.visData = visData;
render(params) {
return new Promise((resolve) => {
Promise.resolve().then(() => {
if (this.vis.params.index_pattern === '') {
@ -53,7 +48,15 @@ function ReactEditorControllerProvider(Private, localStorage, config) {
fetchFields(indexPatterns).then(fields => {
this.vis.fields = { ...fields, ...this.vis.fields };
const Component = this.vis.type.editorConfig.component;
render(<Component config={config} vis={this.vis} visData={visData} renderComplete={resolve}/>, this.el);
render(<Component
config={config}
vis={this.vis}
savedObj={this.savedObj}
timeRange={params.timeRange}
renderComplete={resolve}
isEditorMode={true}
appState={params.appState}
/>, this.el);
});
});
});

View file

@ -300,7 +300,8 @@ class TagCloud extends EventEmitter {
rotate: tag.rotate,
size: tag.size,
rawText: tag.rawText || tag.text,
displayText: tag.displayText
displayText: tag.displayText,
meta: tag.meta,
};
})
};

View file

@ -21,7 +21,6 @@ import $ from 'jquery';
import moment from 'moment';
import dateMath from '@kbn/datemath';
import * as vega from 'vega-lib';
import * as vegaLite from 'vega-lite';
import { Utils } from '../data_model/utils';
import { VISUALIZATION_COLORS } from '@elastic/eui';
import { TooltipHandler } from './vega_tooltip';
@ -63,7 +62,6 @@ export class VegaBaseView {
// $rootScope is a temp workaround, see usage below
this._$rootScope = opts.$rootScope;
this._vegaConfig = opts.vegaConfig;
this._editorMode = opts.editorMode;
this._$parentEl = $(opts.parentEl);
this._parser = opts.vegaParser;
this._serviceSettings = opts.serviceSettings;
@ -331,38 +329,6 @@ export class VegaBaseView {
return { from, to, mode };
}
/**
* Set global debug variable to simplify vega debugging in console. Show info message first time
*/
setDebugValues(view, spec, vlspec) {
if (!this._editorMode) {
// VEGA_DEBUG should only be enabled in the editor mode
return;
}
if (window) {
if (window.VEGA_DEBUG === undefined && console) {
console.log('%cWelcome to Kibana Vega Plugin!', 'font-size: 16px; font-weight: bold;');
console.log('You can access the Vega view with VEGA_DEBUG. ' +
'Learn more at https://vega.github.io/vega/docs/api/debugging/.');
}
const debugObj = {};
window.VEGA_DEBUG = debugObj;
window.VEGA_DEBUG.VEGA_VERSION = vega.version;
window.VEGA_DEBUG.VEGA_LITE_VERSION = vegaLite.version;
window.VEGA_DEBUG.view = view;
window.VEGA_DEBUG.vega_spec = spec;
window.VEGA_DEBUG.vegalite_spec = vlspec;
// On dispose, clean up, but don't use undefined to prevent repeated debug statements
this._addDestroyHandler(() => {
if (debugObj === window.VEGA_DEBUG) {
window.VEGA_DEBUG = null;
}
});
}
}
destroy() {
// properly handle multiple destroy() calls by converting this._destroyHandlers
// into the _ongoingDestroy promise, while handlers are being disposed

View file

@ -107,7 +107,6 @@ export class VegaMapView extends VegaBaseView {
});
const vegaView = vegaMapLayer.getVegaView();
this.setDebugValues(vegaView, vegaMapLayer.getVegaSpec());
await this.setView(vegaView);
}

View file

@ -26,7 +26,6 @@ export class VegaView extends VegaBaseView {
if (!this._$container) return;
const view = new vega.View(vega.parse(this._parser.spec), this._vegaViewConfig);
this.setDebugValues(view, this._parser.spec, this._parser.vlspec);
view.warn = this.onWarn.bind(this);
view.error = this.onError.bind(this);

View file

@ -87,7 +87,6 @@ export function VegaVisualizationProvider(Private, vegaConfig, serviceSettings,
const vegaViewParams = {
vegaConfig,
editorMode: this._vis.editorMode,
parentEl: this._el,
vegaParser,
serviceSettings,

View file

@ -30,15 +30,16 @@ import { keyCodes } from '@elastic/eui';
import { DefaultEditorSize } from '../../editor_size';
import { VisEditorTypesRegistryProvider } from '../../../registry/vis_editor_types';
import { visualizationLoader } from '../../../visualize/loader/visualization_loader';
import { getVisualizeLoader } from '../../../visualize/loader/visualize_loader';
const defaultEditor = function ($rootScope, $compile) {
return class DefaultEditor {
static key = 'default';
constructor(el, vis) {
constructor(el, savedObj) {
this.el = $(el);
this.vis = vis;
this.savedObj = savedObj;
this.vis = savedObj.vis;
if (!this.vis.type.editorConfig.optionTabs && this.vis.type.editorConfig.optionsTemplate) {
this.vis.type.editorConfig.optionTabs = [
@ -47,15 +48,13 @@ const defaultEditor = function ($rootScope, $compile) {
}
}
render(visData, searchSource, updateStatus, uiState) {
render({ uiState, timeRange, appState }) {
let $scope;
const updateScope = () => {
$scope.vis = this.vis;
$scope.visData = visData;
$scope.uiState = uiState;
$scope.searchSource = searchSource;
$scope.$apply();
//$scope.$apply();
};
return new Promise(resolve => {
@ -138,8 +137,26 @@ const defaultEditor = function ($rootScope, $compile) {
updateScope();
}
const visualizationEl = this.el.find('.vis-editor-canvas')[0];
visualizationLoader(visualizationEl, this.vis, visData, uiState, { listenOnChange: false });
if (!this._handler) {
const visualizationEl = this.el.find('.vis-editor-canvas')[0];
getVisualizeLoader().then(loader => {
if (!visualizationEl) {
return;
}
this._loader = loader;
this._handler = this._loader.embedVisualizationWithSavedObject(visualizationEl, this.savedObj, {
uiState: uiState,
listenOnChange: false,
timeRange: timeRange,
appState: appState,
});
});
} else {
this._handler.update({
timeRange: timeRange
});
}
});
}
@ -152,6 +169,9 @@ const defaultEditor = function ($rootScope, $compile) {
this.$scope.$destroy();
this.$scope = null;
}
if (this._handler) {
this._handler.destroy();
}
}
};
};

View file

@ -161,10 +161,6 @@ export function VisProvider(Private, indexPatterns, getAppState) {
return adapters;
}
isEditorMode() {
return this.editorMode || false;
}
setCurrentState(state) {
this.title = state.title || '';
const type = state.type || this.type;

View file

@ -46,8 +46,8 @@ export function AngularVisTypeProvider(Private, $compile, $rootScope) {
if (!this.$scope) {
this.$scope = $rootScope.$new();
updateScope();
this.$scope.uiState = this.vis.getUiState();
updateScope();
this.el.html($compile(this.vis.type.visConfig.template)(this.$scope));
} else {
updateScope();

View file

@ -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 $ from 'jquery';
import expect from 'expect.js';
import ngMock from 'ng_mock';
import { VisProvider } from '../../vis';
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
import FixturesStubbedSearchSourceProvider from 'fixtures/stubbed_search_source';
import MockState from 'fixtures/mock_state';
import { AggResponseIndexProvider } from '../../agg_response';
describe('visualization_editor directive', function () {
let $rootScope;
let $compile;
let $scope;
let $el;
let Vis;
let indexPattern;
let fixtures;
let searchSource;
let appState;
let $timeout;
let vis;
let aggResponse;
beforeEach(ngMock.module('kibana', 'kibana/table_vis'));
beforeEach(ngMock.inject(function (Private, $injector) {
$rootScope = $injector.get('$rootScope');
$compile = $injector.get('$compile');
$timeout = $injector.get('$timeout');
fixtures = require('fixtures/fake_hierarchical_data');
Vis = Private(VisProvider);
appState = new MockState({ filters: [] });
appState.toJSON = () => { return {}; };
indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
searchSource = Private(FixturesStubbedSearchSourceProvider);
aggResponse = Private(AggResponseIndexProvider);
const requiresSearch = false;
vis = new CreateVis(null, requiresSearch);
init(vis, fixtures.oneRangeBucket);
$timeout.flush();
$timeout.verifyNoPendingTasks();
}));
// basically a parameterized beforeEach
function init(vis, esResponse) {
vis.aggs.forEach(function (agg, i) { agg.id = 'agg_' + (i + 1); });
$rootScope.vis = vis;
$rootScope.visData = aggResponse.tabify(vis.getAggConfig().getResponseAggs(), esResponse, {
isHierarchical: vis.isHierarchical()
});
$rootScope.searchSource = searchSource;
$el = $('<visualization-editor vis="vis" vis-data="visData" search-source="searchSource">');
$compile($el)($rootScope);
$rootScope.$apply();
$scope = $el.isolateScope();
}
function CreateVis(params, requiresSearch) {
const vis = new Vis(indexPattern, {
type: 'table',
params: params || {},
aggs: [
{ type: 'count', schema: 'metric' },
{
type: 'range',
schema: 'bucket',
params: {
field: 'bytes',
ranges: [
{ from: 0, to: 1000 },
{ from: 1000, to: 2000 }
]
}
}
]
});
vis.type.requestHandler = requiresSearch ? 'default' : 'none';
vis.type.responseHandler = 'none';
vis.type.requiresSearch = requiresSearch;
return vis;
}
it('calls render complete when editor is rendered', function () {
let renderComplete = 0;
$scope.renderFunction = () => {
renderComplete++;
};
$scope.$emit('render');
$scope.$apply();
$timeout.flush();
$timeout.verifyNoPendingTasks();
expect(renderComplete).to.equal(1);
});
});

View file

@ -24,7 +24,6 @@ import sinon from 'sinon';
import { VisProvider } from '../../vis';
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
import FixturesStubbedSearchSourceProvider from 'fixtures/stubbed_search_source';
import MockState from 'fixtures/mock_state';
import { PersistedState } from '../../persisted_state';
describe('visualize directive', function () {
@ -36,7 +35,7 @@ describe('visualize directive', function () {
let indexPattern;
let fixtures;
let searchSource;
let appState;
let updateState;
let uiState;
beforeEach(ngMock.module('kibana', 'kibana/table_vis'));
@ -45,8 +44,6 @@ describe('visualize directive', function () {
$compile = $injector.get('$compile');
fixtures = require('fixtures/fake_hierarchical_data');
Vis = Private(VisProvider);
appState = new MockState({ filters: [] });
appState.toJSON = () => { return {}; };
uiState = new PersistedState({});
indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
searchSource = Private(FixturesStubbedSearchSourceProvider);
@ -65,14 +62,14 @@ describe('visualize directive', function () {
$rootScope.vis = vis;
$rootScope.esResponse = esResponse;
$rootScope.uiState = uiState;
$rootScope.appState = appState;
$rootScope.appState.vis = vis.getState();
$rootScope.searchSource = searchSource;
$rootScope.savedObject = {
vis: vis,
searchSource: searchSource
};
$el = $('<visualize saved-obj="savedObject" ui-state="uiState" app-state="appState">');
$rootScope.updateState = updateState;
$el = $('<visualize saved-obj="savedObject" ui-state="uiState" update-state="updateState">');
$compile($el)($rootScope);
$rootScope.$apply();
@ -118,11 +115,13 @@ describe('visualize directive', function () {
expect(counter).to.equal(1);
});
it('updates the appState in editor mode on update event', () => {
$scope.editorMode = true;
$scope.appState.vis = {};
it('calls the updateState on update event', () => {
let visState = {};
updateState = (state) => {
visState = state.vis;
};
$scope.vis.emit('update');
expect($scope.appState.vis).to.not.equal({});
expect(visState).to.not.equal({});
});
describe('request handler', () => {

View file

@ -38,7 +38,7 @@ export class VisualizationChart extends Component {
tap(() => {
dispatchRenderStart(this.chartDiv.current);
}),
filter(({ vis, visData, container }) => vis && vis.initialized && container && (!vis.type.requiresSearch || visData)),
filter(({ vis, visData, container }) => vis && container && (!vis.type.requiresSearch || visData)),
debounceTime(100),
switchMap(async ({ vis, visData, container }) => {
vis.size = [container.clientWidth, container.clientHeight];
@ -85,23 +85,18 @@ export class VisualizationChart extends Component {
};
componentDidMount() {
const { vis, listenOnChange } = this.props;
const { vis, onInit } = this.props;
const Visualization = vis.type.visualization;
this.visualization = new Visualization(this.chartDiv.current, vis);
if (this.visualization.isLoaded) {
this.visualization.isLoaded().then(() => {
vis.initialized = true;
});
} else {
vis.initialized = true;
if (onInit) {
const visLoaded = this.visualization.isLoaded ? this.visualization.isLoaded() : true;
Promise.resolve(visLoaded).then(onInit);
}
if (listenOnChange) {
this.resizeChecker = new ResizeChecker(this.containerDiv.current);
this.resizeChecker.on('resize', this._startRenderVisualization);
}
this.resizeChecker = new ResizeChecker(this.containerDiv.current);
this.resizeChecker.on('resize', this._startRenderVisualization);
this._startRenderVisualization();
}

View file

@ -43,7 +43,6 @@ class VisualizationStub {
describe('VisualizationChart', () => {
const vis = {
initialized: true,
type: {
title: 'Test Visualization',
visualization: VisualizationStub
@ -65,7 +64,7 @@ describe('VisualizationChart', () => {
domNode.addEventListener('renderStart', renderStart);
domNode.addEventListener('renderComplete', renderComplete);
mount(<VisualizationChart vis={vis} listenOnChange={true} />, {
mount(<VisualizationChart vis={vis} />, {
attachTo: domNode
});
@ -77,7 +76,7 @@ describe('VisualizationChart', () => {
});
it('should render visualization', async () => {
const wrapper = mount(<VisualizationChart vis={vis} listenOnChange={true} />);
const wrapper = mount(<VisualizationChart vis={vis} />);
jest.runAllTimers();
await renderPromise;
expect(wrapper.find('.visualize-chart').text()).toMatch(/markdown/);
@ -85,7 +84,7 @@ describe('VisualizationChart', () => {
it('should re-render on param change', async () => {
const renderComplete = jest.fn();
const wrapper = mount(<VisualizationChart vis={vis} listenOnChange={true} />);
const wrapper = mount(<VisualizationChart vis={vis} />);
const domNode = wrapper.getDOMNode();
domNode.addEventListener('renderComplete', renderComplete);
jest.runAllTimers();

View file

@ -1,6 +1,6 @@
<visualize
saved-obj="savedObj"
app-state="appState"
update-state="updateState"
ui-state="uiState"
time-range="timeRange"
filters="filters"

View file

@ -24,8 +24,16 @@ import { Visualization } from 'ui/visualize/visualization';
export const visualizationLoader = (element, vis, visData, uiState, params) => {
const listenOnChange = _.get(params, 'listenOnChange', false);
render(<Visualization vis={vis} visData={visData} uiState={uiState} listenOnChange={listenOnChange} />, element);
return new Promise(resolve => {
const listenOnChange = _.get(params, 'listenOnChange', false);
render(<Visualization
vis={vis}
visData={visData}
uiState={uiState}
listenOnChange={listenOnChange}
onInit={resolve}
/>, element);
});
};
visualizationLoader.destroy = (element) => {

View file

@ -57,11 +57,16 @@ const VisualizeLoaderProvider = ($compile, $rootScope, savedVisualizations) => {
const scope = $rootScope.$new();
params = params || {};
scope.savedObj = savedObj;
scope.appState = params.appState;
scope.uiState = params.uiState;
scope.timeRange = params.timeRange;
scope.filters = params.filters;
scope.query = params.query;
scope.updateState = (visState) => {
if (params.appState) {
params.appState.vis = visState;
params.appState.save();
}
};
const container = angular.element(el);

View file

@ -62,7 +62,7 @@ export class Visualization extends Component {
}
render() {
const { vis, visData, listenOnChange } = this.props;
const { vis, visData, onInit } = this.props;
return (
<div className="visualization">
@ -70,7 +70,7 @@ export class Visualization extends Component {
(<VisualizationChart
vis={vis}
visData={visData}
listenOnChange={listenOnChange}
onInit={onInit}
/>)
}
</div>

View file

@ -1,9 +0,0 @@
<visualization-editor
ng-if="editorMode===true"
vis="vis"
vis-data="visData"
ui-state="uiState"
class="vis-editor-content"
search-source="savedObj.searchSource"
/>

View file

@ -19,14 +19,11 @@
import _ from 'lodash';
import { uiModules } from '../modules';
import visualizeTemplate from './visualize.html';
import { VisRequestHandlersRegistryProvider } from '../registry/vis_request_handlers';
import { VisResponseHandlersRegistryProvider } from '../registry/vis_response_handlers';
import 'angular-sanitize';
import './visualization';
import './visualization_editor';
import { FilterBarQueryFilterProvider } from '../filter_bar/query_filter';
import { ResizeChecker } from '../resize_checker';
import { visualizationLoader } from './loader/visualization_loader';
import {
@ -35,7 +32,7 @@ import {
uiModules
.get('kibana/directive', ['ngSanitize'])
.directive('visualize', function ($timeout, Notifier, Private, getAppState, Promise) {
.directive('visualize', function ($timeout, Notifier, Private, Promise) {
const notify = new Notifier({ location: 'Visualize' });
const requestHandlers = Private(VisRequestHandlersRegistryProvider);
const responseHandlers = Private(VisResponseHandlersRegistryProvider);
@ -49,28 +46,18 @@ uiModules
return {
restrict: 'E',
scope: {
editorMode: '=?',
savedObj: '=?',
appState: '=?',
uiState: '=?',
timeRange: '=?',
filters: '=?',
query: '=?',
updateState: '=?',
},
template: visualizeTemplate,
link: async function ($scope, $el) {
let destroyed = false;
let loaded = false;
let forceFetch = false;
if (!$scope.savedObj) throw(`saved object was not provided to <visualize> directive`);
if (!$scope.appState) $scope.appState = getAppState();
const resizeChecker = new ResizeChecker($el, { disabled: true });
$timeout(() => {
// We give the visualize one digest cycle time to actually render before
// we start tracking its size. If we don't do that, we cause a double
// initial rendering in editor mode.
resizeChecker.enable();
});
$scope.vis = $scope.savedObj.vis;
$scope.vis.searchSource = $scope.savedObj.searchSource;
@ -81,9 +68,6 @@ uiModules
$scope.vis.description = $scope.savedObj.description;
$scope.editorMode = $scope.editorMode || false;
$scope.vis.editorMode = $scope.editorMode;
const requestHandler = getHandler(requestHandlers, $scope.vis.type.requestHandler);
const responseHandler = getHandler(responseHandlers, $scope.vis.type.responseHandler);
@ -91,12 +75,11 @@ uiModules
// If destroyed == true the scope has already been destroyed, while this method
// was still waiting for its debounce, in this case we don't want to start
// fetching new data and rendering.
if (!$scope.vis.initialized || !$scope.savedObj || destroyed) return;
if (!loaded || !$scope.savedObj || destroyed) return;
$scope.vis.filters = { timeRange: $scope.timeRange };
const handlerParams = {
appState: $scope.appState,
uiState: $scope.uiState,
queryFilter: queryFilter,
searchSource: $scope.savedObj.searchSource,
@ -138,21 +121,17 @@ uiModules
})
.then(resp => {
$scope.visData = resp;
$scope.$apply();
$scope.$broadcast('render');
if (!$scope.editorMode) {
visualizationLoader($el[0], $scope.vis, $scope.visData, $scope.uiState, { listenOnChange: false });
}
visualizationLoader($el[0], $scope.vis, $scope.visData, $scope.uiState, { listenOnChange: false });
return resp;
});
}, 100);
const handleVisUpdate = () => {
if ($scope.appState.vis) {
$scope.appState.vis = $scope.vis.getState();
$scope.appState.save();
if ($scope.updateState) {
const visState = $scope.vis.getState();
$scope.updateState(visState);
}
$scope.fetch();
};
@ -178,29 +157,25 @@ uiModules
// cached data otherwise.
$scope.uiState.on('change', $scope.fetch);
resizeChecker.on('resize', $scope.fetch);
$scope.$on('$destroy', () => {
destroyed = true;
$scope.vis.removeListener('reload', reload);
$scope.vis.removeListener('update', handleVisUpdate);
$scope.uiState.off('change', $scope.fetch);
resizeChecker.destroy();
visualizationLoader.destroy($el[0]);
});
$scope.$watch('vis.initialized', () => {
visualizationLoader(
$el[0],
$scope.vis,
$scope.visData,
$scope.uiState,
{ listenOnChange: false }
).then(() => {
loaded = true;
$scope.fetch();
});
if (!$scope.editorMode) {
visualizationLoader(
$el[0],
$scope.vis,
$scope.visData,
$scope.uiState,
{ listenOnChange: false }
);
} }
}
};
});

View file

@ -1,5 +1,11 @@
@import (reference) "~ui/styles/variables";
visualize {
display: flex;
flex: 1 1 100%;
overflow-x: hidden;
}
.visualization {
display: flex;
flex-direction: column;
@ -8,6 +14,7 @@
overflow: auto;
position: relative;
padding: 8px 8px 8px 8px;
flex: 1 1 100%;
.k4tip {
white-space: pre-line;

View file

@ -167,7 +167,7 @@ export default function ({ getService, getPageObjects }) {
it('should apply filter with unformatted value', async function () {
await PageObjects.visualize.selectTagCloudTag('30GB');
await PageObjects.common.sleep(500);
await PageObjects.header.waitUntilLoadingHasFinished();
const data = await PageObjects.visualize.getTextTag();
expect(data).to.eql([ '30GB' ]);
});

View file

@ -38,12 +38,14 @@ export default function ({ getService, getPageObjects }) {
it('should show the correct count in the legend with 2h offset', async function () {
await PageObjects.visualBuilder.clickSeriesOption();
await PageObjects.visualBuilder.enterOffsetSeries('2h');
await PageObjects.header.waitUntilLoadingHasFinished();
const actualCount = await PageObjects.visualBuilder.getRhythmChartLegendValue();
expect(actualCount).to.be('293');
});
it('should show the correct count in the legend with -2h offset', async function () {
await PageObjects.visualBuilder.enterOffsetSeries('-2h');
await PageObjects.header.waitUntilLoadingHasFinished();
const actualCount = await PageObjects.visualBuilder.getRhythmChartLegendValue();
expect(actualCount).to.be('53');
});
@ -163,6 +165,7 @@ export default function ({ getService, getPageObjects }) {
it('allow positive time offsets', async () => {
await PageObjects.visualBuilder.enterOffsetSeries('2h');
await PageObjects.header.waitUntilLoadingHasFinished();
const text = await PageObjects.visualBuilder.getMarkdownText();
const [timestamp, value] = text.split('#');
expect(timestamp).to.be('1442901600000');
@ -171,6 +174,7 @@ export default function ({ getService, getPageObjects }) {
it('allow negative time offsets', async () => {
await PageObjects.visualBuilder.enterOffsetSeries('-2h');
await PageObjects.header.waitUntilLoadingHasFinished();
const text = await PageObjects.visualBuilder.getMarkdownText();
const [timestamp, value] = text.split('#');
expect(timestamp).to.be('1442901600000');