From 5b84b6904241ab5aea8a47780fb9b4ca25a46c88 Mon Sep 17 00:00:00 2001 From: Maryia Lapata Date: Mon, 2 Dec 2019 14:19:54 +0300 Subject: [PATCH] [Vis] TableVis uses local angular (#50759) * TableVis uses local angular * Clean up * Fix TS * Update angular_config * Fix export * Update render_app.ts * Cetralize ui deps * Fix loading KbnTableVisController in Dashboard * Fix graph * Rename const * Add table vis mocks * Fix kbn_top_nav * Add TS for test * Complete conversion paginated_table test to Jest * Convert table_vis_controller test to Jest * Convert table_vis_controller test to Jest * Create agg_table.test.ts * Fix mocha tests * Refactoring * Remove module dep * Remove LegacyDependenciesPlugin * Move file * Fix path * Fix path * Fix TS * Fix Jest test --- .../public/__tests__/table_vis_controller.js | 193 ----- .../public/agg_table/__tests__/agg_table.js | 16 +- .../agg_table/__tests__/agg_table_group.js | 16 +- .../public/agg_table/agg_table.js | 2 +- .../public/components/table_vis_options.tsx | 3 +- .../public/get_inner_angular.ts | 104 +++ .../vis_type_table/public/legacy.ts | 7 +- .../vis_type_table/public/legacy_imports.ts | 46 ++ .../__tests__/paginated_table.js | 419 ---------- .../paginated_table/paginated_table.test.ts | 713 ++++++++++++++++++ .../vis_type_table/public/plugin.ts | 6 +- .../vis_type_table/public/shim/index.ts | 20 - ...pendencies_plugin.ts => table_vis.mock.ts} | 36 +- .../public/table_vis_controller.test.ts | 257 +++++++ .../public/table_vis_fn.test.ts | 6 +- .../{shim => }/table_vis_legacy_module.ts | 25 +- .../public/table_vis_request_handler.ts | 3 +- .../vis_type_table/public/table_vis_type.ts | 13 +- .../vis_type_table/public/vis_controller.ts | 104 +++ src/legacy/ui/public/directives/paginate.js | 338 +++++---- .../new_platform/new_platform.karma_mock.js | 12 +- 21 files changed, 1473 insertions(+), 866 deletions(-) delete mode 100644 src/legacy/core_plugins/vis_type_table/public/__tests__/table_vis_controller.js create mode 100644 src/legacy/core_plugins/vis_type_table/public/get_inner_angular.ts create mode 100644 src/legacy/core_plugins/vis_type_table/public/legacy_imports.ts delete mode 100644 src/legacy/core_plugins/vis_type_table/public/paginated_table/__tests__/paginated_table.js create mode 100644 src/legacy/core_plugins/vis_type_table/public/paginated_table/paginated_table.test.ts delete mode 100644 src/legacy/core_plugins/vis_type_table/public/shim/index.ts rename src/legacy/core_plugins/vis_type_table/public/{shim/legacy_dependencies_plugin.ts => table_vis.mock.ts} (51%) create mode 100644 src/legacy/core_plugins/vis_type_table/public/table_vis_controller.test.ts rename src/legacy/core_plugins/vis_type_table/public/{shim => }/table_vis_legacy_module.ts (65%) create mode 100644 src/legacy/core_plugins/vis_type_table/public/vis_controller.ts diff --git a/src/legacy/core_plugins/vis_type_table/public/__tests__/table_vis_controller.js b/src/legacy/core_plugins/vis_type_table/public/__tests__/table_vis_controller.js deleted file mode 100644 index e22dd4caa6d011..00000000000000 --- a/src/legacy/core_plugins/vis_type_table/public/__tests__/table_vis_controller.js +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import $ from 'jquery'; -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { legacyResponseHandlerProvider } from 'ui/vis/response_handlers/legacy'; -import { Vis } from '../../../visualizations/public'; -import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; -import { AppStateProvider } from 'ui/state_management/app_state'; -import { tabifyAggResponse } from 'ui/agg_response/tabify'; - -import { tableVisTypeDefinition } from '../table_vis_type'; -import { setup as visualizationsSetup } from '../../../visualizations/public/np_ready/public/legacy'; - -describe('Table Vis - Controller', async function () { - let $rootScope; - let $compile; - let Private; - let $scope; - let $el; - let fixtures; - let AppState; - let tableAggResponse; - let tabifiedResponse; - - ngMock.inject(function () { - - visualizationsSetup.types.createBaseVisualization(tableVisTypeDefinition); - }); - - beforeEach(ngMock.module('kibana', 'kibana/table_vis')); - beforeEach( - ngMock.inject(function ($injector) { - Private = $injector.get('Private'); - $rootScope = $injector.get('$rootScope'); - $compile = $injector.get('$compile'); - fixtures = require('fixtures/fake_hierarchical_data'); - AppState = Private(AppStateProvider); - tableAggResponse = legacyResponseHandlerProvider().handler; - }) - ); - - function OneRangeVis(params) { - return new Vis(Private(FixturesStubbedLogstashIndexPatternProvider), { - type: 'table', - params: params || {}, - aggs: [ - { type: 'count', schema: 'metric' }, - { - type: 'range', - schema: 'bucket', - params: { - field: 'bytes', - ranges: [{ from: 0, to: 1000 }, { from: 1000, to: 2000 }], - }, - }, - ], - }); - } - - const dimensions = { - buckets: [ - { - accessor: 0, - }, - ], - metrics: [ - { - accessor: 1, - format: { id: 'range' }, - }, - ], - }; - - // basically a parameterized beforeEach - function initController(vis) { - vis.aggs.aggs.forEach(function (agg, i) { - agg.id = 'agg_' + (i + 1); - }); - - tabifiedResponse = tabifyAggResponse(vis.aggs, fixtures.oneRangeBucket); - $rootScope.vis = vis; - $rootScope.visParams = vis.params; - $rootScope.uiState = new AppState({ uiState: {} }).makeStateful('uiState'); - $rootScope.renderComplete = () => {}; - $rootScope.newScope = function (scope) { - $scope = scope; - }; - - $el = $('
') - .attr('ng-controller', 'KbnTableVisController') - .attr('ng-init', 'newScope(this)'); - - $compile($el)($rootScope); - } - - // put a response into the controller - function attachEsResponseToScope(resp) { - $rootScope.esResponse = resp; - $rootScope.$apply(); - } - - // remove the response from the controller - function removeEsResponseFromScope() { - delete $rootScope.esResponse; - $rootScope.renderComplete = () => {}; - $rootScope.$apply(); - } - - it('exposes #tableGroups and #hasSomeRows when a response is attached to scope', async function () { - const vis = new OneRangeVis(); - initController(vis); - - expect(!$scope.tableGroups).to.be.ok(); - expect(!$scope.hasSomeRows).to.be.ok(); - - attachEsResponseToScope(await tableAggResponse(tabifiedResponse, dimensions)); - - expect($scope.hasSomeRows).to.be(true); - expect($scope.tableGroups).to.have.property('tables'); - expect($scope.tableGroups.tables).to.have.length(1); - expect($scope.tableGroups.tables[0].columns).to.have.length(2); - expect($scope.tableGroups.tables[0].rows).to.have.length(2); - }); - - it('clears #tableGroups and #hasSomeRows when the response is removed', async function () { - const vis = new OneRangeVis(); - initController(vis); - - attachEsResponseToScope(await tableAggResponse(tabifiedResponse, dimensions)); - removeEsResponseFromScope(); - - expect(!$scope.hasSomeRows).to.be.ok(); - expect(!$scope.tableGroups).to.be.ok(); - }); - - it('sets the sort on the scope when it is passed as a vis param', async function () { - const sortObj = { - columnIndex: 1, - direction: 'asc', - }; - const vis = new OneRangeVis({ sort: sortObj }); - initController(vis); - - attachEsResponseToScope(await tableAggResponse(tabifiedResponse, dimensions)); - - expect($scope.sort.columnIndex).to.equal(sortObj.columnIndex); - expect($scope.sort.direction).to.equal(sortObj.direction); - }); - - it('sets #hasSomeRows properly if the table group is empty', async function () { - const vis = new OneRangeVis(); - initController(vis); - - tabifiedResponse.rows = []; - - attachEsResponseToScope(await tableAggResponse(tabifiedResponse, dimensions)); - - expect($scope.hasSomeRows).to.be(false); - expect(!$scope.tableGroups).to.be.ok(); - }); - - it('passes partialRows:true to tabify based on the vis params', function () { - const vis = new OneRangeVis({ showPartialRows: true }); - initController(vis); - - expect(vis.isHierarchical()).to.equal(true); - }); - - it('passes partialRows:false to tabify based on the vis params', function () { - const vis = new OneRangeVis({ showPartialRows: false }); - initController(vis); - - expect(vis.isHierarchical()).to.equal(false); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table.js b/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table.js index 2978856a3511d9..25a28333b07ff9 100644 --- a/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table.js +++ b/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table.js @@ -23,14 +23,15 @@ import ngMock from 'ng_mock'; import expect from '@kbn/expect'; import fixtures from 'fixtures/fake_hierarchical_data'; import sinon from 'sinon'; -import { legacyResponseHandlerProvider } from 'ui/vis/response_handlers/legacy'; +import { legacyResponseHandlerProvider, tabifyAggResponse, npStart } from '../../legacy_imports'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; -import { Vis } from '../../../../visualizations/public'; -import { tabifyAggResponse } from 'ui/agg_response/tabify'; import { round } from 'lodash'; +import { Vis } from '../../../../visualizations/public'; import { tableVisTypeDefinition } from '../../table_vis_type'; import { setup as visualizationsSetup } from '../../../../visualizations/public/np_ready/public/legacy'; +import { getAngularModule } from '../../get_inner_angular'; +import { initTableVisLegacyModule } from '../../table_vis_legacy_module'; describe('Table Vis - AggTable Directive', function () { let $rootScope; @@ -96,11 +97,18 @@ describe('Table Vis - AggTable Directive', function () { ); }; + const initLocalAngular = () => { + const tableVisModule = getAngularModule('kibana/table_vis', npStart.core); + initTableVisLegacyModule(tableVisModule); + }; + + beforeEach(initLocalAngular); + ngMock.inject(function () { visualizationsSetup.types.createBaseVisualization(tableVisTypeDefinition); }); - beforeEach(ngMock.module('kibana')); + beforeEach(ngMock.module('kibana/table_vis')); beforeEach( ngMock.inject(function ($injector, Private, config) { tableAggResponse = legacyResponseHandlerProvider().handler; diff --git a/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table_group.js b/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table_group.js index f4e3a8e36605cf..be981829ae909a 100644 --- a/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table_group.js +++ b/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table_group.js @@ -21,10 +21,11 @@ import $ from 'jquery'; import ngMock from 'ng_mock'; import expect from '@kbn/expect'; import fixtures from 'fixtures/fake_hierarchical_data'; -import { legacyResponseHandlerProvider } from 'ui/vis/response_handlers/legacy'; +import { legacyResponseHandlerProvider, tabifyAggResponse, npStart } from '../../legacy_imports'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; -import { Vis } from 'ui/vis'; -import { tabifyAggResponse } from 'ui/agg_response/tabify'; +import { Vis } from '../../../../visualizations/public'; +import { getAngularModule } from '../../get_inner_angular'; +import { initTableVisLegacyModule } from '../../table_vis_legacy_module'; describe('Table Vis - AggTableGroup Directive', function () { let $rootScope; @@ -52,7 +53,14 @@ describe('Table Vis - AggTableGroup Directive', function () { tabifiedData.threeTermBuckets = tabifyAggResponse(vis2.aggs, fixtures.threeTermBuckets); }; - beforeEach(ngMock.module('kibana')); + const initLocalAngular = () => { + const tableVisModule = getAngularModule('kibana/table_vis', npStart.core); + initTableVisLegacyModule(tableVisModule); + }; + + beforeEach(initLocalAngular); + + beforeEach(ngMock.module('kibana/table_vis')); beforeEach( ngMock.inject(function ($injector, Private) { // this is provided in table_vis_controller.js diff --git a/src/legacy/core_plugins/vis_type_table/public/agg_table/agg_table.js b/src/legacy/core_plugins/vis_type_table/public/agg_table/agg_table.js index 06cca15f885568..8bc275f5255bbe 100644 --- a/src/legacy/core_plugins/vis_type_table/public/agg_table/agg_table.js +++ b/src/legacy/core_plugins/vis_type_table/public/agg_table/agg_table.js @@ -18,7 +18,7 @@ */ import _ from 'lodash'; import aggTableTemplate from './agg_table.html'; -import { getFormat } from 'ui/visualize/loader/pipeline_helpers/utilities'; +import { getFormat } from '../legacy_imports'; import { i18n } from '@kbn/i18n'; export function KbnAggTable(config, RecursionHelper) { diff --git a/src/legacy/core_plugins/vis_type_table/public/components/table_vis_options.tsx b/src/legacy/core_plugins/vis_type_table/public/components/table_vis_options.tsx index fdcd531ad6930a..4d69af59b0c99f 100644 --- a/src/legacy/core_plugins/vis_type_table/public/components/table_vis_options.tsx +++ b/src/legacy/core_plugins/vis_type_table/public/components/table_vis_options.tsx @@ -23,8 +23,7 @@ import { EuiIconTip, EuiPanel } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { tabifyGetColumns } from 'ui/agg_response/tabify/_get_columns'; -import { VisOptionsProps } from 'ui/vis/editors/default'; +import { tabifyGetColumns, VisOptionsProps } from '../legacy_imports'; import { NumberInputOption, SwitchOption, diff --git a/src/legacy/core_plugins/vis_type_table/public/get_inner_angular.ts b/src/legacy/core_plugins/vis_type_table/public/get_inner_angular.ts new file mode 100644 index 00000000000000..9f3a8327c9ad94 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_table/public/get_inner_angular.ts @@ -0,0 +1,104 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +// inner angular imports +// these are necessary to bootstrap the local angular. +// They can stay even after NP cutover +import angular from 'angular'; +import 'ui/angular-bootstrap'; +import 'angular-recursion'; +import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular'; +import { CoreStart, LegacyCoreStart, IUiSettingsClient } from 'kibana/public'; +import { + PrivateProvider, + PaginateDirectiveProvider, + PaginateControlsDirectiveProvider, + watchMultiDecorator, + KbnAccessibleClickProvider, + StateManagementConfigProvider, + configureAppAngularModule, +} from './legacy_imports'; + +const thirdPartyAngularDependencies = ['ngSanitize', 'ui.bootstrap', 'RecursionHelper']; + +export function getAngularModule(name: string, core: CoreStart) { + const uiModule = getInnerAngular(name, core); + configureAppAngularModule(uiModule, core as LegacyCoreStart, true); + return uiModule; +} + +let initialized = false; + +export function getInnerAngular(name = 'kibana/table_vis', core: CoreStart) { + if (!initialized) { + createLocalPrivateModule(); + createLocalI18nModule(); + createLocalConfigModule(core.uiSettings); + createLocalPaginateModule(); + initialized = true; + } + return angular + .module(name, [ + ...thirdPartyAngularDependencies, + 'tableVisPaginate', + 'tableVisConfig', + 'tableVisPrivate', + 'tableVisI18n', + ]) + .config(watchMultiDecorator) + .directive('kbnAccessibleClick', KbnAccessibleClickProvider); +} + +function createLocalPrivateModule() { + angular.module('tableVisPrivate', []).provider('Private', PrivateProvider); +} + +function createLocalConfigModule(uiSettings: IUiSettingsClient) { + angular + .module('tableVisConfig', ['tableVisPrivate']) + .provider('stateManagementConfig', StateManagementConfigProvider) + .provider('config', function() { + return { + $get: () => ({ + get: (value: string) => { + return uiSettings ? uiSettings.get(value) : undefined; + }, + // set method is used in agg_table mocha test + set: (key: string, value: string) => { + return uiSettings ? uiSettings.set(key, value) : undefined; + }, + }), + }; + }); +} + +function createLocalI18nModule() { + angular + .module('tableVisI18n', []) + .provider('i18n', I18nProvider) + .filter('i18n', i18nFilter) + .directive('i18nId', i18nDirective); +} + +function createLocalPaginateModule() { + angular + .module('tableVisPaginate', []) + .directive('paginate', PaginateDirectiveProvider) + .directive('paginateControls', PaginateControlsDirectiveProvider); +} diff --git a/src/legacy/core_plugins/vis_type_table/public/legacy.ts b/src/legacy/core_plugins/vis_type_table/public/legacy.ts index 8513622dec9aad..e5b2619ef29702 100644 --- a/src/legacy/core_plugins/vis_type_table/public/legacy.ts +++ b/src/legacy/core_plugins/vis_type_table/public/legacy.ts @@ -18,20 +18,15 @@ */ import { PluginInitializerContext } from 'kibana/public'; -import { npSetup, npStart } from 'ui/new_platform'; +import { npSetup, npStart } from './legacy_imports'; import { plugin } from '.'; import { TablePluginSetupDependencies } from './plugin'; import { setup as visualizationsSetup } from '../../visualizations/public/np_ready/public/legacy'; -import { LegacyDependenciesPlugin } from './shim'; const plugins: Readonly = { expressions: npSetup.plugins.expressions, visualizations: visualizationsSetup, - - // Temporary solution - // It will be removed when all dependent services are migrated to the new platform. - __LEGACY: new LegacyDependenciesPlugin(), }; const pluginInstance = plugin({} as PluginInitializerContext); diff --git a/src/legacy/core_plugins/vis_type_table/public/legacy_imports.ts b/src/legacy/core_plugins/vis_type_table/public/legacy_imports.ts new file mode 100644 index 00000000000000..a372eced3e34b6 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_table/public/legacy_imports.ts @@ -0,0 +1,46 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { npSetup, npStart } from 'ui/new_platform'; +export { getFormat } from 'ui/visualize/loader/pipeline_helpers/utilities'; +export { AggConfig } from 'ui/vis'; +export { AggGroupNames, VisOptionsProps } from 'ui/vis/editors/default'; +// @ts-ignore +export { Schemas } from 'ui/vis/editors/default/schemas'; +// @ts-ignore +export { legacyResponseHandlerProvider } from 'ui/vis/response_handlers/legacy'; + +// @ts-ignore +export { PrivateProvider } from 'ui/private/private'; +// @ts-ignore +export { PaginateDirectiveProvider } from 'ui/directives/paginate'; +// @ts-ignore +export { PaginateControlsDirectiveProvider } from 'ui/directives/paginate'; +// @ts-ignore +export { watchMultiDecorator } from 'ui/directives/watch_multi/watch_multi'; + +// @ts-ignore +export { KbnAccessibleClickProvider } from 'ui/accessibility/kbn_accessible_click'; +// @ts-ignore +export { StateManagementConfigProvider } from 'ui/state_management/config_provider'; +export { configureAppAngularModule } from 'ui/legacy_compat'; + +export { tabifyGetColumns } from 'ui/agg_response/tabify/_get_columns'; +// @ts-ignore +export { tabifyAggResponse } from 'ui/agg_response/tabify'; diff --git a/src/legacy/core_plugins/vis_type_table/public/paginated_table/__tests__/paginated_table.js b/src/legacy/core_plugins/vis_type_table/public/paginated_table/__tests__/paginated_table.js deleted file mode 100644 index d146a1bddf2601..00000000000000 --- a/src/legacy/core_plugins/vis_type_table/public/paginated_table/__tests__/paginated_table.js +++ /dev/null @@ -1,419 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - - -import _ from 'lodash'; -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; - -import $ from 'jquery'; - -describe('Table Vis - 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({ id: i, title: 'column' + i, formatter: { convert: _.identity } }); - }); - } else { - columns = colCount.map((col, i) => ({ - id: i, - title: col.title, - formatter: col.formatter || { convert: _.identity } - })); - } - - if (_.isNumber(rowCount)) { - _.times(rowCount, function (row) { - const rowItems = {}; - - _.times(columns.length, function (col) { - rowItems[col] = 'item' + col + row; - }); - - rows.push(rowItems); - }); - } else { - rows = rowCount.map(row => { - const newRow = {}; - row.forEach((v, i) => newRow[i] = v); - return newRow; - }); - } - - return { - columns: columns, - rows: rows - }; - }; - - const renderTable = function (table, cols, rows, perPage, sort, linkToTop) { - $scope.table = table || { columns: [], rows: [] }; - $scope.cols = cols || []; - $scope.rows = rows || []; - $scope.perPage = perPage || defaultPerPage; - $scope.sort = sort || {}; - $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(null, cols, rows); - expect($el.children().length).to.be(0); - }); - - it('should render columns and rows', function () { - const data = makeData(2, 2); - const cols = data.columns; - const rows = data.rows; - - renderTable(data, cols, rows); - expect($el.children().length).to.be(1); - const tableRows = $el.find('tbody tr'); - // 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, data.columns, data.rows, perPageCount); - const tableRows = $el.find('tbody tr'); - expect(tableRows.length).to.be(perPageCount); - // add 2 for the first and last page links - expect($el.find('paginate-controls button').length).to.be(pageCount + 2); - }); - - it('should not show blank rows on last page', function () { - const rowCount = 7; - const perPageCount = 10; - const data = makeData(3, rowCount); - - renderTable(data, data.columns, data.rows, perPageCount, null); - const tableRows = $el.find('tbody tr'); - expect(tableRows.length).to.be(rowCount); - }); - - it('should not show link to top when not set', function () { - const data = makeData(5, 5); - renderTable(data, data.columns, data.rows, 10, null); - - const linkToTop = $el.find('[data-test-subj="paginateControlsLinkToTop"]'); - expect(linkToTop.length).to.be(0); - }); - - it('should show link to top when set', function () { - const data = makeData(5, 5); - renderTable(data, data.columns, data.rows, 10, null, true); - - const linkToTop = $el.find('[data-test-subj="paginateControlsLinkToTop"]'); - expect(linkToTop.length).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, 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 explicitly 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, 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', - id: 0, - formatter: { convert: val => { - return val === 'zzz' ? '

hello

' : val; - } } - }]; - rows = [ - ['aaaa'], - ['zzz'], - ['bbbb'] - ]; - renderTable({ columns: cols, rows }, cols, rows); - paginatedTable = $el.isolateScope().paginatedTable; - }); - - it('should append object markup', function () { - const tableRows = $el.find('tbody tr'); - expect(tableRows.eq(0).find('h1').length).to.be(0); - expect(tableRows.eq(1).find('h1').length).to.be(1); - expect(tableRows.eq(2).find('h1').length).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').length).to.be(0); - expect(tableRows.eq(1).find('h1').length).to.be(0); - // html row should be the last row - expect(tableRows.eq(2).find('h1').length).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').length).to.be(1); - expect(tableRows.eq(1).find('h1').length).to.be(0); - expect(tableRows.eq(2).find('h1').length).to.be(0); - }); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_table/public/paginated_table/paginated_table.test.ts b/src/legacy/core_plugins/vis_type_table/public/paginated_table/paginated_table.test.ts new file mode 100644 index 00000000000000..781782e42fbaf2 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_table/public/paginated_table/paginated_table.test.ts @@ -0,0 +1,713 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { isNumber, times, identity, random } from 'lodash'; +import angular, { IRootScopeService, IScope, ICompileService } from 'angular'; +import $ from 'jquery'; +import 'angular-sanitize'; +import 'angular-mocks'; +import '../table_vis.mock'; + +import { getAngularModule } from '../get_inner_angular'; +import { initTableVisLegacyModule } from '../table_vis_legacy_module'; +import { npStart } from '../legacy_imports'; + +interface Sort { + columnIndex: number; + direction: string; +} + +interface Row { + [key: string]: number | string; +} + +interface Column { + id?: string; + title: string; + formatter?: { + convert?: (val: string) => string; + }; + sortable?: boolean; +} + +interface Table { + columns: Column[]; + rows: Row[]; +} + +interface PaginatedTableScope extends IScope { + table?: Table; + cols?: Column[]; + rows?: Row[]; + perPage?: number; + sort?: Sort; + linkToTop?: boolean; +} + +describe('Table Vis - Paginated table', () => { + let $el: JQuery; + let $rootScope: IRootScopeService; + let $compile: ICompileService; + let $scope: PaginatedTableScope; + const defaultPerPage = 10; + let paginatedTable: any; + + const initLocalAngular = () => { + const tableVisModule = getAngularModule('kibana/table_vis', npStart.core); + initTableVisLegacyModule(tableVisModule); + }; + + beforeEach(initLocalAngular); + beforeEach(angular.mock.module('kibana/table_vis')); + + beforeEach( + angular.mock.inject((_$rootScope_: IRootScopeService, _$compile_: ICompileService) => { + $rootScope = _$rootScope_; + $compile = _$compile_; + $scope = $rootScope.$new(); + }) + ); + + afterEach(() => { + $scope.$destroy(); + }); + + const makeData = (colCount: number | Column[], rowCount: number | string[][]) => { + let columns: Column[] = []; + let rows: Row[] = []; + + if (isNumber(colCount)) { + times(colCount, i => { + columns.push({ id: `${i}`, title: `column${i}`, formatter: { convert: identity } }); + }); + } else { + columns = colCount.map( + (col, i) => + ({ + id: `${i}`, + title: col.title, + formatter: col.formatter || { convert: identity }, + } as Column) + ); + } + + if (isNumber(rowCount)) { + times(rowCount, row => { + const rowItems: Row = {}; + + times(columns.length, col => { + rowItems[`${col}`] = `item-${col}-${row}`; + }); + + rows.push(rowItems); + }); + } else { + rows = rowCount.map((row: string[]) => { + const newRow: Row = {}; + row.forEach((v, i) => (newRow[i] = v)); + return newRow; + }); + } + + return { + columns, + rows, + }; + }; + + const renderTable = ( + table: { columns: Column[]; rows: Row[] } | null, + cols: Column[], + rows: Row[], + perPage?: number, + sort?: Sort, + linkToTop?: boolean + ) => { + $scope.table = table || { columns: [], rows: [] }; + $scope.cols = cols || []; + $scope.rows = rows || []; + $scope.perPage = perPage || defaultPerPage; + $scope.sort = sort; + $scope.linkToTop = linkToTop; + + const template = ` + `; + const element = $compile(template)($scope); + $el = $(element); + + $scope.$digest(); + paginatedTable = element.controller('paginatedTable'); + }; + + describe('rendering', () => { + test('should not display without rows', () => { + const cols: Column[] = [ + { + id: 'col-1-1', + title: 'test1', + }, + ]; + const rows: Row[] = []; + + renderTable(null, cols, rows); + expect($el.children().length).toBe(0); + }); + + test('should render columns and rows', () => { + const data = makeData(2, 2); + const cols = data.columns; + const rows = data.rows; + + renderTable(data, cols, rows); + expect($el.children().length).toBe(1); + const tableRows = $el.find('tbody tr'); + + // should contain the row data + expect( + tableRows + .eq(0) + .find('td') + .eq(0) + .text() + ).toBe(rows[0][0]); + expect( + tableRows + .eq(0) + .find('td') + .eq(1) + .text() + ).toBe(rows[0][1]); + expect( + tableRows + .eq(1) + .find('td') + .eq(0) + .text() + ).toBe(rows[1][0]); + expect( + tableRows + .eq(1) + .find('td') + .eq(1) + .text() + ).toBe(rows[1][1]); + }); + + test('should paginate rows', () => { + // 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, data.columns, data.rows, perPageCount); + const tableRows = $el.find('tbody tr'); + expect(tableRows.length).toBe(perPageCount); + // add 2 for the first and last page links + expect($el.find('paginate-controls button').length).toBe(pageCount + 2); + }); + + test('should not show blank rows on last page', () => { + const rowCount = 7; + const perPageCount = 10; + const data = makeData(3, rowCount); + + renderTable(data, data.columns, data.rows, perPageCount); + const tableRows = $el.find('tbody tr'); + expect(tableRows.length).toBe(rowCount); + }); + + test('should not show link to top when not set', () => { + const data = makeData(5, 5); + renderTable(data, data.columns, data.rows, 10); + + const linkToTop = $el.find('[data-test-subj="paginateControlsLinkToTop"]'); + expect(linkToTop.length).toBe(0); + }); + + test('should show link to top when set', () => { + const data = makeData(5, 5); + renderTable(data, data.columns, data.rows, 10, undefined, true); + + const linkToTop = $el.find('[data-test-subj="paginateControlsLinkToTop"]'); + expect(linkToTop.length).toBe(1); + }); + }); + + describe('sorting', () => { + let data: Table; + let lastRowIndex: number; + + beforeEach(() => { + data = makeData(3, [ + ['bbbb', 'aaaa', 'zzzz'], + ['cccc', 'cccc', 'aaaa'], + ['zzzz', 'bbbb', 'bbbb'], + ['aaaa', 'zzzz', 'cccc'], + ]); + + lastRowIndex = data.rows.length - 1; + renderTable(data, data.columns, data.rows); + }); + + test('should not sort by default', () => { + const tableRows = $el.find('tbody tr'); + expect( + tableRows + .eq(0) + .find('td') + .eq(0) + .text() + ).toBe(data.rows[0][0]); + expect( + tableRows + .eq(lastRowIndex) + .find('td') + .eq(0) + .text() + ).toBe(data.rows[lastRowIndex][0]); + }); + + test('should do nothing when sorting by invalid column id', () => { + // sortColumn + paginatedTable.sortColumn(999); + $scope.$digest(); + + const tableRows = $el.find('tbody tr'); + expect( + tableRows + .eq(0) + .find('td') + .eq(0) + .text() + ).toBe('bbbb'); + expect( + tableRows + .eq(0) + .find('td') + .eq(1) + .text() + ).toBe('aaaa'); + expect( + tableRows + .eq(0) + .find('td') + .eq(2) + .text() + ).toBe('zzzz'); + }); + + test('should do nothing when sorting by non sortable column', () => { + 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() + ).toBe('bbbb'); + expect( + tableRows + .eq(0) + .find('td') + .eq(1) + .text() + ).toBe('aaaa'); + expect( + tableRows + .eq(0) + .find('td') + .eq(2) + .text() + ).toBe('zzzz'); + }); + + test("should set the sort direction to asc when it's not explicitly set", () => { + paginatedTable.sortColumn(1); + $scope.$digest(); + + const tableRows = $el.find('tbody tr'); + expect( + tableRows + .eq(2) + .find('td') + .eq(1) + .text() + ).toBe('cccc'); + expect( + tableRows + .eq(1) + .find('td') + .eq(1) + .text() + ).toBe('bbbb'); + expect( + tableRows + .eq(0) + .find('td') + .eq(1) + .text() + ).toBe('aaaa'); + }); + + test('should allow you to explicitly set the sort direction', () => { + paginatedTable.sortColumn(1, 'desc'); + $scope.$digest(); + + const tableRows = $el.find('tbody tr'); + expect( + tableRows + .eq(0) + .find('td') + .eq(1) + .text() + ).toBe('zzzz'); + expect( + tableRows + .eq(1) + .find('td') + .eq(1) + .text() + ).toBe('cccc'); + expect( + tableRows + .eq(2) + .find('td') + .eq(1) + .text() + ).toBe('bbbb'); + }); + + test('should sort ascending on first invocation', () => { + // sortColumn + paginatedTable.sortColumn(0); + $scope.$digest(); + + const tableRows = $el.find('tbody tr'); + expect( + tableRows + .eq(0) + .find('td') + .eq(0) + .text() + ).toBe('aaaa'); + expect( + tableRows + .eq(lastRowIndex) + .find('td') + .eq(0) + .text() + ).toBe('zzzz'); + }); + + test('should sort descending on second invocation', () => { + // sortColumn + paginatedTable.sortColumn(0); + paginatedTable.sortColumn(0); + $scope.$digest(); + + const tableRows = $el.find('tbody tr'); + expect( + tableRows + .eq(0) + .find('td') + .eq(0) + .text() + ).toBe('zzzz'); + expect( + tableRows + .eq(lastRowIndex) + .find('td') + .eq(0) + .text() + ).toBe('aaaa'); + }); + + test('should clear sorting on third invocation', () => { + // 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() + ).toBe(data.rows[0][0]); + expect( + tableRows + .eq(lastRowIndex) + .find('td') + .eq(0) + .text() + ).toBe('aaaa'); + }); + + test('should sort new column ascending', () => { + // 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() + ).toBe('aaaa'); + expect( + tableRows + .eq(lastRowIndex) + .find('td') + .eq(1) + .text() + ).toBe('zzzz'); + }); + }); + + describe('sorting duplicate columns', () => { + let data; + const colText = 'test row'; + + beforeEach(() => { + const cols: Column[] = [{ 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, data.columns, data.rows); + }); + + test('should have duplicate column titles', () => { + const columns = $el.find('thead th span'); + columns.each((i, col) => { + expect($(col).text()).toBe(colText); + }); + }); + + test('should handle sorting on columns with the same name', () => { + // 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() + ).toBe('cccc'); + expect( + tableRows + .eq(0) + .find('td') + .eq(1) + .text() + ).toBe('cccc'); + expect( + tableRows + .eq(0) + .find('td') + .eq(2) + .text() + ).toBe('aaaa'); + expect( + tableRows + .eq(1) + .find('td') + .eq(2) + .text() + ).toBe('bbbb'); + expect( + tableRows + .eq(2) + .find('td') + .eq(2) + .text() + ).toBe('cccc'); + expect( + tableRows + .eq(3) + .find('td') + .eq(2) + .text() + ).toBe('zzzz'); + }); + + test('should sort correctly between columns', () => { + // 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() + ).toBe('cccc'); + expect( + tableRows + .eq(0) + .find('td') + .eq(1) + .text() + ).toBe('cccc'); + expect( + tableRows + .eq(0) + .find('td') + .eq(2) + .text() + ).toBe('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() + ).toBe('aaaa'); + expect( + tableRows + .eq(0) + .find('td') + .eq(1) + .text() + ).toBe('zzzz'); + expect( + tableRows + .eq(0) + .find('td') + .eq(2) + .text() + ).toBe('cccc'); + + expect( + tableRows + .eq(1) + .find('td') + .eq(0) + .text() + ).toBe('bbbb'); + expect( + tableRows + .eq(2) + .find('td') + .eq(0) + .text() + ).toBe('cccc'); + expect( + tableRows + .eq(3) + .find('td') + .eq(0) + .text() + ).toBe('zzzz'); + }); + + test('should not sort duplicate columns', () => { + paginatedTable.sortColumn(1); + $scope.$digest(); + + const sorters = $el.find('thead th i'); + expect(sorters.eq(0).hasClass('fa-sort')).toBe(true); + expect(sorters.eq(1).hasClass('fa-sort')).toBe(false); + expect(sorters.eq(2).hasClass('fa-sort')).toBe(true); + }); + }); + + describe('object rows', () => { + let cols: Column[]; + let rows: any; + + beforeEach(() => { + cols = [ + { + title: 'object test', + id: '0', + formatter: { + convert: val => { + return val === 'zzz' ? '

hello

' : val; + }, + }, + }, + ]; + rows = [['aaaa'], ['zzz'], ['bbbb']]; + renderTable({ columns: cols, rows }, cols, rows); + }); + + test('should append object markup', () => { + const tableRows = $el.find('tbody tr'); + expect(tableRows.eq(0).find('h1').length).toBe(0); + expect(tableRows.eq(1).find('h1').length).toBe(1); + expect(tableRows.eq(2).find('h1').length).toBe(0); + }); + + test('should sort using object value', () => { + paginatedTable.sortColumn(0); + $scope.$digest(); + let tableRows = $el.find('tbody tr'); + expect(tableRows.eq(0).find('h1').length).toBe(0); + expect(tableRows.eq(1).find('h1').length).toBe(0); + // html row should be the last row + expect(tableRows.eq(2).find('h1').length).toBe(1); + + paginatedTable.sortColumn(0); + $scope.$digest(); + tableRows = $el.find('tbody tr'); + // html row should be the first row + expect(tableRows.eq(0).find('h1').length).toBe(1); + expect(tableRows.eq(1).find('h1').length).toBe(0); + expect(tableRows.eq(2).find('h1').length).toBe(0); + }); + }); +}); diff --git a/src/legacy/core_plugins/vis_type_table/public/plugin.ts b/src/legacy/core_plugins/vis_type_table/public/plugin.ts index ce8d349d8dd7aa..17c50b0567b67d 100644 --- a/src/legacy/core_plugins/vis_type_table/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_table/public/plugin.ts @@ -21,8 +21,6 @@ import { VisualizationsSetup } from '../../visualizations/public'; import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../../core/public'; -import { LegacyDependenciesPlugin } from './shim'; - import { createTableVisFn } from './table_vis_fn'; import { tableVisTypeDefinition } from './table_vis_type'; @@ -30,7 +28,6 @@ import { tableVisTypeDefinition } from './table_vis_type'; export interface TablePluginSetupDependencies { expressions: ReturnType; visualizations: VisualizationsSetup; - __LEGACY: LegacyDependenciesPlugin; } /** @internal */ @@ -43,9 +40,8 @@ export class TableVisPlugin implements Plugin, void> { public async setup( core: CoreSetup, - { expressions, visualizations, __LEGACY }: TablePluginSetupDependencies + { expressions, visualizations }: TablePluginSetupDependencies ) { - __LEGACY.setup(); expressions.registerFunction(createTableVisFn); visualizations.types.createBaseVisualization(tableVisTypeDefinition); diff --git a/src/legacy/core_plugins/vis_type_table/public/shim/index.ts b/src/legacy/core_plugins/vis_type_table/public/shim/index.ts deleted file mode 100644 index cfc7b62ff4f86d..00000000000000 --- a/src/legacy/core_plugins/vis_type_table/public/shim/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export * from './legacy_dependencies_plugin'; diff --git a/src/legacy/core_plugins/vis_type_table/public/shim/legacy_dependencies_plugin.ts b/src/legacy/core_plugins/vis_type_table/public/table_vis.mock.ts similarity index 51% rename from src/legacy/core_plugins/vis_type_table/public/shim/legacy_dependencies_plugin.ts rename to src/legacy/core_plugins/vis_type_table/public/table_vis.mock.ts index ba30664951f473..d04964cb7af038 100644 --- a/src/legacy/core_plugins/vis_type_table/public/shim/legacy_dependencies_plugin.ts +++ b/src/legacy/core_plugins/vis_type_table/public/table_vis.mock.ts @@ -17,16 +17,30 @@ * under the License. */ -import { CoreStart, Plugin } from '../../../../../core/public'; -import { initTableVisLegacyModule } from './table_vis_legacy_module'; +import { createUiNewPlatformMock } from 'ui/new_platform/__mocks__/helpers'; +import { StubBrowserStorage } from 'test_utils/stub_browser_storage'; +import { injectedMetadataServiceMock } from '../../../../core/public/mocks'; -/** @internal */ -export class LegacyDependenciesPlugin implements Plugin { - public setup() { - initTableVisLegacyModule(); - } +jest.doMock('ui/new_platform', () => { + const npMock = createUiNewPlatformMock(); + return { + npSetup: { + ...npMock.npSetup, + core: { + ...npMock.npSetup.core, + injectedMetadata: injectedMetadataServiceMock.createSetupContract(), + }, + }, + npStart: { + ...npMock.npStart, + core: { + ...npMock.npStart.core, + injectedMetadata: injectedMetadataServiceMock.createStartContract(), + }, + }, + }; +}); - public start(core: CoreStart) { - // nothing to do here yet - } -} +Object.assign(window, { + sessionStorage: new StubBrowserStorage(), +}); diff --git a/src/legacy/core_plugins/vis_type_table/public/table_vis_controller.test.ts b/src/legacy/core_plugins/vis_type_table/public/table_vis_controller.test.ts new file mode 100644 index 00000000000000..5ae58204a8cf30 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_table/public/table_vis_controller.test.ts @@ -0,0 +1,257 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import angular, { IRootScopeService, IScope, ICompileService } from 'angular'; +import 'angular-mocks'; +import 'angular-sanitize'; +import $ from 'jquery'; +import './table_vis.mock'; + +// @ts-ignore +import StubIndexPattern from 'test_utils/stub_index_pattern'; +import { getAngularModule } from './get_inner_angular'; +import { initTableVisLegacyModule } from './table_vis_legacy_module'; +import { + npStart, + legacyResponseHandlerProvider, + AggConfig, + tabifyAggResponse, +} from './legacy_imports'; +import { tableVisTypeDefinition } from './table_vis_type'; +import { Vis } from '../../visualizations/public'; +import { setup as visualizationsSetup } from '../../visualizations/public/np_ready/public/legacy'; +// eslint-disable-next-line +import { stubFields } from '../../../../plugins/data/public/stubs'; +// eslint-disable-next-line +import { setFieldFormats } from '../../../../plugins/data/public/index_patterns/services'; + +interface TableVisScope extends IScope { + [key: string]: any; +} + +const oneRangeBucket = { + hits: { + total: 6039, + max_score: 0, + hits: [], + }, + aggregations: { + agg_2: { + buckets: { + '0.0-1000.0': { + from: 0, + from_as_string: '0.0', + to: 1000, + to_as_string: '1000.0', + doc_count: 606, + }, + '1000.0-2000.0': { + from: 1000, + from_as_string: '1000.0', + to: 2000, + to_as_string: '2000.0', + doc_count: 298, + }, + }, + }, + }, +}; + +describe('Table Vis - Controller', () => { + let $rootScope: IRootScopeService & { [key: string]: any }; + let $compile: ICompileService; + let $scope: TableVisScope; + let $el: JQuery; + let tableAggResponse: any; + let tabifiedResponse: any; + let stubIndexPattern: any; + + const initLocalAngular = () => { + const tableVisModule = getAngularModule('kibana/table_vis', npStart.core); + initTableVisLegacyModule(tableVisModule); + }; + + beforeEach(initLocalAngular); + beforeAll(() => { + visualizationsSetup.types.createBaseVisualization(tableVisTypeDefinition); + }); + beforeEach(angular.mock.module('kibana/table_vis')); + + beforeEach( + angular.mock.inject((_$rootScope_: IRootScopeService, _$compile_: ICompileService) => { + $rootScope = _$rootScope_; + $compile = _$compile_; + tableAggResponse = legacyResponseHandlerProvider().handler; + }) + ); + + beforeEach(() => { + setFieldFormats(({ + getDefaultInstance: jest.fn(), + } as unknown) as any); + stubIndexPattern = new StubIndexPattern( + 'logstash-*', + (cfg: any) => cfg, + 'time', + stubFields, + npStart.core.uiSettings + ); + }); + + function getRangeVis(params?: object) { + // @ts-ignore + return new Vis(stubIndexPattern, { + type: 'table', + params: params || {}, + aggs: [ + { type: 'count', schema: 'metric' }, + { + type: 'range', + schema: 'bucket', + params: { + field: 'bytes', + ranges: [ + { from: 0, to: 1000 }, + { from: 1000, to: 2000 }, + ], + }, + }, + ], + }); + } + + const dimensions = { + buckets: [ + { + accessor: 0, + }, + ], + metrics: [ + { + accessor: 1, + format: { id: 'range' }, + }, + ], + }; + + // basically a parameterized beforeEach + function initController(vis: Vis) { + vis.aggs.aggs.forEach((agg: AggConfig, i: number) => { + agg.id = 'agg_' + (i + 1); + }); + + tabifiedResponse = tabifyAggResponse(vis.aggs, oneRangeBucket); + $rootScope.vis = vis; + $rootScope.visParams = vis.params; + $rootScope.uiState = { + get: jest.fn(), + set: jest.fn(), + }; + $rootScope.renderComplete = () => {}; + $rootScope.newScope = (scope: TableVisScope) => { + $scope = scope; + }; + + $el = $('
') + .attr('ng-controller', 'KbnTableVisController') + .attr('ng-init', 'newScope(this)'); + + $compile($el)($rootScope); + } + + // put a response into the controller + function attachEsResponseToScope(resp: object) { + $rootScope.esResponse = resp; + $rootScope.$apply(); + } + + // remove the response from the controller + function removeEsResponseFromScope() { + delete $rootScope.esResponse; + $rootScope.renderComplete = () => {}; + $rootScope.$apply(); + } + + test('exposes #tableGroups and #hasSomeRows when a response is attached to scope', async () => { + const vis: Vis = getRangeVis(); + initController(vis); + + expect(!$scope.tableGroups).toBeTruthy(); + expect(!$scope.hasSomeRows).toBeTruthy(); + + attachEsResponseToScope(await tableAggResponse(tabifiedResponse, dimensions)); + + expect($scope.hasSomeRows).toBeTruthy(); + expect($scope.tableGroups.tables).toBeDefined(); + expect($scope.tableGroups.tables.length).toBe(1); + expect($scope.tableGroups.tables[0].columns.length).toBe(2); + expect($scope.tableGroups.tables[0].rows.length).toBe(2); + }); + + test('clears #tableGroups and #hasSomeRows when the response is removed', async () => { + const vis = getRangeVis(); + initController(vis); + + attachEsResponseToScope(await tableAggResponse(tabifiedResponse, dimensions)); + removeEsResponseFromScope(); + + expect(!$scope.hasSomeRows).toBeTruthy(); + expect(!$scope.tableGroups).toBeTruthy(); + }); + + test('sets the sort on the scope when it is passed as a vis param', async () => { + const sortObj = { + columnIndex: 1, + direction: 'asc', + }; + const vis = getRangeVis({ sort: sortObj }); + initController(vis); + + attachEsResponseToScope(await tableAggResponse(tabifiedResponse, dimensions)); + + expect($scope.sort.columnIndex).toEqual(sortObj.columnIndex); + expect($scope.sort.direction).toEqual(sortObj.direction); + }); + + test('sets #hasSomeRows properly if the table group is empty', async () => { + const vis = getRangeVis(); + initController(vis); + + tabifiedResponse.rows = []; + + attachEsResponseToScope(await tableAggResponse(tabifiedResponse, dimensions)); + + expect($scope.hasSomeRows).toBeFalsy(); + expect(!$scope.tableGroups).toBeTruthy(); + }); + + test('passes partialRows:true to tabify based on the vis params', () => { + const vis = getRangeVis({ showPartialRows: true }); + initController(vis); + + expect(vis.isHierarchical()).toEqual(true); + }); + + test('passes partialRows:false to tabify based on the vis params', () => { + const vis = getRangeVis({ showPartialRows: false }); + initController(vis); + + expect(vis.isHierarchical()).toEqual(false); + }); +}); diff --git a/src/legacy/core_plugins/vis_type_table/public/table_vis_fn.test.ts b/src/legacy/core_plugins/vis_type_table/public/table_vis_fn.test.ts index d909842a71e2d1..c1def9b55aae20 100644 --- a/src/legacy/core_plugins/vis_type_table/public/table_vis_fn.test.ts +++ b/src/legacy/core_plugins/vis_type_table/public/table_vis_fn.test.ts @@ -22,9 +22,7 @@ import { createTableVisFn } from './table_vis_fn'; // eslint-disable-next-line import { functionWrapper } from '../../../../plugins/expressions/public/functions/tests/utils'; -jest.mock('ui/new_platform'); - -jest.mock('ui/vis/response_handlers/legacy', () => { +jest.mock('./legacy_imports', () => { const mockResponseHandler = jest.fn().mockReturnValue( Promise.resolve({ tables: [{ columns: [], rows: [] }], @@ -37,7 +35,7 @@ jest.mock('ui/vis/response_handlers/legacy', () => { }; }); -const { mockResponseHandler } = jest.requireMock('ui/vis/response_handlers/legacy'); +const { mockResponseHandler } = jest.requireMock('./legacy_imports'); describe('interpreter/functions#table', () => { const fn = functionWrapper(createTableVisFn); diff --git a/src/legacy/core_plugins/vis_type_table/public/shim/table_vis_legacy_module.ts b/src/legacy/core_plugins/vis_type_table/public/table_vis_legacy_module.ts similarity index 65% rename from src/legacy/core_plugins/vis_type_table/public/shim/table_vis_legacy_module.ts rename to src/legacy/core_plugins/vis_type_table/public/table_vis_legacy_module.ts index e148fd98490df2..57d8b7c448b4c4 100644 --- a/src/legacy/core_plugins/vis_type_table/public/shim/table_vis_legacy_module.ts +++ b/src/legacy/core_plugins/vis_type_table/public/table_vis_legacy_module.ts @@ -17,32 +17,25 @@ * under the License. */ -import { once } from 'lodash'; +import { IModule } from 'angular'; // @ts-ignore -import { uiModules } from 'ui/modules'; - -import 'angular-recursion'; -import 'ui/directives/paginate'; - -// @ts-ignore -import { TableVisController } from '../table_vis_controller.js'; +import { TableVisController } from './table_vis_controller.js'; // @ts-ignore -import { KbnAggTable } from '../agg_table/agg_table'; +import { KbnAggTable } from './agg_table/agg_table'; // @ts-ignore -import { KbnAggTableGroup } from '../agg_table/agg_table_group'; +import { KbnAggTableGroup } from './agg_table/agg_table_group'; // @ts-ignore -import { KbnRows } from '../paginated_table/rows'; +import { KbnRows } from './paginated_table/rows'; // @ts-ignore -import { PaginatedTable } from '../paginated_table/paginated_table'; +import { PaginatedTable } from './paginated_table/paginated_table'; /** @internal */ -export const initTableVisLegacyModule = once((): void => { - uiModules - .get('kibana/table_vis', ['kibana', 'RecursionHelper']) +export const initTableVisLegacyModule = (angularIns: IModule): void => { + angularIns .controller('KbnTableVisController', TableVisController) .directive('kbnAggTable', KbnAggTable) .directive('kbnAggTableGroup', KbnAggTableGroup) .directive('kbnRows', KbnRows) .directive('paginatedTable', PaginatedTable); -}); +}; diff --git a/src/legacy/core_plugins/vis_type_table/public/table_vis_request_handler.ts b/src/legacy/core_plugins/vis_type_table/public/table_vis_request_handler.ts index c432dfd84cbb83..1a6d4600025f19 100644 --- a/src/legacy/core_plugins/vis_type_table/public/table_vis_request_handler.ts +++ b/src/legacy/core_plugins/vis_type_table/public/table_vis_request_handler.ts @@ -17,7 +17,6 @@ * under the License. */ -// @ts-ignore -import { legacyResponseHandlerProvider } from 'ui/vis/response_handlers/legacy'; +import { legacyResponseHandlerProvider } from './legacy_imports'; export const tableVisResponseHandler = legacyResponseHandlerProvider().handler; diff --git a/src/legacy/core_plugins/vis_type_table/public/table_vis_type.ts b/src/legacy/core_plugins/vis_type_table/public/table_vis_type.ts index 7e8537a1fee54d..5186b6cf59dfdd 100644 --- a/src/legacy/core_plugins/vis_type_table/public/table_vis_type.ts +++ b/src/legacy/core_plugins/vis_type_table/public/table_vis_type.ts @@ -18,18 +18,13 @@ */ import { i18n } from '@kbn/i18n'; -import { Vis } from 'ui/vis'; -// @ts-ignore - -// @ts-ignore -import { Schemas } from 'ui/vis/editors/default/schemas'; -// @ts-ignore -import { AngularVisController } from 'ui/vis/vis_types/angular_vis_type'; -import { AggGroupNames } from 'ui/vis/editors/default'; +import { AggGroupNames, Schemas } from './legacy_imports'; +import { Vis } from '../../visualizations/public'; import { tableVisResponseHandler } from './table_vis_request_handler'; // @ts-ignore import tableVisTemplate from './table_vis.html'; import { TableOptions } from './components/table_vis_options'; +import { TableVisualizationController } from './vis_controller'; export const tableVisTypeDefinition = { type: 'table', @@ -41,7 +36,7 @@ export const tableVisTypeDefinition = { description: i18n.translate('visTypeTable.tableVisDescription', { defaultMessage: 'Display values in a table', }), - visualization: AngularVisController, + visualization: TableVisualizationController, visConfig: { defaults: { perPage: 10, diff --git a/src/legacy/core_plugins/vis_type_table/public/vis_controller.ts b/src/legacy/core_plugins/vis_type_table/public/vis_controller.ts new file mode 100644 index 00000000000000..7adaa21cac5938 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_table/public/vis_controller.ts @@ -0,0 +1,104 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import angular, { IModule, auto, IRootScopeService, IScope, ICompileService } from 'angular'; +import $ from 'jquery'; + +import { Vis, VisParams } from '../../visualizations/public'; +import { npStart } from './legacy_imports'; +import { getAngularModule } from './get_inner_angular'; +import { initTableVisLegacyModule } from './table_vis_legacy_module'; + +const innerAngularName = 'kibana/table_vis'; + +export class TableVisualizationController { + private tableVisModule: IModule | undefined; + private injector: auto.IInjectorService | undefined; + el: JQuery; + vis: Vis; + $rootScope: IRootScopeService | null = null; + $scope: (IScope & { [key: string]: any }) | undefined; + $compile: ICompileService | undefined; + + constructor(domeElement: Element, vis: Vis) { + this.el = $(domeElement); + this.vis = vis; + } + + getInjector() { + if (!this.injector) { + const mountpoint = document.createElement('div'); + mountpoint.setAttribute('style', 'height: 100%; width: 100%;'); + this.injector = angular.bootstrap(mountpoint, [innerAngularName]); + this.el.append(mountpoint); + } + + return this.injector; + } + + initLocalAngular() { + if (!this.tableVisModule) { + this.tableVisModule = getAngularModule(innerAngularName, npStart.core); + initTableVisLegacyModule(this.tableVisModule); + } + } + + async render(esResponse: object, visParams: VisParams, status: { [key: string]: boolean }) { + this.initLocalAngular(); + + return new Promise(async (resolve, reject) => { + if (!this.$rootScope) { + const $injector = this.getInjector(); + this.$rootScope = $injector.get('$rootScope'); + this.$compile = $injector.get('$compile'); + } + const updateScope = () => { + if (!this.$scope) { + return; + } + this.$scope.vis = this.vis; + this.$scope.visState = this.vis.getState(); + this.$scope.esResponse = esResponse; + this.$scope.visParams = visParams; + this.$scope.renderComplete = resolve; + this.$scope.renderFailed = reject; + this.$scope.resize = Date.now(); + this.$scope.updateStatus = status; + this.$scope.$apply(); + }; + + if (!this.$scope && this.$compile) { + this.$scope = this.$rootScope.$new(); + this.$scope.uiState = this.vis.getUiState(); + updateScope(); + this.el.find('div').append(this.$compile(this.vis.type.visConfig.template)(this.$scope)); + this.$scope.$apply(); + } else { + updateScope(); + } + }); + } + + destroy() { + if (this.$rootScope) { + this.$rootScope.$destroy(); + this.$rootScope = null; + } + } +} diff --git a/src/legacy/ui/public/directives/paginate.js b/src/legacy/ui/public/directives/paginate.js index 7ecd5fefe67103..6663e1fb8b6e48 100644 --- a/src/legacy/ui/public/directives/paginate.js +++ b/src/legacy/ui/public/directives/paginate.js @@ -22,209 +22,215 @@ import { i18n } from '@kbn/i18n'; import { uiModules } from '../modules'; import paginateControlsTemplate from './partials/paginate_controls.html'; -uiModules.get('kibana') - .directive('paginate', function ($parse, $compile) { - return { - restrict: 'E', - scope: true, - link: { - pre: function ($scope, $el, attrs) { - if (_.isUndefined(attrs.bottomControls)) attrs.bottomControls = true; - if ($el.find('paginate-controls.paginate-bottom').length === 0 && attrs.bottomControls) { - $el.append($compile('')($scope)); - } - }, - post: function ($scope, $el, attrs) { - if (_.isUndefined(attrs.topControls)) attrs.topControls = false; - if ($el.find('paginate-controls.paginate-top').length === 0 && attrs.topControls) { - $el.prepend($compile('')($scope)); - } - - const paginate = $scope.paginate; - - // add some getters to the controller powered by attributes - paginate.getList = $parse(attrs.list); - paginate.perPageProp = attrs.perPageProp; - - if (attrs.perPage) { - paginate.perPage = attrs.perPage; - $scope.showSelector = false; - } else { - $scope.showSelector = true; - } - - paginate.otherWidthGetter = $parse(attrs.otherWidth); - - paginate.init(); +export function PaginateDirectiveProvider($parse, $compile) { + return { + restrict: 'E', + scope: true, + link: { + pre: function ($scope, $el, attrs) { + if (_.isUndefined(attrs.bottomControls)) attrs.bottomControls = true; + if ($el.find('paginate-controls.paginate-bottom').length === 0 && attrs.bottomControls) { + $el.append($compile('')($scope)); } }, - controllerAs: 'paginate', - controller: function ($scope, $document) { - const self = this; - const ALL = 0; - const allSizeTitle = i18n.translate('common.ui.directives.paginate.size.allDropDownOptionLabel', { - defaultMessage: 'All', - }); + post: function ($scope, $el, attrs) { + if (_.isUndefined(attrs.topControls)) attrs.topControls = false; + if ($el.find('paginate-controls.paginate-top').length === 0 && attrs.topControls) { + $el.prepend($compile('')($scope)); + } - self.sizeOptions = [ - { title: '10', value: 10 }, - { title: '25', value: 25 }, - { title: '100', value: 100 }, - { title: allSizeTitle, value: ALL } - ]; + const paginate = $scope.paginate; - // setup the watchers, called in the post-link function - self.init = function () { + // add some getters to the controller powered by attributes + paginate.getList = $parse(attrs.list); + paginate.perPageProp = attrs.perPageProp; - self.perPage = _.parseInt(self.perPage) || $scope[self.perPageProp]; + if (attrs.perPage) { + paginate.perPage = attrs.perPage; + $scope.showSelector = false; + } else { + $scope.showSelector = true; + } - $scope.$watchMulti([ - 'paginate.perPage', - self.perPageProp, - self.otherWidthGetter - ], function (vals, oldVals) { - const intChanges = vals[0] !== oldVals[0]; + paginate.otherWidthGetter = $parse(attrs.otherWidth); - if (intChanges) { - if (!setPerPage(self.perPage)) { + paginate.init(); + }, + }, + controllerAs: 'paginate', + controller: function ($scope, $document) { + const self = this; + const ALL = 0; + const allSizeTitle = i18n.translate( + 'common.ui.directives.paginate.size.allDropDownOptionLabel', + { + defaultMessage: 'All', + } + ); + + self.sizeOptions = [ + { title: '10', value: 10 }, + { title: '25', value: 25 }, + { title: '100', value: 100 }, + { title: allSizeTitle, value: ALL }, + ]; + + // setup the watchers, called in the post-link function + self.init = function () { + self.perPage = _.parseInt(self.perPage) || $scope[self.perPageProp]; + + $scope.$watchMulti(['paginate.perPage', self.perPageProp, self.otherWidthGetter], function ( + vals, + oldVals + ) { + const intChanges = vals[0] !== oldVals[0]; + + if (intChanges) { + if (!setPerPage(self.perPage)) { // if we are not able to set the external value, // render now, otherwise wait for the external value // to trigger the watcher again - self.renderList(); - } - return; + self.renderList(); } + return; + } - self.perPage = _.parseInt(self.perPage) || $scope[self.perPageProp]; - if (self.perPage == null) { - self.perPage = ALL; - return; - } + self.perPage = _.parseInt(self.perPage) || $scope[self.perPageProp]; + if (self.perPage == null) { + self.perPage = ALL; + return; + } - self.renderList(); - }); + self.renderList(); + }); - $scope.$watch('page', self.changePage); - $scope.$watchCollection(self.getList, function (list) { - $scope.list = list; - self.renderList(); - }); - }; + $scope.$watch('page', self.changePage); + $scope.$watchCollection(self.getList, function (list) { + $scope.list = list; + self.renderList(); + }); + }; - self.goToPage = function (number) { - if (number) { - if (number.hasOwnProperty('number')) number = number.number; - $scope.page = $scope.pages[number - 1] || $scope.pages[0]; - } - }; + self.goToPage = function (number) { + if (number) { + if (number.hasOwnProperty('number')) number = number.number; + $scope.page = $scope.pages[number - 1] || $scope.pages[0]; + } + }; - self.goToTop = function goToTop() { - $document.scrollTop(0); - }; + self.goToTop = function goToTop() { + $document.scrollTop(0); + }; - self.renderList = function () { - $scope.pages = []; - if (!$scope.list) return; + self.renderList = function () { + $scope.pages = []; + if (!$scope.list) return; - const perPage = _.parseInt(self.perPage); - const count = perPage ? Math.ceil($scope.list.length / perPage) : 1; + const perPage = _.parseInt(self.perPage); + const count = perPage ? Math.ceil($scope.list.length / perPage) : 1; - _.times(count, function (i) { - let page; + _.times(count, function (i) { + let page; - if (perPage) { - const start = perPage * i; - page = $scope.list.slice(start, start + perPage); - } else { - page = $scope.list.slice(0); - } + if (perPage) { + const start = perPage * i; + page = $scope.list.slice(start, start + perPage); + } else { + page = $scope.list.slice(0); + } - page.number = i + 1; - page.i = i; + page.number = i + 1; + page.i = i; - page.count = count; - page.first = page.number === 1; - page.last = page.number === count; - page.firstItem = (page.number - 1) * perPage + 1; - page.lastItem = Math.min(page.number * perPage, $scope.list.length); + page.count = count; + page.first = page.number === 1; + page.last = page.number === count; + page.firstItem = (page.number - 1) * perPage + 1; + page.lastItem = Math.min(page.number * perPage, $scope.list.length); - page.prev = $scope.pages[i - 1]; - if (page.prev) page.prev.next = page; + page.prev = $scope.pages[i - 1]; + if (page.prev) page.prev.next = page; - $scope.pages.push(page); - }); + $scope.pages.push(page); + }); - // set the new page, or restore the previous page number - if ($scope.page && $scope.page.i < $scope.pages.length) { - $scope.page = $scope.pages[$scope.page.i]; - } else { - $scope.page = $scope.pages[0]; - } + // set the new page, or restore the previous page number + if ($scope.page && $scope.page.i < $scope.pages.length) { + $scope.page = $scope.pages[$scope.page.i]; + } else { + $scope.page = $scope.pages[0]; + } - if ($scope.page && $scope.onPageChanged) { - $scope.onPageChanged($scope.page); - } - }; + if ($scope.page && $scope.onPageChanged) { + $scope.onPageChanged($scope.page); + } + }; - self.changePage = function (page) { - if (!page) { - $scope.otherPages = null; - return; - } + self.changePage = function (page) { + if (!page) { + $scope.otherPages = null; + return; + } - // setup the list of the other pages to link to - $scope.otherPages = []; - const width = +self.otherWidthGetter($scope) || 5; - let left = page.i - Math.round((width - 1) / 2); - let right = left + width - 1; + // setup the list of the other pages to link to + $scope.otherPages = []; + const width = +self.otherWidthGetter($scope) || 5; + let left = page.i - Math.round((width - 1) / 2); + let right = left + width - 1; - // shift neg count from left to right - if (left < 0) { - right += 0 - left; - left = 0; - } + // shift neg count from left to right + if (left < 0) { + right += 0 - left; + left = 0; + } - // shift extra right nums to left - const lastI = page.count - 1; - if (right > lastI) { - right = lastI; - left = right - width + 1; - } + // shift extra right nums to left + const lastI = page.count - 1; + if (right > lastI) { + right = lastI; + left = right - width + 1; + } - for (let i = left; i <= right; i++) { - const other = $scope.pages[i]; + for (let i = left; i <= right; i++) { + const other = $scope.pages[i]; - if (!other) continue; + if (!other) continue; - $scope.otherPages.push(other); - if (other.last) $scope.otherPages.containsLast = true; - if (other.first) $scope.otherPages.containsFirst = true; - } + $scope.otherPages.push(other); + if (other.last) $scope.otherPages.containsLast = true; + if (other.first) $scope.otherPages.containsFirst = true; + } - if ($scope.onPageChanged) { - $scope.onPageChanged($scope.page); - } - }; + if ($scope.onPageChanged) { + $scope.onPageChanged($scope.page); + } + }; - function setPerPage(val) { - let $ppParent = $scope; + function setPerPage(val) { + let $ppParent = $scope; - while ($ppParent && !_.has($ppParent, self.perPageProp)) { - $ppParent = $ppParent.$parent; - } + while ($ppParent && !_.has($ppParent, self.perPageProp)) { + $ppParent = $ppParent.$parent; + } - if ($ppParent) { - $ppParent[self.perPageProp] = val; - return true; - } + if ($ppParent) { + $ppParent[self.perPageProp] = val; + return true; } } - }; - }) - .directive('paginateControls', function () { + }, + }; +} + +export function PaginateControlsDirectiveProvider() { // this directive is automatically added by paginate if not found within it's $el - return { - restrict: 'E', - template: paginateControlsTemplate - }; - }); + return { + restrict: 'E', + template: paginateControlsTemplate, + }; +} + +uiModules + .get('kibana') + .directive('paginate', PaginateDirectiveProvider) + .directive('paginateControls', PaginateControlsDirectiveProvider); diff --git a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js index 80031efba6e48b..f8850d1691cddf 100644 --- a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js +++ b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js @@ -114,8 +114,10 @@ export const npSetup = { registerAction: sinon.fake(), registerTrigger: sinon.fake(), }, - feature_catalogue: { - register: sinon.fake(), + home: { + featureCatalogue: { + register: sinon.fake(), + }, }, }, }; @@ -229,8 +231,10 @@ export const npStart = { getTriggerActions: sinon.fake(), getTriggerCompatibleActions: sinon.fake(), }, - feature_catalogue: { - register: sinon.fake(), + home: { + featureCatalogue: { + register: sinon.fake(), + }, }, }, };