diff --git a/CHANGELOG.md b/CHANGELOG.md index 17059703937e..c2d96b060c0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -139,6 +139,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - [BUG][Multiple Datasource] Fix on data source selectable and readonly component are not consistent ([#6545]https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6545) - [BUG][Multiple Datasource] Add validation for title length to be no longer than 32 characters [#6452](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6452)) - [Dev Tool] Add additional themed styles to ace overrides ([#5327](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5327)) +- [VisBuilder] Allow saving and loading filter and query in a saved VisBuilder ([#6460](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6460)) ### 🚞 Infrastructure diff --git a/src/plugins/vis_builder/public/application/utils/get_top_nav_config.test.tsx b/src/plugins/vis_builder/public/application/utils/get_top_nav_config.test.tsx index 353b9d90e1ff..536596cc9e1f 100644 --- a/src/plugins/vis_builder/public/application/utils/get_top_nav_config.test.tsx +++ b/src/plugins/vis_builder/public/application/utils/get_top_nav_config.test.tsx @@ -95,7 +95,10 @@ describe('getOnSave', () => { }, ], }, - "searchSourceFields": Object {}, + "searchSourceFields": Object { + "filter": null, + "query": null, + }, "styleState": "", "title": "new title", "version": 0, diff --git a/src/plugins/vis_builder/public/application/utils/get_top_nav_config.tsx b/src/plugins/vis_builder/public/application/utils/get_top_nav_config.tsx index 2a30e1700b43..945e09a09734 100644 --- a/src/plugins/vis_builder/public/application/utils/get_top_nav_config.tsx +++ b/src/plugins/vis_builder/public/application/utils/get_top_nav_config.tsx @@ -172,7 +172,7 @@ export const getOnSave = ( returnToOrigin: boolean; newDescription?: string; }) => { - const { embeddable, toastNotifications, application, history } = services; + const { data, embeddable, toastNotifications, application, history } = services; const stateTransfer = embeddable.getStateTransfer(); if (!savedVisBuilderVis) { @@ -183,6 +183,9 @@ export const getOnSave = ( savedVisBuilderVis.title = newTitle; savedVisBuilderVis.description = newDescription; savedVisBuilderVis.copyOnSave = newCopyOnSave; + const searchSourceInstance = savedVisBuilderVis.searchSourceFields; + searchSourceInstance.query = data.query.queryString.getQuery() || null; + searchSourceInstance.filter = data.query.filterManager.getFilters() || null; const newlyCreated = !savedVisBuilderVis.id || savedVisBuilderVis.copyOnSave; try { diff --git a/src/plugins/vis_builder/public/application/utils/use/use_saved_vis_builder_vis.ts b/src/plugins/vis_builder/public/application/utils/use/use_saved_vis_builder_vis.ts index 44ffbaf75953..3ec69902c9c2 100644 --- a/src/plugins/vis_builder/public/application/utils/use/use_saved_vis_builder_vis.ts +++ b/src/plugins/vis_builder/public/application/utils/use/use_saved_vis_builder_vis.ts @@ -35,6 +35,7 @@ export const useSavedVisBuilderVis = (visualizationIdFromUrl: string | undefined const { application: { navigateToApp }, chrome, + data, history, http: { basePath }, toastNotifications, @@ -61,6 +62,17 @@ export const useSavedVisBuilderVis = (visualizationIdFromUrl: string | undefined const { title, state } = getStateFromSavedObject(savedVisBuilderVis); chrome.setBreadcrumbs(getEditBreadcrumbs(title, navigateToApp)); chrome.docTitle.change(title); + // sync initial app filters from savedObject to filterManager + const filters = savedVisBuilderVis.searchSourceFields.filter; + const query = + savedVisBuilderVis.searchSourceFields.query || data.query.queryString.getDefaultQuery(); + const actualFilters = []; + const tempFilters = typeof filters === 'function' ? filters() : filters; + (Array.isArray(tempFilters) ? tempFilters : [tempFilters]).forEach((filter) => { + if (filter) actualFilters.push(filter); + }); + data.query.filterManager.setAppFilters(actualFilters); + data.query.queryString.setQuery(query); dispatch(setUIStateState(state.ui)); dispatch(setStyleState(state.style)); diff --git a/src/plugins/vis_builder/public/embeddable/vis_builder_embeddable.tsx b/src/plugins/vis_builder/public/embeddable/vis_builder_embeddable.tsx index a931877ffe6d..38b84452ff4c 100644 --- a/src/plugins/vis_builder/public/embeddable/vis_builder_embeddable.tsx +++ b/src/plugins/vis_builder/public/embeddable/vis_builder_embeddable.tsx @@ -35,7 +35,7 @@ import { getTypeService, getUIActions, } from '../plugin_services'; -import { PersistedState } from '../../../visualizations/public'; +import { PersistedState, prepareJson } from '../../../visualizations/public'; import { VisBuilderSavedVis } from '../saved_visualizations/transforms'; import { handleVisEvent } from '../application/utils/handle_vis_event'; import { VisBuilderEmbeddableFactoryDeps } from './vis_builder_embeddable_factory'; @@ -246,6 +246,28 @@ export class VisBuilderEmbeddable extends Embeddable<VisBuilderInput, VisBuilder this.autoRefreshFetchSubscription.unsubscribe(); } + private async updateExpression() { + // Construct the initial part of the pipeline with context management. + let pipeline = `opensearchDashboards | opensearch_dashboards_context `; + + // Access the query and filters from savedObject if available. + const query = this.savedVis?.searchSourceFields?.query; + const filters = this.savedVis?.searchSourceFields?.filter; + + // Append query and filters to the pipeline string if they exist. + if (query) { + pipeline += prepareJson('query', query); + } + if (filters) { + pipeline += prepareJson('filters', filters); + } + + const currentExpression = (await this.getExpression()) ?? ''; + + // Replace 'opensearchDashboards' with the constructed pipeline in the existing expression. + return currentExpression.replace('opensearchDashboards', pipeline); + } + private async updateHandler() { const expressionParams: IExpressionLoaderParams = { searchContext: { @@ -261,6 +283,8 @@ export class VisBuilderEmbeddable extends Embeddable<VisBuilderInput, VisBuilder this.abortController = new AbortController(); const abortController = this.abortController; + this.expression = await this.updateExpression(); + if (this.handler && !abortController.signal.aborted) { this.handler.update(this.expression, expressionParams); } @@ -296,12 +320,8 @@ export class VisBuilderEmbeddable extends Embeddable<VisBuilderInput, VisBuilder dirty = true; } - if (dirty) { - this.expression = (await this.getExpression()) ?? ''; - - if (this.handler) { - this.updateHandler(); - } + if (this.handler && dirty) { + this.updateHandler(); } } diff --git a/src/plugins/vis_builder/public/plugin.ts b/src/plugins/vis_builder/public/plugin.ts index 4e8f020d1fe8..fefd3107be66 100644 --- a/src/plugins/vis_builder/public/plugin.ts +++ b/src/plugins/vis_builder/public/plugin.ts @@ -125,6 +125,10 @@ export class VisBuilderPlugin // make sure the index pattern list is up to date pluginsStart.data.indexPatterns.clearCache(); + // make sure the filterManager is refreshed + const filters = pluginsStart.data.query.filterManager.getFilters(); + const pinFilters = filters.filter(opensearchFilters.isFilterPinned); + pluginsStart.data.query.filterManager.setFilters(pinFilters ? pinFilters : []); // make sure a default index pattern exists // if not, the page will be redirected to management and visualize won't be rendered // TODO: Add the redirect diff --git a/src/plugins/vis_builder/public/saved_visualizations/transforms.ts b/src/plugins/vis_builder/public/saved_visualizations/transforms.ts index 9f8dd705e3e4..d1192e8977fe 100644 --- a/src/plugins/vis_builder/public/saved_visualizations/transforms.ts +++ b/src/plugins/vis_builder/public/saved_visualizations/transforms.ts @@ -33,14 +33,17 @@ export const saveStateToSavedObject = ( }; export interface VisBuilderSavedVis - extends Pick<VisBuilderSavedObjectAttributes, 'id' | 'title' | 'description'> { + extends Pick< + VisBuilderSavedObjectAttributes, + 'id' | 'title' | 'description' | 'searchSourceFields' + > { state: RenderState; } export const getStateFromSavedObject = ( obj: VisBuilderSavedObjectAttributes ): VisBuilderSavedVis => { - const { id, title, description } = obj; + const { id, title, description, searchSourceFields } = obj; const styleState = JSON.parse(obj.styleState || '{}'); const uiState = JSON.parse(obj.uiState || '{}'); const vizStateWithoutIndex = JSON.parse(obj.visualizationState || ''); @@ -74,6 +77,7 @@ export const getStateFromSavedObject = ( id, title, description, + searchSourceFields, state: { visualization: visualizationState, style: styleState, diff --git a/src/plugins/visualizations/public/index.ts b/src/plugins/visualizations/public/index.ts index 6c8cf4ec51d2..588140f7da0a 100644 --- a/src/plugins/visualizations/public/index.ts +++ b/src/plugins/visualizations/public/index.ts @@ -77,3 +77,4 @@ export { ExprVisAPIEvents } from './expressions/vis'; export { VisualizationListItem } from './vis_types/vis_type_alias_registry'; export { VISUALIZE_ENABLE_LABS_SETTING } from '../common/constants'; export { createSavedVisLoader } from './saved_visualizations'; +export { prepareJson } from './legacy/build_pipeline';