Skip to content

Commit

Permalink
[Dashboard] Use SavedObjectResolve (elastic#111040)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
2 people authored and artem-shelkovnikov committed Oct 20, 2021
1 parent 548c527 commit a5e4841
Show file tree
Hide file tree
Showing 9 changed files with 134 additions and 25 deletions.
8 changes: 7 additions & 1 deletion src/plugins/dashboard/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
15 changes: 14 additions & 1 deletion src/plugins/dashboard/public/application/dashboard_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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(
Expand Down Expand Up @@ -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>
Expand Down
3 changes: 3 additions & 0 deletions src/plugins/dashboard/public/application/dashboard_router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ export async function mountApp({
savedObjectsTaggingOss,
visualizations,
presentationUtil,
screenshotMode,
} = pluginsStart;

const activeSpaceId =
Expand Down Expand Up @@ -129,6 +130,8 @@ export async function mountApp({
core.notifications.toasts,
activeSpaceId || 'default'
),
spacesService: spacesApi,
screenshotModeService: screenshotMode,
};

const getUrlStateStorage = (history: RouteComponentProps['history']) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ export const useDashboardAppState = ({
dashboardCapabilities,
dashboardSessionStorage,
scopedHistory,
spacesService,
screenshotModeService,
} = services;
const { docTitle } = chrome;
const { notifications } = core;
Expand Down Expand Up @@ -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.
*/
Expand Down Expand Up @@ -340,6 +361,8 @@ export const useDashboardAppState = ({
search,
query,
data,
spacesService?.ui,
screenshotModeService,
]);

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions src/plugins/dashboard/public/plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -115,6 +116,7 @@ export interface DashboardStartDependencies {
savedObjects: SavedObjectsStart;
presentationUtil: PresentationUtilPluginStart;
savedObjectsTaggingOss?: SavedObjectTaggingOssPluginStart;
screenshotMode?: ScreenshotModePluginStart;
spaces?: SpacesPluginStart;
visualizations: VisualizationsStart;
}
Expand Down
93 changes: 72 additions & 21 deletions src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
4 changes: 4 additions & 0 deletions src/plugins/dashboard/public/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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 };

Expand Down Expand Up @@ -203,4 +205,6 @@ export interface DashboardAppServices {
dashboardSessionStorage: DashboardSessionStorage;
setHeaderActionMenu: AppMountParameters['setHeaderActionMenu'];
savedQueryService: DataPublicPluginStart['query']['savedQueries'];
spacesService?: SpacesPluginStart;
screenshotModeService?: ScreenshotModePluginStart;
}

0 comments on commit a5e4841

Please sign in to comment.