Update Saved Objects view with new components. (#9598)

Backports PR #9535

**Commit 1:**
Componentize Saved Objects table.

* Original sha: b7c08acd46
* Authored by CJ Cenizal <cj@cenizal.com> on 2016-12-16T05:12:38Z

**Commit 2:**
Remove Edit button from Saved Objects table.

* Original sha: 09b7a08e73
* Authored by CJ Cenizal <cj@cenizal.com> on 2016-12-16T23:28:26Z

**Commit 3:**
Remove inline Buttons from Table. Add MicroButtons to serve the same purpose.

* Original sha: 0f32b83747
* Authored by CJ Cenizal <cj@cenizal.com> on 2016-12-17T00:36:56Z

**Commit 4:**
Use fat arrow function instead of self var in _objects.js. Add 'areAllRowsChecked' controller method.

* Original sha: fd29dbb445
* Authored by CJ Cenizal <cj@cenizal.com> on 2016-12-20T17:04:07Z

**Commit 5:**
Create visual separation between tabs and table, and add a border.

* Original sha: 94ea28559c
* Authored by CJ Cenizal <cj@cenizal.com> on 2016-12-21T01:03:48Z

**Commit 6:**
Add TODO comments to migrate scope vars to the controller.

* Original sha: c2a164d18a
* Authored by CJ Cenizal <cj@cenizal.com> on 2016-12-21T19:11:26Z
This commit is contained in:
jasper 2016-12-21 15:01:14 -05:00 committed by CJ Cenizal
parent eb8866cea1
commit b6b2661408
17 changed files with 315 additions and 128 deletions

View file

@ -1,80 +1,179 @@
<kbn-management-app section="kibana">
<kbn-management-objects class="container-fluid">
<div class="header">
<h2 class="title">Edit Saved Objects</h2>
<button class="btn btn-default controls" ng-click="exportAll()"><i aria-hidden="true" class="fa fa-download"></i> Export Everything</button>
<file-upload on-read="importAll(fileContents)" upload-selector="button.upload">
<button class="btn btn-default controls upload" ng-click>
<i aria-hidden="true" class="fa fa-upload"></i> Import
<kbn-management-app section="kibana" class="kuiView">
<kbn-management-objects class="kuiViewContent kuiViewContent--constrainedWidth">
<!-- Header -->
<div class="kuiViewContentItem kuiSubHeader">
<h1 class="kuiTitle">
Edit Saved Objects
</h1>
<div>
<button
class="kuiButton kuiButton--basic kuiButton--iconText"
ng-click="exportAll()"
>
<span aria-hidden="true" class="kuiButton__icon kuiIcon fa-download"></span>
Export Everything
</button>
</file-upload>
<file-upload
on-read="importAll(fileContents)"
upload-selector="button.upload"
>
<button class="kuiButton kuiButton--basic kuiButton--iconText">
<span aria-hidden="true" class="kuiButton__icon kuiIcon fa-upload"></span>
Import
</button>
</file-upload>
</div>
</div>
<p>
<!-- Intro -->
<p class="kuiViewContentItem kuiVerticalRhythm">
From here you can delete saved objects, such as saved searches. You can also edit the raw data of saved objects. Typically objects are only modified via their associated application, which is probably what you should use instead of this screen. Each tab is limited to 100 results. You can use the filter to find objects not in the default list.
</p>
<form role="form">
<input aria-label="Filter" ng-model="advancedFilter" class="form-control span12" type="text" placeholder="Filter"/>
</form>
<ul class="nav nav-tabs">
<li class="kbn-management-tab" ng-class="{ active: state.tab === service.title }" ng-repeat="service in services">
<a title="{{ service.title }}" ng-click="changeTab(service)">{{ service.title }}
<!-- Tabs -->
<div class="kuiViewContentItem kuiVerticalRhythm">
<div class="kuiTabs">
<button
class="kuiTab kbn-management-tab"
ng-class="{ 'kuiTab-isSelected': state.tab === service.title }"
ng-repeat="service in services"
title="{{ service.title }}"
ng-click="changeTab(service)"
>
{{ service.title }}
<small>
({{service.data.length}}<span ng-show="service.total > service.data.length"> of {{service.total}}</span>)
</small>
</a>
</li>
</ul>
<div class="tab-content">
<div class="action-bar">
<label>
<input type="checkbox" ng-checked="currentTab.data.length > 0 && selectedItems.length == currentTab.data.length" ng-click="toggleAll()" />
Select All
</label>
<a ng-disabled="selectedItems.length == 0"
confirm-click="bulkDelete()"
confirmation="Are you sure you want to delete the selected {{currentTab.title}}? This action is irreversible!"
class="btn btn-xs btn-danger" aria-label="Delete"><i aria-hidden="true" class="fa fa-trash"></i> Delete</a>
<a ng-disabled="selectedItems.length == 0"
ng-click="bulkExport()"
class="btn btn-xs btn-default" aria-label="Export"><i aria-hidden="true" class="fa fa-download"></i> Export</a>
</div>
<div ng-repeat="service in services" ng-class="{ active: state.tab === service.title }" class="tab-pane">
<ul class="list-unstyled">
<li class="item" ng-repeat="item in service.data | orderBy:'title'">
<div class="actions pull-right">
<button
ng-click="edit(service, item)"
class="btn btn-default"
aria-label="Edit">
<span class="sr-only">Edit</span>
<i aria-hidden="true" class="fa fa-pencil"></i>
</button>
<button
ng-click="open(item)"
class="btn btn-info"
aria-label="Hide">
<span class="sr-only">Hide</span>
<i aria-hidden="true" class="fa fa-eye"></i>
</button>
</div>
<div class="pull-left">
<input
ng-click="toggleItem(item)"
ng-checked="selectedItems.indexOf(item) >= 0"
type="checkbox" >
</div>
<div class="item-title">
<a ng-click="edit(service, item)">{{ item.title }}</a>
</div>
</li>
<li ng-if="!service.data.length" class="empty">No "{{service.title}}" found.</li>
</ul>
</button>
</div>
</div>
<!-- ControlledTable -->
<div
class="kuiViewContentItem kuiControlledTable kuiVerticalRhythm"
ng-repeat="service in services track by $index"
ng-show="state.tab === service.title"
>
<!-- 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="managementObjectsController.advancedFilter"
>
</div>
</div>
<div class="kuiToolBarSection">
<!-- Bulk delete button -->
<button
class="kuiButton kuiButton--danger kuiButton--iconText"
confirm-click="bulkDelete()"
confirmation="Are you sure you want to delete the selected {{currentTab.title}}? This action is irreversible!"
aria-label="Delete selected objects"
ng-disabled="selectedItems.length == 0"
>
<span aria-hidden="true" class="kuiButton__icon kuiIcon fa-trash"></span>
Delete
</button>
<!-- Bulk export button -->
<button
class="kuiButton kuiButton--basic kuiButton--iconText"
ng-click="bulkExport()"
aria-label="Export selected objects"
ng-disabled="selectedItems.length == 0"
>
<span aria-hidden="true" class="kuiButton__icon kuiIcon fa-download"></span>
Export
</button>
</div>
<div class="kuiToolBarSection">
<!-- We need an empty section for the buttons to be positioned consistently. -->
</div>
</div>
<!-- NoResults -->
<div class="kuiPanel kuiPanel--centered" ng-if="!service.data.length">
<div class="kuiNoResults">
No {{service.title}} matched your search.
</div>
</div>
<!-- Table -->
<table class="kuiTable" ng-if="service.data.length">
<thead>
<tr>
<th class="kuiTableHeaderCell kuiTableHeaderCell--checkBox">
<input
type="checkbox"
class="kuiCheckBox"
ng-checked="managementObjectsController.areAllRowsChecked()"
ng-click="toggleAll()"
>
</th>
<th class="kuiTableHeaderCell">
Title
</th>
</tr>
</thead>
<tbody>
<tr
ng-repeat="item in service.data | orderBy:'title'"
class="kuiTableRow"
>
<td class="kuiTableRowCell kuiTableRowCell--checkBox">
<input
type="checkbox"
class="kuiCheckBox"
ng-click="toggleItem(item)"
ng-checked="selectedItems.indexOf(item) >= 0"
>
</td>
<td class="kuiTableRowCell">
<div class="kuiTableRowCell__liner">
<a class="kuiLink" href="" ng-click="edit(service, item)">
{{ item.title }}
</a>
<button
class="kuiMicroButton kuiTableRowHoverReveal"
ng-click="open(item)"
aria-label="View"
tooltip="View in app"
>
<span
aria-hidden="true"
class="kuiIcon fa-eye"
></span>
</button>
</div>
</td>
</tr>
</tbody>
</table>
<!-- ToolBarFooter -->
<div class="kuiToolBarFooter">
<div class="kuiToolBarFooterSection">
<div class="kuiToolBarText" ng-hide="selectedItems.length === 0">
{{ selectedItems.length }} selected
</div>
</div>
<div class="kuiToolBarFooterSection">
<!-- We need an empty section for the buttons to be positioned consistently. -->
</div>
</div>
</div>
</kbn-management-objects>
</kbn-management-app>

View file

@ -18,13 +18,22 @@ uiModules.get('apps/management')
.directive('kbnManagementObjects', function (kbnIndex, Notifier, Private, kbnUrl, Promise) {
return {
restrict: 'E',
controllerAs: 'managementObjectsController',
controller: function ($scope, $injector, $q, AppState, es) {
const notify = new Notifier({ location: 'Saved Objects' });
// TODO: Migrate all scope variables to the controller.
const $state = $scope.state = new AppState();
$scope.currentTab = null;
$scope.selectedItems = [];
this.areAllRowsChecked = function areAllRowsChecked() {
if ($scope.currentTab.data.length === 0) {
return false;
}
return $scope.selectedItems.length === $scope.currentTab.data.length;
};
const getData = function (filter) {
const services = registry.all().map(function (obj) {
const service = $injector.get(obj.service);
@ -51,7 +60,11 @@ uiModules.get('apps/management')
});
};
const refreshData = () => {
return getData(this.advancedFilter);
};
// TODO: Migrate all scope methods to the controller.
$scope.toggleAll = function () {
if ($scope.selectedItems.length === $scope.currentTab.data.length) {
$scope.selectedItems.length = 0;
@ -60,6 +73,7 @@ uiModules.get('apps/management')
}
};
// TODO: Migrate all scope methods to the controller.
$scope.toggleItem = function (item) {
const i = $scope.selectedItems.indexOf(item);
if (i >= 0) {
@ -69,10 +83,12 @@ uiModules.get('apps/management')
}
};
// TODO: Migrate all scope methods to the controller.
$scope.open = function (item) {
kbnUrl.change(item.url.substr(1));
};
// TODO: Migrate all scope methods to the controller.
$scope.edit = function (service, item) {
const params = {
service: service.serviceName,
@ -82,6 +98,7 @@ uiModules.get('apps/management')
kbnUrl.change('/management/kibana/objects/{{ service }}/{{ id }}', params);
};
// TODO: Migrate all scope methods to the controller.
$scope.bulkDelete = function () {
$scope.currentTab.service.delete(pluck($scope.selectedItems, 'id'))
.then(refreshData)
@ -91,11 +108,13 @@ uiModules.get('apps/management')
.catch(error => notify.error(error));
};
// TODO: Migrate all scope methods to the controller.
$scope.bulkExport = function () {
const objs = $scope.selectedItems.map(partialRight(extend, {type: $scope.currentTab.type}));
retrieveAndExportDocs(objs);
};
// TODO: Migrate all scope methods to the controller.
$scope.exportAll = () => Promise
.map($scope.services, service => service.service
.scanAll('')
@ -125,6 +144,7 @@ uiModules.get('apps/management')
saveAs(blob, 'export.json');
}
// TODO: Migrate all scope methods to the controller.
$scope.importAll = function (fileContents) {
let docs;
try {
@ -152,10 +172,7 @@ uiModules.get('apps/management')
});
}
function refreshData() {
return getData($scope.advancedFilter);
}
// TODO: Migrate all scope methods to the controller.
$scope.changeTab = function (tab) {
$scope.currentTab = tab;
$scope.selectedItems.length = 0;
@ -163,7 +180,7 @@ uiModules.get('apps/management')
$state.save();
};
$scope.$watch('advancedFilter', function (filter) {
$scope.$watch('managementObjectsController.advancedFilter', function (filter) {
getData(filter);
});
}

View file

@ -12,26 +12,7 @@ kbn-management-objects-view {
display: block;
}
/**
* 1. Allow navbar to get taller on narrow screens.
*/
.management-navbar {
min-height: 70px; /* 1 */
}
.tab-account {
background-color: @kibanaGray6;
}
.tab-management {
background-color: @kibanaGray6;
}
.settings-nav {
text-transform: capitalize;
}
li.kbn-management-tab:first-letter {
.kbn-management-tab:first-letter {
text-transform: capitalize;
}

View file

@ -6,6 +6,7 @@
.kuiButton {
display: inline-block; /* 1 */
appearance: none;
cursor: pointer;
padding: 4px 12px 5px;
font-size: $fontSize;
font-weight: 400;
@ -13,7 +14,6 @@
text-decoration: none;
border: none;
border-radius: $buttonBorderRadius;
cursor: pointer;
&:disabled {
cursor: default;

View file

@ -73,6 +73,7 @@ body {
@import "icon/index";
@import "link/index";
@import "local_nav/index";
@import "micro_button/index";
@import "no_results/index";
@import "panel/index";
@import "table/index";

View file

@ -0,0 +1,2 @@
@import 'micro_button';
@import 'micro_button_group';

View file

@ -0,0 +1,20 @@
/**
* 1. Setting to inline-block guarantees the same height when applied to both
* button elements and anchor tags.
* 2. Fit MicroButton inside of Table rows without pushing them taller.
*/
.kuiMicroButton {
display: inline-block; /* 1 */
appearance: none;
cursor: pointer;
padding: 2px 5px;
border: 1px solid transparent;
color: $subduedFontColor;
background-color: transparent;
font-size: 12px;
line-height: 1; /* 2 */
&:hover {
color: $fontColor;
}
}

View file

@ -0,0 +1,7 @@
.kuiMicroButtonGroup {
display: flex;
.kuiMicroButton + .kuiMicroButton {
margin-left: 2px;
}
}

View file

@ -37,10 +37,25 @@
}
}
.kuiTableHeaderCell--alignRight {
text-align: right;
}
.kuiTableSortIcon {
pointer-events: none;
}
.kuiTableRow {
&:hover {
.kuiTableRowHoverReveal {
display: inline-block;
}
}
}
.kuiTableRowHoverReveal {
display: none;
}
.kuiTableRowCell {
@include tableCell;
@ -48,10 +63,17 @@
border-top: $tableBorder;
}
/**
* 1. Vertically align all children.
*/
.kuiTableRowCell__liner {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
* > {
vertical-align: middle; /* 1 */
}
}
.kuiTableRowCell--alignRight {
@ -62,18 +84,6 @@
}
}
.kuiTableRowCell--actions {
padding-top: 0;
padding-bottom: 0;
/**
* 1. We don't want to clip the focused state of buttons.
*/
.kuiTableRowCell__liner {
overflow: visible; /* 1 */
}
}
/**
* 1. Rendered width of cell with checkbox inside of it.
* 2. Align checkbox with text in other cells.

View file

@ -1,5 +1,6 @@
.kuiTabs {
display: flex;
border-bottom: $tableBorder;
}
/**

View file

@ -16,6 +16,9 @@ import LinkExample
import LocalNavExample
from '../../views/local_nav/local_nav_example.jsx';
import MicroButtonExample
from '../../views/micro_button/micro_button_example.jsx';
import TableExample
from '../../views/table/table_example.jsx';
@ -41,6 +44,9 @@ const components = [{
}, {
name: 'LocalNav',
component: LocalNavExample,
}, {
name: 'MicroButton',
component: MicroButtonExample,
}, {
name: 'Table',
component: TableExample,

View file

@ -2,12 +2,16 @@
Button element
</button>
&nbsp;
<input
type="submit"
class="kuiButton kuiButton--basic"
value="Submit input element"
>
&nbsp;
<a href="#" class="kuiButton kuiButton--basic">
Anchor element
</a>

View file

@ -0,0 +1,3 @@
<button class="kuiMicroButton" title="Edit">
<soan class="kuiIcon fa-gear"></soan>
</button>

View file

@ -0,0 +1,9 @@
<button class="kuiMicroButton" title="Edit">
<soan class="kuiIcon fa-gear"></soan>
</button>
&nbsp;
<a href="#" class="kuiMicroButton" title="View">
<soan class="kuiIcon fa-eye"></soan>
</a>

View file

@ -0,0 +1,28 @@
import React from 'react';
import {
createExample,
} from '../../services';
export default createExample([{
title: 'MicroButton',
description: (
<p>Use MicroButtons for inline actions inside of Table rows.</p>
),
html: require('./micro_button.html'),
hasDarkTheme: false,
}, {
title: 'MicroButtonGroup',
description: (
<p>Use the MicroButtonGroup to emphasize the relationships between a set of MicroButtons, and differentiate them from MicroButtons outside of the set.</p>
),
html: require('./micro_button_group.html'),
hasDarkTheme: false,
}, {
title: 'Element variations',
description: (
<p>You can create a MicroButton using a button element or a link.</p>
),
html: require('./micro_button_elements.html'),
hasDarkTheme: false,
}]);

View file

@ -0,0 +1,13 @@
<div class="kuiMicroButtonGroup">
<button class="kuiMicroButton" title="Edit">
<soan class="kuiIcon fa-gear"></soan>
</button>
<button class="kuiMicroButton" title="View">
<soan class="kuiIcon fa-eye"></soan>
</button>
<button class="kuiMicroButton" title="Delete">
<soan class="kuiIcon fa-trash"></soan>
</button>
</div>

View file

@ -58,7 +58,8 @@
<th class="kuiTableHeaderCell">
Date created
</th>
<th class="kuiTableHeaderCell">
<th class="kuiTableHeaderCell kuiTableHeaderCell--alignRight">
Orders of magnitude
</th>
</tr>
</thead>
@ -79,14 +80,9 @@
Tue Dec 06 2016 12:56:15 GMT-0800 (PST)
</div>
</td>
<td class="kuiTableRowCell kuiTableRowCell--alignRight kuiTableRowCell--actions">
<td class="kuiTableRowCell kuiTableRowCell--alignRight">
<div class="kuiTableRowCell__liner">
<button class="kuiButton kuiButton--basic">
View
</button>
<button class="kuiButton kuiButton--basic">
Edit
</button>
1
</div>
</td>
</tr>
@ -106,14 +102,9 @@
Tue Dec 06 2016 12:56:15 GMT-0800 (PST)
</div>
</td>
<td class="kuiTableRowCell kuiTableRowCell--alignRight kuiTableRowCell--actions">
<td class="kuiTableRowCell kuiTableRowCell--alignRight">
<div class="kuiTableRowCell__liner">
<button class="kuiButton kuiButton--basic">
View
</button>
<button class="kuiButton kuiButton--basic">
Edit
</button>
10
</div>
</td>
</tr>
@ -133,14 +124,9 @@
Tue Dec 06 2016 12:56:15 GMT-0800 (PST)
</div>
</td>
<td class="kuiTableRowCell kuiTableRowCell--alignRight kuiTableRowCell--actions">
<td class="kuiTableRowCell kuiTableRowCell--alignRight">
<div class="kuiTableRowCell__liner">
<button class="kuiButton kuiButton--basic">
View
</button>
<button class="kuiButton kuiButton--basic">
Edit
</button>
100
</div>
</td>
</tr>
@ -162,7 +148,7 @@
</td>
<td class="kuiTableRowCell kuiTableRowCell--alignRight">
<div class="kuiTableRowCell__liner">
(Unavailable)
1000
</div>
</td>
</tr>