-
Notifications
You must be signed in to change notification settings - Fork 870
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[MDS] Add Vega support for importing saved objects #6123
Changes from 26 commits
efe5116
eb9c9ef
ddba426
76aa282
5658eb5
3c4a03b
ef3fd46
be65384
a5aa803
fb8fe84
f9747f3
796551b
c22ce7c
24a34a5
d8b58e6
d3f4c9d
9808989
89b8ccc
f93caca
bbfe01e
27fb804
21e4273
612154c
437bae6
f1b1b51
c1e0406
85aa317
e6a3449
6238bc5
c392de4
18f412c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,7 @@ | |
|
||
import { mockUuidv4 } from './__mocks__'; | ||
import { SavedObjectReference, SavedObjectsImportRetry } from 'opensearch-dashboards/public'; | ||
import { SavedObject } from '../types'; | ||
import { SavedObject, SavedObjectsClientContract } from '../types'; | ||
import { SavedObjectsErrorHelpers } from '..'; | ||
import { | ||
checkConflictsForDataSource, | ||
|
@@ -24,6 +24,45 @@ const createObject = (type: string, id: string): SavedObjectType => ({ | |
references: (Symbol() as unknown) as SavedObjectReference[], | ||
}); | ||
|
||
const createVegaVisualizationObject = (id: string): SavedObjectType => { | ||
const visState = | ||
id.split('_').length > 1 | ||
? '{"title":"some-title","type":"vega","aggs":[],"params":{"spec":"{\\n data: {\\n url: {\\n index: example_index\\n data_source_name: old-datasource-title\\n }\\n }\\n}"}}' | ||
: '{"title":"some-title","type":"vega","aggs":[],"params":{"spec":"{\\n data: {\\n url: {\\n index: example_index\\n }\\n }\\n}"}}'; | ||
return { | ||
type: 'visualization', | ||
id, | ||
attributes: { title: 'some-title', visState }, | ||
references: | ||
id.split('_').length > 1 | ||
? [{ id: id.split('_')[0], type: 'data-source', name: 'dataSource' }] | ||
: [], | ||
} as SavedObjectType; | ||
}; | ||
|
||
const getSavedObjectClient = (): SavedObjectsClientContract => { | ||
const savedObject = {} as SavedObjectsClientContract; | ||
savedObject.get = jest.fn().mockImplementation((type, id) => { | ||
if (type === 'data-source' && id === 'old-datasource-id') { | ||
return Promise.resolve({ | ||
attributes: { | ||
title: 'old-datasource-title', | ||
}, | ||
}); | ||
} else if (type === 'data-source') { | ||
return Promise.resolve({ | ||
attributes: { | ||
title: 'some-datasource-title', | ||
}, | ||
}); | ||
} | ||
|
||
return Promise.resolve(undefined); | ||
}); | ||
|
||
return savedObject; | ||
}; | ||
|
||
const getResultMock = { | ||
conflict: (type: string, id: string) => { | ||
const error = SavedObjectsErrorHelpers.createConflictError(type, id).output.payload; | ||
|
@@ -56,6 +95,7 @@ describe('#checkConflictsForDataSource', () => { | |
retries?: SavedObjectsImportRetry[]; | ||
createNewCopies?: boolean; | ||
dataSourceId?: string; | ||
savedObjectsClient?: SavedObjectsClientContract; | ||
}): ConflictsForDataSourceParams => { | ||
return { ...partial }; | ||
}; | ||
|
@@ -140,4 +180,82 @@ describe('#checkConflictsForDataSource', () => { | |
importIdMap: new Map(), | ||
}); | ||
}); | ||
|
||
/* | ||
Vega test cases | ||
*/ | ||
it('will attach datasource name to Vega spec when importing from local to datasource', async () => { | ||
const vegaSavedObject = createVegaVisualizationObject('some-object-id'); | ||
const params = setupParams({ | ||
objects: [vegaSavedObject], | ||
ignoreRegularConflicts: true, | ||
dataSourceId: 'some-datasource-id', | ||
savedObjectsClient: getSavedObjectClient(), | ||
}); | ||
const checkConflictsForDataSourceResult = await checkConflictsForDataSource(params); | ||
|
||
expect(checkConflictsForDataSourceResult).toEqual( | ||
expect.objectContaining({ | ||
filteredObjects: [ | ||
{ | ||
...vegaSavedObject, | ||
attributes: { | ||
title: 'some-title', | ||
visState: | ||
'{"title":"some-title","type":"vega","aggs":[],"params":{"spec":"{\\n data: {\\n url: {\\n index: example_index\\n data_source_name: some-datasource-title\\n }\\n }\\n}"}}', | ||
}, | ||
id: 'some-datasource-id_some-object-id', | ||
references: [ | ||
{ | ||
id: 'some-datasource-id', | ||
type: 'data-source', | ||
name: 'dataSource', | ||
}, | ||
], | ||
}, | ||
], | ||
errors: [], | ||
importIdMap: new Map([ | ||
[ | ||
`visualization:some-object-id`, | ||
{ id: 'some-datasource-id_some-object-id', omitOriginId: true }, | ||
], | ||
]), | ||
}) | ||
); | ||
}); | ||
|
||
it('will not change Vega spec when importing from datasource to different datasource', async () => { | ||
const vegaSavedObject = createVegaVisualizationObject('old-datasource-id_some-object-id'); | ||
const params = setupParams({ | ||
objects: [vegaSavedObject], | ||
ignoreRegularConflicts: true, | ||
dataSourceId: 'some-datasource-id', | ||
savedObjectsClient: getSavedObjectClient(), | ||
}); | ||
const checkConflictsForDataSourceResult = await checkConflictsForDataSource(params); | ||
|
||
expect(checkConflictsForDataSourceResult).toEqual( | ||
expect.objectContaining({ | ||
filteredObjects: [ | ||
{ | ||
...vegaSavedObject, | ||
attributes: { | ||
title: 'some-title', | ||
visState: | ||
'{"title":"some-title","type":"vega","aggs":[],"params":{"spec":"{\\n data: {\\n url: {\\n index: example_index\\n data_source_name: old-datasource-title\\n }\\n }\\n}"}}', | ||
}, | ||
id: 'some-datasource-id_some-object-id', | ||
}, | ||
], | ||
errors: [], | ||
importIdMap: new Map([ | ||
[ | ||
`visualization:some-object-id`, | ||
{ id: 'some-datasource-id_some-object-id', omitOriginId: true }, | ||
], | ||
]), | ||
}) | ||
); | ||
}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same as above There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. also can we add test for duplicate and undefined data source title? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since this makes a |
||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,13 +3,24 @@ | |
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import { SavedObject, SavedObjectsImportError, SavedObjectsImportRetry } from '../types'; | ||
import { | ||
SavedObject, | ||
SavedObjectsClientContract, | ||
SavedObjectsImportError, | ||
SavedObjectsImportRetry, | ||
} from '../types'; | ||
import { | ||
extractVegaSpecFromSavedObject, | ||
getDataSourceTitleFromId, | ||
updateDataSourceNameInVegaSpec, | ||
} from './utils'; | ||
|
||
export interface ConflictsForDataSourceParams { | ||
objects: Array<SavedObject<{ title?: string }>>; | ||
ignoreRegularConflicts?: boolean; | ||
retries?: SavedObjectsImportRetry[]; | ||
dataSourceId?: string; | ||
savedObjectsClient?: SavedObjectsClientContract; | ||
} | ||
|
||
interface ImportIdMapEntry { | ||
|
@@ -31,6 +42,7 @@ export async function checkConflictsForDataSource({ | |
ignoreRegularConflicts, | ||
retries = [], | ||
dataSourceId, | ||
savedObjectsClient, | ||
}: ConflictsForDataSourceParams) { | ||
const filteredObjects: Array<SavedObject<{ title?: string }>> = []; | ||
const errors: SavedObjectsImportError[] = []; | ||
|
@@ -43,6 +55,12 @@ export async function checkConflictsForDataSource({ | |
(acc, cur) => acc.set(`${cur.type}:${cur.id}`, cur), | ||
new Map<string, SavedObjectsImportRetry>() | ||
); | ||
|
||
const dataSourceTitle = | ||
!!dataSourceId && !!savedObjectsClient | ||
? await getDataSourceTitleFromId(dataSourceId, savedObjectsClient) | ||
: undefined; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. how do we handle undefined data source title here? |
||
|
||
objects.forEach((object) => { | ||
const { | ||
type, | ||
|
@@ -74,6 +92,33 @@ export async function checkConflictsForDataSource({ | |
/** | ||
* Only update importIdMap and filtered objects | ||
*/ | ||
|
||
// Some visualization types will need special modifications, like Vega visualizations | ||
if (object.type === 'visualization') { | ||
const vegaSpec = extractVegaSpecFromSavedObject(object); | ||
|
||
if (!!vegaSpec && !!dataSourceTitle) { | ||
const updatedVegaSpec = updateDataSourceNameInVegaSpec({ | ||
spec: vegaSpec, | ||
newDataSourceName: dataSourceTitle, | ||
}); | ||
|
||
// @ts-expect-error | ||
const visStateObject = JSON.parse(object.attributes?.visState); | ||
visStateObject.params.spec = updatedVegaSpec; | ||
|
||
// @ts-expect-error | ||
object.attributes.visState = JSON.stringify(visStateObject); | ||
if (!!dataSourceId) { | ||
object.references.push({ | ||
id: dataSourceId, | ||
name: 'dataSource', | ||
type: 'data-source', | ||
}); | ||
} | ||
} | ||
} | ||
|
||
const omitOriginId = ignoreRegularConflicts; | ||
importIdMap.set(`${type}:${id}`, { id: `${dataSourceId}_${rawId}`, omitOriginId }); | ||
filteredObjects.push({ ...object, id: `${dataSourceId}_${rawId}` }); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minor: for better coverage, maybe we want to test that savedobjectclient.get have been called? And also test called with expected attributes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, added a test to ensure
get()
was called.