Revert "Visualize Refactor (#11786)" (#12711)

This reverts commit 3000221fac.

This change broke Xpack, due to some modules being moved to a different location.
This commit is contained in:
Thomas Neirynck 2017-07-07 16:32:18 -04:00 committed by GitHub
parent 363a06555c
commit c20a48cbc9
170 changed files with 3003 additions and 2837 deletions

View file

@ -1,22 +1,22 @@
import { VisFactoryProvider } from 'ui/vis/vis_factory';
import { VisSchemasProvider } from 'ui/vis/editors/default/schemas';
import { CATEGORY } from 'ui/vis/vis_category';
import { VisVisTypeProvider } from 'ui/vis/vis_type';
import { VislibVisTypeVislibVisTypeProvider } from 'ui/vislib_vis_type/vislib_vis_type';
import { VisSchemasProvider } from 'ui/vis/schemas';
import pointSeriesTemplate from 'plugins/kbn_vislib_vis_types/editors/point_series.html';
import image from './images/icon-area.svg';
export default function PointSeriesVisType(Private) {
const VisFactory = Private(VisFactoryProvider);
const VisType = Private(VisVisTypeProvider);
const VislibVisType = Private(VislibVisTypeVislibVisTypeProvider);
const Schemas = Private(VisSchemasProvider);
return VisFactory.createVislibVisualization({
return new VislibVisType({
name: 'area',
title: 'Area',
image,
description: 'Emphasize the quantity beneath a line chart',
category: CATEGORY.BASIC,
visConfig: {
category: VisType.CATEGORY.BASIC,
params: {
defaults: {
type: 'area',
grid: {
categoryLines: false,
style: {
@ -29,7 +29,8 @@ export default function PointSeriesVisType(Private) {
type: 'category',
position: 'bottom',
show: true,
style: {},
style: {
},
scale: {
type: 'linear'
},
@ -47,7 +48,8 @@ export default function PointSeriesVisType(Private) {
type: 'value',
position: 'left',
show: true,
style: {},
style: {
},
scale: {
type: 'linear',
mode: 'normal'
@ -82,34 +84,31 @@ export default function PointSeriesVisType(Private) {
times: [],
addTimeMarker: false,
},
},
editorConfig: {
collections: {
positions: ['top', 'left', 'right', 'bottom'],
chartTypes: [{
value: 'line',
text: 'line'
}, {
value: 'area',
text: 'area'
}, {
value: 'histogram',
text: 'bar'
}],
axisModes: ['normal', 'percentage', 'wiggle', 'silhouette'],
scaleTypes: ['linear', 'log', 'square root'],
chartModes: ['normal', 'stacked'],
interpolationModes: [{
value: 'linear',
text: 'straight',
}, {
value: 'cardinal',
text: 'smoothed',
}, {
value: 'step-after',
text: 'stepped',
}],
},
positions: ['top', 'left', 'right', 'bottom'],
chartTypes: [{
value: 'line',
text: 'line'
}, {
value: 'area',
text: 'area'
}, {
value: 'histogram',
text: 'bar'
}],
axisModes: ['normal', 'percentage', 'wiggle', 'silhouette'],
scaleTypes: ['linear', 'log', 'square root'],
chartModes: ['normal', 'stacked'],
interpolationModes: [{
value: 'linear',
text: 'straight',
}, {
value: 'cardinal',
text: 'smoothed',
}, {
value: 'step-after',
text: 'stepped',
}],
editor: pointSeriesTemplate,
optionTabs: [
{
name: 'advanced',
@ -119,50 +118,50 @@ export default function PointSeriesVisType(Private) {
},
{ name: 'options', title: 'Panel Settings', editor: pointSeriesTemplate },
],
schemas: new Schemas([
{
group: 'metrics',
name: 'metric',
title: 'Y-Axis',
aggFilter: ['!geo_centroid'],
min: 1,
defaults: [
{ schema: 'metric', type: 'count' }
]
},
{
group: 'metrics',
name: 'radius',
title: 'Dot Size',
min: 0,
max: 1,
aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality']
},
{
group: 'buckets',
name: 'segment',
title: 'X-Axis',
min: 0,
max: 1,
aggFilter: '!geohash_grid'
},
{
group: 'buckets',
name: 'group',
title: 'Split Series',
min: 0,
max: 1,
aggFilter: '!geohash_grid'
},
{
group: 'buckets',
name: 'split',
title: 'Split Chart',
min: 0,
max: 1,
aggFilter: '!geohash_grid'
}
])
}
},
schemas: new Schemas([
{
group: 'metrics',
name: 'metric',
title: 'Y-Axis',
aggFilter: ['!geo_centroid'],
min: 1,
defaults: [
{ schema: 'metric', type: 'count' }
]
},
{
group: 'metrics',
name: 'radius',
title: 'Dot Size',
min: 0,
max: 1,
aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality']
},
{
group: 'buckets',
name: 'segment',
title: 'X-Axis',
min: 0,
max: 1,
aggFilter: '!geohash_grid'
},
{
group: 'buckets',
name: 'group',
title: 'Split Series',
min: 0,
max: 1,
aggFilter: '!geohash_grid'
},
{
group: 'buckets',
name: 'split',
title: 'Split Chart',
min: 0,
max: 1,
aggFilter: '!geohash_grid'
}
])
});
}

View file

@ -8,7 +8,7 @@
id="gaugeType"
class="kuiSelect kuiSideBarSelect"
ng-model="vis.params.gauge.gaugeType"
ng-options="mode for mode in collections.gaugeTypes"
ng-options="mode for mode in vis.type.params.gaugeTypes"
></select>
</div>
</div>
@ -183,7 +183,7 @@
id="colorSchema"
class="kuiSelect kuiSideBarSelect"
ng-model="vis.params.gauge.colorSchema"
ng-options="mode for mode in collections.colorSchemas"
ng-options="mode for mode in vis.type.params.colorSchemas"
></select>
</div>
<div class="text-info text-center" ng-show="customColors" ng-click="resetColors()">reset colors</div>
@ -255,7 +255,7 @@
id="gaugeColorMode"
class="kuiSelect kuiSideBarSelect"
ng-model="vis.params.gauge.gaugeColorMode"
ng-options="mode for mode in collections.gaugeColorMode"
ng-options="mode for mode in vis.type.params.gaugeColorMode"
></select>
</div>
</div>

View file

@ -9,7 +9,7 @@ module.directive('gaugeOptions', function () {
template: gaugeOptionsTemplate,
replace: true,
link: function ($scope) {
$scope.collections = $scope.vis.type.editorConfig.collections;
$scope.showColorRange = true;
$scope.$watch('vis.params.gauge.gaugeType', type => {

View file

@ -8,7 +8,7 @@
id="colorSchema"
class="kuiSelect kuiSideBarSelect"
ng-model="vis.params.colorSchema"
ng-options="mode for mode in collections.colorSchemas"
ng-options="mode for mode in vis.type.params.colorSchemas"
></select>
</div>
<div
@ -39,7 +39,7 @@
id="axisScale"
class="kuiSelect kuiSideBarSelect"
ng-model="vis.params.valueAxes[0].scale.type"
ng-options="mode for mode in collections.scales"
ng-options="mode for mode in vis.type.params.scales"
></select>
</div>
</div>
@ -141,7 +141,7 @@
ng-click="removeRange($index)"
class="kuiButton kuiButton--danger kuiButton--small"
>
<span class="kuiIcon fa-times"></span>
<span class="kuiIcon fa-times"><span>
</button>
</td>
</tr>
@ -149,7 +149,7 @@
<div class="hintbox" ng-show="!vis.params.colorsRange.length">
<p>
<span class="kuiIcon fa-danger text-danger"></span>
<span class="kuiIcon fa-danger text-danger"><span>
<strong>Required:</strong> You must specify at least one range.
</p>
</div>

View file

@ -9,9 +9,6 @@ module.directive('heatmapOptions', function () {
template: heatmapOptionsTemplate,
replace: true,
link: function ($scope) {
$scope.collections = $scope.vis.type.editorConfig.collections;
const verticalRotation = 270;
$scope.showColorRange = false;
$scope.showLabels = false;

View file

@ -25,7 +25,7 @@
id="categoryAxisPosition"
class="kuiSelect kuiSideBarSelect"
ng-model="vis.params.categoryAxes[0].position"
ng-options="mode for mode in vis.type.editorConfig.collections.positions"
ng-options="mode for mode in vis.type.params.positions"
></select>
</div>
</div>

View file

@ -40,7 +40,7 @@
id="{{ 'seriesType' + $index }}"
class="kuiSelect kuiSideBarSelect"
ng-model="chart.type"
ng-options="mode.value as mode.text for mode in vis.type.editorConfig.collections.chartTypes"
ng-options="mode.value as mode.text for mode in vis.type.params.chartTypes"
></select>
</div>
</div>
@ -54,7 +54,7 @@
id="{{ 'seriesMode' + $index }}"
class="kuiSelect kuiSideBarSelect"
ng-model="chart.mode"
ng-options="mode for mode in vis.type.editorConfig.collections.chartModes"
ng-options="mode for mode in vis.type.params.chartModes"
></select>
</div>
</div>
@ -86,7 +86,7 @@
id="{{ 'lineMode' + $index }}"
class="kuiSelect kuiSideBarSelect"
ng-model="chart.interpolate"
ng-options="mode.value as mode.text for mode in vis.type.editorConfig.collections.interpolationModes"
ng-options="mode.value as mode.text for mode in vis.type.params.interpolationModes"
>
</select>
</div>

View file

@ -61,7 +61,7 @@ module.directive('vislibSeries', function () {
return $scope.vis.params.seriesParams.map(series => series.type).join();
}, () => {
const types = _.uniq(_.map($scope.vis.params.seriesParams, 'type'));
$scope.vis.type.type = types.length === 1 ? types[0] : 'histogram';
$scope.savedVis.type = types.length === 1 ? types[0] : 'histogram';
});
$scope.$watch('vis.params.valueAxes.length', () => {

View file

@ -84,7 +84,7 @@
class="kuiSelect kuiSideBarSelect"
ng-change="updateAxisName(axis)"
ng-model="axis.position"
ng-options="mode disable when isPositionDisabled(mode) for mode in vis.type.editorConfig.collections.positions"
ng-options="mode disable when isPositionDisabled(mode) for mode in vis.type.params.positions"
></select>
</div>
</div>
@ -98,7 +98,7 @@
id="{{ 'valueAxisMode' + $index }}"
class="kuiSelect kuiSideBarSelect"
ng-model="axis.scale.mode"
ng-options="mode for mode in vis.type.editorConfig.collections.axisModes"
ng-options="mode for mode in vis.type.params.axisModes"
></select>
</div>
</div>
@ -112,7 +112,7 @@
id="{{ 'valueAxisScaleType' + $index }}"
class="kuiSelect kuiSideBarSelect"
ng-model="axis.scale.type"
ng-options="type for type in vis.type.editorConfig.collections.scaleTypes"
ng-options="type for type in vis.type.params.scaleTypes"
></select>
</div>
</div>

View file

@ -6,7 +6,7 @@
<select
class="form-control"
ng-model="vis.params.legendPosition"
ng-options="position.value as position.text for position in vis.type.editorConfig.collections.legendPositions"
ng-options="position.value as position.text for position in vis.type.params.legendPositions"
>
</select>
</div>

View file

@ -20,7 +20,7 @@ describe('point series editor', function () {
function makeConfig() {
return {
type: 'line',
params: lineVisType.visConfig.defaults,
params: lineVisType.params.defaults,
aggs: [
{ type: 'count', schema: 'metric', params: { field: 'bytes' } },
{ type: 'terms', schema: 'segment', params: { field: 'machine.os' } },

View file

@ -32,7 +32,7 @@
id="legendPosition"
class="kuiSelect kuiSideBarSelect"
ng-model="vis.params.legendPosition"
ng-options="position.value as position.text for position in vis.type.editorConfig.collections.legendPositions"
ng-options="position.value as position.text for position in vis.type.params.legendPositions"
></select>
</div>
</div>

View file

@ -4,8 +4,8 @@
<select name="agg"
class="form-control"
ng-model="vis.params.mapType"
ng-init="vis.params.mapType || vis.type.editorConfig.collections.mapTypes[0]"
ng-options="mapType as mapType for mapType in vis.type.editorConfig.collections.mapTypes"
ng-init="vis.params.mapType || vis.type.params.mapTypes[0]"
ng-options="mapType as mapType for mapType in vis.type.params.mapTypes"
>
</select>
</div>
@ -104,7 +104,7 @@
<label>
<input type="checkbox"
name="isDesaturated"
ng-disabled="!vis.type.visConfig.canDesaturate"
ng-disabled="!vis.type.params.canDesaturate"
ng-model="vis.params.isDesaturated">
Desaturate map tiles

View file

@ -1,24 +1,24 @@
import { VisFactoryProvider } from 'ui/vis/vis_factory';
import { VisSchemasProvider } from 'ui/vis/editors/default/schemas';
import { CATEGORY } from 'ui/vis/vis_category';
import { VisVisTypeProvider } from 'ui/vis/vis_type';
import { VislibVisTypeVislibVisTypeProvider } from 'ui/vislib_vis_type/vislib_vis_type';
import { VisSchemasProvider } from 'ui/vis/schemas';
import gaugeTemplate from 'plugins/kbn_vislib_vis_types/editors/gauge.html';
import { vislibColorMaps } from 'ui/vislib/components/color/colormaps';
import image from './images/icon-gauge.svg';
export default function GaugeVisType(Private) {
const VisFactory = Private(VisFactoryProvider);
const VisType = Private(VisVisTypeProvider);
const VislibVisType = Private(VislibVisTypeVislibVisTypeProvider);
const Schemas = Private(VisSchemasProvider);
return VisFactory.createVislibVisualization({
return new VislibVisType({
name: 'gauge',
title: 'Gauge',
image,
description: `Gauges indicate the status of a metric. Use it to show how a metric's value relates
to reference threshold values.`,
category: CATEGORY.DATA,
visConfig: {
category: VisType.CATEGORY.DATA,
params: {
defaults: {
type:'gauge',
addTooltip: true,
addLegend: true,
@ -61,37 +61,35 @@ export default function GaugeVisType(Private) {
}
}
},
gaugeTypes: ['Arc', 'Circle', 'Metric'],
gaugeColorMode: ['None', 'Labels', 'Background'],
gaugeStyles: ['Full', 'Bars', 'Lines'],
scales: ['linear', 'log', 'square root'],
colorSchemas: Object.keys(vislibColorMaps),
editor: gaugeTemplate
},
editorConfig: {
collections: {
gaugeTypes: ['Arc', 'Circle', 'Metric'],
gaugeColorMode: ['None', 'Labels', 'Background'],
scales: ['linear', 'log', 'square root'],
colorSchemas: Object.keys(vislibColorMaps),
implementsRenderComplete: true,
schemas: new Schemas([
{
group: 'metrics',
name: 'metric',
title: 'Metric',
min: 1,
aggFilter: [
'!std_dev', '!geo_centroid', '!percentiles', '!percentile_ranks',
'!derivative', '!serial_diff', '!moving_avg', '!cumulative_sum'],
defaults: [
{ schema: 'metric', type: 'count' }
]
},
optionsTemplate: gaugeTemplate,
schemas: new Schemas([
{
group: 'metrics',
name: 'metric',
title: 'Metric',
min: 1,
aggFilter: [
'!std_dev', '!geo_centroid', '!percentiles', '!percentile_ranks',
'!derivative', '!serial_diff', '!moving_avg', '!cumulative_sum'],
defaults: [
{ schema: 'metric', type: 'count' }
]
},
{
group: 'buckets',
name: 'group',
title: 'Split Group',
min: 0,
max: 1,
aggFilter: '!geohash_grid'
}
])
}
{
group: 'buckets',
name: 'group',
title: 'Split Group',
min: 0,
max: 1,
aggFilter: '!geohash_grid'
}
])
});
}

View file

@ -1,21 +1,22 @@
import { VisFactoryProvider } from 'ui/vis/vis_factory';
import { VisSchemasProvider } from 'ui/vis/editors/default/schemas';
import { CATEGORY } from 'ui/vis/vis_category';
import { VisVisTypeProvider } from 'ui/vis/vis_type';
import { VislibVisTypeVislibVisTypeProvider } from 'ui/vislib_vis_type/vislib_vis_type';
import { VisSchemasProvider } from 'ui/vis/schemas';
import gaugeTemplate from 'plugins/kbn_vislib_vis_types/editors/gauge.html';
import { vislibColorMaps } from 'ui/vislib/components/color/colormaps';
import image from './images/icon-goal.svg';
export default function GoalVisType(Private) {
const VisFactory = Private(VisFactoryProvider);
const VisType = Private(VisVisTypeProvider);
const VislibVisType = Private(VislibVisTypeVislibVisTypeProvider);
const Schemas = Private(VisSchemasProvider);
return VisFactory.createVislibVisualization({
return new VislibVisType({
name: 'goal',
title: 'Goal',
image,
description: 'A goal chart indicates how close you are to your final goal.',
category: CATEGORY.DATA,
visConfig: {
category: VisType.CATEGORY.DATA,
params: {
defaults: {
addTooltip: true,
addLegend: false,
@ -55,37 +56,34 @@ export default function GoalVisType(Private) {
}
}
},
gaugeTypes: ['Arc', 'Circle', 'Metric'],
gaugeColorMode: ['None', 'Labels', 'Background'],
scales: ['linear', 'log', 'square root'],
colorSchemas: Object.keys(vislibColorMaps),
editor: gaugeTemplate
},
editorConfig: {
collections: {
gaugeTypes: ['Arc', 'Circle', 'Metric'],
gaugeColorMode: ['None', 'Labels', 'Background'],
scales: ['linear', 'log', 'square root'],
colorSchemas: Object.keys(vislibColorMaps),
implementsRenderComplete: true,
schemas: new Schemas([
{
group: 'metrics',
name: 'metric',
title: 'Metric',
min: 1,
aggFilter: [
'!std_dev', '!geo_centroid', '!percentiles', '!percentile_ranks',
'!derivative', '!serial_diff', '!moving_avg', '!cumulative_sum'],
defaults: [
{ schema: 'metric', type: 'count' }
]
},
optionsTemplate: gaugeTemplate,
schemas: new Schemas([
{
group: 'metrics',
name: 'metric',
title: 'Metric',
min: 1,
aggFilter: [
'!std_dev', '!geo_centroid', '!percentiles', '!percentile_ranks',
'!derivative', '!serial_diff', '!moving_avg', '!cumulative_sum'],
defaults: [
{ schema: 'metric', type: 'count' }
]
},
{
group: 'buckets',
name: 'group',
title: 'Split Group',
min: 0,
max: 1,
aggFilter: '!geohash_grid'
}
])
}
{
group: 'buckets',
name: 'group',
title: 'Split Group',
min: 0,
max: 1,
aggFilter: '!geohash_grid'
}
])
});
}

View file

@ -1,23 +1,23 @@
import { VisFactoryProvider } from 'ui/vis/vis_factory';
import { VisSchemasProvider } from 'ui/vis/editors/default/schemas';
import { CATEGORY } from 'ui/vis/vis_category';
import { VisVisTypeProvider } from 'ui/vis/vis_type';
import { VislibVisTypeVislibVisTypeProvider } from 'ui/vislib_vis_type/vislib_vis_type';
import { VisSchemasProvider } from 'ui/vis/schemas';
import heatmapTemplate from 'plugins/kbn_vislib_vis_types/editors/heatmap.html';
import { vislibColorMaps } from 'ui/vislib/components/color/colormaps';
import image from './images/icon-heatmap.svg';
export default function HeatmapVisType(Private) {
const VisFactory = Private(VisFactoryProvider);
const VisType = Private(VisVisTypeProvider);
const VislibVisType = Private(VislibVisTypeVislibVisTypeProvider);
const Schemas = Private(VisSchemasProvider);
return VisFactory.createVislibVisualization({
return new VislibVisType({
name: 'heatmap',
title: 'Heat Map',
image,
description: 'Shade cells within a matrix',
category: CATEGORY.BASIC,
visConfig: {
category: VisType.CATEGORY.BASIC,
params: {
defaults: {
type: 'heatmap',
addTooltip: true,
addLegend: true,
enableHover: false,
@ -44,64 +44,59 @@ export default function HeatmapVisType(Private) {
}
}]
},
legendPositions: [{
value: 'left',
text: 'left',
}, {
value: 'right',
text: 'right',
}, {
value: 'top',
text: 'top',
}, {
value: 'bottom',
text: 'bottom',
}],
scales: ['linear', 'log', 'square root'],
colorSchemas: Object.keys(vislibColorMaps),
editor: heatmapTemplate
},
editorConfig: {
collections: {
legendPositions: [{
value: 'left',
text: 'left',
}, {
value: 'right',
text: 'right',
}, {
value: 'top',
text: 'top',
}, {
value: 'bottom',
text: 'bottom',
}],
scales: ['linear', 'log', 'square root'],
colorSchemas: Object.keys(vislibColorMaps),
schemas: new Schemas([
{
group: 'metrics',
name: 'metric',
title: 'Value',
min: 1,
max: 1,
aggFilter: ['count', 'avg', 'median', 'sum', 'min', 'max', 'cardinality', 'std_dev', 'top_hits'],
defaults: [
{ schema: 'metric', type: 'count' }
]
},
optionsTemplate: heatmapTemplate,
schemas: new Schemas([
{
group: 'metrics',
name: 'metric',
title: 'Value',
min: 1,
max: 1,
aggFilter: ['count', 'avg', 'median', 'sum', 'min', 'max', 'cardinality', 'std_dev', 'top_hits'],
defaults: [
{ schema: 'metric', type: 'count' }
]
},
{
group: 'buckets',
name: 'segment',
title: 'X-Axis',
min: 0,
max: 1,
aggFilter: '!geohash_grid'
},
{
group: 'buckets',
name: 'group',
title: 'Y-Axis',
min: 0,
max: 1,
aggFilter: '!geohash_grid'
},
{
group: 'buckets',
name: 'split',
title: 'Split Chart',
min: 0,
max: 1,
aggFilter: '!geohash_grid'
}
])
}
{
group: 'buckets',
name: 'segment',
title: 'X-Axis',
min: 0,
max: 1,
aggFilter: '!geohash_grid'
},
{
group: 'buckets',
name: 'group',
title: 'Y-Axis',
min: 0,
max: 1,
aggFilter: '!geohash_grid'
},
{
group: 'buckets',
name: 'split',
title: 'Split Chart',
min: 0,
max: 1,
aggFilter: '!geohash_grid'
}
])
});
}

View file

@ -1,22 +1,22 @@
import { VisFactoryProvider } from 'ui/vis/vis_factory';
import { VisSchemasProvider } from 'ui/vis/editors/default/schemas';
import { CATEGORY } from 'ui/vis/vis_category';
import { VisVisTypeProvider } from 'ui/vis/vis_type';
import { VislibVisTypeVislibVisTypeProvider } from 'ui/vislib_vis_type/vislib_vis_type';
import { VisSchemasProvider } from 'ui/vis/schemas';
import pointSeriesTemplate from 'plugins/kbn_vislib_vis_types/editors/point_series.html';
import image from './images/icon-vertical.svg';
export default function PointSeriesVisType(Private) {
const VisFactory = Private(VisFactoryProvider);
const VisType = Private(VisVisTypeProvider);
const VislibVisType = Private(VislibVisTypeVislibVisTypeProvider);
const Schemas = Private(VisSchemasProvider);
return VisFactory.createVislibVisualization({
return new VislibVisType({
name: 'histogram',
title: 'Vertical Bar',
image,
description: 'Assign a continuous variable to each axis',
category: CATEGORY.BASIC,
visConfig: {
category: VisType.CATEGORY.BASIC,
params: {
defaults: {
type: 'histogram',
grid: {
categoryLines: false,
style: {
@ -29,7 +29,8 @@ export default function PointSeriesVisType(Private) {
type: 'category',
position: 'bottom',
show: true,
style: {},
style: {
},
scale: {
type: 'linear'
},
@ -47,7 +48,8 @@ export default function PointSeriesVisType(Private) {
type: 'value',
position: 'left',
show: true,
style: {},
style: {
},
scale: {
type: 'linear',
mode: 'normal'
@ -83,34 +85,31 @@ export default function PointSeriesVisType(Private) {
times: [],
addTimeMarker: false,
},
},
editorConfig: {
collections: {
positions: ['top', 'left', 'right', 'bottom'],
chartTypes: [{
value: 'line',
text: 'line'
}, {
value: 'area',
text: 'area'
}, {
value: 'histogram',
text: 'bar'
}],
axisModes: ['normal', 'percentage', 'wiggle', 'silhouette'],
scaleTypes: ['linear', 'log', 'square root'],
chartModes: ['normal', 'stacked'],
interpolationModes: [{
value: 'linear',
text: 'straight',
}, {
value: 'cardinal',
text: 'smoothed',
}, {
value: 'step-after',
text: 'stepped',
}],
},
positions: ['top', 'left', 'right', 'bottom'],
chartTypes: [{
value: 'line',
text: 'line'
}, {
value: 'area',
text: 'area'
}, {
value: 'histogram',
text: 'bar'
}],
axisModes: ['normal', 'percentage', 'wiggle', 'silhouette'],
scaleTypes: ['linear', 'log', 'square root'],
chartModes: ['normal', 'stacked'],
interpolationModes: [{
value: 'linear',
text: 'straight',
}, {
value: 'cardinal',
text: 'smoothed',
}, {
value: 'step-after',
text: 'stepped',
}],
editor: pointSeriesTemplate,
optionTabs: [
{
name: 'advanced',
@ -120,51 +119,50 @@ export default function PointSeriesVisType(Private) {
},
{ name: 'options', title: 'Panel Settings', editor: pointSeriesTemplate },
],
schemas: new Schemas([
{
group: 'metrics',
name: 'metric',
title: 'Y-Axis',
min: 1,
aggFilter: ['!geo_centroid'],
defaults: [
{ schema: 'metric', type: 'count' }
]
},
{
group: 'metrics',
name: 'radius',
title: 'Dot Size',
min: 0,
max: 1,
aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality']
},
{
group: 'buckets',
name: 'segment',
title: 'X-Axis',
min: 0,
max: 1,
aggFilter: '!geohash_grid'
},
{
group: 'buckets',
name: 'group',
title: 'Split Series',
min: 0,
max: 1,
aggFilter: '!geohash_grid'
},
{
group: 'buckets',
name: 'split',
title: 'Split Chart',
min: 0,
max: 1,
aggFilter: '!geohash_grid'
}
])
}
},
schemas: new Schemas([
{
group: 'metrics',
name: 'metric',
title: 'Y-Axis',
min: 1,
aggFilter: ['!geo_centroid'],
defaults: [
{ schema: 'metric', type: 'count' }
]
},
{
group: 'metrics',
name: 'radius',
title: 'Dot Size',
min: 0,
max: 1,
aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality']
},
{
group: 'buckets',
name: 'segment',
title: 'X-Axis',
min: 0,
max: 1,
aggFilter: '!geohash_grid'
},
{
group: 'buckets',
name: 'group',
title: 'Split Series',
min: 0,
max: 1,
aggFilter: '!geohash_grid'
},
{
group: 'buckets',
name: 'split',
title: 'Split Chart',
min: 0,
max: 1,
aggFilter: '!geohash_grid'
}
])
});
}

View file

@ -1,22 +1,22 @@
import { VisFactoryProvider } from 'ui/vis/vis_factory';
import { VisSchemasProvider } from 'ui/vis/editors/default/schemas';
import { CATEGORY } from 'ui/vis/vis_category';
import { VisVisTypeProvider } from 'ui/vis/vis_type';
import { VislibVisTypeVislibVisTypeProvider } from 'ui/vislib_vis_type/vislib_vis_type';
import { VisSchemasProvider } from 'ui/vis/schemas';
import pointSeriesTemplate from 'plugins/kbn_vislib_vis_types/editors/point_series.html';
import image from './images/icon-horizontal.svg';
export default function PointSeriesVisType(Private) {
const VisFactory = Private(VisFactoryProvider);
const VisType = Private(VisVisTypeProvider);
const VislibVisType = Private(VislibVisTypeVislibVisTypeProvider);
const Schemas = Private(VisSchemasProvider);
return VisFactory.createVislibVisualization({
return new VislibVisType({
name: 'horizontal_bar',
title: 'Horizontal Bar',
image,
description: 'Assign a continuous variable to each axis',
category: CATEGORY.BASIC,
visConfig: {
category: VisType.CATEGORY.BASIC,
params: {
defaults: {
type: 'histogram',
grid: {
categoryLines: false,
style: {
@ -85,34 +85,31 @@ export default function PointSeriesVisType(Private) {
times: [],
addTimeMarker: false,
},
},
editorConfig: {
collections: {
positions: ['top', 'left', 'right', 'bottom'],
chartTypes: [{
value: 'line',
text: 'line'
}, {
value: 'area',
text: 'area'
}, {
value: 'histogram',
text: 'bar'
}],
axisModes: ['normal', 'percentage', 'wiggle', 'silhouette'],
scaleTypes: ['linear', 'log', 'square root'],
chartModes: ['normal', 'stacked'],
interpolationModes: [{
value: 'linear',
text: 'straight',
}, {
value: 'cardinal',
text: 'smoothed',
}, {
value: 'step-after',
text: 'stepped',
}],
},
positions: ['top', 'left', 'right', 'bottom'],
chartTypes: [{
value: 'line',
text: 'line'
}, {
value: 'area',
text: 'area'
}, {
value: 'histogram',
text: 'bar'
}],
axisModes: ['normal', 'percentage', 'wiggle', 'silhouette'],
scaleTypes: ['linear', 'log', 'square root'],
chartModes: ['normal', 'stacked'],
interpolationModes: [{
value: 'linear',
text: 'straight',
}, {
value: 'cardinal',
text: 'smoothed',
}, {
value: 'step-after',
text: 'stepped',
}],
editor: pointSeriesTemplate,
optionTabs: [
{
name: 'advanced',
@ -122,50 +119,50 @@ export default function PointSeriesVisType(Private) {
},
{ name: 'options', title: 'Panel Settings', editor: pointSeriesTemplate },
],
schemas: new Schemas([
{
group: 'metrics',
name: 'metric',
title: 'Y-Axis',
min: 1,
aggFilter: ['!geo_centroid'],
defaults: [
{ schema: 'metric', type: 'count' }
]
},
{
group: 'metrics',
name: 'radius',
title: 'Dot Size',
min: 0,
max: 1,
aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality']
},
{
group: 'buckets',
name: 'segment',
title: 'X-Axis',
min: 0,
max: 1,
aggFilter: '!geohash_grid'
},
{
group: 'buckets',
name: 'group',
title: 'Split Series',
min: 0,
max: 1,
aggFilter: '!geohash_grid'
},
{
group: 'buckets',
name: 'split',
title: 'Split Chart',
min: 0,
max: 1,
aggFilter: '!geohash_grid'
}
])
}
},
schemas: new Schemas([
{
group: 'metrics',
name: 'metric',
title: 'Y-Axis',
min: 1,
aggFilter: ['!geo_centroid'],
defaults: [
{ schema: 'metric', type: 'count' }
]
},
{
group: 'metrics',
name: 'radius',
title: 'Dot Size',
min: 0,
max: 1,
aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality']
},
{
group: 'buckets',
name: 'segment',
title: 'X-Axis',
min: 0,
max: 1,
aggFilter: '!geohash_grid'
},
{
group: 'buckets',
name: 'group',
title: 'Split Series',
min: 0,
max: 1,
aggFilter: '!geohash_grid'
},
{
group: 'buckets',
name: 'split',
title: 'Split Chart',
min: 0,
max: 1,
aggFilter: '!geohash_grid'
}
])
});
}

View file

@ -4,6 +4,7 @@ import histogramVisTypeProvider from 'plugins/kbn_vislib_vis_types/histogram';
import lineVisTypeProvider from 'plugins/kbn_vislib_vis_types/line';
import pieVisTypeProvider from 'plugins/kbn_vislib_vis_types/pie';
import areaVisTypeProvider from 'plugins/kbn_vislib_vis_types/area';
import tileMapVisTypeProvider from 'plugins/kbn_vislib_vis_types/tile_map';
import heatmapVisTypeProvider from 'plugins/kbn_vislib_vis_types/heatmap';
import horizontalBarVisTypeProvider from 'plugins/kbn_vislib_vis_types/horizontal_bar';
import gaugeVisTypeProvider from 'plugins/kbn_vislib_vis_types/gauge';
@ -14,6 +15,7 @@ VisTypesRegistryProvider.register(histogramVisTypeProvider);
VisTypesRegistryProvider.register(lineVisTypeProvider);
VisTypesRegistryProvider.register(pieVisTypeProvider);
VisTypesRegistryProvider.register(areaVisTypeProvider);
VisTypesRegistryProvider.register(tileMapVisTypeProvider);
VisTypesRegistryProvider.register(heatmapVisTypeProvider);
VisTypesRegistryProvider.register(horizontalBarVisTypeProvider);
VisTypesRegistryProvider.register(gaugeVisTypeProvider);

View file

@ -1,22 +1,22 @@
import { VisFactoryProvider } from 'ui/vis/vis_factory';
import { VisSchemasProvider } from 'ui/vis/editors/default/schemas';
import { CATEGORY } from 'ui/vis/vis_category';
import { VisVisTypeProvider } from 'ui/vis/vis_type';
import { VislibVisTypeVislibVisTypeProvider } from 'ui/vislib_vis_type/vislib_vis_type';
import { VisSchemasProvider } from 'ui/vis/schemas';
import pointSeriesTemplate from 'plugins/kbn_vislib_vis_types/editors/point_series.html';
import image from './images/icon-line.svg';
export default function PointSeriesVisType(Private) {
const VisFactory = Private(VisFactoryProvider);
const VisType = Private(VisVisTypeProvider);
const VislibVisType = Private(VislibVisTypeVislibVisTypeProvider);
const Schemas = Private(VisSchemasProvider);
return VisFactory.createVislibVisualization({
return new VislibVisType({
name: 'line',
title: 'Line',
image,
description: 'Emphasize trends',
category: CATEGORY.BASIC,
visConfig: {
category: VisType.CATEGORY.BASIC,
params: {
defaults: {
type: 'line',
grid: {
categoryLines: false,
style: {
@ -29,7 +29,8 @@ export default function PointSeriesVisType(Private) {
type: 'category',
position: 'bottom',
show: true,
style: {},
style: {
},
scale: {
type: 'linear'
},
@ -47,7 +48,8 @@ export default function PointSeriesVisType(Private) {
type: 'value',
position: 'left',
show: true,
style: {},
style: {
},
scale: {
type: 'linear',
mode: 'normal'
@ -83,34 +85,31 @@ export default function PointSeriesVisType(Private) {
times: [],
addTimeMarker: false,
},
},
editorConfig: {
collections: {
positions: ['top', 'left', 'right', 'bottom'],
chartTypes: [{
value: 'line',
text: 'line'
}, {
value: 'area',
text: 'area'
}, {
value: 'histogram',
text: 'bar'
}],
axisModes: ['normal', 'percentage', 'wiggle', 'silhouette'],
scaleTypes: ['linear', 'log', 'square root'],
chartModes: ['normal', 'stacked'],
interpolationModes: [{
value: 'linear',
text: 'straight',
}, {
value: 'cardinal',
text: 'smoothed',
}, {
value: 'step-after',
text: 'stepped',
}],
},
positions: ['top', 'left', 'right', 'bottom'],
chartTypes: [{
value: 'line',
text: 'line'
}, {
value: 'area',
text: 'area'
}, {
value: 'histogram',
text: 'bar'
}],
axisModes: ['normal', 'percentage', 'wiggle', 'silhouette'],
scaleTypes: ['linear', 'log', 'square root'],
chartModes: ['normal', 'stacked'],
interpolationModes: [{
value: 'linear',
text: 'straight',
}, {
value: 'cardinal',
text: 'smoothed',
}, {
value: 'step-after',
text: 'stepped',
}],
editor: pointSeriesTemplate,
optionTabs: [
{
name: 'advanced',
@ -120,50 +119,50 @@ export default function PointSeriesVisType(Private) {
},
{ name: 'options', title: 'Panel Settings', editor: pointSeriesTemplate },
],
schemas: new Schemas([
{
group: 'metrics',
name: 'metric',
title: 'Y-Axis',
min: 1,
aggFilter: ['!geo_centroid'],
defaults: [
{ schema: 'metric', type: 'count' }
]
},
{
group: 'metrics',
name: 'radius',
title: 'Dot Size',
min: 0,
max: 1,
aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality', 'top_hits']
},
{
group: 'buckets',
name: 'segment',
title: 'X-Axis',
min: 0,
max: 1,
aggFilter: '!geohash_grid'
},
{
group: 'buckets',
name: 'group',
title: 'Split Series',
min: 0,
max: 1,
aggFilter: '!geohash_grid'
},
{
group: 'buckets',
name: 'split',
title: 'Split Chart',
min: 0,
max: 1,
aggFilter: '!geohash_grid'
}
])
}
},
schemas: new Schemas([
{
group: 'metrics',
name: 'metric',
title: 'Y-Axis',
min: 1,
aggFilter: ['!geo_centroid'],
defaults: [
{ schema: 'metric', type: 'count' }
]
},
{
group: 'metrics',
name: 'radius',
title: 'Dot Size',
min: 0,
max: 1,
aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality', 'top_hits']
},
{
group: 'buckets',
name: 'segment',
title: 'X-Axis',
min: 0,
max: 1,
aggFilter: '!geohash_grid'
},
{
group: 'buckets',
name: 'group',
title: 'Split Series',
min: 0,
max: 1,
aggFilter: '!geohash_grid'
},
{
group: 'buckets',
name: 'split',
title: 'Split Chart',
min: 0,
max: 1,
aggFilter: '!geohash_grid'
}
])
});
}

View file

@ -1,21 +1,22 @@
import { VisFactoryProvider } from 'ui/vis/vis_factory';
import { VisSchemasProvider } from 'ui/vis/editors/default/schemas';
import { CATEGORY } from 'ui/vis/vis_category';
import { VisVisTypeProvider } from 'ui/vis/vis_type';
import { VislibVisTypeVislibVisTypeProvider } from 'ui/vislib_vis_type/vislib_vis_type';
import { VisSchemasProvider } from 'ui/vis/schemas';
import gaugeTemplate from 'plugins/kbn_vislib_vis_types/editors/gauge.html';
import { vislibColorMaps } from 'ui/vislib/components/color/colormaps';
import image from './images/icon-number.svg';
export default function MetricVisType(Private) {
const VisFactory = Private(VisFactoryProvider);
const VisType = Private(VisVisTypeProvider);
const VislibVisType = Private(VislibVisTypeVislibVisTypeProvider);
const Schemas = Private(VisSchemasProvider);
return VisFactory.createVislibVisualization({
return new VislibVisType({
name: 'metric',
title: 'Metric',
image,
description: 'Display a calculation as a single number',
category: CATEGORY.DATA,
visConfig: {
category: VisType.CATEGORY.DATA,
params: {
defaults: {
addTooltip: true,
addLegend: false,
@ -55,37 +56,34 @@ export default function MetricVisType(Private) {
}
}
},
gaugeTypes: ['Arc', 'Circle', 'Metric'],
gaugeColorMode: ['None', 'Labels', 'Background'],
scales: ['linear', 'log', 'square root'],
colorSchemas: Object.keys(vislibColorMaps),
editor: gaugeTemplate
},
editorConfig: {
collections: {
gaugeTypes: ['Arc', 'Circle', 'Metric'],
gaugeColorMode: ['None', 'Labels', 'Background'],
scales: ['linear', 'log', 'square root'],
colorSchemas: Object.keys(vislibColorMaps),
implementsRenderComplete: true,
schemas: new Schemas([
{
group: 'metrics',
name: 'metric',
title: 'Metric',
min: 1,
aggFilter: [
'!std_dev', '!geo_centroid', '!percentiles', '!percentile_ranks',
'!derivative', '!serial_diff', '!moving_avg', '!cumulative_sum'],
defaults: [
{ schema: 'metric', type: 'count' }
]
},
optionsTemplate: gaugeTemplate,
schemas: new Schemas([
{
group: 'metrics',
name: 'metric',
title: 'Metric',
min: 1,
aggFilter: [
'!std_dev', '!geo_centroid', '!percentiles', '!percentile_ranks',
'!derivative', '!serial_diff', '!moving_avg', '!cumulative_sum'],
defaults: [
{ schema: 'metric', type: 'count' }
]
},
{
group: 'buckets',
name: 'group',
title: 'Split Group',
min: 0,
max: 1,
aggFilter: '!geohash_grid'
}
])
}
{
group: 'buckets',
name: 'group',
title: 'Split Group',
min: 0,
max: 1,
aggFilter: '!geohash_grid'
}
])
});
}

View file

@ -1,79 +1,76 @@
import { VisFactoryProvider } from 'ui/vis/vis_factory';
import { VisSchemasProvider } from 'ui/vis/editors/default/schemas';
import { CATEGORY } from 'ui/vis/vis_category';
import { VisVisTypeProvider } from 'ui/vis/vis_type';
import { VislibVisTypeVislibVisTypeProvider } from 'ui/vislib_vis_type/vislib_vis_type';
import { VisSchemasProvider } from 'ui/vis/schemas';
import pieTemplate from 'plugins/kbn_vislib_vis_types/editors/pie.html';
import image from './images/icon-pie.svg';
export default function HistogramVisType(Private) {
const VisFactory = Private(VisFactoryProvider);
const VisType = Private(VisVisTypeProvider);
const VislibVisType = Private(VislibVisTypeVislibVisTypeProvider);
const Schemas = Private(VisSchemasProvider);
return VisFactory.createVislibVisualization({
return new VislibVisType({
name: 'pie',
title: 'Pie',
image,
description: 'Compare parts of a whole',
category: CATEGORY.BASIC,
visConfig: {
category: VisType.CATEGORY.BASIC,
params: {
defaults: {
type: 'pie',
addTooltip: true,
addLegend: true,
legendPosition: 'right',
isDonut: false
},
legendPositions: [{
value: 'left',
text: 'left',
}, {
value: 'right',
text: 'right',
}, {
value: 'top',
text: 'top',
}, {
value: 'bottom',
text: 'bottom',
}],
editor: pieTemplate
},
editorConfig: {
collections: {
legendPositions: [{
value: 'left',
text: 'left',
}, {
value: 'right',
text: 'right',
}, {
value: 'top',
text: 'top',
}, {
value: 'bottom',
text: 'bottom',
}],
},
optionsTemplate: pieTemplate,
schemas: new Schemas([
{
group: 'metrics',
name: 'metric',
title: 'Slice Size',
min: 1,
max: 1,
aggFilter: ['sum', 'count', 'cardinality', 'top_hits'],
defaults: [
{ schema: 'metric', type: 'count' }
]
},
{
group: 'buckets',
name: 'segment',
icon: 'fa fa-scissors',
title: 'Split Slices',
min: 0,
max: Infinity,
aggFilter: '!geohash_grid'
},
{
group: 'buckets',
name: 'split',
icon: 'fa fa-th',
title: 'Split Chart',
mustBeFirst: true,
min: 0,
max: 1,
aggFilter: '!geohash_grid'
}
])
},
responseConverter: false,
hierarchicalData: true,
implementsRenderComplete: true
implementsRenderComplete: true,
schemas: new Schemas([
{
group: 'metrics',
name: 'metric',
title: 'Slice Size',
min: 1,
max: 1,
aggFilter: ['sum', 'count', 'cardinality', 'top_hits'],
defaults: [
{ schema: 'metric', type: 'count' }
]
},
{
group: 'buckets',
name: 'segment',
icon: 'fa fa-scissors',
title: 'Split Slices',
min: 0,
max: Infinity,
aggFilter: '!geohash_grid'
},
{
group: 'buckets',
name: 'split',
icon: 'fa fa-th',
title: 'Split Chart',
mustBeFirst: true,
min: 0,
max: 1,
aggFilter: '!geohash_grid'
}
])
});
}

View file

@ -0,0 +1,80 @@
import { supports } from 'ui/utils/supports';
import { VisVisTypeProvider } from 'ui/vis/vis_type';
import { MapsVisTypeProvider } from 'ui/vis_maps/maps_vis_type';
import { VisSchemasProvider } from 'ui/vis/schemas';
import { AggResponseGeoJsonProvider } from 'ui/agg_response/geo_json/geo_json';
import tileMapTemplate from 'plugins/kbn_vislib_vis_types/editors/tile_map.html';
import image from './images/icon-tilemap.svg';
export default function TileMapVisType(Private, getAppState, courier, config) {
const VisType = Private(VisVisTypeProvider);
const MapsVisType = Private(MapsVisTypeProvider);
const Schemas = Private(VisSchemasProvider);
const geoJsonConverter = Private(AggResponseGeoJsonProvider);
return new MapsVisType({
name: 'tile_map',
title: 'Coordinate Map',
image,
description: 'Plot latitude and longitude coordinates on a map',
category: VisType.CATEGORY.MAP,
params: {
defaults: {
mapType: 'Scaled Circle Markers',
isDesaturated: true,
addTooltip: true,
heatMaxZoom: 0,
heatMinOpacity: 0.1,
heatRadius: 25,
heatBlur: 15,
legendPosition: 'bottomright',
mapZoom: 2,
mapCenter: [0, 0],
wms: config.get('visualization:tileMap:WMSdefaults')
},
legendPositions: [{
value: 'bottomleft',
text: 'bottom left',
}, {
value: 'bottomright',
text: 'bottom right',
}, {
value: 'topleft',
text: 'top left',
}, {
value: 'topright',
text: 'top right',
}],
mapTypes: ['Scaled Circle Markers',
'Shaded Circle Markers',
'Shaded Geohash Grid',
'Heatmap'
],
canDesaturate: !!supports.cssFilters,
editor: tileMapTemplate
},
responseConverter: geoJsonConverter,
implementsRenderComplete: true,
schemas: new Schemas([
{
group: 'metrics',
name: 'metric',
title: 'Value',
min: 1,
max: 1,
aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality', 'top_hits'],
defaults: [
{ schema: 'metric', type: 'count' }
]
},
{
group: 'buckets',
name: 'segment',
title: 'Geo Coordinates',
aggFilter: 'geohash_grid',
min: 1,
max: 1
}
])
});
}

View file

@ -117,7 +117,6 @@
get-vis-click-handler="getFilterBarClickHandler"
get-vis-brush-handler="getBrushEvent"
save-state="saveState"
app-state="appState"
toggle-expand="toggleExpandPanel"
create-child-ui-state="createChildUiState"
toggle-expand="toggleExpandPanel"
@ -134,7 +133,6 @@
get-vis-click-handler="getFilterBarClickHandler"
get-vis-brush-handler="getBrushEvent"
save-state="saveState"
app-state="appState"
register-panel-index-pattern="registerPanelIndexPattern"
create-child-ui-state="createChildUiState"
toggle-expand="toggleExpandPanel(expandedPanel.panelIndex)"

View file

@ -147,7 +147,6 @@ app.directive('dashboardApp', function ($injector) {
$scope.timefilter = timefilter;
$scope.expandedPanel = null;
$scope.dashboardViewMode = dashboardState.getViewMode();
$scope.appState = dashboardState.getAppState();
$scope.landingPageUrl = () => `#${DashboardConstants.LANDING_PAGE_PATH}`;
$scope.getBrushEvent = () => brushEvent(dashboardState.getAppState());

View file

@ -53,7 +53,6 @@ app.directive('dashboardGrid', function ($compile, Notifier) {
* @type {function}
*/
saveState: '=',
appState: '=',
/**
* Expand or collapse a panel, so it either takes up the whole screen or goes back to its
* natural size.
@ -220,7 +219,6 @@ app.directive('dashboardGrid', function ($compile, Notifier) {
get-vis-click-handler="getVisClickHandler"
get-vis-brush-handler="getVisBrushHandler"
save-state="saveState"
app-state="appState"
register-panel-index-pattern="registerPanelIndexPattern"
toggle-expand="toggleExpand(${panel.panelIndex})"
create-child-ui-state="createChildUiState">

View file

@ -79,9 +79,9 @@
<visualize
ng-if="!error"
ng-switch-when="visualization"
vis="savedObj.vis"
search-source="savedObj.searchSource"
show-spy-panel="!isFullScreenMode"
saved-obj="savedObj"
app-state="appState"
ui-state="uiState"
data-shared-item
data-title="{{savedObj.title}}"

View file

@ -85,8 +85,7 @@ uiModules
* Call when changes should be propagated to the url and thus saved in state.
* @type {function}
*/
saveState: '=',
appState: '=',
saveState: '='
},
link: function ($scope, element) {
if (!$scope.panel.id || !$scope.panel.type) return;

View file

@ -16,7 +16,6 @@ import 'ui/state_management/app_state';
import 'ui/timefilter';
import 'ui/share';
import { VisProvider } from 'ui/vis';
import { BasicResponseHandlerProvider } from 'ui/vis/response_handlers/basic';
import { DocTitleProvider } from 'ui/doc_title';
import { UtilsBrushEventProvider } from 'ui/utils/brush_event';
import PluginsKibanaDiscoverHitSortFnProvider from 'plugins/kibana/discover/_hit_sort_fn';
@ -99,7 +98,7 @@ function discoverController($scope, config, courier, $route, $window, Notifier,
const HitSortFn = Private(PluginsKibanaDiscoverHitSortFnProvider);
const queryFilter = Private(FilterBarQueryFilterProvider);
const filterManager = Private(FilterManagerProvider);
const responseHandler = Private(BasicResponseHandlerProvider).handler;
const notify = new Notifier({
location: 'Discover'
});
@ -488,9 +487,6 @@ function discoverController($scope, config, courier, $route, $window, Notifier,
segmented.on('mergedSegment', function (merged) {
$scope.mergedEsResp = merged;
responseHandler($scope.vis, merged).then(resp => {
$scope.visData = resp;
});
$scope.hits = merged.hits.total;
const indexPattern = $scope.searchSource.get('index');

View file

@ -130,14 +130,13 @@
</header>
<visualization
<visualize
ng-if="vis && rows.length != 0"
vis="vis"
ui-state="uiState"
vis-data="visData"
style="height: 200px"
>
</visualization>
es-resp="mergedEsResp"
search-source="searchSource">
</visualize>
</div>
<div class="discover-table" fixed-scroll>

View file

@ -3,7 +3,7 @@ import angular from 'angular';
import _ from 'lodash';
import expect from 'expect.js';
import ngMock from 'ng_mock';
import '../agg';
import 'plugins/kibana/visualize/editor/agg';
describe('Vis-Editor-Agg plugin directive', function () {

View file

@ -3,10 +3,10 @@ import angular from 'angular';
import _ from 'lodash';
import expect from 'expect.js';
import ngMock from 'ng_mock';
import '../agg_params';
import 'plugins/kibana/visualize/editor/agg_params';
import { VisProvider } from 'ui/vis';
import { VisAggConfigProvider } from 'ui/vis/agg_config';
import { VisSchemasProvider } from 'ui/vis/editors/default/schemas';
import { VisSchemasProvider } from 'ui/vis/schemas';
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';

View file

@ -90,5 +90,5 @@
<vis-editor-agg-add
ng-if="$index + 1 === stats.count"
ng-hide="dragging"
class="vis-editor-agg-add vis-editor-agg-add-subagg">
class="vis-editor-agg-add">
</vis-editor-agg-add>

View file

@ -1,8 +1,8 @@
import './agg_params';
import './agg_add';
import 'plugins/kibana/visualize/editor/agg_params';
import 'plugins/kibana/visualize/editor/agg_add';
import _ from 'lodash';
import { uiModules } from 'ui/modules';
import aggTemplate from './agg.html';
import aggTemplate from 'plugins/kibana/visualize/editor/agg.html';
uiModules
.get('app/visualize')
.directive('visEditorAgg', function ($compile, $parse, $filter, Private, Notifier) {

View file

@ -1,6 +1,6 @@
import { VisAggConfigProvider } from 'ui/vis/agg_config';
import { uiModules } from 'ui/modules';
import aggAddTemplate from './agg_add.html';
import aggAddTemplate from 'plugins/kibana/visualize/editor/agg_add.html';
uiModules
.get('kibana')

View file

@ -5,7 +5,7 @@
<div ng-class="groupName" draggable-container="group" class="vis-editor-agg-group">
<!-- wrapper needed for nesting-indicator -->
<div ng-repeat="agg in group track by agg.id" draggable-item="agg" class="vis-editor-agg-wrapper">
<div ng-repeat="agg in group" draggable-item="agg" class="vis-editor-agg-wrapper">
<!-- agg.html - controls for aggregation -->
<ng-form vis-editor-agg name="aggForm" class="vis-editor-agg"></ng-form>
</div>

View file

@ -1,10 +1,9 @@
import _ from 'lodash';
import './agg';
import './agg_add';
import './nesting_indicator';
import 'plugins/kibana/visualize/editor/agg';
import 'plugins/kibana/visualize/editor/agg_add';
import 'plugins/kibana/visualize/editor/nesting_indicator';
import { uiModules } from 'ui/modules';
import aggGroupTemplate from './agg_group.html';
import aggGroupTemplate from 'plugins/kibana/visualize/editor/agg_group.html';
uiModules
.get('app/visualize')

View file

@ -1,11 +1,11 @@
import $ from 'jquery';
import aggSelectHtml from './agg_select.html';
import advancedToggleHtml from './advanced_toggle.html';
import aggSelectHtml from 'plugins/kibana/visualize/editor/agg_select.html';
import advancedToggleHtml from 'plugins/kibana/visualize/editor/advanced_toggle.html';
import 'ui/filters/match_any';
import './agg_param';
import 'plugins/kibana/visualize/editor/agg_param';
import { AggTypesIndexProvider } from 'ui/agg_types/index';
import { uiModules } from 'ui/modules';
import aggParamsTemplate from './agg_params.html';
import aggParamsTemplate from 'plugins/kibana/visualize/editor/agg_params.html';
uiModules
.get('app/visualize')

View file

@ -85,16 +85,53 @@
index-patterns="[indexPattern]"
></filter-bar>
<visualize
saved-obj="savedVis"
ui-state="uiState"
app-state="state"
editor-mode="true"
data-shared-item
render-counter
data-title="{{savedVis.lastSavedTitle}}"
data-description="{{savedVis.description}}"
show-spy-panel="chrome.getVisible()">
</visualize>
<!-- Custom, full-screen editing UI. -->
<div
ng-if="vis.type.fullEditor"
class="vis-editor-content"
>
<vis-editor-vis-options
vis="vis"
saved-vis="savedVis"
editor="vis.type.params.editor"
ui-state="uiState"
stage-editable-vis="stageEditableVis"
class="vis-editor-full-options"
></vis-editor-vis-options>
</div>
<!-- Traditional sidebar-and-visualization editor. -->
<div
ng-if="!vis.type.fullEditor"
class="vis-editor-content"
>
<!-- Sidebar -->
<div
class="collapsible-sidebar"
ng-if="chrome.getVisible()"
>
<vis-editor-sidebar
vis="editableVis"
class="vis-editor-sidebar"
></vis-editor-sidebar>
</div>
<!-- Visualization -->
<div
class="vis-editor-canvas"
ng-class="{ embedded: !chrome.getVisible() }"
>
<visualize
vis="vis"
data-shared-item
data-title="{{savedVis.lastSavedTitle}}"
data-description="{{savedVis.description}}"
render-counter
ui-state="uiState"
show-spy-panel="chrome.getVisible()"
editable-vis="editableVis"
search-source="savedVis.searchSource">
</visualize>
</div>
</div>
</visualize-app>

View file

@ -1,6 +1,6 @@
import _ from 'lodash';
import 'plugins/kibana/visualize/saved_visualizations/saved_visualizations';
import 'ui/vis/editors/default/sidebar';
import 'plugins/kibana/visualize/editor/sidebar';
import 'plugins/kibana/visualize/editor/agg_filter';
import 'ui/visualize';
import 'ui/collapsible_sidebar';
@ -10,7 +10,9 @@ import angular from 'angular';
import { Notifier } from 'ui/notify/notifier';
import { VisTypesRegistryProvider } from 'ui/registry/vis_types';
import { DocTitleProvider } from 'ui/doc_title';
import { UtilsBrushEventProvider } from 'ui/utils/brush_event';
import { FilterBarQueryFilterProvider } from 'ui/filter_bar/query_filter';
import { FilterBarClickHandlerProvider } from 'ui/filter_bar/filter_bar_click_handler';
import { stateMonitorFactory } from 'ui/state_management/state_monitor_factory';
import uiRoutes from 'ui/routes';
import { uiModules } from 'ui/modules';
@ -28,9 +30,7 @@ uiRoutes
savedVis: function (savedVisualizations, courier, $route, Private) {
const visTypes = Private(VisTypesRegistryProvider);
const visType = _.find(visTypes, { name: $route.current.params.type });
const shouldHaveIndex = visType.requiresSearch && visType.options.showIndexSelection;
const hasIndex = $route.current.params.indexPattern || $route.current.params.savedSearchId;
if (shouldHaveIndex && !hasIndex) {
if (visType.requiresSearch && !$route.current.params.indexPattern && !$route.current.params.savedSearchId) {
throw new Error('You must provide either an indexPattern or a savedSearchId');
}
@ -69,34 +69,52 @@ uiModules
};
});
function VisEditor($scope, $route, timefilter, AppState, $window, kbnUrl, courier, Private, Promise, kbnBaseUrl) {
function VisEditor($rootScope, $scope, $route, timefilter, AppState, $window, kbnUrl, courier, Private, Promise, kbnBaseUrl) {
const docTitle = Private(DocTitleProvider);
const brushEvent = Private(UtilsBrushEventProvider);
const queryFilter = Private(FilterBarQueryFilterProvider);
const filterBarClickHandler = Private(FilterBarClickHandlerProvider);
const notify = new Notifier({
location: 'Visualization Editor'
});
let stateMonitor;
// Retrieve the resolved SavedVis instance.
const savedVis = $route.current.locals.savedVis;
// vis is instance of src/ui/public/vis/vis.js.
// SearchSource is a promise-based stream of search results that can inherit from other search sources.
const { vis, searchSource } = savedVis;
$scope.vis = vis;
const $appStatus = this.appStatus = {
dirty: !savedVis.id
};
// Instance of src/ui/public/vis/vis.js.
const vis = savedVis.vis;
// Clone the _vis instance.
const editableVis = vis.createEditableVis();
// We intend to keep editableVis and vis in sync with one another, so calling `requesting` on
// vis should call it on both.
vis.requesting = function () {
const requesting = editableVis.requesting;
// Invoking requesting() calls onRequest on each agg's type param. When a vis is marked as being
// requested, the bounds of that vis are updated and new data is fetched using the new bounds.
requesting.call(vis);
// We need to keep editableVis in sync with vis.
requesting.call(editableVis);
};
// SearchSource is a promise-based stream of search results that can inherit from other search
// sources.
const searchSource = savedVis.searchSource;
$scope.topNavMenu = [{
key: 'save',
description: 'Save Visualization',
template: require('plugins/kibana/visualize/editor/panels/save.html'),
testId: 'visualizeSaveButton',
disableButton() {
return Boolean(vis.dirty);
},
tooltip() {
if (vis.dirty) {
return 'Apply or Discard your changes before saving';
}
}
}, {
key: 'share',
description: 'Share Visualization',
@ -109,12 +127,6 @@ function VisEditor($scope, $route, timefilter, AppState, $window, kbnUrl, courie
testId: 'visualizeRefreshButton',
}];
let stateMonitor;
const $appStatus = this.appStatus = {
dirty: !savedVis.id
};
if (savedVis.id) {
docTitle.change(savedVis.title);
}
@ -125,13 +137,13 @@ function VisEditor($scope, $route, timefilter, AppState, $window, kbnUrl, courie
const stateDefaults = {
uiState: savedVis.uiStateJSON ? JSON.parse(savedVis.uiStateJSON) : {},
linked: !!savedVis.savedSearchId,
query: searchSource.getOwn('query') || { query_string: { analyze_wildcard: true, query: '*' } },
query: searchSource.getOwn('query') || { query_string: { query: '*' } },
filters: searchSource.getOwn('filter') || [],
vis: savedVisState
};
// Instance of app_state.js.
const $state = (function initState() {
const $state = $scope.$state = (function initState() {
// This is used to sync visualization state with the url when `appState.save()` is called.
const appState = new AppState(stateDefaults);
@ -140,7 +152,8 @@ function VisEditor($scope, $route, timefilter, AppState, $window, kbnUrl, courie
// appState then they won't be equal.
if (!angular.equals(appState.vis, savedVisState)) {
Promise.try(function () {
vis.setState(appState.vis);
editableVis.setState(appState.vis);
vis.setState(editableVis.getEnabledState());
})
.catch(courier.redirectWhenMissing({
'index-pattern-field': '/visualize'
@ -153,8 +166,10 @@ function VisEditor($scope, $route, timefilter, AppState, $window, kbnUrl, courie
function init() {
// export some objects
$scope.savedVis = savedVis;
$scope.indexPattern = vis.indexPattern;
$scope.searchSource = searchSource;
$scope.vis = vis;
$scope.indexPattern = vis.indexPattern;
$scope.editableVis = editableVis;
$scope.state = $state;
$scope.queryDocLinks = documentationLinks.query;
$scope.dateDocLinks = documentationLinks.date;
@ -181,6 +196,30 @@ function VisEditor($scope, $route, timefilter, AppState, $window, kbnUrl, courie
stateMonitor.ignoreProps([ 'vis.listeners' ]).onChange((status) => {
$appStatus.dirty = status.dirty || !savedVis.id;
});
$scope.$on('$destroy', () => stateMonitor.destroy());
editableVis.listeners.click = vis.listeners.click = filterBarClickHandler($state);
editableVis.listeners.brush = vis.listeners.brush = brushEvent($state);
// track state of editable vis vs. "actual" vis
$scope.stageEditableVis = transferVisState(editableVis, vis, true);
$scope.resetEditableVis = transferVisState(vis, editableVis);
$scope.$watch(function () {
return editableVis.getEnabledState();
}, function (newState) {
editableVis.dirty = !angular.equals(newState, vis.getEnabledState());
$scope.responseValueAggs = null;
try {
$scope.responseValueAggs = editableVis.aggs.getResponseAggs().filter(function (agg) {
return _.get(agg, 'schema.group') === 'metrics';
});
}
// this can fail when the agg.type is changed but the
// params have not been set yet. watcher will trigger again
// when the params update
catch (e) {} // eslint-disable-line no-empty
}, true);
$state.replace();
@ -190,20 +229,56 @@ function VisEditor($scope, $route, timefilter, AppState, $window, kbnUrl, courie
$scope.$watchMulti([
'searchSource.get("index").timeFieldName',
'vis.type.options.showTimePicker',
'vis.type.requiresTimePicker',
], function ([timeField, requiresTimePicker]) {
timefilter.enabled = Boolean(timeField || requiresTimePicker);
});
// update the searchSource when filters update
$scope.$listen(queryFilter, 'update', function () {
searchSource.set('filter', queryFilter.getFilters());
$state.save();
});
// update the searchSource when query updates
$scope.fetch = function () {
$state.save();
};
// fetch data when filters fire fetch event
$scope.$listen(queryFilter, 'fetch', $scope.fetch);
$scope.$listen($state, 'fetch_with_changes', function (keys) {
if (_.contains(keys, 'linked') && $state.linked === true) {
// abort and reload route
$route.reload();
return;
}
if (_.contains(keys, 'vis')) {
$state.vis.listeners = _.defaults($state.vis.listeners || {}, vis.listeners);
// only update when we need to, otherwise colors change and we
// risk loosing an in-progress result
vis.setState($state.vis);
editableVis.setState($state.vis);
}
// we use state to track query, must write before we fetch
if ($state.query && !$state.linked) {
searchSource.set('query', $state.query);
} else {
searchSource.set('query', null);
}
if (_.isEqual(keys, ['filters'])) {
// updates will happen in filter watcher if needed
return;
}
$scope.fetch();
});
// Without this manual emission, we'd miss filters and queries that were on the $state initially
$state.emit('fetch_with_changes');
$scope.$listen(timefilter, 'fetch', _.bindKey($scope, 'fetch'));
$scope.$on('ready:vis', function () {
$scope.$emit('application.load');
@ -211,10 +286,20 @@ function VisEditor($scope, $route, timefilter, AppState, $window, kbnUrl, courie
$scope.$on('$destroy', function () {
savedVis.destroy();
stateMonitor.destroy();
});
}
$scope.fetch = function () {
// This is used by some plugins to trigger a fetch (Timelion and Time Series Visual Builder)
$rootScope.$broadcast('fetch');
$state.save();
searchSource.set('filter', queryFilter.getFilters());
if (!$state.linked) searchSource.set('query', $state.query);
if ($scope.vis.type.requiresSearch) {
courier.fetch();
}
};
/**
* Called when the user clicks "Save" button.
*/
@ -283,5 +368,29 @@ function VisEditor($scope, $route, timefilter, AppState, $window, kbnUrl, courie
searchSource.inherits(parentsParent);
};
function transferVisState(fromVis, toVis, stage) {
return function () {
//verify this before we copy the "new" state
const isAggregationsChanged = !fromVis.aggs.jsonDataEquals(toVis.aggs);
const view = fromVis.getEnabledState();
const full = fromVis.getState();
toVis.setState(view);
editableVis.dirty = false;
$state.vis = full;
/**
* Only fetch (full ES round trip), if the play-button has been pressed (ie. 'stage' variable) and if there
* has been changes in the Data-tab.
*/
if (stage && isAggregationsChanged) {
$scope.fetch();
} else {
$state.save();
}
};
}
init();
}

View file

@ -8,11 +8,11 @@
<div
css-truncate
aria-label="{{:: 'Index pattern: ' + vis.indexPattern.id}}"
aria-label="{{:: 'Index pattern: ' + indexPattern.id}}"
ng-if="vis.type.requiresSearch"
class="index-pattern"
>
{{ vis.indexPattern.id }}
{{ indexPattern.id }}
</div>
<nav class="navbar navbar-default subnav">
@ -31,7 +31,7 @@
</a>
</li>
<li ng-repeat="tab in vis.type.editorConfig.optionTabs" ng-class="{active: sidebar.section == tab.name}">
<li ng-repeat="tab in vis.type.params.optionTabs" ng-class="{active: sidebar.section == tab.name}">
<a
class="vis-editor-subnav-link"
ng-class="{'is-vis-editor-sub-nav-link-selected': sidebar.section == tab.name}"
@ -44,7 +44,7 @@
</ul>
<!-- controls -->
<ul class="nav navbar-nav navbar-right">
<ul class="nav navbar-nav navbar-right sidebar-controls">
<li
ng-if="visualizeEditor.softErrorCount() > 0"
disabled
@ -68,7 +68,7 @@
>
<button
data-test-subj="visualizeEditorRenderButton"
class="kuiButton kuiButton--primary navbar-btn-link"
class="kuiButton kuiButton--success navbar-btn-link"
type="submit"
ng-disabled="!vis.dirty || visualizeEditor.errorCount() > 0"
aria-label="Update the visualization with your changes"
@ -104,14 +104,8 @@
<vis-editor-agg-group ng-if="vis.type.schemas.buckets" group-name="buckets"></vis-editor-agg-group>
</div>
<div class="vis-editor-config" ng-repeat="tab in vis.type.editorConfig.optionTabs" ng-show="sidebar.section == tab.name">
<vis-editor-vis-options
vis="vis"
vis-data="visData"
ui-state="uiState"
visualize-editor="visualizeEditor"
editor="tab.editor"
></vis-editor-vis-options>
<div class="vis-editor-config" ng-repeat="tab in vis.type.params.optionTabs" ng-show="sidebar.section == tab.name">
<vis-editor-vis-options vis="vis" saved-vis="savedVis" ui-state="uiState" editor="tab.editor"></vis-editor-vis-options>
</div>
</form>

View file

@ -1,8 +1,7 @@
import './agg_group';
import './vis_options';
import 'plugins/kibana/visualize/editor/agg_group';
import 'plugins/kibana/visualize/editor/vis_options';
import { uiModules } from 'ui/modules';
import sidebarTemplate from './sidebar.html';
import sidebarTemplate from 'plugins/kibana/visualize/editor/sidebar.html';
uiModules
.get('app/visualize')
.directive('visEditorSidebar', function () {
@ -14,6 +13,7 @@ uiModules
scope: true,
controllerAs: 'sidebar',
controller: function ($scope) {
$scope.$bind('vis', 'editableVis');
$scope.$watch('vis.type', (visType) => {
if (visType) {

View file

@ -47,11 +47,7 @@
.flex-parent(0, 1, auto);
}
> visualize {
height: 100%;
flex: 1 1 auto;
display: flex;
}
}
@ -61,7 +57,6 @@
.vis-editor-content {
.flex-parent();
width: 100%;
z-index: 0;
// overrides for tablet and desktop
@ -306,8 +301,9 @@
}
.vis-editor-canvas {
flex: 1 0 (@screen-md-min - @vis-editor-sidebar-basis);
display: flex;
flex-direction: row;
flex-direction: column;
overflow: auto;
padding-left: @collapser-width;

View file

@ -1,4 +1,6 @@
<div class="sidebar-item">
<div
ng-show="vis.type.params.editor"
>
<!-- Visualization and other editing UI gets inserted in here. -->
<div
data-visualization-options

View file

@ -0,0 +1,35 @@
import { uiModules } from 'ui/modules';
import visOptionsTemplate from 'plugins/kibana/visualize/editor/vis_options.html';
/**
* This directive sort of "transcludes" in whatever template you pass in via the `editor` attribute.
* This lets you specify a full-screen UI for editing a vis type, instead of using the regular
* sidebar.
*/
uiModules
.get('app/visualize')
.directive('visEditorVisOptions', function (Private, $timeout, $compile) {
return {
restrict: 'E',
template: visOptionsTemplate,
scope: {
vis: '=',
savedVis: '=',
uiState: '=',
editor: '=',
stageEditableVis: '='
},
link: function ($scope, $el) {
const $optionContainer = $el.find('[data-visualization-options]');
// Bind the `editor` template with the scope.
const $editor = $compile($scope.editor)($scope);
$optionContainer.append($editor);
$scope.$watch('vis.type.schemas.all.length', function (len) {
$scope.alwaysShowOptions = len === 0;
});
}
};
});

View file

@ -1,7 +1,16 @@
import 'plugins/kibana/visualize/styles/main.less';
import 'plugins/kibana/visualize/editor/editor';
import 'plugins/kibana/visualize/wizard/wizard';
import 'plugins/kibana/visualize/editor/add_bucket_agg';
import 'plugins/kibana/visualize/editor/agg';
import 'plugins/kibana/visualize/editor/agg_add';
import 'plugins/kibana/visualize/editor/agg_filter';
import 'plugins/kibana/visualize/editor/agg_group';
import 'plugins/kibana/visualize/editor/agg_param';
import 'plugins/kibana/visualize/editor/agg_params';
import 'plugins/kibana/visualize/editor/nesting_indicator';
import 'plugins/kibana/visualize/editor/sidebar';
import 'plugins/kibana/visualize/editor/vis_options';
import 'ui/draggable/draggable_container';
import 'ui/draggable/draggable_item';
import 'ui/draggable/draggable_handle';
@ -15,17 +24,6 @@ import { VisualizeListingController } from './listing/visualize_listing';
import { VisualizeConstants } from './visualize_constants';
import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry';
import { savedVisualizationProvider } from 'plugins/kibana/visualize/saved_visualizations/saved_visualization_register';
import { noneRequestHandlerProvider } from 'ui/vis/request_handlers/none';
import { CourierRequestHandlerProvider } from 'ui/vis/request_handlers/courier';
import { noneResponseHandler } from 'ui/vis/response_handlers/none';
import { BasicResponseHandlerProvider } from 'ui/vis/response_handlers/basic';
import { defaultEditor } from 'ui/vis/editors/default/default';
import { VisRequestHandlersRegistryProvider } from 'ui/registry/vis_request_handlers';
import { VisResponseHandlersRegistryProvider } from 'ui/registry/vis_response_handlers';
import { VisEditorTypesRegistryProvider } from 'ui/registry/vis_editor_types';
uiRoutes
.defaults(/visualize/, {
@ -38,10 +36,5 @@ uiRoutes
});
// preloading
SavedObjectRegistryProvider.register(savedVisualizationProvider);
VisRequestHandlersRegistryProvider.register(CourierRequestHandlerProvider);
VisRequestHandlersRegistryProvider.register(noneRequestHandlerProvider);
VisResponseHandlersRegistryProvider.register(noneResponseHandler);
VisResponseHandlersRegistryProvider.register(BasicResponseHandlerProvider);
VisEditorTypesRegistryProvider.register(defaultEditor);
SavedObjectRegistryProvider.register(savedVisualizationProvider);

View file

@ -5,7 +5,7 @@ import 'plugins/kibana/discover/saved_searches/saved_searches';
import './wizard.less';
import _ from 'lodash';
import { CATEGORY } from 'ui/vis/vis_category';
import { VisVisTypeProvider } from 'ui/vis/vis_type';
import { DashboardConstants } from 'plugins/kibana/dashboard/dashboard_constants';
import { VisualizeConstants } from '../visualize_constants';
import routes from 'ui/routes';
@ -33,13 +33,15 @@ routes.when(VisualizeConstants.WIZARD_STEP_1_PAGE_PATH, {
module.controller('VisualizeWizardStep1', function ($scope, $route, kbnUrl, timefilter, Private) {
timefilter.enabled = false;
const VisType = Private(VisVisTypeProvider);
const visTypeCategoryToHumanReadableMap = {
[CATEGORY.BASIC]: 'Basic Charts',
[CATEGORY.DATA]: 'Data',
[CATEGORY.GRAPHIC]: 'Graphic',
[CATEGORY.MAP]: 'Maps',
[CATEGORY.OTHER]: 'Other',
[CATEGORY.TIME]: 'Time Series'
[VisType.CATEGORY.BASIC]: 'Basic Charts',
[VisType.CATEGORY.DATA]: 'Data',
[VisType.CATEGORY.GRAPHIC]: 'Graphic',
[VisType.CATEGORY.MAP]: 'Maps',
[VisType.CATEGORY.OTHER]: 'Other',
[VisType.CATEGORY.TIME]: 'Time Series',
};
const addToDashMode = $route.current.params[DashboardConstants.ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM];
@ -52,8 +54,6 @@ module.controller('VisualizeWizardStep1', function ($scope, $route, kbnUrl, time
visTypes.forEach(visType => {
const categoryName = visType.category;
if (categoryName === CATEGORY.HIDDEN) return;
// Create category object if it doesn't exist yet.
if (!categoryToVisTypesMap[categoryName]) {
categoryToVisTypesMap[categoryName] = {
@ -74,7 +74,7 @@ module.controller('VisualizeWizardStep1', function ($scope, $route, kbnUrl, time
// Sort the categories alphabetically.
const sortedVisTypeCategories = Object.values(categoryToVisTypesMap).sort((a, b) => {
const other = CATEGORY.OTHER.toLowerCase();
const other = VisType.CATEGORY.OTHER.toLowerCase();
// Put "other" category at the end of the list.
const labelA = a.label.toLowerCase();
@ -137,7 +137,7 @@ module.controller('VisualizeWizardStep1', function ($scope, $route, kbnUrl, time
$scope.getVisTypeUrl = function (visType) {
const baseUrl =
visType.requiresSearch && visType.options.showIndexSelection
visType.requiresSearch
? `#${VisualizeConstants.WIZARD_STEP_2_PAGE_PATH}?`
: `#${VisualizeConstants.CREATE_PATH}?`;

View file

@ -8,7 +8,6 @@ describe('markdown vis controller', function () {
beforeEach(ngMock.module('kibana/markdown_vis'));
beforeEach(ngMock.inject(function ($rootScope, $controller) {
$scope = $rootScope.$new();
$scope.renderComplete = () => {};
const $element = $('<div>');
$controller('KbnMarkdownVisController', { $scope, $element });
$scope.$digest();

View file

@ -1,7 +1,7 @@
import 'plugins/markdown_vis/markdown_vis.less';
import 'plugins/markdown_vis/markdown_vis_controller';
import { VisFactoryProvider } from 'ui/vis/vis_factory';
import { CATEGORY } from 'ui/vis/vis_category';
import { VisVisTypeProvider } from 'ui/vis/vis_type';
import { TemplateVisTypeProvider } from 'ui/template_vis_type/template_vis_type';
import markdownVisTemplate from 'plugins/markdown_vis/markdown_vis.html';
import markdownVisParamsTemplate from 'plugins/markdown_vis/markdown_vis_params.html';
import { VisTypesRegistryProvider } from 'ui/registry/vis_types';
@ -14,23 +14,22 @@ import image from './images/icon-markdown.svg';
VisTypesRegistryProvider.register(MarkdownVisProvider);
function MarkdownVisProvider(Private) {
const VisFactory = Private(VisFactoryProvider);
const VisType = Private(VisVisTypeProvider);
const TemplateVisType = Private(TemplateVisTypeProvider);
// return the visType object, which kibana will use to display and configure new
// Vis object of this type.
return VisFactory.createAngularVisualization({
return new TemplateVisType({
name: 'markdown',
title: 'Markdown',
image,
description: 'Create a document using markdown syntax',
category: CATEGORY.OTHER,
visConfig: {
template: markdownVisTemplate,
category: VisType.CATEGORY.OTHER,
template: markdownVisTemplate,
params: {
editor: markdownVisParamsTemplate
},
editorConfig: {
optionsTemplate: markdownVisParamsTemplate
},
requestHandler: 'none',
requiresSearch: false,
implementsRenderComplete: true,
});
}

View file

@ -9,11 +9,11 @@ marked.setOptions({
const module = uiModules.get('kibana/markdown_vis', ['kibana', 'ngSanitize']);
module.controller('KbnMarkdownVisController', function ($scope) {
module.controller('KbnMarkdownVisController', function ($scope, $element) {
$scope.$watch('vis.params.markdown', function (html) {
if (html) {
$scope.html = marked(html);
}
$scope.renderComplete();
$element.trigger('renderComplete');
});
});

View file

@ -3,42 +3,28 @@ import VisEditorVisualization from './vis_editor_visualization';
import Visualization from './visualization';
import VisPicker from './vis_picker';
import PanelConfig from './panel_config';
import brushHandler from '../lib/create_brush_handler';
class VisEditor extends Component {
constructor(props) {
super(props);
this.state = { model: props.vis.params, dirty: false, autoApply: true };
this.onBrush = brushHandler(props.vis.API.timeFilter);
this.state = { model: props.model };
}
render() {
const handleChange = (part) => {
const nextModel = { ...this.state.model, ...part };
this.props.vis.params = nextModel;
if (this.state.autoApply) {
this.props.vis.updateState();
this.setState({ model: nextModel });
if (this.props.onChange) {
this.props.onChange(nextModel);
}
this.setState({ model: nextModel, dirty: !this.state.autoApply });
};
const handleAutoApplyToggle = (part) => {
this.setState({ autoApply: part.target.checked });
};
const handleCommit = () => {
this.props.vis.updateState();
this.setState({ dirty: false });
};
if (!this.props.vis.isEditorMode()) {
if (this.props.embedded) {
return (
<Visualization
fields={this.props.vis.fields}
model={this.props.vis.params}
fields={this.props.fields}
model={this.props.model}
visData={this.props.visData} />
);
}
@ -53,30 +39,25 @@ class VisEditor extends Component {
model={model}
onChange={handleChange} />
<VisEditorVisualization
dirty={this.state.dirty}
autoApply={this.state.autoApply}
dirty={this.props.dirty}
autoApply={this.props.autoApply}
model={model}
visData={this.props.visData}
onBrush={this.onBrush}
onCommit={handleCommit}
onToggleAutoApply={handleAutoApplyToggle}
onBrush={this.props.onBrush}
onCommit={this.props.onCommit}
onToggleAutoApply={this.props.onToggleAutoApply}
onChange={handleChange} />
<PanelConfig
fields={this.props.vis.fields}
fields={this.props.fields}
model={model}
visData={this.props.visData}
onChange={handleChange} />
</div>
);
}
return null;
}
componentDidMount() {
this.props.renderComplete();
}
}
VisEditor.propTypes = {

View file

@ -0,0 +1,37 @@
import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import { uiModules } from 'ui/modules';
import VisEditor from '../components/vis_editor';
import addScope from '../lib/add_scope';
import angular from 'angular';
import createBrushHandler from '../lib/create_brush_handler';
const app = uiModules.get('apps/metrics/directives');
app.directive('metricsVisEditor', (timefilter) => {
return {
restrict: 'E',
link: ($scope, $el) => {
const addToState = ['autoApply', 'dirty', 'embedded', 'fields', 'visData'];
const Component = addScope(VisEditor, $scope, addToState);
const handleBrush = createBrushHandler($scope, timefilter);
const handleChange = part => {
$scope.$evalAsync(() => angular.copy(part, $scope.model));
};
const handleCommit = () => {
$scope.$evalAsync(() => $scope.commit());
};
const handleToggleAutoApply = () => {
$scope.$evalAsync(() => $scope.toggleAutoApply());
};
render(<Component
model={$scope.model}
onCommit={handleCommit}
onToggleAutoApply={handleToggleAutoApply}
onChange={handleChange}
onBrush={handleBrush} />, $el[0]);
$scope.$on('$destroy', () => {
unmountComponentAtNode($el[0]);
});
}
};
});

View file

@ -0,0 +1,46 @@
import _ from 'lodash';
import $ from 'jquery';
import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import Visualization from '../components/visualization';
import addScope from '../lib/add_scope';
import { uiModules } from 'ui/modules';
import createBrushHandler from '../lib/create_brush_handler';
const app = uiModules.get('apps/metrics/directives');
app.directive('metricsVisualization', (timefilter, $timeout) => {
return {
restrict: 'E',
link: ($scope, $el) => {
const addToState = ['model', 'visData', 'reversed'];
const Component = addScope(Visualization, $scope, addToState);
const handleBrush = createBrushHandler($scope, timefilter);
render(<Component onBrush={handleBrush} className="dashboard__visualization"/>, $el[0]);
$scope.$on('$destroy', () => unmountComponentAtNode($el[0]));
// For Metrics, Gauges and markdown visualizations we want to hide the
// panel title because it just doens't make sense to show it.
// This is wrapped in a timeout so it happens after the directive is mouted.
// otherwise the .panel might not be available.
$timeout(() => {
const panel = $($el[0]).parents('.panel');
if (panel.length) {
const panelHeading = panel.find('.panel-heading');
const panelTitle = panel.find('.panel-title');
const matchingTypes = ['metric', 'gauge', 'markdown'];
if (panelHeading.length && panelTitle.length && _.contains(matchingTypes, $scope.model.type)) {
panel.css({ position: 'relative' });
panelHeading.css({
position: 'absolute',
top: 0,
right: 0,
zIndex: 100
});
panelTitle.css({ display: 'none' });
}
}
}, 1);
}
};
});

View file

@ -0,0 +1,5 @@
<div
class="vis_editor_container"
ng-controller="MetricsEditorController" >
<metrics-vis-editor/>
</div>

View file

@ -1,47 +1,122 @@
import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import { FetchFieldsProvider } from '../lib/fetch_fields';
import { uiModules } from 'ui/modules';
import '../services/executor';
import createNewPanel from '../lib/create_new_panel';
import '../directives/vis_editor';
import _ from 'lodash';
import angular from 'angular';
import { FilterBarQueryFilterProvider } from 'ui/filter_bar/query_filter';
const AUTO_APPLY_KEY = 'metrics_autoApply';
function ReactEditorControllerProvider(Private, localStorage) {
const fetchFields = Private(FetchFieldsProvider);
const app = uiModules.get('kibana/metrics_vis', ['kibana']);
app.controller('MetricsEditorController', (
$location,
$element,
$scope,
Private,
timefilter,
localStorage,
metricsExecutor
) => {
class ReactEditorController {
constructor(el, vis) {
this.el = el;
this.vis = vis;
const autoApply = localStorage.get(AUTO_APPLY_KEY);
$scope.autoApply = autoApply != null ? autoApply : true;
$scope.embedded = $location.search().embed === 'true';
const queryFilter = Private(FilterBarQueryFilterProvider);
const createFetch = Private(require('../lib/fetch'));
const fetch = () => {
const fn = createFetch($scope);
return fn().then((resp) => {
$element.trigger('renderComplete');
return resp;
});
};
const fetchFields = Private(require('../lib/fetch_fields'));
const autoApply = localStorage.get(AUTO_APPLY_KEY);
vis.autoApply = autoApply != null ? autoApply : true;
const debouncedFetch = _.debounce(() => fetch(), 1000, {
leading: false,
trailing: true
});
const debouncedFetchFields = _.debounce(fetchFields($scope), 1000, {
leading: false,
trailing: true
});
// If the model doesn't exist we need to either intialize it with a copy from
// the $scope.vis._editableVis.params or create a new panel all together.
if (!$scope.model) {
if ($scope.vis._editableVis.params.type) {
$scope.model = _.assign({}, $scope.vis._editableVis.params);
} else {
$scope.model = createNewPanel();
angular.copy($scope.model, $scope.vis._editableVis.params);
}
fetch();
}
$scope.commit = () => {
fetch();
$scope.dirty = false;
};
$scope.toggleAutoApply = () => {
$scope.autoApply = !$scope.autoApply;
localStorage.set(AUTO_APPLY_KEY, $scope.autoApply);
};
$scope.$watchCollection('model', (newValue, oldValue) => {
angular.copy(newValue, $scope.vis._editableVis.params);
$scope.stageEditableVis();
$scope.dirty = !_.isEqual(newValue, oldValue);
if ($scope.dirty && $scope.autoApply) {
debouncedFetch();
$scope.dirty = false;
}
render(visData) {
this.visData = visData;
const patternsToFetch = [];
// Fetch any missing index patterns
if (!$scope.fields[newValue.index_pattern]) {
patternsToFetch.push(newValue.index_pattern);
}
return new Promise((resolve) => {
fetchFields(this.vis.params.index_pattern).then(fields => {
this.vis.fields = fields;
const Component = this.vis.type.editorConfig.component;
render(<Component vis={this.vis} visData={visData} renderComplete={resolve}/>, this.el);
});
newValue.series.forEach(series => {
if (series.override_index_pattern &&
!$scope.fields[series.series_index_pattern]) {
patternsToFetch.push(series.series_index_pattern);
}
});
if (newValue.annotations) {
newValue.annotations.forEach(item => {
if (item.index_pattern &&
!$scope.fields[item.index_pattern]) {
patternsToFetch.push(item.index_pattern);
}
});
}
resize() {
if (this.visData) {
this.render(this.visData);
}
if(patternsToFetch.length) {
debouncedFetchFields(_.unique(patternsToFetch));
}
});
destroy() {
unmountComponentAtNode(this.el);
}
}
$scope.visData = {};
$scope.fields = {};
// All those need to be consolidated
$scope.$listen(queryFilter, 'fetch', fetch);
$scope.$on('fetch', fetch);
return {
name: 'react_editor',
handler: ReactEditorController
};
}
fetchFields($scope)($scope.model.index_pattern);
// Register fetch
metricsExecutor.register({ execute: fetch });
// Start the executor
metricsExecutor.start();
// Destory the executor
$scope.$on('$destroy', metricsExecutor.destroy);
});
export { ReactEditorControllerProvider };

View file

@ -1,65 +1,36 @@
import './vis_controller';
import './editor_controller';
import '../visualizations/less/main.less';
import 'react-select/dist/react-select.css';
import '../less/main.less';
import image from '../images/icon-visualbuilder.svg';
import { MetricsRequestHandlerProvider } from './request_handler';
import { ReactEditorControllerProvider } from './editor_controller';
import { VisFactoryProvider } from 'ui/vis/vis_factory';
import { CATEGORY } from 'ui/vis/vis_category';
import { TemplateVisTypeProvider } from 'ui/template_vis_type';
import { VisVisTypeProvider } from 'ui/vis/vis_type';
// register the provider with the visTypes registry so that other know it exists
import { VisTypesRegistryProvider } from 'ui/registry/vis_types';
VisTypesRegistryProvider.register(MetricsVisProvider);
export default function MetricsVisProvider(Private) {
const VisFactory = Private(VisFactoryProvider);
const ReactEditorController = Private(ReactEditorControllerProvider).handler;
const metricsRequestHandler = Private(MetricsRequestHandlerProvider).handler;
const VisType = Private(VisVisTypeProvider);
const TemplateVisType = Private(TemplateVisTypeProvider);
return VisFactory.createReactVisualization({
// return the visType object, which kibana will use to display and configure new
// Vis object of this type.
return new TemplateVisType({
name: 'metrics',
title: 'Visual Builder',
description: 'Build time-series using a visual pipeline interface',
category: CATEGORY.TIME,
image,
description: 'Build time-series using a visual pipeline interface',
category: VisType.CATEGORY.TIME,
isExperimental: true,
visConfig: {
defaults: {
id: '61ca57f0-469d-11e7-af02-69e470af7417',
type: 'timeseries',
series: [
{
id: '61ca57f1-469d-11e7-af02-69e470af7417',
color: '#68BC00',
split_mode: 'everything',
metrics: [
{
id: '61ca57f2-469d-11e7-af02-69e470af7417',
type: 'count'
}],
seperate_axis: 0,
axis_position: 'right',
formatter: 'number',
chart_type: 'line',
line_width: 1,
point_size: 1,
fill: 0.5,
stacked: 'none'
}],
time_field: '@timestamp',
index_pattern: '*',
interval: 'auto',
axis_position: 'left',
axis_formatter: 'number',
show_legend:1
},
component: require('../components/vis_editor')
template: require('./vis.html'),
fullEditor: true,
params: {
editor: require('./editor.html')
},
editor: ReactEditorController,
editorConfig: {
component: require('../components/vis_editor')
},
requestHandler: metricsRequestHandler,
responseHandler: 'none'
requiresSearch: false,
requiresTimePicker: true,
implementsRenderComplete: true,
});
}

View file

@ -1,69 +0,0 @@
import { validateInterval } from '../lib/validate_interval';
import { dashboardContextProvider } from 'plugins/kibana/dashboard/dashboard_context';
const MetricsRequestHandlerProvider = function (Private, Notifier, config, timefilter, $http) {
const dashboardContext = Private(dashboardContextProvider);
const notify = new Notifier({ location: 'Metrics' });
return {
name: 'metrics',
handler: function (vis /*, appState, uiState*/) {
return new Promise((resolve) => {
const panel = vis.params;
if (panel && panel.id) {
const params = {
timerange: timefilter.getBounds(),
filters: [dashboardContext()],
panels: [panel]
};
try {
const maxBuckets = config.get('metrics:max_buckets');
validateInterval(timefilter, panel, maxBuckets);
return $http.post('../api/metrics/vis/data', params)
.success(resp => {
const patternsToFetch = [];
// Fetch any missing index patterns
if (!vis.fields) vis.fields = {};
if (!vis.fields[vis.params.index_pattern]) {
patternsToFetch.push(vis.params.index_pattern);
}
vis.params.series.forEach(series => {
if (series.override_index_pattern &&
!vis.fields[series.series_index_pattern]) {
patternsToFetch.push(series.series_index_pattern);
}
});
if (vis.params.annotations) {
vis.params.annotations.forEach(item => {
if (item.index_pattern &&
!vis.fields[item.index_pattern]) {
patternsToFetch.push(item.index_pattern);
}
});
}
resolve(resp);
})
.error(resp => {
resolve({});
const err = new Error(resp.message);
err.stack = resp.stack;
notify.error(err);
});
} catch (e) {
notify.error(e);
return resolve();
}
}
});
}
};
};
export { MetricsRequestHandlerProvider };

View file

@ -0,0 +1,4 @@
<div
ng-controller="MetricsVisController">
<metrics-visualization/>
</div>

View file

@ -0,0 +1,49 @@
import { uiModules } from 'ui/modules';
import 'ui/state_management/app_state';
import '../directives/visualization';
import { FilterBarQueryFilterProvider } from 'ui/filter_bar/query_filter';
const app = uiModules.get('kibana/metrics_vis');
app.controller('MetricsVisController', (
$scope,
$element,
Private,
timefilter,
getAppState,
$location
) => {
// If we are in the visualize editor context (and not embedded) we should not
// render the visualizations. This is handled by the editor itself.
const embedded = $location.search().embed === 'true';
if (!embedded && $scope.vis._editableVis) {
return;
}
// We need to watch the app state for changes to the dark theme attribute.
$scope.state = getAppState();
$scope.$watch('state.options.darkTheme', newValue => {
$scope.reversed = Boolean(newValue);
});
const queryFilter = Private(FilterBarQueryFilterProvider);
const createFetch = Private(require('../lib/fetch'));
const fetch = () => {
const fn = createFetch($scope);
return fn().then((resp) => {
$element.trigger('renderComplete');
return resp;
});
};
$scope.model = $scope.vis.params;
$scope.$watch('vis.params', fetch);
// All those need to be consolidated
$scope.$listen(timefilter, 'fetch', fetch);
$scope.$listen(queryFilter, 'fetch', fetch);
$scope.$on('courier:searchRefresh', fetch);
$scope.$on('fetch', fetch);
});

View file

@ -1,8 +1,5 @@
@borderRadius: 4px;
.vis_editor {
flex: 1;
}
.vis_editor_container {
background: @pageColor;
}

View file

@ -0,0 +1,58 @@
import React from 'react';
import { expect } from 'chai';
import { shallow } from 'enzyme';
import sinon from 'sinon';
import addScope from '../add_scope';
const Component = React.createClass({
render() {
return (<div/>);
}
});
describe('addScope()', () => {
let unsubStub;
let watchCollectionStub;
let $scope;
beforeEach(() => {
unsubStub = sinon.stub();
watchCollectionStub = sinon.stub().returns(unsubStub);
$scope = {
$watchCollection: watchCollectionStub,
testOne: 1,
testTwo: 2
};
});
it('adds $scope variables as props to wrapped component', () => {
const WrappedComponent = addScope(Component, $scope, ['testOne', 'testTwo']);
const wrapper = shallow(<WrappedComponent/>);
expect(wrapper.state('testOne')).to.equal(1);
expect(wrapper.state('testTwo')).to.equal(2);
});
it('calls $scope.$watchCollection on each scoped item', () => {
const WrappedComponent = addScope(Component, $scope, ['testOne', 'testTwo']);
shallow(<WrappedComponent/>);
expect(watchCollectionStub.calledTwice).to.equal(true);
expect(watchCollectionStub.firstCall.args[0]).to.equal('testOne');
expect(watchCollectionStub.secondCall.args[0]).to.equal('testTwo');
});
it('unsubscribes from watches', () => {
const WrappedComponent = addScope(Component, $scope, ['testOne', 'testTwo']);
const wrapper = shallow(<WrappedComponent/>);
wrapper.unmount();
expect(unsubStub.calledTwice).to.equal(true);
});
it('updates state when watch is called', () => {
const WrappedComponent = addScope(Component, $scope, ['testOne']);
const wrapper = shallow(<WrappedComponent/>);
watchCollectionStub.firstCall.args[1].call(null, 3);
expect(wrapper.state('testOne')).to.equal(3);
});
});

View file

@ -1,19 +1,29 @@
import createBrushHandler from '../create_brush_handler';
import sinon from 'sinon';
import moment from 'moment';
import { expect } from 'chai';
describe('createBrushHandler', () => {
let evalAsyncStub;
let $scope;
let timefilter;
let fn;
let range;
beforeEach(() => {
timefilter = { time: {}, update: () => {} };
fn = createBrushHandler(timefilter);
evalAsyncStub = sinon.stub().yields();
$scope = { $evalAsync: evalAsyncStub };
timefilter = { time: {} };
fn = createBrushHandler($scope, timefilter);
range = { xaxis: { from: '2017-01-01T00:00:00Z', to: '2017-01-01T00:10:00Z' } };
fn(range);
});
it('returns brushHandler() that calls $scope.$evalAsync()', () => {
expect(evalAsyncStub.calledOnce).to.equal(true);
});
it('returns brushHandler() that updates timefilter', () => {
expect(timefilter.time.from).to.equal(moment(range.xaxis.from).toISOString());
expect(timefilter.time.to).to.equal(moment(range.xaxis.to).toISOString());

View file

@ -0,0 +1,33 @@
import React from 'react';
export default function addScope(WrappedComponent, $scope, addToState = []) {
return React.createClass({
getInitialState() {
const state = {};
addToState.forEach(key => {
state[key] = $scope[key];
});
return state;
},
componentWillMount() {
this.unsubs = addToState.map(key => {
return $scope.$watchCollection(key, newValue => {
const newState = {};
newState[key] = newValue;
this.setState(newState);
});
});
},
componentWillUnmount() {
this.unsubs.forEach(fn => fn());
},
render() {
return (
<WrappedComponent {...this.state} {...this.props}/>
);
}
});
}

View file

@ -1,9 +1,8 @@
import moment from 'moment';
export default (timefilter) => ranges => {
//$scope.$evalAsync(() => {
timefilter.time.from = moment(ranges.xaxis.from).toISOString();
timefilter.time.to = moment(ranges.xaxis.to).toISOString();
timefilter.time.mode = 'absolute';
timefilter.update();
//});
export default ($scope, timefilter) => ranges => {
$scope.$evalAsync(() => {
timefilter.time.from = moment(ranges.xaxis.from).toISOString();
timefilter.time.to = moment(ranges.xaxis.to).toISOString();
timefilter.time.mode = 'absolute';
});
};

View file

@ -0,0 +1,42 @@
import { validateInterval } from './validate_interval';
import { dashboardContextProvider } from 'plugins/kibana/dashboard/dashboard_context';
export default (
timefilter,
Private,
Notifier,
$http,
config,
) => {
const dashboardContext = Private(dashboardContextProvider);
const notify = new Notifier({ location: 'Metrics' });
return $scope => () => {
const panel = $scope.model;
if (panel && panel.id) {
const params = {
timerange: timefilter.getBounds(),
filters: [dashboardContext()],
panels: [panel]
};
try {
const maxBuckets = config.get('metrics:max_buckets');
validateInterval(timefilter, panel, maxBuckets);
return $http.post('../api/metrics/vis/data', params)
.success(resp => {
$scope.visData = resp;
})
.error(resp => {
$scope.visData = {};
const err = new Error(resp.message);
err.stack = resp.stack;
notify.error(err);
});
} catch (e) {
notify.error(e);
return Promise.resolve();
}
}
return Promise.resolve();
};
};

View file

@ -1,29 +1,25 @@
const FetchFieldsProvider = (Notifier, $http) => {
export default (
Notifier,
$http
) => {
const notify = new Notifier({ location: 'Metrics' });
return (indexPatterns = ['*']) => {
return $scope => (indexPatterns = ['*']) => {
if (!Array.isArray(indexPatterns)) indexPatterns = [indexPatterns];
return new Promise((resolve, reject) => {
const fields = {};
Promise.all(indexPatterns.map(pattern => {
return $http.get(`../api/metrics/fields?index=${pattern}`)
.success(resp => {
if (resp.length && pattern) {
fields[pattern] = resp;
}
})
.error(resp => {
const err = new Error(resp.message);
err.stack = resp.stack;
notify.error(err);
reject(err);
});
})).then(() => {
resolve(fields);
});
});
return Promise.all(indexPatterns.map(pattern => {
return $http.get(`../api/metrics/fields?index=${pattern}`)
.success(resp => {
if (!$scope.fields) $scope.fields = {};
if (resp.length && pattern) {
$scope.fields[pattern] = resp;
}
})
.error(resp => {
$scope.visData = {};
const err = new Error(resp.message);
err.stack = resp.stack;
notify.error(err);
});
}));
};
};
export { FetchFieldsProvider };

View file

@ -2,7 +2,7 @@ import $ from 'jquery';
import L from 'leaflet';
import _ from 'lodash';
import d3 from 'd3';
import { KibanaMapLayer } from '../../tile_map/public/kibana_map_layer';
import { KibanaMapLayer } from 'ui/vis_maps/kibana_map_layer';
import { truncatedColorMaps } from 'ui/vislib/components/color/truncated_colormaps';
export default class ChoroplethLayer extends KibanaMapLayer {

View file

@ -2,11 +2,12 @@ import { uiModules } from 'ui/modules';
import 'plugins/kbn_vislib_vis_types/controls/vislib_basic_options';
import _ from 'lodash';
import AggConfigResult from 'ui/vis/agg_config_result';
import { KibanaMap } from '../../tile_map/public/kibana_map';
import { KibanaMap } from 'ui/vis_maps/kibana_map';
import ChoroplethLayer from './choropleth_layer';
import { truncatedColorMaps } from 'ui/vislib/components/color/truncated_colormaps';
import AggResponsePointSeriesTooltipFormatterProvider from './tooltip_formatter';
import '../../tile_map/public/lib/service_settings';
import { ResizeCheckerProvider } from 'ui/resize_checker';
import 'ui/vis_maps/lib/service_settings';
const module = uiModules.get('kibana/region_map', ['kibana']);
@ -14,18 +15,18 @@ module.controller('KbnRegionMapController', function ($scope, $element, Private,
serviceSettings, config) {
const tooltipFormatter = Private(AggResponsePointSeriesTooltipFormatterProvider);
const ResizeChecker = Private(ResizeCheckerProvider);
const notify = new Notifier({ location: 'Region map' });
const resizeChecker = new ResizeChecker($element);
let kibanaMap = null;
let choroplethLayer = null;
const kibanaMapReady = makeKibanaMap();
$scope.$watch('resize', () => {
resizeChecker.on('resize', () => {
if (kibanaMap) {
kibanaMap.resize();
}
});
let choroplethLayer = null;
const kibanaMapReady = makeKibanaMap();
$scope.$watch('esResponse', async function (response) {
kibanaMapReady.then(() => {

View file

@ -3,29 +3,32 @@ import './region_map_controller';
import './region_map_vis_params';
import regionTemplate from './region_map_controller.html';
import image from './images/icon-vector-map.svg';
import { VisFactoryProvider } from 'ui/vis/vis_factory';
import { CATEGORY } from 'ui/vis/vis_category';
import { VisSchemasProvider } from 'ui/vis/editors/default/schemas';
import { TemplateVisTypeProvider } from 'ui/template_vis_type/template_vis_type';
import { VisSchemasProvider } from 'ui/vis/schemas';
import { VisTypesRegistryProvider } from 'ui/registry/vis_types';
import { VisVisTypeProvider } from 'ui/vis/vis_type';
import { truncatedColorMaps } from 'ui/vislib/components/color/truncated_colormaps';
VisTypesRegistryProvider.register(function RegionMapProvider(Private, regionmapsConfig) {
const VisFactory = Private(VisFactoryProvider);
const VisType = Private(VisVisTypeProvider);
const TemplateVisType = Private(TemplateVisTypeProvider);
const Schemas = Private(VisSchemasProvider);
const vectorLayers = regionmapsConfig.layers;
const selectedLayer = vectorLayers[0];
const selectedJoinField = selectedLayer ? vectorLayers[0].fields[0] : null;
return VisFactory.createAngularVisualization({
return new TemplateVisType({
name: 'region_map',
title: 'Region Map',
implementsRenderComplete: true,
description: 'Show metrics on a thematic map. Use one of the provide base maps, or add your own. ' +
'Darker colors represent higher values.',
category: CATEGORY.MAP,
category: VisType.CATEGORY.MAP,
image,
visConfig: {
template: regionTemplate,
params: {
defaults: {
legendPosition: 'bottomright',
addTooltip: true,
@ -33,51 +36,45 @@ VisTypesRegistryProvider.register(function RegionMapProvider(Private, regionmaps
selectedLayer: selectedLayer,
selectedJoinField: selectedJoinField
},
template: regionTemplate,
legendPositions: [{
value: 'bottomleft',
text: 'bottom left',
}, {
value: 'bottomright',
text: 'bottom right',
}, {
value: 'topleft',
text: 'top left',
}, {
value: 'topright',
text: 'top right',
}],
colorSchemas: Object.keys(truncatedColorMaps),
vectorLayers: vectorLayers,
editor: '<region_map-vis-params></region_map-vis-params>'
},
editorConfig: {
optionsTemplate: '<region_map-vis-params></region_map-vis-params>',
collections: {
legendPositions: [{
value: 'bottomleft',
text: 'bottom left',
}, {
value: 'bottomright',
text: 'bottom right',
}, {
value: 'topleft',
text: 'top left',
}, {
value: 'topright',
text: 'top right',
}],
colorSchemas: Object.keys(truncatedColorMaps),
vectorLayers: vectorLayers,
schemas: new Schemas([
{
group: 'metrics',
name: 'metric',
title: 'Value',
min: 1,
max: 1,
aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality', 'top_hits', 'sum_bucket', 'min_bucket', 'max_bucket', 'avg_bucket'],
defaults: [
{ schema: 'metric', type: 'count' }
]
},
schemas: new Schemas([
{
group: 'metrics',
name: 'metric',
title: 'Value',
min: 1,
max: 1,
aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality', 'top_hits',
'sum_bucket', 'min_bucket', 'max_bucket', 'avg_bucket'],
defaults: [
{ schema: 'metric', type: 'count' }
]
},
{
group: 'buckets',
name: 'segment',
icon: 'fa fa-globe',
title: 'shape field',
min: 1,
max: 1,
aggFilter: ['terms']
}
])
}
{
group: 'buckets',
name: 'segment',
icon: 'fa fa-globe',
title: 'shape field',
min: 1,
max: 1,
aggFilter: ['terms']
}
])
});
});

View file

@ -15,9 +15,9 @@
id="regionMap"
class="kuiSelect kuiSideBarSelect"
ng-model="vis.params.selectedLayer"
ng-options="layer.name for layer in collections.vectorLayers"
ng-options="layer.name for layer in vis.type.params.vectorLayers"
ng-change="onLayerChange()"
ng-init="vis.params.selectedLayer=collections.vectorLayers[0]"
ng-init="vis.params.selectedLayer=vis.type.params.vectorLayers[0]"
></select>
</div>
</div>
@ -51,7 +51,7 @@
id="colorSchema"
class="kuiSelect kuiSideBarSelect"
ng-model="vis.params.colorSchema"
ng-options="mode for mode in collections.colorSchemas"
ng-options="mode for mode in vis.type.params.colorSchemas"
></select>
</div>
</div>

View file

@ -13,13 +13,11 @@ uiModules.get('kibana/region_map')
template: regionMapVisParamsTemplate,
link: function ($scope) {
$scope.collections = $scope.vis.type.editorConfig.collections;
$scope.onLayerChange = onLayerChange;
serviceSettings.getFileLayers()
.then(function (layersFromService) {
const newVectorLayers = $scope.collections.vectorLayers.slice();
const newVectorLayers = $scope.vis.type.params.vectorLayers.slice();
for (let i = 0; i < layersFromService.length; i += 1) {
const layerFromService = layersFromService[i];
const alreadyAdded = newVectorLayers.some((layer) =>_.eq(layerFromService, layer));
@ -28,10 +26,10 @@ uiModules.get('kibana/region_map')
}
}
$scope.collections.vectorLayers = newVectorLayers;
$scope.vis.type.params.vectorLayers = newVectorLayers;
if ($scope.collections.vectorLayers[0] && !$scope.vis.params.selectedLayer) {
$scope.vis.params.selectedLayer = $scope.collections.vectorLayers[0];
if ($scope.vis.type.params.vectorLayers[0] && !$scope.vis.params.selectedLayer) {
$scope.vis.params.selectedLayer = $scope.vis.type.params.vectorLayers[0];
onLayerChange();
}

View file

@ -15,16 +15,16 @@ function VisSpyTableProvider(Notifier, $filter, $rootScope, config, Private) {
link: function tableLinkFn($scope) {
$rootScope.$watchMulti.call($scope, [
'vis',
'visData'
'esResp'
], function () {
if (!$scope.vis || !$scope.visData) {
if (!$scope.vis || !$scope.esResp) {
$scope.table = null;
} else {
if (!$scope.spy.params.spyPerPage) {
$scope.spy.params.spyPerPage = PER_PAGE_DEFAULT;
}
$scope.table = tabifyAggResponse($scope.vis, $scope.searchSource.rawResponse, {
$scope.table = tabifyAggResponse($scope.vis, $scope.esResp, {
canSplit: false,
asAggConfigResults: true,
partialRows: true

View file

@ -29,10 +29,9 @@ describe('Integration', function () {
$rootScope.vis = vis;
$rootScope.esResponse = esResponse;
$rootScope.uiState = require('fixtures/mock_ui_state');
$el = $('<visualization vis="vis" vis-data="esResponse" ui-state="uiState">');
$el = $('<visualize vis="vis" es-resp="esResponse" ui-state="uiState">');
$compile($el)($rootScope);
$rootScope.$apply();
}
function OneRangeVis(params) {
@ -90,25 +89,20 @@ describe('Integration', function () {
it('passes the table groups to the kbnAggTableGroup directive', function () {
init(new OneRangeVis(), fixtures.oneRangeBucket);
$rootScope.$on('renderComplete', () => {
const $atg = $el.find('kbn-agg-table-group').first();
expect($atg.size()).to.be(1);
expect($atg.attr('group')).to.be('tableGroups');
expect($atg.isolateScope().group).to.be($atg.scope().tableGroups);
});
const $atg = $el.find('kbn-agg-table-group').first();
expect($atg.size()).to.be(1);
expect($atg.attr('group')).to.be('tableGroups');
expect($atg.isolateScope().group).to.be($atg.scope().tableGroups);
});
it('displays an error if the search had no hits', function () {
init(new OneRangeVis(), { hits: { total: 0, hits: [] } });
$rootScope.$on('renderComplete', () => {
expect($el.find('kbn-agg-table-group').size()).to.be(0);
expect($el.find('kbn-agg-table-group').size()).to.be(0);
const $err = $el.find('.table-vis-error');
expect($err.size()).to.be(1);
expect($err.text().trim()).to.be('No results found');
});
const $err = $el.find('.table-vis-error');
expect($err.size()).to.be(1);
expect($err.text().trim()).to.be('No results found');
});
it('displays an error if the search hits, but didn\'t create any rows', function () {
@ -127,12 +121,10 @@ describe('Integration', function () {
init(new ThreeTermVis(visParams), resp);
$rootScope.$on('renderComplete', () => {
expect($el.find('kbn-agg-table-group').size()).to.be(0);
expect($el.find('kbn-agg-table-group').size()).to.be(0);
const $err = $el.find('.table-vis-error');
expect($err.size()).to.be(1);
expect($err.text().trim()).to.be('No results found');
});
const $err = $el.find('.table-vis-error');
expect($err.size()).to.be(1);
expect($err.text().trim()).to.be('No results found');
});
});

View file

@ -58,7 +58,6 @@ describe('Controller', function () {
$rootScope.vis = vis;
$rootScope.uiState = new AppState({ uiState: {} }).makeStateful('uiState');
$rootScope.renderComplete = () => {};
$rootScope.newScope = function (scope) { $scope = scope; };
$el = $('<div>')

View file

@ -3,9 +3,9 @@ import 'plugins/table_vis/table_vis_controller';
import 'plugins/table_vis/table_vis_params';
import 'ui/agg_table';
import 'ui/agg_table/agg_table_group';
import { VisFactoryProvider } from 'ui/vis/vis_factory';
import { CATEGORY } from 'ui/vis/vis_category';
import { VisSchemasProvider } from 'ui/vis/editors/default/schemas';
import { VisVisTypeProvider } from 'ui/vis/vis_type';
import { TemplateVisTypeProvider } from 'ui/template_vis_type/template_vis_type';
import { VisSchemasProvider } from 'ui/vis/schemas';
import tableVisTemplate from 'plugins/table_vis/table_vis.html';
import { VisTypesRegistryProvider } from 'ui/registry/vis_types';
import image from './images/icon-table.svg';
@ -22,7 +22,8 @@ VisTypesRegistryProvider.register(TableVisTypeProvider);
// define the TableVisType
function TableVisTypeProvider(Private) {
const VisFactory = Private(VisFactoryProvider);
const VisType = Private(VisVisTypeProvider);
const TemplateVisType = Private(TemplateVisTypeProvider);
const Schemas = Private(VisSchemasProvider);
// define the TableVisController which is used in the template
@ -30,14 +31,14 @@ function TableVisTypeProvider(Private) {
// return the visType object, which kibana will use to display and configure new
// Vis object of this type.
return VisFactory.createAngularVisualization({
type: 'table',
return new TemplateVisType({
name: 'table',
title: 'Data Table',
image,
description: 'Display values in a table',
category: CATEGORY.DATA,
visConfig: {
category: VisType.CATEGORY.DATA,
template: tableVisTemplate,
params: {
defaults: {
perPage: 10,
showPartialRows: false,
@ -49,36 +50,34 @@ function TableVisTypeProvider(Private) {
showTotal: false,
totalFunc: 'sum'
},
template: tableVisTemplate,
},
editorConfig: {
optionsTemplate: '<table-vis-params></table-vis-params>',
schemas: new Schemas([
{
group: 'metrics',
name: 'metric',
title: 'Metric',
aggFilter: '!geo_centroid',
min: 1,
defaults: [
{ type: 'count', schema: 'metric' }
]
},
{
group: 'buckets',
name: 'bucket',
title: 'Split Rows'
},
{
group: 'buckets',
name: 'split',
title: 'Split Table'
}
])
editor: '<table-vis-params></table-vis-params>'
},
implementsRenderComplete: true,
hierarchicalData: function (vis) {
return Boolean(vis.params.showPartialRows || vis.params.showMeticsAtAllLevels);
}
},
schemas: new Schemas([
{
group: 'metrics',
name: 'metric',
title: 'Metric',
aggFilter: '!geo_centroid',
min: 1,
defaults: [
{ type: 'count', schema: 'metric' }
]
},
{
group: 'buckets',
name: 'bucket',
title: 'Split Rows'
},
{
group: 'buckets',
name: 'split',
title: 'Split Table'
}
])
});
}

View file

@ -44,7 +44,7 @@ module.controller('KbnTableVisController', function ($scope, $element, Private)
return table.rows.length > 0;
});
$scope.renderComplete();
$element.trigger('renderComplete');
}
$scope.hasSomeRows = hasSomeRows;

View file

@ -20,7 +20,6 @@ module.controller('KbnTagCloudController', function ($scope, $element, Private,
const aggConfigResult = new AggConfigResult(aggs[0], false, event, event);
clickHandler({ point: { aggConfigResult: aggConfigResult } });
});
tagCloud.on('renderComplete', () => {
const truncatedMessage = containerNode.querySelector('.tagcloud-truncated-message');
@ -34,9 +33,13 @@ module.controller('KbnTagCloudController', function ($scope, $element, Private,
const bucketName = containerNode.querySelector('.tagcloud-custom-label');
bucketName.innerHTML = `${$scope.vis.aggs[0].makeLabel()} - ${$scope.vis.aggs[1].makeLabel()}`;
truncatedMessage.style.display = truncated ? 'block' : 'none';
const status = tagCloud.getStatus();
if (TagCloud.STATUS.COMPLETE === status) {
incompleteMessage.style.display = 'none';
} else if (TagCloud.STATUS.INCOMPLETE === status) {
@ -44,7 +47,7 @@ module.controller('KbnTagCloudController', function ($scope, $element, Private,
}
$scope.renderComplete();
$element.trigger('renderComplete');
});
$scope.$watch('esResponse', async function (response) {
@ -84,9 +87,14 @@ module.controller('KbnTagCloudController', function ($scope, $element, Private,
$scope.$watch('vis.params', (options) => tagCloud.setOptions(options));
$scope.$watch('resize', () => {
$scope.$watch(getContainerSize, _.debounce(() => {
tagCloud.resize();
});
}, 1000, { trailing: true }), true);
function getContainerSize() {
return { width: $element.width(), height: $element.height() };
}
function getValue(metricsAgg, bucket) {
let size = metricsAgg.getValue(bucket);

View file

@ -1,62 +1,59 @@
import 'plugins/tagcloud/tag_cloud.less';
import 'plugins/tagcloud/tag_cloud_controller';
import 'plugins/tagcloud/tag_cloud_vis_params';
import { VisFactoryProvider } from 'ui/vis/vis_factory';
import { CATEGORY } from 'ui/vis/vis_category';
import { VisSchemasProvider } from 'ui/vis/editors/default/schemas';
import { VisVisTypeProvider } from 'ui/vis/vis_type';
import { TemplateVisTypeProvider } from 'ui/template_vis_type/template_vis_type';
import { VisSchemasProvider } from 'ui/vis/schemas';
import tagCloudTemplate from 'plugins/tagcloud/tag_cloud_controller.html';
import { VisTypesRegistryProvider } from 'ui/registry/vis_types';
import image from './images/icon-tagcloud.svg';
VisTypesRegistryProvider.register(function TagCloudProvider(Private) {
const VisFactory = Private(VisFactoryProvider);
const VisType = Private(VisVisTypeProvider);
const TemplateVisType = Private(TemplateVisTypeProvider);
const Schemas = Private(VisSchemasProvider);
return VisFactory.createAngularVisualization({
return new TemplateVisType({
name: 'tagcloud',
title: 'Tag Cloud',
image,
implementsRenderComplete: true,
description: 'A group of words, sized according to their importance',
category: CATEGORY.OTHER,
visConfig: {
category: VisType.CATEGORY.OTHER,
template: tagCloudTemplate,
params: {
defaults: {
scale: 'linear',
orientation: 'single',
minFontSize: 18,
maxFontSize: 72
},
template: tagCloudTemplate,
scales: ['linear', 'log', 'square root'],
orientations: ['single', 'right angled', 'multiple'],
editor: '<tagcloud-vis-params></tagcloud-vis-params>'
},
responseHandler: 'none',
editorConfig: {
collections: {
scales: ['linear', 'log', 'square root'],
orientations: ['single', 'right angled', 'multiple'],
schemas: new Schemas([
{
group: 'metrics',
name: 'metric',
title: 'Tag Size',
min: 1,
max: 1,
aggFilter: ['!std_dev', '!percentiles', '!percentile_ranks', '!derivative'],
defaults: [
{ schema: 'metric', type: 'count' }
]
},
optionsTemplate: '<tagcloud-vis-params></tagcloud-vis-params>',
schemas: new Schemas([
{
group: 'metrics',
name: 'metric',
title: 'Tag Size',
min: 1,
max: 1,
aggFilter: ['!std_dev', '!percentiles', '!percentile_ranks', '!derivative'],
defaults: [
{ schema: 'metric', type: 'count' }
]
},
{
group: 'buckets',
name: 'segment',
icon: 'fa fa-cloud',
title: 'Tags',
min: 1,
max: 1,
aggFilter: ['terms']
}
])
}
{
group: 'buckets',
name: 'segment',
icon: 'fa fa-cloud',
title: 'Tags',
min: 1,
max: 1,
aggFilter: ['terms']
}
])
});
});

View file

@ -1,11 +1,11 @@
<div class="form-group">
<div class="form-group">
<label>Text Scale</label>
<select class="form-control" ng-model="vis.params.scale" ng-options="mode for mode in config.collections.scales" ng-change="$parent.stageEditableVis()"></select>
<select class="form-control" ng-model="vis.params.scale" ng-options="mode for mode in vis.type.params.scales"></select>
</div>
<div class="form-group">
<label>Orientations</label>
<select class="form-control" ng-model="vis.params.orientation" ng-options="mode for mode in config.collections.orientations" ng-change="$parent.stageEditableVis()"></select>
<select class="form-control" ng-model="vis.params.orientation" ng-options="mode for mode in vis.type.params.orientations"></select>
</div>
<div class="form-group">
<label>Font Size</label>

View file

@ -13,7 +13,6 @@ uiModules.get('kibana/table_vis')
link: function ($scope, $element) {
const sliderContainer = $element[0];
const slider = sliderContainer.querySelector('.tag-cloud-fontsize-slider');
$scope.config = $scope.vis.type.editorConfig;
noUiSlider.create(slider, {
start: [$scope.vis.params.minFontSize, $scope.vis.params.maxFontSize],
connect: true,
@ -26,9 +25,7 @@ uiModules.get('kibana/table_vis')
const fontSize = slider.noUiSlider.get();
$scope.vis.params.minFontSize = parseInt(fontSize[0], 10);
$scope.vis.params.maxFontSize = parseInt(fontSize[1], 10);
$scope.vis.updateState();
$scope.$apply();
});
}
};

View file

@ -1,8 +0,0 @@
export default function (kibana) {
return new kibana.Plugin({
uiExports: {
visTypes: ['plugins/tile_map/tile_map_vis']
}
});
}

View file

@ -1,4 +0,0 @@
{
"name": "tile_map",
"version": "kibana"
}

View file

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="30px" height="46px" viewBox="0 0 30 46" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 42 (36781) - http://www.bohemiancoding.com/sketch -->
<title>icon-tilemap</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Visualize" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Visualize-Create-New" transform="translate(-130.000000, -436.000000)" fill="#000000">
<g id="maps" transform="translate(74.000000, 361.000000)">
<g id="tile-map" transform="translate(2.000000, 45.000000)">
<path d="M76.5,45.3333333 C76.5,41.1106771 73.1308594,37.6666667 69,37.6666667 C64.8691406,37.6666667 61.5,41.1106771 61.5,45.3333333 C61.5,49.5559896 64.8691406,53 69,53 C73.1308594,53 76.5,49.5559896 76.5,45.3333333 Z M84,45.3333333 C84,47.1601562 83.7949219,49.046875 83.0332031,50.6940104 L72.3691406,73.8736979 C71.7539062,75.1914062 70.40625,76 69,76 C67.59375,76 66.2460938,75.1914062 65.6601562,73.8736979 L54.9667969,50.6940104 C54.2050781,49.046875 54,47.1601562 54,45.3333333 C54,36.8580729 60.7089844,30 69,30 C77.2910156,30 84,36.8580729 84,45.3333333 Z" id="icon-tilemap"></path>
</g>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -1,270 +0,0 @@
import 'ui/vislib';
import 'plugins/kbn_vislib_vis_types/controls/vislib_basic_options';
import $ from 'jquery';
import _ from 'lodash';
import { KibanaMap } from './kibana_map';
import { GeohashLayer } from './geohash_layer';
import './lib/service_settings';
import './styles/_tilemap.less';
export function MapsVisualizationProvider(serviceSettings, Notifier, getAppState) {
const notify = new Notifier({ location: 'Coordinate Map' });
class MapsVisualization {
constructor(element, vis) {
this.vis = vis;
this.$el = $(element);
this._$container = this.$el;
this._geohashLayer = null;
this._kibanaMap = null;
this._kibanaMapReady = this._makeKibanaMap();
this._baseLayerDirty = true;
this._currentParams = null;
}
destroy() {
if (this._kibanaMap) {
this._kibanaMap.destroy();
}
}
async render(esResponse, status) {
return new Promise(async(resolve) => {
await this._kibanaMapReady;
if (status.params || status.aggs) await this._updateParams();
if (esResponse && typeof esResponse.geohashGridAgg === 'undefined') {
return resolve();
}
if (status.data) this._recreateGeohashLayer(esResponse);
if (status.uiState) this._kibanaMap.useUiStateFromVisualization(this.vis);
if (status.resize) this._kibanaMap.resize();
this._doRenderComplete(resolve);
});
}
//**********************************************************************************************************
async _makeKibanaMap() {
try {
this._tmsService = await serviceSettings.getTMSService();
this._tmsError = null;
} catch (e) {
this._tmsService = null;
this._tmsError = e;
notify.warning(e.message);
}
if (this._kibanaMap) {
this._kibanaMap.destroy();
}
const containerElement = $(this._$container)[0];
const options = _.clone(this._getMinMaxZoom());
const uiState = this.vis.getUiState();
const zoomFromUiState = parseInt(uiState.get('mapZoom'));
const centerFromUIState = uiState.get('mapCenter');
options.zoom = !isNaN(zoomFromUiState) ? zoomFromUiState : this.vis.type.visConfig.defaults.mapZoom;
options.center = centerFromUIState ? centerFromUIState : this.vis.type.visConfig.defaults.mapCenter;
this._kibanaMap = new KibanaMap(containerElement, options);
this._kibanaMap.addDrawControl();
this._kibanaMap.addFitControl();
this._kibanaMap.addLegendControl();
this._kibanaMap.persistUiStateForVisualization(this.vis);
let previousPrecision = this._kibanaMap.getAutoPrecision();
let precisionChange = false;
this._kibanaMap.on('zoomchange', () => {
precisionChange = (previousPrecision !== this._kibanaMap.getAutoPrecision());
previousPrecision = this._kibanaMap.getAutoPrecision();
this.vis.aggs[1].params.precision = previousPrecision;
});
this._kibanaMap.on('zoomend', () => {
const isAutoPrecision = _.get(this._chartData, 'geohashGridAgg.params.autoPrecision', true);
if (!isAutoPrecision) {
return;
}
if (precisionChange) {
this.vis.updateState();
} else {
this._recreateGeohashLayer(this._chartData);
}
});
this._kibanaMap.on('drawCreated:rectangle', event => {
this.addSpatialFilter(_.get(this._chartData, 'geohashGridAgg'), 'geo_bounding_box', event.bounds);
});
this._kibanaMap.on('drawCreated:polygon', event => {
this.addSpatialFilter(_.get(this._chartData, 'geohashGridAgg'), 'geo_polygon', { points: event.points });
});
this._kibanaMap.on('baseLayer:loaded', () => {
this._baseLayerDirty = false;
});
this._kibanaMap.on('baseLayer:loading', () => {
this._baseLayerDirty = true;
});
}
_getMinMaxZoom() {
const mapParams = this._getMapsParams();
if (this._tmsError) {
return serviceSettings.getFallbackZoomSettings(mapParams.wms.enabled);
} else {
return this._tmsService.getMinMaxZoom(mapParams.wms.enabled);
}
}
_recreateGeohashLayer(esResponse) {
if (esResponse === this._chartData) {
return;
}
this._chartData = esResponse;
if (this._geohashLayer) {
this._kibanaMap.removeLayer(this._geohashLayer);
}
if (!this._chartData || !this._chartData.geoJson) {
return;
}
const geohashOptions = this._getGeohashOptions();
this._geohashLayer = new GeohashLayer(this._chartData.geoJson, geohashOptions, this._kibanaMap.getZoomLevel(), this._kibanaMap);
this._kibanaMap.addLayer(this._geohashLayer);
}
/**
* called on options change (vis.params change)
*/
async _updateParams() {
const mapParams = this._getMapsParams();
if (_.eq(this._currentParams, mapParams)) {
return;
}
this._currentParams = _.cloneDeep(mapParams);
const { minZoom, maxZoom } = this._getMinMaxZoom();
if (mapParams.wms.enabled) {
//Switch to WMS
if (maxZoom > this._kibanaMap.getMaxZoomLevel()) {
//need to recreate the map with less restrictive zoom
this._geohashLayer = null;
this._kibanaMapReady = this._makeKibanaMap();
await this._kibanaMapReady;
}
this._kibanaMap.setBaseLayer({
baseLayerType: 'wms',
options: {
minZoom: minZoom,
maxZoom: maxZoom,
url: mapParams.wms.url,
...mapParams.wms.options
}
});
} else {
//switch to regular
if (maxZoom < this._kibanaMap.getMaxZoomLevel()) {
//need to recreate the map with more restrictive zoom level
this._geohashLayer = null;
this._kibanaMapReady = this._makeKibanaMap();
await this._kibanaMapReady;
if (this._kibanaMap.getZoomLevel() > maxZoom) {
this._kibanaMap.setZoomLevel(maxZoom);
}
}
if (!this._tmsError) {
const url = this._tmsService.getUrl();
const options = this._tmsService.getTMSOptions();
this._kibanaMap.setBaseLayer({
baseLayerType: 'tms',
options: { url, ...options }
});
}
}
const geohashOptions = this._getGeohashOptions();
if (!this._geohashLayer || !this._geohashLayer.isReusable(geohashOptions)) {
this._recreateGeohashLayer(this._chartData);
}
this._kibanaMap.setLegendPosition(mapParams.legendPosition);
this._kibanaMap.setDesaturateBaseLayer(mapParams.isDesaturated);
this._kibanaMap.setShowTooltip(mapParams.addTooltip);
this._kibanaMap.useUiStateFromVisualization(this.vis);
}
_getMapsParams() {
return _.assign(
{},
this.vis.type.visConfig.defaults,
{ type: this.vis.type.name },
this.vis.params
);
}
_getGeohashOptions() {
const newParams = this._getMapsParams();
return {
valueFormatter: this._chartData ? this._chartData.valueFormatter : null,
tooltipFormatter: this._chartData ? this._chartData.tooltipFormatter : null,
mapType: newParams.mapType,
heatmap: {
heatBlur: newParams.heatBlur,
heatMaxZoom: newParams.heatMaxZoom,
heatMinOpacity: newParams.heatMinOpacity,
heatRadius: newParams.heatRadius
}
};
}
_doRenderComplete(resolve) {
if (this._baseLayerDirty) {//as long as the baselayer is dirty, we cannot fire the render complete event
setTimeout(() => {
this._doRenderComplete(resolve);
}, 10);
} else {
resolve();
}
}
addSpatialFilter(agg, filterName, filterData) {
if (!agg) {
return;
}
const indexPatternName = agg.vis.indexPattern.id;
const field = agg.fieldName();
const filter = { meta: { negate: false, index: indexPatternName } };
filter[filterName] = { ignore_unmapped: true };
filter[filterName][field] = filterData;
getAppState().filters.push(filter);
this.vis.updateState();
}
}
return MapsVisualization;
}

View file

@ -1,94 +0,0 @@
import { supports } from 'ui/utils/supports';
import { CATEGORY } from 'ui/vis/vis_category';
import { VisFactoryProvider } from 'ui/vis/vis_factory';
import { MapsVisualizationProvider } from './maps_visualization';
import { VisSchemasProvider } from 'ui/vis/editors/default/schemas';
import { AggResponseGeoJsonProvider } from 'ui/agg_response/geo_json/geo_json';
import tileMapTemplate from './editors/tile_map.html';
import image from './images/icon-tilemap.svg';
import { VisTypesRegistryProvider } from 'ui/registry/vis_types';
VisTypesRegistryProvider.register(function TileMapVisType(Private, getAppState, courier, config) {
const Schemas = Private(VisSchemasProvider);
const geoJsonConverter = Private(AggResponseGeoJsonProvider);
const VisFactory = Private(VisFactoryProvider);
const MapsVisualization = Private(MapsVisualizationProvider);
return VisFactory.createBaseVisualization({
name: 'tile_map',
title: 'Coordinate Map',
image,
description: 'Plot latitude and longitude coordinates on a map',
category: CATEGORY.MAP,
visConfig: {
canDesaturate: true,
defaults: {
mapType: 'Scaled Circle Markers',
isDesaturated: true,
addTooltip: true,
heatMaxZoom: 0,
heatMinOpacity: 0.1,
heatRadius: 25,
heatBlur: 15,
legendPosition: 'bottomright',
mapZoom: 2,
mapCenter: [0, 0],
wms: config.get('visualization:tileMap:WMSdefaults')
}
},
responseConverter: geoJsonConverter,
responseHandler: 'basic',
implementsRenderComplete: true,
visualization: MapsVisualization,
editorConfig: {
collections: {
legendPositions: [{
value: 'bottomleft',
text: 'bottom left',
}, {
value: 'bottomright',
text: 'bottom right',
}, {
value: 'topleft',
text: 'top left',
}, {
value: 'topright',
text: 'top right',
}],
mapTypes: [
'Scaled Circle Markers',
'Shaded Circle Markers',
'Shaded Geohash Grid',
'Heatmap'
],
canDesaturate: !!supports.cssFilters
},
optionsTemplate: tileMapTemplate,
schemas: new Schemas([
{
group: 'metrics',
name: 'metric',
title: 'Value',
min: 1,
max: 1,
aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality', 'top_hits'],
defaults: [
{ schema: 'metric', type: 'count' }
]
},
{
group: 'buckets',
name: 'segment',
title: 'Geo Coordinates',
aggFilter: 'geohash_grid',
min: 1,
max: 1
}
])
}
});
});

View file

@ -97,7 +97,7 @@ export default function timechartFn(Private, config, $rootScope, timefilter, $co
timefilter.time.from = moment(ranges.xaxis.from);
timefilter.time.to = moment(ranges.xaxis.to);
timefilter.time.mode = 'absolute';
$scope.$apply();
$scope.search();
});
$elem.on('mouseleave', function () {

View file

@ -1,51 +1,35 @@
import { VisFactoryProvider } from 'ui/vis/vis_factory';
import { CATEGORY } from 'ui/vis/vis_category';
import { VisVisTypeProvider } from 'ui/vis/vis_type';
import image from '../images/icon-timelion.svg';
import { VisTypesRegistryProvider } from 'ui/registry/vis_types';
import { TimelionRequestHandlerProvider } from './timelion_request_handler';
import { TemplateVisTypeProvider } from 'ui/template_vis_type';
import template from 'plugins/timelion/vis/timelion_vis.html';
import editorTemplate from 'plugins/timelion/vis/timelion_vis_params.html';
import 'plugins/timelion/vis/timelion_vis_controller';
import 'plugins/timelion/vis/timelion_vis_params_controller';
import 'plugins/timelion/vis/timelion_vis.less';
define(function (require) {
// we also need to load the controller and directive used by the template
require('plugins/timelion/vis/timelion_vis_controller');
require('plugins/timelion/directives/timelion_expression_input');
// export the provider so that the visType can be required with Private()
export default function TimelionVisProvider(Private) {
const VisType = Private(VisVisTypeProvider);
const TemplateVisType = Private(TemplateVisTypeProvider);
// Stylin
require('plugins/timelion/vis/timelion_vis.less');
// return the visType object, which kibana will use to display and configure new
// Vis object of this type.
return new TemplateVisType({
name: 'timelion',
title: 'Timelion',
image,
description: 'Build time-series using functional expressions',
category: VisType.CATEGORY.TIME,
template,
params: {
editor: editorTemplate,
},
requiresSearch: false,
requiresTimePicker: true,
implementsRenderComplete: true,
});
}
// register the provider with the visTypes registry so that other know it exists
VisTypesRegistryProvider.register(TimelionVisProvider);
function TimelionVisProvider(Private) {
const VisFactory = Private(VisFactoryProvider);
const timelionRequestHandler = Private(TimelionRequestHandlerProvider);
// return the visType object, which kibana will use to display and configure new
// Vis object of this type.
return VisFactory.createAngularVisualization({
name: 'timelion',
title: 'Timelion',
image,
description: 'Build time-series using functional expressions',
category: CATEGORY.TIME,
visConfig: {
defaults: {
expression: '.es(*)',
interval: 'auto'
},
template: require('plugins/timelion/vis/timelion_vis.html'),
},
editorConfig: {
optionsTemplate: require('plugins/timelion/vis/timelion_vis_params.html')
},
requestHandler: timelionRequestHandler.handler,
responseHandler: 'none',
options: {
showIndexSelection: false
}
});
}
// export the provider so that the visType can be required with Private()
return TimelionVisProvider;
});
// register the provider with the visTypes registry so that other know it exists
VisTypesRegistryProvider.register(TimelionVisProvider);

View file

@ -1,48 +0,0 @@
import _ from 'lodash';
import { dashboardContextProvider } from 'plugins/kibana/dashboard/dashboard_context';
const TimelionRequestHandlerProvider = function (Private, Notifier, $http, $rootScope, timefilter) {
const timezone = Private(require('plugins/timelion/services/timezone'))();
const dashboardContext = Private(dashboardContextProvider);
const notify = new Notifier({
location: 'Timelion'
});
return {
name: 'timelion',
handler: function (vis /*, appState, uiState */) {
return new Promise((resolve, reject) => {
console.log('[timelion] get');
const expression = vis.params.expression;
if (!expression) return;
$http.post('../api/timelion/run', {
sheet: [expression],
extended: {
es: {
filter: dashboardContext()
}
},
time: _.extend(timefilter.time, {
interval: vis.params.interval,
timezone: timezone
}),
})
.success(function (resp) {
resolve(resp);
})
.error(function (resp) {
const err = new Error(resp.message);
err.stack = resp.stack;
notify.error(err);
reject(err);
});
});
}
};
};
export { TimelionRequestHandlerProvider };

View file

@ -1,3 +1,3 @@
<div ng-controller="TimelionVisController" class="timelion-vis">
<div chart="esResponse.sheet[0]" interval="vis.params.interval"></div>
<div chart="sheet[0]" interval="vis.params.interval"></div>
</div>

View file

@ -1,14 +1,69 @@
define(function (require) {
require('plugins/timelion/directives/chart/chart');
require('plugins/timelion/directives/timelion_interval/timelion_interval');
require('ui/state_management/app_state');
import _ from 'lodash';
import { FilterBarQueryFilterProvider } from 'ui/filter_bar/query_filter';
import { uiModules } from 'ui/modules';
import timezoneProvider from 'plugins/timelion/services/timezone';
import { dashboardContextProvider } from 'plugins/kibana/dashboard/dashboard_context';
import 'plugins/timelion/directives/chart/chart';
import 'plugins/timelion/directives/timelion_interval/timelion_interval';
import 'ui/state_management/app_state';
const module = require('ui/modules').get('kibana/timelion_vis', ['kibana']);
module.controller('TimelionVisController', function ($scope) {
const module = uiModules.get('kibana/timelion_vis', ['kibana']);
$scope.$on('renderComplete', event => {
event.stopPropagation();
$scope.renderComplete();
module.controller('TimelionVisController', function ($scope, $element, Private, Notifier, $http, $rootScope, timefilter) {
const queryFilter = Private(FilterBarQueryFilterProvider);
const timezone = Private(timezoneProvider)();
const dashboardContext = Private(dashboardContextProvider);
const notify = new Notifier({
location: 'Timelion'
});
$scope.search = function run() {
const expression = $scope.vis.params.expression;
if (!expression) return;
$http.post('../api/timelion/run', {
sheet: [expression],
extended: {
es: {
filter: dashboardContext()
}
},
time: _.extend(timefilter.time, {
interval: $scope.vis.params.interval,
timezone: timezone
}),
})
// data, status, headers, config
.success(function (resp) {
$scope.sheet = resp.sheet;
})
.error(function (resp) {
$scope.sheet = [];
const err = new Error(resp.message);
err.stack = resp.stack;
notify.error(err);
});
};
// This is bad, there should be a single event that triggers a refresh of data.
// When the expression updates
$scope.$watchMulti(['vis.params.expression', 'vis.params.interval'], $scope.search);
// When the time filter changes
$scope.$listen(timefilter, 'fetch', $scope.search);
// When a filter is added to the filter bar?
$scope.$listen(queryFilter, 'fetch', $scope.search);
// When auto refresh happens
$scope.$on('courier:searchRefresh', $scope.search);
$scope.$on('fetch', $scope.search);
$scope.$on('renderComplete', event => {
event.stopPropagation();
$element.trigger('renderComplete');
});
});

View file

@ -1,4 +1,4 @@
<div>
<div ng-controller="TimelionVisParamsController">
<div class="form-group">
<label>Interval</label>
<div class="form-group">

View file

@ -0,0 +1,12 @@
import { uiModules } from 'ui/modules';
import 'plugins/timelion/directives/timelion_expression_input';
const module = uiModules.get('kibana/timelion_vis', ['kibana']);
module.controller('TimelionVisParamsController', function ($scope, $rootScope) {
$scope.vis.params.expression = $scope.vis.params.expression || '.es(*)';
$scope.vis.params.interval = $scope.vis.params.interval || '1m';
$scope.search = function () {
$rootScope.$broadcast('courier:searchRefresh');
};
});

View file

@ -3,7 +3,6 @@ import { VisProvider } from 'ui/vis';
import { AggTypesIndexProvider } from 'ui/agg_types/index';
import { VisTypesRegistryProvider } from 'ui/registry/vis_types';
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
import { VisAggConfigProvider } from 'ui/vis/agg_config';
// eslint-disable-next-line kibana-custom/no-default-export
export default function AggParamWriterHelper(Private) {
@ -11,7 +10,6 @@ export default function AggParamWriterHelper(Private) {
const aggTypes = Private(AggTypesIndexProvider);
const visTypes = Private(VisTypesRegistryProvider);
const stubbedLogstashIndexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
const AggConfig = Private(VisAggConfigProvider);
/**
* Helper object for writing aggParams. Specify an aggType and it will find a vis & schema, and
@ -68,7 +66,7 @@ export default function AggParamWriterHelper(Private) {
}
self.vis = new Vis(self.indexPattern, {
type: self.visType.name
type: self.visType
});
}
@ -81,26 +79,17 @@ export default function AggParamWriterHelper(Private) {
if (self.aggType.type === 'metrics') {
paramValues.field = _.sample(self.indexPattern.fields.byType.number);
} else {
const type = self.aggType.params.byName.field.filterFieldTypes || 'string';
let field;
do {
field = _.sample(self.indexPattern.fields.byType[type]);
} while (!field.aggregatable);
paramValues.field = field.name;
paramValues.field = _.sample(self.indexPattern.fields.byType.string);
}
}
const agg = new AggConfig(self.vis, {
id: 1,
schema: self.visAggSchema.name,
type: self.aggType.name,
params: paramValues
});
self.vis.setState({
type: self.vis.type.name,
aggs: [agg.toJSON()]
aggs: [{
type: self.aggType,
schema: self.visAggSchema,
params: paramValues
}]
});
const aggConfig = _.find(self.vis.aggs, function (aggConfig) {

Some files were not shown because too many files have changed in this diff Show more