mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
Add landing page with table to Visualize app. (#9605)
* Refactor Visualize Wizard to use explicit controller names, instead of defining them dynamically. * Add landing page with table to Visualize app. * Update Visualize wizard UI. * Add kuiIcon--basic and support icons in Table component. Display icons in Visualize landing page. * Refactor Visualize Wizard templates to use import syntax. * Set kuiViewContent width to 100% to avoid it shrink wrapping its content. * Move ToolBar buttons to the right side. Remove labels and add tooltips. * Remove unused Visualize load menu template. * Disable timepicker in Visualization listing. * Change Visualize route names. Add omitPages attribute to breadcrumbs directive. Make Visualize breadcrumbs into links. * Remove Open and New menu buttons from Visualize. * Add ConfirmationModal for deleting visualizations. * Implement sorting for name and type columns in Visualize listing. * Refactor Visualize routes into VisualizeConstants file. Fix functional tests. * Add pager_controls directive and pager service. Add pagination to Dashboard and Visualize landing pages. Change Dashboard listing to use hrefs for each dashboard. * Use ng-if instead of ng-hide to hide/reveal Table action buttons in Dashboard listing. * Redirect from old Visualize wizard routes to new ones. * Use ViewContent and Title components in Visualize wizard. * Fix Visualize and Dashboard listing table logic so that selection only applies to the current page of items. * Paging clears the selection. * Searching clears the selection. * Sorting clears the selection. * Use consistent "Create" terminology in both Visualize and Dashboard for creating new items. * Use NoItems and PromptForItems components in Visualize listing view. * Use $injector to inject dependencies in Visualize and Dashboard listing.
This commit is contained in:
parent
c8c03e5fec
commit
a5705f8dd0
43 changed files with 853 additions and 208 deletions
|
@ -2,5 +2,5 @@
|
|||
export const DashboardConstants = {
|
||||
ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM: 'addToDashboard',
|
||||
NEW_VISUALIZATION_ID_PARAM: 'addVisualization',
|
||||
LANDING_PAGE_URL: '/dashboard'
|
||||
LANDING_PAGE_PATH: '/dashboard'
|
||||
};
|
||||
|
|
|
@ -14,7 +14,7 @@ uiRoutes
|
|||
.defaults(/dashboard/, {
|
||||
requireDefaultIndex: true
|
||||
})
|
||||
.when(DashboardConstants.LANDING_PAGE_URL, {
|
||||
.when(DashboardConstants.LANDING_PAGE_PATH, {
|
||||
template: dashboardListingTemplate,
|
||||
controller: DashboardListingController,
|
||||
controllerAs: 'listingController'
|
||||
|
|
|
@ -31,17 +31,13 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="kuiToolBarSection">
|
||||
<!-- We need an empty section for the buttons to be positioned consistently. -->
|
||||
</div>
|
||||
|
||||
<div class="kuiToolBarSection">
|
||||
<!-- Bulk delete button -->
|
||||
<button
|
||||
class="kuiButton kuiButton--danger"
|
||||
ng-click="listingController.deleteSelectedItems()"
|
||||
aria-label="Delete selected objects"
|
||||
ng-hide="listingController.getSelectedItemsCount() === 0"
|
||||
ng-if="listingController.getSelectedItemsCount() > 0"
|
||||
tooltip="Delete selected dashboards"
|
||||
>
|
||||
<span aria-hidden="true" class="kuiButton__icon kuiIcon fa-trash"></span>
|
||||
|
@ -53,15 +49,30 @@
|
|||
href="#/dashboard/create"
|
||||
aria-label="Create new dashboard"
|
||||
data-test-subj="newDashboardLink"
|
||||
ng-hide="listingController.getSelectedItemsCount() > 0"
|
||||
ng-if="listingController.getSelectedItemsCount() === 0"
|
||||
tooltip="Create new dashboard"
|
||||
>
|
||||
<span aria-hidden="true" class="kuiButton__icon kuiIcon fa-plus"></span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="kuiToolBarSection">
|
||||
<!-- Pagination -->
|
||||
<tool-bar-pager-text
|
||||
start-item="listingController.pager.startItem"
|
||||
end-item="listingController.pager.endItem"
|
||||
total-items="listingController.pager.totalItems"
|
||||
></tool-bar-pager-text>
|
||||
<tool-bar-pager-buttons
|
||||
has-previous-page="listingController.pager.hasPreviousPage"
|
||||
has-next-page="listingController.pager.hasNextPage"
|
||||
on-page-next="listingController.onPageNext"
|
||||
on-page-previous="listingController.onPagePrevious"
|
||||
></tool-bar-pager-buttons>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- NoResults -->
|
||||
<!-- NoItems -->
|
||||
<div
|
||||
class="kuiPanel kuiPanel--centered kuiPanel--withHeader"
|
||||
ng-if="!listingController.items.length && listingController.filter"
|
||||
|
@ -78,7 +89,7 @@
|
|||
>
|
||||
<div class="kuiPromptForItems">
|
||||
<div class="kuiPromptForItems__message">
|
||||
Looks like you don’t have any dashboards. Let’s add some!
|
||||
Looks like you don’t have any dashboards. Let’s create some!
|
||||
</div>
|
||||
|
||||
<div class="kuiPromptForItems__actions">
|
||||
|
@ -87,7 +98,7 @@
|
|||
href="#/dashboard/create"
|
||||
>
|
||||
<span class="kuiButton__icon kuiIcon fa-plus"></span>
|
||||
Add a dashboard
|
||||
Create a dashboard
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -118,7 +129,7 @@
|
|||
|
||||
<tbody>
|
||||
<tr
|
||||
ng-repeat="item in listingController.items track by item.id"
|
||||
ng-repeat="item in listingController.pageOfItems track by item.id"
|
||||
class="kuiTableRow"
|
||||
>
|
||||
<td class="kuiTableRowCell kuiTableRowCell--checkBox">
|
||||
|
@ -132,7 +143,7 @@
|
|||
|
||||
<td class="kuiTableRowCell">
|
||||
<div class="kuiTableRowCell__liner">
|
||||
<a class="kuiLink" ng-click="listingController.open(item)">
|
||||
<a class="kuiLink" ng-href="{{ listingController.getUrlForItem(item) }}">
|
||||
{{ item.title }}
|
||||
</a>
|
||||
</div>
|
||||
|
@ -149,8 +160,20 @@
|
|||
{{ listingController.getSelectedItemsCount() }} selected
|
||||
</div>
|
||||
</div>
|
||||
<div class="kuiToolBarFooterSection">
|
||||
<!-- We need an empty section for the buttons to be positioned consistently. -->
|
||||
|
||||
<div class="kuiToolBarSection">
|
||||
<!-- Pagination -->
|
||||
<tool-bar-pager-text
|
||||
start-item="listingController.pager.startItem"
|
||||
end-item="listingController.pager.endItem"
|
||||
total-items="listingController.pager.totalItems"
|
||||
></tool-bar-pager-text>
|
||||
<tool-bar-pager-buttons
|
||||
has-previous-page="listingController.pager.hasPreviousPage"
|
||||
has-next-page="listingController.pager.hasNextPage"
|
||||
on-page-next="listingController.onPageNext"
|
||||
on-page-previous="listingController.onPagePrevious"
|
||||
></tool-bar-pager-buttons>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,17 +1,21 @@
|
|||
import SavedObjectRegistryProvider from 'ui/saved_objects/saved_object_registry';
|
||||
import 'ui/pager_control';
|
||||
import 'ui/pager';
|
||||
import { DashboardConstants } from '../dashboard_constants';
|
||||
import _ from 'lodash';
|
||||
|
||||
export function DashboardListingController(
|
||||
$scope,
|
||||
kbnUrl,
|
||||
Notifier,
|
||||
Private,
|
||||
timefilter,
|
||||
confirmModal
|
||||
) {
|
||||
export function DashboardListingController($injector, $scope) {
|
||||
const $filter = $injector.get('$filter');
|
||||
const confirmModal = $injector.get('confirmModal');
|
||||
const kbnUrl = $injector.get('kbnUrl');
|
||||
const Notifier = $injector.get('Notifier');
|
||||
const pagerFactory = $injector.get('pagerFactory');
|
||||
const Private = $injector.get('Private');
|
||||
const timefilter = $injector.get('timefilter');
|
||||
|
||||
timefilter.enabled = false;
|
||||
|
||||
const limitTo = $filter('limitTo');
|
||||
// TODO: Extract this into an external service.
|
||||
const services = Private(SavedObjectRegistryProvider).byLoaderPropertiesName;
|
||||
const dashboardService = services.dashboards;
|
||||
|
@ -19,17 +23,51 @@ export function DashboardListingController(
|
|||
|
||||
let selectedItems = [];
|
||||
|
||||
/**
|
||||
* Sorts hits either ascending or descending
|
||||
* @param {Array} hits Array of saved finder object hits
|
||||
* @return {Array} Array sorted either ascending or descending
|
||||
*/
|
||||
const sortItems = () => {
|
||||
this.items =
|
||||
this.isAscending
|
||||
? _.sortBy(this.items, 'title')
|
||||
: _.sortBy(this.items, 'title').reverse();
|
||||
};
|
||||
|
||||
const calculateItemsOnPage = () => {
|
||||
sortItems();
|
||||
this.pager.setTotalItems(this.items.length);
|
||||
this.pageOfItems = limitTo(this.items, this.pager.pageSize, this.pager.startIndex);
|
||||
};
|
||||
|
||||
const fetchObjects = () => {
|
||||
dashboardService.find(this.filter)
|
||||
.then(result => {
|
||||
this.items = result.hits;
|
||||
this.sortItems();
|
||||
calculateItemsOnPage();
|
||||
});
|
||||
};
|
||||
|
||||
const deselectAll = () => {
|
||||
selectedItems = [];
|
||||
};
|
||||
|
||||
const selectAll = () => {
|
||||
selectedItems = this.pageOfItems.slice(0);
|
||||
};
|
||||
|
||||
this.items = [];
|
||||
this.pageOfItems = [];
|
||||
this.filter = '';
|
||||
|
||||
this.pager = pagerFactory.create(this.items.length, 20, 1);
|
||||
|
||||
$scope.$watch(() => this.filter, () => {
|
||||
deselectAll();
|
||||
fetchObjects();
|
||||
});
|
||||
|
||||
/**
|
||||
* Boolean that keeps track of whether hits are sorted ascending (true)
|
||||
* or descending (false) by title
|
||||
|
@ -37,28 +75,17 @@ export function DashboardListingController(
|
|||
*/
|
||||
this.isAscending = true;
|
||||
|
||||
/**
|
||||
* Sorts hits either ascending or descending
|
||||
* @param {Array} hits Array of saved finder object hits
|
||||
* @return {Array} Array sorted either ascending or descending
|
||||
*/
|
||||
this.sortItems = function sortItems() {
|
||||
this.items =
|
||||
this.isAscending
|
||||
? _.sortBy(this.items, 'title')
|
||||
: _.sortBy(this.items, 'title').reverse();
|
||||
};
|
||||
|
||||
this.toggleSort = function toggleSort() {
|
||||
this.isAscending = !this.isAscending;
|
||||
this.sortItems();
|
||||
deselectAll();
|
||||
calculateItemsOnPage();
|
||||
};
|
||||
|
||||
this.toggleAll = function toggleAll() {
|
||||
if (this.areAllItemsChecked()) {
|
||||
selectedItems = [];
|
||||
deselectAll();
|
||||
} else {
|
||||
selectedItems = this.items.slice(0);
|
||||
selectAll();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -76,7 +103,7 @@ export function DashboardListingController(
|
|||
};
|
||||
|
||||
this.areAllItemsChecked = function areAllItemsChecked() {
|
||||
return this.getSelectedItemsCount() === this.items.length;
|
||||
return this.getSelectedItemsCount() === this.pageOfItems.length;
|
||||
};
|
||||
|
||||
this.getSelectedItemsCount = function getSelectedItemsCount() {
|
||||
|
@ -90,10 +117,11 @@ export function DashboardListingController(
|
|||
dashboardService.delete(selectedIds)
|
||||
.then(fetchObjects)
|
||||
.then(() => {
|
||||
selectedItems = [];
|
||||
deselectAll();
|
||||
})
|
||||
.catch(error => notify.error(error));
|
||||
};
|
||||
|
||||
confirmModal(
|
||||
'Are you sure you want to delete the selected dashboards? This action is irreversible!',
|
||||
{
|
||||
|
@ -102,11 +130,19 @@ export function DashboardListingController(
|
|||
});
|
||||
};
|
||||
|
||||
this.open = function open(item) {
|
||||
kbnUrl.change(item.url.substr(1));
|
||||
this.onPageNext = () => {
|
||||
deselectAll();
|
||||
this.pager.nextPage();
|
||||
calculateItemsOnPage();
|
||||
};
|
||||
|
||||
$scope.$watch(() => this.filter, () => {
|
||||
fetchObjects();
|
||||
});
|
||||
this.onPagePrevious = () => {
|
||||
deselectAll();
|
||||
this.pager.previousPage();
|
||||
calculateItemsOnPage();
|
||||
};
|
||||
|
||||
this.getUrlForItem = function getUrlForItem(item) {
|
||||
return `#/dashboard/${item.id}`;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -3,16 +3,14 @@
|
|||
<kbn-top-nav name="visualize" config="topNavMenu">
|
||||
<!-- Transcluded elements. -->
|
||||
<div data-transclude-slots>
|
||||
<!-- Title. -->
|
||||
<div
|
||||
<!-- Breadcrumbs. -->
|
||||
<bread-crumbs
|
||||
data-transclude-slot="topLeftCorner"
|
||||
class="kuiLocalTitle"
|
||||
>
|
||||
<span
|
||||
ng-show="savedVis.id"
|
||||
ng-bind="savedVis.lastSavedTitle"
|
||||
></span>
|
||||
</div>
|
||||
page-title="getVisualizationTitle()"
|
||||
use-links="true"
|
||||
omit-current-page="true"
|
||||
omit-pages="['edit']"
|
||||
></bread-crumbs>
|
||||
|
||||
<!-- Search. -->
|
||||
<div
|
||||
|
@ -29,11 +27,11 @@
|
|||
ng-dblclick="unlink()"
|
||||
tooltip="Double click to unlink from Saved Search"
|
||||
>
|
||||
<i aria-hidden="true" class="fa fa-chain-broken"></i>
|
||||
<span aria-hidden="true" class="fa fa-chain-broken"></span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Allow searching if there is no linked Saved Searc. -->
|
||||
<!-- Allow searching if there is no linked Saved Search. -->
|
||||
<form
|
||||
ng-if="vis.type.requiresSearch && !$state.linked"
|
||||
name="queryInput"
|
||||
|
|
|
@ -18,9 +18,10 @@ import uiRoutes from 'ui/routes';
|
|||
import uiModules from 'ui/modules';
|
||||
import editorTemplate from 'plugins/kibana/visualize/editor/editor.html';
|
||||
import { DashboardConstants } from 'plugins/kibana/dashboard/dashboard_constants';
|
||||
import { VisualizeConstants } from '../visualize_constants';
|
||||
|
||||
uiRoutes
|
||||
.when('/visualize/create', {
|
||||
.when(VisualizeConstants.CREATE_PATH, {
|
||||
template: editorTemplate,
|
||||
resolve: {
|
||||
savedVis: function (savedVisualizations, courier, $route, Private) {
|
||||
|
@ -37,7 +38,7 @@ uiRoutes
|
|||
}
|
||||
}
|
||||
})
|
||||
.when('/visualize/edit/:id', {
|
||||
.when(`${VisualizeConstants.EDIT_PATH}/:id`, {
|
||||
template: editorTemplate,
|
||||
resolve: {
|
||||
savedVis: function (savedVisualizations, courier, $route) {
|
||||
|
@ -104,20 +105,10 @@ function VisEditor($scope, $route, timefilter, AppState, $window, kbnUrl, courie
|
|||
const searchSource = savedVis.searchSource;
|
||||
|
||||
$scope.topNavMenu = [{
|
||||
key: 'new',
|
||||
description: 'New Visualization',
|
||||
run: function () { kbnUrl.change('/visualize', {}); },
|
||||
testId: 'visualizeNewButton',
|
||||
}, {
|
||||
key: 'save',
|
||||
description: 'Save Visualization',
|
||||
template: require('plugins/kibana/visualize/editor/panels/save.html'),
|
||||
testId: 'visualizeSaveButton',
|
||||
}, {
|
||||
key: 'open',
|
||||
description: 'Open Saved Visualization',
|
||||
template: require('plugins/kibana/visualize/editor/panels/load.html'),
|
||||
testId: 'visualizeOpenButton',
|
||||
}, {
|
||||
key: 'share',
|
||||
description: 'Share Visualization',
|
||||
|
@ -224,6 +215,10 @@ function VisEditor($scope, $route, timefilter, AppState, $window, kbnUrl, courie
|
|||
|
||||
$state.replace();
|
||||
|
||||
$scope.getVisualizationTitle = function getVisualizationTitle() {
|
||||
return savedVis.lastSavedTitle || `${savedVis.title} (unsaved)`;
|
||||
};
|
||||
|
||||
$scope.$watch('searchSource.get("index").timeFieldName', function (timeField) {
|
||||
timefilter.enabled = !!timeField;
|
||||
});
|
||||
|
@ -292,10 +287,6 @@ function VisEditor($scope, $route, timefilter, AppState, $window, kbnUrl, courie
|
|||
}
|
||||
};
|
||||
|
||||
$scope.startOver = function () {
|
||||
kbnUrl.change('/visualize', {});
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when the user clicks "Save" button.
|
||||
*/
|
||||
|
@ -321,7 +312,7 @@ function VisEditor($scope, $route, timefilter, AppState, $window, kbnUrl, courie
|
|||
} else if (savedVis.id === $route.current.params.id) {
|
||||
docTitle.change(savedVis.lastSavedTitle);
|
||||
} else {
|
||||
kbnUrl.change('/visualize/edit/{{id}}', { id: savedVis.id });
|
||||
kbnUrl.change(`${VisualizeConstants.EDIT_PATH}/{{id}}`, { id: savedVis.id });
|
||||
}
|
||||
}
|
||||
}, notify.fatal);
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
<div class="kuiLocalDropdownTitle">Open Visualization</div>
|
||||
<saved-object-finder type="visualizations"></saved-object-finder>
|
|
@ -18,13 +18,18 @@ import 'plugins/kibana/visualize/saved_visualizations/_saved_vis';
|
|||
import 'plugins/kibana/visualize/saved_visualizations/saved_visualizations';
|
||||
import uiRoutes from 'ui/routes';
|
||||
|
||||
import visualizeListingTemplate from './listing/visualize_listing.html';
|
||||
import { VisualizeListingController } from './listing/visualize_listing';
|
||||
import { VisualizeConstants } from './visualize_constants';
|
||||
|
||||
uiRoutes
|
||||
.defaults(/visualize/, {
|
||||
requireDefaultIndex: true
|
||||
})
|
||||
.when('/visualize', {
|
||||
redirectTo: '/visualize/step/1'
|
||||
.when(VisualizeConstants.LANDING_PAGE_PATH, {
|
||||
template: visualizeListingTemplate,
|
||||
controller: VisualizeListingController,
|
||||
controllerAs: 'listingController',
|
||||
});
|
||||
|
||||
// preloading
|
||||
|
|
|
@ -0,0 +1,197 @@
|
|||
<!-- Local nav. -->
|
||||
<kbn-top-nav name="visualize">
|
||||
<!-- Transcluded elements. -->
|
||||
<div data-transclude-slots>
|
||||
<!-- Title. -->
|
||||
<div
|
||||
data-transclude-slot="topLeftCorner"
|
||||
class="kuiLocalTitle"
|
||||
>
|
||||
Visualize
|
||||
</div>
|
||||
</div>
|
||||
</kbn-top-nav>
|
||||
|
||||
<div class="kuiViewContent kuiViewContent--constrainedWidth">
|
||||
<!-- ControlledTable -->
|
||||
<div class="kuiViewContentItem kuiControlledTable kuiVerticalRhythm">
|
||||
<!-- ToolBar -->
|
||||
<div class="kuiToolBar">
|
||||
<div class="kuiToolBarSearch">
|
||||
<div class="kuiToolBarSearchBox">
|
||||
<div class="kuiToolBarSearchBox__icon kuiIcon fa-search"></div>
|
||||
<input
|
||||
class="kuiToolBarSearchBox__input"
|
||||
type="text"
|
||||
placeholder="Search..."
|
||||
aria-label="Filter"
|
||||
ng-model="listingController.filter"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="kuiToolBarSection">
|
||||
<!-- Bulk delete button -->
|
||||
<button
|
||||
class="kuiButton kuiButton--danger"
|
||||
aria-label="Delete selected objects"
|
||||
ng-if="listingController.getSelectedItemsCount() > 0"
|
||||
ng-click="listingController.deleteSelectedItems()"
|
||||
tooltip="Delete selected visualizations"
|
||||
>
|
||||
<span aria-hidden="true" class="kuiButton__icon kuiIcon fa-trash"></span>
|
||||
</button>
|
||||
|
||||
<!-- Create visualization button -->
|
||||
<a
|
||||
class="kuiButton kuiButton--primary"
|
||||
href="#/visualize/new"
|
||||
aria-label="Create new visualization"
|
||||
ng-if="listingController.getSelectedItemsCount() === 0"
|
||||
tooltip="Create new visualization"
|
||||
>
|
||||
<span aria-hidden="true" class="kuiButton__icon kuiIcon fa-plus"></span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="kuiToolBarSection">
|
||||
<!-- Pagination -->
|
||||
<tool-bar-pager-text
|
||||
start-item="listingController.pager.startItem"
|
||||
end-item="listingController.pager.endItem"
|
||||
total-items="listingController.pager.totalItems"
|
||||
></tool-bar-pager-text>
|
||||
<tool-bar-pager-buttons
|
||||
has-previous-page="listingController.pager.hasPreviousPage"
|
||||
has-next-page="listingController.pager.hasNextPage"
|
||||
on-page-next="listingController.onPageNext"
|
||||
on-page-previous="listingController.onPagePrevious"
|
||||
></tool-bar-pager-buttons>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- NoItems -->
|
||||
<div
|
||||
class="kuiPanel kuiPanel--centered kuiPanel--withHeader"
|
||||
ng-if="!listingController.items.length && listingController.filter"
|
||||
>
|
||||
<div class="kuiNoItems">
|
||||
No visualizations matched your search.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PromptForItems -->
|
||||
<div
|
||||
class="kuiPanel kuiPanel--centered kuiPanel--withHeader"
|
||||
ng-if="!listingController.items.length && !listingController.filter"
|
||||
>
|
||||
<div class="kuiPromptForItems">
|
||||
<div class="kuiPromptForItems__message">
|
||||
Looks like you don’t have any visualizations. Let’s create some!
|
||||
</div>
|
||||
|
||||
<div class="kuiPromptForItems__actions">
|
||||
<a
|
||||
class="kuiButton kuiButton--primary kuiButton--iconText"
|
||||
href="#/visualize/new"
|
||||
>
|
||||
<span class="kuiButton__icon kuiIcon fa-plus"></span>
|
||||
Create a visualization
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Table -->
|
||||
<table class="kuiTable" ng-if="listingController.items.length">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="kuiTableHeaderCell kuiTableHeaderCell--checkBox">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="kuiCheckBox"
|
||||
ng-checked="listingController.areAllItemsChecked()"
|
||||
ng-click="listingController.toggleAll()"
|
||||
>
|
||||
</th>
|
||||
|
||||
<th class="kuiTableHeaderCell" ng-click="listingController.sortOn('title')">
|
||||
Name
|
||||
<span
|
||||
class="kuiIcon"
|
||||
ng-show="listingController.getSortProperty().name === 'title'"
|
||||
ng-class="listingController.isAscending() ? 'fa-caret-up' : 'fa-caret-down'"
|
||||
></span>
|
||||
</th>
|
||||
|
||||
<th class="kuiTableHeaderCell" ng-click="listingController.sortOn('type')">
|
||||
Type
|
||||
<span
|
||||
class="kuiIcon"
|
||||
ng-show="listingController.getSortProperty().name === 'type'"
|
||||
ng-class="listingController.isAscending() ? 'fa-caret-up' : 'fa-caret-down'"
|
||||
></span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr
|
||||
ng-repeat="item in listingController.pageOfItems track by item.id"
|
||||
class="kuiTableRow"
|
||||
>
|
||||
<td class="kuiTableRowCell kuiTableRowCell--checkBox">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="kuiCheckBox"
|
||||
ng-click="listingController.toggleItem(item)"
|
||||
ng-checked="listingController.isItemChecked(item)"
|
||||
>
|
||||
</td>
|
||||
|
||||
<td class="kuiTableRowCell">
|
||||
<div class="kuiTableRowCell__liner">
|
||||
<a class="kuiLink" ng-href="{{ listingController.getUrlForItem(item) }}">
|
||||
{{ item.title }}
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td class="kuiTableRowCell">
|
||||
<div class="kuiTableRowCell__liner">
|
||||
<span class="kuiStatusText">
|
||||
<span class="kuiStatusText__icon kuiIcon {{ item.icon }}"></span>
|
||||
{{ item.type.title }}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- ToolBarFooter -->
|
||||
<div class="kuiToolBarFooter">
|
||||
<div class="kuiToolBarFooterSection">
|
||||
<div class="kuiToolBarText" ng-hide="listingController.getSelectedItemsCount() === 0">
|
||||
{{ listingController.getSelectedItemsCount() }} selected
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="kuiToolBarFooterSection">
|
||||
<!-- Pagination -->
|
||||
<tool-bar-pager-text
|
||||
start-item="listingController.pager.startItem"
|
||||
end-item="listingController.pager.endItem"
|
||||
total-items="listingController.pager.totalItems"
|
||||
></tool-bar-pager-text>
|
||||
<tool-bar-pager-buttons
|
||||
has-previous-page="listingController.pager.hasPreviousPage"
|
||||
has-next-page="listingController.pager.hasNextPage"
|
||||
on-page-next="listingController.onPageNext"
|
||||
on-page-previous="listingController.onPagePrevious"
|
||||
></tool-bar-pager-buttons>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,178 @@
|
|||
import SavedObjectRegistryProvider from 'ui/saved_objects/saved_object_registry';
|
||||
import 'ui/pager_control';
|
||||
import 'ui/pager';
|
||||
import _ from 'lodash';
|
||||
|
||||
export function VisualizeListingController($injector, $scope) {
|
||||
const $filter = $injector.get('$filter');
|
||||
const confirmModal = $injector.get('confirmModal');
|
||||
const kbnUrl = $injector.get('kbnUrl');
|
||||
const Notifier = $injector.get('Notifier');
|
||||
const pagerFactory = $injector.get('pagerFactory');
|
||||
const Private = $injector.get('Private');
|
||||
const timefilter = $injector.get('timefilter');
|
||||
|
||||
timefilter.enabled = false;
|
||||
|
||||
const limitTo = $filter('limitTo');
|
||||
// TODO: Extract this into an external service.
|
||||
const services = Private(SavedObjectRegistryProvider).byLoaderPropertiesName;
|
||||
const visualizationService = services.visualizations;
|
||||
const notify = new Notifier({ location: 'Visualize' });
|
||||
|
||||
let selectedItems = [];
|
||||
|
||||
/**
|
||||
* Sorts hits either ascending or descending
|
||||
* @param {Array} hits Array of saved finder object hits
|
||||
* @return {Array} Array sorted either ascending or descending
|
||||
*/
|
||||
const sortItems = () => {
|
||||
const sortProperty = this.getSortProperty();
|
||||
|
||||
this.items =
|
||||
sortProperty.isAscending
|
||||
? _.sortBy(this.items, sortProperty.getValue)
|
||||
: _.sortBy(this.items, sortProperty.getValue).reverse();
|
||||
};
|
||||
|
||||
const calculateItemsOnPage = () => {
|
||||
sortItems();
|
||||
this.pager.setTotalItems(this.items.length);
|
||||
this.pageOfItems = limitTo(this.items, this.pager.pageSize, this.pager.startIndex);
|
||||
};
|
||||
|
||||
const fetchObjects = () => {
|
||||
visualizationService.find(this.filter)
|
||||
.then(result => {
|
||||
this.items = result.hits;
|
||||
calculateItemsOnPage();
|
||||
});
|
||||
};
|
||||
|
||||
const deselectAll = () => {
|
||||
selectedItems = [];
|
||||
};
|
||||
|
||||
const selectAll = () => {
|
||||
selectedItems = this.pageOfItems.slice(0);
|
||||
};
|
||||
|
||||
this.items = [];
|
||||
this.pageOfItems = [];
|
||||
this.filter = '';
|
||||
|
||||
this.pager = pagerFactory.create(this.items.length, 20, 1);
|
||||
|
||||
$scope.$watch(() => this.filter, () => {
|
||||
deselectAll();
|
||||
fetchObjects();
|
||||
});
|
||||
|
||||
/**
|
||||
* Remember sort direction per property.
|
||||
*/
|
||||
this.sortProperties = [{
|
||||
name: 'title',
|
||||
getValue: item => item.title,
|
||||
isSelected: true,
|
||||
isAscending: true,
|
||||
}, {
|
||||
name: 'type',
|
||||
getValue: item => item.type.title,
|
||||
isSelected: false,
|
||||
isAscending: true,
|
||||
}];
|
||||
|
||||
this.getSortProperty = function getSortProperty() {
|
||||
return this.sortProperties.find(property => property.isSelected);
|
||||
};
|
||||
|
||||
this.getSortPropertyByName = function getSortPropertyByName(name) {
|
||||
return this.sortProperties.find(property => property.name === name);
|
||||
};
|
||||
|
||||
this.isAscending = function isAscending() {
|
||||
const sortProperty = this.getSortProperty();
|
||||
return sortProperty.isAscending;
|
||||
};
|
||||
|
||||
this.sortOn = function sortOn(propertyName) {
|
||||
const sortProperty = this.getSortProperty();
|
||||
|
||||
if (sortProperty.name === propertyName) {
|
||||
sortProperty.isAscending = !sortProperty.isAscending;
|
||||
} else {
|
||||
sortProperty.isSelected = false;
|
||||
this.getSortPropertyByName(propertyName).isSelected = true;
|
||||
}
|
||||
|
||||
deselectAll();
|
||||
calculateItemsOnPage();
|
||||
};
|
||||
|
||||
this.toggleAll = function toggleAll() {
|
||||
if (this.areAllItemsChecked()) {
|
||||
deselectAll();
|
||||
} else {
|
||||
selectAll();
|
||||
}
|
||||
};
|
||||
|
||||
this.toggleItem = function toggleItem(item) {
|
||||
if (this.isItemChecked(item)) {
|
||||
const index = selectedItems.indexOf(item);
|
||||
selectedItems.splice(index, 1);
|
||||
} else {
|
||||
selectedItems.push(item);
|
||||
}
|
||||
};
|
||||
|
||||
this.isItemChecked = function isItemChecked(item) {
|
||||
return selectedItems.includes(item);
|
||||
};
|
||||
|
||||
this.areAllItemsChecked = function areAllItemsChecked() {
|
||||
return this.getSelectedItemsCount() === this.pageOfItems.length;
|
||||
};
|
||||
|
||||
this.getSelectedItemsCount = function getSelectedItemsCount() {
|
||||
return selectedItems.length;
|
||||
};
|
||||
|
||||
this.deleteSelectedItems = function deleteSelectedItems() {
|
||||
const doDelete = () => {
|
||||
const selectedIds = selectedItems.map(item => item.id);
|
||||
|
||||
visualizationService.delete(selectedIds)
|
||||
.then(fetchObjects)
|
||||
.then(() => {
|
||||
deselectAll();
|
||||
})
|
||||
.catch(error => notify.error(error));
|
||||
};
|
||||
|
||||
confirmModal(
|
||||
'Are you sure you want to delete the selected visualizations? This action is irreversible!',
|
||||
{
|
||||
confirmButtonText: 'Delete',
|
||||
onConfirm: doDelete
|
||||
});
|
||||
};
|
||||
|
||||
this.onPageNext = () => {
|
||||
deselectAll();
|
||||
this.pager.nextPage();
|
||||
calculateItemsOnPage();
|
||||
};
|
||||
|
||||
this.onPagePrevious = () => {
|
||||
deselectAll();
|
||||
this.pager.previousPage();
|
||||
calculateItemsOnPage();
|
||||
};
|
||||
|
||||
this.getUrlForItem = function getUrlForItem(item) {
|
||||
return `#/visualize/edit/${item.id}`;
|
||||
};
|
||||
}
|
|
@ -2,59 +2,50 @@
|
|||
@import (reference) "~ui/styles/bootstrap/list-group";
|
||||
@import (reference) "~ui/styles/list-group-menu";
|
||||
|
||||
/**
|
||||
* 1. Push down the breadcrumbs a bit.
|
||||
*/
|
||||
.vis-wizard {
|
||||
margin: 0;
|
||||
padding: 12px 0 0; // 1
|
||||
.wizard-sub-title {
|
||||
margin-top: 0px;
|
||||
margin-bottom: 8px;
|
||||
padding: 0px 5px;
|
||||
}
|
||||
|
||||
.wizard-sub-title {
|
||||
margin-top: 0px;
|
||||
margin-bottom: 8px;
|
||||
padding: 0px 5px;
|
||||
.wizard-type {
|
||||
flex: 1;
|
||||
|
||||
// TODO: When we migrate off Bootstrap, we can eliminate these "mixins".
|
||||
.list-group-item();
|
||||
.list-group-menu .list-group-menu-item();
|
||||
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
background-color: @kibanaGray6;
|
||||
}
|
||||
|
||||
.wizard-type-heading {
|
||||
flex: 0 0 200px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.wizard-type {
|
||||
flex: 1;
|
||||
.wizard-type-heading-icon {
|
||||
flex: 0 0 auto;
|
||||
margin-right: @padding-base-horizontal;
|
||||
font-size: 1.5em;
|
||||
text-align: center;
|
||||
color: @saved-object-finder-icon-color;
|
||||
}
|
||||
|
||||
// TODO: When we migrate off Bootstrap, we can eliminate these "mixins".
|
||||
.list-group-item();
|
||||
.list-group-menu .list-group-menu-item();
|
||||
.wizard-type-heading-text {
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
background-color: @kibanaGray6;
|
||||
.wizard-type-description {
|
||||
flex: 1 1 auto;
|
||||
color: @wizard-vis-type-description-color;
|
||||
}
|
||||
|
||||
.wizard-type-heading {
|
||||
flex: 0 0 200px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.wizard-type-heading-icon {
|
||||
flex: 0 0 auto;
|
||||
margin-right: @padding-base-horizontal;
|
||||
font-size: 1.5em;
|
||||
text-align: center;
|
||||
color: @saved-object-finder-icon-color;
|
||||
}
|
||||
|
||||
.wizard-type-heading-text {
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
|
||||
.wizard-type-description {
|
||||
flex: 1 1 auto;
|
||||
color: @wizard-vis-type-description-color;
|
||||
}
|
||||
|
||||
@media (min-width: @screen-lg) {
|
||||
.wizard {
|
||||
padding: 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
export const VisualizeConstants = {
|
||||
LANDING_PAGE_PATH: '/visualize',
|
||||
WIZARD_STEP_1_PAGE_PATH: '/visualize/new',
|
||||
WIZARD_STEP_2_PAGE_PATH: '/visualize/new/configure',
|
||||
CREATE_PATH: '/visualize/create',
|
||||
EDIT_PATH: '/visualize/edit',
|
||||
};
|
|
@ -1,33 +1,47 @@
|
|||
<div class="visualizeWizardBreadcrumbs">
|
||||
<bread-crumbs></bread-crumbs>
|
||||
</div>
|
||||
<div class="wizard">
|
||||
<div class="wizard-column">
|
||||
<h3 class="wizard-sub-title">Create New Visualization</h3>
|
||||
<div class="wizard-row">
|
||||
<!-- Local nav. -->
|
||||
<kbn-top-nav name="visualize">
|
||||
<!-- Transcluded elements. -->
|
||||
<div data-transclude-slots>
|
||||
<!-- Breadcrumbs. -->
|
||||
<bread-crumbs
|
||||
data-transclude-slot="topLeftCorner"
|
||||
use-links="true"
|
||||
omit-current-page="true"
|
||||
page-title="'New'"
|
||||
></bread-crumbs>
|
||||
</div>
|
||||
</kbn-top-nav>
|
||||
|
||||
<div class="kuiViewContent kuiViewContent--constrainedWidth">
|
||||
<!-- Header -->
|
||||
<div class="kuiViewContentItem">
|
||||
<h1 class="kuiTitle kuiVerticalRhythm">
|
||||
Select visualization type
|
||||
</h1>
|
||||
|
||||
<div class="kuiVerticalRhythm">
|
||||
<a
|
||||
class="wizard-type"
|
||||
ng-repeat="type in visTypes.inTitleOrder"
|
||||
ng-href="{{ visTypeUrl(type) }}"
|
||||
>
|
||||
<div class="wizard-type-heading">
|
||||
<i
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="wizard-type-heading-icon fa fa-fw"
|
||||
ng-class="type.icon"
|
||||
></i>
|
||||
<h4 class="wizard-type-heading-text">{{type.title}}</h4>
|
||||
></span>
|
||||
|
||||
<h4
|
||||
data-test-subj="visualizeWizardChartTypeTitle"
|
||||
class="wizard-type-heading-text"
|
||||
>
|
||||
{{type.title}}
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
<p class="wizard-type-description">{{type.description}}</p>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wizard-column">
|
||||
<h3 class="wizard-sub-title">Or, Open a Saved Visualization</h3>
|
||||
<saved-object-finder
|
||||
title="Saved Visualizations"
|
||||
type="visualizations"
|
||||
class="wizard-row"
|
||||
></saved-object-finder>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,24 +1,44 @@
|
|||
<div class="visualizeWizardBreadcrumbs">
|
||||
<bread-crumbs></bread-crumbs>
|
||||
</div>
|
||||
<div class="wizard">
|
||||
<div class="wizard-column wizard-column--small">
|
||||
<h3 class="wizard-sub-title">From a New Search, Select Index</h3>
|
||||
<paginated-selectable-list
|
||||
per-page="20"
|
||||
list="indexPattern.list"
|
||||
user-make-url="makeUrl"
|
||||
class="wizard-row"
|
||||
></paginated-selectable-list>
|
||||
<!-- Local nav. -->
|
||||
<kbn-top-nav name="visualize">
|
||||
<!-- Transcluded elements. -->
|
||||
<div data-transclude-slots>
|
||||
<!-- Breadcrumbs. -->
|
||||
<bread-crumbs
|
||||
data-transclude-slot="topLeftCorner"
|
||||
use-links="true"
|
||||
omit-current-page="true"
|
||||
page-title="'Choose search source'"
|
||||
></bread-crumbs>
|
||||
</div>
|
||||
<div class="wizard-column wizard-column--large">
|
||||
<h3 class="wizard-sub-title">Or, From a Saved Search</h3>
|
||||
<!-- Saved searches -->
|
||||
<saved-object-finder
|
||||
</kbn-top-nav>
|
||||
|
||||
<div class="kuiViewContent kuiViewContent--constrainedWidth">
|
||||
<div class="wizard kuiViewContentItem">
|
||||
<div class="wizard-column wizard-column--small">
|
||||
<h1 class="kuiTitle kuiVerticalRhythm">
|
||||
From a New Search, Select Index
|
||||
</h1>
|
||||
|
||||
<paginated-selectable-list
|
||||
per-page="20"
|
||||
list="indexPattern.list"
|
||||
user-make-url="makeUrl"
|
||||
class="wizard-row kuiVerticalRhythm"
|
||||
></paginated-selectable-list>
|
||||
</div>
|
||||
|
||||
<div class="wizard-column wizard-column--large">
|
||||
<h2 class="kuiTitle kuiVerticalRhythm">
|
||||
Or, From a Saved Search
|
||||
</h2>
|
||||
|
||||
<!-- Saved searches -->
|
||||
<saved-object-finder
|
||||
title="Saved Searches"
|
||||
type="searches"
|
||||
class="wizard-row"
|
||||
class="wizard-row kuiVerticalRhythm"
|
||||
make-url="step2WithSearchUrl"
|
||||
></saved-object-finder>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,25 +1,29 @@
|
|||
|
||||
import 'plugins/kibana/visualize/saved_visualizations/saved_visualizations';
|
||||
import 'ui/directives/saved_object_finder';
|
||||
import 'ui/directives/paginated_selectable_list';
|
||||
import 'plugins/kibana/discover/saved_searches/saved_searches';
|
||||
import { DashboardConstants } from 'plugins/kibana/dashboard/dashboard_constants';
|
||||
import { VisualizeConstants } from '../visualize_constants';
|
||||
import routes from 'ui/routes';
|
||||
import RegistryVisTypesProvider from 'ui/registry/vis_types';
|
||||
import uiModules from 'ui/modules';
|
||||
import './wizard.less';
|
||||
|
||||
const templateStep = function (num, txt) {
|
||||
return '<div ng-controller="VisualizeWizardStep' + num + '" class="container-fluid vis-wizard">' + txt + '</div>';
|
||||
};
|
||||
import visualizeWizardStep1Template from './step_1.html';
|
||||
import visualizeWizardStep2Template from './step_2.html';
|
||||
|
||||
const module = uiModules.get('app/visualize', ['kibana/courier']);
|
||||
|
||||
/********
|
||||
/** Wizard Step 1
|
||||
/********/
|
||||
|
||||
// Redirect old route to new route.
|
||||
routes.when('/visualize/step/1', {
|
||||
template: templateStep(1, require('plugins/kibana/visualize/wizard/step_1.html'))
|
||||
redirectTo: VisualizeConstants.WIZARD_STEP_1_PAGE_PATH,
|
||||
});
|
||||
|
||||
routes.when(VisualizeConstants.WIZARD_STEP_1_PAGE_PATH, {
|
||||
template: visualizeWizardStep1Template,
|
||||
controller: 'VisualizeWizardStep1',
|
||||
});
|
||||
|
||||
module.controller('VisualizeWizardStep1', function ($scope, $route, kbnUrl, timefilter, Private) {
|
||||
|
@ -29,9 +33,15 @@ module.controller('VisualizeWizardStep1', function ($scope, $route, kbnUrl, time
|
|||
kbnUrl.removeParam(DashboardConstants.ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM);
|
||||
|
||||
$scope.visTypes = Private(RegistryVisTypesProvider);
|
||||
|
||||
$scope.visTypeUrl = function (visType) {
|
||||
const baseUrl = visType.requiresSearch ? '#/visualize/step/2?' : '#/visualize/create?';
|
||||
const baseUrl =
|
||||
visType.requiresSearch
|
||||
? `#${VisualizeConstants.WIZARD_STEP_2_PAGE_PATH}?`
|
||||
: `#${VisualizeConstants.CREATE_PATH}?`;
|
||||
|
||||
const params = [`type=${encodeURIComponent(visType.name)}`];
|
||||
|
||||
if (addToDashMode) {
|
||||
params.push(DashboardConstants.ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM);
|
||||
}
|
||||
|
@ -43,8 +53,17 @@ module.controller('VisualizeWizardStep1', function ($scope, $route, kbnUrl, time
|
|||
/********
|
||||
/** Wizard Step 2
|
||||
/********/
|
||||
|
||||
// Redirect old route to new route.
|
||||
// NOTE: Accessing this route directly means the user has entered into the wizard UX without
|
||||
// selecting a Visualization type in step 1. So we want to redirect them to step 1, not step 2.
|
||||
routes.when('/visualize/step/2', {
|
||||
template: templateStep(2, require('plugins/kibana/visualize/wizard/step_2.html')),
|
||||
redirectTo: VisualizeConstants.WIZARD_STEP_1_PAGE_PATH,
|
||||
});
|
||||
|
||||
routes.when(VisualizeConstants.WIZARD_STEP_2_PAGE_PATH, {
|
||||
template: visualizeWizardStep2Template,
|
||||
controller: 'VisualizeWizardStep2',
|
||||
resolve: {
|
||||
indexPatternIds: function (courier) {
|
||||
return courier.indexPatterns.getIds();
|
||||
|
@ -60,10 +79,17 @@ module.controller('VisualizeWizardStep2', function ($route, $scope, timefilter,
|
|||
$scope.step2WithSearchUrl = function (hit) {
|
||||
if (addToDashMode) {
|
||||
return kbnUrl.eval(
|
||||
`#/visualize/create?&type={{type}}&savedSearchId={{id}}&${DashboardConstants.ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM}`,
|
||||
{ type: type, id: hit.id });
|
||||
`#${VisualizeConstants.CREATE_PATH}` +
|
||||
`?type={{type}}&savedSearchId={{id}}` +
|
||||
`&${DashboardConstants.ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM}`,
|
||||
{ type: type, id: hit.id }
|
||||
);
|
||||
}
|
||||
return kbnUrl.eval('#/visualize/create?&type={{type}}&savedSearchId={{id}}', { type: type, id: hit.id });
|
||||
|
||||
return kbnUrl.eval(
|
||||
`#${VisualizeConstants.CREATE_PATH}?type={{type}}&savedSearchId={{id}}`,
|
||||
{ type: type, id: hit.id }
|
||||
);
|
||||
};
|
||||
|
||||
timefilter.enabled = false;
|
||||
|
@ -77,8 +103,11 @@ module.controller('VisualizeWizardStep2', function ($route, $scope, timefilter,
|
|||
if (!pattern) return;
|
||||
|
||||
if (addToDashMode) {
|
||||
return `#/visualize/create?${DashboardConstants.ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM}&type=${type}&indexPattern=${pattern}`;
|
||||
return `#${VisualizeConstants.CREATE_PATH}` +
|
||||
`?${DashboardConstants.ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM}` +
|
||||
`&type=${type}&indexPattern=${pattern}`;
|
||||
}
|
||||
return `#/visualize/create?type=${type}&indexPattern=${pattern}`;
|
||||
|
||||
return `#${VisualizeConstants.CREATE_PATH}?type=${type}&indexPattern=${pattern}`;
|
||||
};
|
||||
});
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
.visualizeWizardBreadcrumbs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 32px;
|
||||
}
|
|
@ -11,6 +11,11 @@ module.directive('breadCrumbs', function ($location) {
|
|||
replace: true,
|
||||
scope: {
|
||||
omitCurrentPage: '=',
|
||||
/**
|
||||
* Pages to omit from the breadcrumbs. Should be lower-case.
|
||||
* @type {Array}
|
||||
*/
|
||||
omitPages: '=',
|
||||
/**
|
||||
* Optional title to append at the end of the breadcrumbs. Note that this can't just be
|
||||
* 'title', because that will be interpreted by browsers as an actual 'title' HTML attribute.
|
||||
|
@ -32,6 +37,12 @@ module.directive('breadCrumbs', function ($location) {
|
|||
$scope.breadcrumbs.pop();
|
||||
}
|
||||
|
||||
if ($scope.omitPages) {
|
||||
$scope.breadcrumbs = $scope.breadcrumbs.filter(breadcrumb =>
|
||||
!$scope.omitPages.includes(breadcrumb.toLowerCase())
|
||||
);
|
||||
}
|
||||
|
||||
if ($scope.useLinks) {
|
||||
const url = '#' + $location.path();
|
||||
$scope.breadCrumbUrls = getBreadCrumbUrls($scope.breadcrumbs, url);
|
||||
|
|
|
@ -100,8 +100,8 @@ module.directive('kbnTopNav', function (Private) {
|
|||
});
|
||||
|
||||
const extensions = getNavbarExtensions($attrs.name);
|
||||
|
||||
let controls = _.get($scope, $attrs.config, []);
|
||||
|
||||
if (controls instanceof KbnTopNavController) {
|
||||
controls.addItems(extensions);
|
||||
} else {
|
||||
|
|
1
src/ui/public/pager/index.js
Normal file
1
src/ui/public/pager/index.js
Normal file
|
@ -0,0 +1 @@
|
|||
import './pager_factory';
|
58
src/ui/public/pager/pager.js
Normal file
58
src/ui/public/pager/pager.js
Normal file
|
@ -0,0 +1,58 @@
|
|||
function clamp(val, min, max) {
|
||||
return Math.min(Math.max(min, val), max);
|
||||
}
|
||||
|
||||
export class Pager {
|
||||
constructor(totalItems, pageSize, startingPage) {
|
||||
this.currentPage = startingPage;
|
||||
this.totalItems = totalItems;
|
||||
this.pageSize = pageSize;
|
||||
this.startIndex = 0;
|
||||
this.updateMeta();
|
||||
}
|
||||
|
||||
get pageCount() {
|
||||
return Math.ceil(this.totalItems / this.pageSize);
|
||||
}
|
||||
|
||||
get hasNextPage() {
|
||||
return this.currentPage < this.totalPages;
|
||||
}
|
||||
|
||||
get hasPreviousPage() {
|
||||
return this.currentPage > 1;
|
||||
}
|
||||
|
||||
nextPage() {
|
||||
this.currentPage += 1;
|
||||
this.updateMeta();
|
||||
}
|
||||
|
||||
previousPage() {
|
||||
this.currentPage -= 1;
|
||||
this.updateMeta();
|
||||
}
|
||||
|
||||
setTotalItems(count) {
|
||||
this.totalItems = count;
|
||||
this.updateMeta();
|
||||
}
|
||||
|
||||
setPageSize(count) {
|
||||
this.pageSize = count;
|
||||
this.updateMeta();
|
||||
}
|
||||
|
||||
updateMeta() {
|
||||
this.totalPages = Math.ceil(this.totalItems / this.pageSize);
|
||||
this.currentPage = clamp(this.currentPage, 1, this.totalPages);
|
||||
|
||||
this.startItem = ((this.currentPage - 1) * this.pageSize) + 1;
|
||||
this.startItem = clamp(this.startItem, 0, this.totalItems);
|
||||
|
||||
this.endItem = (this.startItem - 1) + this.pageSize;
|
||||
this.endItem = clamp(this.endItem, 0, this.totalItems);
|
||||
|
||||
this.startIndex = this.startItem - 1;
|
||||
}
|
||||
}
|
12
src/ui/public/pager/pager_factory.js
Normal file
12
src/ui/public/pager/pager_factory.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
import uiModules from 'ui/modules';
|
||||
import { Pager } from './pager';
|
||||
|
||||
const app = uiModules.get('kibana');
|
||||
|
||||
app.factory('pagerFactory', () => {
|
||||
return {
|
||||
create(...args) {
|
||||
return new Pager(...args);
|
||||
}
|
||||
};
|
||||
});
|
|
@ -0,0 +1,16 @@
|
|||
<div class="kuiButtonGroup">
|
||||
<button
|
||||
class="kuiButton kuiButton--basic kuiButton--icon"
|
||||
ng-click="toolBarPagerButtons.previousPage()"
|
||||
ng-disabled="!toolBarPagerButtons.hasPreviousPage"
|
||||
>
|
||||
<span class="kuiButton__icon kuiIcon fa-chevron-left"></span>
|
||||
</button>
|
||||
<button
|
||||
class="kuiButton kuiButton--basic kuiButton--icon"
|
||||
ng-click="toolBarPagerButtons.nextPage()"
|
||||
ng-disabled="!toolBarPagerButtons.hasNextPage"
|
||||
>
|
||||
<span class="kuiButton__icon kuiIcon fa-chevron-right"></span>
|
||||
</button>
|
||||
</div>
|
|
@ -0,0 +1,29 @@
|
|||
import uiModules from 'ui/modules';
|
||||
import template from './tool_bar_pager_buttons.html';
|
||||
|
||||
const app = uiModules.get('kibana');
|
||||
|
||||
app.directive('toolBarPagerButtons', function () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
template: template,
|
||||
scope: {
|
||||
hasNextPage: '=',
|
||||
hasPreviousPage: '=',
|
||||
onPageNext: '=',
|
||||
onPagePrevious: '=',
|
||||
},
|
||||
controllerAs: 'toolBarPagerButtons',
|
||||
bindToController: true,
|
||||
controller: class ToolBarPagerButtonsController {
|
||||
nextPage = () => {
|
||||
this.onPageNext();
|
||||
};
|
||||
|
||||
previousPage = () => {
|
||||
this.onPagePrevious();
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
|
@ -0,0 +1,3 @@
|
|||
<div class="kuiToolBarText">
|
||||
{{ toolBarPagerText.startItem | number }}–{{ toolBarPagerText.endItem | number }} of {{ toolBarPagerText.totalItems | number }}
|
||||
</div>
|
|
@ -0,0 +1,21 @@
|
|||
import uiModules from 'ui/modules';
|
||||
import template from './tool_bar_pager_text.html';
|
||||
|
||||
const app = uiModules.get('kibana');
|
||||
|
||||
app.directive('toolBarPagerText', function () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
template: template,
|
||||
scope: {
|
||||
startItem: '=',
|
||||
endItem: '=',
|
||||
totalItems: '=',
|
||||
},
|
||||
controllerAs: 'toolBarPagerText',
|
||||
bindToController: true,
|
||||
controller: class ToolBarPagerTextController {
|
||||
}
|
||||
};
|
||||
});
|
2
src/ui/public/pager_control/index.js
Normal file
2
src/ui/public/pager_control/index.js
Normal file
|
@ -0,0 +1,2 @@
|
|||
import './components/tool_bar_pager_text/tool_bar_pager_text';
|
||||
import './components/tool_bar_pager_buttons/tool_bar_pager_buttons';
|
|
@ -621,6 +621,7 @@ fieldset {
|
|||
.kuiViewContent {
|
||||
padding-top: 20px;
|
||||
padding-bottom: 20px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.kuiViewContent--constrainedWidth {
|
||||
|
|
|
@ -14,7 +14,7 @@ bdd.describe('visualize app', function describeIndexTests() {
|
|||
const toTime = '2015-09-23 18:31:44.000';
|
||||
|
||||
PageObjects.common.debug('navigateToApp visualize');
|
||||
return PageObjects.common.navigateToApp('visualize')
|
||||
return PageObjects.common.navigateToUrl('visualize', 'new')
|
||||
.then(function () {
|
||||
PageObjects.common.debug('clickAreaChart');
|
||||
return PageObjects.visualize.clickAreaChart();
|
||||
|
|
|
@ -10,7 +10,7 @@ bdd.describe('visualize app', function describeIndexTests() {
|
|||
|
||||
bdd.before(function () {
|
||||
PageObjects.common.debug('navigateToApp visualize');
|
||||
return PageObjects.common.navigateToApp('visualize');
|
||||
return PageObjects.common.navigateToUrl('visualize', 'new');
|
||||
});
|
||||
|
||||
bdd.describe('chart types', function indexPatternCreation() {
|
||||
|
|
|
@ -14,7 +14,7 @@ bdd.describe('visualize app', function describeIndexTests() {
|
|||
|
||||
bdd.before(function () {
|
||||
PageObjects.common.debug('navigateToApp visualize');
|
||||
return PageObjects.common.navigateToApp('visualize')
|
||||
return PageObjects.common.navigateToUrl('visualize', 'new')
|
||||
.then(function () {
|
||||
PageObjects.common.debug('clickDataTable');
|
||||
return PageObjects.visualize.clickDataTable();
|
||||
|
|
|
@ -14,7 +14,7 @@ bdd.describe('visualize app', function describeIndexTests() {
|
|||
|
||||
bdd.before(function () {
|
||||
PageObjects.common.debug('navigateToApp visualize');
|
||||
return PageObjects.common.navigateToApp('visualize')
|
||||
return PageObjects.common.navigateToUrl('visualize', 'new')
|
||||
.then(function () {
|
||||
PageObjects.common.debug('clickHeatmapChart');
|
||||
return PageObjects.visualize.clickHeatmapChart();
|
||||
|
|
|
@ -14,7 +14,7 @@ bdd.describe('visualize app', function describeIndexTests() {
|
|||
const toTime = '2015-09-23 18:31:44.000';
|
||||
|
||||
PageObjects.common.debug('navigateToApp visualize');
|
||||
return PageObjects.common.navigateToApp('visualize')
|
||||
return PageObjects.common.navigateToUrl('visualize', 'new')
|
||||
.then(function () {
|
||||
PageObjects.common.debug('clickLineChart');
|
||||
return PageObjects.visualize.clickLineChart();
|
||||
|
|
|
@ -14,7 +14,7 @@ bdd.describe('visualize app', function describeIndexTests() {
|
|||
|
||||
bdd.before(function () {
|
||||
PageObjects.common.debug('navigateToApp visualize');
|
||||
return PageObjects.common.navigateToApp('visualize')
|
||||
return PageObjects.common.navigateToUrl('visualize', 'new')
|
||||
.then(function () {
|
||||
PageObjects.common.debug('clickMetric');
|
||||
return PageObjects.visualize.clickMetric();
|
||||
|
|
|
@ -14,7 +14,7 @@ bdd.describe('visualize app', function describeIndexTests() {
|
|||
const toTime = '2015-09-23 18:31:44.000';
|
||||
|
||||
PageObjects.common.debug('navigateToApp visualize');
|
||||
return PageObjects.common.navigateToApp('visualize')
|
||||
return PageObjects.common.navigateToUrl('visualize', 'new')
|
||||
.then(function () {
|
||||
PageObjects.common.debug('clickPieChart');
|
||||
return PageObjects.visualize.clickPieChart();
|
||||
|
|
|
@ -15,7 +15,7 @@ bdd.describe('visualize app', function describeIndexTests() {
|
|||
bdd.before(function () {
|
||||
|
||||
PageObjects.common.debug('navigateToApp visualize');
|
||||
return PageObjects.common.navigateToApp('visualize')
|
||||
return PageObjects.common.navigateToUrl('visualize', 'new')
|
||||
.then(function () {
|
||||
PageObjects.common.debug('clickTileMap');
|
||||
return PageObjects.visualize.clickTileMap();
|
||||
|
|
|
@ -14,7 +14,7 @@ bdd.describe('visualize app', function describeIndexTests() {
|
|||
|
||||
bdd.before(function () {
|
||||
PageObjects.common.debug('navigateToApp visualize');
|
||||
return PageObjects.common.navigateToApp('visualize')
|
||||
return PageObjects.common.navigateToUrl('visualize', 'new')
|
||||
.then(function () {
|
||||
PageObjects.common.debug('clickVerticalBarChart');
|
||||
return PageObjects.visualize.clickVerticalBarChart();
|
||||
|
|
|
@ -80,6 +80,15 @@ export default class Common {
|
|||
return getUrl.baseUrl(config.servers.elasticsearch);
|
||||
}
|
||||
|
||||
navigateToUrl(appName, subUrl) {
|
||||
const appConfig = Object.assign({}, config.apps[appName], {
|
||||
// Overwrite the default hash with the URL we really want.
|
||||
hash: `${appName}/${subUrl}`,
|
||||
});
|
||||
const appUrl = getUrl.noAuth(config.servers.kibana, appConfig);
|
||||
return this.remote.get(appUrl);
|
||||
}
|
||||
|
||||
navigateToApp(appName, testStatusPage) {
|
||||
const self = this;
|
||||
const appUrl = getUrl.noAuth(config.servers.kibana, config.apps[appName]);
|
||||
|
|
|
@ -82,10 +82,7 @@ export default class VisualizePage {
|
|||
}
|
||||
|
||||
getChartTypes() {
|
||||
|
||||
return this.remote
|
||||
.setFindTimeout(defaultFindTimeout)
|
||||
.findAllByCssSelector('.wizard-type-heading h4')
|
||||
return PageObjects.common.findAllTestSubjects('visualizeWizardChartTypeTitle')
|
||||
.then(function (chartTypes) {
|
||||
function getChartType(chart) {
|
||||
return chart.getVisibleText();
|
||||
|
@ -299,11 +296,6 @@ export default class VisualizePage {
|
|||
});
|
||||
}
|
||||
|
||||
clickNewVisualization() {
|
||||
return PageObjects.common.findTestSubject('visualizeNewButton')
|
||||
.click();
|
||||
}
|
||||
|
||||
saveVisualization(vizName) {
|
||||
return PageObjects.common.findTestSubject('visualizeSaveButton')
|
||||
.click()
|
||||
|
@ -340,7 +332,11 @@ export default class VisualizePage {
|
|||
}
|
||||
|
||||
clickLoadSavedVisButton() {
|
||||
return PageObjects.common.findTestSubject('visualizeOpenButton')
|
||||
// TODO: Use a test subject selector once we rewrite breadcrumbs to accept each breadcrumb
|
||||
// element as a child instead of building the breadcrumbs dynamically.
|
||||
return this.remote
|
||||
.setFindTimeout(defaultFindTimeout)
|
||||
.findByCssSelector('[href="#/visualize"]')
|
||||
.click();
|
||||
}
|
||||
|
||||
|
@ -376,17 +372,8 @@ export default class VisualizePage {
|
|||
});
|
||||
}
|
||||
|
||||
// this is for starting on the
|
||||
// bottom half of the "Create a new visualization Step 1" page
|
||||
openSavedVisualization(vizName) {
|
||||
const self = this;
|
||||
return self.filterVisByName(vizName)
|
||||
.then(() => {
|
||||
return PageObjects.common.sleep(1000);
|
||||
})
|
||||
.then(function clickDashboardByLinkedText() {
|
||||
return self.clickVisualizationByLinkText(vizName);
|
||||
});
|
||||
return this.clickVisualizationByLinkText(vizName);
|
||||
}
|
||||
|
||||
getXAxisLabels() {
|
||||
|
|
|
@ -30,3 +30,7 @@
|
|||
.kuiIcon--inactive {
|
||||
color: $inactiveColor;
|
||||
}
|
||||
|
||||
.kuiIcon--basic {
|
||||
color: #565656;
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@
|
|||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
* > {
|
||||
& > * {
|
||||
vertical-align: middle; /* 1 */
|
||||
}
|
||||
}
|
||||
|
|
5
ui_framework/dist/ui_framework.css
vendored
5
ui_framework/dist/ui_framework.css
vendored
|
@ -542,6 +542,9 @@ body {
|
|||
.kuiIcon--inactive {
|
||||
color: #c3c3c3; }
|
||||
|
||||
.kuiIcon--basic {
|
||||
color: #565656; }
|
||||
|
||||
.kuiInfoPanel {
|
||||
padding: 14px 20px 18px;
|
||||
line-height: 1.5;
|
||||
|
@ -1407,7 +1410,7 @@ body {
|
|||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis; }
|
||||
.kuiTableRowCell__liner * > {
|
||||
.kuiTableRowCell__liner > * {
|
||||
vertical-align: middle;
|
||||
/* 1 */ }
|
||||
|
||||
|
|
1
ui_framework/doc_site/src/views/icon/icon_basic.html
Normal file
1
ui_framework/doc_site/src/views/icon/icon_basic.html
Normal file
|
@ -0,0 +1 @@
|
|||
<div class="kuiIcon kuiIcon--basic fa-train"></div>
|
|
@ -17,6 +17,12 @@ export default createExample([{
|
|||
<p>Use this Icon to denote useful information.</p>
|
||||
),
|
||||
html: require('./icon_info.html'),
|
||||
}, {
|
||||
title: 'Basic',
|
||||
description: (
|
||||
<p>Use this Icon when you don't want to communicate any particular meaning with the icon's color.</p>
|
||||
),
|
||||
html: require('./icon_basic.html'),
|
||||
hasDarkTheme: false,
|
||||
}, {
|
||||
title: 'Success',
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue