diff --git a/src/legacy/core_plugins/kibana/migrations/migrations.js b/src/legacy/core_plugins/kibana/migrations/migrations.js index 03b2f5b898345..9f3561f39101d 100644 --- a/src/legacy/core_plugins/kibana/migrations/migrations.js +++ b/src/legacy/core_plugins/kibana/migrations/migrations.js @@ -285,6 +285,50 @@ function transformFilterStringToQueryObject(doc) { } return newDoc; } +function transformSplitFiltersStringToQueryObject(doc) { + // Migrate split_filters in TSVB objects that weren't migrated in 7.3 + // If any filters exist and they are a string, we assume them to be lucene syntax and transform the filter into an object accordingly + const newDoc = cloneDeep(doc); + const visStateJSON = get(doc, 'attributes.visState'); + if (visStateJSON) { + let visState; + try { + visState = JSON.parse(visStateJSON); + } catch (e) { + // let it go, the data is invalid and we'll leave it as is + } + if (visState) { + const visType = get(visState, 'params.type'); + const tsvbTypes = ['metric', 'markdown', 'top_n', 'gauge', 'table', 'timeseries']; + if (tsvbTypes.indexOf(visType) === -1) { + // skip + return doc; + } + // migrate the series split_filter filters + const series = get(visState, 'params.series') || []; + series.forEach(item => { + // series item split filters filter + if (item.split_filters) { + const splitFilters = get(item, 'split_filters') || []; + if (splitFilters.length > 0) { + // only transform split_filter filters if we have filters + splitFilters.forEach(filter => { + if (typeof filter.filter === 'string') { + const filterfilterObject = { + query: filter.filter, + language: 'lucene', + }; + filter.filter = filterfilterObject; + } + }); + } + } + }); + newDoc.attributes.visState = JSON.stringify(visState); + } + } + return newDoc; +} function migrateFiltersAggQuery(doc) { const visStateJSON = get(doc, 'attributes.visState'); @@ -435,6 +479,10 @@ const executeSearchMigrations740 = flow( migrateSearchSortToNestedArray, ); +const executeMigrations742 = flow( + transformSplitFiltersStringToQueryObject +); + export const migrations = { 'index-pattern': { '6.5.0': doc => { @@ -541,6 +589,8 @@ export const migrations = { '7.2.0': doc => executeMigrations720(doc), '7.3.0': executeMigrations730, '7.3.1': executeVisualizationMigrations731, + // migrate split_filters that were not migrated in 7.3.0 (transformFilterStringToQueryObject). + '7.4.2': executeMigrations742, }, dashboard: { '7.0.0': doc => { diff --git a/src/legacy/core_plugins/kibana/migrations/migrations.test.js b/src/legacy/core_plugins/kibana/migrations/migrations.test.js index 77a4c7eabe86e..5368169d6dd6d 100644 --- a/src/legacy/core_plugins/kibana/migrations/migrations.test.js +++ b/src/legacy/core_plugins/kibana/migrations/migrations.test.js @@ -1180,6 +1180,123 @@ Array [ expect(migratedDoc).toEqual({ attributes: { visState: JSON.stringify(expected) } }); }); }); + describe('7.4.2 tsvb split_filters migration', () => { + const migrate = doc => migrations.visualization['7.4.2'](doc); + const generateDoc = ({ params }) => ({ + attributes: { + title: 'My Vis', + description: 'This is my super cool vis.', + visState: JSON.stringify({ params }), + uiStateJSON: '{}', + version: 1, + kibanaSavedObjectMeta: { + searchSourceJSON: '{}', + }, + }, + }); + it('should change series item filters from a string into an object for all filters', () => { + const params = { + type: 'timeseries', + filter: { + query: 'bytes:>1000', + language: 'lucene' + }, + series: [ + { + split_filters: [{ filter: 'bytes:>1000' }], + }, + ] + }; + const timeSeriesDoc = generateDoc({ params: params }); + const migratedtimeSeriesDoc = migrate(timeSeriesDoc); + const timeSeriesParams = JSON.parse(migratedtimeSeriesDoc.attributes.visState).params; + expect(Object.keys(timeSeriesParams.filter)).toEqual( + expect.arrayContaining(['query', 'language']) + ); + expect(timeSeriesParams.series[0].split_filters[0].filter).toEqual( + { query: 'bytes:>1000', language: 'lucene' } + ); + }); + it('should change series item split filters when there is no filter item', () => { + const params = { + type: 'timeseries', + filter: { + query: 'bytes:>1000', + language: 'lucene' + }, + series: [ + { + split_filters: [{ filter: 'bytes:>1000' }], + }, + ], + annotations: [ + { + query_string: { + query: 'bytes:>1000', + language: 'lucene' + } + } + ], + }; + const timeSeriesDoc = generateDoc({ params: params }); + const migratedtimeSeriesDoc = migrate(timeSeriesDoc); + const timeSeriesParams = JSON.parse(migratedtimeSeriesDoc.attributes.visState).params; + expect(timeSeriesParams.series[0].split_filters[0].filter).toEqual( + { query: 'bytes:>1000', language: 'lucene' } + ); + }); + it('should not convert split_filters to objects if there are no split filter filters', () => { + const params = { + type: 'timeseries', + filter: { + query: 'bytes:>1000', + language: 'lucene' + }, + series: [ + { + split_filters: [], + }, + ] + }; + const timeSeriesDoc = generateDoc({ params: params }); + const migratedtimeSeriesDoc = migrate(timeSeriesDoc); + const timeSeriesParams = JSON.parse(migratedtimeSeriesDoc.attributes.visState).params; + expect(timeSeriesParams.series[0].split_filters).not.toHaveProperty('query'); + }); + it('should do nothing if a split_filter is already a query:language object', () => { + const params = { + type: 'timeseries', + filter: { + query: 'bytes:>1000', + language: 'lucene' + }, + series: [ + { + split_filters: [{ + filter: { + query: 'bytes:>1000', + language: 'lucene', + } + }], + }, + ], + annotations: [ + { + query_string: { + query: 'bytes:>1000', + language: 'lucene' + } + } + ], + }; + const timeSeriesDoc = generateDoc({ params: params }); + const migratedtimeSeriesDoc = migrate(timeSeriesDoc); + const timeSeriesParams = JSON.parse(migratedtimeSeriesDoc.attributes.visState).params; + expect(timeSeriesParams.series[0].split_filters[0].filter.query).toEqual('bytes:>1000'); + expect(timeSeriesParams.series[0].split_filters[0].filter.language).toEqual('lucene'); + + }); + }); }); describe('dashboard', () => {