mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Dashboard] Cleanup services (#193644)
Closes https://github.com/elastic/kibana/issues/167437 ## Summary This PR refactors the Dashboard services to no longer use the `PluginServiceProvider` from the `PresentationUtil` plugin. ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
2f45c90d2f
commit
ce08d4e373
207 changed files with 1741 additions and 4765 deletions
|
@ -7,8 +7,6 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { setStubDashboardServices } from './public/services/mocks';
|
||||
|
||||
/**
|
||||
* CAUTION: Be very mindful of the things you import in to this `jest_setup` file - anything that is imported
|
||||
* here (either directly or implicitly through dependencies) will be **unable** to be mocked elsewhere!
|
||||
|
@ -16,4 +14,36 @@ import { setStubDashboardServices } from './public/services/mocks';
|
|||
* Refer to the "Caution" section here:
|
||||
* https://jestjs.io/docs/jest-object#jestmockmodulename-factory-options
|
||||
*/
|
||||
setStubDashboardServices();
|
||||
import {
|
||||
mockDashboardBackupService,
|
||||
mockDashboardContentManagementCache,
|
||||
mockDashboardContentManagementService,
|
||||
setStubKibanaServices,
|
||||
} from './public/services/mocks';
|
||||
|
||||
// Start the kibana services with stubs
|
||||
setStubKibanaServices();
|
||||
|
||||
// Mock the dashboard services
|
||||
jest.mock('./public/services/dashboard_content_management_service', () => {
|
||||
return {
|
||||
getDashboardContentManagementCache: () => mockDashboardContentManagementCache,
|
||||
getDashboardContentManagementService: () => mockDashboardContentManagementService,
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('./public/services/dashboard_backup_service', () => {
|
||||
return {
|
||||
getDashboardBackupService: () => mockDashboardBackupService,
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('./public/services/dashboard_recently_accessed_service', () => {
|
||||
return {
|
||||
getDashboardRecentlyAccessedService: () => ({
|
||||
add: jest.fn(),
|
||||
get: jest.fn(),
|
||||
get$: jest.fn(),
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
|
|
@ -8,34 +8,36 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { PresentationContainer } from '@kbn/presentation-containers';
|
||||
import {
|
||||
apiCanAccessViewMode,
|
||||
apiHasLibraryTransforms,
|
||||
EmbeddableApiContext,
|
||||
getPanelTitle,
|
||||
PublishesPanelTitle,
|
||||
CanAccessViewMode,
|
||||
getInheritedViewMode,
|
||||
EmbeddableApiContext,
|
||||
HasInPlaceLibraryTransforms,
|
||||
HasLibraryTransforms,
|
||||
HasParentApi,
|
||||
HasType,
|
||||
HasTypeDisplayName,
|
||||
apiHasType,
|
||||
HasUniqueId,
|
||||
HasParentApi,
|
||||
apiHasUniqueId,
|
||||
apiHasParentApi,
|
||||
HasInPlaceLibraryTransforms,
|
||||
PublishesPanelTitle,
|
||||
apiCanAccessViewMode,
|
||||
apiHasInPlaceLibraryTransforms,
|
||||
apiHasLibraryTransforms,
|
||||
apiHasParentApi,
|
||||
apiHasType,
|
||||
apiHasUniqueId,
|
||||
getInheritedViewMode,
|
||||
getPanelTitle,
|
||||
} from '@kbn/presentation-publishing';
|
||||
import {
|
||||
OnSaveProps,
|
||||
SavedObjectSaveModal,
|
||||
SaveResult,
|
||||
SavedObjectSaveModal,
|
||||
showSaveModal,
|
||||
} from '@kbn/saved-objects-plugin/public';
|
||||
import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
|
||||
import { PresentationContainer } from '@kbn/presentation-containers';
|
||||
import { pluginServices } from '../services/plugin_services';
|
||||
|
||||
import { coreServices } from '../services/kibana_services';
|
||||
import { dashboardAddToLibraryActionStrings } from './_dashboard_actions_strings';
|
||||
|
||||
export const ACTION_ADD_TO_LIBRARY = 'saveToLibrary';
|
||||
|
@ -62,14 +64,6 @@ export class AddToLibraryAction implements Action<EmbeddableApiContext> {
|
|||
public readonly id = ACTION_ADD_TO_LIBRARY;
|
||||
public order = 8;
|
||||
|
||||
private toastsService;
|
||||
|
||||
constructor() {
|
||||
({
|
||||
notifications: { toasts: this.toastsService },
|
||||
} = pluginServices.getServices());
|
||||
}
|
||||
|
||||
public getDisplayName({ embeddable }: EmbeddableApiContext) {
|
||||
if (!isApiCompatible(embeddable)) throw new IncompatibleActionError();
|
||||
return dashboardAddToLibraryActionStrings.getDisplayName();
|
||||
|
@ -134,12 +128,12 @@ export class AddToLibraryAction implements Action<EmbeddableApiContext> {
|
|||
initialState: byRefState,
|
||||
});
|
||||
}
|
||||
this.toastsService.addSuccess({
|
||||
coreServices.notifications.toasts.addSuccess({
|
||||
title: dashboardAddToLibraryActionStrings.getSuccessMessage(title ? `'${title}'` : ''),
|
||||
'data-test-subj': 'addPanelToLibrarySuccess',
|
||||
});
|
||||
} catch (e) {
|
||||
this.toastsService.addDanger({
|
||||
coreServices.notifications.toasts.addDanger({
|
||||
title: dashboardAddToLibraryActionStrings.getErrorMessage(title),
|
||||
'data-test-subj': 'addPanelToLibraryError',
|
||||
});
|
||||
|
|
|
@ -42,8 +42,6 @@ export class ClonePanelAction implements Action<EmbeddableApiContext> {
|
|||
public readonly id = ACTION_CLONE_PANEL;
|
||||
public order = 45;
|
||||
|
||||
constructor() {}
|
||||
|
||||
public getDisplayName({ embeddable }: EmbeddableApiContext) {
|
||||
if (!isApiCompatible(embeddable)) throw new IncompatibleActionError();
|
||||
return dashboardClonePanelActionStrings.getDisplayName();
|
||||
|
|
|
@ -9,26 +9,26 @@
|
|||
|
||||
import React from 'react';
|
||||
|
||||
import { CoreStart } from '@kbn/core-lifecycle-browser';
|
||||
import {
|
||||
apiIsOfType,
|
||||
apiHasUniqueId,
|
||||
apiHasParentApi,
|
||||
apiPublishesSavedObjectId,
|
||||
HasType,
|
||||
EmbeddableApiContext,
|
||||
HasUniqueId,
|
||||
HasParentApi,
|
||||
HasType,
|
||||
HasUniqueId,
|
||||
PublishesSavedObjectId,
|
||||
apiHasParentApi,
|
||||
apiHasUniqueId,
|
||||
apiIsOfType,
|
||||
apiPublishesSavedObjectId,
|
||||
} from '@kbn/presentation-publishing';
|
||||
import { toMountPoint } from '@kbn/react-kibana-mount';
|
||||
import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
|
||||
|
||||
import { DASHBOARD_CONTAINER_TYPE } from '../dashboard_container';
|
||||
import { DashboardApi } from '../dashboard_api/types';
|
||||
import { pluginServices } from '../services/plugin_services';
|
||||
import { CopyToDashboardModal } from './copy_to_dashboard_modal';
|
||||
import { DASHBOARD_CONTAINER_TYPE } from '../dashboard_container';
|
||||
import { coreServices } from '../services/kibana_services';
|
||||
import { getDashboardCapabilities } from '../utils/get_dashboard_capabilities';
|
||||
import { dashboardCopyToDashboardActionStrings } from './_dashboard_actions_strings';
|
||||
import { CopyToDashboardModal } from './copy_to_dashboard_modal';
|
||||
|
||||
export const ACTION_COPY_TO_DASHBOARD = 'copyToDashboard';
|
||||
|
||||
|
@ -60,16 +60,6 @@ export class CopyToDashboardAction implements Action<EmbeddableApiContext> {
|
|||
public readonly id = ACTION_COPY_TO_DASHBOARD;
|
||||
public order = 1;
|
||||
|
||||
private dashboardCapabilities;
|
||||
private openModal;
|
||||
|
||||
constructor(private core: CoreStart) {
|
||||
({
|
||||
dashboardCapabilities: this.dashboardCapabilities,
|
||||
overlays: { openModal: this.openModal },
|
||||
} = pluginServices.getServices());
|
||||
}
|
||||
|
||||
public getDisplayName({ embeddable }: EmbeddableApiContext) {
|
||||
if (!apiIsCompatible(embeddable)) throw new IncompatibleActionError();
|
||||
|
||||
|
@ -84,15 +74,15 @@ export class CopyToDashboardAction implements Action<EmbeddableApiContext> {
|
|||
public async isCompatible({ embeddable }: EmbeddableApiContext) {
|
||||
if (!apiIsCompatible(embeddable)) return false;
|
||||
const { createNew: canCreateNew, showWriteControls: canEditExisting } =
|
||||
this.dashboardCapabilities;
|
||||
getDashboardCapabilities();
|
||||
return Boolean(canCreateNew || canEditExisting);
|
||||
}
|
||||
|
||||
public async execute({ embeddable }: EmbeddableApiContext) {
|
||||
if (!apiIsCompatible(embeddable)) throw new IncompatibleActionError();
|
||||
|
||||
const { theme, i18n } = this.core;
|
||||
const session = this.openModal(
|
||||
const { theme, i18n } = coreServices;
|
||||
const session = coreServices.overlays.openModal(
|
||||
toMountPoint(<CopyToDashboardModal closeModal={() => session.close()} api={embeddable} />, {
|
||||
theme,
|
||||
i18n,
|
||||
|
|
|
@ -22,11 +22,12 @@ import { EmbeddablePackageState, PanelNotFoundError } from '@kbn/embeddable-plug
|
|||
import { apiHasSnapshottableState } from '@kbn/presentation-containers/interfaces/serialized_state';
|
||||
import { LazyDashboardPicker, withSuspense } from '@kbn/presentation-util-plugin/public';
|
||||
import { omit } from 'lodash';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { createDashboardEditUrl, CREATE_NEW_DASHBOARD_URL } from '../dashboard_constants';
|
||||
import { pluginServices } from '../services/plugin_services';
|
||||
import { CopyToDashboardAPI } from './copy_to_dashboard_action';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { CREATE_NEW_DASHBOARD_URL, createDashboardEditUrl } from '../dashboard_constants';
|
||||
import { embeddableService } from '../services/kibana_services';
|
||||
import { getDashboardCapabilities } from '../utils/get_dashboard_capabilities';
|
||||
import { dashboardCopyToDashboardActionStrings } from './_dashboard_actions_strings';
|
||||
import { CopyToDashboardAPI } from './copy_to_dashboard_action';
|
||||
|
||||
interface CopyToDashboardModalProps {
|
||||
api: CopyToDashboardAPI;
|
||||
|
@ -36,11 +37,11 @@ interface CopyToDashboardModalProps {
|
|||
const DashboardPicker = withSuspense(LazyDashboardPicker);
|
||||
|
||||
export function CopyToDashboardModal({ api, closeModal }: CopyToDashboardModalProps) {
|
||||
const {
|
||||
embeddable: { getStateTransfer },
|
||||
dashboardCapabilities: { createNew: canCreateNew, showWriteControls: canEditExisting },
|
||||
} = pluginServices.getServices();
|
||||
const stateTransfer = getStateTransfer();
|
||||
const stateTransfer = useMemo(() => embeddableService.getStateTransfer(), []);
|
||||
const { createNew: canCreateNew, showWriteControls: canEditExisting } = useMemo(
|
||||
() => getDashboardCapabilities(),
|
||||
[]
|
||||
);
|
||||
|
||||
const [dashboardOption, setDashboardOption] = useState<'new' | 'existing'>('existing');
|
||||
const [selectedDashboard, setSelectedDashboard] = useState<{ id: string; name: string } | null>(
|
||||
|
|
|
@ -31,8 +31,6 @@ export class ExpandPanelAction implements Action<EmbeddableApiContext> {
|
|||
public readonly id = ACTION_EXPAND_PANEL;
|
||||
public order = 7;
|
||||
|
||||
constructor() {}
|
||||
|
||||
public getDisplayName({ embeddable }: EmbeddableApiContext) {
|
||||
if (!isApiCompatible(embeddable)) throw new IncompatibleActionError();
|
||||
return embeddable.parentApi.expandedPanelId.value
|
||||
|
|
|
@ -14,16 +14,16 @@ import { downloadMultipleAs } from '@kbn/share-plugin/public';
|
|||
import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
|
||||
|
||||
import {
|
||||
apiHasInspectorAdapters,
|
||||
HasInspectorAdapters,
|
||||
apiHasInspectorAdapters,
|
||||
type Adapters,
|
||||
} from '@kbn/inspector-plugin/public';
|
||||
import {
|
||||
EmbeddableApiContext,
|
||||
getPanelTitle,
|
||||
PublishesPanelTitle,
|
||||
getPanelTitle,
|
||||
} from '@kbn/presentation-publishing';
|
||||
import { pluginServices } from '../services/plugin_services';
|
||||
import { coreServices, fieldFormatService } from '../services/kibana_services';
|
||||
import { dashboardExportCsvActionStrings } from './_dashboard_actions_strings';
|
||||
|
||||
export const ACTION_EXPORT_CSV = 'ACTION_EXPORT_CSV';
|
||||
|
@ -43,16 +43,6 @@ export class ExportCSVAction implements Action<ExportContext> {
|
|||
public readonly type = ACTION_EXPORT_CSV;
|
||||
public readonly order = 18; // right after Export in discover which is 19
|
||||
|
||||
private fieldFormats;
|
||||
private uiSettings;
|
||||
|
||||
constructor() {
|
||||
({
|
||||
data: { fieldFormats: this.fieldFormats },
|
||||
settings: { uiSettings: this.uiSettings },
|
||||
} = pluginServices.getServices());
|
||||
}
|
||||
|
||||
public getIconType() {
|
||||
return 'exportAction';
|
||||
}
|
||||
|
@ -70,9 +60,7 @@ export class ExportCSVAction implements Action<ExportContext> {
|
|||
};
|
||||
|
||||
private getFormatter = (): FormatFactory | undefined => {
|
||||
if (this.fieldFormats) {
|
||||
return this.fieldFormats.deserialize;
|
||||
}
|
||||
return fieldFormatService.deserialize;
|
||||
};
|
||||
|
||||
private getDataTableContent = (adapters: Adapters | undefined) => {
|
||||
|
@ -105,8 +93,8 @@ export class ExportCSVAction implements Action<ExportContext> {
|
|||
|
||||
memo[`${getPanelTitle(embeddable) || untitledFilename}${postFix}.csv`] = {
|
||||
content: exporters.datatableToCSV(datatable, {
|
||||
csvSeparator: this.uiSettings.get('csv:separator', ','),
|
||||
quoteValues: this.uiSettings.get('csv:quoteValues', true),
|
||||
csvSeparator: coreServices.uiSettings.get('csv:separator', ','),
|
||||
quoteValues: coreServices.uiSettings.get('csv:quoteValues', true),
|
||||
formatFactory,
|
||||
escapeFormulaValues: false,
|
||||
}),
|
||||
|
|
|
@ -8,28 +8,28 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { merge } from 'rxjs';
|
||||
|
||||
import { isOfAggregateQueryType, isOfQueryType } from '@kbn/es-query';
|
||||
import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public';
|
||||
import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
|
||||
|
||||
import {
|
||||
apiCanAccessViewMode,
|
||||
apiPublishesPartialUnifiedSearch,
|
||||
apiHasUniqueId,
|
||||
CanAccessViewMode,
|
||||
EmbeddableApiContext,
|
||||
getInheritedViewMode,
|
||||
getViewModeSubject,
|
||||
HasParentApi,
|
||||
PublishesUnifiedSearch,
|
||||
HasUniqueId,
|
||||
PublishesDataViews,
|
||||
PublishesUnifiedSearch,
|
||||
apiCanAccessViewMode,
|
||||
apiHasUniqueId,
|
||||
apiPublishesPartialUnifiedSearch,
|
||||
getInheritedViewMode,
|
||||
getViewModeSubject,
|
||||
} from '@kbn/presentation-publishing';
|
||||
import { merge } from 'rxjs';
|
||||
import { pluginServices } from '../services/plugin_services';
|
||||
import { FiltersNotificationPopover } from './filters_notification_popover';
|
||||
import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
|
||||
|
||||
import { coreServices } from '../services/kibana_services';
|
||||
import { dashboardFilterNotificationActionStrings } from './_dashboard_actions_strings';
|
||||
import { FiltersNotificationPopover } from './filters_notification_popover';
|
||||
|
||||
export const BADGE_FILTERS_NOTIFICATION = 'ACTION_FILTERS_NOTIFICATION';
|
||||
|
||||
|
@ -58,18 +58,12 @@ export class FiltersNotificationAction implements Action<EmbeddableApiContext> {
|
|||
public readonly type = BADGE_FILTERS_NOTIFICATION;
|
||||
public readonly order = 2;
|
||||
|
||||
private settingsService;
|
||||
|
||||
constructor() {
|
||||
({ settings: this.settingsService } = pluginServices.getServices());
|
||||
}
|
||||
|
||||
public readonly MenuItem = ({ context }: { context: EmbeddableApiContext }) => {
|
||||
const { embeddable } = context;
|
||||
if (!isApiCompatible(embeddable)) throw new IncompatibleActionError();
|
||||
|
||||
const { Provider: KibanaReactContextProvider } = createKibanaReactContext({
|
||||
uiSettings: this.settingsService.uiSettings,
|
||||
uiSettings: coreServices.uiSettings,
|
||||
});
|
||||
|
||||
return (
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { CoreStart } from '@kbn/core/public';
|
||||
import { CONTEXT_MENU_TRIGGER, PANEL_NOTIFICATION_TRIGGER } from '@kbn/embeddable-plugin/public';
|
||||
import { DashboardStartDependencies } from '../plugin';
|
||||
import { AddToLibraryAction } from './add_to_library_action';
|
||||
|
@ -21,13 +20,11 @@ import { UnlinkFromLibraryAction } from './unlink_from_library_action';
|
|||
import { LegacyUnlinkFromLibraryAction } from './legacy_unlink_from_library_action';
|
||||
|
||||
interface BuildAllDashboardActionsProps {
|
||||
core: CoreStart;
|
||||
allowByValueEmbeddables?: boolean;
|
||||
plugins: DashboardStartDependencies;
|
||||
}
|
||||
|
||||
export const buildAllDashboardActions = async ({
|
||||
core,
|
||||
plugins,
|
||||
allowByValueEmbeddables,
|
||||
}: BuildAllDashboardActionsProps) => {
|
||||
|
@ -66,7 +63,7 @@ export const buildAllDashboardActions = async ({
|
|||
uiActions.registerAction(legacyUnlinkFromLibraryAction);
|
||||
uiActions.attachAction(CONTEXT_MENU_TRIGGER, legacyUnlinkFromLibraryAction.id);
|
||||
|
||||
const copyToDashboardAction = new CopyToDashboardAction(core);
|
||||
const copyToDashboardAction = new CopyToDashboardAction();
|
||||
uiActions.registerAction(copyToDashboardAction);
|
||||
uiActions.attachAction(CONTEXT_MENU_TRIGGER, copyToDashboardAction.id);
|
||||
}
|
||||
|
|
|
@ -9,11 +9,11 @@
|
|||
|
||||
import { PublishesViewMode, ViewMode } from '@kbn/presentation-publishing';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { pluginServices } from '../services/plugin_services';
|
||||
import {
|
||||
LegacyAddToLibraryAction,
|
||||
LegacyAddPanelToLibraryActionApi,
|
||||
} from './legacy_add_to_library_action';
|
||||
import { coreServices } from '../services/kibana_services';
|
||||
|
||||
describe('Add to library action', () => {
|
||||
let action: LegacyAddToLibraryAction;
|
||||
|
@ -62,7 +62,7 @@ describe('Add to library action', () => {
|
|||
|
||||
it('shows a toast with a title from the API when successful', async () => {
|
||||
await action.execute(context);
|
||||
expect(pluginServices.getServices().notifications.toasts.addSuccess).toHaveBeenCalledWith({
|
||||
expect(coreServices.notifications.toasts.addSuccess).toHaveBeenCalledWith({
|
||||
'data-test-subj': 'addPanelToLibrarySuccess',
|
||||
title: "Panel 'A very compatible API' was added to the library",
|
||||
});
|
||||
|
@ -71,7 +71,7 @@ describe('Add to library action', () => {
|
|||
it('shows a danger toast when the link operation is unsuccessful', async () => {
|
||||
context.embeddable.linkToLibrary = jest.fn().mockRejectedValue(new Error('Oh dang'));
|
||||
await action.execute(context);
|
||||
expect(pluginServices.getServices().notifications.toasts.addDanger).toHaveBeenCalledWith({
|
||||
expect(coreServices.notifications.toasts.addDanger).toHaveBeenCalledWith({
|
||||
'data-test-subj': 'addPanelToLibraryError',
|
||||
title: 'An error was encountered adding panel A very compatible API to the library',
|
||||
});
|
||||
|
|
|
@ -18,8 +18,9 @@ import {
|
|||
HasLegacyLibraryTransforms,
|
||||
} from '@kbn/presentation-publishing';
|
||||
import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
|
||||
import { pluginServices } from '../services/plugin_services';
|
||||
|
||||
import { dashboardAddToLibraryActionStrings } from './_dashboard_actions_strings';
|
||||
import { coreServices } from '../services/kibana_services';
|
||||
|
||||
export const ACTION_LEGACY_ADD_TO_LIBRARY = 'legacySaveToLibrary';
|
||||
|
||||
|
@ -35,14 +36,6 @@ export class LegacyAddToLibraryAction implements Action<EmbeddableApiContext> {
|
|||
public readonly id = ACTION_LEGACY_ADD_TO_LIBRARY;
|
||||
public order = 15;
|
||||
|
||||
private toastsService;
|
||||
|
||||
constructor() {
|
||||
({
|
||||
notifications: { toasts: this.toastsService },
|
||||
} = pluginServices.getServices());
|
||||
}
|
||||
|
||||
public getDisplayName({ embeddable }: EmbeddableApiContext) {
|
||||
if (!isApiCompatible(embeddable)) throw new IncompatibleActionError();
|
||||
return dashboardAddToLibraryActionStrings.getDisplayName();
|
||||
|
@ -63,14 +56,14 @@ export class LegacyAddToLibraryAction implements Action<EmbeddableApiContext> {
|
|||
const panelTitle = getPanelTitle(embeddable);
|
||||
try {
|
||||
await embeddable.linkToLibrary();
|
||||
this.toastsService.addSuccess({
|
||||
coreServices.notifications.toasts.addSuccess({
|
||||
title: dashboardAddToLibraryActionStrings.getSuccessMessage(
|
||||
panelTitle ? `'${panelTitle}'` : ''
|
||||
),
|
||||
'data-test-subj': 'addPanelToLibrarySuccess',
|
||||
});
|
||||
} catch (e) {
|
||||
this.toastsService.addDanger({
|
||||
coreServices.notifications.toasts.addDanger({
|
||||
title: dashboardAddToLibraryActionStrings.getErrorMessage(panelTitle),
|
||||
'data-test-subj': 'addPanelToLibraryError',
|
||||
});
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
import { PublishesViewMode, ViewMode } from '@kbn/presentation-publishing';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { pluginServices } from '../services/plugin_services';
|
||||
import { coreServices } from '../services/kibana_services';
|
||||
import {
|
||||
LegacyUnlinkFromLibraryAction,
|
||||
LegacyUnlinkPanelFromLibraryActionApi,
|
||||
|
@ -61,7 +61,7 @@ describe('Unlink from library action', () => {
|
|||
|
||||
it('shows a toast with a title from the API when successful', async () => {
|
||||
await action.execute(context);
|
||||
expect(pluginServices.getServices().notifications.toasts.addSuccess).toHaveBeenCalledWith({
|
||||
expect(coreServices.notifications.toasts.addSuccess).toHaveBeenCalledWith({
|
||||
'data-test-subj': 'unlinkPanelSuccess',
|
||||
title: "Panel 'A very compatible API' is no longer connected to the library.",
|
||||
});
|
||||
|
@ -70,7 +70,7 @@ describe('Unlink from library action', () => {
|
|||
it('shows a danger toast when the link operation is unsuccessful', async () => {
|
||||
context.embeddable.unlinkFromLibrary = jest.fn().mockRejectedValue(new Error('Oh dang'));
|
||||
await action.execute(context);
|
||||
expect(pluginServices.getServices().notifications.toasts.addDanger).toHaveBeenCalledWith({
|
||||
expect(coreServices.notifications.toasts.addDanger).toHaveBeenCalledWith({
|
||||
'data-test-subj': 'unlinkPanelFailure',
|
||||
title: "An error occured while unlinking 'A very compatible API' from the library.",
|
||||
});
|
||||
|
|
|
@ -19,8 +19,8 @@ import {
|
|||
PublishesPanelTitle,
|
||||
HasLegacyLibraryTransforms,
|
||||
} from '@kbn/presentation-publishing';
|
||||
import { pluginServices } from '../services/plugin_services';
|
||||
import { dashboardUnlinkFromLibraryActionStrings } from './_dashboard_actions_strings';
|
||||
import { coreServices } from '../services/kibana_services';
|
||||
|
||||
export const ACTION_LEGACY_UNLINK_FROM_LIBRARY = 'legacyUnlinkFromLibrary';
|
||||
|
||||
|
@ -38,14 +38,6 @@ export class LegacyUnlinkFromLibraryAction implements Action<EmbeddableApiContex
|
|||
public readonly id = ACTION_LEGACY_UNLINK_FROM_LIBRARY;
|
||||
public order = 15;
|
||||
|
||||
private toastsService;
|
||||
|
||||
constructor() {
|
||||
({
|
||||
notifications: { toasts: this.toastsService },
|
||||
} = pluginServices.getServices());
|
||||
}
|
||||
|
||||
public getDisplayName({ embeddable }: EmbeddableApiContext) {
|
||||
if (!legacyUnlinkActionIsCompatible(embeddable)) throw new IncompatibleActionError();
|
||||
return dashboardUnlinkFromLibraryActionStrings.getDisplayName();
|
||||
|
@ -66,12 +58,12 @@ export class LegacyUnlinkFromLibraryAction implements Action<EmbeddableApiContex
|
|||
const title = getPanelTitle(embeddable);
|
||||
try {
|
||||
await embeddable.unlinkFromLibrary();
|
||||
this.toastsService.addSuccess({
|
||||
coreServices.notifications.toasts.addSuccess({
|
||||
title: dashboardUnlinkFromLibraryActionStrings.getSuccessMessage(title ? `'${title}'` : ''),
|
||||
'data-test-subj': 'unlinkPanelSuccess',
|
||||
});
|
||||
} catch (e) {
|
||||
this.toastsService.addDanger({
|
||||
coreServices.notifications.toasts.addDanger({
|
||||
title: dashboardUnlinkFromLibraryActionStrings.getFailureMessage(title ? `'${title}'` : ''),
|
||||
'data-test-subj': 'unlinkPanelFailure',
|
||||
});
|
||||
|
|
|
@ -7,28 +7,28 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
|
||||
|
||||
import { PresentationContainer } from '@kbn/presentation-containers';
|
||||
import {
|
||||
apiCanAccessViewMode,
|
||||
apiHasLibraryTransforms,
|
||||
CanAccessViewMode,
|
||||
EmbeddableApiContext,
|
||||
getInheritedViewMode,
|
||||
getPanelTitle,
|
||||
PublishesPanelTitle,
|
||||
HasInPlaceLibraryTransforms,
|
||||
HasLibraryTransforms,
|
||||
HasParentApi,
|
||||
apiHasParentApi,
|
||||
HasUniqueId,
|
||||
apiHasUniqueId,
|
||||
HasType,
|
||||
apiHasType,
|
||||
HasInPlaceLibraryTransforms,
|
||||
HasUniqueId,
|
||||
PublishesPanelTitle,
|
||||
apiCanAccessViewMode,
|
||||
apiHasInPlaceLibraryTransforms,
|
||||
apiHasLibraryTransforms,
|
||||
apiHasParentApi,
|
||||
apiHasType,
|
||||
apiHasUniqueId,
|
||||
getInheritedViewMode,
|
||||
getPanelTitle,
|
||||
} from '@kbn/presentation-publishing';
|
||||
import { PresentationContainer } from '@kbn/presentation-containers';
|
||||
import { pluginServices } from '../services/plugin_services';
|
||||
import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
|
||||
|
||||
import { coreServices } from '../services/kibana_services';
|
||||
import { dashboardUnlinkFromLibraryActionStrings } from './_dashboard_actions_strings';
|
||||
|
||||
export const ACTION_UNLINK_FROM_LIBRARY = 'unlinkFromLibrary';
|
||||
|
@ -55,14 +55,6 @@ export class UnlinkFromLibraryAction implements Action<EmbeddableApiContext> {
|
|||
public readonly id = ACTION_UNLINK_FROM_LIBRARY;
|
||||
public order = 15;
|
||||
|
||||
private toastsService;
|
||||
|
||||
constructor() {
|
||||
({
|
||||
notifications: { toasts: this.toastsService },
|
||||
} = pluginServices.getServices());
|
||||
}
|
||||
|
||||
public getDisplayName({ embeddable }: EmbeddableApiContext) {
|
||||
if (!isApiCompatible(embeddable)) throw new IncompatibleActionError();
|
||||
return dashboardUnlinkFromLibraryActionStrings.getDisplayName();
|
||||
|
@ -107,12 +99,12 @@ export class UnlinkFromLibraryAction implements Action<EmbeddableApiContext> {
|
|||
} else {
|
||||
throw new IncompatibleActionError();
|
||||
}
|
||||
this.toastsService.addSuccess({
|
||||
coreServices.notifications.toasts.addSuccess({
|
||||
title: dashboardUnlinkFromLibraryActionStrings.getSuccessMessage(title ? `'${title}'` : ''),
|
||||
'data-test-subj': 'unlinkPanelSuccess',
|
||||
});
|
||||
} catch (e) {
|
||||
this.toastsService.addDanger({
|
||||
coreServices.notifications.toasts.addDanger({
|
||||
title: dashboardUnlinkFromLibraryActionStrings.getFailureMessage(title ? `'${title}'` : ''),
|
||||
'data-test-subj': 'unlinkPanelFailure',
|
||||
});
|
||||
|
|
|
@ -31,7 +31,7 @@ import { ControlGroupApi, ControlGroupSerializedState } from '@kbn/controls-plug
|
|||
import { Filter, Query, TimeRange } from '@kbn/es-query';
|
||||
import { DefaultEmbeddableApi, ErrorEmbeddable, IEmbeddable } from '@kbn/embeddable-plugin/public';
|
||||
import { DashboardPanelMap, DashboardPanelState } from '../../common';
|
||||
import { SaveDashboardReturn } from '../services/dashboard_content_management/types';
|
||||
import { SaveDashboardReturn } from '../services/dashboard_content_management_service/types';
|
||||
import { DashboardStateFromSettingsFlyout, UnsavedPanelState } from '../dashboard_container/types';
|
||||
|
||||
export type DashboardApi = CanExpandPanels &
|
||||
|
|
|
@ -7,22 +7,25 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { render, waitFor } from '@testing-library/react';
|
||||
import { MemoryHistory, createMemoryHistory } from 'history';
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
import { render, waitFor } from '@testing-library/react';
|
||||
|
||||
import { DashboardApi } from '..';
|
||||
import type { DashboardRendererProps } from '../dashboard_container/external_api/dashboard_renderer';
|
||||
import { LazyDashboardRenderer } from '../dashboard_container/external_api/lazy_dashboard_renderer';
|
||||
import { DashboardTopNav } from '../dashboard_top_nav';
|
||||
import { buildMockDashboard } from '../mocks';
|
||||
import { dataService } from '../services/kibana_services';
|
||||
import { DashboardApp } from './dashboard_app';
|
||||
|
||||
import * as dashboardRendererStuff from '../dashboard_container/external_api/lazy_dashboard_renderer';
|
||||
import { DashboardApi } from '..';
|
||||
|
||||
/* These tests circumvent the need to test the router and legacy code
|
||||
/* the dashboard app will be passed the expanded panel id from the DashboardRouter through mountApp()
|
||||
/* @link https://github.com/elastic/kibana/pull/190086/
|
||||
*/
|
||||
jest.mock('../dashboard_container/external_api/lazy_dashboard_renderer');
|
||||
jest.mock('../dashboard_top_nav');
|
||||
|
||||
describe('Dashboard App', () => {
|
||||
dataService.query.filterManager.getFilters = jest.fn().mockImplementation(() => []);
|
||||
|
||||
const mockDashboard = buildMockDashboard();
|
||||
let mockHistory: MemoryHistory;
|
||||
// this is in url_utils dashboardApi expandedPanel subscription
|
||||
|
@ -35,19 +38,20 @@ describe('Dashboard App', () => {
|
|||
historySpy = jest.spyOn(mockHistory, 'replace');
|
||||
|
||||
/**
|
||||
* Mock the LazyDashboardRenderer component to avoid rendering the actual dashboard
|
||||
* Mock the DashboardTopNav + LazyDashboardRenderer component to avoid rendering the actual dashboard
|
||||
* and hitting errors that aren't relevant
|
||||
*/
|
||||
jest
|
||||
.spyOn(dashboardRendererStuff, 'LazyDashboardRenderer')
|
||||
// we need overwrite the onApiAvailable prop to get the dashboard Api in the dashboard app
|
||||
.mockImplementation(({ onApiAvailable }: DashboardRendererProps) => {
|
||||
(DashboardTopNav as jest.Mock).mockImplementation(() => <>Top nav</>);
|
||||
(LazyDashboardRenderer as jest.Mock).mockImplementation(
|
||||
({ onApiAvailable }: DashboardRendererProps) => {
|
||||
// we need overwrite the onApiAvailable prop to get access to the dashboard API in this test
|
||||
useEffect(() => {
|
||||
onApiAvailable?.(mockDashboard as DashboardApi);
|
||||
}, [onApiAvailable]);
|
||||
|
||||
return <div>Test renderer</div>;
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -7,46 +7,52 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { History } from 'history';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import useMount from 'react-use/lib/useMount';
|
||||
import useObservable from 'react-use/lib/useObservable';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { debounceTime } from 'rxjs';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { DASHBOARD_APP_LOCATOR } from '@kbn/deeplinks-analytics';
|
||||
import { ViewMode } from '@kbn/embeddable-plugin/public';
|
||||
import { useExecutionContext } from '@kbn/kibana-react-plugin/public';
|
||||
import { createKbnUrlStateStorage, withNotifyOnErrors } from '@kbn/kibana-utils-plugin/public';
|
||||
|
||||
import { DASHBOARD_APP_LOCATOR } from '@kbn/deeplinks-analytics';
|
||||
import { debounceTime } from 'rxjs';
|
||||
import { DashboardApi, DashboardRenderer } from '..';
|
||||
import { SharedDashboardState } from '../../common';
|
||||
import {
|
||||
DASHBOARD_APP_ID,
|
||||
DASHBOARD_STATE_STORAGE_KEY,
|
||||
createDashboardEditUrl,
|
||||
} from '../dashboard_constants';
|
||||
import type { DashboardCreationOptions } from '../dashboard_container/embeddable/dashboard_container_factory';
|
||||
import { DashboardRedirect } from '../dashboard_container/types';
|
||||
import { DashboardTopNav } from '../dashboard_top_nav';
|
||||
import {
|
||||
coreServices,
|
||||
dataService,
|
||||
embeddableService,
|
||||
screenshotModeService,
|
||||
shareService,
|
||||
} from '../services/kibana_services';
|
||||
import { useDashboardMountContext } from './hooks/dashboard_mount_context';
|
||||
import { useDashboardOutcomeValidation } from './hooks/use_dashboard_outcome_validation';
|
||||
import { useObservabilityAIAssistantContext } from './hooks/use_observability_ai_assistant_context';
|
||||
import { loadDashboardHistoryLocationState } from './locator/load_dashboard_history_location_state';
|
||||
import {
|
||||
DashboardAppNoDataPage,
|
||||
isDashboardAppInNoDataState,
|
||||
} from './no_data/dashboard_app_no_data';
|
||||
import { loadAndRemoveDashboardState, startSyncingExpandedPanelState } from './url/url_utils';
|
||||
import {
|
||||
getSessionURLObservable,
|
||||
getSearchSessionIdFromURL,
|
||||
removeSearchSessionIdFromURL,
|
||||
createSessionRestorationDataProvider,
|
||||
} from './url/search_sessions_integration';
|
||||
import { DashboardApi, DashboardRenderer } from '..';
|
||||
import { type DashboardEmbedSettings } from './types';
|
||||
import { pluginServices } from '../services/plugin_services';
|
||||
import { DashboardRedirect } from '../dashboard_container/types';
|
||||
import { useDashboardMountContext } from './hooks/dashboard_mount_context';
|
||||
import {
|
||||
createDashboardEditUrl,
|
||||
DASHBOARD_APP_ID,
|
||||
DASHBOARD_STATE_STORAGE_KEY,
|
||||
} from '../dashboard_constants';
|
||||
import { useDashboardOutcomeValidation } from './hooks/use_dashboard_outcome_validation';
|
||||
import { loadDashboardHistoryLocationState } from './locator/load_dashboard_history_location_state';
|
||||
import type { DashboardCreationOptions } from '../dashboard_container/embeddable/dashboard_container_factory';
|
||||
import { DashboardTopNav } from '../dashboard_top_nav';
|
||||
import { DashboardTabTitleSetter } from './tab_title_setter/dashboard_tab_title_setter';
|
||||
import { useObservabilityAIAssistantContext } from './hooks/use_observability_ai_assistant_context';
|
||||
import { SharedDashboardState } from '../../common';
|
||||
import { type DashboardEmbedSettings } from './types';
|
||||
import {
|
||||
createSessionRestorationDataProvider,
|
||||
getSearchSessionIdFromURL,
|
||||
getSessionURLObservable,
|
||||
removeSearchSessionIdFromURL,
|
||||
} from './url/search_sessions_integration';
|
||||
import { loadAndRemoveDashboardState, startSyncingExpandedPanelState } from './url/url_utils';
|
||||
|
||||
export interface DashboardAppProps {
|
||||
history: History;
|
||||
|
@ -71,31 +77,15 @@ export function DashboardApp({
|
|||
});
|
||||
const [dashboardApi, setDashboardApi] = useState<DashboardApi | undefined>(undefined);
|
||||
|
||||
/**
|
||||
* Unpack & set up dashboard services
|
||||
*/
|
||||
const {
|
||||
screenshotMode: { isScreenshotMode, getScreenshotContext },
|
||||
coreContext: { executionContext },
|
||||
embeddable: { getStateTransfer },
|
||||
notifications: { toasts },
|
||||
settings: { uiSettings },
|
||||
data: { search, dataViews },
|
||||
customBranding,
|
||||
share: { url },
|
||||
observabilityAIAssistant,
|
||||
} = pluginServices.getServices();
|
||||
const showPlainSpinner = useObservable(customBranding.hasCustomBranding$, false);
|
||||
const showPlainSpinner = useObservable(coreServices.customBranding.hasCustomBranding$, false);
|
||||
|
||||
const { scopedHistory: getScopedHistory } = useDashboardMountContext();
|
||||
|
||||
useObservabilityAIAssistantContext({
|
||||
observabilityAIAssistant: observabilityAIAssistant.start,
|
||||
dashboardApi,
|
||||
search,
|
||||
dataViews,
|
||||
});
|
||||
|
||||
useExecutionContext(executionContext, {
|
||||
useExecutionContext(coreServices.executionContext, {
|
||||
type: 'application',
|
||||
page: 'app',
|
||||
id: savedDashboardId || 'new',
|
||||
|
@ -105,10 +95,10 @@ export function DashboardApp({
|
|||
() =>
|
||||
createKbnUrlStateStorage({
|
||||
history,
|
||||
useHash: uiSettings.get('state:storeInSessionStorage'),
|
||||
...withNotifyOnErrors(toasts),
|
||||
useHash: coreServices.uiSettings.get('state:storeInSessionStorage'),
|
||||
...withNotifyOnErrors(coreServices.notifications.toasts),
|
||||
}),
|
||||
[toasts, history, uiSettings]
|
||||
[history]
|
||||
);
|
||||
|
||||
/**
|
||||
|
@ -116,9 +106,9 @@ export function DashboardApp({
|
|||
*/
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
search.session.clear();
|
||||
dataService.search.session.clear();
|
||||
};
|
||||
}, [search.session]);
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Validate saved object load outcome
|
||||
|
@ -141,7 +131,8 @@ export function DashboardApp({
|
|||
...stateFromLocator,
|
||||
|
||||
// if print mode is active, force viewMode.PRINT
|
||||
...(isScreenshotMode() && getScreenshotContext('layout') === 'print'
|
||||
...(screenshotModeService.isScreenshotMode() &&
|
||||
screenshotModeService.getScreenshotContext('layout') === 'print'
|
||||
? { viewMode: ViewMode.PRINT }
|
||||
: {}),
|
||||
};
|
||||
|
@ -149,7 +140,7 @@ export function DashboardApp({
|
|||
|
||||
return Promise.resolve<DashboardCreationOptions>({
|
||||
getIncomingEmbeddable: () =>
|
||||
getStateTransfer().getIncomingEmbeddablePackage(DASHBOARD_APP_ID, true),
|
||||
embeddableService.getStateTransfer().getIncomingEmbeddablePackage(DASHBOARD_APP_ID, true),
|
||||
|
||||
// integrations
|
||||
useControlGroupIntegration: true,
|
||||
|
@ -174,16 +165,7 @@ export function DashboardApp({
|
|||
getCurrentPath: () => `#${createDashboardEditUrl(dashboardId)}`,
|
||||
}),
|
||||
});
|
||||
}, [
|
||||
history,
|
||||
embedSettings,
|
||||
validateOutcome,
|
||||
getScopedHistory,
|
||||
isScreenshotMode,
|
||||
getStateTransfer,
|
||||
kbnUrlStateStorage,
|
||||
getScreenshotContext,
|
||||
]);
|
||||
}, [history, embedSettings, validateOutcome, getScopedHistory, kbnUrlStateStorage]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!dashboardApi) return;
|
||||
|
@ -208,7 +190,7 @@ export function DashboardApp({
|
|||
return () => appStateSubscription.unsubscribe();
|
||||
}, [dashboardApi, kbnUrlStateStorage, savedDashboardId]);
|
||||
|
||||
const locator = useMemo(() => url?.locators.get(DASHBOARD_APP_LOCATOR), [url]);
|
||||
const locator = useMemo(() => shareService?.url.locators.get(DASHBOARD_APP_LOCATOR), []);
|
||||
|
||||
return showNoDataPage ? (
|
||||
<DashboardAppNoDataPage onDataViewCreated={() => setShowNoDataPage(false)} />
|
||||
|
|
|
@ -9,32 +9,33 @@
|
|||
|
||||
import './_dashboard_app.scss';
|
||||
|
||||
import React from 'react';
|
||||
import { parse, ParsedQuery } from 'query-string';
|
||||
import { render, unmountComponentAtNode } from 'react-dom';
|
||||
import { HashRouter, RouteComponentProps, Redirect } from 'react-router-dom';
|
||||
import { Routes, Route } from '@kbn/shared-ux-router';
|
||||
import { ViewMode } from '@kbn/embeddable-plugin/public';
|
||||
import { AppMountParameters, CoreStart } from '@kbn/core/public';
|
||||
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
|
||||
import { ViewMode } from '@kbn/embeddable-plugin/public';
|
||||
import { createKbnUrlStateStorage, withNotifyOnErrors } from '@kbn/kibana-utils-plugin/public';
|
||||
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
|
||||
import { Route, Routes } from '@kbn/shared-ux-router';
|
||||
import { parse, ParsedQuery } from 'query-string';
|
||||
import React from 'react';
|
||||
import { render, unmountComponentAtNode } from 'react-dom';
|
||||
import { HashRouter, Redirect, RouteComponentProps } from 'react-router-dom';
|
||||
|
||||
import {
|
||||
createDashboardListingFilterUrl,
|
||||
CREATE_NEW_DASHBOARD_URL,
|
||||
createDashboardEditUrl,
|
||||
createDashboardListingFilterUrl,
|
||||
DASHBOARD_APP_ID,
|
||||
LANDING_PAGE_PATH,
|
||||
VIEW_DASHBOARD_URL,
|
||||
} from '../dashboard_constants';
|
||||
import { DashboardApp } from './dashboard_app';
|
||||
import { pluginServices } from '../services/plugin_services';
|
||||
import { RedirectToProps } from '../dashboard_container/types';
|
||||
import { createDashboardEditUrl } from '../dashboard_constants';
|
||||
import { DashboardNoMatch } from './listing_page/dashboard_no_match';
|
||||
import { DashboardMountContext } from './hooks/dashboard_mount_context';
|
||||
import { DashboardEmbedSettings, DashboardMountContextProps } from './types';
|
||||
import { DashboardListingPage } from './listing_page/dashboard_listing_page';
|
||||
import { coreServices, dataService, embeddableService } from '../services/kibana_services';
|
||||
import { getDashboardCapabilities } from '../utils/get_dashboard_capabilities';
|
||||
import { dashboardReadonlyBadge, getDashboardPageTitle } from './_dashboard_app_strings';
|
||||
import { DashboardApp } from './dashboard_app';
|
||||
import { DashboardMountContext } from './hooks/dashboard_mount_context';
|
||||
import { DashboardListingPage } from './listing_page/dashboard_listing_page';
|
||||
import { DashboardNoMatch } from './listing_page/dashboard_no_match';
|
||||
import { DashboardEmbedSettings, DashboardMountContextProps } from './types';
|
||||
|
||||
export const dashboardUrlParams = {
|
||||
showTopMenu: 'show-top-menu',
|
||||
|
@ -56,24 +57,13 @@ export async function mountApp({
|
|||
appUnMounted,
|
||||
mountContext,
|
||||
}: DashboardMountProps) {
|
||||
const {
|
||||
chrome: { setBadge, docTitle, setHelpExtension },
|
||||
dashboardCapabilities: { showWriteControls },
|
||||
documentationLinks: { dashboardDocLink },
|
||||
application: { navigateToApp },
|
||||
settings: { uiSettings },
|
||||
data: dataStart,
|
||||
notifications,
|
||||
embeddable,
|
||||
} = pluginServices.getServices();
|
||||
|
||||
let globalEmbedSettings: DashboardEmbedSettings | undefined;
|
||||
|
||||
const getUrlStateStorage = (history: RouteComponentProps['history']) =>
|
||||
createKbnUrlStateStorage({
|
||||
history,
|
||||
useHash: uiSettings.get('state:storeInSessionStorage'),
|
||||
...withNotifyOnErrors(notifications.toasts),
|
||||
useHash: coreServices.uiSettings.get('state:storeInSessionStorage'),
|
||||
...withNotifyOnErrors(coreServices.notifications.toasts),
|
||||
});
|
||||
|
||||
const redirect = (redirectTo: RedirectToProps) => {
|
||||
|
@ -87,7 +77,11 @@ export async function mountApp({
|
|||
} else {
|
||||
path = createDashboardListingFilterUrl(redirectTo.filter);
|
||||
}
|
||||
navigateToApp(DASHBOARD_APP_ID, { path: `#/${path}`, state, replace: redirectTo.useReplace });
|
||||
coreServices.application.navigateToApp(DASHBOARD_APP_ID, {
|
||||
path: `#/${path}`,
|
||||
state,
|
||||
replace: redirectTo.useReplace,
|
||||
});
|
||||
};
|
||||
|
||||
const getDashboardEmbedSettings = (
|
||||
|
@ -120,7 +114,7 @@ export async function mountApp({
|
|||
};
|
||||
|
||||
const renderListingPage = (routeProps: RouteComponentProps) => {
|
||||
docTitle.change(getDashboardPageTitle());
|
||||
coreServices.chrome.docTitle.change(getDashboardPageTitle());
|
||||
const routeParams = parse(routeProps.history.location.search);
|
||||
const title = (routeParams.title as string) || undefined;
|
||||
const filter = (routeParams.filter as string) || undefined;
|
||||
|
@ -139,10 +133,10 @@ export async function mountApp({
|
|||
};
|
||||
|
||||
const hasEmbeddableIncoming = Boolean(
|
||||
embeddable.getStateTransfer().getIncomingEmbeddablePackage(DASHBOARD_APP_ID, false)
|
||||
embeddableService.getStateTransfer().getIncomingEmbeddablePackage(DASHBOARD_APP_ID, false)
|
||||
);
|
||||
if (!hasEmbeddableIncoming) {
|
||||
dataStart.dataViews.clearCache();
|
||||
dataService.dataViews.clearCache();
|
||||
}
|
||||
|
||||
// dispatch synthetic hash change event to update hash history objects
|
||||
|
@ -175,18 +169,18 @@ export async function mountApp({
|
|||
</KibanaRenderContextProvider>
|
||||
);
|
||||
|
||||
setHelpExtension({
|
||||
coreServices.chrome.setHelpExtension({
|
||||
appName: getDashboardPageTitle(),
|
||||
links: [
|
||||
{
|
||||
linkType: 'documentation',
|
||||
href: `${dashboardDocLink}`,
|
||||
href: `${coreServices.docLinks.links.dashboard.guide}`,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
if (!showWriteControls) {
|
||||
setBadge({
|
||||
if (!getDashboardCapabilities().showWriteControls) {
|
||||
coreServices.chrome.setBadge({
|
||||
text: dashboardReadonlyBadge.getText(),
|
||||
tooltip: dashboardReadonlyBadge.getTooltip(),
|
||||
iconType: 'glasses',
|
||||
|
@ -194,7 +188,7 @@ export async function mountApp({
|
|||
}
|
||||
render(app, element);
|
||||
return () => {
|
||||
dataStart.search.session.clear();
|
||||
dataService.search.session.clear();
|
||||
unlistenParentHistory();
|
||||
unmountComponentAtNode(element);
|
||||
appUnMounted();
|
||||
|
|
|
@ -9,11 +9,11 @@
|
|||
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { pluginServices } from '../../services/plugin_services';
|
||||
import { createDashboardEditUrl } from '../../dashboard_constants';
|
||||
import { useDashboardMountContext } from './dashboard_mount_context';
|
||||
import { LoadDashboardReturn } from '../../services/dashboard_content_management/types';
|
||||
import { DashboardCreationOptions } from '../..';
|
||||
import { createDashboardEditUrl } from '../../dashboard_constants';
|
||||
import { LoadDashboardReturn } from '../../services/dashboard_content_management_service/types';
|
||||
import { screenshotModeService, spacesService } from '../../services/kibana_services';
|
||||
import { useDashboardMountContext } from './dashboard_mount_context';
|
||||
|
||||
export const useDashboardOutcomeValidation = () => {
|
||||
const [aliasId, setAliasId] = useState<string>();
|
||||
|
@ -23,11 +23,6 @@ export const useDashboardOutcomeValidation = () => {
|
|||
const { scopedHistory: getScopedHistory } = useDashboardMountContext();
|
||||
const scopedHistory = getScopedHistory?.();
|
||||
|
||||
/**
|
||||
* Unpack dashboard services
|
||||
*/
|
||||
const { screenshotMode, spaces } = pluginServices.getServices();
|
||||
|
||||
const validateOutcome: DashboardCreationOptions['validateLoadedSavedObject'] = useCallback(
|
||||
({ dashboardFound, resolveMeta, dashboardId }: LoadDashboardReturn) => {
|
||||
if (!dashboardFound) {
|
||||
|
@ -41,10 +36,10 @@ export const useDashboardOutcomeValidation = () => {
|
|||
*/
|
||||
if (loadOutcome === 'aliasMatch' && dashboardId && alias) {
|
||||
const path = scopedHistory.location.hash.replace(dashboardId, alias);
|
||||
if (screenshotMode.isScreenshotMode()) {
|
||||
if (screenshotModeService.isScreenshotMode()) {
|
||||
scopedHistory.replace(path); // redirect without the toast when in screenshot mode.
|
||||
} else {
|
||||
spaces.redirectLegacyUrl?.({ path, aliasPurpose });
|
||||
spacesService?.ui.redirectLegacyUrl({ path, aliasPurpose });
|
||||
}
|
||||
return 'redirected'; // redirected. Stop loading dashboard.
|
||||
}
|
||||
|
@ -54,20 +49,20 @@ export const useDashboardOutcomeValidation = () => {
|
|||
}
|
||||
return 'valid';
|
||||
},
|
||||
[scopedHistory, screenshotMode, spaces]
|
||||
[scopedHistory]
|
||||
);
|
||||
|
||||
const getLegacyConflictWarning = useMemo(() => {
|
||||
if (savedObjectId && outcome === 'conflict' && aliasId) {
|
||||
return () =>
|
||||
spaces.getLegacyUrlConflict?.({
|
||||
spacesService?.ui.components.getLegacyUrlConflict({
|
||||
currentObjectId: savedObjectId,
|
||||
otherObjectId: aliasId,
|
||||
otherObjectPath: `#${createDashboardEditUrl(aliasId)}${scopedHistory.location.search}`,
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}, [aliasId, outcome, savedObjectId, scopedHistory, spaces]);
|
||||
}, [aliasId, outcome, savedObjectId, scopedHistory]);
|
||||
|
||||
return { validateOutcome, getLegacyConflictWarning };
|
||||
};
|
||||
|
|
|
@ -7,28 +7,26 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import type { ObservabilityAIAssistantPublicStart } from '@kbn/observability-ai-assistant-plugin/public';
|
||||
import { useEffect } from 'react';
|
||||
import { getESQLQueryColumns } from '@kbn/esql-utils';
|
||||
import type { ISearchStart } from '@kbn/data-plugin/public';
|
||||
import {
|
||||
LensConfigBuilder,
|
||||
LensDataset,
|
||||
type LensConfig,
|
||||
type LensMetricConfig,
|
||||
type LensPieConfig,
|
||||
type LensGaugeConfig,
|
||||
type LensXYConfig,
|
||||
type LensHeatmapConfig,
|
||||
type LensMetricConfig,
|
||||
type LensMosaicConfig,
|
||||
type LensPieConfig,
|
||||
type LensRegionMapConfig,
|
||||
type LensTableConfig,
|
||||
type LensTagCloudConfig,
|
||||
type LensTreeMapConfig,
|
||||
LensDataset,
|
||||
type LensXYConfig,
|
||||
} from '@kbn/lens-embeddable-utils/config_builder';
|
||||
import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
|
||||
import { LensEmbeddableInput } from '@kbn/lens-plugin/public';
|
||||
import { useEffect } from 'react';
|
||||
import { DashboardApi } from '../../dashboard_api/types';
|
||||
import { dataService, observabilityAssistantService } from '../../services/kibana_services';
|
||||
|
||||
const chartTypes = [
|
||||
'xy',
|
||||
|
@ -45,25 +43,19 @@ const chartTypes = [
|
|||
] as const;
|
||||
|
||||
export function useObservabilityAIAssistantContext({
|
||||
observabilityAIAssistant,
|
||||
dashboardApi,
|
||||
search,
|
||||
dataViews,
|
||||
}: {
|
||||
observabilityAIAssistant: ObservabilityAIAssistantPublicStart | undefined;
|
||||
dashboardApi: DashboardApi | undefined;
|
||||
search: ISearchStart;
|
||||
dataViews: DataViewsPublicPluginStart;
|
||||
}) {
|
||||
useEffect(() => {
|
||||
if (!observabilityAIAssistant) {
|
||||
if (!observabilityAssistantService) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
service: { setScreenContext },
|
||||
createScreenContextAction,
|
||||
} = observabilityAIAssistant;
|
||||
} = observabilityAssistantService;
|
||||
|
||||
return setScreenContext({
|
||||
screenDescription:
|
||||
|
@ -205,12 +197,12 @@ export function useObservabilityAIAssistantContext({
|
|||
const [columns] = await Promise.all([
|
||||
getESQLQueryColumns({
|
||||
esqlQuery: query,
|
||||
search: search.search,
|
||||
search: dataService.search.search,
|
||||
signal,
|
||||
}),
|
||||
]);
|
||||
|
||||
const configBuilder = new LensConfigBuilder(dataViews);
|
||||
const configBuilder = new LensConfigBuilder(dataService.dataViews);
|
||||
|
||||
let config: LensConfig;
|
||||
|
||||
|
@ -382,5 +374,5 @@ export function useObservabilityAIAssistantContext({
|
|||
]
|
||||
: [],
|
||||
});
|
||||
}, [observabilityAIAssistant, dashboardApi, search, dataViews]);
|
||||
}, [dashboardApi]);
|
||||
}
|
||||
|
|
|
@ -13,7 +13,6 @@ import { mount, ReactWrapper, ComponentType } from 'enzyme';
|
|||
import { I18nProvider } from '@kbn/i18n-react';
|
||||
import { createKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public';
|
||||
|
||||
import { pluginServices } from '../../services/plugin_services';
|
||||
import { DashboardListingPage, DashboardListingPageProps } from './dashboard_listing_page';
|
||||
|
||||
// Mock child components. The Dashboard listing page mostly passes down props to shared UX components which are tested in their own packages.
|
||||
|
@ -26,7 +25,12 @@ jest.mock('../../dashboard_listing/dashboard_listing', () => {
|
|||
});
|
||||
|
||||
import { DashboardAppNoDataPage } from '../no_data/dashboard_app_no_data';
|
||||
import { dataService } from '../../services/kibana_services';
|
||||
import { getDashboardContentManagementService } from '../../services/dashboard_content_management_service';
|
||||
|
||||
const dashboardContentManagementService = getDashboardContentManagementService();
|
||||
const mockIsDashboardAppInNoDataState = jest.fn().mockResolvedValue(false);
|
||||
|
||||
jest.mock('../no_data/dashboard_app_no_data', () => {
|
||||
const originalModule = jest.requireActual('../no_data/dashboard_app_no_data');
|
||||
return {
|
||||
|
@ -59,9 +63,7 @@ function mountWith({ props: incomingProps }: { props?: DashboardListingPageProps
|
|||
|
||||
test('renders analytics no data page when the user has no data view', async () => {
|
||||
mockIsDashboardAppInNoDataState.mockResolvedValueOnce(true);
|
||||
pluginServices.getServices().data.dataViews.hasData.hasUserDataView = jest
|
||||
.fn()
|
||||
.mockResolvedValue(false);
|
||||
dataService.dataViews.hasData.hasUserDataView = jest.fn().mockResolvedValue(false);
|
||||
|
||||
let component: ReactWrapper;
|
||||
await act(async () => {
|
||||
|
@ -93,9 +95,9 @@ test('When given a title that matches multiple dashboards, filter on the title',
|
|||
const props = makeDefaultProps();
|
||||
props.title = title;
|
||||
|
||||
(
|
||||
pluginServices.getServices().dashboardContentManagement.findDashboards.findByTitle as jest.Mock
|
||||
).mockResolvedValue(undefined);
|
||||
(dashboardContentManagementService.findDashboards.findByTitle as jest.Mock).mockResolvedValue(
|
||||
undefined
|
||||
);
|
||||
|
||||
let component: ReactWrapper;
|
||||
|
||||
|
@ -115,9 +117,9 @@ test('When given a title that matches one dashboard, redirect to dashboard', asy
|
|||
const title = 'search by title';
|
||||
const props = makeDefaultProps();
|
||||
props.title = title;
|
||||
(
|
||||
pluginServices.getServices().dashboardContentManagement.findDashboards.findByTitle as jest.Mock
|
||||
).mockResolvedValue({ id: 'you_found_me' });
|
||||
(dashboardContentManagementService.findDashboards.findByTitle as jest.Mock).mockResolvedValue({
|
||||
id: 'you_found_me',
|
||||
});
|
||||
|
||||
let component: ReactWrapper;
|
||||
|
||||
|
|
|
@ -9,19 +9,20 @@
|
|||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { ViewMode } from '@kbn/embeddable-plugin/public';
|
||||
import { syncGlobalQueryStateWithUrl } from '@kbn/data-plugin/public';
|
||||
import { ViewMode } from '@kbn/embeddable-plugin/public';
|
||||
import type { IKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public';
|
||||
|
||||
import { DashboardRedirect } from '../../dashboard_container/types';
|
||||
import { DashboardListing } from '../../dashboard_listing/dashboard_listing';
|
||||
import { coreServices, dataService, serverlessService } from '../../services/kibana_services';
|
||||
import { getDashboardBreadcrumb } from '../_dashboard_app_strings';
|
||||
import {
|
||||
DashboardAppNoDataPage,
|
||||
isDashboardAppInNoDataState,
|
||||
} from '../no_data/dashboard_app_no_data';
|
||||
import { pluginServices } from '../../services/plugin_services';
|
||||
import { getDashboardBreadcrumb } from '../_dashboard_app_strings';
|
||||
import { DashboardRedirect } from '../../dashboard_container/types';
|
||||
import { getDashboardListItemLink } from './get_dashboard_list_item_link';
|
||||
import { DashboardListing } from '../../dashboard_listing/dashboard_listing';
|
||||
import { getDashboardContentManagementService } from '../../services/dashboard_content_management_service';
|
||||
|
||||
export interface DashboardListingPageProps {
|
||||
kbnUrlStateStorage: IKbnUrlStateStorage;
|
||||
|
@ -36,13 +37,6 @@ export const DashboardListingPage = ({
|
|||
initialFilter,
|
||||
kbnUrlStateStorage,
|
||||
}: DashboardListingPageProps) => {
|
||||
const {
|
||||
data: { query },
|
||||
serverless,
|
||||
chrome: { setBreadcrumbs },
|
||||
dashboardContentManagement: { findDashboards },
|
||||
} = pluginServices.getServices();
|
||||
|
||||
const [showNoDataPage, setShowNoDataPage] = useState<boolean | undefined>();
|
||||
useEffect(() => {
|
||||
let isMounted = true;
|
||||
|
@ -56,40 +50,42 @@ export const DashboardListingPage = ({
|
|||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumbs([
|
||||
coreServices.chrome.setBreadcrumbs([
|
||||
{
|
||||
text: getDashboardBreadcrumb(),
|
||||
},
|
||||
]);
|
||||
|
||||
if (serverless?.setBreadcrumbs) {
|
||||
if (serverlessService) {
|
||||
// if serverless breadcrumbs available,
|
||||
// reset any deeper context breadcrumbs to only keep the main "dashboard" part that comes from the navigation config
|
||||
serverless.setBreadcrumbs([]);
|
||||
serverlessService.setBreadcrumbs([]);
|
||||
}
|
||||
}, [setBreadcrumbs, serverless]);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// syncs `_g` portion of url with query services
|
||||
const { stop: stopSyncingQueryServiceStateWithUrl } = syncGlobalQueryStateWithUrl(
|
||||
query,
|
||||
dataService.query,
|
||||
kbnUrlStateStorage
|
||||
);
|
||||
if (title) {
|
||||
findDashboards.findByTitle(title).then((result) => {
|
||||
if (!result) return;
|
||||
redirectTo({
|
||||
destination: 'dashboard',
|
||||
id: result.id,
|
||||
useReplace: true,
|
||||
getDashboardContentManagementService()
|
||||
.findDashboards.findByTitle(title)
|
||||
.then((result) => {
|
||||
if (!result) return;
|
||||
redirectTo({
|
||||
destination: 'dashboard',
|
||||
id: result.id,
|
||||
useReplace: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return () => {
|
||||
stopSyncingQueryServiceStateWithUrl();
|
||||
};
|
||||
}, [title, redirectTo, query, kbnUrlStateStorage, findDashboards]);
|
||||
}, [title, redirectTo, kbnUrlStateStorage]);
|
||||
|
||||
const titleFilter = title ? `${title}` : '';
|
||||
|
||||
|
|
|
@ -10,29 +10,23 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiCallOut } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { toMountPoint } from '@kbn/react-kibana-mount';
|
||||
|
||||
import { LANDING_PAGE_PATH } from '../../dashboard_constants';
|
||||
import { pluginServices } from '../../services/plugin_services';
|
||||
import { coreServices, urlForwardingService } from '../../services/kibana_services';
|
||||
import { useDashboardMountContext } from '../hooks/dashboard_mount_context';
|
||||
|
||||
let bannerId: string | undefined;
|
||||
|
||||
export const DashboardNoMatch = ({ history }: { history: RouteComponentProps['history'] }) => {
|
||||
const { restorePreviousUrl } = useDashboardMountContext();
|
||||
const {
|
||||
analytics,
|
||||
settings: { i18n: i18nStart, theme },
|
||||
overlays: { banners },
|
||||
urlForwarding: { navigateToLegacyKibanaUrl },
|
||||
} = pluginServices.getServices();
|
||||
|
||||
useEffect(() => {
|
||||
restorePreviousUrl();
|
||||
const { navigated } = navigateToLegacyKibanaUrl(
|
||||
const { navigated } = urlForwardingService.navigateToLegacyKibanaUrl(
|
||||
history.location.pathname + history.location.search
|
||||
);
|
||||
|
||||
|
@ -41,7 +35,7 @@ export const DashboardNoMatch = ({ history }: { history: RouteComponentProps['hi
|
|||
defaultMessage: 'Page not found',
|
||||
});
|
||||
|
||||
bannerId = banners.replace(
|
||||
bannerId = coreServices.overlays.banners.replace(
|
||||
bannerId,
|
||||
toMountPoint(
|
||||
<EuiCallOut color="warning" iconType="iInCircle" title={bannerMessage}>
|
||||
|
@ -55,28 +49,20 @@ export const DashboardNoMatch = ({ history }: { history: RouteComponentProps['hi
|
|||
/>
|
||||
</p>
|
||||
</EuiCallOut>,
|
||||
{ analytics, i18n: i18nStart, theme }
|
||||
{ analytics: coreServices.analytics, i18n: coreServices.i18n, theme: coreServices.theme }
|
||||
)
|
||||
);
|
||||
|
||||
// hide the message after the user has had a chance to acknowledge it -- so it doesn't permanently stick around
|
||||
setTimeout(() => {
|
||||
if (bannerId) {
|
||||
banners.remove(bannerId);
|
||||
coreServices.overlays.banners.remove(bannerId);
|
||||
}
|
||||
}, 15000);
|
||||
|
||||
history.replace(LANDING_PAGE_PATH);
|
||||
}
|
||||
}, [
|
||||
restorePreviousUrl,
|
||||
navigateToLegacyKibanaUrl,
|
||||
banners,
|
||||
analytics,
|
||||
i18nStart,
|
||||
theme,
|
||||
history,
|
||||
]);
|
||||
}, [restorePreviousUrl, history]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
|
|
@ -8,27 +8,23 @@
|
|||
*/
|
||||
|
||||
import type { QueryState } from '@kbn/data-plugin/public';
|
||||
import { setStateToKbnUrl } from '@kbn/kibana-utils-plugin/public';
|
||||
import { IKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public';
|
||||
import { IKbnUrlStateStorage, setStateToKbnUrl } from '@kbn/kibana-utils-plugin/public';
|
||||
|
||||
import {
|
||||
DASHBOARD_APP_ID,
|
||||
createDashboardEditUrl,
|
||||
GLOBAL_STATE_STORAGE_KEY,
|
||||
createDashboardEditUrl,
|
||||
} from '../../dashboard_constants';
|
||||
import { pluginServices } from '../../services/plugin_services';
|
||||
import { coreServices } from '../../services/kibana_services';
|
||||
|
||||
export const getDashboardListItemLink = (
|
||||
kbnUrlStateStorage: IKbnUrlStateStorage,
|
||||
id: string,
|
||||
timeRestore: boolean
|
||||
) => {
|
||||
const {
|
||||
application: { getUrlForApp },
|
||||
settings: { uiSettings },
|
||||
} = pluginServices.getServices();
|
||||
const useHash = uiSettings.get('state:storeInSessionStorage'); // use hash
|
||||
const useHash = coreServices.uiSettings.get('state:storeInSessionStorage'); // use hash
|
||||
|
||||
let url = getUrlForApp(DASHBOARD_APP_ID, {
|
||||
let url = coreServices.application.getUrlForApp(DASHBOARD_APP_ID, {
|
||||
path: `#${createDashboardEditUrl(id)}`,
|
||||
});
|
||||
const globalStateInUrl = kbnUrlStateStorage.get<QueryState>(GLOBAL_STATE_STORAGE_KEY) || {};
|
||||
|
|
|
@ -10,44 +10,30 @@
|
|||
import React from 'react';
|
||||
|
||||
import { withSuspense } from '@kbn/shared-ux-utility';
|
||||
import { pluginServices } from '../../services/plugin_services';
|
||||
|
||||
import { DASHBOARD_APP_ID } from '../../dashboard_constants';
|
||||
import {
|
||||
coreServices,
|
||||
dataService,
|
||||
dataViewEditorService,
|
||||
embeddableService,
|
||||
noDataPageService,
|
||||
shareService,
|
||||
} from '../../services/kibana_services';
|
||||
import { getDashboardBackupService } from '../../services/dashboard_backup_service';
|
||||
import { getDashboardContentManagementService } from '../../services/dashboard_content_management_service';
|
||||
|
||||
export const DashboardAppNoDataPage = ({
|
||||
onDataViewCreated,
|
||||
}: {
|
||||
onDataViewCreated: () => void;
|
||||
}) => {
|
||||
const {
|
||||
application,
|
||||
data: { dataViews },
|
||||
dataViewEditor,
|
||||
http: { basePath, get },
|
||||
documentationLinks: { indexPatternsDocLink, kibanaGuideDocLink, esqlDocLink },
|
||||
customBranding,
|
||||
noDataPage,
|
||||
share,
|
||||
} = pluginServices.getServices();
|
||||
|
||||
const analyticsServices = {
|
||||
coreStart: {
|
||||
docLinks: {
|
||||
links: {
|
||||
kibana: { guide: kibanaGuideDocLink },
|
||||
indexPatterns: { introduction: indexPatternsDocLink },
|
||||
query: { queryESQL: esqlDocLink },
|
||||
},
|
||||
},
|
||||
application,
|
||||
http: { basePath, get },
|
||||
customBranding: {
|
||||
hasCustomBranding$: customBranding.hasCustomBranding$,
|
||||
},
|
||||
},
|
||||
dataViews,
|
||||
dataViewEditor,
|
||||
noDataPage,
|
||||
share: share.url ? { url: share.url } : undefined,
|
||||
coreStart: coreServices,
|
||||
dataViews: dataService.dataViews,
|
||||
dataViewEditor: dataViewEditorService,
|
||||
noDataPage: noDataPageService,
|
||||
share: shareService,
|
||||
};
|
||||
|
||||
const importPromise = import('@kbn/shared-ux-page-analytics-no-data');
|
||||
|
@ -74,29 +60,22 @@ export const DashboardAppNoDataPage = ({
|
|||
};
|
||||
|
||||
export const isDashboardAppInNoDataState = async () => {
|
||||
const {
|
||||
data: { dataViews },
|
||||
embeddable,
|
||||
dashboardContentManagement,
|
||||
dashboardBackup,
|
||||
} = pluginServices.getServices();
|
||||
|
||||
const hasUserDataView = await dataViews.hasData.hasUserDataView().catch(() => false);
|
||||
const hasUserDataView = await dataService.dataViews.hasData.hasUserDataView().catch(() => false);
|
||||
|
||||
if (hasUserDataView) return false;
|
||||
|
||||
// consider has data if there is an incoming embeddable
|
||||
const hasIncomingEmbeddable = embeddable
|
||||
const hasIncomingEmbeddable = embeddableService
|
||||
.getStateTransfer()
|
||||
.getIncomingEmbeddablePackage(DASHBOARD_APP_ID, false);
|
||||
if (hasIncomingEmbeddable) return false;
|
||||
|
||||
// consider has data if there is unsaved dashboard with edits
|
||||
if (dashboardBackup.dashboardHasUnsavedEdits()) return false;
|
||||
if (getDashboardBackupService().dashboardHasUnsavedEdits()) return false;
|
||||
|
||||
// consider has data if there is at least one dashboard
|
||||
const { total } = await dashboardContentManagement.findDashboards
|
||||
.search({ search: '', size: 1 })
|
||||
const { total } = await getDashboardContentManagementService()
|
||||
.findDashboards.search({ search: '', size: 1 })
|
||||
.catch(() => ({ total: 0 }));
|
||||
if (total > 0) return false;
|
||||
|
||||
|
|
|
@ -10,14 +10,11 @@
|
|||
import { useEffect } from 'react';
|
||||
|
||||
import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing';
|
||||
import { pluginServices } from '../../services/plugin_services';
|
||||
import { DashboardApi } from '../..';
|
||||
import { getNewDashboardTitle } from '../_dashboard_app_strings';
|
||||
import { coreServices } from '../../services/kibana_services';
|
||||
|
||||
export const DashboardTabTitleSetter = ({ dashboardApi }: { dashboardApi: DashboardApi }) => {
|
||||
const {
|
||||
chrome: { docTitle: chromeDocTitle },
|
||||
} = pluginServices.getServices();
|
||||
const [title, lastSavedId] = useBatchedPublishingSubjects(
|
||||
dashboardApi.panelTitle,
|
||||
dashboardApi.savedObjectId
|
||||
|
@ -27,8 +24,10 @@ export const DashboardTabTitleSetter = ({ dashboardApi }: { dashboardApi: Dashbo
|
|||
* Set chrome tab title when dashboard's title changes
|
||||
*/
|
||||
useEffect(() => {
|
||||
chromeDocTitle.change(!lastSavedId ? getNewDashboardTitle() : title ?? lastSavedId);
|
||||
}, [title, chromeDocTitle, lastSavedId]);
|
||||
coreServices.chrome.docTitle.change(
|
||||
!lastSavedId ? getNewDashboardTitle() : title ?? lastSavedId
|
||||
);
|
||||
}, [title, lastSavedId]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
|
|
@ -7,14 +7,20 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { ADD_PANEL_TRIGGER } from '@kbn/ui-actions-plugin/public';
|
||||
import type { PresentationContainer } from '@kbn/presentation-containers';
|
||||
import type { Action } from '@kbn/ui-actions-plugin/public';
|
||||
import { type BaseVisType, VisGroups, VisTypeAlias } from '@kbn/visualizations-plugin/public';
|
||||
import { COMMON_EMBEDDABLE_GROUPING } from '@kbn/embeddable-plugin/public';
|
||||
import type { PresentationContainer } from '@kbn/presentation-containers';
|
||||
import type { Action, UiActionsService } from '@kbn/ui-actions-plugin/public';
|
||||
import { ADD_PANEL_TRIGGER } from '@kbn/ui-actions-plugin/public';
|
||||
import {
|
||||
VisGroups,
|
||||
VisTypeAlias,
|
||||
VisualizationsStart,
|
||||
type BaseVisType,
|
||||
} from '@kbn/visualizations-plugin/public';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
|
||||
import { uiActionsService, visualizationsService } from '../../../services/kibana_services';
|
||||
import { useGetDashboardPanels } from './use_get_dashboard_panels';
|
||||
import { pluginServices } from '../../../services/plugin_services';
|
||||
|
||||
const mockApi = { addNewPanel: jest.fn() } as unknown as jest.Mocked<PresentationContainer>;
|
||||
|
||||
|
@ -24,34 +30,25 @@ describe('Get dashboard panels hook', () => {
|
|||
createNewVisType: jest.fn(),
|
||||
};
|
||||
|
||||
type PluginServices = ReturnType<typeof pluginServices.getServices>;
|
||||
|
||||
let compatibleTriggerActionsRequestSpy: jest.SpyInstance<
|
||||
ReturnType<NonNullable<PluginServices['uiActions']['getTriggerCompatibleActions']>>
|
||||
ReturnType<NonNullable<UiActionsService['getTriggerCompatibleActions']>>
|
||||
>;
|
||||
|
||||
let dashboardVisualizationGroupGetterSpy: jest.SpyInstance<
|
||||
ReturnType<PluginServices['visualizations']['getByGroup']>
|
||||
ReturnType<VisualizationsStart['getByGroup']>
|
||||
>;
|
||||
|
||||
let dashboardVisualizationAliasesGetterSpy: jest.SpyInstance<
|
||||
ReturnType<PluginServices['visualizations']['getAliases']>
|
||||
ReturnType<VisualizationsStart['getAliases']>
|
||||
>;
|
||||
|
||||
beforeAll(() => {
|
||||
const _pluginServices = pluginServices.getServices();
|
||||
|
||||
compatibleTriggerActionsRequestSpy = jest.spyOn(
|
||||
_pluginServices.uiActions,
|
||||
uiActionsService,
|
||||
'getTriggerCompatibleActions'
|
||||
);
|
||||
|
||||
dashboardVisualizationGroupGetterSpy = jest.spyOn(_pluginServices.visualizations, 'getByGroup');
|
||||
|
||||
dashboardVisualizationAliasesGetterSpy = jest.spyOn(
|
||||
_pluginServices.visualizations,
|
||||
'getAliases'
|
||||
);
|
||||
dashboardVisualizationGroupGetterSpy = jest.spyOn(visualizationsService, 'getByGroup');
|
||||
dashboardVisualizationAliasesGetterSpy = jest.spyOn(visualizationsService, 'getAliases');
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -7,19 +7,20 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { useMemo, useRef, useCallback } from 'react';
|
||||
import type { IconType } from '@elastic/eui';
|
||||
import { ADD_PANEL_TRIGGER } from '@kbn/ui-actions-plugin/public';
|
||||
import { type Subscription, AsyncSubject, from, defer, map, lastValueFrom } from 'rxjs';
|
||||
import { EmbeddableFactory, COMMON_EMBEDDABLE_GROUPING } from '@kbn/embeddable-plugin/public';
|
||||
import { PresentationContainer } from '@kbn/presentation-containers';
|
||||
import { type BaseVisType, VisGroups, type VisTypeAlias } from '@kbn/visualizations-plugin/public';
|
||||
import { useCallback, useMemo, useRef } from 'react';
|
||||
import { AsyncSubject, defer, from, lastValueFrom, map, type Subscription } from 'rxjs';
|
||||
|
||||
import { pluginServices } from '../../../services/plugin_services';
|
||||
import type { IconType } from '@elastic/eui';
|
||||
import { COMMON_EMBEDDABLE_GROUPING, EmbeddableFactory } from '@kbn/embeddable-plugin/public';
|
||||
import { PresentationContainer } from '@kbn/presentation-containers';
|
||||
import { ADD_PANEL_TRIGGER } from '@kbn/ui-actions-plugin/public';
|
||||
import { VisGroups, type BaseVisType, type VisTypeAlias } from '@kbn/visualizations-plugin/public';
|
||||
|
||||
import { uiActionsService, visualizationsService } from '../../../services/kibana_services';
|
||||
import {
|
||||
getAddPanelActionMenuItemsGroup,
|
||||
type PanelSelectionMenuItem,
|
||||
type GroupedAddPanelActions,
|
||||
type PanelSelectionMenuItem,
|
||||
} from './add_panel_action_menu_items';
|
||||
|
||||
interface UseGetDashboardPanelsArgs {
|
||||
|
@ -46,13 +47,9 @@ export const useGetDashboardPanels = ({ api, createNewVisType }: UseGetDashboard
|
|||
const panelsComputeResultCache = useRef(new AsyncSubject<GroupedAddPanelActions[]>());
|
||||
const panelsComputeSubscription = useRef<Subscription | null>(null);
|
||||
|
||||
const {
|
||||
uiActions,
|
||||
visualizations: { getAliases: getVisTypeAliases, getByGroup: getVisTypesByGroup },
|
||||
} = pluginServices.getServices();
|
||||
|
||||
const getSortedVisTypesByGroup = (group: VisGroups) =>
|
||||
getVisTypesByGroup(group)
|
||||
visualizationsService
|
||||
.getByGroup(group)
|
||||
.sort((a: BaseVisType | VisTypeAlias, b: BaseVisType | VisTypeAlias) => {
|
||||
const labelA = 'titleInWizard' in a ? a.titleInWizard || a.title : a.title;
|
||||
const labelB = 'titleInWizard' in b ? b.titleInWizard || a.title : a.title;
|
||||
|
@ -70,7 +67,8 @@ export const useGetDashboardPanels = ({ api, createNewVisType }: UseGetDashboard
|
|||
const toolVisTypes = getSortedVisTypesByGroup(VisGroups.TOOLS);
|
||||
const legacyVisTypes = getSortedVisTypesByGroup(VisGroups.LEGACY);
|
||||
|
||||
const visTypeAliases = getVisTypeAliases()
|
||||
const visTypeAliases = visualizationsService
|
||||
.getAliases()
|
||||
.sort(({ promotion: a = false }: VisTypeAlias, { promotion: b = false }: VisTypeAlias) =>
|
||||
a === b ? 0 : a ? -1 : 1
|
||||
)
|
||||
|
@ -133,12 +131,12 @@ export const useGetDashboardPanels = ({ api, createNewVisType }: UseGetDashboard
|
|||
() =>
|
||||
defer(() => {
|
||||
return from(
|
||||
uiActions?.getTriggerCompatibleActions?.(ADD_PANEL_TRIGGER, {
|
||||
uiActionsService.getTriggerCompatibleActions?.(ADD_PANEL_TRIGGER, {
|
||||
embeddable: api,
|
||||
}) ?? []
|
||||
);
|
||||
}),
|
||||
[api, uiActions]
|
||||
[api]
|
||||
);
|
||||
|
||||
const computeAvailablePanels = useCallback(
|
||||
|
|
|
@ -7,40 +7,35 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { css } from '@emotion/react';
|
||||
import React, { useCallback } from 'react';
|
||||
import { METRIC_TYPE } from '@kbn/analytics';
|
||||
import { useEuiTheme } from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
import { METRIC_TYPE } from '@kbn/analytics';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
|
||||
import { AddFromLibraryButton, Toolbar, ToolbarButton } from '@kbn/shared-ux-button-toolbar';
|
||||
import { BaseVisType, VisTypeAlias } from '@kbn/visualizations-plugin/public';
|
||||
|
||||
import { useStateFromPublishingSubject } from '@kbn/presentation-publishing';
|
||||
import { getCreateVisualizationButtonTitle } from '../_dashboard_app_strings';
|
||||
import { EditorMenu } from './editor_menu';
|
||||
import { pluginServices } from '../../services/plugin_services';
|
||||
import { ControlsToolbarButton } from './controls_toolbar_button';
|
||||
import { DASHBOARD_UI_METRIC_ID } from '../../dashboard_constants';
|
||||
import { useDashboardApi } from '../../dashboard_api/use_dashboard_api';
|
||||
import { DASHBOARD_UI_METRIC_ID } from '../../dashboard_constants';
|
||||
import {
|
||||
dataService,
|
||||
embeddableService,
|
||||
usageCollectionService,
|
||||
visualizationsService,
|
||||
} from '../../services/kibana_services';
|
||||
import { getCreateVisualizationButtonTitle } from '../_dashboard_app_strings';
|
||||
import { ControlsToolbarButton } from './controls_toolbar_button';
|
||||
import { EditorMenu } from './editor_menu';
|
||||
|
||||
export function DashboardEditingToolbar({ isDisabled }: { isDisabled?: boolean }) {
|
||||
const {
|
||||
usageCollection,
|
||||
data: { search },
|
||||
embeddable: { getStateTransfer },
|
||||
visualizations: { getAliases: getVisTypeAliases },
|
||||
} = pluginServices.getServices();
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
||||
const dashboardApi = useDashboardApi();
|
||||
|
||||
const stateTransferService = getStateTransfer();
|
||||
|
||||
const lensAlias = getVisTypeAliases().find(({ name }) => name === 'lens');
|
||||
|
||||
const trackUiMetric = usageCollection.reportUiCounter?.bind(
|
||||
usageCollection,
|
||||
DASHBOARD_UI_METRIC_ID
|
||||
const lensAlias = useMemo(
|
||||
() => visualizationsService.getAliases().find(({ name }) => name === 'lens'),
|
||||
[]
|
||||
);
|
||||
|
||||
const createNewVisType = useCallback(
|
||||
|
@ -49,6 +44,10 @@ export function DashboardEditingToolbar({ isDisabled }: { isDisabled?: boolean }
|
|||
let appId = '';
|
||||
|
||||
if (visType) {
|
||||
const trackUiMetric = usageCollectionService?.reportUiCounter.bind(
|
||||
usageCollectionService,
|
||||
DASHBOARD_UI_METRIC_ID
|
||||
);
|
||||
if (trackUiMetric) {
|
||||
trackUiMetric(METRIC_TYPE.CLICK, `${visType.name}:create`);
|
||||
}
|
||||
|
@ -67,16 +66,17 @@ export function DashboardEditingToolbar({ isDisabled }: { isDisabled?: boolean }
|
|||
path = '#/create?';
|
||||
}
|
||||
|
||||
const stateTransferService = embeddableService.getStateTransfer();
|
||||
stateTransferService.navigateToEditor(appId, {
|
||||
path,
|
||||
state: {
|
||||
originatingApp: dashboardApi.getAppContext()?.currentAppId,
|
||||
originatingPath: dashboardApi.getAppContext()?.getCurrentPath?.(),
|
||||
searchSessionId: search.session.getSessionId(),
|
||||
searchSessionId: dataService.search.session.getSessionId(),
|
||||
},
|
||||
});
|
||||
},
|
||||
[stateTransferService, dashboardApi, search.session, trackUiMetric]
|
||||
[dashboardApi]
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
|
@ -7,35 +7,23 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { EditorMenu } from './editor_menu';
|
||||
import React from 'react';
|
||||
import { buildMockDashboard } from '../../mocks';
|
||||
import { EditorMenu } from './editor_menu';
|
||||
|
||||
import { pluginServices } from '../../services/plugin_services';
|
||||
import { DashboardContext } from '../../dashboard_api/use_dashboard_api';
|
||||
import { DashboardApi } from '../../dashboard_api/types';
|
||||
import { DashboardContext } from '../../dashboard_api/use_dashboard_api';
|
||||
import {
|
||||
embeddableService,
|
||||
uiActionsService,
|
||||
visualizationsService,
|
||||
} from '../../services/kibana_services';
|
||||
|
||||
jest.mock('../../services/plugin_services', () => {
|
||||
const module = jest.requireActual('../../services/plugin_services');
|
||||
|
||||
const _pluginServices = (module.pluginServices as typeof pluginServices).getServices();
|
||||
|
||||
jest
|
||||
.spyOn(_pluginServices.embeddable, 'getEmbeddableFactories')
|
||||
.mockReturnValue(new Map().values());
|
||||
jest.spyOn(_pluginServices.uiActions, 'getTriggerCompatibleActions').mockResolvedValue([]);
|
||||
jest.spyOn(_pluginServices.visualizations, 'getByGroup').mockReturnValue([]);
|
||||
jest.spyOn(_pluginServices.visualizations, 'getAliases').mockReturnValue([]);
|
||||
|
||||
return {
|
||||
...module,
|
||||
pluginServices: {
|
||||
...module.pluginServices,
|
||||
getServices: jest.fn().mockReturnValue(_pluginServices),
|
||||
},
|
||||
};
|
||||
});
|
||||
jest.spyOn(embeddableService, 'getEmbeddableFactories').mockReturnValue(new Map().values());
|
||||
jest.spyOn(uiActionsService, 'getTriggerCompatibleActions').mockResolvedValue([]);
|
||||
jest.spyOn(visualizationsService, 'getByGroup').mockReturnValue([]);
|
||||
jest.spyOn(visualizationsService, 'getAliases').mockReturnValue([]);
|
||||
|
||||
describe('editor menu', () => {
|
||||
it('renders without crashing', async () => {
|
||||
|
|
|
@ -16,8 +16,8 @@ import { toMountPoint } from '@kbn/react-kibana-mount';
|
|||
import { ToolbarButton } from '@kbn/shared-ux-button-toolbar';
|
||||
|
||||
import { useGetDashboardPanels, DashboardPanelSelectionListFlyout } from './add_new_panel';
|
||||
import { pluginServices } from '../../services/plugin_services';
|
||||
import { useDashboardApi } from '../../dashboard_api/use_dashboard_api';
|
||||
import { coreServices } from '../../services/kibana_services';
|
||||
|
||||
interface EditorMenuProps
|
||||
extends Pick<Parameters<typeof useGetDashboardPanels>[0], 'createNewVisType'> {
|
||||
|
@ -27,12 +27,6 @@ interface EditorMenuProps
|
|||
export const EditorMenu = ({ createNewVisType, isDisabled }: EditorMenuProps) => {
|
||||
const dashboardApi = useDashboardApi();
|
||||
|
||||
const {
|
||||
overlays,
|
||||
analytics,
|
||||
settings: { i18n: i18nStart, theme },
|
||||
} = pluginServices.getServices();
|
||||
|
||||
const fetchDashboardPanels = useGetDashboardPanels({
|
||||
api: dashboardApi,
|
||||
createNewVisType,
|
||||
|
@ -63,11 +57,11 @@ export const EditorMenu = ({ createNewVisType, isDisabled }: EditorMenuProps) =>
|
|||
/>
|
||||
);
|
||||
}),
|
||||
{ analytics, theme, i18n: i18nStart }
|
||||
{ analytics: coreServices.analytics, theme: coreServices.theme, i18n: coreServices.i18n }
|
||||
);
|
||||
|
||||
dashboardApi.openOverlay(
|
||||
overlays.openFlyout(mount, {
|
||||
coreServices.overlays.openFlyout(mount, {
|
||||
size: 'm',
|
||||
maxWidth: 500,
|
||||
paddingSize: flyoutPanelPaddingSize,
|
||||
|
@ -80,7 +74,7 @@ export const EditorMenu = ({ createNewVisType, isDisabled }: EditorMenuProps) =>
|
|||
})
|
||||
);
|
||||
},
|
||||
[analytics, theme, i18nStart, dashboardApi, overlays, fetchDashboardPanels]
|
||||
[dashboardApi, fetchDashboardPanels]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
@ -8,11 +8,12 @@
|
|||
*/
|
||||
|
||||
import { Capabilities } from '@kbn/core/public';
|
||||
import { DashboardLocatorParams } from '../../../dashboard_container';
|
||||
import { convertPanelMapToSavedPanels, DashboardContainerInput } from '../../../../common';
|
||||
import { DashboardLocatorParams } from '../../../dashboard_container';
|
||||
|
||||
import { pluginServices } from '../../../services/plugin_services';
|
||||
import { shareService } from '../../../services/kibana_services';
|
||||
import { showPublicUrlSwitch, ShowShareModal, ShowShareModalProps } from './show_share_modal';
|
||||
import { getDashboardBackupService } from '../../../services/dashboard_backup_service';
|
||||
|
||||
describe('showPublicUrlSwitch', () => {
|
||||
test('returns false if "dashboard" app is not available', () => {
|
||||
|
@ -56,13 +57,11 @@ describe('showPublicUrlSwitch', () => {
|
|||
});
|
||||
|
||||
describe('ShowShareModal', () => {
|
||||
const dashboardBackupService = getDashboardBackupService();
|
||||
const unsavedStateKeys = ['query', 'filters', 'options', 'savedQuery', 'panels'] as Array<
|
||||
keyof DashboardLocatorParams
|
||||
>;
|
||||
const toggleShareMenuSpy = jest.spyOn(
|
||||
pluginServices.getServices().share,
|
||||
'toggleShareContextMenu'
|
||||
);
|
||||
const toggleShareMenuSpy = jest.spyOn(shareService!, 'toggleShareContextMenu');
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
@ -71,9 +70,7 @@ describe('ShowShareModal', () => {
|
|||
const getPropsAndShare = (
|
||||
unsavedState?: Partial<DashboardContainerInput>
|
||||
): ShowShareModalProps => {
|
||||
pluginServices.getServices().dashboardBackup.getState = jest
|
||||
.fn()
|
||||
.mockReturnValue({ dashboardState: unsavedState });
|
||||
dashboardBackupService.getState = jest.fn().mockReturnValue({ dashboardState: unsavedState });
|
||||
return {
|
||||
isDirty: true,
|
||||
anchorElement: document.createElement('div'),
|
||||
|
@ -169,7 +166,7 @@ describe('ShowShareModal', () => {
|
|||
},
|
||||
};
|
||||
const props = getPropsAndShare(unsavedDashboardState);
|
||||
pluginServices.getServices().dashboardBackup.getState = jest.fn().mockReturnValue({
|
||||
dashboardBackupService.getState = jest.fn().mockReturnValue({
|
||||
dashboardState: unsavedDashboardState,
|
||||
panels: {
|
||||
panel_1: { changedKey1: 'changed' },
|
||||
|
|
|
@ -7,6 +7,10 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { omit } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import React, { ReactElement, useState } from 'react';
|
||||
|
||||
import { EuiCheckboxGroup } from '@elastic/eui';
|
||||
import type { Capabilities } from '@kbn/core/public';
|
||||
import { QueryState } from '@kbn/data-plugin/common';
|
||||
|
@ -14,15 +18,17 @@ import { DASHBOARD_APP_LOCATOR } from '@kbn/deeplinks-analytics';
|
|||
import { ViewMode } from '@kbn/embeddable-plugin/public';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { getStateFromKbnUrl, setStateToKbnUrl, unhashUrl } from '@kbn/kibana-utils-plugin/public';
|
||||
import { omit } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import React, { ReactElement, useState } from 'react';
|
||||
|
||||
import { convertPanelMapToSavedPanels, DashboardPanelMap } from '../../../../common';
|
||||
import { DashboardLocatorParams } from '../../../dashboard_container';
|
||||
import { pluginServices } from '../../../services/plugin_services';
|
||||
import { dashboardUrlParams } from '../../dashboard_router';
|
||||
import {
|
||||
getDashboardBackupService,
|
||||
PANELS_CONTROL_GROUP_KEY,
|
||||
} from '../../../services/dashboard_backup_service';
|
||||
import { coreServices, dataService, shareService } from '../../../services/kibana_services';
|
||||
import { getDashboardCapabilities } from '../../../utils/get_dashboard_capabilities';
|
||||
import { shareModalStrings } from '../../_dashboard_app_strings';
|
||||
import { PANELS_CONTROL_GROUP_KEY } from '../../../services/dashboard_backup/dashboard_backup_service';
|
||||
import { dashboardUrlParams } from '../../dashboard_router';
|
||||
|
||||
const showFilterBarId = 'showFilterBar';
|
||||
|
||||
|
@ -49,21 +55,7 @@ export function ShowShareModal({
|
|||
dashboardTitle,
|
||||
getPanelsState,
|
||||
}: ShowShareModalProps) {
|
||||
const {
|
||||
dashboardCapabilities: { createShortUrl: allowShortUrl },
|
||||
dashboardBackup,
|
||||
data: {
|
||||
query: {
|
||||
timefilter: {
|
||||
timefilter: { getTime },
|
||||
},
|
||||
},
|
||||
},
|
||||
notifications,
|
||||
share: { toggleShareContextMenu },
|
||||
} = pluginServices.getServices();
|
||||
|
||||
if (!toggleShareContextMenu) return; // TODO: Make this logic cleaner once share is an optional service
|
||||
if (!shareService) return;
|
||||
|
||||
const EmbedUrlParamExtension = ({
|
||||
setParamValue,
|
||||
|
@ -125,7 +117,7 @@ export function ShowShareModal({
|
|||
let unsavedStateForLocator: DashboardLocatorParams = {};
|
||||
|
||||
const { dashboardState: unsavedDashboardState, panels: panelModifications } =
|
||||
dashboardBackup.getState(savedObjectId) ?? {};
|
||||
getDashboardBackupService().getState(savedObjectId) ?? {};
|
||||
|
||||
const allUnsavedPanels = (() => {
|
||||
if (
|
||||
|
@ -186,7 +178,7 @@ export function ShowShareModal({
|
|||
refreshInterval: undefined, // We don't share refresh interval externally
|
||||
viewMode: ViewMode.VIEW, // For share locators we always load the dashboard in view mode
|
||||
useHash: false,
|
||||
timeRange: getTime(),
|
||||
timeRange: dataService.query.timefilter.timefilter.getTime(),
|
||||
...unsavedStateForLocator,
|
||||
};
|
||||
|
||||
|
@ -203,11 +195,11 @@ export function ShowShareModal({
|
|||
unhashUrl(baseUrl)
|
||||
);
|
||||
|
||||
toggleShareContextMenu({
|
||||
shareService.toggleShareContextMenu({
|
||||
isDirty,
|
||||
anchorElement,
|
||||
allowEmbed: true,
|
||||
allowShortUrl,
|
||||
allowShortUrl: getDashboardCapabilities().createShortUrl,
|
||||
shareableUrl,
|
||||
objectId: savedObjectId,
|
||||
objectType: 'dashboard',
|
||||
|
@ -238,6 +230,6 @@ export function ShowShareModal({
|
|||
snapshotShareWarning: Boolean(unsavedDashboardState?.panels)
|
||||
? shareModalStrings.getSnapshotShareWarning()
|
||||
: undefined,
|
||||
toasts: notifications.toasts,
|
||||
toasts: coreServices.notifications.toasts,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -7,23 +7,25 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { batch } from 'react-redux';
|
||||
import { Dispatch, SetStateAction, useCallback, useMemo, useState } from 'react';
|
||||
import { batch } from 'react-redux';
|
||||
|
||||
import { ViewMode } from '@kbn/embeddable-plugin/public';
|
||||
import { TopNavMenuData } from '@kbn/navigation-plugin/public';
|
||||
import type { TopNavMenuData } from '@kbn/navigation-plugin/public';
|
||||
import useMountedState from 'react-use/lib/useMountedState';
|
||||
|
||||
import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing';
|
||||
import { UI_SETTINGS } from '../../../common';
|
||||
import { useDashboardApi } from '../../dashboard_api/use_dashboard_api';
|
||||
import { CHANGE_CHECK_DEBOUNCE } from '../../dashboard_constants';
|
||||
import { openSettingsFlyout } from '../../dashboard_container/embeddable/api';
|
||||
import { confirmDiscardUnsavedChanges } from '../../dashboard_listing/confirm_overlays';
|
||||
import { getDashboardBackupService } from '../../services/dashboard_backup_service';
|
||||
import { SaveDashboardReturn } from '../../services/dashboard_content_management_service/types';
|
||||
import { coreServices, shareService } from '../../services/kibana_services';
|
||||
import { getDashboardCapabilities } from '../../utils/get_dashboard_capabilities';
|
||||
import { topNavStrings } from '../_dashboard_app_strings';
|
||||
import { ShowShareModal } from './share/show_share_modal';
|
||||
import { pluginServices } from '../../services/plugin_services';
|
||||
import { CHANGE_CHECK_DEBOUNCE } from '../../dashboard_constants';
|
||||
import { confirmDiscardUnsavedChanges } from '../../dashboard_listing/confirm_overlays';
|
||||
import { SaveDashboardReturn } from '../../services/dashboard_content_management/types';
|
||||
import { useDashboardApi } from '../../dashboard_api/use_dashboard_api';
|
||||
import { openSettingsFlyout } from '../../dashboard_container/embeddable/api';
|
||||
|
||||
export const useDashboardMenuItems = ({
|
||||
isLabsShown,
|
||||
|
@ -40,17 +42,6 @@ export const useDashboardMenuItems = ({
|
|||
|
||||
const [isSaveInProgress, setIsSaveInProgress] = useState(false);
|
||||
|
||||
/**
|
||||
* Unpack dashboard services
|
||||
*/
|
||||
const {
|
||||
share,
|
||||
dashboardBackup,
|
||||
settings: { uiSettings },
|
||||
dashboardCapabilities: { showWriteControls },
|
||||
} = pluginServices.getServices();
|
||||
const isLabsEnabled = uiSettings.get(UI_SETTINGS.ENABLE_LABS_UI);
|
||||
|
||||
/**
|
||||
* Unpack dashboard state from redux
|
||||
*/
|
||||
|
@ -120,7 +111,7 @@ export const useDashboardMenuItems = ({
|
|||
const switchModes = switchToViewMode
|
||||
? () => {
|
||||
dashboardApi.setViewMode(ViewMode.VIEW);
|
||||
dashboardBackup.storeViewMode(ViewMode.VIEW);
|
||||
getDashboardBackupService().storeViewMode(ViewMode.VIEW);
|
||||
}
|
||||
: undefined;
|
||||
if (!hasUnsavedChanges) {
|
||||
|
@ -138,7 +129,7 @@ export const useDashboardMenuItems = ({
|
|||
});
|
||||
}, viewMode as ViewMode);
|
||||
},
|
||||
[dashboardApi, dashboardBackup, hasUnsavedChanges, viewMode, isMounted]
|
||||
[dashboardApi, hasUnsavedChanges, viewMode, isMounted]
|
||||
);
|
||||
|
||||
/**
|
||||
|
@ -170,7 +161,7 @@ export const useDashboardMenuItems = ({
|
|||
testId: 'dashboardEditMode',
|
||||
className: 'eui-hideFor--s eui-hideFor--xs', // hide for small screens - editing doesn't work in mobile mode.
|
||||
run: () => {
|
||||
dashboardBackup.storeViewMode(ViewMode.EDIT);
|
||||
getDashboardBackupService().storeViewMode(ViewMode.EDIT);
|
||||
dashboardApi.setViewMode(ViewMode.EDIT);
|
||||
dashboardApi.clearOverlays();
|
||||
},
|
||||
|
@ -243,7 +234,6 @@ export const useDashboardMenuItems = ({
|
|||
dashboardApi,
|
||||
setIsLabsShown,
|
||||
isLabsShown,
|
||||
dashboardBackup,
|
||||
quickSaveDashboard,
|
||||
resetChanges,
|
||||
isResetting,
|
||||
|
@ -275,9 +265,13 @@ export const useDashboardMenuItems = ({
|
|||
/**
|
||||
* Build ordered menus for view and edit mode.
|
||||
*/
|
||||
const isLabsEnabled = useMemo(() => coreServices.uiSettings.get(UI_SETTINGS.ENABLE_LABS_UI), []);
|
||||
|
||||
const viewModeTopNavConfig = useMemo(() => {
|
||||
const { showWriteControls } = getDashboardCapabilities();
|
||||
|
||||
const labsMenuItem = isLabsEnabled ? [menuItems.labs] : [];
|
||||
const shareMenuItem = share ? [menuItems.share] : [];
|
||||
const shareMenuItem = shareService ? [menuItems.share] : [];
|
||||
const duplicateMenuItem = showWriteControls ? [menuItems.interactiveSave] : [];
|
||||
const editMenuItem = showWriteControls && !managed ? [menuItems.edit] : [];
|
||||
const mayberesetChangesMenuItem = showResetChange ? [resetChangesMenuItem] : [];
|
||||
|
@ -290,19 +284,11 @@ export const useDashboardMenuItems = ({
|
|||
...mayberesetChangesMenuItem,
|
||||
...editMenuItem,
|
||||
];
|
||||
}, [
|
||||
isLabsEnabled,
|
||||
menuItems,
|
||||
share,
|
||||
showWriteControls,
|
||||
managed,
|
||||
showResetChange,
|
||||
resetChangesMenuItem,
|
||||
]);
|
||||
}, [isLabsEnabled, menuItems, managed, showResetChange, resetChangesMenuItem]);
|
||||
|
||||
const editModeTopNavConfig = useMemo(() => {
|
||||
const labsMenuItem = isLabsEnabled ? [menuItems.labs] : [];
|
||||
const shareMenuItem = share ? [menuItems.share] : [];
|
||||
const shareMenuItem = shareService ? [menuItems.share] : [];
|
||||
const editModeItems: TopNavMenuData[] = [];
|
||||
|
||||
if (lastSavedId) {
|
||||
|
@ -317,7 +303,7 @@ export const useDashboardMenuItems = ({
|
|||
editModeItems.push(menuItems.switchToViewMode, menuItems.interactiveSave);
|
||||
}
|
||||
return [...labsMenuItem, menuItems.settings, ...shareMenuItem, ...editModeItems];
|
||||
}, [isLabsEnabled, menuItems, share, lastSavedId, showResetChange, resetChangesMenuItem]);
|
||||
}, [isLabsEnabled, menuItems, lastSavedId, showResetChange, resetChangesMenuItem]);
|
||||
|
||||
return { viewModeTopNavConfig, editModeTopNavConfig };
|
||||
};
|
||||
|
|
|
@ -23,7 +23,7 @@ import { DASHBOARD_APP_LOCATOR } from '@kbn/deeplinks-analytics';
|
|||
import { SEARCH_SESSION_ID } from '../../dashboard_constants';
|
||||
import { DashboardContainer, DashboardLocatorParams } from '../../dashboard_container';
|
||||
import { convertPanelMapToSavedPanels } from '../../../common';
|
||||
import { pluginServices } from '../../services/plugin_services';
|
||||
import { dataService } from '../../services/kibana_services';
|
||||
|
||||
export const removeSearchSessionIdFromURL = (kbnUrlStateStorage: IKbnUrlStateStorage) => {
|
||||
kbnUrlStateStorage.kbnUrlControls.updateAsync((nextUrl) => {
|
||||
|
@ -69,17 +69,6 @@ function getLocatorParams({
|
|||
container: DashboardContainer;
|
||||
shouldRestoreSearchSession: boolean;
|
||||
}): DashboardLocatorParams {
|
||||
const {
|
||||
data: {
|
||||
query: {
|
||||
queryString,
|
||||
filterManager,
|
||||
timefilter: { timefilter },
|
||||
},
|
||||
search: { session },
|
||||
},
|
||||
} = pluginServices.getServices();
|
||||
|
||||
const {
|
||||
componentState: { lastSavedId },
|
||||
explicitInput: { panels, query, viewMode },
|
||||
|
@ -89,11 +78,15 @@ function getLocatorParams({
|
|||
viewMode,
|
||||
useHash: false,
|
||||
preserveSavedFilters: false,
|
||||
filters: filterManager.getFilters(),
|
||||
query: queryString.formatQuery(query) as Query,
|
||||
filters: dataService.query.filterManager.getFilters(),
|
||||
query: dataService.query.queryString.formatQuery(query) as Query,
|
||||
dashboardId: container.getDashboardSavedObjectId(),
|
||||
searchSessionId: shouldRestoreSearchSession ? session.getSessionId() : undefined,
|
||||
timeRange: shouldRestoreSearchSession ? timefilter.getAbsoluteTime() : timefilter.getTime(),
|
||||
searchSessionId: shouldRestoreSearchSession
|
||||
? dataService.search.session.getSessionId()
|
||||
: undefined,
|
||||
timeRange: shouldRestoreSearchSession
|
||||
? dataService.query.timefilter.timefilter.getAbsoluteTime()
|
||||
: dataService.query.timefilter.timefilter.getTime(),
|
||||
refreshInterval: shouldRestoreSearchSession
|
||||
? {
|
||||
pause: true, // force pause refresh interval when restoring a session
|
||||
|
|
|
@ -7,26 +7,26 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { History } from 'history';
|
||||
import _ from 'lodash';
|
||||
import { skip } from 'rxjs';
|
||||
import semverSatisfies from 'semver/functions/satisfies';
|
||||
import { History } from 'history';
|
||||
|
||||
import { IKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public';
|
||||
import { replaceUrlHashQuery } from '@kbn/kibana-utils-plugin/common';
|
||||
import { IKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public';
|
||||
|
||||
import {
|
||||
DashboardContainerInput,
|
||||
DashboardPanelMap,
|
||||
SharedDashboardState,
|
||||
convertSavedPanelsToPanelMap,
|
||||
DashboardContainerInput,
|
||||
} from '../../../common';
|
||||
import { pluginServices } from '../../services/plugin_services';
|
||||
import { getPanelTooOldErrorString } from '../_dashboard_app_strings';
|
||||
import { DASHBOARD_STATE_STORAGE_KEY, createDashboardEditUrl } from '../../dashboard_constants';
|
||||
import { SavedDashboardPanel } from '../../../common/content_management';
|
||||
import { migrateLegacyQuery } from '../../services/dashboard_content_management/lib/load_dashboard_state';
|
||||
import { DashboardApi } from '../../dashboard_api/types';
|
||||
import { DASHBOARD_STATE_STORAGE_KEY, createDashboardEditUrl } from '../../dashboard_constants';
|
||||
import { migrateLegacyQuery } from '../../services/dashboard_content_management_service/lib/load_dashboard_state';
|
||||
import { coreServices } from '../../services/kibana_services';
|
||||
import { getPanelTooOldErrorString } from '../_dashboard_app_strings';
|
||||
|
||||
/**
|
||||
* We no longer support loading panels from a version older than 7.3 in the URL.
|
||||
|
@ -54,7 +54,7 @@ function getPanelsMap(appStateInUrl: SharedDashboardState): DashboardPanelMap |
|
|||
}
|
||||
|
||||
if (isPanelVersionTooOld(appStateInUrl.panels)) {
|
||||
pluginServices.getServices().notifications.toasts.addWarning(getPanelTooOldErrorString());
|
||||
coreServices.notifications.toasts.addWarning(getPanelTooOldErrorString());
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,20 +7,18 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { mountWithIntl } from '@kbn/test-jest-helpers';
|
||||
import { findTestSubject } from '@elastic/eui/lib/test';
|
||||
import { mountWithIntl } from '@kbn/test-jest-helpers';
|
||||
import React from 'react';
|
||||
|
||||
import { buildMockDashboard } from '../../../mocks';
|
||||
import { DashboardEmptyScreen } from './dashboard_empty_screen';
|
||||
import { pluginServices } from '../../../services/plugin_services';
|
||||
import { DashboardContext } from '../../../dashboard_api/use_dashboard_api';
|
||||
import { DashboardApi } from '../../../dashboard_api/types';
|
||||
import { ViewMode } from '@kbn/embeddable-plugin/public';
|
||||
import { DashboardApi } from '../../../dashboard_api/types';
|
||||
import { DashboardContext } from '../../../dashboard_api/use_dashboard_api';
|
||||
import { buildMockDashboard } from '../../../mocks';
|
||||
import { coreServices, visualizationsService } from '../../../services/kibana_services';
|
||||
import { DashboardEmptyScreen } from './dashboard_empty_screen';
|
||||
|
||||
pluginServices.getServices().visualizations.getAliases = jest
|
||||
.fn()
|
||||
.mockReturnValue([{ name: 'lens' }]);
|
||||
visualizationsService.getAliases = jest.fn().mockReturnValue([{ name: 'lens' }]);
|
||||
|
||||
describe('DashboardEmptyScreen', () => {
|
||||
function mountComponent(viewMode: ViewMode) {
|
||||
|
@ -57,7 +55,7 @@ describe('DashboardEmptyScreen', () => {
|
|||
});
|
||||
|
||||
test('renders correctly with readonly mode', () => {
|
||||
pluginServices.getServices().dashboardCapabilities.showWriteControls = false;
|
||||
(coreServices.application.capabilities as any).dashboard.showWriteControls = false;
|
||||
|
||||
const component = mountComponent(ViewMode.VIEW);
|
||||
expect(component.render()).toMatchSnapshot();
|
||||
|
@ -72,7 +70,7 @@ describe('DashboardEmptyScreen', () => {
|
|||
|
||||
// even when in edit mode, readonly users should not have access to the editing buttons in the empty prompt.
|
||||
test('renders correctly with readonly and edit mode', () => {
|
||||
pluginServices.getServices().dashboardCapabilities.showWriteControls = false;
|
||||
(coreServices.application.capabilities as any).dashboard.showWriteControls = false;
|
||||
|
||||
const component = mountComponent(ViewMode.EDIT);
|
||||
expect(component.render()).toMatchSnapshot();
|
||||
|
|
|
@ -21,33 +21,31 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { METRIC_TYPE } from '@kbn/analytics';
|
||||
import { ViewMode } from '@kbn/embeddable-plugin/public';
|
||||
|
||||
import { useStateFromPublishingSubject } from '@kbn/presentation-publishing';
|
||||
import { DASHBOARD_UI_METRIC_ID } from '../../../dashboard_constants';
|
||||
import { pluginServices } from '../../../services/plugin_services';
|
||||
|
||||
import { useDashboardApi } from '../../../dashboard_api/use_dashboard_api';
|
||||
import { DASHBOARD_UI_METRIC_ID } from '../../../dashboard_constants';
|
||||
import {
|
||||
coreServices,
|
||||
dataService,
|
||||
embeddableService,
|
||||
usageCollectionService,
|
||||
visualizationsService,
|
||||
} from '../../../services/kibana_services';
|
||||
import { getDashboardCapabilities } from '../../../utils/get_dashboard_capabilities';
|
||||
import { emptyScreenStrings } from '../../_dashboard_container_strings';
|
||||
|
||||
export function DashboardEmptyScreen() {
|
||||
const {
|
||||
settings: {
|
||||
theme: { theme$ },
|
||||
},
|
||||
usageCollection,
|
||||
data: { search },
|
||||
http: { basePath },
|
||||
embeddable: { getStateTransfer },
|
||||
dashboardCapabilities: { showWriteControls },
|
||||
visualizations: { getAliases: getVisTypeAliases },
|
||||
} = pluginServices.getServices();
|
||||
|
||||
const lensAlias = useMemo(
|
||||
() => getVisTypeAliases().find(({ name }) => name === 'lens'),
|
||||
[getVisTypeAliases]
|
||||
() => visualizationsService.getAliases().find(({ name }) => name === 'lens'),
|
||||
[]
|
||||
);
|
||||
const { showWriteControls } = useMemo(() => {
|
||||
return getDashboardCapabilities();
|
||||
}, []);
|
||||
|
||||
const dashboardApi = useDashboardApi();
|
||||
const isDarkTheme = useObservable(theme$)?.darkMode;
|
||||
const isDarkTheme = useObservable(coreServices.theme.theme$)?.darkMode;
|
||||
const viewMode = useStateFromPublishingSubject(dashboardApi.viewMode);
|
||||
const isEditMode = useMemo(() => {
|
||||
return viewMode === 'edit';
|
||||
|
@ -55,8 +53,8 @@ export function DashboardEmptyScreen() {
|
|||
|
||||
const goToLens = useCallback(() => {
|
||||
if (!lensAlias || !lensAlias.alias) return;
|
||||
const trackUiMetric = usageCollection.reportUiCounter?.bind(
|
||||
usageCollection,
|
||||
const trackUiMetric = usageCollectionService?.reportUiCounter.bind(
|
||||
usageCollectionService,
|
||||
DASHBOARD_UI_METRIC_ID
|
||||
);
|
||||
|
||||
|
@ -64,18 +62,18 @@ export function DashboardEmptyScreen() {
|
|||
trackUiMetric(METRIC_TYPE.CLICK, `${lensAlias.name}:create`);
|
||||
}
|
||||
const appContext = dashboardApi.getAppContext();
|
||||
getStateTransfer().navigateToEditor(lensAlias.alias.app, {
|
||||
embeddableService.getStateTransfer().navigateToEditor(lensAlias.alias.app, {
|
||||
path: lensAlias.alias.path,
|
||||
state: {
|
||||
originatingApp: appContext?.currentAppId,
|
||||
originatingPath: appContext?.getCurrentPath?.() ?? '',
|
||||
searchSessionId: search.session.getSessionId(),
|
||||
searchSessionId: dataService.search.session.getSessionId(),
|
||||
},
|
||||
});
|
||||
}, [getStateTransfer, lensAlias, dashboardApi, search.session, usageCollection]);
|
||||
}, [lensAlias, dashboardApi]);
|
||||
|
||||
// TODO replace these SVGs with versions from EuiIllustration as soon as it becomes available.
|
||||
const imageUrl = basePath.prepend(
|
||||
const imageUrl = coreServices.http.basePath.prepend(
|
||||
`/plugins/dashboard/assets/${isDarkTheme ? 'dashboards_dark' : 'dashboards_light'}.svg`
|
||||
);
|
||||
|
||||
|
|
|
@ -7,15 +7,17 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import classNames from 'classnames';
|
||||
import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { EuiLoadingChart } from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
import { EmbeddablePanel, ReactEmbeddableRenderer } from '@kbn/embeddable-plugin/public';
|
||||
import classNames from 'classnames';
|
||||
import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing';
|
||||
import { DashboardPanelState } from '../../../../common';
|
||||
import { pluginServices } from '../../../services/plugin_services';
|
||||
import { useDashboardApi } from '../../../dashboard_api/use_dashboard_api';
|
||||
import { embeddableService, presentationUtilService } from '../../../services/kibana_services';
|
||||
|
||||
type DivProps = Pick<React.HTMLAttributes<HTMLDivElement>, 'className' | 'style' | 'children'>;
|
||||
|
||||
|
@ -97,10 +99,6 @@ export const Item = React.forwardRef<HTMLDivElement, Props>(
|
|||
: undefined;
|
||||
|
||||
const renderedEmbeddable = useMemo(() => {
|
||||
const {
|
||||
embeddable: { reactEmbeddableRegistryHasKey },
|
||||
} = pluginServices.getServices();
|
||||
|
||||
const panelProps = {
|
||||
showBadges: true,
|
||||
showBorder: useMargins,
|
||||
|
@ -109,7 +107,7 @@ export const Item = React.forwardRef<HTMLDivElement, Props>(
|
|||
};
|
||||
|
||||
// render React embeddable
|
||||
if (reactEmbeddableRegistryHasKey(type)) {
|
||||
if (embeddableService.reactEmbeddableRegistryHasKey(type)) {
|
||||
return (
|
||||
<ReactEmbeddableRenderer
|
||||
type={type}
|
||||
|
@ -190,18 +188,20 @@ export const ObservedItem = React.forwardRef<HTMLDivElement, Props>((props, pane
|
|||
// ReactGridLayout passes ref to children. Functional component children require forwardRef to avoid react warning
|
||||
// https://github.com/react-grid-layout/react-grid-layout#custom-child-components-and-draggable-handles
|
||||
export const DashboardGridItem = React.forwardRef<HTMLDivElement, Props>((props, ref) => {
|
||||
const {
|
||||
settings: { isProjectEnabledInLabs },
|
||||
} = pluginServices.getServices();
|
||||
const dashboardApi = useDashboardApi();
|
||||
const [focusedPanelId, viewMode] = useBatchedPublishingSubjects(
|
||||
dashboardApi.focusedPanelId$,
|
||||
dashboardApi.viewMode
|
||||
);
|
||||
|
||||
const deferBelowFoldEnabled = useMemo(
|
||||
() => presentationUtilService.labsService.isProjectEnabled('labs:dashboard:deferBelowFold'),
|
||||
[]
|
||||
);
|
||||
|
||||
const isEnabled =
|
||||
viewMode !== 'print' &&
|
||||
isProjectEnabledInLabs('labs:dashboard:deferBelowFold') &&
|
||||
deferBelowFoldEnabled &&
|
||||
(!focusedPanelId || focusedPanelId === props.id);
|
||||
|
||||
return isEnabled ? <ObservedItem ref={ref} {...props} /> : <Item ref={ref} {...props} />;
|
||||
|
|
|
@ -9,29 +9,32 @@
|
|||
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import useMountedState from 'react-use/lib/useMountedState';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import {
|
||||
EuiFormRow,
|
||||
EuiFieldText,
|
||||
EuiTextArea,
|
||||
EuiForm,
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
EuiCallOut,
|
||||
EuiFieldText,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFlyoutBody,
|
||||
EuiFlyoutFooter,
|
||||
EuiFlyoutHeader,
|
||||
EuiTitle,
|
||||
EuiCallOut,
|
||||
EuiForm,
|
||||
EuiFormRow,
|
||||
EuiIconTip,
|
||||
EuiSwitch,
|
||||
EuiText,
|
||||
EuiIconTip,
|
||||
EuiTextArea,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import { DashboardContainerInput } from '../../../../common';
|
||||
import { pluginServices } from '../../../services/plugin_services';
|
||||
import { useDashboardApi } from '../../../dashboard_api/use_dashboard_api';
|
||||
import { getDashboardContentManagementService } from '../../../services/dashboard_content_management_service';
|
||||
import { savedObjectsTaggingService } from '../../../services/kibana_services';
|
||||
|
||||
interface DashboardSettingsProps {
|
||||
onClose: () => void;
|
||||
|
@ -40,11 +43,6 @@ interface DashboardSettingsProps {
|
|||
const DUPLICATE_TITLE_CALLOUT_ID = 'duplicateTitleCallout';
|
||||
|
||||
export const DashboardSettings = ({ onClose }: DashboardSettingsProps) => {
|
||||
const {
|
||||
savedObjectsTagging: { components },
|
||||
dashboardContentManagement: { checkForDuplicateDashboardTitle },
|
||||
} = pluginServices.getServices();
|
||||
|
||||
const dashboardApi = useDashboardApi();
|
||||
|
||||
const [localSettings, setLocalSettings] = useState(dashboardApi.getSettings());
|
||||
|
@ -63,13 +61,15 @@ export const DashboardSettings = ({ onClose }: DashboardSettingsProps) => {
|
|||
|
||||
const onApply = async () => {
|
||||
setIsApplying(true);
|
||||
const validTitle = await checkForDuplicateDashboardTitle({
|
||||
title: localSettings.title,
|
||||
copyOnSave: false,
|
||||
lastSavedTitle: dashboardApi.panelTitle.value ?? '',
|
||||
onTitleDuplicate,
|
||||
isTitleDuplicateConfirmed,
|
||||
});
|
||||
const validTitle = await getDashboardContentManagementService().checkForDuplicateDashboardTitle(
|
||||
{
|
||||
title: localSettings.title,
|
||||
copyOnSave: false,
|
||||
lastSavedTitle: dashboardApi.panelTitle.value ?? '',
|
||||
onTitleDuplicate,
|
||||
isTitleDuplicateConfirmed,
|
||||
}
|
||||
);
|
||||
|
||||
if (!isMounted()) return;
|
||||
|
||||
|
@ -121,7 +121,9 @@ export const DashboardSettings = ({ onClose }: DashboardSettingsProps) => {
|
|||
};
|
||||
|
||||
const renderTagSelector = () => {
|
||||
if (!components) return;
|
||||
const savedObjectsTaggingApi = savedObjectsTaggingService?.getTaggingApi();
|
||||
if (!savedObjectsTaggingApi) return;
|
||||
|
||||
return (
|
||||
<EuiFormRow
|
||||
label={
|
||||
|
@ -131,7 +133,7 @@ export const DashboardSettings = ({ onClose }: DashboardSettingsProps) => {
|
|||
/>
|
||||
}
|
||||
>
|
||||
<components.TagSelector
|
||||
<savedObjectsTaggingApi.ui.components.TagSelector
|
||||
selected={localSettings.tags}
|
||||
onTagsSelected={(selectedTags) => updateDashboardSetting({ tags: selectedTags })}
|
||||
/>
|
||||
|
|
|
@ -15,11 +15,11 @@ import {
|
|||
ReferenceOrValueEmbeddable,
|
||||
} from '@kbn/embeddable-plugin/public';
|
||||
import {
|
||||
CONTACT_CARD_EMBEDDABLE,
|
||||
ContactCardEmbeddable,
|
||||
ContactCardEmbeddableFactory,
|
||||
ContactCardEmbeddableInput,
|
||||
ContactCardEmbeddableOutput,
|
||||
CONTACT_CARD_EMBEDDABLE,
|
||||
} from '@kbn/embeddable-plugin/public/lib/test_samples/embeddables';
|
||||
import { embeddablePluginMock } from '@kbn/embeddable-plugin/public/mocks';
|
||||
import {
|
||||
|
@ -34,7 +34,7 @@ import { render } from '@testing-library/react';
|
|||
import React from 'react';
|
||||
import { BehaviorSubject, lastValueFrom, Subject } from 'rxjs';
|
||||
import { buildMockDashboard, getSampleDashboardPanel } from '../../../mocks';
|
||||
import { pluginServices } from '../../../services/plugin_services';
|
||||
import { embeddableService } from '../../../services/kibana_services';
|
||||
import { DashboardContainer } from '../dashboard_container';
|
||||
import { duplicateDashboardPanel, incrementPanelTitle } from './duplicate_dashboard_panel';
|
||||
|
||||
|
@ -54,9 +54,7 @@ describe('Legacy embeddables', () => {
|
|||
|
||||
const mockEmbeddableFactory = new ContactCardEmbeddableFactory((() => null) as any, {} as any);
|
||||
|
||||
pluginServices.getServices().embeddable.getEmbeddableFactory = jest
|
||||
.fn()
|
||||
.mockReturnValue(mockEmbeddableFactory);
|
||||
embeddableService.getEmbeddableFactory = jest.fn().mockReturnValue(mockEmbeddableFactory);
|
||||
container = buildMockDashboard({
|
||||
overrides: {
|
||||
panels: {
|
||||
|
|
|
@ -7,6 +7,9 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { filter, map, max } from 'lodash';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { isReferenceOrValueEmbeddable, PanelNotFoundError } from '@kbn/embeddable-plugin/public';
|
||||
import { apiHasSnapshottableState } from '@kbn/presentation-containers/interfaces/serialized_state';
|
||||
import {
|
||||
|
@ -16,11 +19,10 @@ import {
|
|||
getPanelTitle,
|
||||
stateHasTitles,
|
||||
} from '@kbn/presentation-publishing';
|
||||
import { filter, map, max } from 'lodash';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { DashboardPanelState, prefixReferencesFromPanel } from '../../../../common';
|
||||
import { dashboardClonePanelActionStrings } from '../../../dashboard_actions/_dashboard_actions_strings';
|
||||
import { pluginServices } from '../../../services/plugin_services';
|
||||
import { coreServices, embeddableService } from '../../../services/kibana_services';
|
||||
import { placeClonePanel } from '../../panel_placement';
|
||||
import { DashboardContainer } from '../dashboard_container';
|
||||
|
||||
|
@ -105,17 +107,13 @@ const duplicateReactEmbeddableInput = async (
|
|||
};
|
||||
|
||||
export async function duplicateDashboardPanel(this: DashboardContainer, idToDuplicate: string) {
|
||||
const {
|
||||
notifications: { toasts },
|
||||
embeddable: { reactEmbeddableRegistryHasKey },
|
||||
} = pluginServices.getServices();
|
||||
const panelToClone = await this.getDashboardPanelFromId(idToDuplicate);
|
||||
|
||||
const duplicatedPanelState = reactEmbeddableRegistryHasKey(panelToClone.type)
|
||||
const duplicatedPanelState = embeddableService.reactEmbeddableRegistryHasKey(panelToClone.type)
|
||||
? await duplicateReactEmbeddableInput(this, panelToClone, idToDuplicate)
|
||||
: await duplicateLegacyInput(this, panelToClone, idToDuplicate);
|
||||
|
||||
toasts.addSuccess({
|
||||
coreServices.notifications.toasts.addSuccess({
|
||||
title: dashboardClonePanelActionStrings.getSuccessMessage(),
|
||||
'data-test-subj': 'addObjectToContainerSuccess',
|
||||
});
|
||||
|
|
|
@ -11,20 +11,14 @@ import React from 'react';
|
|||
|
||||
import { toMountPoint } from '@kbn/react-kibana-mount';
|
||||
|
||||
import { pluginServices } from '../../../services/plugin_services';
|
||||
import { DashboardSettings } from '../../component/settings/settings_flyout';
|
||||
import { DashboardContext } from '../../../dashboard_api/use_dashboard_api';
|
||||
import { DashboardApi } from '../../../dashboard_api/types';
|
||||
import { DashboardContext } from '../../../dashboard_api/use_dashboard_api';
|
||||
import { coreServices } from '../../../services/kibana_services';
|
||||
import { DashboardSettings } from '../../component/settings/settings_flyout';
|
||||
|
||||
export function openSettingsFlyout(dashboardApi: DashboardApi) {
|
||||
const {
|
||||
analytics,
|
||||
settings: { i18n, theme },
|
||||
overlays,
|
||||
} = pluginServices.getServices();
|
||||
|
||||
dashboardApi.openOverlay(
|
||||
overlays.openFlyout(
|
||||
coreServices.overlays.openFlyout(
|
||||
toMountPoint(
|
||||
<DashboardContext.Provider value={dashboardApi}>
|
||||
<DashboardSettings
|
||||
|
@ -33,7 +27,7 @@ export function openSettingsFlyout(dashboardApi: DashboardApi) {
|
|||
}}
|
||||
/>
|
||||
</DashboardContext.Provider>,
|
||||
{ analytics, i18n, theme }
|
||||
{ analytics: coreServices.analytics, i18n: coreServices.i18n, theme: coreServices.theme }
|
||||
),
|
||||
{
|
||||
size: 's',
|
||||
|
|
|
@ -7,14 +7,15 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { Fragment, useCallback } from 'react';
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiIconTip, EuiSwitch } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiFormRow, EuiSwitch, EuiIconTip, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { SavedObjectSaveModal } from '@kbn/saved-objects-plugin/public';
|
||||
|
||||
import { savedObjectsTaggingService } from '../../../../services/kibana_services';
|
||||
import type { DashboardSaveOptions } from '../../../types';
|
||||
import { pluginServices } from '../../../../services/plugin_services';
|
||||
|
||||
/**
|
||||
* TODO: Portable Dashboard followup, use redux for the state.
|
||||
|
@ -79,12 +80,9 @@ export const DashboardSaveModal: React.FC<DashboardSaveModalProps> = ({
|
|||
);
|
||||
|
||||
const renderDashboardSaveOptions = useCallback(() => {
|
||||
const {
|
||||
savedObjectsTagging: { components },
|
||||
} = pluginServices.getServices();
|
||||
|
||||
const tagSelector = components ? (
|
||||
<components.SavedObjectSaveModalTagSelector
|
||||
const savedObjectsTaggingApi = savedObjectsTaggingService?.getTaggingApi();
|
||||
const tagSelector = savedObjectsTaggingApi ? (
|
||||
<savedObjectsTaggingApi.ui.components.SavedObjectSaveModalTagSelector
|
||||
initialSelection={selectedTags}
|
||||
onTagsSelected={(selectedTagIds) => {
|
||||
setSelectedTags(selectedTagIds);
|
||||
|
|
|
@ -7,6 +7,10 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { cloneDeep } from 'lodash';
|
||||
import React from 'react';
|
||||
import { batch } from 'react-redux';
|
||||
|
||||
import type { Reference } from '@kbn/content-management-utils';
|
||||
import { reportPerformanceMetricEvent } from '@kbn/ebt-tools';
|
||||
import {
|
||||
|
@ -14,12 +18,10 @@ import {
|
|||
isReferenceOrValueEmbeddable,
|
||||
ViewMode,
|
||||
} from '@kbn/embeddable-plugin/public';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { apiHasSerializableState, SerializedPanelState } from '@kbn/presentation-containers';
|
||||
import { showSaveModal } from '@kbn/saved-objects-plugin/public';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import React from 'react';
|
||||
import { batch } from 'react-redux';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import {
|
||||
DashboardContainerInput,
|
||||
DashboardPanelMap,
|
||||
|
@ -29,8 +31,14 @@ import { DASHBOARD_CONTENT_ID, SAVED_OBJECT_POST_TIME } from '../../../dashboard
|
|||
import {
|
||||
SaveDashboardReturn,
|
||||
SavedDashboardInput,
|
||||
} from '../../../services/dashboard_content_management/types';
|
||||
import { pluginServices } from '../../../services/plugin_services';
|
||||
} from '../../../services/dashboard_content_management_service/types';
|
||||
import { getDashboardContentManagementService } from '../../../services/dashboard_content_management_service';
|
||||
import {
|
||||
coreServices,
|
||||
dataService,
|
||||
embeddableService,
|
||||
savedObjectsTaggingService,
|
||||
} from '../../../services/kibana_services';
|
||||
import { DashboardSaveOptions, DashboardStateFromSaveModal } from '../../types';
|
||||
import { DashboardContainer } from '../dashboard_container';
|
||||
import { extractTitleAndCount } from './lib/extract_title_and_count';
|
||||
|
@ -39,9 +47,6 @@ import { DashboardSaveModal } from './overlays/save_modal';
|
|||
const serializeAllPanelState = async (
|
||||
dashboard: DashboardContainer
|
||||
): Promise<{ panels: DashboardContainerInput['panels']; references: Reference[] }> => {
|
||||
const {
|
||||
embeddable: { reactEmbeddableRegistryHasKey },
|
||||
} = pluginServices.getServices();
|
||||
const references: Reference[] = [];
|
||||
const panels = cloneDeep(dashboard.getInput().panels);
|
||||
|
||||
|
@ -49,7 +54,7 @@ const serializeAllPanelState = async (
|
|||
Promise<{ uuid: string; serialized: SerializedPanelState<object> }>
|
||||
> = [];
|
||||
for (const [uuid, panel] of Object.entries(panels)) {
|
||||
if (!reactEmbeddableRegistryHasKey(panel.type)) continue;
|
||||
if (!embeddableService.reactEmbeddableRegistryHasKey(panel.type)) continue;
|
||||
const api = dashboard.children$.value[uuid];
|
||||
|
||||
if (api && apiHasSerializableState(api)) {
|
||||
|
@ -75,10 +80,6 @@ const serializeAllPanelState = async (
|
|||
* Save the current state of this dashboard to a saved object without showing any save modal.
|
||||
*/
|
||||
export async function runQuickSave(this: DashboardContainer) {
|
||||
const {
|
||||
dashboardContentManagement: { saveDashboardState },
|
||||
} = pluginServices.getServices();
|
||||
|
||||
const {
|
||||
explicitInput: currentState,
|
||||
componentState: { lastSavedId, managed },
|
||||
|
@ -98,7 +99,7 @@ export async function runQuickSave(this: DashboardContainer) {
|
|||
stateToSave = { ...stateToSave, controlGroupInput: controlGroupSerializedState };
|
||||
}
|
||||
|
||||
const saveResult = await saveDashboardState({
|
||||
const saveResult = await getDashboardContentManagementService().saveDashboardState({
|
||||
controlGroupReferences,
|
||||
panelReferences: references,
|
||||
currentState: stateToSave,
|
||||
|
@ -118,20 +119,11 @@ export async function runQuickSave(this: DashboardContainer) {
|
|||
* accounts for scenarios of cloning elastic managed dashboard into user managed dashboards
|
||||
*/
|
||||
export async function runInteractiveSave(this: DashboardContainer, interactionMode: ViewMode) {
|
||||
const {
|
||||
data: {
|
||||
query: {
|
||||
timefilter: { timefilter },
|
||||
},
|
||||
},
|
||||
savedObjectsTagging: { hasApi: hasSavedObjectsTagging },
|
||||
dashboardContentManagement: { checkForDuplicateDashboardTitle, saveDashboardState },
|
||||
} = pluginServices.getServices();
|
||||
|
||||
const {
|
||||
explicitInput: currentState,
|
||||
componentState: { lastSavedId, managed },
|
||||
} = this.getState();
|
||||
const dashboardContentManagementService = getDashboardContentManagementService();
|
||||
|
||||
return new Promise<SaveDashboardReturn | undefined>((resolve, reject) => {
|
||||
if (interactionMode === ViewMode.EDIT && managed) {
|
||||
|
@ -156,7 +148,7 @@ export async function runInteractiveSave(this: DashboardContainer, interactionMo
|
|||
|
||||
try {
|
||||
if (
|
||||
!(await checkForDuplicateDashboardTitle({
|
||||
!(await dashboardContentManagementService.checkForDuplicateDashboardTitle({
|
||||
title: newTitle,
|
||||
onTitleDuplicate,
|
||||
lastSavedTitle: currentState.title,
|
||||
|
@ -172,11 +164,13 @@ export async function runInteractiveSave(this: DashboardContainer, interactionMo
|
|||
tags: [] as string[],
|
||||
description: newDescription,
|
||||
timeRestore: newTimeRestore,
|
||||
timeRange: newTimeRestore ? timefilter.getTime() : undefined,
|
||||
refreshInterval: newTimeRestore ? timefilter.getRefreshInterval() : undefined,
|
||||
timeRange: newTimeRestore ? dataService.query.timefilter.timefilter.getTime() : undefined,
|
||||
refreshInterval: newTimeRestore
|
||||
? dataService.query.timefilter.timefilter.getRefreshInterval()
|
||||
: undefined,
|
||||
};
|
||||
|
||||
if (hasSavedObjectsTagging && newTags) {
|
||||
if (savedObjectsTaggingService && newTags) {
|
||||
// remove `hasSavedObjectsTagging` once the savedObjectsTagging service is optional
|
||||
stateFromSaveModal.tags = newTags;
|
||||
}
|
||||
|
@ -226,7 +220,7 @@ export async function runInteractiveSave(this: DashboardContainer, interactionMo
|
|||
|
||||
const beforeAddTime = window.performance.now();
|
||||
|
||||
const saveResult = await saveDashboardState({
|
||||
const saveResult = await dashboardContentManagementService.saveDashboardState({
|
||||
controlGroupReferences,
|
||||
panelReferences: references,
|
||||
saveOptions,
|
||||
|
@ -240,7 +234,7 @@ export async function runInteractiveSave(this: DashboardContainer, interactionMo
|
|||
|
||||
const addDuration = window.performance.now() - beforeAddTime;
|
||||
|
||||
reportPerformanceMetricEvent(pluginServices.getServices().analytics, {
|
||||
reportPerformanceMetricEvent(coreServices.analytics, {
|
||||
eventName: SAVED_OBJECT_POST_TIME,
|
||||
duration: addDuration,
|
||||
meta: {
|
||||
|
@ -279,7 +273,7 @@ export async function runInteractiveSave(this: DashboardContainer, interactionMo
|
|||
|
||||
newTitle = `${baseTitle} (${baseCount + 1})`;
|
||||
|
||||
await checkForDuplicateDashboardTitle({
|
||||
await dashboardContentManagementService.checkForDuplicateDashboardTitle({
|
||||
title: newTitle,
|
||||
lastSavedTitle: currentState.title,
|
||||
copyOnSave: true,
|
||||
|
|
|
@ -7,34 +7,34 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { EmbeddablePackageState, ViewMode } from '@kbn/embeddable-plugin/public';
|
||||
import {
|
||||
CONTACT_CARD_EMBEDDABLE,
|
||||
ContactCardEmbeddable,
|
||||
ContactCardEmbeddableFactory,
|
||||
ContactCardEmbeddableInput,
|
||||
ContactCardEmbeddableOutput,
|
||||
CONTACT_CARD_EMBEDDABLE,
|
||||
} from '@kbn/embeddable-plugin/public/lib/test_samples';
|
||||
import { Filter } from '@kbn/es-query';
|
||||
import { EmbeddablePackageState, ViewMode } from '@kbn/embeddable-plugin/public';
|
||||
import { createKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public';
|
||||
|
||||
import { createDashboard } from './create_dashboard';
|
||||
import { getSampleDashboardPanel } from '../../../mocks';
|
||||
import { pluginServices } from '../../../services/plugin_services';
|
||||
import { DashboardCreationOptions } from '../dashboard_container_factory';
|
||||
import { DEFAULT_DASHBOARD_INPUT } from '../../../dashboard_constants';
|
||||
import { mockControlGroupApi } from '../../../mocks';
|
||||
import { getSampleDashboardPanel, mockControlGroupApi } from '../../../mocks';
|
||||
import { dataService, embeddableService } from '../../../services/kibana_services';
|
||||
import { DashboardCreationOptions } from '../dashboard_container_factory';
|
||||
import { createDashboard } from './create_dashboard';
|
||||
import { getDashboardContentManagementService } from '../../../services/dashboard_content_management_service';
|
||||
import { getDashboardBackupService } from '../../../services/dashboard_backup_service';
|
||||
|
||||
const dashboardBackupService = getDashboardBackupService();
|
||||
const dashboardContentManagementService = getDashboardContentManagementService();
|
||||
|
||||
test("doesn't throw error when no data views are available", async () => {
|
||||
pluginServices.getServices().data.dataViews.defaultDataViewExists = jest
|
||||
.fn()
|
||||
.mockReturnValue(false);
|
||||
dataService.dataViews.defaultDataViewExists = jest.fn().mockReturnValue(false);
|
||||
expect(await createDashboard()).toBeDefined();
|
||||
|
||||
// reset get default data view
|
||||
pluginServices.getServices().data.dataViews.defaultDataViewExists = jest
|
||||
.fn()
|
||||
.mockResolvedValue(true);
|
||||
dataService.dataViews.defaultDataViewExists = jest.fn().mockResolvedValue(true);
|
||||
});
|
||||
|
||||
test('throws error when provided validation function returns invalid', async () => {
|
||||
|
@ -73,79 +73,63 @@ test('does not get initial input when provided validation function returns redir
|
|||
});
|
||||
|
||||
test('pulls state from dashboard saved object when given a saved object id', async () => {
|
||||
pluginServices.getServices().dashboardContentManagement.loadDashboardState = jest
|
||||
.fn()
|
||||
.mockResolvedValue({
|
||||
dashboardInput: {
|
||||
...DEFAULT_DASHBOARD_INPUT,
|
||||
description: `wow would you look at that? Wow.`,
|
||||
},
|
||||
});
|
||||
dashboardContentManagementService.loadDashboardState = jest.fn().mockResolvedValue({
|
||||
dashboardInput: {
|
||||
...DEFAULT_DASHBOARD_INPUT,
|
||||
description: `wow would you look at that? Wow.`,
|
||||
},
|
||||
});
|
||||
const dashboard = await createDashboard({}, 0, 'wow-such-id');
|
||||
expect(
|
||||
pluginServices.getServices().dashboardContentManagement.loadDashboardState
|
||||
).toHaveBeenCalledWith({ id: 'wow-such-id' });
|
||||
expect(dashboardContentManagementService.loadDashboardState).toHaveBeenCalledWith({
|
||||
id: 'wow-such-id',
|
||||
});
|
||||
expect(dashboard).toBeDefined();
|
||||
expect(dashboard!.getState().explicitInput.description).toBe(`wow would you look at that? Wow.`);
|
||||
});
|
||||
|
||||
test('passes managed state from the saved object into the Dashboard component state', async () => {
|
||||
pluginServices.getServices().dashboardContentManagement.loadDashboardState = jest
|
||||
.fn()
|
||||
.mockResolvedValue({
|
||||
dashboardInput: {
|
||||
...DEFAULT_DASHBOARD_INPUT,
|
||||
description: 'wow this description is okay',
|
||||
},
|
||||
managed: true,
|
||||
});
|
||||
dashboardContentManagementService.loadDashboardState = jest.fn().mockResolvedValue({
|
||||
dashboardInput: {
|
||||
...DEFAULT_DASHBOARD_INPUT,
|
||||
description: 'wow this description is okay',
|
||||
},
|
||||
managed: true,
|
||||
});
|
||||
const dashboard = await createDashboard({}, 0, 'what-an-id');
|
||||
expect(dashboard).toBeDefined();
|
||||
expect(dashboard!.getState().componentState.managed).toBe(true);
|
||||
});
|
||||
|
||||
test('pulls view mode from dashboard backup', async () => {
|
||||
pluginServices.getServices().dashboardContentManagement.loadDashboardState = jest
|
||||
.fn()
|
||||
.mockResolvedValue({
|
||||
dashboardInput: DEFAULT_DASHBOARD_INPUT,
|
||||
});
|
||||
pluginServices.getServices().dashboardBackup.getViewMode = jest
|
||||
.fn()
|
||||
.mockReturnValue(ViewMode.EDIT);
|
||||
dashboardContentManagementService.loadDashboardState = jest.fn().mockResolvedValue({
|
||||
dashboardInput: DEFAULT_DASHBOARD_INPUT,
|
||||
});
|
||||
dashboardBackupService.getViewMode = jest.fn().mockReturnValue(ViewMode.EDIT);
|
||||
const dashboard = await createDashboard({ useSessionStorageIntegration: true }, 0, 'what-an-id');
|
||||
expect(dashboard).toBeDefined();
|
||||
expect(dashboard!.getState().explicitInput.viewMode).toBe(ViewMode.EDIT);
|
||||
});
|
||||
|
||||
test('new dashboards start in edit mode', async () => {
|
||||
pluginServices.getServices().dashboardBackup.getViewMode = jest
|
||||
.fn()
|
||||
.mockReturnValue(ViewMode.VIEW);
|
||||
pluginServices.getServices().dashboardContentManagement.loadDashboardState = jest
|
||||
.fn()
|
||||
.mockResolvedValue({
|
||||
newDashboardCreated: true,
|
||||
dashboardInput: {
|
||||
...DEFAULT_DASHBOARD_INPUT,
|
||||
description: 'wow this description is okay',
|
||||
},
|
||||
});
|
||||
dashboardBackupService.getViewMode = jest.fn().mockReturnValue(ViewMode.VIEW);
|
||||
dashboardContentManagementService.loadDashboardState = jest.fn().mockResolvedValue({
|
||||
newDashboardCreated: true,
|
||||
dashboardInput: {
|
||||
...DEFAULT_DASHBOARD_INPUT,
|
||||
description: 'wow this description is okay',
|
||||
},
|
||||
});
|
||||
const dashboard = await createDashboard({ useSessionStorageIntegration: true }, 0, 'wow-such-id');
|
||||
expect(dashboard).toBeDefined();
|
||||
expect(dashboard!.getState().explicitInput.viewMode).toBe(ViewMode.EDIT);
|
||||
});
|
||||
|
||||
test('managed dashboards start in view mode', async () => {
|
||||
pluginServices.getServices().dashboardBackup.getViewMode = jest
|
||||
.fn()
|
||||
.mockReturnValue(ViewMode.EDIT);
|
||||
pluginServices.getServices().dashboardContentManagement.loadDashboardState = jest
|
||||
.fn()
|
||||
.mockResolvedValue({
|
||||
dashboardInput: DEFAULT_DASHBOARD_INPUT,
|
||||
managed: true,
|
||||
});
|
||||
dashboardBackupService.getViewMode = jest.fn().mockReturnValue(ViewMode.EDIT);
|
||||
dashboardContentManagementService.loadDashboardState = jest.fn().mockResolvedValue({
|
||||
dashboardInput: DEFAULT_DASHBOARD_INPUT,
|
||||
managed: true,
|
||||
});
|
||||
const dashboard = await createDashboard({}, 0, 'what-an-id');
|
||||
expect(dashboard).toBeDefined();
|
||||
expect(dashboard!.getState().componentState.managed).toBe(true);
|
||||
|
@ -153,15 +137,13 @@ test('managed dashboards start in view mode', async () => {
|
|||
});
|
||||
|
||||
test('pulls state from backup which overrides state from saved object', async () => {
|
||||
pluginServices.getServices().dashboardContentManagement.loadDashboardState = jest
|
||||
.fn()
|
||||
.mockResolvedValue({
|
||||
dashboardInput: {
|
||||
...DEFAULT_DASHBOARD_INPUT,
|
||||
description: 'wow this description is okay',
|
||||
},
|
||||
});
|
||||
pluginServices.getServices().dashboardBackup.getState = jest
|
||||
dashboardContentManagementService.loadDashboardState = jest.fn().mockResolvedValue({
|
||||
dashboardInput: {
|
||||
...DEFAULT_DASHBOARD_INPUT,
|
||||
description: 'wow this description is okay',
|
||||
},
|
||||
});
|
||||
dashboardBackupService.getState = jest
|
||||
.fn()
|
||||
.mockReturnValue({ dashboardState: { description: 'wow this description marginally better' } });
|
||||
const dashboard = await createDashboard({ useSessionStorageIntegration: true }, 0, 'wow-such-id');
|
||||
|
@ -172,15 +154,13 @@ test('pulls state from backup which overrides state from saved object', async ()
|
|||
});
|
||||
|
||||
test('pulls state from override input which overrides all other state sources', async () => {
|
||||
pluginServices.getServices().dashboardContentManagement.loadDashboardState = jest
|
||||
.fn()
|
||||
.mockResolvedValue({
|
||||
dashboardInput: {
|
||||
...DEFAULT_DASHBOARD_INPUT,
|
||||
description: 'wow this description is okay',
|
||||
},
|
||||
});
|
||||
pluginServices.getServices().dashboardBackup.getState = jest
|
||||
dashboardContentManagementService.loadDashboardState = jest.fn().mockResolvedValue({
|
||||
dashboardInput: {
|
||||
...DEFAULT_DASHBOARD_INPUT,
|
||||
description: 'wow this description is okay',
|
||||
},
|
||||
});
|
||||
dashboardBackupService.getState = jest
|
||||
.fn()
|
||||
.mockReturnValue({ description: 'wow this description marginally better' });
|
||||
const dashboard = await createDashboard(
|
||||
|
@ -198,35 +178,33 @@ test('pulls state from override input which overrides all other state sources',
|
|||
});
|
||||
|
||||
test('pulls panels from override input', async () => {
|
||||
pluginServices.getServices().embeddable.reactEmbeddableRegistryHasKey = jest
|
||||
embeddableService.reactEmbeddableRegistryHasKey = jest
|
||||
.fn()
|
||||
.mockImplementation((type: string) => type === 'reactEmbeddable');
|
||||
pluginServices.getServices().dashboardContentManagement.loadDashboardState = jest
|
||||
.fn()
|
||||
.mockResolvedValue({
|
||||
dashboardInput: {
|
||||
...DEFAULT_DASHBOARD_INPUT,
|
||||
panels: {
|
||||
...DEFAULT_DASHBOARD_INPUT.panels,
|
||||
someLegacyPanel: {
|
||||
type: 'legacy',
|
||||
gridData: { x: 0, y: 0, w: 0, h: 0, i: 'someLegacyPanel' },
|
||||
explicitInput: {
|
||||
id: 'someLegacyPanel',
|
||||
title: 'stateFromSavedObject',
|
||||
},
|
||||
dashboardContentManagementService.loadDashboardState = jest.fn().mockResolvedValue({
|
||||
dashboardInput: {
|
||||
...DEFAULT_DASHBOARD_INPUT,
|
||||
panels: {
|
||||
...DEFAULT_DASHBOARD_INPUT.panels,
|
||||
someLegacyPanel: {
|
||||
type: 'legacy',
|
||||
gridData: { x: 0, y: 0, w: 0, h: 0, i: 'someLegacyPanel' },
|
||||
explicitInput: {
|
||||
id: 'someLegacyPanel',
|
||||
title: 'stateFromSavedObject',
|
||||
},
|
||||
someReactEmbeddablePanel: {
|
||||
type: 'reactEmbeddable',
|
||||
gridData: { x: 0, y: 0, w: 0, h: 0, i: 'someReactEmbeddablePanel' },
|
||||
explicitInput: {
|
||||
id: 'someReactEmbeddablePanel',
|
||||
title: 'stateFromSavedObject',
|
||||
},
|
||||
},
|
||||
someReactEmbeddablePanel: {
|
||||
type: 'reactEmbeddable',
|
||||
gridData: { x: 0, y: 0, w: 0, h: 0, i: 'someReactEmbeddablePanel' },
|
||||
explicitInput: {
|
||||
id: 'someReactEmbeddablePanel',
|
||||
title: 'stateFromSavedObject',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
const dashboard = await createDashboard(
|
||||
{
|
||||
useSessionStorageIntegration: true,
|
||||
|
@ -286,10 +264,8 @@ test('applies filters and query from state to query service', async () => {
|
|||
},
|
||||
getInitialInput: () => ({ filters, query }),
|
||||
});
|
||||
expect(pluginServices.getServices().data.query.queryString.setQuery).toHaveBeenCalledWith(query);
|
||||
expect(pluginServices.getServices().data.query.filterManager.setAppFilters).toHaveBeenCalledWith(
|
||||
filters
|
||||
);
|
||||
expect(dataService.query.queryString.setQuery).toHaveBeenCalledWith(query);
|
||||
expect(dataService.query.filterManager.setAppFilters).toHaveBeenCalledWith(filters);
|
||||
});
|
||||
|
||||
test('applies time range and refresh interval from initial input to query service if time restore is on', async () => {
|
||||
|
@ -302,20 +278,16 @@ test('applies time range and refresh interval from initial input to query servic
|
|||
},
|
||||
getInitialInput: () => ({ timeRange, refreshInterval, timeRestore: true }),
|
||||
});
|
||||
expect(
|
||||
pluginServices.getServices().data.query.timefilter.timefilter.setTime
|
||||
).toHaveBeenCalledWith(timeRange);
|
||||
expect(
|
||||
pluginServices.getServices().data.query.timefilter.timefilter.setRefreshInterval
|
||||
).toHaveBeenCalledWith(refreshInterval);
|
||||
expect(dataService.query.timefilter.timefilter.setTime).toHaveBeenCalledWith(timeRange);
|
||||
expect(dataService.query.timefilter.timefilter.setRefreshInterval).toHaveBeenCalledWith(
|
||||
refreshInterval
|
||||
);
|
||||
});
|
||||
|
||||
test('applies time range from query service to initial input if time restore is on but there is an explicit time range in the URL', async () => {
|
||||
const urlTimeRange = { from: new Date().toISOString(), to: new Date().toISOString() };
|
||||
const savedTimeRange = { from: 'now - 7 days', to: 'now' };
|
||||
pluginServices.getServices().data.query.timefilter.timefilter.getTime = jest
|
||||
.fn()
|
||||
.mockReturnValue(urlTimeRange);
|
||||
dataService.query.timefilter.timefilter.getTime = jest.fn().mockReturnValue(urlTimeRange);
|
||||
const kbnUrlStateStorage = createKbnUrlStateStorage();
|
||||
kbnUrlStateStorage.get = jest.fn().mockReturnValue({ time: urlTimeRange });
|
||||
|
||||
|
@ -335,9 +307,7 @@ test('applies time range from query service to initial input if time restore is
|
|||
|
||||
test('applies time range from query service to initial input if time restore is off', async () => {
|
||||
const timeRange = { from: new Date().toISOString(), to: new Date().toISOString() };
|
||||
pluginServices.getServices().data.query.timefilter.timefilter.getTime = jest
|
||||
.fn()
|
||||
.mockReturnValue(timeRange);
|
||||
dataService.query.timefilter.timefilter.getTime = jest.fn().mockReturnValue(timeRange);
|
||||
const dashboard = await createDashboard({
|
||||
useUnifiedSearchIntegration: true,
|
||||
unifiedSearchSettings: {
|
||||
|
@ -393,9 +363,7 @@ test('creates new embeddable with incoming embeddable if id does not match exist
|
|||
create: jest.fn().mockReturnValue({ destroy: jest.fn() }),
|
||||
getDefaultInput: jest.fn().mockResolvedValue({}),
|
||||
};
|
||||
pluginServices.getServices().embeddable.getEmbeddableFactory = jest
|
||||
.fn()
|
||||
.mockReturnValue(mockContactCardFactory);
|
||||
embeddableService.getEmbeddableFactory = jest.fn().mockReturnValue(mockContactCardFactory);
|
||||
|
||||
const dashboard = await createDashboard({
|
||||
getIncomingEmbeddable: () => incomingEmbeddable,
|
||||
|
@ -454,9 +422,7 @@ test('creates new embeddable with specified size if size is provided', async ()
|
|||
create: jest.fn().mockReturnValue({ destroy: jest.fn() }),
|
||||
getDefaultInput: jest.fn().mockResolvedValue({}),
|
||||
};
|
||||
pluginServices.getServices().embeddable.getEmbeddableFactory = jest
|
||||
.fn()
|
||||
.mockReturnValue(mockContactCardFactory);
|
||||
embeddableService.getEmbeddableFactory = jest.fn().mockReturnValue(mockContactCardFactory);
|
||||
|
||||
const dashboard = await createDashboard({
|
||||
getIncomingEmbeddable: () => incomingEmbeddable,
|
||||
|
@ -513,11 +479,9 @@ test('searchSessionId is updated prior to child embeddable parent subscription e
|
|||
},
|
||||
}),
|
||||
};
|
||||
pluginServices.getServices().embeddable.getEmbeddableFactory = jest
|
||||
.fn()
|
||||
.mockReturnValue(embeddableFactory);
|
||||
embeddableService.getEmbeddableFactory = jest.fn().mockReturnValue(embeddableFactory);
|
||||
let sessionCount = 0;
|
||||
pluginServices.getServices().data.search.session.start = () => {
|
||||
dataService.search.session.start = () => {
|
||||
sessionCount++;
|
||||
return `searchSessionId${sessionCount}`;
|
||||
};
|
||||
|
|
|
@ -7,13 +7,16 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { cloneDeep, omit } from 'lodash';
|
||||
import { Subject } from 'rxjs';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { ContentInsightsClient } from '@kbn/content-management-content-insights-public';
|
||||
import { GlobalQueryStateFromUrl, syncGlobalQueryStateWithUrl } from '@kbn/data-plugin/public';
|
||||
import { ViewMode } from '@kbn/embeddable-plugin/public';
|
||||
import { TimeRange } from '@kbn/es-query';
|
||||
import { lazyLoadReduxToolsPackage } from '@kbn/presentation-util-plugin/public';
|
||||
import { cloneDeep, omit } from 'lodash';
|
||||
import { Subject } from 'rxjs';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import {
|
||||
DashboardContainerInput,
|
||||
DashboardPanelMap,
|
||||
|
@ -26,11 +29,17 @@ import {
|
|||
GLOBAL_STATE_STORAGE_KEY,
|
||||
PanelPlacementStrategy,
|
||||
} from '../../../dashboard_constants';
|
||||
import {
|
||||
PANELS_CONTROL_GROUP_KEY,
|
||||
getDashboardBackupService,
|
||||
} from '../../../services/dashboard_backup_service';
|
||||
import { getDashboardContentManagementService } from '../../../services/dashboard_content_management_service';
|
||||
import {
|
||||
LoadDashboardReturn,
|
||||
SavedDashboardInput,
|
||||
} from '../../../services/dashboard_content_management/types';
|
||||
import { pluginServices } from '../../../services/plugin_services';
|
||||
} from '../../../services/dashboard_content_management_service/types';
|
||||
import { coreServices, dataService, embeddableService } from '../../../services/kibana_services';
|
||||
import { getDashboardCapabilities } from '../../../utils/get_dashboard_capabilities';
|
||||
import { runPanelPlacementStrategy } from '../../panel_placement/place_new_panel_strategies';
|
||||
import { startDiffingDashboardState } from '../../state/diffing/dashboard_diffing_integration';
|
||||
import { DashboardPublicState, UnsavedPanelState } from '../../types';
|
||||
|
@ -40,7 +49,6 @@ import { startSyncingDashboardDataViews } from './data_views/sync_dashboard_data
|
|||
import { startQueryPerformanceTracking } from './performance/query_performance_tracking';
|
||||
import { startDashboardSearchSessionIntegration } from './search_sessions/start_dashboard_search_session_integration';
|
||||
import { syncUnifiedSearchState } from './unified_search/sync_dashboard_unified_search_state';
|
||||
import { PANELS_CONTROL_GROUP_KEY } from '../../../services/dashboard_backup/dashboard_backup_service';
|
||||
|
||||
/**
|
||||
* Builds a new Dashboard from scratch.
|
||||
|
@ -50,11 +58,6 @@ export const createDashboard = async (
|
|||
dashboardCreationStartTime?: number,
|
||||
savedObjectId?: string
|
||||
): Promise<DashboardContainer | undefined> => {
|
||||
const {
|
||||
data: { dataViews },
|
||||
dashboardContentManagement: { loadDashboardState },
|
||||
} = pluginServices.getServices();
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// Create method which allows work to be done on the dashboard container when it's ready.
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
@ -71,8 +74,11 @@ export const createDashboard = async (
|
|||
// Lazy load required systems and Dashboard saved object.
|
||||
// --------------------------------------------------------------------------------------
|
||||
const reduxEmbeddablePackagePromise = lazyLoadReduxToolsPackage();
|
||||
const defaultDataViewExistsPromise = dataViews.defaultDataViewExists();
|
||||
const dashboardSavedObjectPromise = loadDashboardState({ id: savedObjectId });
|
||||
const defaultDataViewExistsPromise = dataService.dataViews.defaultDataViewExists();
|
||||
const dashboardContentManagementService = getDashboardContentManagementService();
|
||||
const dashboardSavedObjectPromise = dashboardContentManagementService.loadDashboardState({
|
||||
id: savedObjectId,
|
||||
});
|
||||
|
||||
const [reduxEmbeddablePackage, savedObjectResult] = await Promise.all([
|
||||
reduxEmbeddablePackagePromise,
|
||||
|
@ -140,21 +146,12 @@ export const initializeDashboard = async ({
|
|||
untilDashboardReady: () => Promise<DashboardContainer>;
|
||||
creationOptions?: DashboardCreationOptions;
|
||||
}) => {
|
||||
const {
|
||||
dashboardBackup,
|
||||
dashboardCapabilities: { showWriteControls },
|
||||
embeddable: { reactEmbeddableRegistryHasKey },
|
||||
data: {
|
||||
query: queryService,
|
||||
search: { session },
|
||||
},
|
||||
dashboardContentInsights,
|
||||
} = pluginServices.getServices();
|
||||
const {
|
||||
queryString,
|
||||
filterManager,
|
||||
timefilter: { timefilter: timefilterService },
|
||||
} = queryService;
|
||||
} = dataService.query;
|
||||
const dashboardBackupService = getDashboardBackupService();
|
||||
|
||||
const {
|
||||
getInitialInput,
|
||||
|
@ -179,7 +176,7 @@ export const initializeDashboard = async ({
|
|||
// --------------------------------------------------------------------------------------
|
||||
// Combine input from saved object, and session storage
|
||||
// --------------------------------------------------------------------------------------
|
||||
const dashboardBackupState = dashboardBackup.getState(loadDashboardReturn.dashboardId);
|
||||
const dashboardBackupState = dashboardBackupService.getState(loadDashboardReturn.dashboardId);
|
||||
const runtimePanelsToRestore: UnsavedPanelState = useSessionStorageIntegration
|
||||
? dashboardBackupState?.panels ?? {}
|
||||
: {};
|
||||
|
@ -189,15 +186,16 @@ export const initializeDashboard = async ({
|
|||
return dashboardBackupState?.dashboardState;
|
||||
})();
|
||||
const initialViewMode = (() => {
|
||||
if (loadDashboardReturn.managed || !showWriteControls) return ViewMode.VIEW;
|
||||
if (loadDashboardReturn.managed || !getDashboardCapabilities().showWriteControls)
|
||||
return ViewMode.VIEW;
|
||||
if (
|
||||
loadDashboardReturn.newDashboardCreated ||
|
||||
dashboardBackup.dashboardHasUnsavedEdits(loadDashboardReturn.dashboardId)
|
||||
dashboardBackupService.dashboardHasUnsavedEdits(loadDashboardReturn.dashboardId)
|
||||
) {
|
||||
return ViewMode.EDIT;
|
||||
}
|
||||
|
||||
return dashboardBackup.getViewMode();
|
||||
return dashboardBackupService.getViewMode();
|
||||
})();
|
||||
|
||||
const combinedSessionInput: DashboardContainerInput = {
|
||||
|
@ -218,7 +216,7 @@ export const initializeDashboard = async ({
|
|||
const overridePanels: DashboardPanelMap = {};
|
||||
|
||||
for (const panel of Object.values(overrideInput?.panels)) {
|
||||
if (reactEmbeddableRegistryHasKey(panel.type)) {
|
||||
if (embeddableService.reactEmbeddableRegistryHasKey(panel.type)) {
|
||||
overridePanels[panel.explicitInput.id] = {
|
||||
...panel,
|
||||
|
||||
|
@ -263,7 +261,7 @@ export const initializeDashboard = async ({
|
|||
|
||||
// Back up any view mode passed in explicitly.
|
||||
if (overrideInput?.viewMode) {
|
||||
dashboardBackup.storeViewMode(overrideInput?.viewMode);
|
||||
dashboardBackupService.storeViewMode(overrideInput?.viewMode);
|
||||
}
|
||||
|
||||
initialDashboardInput.executionContext = {
|
||||
|
@ -319,7 +317,7 @@ export const initializeDashboard = async ({
|
|||
|
||||
// start syncing global query state with the URL.
|
||||
const { stop: stopSyncingQueryServiceStateWithUrl } = syncGlobalQueryStateWithUrl(
|
||||
queryService,
|
||||
dataService.query,
|
||||
kbnUrlStateStorage
|
||||
);
|
||||
|
||||
|
@ -363,7 +361,7 @@ export const initializeDashboard = async ({
|
|||
// maintain hide panel titles setting.
|
||||
hidePanelTitles: panelToUpdate.explicitInput.hidePanelTitles,
|
||||
};
|
||||
if (reactEmbeddableRegistryHasKey(incomingEmbeddable.type)) {
|
||||
if (embeddableService.reactEmbeddableRegistryHasKey(incomingEmbeddable.type)) {
|
||||
panelToUpdate.explicitInput = { id: panelToUpdate.explicitInput.id };
|
||||
runtimePanelsToRestore[incomingEmbeddable.embeddableId] = nextRuntimeState;
|
||||
} else {
|
||||
|
@ -399,7 +397,7 @@ export const initializeDashboard = async ({
|
|||
}
|
||||
);
|
||||
const newPanelState: DashboardPanelState = (() => {
|
||||
if (reactEmbeddableRegistryHasKey(incomingEmbeddable.type)) {
|
||||
if (embeddableService.reactEmbeddableRegistryHasKey(incomingEmbeddable.type)) {
|
||||
runtimePanelsToRestore[embeddableId] = incomingEmbeddable.input;
|
||||
return {
|
||||
explicitInput: { id: embeddableId },
|
||||
|
@ -487,16 +485,18 @@ export const initializeDashboard = async ({
|
|||
|
||||
// if this incoming embeddable has a session, continue it.
|
||||
if (incomingEmbeddable?.searchSessionId) {
|
||||
session.continue(incomingEmbeddable.searchSessionId);
|
||||
dataService.search.session.continue(incomingEmbeddable.searchSessionId);
|
||||
}
|
||||
if (sessionIdToRestore) {
|
||||
session.restore(sessionIdToRestore);
|
||||
dataService.search.session.restore(sessionIdToRestore);
|
||||
}
|
||||
const existingSession = session.getSessionId();
|
||||
const existingSession = dataService.search.session.getSessionId();
|
||||
|
||||
initialSearchSessionId =
|
||||
sessionIdToRestore ??
|
||||
(existingSession && incomingEmbeddable ? existingSession : session.start());
|
||||
(existingSession && incomingEmbeddable
|
||||
? existingSession
|
||||
: dataService.search.session.start());
|
||||
|
||||
untilDashboardReady().then(async (container) => {
|
||||
await container.untilContainerInitialized();
|
||||
|
@ -511,7 +511,11 @@ export const initializeDashboard = async ({
|
|||
// We don't count views when a user is editing a dashboard and is returning from an editor after saving
|
||||
// however, there is an edge case that we now count a new view when a user is editing a dashboard and is returning from an editor by canceling
|
||||
// TODO: this should be revisited by making embeddable transfer support canceling logic https://github.com/elastic/kibana/issues/190485
|
||||
dashboardContentInsights.trackDashboardView(loadDashboardReturn.dashboardId);
|
||||
const contentInsightsClient = new ContentInsightsClient(
|
||||
{ http: coreServices.http },
|
||||
{ domainId: 'dashboard' }
|
||||
);
|
||||
contentInsightsClient.track(loadDashboardReturn.dashboardId, 'viewed');
|
||||
}
|
||||
|
||||
return { input: initialDashboardInput, searchSessionId: initialSearchSessionId };
|
||||
|
|
|
@ -7,19 +7,17 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { uniqBy } from 'lodash';
|
||||
import { combineLatest, Observable, of, switchMap } from 'rxjs';
|
||||
|
||||
import { DataView } from '@kbn/data-views-plugin/common';
|
||||
import { combineCompatibleChildrenApis } from '@kbn/presentation-containers';
|
||||
import { apiPublishesDataViews, PublishesDataViews } from '@kbn/presentation-publishing';
|
||||
import { uniqBy } from 'lodash';
|
||||
import { combineLatest, Observable, of, switchMap } from 'rxjs';
|
||||
import { pluginServices } from '../../../../services/plugin_services';
|
||||
|
||||
import { dataService } from '../../../../services/kibana_services';
|
||||
import { DashboardContainer } from '../../dashboard_container';
|
||||
|
||||
export function startSyncingDashboardDataViews(this: DashboardContainer) {
|
||||
const {
|
||||
data: { dataViews },
|
||||
} = pluginServices.getServices();
|
||||
|
||||
const controlGroupDataViewsPipe: Observable<DataView[] | undefined> = this.controlGroupApi$.pipe(
|
||||
switchMap((controlGroupApi) => {
|
||||
return controlGroupApi ? controlGroupApi.dataViews : of([]);
|
||||
|
@ -42,8 +40,8 @@ export function startSyncingDashboardDataViews(this: DashboardContainer) {
|
|||
];
|
||||
if (allDataViews.length === 0) {
|
||||
return (async () => {
|
||||
const defaultDataViewId = await dataViews.getDefaultId();
|
||||
return [await dataViews.get(defaultDataViewId!)];
|
||||
const defaultDataViewId = await dataService.dataViews.getDefaultId();
|
||||
return [await dataService.dataViews.get(defaultDataViewId!)];
|
||||
})();
|
||||
}
|
||||
return of(uniqBy(allDataViews, 'id'));
|
||||
|
|
|
@ -7,18 +7,18 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import type { CoreStart } from '@kbn/core/public';
|
||||
import { PerformanceMetricEvent } from '@kbn/ebt-tools';
|
||||
import { PresentationContainer, TracksQueryPerformance } from '@kbn/presentation-containers';
|
||||
import { getMockPresentationContainer } from '@kbn/presentation-containers/mocks';
|
||||
import { apiPublishesPhaseEvents, PhaseEvent, PhaseEventType } from '@kbn/presentation-publishing';
|
||||
import { PhaseEvent, PhaseEventType, apiPublishesPhaseEvents } from '@kbn/presentation-publishing';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { DashboardAnalyticsService } from '../../../../services/analytics/types';
|
||||
import { startQueryPerformanceTracking } from './query_performance_tracking';
|
||||
|
||||
const mockMetricEvent = jest.fn();
|
||||
jest.mock('@kbn/ebt-tools', () => ({
|
||||
reportPerformanceMetricEvent: (_: DashboardAnalyticsService, args: PerformanceMetricEvent) => {
|
||||
reportPerformanceMetricEvent: (_: CoreStart['analytics'], args: PerformanceMetricEvent) => {
|
||||
mockMetricEvent(args);
|
||||
},
|
||||
}));
|
||||
|
|
|
@ -7,12 +7,14 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { combineLatest, map, of, pairwise, startWith, switchMap } from 'rxjs';
|
||||
|
||||
import { reportPerformanceMetricEvent } from '@kbn/ebt-tools';
|
||||
import { PresentationContainer, TracksQueryPerformance } from '@kbn/presentation-containers';
|
||||
import { apiPublishesPhaseEvents, PublishesPhaseEvents } from '@kbn/presentation-publishing';
|
||||
import { combineLatest, map, of, pairwise, startWith, switchMap } from 'rxjs';
|
||||
import { PublishesPhaseEvents, apiPublishesPhaseEvents } from '@kbn/presentation-publishing';
|
||||
|
||||
import { DASHBOARD_LOADED_EVENT } from '../../../../dashboard_constants';
|
||||
import { pluginServices } from '../../../../services/plugin_services';
|
||||
import { coreServices } from '../../../../services/kibana_services';
|
||||
import { DashboardLoadType } from '../../../types';
|
||||
|
||||
let isFirstDashboardLoadOfSession = true;
|
||||
|
@ -26,7 +28,6 @@ const loadTypesMapping: { [key in DashboardLoadType]: number } = {
|
|||
export const startQueryPerformanceTracking = (
|
||||
dashboard: PresentationContainer & TracksQueryPerformance
|
||||
) => {
|
||||
const { analytics } = pluginServices.getServices();
|
||||
const reportPerformanceMetrics = ({
|
||||
timeToData,
|
||||
panelCount,
|
||||
|
@ -41,7 +42,7 @@ export const startQueryPerformanceTracking = (
|
|||
const duration =
|
||||
loadType === 'dashboardSubsequentLoad' ? timeToData : Math.max(timeToData, totalLoadTime);
|
||||
|
||||
reportPerformanceMetricEvent(analytics, {
|
||||
reportPerformanceMetricEvent(coreServices.analytics, {
|
||||
eventName: DASHBOARD_LOADED_EVENT,
|
||||
duration,
|
||||
key1: 'time_to_data',
|
||||
|
|
|
@ -11,10 +11,11 @@ import { skip } from 'rxjs';
|
|||
|
||||
import { noSearchSessionStorageCapabilityMessage } from '@kbn/data-plugin/public';
|
||||
|
||||
import { dataService } from '../../../../services/kibana_services';
|
||||
import { DashboardContainer } from '../../dashboard_container';
|
||||
import { pluginServices } from '../../../../services/plugin_services';
|
||||
import { DashboardCreationOptions } from '../../dashboard_container_factory';
|
||||
import { newSession$ } from './new_session';
|
||||
import { getDashboardCapabilities } from '../../../../utils/get_dashboard_capabilities';
|
||||
|
||||
/**
|
||||
* Enables dashboard search sessions.
|
||||
|
@ -25,13 +26,6 @@ export function startDashboardSearchSessionIntegration(
|
|||
) {
|
||||
if (!searchSessionSettings) return;
|
||||
|
||||
const {
|
||||
data: {
|
||||
search: { session },
|
||||
},
|
||||
dashboardCapabilities: { storeSearchSession: canStoreSearchSession },
|
||||
} = pluginServices.getServices();
|
||||
|
||||
const {
|
||||
sessionIdUrlChangeObservable,
|
||||
getSearchSessionIdFromURL,
|
||||
|
@ -39,9 +33,9 @@ export function startDashboardSearchSessionIntegration(
|
|||
createSessionRestorationDataProvider,
|
||||
} = searchSessionSettings;
|
||||
|
||||
session.enableStorage(createSessionRestorationDataProvider(this), {
|
||||
dataService.search.session.enableStorage(createSessionRestorationDataProvider(this), {
|
||||
isDisabled: () =>
|
||||
canStoreSearchSession
|
||||
getDashboardCapabilities().storeSearchSession
|
||||
? { disabled: false }
|
||||
: {
|
||||
disabled: true,
|
||||
|
@ -60,15 +54,18 @@ export function startDashboardSearchSessionIntegration(
|
|||
const updatedSearchSessionId: string | undefined = (() => {
|
||||
let searchSessionIdFromURL = getSearchSessionIdFromURL();
|
||||
if (searchSessionIdFromURL) {
|
||||
if (session.isRestore() && session.isCurrentSession(searchSessionIdFromURL)) {
|
||||
if (
|
||||
dataService.search.session.isRestore() &&
|
||||
dataService.search.session.isCurrentSession(searchSessionIdFromURL)
|
||||
) {
|
||||
// we had previously been in a restored session but have now changed state so remove the session id from the URL.
|
||||
removeSessionIdFromUrl();
|
||||
searchSessionIdFromURL = undefined;
|
||||
} else {
|
||||
session.restore(searchSessionIdFromURL);
|
||||
dataService.search.session.restore(searchSessionIdFromURL);
|
||||
}
|
||||
}
|
||||
return searchSessionIdFromURL ?? session.start();
|
||||
return searchSessionIdFromURL ?? dataService.search.session.start();
|
||||
})();
|
||||
|
||||
if (updatedSearchSessionId && updatedSearchSessionId !== currentSearchSessionId) {
|
||||
|
|
|
@ -21,9 +21,9 @@ import {
|
|||
} from '@kbn/data-plugin/public';
|
||||
|
||||
import { DashboardContainer } from '../../dashboard_container';
|
||||
import { pluginServices } from '../../../../services/plugin_services';
|
||||
import { GLOBAL_STATE_STORAGE_KEY } from '../../../../dashboard_constants';
|
||||
import { areTimesEqual } from '../../../state/diffing/dashboard_diffing_utils';
|
||||
import { dataService } from '../../../../services/kibana_services';
|
||||
|
||||
/**
|
||||
* Sets up syncing and subscriptions between the filter state from the Data plugin
|
||||
|
@ -33,11 +33,7 @@ export function syncUnifiedSearchState(
|
|||
this: DashboardContainer,
|
||||
kbnUrlStateStorage: IKbnUrlStateStorage
|
||||
) {
|
||||
const {
|
||||
data: { query: queryService, search },
|
||||
} = pluginServices.getServices();
|
||||
const { queryString, timefilter } = queryService;
|
||||
const { timefilter: timefilterService } = timefilter;
|
||||
const timefilterService = dataService.query.timefilter.timefilter;
|
||||
|
||||
// get Observable for when the dashboard's saved filters or query change.
|
||||
const OnFiltersChange$ = new Subject<{ filters: Filter[]; query: Query }>();
|
||||
|
@ -47,7 +43,7 @@ export function syncUnifiedSearchState(
|
|||
} = this.getState();
|
||||
OnFiltersChange$.next({
|
||||
filters: filters ?? [],
|
||||
query: query ?? queryString.getDefaultQuery(),
|
||||
query: query ?? dataService.query.queryString.getDefaultQuery(),
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -56,12 +52,12 @@ export function syncUnifiedSearchState(
|
|||
explicitInput: { filters, query },
|
||||
} = this.getState();
|
||||
const intermediateFilterState: { filters: Filter[]; query: Query } = {
|
||||
query: query ?? queryString.getDefaultQuery(),
|
||||
query: query ?? dataService.query.queryString.getDefaultQuery(),
|
||||
filters: filters ?? [],
|
||||
};
|
||||
|
||||
const stopSyncingAppFilters = connectToQueryState(
|
||||
queryService,
|
||||
dataService.query,
|
||||
{
|
||||
get: () => intermediateFilterState,
|
||||
set: ({ filters: newFilters, query: newQuery }) => {
|
||||
|
@ -144,7 +140,7 @@ export function syncUnifiedSearchState(
|
|||
}),
|
||||
switchMap((done) =>
|
||||
// best way on a dashboard to estimate that panels are updated is to rely on search session service state
|
||||
waitUntilNextSessionCompletes$(search.session).pipe(finalize(done))
|
||||
waitUntilNextSessionCompletes$(dataService.search.session).pipe(finalize(done))
|
||||
)
|
||||
)
|
||||
.subscribe();
|
||||
|
|
|
@ -9,11 +9,11 @@
|
|||
|
||||
import { isErrorEmbeddable, ViewMode } from '@kbn/embeddable-plugin/public';
|
||||
import {
|
||||
CONTACT_CARD_EMBEDDABLE,
|
||||
ContactCardEmbeddable,
|
||||
ContactCardEmbeddableFactory,
|
||||
ContactCardEmbeddableInput,
|
||||
ContactCardEmbeddableOutput,
|
||||
CONTACT_CARD_EMBEDDABLE,
|
||||
EMPTY_EMBEDDABLE,
|
||||
} from '@kbn/embeddable-plugin/public/lib/test_samples/embeddables';
|
||||
import type { TimeRange } from '@kbn/es-query';
|
||||
|
@ -25,13 +25,11 @@ import {
|
|||
getSampleDashboardPanel,
|
||||
mockControlGroupApi,
|
||||
} from '../../mocks';
|
||||
import { pluginServices } from '../../services/plugin_services';
|
||||
import { embeddableService } from '../../services/kibana_services';
|
||||
import { DashboardContainer } from './dashboard_container';
|
||||
|
||||
const embeddableFactory = new ContactCardEmbeddableFactory((() => null) as any, {} as any);
|
||||
pluginServices.getServices().embeddable.getEmbeddableFactory = jest
|
||||
.fn()
|
||||
.mockReturnValue(embeddableFactory);
|
||||
embeddableService.getEmbeddableFactory = jest.fn().mockReturnValue(embeddableFactory);
|
||||
|
||||
test('DashboardContainer initializes embeddables', (done) => {
|
||||
const container = buildMockDashboard({
|
||||
|
|
|
@ -7,59 +7,69 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import deepEqual from 'fast-deep-equal';
|
||||
import { omit } from 'lodash';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { batch } from 'react-redux';
|
||||
import {
|
||||
BehaviorSubject,
|
||||
Subject,
|
||||
Subscription,
|
||||
distinctUntilChanged,
|
||||
first,
|
||||
map,
|
||||
skipWhile,
|
||||
switchMap,
|
||||
} from 'rxjs';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { METRIC_TYPE } from '@kbn/analytics';
|
||||
import type { Reference } from '@kbn/content-management-utils';
|
||||
import type { I18nStart, KibanaExecutionContext, OverlayRef } from '@kbn/core/public';
|
||||
import {
|
||||
type PublishingSubject,
|
||||
apiPublishesPanelTitle,
|
||||
apiPublishesUnsavedChanges,
|
||||
getPanelTitle,
|
||||
PublishesViewMode,
|
||||
PublishesDataLoading,
|
||||
apiPublishesDataLoading,
|
||||
} from '@kbn/presentation-publishing';
|
||||
import { ControlGroupApi, ControlGroupSerializedState } from '@kbn/controls-plugin/public';
|
||||
import type { KibanaExecutionContext, OverlayRef } from '@kbn/core/public';
|
||||
import { RefreshInterval } from '@kbn/data-plugin/public';
|
||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||
import {
|
||||
Container,
|
||||
DefaultEmbeddableApi,
|
||||
EmbeddableFactoryNotFoundError,
|
||||
embeddableInputToSubject,
|
||||
isExplicitInputWithAttributes,
|
||||
PanelNotFoundError,
|
||||
ViewMode,
|
||||
embeddableInputToSubject,
|
||||
isExplicitInputWithAttributes,
|
||||
type EmbeddableFactory,
|
||||
type EmbeddableInput,
|
||||
type EmbeddableOutput,
|
||||
type IEmbeddable,
|
||||
} from '@kbn/embeddable-plugin/public';
|
||||
import type { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query';
|
||||
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
|
||||
import {
|
||||
HasRuntimeChildState,
|
||||
HasSaveNotification,
|
||||
HasSerializedChildState,
|
||||
PanelPackage,
|
||||
TrackContentfulRender,
|
||||
TracksQueryPerformance,
|
||||
combineCompatibleChildrenApis,
|
||||
} from '@kbn/presentation-containers';
|
||||
import { PanelPackage } from '@kbn/presentation-containers';
|
||||
import { ReduxEmbeddableTools, ReduxToolsPackage } from '@kbn/presentation-util-plugin/public';
|
||||
import { LocatorPublic } from '@kbn/share-plugin/common';
|
||||
import { ExitFullScreenButtonKibanaProvider } from '@kbn/shared-ux-button-exit-full-screen';
|
||||
import deepEqual from 'fast-deep-equal';
|
||||
import { omit } from 'lodash';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { batch } from 'react-redux';
|
||||
import { BehaviorSubject, Subject, Subscription, first, skipWhile, switchMap } from 'rxjs';
|
||||
import { distinctUntilChanged, map } from 'rxjs';
|
||||
import { v4 } from 'uuid';
|
||||
import { PublishesSettings } from '@kbn/presentation-containers/interfaces/publishes_settings';
|
||||
import { apiHasSerializableState } from '@kbn/presentation-containers/interfaces/serialized_state';
|
||||
import { ControlGroupApi, ControlGroupSerializedState } from '@kbn/controls-plugin/public';
|
||||
import { DashboardLocatorParams, DASHBOARD_CONTAINER_TYPE, DashboardApi } from '../..';
|
||||
import {
|
||||
PublishesDataLoading,
|
||||
PublishesViewMode,
|
||||
apiPublishesDataLoading,
|
||||
apiPublishesPanelTitle,
|
||||
apiPublishesUnsavedChanges,
|
||||
getPanelTitle,
|
||||
type PublishingSubject,
|
||||
} from '@kbn/presentation-publishing';
|
||||
import { ReduxEmbeddableTools, ReduxToolsPackage } from '@kbn/presentation-util-plugin/public';
|
||||
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
|
||||
import { LocatorPublic } from '@kbn/share-plugin/common';
|
||||
import { ExitFullScreenButtonKibanaProvider } from '@kbn/shared-ux-button-exit-full-screen';
|
||||
|
||||
import { DASHBOARD_CONTAINER_TYPE, DashboardApi, DashboardLocatorParams } from '../..';
|
||||
import {
|
||||
DashboardAttributes,
|
||||
DashboardContainerInput,
|
||||
|
@ -70,6 +80,8 @@ import {
|
|||
getReferencesForControls,
|
||||
getReferencesForPanelId,
|
||||
} from '../../../common/dashboard_container/persistable_state/dashboard_container_references';
|
||||
import { DashboardContext } from '../../dashboard_api/use_dashboard_api';
|
||||
import { getPanelAddedSuccessString } from '../../dashboard_app/_dashboard_app_strings';
|
||||
import {
|
||||
DASHBOARD_APP_ID,
|
||||
DASHBOARD_UI_METRIC_ID,
|
||||
|
@ -77,13 +89,19 @@ import {
|
|||
DEFAULT_PANEL_WIDTH,
|
||||
PanelPlacementStrategy,
|
||||
} from '../../dashboard_constants';
|
||||
import { DashboardAnalyticsService } from '../../services/analytics/types';
|
||||
import { DashboardCapabilitiesService } from '../../services/dashboard_capabilities/types';
|
||||
import { pluginServices } from '../../services/plugin_services';
|
||||
import { placePanel } from '../panel_placement';
|
||||
import { runPanelPlacementStrategy } from '../panel_placement/place_new_panel_strategies';
|
||||
import { PANELS_CONTROL_GROUP_KEY } from '../../services/dashboard_backup_service';
|
||||
import { getDashboardContentManagementService } from '../../services/dashboard_content_management_service';
|
||||
import {
|
||||
coreServices,
|
||||
dataService,
|
||||
embeddableService,
|
||||
usageCollectionService,
|
||||
} from '../../services/kibana_services';
|
||||
import { getDashboardCapabilities } from '../../utils/get_dashboard_capabilities';
|
||||
import { DashboardViewport } from '../component/viewport/dashboard_viewport';
|
||||
import { placePanel } from '../panel_placement';
|
||||
import { getDashboardPanelPlacementSetting } from '../panel_placement/panel_placement_registry';
|
||||
import { runPanelPlacementStrategy } from '../panel_placement/place_new_panel_strategies';
|
||||
import { dashboardContainerReducers } from '../state/dashboard_container_reducers';
|
||||
import { getDiffingMiddleware } from '../state/diffing/dashboard_diffing_integration';
|
||||
import {
|
||||
|
@ -92,7 +110,7 @@ import {
|
|||
DashboardStateFromSettingsFlyout,
|
||||
UnsavedPanelState,
|
||||
} from '../types';
|
||||
import { addFromLibrary, addOrUpdateEmbeddable, runQuickSave, runInteractiveSave } from './api';
|
||||
import { addFromLibrary, addOrUpdateEmbeddable, runInteractiveSave, runQuickSave } from './api';
|
||||
import { duplicateDashboardPanel } from './api/duplicate_dashboard_panel';
|
||||
import {
|
||||
combineDashboardFiltersWithControlGroupFilters,
|
||||
|
@ -104,9 +122,6 @@ import {
|
|||
dashboardTypeDisplayLowercase,
|
||||
dashboardTypeDisplayName,
|
||||
} from './dashboard_container_factory';
|
||||
import { getPanelAddedSuccessString } from '../../dashboard_app/_dashboard_app_strings';
|
||||
import { PANELS_CONTROL_GROUP_KEY } from '../../services/dashboard_backup/dashboard_backup_service';
|
||||
import { DashboardContext } from '../../dashboard_api/use_dashboard_api';
|
||||
|
||||
export interface InheritedChildInput {
|
||||
filters: Filter[];
|
||||
|
@ -186,16 +201,11 @@ export class DashboardContainer
|
|||
|
||||
// Services that are used in the Dashboard container code
|
||||
private creationOptions?: DashboardCreationOptions;
|
||||
private analyticsService: DashboardAnalyticsService;
|
||||
private showWriteControls: DashboardCapabilitiesService['showWriteControls'];
|
||||
private i18n: I18nStart;
|
||||
private theme;
|
||||
private chrome;
|
||||
private customBranding;
|
||||
private showWriteControls: boolean;
|
||||
|
||||
public trackContentfulRender() {
|
||||
if (!this.hadContentfulRender && this.analyticsService) {
|
||||
this.analyticsService.reportEvent('dashboard_loaded_with_data', {});
|
||||
if (!this.hadContentfulRender) {
|
||||
coreServices.analytics.reportEvent('dashboard_loaded_with_data', {});
|
||||
}
|
||||
this.hadContentfulRender = true;
|
||||
}
|
||||
|
@ -238,37 +248,26 @@ export class DashboardContainer
|
|||
});
|
||||
}
|
||||
|
||||
const {
|
||||
usageCollection,
|
||||
embeddable: { getEmbeddableFactory },
|
||||
} = pluginServices.getServices();
|
||||
|
||||
super(
|
||||
{
|
||||
...initialInput,
|
||||
},
|
||||
{ embeddableLoaded: {} },
|
||||
getEmbeddableFactory,
|
||||
embeddableService.getEmbeddableFactory,
|
||||
parent,
|
||||
{ untilContainerInitialized }
|
||||
);
|
||||
|
||||
({ showWriteControls: this.showWriteControls } = getDashboardCapabilities());
|
||||
|
||||
this.controlGroupApi$ = controlGroupApi$;
|
||||
this.untilContainerInitialized = untilContainerInitialized;
|
||||
|
||||
this.trackPanelAddMetric = usageCollection.reportUiCounter?.bind(
|
||||
usageCollection,
|
||||
this.trackPanelAddMetric = usageCollectionService?.reportUiCounter.bind(
|
||||
usageCollectionService,
|
||||
DASHBOARD_UI_METRIC_ID
|
||||
);
|
||||
|
||||
({
|
||||
analytics: this.analyticsService,
|
||||
settings: { theme: this.theme, i18n: this.i18n },
|
||||
chrome: this.chrome,
|
||||
customBranding: this.customBranding,
|
||||
dashboardCapabilities: { showWriteControls: this.showWriteControls },
|
||||
} = pluginServices.getServices());
|
||||
|
||||
this.creationOptions = creationOptions;
|
||||
this.searchSessionId = initialSessionId;
|
||||
this.searchSessionId$.next(initialSessionId);
|
||||
|
@ -475,12 +474,12 @@ export class DashboardContainer
|
|||
|
||||
ReactDOM.render(
|
||||
<KibanaRenderContextProvider
|
||||
analytics={this.analyticsService}
|
||||
i18n={this.i18n}
|
||||
theme={this.theme}
|
||||
analytics={coreServices.analytics}
|
||||
i18n={coreServices.i18n}
|
||||
theme={coreServices.theme}
|
||||
>
|
||||
<ExitFullScreenButtonKibanaProvider
|
||||
coreStart={{ chrome: this.chrome, customBranding: this.customBranding }}
|
||||
coreStart={{ chrome: coreServices.chrome, customBranding: coreServices.customBranding }}
|
||||
>
|
||||
<DashboardContext.Provider value={this as DashboardApi}>
|
||||
<DashboardViewport />
|
||||
|
@ -610,14 +609,9 @@ export class DashboardContainer
|
|||
panelPackage: PanelPackage,
|
||||
displaySuccessMessage?: boolean
|
||||
) {
|
||||
const {
|
||||
notifications: { toasts },
|
||||
embeddable: { getEmbeddableFactory, reactEmbeddableRegistryHasKey },
|
||||
} = pluginServices.getServices();
|
||||
|
||||
const onSuccess = (id?: string, title?: string) => {
|
||||
if (!displaySuccessMessage) return;
|
||||
toasts.addSuccess({
|
||||
coreServices.notifications.toasts.addSuccess({
|
||||
title: getPanelAddedSuccessString(title),
|
||||
'data-test-subj': 'addEmbeddableToDashboardSuccess',
|
||||
});
|
||||
|
@ -628,10 +622,10 @@ export class DashboardContainer
|
|||
if (this.trackPanelAddMetric) {
|
||||
this.trackPanelAddMetric(METRIC_TYPE.CLICK, panelPackage.panelType);
|
||||
}
|
||||
if (reactEmbeddableRegistryHasKey(panelPackage.panelType)) {
|
||||
if (embeddableService.reactEmbeddableRegistryHasKey(panelPackage.panelType)) {
|
||||
const newId = v4();
|
||||
|
||||
const getCustomPlacementSettingFunc = await getDashboardPanelPlacementSetting(
|
||||
const getCustomPlacementSettingFunc = getDashboardPanelPlacementSetting(
|
||||
panelPackage.panelType
|
||||
);
|
||||
|
||||
|
@ -671,7 +665,7 @@ export class DashboardContainer
|
|||
return await this.untilReactEmbeddableLoaded<ApiType>(newId);
|
||||
}
|
||||
|
||||
const embeddableFactory = getEmbeddableFactory(panelPackage.panelType);
|
||||
const embeddableFactory = embeddableService.getEmbeddableFactory(panelPackage.panelType);
|
||||
if (!embeddableFactory) {
|
||||
throw new EmbeddableFactoryNotFoundError(panelPackage.panelType);
|
||||
}
|
||||
|
@ -709,11 +703,8 @@ export class DashboardContainer
|
|||
}
|
||||
|
||||
public getDashboardPanelFromId = async (panelId: string) => {
|
||||
const {
|
||||
embeddable: { reactEmbeddableRegistryHasKey },
|
||||
} = pluginServices.getServices();
|
||||
const panel = this.getInput().panels[panelId];
|
||||
if (reactEmbeddableRegistryHasKey(panel.type)) {
|
||||
if (embeddableService.reactEmbeddableRegistryHasKey(panel.type)) {
|
||||
const child = this.children$.value[panelId];
|
||||
if (!child) throw new PanelNotFoundError();
|
||||
const serialized = apiHasSerializableState(child)
|
||||
|
@ -769,13 +760,7 @@ export class DashboardContainer
|
|||
|
||||
// if we are using the unified search integration, we need to force reset the time picker.
|
||||
if (this.creationOptions?.useUnifiedSearchIntegration && lastSavedTimeRestore) {
|
||||
const {
|
||||
data: {
|
||||
query: {
|
||||
timefilter: { timefilter: timeFilterService },
|
||||
},
|
||||
},
|
||||
} = pluginServices.getServices();
|
||||
const timeFilterService = dataService.query.timefilter.timefilter;
|
||||
if (timeRange) timeFilterService.setTime(timeRange);
|
||||
if (refreshInterval) timeFilterService.setRefreshInterval(refreshInterval);
|
||||
}
|
||||
|
@ -790,13 +775,12 @@ export class DashboardContainer
|
|||
this.integrationSubscriptions = new Subscription();
|
||||
this.stopSyncingWithUnifiedSearch?.();
|
||||
|
||||
const {
|
||||
dashboardContentManagement: { loadDashboardState },
|
||||
} = pluginServices.getServices();
|
||||
if (newCreationOptions) {
|
||||
this.creationOptions = { ...this.creationOptions, ...newCreationOptions };
|
||||
}
|
||||
const loadDashboardReturn = await loadDashboardState({ id: newSavedObjectId });
|
||||
const loadDashboardReturn = await getDashboardContentManagementService().loadDashboardState({
|
||||
id: newSavedObjectId,
|
||||
});
|
||||
|
||||
const dashboardContainerReady$ = new Subject<DashboardContainer>();
|
||||
const untilDashboardReady = () =>
|
||||
|
@ -908,13 +892,10 @@ export class DashboardContainer
|
|||
};
|
||||
|
||||
public async getPanelTitles(): Promise<string[]> {
|
||||
const {
|
||||
embeddable: { reactEmbeddableRegistryHasKey },
|
||||
} = pluginServices.getServices();
|
||||
const titles: string[] = [];
|
||||
for (const [id, panel] of Object.entries(this.getInput().panels)) {
|
||||
const title = await (async () => {
|
||||
if (reactEmbeddableRegistryHasKey(panel.type)) {
|
||||
if (embeddableService.reactEmbeddableRegistryHasKey(panel.type)) {
|
||||
const child = this.children$.value[id];
|
||||
return apiPublishesPanelTitle(child) ? getPanelTitle(child) : '';
|
||||
}
|
||||
|
@ -1039,12 +1020,9 @@ export class DashboardContainer
|
|||
};
|
||||
|
||||
public removePanel(id: string) {
|
||||
const {
|
||||
embeddable: { reactEmbeddableRegistryHasKey },
|
||||
} = pluginServices.getServices();
|
||||
const type = this.getInput().panels[id]?.type;
|
||||
this.removeEmbeddable(id);
|
||||
if (reactEmbeddableRegistryHasKey(type)) {
|
||||
if (embeddableService.reactEmbeddableRegistryHasKey(type)) {
|
||||
const { [id]: childToRemove, ...otherChildren } = this.children$.value;
|
||||
this.children$.next(otherChildren);
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ import { DEFAULT_DASHBOARD_INPUT } from '../../dashboard_constants';
|
|||
import {
|
||||
LoadDashboardReturn,
|
||||
SavedDashboardInput,
|
||||
} from '../../services/dashboard_content_management/types';
|
||||
} from '../../services/dashboard_content_management_service/types';
|
||||
import type { DashboardContainer } from './dashboard_container';
|
||||
|
||||
export type DashboardContainerFactory = EmbeddableFactory<
|
||||
|
|
|
@ -7,22 +7,22 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { ReactWrapper } from 'enzyme';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { mountWithIntl } from '@kbn/test-jest-helpers';
|
||||
import { NotFoundPrompt } from '@kbn/shared-ux-prompt-not-found';
|
||||
import { setStubKibanaServices } from '@kbn/embeddable-plugin/public/mocks';
|
||||
import { NotFoundPrompt } from '@kbn/shared-ux-prompt-not-found';
|
||||
import { mountWithIntl } from '@kbn/test-jest-helpers';
|
||||
import { ReactWrapper } from 'enzyme';
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
|
||||
import { DashboardContainerFactory } from '..';
|
||||
import { DASHBOARD_CONTAINER_TYPE } from '../..';
|
||||
import { DashboardRenderer } from './dashboard_renderer';
|
||||
import { pluginServices } from '../../services/plugin_services';
|
||||
import { SavedObjectNotFound } from '@kbn/kibana-utils-plugin/common';
|
||||
import { DashboardContainer } from '../embeddable/dashboard_container';
|
||||
import { DashboardCreationOptions } from '../embeddable/dashboard_container_factory';
|
||||
import { setStubKibanaServices as setPresentationPanelMocks } from '@kbn/presentation-panel-plugin/public/mocks';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { DashboardContainerFactory } from '..';
|
||||
import { DASHBOARD_CONTAINER_TYPE } from '../..';
|
||||
import { embeddableService } from '../../services/kibana_services';
|
||||
import { DashboardContainer } from '../embeddable/dashboard_container';
|
||||
import { DashboardCreationOptions } from '../embeddable/dashboard_container_factory';
|
||||
import { DashboardRenderer } from './dashboard_renderer';
|
||||
|
||||
describe('dashboard renderer', () => {
|
||||
let mockDashboardContainer: DashboardContainer;
|
||||
|
@ -39,9 +39,7 @@ describe('dashboard renderer', () => {
|
|||
mockDashboardFactory = {
|
||||
create: jest.fn().mockReturnValue(mockDashboardContainer),
|
||||
} as unknown as DashboardContainerFactory;
|
||||
pluginServices.getServices().embeddable.getEmbeddableFactory = jest
|
||||
.fn()
|
||||
.mockReturnValue(mockDashboardFactory);
|
||||
embeddableService.getEmbeddableFactory = jest.fn().mockReturnValue(mockDashboardFactory);
|
||||
setPresentationPanelMocks();
|
||||
});
|
||||
|
||||
|
@ -49,9 +47,7 @@ describe('dashboard renderer', () => {
|
|||
await act(async () => {
|
||||
mountWithIntl(<DashboardRenderer />);
|
||||
});
|
||||
expect(pluginServices.getServices().embeddable.getEmbeddableFactory).toHaveBeenCalledWith(
|
||||
DASHBOARD_CONTAINER_TYPE
|
||||
);
|
||||
expect(embeddableService.getEmbeddableFactory).toHaveBeenCalledWith(DASHBOARD_CONTAINER_TYPE);
|
||||
expect(mockDashboardFactory.create).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
@ -109,9 +105,7 @@ describe('dashboard renderer', () => {
|
|||
mockDashboardFactory = {
|
||||
create: jest.fn().mockReturnValue(mockErrorEmbeddable),
|
||||
} as unknown as DashboardContainerFactory;
|
||||
pluginServices.getServices().embeddable.getEmbeddableFactory = jest
|
||||
.fn()
|
||||
.mockReturnValue(mockDashboardFactory);
|
||||
embeddableService.getEmbeddableFactory = jest.fn().mockReturnValue(mockDashboardFactory);
|
||||
|
||||
let wrapper: ReactWrapper;
|
||||
await act(async () => {
|
||||
|
@ -133,9 +127,7 @@ describe('dashboard renderer', () => {
|
|||
const mockErrorFactory = {
|
||||
create: jest.fn().mockReturnValue(mockErrorEmbeddable),
|
||||
} as unknown as DashboardContainerFactory;
|
||||
pluginServices.getServices().embeddable.getEmbeddableFactory = jest
|
||||
.fn()
|
||||
.mockReturnValue(mockErrorFactory);
|
||||
embeddableService.getEmbeddableFactory = jest.fn().mockReturnValue(mockErrorFactory);
|
||||
|
||||
// render the dashboard - it should run into an error and render the error embeddable.
|
||||
let wrapper: ReactWrapper;
|
||||
|
@ -156,9 +148,7 @@ describe('dashboard renderer', () => {
|
|||
const mockSuccessFactory = {
|
||||
create: jest.fn().mockReturnValue(mockSuccessEmbeddable),
|
||||
} as unknown as DashboardContainerFactory;
|
||||
pluginServices.getServices().embeddable.getEmbeddableFactory = jest
|
||||
.fn()
|
||||
.mockReturnValue(mockSuccessFactory);
|
||||
embeddableService.getEmbeddableFactory = jest.fn().mockReturnValue(mockSuccessFactory);
|
||||
|
||||
// update the saved object id to trigger another dashboard load.
|
||||
await act(async () => {
|
||||
|
@ -187,9 +177,7 @@ describe('dashboard renderer', () => {
|
|||
const mockErrorFactory = {
|
||||
create: jest.fn().mockReturnValue(mockErrorEmbeddable),
|
||||
} as unknown as DashboardContainerFactory;
|
||||
pluginServices.getServices().embeddable.getEmbeddableFactory = jest
|
||||
.fn()
|
||||
.mockReturnValue(mockErrorFactory);
|
||||
embeddableService.getEmbeddableFactory = jest.fn().mockReturnValue(mockErrorFactory);
|
||||
|
||||
// render the dashboard - it should run into an error and render the error embeddable.
|
||||
let wrapper: ReactWrapper;
|
||||
|
@ -252,9 +240,7 @@ describe('dashboard renderer', () => {
|
|||
const mockSuccessFactory = {
|
||||
create: jest.fn().mockReturnValue(mockSuccessEmbeddable),
|
||||
} as unknown as DashboardContainerFactory;
|
||||
pluginServices.getServices().embeddable.getEmbeddableFactory = jest
|
||||
.fn()
|
||||
.mockReturnValue(mockSuccessFactory);
|
||||
embeddableService.getEmbeddableFactory = jest.fn().mockReturnValue(mockSuccessFactory);
|
||||
|
||||
let wrapper: ReactWrapper;
|
||||
await act(async () => {
|
||||
|
@ -279,9 +265,7 @@ describe('dashboard renderer', () => {
|
|||
const mockUseMarginFalseFactory = {
|
||||
create: jest.fn().mockReturnValue(mockUseMarginFalseEmbeddable),
|
||||
} as unknown as DashboardContainerFactory;
|
||||
pluginServices.getServices().embeddable.getEmbeddableFactory = jest
|
||||
.fn()
|
||||
.mockReturnValue(mockUseMarginFalseFactory);
|
||||
embeddableService.getEmbeddableFactory = jest.fn().mockReturnValue(mockUseMarginFalseFactory);
|
||||
|
||||
let wrapper: ReactWrapper;
|
||||
await act(async () => {
|
||||
|
|
|
@ -17,11 +17,13 @@ import { v4 as uuidv4 } from 'uuid';
|
|||
import { EuiLoadingElastic, EuiLoadingSpinner } from '@elastic/eui';
|
||||
import { ErrorEmbeddable, isErrorEmbeddable } from '@kbn/embeddable-plugin/public';
|
||||
import { SavedObjectNotFound } from '@kbn/kibana-utils-plugin/common';
|
||||
|
||||
import { LocatorPublic } from '@kbn/share-plugin/common';
|
||||
import { useStateFromPublishingSubject } from '@kbn/presentation-publishing';
|
||||
import { LocatorPublic } from '@kbn/share-plugin/common';
|
||||
|
||||
import { DASHBOARD_CONTAINER_TYPE } from '..';
|
||||
import { DashboardContainerInput } from '../../../common';
|
||||
import { DashboardApi } from '../../dashboard_api/types';
|
||||
import { embeddableService, screenshotModeService } from '../../services/kibana_services';
|
||||
import type { DashboardContainer } from '../embeddable/dashboard_container';
|
||||
import {
|
||||
DashboardContainerFactory,
|
||||
|
@ -30,8 +32,6 @@ import {
|
|||
} from '../embeddable/dashboard_container_factory';
|
||||
import { DashboardLocatorParams, DashboardRedirect } from '../types';
|
||||
import { Dashboard404Page } from './dashboard_404';
|
||||
import { DashboardApi } from '../../dashboard_api/types';
|
||||
import { pluginServices } from '../../services/plugin_services';
|
||||
|
||||
export interface DashboardRendererProps {
|
||||
onApiAvailable?: (api: DashboardApi) => void;
|
||||
|
@ -57,8 +57,6 @@ export function DashboardRenderer({
|
|||
const [fatalError, setFatalError] = useState<ErrorEmbeddable | undefined>();
|
||||
const [dashboardMissing, setDashboardMissing] = useState(false);
|
||||
|
||||
const { embeddable, screenshotMode } = pluginServices.getServices();
|
||||
|
||||
const id = useMemo(() => uuidv4(), []);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -93,7 +91,7 @@ export function DashboardRenderer({
|
|||
(async () => {
|
||||
const creationOptions = await getCreationOptions?.();
|
||||
|
||||
const dashboardFactory = embeddable.getEmbeddableFactory(
|
||||
const dashboardFactory = embeddableService.getEmbeddableFactory(
|
||||
DASHBOARD_CONTAINER_TYPE
|
||||
) as DashboardContainerFactory & {
|
||||
create: DashboardContainerFactoryDefinition['create'];
|
||||
|
@ -141,7 +139,7 @@ export function DashboardRenderer({
|
|||
|
||||
const viewportClasses = classNames(
|
||||
'dashboardViewport',
|
||||
{ 'dashboardViewport--screenshotMode': screenshotMode },
|
||||
{ 'dashboardViewport--screenshotMode': screenshotModeService.isScreenshotMode() },
|
||||
{ 'dashboardViewport--loading': loading }
|
||||
);
|
||||
|
||||
|
|
|
@ -10,9 +10,13 @@
|
|||
import React from 'react';
|
||||
import { dynamic } from '@kbn/shared-ux-utility';
|
||||
import type { DashboardRendererProps } from './dashboard_renderer';
|
||||
import { untilPluginStartServicesReady } from '../../services/kibana_services';
|
||||
|
||||
const Component = dynamic(async () => {
|
||||
const { DashboardRenderer } = await import('./dashboard_renderer');
|
||||
const [{ DashboardRenderer }] = await Promise.all([
|
||||
import('./dashboard_renderer'),
|
||||
untilPluginStartServicesReady(),
|
||||
]);
|
||||
return {
|
||||
default: DashboardRenderer,
|
||||
};
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
*/
|
||||
|
||||
import { LATEST_VERSION } from '../../common/content_management';
|
||||
import { convertNumberToDashboardVersion } from '../services/dashboard_content_management/lib/dashboard_versioning';
|
||||
import { convertNumberToDashboardVersion } from '../services/dashboard_content_management_service/lib/dashboard_versioning';
|
||||
|
||||
export const DASHBOARD_CONTAINER_TYPE = 'dashboard';
|
||||
|
||||
|
|
|
@ -7,10 +7,12 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { compareFilters, COMPARE_ALL_OPTIONS, isFilterPinned } from '@kbn/es-query';
|
||||
import fastIsEqual from 'fast-deep-equal';
|
||||
|
||||
import { COMPARE_ALL_OPTIONS, compareFilters, isFilterPinned } from '@kbn/es-query';
|
||||
|
||||
import { DashboardContainerInput } from '../../../../common';
|
||||
import { pluginServices } from '../../../services/plugin_services';
|
||||
import { embeddableService } from '../../../services/kibana_services';
|
||||
import { DashboardContainer } from '../../embeddable/dashboard_container';
|
||||
import { DashboardContainerInputWithoutId } from '../../types';
|
||||
import { areTimesEqual, getPanelLayoutsAreEqual } from './dashboard_diffing_utils';
|
||||
|
@ -75,11 +77,8 @@ export const unsavedChangesDiffingFunctions: DashboardDiffFunctions = {
|
|||
const explicitInputComparePromises = Object.values(currentValue ?? {}).map(
|
||||
(panel) =>
|
||||
new Promise<boolean>((resolve, reject) => {
|
||||
const {
|
||||
embeddable: { reactEmbeddableRegistryHasKey },
|
||||
} = pluginServices.getServices();
|
||||
const embeddableId = panel.explicitInput.id;
|
||||
if (!embeddableId || reactEmbeddableRegistryHasKey(panel.type)) {
|
||||
if (!embeddableId || embeddableService.reactEmbeddableRegistryHasKey(panel.type)) {
|
||||
// if this is a new style embeddable, it will handle its own diffing.
|
||||
reject();
|
||||
return;
|
||||
|
|
|
@ -13,11 +13,13 @@ import { combineLatest, debounceTime, skipWhile, startWith, switchMap } from 'rx
|
|||
import { DashboardContainer, DashboardCreationOptions } from '../..';
|
||||
import { DashboardContainerInput } from '../../../../common';
|
||||
import { CHANGE_CHECK_DEBOUNCE } from '../../../dashboard_constants';
|
||||
import { pluginServices } from '../../../services/plugin_services';
|
||||
import {
|
||||
PANELS_CONTROL_GROUP_KEY,
|
||||
getDashboardBackupService,
|
||||
} from '../../../services/dashboard_backup_service';
|
||||
import { UnsavedPanelState } from '../../types';
|
||||
import { dashboardContainerReducers } from '../dashboard_container_reducers';
|
||||
import { isKeyEqualAsync, unsavedChangesDiffingFunctions } from './dashboard_diffing_functions';
|
||||
import { PANELS_CONTROL_GROUP_KEY } from '../../../services/dashboard_backup/dashboard_backup_service';
|
||||
|
||||
/**
|
||||
* An array of reducers which cannot cause unsaved changes. Unsaved changes only compares the explicit input
|
||||
|
@ -188,10 +190,9 @@ function backupUnsavedChanges(
|
|||
dashboardChanges: Partial<DashboardContainerInput>,
|
||||
reactEmbeddableChanges: UnsavedPanelState
|
||||
) {
|
||||
const { dashboardBackup } = pluginServices.getServices();
|
||||
const dashboardStateToBackup = omit(dashboardChanges, keysToOmitFromSessionStorage);
|
||||
|
||||
dashboardBackup.setState(
|
||||
getDashboardBackupService().setState(
|
||||
this.getDashboardSavedObjectId(),
|
||||
{
|
||||
...dashboardStateToBackup,
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
import React from 'react';
|
||||
|
||||
import {
|
||||
EUI_MODAL_CANCEL_BUTTON,
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
EuiFocusTrap,
|
||||
|
@ -19,12 +20,11 @@ import {
|
|||
EuiModalHeaderTitle,
|
||||
EuiOutsideClickDetector,
|
||||
EuiText,
|
||||
EUI_MODAL_CANCEL_BUTTON,
|
||||
} from '@elastic/eui';
|
||||
import { ViewMode } from '@kbn/embeddable-plugin/public';
|
||||
import { toMountPoint } from '@kbn/react-kibana-mount';
|
||||
|
||||
import { pluginServices } from '../services/plugin_services';
|
||||
import { coreServices } from '../services/kibana_services';
|
||||
import { createConfirmStrings, resetConfirmStrings } from './_dashboard_listing_strings';
|
||||
|
||||
export type DiscardOrKeepSelection = 'cancel' | 'discard' | 'keep';
|
||||
|
@ -33,21 +33,19 @@ export const confirmDiscardUnsavedChanges = (
|
|||
discardCallback: () => void,
|
||||
viewMode: ViewMode = ViewMode.EDIT // we want to show the danger modal on the listing page
|
||||
) => {
|
||||
const {
|
||||
overlays: { openConfirm },
|
||||
} = pluginServices.getServices();
|
||||
|
||||
openConfirm(resetConfirmStrings.getResetSubtitle(viewMode), {
|
||||
confirmButtonText: resetConfirmStrings.getResetConfirmButtonText(),
|
||||
buttonColor: viewMode === ViewMode.EDIT ? 'danger' : 'primary',
|
||||
maxWidth: 500,
|
||||
defaultFocusedButton: EUI_MODAL_CANCEL_BUTTON,
|
||||
title: resetConfirmStrings.getResetTitle(),
|
||||
}).then((isConfirmed) => {
|
||||
if (isConfirmed) {
|
||||
discardCallback();
|
||||
}
|
||||
});
|
||||
coreServices.overlays
|
||||
.openConfirm(resetConfirmStrings.getResetSubtitle(viewMode), {
|
||||
confirmButtonText: resetConfirmStrings.getResetConfirmButtonText(),
|
||||
buttonColor: viewMode === ViewMode.EDIT ? 'danger' : 'primary',
|
||||
maxWidth: 500,
|
||||
defaultFocusedButton: EUI_MODAL_CANCEL_BUTTON,
|
||||
title: resetConfirmStrings.getResetTitle(),
|
||||
})
|
||||
.then((isConfirmed) => {
|
||||
if (isConfirmed) {
|
||||
discardCallback();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const confirmCreateWithUnsaved = (
|
||||
|
@ -57,13 +55,7 @@ export const confirmCreateWithUnsaved = (
|
|||
const titleId = 'confirmDiscardOrKeepTitle';
|
||||
const descriptionId = 'confirmDiscardOrKeepDescription';
|
||||
|
||||
const {
|
||||
analytics,
|
||||
settings: { i18n, theme },
|
||||
overlays: { openModal },
|
||||
} = pluginServices.getServices();
|
||||
|
||||
const session = openModal(
|
||||
const session = coreServices.overlays.openModal(
|
||||
toMountPoint(
|
||||
<EuiFocusTrap
|
||||
clickOutsideDisables={true}
|
||||
|
@ -120,7 +112,7 @@ export const confirmCreateWithUnsaved = (
|
|||
</div>
|
||||
</EuiOutsideClickDetector>
|
||||
</EuiFocusTrap>,
|
||||
{ analytics, i18n, theme }
|
||||
{ analytics: coreServices.analytics, i18n: coreServices.i18n, theme: coreServices.theme }
|
||||
),
|
||||
{
|
||||
'data-test-subj': 'dashboardCreateConfirmModal',
|
||||
|
|
|
@ -7,22 +7,22 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { ComponentType, ReactWrapper, mount } from 'enzyme';
|
||||
import React, { PropsWithChildren } from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { mount, ReactWrapper, ComponentType } from 'enzyme';
|
||||
|
||||
import { I18nProvider } from '@kbn/i18n-react';
|
||||
|
||||
import { pluginServices } from '../services/plugin_services';
|
||||
import { DashboardListing } from './dashboard_listing';
|
||||
|
||||
/**
|
||||
* Mock Table List view. This dashboard component is a wrapper around the shared UX table List view. We
|
||||
* need to ensure we're passing down the correct props, but the table list view itself doesn't need to be rendered
|
||||
* in our tests because it is covered in its package.
|
||||
*/
|
||||
import { TableListView } from '@kbn/content-management-table-list-view';
|
||||
|
||||
import { DashboardListing } from './dashboard_listing';
|
||||
import { DashboardListingProps } from './types';
|
||||
// import { TableListViewKibanaProvider } from '@kbn/content-management-table-list-view';
|
||||
import { coreServices } from '../services/kibana_services';
|
||||
|
||||
jest.mock('@kbn/content-management-table-list-view-table', () => {
|
||||
const originalModule = jest.requireActual('@kbn/content-management-table-list-view-table');
|
||||
return {
|
||||
|
@ -65,7 +65,7 @@ function mountWith({ props: incomingProps }: { props?: Partial<DashboardListingP
|
|||
}
|
||||
|
||||
test('initial filter is passed through', async () => {
|
||||
pluginServices.getServices().dashboardCapabilities.showWriteControls = false;
|
||||
(coreServices.application.capabilities as any).dashboard.showWriteControls = false;
|
||||
|
||||
let component: ReactWrapper;
|
||||
|
||||
|
@ -80,7 +80,7 @@ test('initial filter is passed through', async () => {
|
|||
});
|
||||
|
||||
test('when showWriteControls is true, table list view is passed editing functions', async () => {
|
||||
pluginServices.getServices().dashboardCapabilities.showWriteControls = true;
|
||||
(coreServices.application.capabilities as any).dashboard.showWriteControls = true;
|
||||
|
||||
let component: ReactWrapper;
|
||||
|
||||
|
@ -99,7 +99,7 @@ test('when showWriteControls is true, table list view is passed editing function
|
|||
});
|
||||
|
||||
test('when showWriteControls is false, table list view is not passed editing functions', async () => {
|
||||
pluginServices.getServices().dashboardCapabilities.showWriteControls = false;
|
||||
(coreServices.application.capabilities as any).dashboard.showWriteControls = false;
|
||||
|
||||
let component: ReactWrapper;
|
||||
|
||||
|
|
|
@ -7,26 +7,23 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { FormattedRelative, I18nProvider } from '@kbn/i18n-react';
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import { FavoritesClient } from '@kbn/content-management-favorites-public';
|
||||
import { TableListView } from '@kbn/content-management-table-list-view';
|
||||
import {
|
||||
type TableListViewKibanaDependencies,
|
||||
TableListViewKibanaProvider,
|
||||
} from '@kbn/content-management-table-list-view-table';
|
||||
|
||||
import { TableListViewKibanaProvider } from '@kbn/content-management-table-list-view-table';
|
||||
import { FormattedRelative, I18nProvider } from '@kbn/i18n-react';
|
||||
import { useExecutionContext } from '@kbn/kibana-react-plugin/public';
|
||||
|
||||
import { pluginServices } from '../services/plugin_services';
|
||||
|
||||
import { DASHBOARD_APP_ID, DASHBOARD_CONTENT_ID } from '../dashboard_constants';
|
||||
import {
|
||||
coreServices,
|
||||
savedObjectsTaggingService,
|
||||
usageCollectionService,
|
||||
} from '../services/kibana_services';
|
||||
import { DashboardUnsavedListing } from './dashboard_unsaved_listing';
|
||||
import { useDashboardListingTable } from './hooks/use_dashboard_listing_table';
|
||||
import {
|
||||
DashboardListingProps,
|
||||
DashboardSavedObjectUserContent,
|
||||
TableListViewApplicationService,
|
||||
} from './types';
|
||||
import { DashboardListingProps, DashboardSavedObjectUserContent } from './types';
|
||||
|
||||
export const DashboardListing = ({
|
||||
children,
|
||||
|
@ -35,59 +32,38 @@ export const DashboardListing = ({
|
|||
getDashboardUrl,
|
||||
useSessionStorageIntegration,
|
||||
}: DashboardListingProps) => {
|
||||
const {
|
||||
analytics,
|
||||
application,
|
||||
notifications,
|
||||
overlays,
|
||||
http,
|
||||
i18n,
|
||||
chrome: { theme },
|
||||
savedObjectsTagging,
|
||||
coreContext: { executionContext },
|
||||
userProfile,
|
||||
dashboardContentInsights: { contentInsightsClient },
|
||||
dashboardFavorites,
|
||||
} = pluginServices.getServices();
|
||||
|
||||
useExecutionContext(executionContext, {
|
||||
useExecutionContext(coreServices.executionContext, {
|
||||
type: 'application',
|
||||
page: 'list',
|
||||
});
|
||||
|
||||
const { unsavedDashboardIds, refreshUnsavedDashboards, tableListViewTableProps } =
|
||||
useDashboardListingTable({
|
||||
goToDashboard,
|
||||
getDashboardUrl,
|
||||
useSessionStorageIntegration,
|
||||
initialFilter,
|
||||
});
|
||||
const {
|
||||
unsavedDashboardIds,
|
||||
refreshUnsavedDashboards,
|
||||
tableListViewTableProps,
|
||||
contentInsightsClient,
|
||||
} = useDashboardListingTable({
|
||||
goToDashboard,
|
||||
getDashboardUrl,
|
||||
useSessionStorageIntegration,
|
||||
initialFilter,
|
||||
});
|
||||
|
||||
const savedObjectsTaggingFakePlugin = useMemo(() => {
|
||||
return savedObjectsTagging.hasApi // TODO: clean up this logic once https://github.com/elastic/kibana/issues/140433 is resolved
|
||||
? ({
|
||||
ui: savedObjectsTagging,
|
||||
} as TableListViewKibanaDependencies['savedObjectsTagging'])
|
||||
: undefined;
|
||||
}, [savedObjectsTagging]);
|
||||
const dashboardFavoritesClient = useMemo(() => {
|
||||
return new FavoritesClient(DASHBOARD_APP_ID, DASHBOARD_CONTENT_ID, {
|
||||
http: coreServices.http,
|
||||
usageCollection: usageCollectionService,
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<I18nProvider>
|
||||
<TableListViewKibanaProvider
|
||||
{...{
|
||||
core: {
|
||||
analytics,
|
||||
application: application as TableListViewApplicationService,
|
||||
notifications,
|
||||
overlays,
|
||||
http,
|
||||
i18n,
|
||||
theme,
|
||||
userProfile,
|
||||
},
|
||||
savedObjectsTagging: savedObjectsTaggingFakePlugin,
|
||||
core: coreServices,
|
||||
savedObjectsTagging: savedObjectsTaggingService?.getTaggingApi(),
|
||||
FormattedRelative,
|
||||
favorites: dashboardFavorites,
|
||||
favorites: dashboardFavoritesClient,
|
||||
contentInsightsClient,
|
||||
}}
|
||||
>
|
||||
|
|
|
@ -7,18 +7,18 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { ComponentType, ReactWrapper, mount } from 'enzyme';
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { mount, ReactWrapper, ComponentType } from 'enzyme';
|
||||
|
||||
import { I18nProvider } from '@kbn/i18n-react';
|
||||
|
||||
import { coreServices } from '../services/kibana_services';
|
||||
import { confirmDiscardUnsavedChanges } from './confirm_overlays';
|
||||
import {
|
||||
DashboardListingEmptyPrompt,
|
||||
DashboardListingEmptyPromptProps,
|
||||
} from './dashboard_listing_empty_prompt';
|
||||
import { pluginServices } from '../services/plugin_services';
|
||||
import { confirmDiscardUnsavedChanges } from './confirm_overlays';
|
||||
|
||||
jest.mock('./confirm_overlays', () => {
|
||||
const originalModule = jest.requireActual('./confirm_overlays');
|
||||
|
@ -56,7 +56,7 @@ function mountWith({
|
|||
}
|
||||
|
||||
test('renders readonly empty prompt when showWriteControls is off', async () => {
|
||||
pluginServices.getServices().dashboardCapabilities.showWriteControls = false;
|
||||
(coreServices.application.capabilities as any).dashboard.showWriteControls = false;
|
||||
|
||||
let component: ReactWrapper;
|
||||
await act(async () => {
|
||||
|
@ -68,7 +68,7 @@ test('renders readonly empty prompt when showWriteControls is off', async () =>
|
|||
});
|
||||
|
||||
test('renders empty prompt with link when showWriteControls is on', async () => {
|
||||
pluginServices.getServices().dashboardCapabilities.showWriteControls = true;
|
||||
(coreServices.application.capabilities as any).dashboard.showWriteControls = true;
|
||||
|
||||
let component: ReactWrapper;
|
||||
await act(async () => {
|
||||
|
@ -80,7 +80,7 @@ test('renders empty prompt with link when showWriteControls is on', async () =>
|
|||
});
|
||||
|
||||
test('renders disabled action button when disableCreateDashboardButton is true', async () => {
|
||||
pluginServices.getServices().dashboardCapabilities.showWriteControls = true;
|
||||
(coreServices.application.capabilities as any).dashboard.showWriteControls = true;
|
||||
|
||||
let component: ReactWrapper;
|
||||
await act(async () => {
|
||||
|
@ -95,7 +95,7 @@ test('renders disabled action button when disableCreateDashboardButton is true',
|
|||
});
|
||||
|
||||
test('renders continue button when no dashboards exist but one is in progress', async () => {
|
||||
pluginServices.getServices().dashboardCapabilities.showWriteControls = true;
|
||||
(coreServices.application.capabilities as any).dashboard.showWriteControls = true;
|
||||
let component: ReactWrapper;
|
||||
let props: DashboardListingEmptyPromptProps;
|
||||
await act(async () => {
|
||||
|
@ -114,7 +114,7 @@ test('renders continue button when no dashboards exist but one is in progress',
|
|||
});
|
||||
|
||||
test('renders discard button when no dashboards exist but one is in progress', async () => {
|
||||
pluginServices.getServices().dashboardCapabilities.showWriteControls = true;
|
||||
(coreServices.application.capabilities as any).dashboard.showWriteControls = true;
|
||||
let component: ReactWrapper;
|
||||
await act(async () => {
|
||||
({ component } = mountWith({
|
||||
|
|
|
@ -8,24 +8,28 @@
|
|||
*/
|
||||
|
||||
import {
|
||||
EuiLink,
|
||||
EuiButton,
|
||||
EuiFlexItem,
|
||||
EuiFlexGroup,
|
||||
EuiButtonEmpty,
|
||||
EuiEmptyPrompt,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiLink,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
|
||||
import {
|
||||
noItemsStrings,
|
||||
getNewDashboardTitle,
|
||||
DASHBOARD_PANELS_UNSAVED_ID,
|
||||
getDashboardBackupService,
|
||||
} from '../services/dashboard_backup_service';
|
||||
import { coreServices } from '../services/kibana_services';
|
||||
import { getDashboardCapabilities } from '../utils/get_dashboard_capabilities';
|
||||
import {
|
||||
dashboardUnsavedListingStrings,
|
||||
getNewDashboardTitle,
|
||||
noItemsStrings,
|
||||
} from './_dashboard_listing_strings';
|
||||
import { pluginServices } from '../services/plugin_services';
|
||||
import { confirmDiscardUnsavedChanges } from './confirm_overlays';
|
||||
import { DASHBOARD_PANELS_UNSAVED_ID } from '../services/dashboard_backup/dashboard_backup_service';
|
||||
import { DashboardListingProps } from './types';
|
||||
|
||||
export interface DashboardListingEmptyPromptProps {
|
||||
|
@ -45,12 +49,6 @@ export const DashboardListingEmptyPrompt = ({
|
|||
createItem,
|
||||
disableCreateDashboardButton,
|
||||
}: DashboardListingEmptyPromptProps) => {
|
||||
const {
|
||||
application,
|
||||
dashboardBackup,
|
||||
dashboardCapabilities: { showWriteControls },
|
||||
} = pluginServices.getServices();
|
||||
|
||||
const isEditingFirstDashboard = useMemo(
|
||||
() => useSessionStorageIntegration && unsavedDashboardIds.length === 1,
|
||||
[unsavedDashboardIds.length, useSessionStorageIntegration]
|
||||
|
@ -78,8 +76,9 @@ export const DashboardListingEmptyPrompt = ({
|
|||
color="danger"
|
||||
onClick={() =>
|
||||
confirmDiscardUnsavedChanges(() => {
|
||||
dashboardBackup.clearState(DASHBOARD_PANELS_UNSAVED_ID);
|
||||
setUnsavedDashboardIds(dashboardBackup.getDashboardIdsWithUnsavedChanges());
|
||||
const dashboardBackupService = getDashboardBackupService();
|
||||
dashboardBackupService.clearState(DASHBOARD_PANELS_UNSAVED_ID);
|
||||
setUnsavedDashboardIds(dashboardBackupService.getDashboardIdsWithUnsavedChanges());
|
||||
})
|
||||
}
|
||||
data-test-subj="discardDashboardPromptButton"
|
||||
|
@ -106,12 +105,11 @@ export const DashboardListingEmptyPrompt = ({
|
|||
isEditingFirstDashboard,
|
||||
createItem,
|
||||
disableCreateDashboardButton,
|
||||
dashboardBackup,
|
||||
goToDashboard,
|
||||
setUnsavedDashboardIds,
|
||||
]);
|
||||
|
||||
if (!showWriteControls) {
|
||||
if (!getDashboardCapabilities().showWriteControls) {
|
||||
return (
|
||||
<EuiEmptyPrompt
|
||||
iconType="glasses"
|
||||
|
@ -147,7 +145,7 @@ export const DashboardListingEmptyPrompt = ({
|
|||
sampleDataInstallLink: (
|
||||
<EuiLink
|
||||
onClick={() =>
|
||||
application.navigateToApp('home', {
|
||||
coreServices.application.navigateToApp('home', {
|
||||
path: '#/tutorial_directory/sampleData',
|
||||
})
|
||||
}
|
||||
|
|
|
@ -7,26 +7,19 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { FormattedRelative, I18nProvider } from '@kbn/i18n-react';
|
||||
import React, { useMemo } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
type TableListViewKibanaDependencies,
|
||||
TableListViewKibanaProvider,
|
||||
TableListViewTable,
|
||||
} from '@kbn/content-management-table-list-view-table';
|
||||
|
||||
import { FormattedRelative, I18nProvider } from '@kbn/i18n-react';
|
||||
import { useExecutionContext } from '@kbn/kibana-react-plugin/public';
|
||||
|
||||
import { pluginServices } from '../services/plugin_services';
|
||||
|
||||
import { coreServices, savedObjectsTaggingService } from '../services/kibana_services';
|
||||
import { DashboardUnsavedListing } from './dashboard_unsaved_listing';
|
||||
import { useDashboardListingTable } from './hooks/use_dashboard_listing_table';
|
||||
import {
|
||||
DashboardListingProps,
|
||||
DashboardSavedObjectUserContent,
|
||||
TableListViewApplicationService,
|
||||
} from './types';
|
||||
import { DashboardListingProps, DashboardSavedObjectUserContent } from './types';
|
||||
|
||||
export const DashboardListingTable = ({
|
||||
disableCreateDashboardButton,
|
||||
|
@ -37,21 +30,7 @@ export const DashboardListingTable = ({
|
|||
urlStateEnabled,
|
||||
showCreateDashboardButton = true,
|
||||
}: DashboardListingProps) => {
|
||||
const {
|
||||
analytics,
|
||||
application,
|
||||
notifications,
|
||||
overlays,
|
||||
http,
|
||||
i18n,
|
||||
savedObjectsTagging,
|
||||
coreContext: { executionContext },
|
||||
chrome: { theme },
|
||||
userProfile,
|
||||
dashboardContentInsights: { contentInsightsClient },
|
||||
} = pluginServices.getServices();
|
||||
|
||||
useExecutionContext(executionContext, {
|
||||
useExecutionContext(coreServices.executionContext, {
|
||||
type: 'application',
|
||||
page: 'list',
|
||||
});
|
||||
|
@ -60,6 +39,7 @@ export const DashboardListingTable = ({
|
|||
unsavedDashboardIds,
|
||||
refreshUnsavedDashboards,
|
||||
tableListViewTableProps: { title: tableCaption, ...tableListViewTable },
|
||||
contentInsightsClient,
|
||||
} = useDashboardListingTable({
|
||||
disableCreateDashboardButton,
|
||||
goToDashboard,
|
||||
|
@ -70,35 +50,11 @@ export const DashboardListingTable = ({
|
|||
showCreateDashboardButton,
|
||||
});
|
||||
|
||||
const savedObjectsTaggingFakePlugin = useMemo(
|
||||
() =>
|
||||
savedObjectsTagging.hasApi // TODO: clean up this logic once https://github.com/elastic/kibana/issues/140433 is resolved
|
||||
? ({
|
||||
ui: savedObjectsTagging,
|
||||
} as TableListViewKibanaDependencies['savedObjectsTagging'])
|
||||
: undefined,
|
||||
[savedObjectsTagging]
|
||||
);
|
||||
|
||||
const core = useMemo(
|
||||
() => ({
|
||||
analytics,
|
||||
application: application as TableListViewApplicationService,
|
||||
notifications,
|
||||
overlays,
|
||||
http,
|
||||
i18n,
|
||||
theme,
|
||||
userProfile,
|
||||
}),
|
||||
[application, notifications, overlays, http, analytics, i18n, theme, userProfile]
|
||||
);
|
||||
|
||||
return (
|
||||
<I18nProvider>
|
||||
<TableListViewKibanaProvider
|
||||
core={core}
|
||||
savedObjectsTagging={savedObjectsTaggingFakePlugin}
|
||||
core={coreServices}
|
||||
savedObjectsTagging={savedObjectsTaggingService?.getTaggingApi()}
|
||||
FormattedRelative={FormattedRelative}
|
||||
contentInsightsClient={contentInsightsClient}
|
||||
>
|
||||
|
|
|
@ -7,17 +7,21 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { ComponentType, mount } from 'enzyme';
|
||||
import React from 'react';
|
||||
import { mount, ComponentType } from 'enzyme';
|
||||
|
||||
import { findTestSubject } from '@elastic/eui/lib/test';
|
||||
import { ViewMode } from '@kbn/embeddable-plugin/public';
|
||||
import { I18nProvider } from '@kbn/i18n-react';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
import { findTestSubject } from '@elastic/eui/lib/test';
|
||||
|
||||
import { pluginServices } from '../services/plugin_services';
|
||||
import {
|
||||
DASHBOARD_PANELS_UNSAVED_ID,
|
||||
getDashboardBackupService,
|
||||
} from '../services/dashboard_backup_service';
|
||||
import { getDashboardContentManagementService } from '../services/dashboard_content_management_service';
|
||||
import { coreServices } from '../services/kibana_services';
|
||||
import { DashboardUnsavedListing, DashboardUnsavedListingProps } from './dashboard_unsaved_listing';
|
||||
import { DASHBOARD_PANELS_UNSAVED_ID } from '../services/dashboard_backup/dashboard_backup_service';
|
||||
import { ViewMode } from '@kbn/embeddable-plugin/public';
|
||||
|
||||
const makeDefaultProps = (): DashboardUnsavedListingProps => ({
|
||||
goToDashboard: jest.fn(),
|
||||
|
@ -39,12 +43,13 @@ function mountWith({ props: incomingProps }: { props?: Partial<DashboardUnsavedL
|
|||
}
|
||||
|
||||
describe('Unsaved listing', () => {
|
||||
const dashboardBackupService = getDashboardBackupService();
|
||||
const dashboardContentManagementService = getDashboardContentManagementService();
|
||||
|
||||
it('Gets information for each unsaved dashboard', async () => {
|
||||
mountWith({});
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
pluginServices.getServices().dashboardContentManagement.findDashboards.findByIds
|
||||
).toHaveBeenCalledTimes(1);
|
||||
expect(dashboardContentManagementService.findDashboards.findByIds).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -53,9 +58,9 @@ describe('Unsaved listing', () => {
|
|||
props.unsavedDashboardIds = ['dashboardUnsavedOne', DASHBOARD_PANELS_UNSAVED_ID];
|
||||
mountWith({ props });
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
pluginServices.getServices().dashboardContentManagement.findDashboards.findByIds
|
||||
).toHaveBeenCalledWith(['dashboardUnsavedOne']);
|
||||
expect(dashboardContentManagementService.findDashboards.findByIds).toHaveBeenCalledWith([
|
||||
'dashboardUnsavedOne',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -94,17 +99,13 @@ describe('Unsaved listing', () => {
|
|||
getDiscardButton().simulate('click');
|
||||
waitFor(() => {
|
||||
component.update();
|
||||
expect(pluginServices.getServices().overlays.openConfirm).toHaveBeenCalled();
|
||||
expect(pluginServices.getServices().dashboardBackup.clearState).toHaveBeenCalledWith(
|
||||
'dashboardUnsavedOne'
|
||||
);
|
||||
expect(coreServices.overlays.openConfirm).toHaveBeenCalled();
|
||||
expect(dashboardBackupService.clearState).toHaveBeenCalledWith('dashboardUnsavedOne');
|
||||
});
|
||||
});
|
||||
|
||||
it('removes unsaved changes from any dashboard which errors on fetch', async () => {
|
||||
(
|
||||
pluginServices.getServices().dashboardContentManagement.findDashboards.findByIds as jest.Mock
|
||||
).mockResolvedValue([
|
||||
(dashboardContentManagementService.findDashboards.findByIds as jest.Mock).mockResolvedValue([
|
||||
{
|
||||
id: 'failCase1',
|
||||
status: 'error',
|
||||
|
@ -129,17 +130,11 @@ describe('Unsaved listing', () => {
|
|||
const { component } = mountWith({ props });
|
||||
waitFor(() => {
|
||||
component.update();
|
||||
expect(pluginServices.getServices().dashboardBackup.clearState).toHaveBeenCalledWith(
|
||||
'failCase1'
|
||||
);
|
||||
expect(pluginServices.getServices().dashboardBackup.clearState).toHaveBeenCalledWith(
|
||||
'failCase2'
|
||||
);
|
||||
expect(dashboardBackupService.clearState).toHaveBeenCalledWith('failCase1');
|
||||
expect(dashboardBackupService.clearState).toHaveBeenCalledWith('failCase2');
|
||||
|
||||
// clearing panels from dashboard with errors should cause getDashboardIdsWithUnsavedChanges to be called again.
|
||||
expect(
|
||||
pluginServices.getServices().dashboardBackup.getDashboardIdsWithUnsavedChanges
|
||||
).toHaveBeenCalledTimes(2);
|
||||
expect(dashboardBackupService.getDashboardIdsWithUnsavedChanges).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -16,15 +16,18 @@ import {
|
|||
EuiSpacer,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { ViewMode } from '@kbn/embeddable-plugin/public';
|
||||
|
||||
import { pluginServices } from '../services/plugin_services';
|
||||
import { confirmDiscardUnsavedChanges } from './confirm_overlays';
|
||||
import { DashboardAttributes } from '../../common/content_management';
|
||||
import {
|
||||
DASHBOARD_PANELS_UNSAVED_ID,
|
||||
getDashboardBackupService,
|
||||
} from '../services/dashboard_backup_service';
|
||||
import { getDashboardContentManagementService } from '../services/dashboard_content_management_service';
|
||||
import { dashboardUnsavedListingStrings, getNewDashboardTitle } from './_dashboard_listing_strings';
|
||||
import { DASHBOARD_PANELS_UNSAVED_ID } from '../services/dashboard_backup/dashboard_backup_service';
|
||||
import { confirmDiscardUnsavedChanges } from './confirm_overlays';
|
||||
|
||||
const DashboardUnsavedItem = ({
|
||||
id,
|
||||
|
@ -116,12 +119,8 @@ export const DashboardUnsavedListing = ({
|
|||
unsavedDashboardIds,
|
||||
refreshUnsavedDashboards,
|
||||
}: DashboardUnsavedListingProps) => {
|
||||
const {
|
||||
dashboardBackup,
|
||||
dashboardContentManagement: { findDashboards },
|
||||
} = pluginServices.getServices();
|
||||
|
||||
const [items, setItems] = useState<UnsavedItemMap>({});
|
||||
const dashboardBackupService = useMemo(() => getDashboardBackupService(), []);
|
||||
|
||||
const onOpen = useCallback(
|
||||
(id?: string) => {
|
||||
|
@ -133,11 +132,11 @@ export const DashboardUnsavedListing = ({
|
|||
const onDiscard = useCallback(
|
||||
(id?: string) => {
|
||||
confirmDiscardUnsavedChanges(() => {
|
||||
dashboardBackup.clearState(id);
|
||||
dashboardBackupService.clearState(id);
|
||||
refreshUnsavedDashboards();
|
||||
});
|
||||
},
|
||||
[refreshUnsavedDashboards, dashboardBackup]
|
||||
[dashboardBackupService, refreshUnsavedDashboards]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -148,37 +147,39 @@ export const DashboardUnsavedListing = ({
|
|||
const existingDashboardsWithUnsavedChanges = unsavedDashboardIds.filter(
|
||||
(id) => id !== DASHBOARD_PANELS_UNSAVED_ID
|
||||
);
|
||||
findDashboards.findByIds(existingDashboardsWithUnsavedChanges).then((results) => {
|
||||
const dashboardMap = {};
|
||||
if (canceled) {
|
||||
return;
|
||||
}
|
||||
let hasError = false;
|
||||
const newItems = results.reduce((map, result) => {
|
||||
if (result.status === 'error') {
|
||||
hasError = true;
|
||||
if (result.error.statusCode === 404) {
|
||||
// Save object not found error
|
||||
dashboardBackup.clearState(result.id);
|
||||
}
|
||||
return map;
|
||||
getDashboardContentManagementService()
|
||||
.findDashboards.findByIds(existingDashboardsWithUnsavedChanges)
|
||||
.then((results) => {
|
||||
const dashboardMap = {};
|
||||
if (canceled) {
|
||||
return;
|
||||
}
|
||||
return {
|
||||
...map,
|
||||
[result.id || DASHBOARD_PANELS_UNSAVED_ID]: result.attributes,
|
||||
};
|
||||
}, dashboardMap);
|
||||
if (hasError) {
|
||||
refreshUnsavedDashboards();
|
||||
return;
|
||||
}
|
||||
setItems(newItems);
|
||||
});
|
||||
let hasError = false;
|
||||
const newItems = results.reduce((map, result) => {
|
||||
if (result.status === 'error') {
|
||||
hasError = true;
|
||||
if (result.error.statusCode === 404) {
|
||||
// Save object not found error
|
||||
dashboardBackupService.clearState(result.id);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
return {
|
||||
...map,
|
||||
[result.id || DASHBOARD_PANELS_UNSAVED_ID]: result.attributes,
|
||||
};
|
||||
}, dashboardMap);
|
||||
if (hasError) {
|
||||
refreshUnsavedDashboards();
|
||||
return;
|
||||
}
|
||||
setItems(newItems);
|
||||
});
|
||||
|
||||
return () => {
|
||||
canceled = true;
|
||||
};
|
||||
}, [refreshUnsavedDashboards, dashboardBackup, unsavedDashboardIds, findDashboards]);
|
||||
}, [dashboardBackupService, refreshUnsavedDashboards, unsavedDashboardIds]);
|
||||
|
||||
return unsavedDashboardIds.length === 0 ? null : (
|
||||
<>
|
||||
|
|
|
@ -9,10 +9,13 @@
|
|||
|
||||
import { act, renderHook } from '@testing-library/react-hooks';
|
||||
|
||||
import { useDashboardListingTable } from './use_dashboard_listing_table';
|
||||
import { pluginServices } from '../../services/plugin_services';
|
||||
import { getDashboardBackupService } from '../../services/dashboard_backup_service';
|
||||
import { getDashboardContentManagementService } from '../../services/dashboard_content_management_service';
|
||||
import { coreServices } from '../../services/kibana_services';
|
||||
import { confirmCreateWithUnsaved } from '../confirm_overlays';
|
||||
import { DashboardSavedObjectUserContent } from '../types';
|
||||
import { useDashboardListingTable } from './use_dashboard_listing_table';
|
||||
|
||||
const clearStateMock = jest.fn();
|
||||
const getDashboardUrl = jest.fn();
|
||||
const goToDashboard = jest.fn();
|
||||
|
@ -26,7 +29,6 @@ const getUiSettingsMock = jest.fn().mockImplementation((key) => {
|
|||
}
|
||||
return null;
|
||||
});
|
||||
const getPluginServices = pluginServices.getServices();
|
||||
|
||||
jest.mock('@kbn/ebt-tools', () => ({
|
||||
reportPerformanceMetricEvent: jest.fn(),
|
||||
|
@ -45,20 +47,20 @@ jest.mock('../_dashboard_listing_strings', () => ({
|
|||
}));
|
||||
|
||||
describe('useDashboardListingTable', () => {
|
||||
const dashboardBackupService = getDashboardBackupService();
|
||||
const dashboardContentManagementService = getDashboardContentManagementService();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
getPluginServices.dashboardBackup.dashboardHasUnsavedEdits = jest.fn().mockReturnValue(true);
|
||||
dashboardBackupService.dashboardHasUnsavedEdits = jest.fn().mockReturnValue(true);
|
||||
|
||||
getPluginServices.dashboardBackup.getDashboardIdsWithUnsavedChanges = jest
|
||||
.fn()
|
||||
.mockReturnValue([]);
|
||||
dashboardBackupService.getDashboardIdsWithUnsavedChanges = jest.fn().mockReturnValue([]);
|
||||
|
||||
getPluginServices.dashboardBackup.clearState = clearStateMock;
|
||||
getPluginServices.dashboardCapabilities.showWriteControls = true;
|
||||
getPluginServices.dashboardContentManagement.deleteDashboards = deleteDashboards;
|
||||
getPluginServices.settings.uiSettings.get = getUiSettingsMock;
|
||||
getPluginServices.notifications.toasts.addError = jest.fn();
|
||||
dashboardBackupService.clearState = clearStateMock;
|
||||
dashboardContentManagementService.deleteDashboards = deleteDashboards;
|
||||
coreServices.uiSettings.get = getUiSettingsMock;
|
||||
coreServices.notifications.toasts.addError = jest.fn();
|
||||
});
|
||||
|
||||
test('should return the correct initial hasInitialFetchReturned state', () => {
|
||||
|
@ -232,8 +234,12 @@ describe('useDashboardListingTable', () => {
|
|||
});
|
||||
|
||||
test('createItem should be undefined when showWriteControls equals false', () => {
|
||||
getPluginServices.dashboardCapabilities.showWriteControls = false;
|
||||
|
||||
coreServices.application.capabilities = {
|
||||
...coreServices.application.capabilities,
|
||||
dashboard: {
|
||||
showWriteControls: false,
|
||||
},
|
||||
};
|
||||
const { result } = renderHook(() =>
|
||||
useDashboardListingTable({
|
||||
getDashboardUrl,
|
||||
|
@ -245,7 +251,8 @@ describe('useDashboardListingTable', () => {
|
|||
});
|
||||
|
||||
test('deleteItems should be undefined when showWriteControls equals false', () => {
|
||||
getPluginServices.dashboardCapabilities.showWriteControls = false;
|
||||
(coreServices.application.capabilities as any).dashboard.showWriteControls = false;
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useDashboardListingTable({
|
||||
getDashboardUrl,
|
||||
|
@ -257,7 +264,8 @@ describe('useDashboardListingTable', () => {
|
|||
});
|
||||
|
||||
test('editItem should be undefined when showWriteControls equals false', () => {
|
||||
getPluginServices.dashboardCapabilities.showWriteControls = false;
|
||||
(coreServices.application.capabilities as any).dashboard.showWriteControls = false;
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useDashboardListingTable({
|
||||
getDashboardUrl,
|
||||
|
|
|
@ -7,29 +7,34 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import React, { useCallback, useState, useMemo } from 'react';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { ViewMode } from '@kbn/embeddable-plugin/public';
|
||||
import { reportPerformanceMetricEvent } from '@kbn/ebt-tools';
|
||||
import type { SavedObjectsFindOptionsReference } from '@kbn/core/public';
|
||||
import { OpenContentEditorParams } from '@kbn/content-management-content-editor';
|
||||
import { ContentInsightsClient } from '@kbn/content-management-content-insights-public';
|
||||
import { TableListViewTableProps } from '@kbn/content-management-table-list-view-table';
|
||||
import type { SavedObjectsFindOptionsReference } from '@kbn/core/public';
|
||||
import { reportPerformanceMetricEvent } from '@kbn/ebt-tools';
|
||||
import { ViewMode } from '@kbn/embeddable-plugin/public';
|
||||
|
||||
import { DashboardContainerInput } from '../../../common';
|
||||
import { DashboardItem } from '../../../common/content_management';
|
||||
import {
|
||||
DASHBOARD_CONTENT_ID,
|
||||
SAVED_OBJECT_DELETE_TIME,
|
||||
SAVED_OBJECT_LOADED_TIME,
|
||||
} from '../../dashboard_constants';
|
||||
import { getDashboardBackupService } from '../../services/dashboard_backup_service';
|
||||
import { getDashboardContentManagementService } from '../../services/dashboard_content_management_service';
|
||||
import { getDashboardRecentlyAccessedService } from '../../services/dashboard_recently_accessed_service';
|
||||
import { coreServices } from '../../services/kibana_services';
|
||||
import { getDashboardCapabilities } from '../../utils/get_dashboard_capabilities';
|
||||
import {
|
||||
dashboardListingErrorStrings,
|
||||
dashboardListingTableStrings,
|
||||
} from '../_dashboard_listing_strings';
|
||||
import { DashboardContainerInput } from '../../../common';
|
||||
import { DashboardSavedObjectUserContent } from '../types';
|
||||
import { confirmCreateWithUnsaved } from '../confirm_overlays';
|
||||
import { pluginServices } from '../../services/plugin_services';
|
||||
import { DashboardItem } from '../../../common/content_management';
|
||||
import { DashboardListingEmptyPrompt } from '../dashboard_listing_empty_prompt';
|
||||
import { DashboardSavedObjectUserContent } from '../types';
|
||||
|
||||
type GetDetailViewLink =
|
||||
TableListViewTableProps<DashboardSavedObjectUserContent>['getDetailViewLink'];
|
||||
|
@ -67,6 +72,7 @@ interface UseDashboardListingTableReturnType {
|
|||
refreshUnsavedDashboards: () => void;
|
||||
tableListViewTableProps: DashboardListingViewTableProps;
|
||||
unsavedDashboardIds: string[];
|
||||
contentInsightsClient: ContentInsightsClient;
|
||||
}
|
||||
|
||||
export const useDashboardListingTable = ({
|
||||
|
@ -90,51 +96,44 @@ export const useDashboardListingTable = ({
|
|||
useSessionStorageIntegration?: boolean;
|
||||
showCreateDashboardButton?: boolean;
|
||||
}): UseDashboardListingTableReturnType => {
|
||||
const {
|
||||
dashboardBackup,
|
||||
dashboardCapabilities: { showWriteControls },
|
||||
settings: { uiSettings },
|
||||
dashboardContentManagement: {
|
||||
findDashboards,
|
||||
deleteDashboards,
|
||||
updateDashboardMeta,
|
||||
checkForDuplicateDashboardTitle,
|
||||
},
|
||||
notifications: { toasts },
|
||||
dashboardRecentlyAccessed,
|
||||
} = pluginServices.getServices();
|
||||
|
||||
const { getEntityName, getTableListTitle, getEntityNamePlural } = dashboardListingTableStrings;
|
||||
const title = getTableListTitle();
|
||||
const entityName = getEntityName();
|
||||
const entityNamePlural = getEntityNamePlural();
|
||||
const [pageDataTestSubject, setPageDataTestSubject] = useState<string>();
|
||||
const [hasInitialFetchReturned, setHasInitialFetchReturned] = useState(false);
|
||||
const [unsavedDashboardIds, setUnsavedDashboardIds] = useState<string[]>(
|
||||
dashboardBackup.getDashboardIdsWithUnsavedChanges()
|
||||
|
||||
const dashboardBackupService = useMemo(() => getDashboardBackupService(), []);
|
||||
const dashboardContentManagementService = useMemo(
|
||||
() => getDashboardContentManagementService(),
|
||||
[]
|
||||
);
|
||||
|
||||
const listingLimit = uiSettings.get(SAVED_OBJECTS_LIMIT_SETTING);
|
||||
const initialPageSize = uiSettings.get(SAVED_OBJECTS_PER_PAGE_SETTING);
|
||||
const [unsavedDashboardIds, setUnsavedDashboardIds] = useState<string[]>(
|
||||
dashboardBackupService.getDashboardIdsWithUnsavedChanges()
|
||||
);
|
||||
|
||||
const listingLimit = coreServices.uiSettings.get(SAVED_OBJECTS_LIMIT_SETTING);
|
||||
const initialPageSize = coreServices.uiSettings.get(SAVED_OBJECTS_PER_PAGE_SETTING);
|
||||
|
||||
const createItem = useCallback(() => {
|
||||
if (useSessionStorageIntegration && dashboardBackup.dashboardHasUnsavedEdits()) {
|
||||
if (useSessionStorageIntegration && dashboardBackupService.dashboardHasUnsavedEdits()) {
|
||||
confirmCreateWithUnsaved(() => {
|
||||
dashboardBackup.clearState();
|
||||
dashboardBackupService.clearState();
|
||||
goToDashboard();
|
||||
}, goToDashboard);
|
||||
return;
|
||||
}
|
||||
goToDashboard();
|
||||
}, [dashboardBackup, goToDashboard, useSessionStorageIntegration]);
|
||||
}, [dashboardBackupService, goToDashboard, useSessionStorageIntegration]);
|
||||
|
||||
const updateItemMeta = useCallback(
|
||||
async (props: Pick<DashboardContainerInput, 'id' | 'title' | 'description' | 'tags'>) => {
|
||||
await updateDashboardMeta(props);
|
||||
await dashboardContentManagementService.updateDashboardMeta(props);
|
||||
|
||||
setUnsavedDashboardIds(dashboardBackup.getDashboardIdsWithUnsavedChanges());
|
||||
setUnsavedDashboardIds(dashboardBackupService.getDashboardIdsWithUnsavedChanges());
|
||||
},
|
||||
[dashboardBackup, updateDashboardMeta]
|
||||
[dashboardBackupService, dashboardContentManagementService]
|
||||
);
|
||||
|
||||
const contentEditorValidators: OpenContentEditorParams['customValidators'] = useMemo(
|
||||
|
@ -145,17 +144,19 @@ export const useDashboardListingTable = ({
|
|||
fn: async (value: string, id: string) => {
|
||||
if (id) {
|
||||
try {
|
||||
const [dashboard] = await findDashboards.findByIds([id]);
|
||||
const [dashboard] =
|
||||
await dashboardContentManagementService.findDashboards.findByIds([id]);
|
||||
if (dashboard.status === 'error') {
|
||||
return;
|
||||
}
|
||||
|
||||
const validTitle = await checkForDuplicateDashboardTitle({
|
||||
title: value,
|
||||
copyOnSave: false,
|
||||
lastSavedTitle: dashboard.attributes.title,
|
||||
isTitleDuplicateConfirmed: false,
|
||||
});
|
||||
const validTitle =
|
||||
await dashboardContentManagementService.checkForDuplicateDashboardTitle({
|
||||
title: value,
|
||||
copyOnSave: false,
|
||||
lastSavedTitle: dashboard.attributes.title,
|
||||
isTitleDuplicateConfirmed: false,
|
||||
});
|
||||
|
||||
if (!validTitle) {
|
||||
throw new Error(dashboardListingErrorStrings.getDuplicateTitleWarning(value));
|
||||
|
@ -168,7 +169,7 @@ export const useDashboardListingTable = ({
|
|||
},
|
||||
],
|
||||
}),
|
||||
[checkForDuplicateDashboardTitle, findDashboards]
|
||||
[dashboardContentManagementService]
|
||||
);
|
||||
|
||||
const emptyPrompt = useMemo(
|
||||
|
@ -204,7 +205,7 @@ export const useDashboardListingTable = ({
|
|||
) => {
|
||||
const searchStartTime = window.performance.now();
|
||||
|
||||
return findDashboards
|
||||
return dashboardContentManagementService.findDashboards
|
||||
.search({
|
||||
search: searchTerm,
|
||||
size: listingLimit,
|
||||
|
@ -214,7 +215,7 @@ export const useDashboardListingTable = ({
|
|||
.then(({ total, hits }) => {
|
||||
const searchEndTime = window.performance.now();
|
||||
const searchDuration = searchEndTime - searchStartTime;
|
||||
reportPerformanceMetricEvent(pluginServices.getServices().analytics, {
|
||||
reportPerformanceMetricEvent(coreServices.analytics, {
|
||||
eventName: SAVED_OBJECT_LOADED_TIME,
|
||||
duration: searchDuration,
|
||||
meta: {
|
||||
|
@ -227,7 +228,7 @@ export const useDashboardListingTable = ({
|
|||
};
|
||||
});
|
||||
},
|
||||
[findDashboards, listingLimit]
|
||||
[listingLimit, dashboardContentManagementService]
|
||||
);
|
||||
|
||||
const deleteItems = useCallback(
|
||||
|
@ -235,15 +236,15 @@ export const useDashboardListingTable = ({
|
|||
try {
|
||||
const deleteStartTime = window.performance.now();
|
||||
|
||||
await deleteDashboards(
|
||||
await dashboardContentManagementService.deleteDashboards(
|
||||
dashboardsToDelete.map(({ id }) => {
|
||||
dashboardBackup.clearState(id);
|
||||
dashboardBackupService.clearState(id);
|
||||
return id;
|
||||
})
|
||||
);
|
||||
|
||||
const deleteDuration = window.performance.now() - deleteStartTime;
|
||||
reportPerformanceMetricEvent(pluginServices.getServices().analytics, {
|
||||
reportPerformanceMetricEvent(coreServices.analytics, {
|
||||
eventName: SAVED_OBJECT_DELETE_TIME,
|
||||
duration: deleteDuration,
|
||||
meta: {
|
||||
|
@ -252,14 +253,14 @@ export const useDashboardListingTable = ({
|
|||
},
|
||||
});
|
||||
} catch (error) {
|
||||
toasts.addError(error, {
|
||||
coreServices.notifications.toasts.addError(error, {
|
||||
title: dashboardListingErrorStrings.getErrorDeletingDashboardToast(),
|
||||
});
|
||||
}
|
||||
|
||||
setUnsavedDashboardIds(dashboardBackup.getDashboardIdsWithUnsavedChanges());
|
||||
setUnsavedDashboardIds(dashboardBackupService.getDashboardIdsWithUnsavedChanges());
|
||||
},
|
||||
[dashboardBackup, deleteDashboards, toasts]
|
||||
[dashboardBackupService, dashboardContentManagementService]
|
||||
);
|
||||
|
||||
const editItem = useCallback(
|
||||
|
@ -278,8 +279,9 @@ export const useDashboardListingTable = ({
|
|||
[getDashboardUrl]
|
||||
);
|
||||
|
||||
const tableListViewTableProps: DashboardListingViewTableProps = useMemo(
|
||||
() => ({
|
||||
const tableListViewTableProps: DashboardListingViewTableProps = useMemo(() => {
|
||||
const { showWriteControls } = getDashboardCapabilities();
|
||||
return {
|
||||
contentEditor: {
|
||||
isReadonly: !showWriteControls,
|
||||
onSave: updateItemMeta,
|
||||
|
@ -303,36 +305,38 @@ export const useDashboardListingTable = ({
|
|||
title,
|
||||
urlStateEnabled,
|
||||
createdByEnabled: true,
|
||||
recentlyAccessed: dashboardRecentlyAccessed,
|
||||
}),
|
||||
[
|
||||
contentEditorValidators,
|
||||
createItem,
|
||||
dashboardListingId,
|
||||
deleteItems,
|
||||
editItem,
|
||||
emptyPrompt,
|
||||
entityName,
|
||||
entityNamePlural,
|
||||
findItems,
|
||||
getDetailViewLink,
|
||||
headingId,
|
||||
initialFilter,
|
||||
initialPageSize,
|
||||
listingLimit,
|
||||
onFetchSuccess,
|
||||
showCreateDashboardButton,
|
||||
showWriteControls,
|
||||
title,
|
||||
updateItemMeta,
|
||||
urlStateEnabled,
|
||||
dashboardRecentlyAccessed,
|
||||
]
|
||||
);
|
||||
recentlyAccessed: getDashboardRecentlyAccessedService(),
|
||||
};
|
||||
}, [
|
||||
contentEditorValidators,
|
||||
createItem,
|
||||
dashboardListingId,
|
||||
deleteItems,
|
||||
editItem,
|
||||
emptyPrompt,
|
||||
entityName,
|
||||
entityNamePlural,
|
||||
findItems,
|
||||
getDetailViewLink,
|
||||
headingId,
|
||||
initialFilter,
|
||||
initialPageSize,
|
||||
listingLimit,
|
||||
onFetchSuccess,
|
||||
showCreateDashboardButton,
|
||||
title,
|
||||
updateItemMeta,
|
||||
urlStateEnabled,
|
||||
]);
|
||||
|
||||
const refreshUnsavedDashboards = useCallback(
|
||||
() => setUnsavedDashboardIds(dashboardBackup.getDashboardIdsWithUnsavedChanges()),
|
||||
[dashboardBackup]
|
||||
() => setUnsavedDashboardIds(getDashboardBackupService().getDashboardIdsWithUnsavedChanges()),
|
||||
[]
|
||||
);
|
||||
|
||||
const contentInsightsClient = useMemo(
|
||||
() => new ContentInsightsClient({ http: coreServices.http }, { domainId: 'dashboard' }),
|
||||
[]
|
||||
);
|
||||
|
||||
return {
|
||||
|
@ -341,5 +345,6 @@ export const useDashboardListingTable = ({
|
|||
refreshUnsavedDashboards,
|
||||
tableListViewTableProps,
|
||||
unsavedDashboardIds,
|
||||
contentInsightsClient,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
import type { PropsWithChildren } from 'react';
|
||||
import type { UserContentCommonSchema } from '@kbn/content-management-table-list-view-common';
|
||||
import type { ViewMode } from '@kbn/embeddable-plugin/public';
|
||||
import type { DashboardApplicationService } from '../services/application/types';
|
||||
|
||||
export type DashboardListingProps = PropsWithChildren<{
|
||||
disableCreateDashboardButton?: boolean;
|
||||
|
@ -22,12 +21,6 @@ export type DashboardListingProps = PropsWithChildren<{
|
|||
showCreateDashboardButton?: boolean;
|
||||
}>;
|
||||
|
||||
// because the type of `application.capabilities.advancedSettings` is so generic, the provider
|
||||
// requiring the `save` key to be part of it is causing type issues - so, creating a custom type
|
||||
export type TableListViewApplicationService = DashboardApplicationService & {
|
||||
capabilities: { advancedSettings: { save: boolean } };
|
||||
};
|
||||
|
||||
export interface DashboardSavedObjectUserContent extends UserContentCommonSchema {
|
||||
managed?: boolean;
|
||||
attributes: {
|
||||
|
|
|
@ -8,13 +8,13 @@
|
|||
*/
|
||||
|
||||
import React, { Suspense } from 'react';
|
||||
import { servicesReady } from '../plugin';
|
||||
import { DashboardTopNavProps } from './dashboard_top_nav_with_context';
|
||||
import { untilPluginStartServicesReady } from '../services/kibana_services';
|
||||
|
||||
const LazyDashboardTopNav = React.lazy(() =>
|
||||
(async () => {
|
||||
const modulePromise = import('./dashboard_top_nav_with_context');
|
||||
const [module] = await Promise.all([modulePromise, servicesReady]);
|
||||
const [module] = await Promise.all([modulePromise, untilPluginStartServicesReady()]);
|
||||
|
||||
return {
|
||||
default: module.DashboardTopNavWithContext,
|
||||
|
|
|
@ -12,10 +12,10 @@ import { render } from '@testing-library/react';
|
|||
import { buildMockDashboard } from '../mocks';
|
||||
import { InternalDashboardTopNav } from './internal_dashboard_top_nav';
|
||||
import { setMockedPresentationUtilServices } from '@kbn/presentation-util-plugin/public/mocks';
|
||||
import { pluginServices } from '../services/plugin_services';
|
||||
import { TopNavMenuProps } from '@kbn/navigation-plugin/public';
|
||||
import { DashboardContext } from '../dashboard_api/use_dashboard_api';
|
||||
import { DashboardApi } from '../dashboard_api/types';
|
||||
import { dataService, navigationService } from '../services/kibana_services';
|
||||
|
||||
describe('Internal dashboard top nav', () => {
|
||||
const mockTopNav = (badges: TopNavMenuProps['badges'] | undefined[]) => {
|
||||
|
@ -32,13 +32,10 @@ describe('Internal dashboard top nav', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
setMockedPresentationUtilServices();
|
||||
pluginServices.getServices().data.query.filterManager.getFilters = jest
|
||||
.fn()
|
||||
.mockReturnValue([]);
|
||||
dataService.query.filterManager.getFilters = jest.fn().mockReturnValue([]);
|
||||
// topNavMenu is mocked as a jest.fn() so we want to mock it with a component
|
||||
// @ts-ignore type issue with the mockTopNav for this test suite
|
||||
pluginServices.getServices().navigation.TopNavMenu = ({ badges }: TopNavMenuProps) =>
|
||||
mockTopNav(badges);
|
||||
navigationService.ui.TopNavMenu = ({ badges }: TopNavMenuProps) => mockTopNav(badges);
|
||||
});
|
||||
|
||||
it('should not render the managed badge by default', async () => {
|
||||
|
|
|
@ -7,48 +7,57 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import UseUnmount from 'react-use/lib/useUnmount';
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import UseUnmount from 'react-use/lib/useUnmount';
|
||||
|
||||
import {
|
||||
withSuspense,
|
||||
LazyLabsFlyout,
|
||||
getContextProvider as getPresentationUtilContextProvider,
|
||||
} from '@kbn/presentation-util-plugin/public';
|
||||
import { TopNavMenuBadgeProps, TopNavMenuProps } from '@kbn/navigation-plugin/public';
|
||||
import {
|
||||
EuiBadge,
|
||||
EuiBreadcrumb,
|
||||
EuiHorizontalRule,
|
||||
EuiIcon,
|
||||
EuiToolTipProps,
|
||||
EuiPopover,
|
||||
EuiBadge,
|
||||
EuiLink,
|
||||
EuiPopover,
|
||||
EuiToolTipProps,
|
||||
} from '@elastic/eui';
|
||||
import { MountPoint } from '@kbn/core/public';
|
||||
import { getManagedContentBadge } from '@kbn/managed-content-badge';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing';
|
||||
import { Query } from '@kbn/es-query';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { getManagedContentBadge } from '@kbn/managed-content-badge';
|
||||
import { TopNavMenuBadgeProps, TopNavMenuProps } from '@kbn/navigation-plugin/public';
|
||||
import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing';
|
||||
import {
|
||||
LazyLabsFlyout,
|
||||
getContextProvider as getPresentationUtilContextProvider,
|
||||
withSuspense,
|
||||
} from '@kbn/presentation-util-plugin/public';
|
||||
|
||||
import { UI_SETTINGS } from '../../common';
|
||||
import { useDashboardApi } from '../dashboard_api/use_dashboard_api';
|
||||
import {
|
||||
dashboardManagedBadge,
|
||||
getDashboardBreadcrumb,
|
||||
getDashboardTitle,
|
||||
leaveConfirmStrings,
|
||||
getDashboardBreadcrumb,
|
||||
unsavedChangesBadgeStrings,
|
||||
dashboardManagedBadge,
|
||||
} from '../dashboard_app/_dashboard_app_strings';
|
||||
import { UI_SETTINGS } from '../../common';
|
||||
import { pluginServices } from '../services/plugin_services';
|
||||
import { useDashboardMountContext } from '../dashboard_app/hooks/dashboard_mount_context';
|
||||
import { DashboardEditingToolbar } from '../dashboard_app/top_nav/dashboard_editing_toolbar';
|
||||
import { useDashboardMenuItems } from '../dashboard_app/top_nav/use_dashboard_menu_items';
|
||||
import { DashboardEmbedSettings } from '../dashboard_app/types';
|
||||
import { DashboardEditingToolbar } from '../dashboard_app/top_nav/dashboard_editing_toolbar';
|
||||
import { useDashboardMountContext } from '../dashboard_app/hooks/dashboard_mount_context';
|
||||
import { getFullEditPath, LEGACY_DASHBOARD_APP_ID } from '../dashboard_constants';
|
||||
import './_dashboard_top_nav.scss';
|
||||
import { DashboardRedirect } from '../dashboard_container/types';
|
||||
import { SaveDashboardReturn } from '../services/dashboard_content_management/types';
|
||||
import { useDashboardApi } from '../dashboard_api/use_dashboard_api';
|
||||
import { LEGACY_DASHBOARD_APP_ID, getFullEditPath } from '../dashboard_constants';
|
||||
import { openSettingsFlyout } from '../dashboard_container/embeddable/api';
|
||||
import { DashboardRedirect } from '../dashboard_container/types';
|
||||
import { SaveDashboardReturn } from '../services/dashboard_content_management_service/types';
|
||||
import { getDashboardRecentlyAccessedService } from '../services/dashboard_recently_accessed_service';
|
||||
import {
|
||||
coreServices,
|
||||
dataService,
|
||||
embeddableService,
|
||||
navigationService,
|
||||
serverlessService,
|
||||
} from '../services/kibana_services';
|
||||
import { getDashboardCapabilities } from '../utils/get_dashboard_capabilities';
|
||||
import './_dashboard_top_nav.scss';
|
||||
|
||||
export interface InternalDashboardTopNavProps {
|
||||
customLeadingBreadCrumbs?: EuiBreadcrumb[];
|
||||
|
@ -75,28 +84,7 @@ export function InternalDashboardTopNav({
|
|||
const [isLabsShown, setIsLabsShown] = useState(false);
|
||||
const dashboardTitleRef = useRef<HTMLHeadingElement>(null);
|
||||
|
||||
/**
|
||||
* Unpack dashboard services
|
||||
*/
|
||||
const {
|
||||
data: {
|
||||
query: { filterManager },
|
||||
},
|
||||
chrome: {
|
||||
setBreadcrumbs,
|
||||
setIsVisible: setChromeVisibility,
|
||||
getIsVisible$: getChromeIsVisible$,
|
||||
recentlyAccessed: chromeRecentlyAccessed,
|
||||
},
|
||||
serverless,
|
||||
settings: { uiSettings },
|
||||
navigation: { TopNavMenu },
|
||||
embeddable: { getStateTransfer },
|
||||
initializerContext: { allowByValueEmbeddables },
|
||||
dashboardCapabilities: { saveQuery: allowSaveQuery, showWriteControls },
|
||||
dashboardRecentlyAccessed,
|
||||
} = pluginServices.getServices();
|
||||
const isLabsEnabled = uiSettings.get(UI_SETTINGS.ENABLE_LABS_UI);
|
||||
const isLabsEnabled = useMemo(() => coreServices.uiSettings.get(UI_SETTINGS.ENABLE_LABS_UI), []);
|
||||
const { setHeaderActionMenu, onAppLeave } = useDashboardMountContext();
|
||||
|
||||
const dashboardApi = useDashboardApi();
|
||||
|
@ -144,36 +132,24 @@ export function InternalDashboardTopNav({
|
|||
* Manage chrome visibility when dashboard is embedded.
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (!embedSettings) setChromeVisibility(viewMode !== 'print');
|
||||
}, [embedSettings, setChromeVisibility, viewMode]);
|
||||
if (!embedSettings) coreServices.chrome.setIsVisible(viewMode !== 'print');
|
||||
}, [embedSettings, viewMode]);
|
||||
|
||||
/**
|
||||
* populate recently accessed, and set is chrome visible.
|
||||
*/
|
||||
useEffect(() => {
|
||||
const subscription = getChromeIsVisible$().subscribe((visible) => setIsChromeVisible(visible));
|
||||
const subscription = coreServices.chrome
|
||||
.getIsVisible$()
|
||||
.subscribe((visible) => setIsChromeVisible(visible));
|
||||
|
||||
if (lastSavedId && title) {
|
||||
chromeRecentlyAccessed.add(
|
||||
getFullEditPath(lastSavedId, viewMode === 'edit'),
|
||||
title,
|
||||
lastSavedId
|
||||
);
|
||||
dashboardRecentlyAccessed.add(
|
||||
getFullEditPath(lastSavedId, viewMode === 'edit'),
|
||||
title,
|
||||
lastSavedId
|
||||
);
|
||||
const fullEditPath = getFullEditPath(lastSavedId, viewMode === 'edit');
|
||||
coreServices.chrome.recentlyAccessed.add(fullEditPath, title, lastSavedId);
|
||||
getDashboardRecentlyAccessedService().add(fullEditPath, title, lastSavedId); // used to sort the listing table
|
||||
}
|
||||
return () => subscription.unsubscribe();
|
||||
}, [
|
||||
allowByValueEmbeddables,
|
||||
chromeRecentlyAccessed,
|
||||
getChromeIsVisible$,
|
||||
lastSavedId,
|
||||
viewMode,
|
||||
title,
|
||||
dashboardRecentlyAccessed,
|
||||
]);
|
||||
}, [lastSavedId, viewMode, title]);
|
||||
|
||||
/**
|
||||
* Set breadcrumbs to dashboard title when dashboard's title or view mode changes
|
||||
|
@ -198,17 +174,17 @@ export function InternalDashboardTopNav({
|
|||
},
|
||||
];
|
||||
|
||||
if (serverless?.setBreadcrumbs) {
|
||||
if (serverlessService) {
|
||||
// set serverless breadcrumbs if available,
|
||||
// set only the dashboardTitleBreadcrumbs because the main breadcrumbs automatically come as part of the navigation config
|
||||
serverless.setBreadcrumbs(dashboardTitleBreadcrumbs);
|
||||
serverlessService.setBreadcrumbs(dashboardTitleBreadcrumbs);
|
||||
} else {
|
||||
/**
|
||||
* non-serverless regular breadcrumbs
|
||||
* Dashboard embedded in other plugins (e.g. SecuritySolution)
|
||||
* will have custom leading breadcrumbs for back to their app.
|
||||
**/
|
||||
setBreadcrumbs(
|
||||
coreServices.chrome.setBreadcrumbs(
|
||||
customLeadingBreadCrumbs.concat([
|
||||
{
|
||||
text: getDashboardBreadcrumb(),
|
||||
|
@ -221,22 +197,18 @@ export function InternalDashboardTopNav({
|
|||
])
|
||||
);
|
||||
}
|
||||
}, [
|
||||
setBreadcrumbs,
|
||||
redirectTo,
|
||||
dashboardTitle,
|
||||
dashboardApi,
|
||||
viewMode,
|
||||
serverless,
|
||||
customLeadingBreadCrumbs,
|
||||
]);
|
||||
}, [redirectTo, dashboardTitle, dashboardApi, viewMode, customLeadingBreadCrumbs]);
|
||||
|
||||
/**
|
||||
* Build app leave handler whenever hasUnsavedChanges changes
|
||||
*/
|
||||
useEffect(() => {
|
||||
onAppLeave((actions) => {
|
||||
if (viewMode === 'edit' && hasUnsavedChanges && !getStateTransfer().isTransferInProgress) {
|
||||
if (
|
||||
viewMode === 'edit' &&
|
||||
hasUnsavedChanges &&
|
||||
!embeddableService.getStateTransfer().isTransferInProgress
|
||||
) {
|
||||
return actions.confirm(
|
||||
leaveConfirmStrings.getLeaveSubtitle(),
|
||||
leaveConfirmStrings.getLeaveTitle()
|
||||
|
@ -248,13 +220,13 @@ export function InternalDashboardTopNav({
|
|||
// reset on app leave handler so leaving from the listing page doesn't trigger a confirmation
|
||||
onAppLeave((actions) => actions.default());
|
||||
};
|
||||
}, [onAppLeave, getStateTransfer, hasUnsavedChanges, viewMode]);
|
||||
}, [onAppLeave, hasUnsavedChanges, viewMode]);
|
||||
|
||||
const visibilityProps = useMemo(() => {
|
||||
const shouldShowNavBarComponent = (forceShow: boolean): boolean =>
|
||||
(forceShow || isChromeVisible) && !fullScreenMode;
|
||||
const shouldShowFilterBar = (forceHide: boolean): boolean =>
|
||||
!forceHide && (filterManager.getFilters().length > 0 || !fullScreenMode);
|
||||
!forceHide && (dataService.query.filterManager.getFilters().length > 0 || !fullScreenMode);
|
||||
|
||||
const showTopNavMenu = shouldShowNavBarComponent(Boolean(embedSettings?.forceShowTopNavMenu));
|
||||
const showQueryInput = Boolean(forceHideUnifiedSearch)
|
||||
|
@ -275,14 +247,7 @@ export function InternalDashboardTopNav({
|
|||
showQueryInput,
|
||||
showDatePicker,
|
||||
};
|
||||
}, [
|
||||
embedSettings,
|
||||
filterManager,
|
||||
forceHideUnifiedSearch,
|
||||
fullScreenMode,
|
||||
isChromeVisible,
|
||||
viewMode,
|
||||
]);
|
||||
}, [embedSettings, forceHideUnifiedSearch, fullScreenMode, isChromeVisible, viewMode]);
|
||||
|
||||
const maybeRedirect = useCallback(
|
||||
(result?: SaveDashboardReturn) => {
|
||||
|
@ -338,6 +303,8 @@ export function InternalDashboardTopNav({
|
|||
} as EuiToolTipProps,
|
||||
});
|
||||
}
|
||||
|
||||
const { showWriteControls } = getDashboardCapabilities();
|
||||
if (showWriteControls && managed) {
|
||||
const badgeProps = {
|
||||
...getManagedContentBadge(dashboardManagedBadge.getBadgeAriaLabel()),
|
||||
|
@ -390,7 +357,6 @@ export function InternalDashboardTopNav({
|
|||
hasUnsavedChanges,
|
||||
viewMode,
|
||||
hasRunMigrations,
|
||||
showWriteControls,
|
||||
managed,
|
||||
isPopoverOpen,
|
||||
dashboardApi,
|
||||
|
@ -405,7 +371,7 @@ export function InternalDashboardTopNav({
|
|||
ref={dashboardTitleRef}
|
||||
tabIndex={-1}
|
||||
>{`${getDashboardBreadcrumb()} - ${dashboardTitle}`}</h1>
|
||||
<TopNavMenu
|
||||
<navigationService.ui.TopNavMenu
|
||||
{...visibilityProps}
|
||||
query={query as Query | undefined}
|
||||
badges={badges}
|
||||
|
@ -413,7 +379,9 @@ export function InternalDashboardTopNav({
|
|||
useDefaultBehaviors={true}
|
||||
savedQueryId={savedQueryId}
|
||||
indexPatterns={allDataViews ?? []}
|
||||
saveQueryMenuVisibility={allowSaveQuery ? 'allowed_by_app_privilege' : 'globally_managed'}
|
||||
saveQueryMenuVisibility={
|
||||
getDashboardCapabilities().saveQuery ? 'allowed_by_app_privilege' : 'globally_managed'
|
||||
}
|
||||
appName={LEGACY_DASHBOARD_APP_ID}
|
||||
visible={viewMode !== 'print'}
|
||||
setMenuMountPoint={
|
||||
|
|
|
@ -32,6 +32,8 @@ export { DashboardTopNav } from './dashboard_top_nav';
|
|||
export { type DashboardAppLocator, cleanEmptyKeys } from './dashboard_app/locator/locator';
|
||||
export { getDashboardLocatorParamsFromEmbeddable } from './dashboard_app/locator/get_dashboard_locator_params';
|
||||
|
||||
export { type SearchDashboardsResponse } from './services/dashboard_content_management_service/lib/find_dashboards';
|
||||
|
||||
export function plugin(initializerContext: PluginInitializerContext) {
|
||||
return new DashboardPlugin(initializerContext);
|
||||
}
|
||||
|
|
|
@ -15,12 +15,6 @@ import { BehaviorSubject } from 'rxjs';
|
|||
import { DashboardContainerInput, DashboardPanelState } from '../common';
|
||||
import { DashboardContainer } from './dashboard_container/embeddable/dashboard_container';
|
||||
import { DashboardStart } from './plugin';
|
||||
import { pluginServices } from './services/plugin_services';
|
||||
export { setStubDashboardServices } from './services/mocks';
|
||||
|
||||
export const getMockedDashboardServices = () => {
|
||||
return pluginServices.getServices();
|
||||
};
|
||||
|
||||
export type Start = jest.Mocked<DashboardStart>;
|
||||
|
||||
|
|
|
@ -7,75 +7,78 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { filter, map } from 'rxjs';
|
||||
import { BehaviorSubject, filter, map } from 'rxjs';
|
||||
|
||||
import {
|
||||
App,
|
||||
Plugin,
|
||||
AppUpdater,
|
||||
ScopedHistory,
|
||||
type CoreSetup,
|
||||
type CoreStart,
|
||||
AppMountParameters,
|
||||
DEFAULT_APP_CATEGORIES,
|
||||
PluginInitializerContext,
|
||||
} from '@kbn/core/public';
|
||||
import type {
|
||||
ScreenshotModePluginSetup,
|
||||
ScreenshotModePluginStart,
|
||||
} from '@kbn/screenshot-mode-plugin/public';
|
||||
import type {
|
||||
UsageCollectionSetup,
|
||||
UsageCollectionStart,
|
||||
} from '@kbn/usage-collection-plugin/public';
|
||||
import { APP_WRAPPER_CLASS } from '@kbn/core/public';
|
||||
import { type UiActionsSetup, type UiActionsStart } from '@kbn/ui-actions-plugin/public';
|
||||
import type { SpacesPluginStart } from '@kbn/spaces-plugin/public';
|
||||
import type { HomePublicPluginSetup } from '@kbn/home-plugin/public';
|
||||
import { replaceUrlHashQuery } from '@kbn/kibana-utils-plugin/common';
|
||||
import { createKbnUrlTracker } from '@kbn/kibana-utils-plugin/public';
|
||||
import type { VisualizationsStart } from '@kbn/visualizations-plugin/public';
|
||||
import type { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public';
|
||||
import type { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public';
|
||||
import type { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public';
|
||||
import type { Start as InspectorStartContract } from '@kbn/inspector-plugin/public';
|
||||
import type { EmbeddableSetup, EmbeddableStart } from '@kbn/embeddable-plugin/public';
|
||||
import type { PresentationUtilPluginStart } from '@kbn/presentation-util-plugin/public';
|
||||
import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public';
|
||||
import type {
|
||||
ContentManagementPublicSetup,
|
||||
ContentManagementPublicStart,
|
||||
} from '@kbn/content-management-plugin/public';
|
||||
import { CustomBrandingStart } from '@kbn/core-custom-branding-browser';
|
||||
import {
|
||||
APP_WRAPPER_CLASS,
|
||||
App,
|
||||
AppMountParameters,
|
||||
AppUpdater,
|
||||
DEFAULT_APP_CATEGORIES,
|
||||
Plugin,
|
||||
PluginInitializerContext,
|
||||
ScopedHistory,
|
||||
type CoreSetup,
|
||||
type CoreStart,
|
||||
} from '@kbn/core/public';
|
||||
import type { DataPublicPluginSetup, DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||
import type { UrlForwardingSetup, UrlForwardingStart } from '@kbn/url-forwarding-plugin/public';
|
||||
import type { SavedObjectTaggingOssPluginStart } from '@kbn/saved-objects-tagging-oss-plugin/public';
|
||||
import type { ServerlessPluginStart } from '@kbn/serverless/public';
|
||||
import type { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public';
|
||||
import type { EmbeddableSetup, EmbeddableStart } from '@kbn/embeddable-plugin/public';
|
||||
import { FieldFormatsStart } from '@kbn/field-formats-plugin/public/plugin';
|
||||
import type { HomePublicPluginSetup } from '@kbn/home-plugin/public';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { Start as InspectorStartContract } from '@kbn/inspector-plugin/public';
|
||||
import { replaceUrlHashQuery } from '@kbn/kibana-utils-plugin/common';
|
||||
import { createKbnUrlTracker } from '@kbn/kibana-utils-plugin/public';
|
||||
import type { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public';
|
||||
import type { NoDataPagePluginStart } from '@kbn/no-data-page-plugin/public';
|
||||
import type {
|
||||
ObservabilityAIAssistantPublicSetup,
|
||||
ObservabilityAIAssistantPublicStart,
|
||||
} from '@kbn/observability-ai-assistant-plugin/public';
|
||||
import type { PresentationUtilPluginStart } from '@kbn/presentation-util-plugin/public';
|
||||
import type { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public';
|
||||
import type { SavedObjectTaggingOssPluginStart } from '@kbn/saved-objects-tagging-oss-plugin/public';
|
||||
import type {
|
||||
ScreenshotModePluginSetup,
|
||||
ScreenshotModePluginStart,
|
||||
} from '@kbn/screenshot-mode-plugin/public';
|
||||
import type { ServerlessPluginStart } from '@kbn/serverless/public';
|
||||
import type { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public';
|
||||
import type { SpacesPluginStart } from '@kbn/spaces-plugin/public';
|
||||
import { type UiActionsSetup, type UiActionsStart } from '@kbn/ui-actions-plugin/public';
|
||||
import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public';
|
||||
import type { UrlForwardingSetup, UrlForwardingStart } from '@kbn/url-forwarding-plugin/public';
|
||||
import type {
|
||||
UsageCollectionSetup,
|
||||
UsageCollectionStart,
|
||||
} from '@kbn/usage-collection-plugin/public';
|
||||
import type { VisualizationsStart } from '@kbn/visualizations-plugin/public';
|
||||
|
||||
import { CustomBrandingStart } from '@kbn/core-custom-branding-browser';
|
||||
import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public';
|
||||
import { DashboardContainerFactoryDefinition } from './dashboard_container/embeddable/dashboard_container_factory';
|
||||
import { registerDashboardPanelPlacementSetting } from './dashboard_container/panel_placement';
|
||||
import { CONTENT_ID, LATEST_VERSION } from '../common/content_management';
|
||||
import {
|
||||
type DashboardAppLocator,
|
||||
DashboardAppLocatorDefinition,
|
||||
type DashboardAppLocator,
|
||||
} from './dashboard_app/locator/locator';
|
||||
import { DashboardMountContextProps } from './dashboard_app/types';
|
||||
import {
|
||||
DASHBOARD_APP_ID,
|
||||
LANDING_PAGE_PATH,
|
||||
LEGACY_DASHBOARD_APP_ID,
|
||||
SEARCH_SESSION_ID,
|
||||
} from './dashboard_constants';
|
||||
import { DashboardMountContextProps } from './dashboard_app/types';
|
||||
import type { FindDashboardsService } from './services/dashboard_content_management/types';
|
||||
import { CONTENT_ID, LATEST_VERSION } from '../common/content_management';
|
||||
import { GetPanelPlacementSettings } from './dashboard_container/panel_placement';
|
||||
import { DashboardContainerFactoryDefinition } from './dashboard_container/embeddable/dashboard_container_factory';
|
||||
import {
|
||||
GetPanelPlacementSettings,
|
||||
registerDashboardPanelPlacementSetting,
|
||||
} from './dashboard_container/panel_placement';
|
||||
import type { FindDashboardsService } from './services/dashboard_content_management_service/types';
|
||||
import { setKibanaServices, untilPluginStartServicesReady } from './services/kibana_services';
|
||||
|
||||
export interface DashboardFeatureFlagConfig {
|
||||
allowByValueEmbeddables: boolean;
|
||||
|
@ -99,6 +102,7 @@ export interface DashboardStartDependencies {
|
|||
data: DataPublicPluginStart;
|
||||
dataViewEditor: DataViewEditorStart;
|
||||
embeddable: EmbeddableStart;
|
||||
fieldFormats: FieldFormatsStart;
|
||||
inspector: InspectorStartContract;
|
||||
navigation: NavigationPublicPluginStart;
|
||||
presentationUtil: PresentationUtilPluginStart;
|
||||
|
@ -148,27 +152,9 @@ export class DashboardPlugin
|
|||
private dashboardFeatureFlagConfig?: DashboardFeatureFlagConfig;
|
||||
private locator?: DashboardAppLocator;
|
||||
|
||||
private async startDashboardKibanaServices(
|
||||
coreStart: CoreStart,
|
||||
startPlugins: DashboardStartDependencies,
|
||||
initContext: PluginInitializerContext
|
||||
) {
|
||||
const { registry, pluginServices } = await import('./services/plugin_services');
|
||||
pluginServices.setRegistry(registry.start({ coreStart, startPlugins, initContext }));
|
||||
resolveServicesReady();
|
||||
}
|
||||
|
||||
public setup(
|
||||
core: CoreSetup<DashboardStartDependencies, DashboardStart>,
|
||||
{
|
||||
share,
|
||||
embeddable,
|
||||
home,
|
||||
urlForwarding,
|
||||
data,
|
||||
contentManagement,
|
||||
uiActions,
|
||||
}: DashboardSetupDependencies
|
||||
{ share, embeddable, home, urlForwarding, data, contentManagement }: DashboardSetupDependencies
|
||||
): DashboardSetup {
|
||||
this.dashboardFeatureFlagConfig =
|
||||
this.initializerContext.config.get<DashboardFeatureFlagConfig>();
|
||||
|
@ -183,11 +169,14 @@ export class DashboardPlugin
|
|||
new DashboardAppLocatorDefinition({
|
||||
useHashedUrl: core.uiSettings.get('state:storeInSessionStorage'),
|
||||
getDashboardFilterFields: async (dashboardId: string) => {
|
||||
const { pluginServices } = await import('./services/plugin_services');
|
||||
const {
|
||||
dashboardContentManagement: { loadDashboardState },
|
||||
} = pluginServices.getServices();
|
||||
return (await loadDashboardState({ id: dashboardId })).dashboardInput?.filters ?? [];
|
||||
const [{ getDashboardContentManagementService }] = await Promise.all([
|
||||
import('./services/dashboard_content_management_service'),
|
||||
untilPluginStartServicesReady(),
|
||||
]);
|
||||
return (
|
||||
(await getDashboardContentManagementService().loadDashboardState({ id: dashboardId }))
|
||||
.dashboardInput?.filters ?? []
|
||||
);
|
||||
},
|
||||
})
|
||||
);
|
||||
|
@ -262,6 +251,7 @@ export class DashboardPlugin
|
|||
mount: async (params: AppMountParameters) => {
|
||||
this.currentHistory = params.history;
|
||||
params.element.classList.add(APP_WRAPPER_CLASS);
|
||||
await untilPluginStartServicesReady();
|
||||
const { mountApp } = await import('./dashboard_app/dashboard_router');
|
||||
appMounted();
|
||||
|
||||
|
@ -340,25 +330,26 @@ export class DashboardPlugin
|
|||
}
|
||||
|
||||
public start(core: CoreStart, plugins: DashboardStartDependencies): DashboardStart {
|
||||
this.startDashboardKibanaServices(core, plugins, this.initializerContext).then(async () => {
|
||||
const { buildAllDashboardActions } = await import('./dashboard_actions');
|
||||
buildAllDashboardActions({
|
||||
core,
|
||||
plugins,
|
||||
allowByValueEmbeddables: this.dashboardFeatureFlagConfig?.allowByValueEmbeddables,
|
||||
});
|
||||
});
|
||||
setKibanaServices(core, plugins);
|
||||
|
||||
Promise.all([import('./dashboard_actions'), untilPluginStartServicesReady()]).then(
|
||||
([{ buildAllDashboardActions }]) => {
|
||||
buildAllDashboardActions({
|
||||
plugins,
|
||||
allowByValueEmbeddables: this.dashboardFeatureFlagConfig?.allowByValueEmbeddables,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
locator: this.locator,
|
||||
dashboardFeatureFlagConfig: this.dashboardFeatureFlagConfig!,
|
||||
registerDashboardPanelPlacementSetting,
|
||||
findDashboardsService: async () => {
|
||||
const { pluginServices } = await import('./services/plugin_services');
|
||||
const {
|
||||
dashboardContentManagement: { findDashboards },
|
||||
} = pluginServices.getServices();
|
||||
return findDashboards;
|
||||
const { getDashboardContentManagementService } = await import(
|
||||
'./services/dashboard_content_management_service'
|
||||
);
|
||||
return getDashboardContentManagementService().findDashboards;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,22 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { analyticsServiceMock } from '@kbn/core-analytics-browser-mocks';
|
||||
import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public';
|
||||
import { DashboardAnalyticsService } from './types';
|
||||
|
||||
type AnalyticsServiceFactory = PluginServiceFactory<DashboardAnalyticsService>;
|
||||
|
||||
export const analyticsServiceFactory: AnalyticsServiceFactory = () => {
|
||||
const pluginMock = analyticsServiceMock.createAnalyticsServiceStart();
|
||||
|
||||
return {
|
||||
reportEvent: pluginMock.reportEvent,
|
||||
};
|
||||
};
|
|
@ -1,27 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import type { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public';
|
||||
import type { DashboardStartDependencies } from '../../plugin';
|
||||
import type { DashboardAnalyticsService } from './types';
|
||||
|
||||
export type AnalyticsServiceFactory = KibanaPluginServiceFactory<
|
||||
DashboardAnalyticsService,
|
||||
DashboardStartDependencies
|
||||
>;
|
||||
|
||||
export const analyticsServiceFactory: AnalyticsServiceFactory = ({ coreStart }) => {
|
||||
const {
|
||||
analytics: { reportEvent },
|
||||
} = coreStart;
|
||||
|
||||
return {
|
||||
reportEvent,
|
||||
};
|
||||
};
|
|
@ -1,14 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import type { CoreStart } from '@kbn/core/public';
|
||||
|
||||
export interface DashboardAnalyticsService {
|
||||
reportEvent: CoreStart['analytics']['reportEvent'];
|
||||
}
|
|
@ -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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { applicationServiceMock } from '@kbn/core-application-browser-mocks';
|
||||
import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public';
|
||||
import { DashboardApplicationService } from './types';
|
||||
|
||||
type ApplicationServiceFactory = PluginServiceFactory<DashboardApplicationService>;
|
||||
|
||||
export const applicationServiceFactory: ApplicationServiceFactory = () => {
|
||||
const pluginMock = applicationServiceMock.createStartContract();
|
||||
|
||||
return {
|
||||
currentAppId$: pluginMock.currentAppId$,
|
||||
navigateToApp: pluginMock.navigateToApp,
|
||||
navigateToUrl: pluginMock.navigateToUrl,
|
||||
getUrlForApp: pluginMock.getUrlForApp,
|
||||
capabilities: {
|
||||
advancedSettings: pluginMock.capabilities.advancedSettings,
|
||||
maps: pluginMock.capabilities.maps,
|
||||
navLinks: pluginMock.capabilities.navLinks,
|
||||
visualize: pluginMock.capabilities.visualize,
|
||||
},
|
||||
};
|
||||
};
|
|
@ -1,42 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import type { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public';
|
||||
import type { DashboardStartDependencies } from '../../plugin';
|
||||
import type { DashboardApplicationService } from './types';
|
||||
|
||||
export type ApplicationServiceFactory = KibanaPluginServiceFactory<
|
||||
DashboardApplicationService,
|
||||
DashboardStartDependencies
|
||||
>;
|
||||
|
||||
export const applicationServiceFactory: ApplicationServiceFactory = ({ coreStart }) => {
|
||||
const {
|
||||
application: {
|
||||
currentAppId$,
|
||||
navigateToApp,
|
||||
navigateToUrl,
|
||||
getUrlForApp,
|
||||
capabilities: { advancedSettings, maps, navLinks, visualize },
|
||||
},
|
||||
} = coreStart;
|
||||
|
||||
return {
|
||||
currentAppId$,
|
||||
navigateToApp,
|
||||
navigateToUrl,
|
||||
getUrlForApp,
|
||||
capabilities: {
|
||||
advancedSettings,
|
||||
maps,
|
||||
navLinks,
|
||||
visualize,
|
||||
},
|
||||
};
|
||||
};
|
|
@ -1,23 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import type { CoreStart } from '@kbn/core/public';
|
||||
|
||||
export interface DashboardApplicationService {
|
||||
currentAppId$: CoreStart['application']['currentAppId$'];
|
||||
navigateToApp: CoreStart['application']['navigateToApp'];
|
||||
navigateToUrl: CoreStart['application']['navigateToUrl'];
|
||||
getUrlForApp: CoreStart['application']['getUrlForApp'];
|
||||
capabilities: {
|
||||
advancedSettings: CoreStart['application']['capabilities']['advancedSettings'];
|
||||
maps: CoreStart['application']['capabilities']['maps']; // only used in `add_to_library_action`
|
||||
navLinks: CoreStart['application']['capabilities']['navLinks'];
|
||||
visualize: CoreStart['application']['capabilities']['visualize']; // only used in `add_to_library_action`
|
||||
};
|
||||
}
|
|
@ -1,29 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { coreMock, chromeServiceMock } from '@kbn/core/public/mocks';
|
||||
import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public';
|
||||
import { DashboardChromeService } from './types';
|
||||
|
||||
type ChromeServiceFactory = PluginServiceFactory<DashboardChromeService>;
|
||||
|
||||
export const chromeServiceFactory: ChromeServiceFactory = () => {
|
||||
const pluginMock = chromeServiceMock.createStartContract();
|
||||
|
||||
return {
|
||||
docTitle: pluginMock.docTitle,
|
||||
setBadge: pluginMock.setBadge,
|
||||
getIsVisible$: pluginMock.getIsVisible$,
|
||||
recentlyAccessed: pluginMock.recentlyAccessed,
|
||||
setBreadcrumbs: pluginMock.setBreadcrumbs,
|
||||
setHelpExtension: pluginMock.setHelpExtension,
|
||||
setIsVisible: pluginMock.setIsVisible,
|
||||
theme: coreMock.createStart().theme,
|
||||
};
|
||||
};
|
|
@ -1,43 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import type { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public';
|
||||
import type { DashboardStartDependencies } from '../../plugin';
|
||||
import type { DashboardChromeService } from './types';
|
||||
|
||||
export type ChromeServiceFactory = KibanaPluginServiceFactory<
|
||||
DashboardChromeService,
|
||||
DashboardStartDependencies
|
||||
>;
|
||||
|
||||
export const chromeServiceFactory: ChromeServiceFactory = ({ coreStart }) => {
|
||||
const {
|
||||
chrome: {
|
||||
docTitle,
|
||||
setBadge,
|
||||
getIsVisible$,
|
||||
recentlyAccessed,
|
||||
setBreadcrumbs,
|
||||
setHelpExtension,
|
||||
setIsVisible,
|
||||
},
|
||||
theme,
|
||||
} = coreStart;
|
||||
|
||||
return {
|
||||
docTitle,
|
||||
setBadge,
|
||||
getIsVisible$,
|
||||
recentlyAccessed,
|
||||
setBreadcrumbs,
|
||||
setHelpExtension,
|
||||
setIsVisible,
|
||||
theme,
|
||||
};
|
||||
};
|
|
@ -1,21 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import type { CoreStart } from '@kbn/core/public';
|
||||
|
||||
export interface DashboardChromeService {
|
||||
docTitle: CoreStart['chrome']['docTitle'];
|
||||
setBadge: CoreStart['chrome']['setBadge'];
|
||||
getIsVisible$: CoreStart['chrome']['getIsVisible$'];
|
||||
recentlyAccessed: CoreStart['chrome']['recentlyAccessed'];
|
||||
setBreadcrumbs: CoreStart['chrome']['setBreadcrumbs'];
|
||||
setHelpExtension: CoreStart['chrome']['setHelpExtension'];
|
||||
setIsVisible: CoreStart['chrome']['setIsVisible'];
|
||||
theme: CoreStart['theme'];
|
||||
}
|
|
@ -1,18 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public';
|
||||
import { ContentManagementPublicStart } from '@kbn/content-management-plugin/public';
|
||||
import { contentManagementMock } from '@kbn/content-management-plugin/public/mocks';
|
||||
|
||||
export type ContentManagementServiceFactory = PluginServiceFactory<ContentManagementPublicStart>;
|
||||
|
||||
export const contentManagementServiceFactory: ContentManagementServiceFactory = () => {
|
||||
return contentManagementMock.createStartContract();
|
||||
};
|
|
@ -1,25 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public';
|
||||
import { ContentManagementPublicStart } from '@kbn/content-management-plugin/public';
|
||||
import { DashboardStartDependencies } from '../../plugin';
|
||||
|
||||
export type ContentManagementServiceFactory = KibanaPluginServiceFactory<
|
||||
ContentManagementPublicStart,
|
||||
DashboardStartDependencies
|
||||
>;
|
||||
|
||||
export const contentManagementServiceFactory: ContentManagementServiceFactory = ({
|
||||
startPlugins,
|
||||
}) => {
|
||||
const { contentManagement } = startPlugins;
|
||||
|
||||
return contentManagement;
|
||||
};
|
|
@ -1,22 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { coreMock } from '@kbn/core/public/mocks';
|
||||
import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public';
|
||||
import { DashboardCoreContextService } from './types';
|
||||
|
||||
type CoreContextServiceFactory = PluginServiceFactory<DashboardCoreContextService>;
|
||||
|
||||
export const coreContextServiceFactory: CoreContextServiceFactory = () => {
|
||||
const pluginMock = coreMock.createStart();
|
||||
return {
|
||||
executionContext: pluginMock.executionContext,
|
||||
i18nContext: pluginMock.i18n.Context,
|
||||
};
|
||||
};
|
|
@ -1,29 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import type { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public';
|
||||
import type { DashboardStartDependencies } from '../../plugin';
|
||||
import type { DashboardCoreContextService } from './types';
|
||||
|
||||
export type CoreContextServiceFactory = KibanaPluginServiceFactory<
|
||||
DashboardCoreContextService,
|
||||
DashboardStartDependencies
|
||||
>;
|
||||
|
||||
export const coreContextServiceFactory: CoreContextServiceFactory = ({ coreStart }) => {
|
||||
const {
|
||||
executionContext,
|
||||
i18n: { Context },
|
||||
} = coreStart;
|
||||
|
||||
return {
|
||||
executionContext,
|
||||
i18nContext: Context,
|
||||
};
|
||||
};
|
|
@ -1,15 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import type { CoreStart } from '@kbn/core/public';
|
||||
|
||||
export interface DashboardCoreContextService {
|
||||
executionContext: CoreStart['executionContext'];
|
||||
i18nContext: CoreStart['i18n']['Context'];
|
||||
}
|
|
@ -1,22 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public';
|
||||
import { coreMock } from '@kbn/core/public/mocks';
|
||||
import { DashboardCustomBrandingService } from './types';
|
||||
|
||||
type CustomBrandingServiceFactory = PluginServiceFactory<DashboardCustomBrandingService>;
|
||||
|
||||
export const customBrandingServiceFactory: CustomBrandingServiceFactory = () => {
|
||||
const pluginMock = coreMock.createStart();
|
||||
return {
|
||||
hasCustomBranding$: pluginMock.customBranding.hasCustomBranding$,
|
||||
customBranding$: pluginMock.customBranding.customBranding$,
|
||||
};
|
||||
};
|
|
@ -1,25 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import type { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public';
|
||||
import { DashboardStartDependencies } from '../../plugin';
|
||||
import { DashboardCustomBrandingService } from './types';
|
||||
|
||||
export type CustomBrandingServiceFactory = KibanaPluginServiceFactory<
|
||||
DashboardCustomBrandingService,
|
||||
DashboardStartDependencies
|
||||
>;
|
||||
|
||||
export const customBrandingServiceFactory: CustomBrandingServiceFactory = ({ coreStart }) => {
|
||||
const { customBranding } = coreStart;
|
||||
return {
|
||||
hasCustomBranding$: customBranding.hasCustomBranding$,
|
||||
customBranding$: customBranding.customBranding$,
|
||||
};
|
||||
};
|
|
@ -1,15 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import type { CustomBrandingStart } from '@kbn/core-custom-branding-browser';
|
||||
|
||||
export interface DashboardCustomBrandingService {
|
||||
hasCustomBranding$: CustomBrandingStart['hasCustomBranding$'];
|
||||
customBranding$: CustomBrandingStart['customBranding$'];
|
||||
}
|
|
@ -1,27 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public';
|
||||
import { DashboardBackupServiceType } from './types';
|
||||
|
||||
type DashboardBackupServiceFactory = PluginServiceFactory<DashboardBackupServiceType>;
|
||||
|
||||
export const dashboardBackupServiceFactory: DashboardBackupServiceFactory = () => {
|
||||
return {
|
||||
clearState: jest.fn(),
|
||||
getState: jest.fn().mockReturnValue(undefined),
|
||||
setState: jest.fn(),
|
||||
getViewMode: jest.fn(),
|
||||
storeViewMode: jest.fn(),
|
||||
getDashboardIdsWithUnsavedChanges: jest
|
||||
.fn()
|
||||
.mockReturnValue(['dashboardUnsavedOne', 'dashboardUnsavedTwo']),
|
||||
dashboardHasUnsavedEdits: jest.fn(),
|
||||
};
|
||||
};
|
|
@ -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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { ViewMode } from '@kbn/embeddable-plugin/public';
|
||||
import { UnsavedPanelState } from '../../dashboard_container/types';
|
||||
import { SavedDashboardInput } from '../dashboard_content_management/types';
|
||||
|
||||
export interface DashboardBackupServiceType {
|
||||
clearState: (id?: string) => void;
|
||||
getState: (id: string | undefined) =>
|
||||
| {
|
||||
dashboardState?: Partial<SavedDashboardInput>;
|
||||
panels?: UnsavedPanelState;
|
||||
}
|
||||
| undefined;
|
||||
setState: (
|
||||
id: string | undefined,
|
||||
dashboardState: Partial<SavedDashboardInput>,
|
||||
panels: UnsavedPanelState
|
||||
) => void;
|
||||
getViewMode: () => ViewMode;
|
||||
storeViewMode: (viewMode: ViewMode) => void;
|
||||
getDashboardIdsWithUnsavedChanges: () => string[];
|
||||
dashboardHasUnsavedEdits: (id?: string) => boolean;
|
||||
}
|
|
@ -7,21 +7,18 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { isEqual } from 'lodash';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
import { set } from '@kbn/safer-lodash-set';
|
||||
import { ViewMode } from '@kbn/embeddable-plugin/public';
|
||||
import { Storage } from '@kbn/kibana-utils-plugin/public';
|
||||
import type { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public';
|
||||
import { set } from '@kbn/safer-lodash-set';
|
||||
|
||||
import { DashboardSpacesService } from '../spaces/types';
|
||||
import type { DashboardStartDependencies } from '../../plugin';
|
||||
import type { DashboardBackupServiceType } from './types';
|
||||
import type { DashboardContainerInput } from '../../../common';
|
||||
import { DashboardNotificationsService } from '../notifications/types';
|
||||
import { backupServiceStrings } from '../../dashboard_container/_dashboard_container_strings';
|
||||
import { UnsavedPanelState } from '../../dashboard_container/types';
|
||||
import type { DashboardContainerInput } from '../../common';
|
||||
import { backupServiceStrings } from '../dashboard_container/_dashboard_container_strings';
|
||||
import { UnsavedPanelState } from '../dashboard_container/types';
|
||||
import { coreServices, spacesService } from './kibana_services';
|
||||
import { SavedDashboardInput } from './dashboard_content_management_service/types';
|
||||
|
||||
export const DASHBOARD_PANELS_UNSAVED_ID = 'unsavedDashboard';
|
||||
export const PANELS_CONTROL_GROUP_KEY = 'controlGroup';
|
||||
|
@ -31,34 +28,39 @@ const DASHBOARD_VIEWMODE_LOCAL_KEY = 'dashboardViewMode';
|
|||
// this key is named `panels` for BWC reasons, but actually contains the entire dashboard state
|
||||
const DASHBOARD_STATE_SESSION_KEY = 'dashboardStateManagerPanels';
|
||||
|
||||
interface DashboardBackupRequiredServices {
|
||||
notifications: DashboardNotificationsService;
|
||||
spaces: DashboardSpacesService;
|
||||
interface DashboardBackupServiceType {
|
||||
clearState: (id?: string) => void;
|
||||
getState: (id: string | undefined) =>
|
||||
| {
|
||||
dashboardState?: Partial<SavedDashboardInput>;
|
||||
panels?: UnsavedPanelState;
|
||||
}
|
||||
| undefined;
|
||||
setState: (
|
||||
id: string | undefined,
|
||||
dashboardState: Partial<SavedDashboardInput>,
|
||||
panels: UnsavedPanelState
|
||||
) => void;
|
||||
getViewMode: () => ViewMode;
|
||||
storeViewMode: (viewMode: ViewMode) => void;
|
||||
getDashboardIdsWithUnsavedChanges: () => string[];
|
||||
dashboardHasUnsavedEdits: (id?: string) => boolean;
|
||||
}
|
||||
|
||||
export type DashboardBackupServiceFactory = KibanaPluginServiceFactory<
|
||||
DashboardBackupServiceType,
|
||||
DashboardStartDependencies,
|
||||
DashboardBackupRequiredServices
|
||||
>;
|
||||
|
||||
class DashboardBackupService implements DashboardBackupServiceType {
|
||||
private activeSpaceId: string;
|
||||
private sessionStorage: Storage;
|
||||
private localStorage: Storage;
|
||||
private notifications: DashboardNotificationsService;
|
||||
private spaces: DashboardSpacesService;
|
||||
|
||||
private oldDashboardsWithUnsavedChanges: string[] = [];
|
||||
|
||||
constructor(requiredServices: DashboardBackupRequiredServices) {
|
||||
({ notifications: this.notifications, spaces: this.spaces } = requiredServices);
|
||||
constructor() {
|
||||
this.sessionStorage = new Storage(sessionStorage);
|
||||
this.localStorage = new Storage(localStorage);
|
||||
|
||||
this.activeSpaceId = 'default';
|
||||
if (this.spaces.getActiveSpace$) {
|
||||
firstValueFrom(this.spaces.getActiveSpace$()).then((space) => {
|
||||
if (spacesService) {
|
||||
firstValueFrom(spacesService.getActiveSpace$()).then((space) => {
|
||||
this.activeSpaceId = space.id;
|
||||
});
|
||||
}
|
||||
|
@ -72,7 +74,7 @@ class DashboardBackupService implements DashboardBackupServiceType {
|
|||
try {
|
||||
this.localStorage.set(DASHBOARD_VIEWMODE_LOCAL_KEY, viewMode);
|
||||
} catch (e) {
|
||||
this.notifications.toasts.addDanger({
|
||||
coreServices.notifications.toasts.addDanger({
|
||||
title: backupServiceStrings.viewModeStorageError(e.message),
|
||||
'data-test-subj': 'dashboardViewmodeBackupFailure',
|
||||
});
|
||||
|
@ -99,7 +101,7 @@ class DashboardBackupService implements DashboardBackupServiceType {
|
|||
});
|
||||
}
|
||||
} catch (e) {
|
||||
this.notifications.toasts.addDanger({
|
||||
coreServices.notifications.toasts.addDanger({
|
||||
title: backupServiceStrings.getPanelsClearError(e.message),
|
||||
'data-test-subj': 'dashboardPanelsClearFailure',
|
||||
});
|
||||
|
@ -117,7 +119,7 @@ class DashboardBackupService implements DashboardBackupServiceType {
|
|||
|
||||
return { dashboardState, panels };
|
||||
} catch (e) {
|
||||
this.notifications.toasts.addDanger({
|
||||
coreServices.notifications.toasts.addDanger({
|
||||
title: backupServiceStrings.getPanelsGetError(e.message),
|
||||
'data-test-subj': 'dashboardPanelsGetFailure',
|
||||
});
|
||||
|
@ -138,7 +140,7 @@ class DashboardBackupService implements DashboardBackupServiceType {
|
|||
set(panelsStorage, [this.activeSpaceId, id], unsavedPanels);
|
||||
this.sessionStorage.set(DASHBOARD_PANELS_SESSION_KEY, panelsStorage, true);
|
||||
} catch (e) {
|
||||
this.notifications.toasts.addDanger({
|
||||
coreServices.notifications.toasts.addDanger({
|
||||
title: backupServiceStrings.getPanelsSetError(e.message),
|
||||
'data-test-subj': 'dashboardPanelsSetFailure',
|
||||
});
|
||||
|
@ -178,7 +180,7 @@ class DashboardBackupService implements DashboardBackupServiceType {
|
|||
|
||||
return this.oldDashboardsWithUnsavedChanges;
|
||||
} catch (e) {
|
||||
this.notifications.toasts.addDanger({
|
||||
coreServices.notifications.toasts.addDanger({
|
||||
title: backupServiceStrings.getPanelsGetError(e.message),
|
||||
'data-test-subj': 'dashboardPanelsGetFailure',
|
||||
});
|
||||
|
@ -191,9 +193,11 @@ class DashboardBackupService implements DashboardBackupServiceType {
|
|||
}
|
||||
}
|
||||
|
||||
export const dashboardBackupServiceFactory: DashboardBackupServiceFactory = (
|
||||
core,
|
||||
requiredServices
|
||||
) => {
|
||||
return new DashboardBackupService(requiredServices);
|
||||
let dashboardBackupService: DashboardBackupService;
|
||||
|
||||
export const getDashboardBackupService = () => {
|
||||
if (!dashboardBackupService) {
|
||||
dashboardBackupService = new DashboardBackupService();
|
||||
}
|
||||
return dashboardBackupService;
|
||||
};
|
|
@ -1,30 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public';
|
||||
import { DashboardCapabilitiesService } from './types';
|
||||
|
||||
const defaultDashboardCapabilities: DashboardCapabilitiesService = {
|
||||
show: true,
|
||||
createNew: true,
|
||||
saveQuery: true,
|
||||
createShortUrl: true,
|
||||
showWriteControls: true,
|
||||
storeSearchSession: true,
|
||||
mapsCapabilities: { save: true },
|
||||
visualizeCapabilities: { save: true },
|
||||
};
|
||||
|
||||
type DashboardCapabilitiesServiceFactory = PluginServiceFactory<DashboardCapabilitiesService>;
|
||||
|
||||
export const dashboardCapabilitiesServiceFactory: DashboardCapabilitiesServiceFactory = () => {
|
||||
return {
|
||||
...defaultDashboardCapabilities,
|
||||
};
|
||||
};
|
|
@ -1,19 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
export interface DashboardCapabilitiesService {
|
||||
show: boolean;
|
||||
saveQuery: boolean;
|
||||
createNew: boolean;
|
||||
mapsCapabilities: { save: boolean };
|
||||
createShortUrl: boolean;
|
||||
showWriteControls: boolean;
|
||||
visualizeCapabilities: { save: boolean };
|
||||
storeSearchSession: boolean;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue