mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
* match parse and stringify version. try/catch added * Adding tests and refactoring logError * Adding relative path to core and kibana utils * remark curstom serializers adapted to version 8 * add error logging to comments migration * Adding tests for mergeMigrationFunctionMap logging Co-authored-by: Jonathan Buttner <jonathan.buttner@elastic.co> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Sergi Massaneda <sergi.massaneda@elastic.co> Co-authored-by: Jonathan Buttner <jonathan.buttner@elastic.co>
This commit is contained in:
parent
74f651f132
commit
61e5a692e9
12 changed files with 555 additions and 166 deletions
|
@ -365,7 +365,7 @@
|
|||
"redux-thunks": "^1.0.0",
|
||||
"regenerator-runtime": "^0.13.3",
|
||||
"remark-parse": "^8.0.3",
|
||||
"remark-stringify": "^9.0.0",
|
||||
"remark-stringify": "^8.0.3",
|
||||
"require-in-the-middle": "^5.1.0",
|
||||
"reselect": "^4.0.0",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { Plugin } from 'unified';
|
||||
import type { TimeRange } from 'src/plugins/data/common';
|
||||
import { LENS_ID } from './constants';
|
||||
|
||||
|
@ -13,8 +14,13 @@ export interface LensSerializerProps {
|
|||
timeRange: TimeRange;
|
||||
}
|
||||
|
||||
export const LensSerializer = ({ timeRange, attributes }: LensSerializerProps) =>
|
||||
const serializeLens = ({ timeRange, attributes }: LensSerializerProps) =>
|
||||
`!{${LENS_ID}${JSON.stringify({
|
||||
timeRange,
|
||||
attributes,
|
||||
})}}`;
|
||||
|
||||
export const LensSerializer: Plugin = function () {
|
||||
const Compiler = this.Compiler;
|
||||
Compiler.prototype.visitors.lens = serializeLens;
|
||||
};
|
||||
|
|
|
@ -5,8 +5,14 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { Plugin } from 'unified';
|
||||
export interface TimelineSerializerProps {
|
||||
match: string;
|
||||
}
|
||||
|
||||
export const TimelineSerializer = ({ match }: TimelineSerializerProps) => match;
|
||||
const serializeTimeline = ({ match }: TimelineSerializerProps) => match;
|
||||
|
||||
export const TimelineSerializer: Plugin = function () {
|
||||
const Compiler = this.Compiler;
|
||||
Compiler.prototype.visitors.timeline = serializeTimeline;
|
||||
};
|
||||
|
|
|
@ -18,5 +18,93 @@ describe('markdown utils', () => {
|
|||
const parsed = parseCommentString('hello\n');
|
||||
expect(stringifyMarkdownComment(parsed)).toEqual('hello\n');
|
||||
});
|
||||
|
||||
// This check ensures the version of remark-stringify supports tables. From version 9+ this is not supported by default.
|
||||
it('parses and stringifies github formatted markdown correctly', () => {
|
||||
const parsed = parseCommentString(`| Tables | Are | Cool |
|
||||
|----------|:-------------:|------:|
|
||||
| col 1 is | left-aligned | $1600 |
|
||||
| col 2 is | centered | $12 |
|
||||
| col 3 is | right-aligned | $1 |`);
|
||||
|
||||
expect(stringifyMarkdownComment(parsed)).toMatchInlineSnapshot(`
|
||||
"| Tables | Are | Cool |
|
||||
| -------- | :-----------: | ----: |
|
||||
| col 1 is | left-aligned | $1600 |
|
||||
| col 2 is | centered | $12 |
|
||||
| col 3 is | right-aligned | $1 |
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
it('parses a timeline url', () => {
|
||||
const timelineUrl =
|
||||
'[asdasdasdasd](http://localhost:5601/moq/app/security/timelines?timeline=(id%3A%27e4362a60-f478-11eb-a4b0-ebefce184d8d%27%2CisOpen%3A!t))';
|
||||
|
||||
const parsedNodes = parseCommentString(timelineUrl);
|
||||
|
||||
expect(parsedNodes).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
"match": "[asdasdasdasd](http://localhost:5601/moq/app/security/timelines?timeline=(id%3A%27e4362a60-f478-11eb-a4b0-ebefce184d8d%27%2CisOpen%3A!t))",
|
||||
"position": Position {
|
||||
"end": Object {
|
||||
"column": 138,
|
||||
"line": 1,
|
||||
"offset": 137,
|
||||
},
|
||||
"indent": Array [],
|
||||
"start": Object {
|
||||
"column": 1,
|
||||
"line": 1,
|
||||
"offset": 0,
|
||||
},
|
||||
},
|
||||
"type": "timeline",
|
||||
},
|
||||
],
|
||||
"position": Object {
|
||||
"end": Object {
|
||||
"column": 138,
|
||||
"line": 1,
|
||||
"offset": 137,
|
||||
},
|
||||
"start": Object {
|
||||
"column": 1,
|
||||
"line": 1,
|
||||
"offset": 0,
|
||||
},
|
||||
},
|
||||
"type": "root",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('stringifies a timeline url', () => {
|
||||
const timelineUrl =
|
||||
'[asdasdasdasd](http://localhost:5601/moq/app/security/timelines?timeline=(id%3A%27e4362a60-f478-11eb-a4b0-ebefce184d8d%27%2CisOpen%3A!t))';
|
||||
|
||||
const parsedNodes = parseCommentString(timelineUrl);
|
||||
|
||||
expect(stringifyMarkdownComment(parsedNodes)).toEqual(`${timelineUrl}\n`);
|
||||
});
|
||||
|
||||
it('parses a lens visualization', () => {
|
||||
const lensVisualization =
|
||||
'!{lens{"timeRange":{"from":"now-7d","to":"now","mode":"relative"},"attributes":{"title":"TEst22","type":"lens","visualizationType":"lnsMetric","state":{"datasourceStates":{"indexpattern":{"layers":{"layer1":{"columnOrder":["col2"],"columns":{"col2":{"dataType":"number","isBucketed":false,"label":"Count of records","operationType":"count","scale":"ratio","sourceField":"Records"}}}}}},"visualization":{"layerId":"layer1","accessor":"col2"},"query":{"language":"kuery","query":""},"filters":[]},"references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-layer-layer1"}]}}}';
|
||||
|
||||
const parsedNodes = parseCommentString(lensVisualization);
|
||||
expect(parsedNodes.children[0].type).toEqual('lens');
|
||||
});
|
||||
|
||||
it('stringifies a lens visualization', () => {
|
||||
const lensVisualization =
|
||||
'!{lens{"timeRange":{"from":"now-7d","to":"now","mode":"relative"},"attributes":{"title":"TEst22","type":"lens","visualizationType":"lnsMetric","state":{"datasourceStates":{"indexpattern":{"layers":{"layer1":{"columnOrder":["col2"],"columns":{"col2":{"dataType":"number","isBucketed":false,"label":"Count of records","operationType":"count","scale":"ratio","sourceField":"Records"}}}}}},"visualization":{"layerId":"layer1","accessor":"col2"},"query":{"language":"kuery","query":""},"filters":[]},"references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-layer-layer1"}]}}}';
|
||||
|
||||
const parsedNodes = parseCommentString(lensVisualization);
|
||||
|
||||
expect(stringifyMarkdownComment(parsedNodes)).toEqual(`${lensVisualization}\n`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -45,20 +45,13 @@ export const parseCommentString = (comment: string) => {
|
|||
export const stringifyMarkdownComment = (comment: MarkdownNode) =>
|
||||
unified()
|
||||
.use([
|
||||
[
|
||||
remarkStringify,
|
||||
{
|
||||
allowDangerousHtml: true,
|
||||
handlers: {
|
||||
/*
|
||||
because we're using rison in the timeline url we need
|
||||
to make sure that markdown parser doesn't modify the url
|
||||
*/
|
||||
timeline: TimelineSerializer,
|
||||
lens: LensSerializer,
|
||||
},
|
||||
},
|
||||
],
|
||||
[remarkStringify],
|
||||
/*
|
||||
because we're using rison in the timeline url we need
|
||||
to make sure that markdown parser doesn't modify the url
|
||||
*/
|
||||
LensSerializer,
|
||||
TimelineSerializer,
|
||||
])
|
||||
.stringify(comment);
|
||||
|
||||
|
|
|
@ -5,7 +5,12 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { createCommentsMigrations, stringifyCommentWithoutTrailingNewline } from './comments';
|
||||
import {
|
||||
createCommentsMigrations,
|
||||
mergeMigrationFunctionMaps,
|
||||
migrateByValueLensVisualizations,
|
||||
stringifyCommentWithoutTrailingNewline,
|
||||
} from './comments';
|
||||
import {
|
||||
getLensVisualizations,
|
||||
parseCommentString,
|
||||
|
@ -14,84 +19,98 @@ import {
|
|||
import { savedObjectsServiceMock } from '../../../../../../src/core/server/mocks';
|
||||
import { lensEmbeddableFactory } from '../../../../lens/server/embeddable/lens_embeddable_factory';
|
||||
import { LensDocShape715 } from '../../../../lens/server';
|
||||
import { SavedObjectReference } from 'kibana/server';
|
||||
import {
|
||||
SavedObjectReference,
|
||||
SavedObjectsMigrationLogger,
|
||||
SavedObjectUnsanitizedDoc,
|
||||
} from 'kibana/server';
|
||||
import {
|
||||
MigrateFunction,
|
||||
MigrateFunctionsObject,
|
||||
} from '../../../../../../src/plugins/kibana_utils/common';
|
||||
import { SerializableRecord } from '@kbn/utility-types';
|
||||
|
||||
const migrations = createCommentsMigrations({
|
||||
lensEmbeddableFactory,
|
||||
});
|
||||
describe('comments migrations', () => {
|
||||
const migrations = createCommentsMigrations({
|
||||
lensEmbeddableFactory,
|
||||
});
|
||||
|
||||
const contextMock = savedObjectsServiceMock.createMigrationContext();
|
||||
describe('index migrations', () => {
|
||||
describe('lens embeddable migrations for by value panels', () => {
|
||||
describe('7.14.0 remove time zone from Lens visualization date histogram', () => {
|
||||
const lensVisualizationToMigrate = {
|
||||
title: 'MyRenamedOps',
|
||||
description: '',
|
||||
visualizationType: 'lnsXY',
|
||||
state: {
|
||||
datasourceStates: {
|
||||
indexpattern: {
|
||||
layers: {
|
||||
'2': {
|
||||
columns: {
|
||||
'3': {
|
||||
label: '@timestamp',
|
||||
dataType: 'date',
|
||||
operationType: 'date_histogram',
|
||||
sourceField: '@timestamp',
|
||||
isBucketed: true,
|
||||
scale: 'interval',
|
||||
params: { interval: 'auto', timeZone: 'Europe/Berlin' },
|
||||
},
|
||||
'4': {
|
||||
label: '@timestamp',
|
||||
dataType: 'date',
|
||||
operationType: 'date_histogram',
|
||||
sourceField: '@timestamp',
|
||||
isBucketed: true,
|
||||
scale: 'interval',
|
||||
params: { interval: 'auto' },
|
||||
},
|
||||
'5': {
|
||||
label: '@timestamp',
|
||||
dataType: 'date',
|
||||
operationType: 'my_unexpected_operation',
|
||||
isBucketed: true,
|
||||
scale: 'interval',
|
||||
params: { timeZone: 'do not delete' },
|
||||
},
|
||||
},
|
||||
columnOrder: ['3', '4', '5'],
|
||||
incompleteColumns: {},
|
||||
const contextMock = savedObjectsServiceMock.createMigrationContext();
|
||||
|
||||
const lensVisualizationToMigrate = {
|
||||
title: 'MyRenamedOps',
|
||||
description: '',
|
||||
visualizationType: 'lnsXY',
|
||||
state: {
|
||||
datasourceStates: {
|
||||
indexpattern: {
|
||||
layers: {
|
||||
'2': {
|
||||
columns: {
|
||||
'3': {
|
||||
label: '@timestamp',
|
||||
dataType: 'date',
|
||||
operationType: 'date_histogram',
|
||||
sourceField: '@timestamp',
|
||||
isBucketed: true,
|
||||
scale: 'interval',
|
||||
params: { interval: 'auto', timeZone: 'Europe/Berlin' },
|
||||
},
|
||||
'4': {
|
||||
label: '@timestamp',
|
||||
dataType: 'date',
|
||||
operationType: 'date_histogram',
|
||||
sourceField: '@timestamp',
|
||||
isBucketed: true,
|
||||
scale: 'interval',
|
||||
params: { interval: 'auto' },
|
||||
},
|
||||
'5': {
|
||||
label: '@timestamp',
|
||||
dataType: 'date',
|
||||
operationType: 'my_unexpected_operation',
|
||||
isBucketed: true,
|
||||
scale: 'interval',
|
||||
params: { timeZone: 'do not delete' },
|
||||
},
|
||||
},
|
||||
columnOrder: ['3', '4', '5'],
|
||||
incompleteColumns: {},
|
||||
},
|
||||
},
|
||||
visualization: {
|
||||
title: 'Empty XY chart',
|
||||
legend: { isVisible: true, position: 'right' },
|
||||
valueLabels: 'hide',
|
||||
preferredSeriesType: 'bar_stacked',
|
||||
layers: [
|
||||
{
|
||||
layerId: '5ab74ddc-93ca-44e2-9857-ecf85c86b53e',
|
||||
accessors: [
|
||||
'5fea2a56-7b73-44b5-9a50-7f0c0c4f8fd0',
|
||||
'e5efca70-edb5-4d6d-a30a-79384066987e',
|
||||
'7ffb7bde-4f42-47ab-b74d-1b4fd8393e0f',
|
||||
],
|
||||
position: 'top',
|
||||
seriesType: 'bar_stacked',
|
||||
showGridlines: false,
|
||||
xAccessor: '2e57a41e-5a52-42d3-877f-bd211d903ef8',
|
||||
},
|
||||
],
|
||||
},
|
||||
query: { query: '', language: 'kuery' },
|
||||
filters: [],
|
||||
},
|
||||
};
|
||||
},
|
||||
visualization: {
|
||||
title: 'Empty XY chart',
|
||||
legend: { isVisible: true, position: 'right' },
|
||||
valueLabels: 'hide',
|
||||
preferredSeriesType: 'bar_stacked',
|
||||
layers: [
|
||||
{
|
||||
layerId: '5ab74ddc-93ca-44e2-9857-ecf85c86b53e',
|
||||
accessors: [
|
||||
'5fea2a56-7b73-44b5-9a50-7f0c0c4f8fd0',
|
||||
'e5efca70-edb5-4d6d-a30a-79384066987e',
|
||||
'7ffb7bde-4f42-47ab-b74d-1b4fd8393e0f',
|
||||
],
|
||||
position: 'top',
|
||||
seriesType: 'bar_stacked',
|
||||
showGridlines: false,
|
||||
xAccessor: '2e57a41e-5a52-42d3-877f-bd211d903ef8',
|
||||
},
|
||||
],
|
||||
},
|
||||
query: { query: '', language: 'kuery' },
|
||||
filters: [],
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('lens embeddable migrations for by value panels', () => {
|
||||
describe('7.14.0 remove time zone from Lens visualization date histogram', () => {
|
||||
const expectedLensVisualizationMigrated = {
|
||||
title: 'MyRenamedOps',
|
||||
description: '',
|
||||
|
@ -241,43 +260,140 @@ describe('index migrations', () => {
|
|||
expect((columns[2] as { params: {} }).params).toEqual({ timeZone: 'do not delete' });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('stringifyCommentWithoutTrailingNewline', () => {
|
||||
it('removes the newline added by the markdown library when the comment did not originally have one', () => {
|
||||
const originalComment = 'awesome';
|
||||
const parsedString = parseCommentString(originalComment);
|
||||
describe('handles errors', () => {
|
||||
interface CommentSerializable extends SerializableRecord {
|
||||
comment?: string;
|
||||
}
|
||||
|
||||
expect(stringifyCommentWithoutTrailingNewline(originalComment, parsedString)).toEqual(
|
||||
'awesome'
|
||||
);
|
||||
const migrationFunction: MigrateFunction<CommentSerializable, CommentSerializable> = (
|
||||
comment
|
||||
) => {
|
||||
throw new Error('an error');
|
||||
};
|
||||
|
||||
const comment = `!{lens{\"timeRange\":{\"from\":\"now-7d\",\"to\":\"now\",\"mode\":\"relative\"},\"editMode\":false,\"attributes\":${JSON.stringify(
|
||||
lensVisualizationToMigrate
|
||||
)}}}\n\n`;
|
||||
|
||||
const caseComment = {
|
||||
type: 'cases-comments',
|
||||
id: '1cefd0d0-e86d-11eb-bae5-3d065cd16a32',
|
||||
attributes: {
|
||||
comment,
|
||||
},
|
||||
references: [],
|
||||
};
|
||||
|
||||
it('logs an error when it fails to parse invalid json', () => {
|
||||
const commentMigrationFunction = migrateByValueLensVisualizations(migrationFunction, '1.0.0');
|
||||
|
||||
const result = commentMigrationFunction(caseComment, contextMock);
|
||||
// the comment should remain unchanged when there is an error
|
||||
expect(result.attributes.comment).toEqual(comment);
|
||||
|
||||
const log = contextMock.log as jest.Mocked<SavedObjectsMigrationLogger>;
|
||||
expect(log.error.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"Failed to migrate comment with doc id: 1cefd0d0-e86d-11eb-bae5-3d065cd16a32 version: 8.0.0 error: an error",
|
||||
Object {
|
||||
"migrations": Object {
|
||||
"comment": Object {
|
||||
"id": "1cefd0d0-e86d-11eb-bae5-3d065cd16a32",
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
describe('mergeMigrationFunctionMaps', () => {
|
||||
it('logs an error when the passed migration functions fails', () => {
|
||||
const migrationObj1 = {
|
||||
'1.0.0': migrateByValueLensVisualizations(migrationFunction, '1.0.0'),
|
||||
} as unknown as MigrateFunctionsObject;
|
||||
|
||||
const migrationObj2 = {
|
||||
'2.0.0': (doc: SavedObjectUnsanitizedDoc<{ comment?: string }>) => {
|
||||
return doc;
|
||||
},
|
||||
};
|
||||
|
||||
const mergedFunctions = mergeMigrationFunctionMaps(migrationObj1, migrationObj2);
|
||||
mergedFunctions['1.0.0'](caseComment, contextMock);
|
||||
|
||||
const log = contextMock.log as jest.Mocked<SavedObjectsMigrationLogger>;
|
||||
expect(log.error.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"Failed to migrate comment with doc id: 1cefd0d0-e86d-11eb-bae5-3d065cd16a32 version: 8.0.0 error: an error",
|
||||
Object {
|
||||
"migrations": Object {
|
||||
"comment": Object {
|
||||
"id": "1cefd0d0-e86d-11eb-bae5-3d065cd16a32",
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('leaves the newline if it was in the original comment', () => {
|
||||
const originalComment = 'awesome\n';
|
||||
const parsedString = parseCommentString(originalComment);
|
||||
it('it does not log an error when the migration function does not use the context', () => {
|
||||
const migrationObj1 = {
|
||||
'1.0.0': migrateByValueLensVisualizations(migrationFunction, '1.0.0'),
|
||||
} as unknown as MigrateFunctionsObject;
|
||||
|
||||
expect(stringifyCommentWithoutTrailingNewline(originalComment, parsedString)).toEqual(
|
||||
'awesome\n'
|
||||
);
|
||||
});
|
||||
const migrationObj2 = {
|
||||
'2.0.0': (doc: SavedObjectUnsanitizedDoc<{ comment?: string }>) => {
|
||||
throw new Error('2.0.0 error');
|
||||
},
|
||||
};
|
||||
|
||||
it('does not remove newlines that are not at the end of the comment', () => {
|
||||
const originalComment = 'awesome\ncomment';
|
||||
const parsedString = parseCommentString(originalComment);
|
||||
const mergedFunctions = mergeMigrationFunctionMaps(migrationObj1, migrationObj2);
|
||||
|
||||
expect(stringifyCommentWithoutTrailingNewline(originalComment, parsedString)).toEqual(
|
||||
'awesome\ncomment'
|
||||
);
|
||||
});
|
||||
expect(() => mergedFunctions['2.0.0'](caseComment, contextMock)).toThrow();
|
||||
|
||||
it('does not remove spaces at the end of the comment', () => {
|
||||
const originalComment = 'awesome ';
|
||||
const parsedString = parseCommentString(originalComment);
|
||||
|
||||
expect(stringifyCommentWithoutTrailingNewline(originalComment, parsedString)).toEqual(
|
||||
'awesome '
|
||||
);
|
||||
const log = contextMock.log as jest.Mocked<SavedObjectsMigrationLogger>;
|
||||
expect(log.error).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('stringifyCommentWithoutTrailingNewline', () => {
|
||||
it('removes the newline added by the markdown library when the comment did not originally have one', () => {
|
||||
const originalComment = 'awesome';
|
||||
const parsedString = parseCommentString(originalComment);
|
||||
|
||||
expect(stringifyCommentWithoutTrailingNewline(originalComment, parsedString)).toEqual(
|
||||
'awesome'
|
||||
);
|
||||
});
|
||||
|
||||
it('leaves the newline if it was in the original comment', () => {
|
||||
const originalComment = 'awesome\n';
|
||||
const parsedString = parseCommentString(originalComment);
|
||||
|
||||
expect(stringifyCommentWithoutTrailingNewline(originalComment, parsedString)).toEqual(
|
||||
'awesome\n'
|
||||
);
|
||||
});
|
||||
|
||||
it('does not remove newlines that are not at the end of the comment', () => {
|
||||
const originalComment = 'awesome\ncomment';
|
||||
const parsedString = parseCommentString(originalComment);
|
||||
|
||||
expect(stringifyCommentWithoutTrailingNewline(originalComment, parsedString)).toEqual(
|
||||
'awesome\ncomment'
|
||||
);
|
||||
});
|
||||
|
||||
it('does not remove spaces at the end of the comment', () => {
|
||||
const originalComment = 'awesome ';
|
||||
const parsedString = parseCommentString(originalComment);
|
||||
|
||||
expect(stringifyCommentWithoutTrailingNewline(originalComment, parsedString)).toEqual(
|
||||
'awesome '
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,12 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { mapValues, trimEnd } from 'lodash';
|
||||
import { SerializableRecord } from '@kbn/utility-types';
|
||||
|
||||
import { LensServerPluginSetup } from '../../../../lens/server';
|
||||
import { mapValues, trimEnd, mergeWith } from 'lodash';
|
||||
import type { SerializableRecord } from '@kbn/utility-types';
|
||||
import {
|
||||
mergeMigrationFunctionMaps,
|
||||
MigrateFunction,
|
||||
MigrateFunctionsObject,
|
||||
} from '../../../../../../src/plugins/kibana_utils/common';
|
||||
|
@ -19,7 +16,9 @@ import {
|
|||
SavedObjectSanitizedDoc,
|
||||
SavedObjectMigrationFn,
|
||||
SavedObjectMigrationMap,
|
||||
SavedObjectMigrationContext,
|
||||
} from '../../../../../../src/core/server';
|
||||
import { LensServerPluginSetup } from '../../../../lens/server';
|
||||
import { CommentType, AssociationType } from '../../../common/api';
|
||||
import {
|
||||
isLensMarkdownNode,
|
||||
|
@ -29,6 +28,7 @@ import {
|
|||
stringifyMarkdownComment,
|
||||
} from '../../../common/utils/markdown_plugins/utils';
|
||||
import { addOwnerToSO, SanitizedCaseOwner } from '.';
|
||||
import { logError } from './utils';
|
||||
|
||||
interface UnsanitizedComment {
|
||||
comment: string;
|
||||
|
@ -103,33 +103,41 @@ export const createCommentsMigrations = (
|
|||
return mergeMigrationFunctionMaps(commentsMigrations, embeddableMigrations);
|
||||
};
|
||||
|
||||
const migrateByValueLensVisualizations =
|
||||
(migrate: MigrateFunction, version: string): SavedObjectMigrationFn<{ comment?: string }> =>
|
||||
(doc: SavedObjectUnsanitizedDoc<{ comment?: string }>) => {
|
||||
export const migrateByValueLensVisualizations =
|
||||
(
|
||||
migrate: MigrateFunction,
|
||||
version: string
|
||||
): SavedObjectMigrationFn<{ comment?: string }, { comment?: string }> =>
|
||||
(doc: SavedObjectUnsanitizedDoc<{ comment?: string }>, context: SavedObjectMigrationContext) => {
|
||||
if (doc.attributes.comment == null) {
|
||||
return doc;
|
||||
}
|
||||
|
||||
const parsedComment = parseCommentString(doc.attributes.comment);
|
||||
const migratedComment = parsedComment.children.map((comment) => {
|
||||
if (isLensMarkdownNode(comment)) {
|
||||
// casting here because ts complains that comment isn't serializable because LensMarkdownNode
|
||||
// extends Node which has fields that conflict with SerializableRecord even though it is serializable
|
||||
return migrate(comment as SerializableRecord) as LensMarkdownNode;
|
||||
}
|
||||
try {
|
||||
const parsedComment = parseCommentString(doc.attributes.comment);
|
||||
const migratedComment = parsedComment.children.map((comment) => {
|
||||
if (isLensMarkdownNode(comment)) {
|
||||
// casting here because ts complains that comment isn't serializable because LensMarkdownNode
|
||||
// extends Node which has fields that conflict with SerializableRecord even though it is serializable
|
||||
return migrate(comment as SerializableRecord) as LensMarkdownNode;
|
||||
}
|
||||
|
||||
return comment;
|
||||
});
|
||||
return comment;
|
||||
});
|
||||
|
||||
const migratedMarkdown = { ...parsedComment, children: migratedComment };
|
||||
const migratedMarkdown = { ...parsedComment, children: migratedComment };
|
||||
|
||||
return {
|
||||
...doc,
|
||||
attributes: {
|
||||
...doc.attributes,
|
||||
comment: stringifyCommentWithoutTrailingNewline(doc.attributes.comment, migratedMarkdown),
|
||||
},
|
||||
};
|
||||
return {
|
||||
...doc,
|
||||
attributes: {
|
||||
...doc.attributes,
|
||||
comment: stringifyCommentWithoutTrailingNewline(doc.attributes.comment, migratedMarkdown),
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
logError({ id: doc.id, context, error, docType: 'comment', docKey: 'comment' });
|
||||
return doc;
|
||||
}
|
||||
};
|
||||
|
||||
export const stringifyCommentWithoutTrailingNewline = (
|
||||
|
@ -147,3 +155,23 @@ export const stringifyCommentWithoutTrailingNewline = (
|
|||
// so the comment stays consistent
|
||||
return trimEnd(stringifiedComment, '\n');
|
||||
};
|
||||
|
||||
/**
|
||||
* merge function maps adds the context param from the original implementation at:
|
||||
* src/plugins/kibana_utils/common/persistable_state/merge_migration_function_map.ts
|
||||
* */
|
||||
export const mergeMigrationFunctionMaps = (
|
||||
// using the saved object framework types here because they include the context, this avoids type errors in our tests
|
||||
obj1: SavedObjectMigrationMap,
|
||||
obj2: SavedObjectMigrationMap
|
||||
) => {
|
||||
const customizer = (objValue: SavedObjectMigrationFn, srcValue: SavedObjectMigrationFn) => {
|
||||
if (!srcValue || !objValue) {
|
||||
return srcValue || objValue;
|
||||
}
|
||||
return (doc: SavedObjectUnsanitizedDoc, context: SavedObjectMigrationContext) =>
|
||||
objValue(srcValue(doc, context), context);
|
||||
};
|
||||
|
||||
return mergeWith({ ...obj1 }, obj2, customizer);
|
||||
};
|
||||
|
|
|
@ -7,7 +7,11 @@
|
|||
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
|
||||
import { SavedObjectMigrationContext, SavedObjectSanitizedDoc } from 'kibana/server';
|
||||
import {
|
||||
SavedObjectMigrationContext,
|
||||
SavedObjectSanitizedDoc,
|
||||
SavedObjectsMigrationLogger,
|
||||
} from 'kibana/server';
|
||||
import { migrationMocks } from 'src/core/server/mocks';
|
||||
import { CaseUserActionAttributes } from '../../../common/api';
|
||||
import { CASE_USER_ACTION_SAVED_OBJECT } from '../../../common/constants';
|
||||
|
@ -217,7 +221,19 @@ describe('user action migrations', () => {
|
|||
|
||||
userActionsConnectorIdMigration(userAction, context);
|
||||
|
||||
expect(context.log.error).toHaveBeenCalled();
|
||||
const log = context.log as jest.Mocked<SavedObjectsMigrationLogger>;
|
||||
expect(log.error.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"Failed to migrate user action connector with doc id: 1 version: 8.0.0 error: Unexpected token a in JSON at position 1",
|
||||
Object {
|
||||
"migrations": Object {
|
||||
"userAction": Object {
|
||||
"id": "1",
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -385,7 +401,19 @@ describe('user action migrations', () => {
|
|||
|
||||
userActionsConnectorIdMigration(userAction, context);
|
||||
|
||||
expect(context.log.error).toHaveBeenCalled();
|
||||
const log = context.log as jest.Mocked<SavedObjectsMigrationLogger>;
|
||||
expect(log.error.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"Failed to migrate user action connector with doc id: 1 version: 8.0.0 error: Unexpected token b in JSON at position 1",
|
||||
Object {
|
||||
"migrations": Object {
|
||||
"userAction": Object {
|
||||
"id": "1",
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -555,7 +583,19 @@ describe('user action migrations', () => {
|
|||
|
||||
userActionsConnectorIdMigration(userAction, context);
|
||||
|
||||
expect(context.log.error).toHaveBeenCalled();
|
||||
const log = context.log as jest.Mocked<SavedObjectsMigrationLogger>;
|
||||
expect(log.error.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"Failed to migrate user action connector with doc id: 1 version: 8.0.0 error: Unexpected token e in JSON at position 1",
|
||||
Object {
|
||||
"migrations": Object {
|
||||
"userAction": Object {
|
||||
"id": "1",
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -12,13 +12,13 @@ import {
|
|||
SavedObjectUnsanitizedDoc,
|
||||
SavedObjectSanitizedDoc,
|
||||
SavedObjectMigrationContext,
|
||||
LogMeta,
|
||||
} from '../../../../../../src/core/server';
|
||||
import { isPush, isUpdateConnector, isCreateConnector } from '../../../common/utils/user_actions';
|
||||
import { ConnectorTypes } from '../../../common/api';
|
||||
|
||||
import { extractConnectorIdFromJson } from '../../services/user_actions/transform';
|
||||
import { UserActionFieldType } from '../../services/user_actions/types';
|
||||
import { logError } from './utils';
|
||||
|
||||
interface UserActions {
|
||||
action_field: string[];
|
||||
|
@ -33,10 +33,6 @@ interface UserActionUnmigratedConnectorDocument {
|
|||
old_value?: string | null;
|
||||
}
|
||||
|
||||
interface UserActionLogMeta extends LogMeta {
|
||||
migrations: { userAction: { id: string } };
|
||||
}
|
||||
|
||||
export function userActionsConnectorIdMigration(
|
||||
doc: SavedObjectUnsanitizedDoc<UserActionUnmigratedConnectorDocument>,
|
||||
context: SavedObjectMigrationContext
|
||||
|
@ -50,7 +46,13 @@ export function userActionsConnectorIdMigration(
|
|||
try {
|
||||
return formatDocumentWithConnectorReferences(doc);
|
||||
} catch (error) {
|
||||
logError(doc.id, context, error);
|
||||
logError({
|
||||
id: doc.id,
|
||||
context,
|
||||
error,
|
||||
docType: 'user action connector',
|
||||
docKey: 'userAction',
|
||||
});
|
||||
|
||||
return originalDocWithReferences;
|
||||
}
|
||||
|
@ -99,19 +101,6 @@ function formatDocumentWithConnectorReferences(
|
|||
};
|
||||
}
|
||||
|
||||
function logError(id: string, context: SavedObjectMigrationContext, error: Error) {
|
||||
context.log.error<UserActionLogMeta>(
|
||||
`Failed to migrate user action connector doc id: ${id} version: ${context.migrationVersion} error: ${error.message}`,
|
||||
{
|
||||
migrations: {
|
||||
userAction: {
|
||||
id,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export const userActionsMigrations = {
|
||||
'7.10.0': (doc: SavedObjectUnsanitizedDoc<UserActions>): SavedObjectSanitizedDoc<UserActions> => {
|
||||
const { action_field, new_value, old_value, ...restAttributes } = doc.attributes;
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { SavedObjectsMigrationLogger } from 'kibana/server';
|
||||
import { migrationMocks } from '../../../../../../src/core/server/mocks';
|
||||
import { logError } from './utils';
|
||||
|
||||
describe('migration utils', () => {
|
||||
const context = migrationMocks.createContext();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('logs an error', () => {
|
||||
const log = context.log as jest.Mocked<SavedObjectsMigrationLogger>;
|
||||
|
||||
logError({
|
||||
id: '1',
|
||||
context,
|
||||
error: new Error('an error'),
|
||||
docType: 'a document',
|
||||
docKey: 'key',
|
||||
});
|
||||
|
||||
expect(log.error.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"Failed to migrate a document with doc id: 1 version: 8.0.0 error: an error",
|
||||
Object {
|
||||
"migrations": Object {
|
||||
"key": Object {
|
||||
"id": "1",
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { LogMeta, SavedObjectMigrationContext } from '../../../../../../src/core/server';
|
||||
|
||||
interface MigrationLogMeta extends LogMeta {
|
||||
migrations: {
|
||||
[x: string]: {
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export function logError({
|
||||
id,
|
||||
context,
|
||||
error,
|
||||
docType,
|
||||
docKey,
|
||||
}: {
|
||||
id: string;
|
||||
context: SavedObjectMigrationContext;
|
||||
error: Error;
|
||||
docType: string;
|
||||
docKey: string;
|
||||
}) {
|
||||
context.log.error<MigrationLogMeta>(
|
||||
`Failed to migrate ${docType} with doc id: ${id} version: ${context.migrationVersion} error: ${error.message}`,
|
||||
{
|
||||
migrations: {
|
||||
[docKey]: {
|
||||
id,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
43
yarn.lock
43
yarn.lock
|
@ -16357,6 +16357,11 @@ is-alphabetical@1.0.4, is-alphabetical@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.4.tgz#9e7d6b94916be22153745d184c298cbf986a686d"
|
||||
integrity sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==
|
||||
|
||||
is-alphanumeric@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-alphanumeric/-/is-alphanumeric-1.0.0.tgz#4a9cef71daf4c001c1d81d63d140cf53fd6889f4"
|
||||
integrity sha1-Spzvcdr0wAHB2B1j0UDPU/1oifQ=
|
||||
|
||||
is-alphanumerical@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-1.0.1.tgz#dfb4aa4d1085e33bdb61c2dee9c80e9c6c19f53b"
|
||||
|
@ -18935,7 +18940,7 @@ long@^4.0.0:
|
|||
resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28"
|
||||
integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==
|
||||
|
||||
longest-streak@^2.0.0:
|
||||
longest-streak@^2.0.0, longest-streak@^2.0.1:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.4.tgz#b8599957da5b5dab64dee3fe316fa774597d90e4"
|
||||
integrity sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==
|
||||
|
@ -19193,6 +19198,13 @@ markdown-it@^11.0.0:
|
|||
mdurl "^1.0.1"
|
||||
uc.micro "^1.0.5"
|
||||
|
||||
markdown-table@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-2.0.0.tgz#194a90ced26d31fe753d8b9434430214c011865b"
|
||||
integrity sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==
|
||||
dependencies:
|
||||
repeat-string "^1.0.0"
|
||||
|
||||
markdown-to-jsx@^6.11.4:
|
||||
version "6.11.4"
|
||||
resolved "https://registry.yarnpkg.com/markdown-to-jsx/-/markdown-to-jsx-6.11.4.tgz#b4528b1ab668aef7fe61c1535c27e837819392c5"
|
||||
|
@ -19265,6 +19277,13 @@ mdast-squeeze-paragraphs@^4.0.0:
|
|||
dependencies:
|
||||
unist-util-remove "^2.0.0"
|
||||
|
||||
mdast-util-compact@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/mdast-util-compact/-/mdast-util-compact-2.0.1.tgz#cabc69a2f43103628326f35b1acf735d55c99490"
|
||||
integrity sha512-7GlnT24gEwDrdAwEHrU4Vv5lLWrEer4KOkAiKT9nYstsTad7Oc1TwqT2zIMKRdZF7cTuaf+GA1E4Kv7jJh8mPA==
|
||||
dependencies:
|
||||
unist-util-visit "^2.0.0"
|
||||
|
||||
mdast-util-definitions@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-4.0.0.tgz#c5c1a84db799173b4dcf7643cda999e440c24db2"
|
||||
|
@ -24351,6 +24370,26 @@ remark-squeeze-paragraphs@4.0.0:
|
|||
dependencies:
|
||||
mdast-squeeze-paragraphs "^4.0.0"
|
||||
|
||||
remark-stringify@^8.0.3:
|
||||
version "8.1.1"
|
||||
resolved "https://registry.yarnpkg.com/remark-stringify/-/remark-stringify-8.1.1.tgz#e2a9dc7a7bf44e46a155ec78996db896780d8ce5"
|
||||
integrity sha512-q4EyPZT3PcA3Eq7vPpT6bIdokXzFGp9i85igjmhRyXWmPs0Y6/d2FYwUNotKAWyLch7g0ASZJn/KHHcHZQ163A==
|
||||
dependencies:
|
||||
ccount "^1.0.0"
|
||||
is-alphanumeric "^1.0.0"
|
||||
is-decimal "^1.0.0"
|
||||
is-whitespace-character "^1.0.0"
|
||||
longest-streak "^2.0.1"
|
||||
markdown-escapes "^1.0.0"
|
||||
markdown-table "^2.0.0"
|
||||
mdast-util-compact "^2.0.0"
|
||||
parse-entities "^2.0.0"
|
||||
repeat-string "^1.5.4"
|
||||
state-toggle "^1.0.0"
|
||||
stringify-entities "^3.0.0"
|
||||
unherit "^1.0.4"
|
||||
xtend "^4.0.1"
|
||||
|
||||
remark-stringify@^9.0.0:
|
||||
version "9.0.1"
|
||||
resolved "https://registry.yarnpkg.com/remark-stringify/-/remark-stringify-9.0.1.tgz#576d06e910548b0a7191a71f27b33f1218862894"
|
||||
|
@ -26347,7 +26386,7 @@ string_decoder@~0.10.x:
|
|||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
|
||||
integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=
|
||||
|
||||
stringify-entities@^3.0.1:
|
||||
stringify-entities@^3.0.0, stringify-entities@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-3.0.1.tgz#32154b91286ab0869ab2c07696223bd23b6dbfc0"
|
||||
integrity sha512-Lsk3ISA2++eJYqBMPKcr/8eby1I6L0gP0NlxF8Zja6c05yr/yCYyb2c9PwXjd08Ib3If1vn1rbs1H5ZtVuOfvQ==
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue