Introduce lab mode for visualizations (#15050)

* apply patch

add styling

remove cruft

split up concept of experimental and labs

adjust wording

* improve wording

* improve wording & punctuation. remove concept of feedback-url

* remove duplicate labeling between labs/experimental; resolve some typos

* merging isExperimental and isLabs flags to a stage setting

* adding the option to override feedback message back (and improving it)

* updating the docs

* change text labs to lab

* visualize:enableLabsVisualizations to visualize:enableLabs

* fixing github link
This commit is contained in:
Peter Pisljar 2017-11-21 20:49:24 +01:00 committed by GitHub
parent 6e1faea678
commit 4e982c0a6d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 133 additions and 22 deletions

View file

@ -72,7 +72,10 @@ The list of common parameters:
- *options.showQueryBar*: <bool> show or hide query bar (defaults to true)
- *options.showFilterBar*: <bool> show or hide filter bar (defaults to true)
- *options.showIndexSelection*: <bool> show or hide index selection (defaults to true)
- *isExperimental*: <bool> mark visualization as experimental (defaults to false)
- *stage*: <string> Set this to "experimental" or "labs" to mark your visualization as experimental.
Labs visualizations can also be disabled from the advanced settings. (defaults to "production")
- *feedbackMessage*: <string> You can provide a message (which can contain HTML), that will be appended
to the experimental notification in visualize, if your visualization is experimental or in lab mode.
Each of the factories have some of the custom parameters, which will be described below.

View file

@ -5,6 +5,7 @@ import { VisTypesRegistryProvider } from 'ui/registry/vis_types';
import { VisController } from './vis_controller';
import { ControlsTab } from './components/editor/controls_tab';
import { OptionsTab } from './components/editor/options_tab';
import { defaultFeedbackMessage } from 'ui/vis/default_feedback_message';
import image from './images/icon-input-control.svg';
@ -18,7 +19,8 @@ function InputControlVisProvider(Private) {
image,
description: 'Create interactive controls for easy dashboard manipulation.',
category: CATEGORY.OTHER,
isExperimental: true,
stage: 'lab',
feedbackMessage: defaultFeedbackMessage,
visualization: VisController,
visConfig: {
defaults: {

View file

@ -50,6 +50,13 @@
index-patterns="[indexPattern]"
></filter-bar>
<div class="kuiInfoPanel kuiInfoPanel--warning kuiVerticalRhythm" ng-if="vis.type.shouldMarkAsExperimentalInUI">
<div class="kuiInfoPanelBody">
<div class="kuiInfoPanelBody__message" ng-bind-html="getAdditionalMessage()">
</div>
</div>
</div>
<visualize
saved-obj="savedVis"
ui-state="uiState"

View file

@ -295,5 +295,10 @@ function VisEditor($scope, $route, timefilter, AppState, $window, kbnUrl, courie
$scope.fetch();
};
$scope.getAdditionalMessage = () => {
return `This visualization is marked as experimental. ${vis.type.feedbackMessage}`;
};
init();
}

View file

@ -10,8 +10,9 @@ import { EmbeddableFactory, Embeddable } from 'ui/embeddable';
import chrome from 'ui/chrome';
export class VisualizeEmbeddableFactory extends EmbeddableFactory {
constructor($compile, $rootScope, visualizeLoader, timefilter, Notifier, Promise, Private) {
constructor($compile, $rootScope, visualizeLoader, timefilter, Notifier, Promise, Private, config) {
super();
this._config = config;
this.$compile = $compile;
this.visualizeLoader = visualizeLoader;
this.$rootScope = $rootScope;
@ -30,6 +31,21 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory {
visualizeScope.editUrl = this.getEditPath(panel.id);
return this.visualizeLoader.get(panel.id)
.then(savedObject => {
const isLabsEnabled = this._config.get('visualize:enableLabs');
if (!isLabsEnabled && savedObject.vis.type.stage === 'lab') {
domNode.innerHTML = `
<div class="disabledLabVisualization">
<div class="kuiVerticalRhythm disabledLabVisualization__icon kuiIcon fa-flask" aria-hidden="true"></div>
<div class="kuiVerticalRhythm"><em>${savedObject.title}</em> is a lab visualization.</div>
<div class="kuiVerticalRhythm">Please turn on lab-mode in the advanced settings to see lab visualizations.</div>
</div>
`;
return new Embeddable({
title: savedObject.title
});
}
if (!container.getHidePanelTitles()) {
visualizeScope.sharedItemTitle = panel.title !== undefined ? panel.title : savedObject.title;
}

View file

@ -9,8 +9,9 @@ export function visualizeEmbeddableFactoryProvider(Private) {
timefilter,
Notifier,
Promise,
Private) => {
return new VisualizeEmbeddableFactory($compile, $rootScope, savedVisualizations, timefilter, Notifier, Promise, Private);
Private,
config) => {
return new VisualizeEmbeddableFactory($compile, $rootScope, savedVisualizations, timefilter, Notifier, Promise, Private, config);
};
return Private(VisualizeEmbeddableFactoryProvider);
}

View file

@ -24,12 +24,13 @@ export function VisualizeListingController($injector) {
const notify = new Notifier({ location: 'Visualize' });
this.fetchItems = (filter) => {
const isLabsEnabled = config.get('visualize:enableLabs');
return visualizationService.find(filter, config.get('savedObjects:listingLimit'))
.then(result => {
this.totalItems = result.total;
this.showLimitError = result.total > config.get('savedObjects:listingLimit');
this.listingLimit = config.get('savedObjects:listingLimit');
return result.hits;
return result.hits.filter(result => (isLabsEnabled || !result.type.stage === 'lab'));
});
};

View file

@ -150,10 +150,21 @@ export class VisualizeListingTable extends Component {
}
renderRowCells(item) {
let flaskHolder;
if (item.type.shouldMarkAsExperimentalInUI()) {
flaskHolder = <span className="kuiIcon fa-flask ng-scope">&nbsp;</span>;
}else{
flaskHolder = <span />;
}
return [
<a className="kuiLink" href={this.getUrlForItem(item)}>
{item.title}
</a>,
<span>
{flaskHolder}
<a className="kuiLink" href={this.getUrlForItem(item)}>
{item.title}
</a>
</span>,
<span className="kuiStatusText">
{this.renderItemTypeIcon(item)}
{item.type.title}

View file

@ -0,0 +1,12 @@
.disabledLabVisualization {
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
}
.disabledLabVisualization__icon {
font-size: 2em;
}

View file

@ -1,6 +1,16 @@
@import (reference) "~ui/styles/variables";
@import (reference) "~ui/styles/bootstrap/list-group";
@import (reference) "~ui/styles/list-group-menu";
@import "./lab_vis";
.visualizeViewContentHeader {
display: flex;
}
.visualizeViewContentHeader .kuiTitle {
flex: 1 1 auto;
}
.wizard-sub-title {
margin-top: 0px;

View file

@ -15,9 +15,11 @@
<div class="kuiViewContent kuiViewContent--constrainedWidth">
<div class="kuiViewContentItem">
<!-- Header -->
<h1 class="kuiTitle kuiVerticalRhythm kuiVerticalRhythm--medium">
Select visualization type
</h1>
<div class="visualizeViewContentHeader">
<h1 class="kuiTitle kuiVerticalRhythm kuiVerticalRhythm--medium">
Select visualization type
</h1>
</div>
<!-- Search -->
<div class="kuiSearchInput kuiVerticalRhythm kuiVerticalRhythm--medium fullWidth" role="search">
@ -29,7 +31,6 @@
ng-model="searchTerm"
>
</div>
<div
class="kuiVerticalRhythm kuiVerticalRhythm--medium"
ng-repeat="category in filteredVisTypeCategories"
@ -75,7 +76,7 @@
<div
class="kuiGalleryItem__icon kuiIcon fa-flask"
ng-if="type.isExperimental"
ng-if="type.shouldMarkAsExperimentalInUI()"
></div>
<span

View file

@ -31,7 +31,7 @@ routes.when(VisualizeConstants.WIZARD_STEP_1_PAGE_PATH, {
controller: 'VisualizeWizardStep1',
});
module.controller('VisualizeWizardStep1', function ($scope, $route, kbnUrl, timefilter, Private) {
module.controller('VisualizeWizardStep1', function ($scope, $route, kbnUrl, timefilter, Private, config) {
timefilter.enabled = false;
const visTypeCategoryToHumanReadableMap = {
@ -47,13 +47,26 @@ module.controller('VisualizeWizardStep1', function ($scope, $route, kbnUrl, time
kbnUrl.removeParam(DashboardConstants.ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM);
const visTypes = Private(VisTypesRegistryProvider);
const isLabsEnabled = config.get('visualize:enableLabs');
$scope.toggleLabView = () => {
$route.current.params.lab = !$route.current.params.lab;
$route.updateParams($route.current.params);
$route.reload();
};
const categoryToVisTypesMap = {};
visTypes.forEach(visType => {
const categoryName = visType.category;
if (categoryName === CATEGORY.HIDDEN) return;
if (categoryName === CATEGORY.HIDDEN) {
return;
}
if (!isLabsEnabled && visType.stage === 'lab') {
return;
}
// Create category object if it doesn't exist yet.
if (!categoryToVisTypesMap[categoryName]) {
@ -66,7 +79,6 @@ module.controller('VisualizeWizardStep1', function ($scope, $route, kbnUrl, time
const categoryVisTypes = categoryToVisTypesMap[categoryName];
// Add the visType to the list and sort them by their title.
// categoryVisTypes.list.push(visType);
categoryVisTypes.list = _.sortBy(
categoryVisTypes.list.concat(visType),
type => type.title
@ -125,7 +137,14 @@ module.controller('VisualizeWizardStep1', function ($scope, $route, kbnUrl, time
};
$scope.getVisTypeTooltip = type => {
const prefix = type.isExperimental ? '(Experimental)' : '';
//to not clutter the tooltip, just only notify if labs or experimental.
//labs is more important in this regard.
let prefix = '';
if (type.stage === 'lab') {
prefix = '(Lab)';
} else if (type.stage === 'experimental') {
prefix = '(Experimental)';
}
return `${prefix} ${type.description}`;
};

View file

@ -122,6 +122,10 @@ export function getUiSettingDefaults() {
value: 100,
description: 'Never show more than this many bars in date histograms, scale values if needed',
},
'visualize:enableLabs': {
value: true,
description: 'Enable lab visualizations in Visualize.'
},
'visualization:tileMap:maxPrecision': {
value: 7,
description: 'The maximum geoHash precision displayed on tile maps: 7 is high, 10 is very high, ' +

View file

@ -5,6 +5,7 @@ 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 { defaultFeedbackMessage } from 'ui/vis/default_feedback_message';
// register the provider with the visTypes registry so that other know it exists
import { VisTypesRegistryProvider } from 'ui/registry/vis_types';
@ -21,7 +22,8 @@ export default function MetricsVisProvider(Private) {
description: 'Build time-series using a visual pipeline interface',
category: CATEGORY.TIME,
image,
isExperimental: true,
stage: 'experimental',
feedbackMessage: defaultFeedbackMessage,
visConfig: {
defaults: {
id: '61ca57f0-469d-11e7-af02-69e470af7417',

View file

@ -262,10 +262,16 @@ module.directive('savedObjectFinder', function ($location, $injector, kbnUrl, Pr
if (prevSearch === filter) return;
prevSearch = filter;
const isLabsEnabled = config.get('visualize:enableLabs');
self.service.find(filter)
.then(function (hits) {
// ensure that we don't display old results
// as we can't really cancel requests
hits.hits = hits.hits.filter((hit) => (isLabsEnabled || !hit.type.stage === 'lab'));
hits.total = hits.hits.length;
// ensure that we don't display old results
// as we can't really cancel requests
if (currentFilter === filter) {
self.hitCount = hits.total;
self.hits = _.sortBy(hits.hits, 'title');

View file

@ -78,6 +78,7 @@
ng-blur="finder.hitBlur($event)"
ng-click="finder.preventClick($event)">
<span aria-hidden="true" class="finder-type fa" ng-if="hit.icon" ng-class="hit.icon"></span>
<div class="kuiIcon fa-flask ng-scope" ng-if="hit.type.shouldMarkAsExperimentalInUI()"></div>
<span>{{hit.title}}</span>
<p ng-if="hit.description" ng-bind="hit.description"></p>
</a>

View file

@ -0,0 +1,2 @@
export const defaultFeedbackMessage = `Have feedback? Please create an issue in
<a href="https://github.com/elastic/kibana/issues/new" rel="noopener noreferrer" target="_blank">GitHub</a>.`;

View file

@ -3,6 +3,7 @@ import _ from 'lodash';
export function VisTypeProvider() {
class VisType {
constructor(opts) {
opts = opts || {};
@ -32,13 +33,20 @@ export function VisTypeProvider() {
showIndexSelection: true,
hierarchicalData: false // we should get rid of this i guess ?
},
isExperimental: false
stage: 'production',
feedbackMessage: ''
};
_.defaultsDeep(this, opts, _defaults);
this.requiresSearch = !(this.requestHandler === 'none');
}
shouldMarkAsExperimentalInUI() {
//we are not making a distinction in the UI if a plugin is experimental and/or labs.
//we just want to indicate it is special. the current flask icon is sufficient for that.
return this.stage === 'experimental' || this.stage === 'lab';
}
}
Object.defineProperty(VisType.prototype, 'schemas', {