mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Dashboard] Use SavedObjectResolve (#111040)
* Switch Dashboard to use savedobjects.resolve when loading * Don't use LegacyURI Redirect if in screenshot mode * Pass query string on redirects * Remove unused import * Fix carrying query params through redirect Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
d009e54199
commit
cba83335ef
9 changed files with 134 additions and 25 deletions
|
@ -18,7 +18,13 @@
|
|||
"presentationUtil",
|
||||
"visualizations"
|
||||
],
|
||||
"optionalPlugins": ["home", "spaces", "savedObjectsTaggingOss", "usageCollection"],
|
||||
"optionalPlugins": [
|
||||
"home",
|
||||
"spaces",
|
||||
"savedObjectsTaggingOss",
|
||||
"screenshotMode",
|
||||
"usageCollection"
|
||||
],
|
||||
"server": true,
|
||||
"ui": true,
|
||||
"requiredBundles": ["home", "kibanaReact", "kibanaUtils", "presentationUtil"]
|
||||
|
|
|
@ -21,6 +21,7 @@ import { EmbeddableRenderer } from '../services/embeddable';
|
|||
import { DashboardTopNav, isCompleteDashboardAppState } from './top_nav/dashboard_top_nav';
|
||||
import { DashboardAppServices, DashboardEmbedSettings, DashboardRedirect } from '../types';
|
||||
import { createKbnUrlStateStorage, withNotifyOnErrors } from '../services/kibana_utils';
|
||||
import { createDashboardEditUrl } from '../dashboard_constants';
|
||||
export interface DashboardAppProps {
|
||||
history: History;
|
||||
savedDashboardId?: string;
|
||||
|
@ -34,7 +35,7 @@ export function DashboardApp({
|
|||
redirectTo,
|
||||
history,
|
||||
}: DashboardAppProps) {
|
||||
const { core, chrome, embeddable, onAppLeave, uiSettings, data } =
|
||||
const { core, chrome, embeddable, onAppLeave, uiSettings, data, spacesService } =
|
||||
useKibana<DashboardAppServices>().services;
|
||||
|
||||
const kbnUrlStateStorage = useMemo(
|
||||
|
@ -109,6 +110,18 @@ export function DashboardApp({
|
|||
embedSettings={embedSettings}
|
||||
dashboardAppState={dashboardAppState}
|
||||
/>
|
||||
|
||||
{dashboardAppState.savedDashboard.outcome === 'conflict' &&
|
||||
dashboardAppState.savedDashboard.id &&
|
||||
dashboardAppState.savedDashboard.aliasId
|
||||
? spacesService?.ui.components.getLegacyUrlConflict({
|
||||
currentObjectId: dashboardAppState.savedDashboard.id,
|
||||
otherObjectId: dashboardAppState.savedDashboard.aliasId,
|
||||
otherObjectPath: `#${createDashboardEditUrl(
|
||||
dashboardAppState.savedDashboard.aliasId
|
||||
)}${history.location.search}`,
|
||||
})
|
||||
: null}
|
||||
<div className="dashboardViewport">
|
||||
<EmbeddableRenderer embeddable={dashboardAppState.dashboardContainer} />
|
||||
</div>
|
||||
|
|
|
@ -84,6 +84,7 @@ export async function mountApp({
|
|||
savedObjectsTaggingOss,
|
||||
visualizations,
|
||||
presentationUtil,
|
||||
screenshotMode,
|
||||
} = pluginsStart;
|
||||
|
||||
const activeSpaceId =
|
||||
|
@ -129,6 +130,8 @@ export async function mountApp({
|
|||
core.notifications.toasts,
|
||||
activeSpaceId || 'default'
|
||||
),
|
||||
spacesService: spacesApi,
|
||||
screenshotModeService: screenshotMode,
|
||||
};
|
||||
|
||||
const getUrlStateStorage = (history: RouteComponentProps['history']) =>
|
||||
|
|
|
@ -92,6 +92,8 @@ export const useDashboardAppState = ({
|
|||
dashboardCapabilities,
|
||||
dashboardSessionStorage,
|
||||
scopedHistory,
|
||||
spacesService,
|
||||
screenshotModeService,
|
||||
} = services;
|
||||
const { docTitle } = chrome;
|
||||
const { notifications } = core;
|
||||
|
@ -149,6 +151,25 @@ export const useDashboardAppState = ({
|
|||
if (canceled || !loadSavedDashboardResult) return;
|
||||
const { savedDashboard, savedDashboardState } = loadSavedDashboardResult;
|
||||
|
||||
// If the saved dashboard is an alias match, then we will redirect
|
||||
if (savedDashboard.outcome === 'aliasMatch' && savedDashboard.id && savedDashboard.aliasId) {
|
||||
// We want to keep the "query" params on our redirect.
|
||||
// But, these aren't true query params, they are technically part of the hash
|
||||
// So, to get the new path, we will just replace the current id in the hash
|
||||
// with the alias id
|
||||
const path = scopedHistory().location.hash.replace(
|
||||
savedDashboard.id,
|
||||
savedDashboard.aliasId
|
||||
);
|
||||
if (screenshotModeService?.isScreenshotMode()) {
|
||||
scopedHistory().replace(path);
|
||||
} else {
|
||||
await spacesService?.ui.redirectLegacyUrl(path);
|
||||
}
|
||||
// Return so we don't run any more of the hook and let it rerun after the redirect that just happened
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine initial state from the saved object, session storage, and URL, then dispatch it to Redux.
|
||||
*/
|
||||
|
@ -340,6 +361,8 @@ export const useDashboardAppState = ({
|
|||
search,
|
||||
query,
|
||||
data,
|
||||
spacesService?.ui,
|
||||
screenshotModeService,
|
||||
]);
|
||||
|
||||
/**
|
||||
|
|
|
@ -54,7 +54,10 @@ export const loadSavedDashboardState = async ({
|
|||
await indexPatterns.ensureDefaultDataView();
|
||||
let savedDashboard: DashboardSavedObject | undefined;
|
||||
try {
|
||||
savedDashboard = (await savedDashboards.get(savedDashboardId)) as DashboardSavedObject;
|
||||
savedDashboard = (await savedDashboards.get({
|
||||
id: savedDashboardId,
|
||||
useResolve: true,
|
||||
})) as DashboardSavedObject;
|
||||
} catch (error) {
|
||||
// E.g. a corrupt or deleted dashboard
|
||||
notifications.toasts.addDanger(error.message);
|
||||
|
|
|
@ -12,6 +12,7 @@ import { filter, map } from 'rxjs/operators';
|
|||
|
||||
import { Start as InspectorStartContract } from 'src/plugins/inspector/public';
|
||||
import { UrlForwardingSetup, UrlForwardingStart } from 'src/plugins/url_forwarding/public';
|
||||
import { ScreenshotModePluginStart } from 'src/plugins/screenshot_mode/public';
|
||||
import { APP_WRAPPER_CLASS } from '../../../core/public';
|
||||
import {
|
||||
App,
|
||||
|
@ -115,6 +116,7 @@ export interface DashboardStartDependencies {
|
|||
savedObjects: SavedObjectsStart;
|
||||
presentationUtil: PresentationUtilPluginStart;
|
||||
savedObjectsTaggingOss?: SavedObjectTaggingOssPluginStart;
|
||||
screenshotMode?: ScreenshotModePluginStart;
|
||||
spaces?: SpacesPluginStart;
|
||||
visualizations: VisualizationsStart;
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { assign, cloneDeep } from 'lodash';
|
||||
import { SavedObjectsClientContract } from 'kibana/public';
|
||||
import { EmbeddableStart } from '../services/embeddable';
|
||||
import { SavedObject, SavedObjectsStart } from '../services/saved_objects';
|
||||
import { Filter, ISearchSource, Query, RefreshInterval } from '../services/data';
|
||||
|
@ -32,12 +34,33 @@ export interface DashboardSavedObject extends SavedObject {
|
|||
getQuery(): Query;
|
||||
getFilters(): Filter[];
|
||||
getFullEditPath: (editMode?: boolean) => string;
|
||||
outcome?: string;
|
||||
aliasId?: string;
|
||||
}
|
||||
|
||||
const defaults = {
|
||||
title: '',
|
||||
hits: 0,
|
||||
description: '',
|
||||
panelsJSON: '[]',
|
||||
optionsJSON: JSON.stringify({
|
||||
// for BWC reasons we can't default dashboards that already exist without this setting to true.
|
||||
useMargins: true,
|
||||
syncColors: false,
|
||||
hidePanelTitles: false,
|
||||
} as DashboardOptions),
|
||||
version: 1,
|
||||
timeRestore: false,
|
||||
timeTo: undefined,
|
||||
timeFrom: undefined,
|
||||
refreshInterval: undefined,
|
||||
};
|
||||
|
||||
// Used only by the savedDashboards service, usually no reason to change this
|
||||
export function createSavedDashboardClass(
|
||||
savedObjectStart: SavedObjectsStart,
|
||||
embeddableStart: EmbeddableStart
|
||||
embeddableStart: EmbeddableStart,
|
||||
savedObjectsClient: SavedObjectsClientContract
|
||||
): new (id: string) => DashboardSavedObject {
|
||||
class SavedDashboard extends savedObjectStart.SavedObjectClass {
|
||||
// save these objects with the 'dashboard' type
|
||||
|
@ -68,7 +91,10 @@ export function createSavedDashboardClass(
|
|||
public static searchSource = true;
|
||||
public showInRecentlyAccessed = true;
|
||||
|
||||
constructor(id: string) {
|
||||
public outcome?: string;
|
||||
public aliasId?: string;
|
||||
|
||||
constructor(arg: { id: string; useResolve: boolean } | string) {
|
||||
super({
|
||||
type: SavedDashboard.type,
|
||||
mapping: SavedDashboard.mapping,
|
||||
|
@ -88,28 +114,53 @@ export function createSavedDashboardClass(
|
|||
},
|
||||
|
||||
// if this is null/undefined then the SavedObject will be assigned the defaults
|
||||
id,
|
||||
id: typeof arg === 'string' ? arg : arg.id,
|
||||
|
||||
// default values that will get assigned if the doc is new
|
||||
defaults: {
|
||||
title: '',
|
||||
hits: 0,
|
||||
description: '',
|
||||
panelsJSON: '[]',
|
||||
optionsJSON: JSON.stringify({
|
||||
// for BWC reasons we can't default dashboards that already exist without this setting to true.
|
||||
useMargins: true,
|
||||
syncColors: false,
|
||||
hidePanelTitles: false,
|
||||
} as DashboardOptions),
|
||||
version: 1,
|
||||
timeRestore: false,
|
||||
timeTo: undefined,
|
||||
timeFrom: undefined,
|
||||
refreshInterval: undefined,
|
||||
},
|
||||
defaults,
|
||||
});
|
||||
this.getFullPath = () => `/app/dashboards#${createDashboardEditUrl(this.id)}`;
|
||||
|
||||
const id: string = typeof arg === 'string' ? arg : arg.id;
|
||||
const useResolve = typeof arg === 'string' ? false : arg.useResolve;
|
||||
|
||||
this.getFullPath = () => `/app/dashboards#${createDashboardEditUrl(this.aliasId || this.id)}`;
|
||||
|
||||
// Overwrite init if we want to use resolve
|
||||
if (useResolve || true) {
|
||||
this.init = async () => {
|
||||
const esType = SavedDashboard.type;
|
||||
// ensure that the esType is defined
|
||||
if (!esType) throw new Error('You must define a type name to use SavedObject objects.');
|
||||
|
||||
if (!id) {
|
||||
// just assign the defaults and be done
|
||||
assign(this, defaults);
|
||||
await this.hydrateIndexPattern!();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
const {
|
||||
outcome,
|
||||
alias_target_id: aliasId,
|
||||
saved_object: resp,
|
||||
} = await savedObjectsClient.resolve(esType, id);
|
||||
|
||||
const respMapped = {
|
||||
_id: resp.id,
|
||||
_type: resp.type,
|
||||
_source: cloneDeep(resp.attributes),
|
||||
references: resp.references,
|
||||
found: !!resp._version,
|
||||
};
|
||||
|
||||
this.outcome = outcome;
|
||||
this.aliasId = aliasId;
|
||||
await this.applyESResp(respMapped);
|
||||
|
||||
return this;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
getQuery() {
|
||||
|
|
|
@ -27,6 +27,10 @@ export function createSavedDashboardLoader({
|
|||
savedObjectsClient,
|
||||
embeddableStart,
|
||||
}: Services) {
|
||||
const SavedDashboard = createSavedDashboardClass(savedObjects, embeddableStart);
|
||||
const SavedDashboard = createSavedDashboardClass(
|
||||
savedObjects,
|
||||
embeddableStart,
|
||||
savedObjectsClient
|
||||
);
|
||||
return new SavedObjectLoader(SavedDashboard, savedObjectsClient);
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import type {
|
|||
import { History } from 'history';
|
||||
import { AnyAction, Dispatch } from 'redux';
|
||||
import { BehaviorSubject, Subject } from 'rxjs';
|
||||
import { ScreenshotModePluginStart } from 'src/plugins/screenshot_mode/public';
|
||||
import { Query, Filter, IndexPattern, RefreshInterval, TimeRange } from './services/data';
|
||||
import { ContainerInput, EmbeddableInput, ViewMode } from './services/embeddable';
|
||||
import { SharePluginStart } from './services/share';
|
||||
|
@ -35,6 +36,7 @@ import { IKbnUrlStateStorage } from './services/kibana_utils';
|
|||
import { DashboardContainer, DashboardSavedObject } from '.';
|
||||
import { VisualizationsStart } from '../../visualizations/public';
|
||||
import { DashboardAppLocatorParams } from './locator';
|
||||
import { SpacesPluginStart } from './services/spaces';
|
||||
|
||||
export { SavedDashboardPanel };
|
||||
|
||||
|
@ -203,4 +205,6 @@ export interface DashboardAppServices {
|
|||
dashboardSessionStorage: DashboardSessionStorage;
|
||||
setHeaderActionMenu: AppMountParameters['setHeaderActionMenu'];
|
||||
savedQueryService: DataPublicPluginStart['query']['savedQueries'];
|
||||
spacesService?: SpacesPluginStart;
|
||||
screenshotModeService?: ScreenshotModePluginStart;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue