mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Embeddables Rebuild] Make parent and ID static (#175137)
Makes the `parentApi` key of the embeddable framework static. Makes the `uuid` key of the embeddable framework static. Introduces a new `CanAccessViewMode` interface.
This commit is contained in:
parent
be9a89d94a
commit
3a544a3b86
46 changed files with 347 additions and 322 deletions
|
@ -6,13 +6,13 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { apiPublishesParentApi } from '@kbn/presentation-publishing';
|
||||
import { apiHasParentApi, PublishesViewMode } from '@kbn/presentation-publishing';
|
||||
|
||||
export interface PanelPackage {
|
||||
panelType: string;
|
||||
initialState: unknown;
|
||||
}
|
||||
export interface PresentationContainer {
|
||||
export interface PresentationContainer extends Partial<PublishesViewMode> {
|
||||
removePanel: (panelId: string) => void;
|
||||
canRemovePanels?: () => boolean;
|
||||
replacePanel: (idToRemove: string, newPanel: PanelPackage) => Promise<string>;
|
||||
|
@ -27,7 +27,7 @@ export const apiIsPresentationContainer = (
|
|||
export const getContainerParentFromAPI = (
|
||||
api: null | unknown
|
||||
): PresentationContainer | undefined => {
|
||||
const apiParent = apiPublishesParentApi(api) ? api.parentApi.value : null;
|
||||
const apiParent = apiHasParentApi(api) ? api.parentApi : null;
|
||||
if (!apiParent) return undefined;
|
||||
return apiIsPresentationContainer(apiParent) ? apiParent : undefined;
|
||||
};
|
||||
|
|
|
@ -10,6 +10,13 @@ export interface EmbeddableApiContext {
|
|||
embeddable: unknown;
|
||||
}
|
||||
|
||||
export {
|
||||
apiCanAccessViewMode,
|
||||
getInheritedViewMode,
|
||||
getViewModeSubject,
|
||||
useInheritedViewMode,
|
||||
type CanAccessViewMode,
|
||||
} from './interfaces/can_access_view_mode';
|
||||
export {
|
||||
apiFiresPhaseEvents,
|
||||
type FiresPhaseEvents,
|
||||
|
@ -17,12 +24,18 @@ export {
|
|||
type PhaseEventType,
|
||||
} from './interfaces/fires_phase_events';
|
||||
export { hasEditCapabilities, type HasEditCapabilities } from './interfaces/has_edit_capabilities';
|
||||
export { apiHasParentApi, type HasParentApi } from './interfaces/has_parent_api';
|
||||
export {
|
||||
apiHasType,
|
||||
apiIsOfType,
|
||||
type HasType,
|
||||
type HasTypeDisplayName,
|
||||
} from './interfaces/has_type';
|
||||
export {
|
||||
apiPublishesBlockingError,
|
||||
useBlockingError,
|
||||
type PublishesBlockingError,
|
||||
} from './interfaces/publishes_blocking_error';
|
||||
export {
|
||||
apiPublishesDataLoading,
|
||||
useDataLoading,
|
||||
|
@ -38,16 +51,6 @@ export {
|
|||
useDisabledActionIds,
|
||||
type PublishesDisabledActionIds,
|
||||
} from './interfaces/publishes_disabled_action_ids';
|
||||
export {
|
||||
apiPublishesBlockingError,
|
||||
useBlockingError,
|
||||
type PublishesBlockingError,
|
||||
} from './interfaces/publishes_blocking_error';
|
||||
export {
|
||||
apiPublishesUniqueId,
|
||||
useUniqueId,
|
||||
type PublishesUniqueId,
|
||||
} from './interfaces/publishes_uuid';
|
||||
export {
|
||||
apiPublishesLocalUnifiedSearch,
|
||||
apiPublishesPartialLocalUnifiedSearch,
|
||||
|
@ -75,16 +78,12 @@ export {
|
|||
type PublishesPanelTitle,
|
||||
type PublishesWritablePanelTitle,
|
||||
} from './interfaces/publishes_panel_title';
|
||||
export {
|
||||
apiPublishesParentApi,
|
||||
useParentApi,
|
||||
type PublishesParentApi,
|
||||
} from './interfaces/publishes_parent_api';
|
||||
export {
|
||||
apiPublishesSavedObjectId,
|
||||
useSavedObjectId,
|
||||
type PublishesSavedObjectId,
|
||||
} from './interfaces/publishes_saved_object_id';
|
||||
export { apiHasUniqueId, type HasUniqueId } from './interfaces/has_uuid';
|
||||
export {
|
||||
apiPublishesViewMode,
|
||||
apiPublishesWritableViewMode,
|
||||
|
@ -99,4 +98,3 @@ export {
|
|||
usePublishingSubject,
|
||||
type PublishingSubject,
|
||||
} from './publishing_subject';
|
||||
export { useApiPublisher } from './publishing_utils';
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { useStateFromPublishingSubject } from '../publishing_subject';
|
||||
import { apiHasParentApi, HasParentApi } from './has_parent_api';
|
||||
import { apiPublishesViewMode, PublishesViewMode, ViewMode } from './publishes_view_mode';
|
||||
|
||||
/**
|
||||
* This API can access a view mode, either its own or from its parent API.
|
||||
*/
|
||||
export type CanAccessViewMode =
|
||||
| Partial<PublishesViewMode>
|
||||
| Partial<HasParentApi<Partial<PublishesViewMode>>>;
|
||||
|
||||
/**
|
||||
* A type guard which can be used to determine if a given API has access to a view mode, its own or from its parent.
|
||||
*/
|
||||
export const apiCanAccessViewMode = (api: unknown): api is CanAccessViewMode => {
|
||||
return apiPublishesViewMode(api) || (apiHasParentApi(api) && apiPublishesViewMode(api.parentApi));
|
||||
};
|
||||
|
||||
/**
|
||||
* A function which will get the view mode from the API or the parent API. if this api has a view mode AND its
|
||||
* parent has a view mode, we consider the APIs version the source of truth.
|
||||
*/
|
||||
export const getInheritedViewMode = (api?: CanAccessViewMode) => {
|
||||
if (apiPublishesViewMode(api)) return api.viewMode.getValue();
|
||||
if (apiHasParentApi(api) && apiPublishesViewMode(api.parentApi)) {
|
||||
return api.parentApi.viewMode.getValue();
|
||||
}
|
||||
};
|
||||
|
||||
export const getViewModeSubject = (api?: CanAccessViewMode) => {
|
||||
if (apiPublishesViewMode(api)) return api.viewMode;
|
||||
if (apiHasParentApi(api) && apiPublishesViewMode(api.parentApi)) {
|
||||
return api.parentApi.viewMode;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A hook that gets a view mode from this API or its parent as a reactive variable which will cause re-renders on change.
|
||||
* if this api has a view mode AND its parent has a view mode, we consider the APIs version the source of truth.
|
||||
*/
|
||||
export const useInheritedViewMode = <ApiType extends CanAccessViewMode = CanAccessViewMode>(
|
||||
api: ApiType | undefined
|
||||
) => {
|
||||
const subject = getViewModeSubject(api);
|
||||
useStateFromPublishingSubject<ViewMode, typeof subject>(subject);
|
||||
};
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export interface HasParentApi<ParentApiType extends unknown = unknown> {
|
||||
parentApi: ParentApiType;
|
||||
}
|
||||
|
||||
/**
|
||||
* A type guard which checks whether or not a given API has a parent API.
|
||||
*/
|
||||
export const apiHasParentApi = (unknownApi: null | unknown): unknownApi is HasParentApi => {
|
||||
return Boolean(unknownApi && (unknownApi as HasParentApi)?.parentApi !== undefined);
|
||||
};
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export interface HasUniqueId {
|
||||
uuid: string;
|
||||
}
|
||||
|
||||
export const apiHasUniqueId = (unknownApi: null | unknown): unknownApi is HasUniqueId => {
|
||||
return Boolean(unknownApi && (unknownApi as HasUniqueId)?.uuid !== undefined);
|
||||
};
|
|
@ -1,35 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { PublishingSubject, useStateFromPublishingSubject } from '../publishing_subject';
|
||||
|
||||
export interface PublishesParentApi<ParentApiType extends unknown = unknown> {
|
||||
parentApi: PublishingSubject<ParentApiType>;
|
||||
}
|
||||
|
||||
type UnwrapParent<ApiType extends unknown> = ApiType extends PublishesParentApi<infer ParentType>
|
||||
? ParentType
|
||||
: unknown;
|
||||
|
||||
/**
|
||||
* A type guard which checks whether or not a given API publishes its parent API.
|
||||
*/
|
||||
export const apiPublishesParentApi = (
|
||||
unknownApi: null | unknown
|
||||
): unknownApi is PublishesParentApi => {
|
||||
return Boolean(unknownApi && (unknownApi as PublishesParentApi)?.parentApi !== undefined);
|
||||
};
|
||||
|
||||
export const useParentApi = <
|
||||
ApiType extends Partial<PublishesParentApi> = Partial<PublishesParentApi>
|
||||
>(
|
||||
api: ApiType
|
||||
): UnwrapParent<ApiType> =>
|
||||
useStateFromPublishingSubject<unknown, ApiType['parentApi']>(
|
||||
apiPublishesParentApi(api) ? api.parentApi : undefined
|
||||
) as UnwrapParent<ApiType>;
|
|
@ -1,31 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { PublishingSubject, useStateFromPublishingSubject } from '../publishing_subject';
|
||||
|
||||
export interface PublishesUniqueId {
|
||||
uuid: PublishingSubject<string>;
|
||||
}
|
||||
|
||||
export const apiPublishesUniqueId = (
|
||||
unknownApi: null | unknown
|
||||
): unknownApi is PublishesUniqueId => {
|
||||
return Boolean(unknownApi && (unknownApi as PublishesUniqueId)?.uuid !== undefined);
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets this API's UUID as a reactive variable which will cause re-renders on change.
|
||||
*/
|
||||
export const useUniqueId = <
|
||||
ApiType extends Partial<PublishesUniqueId> = Partial<PublishesUniqueId>
|
||||
>(
|
||||
api: ApiType
|
||||
) =>
|
||||
useStateFromPublishingSubject<string, ApiType['uuid']>(
|
||||
apiPublishesUniqueId(api) ? api.uuid : undefined
|
||||
);
|
|
@ -18,6 +18,10 @@ export interface PublishesViewMode {
|
|||
viewMode: PublishingSubject<ViewMode>;
|
||||
}
|
||||
|
||||
/**
|
||||
* This API publishes a writable universal view mode which can change compatibility of actions and the
|
||||
* visibility of components.
|
||||
*/
|
||||
export type PublishesWritableViewMode = PublishesViewMode & {
|
||||
setViewMode: (viewMode: ViewMode) => void;
|
||||
};
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { ViewMode } from '@kbn/presentation-publishing';
|
||||
import { PublishesViewMode, ViewMode } from '@kbn/presentation-publishing';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { pluginServices } from '../services/plugin_services';
|
||||
import { AddToLibraryAction, AddPanelToLibraryActionApi } from './add_to_library_action';
|
||||
|
@ -40,7 +40,7 @@ describe('Add to library action', () => {
|
|||
});
|
||||
|
||||
it('is incompatible when view mode is view', async () => {
|
||||
context.embeddable.viewMode = new BehaviorSubject<ViewMode>('view');
|
||||
(context.embeddable as PublishesViewMode).viewMode = new BehaviorSubject<ViewMode>('view');
|
||||
expect(await action.isCompatible(context)).toBe(false);
|
||||
});
|
||||
|
||||
|
|
|
@ -8,10 +8,11 @@
|
|||
|
||||
import { apiCanLinkToLibrary, CanLinkToLibrary } from '@kbn/presentation-library';
|
||||
import {
|
||||
apiPublishesViewMode,
|
||||
apiCanAccessViewMode,
|
||||
EmbeddableApiContext,
|
||||
PublishesPanelTitle,
|
||||
PublishesViewMode,
|
||||
CanAccessViewMode,
|
||||
getInheritedViewMode,
|
||||
} from '@kbn/presentation-publishing';
|
||||
import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
|
||||
import { pluginServices } from '../services/plugin_services';
|
||||
|
@ -19,12 +20,12 @@ import { dashboardAddToLibraryActionStrings } from './_dashboard_actions_strings
|
|||
|
||||
export const ACTION_ADD_TO_LIBRARY = 'saveToLibrary';
|
||||
|
||||
export type AddPanelToLibraryActionApi = PublishesViewMode &
|
||||
export type AddPanelToLibraryActionApi = CanAccessViewMode &
|
||||
CanLinkToLibrary &
|
||||
Partial<PublishesPanelTitle>;
|
||||
|
||||
const isApiCompatible = (api: unknown | null): api is AddPanelToLibraryActionApi =>
|
||||
Boolean(apiPublishesViewMode(api) && apiCanLinkToLibrary(api));
|
||||
Boolean(apiCanAccessViewMode(api) && apiCanLinkToLibrary(api));
|
||||
|
||||
export class AddToLibraryAction implements Action<EmbeddableApiContext> {
|
||||
public readonly type = ACTION_ADD_TO_LIBRARY;
|
||||
|
@ -51,7 +52,7 @@ export class AddToLibraryAction implements Action<EmbeddableApiContext> {
|
|||
|
||||
public async isCompatible({ embeddable }: EmbeddableApiContext) {
|
||||
if (!isApiCompatible(embeddable)) return false;
|
||||
return embeddable.viewMode.value === 'edit' && (await embeddable.canLinkToLibrary());
|
||||
return getInheritedViewMode(embeddable) === 'edit' && (await embeddable.canLinkToLibrary());
|
||||
}
|
||||
|
||||
public async execute({ embeddable }: EmbeddableApiContext) {
|
||||
|
|
|
@ -6,8 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { CanDuplicatePanels } from '@kbn/presentation-containers';
|
||||
import { ViewMode } from '@kbn/presentation-publishing';
|
||||
import { PublishesViewMode, ViewMode } from '@kbn/presentation-publishing';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { ClonePanelAction, ClonePanelActionApi } from './clone_panel_action';
|
||||
|
||||
|
@ -19,11 +18,11 @@ describe('Clone panel action', () => {
|
|||
action = new ClonePanelAction();
|
||||
context = {
|
||||
embeddable: {
|
||||
uuid: new BehaviorSubject<string>('superId'),
|
||||
uuid: 'superId',
|
||||
viewMode: new BehaviorSubject<ViewMode>('edit'),
|
||||
parentApi: new BehaviorSubject<CanDuplicatePanels>({
|
||||
parentApi: {
|
||||
duplicatePanel: jest.fn(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
@ -40,12 +39,12 @@ describe('Clone panel action', () => {
|
|||
});
|
||||
|
||||
it('is incompatible when view mode is view', async () => {
|
||||
context.embeddable.viewMode = new BehaviorSubject<ViewMode>('view');
|
||||
(context.embeddable as PublishesViewMode).viewMode = new BehaviorSubject<ViewMode>('view');
|
||||
expect(await action.isCompatible(context)).toBe(false);
|
||||
});
|
||||
|
||||
it('calls the parent duplicatePanel method on execute', async () => {
|
||||
action.execute(context);
|
||||
expect(context.embeddable.parentApi.value.duplicatePanel).toHaveBeenCalled();
|
||||
expect(context.embeddable.parentApi.duplicatePanel).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,34 +6,34 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
|
||||
|
||||
import { apiCanDuplicatePanels, CanDuplicatePanels } from '@kbn/presentation-containers';
|
||||
import {
|
||||
apiPublishesUniqueId,
|
||||
apiPublishesParentApi,
|
||||
apiPublishesViewMode,
|
||||
apiCanAccessViewMode,
|
||||
apiHasParentApi,
|
||||
apiHasUniqueId,
|
||||
CanAccessViewMode,
|
||||
EmbeddableApiContext,
|
||||
getInheritedViewMode,
|
||||
HasParentApi,
|
||||
PublishesBlockingError,
|
||||
PublishesUniqueId,
|
||||
PublishesParentApi,
|
||||
PublishesViewMode,
|
||||
HasUniqueId,
|
||||
} from '@kbn/presentation-publishing';
|
||||
import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
|
||||
import { dashboardClonePanelActionStrings } from './_dashboard_actions_strings';
|
||||
|
||||
export const ACTION_CLONE_PANEL = 'clonePanel';
|
||||
|
||||
export type ClonePanelActionApi = PublishesViewMode &
|
||||
PublishesUniqueId &
|
||||
PublishesParentApi<CanDuplicatePanels> &
|
||||
export type ClonePanelActionApi = CanAccessViewMode &
|
||||
HasUniqueId &
|
||||
HasParentApi<CanDuplicatePanels> &
|
||||
Partial<PublishesBlockingError>;
|
||||
|
||||
const isApiCompatible = (api: unknown | null): api is ClonePanelActionApi =>
|
||||
Boolean(
|
||||
apiPublishesUniqueId(api) &&
|
||||
apiPublishesViewMode(api) &&
|
||||
apiPublishesParentApi(api) &&
|
||||
apiCanDuplicatePanels(api.parentApi.value)
|
||||
apiHasUniqueId(api) &&
|
||||
apiCanAccessViewMode(api) &&
|
||||
apiHasParentApi(api) &&
|
||||
apiCanDuplicatePanels(api.parentApi)
|
||||
);
|
||||
|
||||
export class ClonePanelAction implements Action<EmbeddableApiContext> {
|
||||
|
@ -55,11 +55,11 @@ export class ClonePanelAction implements Action<EmbeddableApiContext> {
|
|||
|
||||
public async isCompatible({ embeddable }: EmbeddableApiContext) {
|
||||
if (!isApiCompatible(embeddable)) return false;
|
||||
return Boolean(!embeddable.blockingError?.value && embeddable.viewMode.value === 'edit');
|
||||
return Boolean(!embeddable.blockingError?.value && getInheritedViewMode(embeddable) === 'edit');
|
||||
}
|
||||
|
||||
public async execute({ embeddable }: EmbeddableApiContext) {
|
||||
if (!isApiCompatible(embeddable)) throw new IncompatibleActionError();
|
||||
embeddable.parentApi.value.duplicatePanel(embeddable.uuid.value);
|
||||
embeddable.parentApi.duplicatePanel(embeddable.uuid);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,13 +11,13 @@ import React from 'react';
|
|||
import { CoreStart } from '@kbn/core-lifecycle-browser';
|
||||
import {
|
||||
apiIsOfType,
|
||||
apiPublishesUniqueId,
|
||||
apiPublishesParentApi,
|
||||
apiHasUniqueId,
|
||||
apiHasParentApi,
|
||||
apiPublishesSavedObjectId,
|
||||
HasType,
|
||||
EmbeddableApiContext,
|
||||
PublishesUniqueId,
|
||||
PublishesParentApi,
|
||||
HasUniqueId,
|
||||
HasParentApi,
|
||||
PublishesSavedObjectId,
|
||||
} from '@kbn/presentation-publishing';
|
||||
import { toMountPoint } from '@kbn/react-kibana-mount';
|
||||
|
@ -37,18 +37,18 @@ export interface DashboardCopyToCapabilities {
|
|||
}
|
||||
|
||||
export type CopyToDashboardAPI = HasType &
|
||||
PublishesUniqueId &
|
||||
PublishesParentApi<
|
||||
HasUniqueId &
|
||||
HasParentApi<
|
||||
{ type: typeof DASHBOARD_CONTAINER_TYPE } & PublishesSavedObjectId &
|
||||
DashboardPluginInternalFunctions
|
||||
>;
|
||||
|
||||
const apiIsCompatible = (api: unknown): api is CopyToDashboardAPI => {
|
||||
return (
|
||||
apiPublishesUniqueId(api) &&
|
||||
apiPublishesParentApi(api) &&
|
||||
apiIsOfType(api.parentApi.value, DASHBOARD_CONTAINER_TYPE) &&
|
||||
apiPublishesSavedObjectId(api.parentApi.value)
|
||||
apiHasUniqueId(api) &&
|
||||
apiHasParentApi(api) &&
|
||||
apiIsOfType(api.parentApi, DASHBOARD_CONTAINER_TYPE) &&
|
||||
apiPublishesSavedObjectId(api.parentApi)
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -46,11 +46,11 @@ export function CopyToDashboardModal({ api, closeModal }: CopyToDashboardModalPr
|
|||
null
|
||||
);
|
||||
|
||||
const dashboardId = api.parentApi.value.savedObjectId.value;
|
||||
const dashboardId = api.parentApi.savedObjectId.value;
|
||||
|
||||
const onSubmit = useCallback(() => {
|
||||
const dashboard = api.parentApi.value;
|
||||
const panelToCopy = dashboard.getDashboardPanelFromId(api.uuid.value);
|
||||
const dashboard = api.parentApi;
|
||||
const panelToCopy = dashboard.getDashboardPanelFromId(api.uuid);
|
||||
|
||||
if (!panelToCopy) {
|
||||
throw new PanelNotFoundError();
|
||||
|
|
|
@ -6,8 +6,6 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { CanExpandPanels } from '@kbn/presentation-containers';
|
||||
import { ViewMode } from '@kbn/presentation-publishing';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { ExpandPanelActionApi, ExpandPanelAction } from './expand_panel_action';
|
||||
|
||||
|
@ -19,12 +17,11 @@ describe('Expand panel action', () => {
|
|||
action = new ExpandPanelAction();
|
||||
context = {
|
||||
embeddable: {
|
||||
uuid: new BehaviorSubject<string>('superId'),
|
||||
viewMode: new BehaviorSubject<ViewMode>('edit'),
|
||||
parentApi: new BehaviorSubject<CanExpandPanels>({
|
||||
uuid: 'superId',
|
||||
parentApi: {
|
||||
expandPanel: jest.fn(),
|
||||
expandedPanelId: new BehaviorSubject<string | undefined>(undefined),
|
||||
}),
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
@ -42,7 +39,7 @@ describe('Expand panel action', () => {
|
|||
|
||||
it('returns the correct icon based on expanded panel id', async () => {
|
||||
expect(await action.getIconType(context)).toBe('expand');
|
||||
context.embeddable.parentApi.value.expandedPanelId = new BehaviorSubject<string | undefined>(
|
||||
context.embeddable.parentApi.expandedPanelId = new BehaviorSubject<string | undefined>(
|
||||
'superPanelId'
|
||||
);
|
||||
expect(await action.getIconType(context)).toBe('minimize');
|
||||
|
@ -50,7 +47,7 @@ describe('Expand panel action', () => {
|
|||
|
||||
it('returns the correct display name based on expanded panel id', async () => {
|
||||
expect(await action.getDisplayName(context)).toBe('Maximize panel');
|
||||
context.embeddable.parentApi.value.expandedPanelId = new BehaviorSubject<string | undefined>(
|
||||
context.embeddable.parentApi.expandedPanelId = new BehaviorSubject<string | undefined>(
|
||||
'superPanelId'
|
||||
);
|
||||
expect(await action.getDisplayName(context)).toBe('Minimize');
|
||||
|
@ -58,6 +55,6 @@ describe('Expand panel action', () => {
|
|||
|
||||
it('calls the parent expandPanel method on execute', async () => {
|
||||
action.execute(context);
|
||||
expect(context.embeddable.parentApi.value.expandPanel).toHaveBeenCalled();
|
||||
expect(context.embeddable.parentApi.expandPanel).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,13 +8,11 @@
|
|||
|
||||
import { apiCanExpandPanels, CanExpandPanels } from '@kbn/presentation-containers';
|
||||
import {
|
||||
apiPublishesUniqueId,
|
||||
apiPublishesParentApi,
|
||||
apiPublishesViewMode,
|
||||
apiHasParentApi,
|
||||
apiHasUniqueId,
|
||||
EmbeddableApiContext,
|
||||
PublishesUniqueId,
|
||||
PublishesParentApi,
|
||||
PublishesViewMode,
|
||||
HasParentApi,
|
||||
HasUniqueId,
|
||||
} from '@kbn/presentation-publishing';
|
||||
import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
|
||||
|
||||
|
@ -22,17 +20,10 @@ import { dashboardExpandPanelActionStrings } from './_dashboard_actions_strings'
|
|||
|
||||
export const ACTION_EXPAND_PANEL = 'togglePanel';
|
||||
|
||||
export type ExpandPanelActionApi = PublishesViewMode &
|
||||
PublishesUniqueId &
|
||||
PublishesParentApi<CanExpandPanels>;
|
||||
export type ExpandPanelActionApi = HasUniqueId & HasParentApi<CanExpandPanels>;
|
||||
|
||||
const isApiCompatible = (api: unknown | null): api is ExpandPanelActionApi =>
|
||||
Boolean(
|
||||
apiPublishesUniqueId(api) &&
|
||||
apiPublishesViewMode(api) &&
|
||||
apiPublishesParentApi(api) &&
|
||||
apiCanExpandPanels(api.parentApi.value)
|
||||
);
|
||||
Boolean(apiHasUniqueId(api) && apiHasParentApi(api) && apiCanExpandPanels(api.parentApi));
|
||||
|
||||
export class ExpandPanelAction implements Action<EmbeddableApiContext> {
|
||||
public readonly type = ACTION_EXPAND_PANEL;
|
||||
|
@ -43,14 +34,14 @@ export class ExpandPanelAction implements Action<EmbeddableApiContext> {
|
|||
|
||||
public getDisplayName({ embeddable }: EmbeddableApiContext) {
|
||||
if (!isApiCompatible(embeddable)) throw new IncompatibleActionError();
|
||||
return embeddable.parentApi.value.expandedPanelId.value
|
||||
return embeddable.parentApi.expandedPanelId.value
|
||||
? dashboardExpandPanelActionStrings.getMinimizeTitle()
|
||||
: dashboardExpandPanelActionStrings.getMaximizeTitle();
|
||||
}
|
||||
|
||||
public getIconType({ embeddable }: EmbeddableApiContext) {
|
||||
if (!isApiCompatible(embeddable)) throw new IncompatibleActionError();
|
||||
return embeddable.parentApi.value.expandedPanelId.value ? 'minimize' : 'expand';
|
||||
return embeddable.parentApi.expandedPanelId.value ? 'minimize' : 'expand';
|
||||
}
|
||||
|
||||
public async isCompatible({ embeddable }: EmbeddableApiContext) {
|
||||
|
@ -59,8 +50,8 @@ export class ExpandPanelAction implements Action<EmbeddableApiContext> {
|
|||
|
||||
public async execute({ embeddable }: EmbeddableApiContext) {
|
||||
if (!isApiCompatible(embeddable)) throw new IncompatibleActionError();
|
||||
embeddable.parentApi.value.expandPanel(
|
||||
embeddable.parentApi.value.expandedPanelId.value ? undefined : embeddable.uuid.value
|
||||
embeddable.parentApi.expandPanel(
|
||||
embeddable.parentApi.expandedPanelId.value ? undefined : embeddable.uuid
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ import { Filter, FilterStateStore, type AggregateQuery, type Query } from '@kbn/
|
|||
|
||||
import { ViewMode } from '@kbn/presentation-publishing';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { DashboardPluginInternalFunctions } from '../dashboard_container/external_api/dashboard_api';
|
||||
import {
|
||||
FiltersNotificationAction,
|
||||
FiltersNotificationActionApi,
|
||||
|
@ -56,12 +55,12 @@ describe('filters notification action', () => {
|
|||
action = new FiltersNotificationAction();
|
||||
context = {
|
||||
embeddable: {
|
||||
uuid: new BehaviorSubject<string>('testId'),
|
||||
uuid: 'testId',
|
||||
viewMode: viewModeSubject,
|
||||
parentApi: new BehaviorSubject<DashboardPluginInternalFunctions>({
|
||||
parentApi: {
|
||||
getAllDataViews: jest.fn(),
|
||||
getDashboardPanelFromId: jest.fn(),
|
||||
}),
|
||||
},
|
||||
localFilters: filtersSubject,
|
||||
localQuery: querySubject,
|
||||
},
|
||||
|
|
|
@ -13,14 +13,16 @@ import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public';
|
|||
import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
|
||||
|
||||
import {
|
||||
apiCanAccessViewMode,
|
||||
apiPublishesPartialLocalUnifiedSearch,
|
||||
apiPublishesUniqueId,
|
||||
apiPublishesViewMode,
|
||||
apiHasUniqueId,
|
||||
CanAccessViewMode,
|
||||
EmbeddableApiContext,
|
||||
getInheritedViewMode,
|
||||
getViewModeSubject,
|
||||
HasParentApi,
|
||||
PublishesLocalUnifiedSearch,
|
||||
PublishesParentApi,
|
||||
PublishesUniqueId,
|
||||
PublishesViewMode,
|
||||
HasUniqueId,
|
||||
} from '@kbn/presentation-publishing';
|
||||
import { merge } from 'rxjs';
|
||||
import { DashboardPluginInternalFunctions } from '../dashboard_container/external_api/dashboard_api';
|
||||
|
@ -30,20 +32,18 @@ import { dashboardFilterNotificationActionStrings } from './_dashboard_actions_s
|
|||
|
||||
export const BADGE_FILTERS_NOTIFICATION = 'ACTION_FILTERS_NOTIFICATION';
|
||||
|
||||
export type FiltersNotificationActionApi = PublishesUniqueId &
|
||||
PublishesViewMode &
|
||||
export type FiltersNotificationActionApi = HasUniqueId &
|
||||
CanAccessViewMode &
|
||||
Partial<PublishesLocalUnifiedSearch> &
|
||||
PublishesParentApi<DashboardPluginInternalFunctions>;
|
||||
HasParentApi<DashboardPluginInternalFunctions>;
|
||||
|
||||
const isApiCompatible = (api: unknown | null): api is FiltersNotificationActionApi =>
|
||||
Boolean(
|
||||
apiPublishesUniqueId(api) &&
|
||||
apiPublishesViewMode(api) &&
|
||||
apiPublishesPartialLocalUnifiedSearch(api)
|
||||
apiHasUniqueId(api) && apiCanAccessViewMode(api) && apiPublishesPartialLocalUnifiedSearch(api)
|
||||
);
|
||||
|
||||
const compatibilityCheck = (api: EmbeddableApiContext['embeddable']) => {
|
||||
if (!isApiCompatible(api) || api.viewMode.value !== 'edit') return false;
|
||||
if (!isApiCompatible(api) || getInheritedViewMode(api) !== 'edit') return false;
|
||||
const query = api.localQuery?.value;
|
||||
return (
|
||||
(api.localFilters?.value ?? []).length > 0 ||
|
||||
|
@ -102,12 +102,10 @@ export class FiltersNotificationAction implements Action<EmbeddableApiContext> {
|
|||
) {
|
||||
if (!isApiCompatible(embeddable)) return;
|
||||
return merge(
|
||||
...[embeddable.localQuery, embeddable.localFilters, embeddable.viewMode].filter((value) =>
|
||||
Boolean(value)
|
||||
...[embeddable.localQuery, embeddable.localFilters, getViewModeSubject(embeddable)].filter(
|
||||
(value) => Boolean(value)
|
||||
)
|
||||
).subscribe(() => {
|
||||
onChange(compatibilityCheck(embeddable), this);
|
||||
});
|
||||
).subscribe(() => onChange(compatibilityCheck(embeddable), this));
|
||||
}
|
||||
|
||||
public execute = async () => {};
|
||||
|
|
|
@ -14,7 +14,6 @@ import { render, screen } from '@testing-library/react';
|
|||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { DashboardPluginInternalFunctions } from '../dashboard_container/external_api/dashboard_api';
|
||||
import { FiltersNotificationActionApi } from './filters_notification_action';
|
||||
import { FiltersNotificationPopover } from './filters_notification_popover';
|
||||
|
||||
|
@ -58,12 +57,12 @@ describe('filters notification popover', () => {
|
|||
updateQuery = (query) => querySubject.next(query);
|
||||
|
||||
api = {
|
||||
uuid: new BehaviorSubject<string>('testId'),
|
||||
uuid: 'testId',
|
||||
viewMode: new BehaviorSubject<ViewMode>('edit'),
|
||||
parentApi: new BehaviorSubject<DashboardPluginInternalFunctions>({
|
||||
parentApi: {
|
||||
getAllDataViews: jest.fn(),
|
||||
getDashboardPanelFromId: jest.fn(),
|
||||
}),
|
||||
},
|
||||
localFilters: filtersSubject,
|
||||
localQuery: querySubject,
|
||||
};
|
||||
|
@ -75,15 +74,13 @@ describe('filters notification popover', () => {
|
|||
<FiltersNotificationPopover api={api} />
|
||||
</I18nProvider>
|
||||
);
|
||||
await userEvent.click(
|
||||
await screen.findByTestId(`embeddablePanelNotification-${api.uuid.value}`)
|
||||
);
|
||||
await userEvent.click(await screen.findByTestId(`embeddablePanelNotification-${api.uuid}`));
|
||||
await waitForEuiPopoverOpen();
|
||||
};
|
||||
|
||||
it('calls get all dataviews from the parent', async () => {
|
||||
render(<FiltersNotificationPopover api={api} />);
|
||||
expect(api.parentApi.value?.getAllDataViews).toHaveBeenCalled();
|
||||
expect(api.parentApi?.getAllDataViews).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('renders the filter section when given filters', async () => {
|
||||
|
|
|
@ -56,7 +56,7 @@ export function FiltersNotificationPopover({ api }: { api: FiltersNotificationAc
|
|||
}
|
||||
}, [api, setDisableEditButton]);
|
||||
|
||||
const dataViews = useMemo(() => api.parentApi.value?.getAllDataViews(), [api]);
|
||||
const dataViews = useMemo(() => api.parentApi?.getAllDataViews(), [api]);
|
||||
|
||||
return (
|
||||
<EuiPopover
|
||||
|
@ -65,7 +65,7 @@ export function FiltersNotificationPopover({ api }: { api: FiltersNotificationAc
|
|||
color="text"
|
||||
iconType={'filter'}
|
||||
onClick={() => setIsPopoverOpen(!isPopoverOpen)}
|
||||
data-test-subj={`embeddablePanelNotification-${api.uuid.value}`}
|
||||
data-test-subj={`embeddablePanelNotification-${api.uuid}`}
|
||||
aria-label={displayName}
|
||||
/>
|
||||
}
|
||||
|
|
|
@ -8,7 +8,11 @@
|
|||
|
||||
import React from 'react';
|
||||
|
||||
import { EmbeddableApiContext } from '@kbn/presentation-publishing';
|
||||
import {
|
||||
EmbeddableApiContext,
|
||||
getInheritedViewMode,
|
||||
getViewModeSubject,
|
||||
} from '@kbn/presentation-publishing';
|
||||
import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
|
||||
import { LibraryNotificationPopover } from './library_notification_popover';
|
||||
import { unlinkActionIsCompatible, UnlinkFromLibraryAction } from './unlink_from_library_action';
|
||||
|
@ -43,7 +47,7 @@ export class LibraryNotificationAction implements Action<EmbeddableApiContext> {
|
|||
* TODO: Upgrade this action by subscribing to changes in the existance of a saved object id. Currently,
|
||||
* this is unnecessary because a link or unlink operation will cause the panel to unmount and remount.
|
||||
*/
|
||||
return embeddable.viewMode.subscribe((viewMode) => {
|
||||
return getViewModeSubject(embeddable)?.subscribe((viewMode) => {
|
||||
embeddable.canUnlinkFromLibrary().then((canUnlink) => {
|
||||
onChange(viewMode === 'edit' && canUnlink, this);
|
||||
});
|
||||
|
@ -62,7 +66,7 @@ export class LibraryNotificationAction implements Action<EmbeddableApiContext> {
|
|||
|
||||
public isCompatible = async ({ embeddable }: EmbeddableApiContext) => {
|
||||
if (!unlinkActionIsCompatible(embeddable)) return false;
|
||||
return embeddable.viewMode.value === 'edit' && embeddable.canUnlinkFromLibrary();
|
||||
return getInheritedViewMode(embeddable) === 'edit' && embeddable.canUnlinkFromLibrary();
|
||||
};
|
||||
|
||||
public execute = async () => {};
|
||||
|
|
|
@ -31,7 +31,7 @@ export const openReplacePanelFlyout = async ({
|
|||
} = pluginServices.getServices();
|
||||
|
||||
// send the overlay ref to the parent if it is capable of tracking overlays
|
||||
const overlayTracker = tracksOverlays(api.parentApi.value) ? api.parentApi.value : undefined;
|
||||
const overlayTracker = tracksOverlays(api.parentApi) ? api.parentApi : undefined;
|
||||
|
||||
const flyoutSession = openFlyout(
|
||||
toMountPoint(
|
||||
|
|
|
@ -6,8 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { PresentationContainer } from '@kbn/presentation-containers';
|
||||
import { ViewMode } from '@kbn/presentation-publishing';
|
||||
import { PublishesViewMode, ViewMode } from '@kbn/presentation-publishing';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { ReplacePanelSOFinder } from '.';
|
||||
import { ReplacePanelAction, ReplacePanelActionApi } from './replace_panel_action';
|
||||
|
@ -27,12 +26,12 @@ describe('replace panel action', () => {
|
|||
action = new ReplacePanelAction(savedObjectFinder);
|
||||
context = {
|
||||
embeddable: {
|
||||
uuid: new BehaviorSubject<string>('superId'),
|
||||
uuid: 'superId',
|
||||
viewMode: new BehaviorSubject<ViewMode>('edit'),
|
||||
parentApi: new BehaviorSubject<PresentationContainer>({
|
||||
parentApi: {
|
||||
removePanel: jest.fn(),
|
||||
replacePanel: jest.fn(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
@ -49,7 +48,7 @@ describe('replace panel action', () => {
|
|||
});
|
||||
|
||||
it('is incompatible when view mode is view', async () => {
|
||||
context.embeddable.viewMode = new BehaviorSubject<ViewMode>('view');
|
||||
(context.embeddable as PublishesViewMode).viewMode = new BehaviorSubject<ViewMode>('view');
|
||||
expect(await action.isCompatible(context)).toBe(false);
|
||||
});
|
||||
|
||||
|
|
|
@ -12,14 +12,15 @@ import {
|
|||
TracksOverlays,
|
||||
} from '@kbn/presentation-containers';
|
||||
import {
|
||||
apiPublishesUniqueId,
|
||||
apiPublishesParentApi,
|
||||
apiPublishesViewMode,
|
||||
apiHasUniqueId,
|
||||
EmbeddableApiContext,
|
||||
PublishesUniqueId,
|
||||
HasUniqueId,
|
||||
PublishesPanelTitle,
|
||||
PublishesParentApi,
|
||||
PublishesViewMode,
|
||||
apiCanAccessViewMode,
|
||||
CanAccessViewMode,
|
||||
HasParentApi,
|
||||
apiHasParentApi,
|
||||
getInheritedViewMode,
|
||||
} from '@kbn/presentation-publishing';
|
||||
import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
|
||||
import { ReplacePanelSOFinder } from '.';
|
||||
|
@ -28,17 +29,17 @@ import { dashboardReplacePanelActionStrings } from './_dashboard_actions_strings
|
|||
|
||||
export const ACTION_REPLACE_PANEL = 'replacePanel';
|
||||
|
||||
export type ReplacePanelActionApi = PublishesViewMode &
|
||||
PublishesUniqueId &
|
||||
export type ReplacePanelActionApi = CanAccessViewMode &
|
||||
HasUniqueId &
|
||||
Partial<PublishesPanelTitle> &
|
||||
PublishesParentApi<PresentationContainer & Partial<TracksOverlays>>;
|
||||
HasParentApi<PresentationContainer & Partial<TracksOverlays>>;
|
||||
|
||||
const isApiCompatible = (api: unknown | null): api is ReplacePanelActionApi =>
|
||||
Boolean(
|
||||
apiPublishesUniqueId(api) &&
|
||||
apiPublishesViewMode(api) &&
|
||||
apiPublishesParentApi(api) &&
|
||||
apiIsPresentationContainer(api.parentApi.value)
|
||||
apiHasUniqueId(api) &&
|
||||
apiCanAccessViewMode(api) &&
|
||||
apiHasParentApi(api) &&
|
||||
apiIsPresentationContainer(api.parentApi)
|
||||
);
|
||||
|
||||
export class ReplacePanelAction implements Action<EmbeddableApiContext> {
|
||||
|
@ -60,7 +61,7 @@ export class ReplacePanelAction implements Action<EmbeddableApiContext> {
|
|||
|
||||
public async isCompatible({ embeddable }: EmbeddableApiContext) {
|
||||
if (!isApiCompatible(embeddable)) return false;
|
||||
return embeddable.viewMode.value === 'edit';
|
||||
return getInheritedViewMode(embeddable) === 'edit';
|
||||
}
|
||||
|
||||
public async execute({ embeddable }: EmbeddableApiContext) {
|
||||
|
|
|
@ -49,7 +49,7 @@ export class ReplacePanelFlyout extends React.Component<Props> {
|
|||
};
|
||||
|
||||
public onReplacePanel = async (savedObjectId: string, type: string, name: string) => {
|
||||
this.props.api.parentApi.value.replacePanel(this.props.api.uuid.value, {
|
||||
this.props.api.parentApi.replacePanel(this.props.api.uuid, {
|
||||
panelType: type,
|
||||
initialState: { savedObjectId },
|
||||
});
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { ViewMode } from '@kbn/presentation-publishing';
|
||||
import { PublishesViewMode, ViewMode } from '@kbn/presentation-publishing';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { pluginServices } from '../services/plugin_services';
|
||||
import {
|
||||
|
@ -43,7 +43,7 @@ describe('Unlink from library action', () => {
|
|||
});
|
||||
|
||||
it('is incompatible when view mode is view', async () => {
|
||||
context.embeddable.viewMode = new BehaviorSubject<ViewMode>('view');
|
||||
(context.embeddable as PublishesViewMode).viewMode = new BehaviorSubject<ViewMode>('view');
|
||||
expect(await action.isCompatible(context)).toBe(false);
|
||||
});
|
||||
|
||||
|
|
|
@ -10,24 +10,25 @@ import { apiCanUnlinkFromLibrary, CanUnlinkFromLibrary } from '@kbn/presentation
|
|||
import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
|
||||
|
||||
import {
|
||||
apiPublishesViewMode,
|
||||
apiCanAccessViewMode,
|
||||
CanAccessViewMode,
|
||||
EmbeddableApiContext,
|
||||
getInheritedViewMode,
|
||||
PublishesPanelTitle,
|
||||
PublishesViewMode,
|
||||
} from '@kbn/presentation-publishing';
|
||||
import { pluginServices } from '../services/plugin_services';
|
||||
import { dashboardUnlinkFromLibraryActionStrings } from './_dashboard_actions_strings';
|
||||
|
||||
export const ACTION_UNLINK_FROM_LIBRARY = 'unlinkFromLibrary';
|
||||
|
||||
export type UnlinkPanelFromLibraryActionApi = PublishesViewMode &
|
||||
export type UnlinkPanelFromLibraryActionApi = CanAccessViewMode &
|
||||
CanUnlinkFromLibrary &
|
||||
Partial<PublishesPanelTitle>;
|
||||
|
||||
export const unlinkActionIsCompatible = (
|
||||
api: unknown | null
|
||||
): api is UnlinkPanelFromLibraryActionApi =>
|
||||
Boolean(apiPublishesViewMode(api) && apiCanUnlinkFromLibrary(api));
|
||||
Boolean(apiCanAccessViewMode(api) && apiCanUnlinkFromLibrary(api));
|
||||
|
||||
export class UnlinkFromLibraryAction implements Action<EmbeddableApiContext> {
|
||||
public readonly type = ACTION_UNLINK_FROM_LIBRARY;
|
||||
|
@ -54,7 +55,7 @@ export class UnlinkFromLibraryAction implements Action<EmbeddableApiContext> {
|
|||
|
||||
public async isCompatible({ embeddable }: EmbeddableApiContext) {
|
||||
if (!unlinkActionIsCompatible(embeddable)) return false;
|
||||
return embeddable.viewMode.value === 'edit' && (await embeddable.canUnlinkFromLibrary());
|
||||
return getInheritedViewMode(embeddable) === 'edit' && (await embeddable.canUnlinkFromLibrary());
|
||||
}
|
||||
|
||||
public async execute({ embeddable }: EmbeddableApiContext) {
|
||||
|
|
|
@ -8,17 +8,15 @@
|
|||
|
||||
import { css } from '@emotion/react';
|
||||
import { PresentationPanel } from '@kbn/presentation-panel-plugin/public';
|
||||
import { useApiPublisher } from '@kbn/presentation-publishing';
|
||||
import { PanelCompatibleComponent } from '@kbn/presentation-panel-plugin/public/panel_component/types';
|
||||
import { isPromise } from '@kbn/std';
|
||||
import React, { ReactNode, useEffect, useMemo, useState } from 'react';
|
||||
import React, { ReactNode, useEffect, useImperativeHandle, useMemo, useState } from 'react';
|
||||
import { untilPluginStartServicesReady } from '../kibana_services';
|
||||
import { LegacyEmbeddableAPI } from '../lib/embeddables/i_embeddable';
|
||||
import { CreateEmbeddableComponent } from '../registry/create_embeddable_component';
|
||||
import { EmbeddablePanelProps, LegacyEmbeddableCompatibilityComponent } from './types';
|
||||
import { EmbeddablePanelProps } from './types';
|
||||
|
||||
const getComponentFromEmbeddable = async (
|
||||
embeddable: EmbeddablePanelProps['embeddable']
|
||||
): Promise<LegacyEmbeddableCompatibilityComponent> => {
|
||||
): Promise<PanelCompatibleComponent> => {
|
||||
const startServicesPromise = untilPluginStartServicesReady();
|
||||
const embeddablePromise =
|
||||
typeof embeddable === 'function' ? embeddable() : Promise.resolve(embeddable);
|
||||
|
@ -27,7 +25,7 @@ const getComponentFromEmbeddable = async (
|
|||
await unwrappedEmbeddable.parent.untilEmbeddableLoaded(unwrappedEmbeddable.id);
|
||||
}
|
||||
|
||||
return CreateEmbeddableComponent((apiRef) => {
|
||||
return React.forwardRef((props, apiRef) => {
|
||||
const [node, setNode] = useState<ReactNode | undefined>();
|
||||
const embeddableRoot: React.RefObject<HTMLDivElement> = useMemo(() => React.createRef(), []);
|
||||
|
||||
|
@ -45,7 +43,7 @@ const getComponentFromEmbeddable = async (
|
|||
};
|
||||
}, [embeddableRoot]);
|
||||
|
||||
useApiPublisher(unwrappedEmbeddable, apiRef);
|
||||
useImperativeHandle(apiRef, () => unwrappedEmbeddable);
|
||||
|
||||
return (
|
||||
<div css={css(`width: 100%; height: 100%; display:flex`)} ref={embeddableRoot}>
|
||||
|
@ -56,12 +54,10 @@ const getComponentFromEmbeddable = async (
|
|||
};
|
||||
|
||||
/**
|
||||
* Loads and renders an embeddable.
|
||||
* Loads and renders a legacy embeddable.
|
||||
*/
|
||||
export const EmbeddablePanel = (props: EmbeddablePanelProps) => {
|
||||
const { embeddable, ...passThroughProps } = props;
|
||||
const componentPromise = useMemo(() => getComponentFromEmbeddable(embeddable), [embeddable]);
|
||||
return (
|
||||
<PresentationPanel<LegacyEmbeddableAPI> {...passThroughProps} Component={componentPromise} />
|
||||
);
|
||||
return <PresentationPanel {...passThroughProps} Component={componentPromise} />;
|
||||
};
|
||||
|
|
|
@ -93,9 +93,8 @@ export const legacyEmbeddableToApi = (
|
|||
})
|
||||
);
|
||||
|
||||
// legacy embeddables don't support ID changing or parent changing, so we don't need to subscribe to anything.
|
||||
const uuid = new BehaviorSubject<string>(embeddable.id);
|
||||
const parentApi = new BehaviorSubject<unknown>(embeddable.parent ?? undefined);
|
||||
const uuid = embeddable.id;
|
||||
const parentApi = embeddable.parent;
|
||||
|
||||
/**
|
||||
* We treat all legacy embeddable types as if they can support local unified search state, because there is no programmatic way
|
||||
|
@ -150,8 +149,8 @@ export const legacyEmbeddableToApi = (
|
|||
|
||||
return {
|
||||
api: {
|
||||
parentApi: parentApi as LegacyEmbeddableAPI['parentApi'],
|
||||
uuid,
|
||||
parentApi,
|
||||
viewMode,
|
||||
dataLoading,
|
||||
blockingError,
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
import { ErrorLike } from '@kbn/expressions-plugin/common';
|
||||
import { CanLinkToLibrary, CanUnlinkFromLibrary } from '@kbn/presentation-library';
|
||||
import { DefaultPresentationPanelApi } from '@kbn/presentation-panel-plugin/public/panel_component/types';
|
||||
import {
|
||||
HasEditCapabilities,
|
||||
HasType,
|
||||
|
@ -16,8 +17,8 @@ import {
|
|||
PublishesDataViews,
|
||||
PublishesDisabledActionIds,
|
||||
PublishesLocalUnifiedSearch,
|
||||
PublishesParentApi,
|
||||
PublishesUniqueId,
|
||||
HasParentApi,
|
||||
HasUniqueId,
|
||||
PublishesViewMode,
|
||||
PublishesWritablePanelDescription,
|
||||
PublishesWritablePanelTitle,
|
||||
|
@ -36,9 +37,8 @@ export type { EmbeddableInput };
|
|||
* Types for compatibility between the legacy Embeddable system and the new system
|
||||
*/
|
||||
export type LegacyEmbeddableAPI = HasType &
|
||||
PublishesUniqueId &
|
||||
HasUniqueId &
|
||||
PublishesViewMode &
|
||||
PublishesParentApi &
|
||||
PublishesDataViews &
|
||||
HasEditCapabilities &
|
||||
PublishesDataLoading &
|
||||
|
@ -49,6 +49,7 @@ export type LegacyEmbeddableAPI = HasType &
|
|||
PublishesWritablePanelTitle &
|
||||
PublishesWritablePanelDescription &
|
||||
Partial<CanLinkToLibrary & CanUnlinkFromLibrary> &
|
||||
HasParentApi<DefaultPresentationPanelApi['parentApi']> &
|
||||
EmbeddableHasTimeRange;
|
||||
|
||||
export interface EmbeddableAppContext {
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
import { DataView } from '@kbn/data-views-plugin/common';
|
||||
import { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query';
|
||||
import { TracksOverlays } from '@kbn/presentation-containers';
|
||||
import { ViewMode } from '@kbn/presentation-publishing';
|
||||
import { PublishesViewMode, ViewMode } from '@kbn/presentation-publishing';
|
||||
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { core } from '../../kibana_services';
|
||||
|
@ -23,7 +23,7 @@ describe('Customize panel action', () => {
|
|||
action = new CustomizePanelAction();
|
||||
context = {
|
||||
embeddable: {
|
||||
parentApi: new BehaviorSubject<unknown>({}),
|
||||
parentApi: {},
|
||||
viewMode: new BehaviorSubject<ViewMode>('edit'),
|
||||
dataViews: new BehaviorSubject<DataView[] | undefined>(undefined),
|
||||
},
|
||||
|
@ -35,7 +35,7 @@ describe('Customize panel action', () => {
|
|||
});
|
||||
|
||||
it('is compatible in view mode when API exposes writable unified search', async () => {
|
||||
context.embeddable.viewMode = new BehaviorSubject<ViewMode>('view');
|
||||
(context.embeddable as PublishesViewMode).viewMode = new BehaviorSubject<ViewMode>('view');
|
||||
context.embeddable.localTimeRange = new BehaviorSubject<TimeRange | undefined>({
|
||||
from: 'now-15m',
|
||||
to: 'now',
|
||||
|
@ -61,11 +61,11 @@ describe('Customize panel action', () => {
|
|||
});
|
||||
|
||||
it('opens overlay on parent if parent is an overlay tracker', async () => {
|
||||
context.embeddable.parentApi = new BehaviorSubject<unknown>({
|
||||
context.embeddable.parentApi = {
|
||||
openOverlay: jest.fn(),
|
||||
clearOverlays: jest.fn(),
|
||||
});
|
||||
};
|
||||
await action.execute(context);
|
||||
expect((context.embeddable.parentApi.value as TracksOverlays).openOverlay).toHaveBeenCalled();
|
||||
expect((context.embeddable.parentApi as TracksOverlays).openOverlay).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,13 +8,14 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
apiCanAccessViewMode,
|
||||
apiPublishesDataViews,
|
||||
apiPublishesLocalUnifiedSearch,
|
||||
apiPublishesViewMode,
|
||||
CanAccessViewMode,
|
||||
EmbeddableApiContext,
|
||||
getInheritedViewMode,
|
||||
HasParentApi,
|
||||
PublishesDataViews,
|
||||
PublishesParentApi,
|
||||
PublishesViewMode,
|
||||
PublishesWritableLocalUnifiedSearch,
|
||||
PublishesWritablePanelDescription,
|
||||
PublishesWritablePanelTitle,
|
||||
|
@ -24,19 +25,19 @@ import { openCustomizePanelFlyout } from './open_customize_panel';
|
|||
|
||||
export const ACTION_CUSTOMIZE_PANEL = 'ACTION_CUSTOMIZE_PANEL';
|
||||
|
||||
export type CustomizePanelActionApi = PublishesViewMode &
|
||||
export type CustomizePanelActionApi = CanAccessViewMode &
|
||||
PublishesDataViews &
|
||||
Partial<
|
||||
PublishesWritableLocalUnifiedSearch &
|
||||
PublishesWritablePanelDescription &
|
||||
PublishesWritablePanelTitle &
|
||||
PublishesParentApi
|
||||
HasParentApi
|
||||
>;
|
||||
|
||||
export const isApiCompatibleWithCustomizePanelAction = (
|
||||
api: unknown | null
|
||||
): api is CustomizePanelActionApi =>
|
||||
Boolean(apiPublishesViewMode(api) && apiPublishesDataViews(api));
|
||||
Boolean(apiCanAccessViewMode(api) && apiPublishesDataViews(api));
|
||||
|
||||
export class CustomizePanelAction implements Action<EmbeddableApiContext> {
|
||||
public type = ACTION_CUSTOMIZE_PANEL;
|
||||
|
@ -59,7 +60,7 @@ export class CustomizePanelAction implements Action<EmbeddableApiContext> {
|
|||
if (!isApiCompatibleWithCustomizePanelAction(embeddable)) return false;
|
||||
// It should be possible to customize just the time range in View mode
|
||||
return (
|
||||
embeddable.viewMode.value === 'edit' ||
|
||||
getInheritedViewMode(embeddable) === 'edit' ||
|
||||
(apiPublishesLocalUnifiedSearch(embeddable) &&
|
||||
(embeddable.isCompatibleWithLocalUnifiedSearch?.() ?? true))
|
||||
);
|
||||
|
|
|
@ -29,7 +29,7 @@ import { i18n } from '@kbn/i18n';
|
|||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import { UI_SETTINGS } from '@kbn/data-plugin/public';
|
||||
import { apiPublishesLocalUnifiedSearch } from '@kbn/presentation-publishing';
|
||||
import { apiPublishesLocalUnifiedSearch, getInheritedViewMode } from '@kbn/presentation-publishing';
|
||||
|
||||
import { core } from '../../kibana_services';
|
||||
import { CustomizePanelActionApi } from './customize_panel_action';
|
||||
|
@ -54,7 +54,7 @@ export const CustomizePanelEditor = ({
|
|||
* eventually the panel editor could be made to use state from the API instead (which will allow us to use a push flyout)
|
||||
* For now, we copy the state here with `useState` initializing it to the latest value.
|
||||
*/
|
||||
const editMode = api.viewMode.value === 'edit';
|
||||
const editMode = getInheritedViewMode(api) === 'edit';
|
||||
const [hideTitle, setHideTitle] = useState(api.hidePanelTitle?.value);
|
||||
const [panelDescription, setPanelDescription] = useState(
|
||||
api.panelDescription?.value ?? api.defaultPanelDescription?.value
|
||||
|
|
|
@ -22,7 +22,7 @@ export const openCustomizePanelFlyout = ({
|
|||
api: CustomizePanelActionApi;
|
||||
}) => {
|
||||
// send the overlay ref to the parent if it is capable of tracking overlays
|
||||
const parent = api.parentApi?.value;
|
||||
const parent = api.parentApi;
|
||||
const overlayTracker = tracksOverlays(parent) ? parent : undefined;
|
||||
|
||||
const { Provider: KibanaReactContextProvider } = createKibanaReactContext({
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { ViewMode } from '@kbn/presentation-publishing';
|
||||
import { PublishesViewMode, ViewMode } from '@kbn/presentation-publishing';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { EditPanelAction, EditPanelActionApi } from './edit_panel_action';
|
||||
|
||||
|
@ -42,7 +42,7 @@ describe('Edit panel action', () => {
|
|||
});
|
||||
|
||||
it('is incompatible when view mode is view', async () => {
|
||||
context.embeddable.viewMode = new BehaviorSubject<ViewMode>('view');
|
||||
(context.embeddable as PublishesViewMode).viewMode = new BehaviorSubject<ViewMode>('view');
|
||||
expect(await action.isCompatible(context)).toBe(false);
|
||||
});
|
||||
|
||||
|
|
|
@ -9,11 +9,13 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import {
|
||||
apiPublishesViewMode,
|
||||
hasEditCapabilities,
|
||||
HasEditCapabilities,
|
||||
EmbeddableApiContext,
|
||||
PublishesViewMode,
|
||||
CanAccessViewMode,
|
||||
apiCanAccessViewMode,
|
||||
getInheritedViewMode,
|
||||
getViewModeSubject,
|
||||
} from '@kbn/presentation-publishing';
|
||||
import {
|
||||
Action,
|
||||
|
@ -23,10 +25,10 @@ import {
|
|||
|
||||
export const ACTION_EDIT_PANEL = 'editPanel';
|
||||
|
||||
export type EditPanelActionApi = PublishesViewMode & HasEditCapabilities;
|
||||
export type EditPanelActionApi = CanAccessViewMode & HasEditCapabilities;
|
||||
|
||||
const isApiCompatible = (api: unknown | null): api is EditPanelActionApi => {
|
||||
return hasEditCapabilities(api) && apiPublishesViewMode(api);
|
||||
return hasEditCapabilities(api) && apiCanAccessViewMode(api);
|
||||
};
|
||||
|
||||
export class EditPanelAction
|
||||
|
@ -53,7 +55,7 @@ export class EditPanelAction
|
|||
onChange: (isCompatible: boolean, action: Action<EmbeddableApiContext>) => void
|
||||
) {
|
||||
if (!isApiCompatible(embeddable)) return;
|
||||
return embeddable.viewMode.subscribe((viewMode) => {
|
||||
return getViewModeSubject(embeddable)?.subscribe((viewMode) => {
|
||||
if (viewMode === 'edit' && isApiCompatible(embeddable) && embeddable.isEditingEnabled()) {
|
||||
onChange(true, this);
|
||||
return;
|
||||
|
@ -77,7 +79,7 @@ export class EditPanelAction
|
|||
|
||||
public async isCompatible({ embeddable }: EmbeddableApiContext) {
|
||||
if (!isApiCompatible(embeddable) || !embeddable.isEditingEnabled()) return false;
|
||||
return embeddable.viewMode.value === 'edit';
|
||||
return getInheritedViewMode(embeddable) === 'edit';
|
||||
}
|
||||
|
||||
public async execute({ embeddable }: EmbeddableApiContext) {
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
*/
|
||||
|
||||
import { TracksOverlays } from '@kbn/presentation-containers';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { inspector } from '../../kibana_services';
|
||||
import { InspectPanelActionApi, InspectPanelAction } from './inspect_panel_action';
|
||||
|
||||
|
@ -74,11 +73,11 @@ describe('Inspect panel action', () => {
|
|||
|
||||
it('opens overlay on parent if parent is an overlay tracker', async () => {
|
||||
inspector.open = jest.fn().mockReturnValue({ onClose: Promise.resolve(undefined) });
|
||||
context.embeddable.parentApi = new BehaviorSubject<unknown>({
|
||||
context.embeddable.parentApi = {
|
||||
openOverlay: jest.fn(),
|
||||
clearOverlays: jest.fn(),
|
||||
});
|
||||
};
|
||||
await action.execute(context);
|
||||
expect((context.embeddable.parentApi.value as TracksOverlays).openOverlay).toHaveBeenCalled();
|
||||
expect((context.embeddable.parentApi as TracksOverlays).openOverlay).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -12,7 +12,7 @@ import { tracksOverlays } from '@kbn/presentation-containers';
|
|||
import {
|
||||
EmbeddableApiContext,
|
||||
PublishesPanelTitle,
|
||||
PublishesParentApi,
|
||||
HasParentApi,
|
||||
} from '@kbn/presentation-publishing';
|
||||
import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
|
||||
import { inspector } from '../../kibana_services';
|
||||
|
@ -20,7 +20,7 @@ import { inspector } from '../../kibana_services';
|
|||
export const ACTION_INSPECT_PANEL = 'openInspector';
|
||||
|
||||
export type InspectPanelActionApi = HasInspectorAdapters &
|
||||
Partial<PublishesPanelTitle & PublishesParentApi>;
|
||||
Partial<PublishesPanelTitle & HasParentApi>;
|
||||
const isApiCompatible = (api: unknown | null): api is InspectPanelActionApi => {
|
||||
return Boolean(api) && apiHasInspectorAdapters(api);
|
||||
};
|
||||
|
@ -67,11 +67,10 @@ export class InspectPanelAction implements Action<EmbeddableApiContext> {
|
|||
},
|
||||
});
|
||||
session.onClose.finally(() => {
|
||||
if (tracksOverlays(embeddable.parentApi?.value)) embeddable.parentApi?.value.clearOverlays();
|
||||
if (tracksOverlays(embeddable.parentApi)) embeddable.parentApi.clearOverlays();
|
||||
});
|
||||
|
||||
// send the overlay ref to the parent API if it is capable of tracking overlays
|
||||
if (tracksOverlays(embeddable.parentApi?.value))
|
||||
embeddable.parentApi?.value.openOverlay(session);
|
||||
if (tracksOverlays(embeddable.parentApi)) embeddable.parentApi?.openOverlay(session);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { PresentationContainer } from '@kbn/presentation-containers';
|
||||
import { ViewMode } from '@kbn/presentation-publishing';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { RemovePanelAction, RemovePanelActionApi } from './remove_panel_action';
|
||||
|
@ -19,13 +18,13 @@ describe('Remove panel action', () => {
|
|||
action = new RemovePanelAction();
|
||||
context = {
|
||||
embeddable: {
|
||||
uuid: new BehaviorSubject<string>('superId'),
|
||||
uuid: 'superId',
|
||||
viewMode: new BehaviorSubject<ViewMode>('edit'),
|
||||
parentApi: new BehaviorSubject<PresentationContainer>({
|
||||
parentApi: {
|
||||
removePanel: jest.fn(),
|
||||
canRemovePanels: jest.fn().mockReturnValue(true),
|
||||
replacePanel: jest.fn(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
@ -48,6 +47,6 @@ describe('Remove panel action', () => {
|
|||
|
||||
it('calls the parent removePanel method on execute', async () => {
|
||||
action.execute(context);
|
||||
expect(context.embeddable.parentApi.value.removePanel).toHaveBeenCalled();
|
||||
expect(context.embeddable.parentApi.removePanel).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,11 +8,12 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
apiPublishesUniqueId,
|
||||
apiPublishesViewMode,
|
||||
apiCanAccessViewMode,
|
||||
apiHasUniqueId,
|
||||
EmbeddableApiContext,
|
||||
PublishesUniqueId,
|
||||
PublishesParentApi,
|
||||
getInheritedViewMode,
|
||||
HasParentApi,
|
||||
HasUniqueId,
|
||||
PublishesViewMode,
|
||||
} from '@kbn/presentation-publishing';
|
||||
import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
|
||||
|
@ -22,11 +23,11 @@ import { getContainerParentFromAPI, PresentationContainer } from '@kbn/presentat
|
|||
export const ACTION_REMOVE_PANEL = 'deletePanel';
|
||||
|
||||
export type RemovePanelActionApi = PublishesViewMode &
|
||||
PublishesUniqueId &
|
||||
PublishesParentApi<PresentationContainer>;
|
||||
HasUniqueId &
|
||||
HasParentApi<PresentationContainer>;
|
||||
|
||||
const isApiCompatible = (api: unknown | null): api is RemovePanelActionApi =>
|
||||
Boolean(apiPublishesUniqueId(api) && apiPublishesViewMode(api) && getContainerParentFromAPI(api));
|
||||
Boolean(apiHasUniqueId(api) && apiCanAccessViewMode(api) && getContainerParentFromAPI(api));
|
||||
|
||||
export class RemovePanelAction implements Action<EmbeddableApiContext> {
|
||||
public readonly type = ACTION_REMOVE_PANEL;
|
||||
|
@ -50,12 +51,12 @@ export class RemovePanelAction implements Action<EmbeddableApiContext> {
|
|||
|
||||
// any parent can disallow panel removal by implementing canRemovePanels. If this method
|
||||
// is not implemented, panel removal is always allowed.
|
||||
const parentAllowsPanelRemoval = embeddable.parentApi.value.canRemovePanels?.() ?? true;
|
||||
return Boolean(embeddable.viewMode.value === 'edit' && parentAllowsPanelRemoval);
|
||||
const parentAllowsPanelRemoval = embeddable.parentApi.canRemovePanels?.() ?? true;
|
||||
return Boolean(getInheritedViewMode(embeddable) === 'edit' && parentAllowsPanelRemoval);
|
||||
}
|
||||
|
||||
public async execute({ embeddable }: EmbeddableApiContext) {
|
||||
if (!isApiCompatible(embeddable)) throw new IncompatibleActionError();
|
||||
embeddable.parentApi?.value.removePanel(embeddable.uuid.value);
|
||||
embeddable.parentApi?.removePanel(embeddable.uuid);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { Action, buildContextMenuForActions } from '@kbn/ui-actions-plugin/public';
|
||||
|
||||
import { PublishesViewMode, useBatchedPublishingSubjects } from '@kbn/presentation-publishing';
|
||||
import { getViewModeSubject, useBatchedPublishingSubjects } from '@kbn/presentation-publishing';
|
||||
import { uiActions } from '../../kibana_services';
|
||||
import { contextMenuTrigger, CONTEXT_MENU_TRIGGER } from '../../panel_actions';
|
||||
import { getContextMenuAriaLabel } from '../presentation_panel_strings';
|
||||
|
@ -44,7 +44,7 @@ export const PresentationPanelContextMenu = ({
|
|||
const [contextMenuPanels, setContextMenuPanels] = useState<EuiContextMenuPanelDescriptor[]>([]);
|
||||
|
||||
const { title, parentViewMode } = useBatchedPublishingSubjects({
|
||||
title: api?.panelTitle,
|
||||
title: api.panelTitle,
|
||||
|
||||
/**
|
||||
* View mode changes often have the biggest influence over which actions will be compatible,
|
||||
|
@ -52,7 +52,7 @@ export const PresentationPanelContextMenu = ({
|
|||
* actions should eventually all be Frequent Compatibility Change Actions which can track their
|
||||
* own dependencies.
|
||||
*/
|
||||
parentViewMode: (api?.parentApi?.value as Partial<PublishesViewMode>)?.viewMode,
|
||||
parentViewMode: getViewModeSubject(api),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -38,7 +38,7 @@ export const PresentationPanelTitle = ({
|
|||
embPanel__placeholderTitleText: !panelTitle,
|
||||
});
|
||||
|
||||
if (viewMode !== 'edit' && isApiCompatibleWithCustomizePanelAction(api)) {
|
||||
if (viewMode !== 'edit' || !isApiCompatibleWithCustomizePanelAction(api)) {
|
||||
return <span className={titleClassNames}>{panelTitle}</span>;
|
||||
}
|
||||
|
||||
|
|
|
@ -31,7 +31,10 @@ export const PresentationPanelError = ({
|
|||
() => (isEditable ? () => editPanelAction?.execute({ embeddable: api }) : undefined),
|
||||
[api, isEditable]
|
||||
);
|
||||
const label = useMemo(() => editPanelAction?.getDisplayName({ embeddable: api }), [api]);
|
||||
const label = useMemo(
|
||||
() => (isEditable ? editPanelAction?.getDisplayName({ embeddable: api }) : ''),
|
||||
[api, isEditable]
|
||||
);
|
||||
|
||||
const panelTitle = usePanelTitle(api);
|
||||
const ariaLabel = useMemo(
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl';
|
||||
import { DataView } from '@kbn/data-views-plugin/common';
|
||||
import { PublishesDataViews, ViewMode } from '@kbn/presentation-publishing';
|
||||
import { PublishesDataViews, PublishesViewMode, ViewMode } from '@kbn/presentation-publishing';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
|
@ -153,7 +153,7 @@ describe('Presentation panel', () => {
|
|||
});
|
||||
|
||||
it('does not render a title when in view mode when the provided title is blank', async () => {
|
||||
const api: DefaultPresentationPanelApi = {
|
||||
const api: DefaultPresentationPanelApi & PublishesViewMode = {
|
||||
panelTitle: new BehaviorSubject<string | undefined>(''),
|
||||
viewMode: new BehaviorSubject<ViewMode>('view'),
|
||||
};
|
||||
|
@ -164,9 +164,10 @@ describe('Presentation panel', () => {
|
|||
});
|
||||
|
||||
it('renders a placeholder title when in edit mode and the provided title is blank', async () => {
|
||||
const api: DefaultPresentationPanelApi = {
|
||||
const api: DefaultPresentationPanelApi & PublishesDataViews & PublishesViewMode = {
|
||||
panelTitle: new BehaviorSubject<string | undefined>(''),
|
||||
viewMode: new BehaviorSubject<ViewMode>('edit'),
|
||||
dataViews: new BehaviorSubject<DataView[] | undefined>([]),
|
||||
};
|
||||
await renderPresentationPanel({ api });
|
||||
await waitFor(() => {
|
||||
|
@ -177,7 +178,7 @@ describe('Presentation panel', () => {
|
|||
it('opens customize panel flyout on title click when in edit mode', async () => {
|
||||
const spy = jest.spyOn(openCustomizePanel, 'openCustomizePanelFlyout');
|
||||
|
||||
const api: DefaultPresentationPanelApi & PublishesDataViews = {
|
||||
const api: DefaultPresentationPanelApi & PublishesDataViews & PublishesViewMode = {
|
||||
panelTitle: new BehaviorSubject<string | undefined>('TITLE'),
|
||||
viewMode: new BehaviorSubject<ViewMode>('edit'),
|
||||
dataViews: new BehaviorSubject<DataView[] | undefined>([]),
|
||||
|
@ -193,7 +194,7 @@ describe('Presentation panel', () => {
|
|||
});
|
||||
|
||||
it('does not show title customize link in view mode', async () => {
|
||||
const api: DefaultPresentationPanelApi & PublishesDataViews = {
|
||||
const api: DefaultPresentationPanelApi & PublishesDataViews & PublishesViewMode = {
|
||||
panelTitle: new BehaviorSubject<string | undefined>('SUPER TITLE'),
|
||||
viewMode: new BehaviorSubject<ViewMode>('view'),
|
||||
dataViews: new BehaviorSubject<DataView[] | undefined>([]),
|
||||
|
@ -206,7 +207,7 @@ describe('Presentation panel', () => {
|
|||
});
|
||||
|
||||
it('hides title when API hide title option is true', async () => {
|
||||
const api: DefaultPresentationPanelApi = {
|
||||
const api: DefaultPresentationPanelApi & PublishesViewMode = {
|
||||
panelTitle: new BehaviorSubject<string | undefined>('SUPER TITLE'),
|
||||
hidePanelTitle: new BehaviorSubject<boolean | undefined>(true),
|
||||
viewMode: new BehaviorSubject<ViewMode>('view'),
|
||||
|
@ -216,12 +217,14 @@ describe('Presentation panel', () => {
|
|||
});
|
||||
|
||||
it('hides title when parent hide title option is true', async () => {
|
||||
const api: DefaultPresentationPanelApi = {
|
||||
const api: DefaultPresentationPanelApi & PublishesViewMode = {
|
||||
panelTitle: new BehaviorSubject<string | undefined>('SUPER TITLE'),
|
||||
viewMode: new BehaviorSubject<ViewMode>('view'),
|
||||
parentApi: new BehaviorSubject<unknown>({
|
||||
hidePanelTitle: new BehaviorSubject<boolean | undefined>(true),
|
||||
}),
|
||||
parentApi: {
|
||||
removePanel: jest.fn(),
|
||||
replacePanel: jest.fn(),
|
||||
viewMode: new BehaviorSubject<ViewMode>('view'),
|
||||
},
|
||||
};
|
||||
await renderPresentationPanel({ api });
|
||||
expect(screen.queryByTestId('presentationPanelTitle')).not.toBeInTheDocument();
|
||||
|
|
|
@ -7,12 +7,16 @@
|
|||
*/
|
||||
|
||||
import { EuiFlexGroup, EuiPanel, htmlIdGenerator } from '@elastic/eui';
|
||||
import { PanelLoader } from '@kbn/panel-loader';
|
||||
import {
|
||||
apiFiresPhaseEvents,
|
||||
apiHasParentApi,
|
||||
apiPublishesViewMode,
|
||||
useBatchedPublishingSubjects,
|
||||
} from '@kbn/presentation-publishing';
|
||||
import classNames from 'classnames';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
import { PanelLoader } from '@kbn/panel-loader';
|
||||
import { apiFiresPhaseEvents, useBatchedPublishingSubjects } from '@kbn/presentation-publishing';
|
||||
import { PresentationPanelHeader } from './panel_header/presentation_panel_header';
|
||||
import { PresentationPanelError } from './presentation_panel_error';
|
||||
import { DefaultPresentationPanelApi, PresentationPanelInternalProps } from './types';
|
||||
|
@ -38,9 +42,13 @@ export const PresentationPanelInternal = <
|
|||
const [api, setApi] = useState<ApiType | null>(null);
|
||||
const headerId = useMemo(() => htmlIdGenerator()(), []);
|
||||
|
||||
const viewModeSubject = (() => {
|
||||
if (apiPublishesViewMode(api)) return api.viewMode;
|
||||
if (apiHasParentApi(api) && apiPublishesViewMode(api.parentApi)) return api.parentApi.viewMode;
|
||||
})();
|
||||
|
||||
const {
|
||||
uuid,
|
||||
viewMode,
|
||||
rawViewMode,
|
||||
blockingError,
|
||||
panelTitle,
|
||||
dataLoading,
|
||||
|
@ -51,18 +59,19 @@ export const PresentationPanelInternal = <
|
|||
} = useBatchedPublishingSubjects({
|
||||
dataLoading: api?.dataLoading,
|
||||
blockingError: api?.blockingError,
|
||||
viewMode: api?.viewMode,
|
||||
uuid: api?.uuid,
|
||||
|
||||
panelTitle: api?.panelTitle,
|
||||
hidePanelTitle: api?.hidePanelTitle,
|
||||
panelDescription: api?.panelDescription,
|
||||
defaultPanelTitle: api?.defaultPanelTitle,
|
||||
parentHidePanelTitle: (api?.parentApi?.value as DefaultPresentationPanelApi)?.hidePanelTitle,
|
||||
|
||||
rawViewMode: viewModeSubject,
|
||||
parentHidePanelTitle: api?.parentApi?.hidePanelTitle,
|
||||
});
|
||||
const viewMode = rawViewMode ?? 'view';
|
||||
|
||||
const [initialLoadComplete, setInitialLoadComplete] = useState(!dataLoading);
|
||||
if (dataLoading === false && !initialLoadComplete) {
|
||||
if (!initialLoadComplete && (dataLoading === false || (api && !api.dataLoading))) {
|
||||
setInitialLoadComplete(true);
|
||||
}
|
||||
|
||||
|
@ -91,11 +100,11 @@ export const PresentationPanelInternal = <
|
|||
role="figure"
|
||||
paddingSize="none"
|
||||
className={classNames('embPanel', {
|
||||
'embPanel--editing': viewMode !== 'view',
|
||||
'embPanel--editing': viewMode === 'edit',
|
||||
})}
|
||||
hasShadow={showShadow}
|
||||
aria-labelledby={headerId}
|
||||
data-test-embeddable-id={uuid}
|
||||
data-test-embeddable-id={api?.uuid}
|
||||
data-test-subj="embeddablePanel"
|
||||
>
|
||||
{!hideHeader && api && (
|
||||
|
|
|
@ -6,15 +6,16 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { PresentationContainer } from '@kbn/presentation-containers';
|
||||
import {
|
||||
PhaseEvent,
|
||||
PublishesDataLoading,
|
||||
PublishesDisabledActionIds,
|
||||
PublishesBlockingError,
|
||||
PublishesUniqueId,
|
||||
HasUniqueId,
|
||||
PublishesPanelDescription,
|
||||
PublishesPanelTitle,
|
||||
PublishesParentApi,
|
||||
HasParentApi,
|
||||
PublishesViewMode,
|
||||
} from '@kbn/presentation-publishing';
|
||||
import { UiActionsService } from '@kbn/ui-actions-plugin/public';
|
||||
|
@ -56,14 +57,16 @@ export interface PresentationPanelInternalProps<
|
|||
}
|
||||
|
||||
export type DefaultPresentationPanelApi = Partial<
|
||||
PublishesUniqueId &
|
||||
PublishesParentApi &
|
||||
PublishesDataLoading &
|
||||
PublishesViewMode &
|
||||
PublishesBlockingError &
|
||||
HasUniqueId &
|
||||
PublishesPanelTitle &
|
||||
PublishesDataLoading &
|
||||
PublishesBlockingError &
|
||||
PublishesPanelDescription &
|
||||
PublishesDisabledActionIds
|
||||
PublishesDisabledActionIds &
|
||||
HasParentApi<
|
||||
PresentationContainer &
|
||||
Partial<Pick<PublishesPanelTitle, 'hidePanelTitle'> & PublishesViewMode>
|
||||
>
|
||||
>;
|
||||
|
||||
export type PresentationPanelProps<
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue