Skip to content

Commit

Permalink
[Visualize] Allows editing broken visualizations caused by runtime fi…
Browse files Browse the repository at this point in the history
…elds changes (#94798)

* Add possibility to open visualization when saved field doesn't exists anymore

* Fix tests

* Fix some remarks

* Remove unneeded code

* Fix tests

* Fix tests

* Fix some remarks

* Fixed problem with double error popover in visualizations

* Fix CI

* Fix type

* Fix API docs

* Don't show error popup for error related to runtime fields

* Fix some remarks

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
VladLasitsa and kibanamachine committed Apr 8, 2021
1 parent 81adccd commit 2b86715
Show file tree
Hide file tree
Showing 19 changed files with 268 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export declare enum KBN_FIELD_TYPES
| HISTOGRAM | <code>&quot;histogram&quot;</code> | |
| IP | <code>&quot;ip&quot;</code> | |
| IP\_RANGE | <code>&quot;ip_range&quot;</code> | |
| MISSING | <code>&quot;missing&quot;</code> | |
| MURMUR3 | <code>&quot;murmur3&quot;</code> | |
| NESTED | <code>&quot;nested&quot;</code> | |
| NUMBER | <code>&quot;number&quot;</code> | |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export declare enum KBN_FIELD_TYPES
| HISTOGRAM | <code>&quot;histogram&quot;</code> | |
| IP | <code>&quot;ip&quot;</code> | |
| IP\_RANGE | <code>&quot;ip_range&quot;</code> | |
| MISSING | <code>&quot;missing&quot;</code> | |
| MURMUR3 | <code>&quot;murmur3&quot;</code> | |
| NESTED | <code>&quot;nested&quot;</code> | |
| NUMBER | <code>&quot;number&quot;</code> | |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ Merges input$ and output$ streams and debounces emit till next macro-task. Could
<b>Signature:</b>

```typescript
getUpdated$(): Readonly<Rx.Observable<void>>;
getUpdated$(): Readonly<Rx.Observable<TEmbeddableInput | TEmbeddableOutput>>;
```
<b>Returns:</b>

`Readonly<Rx.Observable<void>>`
`Readonly<Rx.Observable<TEmbeddableInput | TEmbeddableOutput>>`

1 change: 1 addition & 0 deletions src/plugins/data/common/kbn_field_types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,5 @@ export enum KBN_FIELD_TYPES {
OBJECT = 'object',
NESTED = 'nested',
HISTOGRAM = 'histogram',
MISSING = 'missing',
}
2 changes: 1 addition & 1 deletion src/plugins/data/common/search/aggs/agg_configs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand Down
78 changes: 60 additions & 18 deletions src/plugins/data/common/search/aggs/buckets/terms.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,53 @@ 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', () => {
const getAggConfigs = (params: Record<string, any> = {}) => {
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,
Expand Down Expand Up @@ -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,
Expand Down
76 changes: 51 additions & 25 deletions src/plugins/data/common/search/aggs/param_types/field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
}
};
}
Expand All @@ -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;
};
}

Expand Down
2 changes: 2 additions & 0 deletions src/plugins/data/public/public.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1794,6 +1794,8 @@ export enum KBN_FIELD_TYPES {
// (undocumented)
IP_RANGE = "ip_range",
// (undocumented)
MISSING = "missing",
// (undocumented)
MURMUR3 = "murmur3",
// (undocumented)
NESTED = "nested",
Expand Down
2 changes: 2 additions & 0 deletions src/plugins/data/server/server.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1088,6 +1088,8 @@ export enum KBN_FIELD_TYPES {
// (undocumented)
IP_RANGE = "ip_range",
// (undocumented)
MISSING = "missing",
// (undocumented)
MURMUR3 = "murmur3",
// (undocumented)
NESTED = "nested",
Expand Down
7 changes: 3 additions & 4 deletions src/plugins/embeddable/public/lib/embeddables/embeddable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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<Rx.Observable<void>> {
public getUpdated$(): Readonly<Rx.Observable<TEmbeddableInput | TEmbeddableOutput>> {
return merge(this.getInput$().pipe(skip(1)), this.getOutput$().pipe(skip(1))).pipe(
debounceTime(0),
mapTo(undefined)
debounceTime(0)
);
}

Expand Down
2 changes: 1 addition & 1 deletion src/plugins/embeddable/public/public.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ export abstract class Embeddable<TEmbeddableInput extends EmbeddableInput = Embe
getRoot(): IEmbeddable | IContainer;
// (undocumented)
getTitle(): string;
getUpdated$(): Readonly<Rx.Observable<void>>;
getUpdated$(): Readonly<Rx.Observable<TEmbeddableInput | TEmbeddableOutput>>;
// (undocumented)
readonly id: string;
// (undocumented)
Expand Down
22 changes: 20 additions & 2 deletions src/plugins/kibana_utils/common/errors/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,39 @@ 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}`;

if (link) {
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
Expand Down
Loading

0 comments on commit 2b86715

Please sign in to comment.