[dashboard] remove legacy embeddable client migrations (#203669)

Part of https://github.com/elastic/kibana/issues/203250
This commit is contained in:
Nathan Reese 2024-12-10 13:41:18 -07:00 committed by GitHub
parent 5acba9678a
commit d67681d519
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 25 additions and 181 deletions

View file

@ -100,7 +100,6 @@ export function getDashboardApi({
timeRestore$: unifiedSearchManager.internalApi.timeRestore$,
});
const unsavedChangesManager = initializeUnsavedChangesManager({
anyMigrationRun: savedObjectResult?.anyMigrationRun ?? false,
creationOptions,
controlGroupApi$,
lastSavedState: omit(savedObjectResult?.dashboardInput, 'controlGroupInput') ?? {

View file

@ -155,7 +155,6 @@ export type DashboardApi = CanExpandPanels &
getSettings: () => DashboardStateFromSettingsFlyout;
getDashboardPanelFromId: (id: string) => Promise<DashboardPanelState>;
hasOverlays$: PublishingSubject<boolean>;
hasRunMigrations$: PublishingSubject<boolean>;
hasUnsavedChanges$: PublishingSubject<boolean>;
highlightPanel: (panelRef: HTMLDivElement) => void;
highlightPanelId$: PublishingSubject<string | undefined>;

View file

@ -23,7 +23,6 @@ import {
import { initializeViewModeManager } from './view_mode_manager';
export function initializeUnsavedChangesManager({
anyMigrationRun,
creationOptions,
controlGroupApi$,
lastSavedState,
@ -33,7 +32,6 @@ export function initializeUnsavedChangesManager({
viewModeManager,
unifiedSearchManager,
}: {
anyMigrationRun: boolean;
creationOptions?: DashboardCreationOptions;
controlGroupApi$: PublishingSubject<ControlGroupApi | undefined>;
lastSavedState: DashboardState;
@ -43,7 +41,6 @@ export function initializeUnsavedChangesManager({
viewModeManager: ReturnType<typeof initializeViewModeManager>;
unifiedSearchManager: ReturnType<typeof initializeUnifiedSearchManager>;
}) {
const hasRunMigrations$ = new BehaviorSubject(anyMigrationRun);
const hasUnsavedChanges$ = new BehaviorSubject(false);
const lastSavedState$ = new BehaviorSubject<DashboardState>(lastSavedState);
const saveNotification$ = new Subject<void>();
@ -113,7 +110,6 @@ export function initializeUnsavedChangesManager({
unifiedSearchManager.internalApi.reset(lastSavedState$.value);
await controlGroupApi$.value?.asyncResetUnsavedChanges();
},
hasRunMigrations$,
hasUnsavedChanges$,
saveNotification$,
},
@ -125,11 +121,6 @@ export function initializeUnsavedChangesManager({
getLastSavedState: () => lastSavedState$.value,
onSave: (savedState: DashboardState) => {
lastSavedState$.next(savedState);
// if we set the last saved input, it means we have saved this Dashboard - therefore clientside migrations have
// been serialized into the SO.
hasRunMigrations$.next(false);
saveNotification$.next();
},
},

View file

@ -67,15 +67,6 @@ export const unsavedChangesBadgeStrings = {
defaultMessage:
' You have unsaved changes in this dashboard. To remove this label, save the dashboard.',
}),
getHasRunMigrationsText: () =>
i18n.translate('dashboard.hasRunMigrationsBadge', {
defaultMessage: 'Save recommended',
}),
getHasRunMigrationsToolTipContent: () =>
i18n.translate('dashboard.hasRunMigrationsBadgeToolTipContent', {
defaultMessage:
'One or more panels on this dashboard have been updated to a new version. Save the dashboard so it loads faster next time.',
}),
};
export const getCreateVisualizationButtonTitle = () =>

View file

@ -43,11 +43,10 @@ export const useDashboardMenuItems = ({
const dashboardApi = useDashboardApi();
const [dashboardTitle, hasOverlays, hasRunMigrations, hasUnsavedChanges, lastSavedId, viewMode] =
const [dashboardTitle, hasOverlays, hasUnsavedChanges, lastSavedId, viewMode] =
useBatchedPublishingSubjects(
dashboardApi.panelTitle,
dashboardApi.hasOverlays$,
dashboardApi.hasRunMigrations$,
dashboardApi.hasUnsavedChanges$,
dashboardApi.savedObjectId,
dashboardApi.viewMode
@ -161,7 +160,7 @@ export const useDashboardMenuItems = ({
emphasize: true,
isLoading: isSaveInProgress,
testId: 'dashboardQuickSaveMenuItem',
disableButton: disableTopNav || !(hasRunMigrations || hasUnsavedChanges),
disableButton: disableTopNav || !hasUnsavedChanges,
run: () => quickSaveDashboard(),
} as TopNavMenuData,
@ -211,7 +210,6 @@ export const useDashboardMenuItems = ({
}, [
disableTopNav,
isSaveInProgress,
hasRunMigrations,
hasUnsavedChanges,
lastSavedId,
dashboardInteractiveSave,

View file

@ -87,7 +87,6 @@ export function InternalDashboardTopNav({
allDataViews,
focusedPanelId,
fullScreenMode,
hasRunMigrations,
hasUnsavedChanges,
lastSavedId,
query,
@ -97,7 +96,6 @@ export function InternalDashboardTopNav({
dashboardApi.dataViews,
dashboardApi.focusedPanelId$,
dashboardApi.fullScreenMode$,
dashboardApi.hasRunMigrations$,
dashboardApi.hasUnsavedChanges$,
dashboardApi.savedObjectId,
dashboardApi.query$,
@ -267,19 +265,6 @@ export function InternalDashboardTopNav({
} as EuiToolTipProps,
});
}
if (hasRunMigrations && viewMode === 'edit') {
allBadges.push({
'data-test-subj': 'dashboardSaveRecommendedBadge',
badgeText: unsavedChangesBadgeStrings.getHasRunMigrationsText(),
title: '',
color: 'success',
iconType: 'save',
toolTipProps: {
content: unsavedChangesBadgeStrings.getHasRunMigrationsToolTipContent(),
position: 'bottom',
} as EuiToolTipProps,
});
}
const { showWriteControls } = getDashboardCapabilities();
if (showWriteControls && dashboardApi.isManaged) {
@ -328,7 +313,7 @@ export function InternalDashboardTopNav({
});
}
return allBadges;
}, [hasUnsavedChanges, viewMode, hasRunMigrations, isPopoverOpen, dashboardApi, maybeRedirect]);
}, [hasUnsavedChanges, viewMode, isPopoverOpen, dashboardApi, maybeRedirect]);
return (
<div className="dashboardTopNav">

View file

@ -100,7 +100,6 @@ export function buildMockDashboardApi({
viewMode: initialState.viewMode as ViewMode,
id: savedObjectId ?? '123',
} as SavedDashboardInput,
anyMigrationRun: false,
references: [],
},
});

View file

@ -32,7 +32,6 @@ import type {
LoadDashboardReturn,
} from '../types';
import { convertNumberToDashboardVersion } from './dashboard_versioning';
import { migrateDashboardInput } from './migrate_dashboard_input';
export function migrateLegacyQuery(query: Query | { [key: string]: any } | string): Query {
// Lucene was the only option before, so language-less queries are all lucene
@ -171,34 +170,32 @@ export const loadDashboardState = async ({
const panelMap = convertPanelsArrayToPanelMap(panels ?? []);
const { dashboardInput, anyMigrationRun } = migrateDashboardInput({
...DEFAULT_DASHBOARD_INPUT,
...options,
id: embeddableId,
refreshInterval,
timeRestore,
description,
timeRange,
filters,
panels: panelMap,
query,
title,
viewMode: ViewMode.VIEW, // dashboards loaded from saved object default to view mode. If it was edited recently, the view mode from session storage will override this.
tags: savedObjectsTaggingService?.getTaggingApi()?.ui.getTagIdsFromReferences(references) ?? [],
controlGroupInput: attributes.controlGroupInput,
...(version && { version: convertNumberToDashboardVersion(version) }),
});
return {
managed,
references,
resolveMeta,
dashboardInput,
anyMigrationRun,
dashboardInput: {
...DEFAULT_DASHBOARD_INPUT,
...options,
id: embeddableId,
refreshInterval,
timeRestore,
description,
timeRange,
filters,
panels: panelMap,
query,
title,
viewMode: ViewMode.VIEW, // dashboards loaded from saved object default to view mode. If it was edited recently, the view mode from session storage will override this.
tags:
savedObjectsTaggingService?.getTaggingApi()?.ui.getTagIdsFromReferences(references) ?? [],
controlGroupInput: attributes.controlGroupInput,
...(version && { version: convertNumberToDashboardVersion(version) }),
},
dashboardFound: true,
dashboardId: savedObjectId,
};

View file

@ -1,54 +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 { getSampleDashboardState, getSampleDashboardPanel } from '../../../mocks';
import { embeddableService } from '../../kibana_services';
import { SavedDashboardInput } from '../types';
import { migrateDashboardInput } from './migrate_dashboard_input';
jest.mock('@kbn/embeddable-plugin/public', () => {
return {
...jest.requireActual('@kbn/embeddable-plugin/public'),
runEmbeddableFactoryMigrations: jest
.fn()
.mockImplementation((input) => ({ input, migrationRun: true })),
};
});
describe('Migrate dashboard input', () => {
it('should run factory migrations on all Dashboard content', () => {
const dashboardInput = {
...getSampleDashboardState(),
id: '1',
viewMode: ViewMode.VIEW,
executionContext: { type: 'dashboard' },
} as SavedDashboardInput;
dashboardInput.panels = {
panel1: getSampleDashboardPanel({ type: 'superLens', explicitInput: { id: 'panel1' } }),
panel2: getSampleDashboardPanel({ type: 'superLens', explicitInput: { id: 'panel2' } }),
panel3: getSampleDashboardPanel({ type: 'ultraDiscover', explicitInput: { id: 'panel3' } }),
panel4: getSampleDashboardPanel({ type: 'ultraDiscover', explicitInput: { id: 'panel4' } }),
};
embeddableService.getEmbeddableFactory = jest.fn(() => ({
latestVersion: '1.0.0',
migrations: {},
})) as unknown as typeof embeddableService.getEmbeddableFactory;
const result = migrateDashboardInput(dashboardInput);
// migration run should be true because the runEmbeddableFactoryMigrations mock above returns true.
expect(result.anyMigrationRun).toBe(true);
expect(embeddableService.getEmbeddableFactory).toHaveBeenCalledTimes(4); // should be called 4 times for the panels, and 3 times for the controls
expect(embeddableService.getEmbeddableFactory).toHaveBeenCalledWith('superLens');
expect(embeddableService.getEmbeddableFactory).toHaveBeenCalledWith('ultraDiscover');
});
});

View file

@ -1,54 +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 {
EmbeddableFactoryNotFoundError,
runEmbeddableFactoryMigrations,
} from '@kbn/embeddable-plugin/public';
import { DashboardContainerInput, DashboardPanelState } from '../../../../common';
import { embeddableService } from '../../kibana_services';
import { SavedDashboardInput } from '../types';
/**
* Run Dashboard migrations clientside. We pre-emptively run all migrations for all content on this Dashboard so that
* we can ensure the `last saved state` which eventually resides in the Dashboard public state is fully migrated.
* This prevents the reset button from un-migrating the panels on the Dashboard. This also means that the migrations may
* get skipped at Embeddable create time - unless states with older versions are saved in the URL or session storage.
*/
export const migrateDashboardInput = (dashboardInput: SavedDashboardInput) => {
let anyMigrationRun = false;
if (!dashboardInput) return dashboardInput;
const migratedPanels: DashboardContainerInput['panels'] = {};
for (const [id, panel] of Object.entries(dashboardInput.panels)) {
// if the panel type is registered in the new embeddable system, we do not need to run migrations for it.
if (embeddableService.reactEmbeddableRegistryHasKey(panel.type)) {
migratedPanels[id] = panel;
continue;
}
const factory = embeddableService.getEmbeddableFactory(panel.type);
if (!factory) throw new EmbeddableFactoryNotFoundError(panel.type);
// run last saved migrations for by value panels only.
if (!panel.explicitInput.savedObjectId) {
const { input: newInput, migrationRun: panelMigrationRun } = runEmbeddableFactoryMigrations(
panel.explicitInput,
factory
);
if (panelMigrationRun) anyMigrationRun = true;
panel.explicitInput = newInput as DashboardPanelState['explicitInput'];
} else if (factory.latestVersion) {
// by reference panels are always considered to be of the latest version
panel.explicitInput.version = factory.latestVersion;
}
migratedPanels[id] = panel;
}
dashboardInput.panels = migratedPanels;
return { dashboardInput, anyMigrationRun };
};

View file

@ -67,7 +67,6 @@ export interface LoadDashboardReturn {
managed?: boolean;
resolveMeta?: DashboardResolveMeta;
dashboardInput: SavedDashboardInput;
anyMigrationRun?: boolean;
/**
* Raw references returned directly from the Dashboard saved object. These

View file

@ -1466,8 +1466,6 @@
"dashboard.featureCatalogue.dashboardDescription": "Affichez et partagez une collection de visualisations et de recherches enregistrées.",
"dashboard.featureCatalogue.dashboardSubtitle": "Analysez des données à laide de tableaux de bord.",
"dashboard.featureCatalogue.dashboardTitle": "Dashboard",
"dashboard.hasRunMigrationsBadge": "Enregistrement recommandé",
"dashboard.hasRunMigrationsBadgeToolTipContent": "Un ou plusieurs panneaux de ce tableau de bord ont été mis à jour vers une nouvelle version. Enregistrez le tableau de bord pour qu'il charge plus rapidement la prochaine fois.",
"dashboard.labs.enableLabsDescription": "Cet indicateur détermine si l'utilisateur a accès au bouton Ateliers, moyen rapide d'activer et de désactiver les fonctionnalités de la version d'évaluation technique dans le tableau de bord.",
"dashboard.labs.enableUI": "Activer le bouton Ateliers dans le tableau de bord",
"dashboard.listing.createNewDashboard.combineDataViewFromKibanaAppDescription": "Analysez toutes vos données Elastic en un seul endroit, en créant un tableau de bord et en ajoutant des visualisations.",

View file

@ -1466,8 +1466,6 @@
"dashboard.featureCatalogue.dashboardDescription": "ビジュアライゼーションと保存された検索のコレクションの表示と共有を行います。",
"dashboard.featureCatalogue.dashboardSubtitle": "ダッシュボードでデータを分析します。",
"dashboard.featureCatalogue.dashboardTitle": "ダッシュボード",
"dashboard.hasRunMigrationsBadge": "推奨を保存",
"dashboard.hasRunMigrationsBadgeToolTipContent": "このダッシュボードの1つ以上のパネルが新しいバージョンに更新されました。ダッシュボードを保存すると、次回の読み込みが速くなります。",
"dashboard.labs.enableLabsDescription": "このフラグはビューアーで[ラボ]ボタンを使用できるかどうかを決定します。ダッシュボードで実験的機能を有効および無効にするための簡単な方法です。",
"dashboard.labs.enableUI": "ダッシュボードで[ラボ]ボタンを有効にする",
"dashboard.listing.createNewDashboard.combineDataViewFromKibanaAppDescription": "ダッシュボードを作成し、ビジュアライゼーションを追加して、すべてのElasticデータを1つの場所で分析します。",

View file

@ -1479,8 +1479,6 @@
"dashboard.featureCatalogue.dashboardDescription": "显示和共享可视化和已保存搜索的集合。",
"dashboard.featureCatalogue.dashboardSubtitle": "在仪表板中分析数据。",
"dashboard.featureCatalogue.dashboardTitle": "仪表板",
"dashboard.hasRunMigrationsBadge": "保存推荐项",
"dashboard.hasRunMigrationsBadgeToolTipContent": "此仪表板上的一个或多个面板已更新到新版本。保存该仪表板以便下次更快加载。",
"dashboard.labs.enableLabsDescription": "此标志决定查看者是否有权访问用于在仪表板中快速启用和禁用技术预览功能的'实验'按钮。",
"dashboard.labs.enableUI": "在仪表板中启用实验按钮",
"dashboard.listing.createNewDashboard.combineDataViewFromKibanaAppDescription": "通过创建仪表板并添加可视化,在一个位置分析所有 Elastic 数据。",