From cd703c21a0e2d21c960eae6aff58c3788c332b3e Mon Sep 17 00:00:00 2001 From: Luke Elmers Date: Wed, 21 Nov 2018 17:02:29 -0700 Subject: [PATCH 01/17] Enforce a max of 1 split agg in table vis. This makes the behavior of table vis consistent with other vis types. --- src/legacy/core_plugins/table_vis/public/table_vis.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/legacy/core_plugins/table_vis/public/table_vis.js b/src/legacy/core_plugins/table_vis/public/table_vis.js index aa6b96648bdfc..69a75fa591b3b 100644 --- a/src/legacy/core_plugins/table_vis/public/table_vis.js +++ b/src/legacy/core_plugins/table_vis/public/table_vis.js @@ -101,6 +101,8 @@ function TableVisTypeProvider(Private) { title: i18n.translate('tableVis.tableVisEditorConfig.schemas.splitTitle', { defaultMessage: 'Split Table', }), + min: 0, + max: 1, aggFilter: ['!filter'] } ]) From 5e407a362bdc207a88e271b824c2a6af535ef49f Mon Sep 17 00:00:00 2001 From: Luke Elmers Date: Wed, 21 Nov 2018 17:03:39 -0700 Subject: [PATCH 02/17] 7.0 migration for saved table visualizations. This migration modifies any table visualizations that have more than one split agg ("nested tables") to ensure they only have a maximum of 1 split. Other aggs are converted to bucket aggs. --- src/legacy/core_plugins/table_vis/index.js | 8 ++- .../core_plugins/table_vis/migrations.js | 56 +++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 src/legacy/core_plugins/table_vis/migrations.js diff --git a/src/legacy/core_plugins/table_vis/index.js b/src/legacy/core_plugins/table_vis/index.js index d6aaae0782d47..42a5c72a45ebd 100644 --- a/src/legacy/core_plugins/table_vis/index.js +++ b/src/legacy/core_plugins/table_vis/index.js @@ -18,6 +18,7 @@ */ import { resolve } from 'path'; +import migrations from './migrations'; export default function (kibana) { @@ -27,7 +28,12 @@ export default function (kibana) { 'plugins/table_vis/table_vis' ], styleSheetPaths: resolve(__dirname, 'public/index.scss'), - } + migrations: { + visualization: { + '7.0.0': migrations.visualization['7.0.0'] + } + } + }, }); } diff --git a/src/legacy/core_plugins/table_vis/migrations.js b/src/legacy/core_plugins/table_vis/migrations.js new file mode 100644 index 0000000000000..85711d08ed5dd --- /dev/null +++ b/src/legacy/core_plugins/table_vis/migrations.js @@ -0,0 +1,56 @@ +/* + * 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 { get, omit } from 'lodash'; + +const migrations = { + visualization: { + '7.0.0': (doc) => { + try { + const visState = JSON.parse(doc.attributes.visState); + if (get(visState, 'type') !== 'table') { + return doc; // do nothing; we only want to touch tables + } + + let splitFound = false; + visState.aggs = visState.aggs.map(agg => { + if (agg.schema === 'split') { + if (!splitFound) { + splitFound = true; + return agg; // leave the first split agg unchanged + } + agg.schema = 'bucket'; + // the `row` param is exclusively used by split aggs, so we remove it + agg.params = omit(agg.params, ['row']); + } + return agg; + }); + + doc.attributes.visState = JSON.stringify(visState); + return doc; + } + catch (e) { + // if anything goes wrong, do nothing and move on + return doc; + } + } + } +}; + +export default migrations; From 3c8b094b4c9c8791d3e54e1a4221075bc814741a Mon Sep 17 00:00:00 2001 From: Luke Elmers Date: Tue, 11 Dec 2018 17:40:09 -0700 Subject: [PATCH 03/17] Add unit tests. --- src/legacy/core_plugins/table_vis/index.js | 2 +- .../core_plugins/table_vis/migrations.js | 21 +- .../core_plugins/table_vis/migrations.test.js | 186 ++++++++++++++++++ 3 files changed, 199 insertions(+), 10 deletions(-) create mode 100644 src/legacy/core_plugins/table_vis/migrations.test.js diff --git a/src/legacy/core_plugins/table_vis/index.js b/src/legacy/core_plugins/table_vis/index.js index 42a5c72a45ebd..c791f7e163b73 100644 --- a/src/legacy/core_plugins/table_vis/index.js +++ b/src/legacy/core_plugins/table_vis/index.js @@ -18,7 +18,7 @@ */ import { resolve } from 'path'; -import migrations from './migrations'; +import { migrations } from './migrations'; export default function (kibana) { diff --git a/src/legacy/core_plugins/table_vis/migrations.js b/src/legacy/core_plugins/table_vis/migrations.js index 85711d08ed5dd..b65b5d0d71e32 100644 --- a/src/legacy/core_plugins/table_vis/migrations.js +++ b/src/legacy/core_plugins/table_vis/migrations.js @@ -17,9 +17,9 @@ * under the License. */ -import { get, omit } from 'lodash'; +import { cloneDeep, get, omit } from 'lodash'; -const migrations = { +export const migrations = { visualization: { '7.0.0': (doc) => { try { @@ -28,11 +28,11 @@ const migrations = { return doc; // do nothing; we only want to touch tables } - let splitFound = false; + let splitCount = 0; visState.aggs = visState.aggs.map(agg => { if (agg.schema === 'split') { - if (!splitFound) { - splitFound = true; + splitCount++; + if (splitCount === 1) { return agg; // leave the first split agg unchanged } agg.schema = 'bucket'; @@ -42,8 +42,13 @@ const migrations = { return agg; }); - doc.attributes.visState = JSON.stringify(visState); - return doc; + if (splitCount <= 1) { + return doc; // do nothing; we only want to touch tables with multiple split aggs + } + + const newDoc = cloneDeep(doc); + newDoc.attributes.visState = JSON.stringify(visState); + return newDoc; } catch (e) { // if anything goes wrong, do nothing and move on @@ -52,5 +57,3 @@ const migrations = { } } }; - -export default migrations; diff --git a/src/legacy/core_plugins/table_vis/migrations.test.js b/src/legacy/core_plugins/table_vis/migrations.test.js new file mode 100644 index 0000000000000..7ce6b9a587d9a --- /dev/null +++ b/src/legacy/core_plugins/table_vis/migrations.test.js @@ -0,0 +1,186 @@ +/* + * 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 { migrations } from './migrations'; + +describe('table vis migrations', () => { + + describe('7.0.0', () => { + const migrate = doc => migrations.visualization['7.0.0'](doc); + const generateDoc = ({ type, aggs }) => ({ + attributes: { + title: 'My Vis', + description: 'This is my super cool vis.', + visState: JSON.stringify({ type, aggs }), + uiStateJSON: '{}', + version: 1, + kibanaSavedObjectMeta: { + searchSourceJSON: '{}' + } + } + }); + + it('should return a new object if vis is table and has multiple split aggs', () => { + const aggs = [ + { + id: '1', + schema: 'metric', + params: {} + }, + { + id: '2', + schema: 'split', + params: { foo: 'bar', row: true } + }, + { + id: '3', + schema: 'split', + params: { hey: 'ya', row: false } + }, + ]; + const tableDoc = generateDoc({ type: 'table', aggs }); + const expected = tableDoc; + const actual = migrate(tableDoc); + expect(actual).not.toBe(expected); + }); + + it('should not touch any vis that is not table', () => { + const aggs = []; + const pieDoc = generateDoc({ type: 'pie', aggs }); + const expected = pieDoc; + const actual = migrate(pieDoc); + expect(actual).toBe(expected); + }); + + it('should not change values in any vis that is not table', () => { + const aggs = [ + { + id: '1', + schema: 'metric', + params: {} + }, + { + id: '2', + schema: 'split', + params: { foo: 'bar', row: true } + }, + { + id: '3', + schema: 'segment', + params: { hey: 'ya' } + } + ]; + const pieDoc = generateDoc({ type: 'pie', aggs }); + const expected = pieDoc; + const actual = migrate(pieDoc); + expect(actual).toEqual(expected); + }); + + it('should not touch table vis if there are not multiple split aggs', () => { + const aggs = [ + { + id: '1', + schema: 'metric', + params: {} + }, + { + id: '2', + schema: 'split', + params: { foo: 'bar', row: true } + } + ]; + const tableDoc = generateDoc({ type: 'table', aggs }); + const expected = tableDoc; + const actual = migrate(tableDoc); + expect(actual).toBe(expected); + }); + + it('should change all split aggs to `bucket` except the first', () => { + const aggs = [ + { + id: '1', + schema: 'metric', + params: {} + }, + { + id: '2', + schema: 'split', + params: { foo: 'bar', row: true } + }, + { + id: '3', + schema: 'split', + params: { hey: 'ya', row: false } + }, + { + id: '4', + schema: 'bucket', + params: { heyyy: 'yaaa' } + } + ]; + const expected = ['metric', 'split', 'bucket', 'bucket']; + const migrated = migrate(generateDoc({ type: 'table', aggs })); + const actual = JSON.parse(migrated.attributes.visState); + expect(actual.aggs.map(agg => agg.schema)).toEqual(expected); + }); + + it('should remove `rows` param from any aggs that are not `split`', () => { + const aggs = [ + { + id: '1', + schema: 'metric', + params: {} + }, + { + id: '2', + schema: 'split', + params: { foo: 'bar', row: true } + }, + { + id: '3', + schema: 'split', + params: { hey: 'ya', row: false } + } + ]; + const expected = [{}, { foo: 'bar', row: true }, { hey: 'ya' }]; + const migrated = migrate(generateDoc({ type: 'table', aggs })); + const actual = JSON.parse(migrated.attributes.visState); + expect(actual.aggs.map(agg => agg.params)).toEqual(expected); + }); + + it('should fail gracefully in the event of an error, returning the original doc unchanged', () => { + const doc = { + attributes: { + title: 'My Vis', + description: 'This is my super cool vis.', + visState: '!/// Intentionally malformed JSON ///!', + uiStateJSON: '{}', + version: 1, + kibanaSavedObjectMeta: { + searchSourceJSON: '{}' + } + } + }; + const actual = migrate(doc); + expect(actual).toEqual(doc); + expect(actual).toBe(doc); + }); + }); + +}); From ec32e827b74d4af4230162ce334c8eca913ce1ce Mon Sep 17 00:00:00 2001 From: Luke Elmers Date: Wed, 12 Dec 2018 12:58:49 -0700 Subject: [PATCH 04/17] Fix failing API integration tests. --- test/api_integration/apis/saved_objects/bulk_get.js | 3 +++ test/api_integration/apis/saved_objects/get.js | 3 +++ 2 files changed, 6 insertions(+) diff --git a/test/api_integration/apis/saved_objects/bulk_get.js b/test/api_integration/apis/saved_objects/bulk_get.js index e8d2bb77973e8..68773e5124039 100644 --- a/test/api_integration/apis/saved_objects/bulk_get.js +++ b/test/api_integration/apis/saved_objects/bulk_get.js @@ -54,6 +54,9 @@ export default function ({ getService }) { saved_objects: [ { id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', + migrationVersion: { + visualization: '7.0.0' + }, type: 'visualization', updated_at: '2017-09-21T18:51:23.794Z', version: resp.body.saved_objects[0].version, diff --git a/test/api_integration/apis/saved_objects/get.js b/test/api_integration/apis/saved_objects/get.js index 5d9af27c9f808..22ea4798f4324 100644 --- a/test/api_integration/apis/saved_objects/get.js +++ b/test/api_integration/apis/saved_objects/get.js @@ -36,6 +36,9 @@ export default function ({ getService }) { .then(resp => { expect(resp.body).to.eql({ id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', + migrationVersion: { + visualization: '7.0.0' + }, type: 'visualization', updated_at: '2017-09-21T18:51:23.794Z', version: resp.body.version, From b31174c3b9da8cf48f68af128f5d2a1a4f008def Mon Sep 17 00:00:00 2001 From: Luke Elmers Date: Wed, 12 Dec 2018 16:07:25 -0700 Subject: [PATCH 05/17] Add migrationVersion to a few more failing tests. --- test/api_integration/apis/saved_objects/bulk_create.js | 3 +++ test/api_integration/apis/saved_objects/create.js | 6 ++++++ test/api_integration/apis/saved_objects/update.js | 3 +++ .../saved_object_api_integration/common/suites/bulk_get.ts | 3 +++ .../saved_object_api_integration/common/suites/create.ts | 3 +++ .../test/saved_object_api_integration/common/suites/find.ts | 3 +++ .../test/saved_object_api_integration/common/suites/get.ts | 3 +++ .../saved_object_api_integration/common/suites/update.ts | 3 +++ 8 files changed, 27 insertions(+) diff --git a/test/api_integration/apis/saved_objects/bulk_create.js b/test/api_integration/apis/saved_objects/bulk_create.js index 153dda4691fa6..30d34914b87cd 100644 --- a/test/api_integration/apis/saved_objects/bulk_create.js +++ b/test/api_integration/apis/saved_objects/bulk_create.js @@ -97,6 +97,9 @@ export default function ({ getService }) { { type: 'visualization', id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', + migrationVersion: { + visualization: '7.0.0', + }, updated_at: resp.body.saved_objects[0].updated_at, version: 1, attributes: { diff --git a/test/api_integration/apis/saved_objects/create.js b/test/api_integration/apis/saved_objects/create.js index 516ca618da2dd..379539928ebaf 100644 --- a/test/api_integration/apis/saved_objects/create.js +++ b/test/api_integration/apis/saved_objects/create.js @@ -47,6 +47,9 @@ export default function ({ getService }) { expect(resp.body).to.eql({ id: resp.body.id, type: 'visualization', + migrationVersion: { + visualization: '7.0.0' + }, updated_at: resp.body.updated_at, version: 1, attributes: { @@ -85,6 +88,9 @@ export default function ({ getService }) { expect(resp.body).to.eql({ id: resp.body.id, type: 'visualization', + migrationVersion: { + visualization: '7.0.0' + }, updated_at: resp.body.updated_at, version: 1, attributes: { diff --git a/test/api_integration/apis/saved_objects/update.js b/test/api_integration/apis/saved_objects/update.js index e6ca3d0317bf3..449844a398559 100644 --- a/test/api_integration/apis/saved_objects/update.js +++ b/test/api_integration/apis/saved_objects/update.js @@ -47,6 +47,9 @@ export default function ({ getService }) { expect(resp.body).to.eql({ id: resp.body.id, type: 'visualization', + migrationVersion: { + visualization: '7.0.0', + }, updated_at: resp.body.updated_at, version: 2, attributes: { diff --git a/x-pack/test/saved_object_api_integration/common/suites/bulk_get.ts b/x-pack/test/saved_object_api_integration/common/suites/bulk_get.ts index dc5b7eaf3c75e..35abd64a3a9af 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/bulk_get.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/bulk_get.ts @@ -97,6 +97,9 @@ export function bulkGetTestSuiteFactory(esArchiver: any, supertest: SuperTest) { type: 'visualization', id: `${getIdPrefix(spaceId)}dd7caf20-9efd-11e7-acb3-3dab96693fab`, + migrationVersion: { + visualization: '7.0.0', + }, version: 1, attributes: { title: 'Count of requests', diff --git a/x-pack/test/saved_object_api_integration/common/suites/get.ts b/x-pack/test/saved_object_api_integration/common/suites/get.ts index 593bf098b0801..c96ed5ada9ded 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/get.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/get.ts @@ -103,6 +103,9 @@ export function getTestSuiteFactory(esArchiver: any, supertest: SuperTest) expect(resp.body).to.eql({ id: `${getIdPrefix(spaceId)}dd7caf20-9efd-11e7-acb3-3dab96693fab`, type: 'visualization', + migrationVersion: { + visualization: '7.0.0', + }, updated_at: '2017-09-21T18:51:23.794Z', version: resp.body.version, attributes: { diff --git a/x-pack/test/saved_object_api_integration/common/suites/update.ts b/x-pack/test/saved_object_api_integration/common/suites/update.ts index e45fa1928b809..1d12093df611b 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/update.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/update.ts @@ -106,6 +106,9 @@ export function updateTestSuiteFactory(esArchiver: any, supertest: SuperTest Date: Wed, 12 Dec 2018 16:26:38 -0700 Subject: [PATCH 06/17] Update another saved object test. --- test/api_integration/apis/saved_objects/find.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/api_integration/apis/saved_objects/find.js b/test/api_integration/apis/saved_objects/find.js index c9b1e9fc73f4a..f9ff4a8424a49 100644 --- a/test/api_integration/apis/saved_objects/find.js +++ b/test/api_integration/apis/saved_objects/find.js @@ -42,6 +42,9 @@ export default function ({ getService }) { { type: 'visualization', id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', + migrationVersion: { + visualization: '7.0.0', + }, version: 1, attributes: { 'title': 'Count of requests' From 81e14792ce57cc7d9a3a6117e0f8f01fa6c42660 Mon Sep 17 00:00:00 2001 From: Luke Elmers Date: Wed, 12 Dec 2018 17:06:51 -0700 Subject: [PATCH 07/17] Switch table vis to using default legacy response handler. --- src/legacy/core_plugins/table_vis/public/table_vis.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/legacy/core_plugins/table_vis/public/table_vis.js b/src/legacy/core_plugins/table_vis/public/table_vis.js index 69a75fa591b3b..04378952d1ec2 100644 --- a/src/legacy/core_plugins/table_vis/public/table_vis.js +++ b/src/legacy/core_plugins/table_vis/public/table_vis.js @@ -26,7 +26,6 @@ import { VisFactoryProvider } from 'ui/vis/vis_factory'; import { Schemas } from 'ui/vis/editors/default/schemas'; import tableVisTemplate from './table_vis.html'; import { VisTypesRegistryProvider } from 'ui/registry/vis_types'; -import { legacyTableResponseHandler } from './legacy_response_handler'; // we need to load the css ourselves @@ -107,7 +106,6 @@ function TableVisTypeProvider(Private) { } ]) }, - responseHandler: legacyTableResponseHandler, responseHandlerConfig: { asAggConfigResults: true }, From 9e4207bf33dd929923bacc21a2a62f4f810adbd2 Mon Sep 17 00:00:00 2001 From: Luke Elmers Date: Wed, 12 Dec 2018 17:18:00 -0700 Subject: [PATCH 08/17] Remove unneeded migrationVersion from a couple tests. --- test/api_integration/apis/saved_objects/bulk_create.js | 3 --- x-pack/test/saved_object_api_integration/common/suites/find.ts | 3 --- 2 files changed, 6 deletions(-) diff --git a/test/api_integration/apis/saved_objects/bulk_create.js b/test/api_integration/apis/saved_objects/bulk_create.js index 30d34914b87cd..153dda4691fa6 100644 --- a/test/api_integration/apis/saved_objects/bulk_create.js +++ b/test/api_integration/apis/saved_objects/bulk_create.js @@ -97,9 +97,6 @@ export default function ({ getService }) { { type: 'visualization', id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', - migrationVersion: { - visualization: '7.0.0', - }, updated_at: resp.body.saved_objects[0].updated_at, version: 1, attributes: { diff --git a/x-pack/test/saved_object_api_integration/common/suites/find.ts b/x-pack/test/saved_object_api_integration/common/suites/find.ts index 68aa882f6c33a..d7bc0180f8e2b 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/find.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/find.ts @@ -104,9 +104,6 @@ export function findTestSuiteFactory(esArchiver: any, supertest: SuperTest) { type: 'visualization', id: `${getIdPrefix(spaceId)}dd7caf20-9efd-11e7-acb3-3dab96693fab`, - migrationVersion: { - visualization: '7.0.0', - }, version: 1, attributes: { title: 'Count of requests', From f01f3ac6cc7ee3e86bb02aa33103333d8bfdedad Mon Sep 17 00:00:00 2001 From: Luke Elmers Date: Wed, 2 Jan 2019 14:28:26 -0700 Subject: [PATCH 09/17] Remove nested split table functional test. --- test/functional/apps/visualize/_data_table.js | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/test/functional/apps/visualize/_data_table.js b/test/functional/apps/visualize/_data_table.js index d482438fee4d7..2200a5e16c7ed 100644 --- a/test/functional/apps/visualize/_data_table.js +++ b/test/functional/apps/visualize/_data_table.js @@ -394,21 +394,6 @@ export default function ({ getService, getPageObjects }) { ] ]); }); - - it.skip('should allow nesting multiple splits', async () => { - // This test can be removed as soon as we remove the nested split table - // feature (https://github.com/elastic/kibana/issues/24560). (7.0) - await PageObjects.visualize.clickData(); - await PageObjects.visualize.clickAddBucket(); - await PageObjects.visualize.clickBucket('Split Table'); - await PageObjects.visualize.selectAggregation('Terms'); - await PageObjects.visualize.clickSplitDirection('column'); - await PageObjects.visualize.selectField('machine.os.raw'); - await PageObjects.visualize.setSize(2); - await PageObjects.visualize.clickGo(); - const splitCount = await PageObjects.visualize.countNestedTables(); - expect(splitCount).to.be.eql([ 12, 10, 8 ]); - }); }); }); From e8eda72f431d5580ab7812e0093fa3bdf3dd5999 Mon Sep 17 00:00:00 2001 From: Luke Elmers Date: Tue, 15 Jan 2019 14:10:20 -0700 Subject: [PATCH 10/17] Fix tests --- test/api_integration/apis/saved_objects/find.js | 3 --- .../test/saved_object_api_integration/common/suites/update.ts | 3 --- 2 files changed, 6 deletions(-) diff --git a/test/api_integration/apis/saved_objects/find.js b/test/api_integration/apis/saved_objects/find.js index f9ff4a8424a49..c9b1e9fc73f4a 100644 --- a/test/api_integration/apis/saved_objects/find.js +++ b/test/api_integration/apis/saved_objects/find.js @@ -42,9 +42,6 @@ export default function ({ getService }) { { type: 'visualization', id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', - migrationVersion: { - visualization: '7.0.0', - }, version: 1, attributes: { 'title': 'Count of requests' diff --git a/x-pack/test/saved_object_api_integration/common/suites/update.ts b/x-pack/test/saved_object_api_integration/common/suites/update.ts index 1d12093df611b..e45fa1928b809 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/update.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/update.ts @@ -106,9 +106,6 @@ export function updateTestSuiteFactory(esArchiver: any, supertest: SuperTest Date: Tue, 15 Jan 2019 15:13:24 -0700 Subject: [PATCH 11/17] Ensure bookmarked URLs do not render invalid appState. --- .../kibana/public/visualize/editor/editor.js | 7 +++ .../public/visualize/editor/lib/index.js | 20 +++++++ .../visualize/editor/lib/migrate_app_state.js | 59 +++++++++++++++++++ 3 files changed, 86 insertions(+) create mode 100644 src/legacy/core_plugins/kibana/public/visualize/editor/lib/index.js create mode 100644 src/legacy/core_plugins/kibana/public/visualize/editor/lib/migrate_app_state.js diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js index b554f761f002d..52914ecf9fb64 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js @@ -32,6 +32,7 @@ import { VisTypesRegistryProvider } from 'ui/registry/vis_types'; import { DocTitleProvider } from 'ui/doc_title'; import { FilterBarQueryFilterProvider } from 'ui/filter_bar/query_filter'; import { stateMonitorFactory } from 'ui/state_management/state_monitor_factory'; +import { migrateAppState } from './lib'; import uiRoutes from 'ui/routes'; import { uiModules } from 'ui/modules'; import editorTemplate from './editor.html'; @@ -264,6 +265,12 @@ function VisEditor( // This is used to sync visualization state with the url when `appState.save()` is called. const appState = new AppState(stateDefaults); + // Initializing appState does two things - first it translates the defaults into AppState, + // second it updates appState based on the url (the url trumps the defaults). This means if + // we update the state format at all and want to handle BWC, we must not only migrate the + // data stored with saved vis, but also any old state in the url. + migrateAppState(appState); + // The savedVis is pulled from elasticsearch, but the appState is pulled from the url, with the // defaults applied. If the url was from a previous session which included modifications to the // appState then they won't be equal. diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/lib/index.js b/src/legacy/core_plugins/kibana/public/visualize/editor/lib/index.js new file mode 100644 index 0000000000000..42284c9a03dcd --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/lib/index.js @@ -0,0 +1,20 @@ +/* + * 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 { migrateAppState } from './migrate_app_state'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/lib/migrate_app_state.js b/src/legacy/core_plugins/kibana/public/visualize/editor/lib/migrate_app_state.js new file mode 100644 index 0000000000000..be0a3b429b0c9 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/lib/migrate_app_state.js @@ -0,0 +1,59 @@ +/* + * 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 { get, omit } from 'lodash'; + +/** + * Creates a new instance of AppState based on the table vis state. + * + * Dashboards have a similar implementation; see + * core_plugins/kibana/public/dashboard/lib/migrate_app_state + * + * @param appState {AppState} AppState class to instantiate + */ +export function migrateAppState(appState) { + + // For BWC in pre 7.0 versions where table visualizations could have multiple aggs + // with `schema === 'split'`. This ensures that bookmarked URLs with deprecated params + // are rewritten to the correct state. See core_plugins/table_vis/migrations. + if (appState.vis.type === 'table') { + const visAggs = get(appState, 'vis.aggs', []); + let splitCount = 0; + const migratedAggs = visAggs.map(agg => { + if (agg.schema === 'split') { + splitCount++; + if (splitCount === 1) { + return agg; // leave the first split agg unchanged + } + agg.schema = 'bucket'; + // the `row` param is exclusively used by split aggs, so we remove it + agg.params = omit(agg.params, ['row']); + } + return agg; + }); + + if (splitCount <= 1 || migratedAggs.length === 0) { + return; // do nothing; we only want to touch tables with multiple split aggs + } + + appState.vis.aggs = migratedAggs; + appState.save(); + } + +} From 299044766379a3ec71d6f406d5c2c70eedc59ab2 Mon Sep 17 00:00:00 2001 From: Luke Elmers Date: Tue, 15 Jan 2019 15:14:54 -0700 Subject: [PATCH 12/17] Fix test. --- test/api_integration/apis/saved_objects/update.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/api_integration/apis/saved_objects/update.js b/test/api_integration/apis/saved_objects/update.js index 449844a398559..e6ca3d0317bf3 100644 --- a/test/api_integration/apis/saved_objects/update.js +++ b/test/api_integration/apis/saved_objects/update.js @@ -47,9 +47,6 @@ export default function ({ getService }) { expect(resp.body).to.eql({ id: resp.body.id, type: 'visualization', - migrationVersion: { - visualization: '7.0.0', - }, updated_at: resp.body.updated_at, version: 2, attributes: { From f56046d19430dcf9d051430e6df7b2ace7f3130c Mon Sep 17 00:00:00 2001 From: Luke Elmers Date: Wed, 16 Jan 2019 10:20:50 -0700 Subject: [PATCH 13/17] Address first set of feedback. --- .../visualize/editor/lib/migrate_app_state.js | 40 ++++++++++--------- src/legacy/core_plugins/table_vis/index.js | 6 +-- .../core_plugins/table_vis/migrations.js | 3 +- 3 files changed, 23 insertions(+), 26 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/lib/migrate_app_state.js b/src/legacy/core_plugins/kibana/public/visualize/editor/lib/migrate_app_state.js index be0a3b429b0c9..049ce048239db 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/lib/migrate_app_state.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/lib/migrate_app_state.js @@ -28,32 +28,34 @@ import { get, omit } from 'lodash'; * @param appState {AppState} AppState class to instantiate */ export function migrateAppState(appState) { - // For BWC in pre 7.0 versions where table visualizations could have multiple aggs // with `schema === 'split'`. This ensures that bookmarked URLs with deprecated params // are rewritten to the correct state. See core_plugins/table_vis/migrations. - if (appState.vis.type === 'table') { - const visAggs = get(appState, 'vis.aggs', []); - let splitCount = 0; - const migratedAggs = visAggs.map(agg => { - if (agg.schema === 'split') { - splitCount++; - if (splitCount === 1) { - return agg; // leave the first split agg unchanged - } - agg.schema = 'bucket'; - // the `row` param is exclusively used by split aggs, so we remove it - agg.params = omit(agg.params, ['row']); - } + if (appState.vis.type !== 'table') { + return; + } + + const visAggs = get(appState, 'vis.aggs', []); + let splitCount = 0; + const migratedAggs = visAggs.map(agg => { + if (agg.schema !== 'split') { return agg; - }); + } - if (splitCount <= 1 || migratedAggs.length === 0) { - return; // do nothing; we only want to touch tables with multiple split aggs + splitCount++; + if (splitCount === 1) { + return agg; // leave the first split agg unchanged } + agg.schema = 'bucket'; + // the `row` param is exclusively used by split aggs, so we remove it + agg.params = omit(agg.params, ['row']); + return agg; + }); - appState.vis.aggs = migratedAggs; - appState.save(); + if (splitCount <= 1) { + return; // do nothing; we only want to touch tables with multiple split aggs } + appState.vis.aggs = migratedAggs; + appState.save(); } diff --git a/src/legacy/core_plugins/table_vis/index.js b/src/legacy/core_plugins/table_vis/index.js index c791f7e163b73..aff7870637f33 100644 --- a/src/legacy/core_plugins/table_vis/index.js +++ b/src/legacy/core_plugins/table_vis/index.js @@ -28,11 +28,7 @@ export default function (kibana) { 'plugins/table_vis/table_vis' ], styleSheetPaths: resolve(__dirname, 'public/index.scss'), - migrations: { - visualization: { - '7.0.0': migrations.visualization['7.0.0'] - } - } + migrations, }, }); diff --git a/src/legacy/core_plugins/table_vis/migrations.js b/src/legacy/core_plugins/table_vis/migrations.js index b65b5d0d71e32..c54a06e44d762 100644 --- a/src/legacy/core_plugins/table_vis/migrations.js +++ b/src/legacy/core_plugins/table_vis/migrations.js @@ -49,8 +49,7 @@ export const migrations = { const newDoc = cloneDeep(doc); newDoc.attributes.visState = JSON.stringify(visState); return newDoc; - } - catch (e) { + } catch (e) { // if anything goes wrong, do nothing and move on return doc; } From 275f2733e23e359e55fe4583eb6e0abb9f1f09ea Mon Sep 17 00:00:00 2001 From: Luke Elmers Date: Wed, 16 Jan 2019 13:02:08 -0700 Subject: [PATCH 14/17] Return early if not split agg. --- .../core_plugins/table_vis/migrations.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/legacy/core_plugins/table_vis/migrations.js b/src/legacy/core_plugins/table_vis/migrations.js index c54a06e44d762..1a2c386cf7f8b 100644 --- a/src/legacy/core_plugins/table_vis/migrations.js +++ b/src/legacy/core_plugins/table_vis/migrations.js @@ -30,15 +30,17 @@ export const migrations = { let splitCount = 0; visState.aggs = visState.aggs.map(agg => { - if (agg.schema === 'split') { - splitCount++; - if (splitCount === 1) { - return agg; // leave the first split agg unchanged - } - agg.schema = 'bucket'; - // the `row` param is exclusively used by split aggs, so we remove it - agg.params = omit(agg.params, ['row']); + if (agg.schema !== 'split') { + return agg; } + + splitCount++; + if (splitCount === 1) { + return agg; // leave the first split agg unchanged + } + agg.schema = 'bucket'; + // the `row` param is exclusively used by split aggs, so we remove it + agg.params = omit(agg.params, ['row']); return agg; }); From 55660b1f5aa9c276c68ee7a899a7f37af3a2544a Mon Sep 17 00:00:00 2001 From: Luke Elmers Date: Thu, 17 Jan 2019 17:25:38 -0700 Subject: [PATCH 15/17] Throw error when vis migrations fail. --- src/legacy/core_plugins/table_vis/migrations.js | 3 +-- src/legacy/core_plugins/table_vis/migrations.test.js | 6 ++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/legacy/core_plugins/table_vis/migrations.js b/src/legacy/core_plugins/table_vis/migrations.js index 1a2c386cf7f8b..8dee9566f0e8a 100644 --- a/src/legacy/core_plugins/table_vis/migrations.js +++ b/src/legacy/core_plugins/table_vis/migrations.js @@ -52,8 +52,7 @@ export const migrations = { newDoc.attributes.visState = JSON.stringify(visState); return newDoc; } catch (e) { - // if anything goes wrong, do nothing and move on - return doc; + throw new Error(`Failure attempting to migrate saved object '${doc.attributes.title}' - ${e}`); } } } diff --git a/src/legacy/core_plugins/table_vis/migrations.test.js b/src/legacy/core_plugins/table_vis/migrations.test.js index 7ce6b9a587d9a..dc8ddb594af02 100644 --- a/src/legacy/core_plugins/table_vis/migrations.test.js +++ b/src/legacy/core_plugins/table_vis/migrations.test.js @@ -164,7 +164,7 @@ describe('table vis migrations', () => { expect(actual.aggs.map(agg => agg.params)).toEqual(expected); }); - it('should fail gracefully in the event of an error, returning the original doc unchanged', () => { + it('should throw with a reference to the doc name if something goes wrong', () => { const doc = { attributes: { title: 'My Vis', @@ -177,9 +177,7 @@ describe('table vis migrations', () => { } } }; - const actual = migrate(doc); - expect(actual).toEqual(doc); - expect(actual).toBe(doc); + expect(() => migrate(doc)).toThrowError(/My Vis/); }); }); From 7f4de8153ef427b9abd0dfdc147df2a69124e1c1 Mon Sep 17 00:00:00 2001 From: Luke Elmers Date: Tue, 22 Jan 2019 10:38:53 -0700 Subject: [PATCH 16/17] Resolve merge conflicts with master --- src/legacy/core_plugins/table_vis/public/table_vis.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/legacy/core_plugins/table_vis/public/table_vis.js b/src/legacy/core_plugins/table_vis/public/table_vis.js index 04378952d1ec2..8d29c2007326b 100644 --- a/src/legacy/core_plugins/table_vis/public/table_vis.js +++ b/src/legacy/core_plugins/table_vis/public/table_vis.js @@ -26,6 +26,7 @@ import { VisFactoryProvider } from 'ui/vis/vis_factory'; import { Schemas } from 'ui/vis/editors/default/schemas'; import tableVisTemplate from './table_vis.html'; import { VisTypesRegistryProvider } from 'ui/registry/vis_types'; +import { VisFiltersProvider } from 'ui/vis/vis_filters'; // we need to load the css ourselves @@ -41,6 +42,7 @@ VisTypesRegistryProvider.register(TableVisTypeProvider); // define the TableVisType function TableVisTypeProvider(Private) { const VisFactory = Private(VisFactoryProvider); + const visFilters = Private(VisFiltersProvider); // define the TableVisController which is used in the template // by angular's ng-controller directive @@ -106,8 +108,10 @@ function TableVisTypeProvider(Private) { } ]) }, - responseHandlerConfig: { - asAggConfigResults: true + events: { + filterBucket: { + defaultAction: visFilters.filter, + }, }, hierarchicalData: function (vis) { return Boolean(vis.params.showPartialRows || vis.params.showMetricsAtAllLevels); From 8107b28955212c0f60fdd15ba39e144cd2f74730 Mon Sep 17 00:00:00 2001 From: Luke Elmers Date: Fri, 25 Jan 2019 09:26:01 -0700 Subject: [PATCH 17/17] Remove legacy response handler. --- .../__tests__/_legacy_response_handler.js | 154 ------------------ .../table_vis/public/__tests__/index.js | 1 - .../public/legacy_response_handler.js | 105 ------------ 3 files changed, 260 deletions(-) delete mode 100644 src/legacy/core_plugins/table_vis/public/__tests__/_legacy_response_handler.js delete mode 100644 src/legacy/core_plugins/table_vis/public/legacy_response_handler.js diff --git a/src/legacy/core_plugins/table_vis/public/__tests__/_legacy_response_handler.js b/src/legacy/core_plugins/table_vis/public/__tests__/_legacy_response_handler.js deleted file mode 100644 index 7759784dd40a4..0000000000000 --- a/src/legacy/core_plugins/table_vis/public/__tests__/_legacy_response_handler.js +++ /dev/null @@ -1,154 +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 expect from 'expect.js'; -import sinon from 'sinon'; -import ngMock from 'ng_mock'; -import { AggConfig } from '../../../../../ui/public/vis/agg_config'; -import AggConfigResult from '../../../../../ui/public/vis/agg_config_result'; -import { VisProvider } from '../../../../../ui/public/vis'; -import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; -import { splitRowsOnColumn, splitTable, legacyTableResponseHandler } from '../legacy_response_handler'; - -const rows = [ - { 'col-0-2': 'A', 'col-1-3': 100, 'col-2-1': 'Jim' }, - { 'col-0-2': 'A', 'col-1-3': 0, 'col-2-1': 'Dwight' }, - { 'col-0-2': 'B', 'col-1-3': 24, 'col-2-1': 'Angela' }, - { 'col-0-2': 'C', 'col-1-3': 1, 'col-2-1': 'Angela' }, - { 'col-0-2': 'C', 'col-1-3': 7, 'col-2-1': 'Angela' }, - { 'col-0-2': 'C', 'col-1-3': -30, 'col-2-1': 'Jim' }, -]; - -describe('Table Vis Legacy Response Handler', () => { - - let Vis; - let indexPattern; - let columns; - let mockAggConfig; - let mockSplitAggConfig; - - beforeEach(ngMock.module('kibana')); - beforeEach(ngMock.inject(function (Private) { - Vis = Private(VisProvider); - indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - const vis = new Vis(indexPattern, { type: 'table', aggs: [] }); - - mockAggConfig = new AggConfig(vis.aggs, { type: 'terms', schema: 'metric' }); - mockSplitAggConfig = new AggConfig(vis.aggs, { type: 'terms', schema: 'split' }); - - sinon.stub(mockSplitAggConfig, 'fieldFormatter').returns(val => val); - sinon.stub(mockSplitAggConfig, 'makeLabel').returns('some label'); - - columns = [ - { id: 'col-0-2', name: 'Team', aggConfig: mockSplitAggConfig }, - { id: 'col-1-3', name: 'Score', aggConfig: mockAggConfig }, - { id: 'col-2-1', name: 'Leader', aggConfig: mockAggConfig }, - ]; - })); - - describe('#splitRowsOnColumn', () => { - it('should be a function', () => { - expect(typeof splitRowsOnColumn).to.be('function'); - }); - - it('.results should return an array with each unique value for the column id', () => { - const expected = ['A', 'B', 'C']; - const actual = splitRowsOnColumn(rows, 'col-0-2'); - expect(actual.results).to.eql(expected); - }); - - it('.results should preserve types in case a result is not a string', () => { - const expected = [0, 1, 7, 24, 100, -30]; - const actual = splitRowsOnColumn(rows, 'col-1-3'); - expect(actual.results).to.eql(expected); - actual.results.forEach(result => expect(typeof result).to.eql('number')); - }); - - it('.rowsGroupedByResult should return an object with rows grouped by value for the column id', () => { - const expected = { - A: [ - { 'col-1-3': 100, 'col-2-1': 'Jim' }, - { 'col-1-3': 0, 'col-2-1': 'Dwight' }, - ], - B: [ - { 'col-1-3': 24, 'col-2-1': 'Angela' }, - ], - C: [ - { 'col-1-3': 1, 'col-2-1': 'Angela' }, - { 'col-1-3': 7, 'col-2-1': 'Angela' }, - { 'col-1-3': -30, 'col-2-1': 'Jim' }, - ], - }; - const actual = splitRowsOnColumn(rows, 'col-0-2'); - expect(actual.rowsGroupedByResult).to.eql(expected); - }); - }); - - describe('#splitTable', () => { - it('should be a function', () => { - expect(typeof splitTable).to.be('function'); - }); - - it('should return an array of objects with the expected keys', () => { - const expected = ['$parent', 'aggConfig', 'title', 'key', 'tables']; - const actual = splitTable(columns, rows, null); - expect(Object.keys(actual[0])).to.eql(expected); - }); - - it('should return a reference to the parent AggConfigResult', () => { - const actual = splitTable(columns, rows, null); - expect(actual[0].$parent).to.be.a(AggConfigResult); - }); - - it('should return the correct split values', () => { - const expected = ['A', 'B', 'C']; - const actual = splitTable(columns, rows, null); - expect(actual.map(i => i.key)).to.eql(expected); - }); - - it('should return the correct titles', () => { - const expected = ['A: some label', 'B: some label', 'C: some label']; - const actual = splitTable(columns, rows, null); - expect(actual.map(i => i.title)).to.eql(expected); - }); - - it('should return nested split tables with the correct number of entries', () => { - const expected = [2, 1, 3]; - const actual = splitTable(columns, rows, null); - expect(actual.map(i => i.tables[0].rows.length)).to.eql(expected); - }); - - it('should return nested split tables with rows of the correct type', () => { - const actual = splitTable(columns, rows, null); - expect(actual[0].tables[0].rows[0][0]).to.be.a(AggConfigResult); - }); - }); - - describe('#legacyTableResponseHandler', () => { - it('should be a function', () => { - expect(typeof legacyTableResponseHandler).to.be('function'); - }); - - it('should return the correct number of tables', async () => { - const actual = await legacyTableResponseHandler({ columns, rows }); - expect(actual.tables).to.have.length(3); - }); - }); - -}); diff --git a/src/legacy/core_plugins/table_vis/public/__tests__/index.js b/src/legacy/core_plugins/table_vis/public/__tests__/index.js index 2508ba5764be8..1f4f3ec30ee4a 100644 --- a/src/legacy/core_plugins/table_vis/public/__tests__/index.js +++ b/src/legacy/core_plugins/table_vis/public/__tests__/index.js @@ -18,7 +18,6 @@ */ import './_table_vis_controller'; -import './_legacy_response_handler'; describe('Table Vis', function () { }); diff --git a/src/legacy/core_plugins/table_vis/public/legacy_response_handler.js b/src/legacy/core_plugins/table_vis/public/legacy_response_handler.js deleted file mode 100644 index 74ca19df1d1fd..0000000000000 --- a/src/legacy/core_plugins/table_vis/public/legacy_response_handler.js +++ /dev/null @@ -1,105 +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 { get, findLastIndex } from 'lodash'; -import AggConfigResult from 'ui/vis/agg_config_result'; - -/** - * Takes an array of tabified rows and splits them by column value: - * - * const rows = [ - * { col-1: 'foo', col-2: 'X' }, - * { col-1: 'bar', col-2: 50 }, - * { col-1: 'baz', col-2: 'X' }, - * ]; - * const splitRows = splitRowsOnColumn(rows, 'col-2'); - * splitRows.results; // ['X', 50]; - * splitRows.rowsGroupedByResult; // { X: [{ col-1: 'foo' }, { col-1: 'baz' }], 50: [{ col-1: 'bar' }] } - */ -export function splitRowsOnColumn(rows, columnId) { - const resultsMap = {}; // Used to preserve types, since object keys are always converted to strings. - return { - rowsGroupedByResult: rows.reduce((acc, row) => { - const { [columnId]: splitValue, ...rest } = row; - resultsMap[splitValue] = splitValue; - acc[splitValue] = [...(acc[splitValue] || []), rest]; - return acc; - }, {}), - results: Object.values(resultsMap), - }; -} - -export function splitTable(columns, rows, $parent) { - const splitColumn = columns.find(column => get(column, 'aggConfig.schema.name') === 'split'); - - if (!splitColumn) { - return [{ - $parent, - columns: columns.map(column => ({ title: column.name, ...column })), - rows: rows.map((row, rowIndex) => { - return columns.map(column => { - const aggConfigResult = new AggConfigResult(column.aggConfig, $parent, row[column.id], row[column.id]); - aggConfigResult.rawData = { - table: { columns, rows }, - column: columns.findIndex(c => c.id === column.id), - row: rowIndex, - }; - return aggConfigResult; - }); - }) - }]; - } - - const splitColumnIndex = columns.findIndex(column => column.id === splitColumn.id); - const splitRows = splitRowsOnColumn(rows, splitColumn.id); - - // Check if there are buckets after the first metric. - const firstMetricsColumnIndex = columns.findIndex(column => get(column, 'aggConfig.type.type') === 'metrics'); - const lastBucketsColumnIndex = findLastIndex(columns, column => get(column, 'aggConfig.type.type') === 'buckets'); - const metricsAtAllLevels = firstMetricsColumnIndex < lastBucketsColumnIndex; - - // Calculate metrics:bucket ratio. - const numberOfMetrics = columns.filter(column => get(column, 'aggConfig.type.type') === 'metrics').length; - const numberOfBuckets = columns.filter(column => get(column, 'aggConfig.type.type') === 'buckets').length; - const metricsPerBucket = numberOfMetrics / numberOfBuckets; - - const filteredColumns = columns - .filter((column, i) => { - const isSplitColumn = i === splitColumnIndex; - const isSplitMetric = metricsAtAllLevels && i > splitColumnIndex && i <= splitColumnIndex + metricsPerBucket; - return !isSplitColumn && !isSplitMetric; - }) - .map(column => ({ title: column.name, ...column })); - - return splitRows.results.map(splitValue => { - const $newParent = new AggConfigResult(splitColumn.aggConfig, $parent, splitValue, splitValue); - return { - $parent: $newParent, - aggConfig: splitColumn.aggConfig, - title: `${splitColumn.aggConfig.fieldFormatter()(splitValue)}: ${splitColumn.aggConfig.makeLabel()}`, - key: splitValue, - // Recurse with filtered data to continue the search for additional split columns. - tables: splitTable(filteredColumns, splitRows.rowsGroupedByResult[splitValue], $newParent), - }; - }); -} - -export async function legacyTableResponseHandler(table) { - return { tables: splitTable(table.columns, table.rows, null) }; -}