diff --git a/package.json b/package.json index cab0142dca69b..70b11d8305399 100644 --- a/package.json +++ b/package.json @@ -72,6 +72,7 @@ "url": "https://github.com/elastic/kibana.git" }, "dependencies": { + "@elastic/eui": "0.0.1", "@elastic/datemath": "2.3.0", "@elastic/filesaver": "1.1.2", "@elastic/numeral": "2.2.1", @@ -154,7 +155,7 @@ "mkdirp": "0.5.1", "moment": "2.13.0", "moment-timezone": "0.5.4", - "ngreact": "0.3.0", + "ngreact": "0.5.1", "no-ui-slider": "1.2.0", "node-fetch": "1.3.2", "pegjs": "0.9.0", @@ -290,6 +291,7 @@ "strip-ansi": "^3.0.1", "supertest": "3.0.0", "supertest-as-promised": "2.0.2", + "svg-sprite-loader": "3.4.1", "tree-kill": "1.1.0", "webpack-dev-server": "2.9.1", "yeoman-generator": "1.1.1", diff --git a/src/core_plugins/kibana/public/management/index.js b/src/core_plugins/kibana/public/management/index.js index 372189fbcb673..867dabde47d14 100644 --- a/src/core_plugins/kibana/public/management/index.js +++ b/src/core_plugins/kibana/public/management/index.js @@ -9,6 +9,8 @@ import landingTemplate from 'plugins/kibana/management/landing.html'; import { management } from 'ui/management'; import 'ui/kbn_top_nav'; +import { ManagementLandingPage } from './management_landing_page'; + uiRoutes .when('/management', { template: landingTemplate @@ -23,9 +25,18 @@ require('ui/index_patterns/route_setup/load_default')({ whenMissingRedirectTo: '/management/kibana/index' }); -uiModules -.get('apps/management') -.directive('kbnManagementApp', function (Private, $location, timefilter) { +const app = uiModules.get('apps/management', ['react']); + +app.directive('managementLandingPage', function (reactDirective) { + return reactDirective(ManagementLandingPage, [ + 'version', + 'addBasePath', + // Prevent the 'Maximum call stack exceeded' error. + ['sections', { watchDepth: 'collection' }], + ]); +}); + +app.directive('kbnManagementApp', function (Private, $location, timefilter) { return { restrict: 'E', template: appTemplate, @@ -50,12 +61,11 @@ uiModules }; }); -uiModules -.get('apps/management') -.directive('kbnManagementLanding', function (kbnVersion) { +app.directive('kbnManagementLanding', function (kbnVersion, chrome) { return { restrict: 'E', link: function ($scope) { + $scope.addBasePath = chrome.addBasePath; $scope.sections = management.items.inOrder; $scope.kbnVersion = kbnVersion; } diff --git a/src/core_plugins/kibana/public/management/landing.html b/src/core_plugins/kibana/public/management/landing.html index ee7e1c3266872..1780723466aa8 100644 --- a/src/core_plugins/kibana/public/management/landing.html +++ b/src/core_plugins/kibana/public/management/landing.html @@ -1,52 +1,9 @@ - -
-
Version: {{::kbnVersion}}
-
- - -
-
-
-
-
-
- {{::section.display}} -
-
-
- -
- -
-
-
+
diff --git a/src/core_plugins/kibana/public/management/management_landing_page.js b/src/core_plugins/kibana/public/management/management_landing_page.js new file mode 100644 index 0000000000000..28efdc53d00fd --- /dev/null +++ b/src/core_plugins/kibana/public/management/management_landing_page.js @@ -0,0 +1,109 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; + +import { + EuiFlexGroup, + EuiFlexItem, + EuiHorizontalRule, + EuiLink, + EuiPage, + EuiPageContentBody, + EuiPanel, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; + +export class ManagementLandingPage extends Component { + renderSections = sections => { + return sections.map((section, index) => { + const items = section.visibleItems.map((item, itemIndex) => { + // TO-DO: Links need a disabled state + return ( + + + {item.display} + + + ); + }); + + const iconClasses = classNames( + 'management-panel__heading-icon', + `management-panel__heading-icon--${section.id}`, + ); + + const title = ( + + +
+ + + + +

{section.display}

+
+
+ + ); + + return ( +
+ + {title} + + + + + {items} + + + + + {(index < sections.length - 1) && + + } +
+ ); + }); + }; + + render() { + const { + sections, + version, + } = this.props; + + const renderedSections = + this.renderSections(sections.filter(section => section.visibleItems.length > 0)); + + return ( + + +

Version: {version}

+
+ + + + {renderedSections} +
+ ); + } +} + +ManagementLandingPage.propTypes = { + sections: PropTypes.array, + version: PropTypes.string, + addBasePath: PropTypes.func, +}; + +ManagementLandingPage.defaultProps = { + sections: [], +}; diff --git a/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/edit_index_pattern.js b/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/edit_index_pattern.js index b8597111c986e..7c6db5a06a90c 100644 --- a/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/edit_index_pattern.js +++ b/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/edit_index_pattern.js @@ -38,7 +38,7 @@ uiRoutes } }); -uiModules.get('apps/management') +uiModules.get('apps/management', ['app/kibana']) .controller('managementIndicesEdit', function ( $scope, $location, $route, config, courier, Notifier, Private, AppState, docTitle, confirmModal) { const notify = new Notifier(); diff --git a/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/field_controls.html b/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/field_controls.html deleted file mode 100644 index 794f1da2a7b79..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/field_controls.html +++ /dev/null @@ -1,19 +0,0 @@ -
- - - - - -
diff --git a/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/indexed_fields_table/indexed_fields_table.html b/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/indexed_fields_table/indexed_fields_table.html index 1581786fc8e6f..ba679d391fef0 100644 --- a/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/indexed_fields_table/indexed_fields_table.html +++ b/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/indexed_fields_table/indexed_fields_table.html @@ -3,7 +3,7 @@ rows="rows" per-page="perPage" link-to-top="true" - show-blank-rows="false"> - + show-blank-rows="false" +>

No matching fields found.

diff --git a/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/indexed_fields_table/indexed_fields_table.js b/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/indexed_fields_table/indexed_fields_table.js index 49b50533d8aeb..2f9cd2871f0ef 100644 --- a/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/indexed_fields_table/indexed_fields_table.js +++ b/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/indexed_fields_table/indexed_fields_table.js @@ -1,16 +1,27 @@ +import React from 'react'; import _ from 'lodash'; -import 'ui/paginated_table'; -import fieldNameHtml from './field_name.html'; -import fieldTypeHtml from './field_type.html'; -import fieldControlsHtml from '../field_controls.html'; import { uiModules } from 'ui/modules'; import { FieldWildcardProvider } from 'ui/field_wildcard'; import template from './indexed_fields_table.html'; +import { + RIGHT_ALIGNMENT, +} from '@elastic/eui'; + +const renderBooleanForCondition = condition => { + return () => { + let content; + + if (condition) { + content = ; + } + + return
{content}
; + }; +}; + uiModules.get('apps/management') -.directive('indexedFieldsTable', function (Private, $filter) { - const yesTemplate = ''; - const noTemplate = ''; +.directive('indexedFieldsTable', function (Private, $filter, chrome, kbnUrl) { const filter = $filter('filter'); const { fieldWildcardMatcher } = Private(FieldWildcardProvider); @@ -19,23 +30,40 @@ uiModules.get('apps/management') template, scope: true, link: function ($scope) { - const rowScopes = []; // track row scopes, so they can be destroyed as needed $scope.perPage = 25; $scope.columns = [ - { title: 'name' }, - { title: 'type' }, - { title: 'format' }, - { title: 'searchable', info: 'These fields can be used in the filter bar' }, - { title: 'aggregatable', info: 'These fields can be used in visualization aggregations' }, - { title: 'excluded', info: 'Fields that are excluded from _source when it is fetched' }, - { title: 'controls', sortable: false } + { + title: 'name', + text: 'Name', + }, { + title: 'type', + text: 'Type', + }, { + title: 'format', + text: 'Format', + }, { + title: 'searchable', + text: 'Searchable', + info: 'These fields can be used in the filter bar' + }, { + title: 'aggregatable', + ext: 'Aggregatable', + info: 'These fields can be used in visualization aggregations' + }, { + title: 'excluded', + text: 'Excluded', + info: 'Fields that are excluded from _source when it is fetched' + }, { + title: 'controls', + text: '', + sortable: false, + align: RIGHT_ALIGNMENT, + }, ]; $scope.$watchMulti(['[]indexPattern.fields', 'fieldFilter', 'indexedFieldTypeFilter'], refreshRows); function refreshRows() { - // clear and destroy row scopes - _.invoke(rowScopes.splice(0), '$destroy'); const fields = filter($scope.indexPattern.getNonScriptedFields(), { name: $scope.fieldFilter, type: $scope.indexedFieldTypeFilter @@ -45,44 +73,92 @@ uiModules.get('apps/management') _.find($scope.editSections, { index: 'indexedFields' }).count = fields.length; // Update the tab count $scope.rows = fields.map(function (field) { - const childScope = _.assign($scope.$new(), { field: field }); - rowScopes.push(childScope); - const excluded = fieldWildcardMatch(field.name); return [ { - markup: fieldNameHtml, - scope: childScope, + render: () => { + let content; + + if ($scope.indexPattern.timeFieldName === field.name) { + content = ( + + + ); + } + + return ( +
+ {field.displayName} +   + {content} +
+ ); + }, value: field.displayName, - attr: { - 'data-test-subj': 'indexedFieldName' - } - }, - { - markup: fieldTypeHtml, - scope: childScope, + }, { + render: () => { + let info; + + if (field.type === 'conflict') { + info = ( + + ); + } + + return ( +
+ {field.type} + {info} +
+ ); + }, value: field.type, - attr: { - 'data-test-subj': 'indexedFieldType' - } - }, - _.get($scope.indexPattern, ['fieldFormatMap', field.name, 'type', 'title']), - { - markup: field.searchable ? yesTemplate : noTemplate, + }, { + // TODO: What is this? + render: () => ( +
+ {_.get($scope.indexPattern, ['fieldFormatMap', field.name, 'type', 'title'])} +
+ ), + }, { + render: renderBooleanForCondition(field.searchable), value: field.searchable - }, - { - markup: field.aggregatable ? yesTemplate : noTemplate, + }, { + render: renderBooleanForCondition(field.aggregatable), value: field.aggregatable - }, - { - markup: excluded ? yesTemplate : noTemplate, + }, { + render: renderBooleanForCondition(excluded), value: excluded - }, - { - markup: fieldControlsHtml, - scope: childScope + }, { + render: () => { + return ( +
+
+ + +
+
+ ); + }, } ]; }); diff --git a/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/scripted_fields_table/date_scripts.js b/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/scripted_fields_table/date_scripts.js deleted file mode 100644 index 559bb2a31a806..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/scripted_fields_table/date_scripts.js +++ /dev/null @@ -1,26 +0,0 @@ -import _ from 'lodash'; - -export function dateScripts(indexPattern) { - const dateScripts = {}; - const scripts = { - __dayOfMonth: 'dayOfMonth', - __dayOfWeek: 'dayOfWeek', - __dayOfYear: 'dayOfYear', - __hourOfDay: 'hourOfDay', - __minuteOfDay: 'minuteOfDay', - __minuteOfHour: 'minuteOfHour', - __monthOfYear: 'monthOfYear', - __weekOfYear: 'weekOfWeekyear', - __year: 'year' - }; - - _.each(indexPattern.fields.byType.date, function (field) { - if (field.indexed) { - _.each(scripts, function (value, key) { - dateScripts[field.name + '.' + key] = 'doc["' + field.name + '"].date.' + value; - }); - } - }); - - return dateScripts; -} diff --git a/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/scripted_fields_table/scripted_fields_table.js b/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/scripted_fields_table/scripted_fields_table.js index 39e91f7a16ffa..8ba4fad55e73c 100644 --- a/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/scripted_fields_table/scripted_fields_table.js +++ b/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/scripted_fields_table/scripted_fields_table.js @@ -1,41 +1,52 @@ +import React from 'react'; import _ from 'lodash'; -import 'ui/paginated_table'; -import fieldControlsHtml from '../field_controls.html'; -import { dateScripts } from './date_scripts'; import { uiModules } from 'ui/modules'; import template from './scripted_fields_table.html'; +import { + RIGHT_ALIGNMENT, +} from '@elastic/eui'; + uiModules.get('apps/management') -.directive('scriptedFieldsTable', function (kbnUrl, Notifier, $filter, confirmModal) { - const rowScopes = []; // track row scopes, so they can be destroyed as needed +.directive('scriptedFieldsTable', function (kbnUrl, $filter, confirmModal, chrome) { const filter = $filter('filter'); - const notify = new Notifier(); - return { restrict: 'E', template, scope: true, link: function ($scope) { - - const fieldCreatorPath = '/management/kibana/indices/{{ indexPattern }}/scriptedField'; - const fieldEditorPath = fieldCreatorPath + '/{{ fieldName }}'; - $scope.perPage = 25; $scope.columns = [ - { title: 'name' }, - { title: 'lang' }, - { title: 'script' }, - { title: 'format' }, - { title: 'controls', sortable: false } + { + title: 'name', + text: 'Name', + }, { + title: 'lang', + text: 'Lang', + }, { + title: 'script', + text: 'Script', + }, { + title: 'format', + text: 'Format', + }, { + title: 'controls', + text: '', + sortable: false, + align: RIGHT_ALIGNMENT, + }, ]; - $scope.$watchMulti(['[]indexPattern.fields', 'fieldFilter', 'scriptedFieldLanguageFilter'], refreshRows); + const remove = field => { + const confirmModalOptions = { + confirmButtonText: 'Delete field', + onConfirm: () => { $scope.indexPattern.removeScriptedField(field.name); } + }; + confirmModal(`Are you sure want to delete ${field.name}? This action is irreversible!`, confirmModalOptions); + }; function refreshRows() { - _.invoke(rowScopes, '$destroy'); - rowScopes.length = 0; - const fields = filter($scope.indexPattern.getScriptedFields(), { name: $scope.fieldFilter, lang: $scope.scriptedFieldLanguageFilter @@ -43,73 +54,74 @@ uiModules.get('apps/management') _.find($scope.editSections, { index: 'scriptedFields' }).count = fields.length; // Update the tab count $scope.rows = fields.map(function (field) { - const rowScope = $scope.$new(); - rowScope.field = field; - rowScopes.push(rowScope); - return [ - _.escape(field.name), { - markup: field.lang, - attr: { - 'data-test-subj': 'scriptedFieldLang' - } - }, - _.escape(field.script), - _.get($scope.indexPattern, ['fieldFormatMap', field.name, 'type', 'title']), - { - markup: fieldControlsHtml, - scope: rowScope + render: () => ( +
+ {_.escape(field.name)} +
+ ), + }, { + render: () => ( +
+ {field.lang} +
+ ), + }, { + render: () => ( +
+ {_.escape(field.script)} +
+ ), + }, { + render: () => ( +
+ {_.get($scope.indexPattern, ['fieldFormatMap', field.name, 'type', 'title'])} +
+ ), + }, { + render: () => { + let deleteButton; + + if (field.scripted) { + deleteButton = ( + + ); + } + + return ( +
+
+ + + + {deleteButton} +
+
+ ); + }, } ]; }); } - $scope.addDateScripts = function () { - const conflictFields = []; - let fieldsAdded = 0; - _.each(dateScripts($scope.indexPattern), function (script, field) { - try { - $scope.indexPattern.addScriptedField(field, script, 'number'); - fieldsAdded++; - } catch (e) { - conflictFields.push(field); - } - }); - - if (fieldsAdded > 0) { - notify.info(fieldsAdded + ' script fields created'); - } - - if (conflictFields.length > 0) { - notify.info('Not adding ' + conflictFields.length + ' duplicate fields: ' + conflictFields.join(', ')); - } - }; - - $scope.create = function () { - const params = { - indexPattern: $scope.indexPattern.id - }; - - kbnUrl.change(fieldCreatorPath, params); - }; - - $scope.edit = function (field) { - const params = { - indexPattern: $scope.indexPattern.id, - fieldName: field.name - }; - - kbnUrl.change(fieldEditorPath, params); - }; - - $scope.remove = function (field) { - const confirmModalOptions = { - confirmButtonText: 'Delete field', - onConfirm: () => { $scope.indexPattern.removeScriptedField(field.name); } - }; - confirmModal(`Are you sure want to delete ${field.name}? This action is irreversible!`, confirmModalOptions); - }; + $scope.$watchMulti([ + '[]indexPattern.fields', + 'fieldFilter', + 'scriptedFieldLanguageFilter', + ], refreshRows); } }; }); diff --git a/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/source_filters_table/controls.html b/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/source_filters_table/controls.html deleted file mode 100644 index 8e4cfc6cc33e5..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/source_filters_table/controls.html +++ /dev/null @@ -1,31 +0,0 @@ -
- - - - - -
diff --git a/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/source_filters_table/filter.html b/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/source_filters_table/filter.html deleted file mode 100644 index e4817c9920513..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/source_filters_table/filter.html +++ /dev/null @@ -1,12 +0,0 @@ -
- {{ filter.value }} - - -
diff --git a/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/source_filters_table/source_filters_table.html b/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/source_filters_table/source_filters_table.html index 7fdbdcffb429b..7dcddf3315353 100644 --- a/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/source_filters_table/source_filters_table.html +++ b/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/source_filters_table/source_filters_table.html @@ -48,8 +48,8 @@

diff --git a/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/source_filters_table/source_filters_table.js b/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/source_filters_table/source_filters_table.js index 959ad244a7fdd..fcc67f4a07311 100644 --- a/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/source_filters_table/source_filters_table.js +++ b/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/source_filters_table/source_filters_table.js @@ -1,21 +1,23 @@ -import { find, each, escape, invoke, size, without } from 'lodash'; +import React from 'react'; +import { find, each, escape, size, without } from 'lodash'; import { uiModules } from 'ui/modules'; import { Notifier } from 'ui/notify/notifier'; import { FieldWildcardProvider } from 'ui/field_wildcard'; -import controlsHtml from './controls.html'; -import filterHtml from './filter.html'; import template from './source_filters_table.html'; import './source_filters_table.less'; +import { + RIGHT_ALIGNMENT, +} from '@elastic/eui'; + const notify = new Notifier(); uiModules.get('kibana') .directive('sourceFiltersTable', function (Private, $filter, confirmModal) { const angularFilter = $filter('filter'); const { fieldWildcardMatcher } = Private(FieldWildcardProvider); - const rowScopes = []; // track row scopes, so they can be destroyed as needed return { restrict: 'E', @@ -33,16 +35,20 @@ uiModules.get('kibana') $scope.perPage = 25; $scope.columns = [ { - title: 'filter' + title: 'filter', + text: 'Filter', }, { title: 'matches', + text: 'Matches', sortable: false, info: 'The source fields that match the filter.' }, { title: 'controls', - sortable: false + text: '', + sortable: false, + align: RIGHT_ALIGNMENT, } ]; @@ -53,9 +59,6 @@ uiModules.get('kibana') this.placeHolder = 'source filter, accepts wildcards (e.g., `user*` to filter fields starting with \'user\')'; $scope.$watchMulti([ '[]indexPattern.sourceFilters', '$parent.fieldFilter' ], () => { - invoke(rowScopes, '$destroy'); - rowScopes.length = 0; - if ($scope.indexPattern.sourceFilters) { $scope.rows = []; each($scope.indexPattern.sourceFilters, (filter) => { @@ -65,19 +68,95 @@ uiModules.get('kibana') if ($scope.$parent.fieldFilter && !angularFilter(matches, $scope.$parent.fieldFilter).length) { return; } - // compute the rows - const rowScope = $scope.$new(); - rowScope.filter = filter; - rowScopes.push(rowScope); + $scope.rows.push([ { - markup: filterHtml, - scope: rowScope - }, - size(matches) ? escape(matches.join(', ')) : 'The source filter doesn\'t match any known fields.', - { - markup: controlsHtml, - scope: rowScope + render: () => { + let content; + if (filter === this.editing) { + // TODO: Ensure value changes during onChange. + content = ( + { + $scope.$apply(() => { + filter.value = e.target.value; + }); + }} + placeholder={this.placeHolder} + type="text" + required + /> + ); + } else { + content = {filter.value}; + } + + return ( +
+ {content} +
+ ); + }, + }, { + render: () => { + return ( +
+ { + size(matches) + ? escape(matches.join(', ')) + : The source filter doesn’t match any known fields. + } +
+ ); + }, + }, { + render: () => { + let button; + + if (filter === this.editing) { + button = ( + + ); + } else { + // TODO: Are these onClicks handled correctly? Do we depend upon + // $scope.$digest being triggered? + button = ( + + ); + } + + return ( +
+ {button} + + +
+ ); + }, } ]); }); diff --git a/src/core_plugins/kibana/public/management/styles/main.less b/src/core_plugins/kibana/public/management/styles/main.less index 80a1a8fe8e96a..d4fe0df039686 100644 --- a/src/core_plugins/kibana/public/management/styles/main.less +++ b/src/core_plugins/kibana/public/management/styles/main.less @@ -188,21 +188,6 @@ kbn-management-objects-view { kbn-management-indices { .fields { display: block; - table { - .table-striped() - } - - th:last-child, - td:last-child { - text-align: right; - } - } - - .indexed-fields { - th:first-child, - td:first-child { - width: 35%; - } } .scripted-fields header { diff --git a/src/optimize/base_optimizer.js b/src/optimize/base_optimizer.js index bc66a8721ccb9..cc1941b5652c3 100644 --- a/src/optimize/base_optimizer.js +++ b/src/optimize/base_optimizer.js @@ -8,6 +8,7 @@ import CommonsChunkPlugin from 'webpack/lib/optimize/CommonsChunkPlugin'; import DefinePlugin from 'webpack/lib/DefinePlugin'; import UglifyJsPlugin from 'webpack/lib/optimize/UglifyJsPlugin'; import NoEmitOnErrorsPlugin from 'webpack/lib/NoEmitOnErrorsPlugin'; +import SpriteLoaderPlugin from 'svg-sprite-loader/plugin'; import Stats from 'webpack/lib/Stats'; import webpackMerge from 'webpack-merge'; @@ -129,6 +130,8 @@ export default class BaseOptimizer { }), new NoEmitOnErrorsPlugin(), + + new SpriteLoaderPlugin(), ], module: { @@ -163,7 +166,11 @@ export default class BaseOptimizer { loader: 'url-loader' }, { - test: /\.(woff|woff2|ttf|eot|svg|ico)(\?|$)/, + test: /\.svg$/, + use: 'svg-sprite-loader' + }, + { + test: /\.(woff|woff2|ttf|eot|ico)(\?|$)/, loader: 'file-loader' }, { diff --git a/src/ui/public/agg_table/agg_table.js b/src/ui/public/agg_table/agg_table.js index f9eedd71e79fe..67a5100616091 100644 --- a/src/ui/public/agg_table/agg_table.js +++ b/src/ui/public/agg_table/agg_table.js @@ -1,4 +1,3 @@ -import 'ui/paginated_table'; import 'ui/compile_recursive_directive'; import 'ui/agg_table/agg_table.less'; import _ from 'lodash'; diff --git a/src/ui/public/autoload/styles.js b/src/ui/public/autoload/styles.js index 929e26ba69b65..60b14e231c056 100644 --- a/src/ui/public/autoload/styles.js +++ b/src/ui/public/autoload/styles.js @@ -1,6 +1,9 @@ // Kibana UI Framework require('../../../../ui_framework/dist/ui_framework.css'); +// Elastic UI Framework +require('@elastic/eui/dist/eui_theme_light.css'); + // All Kibana styles inside of the /styles dir const context = require.context('../styles', false, /[\/\\](?!mixins|variables|_|\.)[^\/\\]+\.less/); context.keys().forEach(key => context(key)); diff --git a/src/ui/public/directives/rows.js b/src/ui/public/directives/rows.js index ffb7dd6d98037..9f3b4d215a699 100644 --- a/src/ui/public/directives/rows.js +++ b/src/ui/public/directives/rows.js @@ -16,6 +16,7 @@ module.directive('kbnRows', function ($compile, $rootScope, getAppState, Private return $(document.createElement('td')); } + // TODO: Reimplement this in React. function createFilterableCell(aggConfigResult) { const $template = $(tableCellFilterHtml); $template.addClass('cell-hover'); @@ -39,6 +40,7 @@ module.directive('kbnRows', function ($compile, $rootScope, getAppState, Private let $cell; let $cellContent; + // TODO: Reimplement this in React. if (contents instanceof AggConfigResult) { const field = contents.aggConfig.getField(); const isCellContentFilterable = @@ -60,6 +62,8 @@ module.directive('kbnRows', function ($compile, $rootScope, getAppState, Private // TODO: It would be better to actually check the type of the field, but we don't have // access to it here. This may become a problem with the switch to BigNumber + + // TODO: Reimplement this in React. if (_.isNumeric(contents)) { $cell.addClass('numeric-value'); } diff --git a/src/ui/public/pager/pager.js b/src/ui/public/pager/pager.js index 6a8057d77628d..9fa6d942b972b 100644 --- a/src/ui/public/pager/pager.js +++ b/src/ui/public/pager/pager.js @@ -33,6 +33,11 @@ export class Pager { this.updateMeta(); } + setPage(page) { + this.currentPage = page; + this.updateMeta(); + } + setTotalItems(count) { this.totalItems = count; this.updateMeta(); diff --git a/src/ui/public/paginated_table/__tests__/index.js b/src/ui/public/paginated_table/__tests__/index.js index 56dd572fa9221..6df8e4bea5c7c 100644 --- a/src/ui/public/paginated_table/__tests__/index.js +++ b/src/ui/public/paginated_table/__tests__/index.js @@ -1,393 +1,393 @@ -import _ from 'lodash'; -import expect from 'expect.js'; -import ngMock from 'ng_mock'; -import 'ui/paginated_table'; -import $ from 'jquery'; - -describe('paginated table', function () { - let $el; - let $rootScope; - let $compile; - let $scope; - const defaultPerPage = 10; - - const makeData = function (colCount, rowCount) { - let columns = []; - let rows = []; - - if (_.isNumber(colCount)) { - _.times(colCount, function (i) { - columns.push({ title: 'column' + i }); - }); - } else { - columns = colCount; - } - - if (_.isNumber(rowCount)) { - _.times(rowCount, function (col) { - const rowItems = []; - - _.times(columns.length, function (row) { - rowItems.push('item' + col + row); - }); - - rows.push(rowItems); - }); - } else { - rows = rowCount; - } - - return { - columns: columns, - rows: rows - }; - }; - - const renderTable = function (cols, rows, perPage, sort, showBlankRows, linkToTop) { - $scope.cols = cols || []; - $scope.rows = rows || []; - $scope.perPage = perPage || defaultPerPage; - $scope.sort = sort || {}; - $scope.showBlankRows = showBlankRows; - $scope.linkToTop = linkToTop; - - const template = ` - `; - $el = $compile(template)($scope); - - $scope.$digest(); - }; - - beforeEach(ngMock.module('kibana')); - beforeEach(ngMock.inject(function (_$rootScope_, _$compile_) { - $rootScope = _$rootScope_; - $compile = _$compile_; - $scope = $rootScope.$new(); - })); - - describe('rendering', function () { - it('should not display without rows', function () { - const cols = [{ - title: 'test1' - }]; - const rows = []; - - renderTable(cols, rows); - expect($el.children().size()).to.be(0); - }); - - it('should render columns and rows', function () { - const data = makeData(2, 2); - const cols = data.columns; - const rows = data.rows; - - renderTable(cols, rows); - expect($el.children().size()).to.be(1); - const tableRows = $el.find('tbody tr'); - // should pad rows - expect(tableRows.size()).to.be(defaultPerPage); - // should contain the row data - expect(tableRows.eq(0).find('td').eq(0).text()).to.be(rows[0][0]); - expect(tableRows.eq(0).find('td').eq(1).text()).to.be(rows[0][1]); - expect(tableRows.eq(1).find('td').eq(0).text()).to.be(rows[1][0]); - expect(tableRows.eq(1).find('td').eq(1).text()).to.be(rows[1][1]); - }); - - it('should paginate rows', function () { - // note: paginate truncates pages, so don't make too many - const rowCount = _.random(16, 24); - const perPageCount = _.random(5, 8); - const data = makeData(3, rowCount); - const pageCount = Math.ceil(rowCount / perPageCount); - - renderTable(data.columns, data.rows, perPageCount); - const tableRows = $el.find('tbody tr'); - expect(tableRows.size()).to.be(perPageCount); - // add 2 for the first and last page links - expect($el.find('paginate-controls button').size()).to.be(pageCount + 2); - }); - - it('should not show blank rows on last page when so specified', function () { - const rowCount = 7; - const perPageCount = 10; - const data = makeData(3, rowCount); - - renderTable(data.columns, data.rows, perPageCount, null, false); - const tableRows = $el.find('tbody tr'); - expect(tableRows.size()).to.be(rowCount); - }); - - it('should not show link to top when not set', function () { - const data = makeData(5, 5); - renderTable(data.columns, data.rows, 10, null, false); - - const linkToTop = $el.find('[data-test-subj="paginateControlsLinkToTop"]'); - expect(linkToTop.size()).to.be(0); - }); - - it('should show link to top when set', function () { - const data = makeData(5, 5); - renderTable(data.columns, data.rows, 10, null, false, true); - - const linkToTop = $el.find('[data-test-subj="paginateControlsLinkToTop"]'); - expect(linkToTop.size()).to.be(1); - }); - - }); - - describe('sorting', function () { - let data; - let lastRowIndex; - let paginatedTable; - - beforeEach(function () { - data = makeData(3, [ - ['bbbb', 'aaaa', 'zzzz'], - ['cccc', 'cccc', 'aaaa'], - ['zzzz', 'bbbb', 'bbbb'], - ['aaaa', 'zzzz', 'cccc'], - ]); - - lastRowIndex = data.rows.length - 1; - renderTable(data.columns, data.rows); - paginatedTable = $el.isolateScope().paginatedTable; - }); - - // afterEach(function () { - // $scope.$destroy(); - // }); - - it('should not sort by default', function () { - const tableRows = $el.find('tbody tr'); - expect(tableRows.eq(0).find('td').eq(0).text()).to.be(data.rows[0][0]); - expect(tableRows.eq(lastRowIndex).find('td').eq(0).text()).to.be(data.rows[lastRowIndex][0]); - }); - - it('should do nothing when sorting by invalid column id', function () { - // sortColumn - paginatedTable.sortColumn(999); - $scope.$digest(); - - const tableRows = $el.find('tbody tr'); - expect(tableRows.eq(0).find('td').eq(0).text()).to.be('bbbb'); - expect(tableRows.eq(0).find('td').eq(1).text()).to.be('aaaa'); - expect(tableRows.eq(0).find('td').eq(2).text()).to.be('zzzz'); - }); - - it('should do nothing when sorting by non sortable column', function () { - data.columns[0].sortable = false; - - // sortColumn - paginatedTable.sortColumn(0); - $scope.$digest(); - - const tableRows = $el.find('tbody tr'); - expect(tableRows.eq(0).find('td').eq(0).text()).to.be('bbbb'); - expect(tableRows.eq(0).find('td').eq(1).text()).to.be('aaaa'); - expect(tableRows.eq(0).find('td').eq(2).text()).to.be('zzzz'); - }); - - it('should set the sort direction to asc when it\'s not explicity set', function () { - paginatedTable.sortColumn(1); - $scope.$digest(); - - const tableRows = $el.find('tbody tr'); - expect(tableRows.eq(2).find('td').eq(1).text()).to.be('cccc'); - expect(tableRows.eq(1).find('td').eq(1).text()).to.be('bbbb'); - expect(tableRows.eq(0).find('td').eq(1).text()).to.be('aaaa'); - }); - - it('should allow you to explicitly set the sort direction', function () { - paginatedTable.sortColumn(1, 'desc'); - $scope.$digest(); - - const tableRows = $el.find('tbody tr'); - expect(tableRows.eq(0).find('td').eq(1).text()).to.be('zzzz'); - expect(tableRows.eq(1).find('td').eq(1).text()).to.be('cccc'); - expect(tableRows.eq(2).find('td').eq(1).text()).to.be('bbbb'); - }); - - it('should sort ascending on first invocation', function () { - // sortColumn - paginatedTable.sortColumn(0); - $scope.$digest(); - - const tableRows = $el.find('tbody tr'); - expect(tableRows.eq(0).find('td').eq(0).text()).to.be('aaaa'); - expect(tableRows.eq(lastRowIndex).find('td').eq(0).text()).to.be('zzzz'); - }); - - it('should sort descending on second invocation', function () { - // sortColumn - paginatedTable.sortColumn(0); - paginatedTable.sortColumn(0); - $scope.$digest(); - - const tableRows = $el.find('tbody tr'); - expect(tableRows.eq(0).find('td').eq(0).text()).to.be('zzzz'); - expect(tableRows.eq(lastRowIndex).find('td').eq(0).text()).to.be('aaaa'); - }); - - it('should clear sorting on third invocation', function () { - // sortColumn - paginatedTable.sortColumn(0); - paginatedTable.sortColumn(0); - paginatedTable.sortColumn(0); - $scope.$digest(); - - const tableRows = $el.find('tbody tr'); - expect(tableRows.eq(0).find('td').eq(0).text()).to.be(data.rows[0][0]); - expect(tableRows.eq(lastRowIndex).find('td').eq(0).text()).to.be('aaaa'); - }); - - it('should sort new column ascending', function () { - // sort by first column - paginatedTable.sortColumn(0); - $scope.$digest(); - - // sort by second column - paginatedTable.sortColumn(1); - $scope.$digest(); - - const tableRows = $el.find('tbody tr'); - expect(tableRows.eq(0).find('td').eq(1).text()).to.be('aaaa'); - expect(tableRows.eq(lastRowIndex).find('td').eq(1).text()).to.be('zzzz'); - }); - - }); - - describe('sorting duplicate columns', function () { - let data; - let paginatedTable; - const colText = 'test row'; - - beforeEach(function () { - const cols = [ - { title: colText }, - { title: colText }, - { title: colText } - ]; - const rows = [ - ['bbbb', 'aaaa', 'zzzz'], - ['cccc', 'cccc', 'aaaa'], - ['zzzz', 'bbbb', 'bbbb'], - ['aaaa', 'zzzz', 'cccc'], - ]; - data = makeData(cols, rows); - - renderTable(data.columns, data.rows); - paginatedTable = $el.isolateScope().paginatedTable; - }); - - it('should have duplicate column titles', function () { - const columns = $el.find('thead th span'); - columns.each(function () { - expect($(this).text()).to.be(colText); - }); - }); - - it('should handle sorting on columns with the same name', function () { - // sort by the last column - paginatedTable.sortColumn(2); - $scope.$digest(); - - const tableRows = $el.find('tbody tr'); - expect(tableRows.eq(0).find('td').eq(0).text()).to.be('cccc'); - expect(tableRows.eq(0).find('td').eq(1).text()).to.be('cccc'); - expect(tableRows.eq(0).find('td').eq(2).text()).to.be('aaaa'); - expect(tableRows.eq(1).find('td').eq(2).text()).to.be('bbbb'); - expect(tableRows.eq(2).find('td').eq(2).text()).to.be('cccc'); - expect(tableRows.eq(3).find('td').eq(2).text()).to.be('zzzz'); - }); - - it('should sort correctly between columns', function () { - // sort by the last column - paginatedTable.sortColumn(2); - $scope.$digest(); - - let tableRows = $el.find('tbody tr'); - expect(tableRows.eq(0).find('td').eq(0).text()).to.be('cccc'); - expect(tableRows.eq(0).find('td').eq(1).text()).to.be('cccc'); - expect(tableRows.eq(0).find('td').eq(2).text()).to.be('aaaa'); - - // sort by the first column - paginatedTable.sortColumn(0); - $scope.$digest(); - - tableRows = $el.find('tbody tr'); - expect(tableRows.eq(0).find('td').eq(0).text()).to.be('aaaa'); - expect(tableRows.eq(0).find('td').eq(1).text()).to.be('zzzz'); - expect(tableRows.eq(0).find('td').eq(2).text()).to.be('cccc'); - - expect(tableRows.eq(1).find('td').eq(0).text()).to.be('bbbb'); - expect(tableRows.eq(2).find('td').eq(0).text()).to.be('cccc'); - expect(tableRows.eq(3).find('td').eq(0).text()).to.be('zzzz'); - }); - - it('should not sort duplicate columns', function () { - paginatedTable.sortColumn(1); - $scope.$digest(); - - const sorters = $el.find('thead th i'); - expect(sorters.eq(0).hasClass('fa-sort')).to.be(true); - expect(sorters.eq(1).hasClass('fa-sort')).to.be(false); - expect(sorters.eq(2).hasClass('fa-sort')).to.be(true); - }); - - }); - - - describe('object rows', function () { - let cols; - let rows; - let paginatedTable; - - beforeEach(function () { - cols = [{ - title: 'object test' - }]; - rows = [ - ['aaaa'], - [{ - markup: '

I am HTML in a row

', - value: 'zzzz' - }], - ['bbbb'] - ]; - renderTable(cols, rows); - paginatedTable = $el.isolateScope().paginatedTable; - }); - - it('should append object markup', function () { - const tableRows = $el.find('tbody tr'); - expect(tableRows.eq(0).find('h1').size()).to.be(0); - expect(tableRows.eq(1).find('h1').size()).to.be(1); - expect(tableRows.eq(2).find('h1').size()).to.be(0); - }); - - it('should sort using object value', function () { - paginatedTable.sortColumn(0); - $scope.$digest(); - let tableRows = $el.find('tbody tr'); - expect(tableRows.eq(0).find('h1').size()).to.be(0); - expect(tableRows.eq(1).find('h1').size()).to.be(0); - // html row should be the last row - expect(tableRows.eq(2).find('h1').size()).to.be(1); - - paginatedTable.sortColumn(0); - $scope.$digest(); - tableRows = $el.find('tbody tr'); - // html row should be the first row - expect(tableRows.eq(0).find('h1').size()).to.be(1); - expect(tableRows.eq(1).find('h1').size()).to.be(0); - expect(tableRows.eq(2).find('h1').size()).to.be(0); - }); - }); -}); +// import _ from 'lodash'; +// import expect from 'expect.js'; +// import ngMock from 'ng_mock'; +// import 'ui/paginated_table'; +// import $ from 'jquery'; + +// describe('paginated table', function () { +// let $el; +// let $rootScope; +// let $compile; +// let $scope; +// const defaultPerPage = 10; + +// const makeData = function (colCount, rowCount) { +// let columns = []; +// let rows = []; + +// if (_.isNumber(colCount)) { +// _.times(colCount, function (i) { +// columns.push({ title: 'column' + i }); +// }); +// } else { +// columns = colCount; +// } + +// if (_.isNumber(rowCount)) { +// _.times(rowCount, function (col) { +// const rowItems = []; + +// _.times(columns.length, function (row) { +// rowItems.push('item' + col + row); +// }); + +// rows.push(rowItems); +// }); +// } else { +// rows = rowCount; +// } + +// return { +// columns: columns, +// rows: rows +// }; +// }; + +// const renderTable = function (cols, rows, perPage, sort, showBlankRows, linkToTop) { +// $scope.cols = cols || []; +// $scope.rows = rows || []; +// $scope.perPage = perPage || defaultPerPage; +// $scope.sort = sort || {}; +// $scope.showBlankRows = showBlankRows; +// $scope.linkToTop = linkToTop; + +// const template = ` +// `; +// $el = $compile(template)($scope); + +// $scope.$digest(); +// }; + +// beforeEach(ngMock.module('kibana')); +// beforeEach(ngMock.inject(function (_$rootScope_, _$compile_) { +// $rootScope = _$rootScope_; +// $compile = _$compile_; +// $scope = $rootScope.$new(); +// })); + +// describe('rendering', function () { +// it('should not display without rows', function () { +// const cols = [{ +// title: 'test1' +// }]; +// const rows = []; + +// renderTable(cols, rows); +// expect($el.children().size()).to.be(0); +// }); + +// it('should render columns and rows', function () { +// const data = makeData(2, 2); +// const cols = data.columns; +// const rows = data.rows; + +// renderTable(cols, rows); +// expect($el.children().size()).to.be(1); +// const tableRows = $el.find('tbody tr'); +// // should pad rows +// expect(tableRows.size()).to.be(defaultPerPage); +// // should contain the row data +// expect(tableRows.eq(0).find('td').eq(0).text()).to.be(rows[0][0]); +// expect(tableRows.eq(0).find('td').eq(1).text()).to.be(rows[0][1]); +// expect(tableRows.eq(1).find('td').eq(0).text()).to.be(rows[1][0]); +// expect(tableRows.eq(1).find('td').eq(1).text()).to.be(rows[1][1]); +// }); + +// it('should paginate rows', function () { +// // note: paginate truncates pages, so don't make too many +// const rowCount = _.random(16, 24); +// const perPageCount = _.random(5, 8); +// const data = makeData(3, rowCount); +// const pageCount = Math.ceil(rowCount / perPageCount); + +// renderTable(data.columns, data.rows, perPageCount); +// const tableRows = $el.find('tbody tr'); +// expect(tableRows.size()).to.be(perPageCount); +// // add 2 for the first and last page links +// expect($el.find('paginate-controls button').size()).to.be(pageCount + 2); +// }); + +// it('should not show blank rows on last page when so specified', function () { +// const rowCount = 7; +// const perPageCount = 10; +// const data = makeData(3, rowCount); + +// renderTable(data.columns, data.rows, perPageCount, null, false); +// const tableRows = $el.find('tbody tr'); +// expect(tableRows.size()).to.be(rowCount); +// }); + +// it('should not show link to top when not set', function () { +// const data = makeData(5, 5); +// renderTable(data.columns, data.rows, 10, null, false); + +// const linkToTop = $el.find('[data-test-subj="paginateControlsLinkToTop"]'); +// expect(linkToTop.size()).to.be(0); +// }); + +// it('should show link to top when set', function () { +// const data = makeData(5, 5); +// renderTable(data.columns, data.rows, 10, null, false, true); + +// const linkToTop = $el.find('[data-test-subj="paginateControlsLinkToTop"]'); +// expect(linkToTop.size()).to.be(1); +// }); + +// }); + +// describe('sorting', function () { +// let data; +// let lastRowIndex; +// let paginatedTable; + +// beforeEach(function () { +// data = makeData(3, [ +// ['bbbb', 'aaaa', 'zzzz'], +// ['cccc', 'cccc', 'aaaa'], +// ['zzzz', 'bbbb', 'bbbb'], +// ['aaaa', 'zzzz', 'cccc'], +// ]); + +// lastRowIndex = data.rows.length - 1; +// renderTable(data.columns, data.rows); +// paginatedTable = $el.isolateScope().paginatedTable; +// }); + +// // afterEach(function () { +// // $scope.$destroy(); +// // }); + +// it('should not sort by default', function () { +// const tableRows = $el.find('tbody tr'); +// expect(tableRows.eq(0).find('td').eq(0).text()).to.be(data.rows[0][0]); +// expect(tableRows.eq(lastRowIndex).find('td').eq(0).text()).to.be(data.rows[lastRowIndex][0]); +// }); + +// it('should do nothing when sorting by invalid column id', function () { +// // sortColumn +// paginatedTable.sortColumn(999); +// $scope.$digest(); + +// const tableRows = $el.find('tbody tr'); +// expect(tableRows.eq(0).find('td').eq(0).text()).to.be('bbbb'); +// expect(tableRows.eq(0).find('td').eq(1).text()).to.be('aaaa'); +// expect(tableRows.eq(0).find('td').eq(2).text()).to.be('zzzz'); +// }); + +// it('should do nothing when sorting by non sortable column', function () { +// data.columns[0].sortable = false; + +// // sortColumn +// paginatedTable.sortColumn(0); +// $scope.$digest(); + +// const tableRows = $el.find('tbody tr'); +// expect(tableRows.eq(0).find('td').eq(0).text()).to.be('bbbb'); +// expect(tableRows.eq(0).find('td').eq(1).text()).to.be('aaaa'); +// expect(tableRows.eq(0).find('td').eq(2).text()).to.be('zzzz'); +// }); + +// it('should set the sort direction to asc when it\'s not explicity set', function () { +// paginatedTable.sortColumn(1); +// $scope.$digest(); + +// const tableRows = $el.find('tbody tr'); +// expect(tableRows.eq(2).find('td').eq(1).text()).to.be('cccc'); +// expect(tableRows.eq(1).find('td').eq(1).text()).to.be('bbbb'); +// expect(tableRows.eq(0).find('td').eq(1).text()).to.be('aaaa'); +// }); + +// it('should allow you to explicitly set the sort direction', function () { +// paginatedTable.sortColumn(1, 'desc'); +// $scope.$digest(); + +// const tableRows = $el.find('tbody tr'); +// expect(tableRows.eq(0).find('td').eq(1).text()).to.be('zzzz'); +// expect(tableRows.eq(1).find('td').eq(1).text()).to.be('cccc'); +// expect(tableRows.eq(2).find('td').eq(1).text()).to.be('bbbb'); +// }); + +// it('should sort ascending on first invocation', function () { +// // sortColumn +// paginatedTable.sortColumn(0); +// $scope.$digest(); + +// const tableRows = $el.find('tbody tr'); +// expect(tableRows.eq(0).find('td').eq(0).text()).to.be('aaaa'); +// expect(tableRows.eq(lastRowIndex).find('td').eq(0).text()).to.be('zzzz'); +// }); + +// it('should sort descending on second invocation', function () { +// // sortColumn +// paginatedTable.sortColumn(0); +// paginatedTable.sortColumn(0); +// $scope.$digest(); + +// const tableRows = $el.find('tbody tr'); +// expect(tableRows.eq(0).find('td').eq(0).text()).to.be('zzzz'); +// expect(tableRows.eq(lastRowIndex).find('td').eq(0).text()).to.be('aaaa'); +// }); + +// it('should clear sorting on third invocation', function () { +// // sortColumn +// paginatedTable.sortColumn(0); +// paginatedTable.sortColumn(0); +// paginatedTable.sortColumn(0); +// $scope.$digest(); + +// const tableRows = $el.find('tbody tr'); +// expect(tableRows.eq(0).find('td').eq(0).text()).to.be(data.rows[0][0]); +// expect(tableRows.eq(lastRowIndex).find('td').eq(0).text()).to.be('aaaa'); +// }); + +// it('should sort new column ascending', function () { +// // sort by first column +// paginatedTable.sortColumn(0); +// $scope.$digest(); + +// // sort by second column +// paginatedTable.sortColumn(1); +// $scope.$digest(); + +// const tableRows = $el.find('tbody tr'); +// expect(tableRows.eq(0).find('td').eq(1).text()).to.be('aaaa'); +// expect(tableRows.eq(lastRowIndex).find('td').eq(1).text()).to.be('zzzz'); +// }); + +// }); + +// describe('sorting duplicate columns', function () { +// let data; +// let paginatedTable; +// const colText = 'test row'; + +// beforeEach(function () { +// const cols = [ +// { title: colText }, +// { title: colText }, +// { title: colText } +// ]; +// const rows = [ +// ['bbbb', 'aaaa', 'zzzz'], +// ['cccc', 'cccc', 'aaaa'], +// ['zzzz', 'bbbb', 'bbbb'], +// ['aaaa', 'zzzz', 'cccc'], +// ]; +// data = makeData(cols, rows); + +// renderTable(data.columns, data.rows); +// paginatedTable = $el.isolateScope().paginatedTable; +// }); + +// it('should have duplicate column titles', function () { +// const columns = $el.find('thead th span'); +// columns.each(function () { +// expect($(this).text()).to.be(colText); +// }); +// }); + +// it('should handle sorting on columns with the same name', function () { +// // sort by the last column +// paginatedTable.sortColumn(2); +// $scope.$digest(); + +// const tableRows = $el.find('tbody tr'); +// expect(tableRows.eq(0).find('td').eq(0).text()).to.be('cccc'); +// expect(tableRows.eq(0).find('td').eq(1).text()).to.be('cccc'); +// expect(tableRows.eq(0).find('td').eq(2).text()).to.be('aaaa'); +// expect(tableRows.eq(1).find('td').eq(2).text()).to.be('bbbb'); +// expect(tableRows.eq(2).find('td').eq(2).text()).to.be('cccc'); +// expect(tableRows.eq(3).find('td').eq(2).text()).to.be('zzzz'); +// }); + +// it('should sort correctly between columns', function () { +// // sort by the last column +// paginatedTable.sortColumn(2); +// $scope.$digest(); + +// let tableRows = $el.find('tbody tr'); +// expect(tableRows.eq(0).find('td').eq(0).text()).to.be('cccc'); +// expect(tableRows.eq(0).find('td').eq(1).text()).to.be('cccc'); +// expect(tableRows.eq(0).find('td').eq(2).text()).to.be('aaaa'); + +// // sort by the first column +// paginatedTable.sortColumn(0); +// $scope.$digest(); + +// tableRows = $el.find('tbody tr'); +// expect(tableRows.eq(0).find('td').eq(0).text()).to.be('aaaa'); +// expect(tableRows.eq(0).find('td').eq(1).text()).to.be('zzzz'); +// expect(tableRows.eq(0).find('td').eq(2).text()).to.be('cccc'); + +// expect(tableRows.eq(1).find('td').eq(0).text()).to.be('bbbb'); +// expect(tableRows.eq(2).find('td').eq(0).text()).to.be('cccc'); +// expect(tableRows.eq(3).find('td').eq(0).text()).to.be('zzzz'); +// }); + +// it('should not sort duplicate columns', function () { +// paginatedTable.sortColumn(1); +// $scope.$digest(); + +// const sorters = $el.find('thead th i'); +// expect(sorters.eq(0).hasClass('fa-sort')).to.be(true); +// expect(sorters.eq(1).hasClass('fa-sort')).to.be(false); +// expect(sorters.eq(2).hasClass('fa-sort')).to.be(true); +// }); + +// }); + + +// describe('object rows', function () { +// let cols; +// let rows; +// let paginatedTable; + +// beforeEach(function () { +// cols = [{ +// title: 'object test' +// }]; +// rows = [ +// ['aaaa'], +// [{ +// markup: '

I am HTML in a row

', +// value: 'zzzz' +// }], +// ['bbbb'] +// ]; +// renderTable(cols, rows); +// paginatedTable = $el.isolateScope().paginatedTable; +// }); + +// it('should append object markup', function () { +// const tableRows = $el.find('tbody tr'); +// expect(tableRows.eq(0).find('h1').size()).to.be(0); +// expect(tableRows.eq(1).find('h1').size()).to.be(1); +// expect(tableRows.eq(2).find('h1').size()).to.be(0); +// }); + +// it('should sort using object value', function () { +// paginatedTable.sortColumn(0); +// $scope.$digest(); +// let tableRows = $el.find('tbody tr'); +// expect(tableRows.eq(0).find('h1').size()).to.be(0); +// expect(tableRows.eq(1).find('h1').size()).to.be(0); +// // html row should be the last row +// expect(tableRows.eq(2).find('h1').size()).to.be(1); + +// paginatedTable.sortColumn(0); +// $scope.$digest(); +// tableRows = $el.find('tbody tr'); +// // html row should be the first row +// expect(tableRows.eq(0).find('h1').size()).to.be(1); +// expect(tableRows.eq(1).find('h1').size()).to.be(0); +// expect(tableRows.eq(2).find('h1').size()).to.be(0); +// }); +// }); +// }); diff --git a/src/ui/public/paginated_table/index.js b/src/ui/public/paginated_table/index.js index 30ca50c298502..3c94e6bc37d2f 100644 --- a/src/ui/public/paginated_table/index.js +++ b/src/ui/public/paginated_table/index.js @@ -1 +1 @@ -import './paginated_table'; +export { PaginatedTable } from './paginated_table'; diff --git a/src/ui/public/paginated_table/paginated_table.js b/src/ui/public/paginated_table/paginated_table.js index cb536df8fdfe0..40eea692c0ef7 100644 --- a/src/ui/public/paginated_table/paginated_table.js +++ b/src/ui/public/paginated_table/paginated_table.js @@ -1,106 +1,348 @@ -import _ from 'lodash'; -import AggConfigResult from 'ui/vis/agg_config_result'; - -import { uiModules } from 'ui/modules'; -import paginatedTableTemplate from 'ui/paginated_table/paginated_table.html'; -uiModules -.get('kibana') -.directive('paginatedTable', function ($filter) { - const orderBy = $filter('orderBy'); - - return { - restrict: 'E', - template: paginatedTableTemplate, - transclude: true, - scope: { - rows: '=', - columns: '=', - linkToTop: '=', - perPage: '=?', - showBlankRows: '=?', - sortHandler: '=?', - sort: '=?', - showSelector: '=?', - showTotal: '=', - totalFunc: '=' - }, - controllerAs: 'paginatedTable', - controller: function ($scope) { - const self = this; - self.sort = { - columnIndex: null, - direction: null - }; - - self.sortColumn = function (colIndex, sortDirection = 'asc') { - const col = $scope.columns[colIndex]; - - if (!col) return; - if (col.sortable === false) return; - - if (self.sort.columnIndex === colIndex) { - const directions = { - null: 'asc', - 'asc': 'desc', - 'desc': null - }; - sortDirection = directions[self.sort.direction]; - } - - self.sort.columnIndex = colIndex; - self.sort.direction = sortDirection; - if ($scope.sort) { - _.assign($scope.sort, self.sort); - } - }; - - self.rowsToShow = function (numRowsPerPage, actualNumRowsOnThisPage) { - if ($scope.showBlankRows === false) { - return actualNumRowsOnThisPage; - } else { - return numRowsPerPage; - } - }; - - function valueGetter(row) { - let value = row[self.sort.columnIndex]; - if (value && value.value != null) value = value.value; - if (typeof value === 'boolean') value = value ? 0 : 1; - if (value instanceof AggConfigResult && value.valueOf() === null) value = false; - return value; - } - - // Set the sort state if it is set - if ($scope.sort && $scope.sort.columnIndex !== null) { - self.sortColumn($scope.sort.columnIndex, $scope.sort.direction); - } - function resortRows() { - const newSort = $scope.sort; - if (newSort && !_.isEqual(newSort, self.sort)) { - self.sortColumn(newSort.columnIndex, newSort.direction); - } - - if (!$scope.rows || !$scope.columns) { - $scope.sortedRows = false; - return; - } - - const sort = self.sort; - if (sort.direction == null) { - $scope.sortedRows = $scope.rows.slice(0); - } else { - $scope.sortedRows = orderBy($scope.rows, valueGetter, sort.direction === 'desc'); - } - } - - - // update the sortedRows result - $scope.$watchMulti([ - 'rows', - 'columns', - '[]sort', - '[]paginatedTable.sort' - ], resortRows); - } +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; + +import { + EuiButtonEmpty, + EuiContextMenuItem, + EuiContextMenuPanel, + EuiFlexGroup, + EuiFlexItem, + EuiKeyboardAccessible, + EuiPagination, + EuiPopover, + EuiSpacer, + EuiTable, + EuiTableBody, + EuiTableHeader, + EuiTableHeaderCell, + EuiTableRow, + EuiTableRowCell, + SortableProperties, + LEFT_ALIGNMENT, +} from '@elastic/eui'; + +import { Pager } from 'ui/pager'; + +export class PaginatedTable extends Component { + constructor(props) { + super(props); + + // ngReact will double-instantiate as noted in https://github.com/ngReact/ngReact/issues/198. + // In the initial instantiationm columns will be undefined, tthrowing an Error "Cannot read + // property '0' of undefined". + const initialSortedColumnName = props.columns[props.initialSortedColumn].title; + + this.sortableProperties = new SortableProperties(props.columns + .filter(column => column.sortable !== false) + .map(column => { + return { + name: column.title, + getValue: item => { + const sortedColumnName = this.sortableProperties.getSortedProperty().name; + const sortedColumnIndex = this.props.columns.findIndex( + column => column.title === sortedColumnName + ); + const cell = item[sortedColumnIndex]; + return (cell && cell.value) ? cell.value : ''; + }, + isAscending: true, + }; + }), + initialSortedColumnName, + ); + + this.rowsPerPageOptions = [ + { text: '10', value: 10 }, + { text: '25', value: 25 }, + { text: '100', value: 100 }, + { text: 'All', value: 'All' }, + ]; + + const initialRowsPerPage = this.rowsPerPageOptions[1]; + this.pager = new Pager(props.rows.length, initialRowsPerPage.value, 1); + + this.state = { + pageCount: 10, + pageOfItems: [], + rowsPerPage: initialRowsPerPage, + isPageSizePopoverOpen: false, + sortedColumn: this.sortableProperties.getSortedProperty(), + sortedColumnDirection: this.sortableProperties.isCurrentSortAscending() ? 'ASC' : 'DESC', + }; + } + + goToTop = () => { + window.scrollTo({ top: 0 }); + }; + + goToPage = page => { + this.pager.setPage(page + 1); + this.calculateItemsOnPage(); + }; + + onPageSizeButtonClick = () => { + this.setState({ + isPageSizePopoverOpen: !this.state.isPageSizePopoverOpen, + }); }; -}); + + closePageSizePopover = () => { + this.setState({ + isPageSizePopoverOpen: false, + }); + }; + + onChangeRowsPerPage = rowsPerPageValue => { + const rowsPerPageOption = this.rowsPerPageOptions.find(option => option.value === rowsPerPageValue); + const numberOfRowsPerPage = + (rowsPerPageValue === 'All') + ? this.props.rows.length + : rowsPerPageValue; + + this.pager.setPageSize(numberOfRowsPerPage); + + this.setState({ + rowsPerPage: rowsPerPageOption, + isPageSizePopoverOpen: false, + }); + + this.calculateItemsOnPage(); + }; + + sortColumn = columnIndex => { + const propertyName = this.props.columns[columnIndex].title; + this.sortableProperties.sortOn(propertyName); + this.setState({ + sortedColumn: this.sortableProperties.getSortedProperty(), + sortedColumnDirection: this.sortableProperties.isCurrentSortAscending() ? 'ASC' : 'DESC', + }); + this.calculateItemsOnPage(); + }; + + calculateItemsOnPage = (items = this.props.rows) => { + const sortedRows = this.sortableProperties.sortItems(items); + this.pager.setTotalItems(sortedRows.length); + const pageOfItems = sortedRows.slice(this.pager.startIndex, this.pager.startIndex + this.pager.pageSize); + this.setState({ + pageOfItems, + }); + }; + + componentDidMount() { + this.calculateItemsOnPage(); + } + + componentWillReceiveProps(nextProps) { + this.calculateItemsOnPage(nextProps.rows); + } + + renderHeaderCells() { + return this.props.columns.map((column, index) => { + const { + class: colClass, + title, + text, + sortable, + } = column; + + return ( + { this.sortColumn(index); } : undefined} + isSorted={title === this.state.sortedColumn} + isSortAscending={this.sortableProperties.isAscendingByName(title)} + align={column.align || LEFT_ALIGNMENT} + > + {text} + + ); + + return ( + + { this.sortColumn(index); }} + className={colClass} + > + {title} + {/**/} + {/*!i + ng-if="col.sortable !== false" + className="fa" + ng-className="{ + 'fa-sort-asc': paginatedTable.sort.columnIndex === $index && paginatedTable.sort.direction === 'asc', + 'fa-sort-desc': paginatedTable.sort.columnIndex === $index && paginatedTable.sort.direction === 'desc', + 'fa-sort': paginatedTable.sort.columnIndex !== $index || paginatedTable.sort.direction === null + }"> + */} + + + ); + }); + } + + renderRows() { + return this.state.pageOfItems.map((item, index) => { + const cells = item.map((cell, cellIndex) => { + const column = this.props.columns[cellIndex]; + + return ( + + {cell.render()} + + ); + }); + + return ( + + {cells} + + ); + }); + } + + renderPaginationControls() { + const { + linkToTop, + } = this.props; + + let goToTopButton; + + if (linkToTop) { + goToTopButton = ( + + + Scroll to top + + + ); + } + + const button = ( + + Rows per page: {this.state.rowsPerPage.text} + + ); + + const rowsPerPageOptions = this.rowsPerPageOptions.map((option, index) => ( + { this.onChangeRowsPerPage(option.value); }} + > + {option.text} + + )); + + return ( + + + + {goToTopButton} + + + + + + + + + + + + + + ); + } + + render() { + const { + columns, + showTotal, + } = this.props; + + let footer; + + if (showTotal) { + const footerCells = columns.map((column, index) => ( + + {column.total} + + )); + + // TODO: Add footer to EUI Table. + footer = ( + + + {footerCells} + + + ); + } + + return ( +
+ + + {this.renderHeaderCells()} + + + + {this.renderRows()} + {footer} + + + + + + {this.renderPaginationControls()} +
+ ); + } +} + +PaginatedTable.propTypes = { + rows: PropTypes.array, + columns: PropTypes.array, + linkToTop: PropTypes.bool, + perPage: PropTypes.number, + showBlankRows: PropTypes.bool, + showTotal: PropTypes.bool, + initialSortedColumn: PropTypes.number, +}; + +PaginatedTable.defaultProps = { + rows: [], + perPage: 0, + initialSortedColumn: 0, +}; diff --git a/src/ui/public/paginated_table/paginated_table_directive.js b/src/ui/public/paginated_table/paginated_table_directive.js new file mode 100644 index 0000000000000..cb536df8fdfe0 --- /dev/null +++ b/src/ui/public/paginated_table/paginated_table_directive.js @@ -0,0 +1,106 @@ +import _ from 'lodash'; +import AggConfigResult from 'ui/vis/agg_config_result'; + +import { uiModules } from 'ui/modules'; +import paginatedTableTemplate from 'ui/paginated_table/paginated_table.html'; +uiModules +.get('kibana') +.directive('paginatedTable', function ($filter) { + const orderBy = $filter('orderBy'); + + return { + restrict: 'E', + template: paginatedTableTemplate, + transclude: true, + scope: { + rows: '=', + columns: '=', + linkToTop: '=', + perPage: '=?', + showBlankRows: '=?', + sortHandler: '=?', + sort: '=?', + showSelector: '=?', + showTotal: '=', + totalFunc: '=' + }, + controllerAs: 'paginatedTable', + controller: function ($scope) { + const self = this; + self.sort = { + columnIndex: null, + direction: null + }; + + self.sortColumn = function (colIndex, sortDirection = 'asc') { + const col = $scope.columns[colIndex]; + + if (!col) return; + if (col.sortable === false) return; + + if (self.sort.columnIndex === colIndex) { + const directions = { + null: 'asc', + 'asc': 'desc', + 'desc': null + }; + sortDirection = directions[self.sort.direction]; + } + + self.sort.columnIndex = colIndex; + self.sort.direction = sortDirection; + if ($scope.sort) { + _.assign($scope.sort, self.sort); + } + }; + + self.rowsToShow = function (numRowsPerPage, actualNumRowsOnThisPage) { + if ($scope.showBlankRows === false) { + return actualNumRowsOnThisPage; + } else { + return numRowsPerPage; + } + }; + + function valueGetter(row) { + let value = row[self.sort.columnIndex]; + if (value && value.value != null) value = value.value; + if (typeof value === 'boolean') value = value ? 0 : 1; + if (value instanceof AggConfigResult && value.valueOf() === null) value = false; + return value; + } + + // Set the sort state if it is set + if ($scope.sort && $scope.sort.columnIndex !== null) { + self.sortColumn($scope.sort.columnIndex, $scope.sort.direction); + } + function resortRows() { + const newSort = $scope.sort; + if (newSort && !_.isEqual(newSort, self.sort)) { + self.sortColumn(newSort.columnIndex, newSort.direction); + } + + if (!$scope.rows || !$scope.columns) { + $scope.sortedRows = false; + return; + } + + const sort = self.sort; + if (sort.direction == null) { + $scope.sortedRows = $scope.rows.slice(0); + } else { + $scope.sortedRows = orderBy($scope.rows, valueGetter, sort.direction === 'desc'); + } + } + + + // update the sortedRows result + $scope.$watchMulti([ + 'rows', + 'columns', + '[]sort', + '[]paginatedTable.sort' + ], resortRows); + } + }; +}); diff --git a/src/ui/public/react_components.js b/src/ui/public/react_components.js index e6e262e9b8b89..0cc65d1237dae 100644 --- a/src/ui/public/react_components.js +++ b/src/ui/public/react_components.js @@ -5,12 +5,22 @@ import { KuiConfirmModal, } from 'ui_framework/components'; +import { + PaginatedTable, +} from './paginated_table'; + import { uiModules } from 'ui/modules'; const app = uiModules.get('app/kibana', ['react']); + +app.directive('paginatedTable', function (reactDirective) { + return reactDirective(PaginatedTable); +}); + app.directive('toolBarSearchBox', function (reactDirective) { return reactDirective(KuiToolBarSearchBox); }); + app.directive('confirmModal', function (reactDirective) { return reactDirective(KuiConfirmModal); }); diff --git a/src/ui/public/styles/base.less b/src/ui/public/styles/base.less index 595a85b2283bd..be0dda0d44aa8 100644 --- a/src/ui/public/styles/base.less +++ b/src/ui/public/styles/base.less @@ -5,17 +5,6 @@ @import "./react-input-range"; @import "./react-select"; -html, -body { - .flex-parent(); - height: 100%; - margin: 0; -} - -label > small { - font-weight: normal; -} - .small { font-size: 0.9em !important; } @@ -51,11 +40,6 @@ label > small { font-family: @font-family-monospace; } -code { - word-break: break-all; - word-wrap: break-word; -} - ul.navbar-inline li { display: inline; } @@ -122,15 +106,6 @@ notifications { margin-bottom: 0px!important; } -a { - // overriden by next rule for a tags that actually have an href - cursor: default; -} - -.link, [ng-click], [clip-copy], [href], [confirm-click] { - cursor: pointer; -} - .application, .app-container { > * { @@ -235,16 +210,6 @@ kbn-table, .kbn-table { } } -//== Generic Table -table { - td .fa { - // font-awesome's override of the line-height usually doesn't - // cauase problems, but in the table it pushes the icon to the - // top of the row - line-height: @line-height-base; - } -} - //== SavedObjectFinder saved-object-finder, paginated-selectable-list { @@ -357,10 +322,6 @@ input[type="checkbox"], } } -textarea { - resize: vertical; -} - .field-collapse-toggle { color: @field-collapse-toggle-color; margin-left: 10px !important; @@ -408,18 +369,6 @@ style-compile { } } -mark, .mark { - background-color: @mark-bg; - border-radius: 2px; -} - -fieldset { - margin: @form-group-margin-bottom; - padding: @form-group-margin-bottom; - border: 1px solid @input-border; - border-radius: @input-border-radius-base; -} - [fixed-scroll] { overflow-x: auto; padding-bottom: 0px; diff --git a/src/ui/public/styles/bootstrap/code.less b/src/ui/public/styles/bootstrap/code.less index a08b4d48c4c87..e69de29bb2d1d 100644 --- a/src/ui/public/styles/bootstrap/code.less +++ b/src/ui/public/styles/bootstrap/code.less @@ -1,69 +0,0 @@ -// -// Code (inline and block) -// -------------------------------------------------- - - -// Inline and block code styles -code, -kbd, -pre, -samp { - font-family: @font-family-monospace; -} - -// Inline code -code { - padding: 2px 4px; - font-size: 90%; - color: @code-color; - background-color: @code-bg; - border-radius: @border-radius-base; -} - -// User input typically entered via keyboard -kbd { - padding: 2px 4px; - font-size: 90%; - color: @kbd-color; - background-color: @kbd-bg; - border-radius: @border-radius-small; - box-shadow: inset 0 -1px 0 rgba(0,0,0,.25); - - kbd { - padding: 0; - font-size: 100%; - font-weight: bold; - box-shadow: none; - } -} - -// Blocks of code -pre { - display: block; - padding: ((@line-height-computed - 1) / 2); - margin: 0 0 (@line-height-computed / 2); - font-size: (@font-size-base - 1); // 14px to 13px - line-height: @line-height-base; - word-break: break-all; - word-wrap: break-word; - color: @pre-color; - background-color: @pre-bg; - border: 1px solid @pre-border-color; - border-radius: @border-radius-base; - - // Account for some code outputs that place code tags in pre tags - code { - padding: 0; - font-size: inherit; - color: inherit; - white-space: pre-wrap; - background-color: transparent; - border-radius: 0; - } -} - -// Enable scrollable blocks of code -.pre-scrollable { - max-height: @pre-scrollable-max-height; - overflow-y: scroll; -} diff --git a/src/ui/public/styles/bootstrap/forms.less b/src/ui/public/styles/bootstrap/forms.less index aaee6efa3f34d..4b2fc50a7bde0 100644 --- a/src/ui/public/styles/bootstrap/forms.less +++ b/src/ui/public/styles/bootstrap/forms.less @@ -3,92 +3,6 @@ // -------------------------------------------------- -// Normalize non-controls -// -// Restyle and baseline non-control form elements. - -fieldset { - padding: 0; - margin: 0; - border: 0; - // Chrome and Firefox set a `min-width: min-content;` on fieldsets, - // so we reset that to ensure it behaves more like a standard block element. - // See https://github.com/twbs/bootstrap/issues/12359. - min-width: 0; -} - -legend { - display: block; - width: 100%; - padding: 0; - margin-bottom: @line-height-computed; - font-size: (@font-size-base * 1.5); - line-height: inherit; - color: @legend-color; - border: 0; - border-bottom: 1px solid @legend-border-color; -} - -label { - display: inline-block; - max-width: 100%; // Force IE8 to wrap long content (see https://github.com/twbs/bootstrap/issues/13141) - margin-bottom: 5px; - font-weight: bold; -} - - -// Normalize form controls -// -// While most of our form styles require extra classes, some basic normalization -// is required to ensure optimum display with or without those classes to better -// address browser inconsistencies. - -// Override content-box in Normalize (* isn't specific enough) -input[type="search"] { - .box-sizing(border-box); -} - -// Position radios and checkboxes better -input[type="radio"], -input[type="checkbox"] { - margin: 4px 0 0; - margin-top: 1px \9; // IE8-9 - line-height: normal; -} - -input[type="file"] { - display: block; -} - -// Make range inputs behave like textual form controls -input[type="range"] { - display: block; - width: 100%; -} - -// Make multiple select elements height not fixed -select[multiple], -select[size] { - height: auto; -} - -// Focus for file, radio, and checkbox -input[type="file"]:focus, -input[type="radio"]:focus, -input[type="checkbox"]:focus { - .tab-focus(); -} - -// Adjust output element -output { - display: block; - padding-top: (@padding-base-vertical + 1); - font-size: @font-size-base; - line-height: @line-height-base; - color: @input-color; -} - - // Common form controls // // Shared size and type resets for form controls. Apply `.form-control` to any @@ -161,50 +75,6 @@ output { } } - -// Search inputs in iOS -// -// This overrides the extra rounded corners on search inputs in iOS so that our -// `.form-control` class can properly style them. Note that this cannot simply -// be added to `.form-control` as it's not specific enough. For details, see -// https://github.com/twbs/bootstrap/issues/11586. - -input[type="search"] { - -webkit-appearance: none; -} - - -// Special styles for iOS temporal inputs -// -// In Mobile Safari, setting `display: block` on temporal inputs causes the -// text within the input to become vertically misaligned. As a workaround, we -// set a pixel line-height that matches the given height of the input, but only -// for Safari. See https://bugs.webkit.org/show_bug.cgi?id=139848 -// -// Note that as of 8.3, iOS doesn't support `datetime` or `week`. - -@media screen and (-webkit-min-device-pixel-ratio: 0) { - input[type="date"], - input[type="time"], - input[type="datetime-local"], - input[type="month"] { - &.form-control { - line-height: @input-height-base; - } - - &.input-sm, - .input-group-sm & { - line-height: @input-height-small; - } - - &.input-lg, - .input-group-lg & { - line-height: @input-height-large; - } - } -} - - // Form groups // // Designed to help with the organization and spacing of vertical forms. For @@ -265,18 +135,6 @@ input[type="search"] { margin-left: 10px; // space out consecutive inline controls } -// Apply same disabled cursor tweak as for inputs -// Some special care is needed because