Skip to content

Commit

Permalink
[D&D] Save index pattern using proper saved object structure (#2218)
Browse files Browse the repository at this point in the history
* Save index pattern as a proper saved object relationship, previously it is only saved as an id in the visualizationState.

Signed-off-by: abbyhu2000 <abigailhu2000@gmail.com>

* remove index id from visualization when saving & add comments

Signed-off-by: abbyhu2000 <abigailhu2000@gmail.com>

* add migration for existing wizard

Signed-off-by: abbyhu2000 <abigailhu2000@gmail.com>

* add migration unit test

Signed-off-by: abbyhu2000 <abigailhu2000@gmail.com>

* change wizard doc version to 2; change migration version to 2.3.0

Signed-off-by: abbyhu2000 <abigailhu2000@gmail.com>

Signed-off-by: abbyhu2000 <abigailhu2000@gmail.com>
(cherry picked from commit 428e832)
  • Loading branch information
abbyhu2000 authored and github-actions[bot] committed Sep 6, 2022
1 parent 71c7374 commit acb92f0
Show file tree
Hide file tree
Showing 7 changed files with 218 additions and 7 deletions.
18 changes: 15 additions & 3 deletions src/plugins/wizard/public/application/utils/get_top_nav_config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ import { WizardVisSavedObject } from '../../types';
import { StyleState, VisualizationState, AppDispatch } from './state_management';
import { EDIT_PATH } from '../../../common';
import { setEditorState } from './state_management/metadata_slice';

interface TopNavConfigParams {
visualizationIdFromUrl: string;
savedWizardVis: WizardVisSavedObject;
Expand All @@ -60,7 +59,12 @@ export const getTopNavConfig = (
saveDisabledReason,
dispatch,
}: TopNavConfigParams,
{ history, toastNotifications, i18n: { Context: I18nContext } }: WizardServices
{
history,
toastNotifications,
i18n: { Context: I18nContext },
data: { indexPatterns },
}: WizardServices
) => {
const topNavConfig: TopNavMenuData[] = [
{
Expand Down Expand Up @@ -96,7 +100,15 @@ export const getTopNavConfig = (
return;
}
const currentTitle = savedWizardVis.title;
savedWizardVis.visualizationState = JSON.stringify(visualizationState);
const indexPattern = await indexPatterns.get(visualizationState.indexPattern || '');
savedWizardVis.searchSourceFields = {
index: indexPattern,
};
const vizStateWithoutIndex = {
searchField: visualizationState.searchField,
activeVisualization: visualizationState.activeVisualization,
};
savedWizardVis.visualizationState = JSON.stringify(vizStateWithoutIndex);
savedWizardVis.styleState = JSON.stringify(styleState);
savedWizardVis.title = newTitle;
savedWizardVis.description = newDescription;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,17 @@ import { WizardServices } from '../../../types';
import { MetricOptionsDefaults } from '../../../visualizations/metric/metric_viz_type';
import { getCreateBreadcrumbs, getEditBreadcrumbs } from '../breadcrumbs';
import { getSavedWizardVis } from '../get_saved_wizard_vis';
import { useTypedDispatch, setStyleState, setVisualizationState } from '../state_management';
import {
useTypedDispatch,
setStyleState,
setVisualizationState,
VisualizationState,
} from '../state_management';
import { useOpenSearchDashboards } from '../../../../../opensearch_dashboards_react/public';
import { setEditorState } from '../state_management/metadata_slice';

// This function can be used when instantiating a saved vis or creating a new one
// using url parameters, embedding and destroying it in DOM
export const useSavedWizardVis = (visualizationIdFromUrl: string | undefined) => {
const { services } = useOpenSearchDashboards<WizardServices>();
const [savedVisState, setSavedVisState] = useState<SavedObject | undefined>(undefined);
Expand All @@ -45,7 +52,12 @@ export const useSavedWizardVis = (visualizationIdFromUrl: string | undefined) =>

if (savedWizardVis.styleState !== '{}' && savedWizardVis.visualizationState !== '{}') {
const styleState = JSON.parse(savedWizardVis.styleState);
const visualizationState = JSON.parse(savedWizardVis.visualizationState);
const vizStateWithoutIndex = JSON.parse(savedWizardVis.visualizationState);
const visualizationState: VisualizationState = {
searchField: vizStateWithoutIndex.searchField,
activeVisualization: vizStateWithoutIndex.activeVisualization,
indexPattern: savedWizardVis.searchSourceFields.index,
};
// TODO: Add validation and transformation, throw/handle errors
dispatch(setStyleState<MetricOptionsDefaults>(styleState));
dispatch(setVisualizationState(visualizationState));
Expand Down
4 changes: 3 additions & 1 deletion src/plugins/wizard/public/saved_visualizations/_saved_vis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
SavedObjectOpenSearchDashboardsServices,
} from '../../../saved_objects/public';
import { EDIT_PATH, PLUGIN_ID, WIZARD_SAVED_OBJECT } from '../../common';
import { injectReferences } from './saved_visualization_references';

export function createSavedWizardVisClass(services: SavedObjectOpenSearchDashboardsServices) {
const SavedObjectClass = createSavedObjectClass(services);
Expand All @@ -32,6 +33,7 @@ export function createSavedWizardVisClass(services: SavedObjectOpenSearchDashboa
super({
type: SavedWizardVis.type,
mapping: SavedWizardVis.mapping,
injectReferences,

// if this is null/undefined then the SavedObject will be assigned the defaults
id,
Expand All @@ -42,7 +44,7 @@ export function createSavedWizardVisClass(services: SavedObjectOpenSearchDashboa
description: '',
visualizationState: '{}',
styleState: '{}',
version: 1,
version: 2,
},
});
this.showInRecentlyAccessed = true;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { SavedObjectReference } from '../../../../core/public';
import { WizardVisSavedObject } from '../types';
import { injectSearchSourceReferences } from '../../../data/public';

export function injectReferences(
savedObject: WizardVisSavedObject,
references: SavedObjectReference[]
) {
if (savedObject.searchSourceFields) {
savedObject.searchSourceFields = injectSearchSourceReferences(
savedObject.searchSourceFields as any,
references
);
}
}
9 changes: 8 additions & 1 deletion src/plugins/wizard/server/saved_objects/wizard_app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
WizardSavedObjectAttributes,
WIZARD_SAVED_OBJECT,
} from '../../common';
import { wizardSavedObjectTypeMigrations } from './wizard_migration';

export const wizardSavedObjectType: SavedObjectsType = {
name: WIZARD_SAVED_OBJECT,
Expand All @@ -29,7 +30,7 @@ export const wizardSavedObjectType: SavedObjectsType = {
};
},
},
migrations: {},
migrations: wizardSavedObjectTypeMigrations,
mappings: {
properties: {
title: {
Expand All @@ -47,6 +48,12 @@ export const wizardSavedObjectType: SavedObjectsType = {
index: false,
},
version: { type: 'integer' },
// Need to add a kibanaSavedObjectMeta attribute here to follow the current saved object flow
// When we save a saved object, the saved object plugin will extract the search source into two parts
// Some information will be put into kibanaSavedObjectMeta while others will be created as a reference object and pushed to the reference array
kibanaSavedObjectMeta: {
properties: { searchSourceJSON: { type: 'text', index: false } },
},
},
},
};
107 changes: 107 additions & 0 deletions src/plugins/wizard/server/saved_objects/wizard_migration.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { SavedObjectMigrationFn, SavedObjectMigrationContext } from '../../../../core/server';
import { wizardSavedObjectTypeMigrations } from './wizard_migration';

const savedObjectMigrationContext = (null as unknown) as SavedObjectMigrationContext;

describe('2.3.0', () => {
const migrate = (doc: any) =>
wizardSavedObjectTypeMigrations['2.3.0'](
doc as Parameters<SavedObjectMigrationFn>[0],
savedObjectMigrationContext
);

it('should return original doc if visualizationState is not found', () => {
const migratedDoc = migrate({
type: 'wizard',
attributes: {},
});

expect(migratedDoc).toEqual({
type: 'wizard',
attributes: {},
});
});

it('should return original doc if indexPattern is not found within visualizationState', () => {
const migratedDoc = migrate({
type: 'wizard',
attributes: {
visualizationState: {
searchSource: '',
activeVisualization: {},
},
},
});

expect(migratedDoc).toEqual({
type: 'wizard',
attributes: {
visualizationState: {
searchSource: '',
activeVisualization: {},
},
},
});
});

it('should return original doc if references is not an array', () => {
const migratedDoc = migrate({
type: 'wizard',
attributes: {
visualizationState: {},
},
references: {},
});

expect(migratedDoc).toEqual({
type: 'wizard',
attributes: {
visualizationState: {},
},
references: {},
});
});

it('should migrate the old version wizard saved object to new version wizard saved object', () => {
const migratedDoc = migrate({
type: 'wizard',
attributes: {
visualizationState: JSON.stringify({
searchFields: {},
activeVisualization: {},
indexPattern: 'indexPatternId',
}),
version: 1,
},
references: [],
});

expect(migratedDoc).toEqual({
type: 'wizard',
attributes: {
visualizationState: JSON.stringify({
searchFields: {},
activeVisualization: {},
}),
version: 2,
kibanaSavedObjectMeta: {
searchSourceJSON: JSON.stringify({
indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.index',
}),
},
},
references: [
{
name: 'kibanaSavedObjectMeta.searchSourceJSON.index',
type: 'index-pattern',
id: 'indexPatternId',
},
],
});
});
});
51 changes: 51 additions & 0 deletions src/plugins/wizard/server/saved_objects/wizard_migration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { get, flow } from 'lodash';
import { SavedObjectMigrationFn } from '../../../../core/server';

const migrateIndexPattern: SavedObjectMigrationFn<any, any> = (doc) => {
try {
const visualizationStateJSON = get(doc, 'attributes.visualizationState');
const visualizationState = JSON.parse(visualizationStateJSON);
const indexPatternId = visualizationState.indexPattern;
const indexRefName = 'kibanaSavedObjectMeta.searchSourceJSON.index';

if (indexPatternId && Array.isArray(doc.references)) {
const searchSourceIndex = {
indexRefName,
};
const visualizationWithoutIndex = {
searchFields: visualizationState.searchFields,
activeVisualization: visualizationState.activeVisualization,
};
doc.attributes.visualizationState = JSON.stringify(visualizationWithoutIndex);

doc.references.push({
name: indexRefName,
type: 'index-pattern',
id: indexPatternId,
});
doc.attributes.version = 2;

return {
...doc,
attributes: {
...doc.attributes,
kibanaSavedObjectMeta: {
searchSourceJSON: JSON.stringify(searchSourceIndex),
},
},
};
}
return doc;
} catch (e) {
return doc;
}
};

export const wizardSavedObjectTypeMigrations = {
'2.3.0': flow(migrateIndexPattern),
};

0 comments on commit acb92f0

Please sign in to comment.