diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.kbn_field_types.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.kbn_field_types.md index 4d75dda61d5c9..521ceeb1e37f2 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.kbn_field_types.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.kbn_field_types.md @@ -27,6 +27,7 @@ export declare enum KBN_FIELD_TYPES | HISTOGRAM | "histogram" | | | IP | "ip" | | | IP\_RANGE | "ip_range" | | +| MISSING | "missing" | | | MURMUR3 | "murmur3" | | | NESTED | "nested" | | | NUMBER | "number" | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.kbn_field_types.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.kbn_field_types.md index be4c3705bd8de..40fa872ff0fc6 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.kbn_field_types.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.kbn_field_types.md @@ -27,6 +27,7 @@ export declare enum KBN_FIELD_TYPES | HISTOGRAM | "histogram" | | | IP | "ip" | | | IP\_RANGE | "ip_range" | | +| MISSING | "missing" | | | MURMUR3 | "murmur3" | | | NESTED | "nested" | | | NUMBER | "number" | | diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddable.getupdated_.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddable.getupdated_.md index 5201444e69867..290dc10662569 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddable.getupdated_.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddable.getupdated_.md @@ -9,9 +9,9 @@ Merges input$ and output$ streams and debounces emit till next macro-task. Could Signature: ```typescript -getUpdated$(): Readonly>; +getUpdated$(): Readonly>; ``` Returns: -`Readonly>` +`Readonly>` diff --git a/src/plugins/data/common/kbn_field_types/types.ts b/src/plugins/data/common/kbn_field_types/types.ts index c46e5c5266f55..e6f815e058ce3 100644 --- a/src/plugins/data/common/kbn_field_types/types.ts +++ b/src/plugins/data/common/kbn_field_types/types.ts @@ -80,4 +80,5 @@ export enum KBN_FIELD_TYPES { OBJECT = 'object', NESTED = 'nested', HISTOGRAM = 'histogram', + MISSING = 'missing', } diff --git a/src/plugins/data/common/search/aggs/agg_configs.test.ts b/src/plugins/data/common/search/aggs/agg_configs.test.ts index 297af560081b1..3ce528e6ed893 100644 --- a/src/plugins/data/common/search/aggs/agg_configs.test.ts +++ b/src/plugins/data/common/search/aggs/agg_configs.test.ts @@ -230,7 +230,7 @@ describe('AggConfigs', () => { describe('#toDsl', () => { beforeEach(() => { indexPattern = stubIndexPattern as IndexPattern; - indexPattern.fields.getByName = (name) => (name as unknown) as IndexPatternField; + indexPattern.fields.getByName = (name) => (({ name } as unknown) as IndexPatternField); }); it('uses the sorted aggs', () => { diff --git a/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.test.ts b/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.test.ts index 4e278d5872a3e..56e720d237c45 100644 --- a/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.test.ts +++ b/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.test.ts @@ -16,16 +16,33 @@ import { AggConfigs, CreateAggConfigParams } from '../agg_configs'; import { BUCKET_TYPES } from './bucket_agg_types'; import { IBucketAggConfig } from './bucket_agg_type'; import { mockAggTypesRegistry } from '../test_helpers'; +import type { IndexPatternField } from '../../../index_patterns'; +import { IndexPattern } from '../../../index_patterns/index_patterns/index_pattern'; const indexPattern = { id: '1234', title: 'logstash-*', fields: [ { - name: 'field', + name: 'machine.os.raw', + type: 'string', + esTypes: ['string'], + aggregatable: true, + filterable: true, + searchable: true, + }, + { + name: 'geo.src', + type: 'string', + esTypes: ['string'], + aggregatable: true, + filterable: true, + searchable: true, }, ], -} as any; +} as IndexPattern; + +indexPattern.fields.getByName = (name) => (({ name } as unknown) as IndexPatternField); const singleTerm = { aggs: [ diff --git a/src/plugins/data/common/search/aggs/buckets/terms.test.ts b/src/plugins/data/common/search/aggs/buckets/terms.test.ts index bb34d7ede453c..09dfbb28a4e53 100644 --- a/src/plugins/data/common/search/aggs/buckets/terms.test.ts +++ b/src/plugins/data/common/search/aggs/buckets/terms.test.ts @@ -10,6 +10,8 @@ import { AggConfigs } from '../agg_configs'; import { METRIC_TYPES } from '../metrics'; import { mockAggTypesRegistry } from '../test_helpers'; import { BUCKET_TYPES } from './bucket_agg_types'; +import type { IndexPatternField } from '../../../index_patterns'; +import { IndexPattern } from '../../../index_patterns/index_patterns/index_pattern'; describe('Terms Agg', () => { describe('order agg editor UI', () => { @@ -17,16 +19,44 @@ describe('Terms Agg', () => { const indexPattern = { id: '1234', title: 'logstash-*', - fields: { - getByName: () => field, - filter: () => [field], - }, - } as any; + fields: [ + { + name: 'field', + type: 'string', + esTypes: ['string'], + aggregatable: true, + filterable: true, + searchable: true, + }, + { + name: 'string_field', + type: 'string', + esTypes: ['string'], + aggregatable: true, + filterable: true, + searchable: true, + }, + { + name: 'empty_number_field', + type: 'number', + esTypes: ['number'], + aggregatable: true, + filterable: true, + searchable: true, + }, + { + name: 'number_field', + type: 'number', + esTypes: ['number'], + aggregatable: true, + filterable: true, + searchable: true, + }, + ], + } as IndexPattern; - const field = { - name: 'field', - indexPattern, - }; + indexPattern.fields.getByName = (name) => (({ name } as unknown) as IndexPatternField); + indexPattern.fields.filter = () => indexPattern.fields; return new AggConfigs( indexPattern, @@ -207,16 +237,28 @@ describe('Terms Agg', () => { const indexPattern = { id: '1234', title: 'logstash-*', - fields: { - getByName: () => field, - filter: () => [field], - }, - } as any; + fields: [ + { + name: 'string_field', + type: 'string', + esTypes: ['string'], + aggregatable: true, + filterable: true, + searchable: true, + }, + { + name: 'number_field', + type: 'number', + esTypes: ['number'], + aggregatable: true, + filterable: true, + searchable: true, + }, + ], + } as IndexPattern; - const field = { - name: 'field', - indexPattern, - }; + indexPattern.fields.getByName = (name) => (({ name } as unknown) as IndexPatternField); + indexPattern.fields.filter = () => indexPattern.fields; const aggConfigs = new AggConfigs( indexPattern, diff --git a/src/plugins/data/common/search/aggs/param_types/field.ts b/src/plugins/data/common/search/aggs/param_types/field.ts index 2d3ff8f5fdba8..62dac9831211a 100644 --- a/src/plugins/data/common/search/aggs/param_types/field.ts +++ b/src/plugins/data/common/search/aggs/param_types/field.ts @@ -8,7 +8,10 @@ import { i18n } from '@kbn/i18n'; import { IAggConfig } from '../agg_config'; -import { SavedObjectNotFound } from '../../../../../../plugins/kibana_utils/common'; +import { + SavedFieldNotFound, + SavedFieldTypeInvalidForAgg, +} from '../../../../../../plugins/kibana_utils/common'; import { BaseParamType } from './base'; import { propFilter } from '../utils'; import { KBN_FIELD_TYPES } from '../../../kbn_field_types/types'; @@ -47,13 +50,49 @@ export class FieldParamType extends BaseParamType { ); } - if (field.scripted) { + if (field.type === KBN_FIELD_TYPES.MISSING) { + throw new SavedFieldNotFound( + i18n.translate( + 'data.search.aggs.paramTypes.field.notFoundSavedFieldParameterErrorMessage', + { + defaultMessage: + 'The field "{fieldParameter}" associated with this object no longer exists in the index pattern. Please use another field.', + values: { + fieldParameter: field.name, + }, + } + ) + ); + } + + const validField = this.getAvailableFields(aggConfig).find( + (f: any) => f.name === field.name + ); + + if (!validField) { + throw new SavedFieldTypeInvalidForAgg( + i18n.translate( + 'data.search.aggs.paramTypes.field.invalidSavedFieldParameterErrorMessage', + { + defaultMessage: + 'Saved field "{fieldParameter}" of index pattern "{indexPatternTitle}" is invalid for use with the "{aggType}" aggregation. Please select a new field.', + values: { + fieldParameter: field.name, + aggType: aggConfig?.type?.title, + indexPatternTitle: aggConfig.getIndexPattern().title, + }, + } + ) + ); + } + + if (validField.scripted) { output.params.script = { - source: field.script, - lang: field.lang, + source: validField.script, + lang: validField.lang, }; } else { - output.params.field = field.name; + output.params.field = validField.name; } }; } @@ -69,28 +108,15 @@ export class FieldParamType extends BaseParamType { const field = aggConfig.getIndexPattern().fields.getByName(fieldName); if (!field) { - throw new SavedObjectNotFound('index-pattern-field', fieldName); - } - - const validField = this.getAvailableFields(aggConfig).find((f: any) => f.name === fieldName); - if (!validField) { - throw new Error( - i18n.translate( - 'data.search.aggs.paramTypes.field.invalidSavedFieldParameterErrorMessage', - { - defaultMessage: - 'Saved field "{fieldParameter}" of index pattern "{indexPatternTitle}" is invalid for use with the "{aggType}" aggregation. Please select a new field.', - values: { - fieldParameter: fieldName, - aggType: aggConfig?.type?.title, - indexPatternTitle: aggConfig.getIndexPattern().title, - }, - } - ) - ); + return new IndexPatternField({ + type: KBN_FIELD_TYPES.MISSING, + name: fieldName, + searchable: false, + aggregatable: false, + }); } - return validField; + return field; }; } diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index ec24a9296674d..5cc4dc6302a1e 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -1781,6 +1781,8 @@ export enum KBN_FIELD_TYPES { // (undocumented) IP_RANGE = "ip_range", // (undocumented) + MISSING = "missing", + // (undocumented) MURMUR3 = "murmur3", // (undocumented) NESTED = "nested", diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index 9fff4ac95c87e..053b60956fa92 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -1082,6 +1082,8 @@ export enum KBN_FIELD_TYPES { // (undocumented) IP_RANGE = "ip_range", // (undocumented) + MISSING = "missing", + // (undocumented) MURMUR3 = "murmur3", // (undocumented) NESTED = "nested", diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx b/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx index e8418970d83f7..a0cd213b7bf24 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx @@ -9,7 +9,7 @@ import { cloneDeep, isEqual } from 'lodash'; import * as Rx from 'rxjs'; import { merge } from 'rxjs'; -import { debounceTime, distinctUntilChanged, map, mapTo, skip } from 'rxjs/operators'; +import { debounceTime, distinctUntilChanged, map, skip } from 'rxjs/operators'; import { RenderCompleteDispatcher } from '../../../../kibana_utils/public'; import { Adapters } from '../types'; import { IContainer } from '../containers'; @@ -111,10 +111,9 @@ export abstract class Embeddable< * In case corresponding state change triggered `reload` this stream is guarantied to emit later, * which allows to skip any state handling in case `reload` already handled it. */ - public getUpdated$(): Readonly> { + public getUpdated$(): Readonly> { return merge(this.getInput$().pipe(skip(1)), this.getOutput$().pipe(skip(1))).pipe( - debounceTime(0), - mapTo(undefined) + debounceTime(0) ); } diff --git a/src/plugins/embeddable/public/public.api.md b/src/plugins/embeddable/public/public.api.md index b9719542adc81..3f0907acabdfa 100644 --- a/src/plugins/embeddable/public/public.api.md +++ b/src/plugins/embeddable/public/public.api.md @@ -282,7 +282,7 @@ export abstract class Embeddable>; + getUpdated$(): Readonly>; // (undocumented) readonly id: string; // (undocumented) diff --git a/src/plugins/kibana_utils/common/errors/errors.ts b/src/plugins/kibana_utils/common/errors/errors.ts index 7a9495cc8f413..7f3efc6d9571f 100644 --- a/src/plugins/kibana_utils/common/errors/errors.ts +++ b/src/plugins/kibana_utils/common/errors/errors.ts @@ -32,7 +32,7 @@ export class DuplicateField extends KbnError { export class SavedObjectNotFound extends KbnError { public savedObjectType: string; public savedObjectId?: string; - constructor(type: string, id?: string, link?: string) { + constructor(type: string, id?: string, link?: string, customMessage?: string) { const idMsg = id ? ` (id: ${id})` : ''; let message = `Could not locate that ${type}${idMsg}`; @@ -40,13 +40,31 @@ export class SavedObjectNotFound extends KbnError { message += `, [click here to re-create it](${link})`; } - super(message); + super(customMessage || message); this.savedObjectType = type; this.savedObjectId = id; } } +/** + * A saved field doesn't exist anymore + */ +export class SavedFieldNotFound extends KbnError { + constructor(message: string) { + super(message); + } +} + +/** + * A saved field type isn't compatible with aggregation + */ +export class SavedFieldTypeInvalidForAgg extends KbnError { + constructor(message: string) { + super(message); + } +} + /** * This error is for scenarios where a saved object is detected that has invalid JSON properties. * There was a scenario where we were importing objects with double-encoded JSON, and the system diff --git a/src/plugins/vis_default_editor/public/components/controls/field.test.tsx b/src/plugins/vis_default_editor/public/components/controls/field.test.tsx index 94f767510c4bd..277804567c2b7 100644 --- a/src/plugins/vis_default_editor/public/components/controls/field.test.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/field.test.tsx @@ -11,7 +11,7 @@ import { act } from 'react-dom/test-utils'; import { mount, shallow, ReactWrapper } from 'enzyme'; import { EuiComboBoxProps, EuiComboBox } from '@elastic/eui'; -import { IAggConfig, IndexPatternField } from 'src/plugins/data/public'; +import { IAggConfig, IndexPatternField, AggParam } from 'src/plugins/data/public'; import { ComboBoxGroupedOptions } from '../../utils'; import { FieldParamEditor, FieldParamEditorProps } from './field'; import { EditorVisState } from '../sidebar/state/reducers'; @@ -42,7 +42,7 @@ describe('FieldParamEditor component', () => { setTouched = jest.fn(); onChange = jest.fn(); - field = { displayName: 'bytes' } as IndexPatternField; + field = { displayName: 'bytes', type: 'bytes' } as IndexPatternField; option = { label: 'bytes', target: field }; indexedFields = [ { @@ -52,7 +52,16 @@ describe('FieldParamEditor component', () => { ]; defaultProps = { - agg: {} as IAggConfig, + agg: { + type: { + params: [ + ({ + name: 'field', + filterFieldTypes: ['bytes'], + } as unknown) as AggParam, + ], + }, + } as IAggConfig, aggParam: { name: 'field', type: 'field', diff --git a/src/plugins/vis_default_editor/public/components/controls/field.tsx b/src/plugins/vis_default_editor/public/components/controls/field.tsx index 95843dc6ae3a8..f8db2d89888a2 100644 --- a/src/plugins/vis_default_editor/public/components/controls/field.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/field.tsx @@ -13,7 +13,13 @@ import useMount from 'react-use/lib/useMount'; import { EuiComboBox, EuiComboBoxOptionOption, EuiFormRow } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { AggParam, IAggConfig, IFieldParamType, IndexPatternField } from 'src/plugins/data/public'; +import { + AggParam, + IAggConfig, + IFieldParamType, + IndexPatternField, + KBN_FIELD_TYPES, +} from '../../../../../plugins/data/public'; import { formatListAsProse, parseCommaSeparatedList, useValidation } from './utils'; import { AggParamEditorProps } from '../agg_param_props'; import { ComboBoxGroupedOptions } from '../../utils'; @@ -55,6 +61,7 @@ function FieldParamEditor({ } }; const errors = customError ? [customError] : []; + let showErrorMessageImmediately = false; if (!indexedFields.length) { errors.push( @@ -69,9 +76,38 @@ function FieldParamEditor({ ); } + if (value && value.type === KBN_FIELD_TYPES.MISSING) { + errors.push( + i18n.translate('visDefaultEditor.controls.field.fieldIsNotExists', { + defaultMessage: + 'The field "{fieldParameter}" associated with this object no longer exists in the index pattern. Please use another field.', + values: { + fieldParameter: value.name, + }, + }) + ); + showErrorMessageImmediately = true; + } else if ( + value && + !getFieldTypes(agg).find((type: string) => type === value.type || type === '*') + ) { + errors.push( + i18n.translate('visDefaultEditor.controls.field.invalidFieldForAggregation', { + defaultMessage: + 'Saved field "{fieldParameter}" of index pattern "{indexPatternTitle}" is invalid for use with this aggregation. Please select a new field.', + values: { + fieldParameter: value?.name, + indexPatternTitle: agg.getIndexPattern && agg.getIndexPattern().title, + }, + }) + ); + showErrorMessageImmediately = true; + } + const isValid = !!value && !errors.length && !isDirty; // we show an error message right away if there is no compatible fields - const showErrorMessage = (showValidation || !indexedFields.length) && !isValid; + const showErrorMessage = + (showValidation || !indexedFields.length || showErrorMessageImmediately) && !isValid; useValidation(setValidity, isValid); useMount(() => { @@ -122,10 +158,14 @@ function FieldParamEditor({ } function getFieldTypesString(agg: IAggConfig) { + return formatListAsProse(getFieldTypes(agg), { inclusive: false }); +} + +function getFieldTypes(agg: IAggConfig) { const param = get(agg, 'type.params', []).find((p: AggParam) => p.name === 'field') || ({} as IFieldParamType); - return formatListAsProse(parseCommaSeparatedList(param.filterFieldTypes), { inclusive: false }); + return parseCommaSeparatedList(param.filterFieldTypes || []); } export { FieldParamEditor }; diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts b/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts index 429dabeeef042..907a2b95a037b 100644 --- a/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts +++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts @@ -149,8 +149,9 @@ export class VisualizeEmbeddable } this.subscriptions.push( - this.getUpdated$().subscribe(() => { + this.getUpdated$().subscribe((value) => { const isDirty = this.handleChanges(); + if (isDirty && this.handler) { this.updateHandler(); } diff --git a/src/plugins/visualize/public/application/utils/get_visualization_instance.ts b/src/plugins/visualize/public/application/utils/get_visualization_instance.ts index cc0f3ce2afae5..9eda709e58c3e 100644 --- a/src/plugins/visualize/public/application/utils/get_visualization_instance.ts +++ b/src/plugins/visualize/public/application/utils/get_visualization_instance.ts @@ -18,8 +18,17 @@ import { SavedObject } from 'src/plugins/saved_objects/public'; import { cloneDeep } from 'lodash'; import { ExpressionValueError } from 'src/plugins/expressions/public'; import { createSavedSearchesLoader } from '../../../../discover/public'; +import { SavedFieldNotFound, SavedFieldTypeInvalidForAgg } from '../../../../kibana_utils/common'; import { VisualizeServices } from '../types'; +function isErrorRelatedToRuntimeFields(error: ExpressionValueError['error']) { + const originalError = error.original || error; + return ( + originalError instanceof SavedFieldNotFound || + originalError instanceof SavedFieldTypeInvalidForAgg + ); +} + const createVisualizeEmbeddableAndLinkSavedSearch = async ( vis: Vis, visualizeServices: VisualizeServices @@ -37,7 +46,7 @@ const createVisualizeEmbeddableAndLinkSavedSearch = async ( })) as VisualizeEmbeddableContract; embeddableHandler.getOutput$().subscribe((output) => { - if (output.error) { + if (output.error && !isErrorRelatedToRuntimeFields(output.error)) { data.search.showError( ((output.error as unknown) as ExpressionValueError['error']).original || output.error ); diff --git a/src/plugins/visualize/public/application/utils/use/use_saved_vis_instance.ts b/src/plugins/visualize/public/application/utils/use/use_saved_vis_instance.ts index 64d61996495d7..965951bfbd88d 100644 --- a/src/plugins/visualize/public/application/utils/use/use_saved_vis_instance.ts +++ b/src/plugins/visualize/public/application/utils/use/use_saved_vis_instance.ts @@ -11,13 +11,12 @@ import { EventEmitter } from 'events'; import { parse } from 'query-string'; import { i18n } from '@kbn/i18n'; -import { redirectWhenMissing } from '../../../../../kibana_utils/public'; - import { getVisualizationInstance } from '../get_visualization_instance'; import { getEditBreadcrumbs, getCreateBreadcrumbs } from '../breadcrumbs'; import { SavedVisInstance, VisualizeServices, IEditorController } from '../../types'; import { VisualizeConstants } from '../../visualize_constants'; import { getVisEditorsRegistry } from '../../../services'; +import { redirectToSavedObjectPage } from '../utils'; /** * This effect is responsible for instantiating a saved vis or creating a new one @@ -43,9 +42,7 @@ export const useSavedVisInstance = ( chrome, history, dashboard, - setActiveUrl, toastNotifications, - http: { basePath }, stateTransferService, application: { navigateToApp }, } = services; @@ -131,27 +128,8 @@ export const useSavedVisInstance = ( visEditorController, }); } catch (error) { - const managementRedirectTarget = { - app: 'management', - path: `kibana/objects/savedVisualizations/${visualizationIdFromUrl}`, - }; - try { - redirectWhenMissing({ - history, - navigateToApp, - toastNotifications, - basePath, - mapping: { - visualization: VisualizeConstants.LANDING_PAGE_PATH, - search: managementRedirectTarget, - 'index-pattern': managementRedirectTarget, - 'index-pattern-field': managementRedirectTarget, - }, - onBeforeRedirect() { - setActiveUrl(VisualizeConstants.LANDING_PAGE_PATH); - }, - })(error); + redirectToSavedObjectPage(services, error, visualizationIdFromUrl); } catch (e) { toastNotifications.addWarning({ title: i18n.translate('visualize.createVisualization.failedToLoadErrorMessage', { diff --git a/src/plugins/visualize/public/application/utils/utils.ts b/src/plugins/visualize/public/application/utils/utils.ts index 0e529507f97e3..c906ff5304c90 100644 --- a/src/plugins/visualize/public/application/utils/utils.ts +++ b/src/plugins/visualize/public/application/utils/utils.ts @@ -10,6 +10,8 @@ import { i18n } from '@kbn/i18n'; import { ChromeStart, DocLinksStart } from 'kibana/public'; import { Filter } from '../../../../data/public'; +import { redirectWhenMissing } from '../../../../kibana_utils/public'; +import { VisualizeConstants } from '../visualize_constants'; import { VisualizeServices, VisualizeEditorVisInstance } from '../types'; export const addHelpMenuToAppChrome = (chrome: ChromeStart, docLinks: DocLinksStart) => { @@ -58,3 +60,36 @@ export const visStateToEditorState = ( linked: savedVis && savedVis.id ? !!savedVis.savedSearchId : !!savedVisState.savedSearchId, }; }; + +export const redirectToSavedObjectPage = ( + services: VisualizeServices, + error: any, + savedVisualizationsId?: string +) => { + const { + history, + setActiveUrl, + toastNotifications, + http: { basePath }, + application: { navigateToApp }, + } = services; + const managementRedirectTarget = { + app: 'management', + path: `kibana/objects/savedVisualizations/${savedVisualizationsId}`, + }; + redirectWhenMissing({ + history, + navigateToApp, + toastNotifications, + basePath, + mapping: { + visualization: VisualizeConstants.LANDING_PAGE_PATH, + search: managementRedirectTarget, + 'index-pattern': managementRedirectTarget, + 'index-pattern-field': managementRedirectTarget, + }, + onBeforeRedirect() { + setActiveUrl(VisualizeConstants.LANDING_PAGE_PATH); + }, + })(error); +};