mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
* Add originatingPath to edit_panel_action Support originatingPath in embeddable state transfer Fix ts error * Fixed jest tests * Parse originatingPath without using hash * provide static container context on embeddable panel * Fixed ts error Co-authored-by: Anton Dosov <anton.dosov@elastic.co> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Catherine Liu <catherine.liu@elastic.co> Co-authored-by: Anton Dosov <anton.dosov@elastic.co>
This commit is contained in:
parent
295f5290e0
commit
14a87f3aa3
15 changed files with 103 additions and 12 deletions
|
@ -35,6 +35,7 @@ export type {
|
|||
EmbeddableEditorState,
|
||||
EmbeddablePackageState,
|
||||
EmbeddableRendererProps,
|
||||
EmbeddableContainerContext,
|
||||
} from './lib';
|
||||
export {
|
||||
ACTION_ADD_PANEL,
|
||||
|
|
|
@ -44,7 +44,13 @@ test('is compatible when edit url is available, in edit mode and editable', asyn
|
|||
|
||||
test('redirects to app using state transfer with by value mode', async () => {
|
||||
applicationMock.currentAppId$ = of('superCoolCurrentApp');
|
||||
const action = new EditPanelAction(getFactory, applicationMock, stateTransferMock);
|
||||
const testPath = '/test-path';
|
||||
const action = new EditPanelAction(
|
||||
getFactory,
|
||||
applicationMock,
|
||||
stateTransferMock,
|
||||
() => testPath
|
||||
);
|
||||
const embeddable = new EditableEmbeddable(
|
||||
{
|
||||
id: '123',
|
||||
|
@ -67,13 +73,20 @@ test('redirects to app using state transfer with by value mode', async () => {
|
|||
coolInput1: 1,
|
||||
coolInput2: 2,
|
||||
},
|
||||
originatingPath: testPath,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('redirects to app using state transfer without by value mode', async () => {
|
||||
applicationMock.currentAppId$ = of('superCoolCurrentApp');
|
||||
const action = new EditPanelAction(getFactory, applicationMock, stateTransferMock);
|
||||
const testPath = '/test-path';
|
||||
const action = new EditPanelAction(
|
||||
getFactory,
|
||||
applicationMock,
|
||||
stateTransferMock,
|
||||
() => testPath
|
||||
);
|
||||
const embeddable = new EditableEmbeddable(
|
||||
{ id: '123', viewMode: ViewMode.EDIT, savedObjectId: '1234' } as SavedObjectEmbeddableInput,
|
||||
true
|
||||
|
@ -86,6 +99,7 @@ test('redirects to app using state transfer without by value mode', async () =>
|
|||
originatingApp: 'superCoolCurrentApp',
|
||||
embeddableId: '123',
|
||||
valueInput: undefined,
|
||||
originatingPath: testPath,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
|
@ -43,7 +43,8 @@ export class EditPanelAction implements Action<ActionContext> {
|
|||
constructor(
|
||||
private readonly getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory'],
|
||||
private readonly application: ApplicationStart,
|
||||
private readonly stateTransfer?: EmbeddableStateTransfer
|
||||
private readonly stateTransfer?: EmbeddableStateTransfer,
|
||||
private readonly getOriginatingPath?: () => string
|
||||
) {
|
||||
if (this.application?.currentAppId$) {
|
||||
this.application.currentAppId$
|
||||
|
@ -104,15 +105,21 @@ export class EditPanelAction implements Action<ActionContext> {
|
|||
public getAppTarget({ embeddable }: ActionContext): NavigationContext | undefined {
|
||||
const app = embeddable ? embeddable.getOutput().editApp : undefined;
|
||||
const path = embeddable ? embeddable.getOutput().editPath : undefined;
|
||||
|
||||
if (app && path) {
|
||||
if (this.currentAppId) {
|
||||
const byValueMode = !(embeddable.getInput() as SavedObjectEmbeddableInput).savedObjectId;
|
||||
|
||||
const originatingPath = this.getOriginatingPath?.();
|
||||
|
||||
const state: EmbeddableEditorState = {
|
||||
originatingApp: this.currentAppId,
|
||||
valueInput: byValueMode ? this.getExplicitInput({ embeddable }) : undefined,
|
||||
embeddableId: embeddable.id,
|
||||
searchSessionId: embeddable.getInput().searchSessionId,
|
||||
originatingPath,
|
||||
};
|
||||
|
||||
return { app, path, state };
|
||||
}
|
||||
return { app, path };
|
||||
|
|
|
@ -53,6 +53,18 @@ const removeById =
|
|||
({ id }: { id: string }) =>
|
||||
disabledActions.indexOf(id) === -1;
|
||||
|
||||
/**
|
||||
* Embeddable container may provide information about its environment,
|
||||
* Use it for drilling down data that is static or doesn't have to be reactive,
|
||||
* otherwise prefer passing data with input$
|
||||
* */
|
||||
export interface EmbeddableContainerContext {
|
||||
/**
|
||||
* Current app's path including query and hash starting from {appId}
|
||||
*/
|
||||
getCurrentPath?: () => string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
embeddable: IEmbeddable<EmbeddableInput, EmbeddableOutput>;
|
||||
getActions: UiActionsService['getTriggerCompatibleActions'];
|
||||
|
@ -70,6 +82,7 @@ interface Props {
|
|||
showShadow?: boolean;
|
||||
showBadges?: boolean;
|
||||
showNotifications?: boolean;
|
||||
containerContext?: EmbeddableContainerContext;
|
||||
}
|
||||
|
||||
interface State {
|
||||
|
@ -373,7 +386,8 @@ export class EmbeddablePanel extends React.Component<Props, State> {
|
|||
editPanel: new EditPanelAction(
|
||||
this.props.getEmbeddableFactory,
|
||||
this.props.application,
|
||||
this.props.stateTransfer
|
||||
this.props.stateTransfer,
|
||||
this.props.containerContext?.getCurrentPath
|
||||
),
|
||||
};
|
||||
};
|
||||
|
|
|
@ -36,6 +36,7 @@ import {
|
|||
IEmbeddable,
|
||||
EmbeddablePanel,
|
||||
SavedObjectEmbeddableInput,
|
||||
EmbeddableContainerContext,
|
||||
} from './lib';
|
||||
import { EmbeddableFactoryDefinition } from './lib/embeddables/embeddable_factory_definition';
|
||||
import { EmbeddableStateTransfer } from './lib/state_transfer';
|
||||
|
@ -97,7 +98,11 @@ export interface EmbeddableStart extends PersistableStateService<EmbeddableState
|
|||
) => AttributeService<A, V, R>;
|
||||
}
|
||||
|
||||
export type EmbeddablePanelHOC = React.FC<{ embeddable: IEmbeddable; hideHeader?: boolean }>;
|
||||
export type EmbeddablePanelHOC = React.FC<{
|
||||
embeddable: IEmbeddable;
|
||||
hideHeader?: boolean;
|
||||
containerContext?: EmbeddableContainerContext;
|
||||
}>;
|
||||
|
||||
export class EmbeddablePublicPlugin implements Plugin<EmbeddableSetup, EmbeddableStart> {
|
||||
private readonly embeddableFactoryDefinitions: Map<string, EmbeddableFactoryDefinition> =
|
||||
|
@ -155,7 +160,15 @@ export class EmbeddablePublicPlugin implements Plugin<EmbeddableSetup, Embeddabl
|
|||
|
||||
const getEmbeddablePanelHoc =
|
||||
() =>
|
||||
({ embeddable, hideHeader }: { embeddable: IEmbeddable; hideHeader?: boolean }) =>
|
||||
({
|
||||
embeddable,
|
||||
hideHeader,
|
||||
containerContext,
|
||||
}: {
|
||||
embeddable: IEmbeddable;
|
||||
hideHeader?: boolean;
|
||||
containerContext?: EmbeddableContainerContext;
|
||||
}) =>
|
||||
(
|
||||
<EmbeddablePanel
|
||||
hideHeader={hideHeader}
|
||||
|
@ -169,6 +182,7 @@ export class EmbeddablePublicPlugin implements Plugin<EmbeddableSetup, Embeddabl
|
|||
application={core.application}
|
||||
inspector={inspector}
|
||||
SavedObjectFinder={getSavedObjectFinder(core.savedObjects, core.uiSettings)}
|
||||
containerContext={containerContext}
|
||||
/>
|
||||
);
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ import { VisualizeConstants } from '../..';
|
|||
|
||||
export const VisualizeByValueEditor = ({ onAppLeave }: VisualizeAppProps) => {
|
||||
const [originatingApp, setOriginatingApp] = useState<string>();
|
||||
const [originatingPath, setOriginatingPath] = useState<string>();
|
||||
const { services } = useKibana<VisualizeServices>();
|
||||
const [eventEmitter] = useState(new EventEmitter());
|
||||
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
|
||||
|
@ -39,8 +40,10 @@ export const VisualizeByValueEditor = ({ onAppLeave }: VisualizeAppProps) => {
|
|||
embeddableId: embeddableIdValue,
|
||||
valueInput: valueInputValue,
|
||||
searchSessionId,
|
||||
originatingPath: pathValue,
|
||||
} = stateTransferService.getIncomingEditorState(VisualizeConstants.APP_ID) || {};
|
||||
|
||||
setOriginatingPath(pathValue);
|
||||
setOriginatingApp(value);
|
||||
setValueInput(valueInputValue);
|
||||
setEmbeddableId(embeddableIdValue);
|
||||
|
@ -64,7 +67,8 @@ export const VisualizeByValueEditor = ({ onAppLeave }: VisualizeAppProps) => {
|
|||
eventEmitter,
|
||||
isChromeVisible,
|
||||
valueInput,
|
||||
originatingApp
|
||||
originatingApp,
|
||||
originatingPath
|
||||
);
|
||||
const { appState, hasUnappliedChanges } = useVisualizeAppState(
|
||||
services,
|
||||
|
@ -99,6 +103,7 @@ export const VisualizeByValueEditor = ({ onAppLeave }: VisualizeAppProps) => {
|
|||
isEmbeddableRendered={isEmbeddableRendered}
|
||||
originatingApp={originatingApp}
|
||||
setOriginatingApp={setOriginatingApp}
|
||||
originatingPath={originatingPath}
|
||||
setHasUnsavedChanges={setHasUnsavedChanges}
|
||||
visEditorRef={visEditorRef}
|
||||
embeddableId={embeddableId}
|
||||
|
|
|
@ -27,6 +27,7 @@ import { VisualizeConstants } from '../..';
|
|||
export const VisualizeEditor = ({ onAppLeave }: VisualizeAppProps) => {
|
||||
const { id: visualizationIdFromUrl } = useParams<{ id: string }>();
|
||||
const [originatingApp, setOriginatingApp] = useState<string>();
|
||||
const [originatingPath, setOriginatingPath] = useState<string>();
|
||||
const [embeddableIdValue, setEmbeddableId] = useState<string>();
|
||||
const { services } = useKibana<VisualizeServices>();
|
||||
const [eventEmitter] = useState(new EventEmitter());
|
||||
|
@ -61,6 +62,7 @@ export const VisualizeEditor = ({ onAppLeave }: VisualizeAppProps) => {
|
|||
originatingApp: value,
|
||||
searchSessionId,
|
||||
embeddableId,
|
||||
originatingPath: pathValue,
|
||||
} = stateTransferService.getIncomingEditorState(VisualizeConstants.APP_ID) || {};
|
||||
|
||||
if (searchSessionId) {
|
||||
|
@ -71,6 +73,7 @@ export const VisualizeEditor = ({ onAppLeave }: VisualizeAppProps) => {
|
|||
|
||||
setEmbeddableId(embeddableId);
|
||||
setOriginatingApp(value);
|
||||
setOriginatingPath(pathValue);
|
||||
}, [services]);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -91,6 +94,7 @@ export const VisualizeEditor = ({ onAppLeave }: VisualizeAppProps) => {
|
|||
isEmbeddableRendered={isEmbeddableRendered}
|
||||
originatingApp={originatingApp}
|
||||
setOriginatingApp={setOriginatingApp}
|
||||
originatingPath={originatingPath}
|
||||
visualizationIdFromUrl={visualizationIdFromUrl}
|
||||
setHasUnsavedChanges={setHasUnsavedChanges}
|
||||
visEditorRef={visEditorRef}
|
||||
|
|
|
@ -37,6 +37,7 @@ interface VisualizeEditorCommonProps {
|
|||
visEditorRef: RefObject<HTMLDivElement>;
|
||||
originatingApp?: string;
|
||||
setOriginatingApp?: (originatingApp: string | undefined) => void;
|
||||
originatingPath?: string;
|
||||
visualizationIdFromUrl?: string;
|
||||
embeddableId?: string;
|
||||
}
|
||||
|
@ -52,6 +53,7 @@ export const VisualizeEditorCommon = ({
|
|||
isEmbeddableRendered,
|
||||
onAppLeave,
|
||||
originatingApp,
|
||||
originatingPath,
|
||||
setOriginatingApp,
|
||||
visualizationIdFromUrl,
|
||||
embeddableId,
|
||||
|
@ -117,6 +119,7 @@ export const VisualizeEditorCommon = ({
|
|||
isEmbeddableRendered={isEmbeddableRendered}
|
||||
hasUnappliedChanges={hasUnappliedChanges}
|
||||
originatingApp={originatingApp}
|
||||
originatingPath={originatingPath}
|
||||
setOriginatingApp={setOriginatingApp}
|
||||
visInstance={visInstance}
|
||||
stateContainer={appState}
|
||||
|
|
|
@ -29,6 +29,7 @@ interface VisualizeTopNavProps {
|
|||
setHasUnsavedChanges: (value: boolean) => void;
|
||||
hasUnappliedChanges: boolean;
|
||||
originatingApp?: string;
|
||||
originatingPath?: string;
|
||||
visInstance: VisualizeEditorVisInstance;
|
||||
setOriginatingApp?: (originatingApp: string | undefined) => void;
|
||||
stateContainer: VisualizeAppStateContainer;
|
||||
|
@ -46,6 +47,7 @@ const TopNav = ({
|
|||
hasUnappliedChanges,
|
||||
originatingApp,
|
||||
setOriginatingApp,
|
||||
originatingPath,
|
||||
visInstance,
|
||||
stateContainer,
|
||||
visualizationIdFromUrl,
|
||||
|
@ -88,6 +90,7 @@ const TopNav = ({
|
|||
openInspector,
|
||||
originatingApp,
|
||||
setOriginatingApp,
|
||||
originatingPath,
|
||||
visInstance,
|
||||
stateContainer,
|
||||
visualizationIdFromUrl,
|
||||
|
@ -104,6 +107,7 @@ const TopNav = ({
|
|||
hasUnappliedChanges,
|
||||
openInspector,
|
||||
originatingApp,
|
||||
originatingPath,
|
||||
visInstance,
|
||||
setOriginatingApp,
|
||||
stateContainer,
|
||||
|
|
|
@ -54,6 +54,7 @@ export interface TopNavConfigParams {
|
|||
setHasUnsavedChanges: (value: boolean) => void;
|
||||
openInspector: () => void;
|
||||
originatingApp?: string;
|
||||
originatingPath?: string;
|
||||
setOriginatingApp?: (originatingApp: string | undefined) => void;
|
||||
hasUnappliedChanges: boolean;
|
||||
visInstance: VisualizeEditorVisInstance;
|
||||
|
@ -79,6 +80,7 @@ export const getTopNavConfig = (
|
|||
setHasUnsavedChanges,
|
||||
openInspector,
|
||||
originatingApp,
|
||||
originatingPath,
|
||||
setOriginatingApp,
|
||||
hasUnappliedChanges,
|
||||
visInstance,
|
||||
|
@ -168,6 +170,8 @@ export const getTopNavConfig = (
|
|||
if (saveOptions.dashboardId) {
|
||||
path =
|
||||
saveOptions.dashboardId === 'new' ? '#/create' : `#/view/${saveOptions.dashboardId}`;
|
||||
} else if (originatingPath) {
|
||||
path = originatingPath;
|
||||
}
|
||||
|
||||
if (stateTransfer) {
|
||||
|
@ -232,7 +236,8 @@ export const getTopNavConfig = (
|
|||
type: VISUALIZE_EMBEDDABLE_TYPE,
|
||||
searchSessionId: data.search.session.getSessionId(),
|
||||
};
|
||||
stateTransfer.navigateToWithEmbeddablePackage(originatingApp, { state });
|
||||
|
||||
stateTransfer.navigateToWithEmbeddablePackage(originatingApp, { state, path: originatingPath });
|
||||
};
|
||||
|
||||
const navigateToOriginatingApp = () => {
|
||||
|
|
|
@ -19,7 +19,8 @@ export const useVisByValue = (
|
|||
eventEmitter: EventEmitter,
|
||||
isChromeVisible: boolean | undefined,
|
||||
valueInput?: VisualizeInput,
|
||||
originatingApp?: string
|
||||
originatingApp?: string,
|
||||
originatingPath?: string
|
||||
) => {
|
||||
const [state, setState] = useState<{
|
||||
byValueVisInstance?: ByValueVisInstance;
|
||||
|
@ -55,7 +56,9 @@ export const useVisByValue = (
|
|||
const originatingAppName = originatingApp
|
||||
? stateTransferService.getAppNameFromId(originatingApp)
|
||||
: undefined;
|
||||
const redirectToOrigin = originatingApp ? () => navigateToApp(originatingApp) : undefined;
|
||||
const redirectToOrigin = originatingApp
|
||||
? () => navigateToApp(originatingApp, { path: originatingPath })
|
||||
: undefined;
|
||||
chrome?.setBreadcrumbs(
|
||||
getEditBreadcrumbs({ byValue: true, originatingAppName, redirectToOrigin })
|
||||
);
|
||||
|
@ -76,6 +79,7 @@ export const useVisByValue = (
|
|||
state.visEditorController,
|
||||
valueInput,
|
||||
originatingApp,
|
||||
originatingPath,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -20,6 +20,7 @@ import { RendererStrings } from '../../../i18n';
|
|||
import { embeddableInputToExpression } from './embeddable_input_to_expression';
|
||||
import { RendererFactory, EmbeddableInput } from '../../../types';
|
||||
import { CANVAS_EMBEDDABLE_CLASSNAME } from '../../../common/lib';
|
||||
import type { EmbeddableContainerContext } from '../../../../../../src/plugins/embeddable/public/';
|
||||
|
||||
const { embeddable: strings } = RendererStrings;
|
||||
|
||||
|
@ -31,6 +32,10 @@ const embeddablesRegistry: {
|
|||
const renderEmbeddableFactory = (core: CoreStart, plugins: StartDeps) => {
|
||||
const I18nContext = core.i18n.Context;
|
||||
|
||||
const embeddableContainerContext: EmbeddableContainerContext = {
|
||||
getCurrentPath: () => window.location.hash,
|
||||
};
|
||||
|
||||
return (embeddableObject: IEmbeddable) => {
|
||||
return (
|
||||
<div
|
||||
|
@ -38,7 +43,10 @@ const renderEmbeddableFactory = (core: CoreStart, plugins: StartDeps) => {
|
|||
style={{ width: '100%', height: '100%', cursor: 'auto' }}
|
||||
>
|
||||
<I18nContext>
|
||||
<plugins.embeddable.EmbeddablePanel embeddable={embeddableObject} />
|
||||
<plugins.embeddable.EmbeddablePanel
|
||||
embeddable={embeddableObject}
|
||||
containerContext={embeddableContainerContext}
|
||||
/>
|
||||
</I18nContext>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -77,7 +77,7 @@ export async function renderApp(
|
|||
setAppChrome();
|
||||
|
||||
function renderMapApp(routeProps: RouteComponentProps<{ savedMapId?: string }>) {
|
||||
const { embeddableId, originatingApp, valueInput } =
|
||||
const { embeddableId, originatingApp, valueInput, originatingPath } =
|
||||
stateTransfer.getIncomingEditorState(APP_ID) || {};
|
||||
|
||||
let mapEmbeddableInput;
|
||||
|
@ -98,6 +98,7 @@ export async function renderApp(
|
|||
setHeaderActionMenu={setHeaderActionMenu}
|
||||
stateTransfer={stateTransfer}
|
||||
originatingApp={originatingApp}
|
||||
originatingPath={originatingPath}
|
||||
history={history}
|
||||
key={routeProps.match.params.savedMapId ? routeProps.match.params.savedMapId : 'new'}
|
||||
/>
|
||||
|
|
|
@ -20,6 +20,7 @@ interface Props {
|
|||
setHeaderActionMenu: AppMountParameters['setHeaderActionMenu'];
|
||||
stateTransfer: EmbeddableStateTransfer;
|
||||
originatingApp?: string;
|
||||
originatingPath?: string;
|
||||
history: AppMountParameters['history'];
|
||||
}
|
||||
|
||||
|
@ -43,6 +44,7 @@ export class MapPage extends Component<Props, State> {
|
|||
mapEmbeddableInput: props.mapEmbeddableInput,
|
||||
embeddableId: props.embeddableId,
|
||||
originatingApp: props.originatingApp,
|
||||
originatingPath: props.originatingPath,
|
||||
stateTransfer: props.stateTransfer,
|
||||
onSaveCallback: this.updateSaveCounter,
|
||||
}),
|
||||
|
|
|
@ -58,6 +58,7 @@ export class SavedMap {
|
|||
private _mapEmbeddableInput?: MapEmbeddableInput;
|
||||
private readonly _onSaveCallback?: () => void;
|
||||
private _originatingApp?: string;
|
||||
private _originatingPath?: string;
|
||||
private readonly _stateTransfer?: EmbeddableStateTransfer;
|
||||
private readonly _store: MapStore;
|
||||
private _tags: string[] = [];
|
||||
|
@ -69,6 +70,7 @@ export class SavedMap {
|
|||
onSaveCallback,
|
||||
originatingApp,
|
||||
stateTransfer,
|
||||
originatingPath,
|
||||
}: {
|
||||
defaultLayers?: LayerDescriptor[];
|
||||
mapEmbeddableInput?: MapEmbeddableInput;
|
||||
|
@ -76,12 +78,14 @@ export class SavedMap {
|
|||
onSaveCallback?: () => void;
|
||||
originatingApp?: string;
|
||||
stateTransfer?: EmbeddableStateTransfer;
|
||||
originatingPath?: string;
|
||||
}) {
|
||||
this._defaultLayers = defaultLayers;
|
||||
this._mapEmbeddableInput = mapEmbeddableInput;
|
||||
this._embeddableId = embeddableId;
|
||||
this._onSaveCallback = onSaveCallback;
|
||||
this._originatingApp = originatingApp;
|
||||
this._originatingPath = originatingPath;
|
||||
this._stateTransfer = stateTransfer;
|
||||
this._store = createMapStore();
|
||||
}
|
||||
|
@ -379,6 +383,7 @@ export class SavedMap {
|
|||
type: MAP_SAVED_OBJECT_TYPE,
|
||||
input: updatedMapEmbeddableInput,
|
||||
},
|
||||
path: this._originatingPath,
|
||||
});
|
||||
return;
|
||||
} else if (dashboardId) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue