Skip to content
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

Remove App communication from URL #67064

Merged
merged 42 commits into from
Jun 23, 2020
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
cc77109
added location state to scopedHistory.ts - used this location state f…
ThomThomson May 18, 2020
07bbfc6
Merge branch 'master' of github.com:elastic/kibana into arch/redirect…
ThomThomson May 18, 2020
a0bad30
Merge branch 'master' of github.com:elastic/kibana into arch/redirect…
ThomThomson May 19, 2020
1d70c31
added the redirectTo scopedHistory system to visualize
ThomThomson May 19, 2020
df86dd7
API changes
ThomThomson May 19, 2020
8be92da
removed lens dashboard dependency
ThomThomson May 19, 2020
c527675
Merge branch 'master' of github.com:elastic/kibana into arch/redirect…
ThomThomson May 20, 2020
78e45af
short circuit for destructuring, changed jest test
ThomThomson May 20, 2020
072fe2e
Removed unnecessary scoped history changes
ThomThomson May 20, 2020
4911b46
Merge branch 'master' of github.com:elastic/kibana into arch/redirect…
ThomThomson May 26, 2020
1bbfbdc
Merge branch 'master' of github.com:elastic/kibana into arch/redirect…
ThomThomson May 27, 2020
77dfdf5
Created an embeddable state transfer API with generic and specific ty…
ThomThomson May 27, 2020
32de3b6
Merge branch 'master' of github.com:elastic/kibana into arch/redirect…
ThomThomson May 28, 2020
3bf46f5
Added embeddablePackage to state transfer API. Used the state transfe…
ThomThomson May 29, 2020
e461319
Merge branch 'master' of github.com:elastic/kibana into arch/redirect…
ThomThomson May 29, 2020
b5a0b29
Updated tests. Added mock for EmbeddablePanel
ThomThomson May 29, 2020
afe51e1
Updated tests. Added mock for EmbeddablePanel
ThomThomson May 29, 2020
a8129c8
Added tests for embeddable state transfer
ThomThomson May 29, 2020
7e8b25a
Merge branch 'master' of github.com:elastic/kibana into arch/redirect…
ThomThomson Jun 1, 2020
9d425f1
fixed type error, added embeddable to NP set_services.ts
ThomThomson Jun 1, 2020
c2feccc
Merge branch 'master' of github.com:elastic/kibana into arch/redirect…
ThomThomson Jun 1, 2020
1b2929c
Made state transfer optional in edit panel and new vis modal
ThomThomson Jun 2, 2020
81e6aa1
Removed all Embeddable panel changes in favor of #68006. Renamed stat…
ThomThomson Jun 2, 2020
65ddfe9
Merge branch 'master' of github.com:elastic/kibana into arch/redirect…
ThomThomson Jun 4, 2020
0263f3a
Merge branch 'master' of github.com:elastic/kibana into arch/redirect…
ThomThomson Jun 4, 2020
4fac70c
Merge branch 'master' of github.com:elastic/kibana into arch/redirect…
ThomThomson Jun 5, 2020
070f9b8
Used removeAfterFetch to make lens state on reload behavior match vis…
ThomThomson Jun 5, 2020
aecab9c
update embeddable state transfer tests to use scoped history mock
ThomThomson Jun 5, 2020
927a40c
Merge branch 'master' of github.com:elastic/kibana into arch/redirect…
ThomThomson Jun 8, 2020
289d458
Changed to a factory approach for embeddable state transfer
ThomThomson Jun 8, 2020
f615d9a
type changes
ThomThomson Jun 8, 2020
d98b8fe
Reinstated dashboard as a required plugin for visualize
ThomThomson Jun 9, 2020
75dda63
Merge branch 'master' of github.com:elastic/kibana into arch/redirect…
ThomThomson Jun 9, 2020
94730d1
Merge branch 'master' of github.com:elastic/kibana into arch/redirect…
ThomThomson Jun 11, 2020
524d1cb
Merge branch 'master' of github.com:elastic/kibana into arch/redirect…
ThomThomson Jun 12, 2020
2cec3f9
changed incoming state fetch to optionally remove a list of keys from…
ThomThomson Jun 15, 2020
8f0504f
Merge branch 'master' of github.com:elastic/kibana into arch/redirect…
ThomThomson Jun 15, 2020
4d68185
Added append mode option to embeddable state transfer outgoing methods
ThomThomson Jun 17, 2020
22e7974
Merge branch 'master' of github.com:elastic/kibana into arch/redirect…
ThomThomson Jun 17, 2020
7950a31
type fixes
ThomThomson Jun 17, 2020
1d5bd74
jest fixes
ThomThomson Jun 18, 2020
8fab749
Merge branch 'master' of github.com:elastic/kibana into arch/redirect…
ThomThomson Jun 23, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/core/public/application/scoped_history.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ const createMock = ({
block: jest.fn(),
createHref: jest.fn(),
createSubHistory: jest.fn(),
fetchLocationState: jest.fn(),
removeFromLocationState: jest.fn(),
go: jest.fn(),
goBack: jest.fn(),
goForward: jest.fn(),
Expand Down
23 changes: 23 additions & 0 deletions src/core/public/application/scoped_history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
Href,
Action,
} from 'history';
import { cloneDeep } from 'lodash';

/**
* A wrapper around a `History` instance that is scoped to a particular base path of the history stack. Behaves
Expand Down Expand Up @@ -63,6 +64,8 @@ export class ScopedHistory<HistoryLocationState = unknown>
*/
private currentLocationKeyIndex: number = 0;

private parentLocationState: { [key: string]: unknown } = {};

constructor(private readonly parentHistory: History, private readonly basePath: string) {
const parentPath = this.parentHistory.location.pathname;
if (!parentPath.startsWith(basePath)) {
Expand All @@ -71,6 +74,7 @@ export class ScopedHistory<HistoryLocationState = unknown>
);
}

this.parentLocationState = parentHistory.location.state as { [key: string]: unknown };
this.locationKeys.push(this.parentHistory.location.key);
this.setupHistoryListener();
}
Expand Down Expand Up @@ -111,6 +115,25 @@ export class ScopedHistory<HistoryLocationState = unknown>
return this.parentHistory.action;
}

/**
* Fetches the parent location state
*/
public fetchLocationState<T extends unknown = unknown>(): T {
this.verifyActive();
return cloneDeep(this.parentLocationState) as T;
}
ThomThomson marked this conversation as resolved.
Show resolved Hide resolved

/**
* Removes a key from the parent location state
*
* * @param key, the key to remove from the parent state
*/
public removeFromLocationState(keys: string[]) {
this.verifyActive();
keys.forEach((key: string) => delete this.parentLocationState[key]);
this.replace(this.location, (this.parentLocationState as unknown) as HistoryLocationState);
}
ThomThomson marked this conversation as resolved.
Show resolved Hide resolved

/**
* Pushes a new location onto the history stack. If there are forward entries in the stack, they will be removed.
*
Expand Down
2 changes: 2 additions & 0 deletions src/core/public/public.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1257,13 +1257,15 @@ export class ScopedHistory<HistoryLocationState = unknown> implements History<Hi
prependBasePath?: boolean | undefined;
}) => string;
createSubHistory: <SubHistoryLocationState = unknown>(basePath: string) => ScopedHistory<SubHistoryLocationState>;
fetchLocationState<T extends unknown = unknown>(): T;
go: (n: number) => void;
goBack: () => void;
goForward: () => void;
get length(): number;
listen: (listener: (location: Location<HistoryLocationState>, action: Action) => void) => UnregisterCallback;
get location(): Location<HistoryLocationState>;
push: (pathOrLocation: string | LocationDescriptorObject<HistoryLocationState>, state?: HistoryLocationState | undefined) => void;
removeFromLocationState(keys: string[]): void;
replace: (pathOrLocation: string | LocationDescriptorObject<HistoryLocationState>, state?: HistoryLocationState | undefined) => void;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ import { getDashboardTitle } from './dashboard_strings';
import { DashboardAppScope } from './dashboard_app';
import { convertSavedDashboardPanelToPanelState } from './lib/embeddable_saved_object_converters';
import { RenderDeps } from './application';
import { IKbnUrlStateStorage, removeQueryParam, unhashUrl } from '../../../kibana_utils/public';
import { IKbnUrlStateStorage, unhashUrl } from '../../../kibana_utils/public';
import {
addFatalError,
AngularHttpError,
Expand All @@ -93,6 +93,11 @@ export interface DashboardAppControllerDependencies extends RenderDeps {
navigation: NavigationStart;
}

export interface DashboardIncomingState {
type: string;
id: string;
ThomThomson marked this conversation as resolved.
Show resolved Hide resolved
}

export class DashboardAppController {
// Part of the exposed plugin API - do not remove without careful consideration.
appStatus: {
Expand All @@ -111,6 +116,7 @@ export class DashboardAppController {
embeddable,
share,
dashboardCapabilities,
scopedHistory,
embeddableCapabilities: { visualizeCapabilities, mapsCapabilities },
data: { query: queryService },
core: {
Expand Down Expand Up @@ -398,15 +404,14 @@ export class DashboardAppController {
refreshDashboardContainer();
});

// This code needs to be replaced with a better mechanism for adding new embeddables of
// any type from the add panel. Likely this will happen via creating a visualization "inline",
// without navigating away from the UX.
if ($routeParams[DashboardConstants.ADD_EMBEDDABLE_TYPE]) {
const type = $routeParams[DashboardConstants.ADD_EMBEDDABLE_TYPE];
const id = $routeParams[DashboardConstants.ADD_EMBEDDABLE_ID];
container.addNewEmbeddable<SavedObjectEmbeddableInput>(type, { savedObjectId: id });
removeQueryParam(history, DashboardConstants.ADD_EMBEDDABLE_TYPE);
removeQueryParam(history, DashboardConstants.ADD_EMBEDDABLE_ID);
const incomingState = scopedHistory().fetchLocationState<DashboardIncomingState>();
if (incomingState) {
scopedHistory().removeFromLocationState(Object.keys(incomingState));
if (incomingState.id) {
container.addNewEmbeddable<SavedObjectEmbeddableInput>(incomingState.type, {
savedObjectId: incomingState.id,
});
}
}
}

Expand Down
1 change: 0 additions & 1 deletion src/plugins/embeddable/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import './index.scss';
import { PluginInitializerContext } from 'src/core/public';
import { EmbeddablePublicPlugin } from './plugin';

export { EMBEDDABLE_ORIGINATING_APP_PARAM } from './types';
export {
ACTION_ADD_PANEL,
ACTION_APPLY_FILTER,
Expand Down
25 changes: 16 additions & 9 deletions src/plugins/embeddable/public/lib/actions/edit_panel_action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,20 @@ import { take } from 'rxjs/operators';
import { ViewMode } from '../types';
import { EmbeddableFactoryNotFoundError } from '../errors';
import { EmbeddableStart } from '../../plugin';
import { EMBEDDABLE_ORIGINATING_APP_PARAM, IEmbeddable } from '../..';
import { IEmbeddable } from '../..';

export const ACTION_EDIT_PANEL = 'editPanel';

interface ActionContext {
embeddable: IEmbeddable;
}

interface NavigationContext {
app: string;
path: string;
state?: unknown;
}

export class EditPanelAction implements Action<ActionContext> {
public readonly type = ACTION_EDIT_PANEL;
public readonly id = ACTION_EDIT_PANEL;
Expand Down Expand Up @@ -81,7 +87,10 @@ export class EditPanelAction implements Action<ActionContext> {
const appTarget = this.getAppTarget(context);

if (appTarget) {
await this.application.navigateToApp(appTarget.app, { path: appTarget.path });
await this.application.navigateToApp(appTarget.app, {
path: appTarget.path,
state: appTarget.state,
});
return;
}

Expand All @@ -92,22 +101,20 @@ export class EditPanelAction implements Action<ActionContext> {
}
}

public getAppTarget({ embeddable }: ActionContext): { app: string; path: string } | undefined {
public getAppTarget({ embeddable }: ActionContext): NavigationContext | undefined {
const app = embeddable ? embeddable.getOutput().editApp : undefined;
let path = embeddable ? embeddable.getOutput().editPath : undefined;
const path = embeddable ? embeddable.getOutput().editPath : undefined;
if (app && path) {
if (this.currentAppId) {
path += `?${EMBEDDABLE_ORIGINATING_APP_PARAM}=${this.currentAppId}`;
const state = { embeddableOriginatingApp: this.currentAppId };
return { app, path, state };
}
return { app, path };
}
}

public async getHref({ embeddable }: ActionContext): Promise<string> {
let editUrl = embeddable ? embeddable.getOutput().editUrl : undefined;
if (editUrl && this.currentAppId) {
editUrl += `?${EMBEDDABLE_ORIGINATING_APP_PARAM}=${this.currentAppId}`;
}
const editUrl = embeddable ? embeddable.getOutput().editUrl : undefined;
return editUrl ? editUrl : '';
}
}
2 changes: 0 additions & 2 deletions src/plugins/embeddable/public/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ import {
EmbeddableFactoryDefinition,
} from './lib/embeddables';

export const EMBEDDABLE_ORIGINATING_APP_PARAM = 'embeddableOriginatingApp';

export type EmbeddableFactoryRegistry = Map<string, EmbeddableFactory>;

export type EmbeddableFactoryProvider = <
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import {
EmbeddableOutput,
ErrorEmbeddable,
IContainer,
EMBEDDABLE_ORIGINATING_APP_PARAM,
} from '../../../embeddable/public';
import { DisabledLabEmbeddable } from './disabled_lab_embeddable';
import { VisualizeEmbeddable, VisualizeInput, VisualizeOutput } from './visualize_embeddable';
Expand Down Expand Up @@ -137,7 +136,7 @@ export class VisualizeEmbeddableFactory
// to allow for in place creation of visualizations without having to navigate away to a new URL.
const originatingAppParam = await this.getCurrentAppId();
showNewVisModal({
editorParams: [`${EMBEDDABLE_ORIGINATING_APP_PARAM}=${originatingAppParam}`],
redirectState: { embeddableOriginatingApp: originatingAppParam },
outsideVisualizeApp: true,
});
return undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,15 +143,16 @@ describe('NewVisModal', () => {
);
});

it('closes and redirects properly if visualization with aliasPath and addToDashboard in editorParams', () => {
it('closes and redirects properly if visualization with aliasPath and originatingApp in redirectState', () => {
const onClose = jest.fn();
const navigateToApp = jest.fn();
const wrapper = mountWithIntl(
<NewVisModal
isOpen={true}
onClose={onClose}
visTypesRegistry={visTypes}
editorParams={['foo=true', 'bar=42', 'embeddableOriginatingApp=notAnApp']}
editorParams={['foo=true', 'bar=42']}
redirectState={{ embeddableOriginatingApp: 'coolJestTestApp' }}
addBasePath={addBasePath}
uiSettings={uiSettings}
application={({ navigateToApp } as unknown) as ApplicationStart}
Expand All @@ -161,7 +162,8 @@ describe('NewVisModal', () => {
const visButton = wrapper.find('button[data-test-subj="visType-visWithAliasUrl"]');
visButton.simulate('click');
expect(navigateToApp).toBeCalledWith('otherApp', {
path: '#/aliasUrl?embeddableOriginatingApp=notAnApp',
path: '#/aliasUrl',
state: { embeddableOriginatingApp: 'coolJestTestApp' },
});
expect(onClose).toHaveBeenCalled();
});
Expand Down
14 changes: 6 additions & 8 deletions src/plugins/visualizations/public/wizard/new_vis_modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import { SearchSelection } from './search_selection';
import { TypeSelection } from './type_selection';
import { TypesStart, VisType, VisTypeAlias } from '../vis_types';
import { UsageCollectionSetup } from '../../../../plugins/usage_collection/public';
import { EMBEDDABLE_ORIGINATING_APP_PARAM } from '../../../embeddable/public';

interface TypeSelectionProps {
isOpen: boolean;
Expand All @@ -40,6 +39,7 @@ interface TypeSelectionProps {
savedObjects: SavedObjectsStart;
usageCollection?: UsageCollectionSetup;
application: ApplicationStart;
redirectState?: unknown;
outsideVisualizeApp?: boolean;
}

Expand Down Expand Up @@ -147,14 +147,11 @@ class NewVisModal extends React.Component<TypeSelectionProps, TypeSelectionState
let params;
if ('aliasPath' in visType) {
params = visType.aliasPath;
if (this.props.editorParams) {
const originatingAppParam = this.props.editorParams?.find((param: string) =>
param.startsWith(EMBEDDABLE_ORIGINATING_APP_PARAM)
);
params = originatingAppParam ? `${params}?${originatingAppParam}` : params;
}
this.props.onClose();
this.props.application.navigateToApp(visType.aliasApp, { path: params });
this.props.application.navigateToApp(visType.aliasApp, {
path: params,
state: this.props.redirectState,
});
return;
}

Expand All @@ -169,6 +166,7 @@ class NewVisModal extends React.Component<TypeSelectionProps, TypeSelectionState
if (this.props.outsideVisualizeApp) {
this.props.application.navigateToApp('visualize', {
path: `#${basePath}${params.join('&')}`,
state: this.props.redirectState,
});
} else {
location.assign(this.props.addBasePath(`${baseUrl}${params.join('&')}`));
Expand Down
3 changes: 3 additions & 0 deletions src/plugins/visualizations/public/wizard/show_new_vis.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
export interface ShowNewVisModalParams {
editorParams?: string[];
onClose?: () => void;
redirectState?: unknown;
outsideVisualizeApp?: boolean;
}

Expand All @@ -45,6 +46,7 @@ export interface ShowNewVisModalParams {
export function showNewVisModal({
editorParams = [],
onClose,
redirectState,
outsideVisualizeApp,
}: ShowNewVisModalParams = {}) {
const container = document.createElement('div');
Expand All @@ -66,6 +68,7 @@ export function showNewVisModal({
isOpen={true}
onClose={handleClose}
outsideVisualizeApp={outsideVisualizeApp}
redirectState={redirectState}
editorParams={editorParams}
visTypesRegistry={getTypes()}
addBasePath={getHttp().basePath.prepend}
Expand Down
30 changes: 11 additions & 19 deletions src/plugins/visualize/public/application/editor/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,8 @@ import { makeStateful, useVisualizeAppState } from './lib';
import { VisualizeConstants } from '../visualize_constants';
import { getEditBreadcrumbs } from '../breadcrumbs';

import { EMBEDDABLE_ORIGINATING_APP_PARAM } from '../../../../embeddable/public';

import { addHelpMenuToAppChrome } from '../help_menu/help_menu_util';
import { unhashUrl, removeQueryParam } from '../../../../kibana_utils/public';
import { unhashUrl } from '../../../../kibana_utils/public';
import { MarkdownSimple, toMountPoint } from '../../../../kibana_react/public';
import {
addFatalError,
Expand Down Expand Up @@ -73,7 +71,7 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState
I18nContext,
setActiveUrl,
visualizations,
dashboard,
scopedHistory,
} = getServices();

const {
Expand Down Expand Up @@ -112,10 +110,11 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState
localStorage.get('kibana.userQueryLanguage') || uiSettings.get('search:queryLanguage'),
};

const originatingApp = $route.current.params[EMBEDDABLE_ORIGINATING_APP_PARAM];
removeQueryParam(history, EMBEDDABLE_ORIGINATING_APP_PARAM);

$scope.getOriginatingApp = () => originatingApp;
const { embeddableOriginatingApp } = scopedHistory().fetchLocationState() || {};
if (embeddableOriginatingApp) {
scopedHistory().removeFromLocationState(['embeddableOriginatingApp']);
}
$scope.getOriginatingApp = () => embeddableOriginatingApp;

const visStateToEditorState = () => {
const savedVisState = visualizations.convertFromSerializedVis(vis.serialize());
Expand Down Expand Up @@ -675,17 +674,10 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState
// Manually insert a new url so the back button will open the saved visualization.
history.replace(appPath);
setActiveUrl(appPath);
const lastAppType = $scope.getOriginatingApp();

if (lastAppType === 'dashboards') {
const savedVisId = firstSave || savedVis.copyOnSave ? savedVis.id : '';
dashboard.addEmbeddableToDashboard({
embeddableId: savedVisId,
embeddableType: VISUALIZE_EMBEDDABLE_TYPE,
});
} else {
application.navigateToApp(lastAppType);
}
const savedVisId = firstSave || savedVis.copyOnSave ? savedVis.id : '';
application.navigateToApp($scope.getOriginatingApp(), {
state: { id: savedVisId, type: VISUALIZE_EMBEDDABLE_TYPE },
});
} else if (savedVis.id === $route.current.params.id) {
chrome.docTitle.change(savedVis.lastSavedTitle);
chrome.setBreadcrumbs($injector.invoke(getEditBreadcrumbs));
Expand Down
2 changes: 0 additions & 2 deletions src/plugins/visualize/public/kibana_services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ import { DataPublicPluginStart } from '../../data/public';
import { VisualizationsStart } from '../../visualizations/public';
import { SavedVisualizations } from './application/types';
import { KibanaLegacyStart } from '../../kibana_legacy/public';
import { DashboardStart } from '../../dashboard/public';
import { SavedObjectsStart } from '../../saved_objects/public';

export interface VisualizeKibanaServices {
Expand All @@ -52,7 +51,6 @@ export interface VisualizeKibanaServices {
kibanaLegacy: KibanaLegacyStart;
visualizeCapabilities: any;
visualizations: VisualizationsStart;
dashboard: DashboardStart;
I18nContext: I18nStart['Context'];
setActiveUrl: (newUrl: string) => void;
createVisEmbeddableFromObject: VisualizationsStart['__LEGACY']['createVisEmbeddableFromObject'];
Expand Down
Loading