[dashboard] remove legacy section from 'Add panel' flyout (#208116)

Users should use Lens to visualize data. Users should not use TSVB or
aggregation based panels. To this end, "easy button" UIs, such as
dashboard's "Add panel" flyout, should guide users to lens and not guide
them to legacy applications.

This PR removes `legacy` section from dashboard "Add panel" flyout.
Users can still add legacy panels to dashboards via "visualize"
application. The path is more steps, but this is as intended. Using old
stuff should be painful and require extra work as a caret to move users
to new stuff.

<img width="500" alt="Screenshot 2025-01-23 at 1 08 41 PM"
src="https://github.com/user-attachments/assets/e142cba9-a880-4ad6-a3f8-a981a03daaec"
/>

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Nathan Reese 2025-01-30 12:22:51 -07:00 committed by GitHub
parent 2212a19bc8
commit 758768136d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 108 additions and 407 deletions

View file

@ -11,7 +11,6 @@ import { VisGroups } from '@kbn/visualizations-plugin/public';
import { ADD_PANEL_TRIGGER } from '@kbn/ui-actions-plugin/public';
import {
ADD_PANEL_ANNOTATION_GROUP,
ADD_PANEL_LEGACY_GROUP,
ADD_PANEL_OTHER_GROUP,
ADD_PANEL_VISUALIZATION_GROUP,
} from '@kbn/embeddable-plugin/public';
@ -23,11 +22,9 @@ import { uiActionsService, visualizationsService } from '../../../services/kiban
import { navigateToVisEditor } from './navigate_to_vis_editor';
import type { MenuItem, MenuItemGroup } from './types';
const VIS_GROUP_TO_ADD_PANEL_GROUP: Record<VisGroups, undefined | PresentableGroup> = {
[VisGroups.AGGBASED]: undefined,
const VIS_GROUP_TO_ADD_PANEL_GROUP: Record<string, PresentableGroup> = {
[VisGroups.PROMOTED]: ADD_PANEL_VISUALIZATION_GROUP,
[VisGroups.TOOLS]: ADD_PANEL_ANNOTATION_GROUP,
[VisGroups.LEGACY]: ADD_PANEL_LEGACY_GROUP,
};
export async function getMenuItemGroups(

View file

@ -56,6 +56,5 @@ export function plugin(initializerContext: PluginInitializerContext) {
export {
ADD_PANEL_ANNOTATION_GROUP,
ADD_PANEL_OTHER_GROUP,
ADD_PANEL_LEGACY_GROUP,
ADD_PANEL_VISUALIZATION_GROUP,
} from './ui_actions/add_panel_groups';

View file

@ -39,12 +39,3 @@ export const ADD_PANEL_OTHER_GROUP = {
getIconType: () => 'empty',
order: -1, // Given an item that doesn't specify a group is assigned zero, this forces other to come after all intentionally grouped section
};
export const ADD_PANEL_LEGACY_GROUP = {
id: 'legacy',
getDisplayName: () =>
i18n.translate('embeddableApi.common.constants.grouping.legacy', {
defaultMessage: 'Legacy',
}),
order: -2, // Given an item that doesn't specify a group is assigned zero, this forces it to the bottom of the list
};

View file

@ -1,62 +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 {
AddAggVisualizationPanelAction,
type AddAggVisualizationPanelActionApi,
} from './add_agg_vis_action';
import type { BaseVisType } from '../vis_types/base_vis_type';
import { VisGroups } from '../vis_types/vis_groups_enum';
import { TypesService, type TypesStart } from '../vis_types/types_service';
const mockCompatibleEmbeddableAPI: AddAggVisualizationPanelActionApi = {
type: 'anyEmbeddable',
addNewPanel: jest.fn(),
getAppContext: jest.fn(),
};
describe('AddAggVisualizationPanelAction', () => {
let typeServiceStart: TypesStart;
beforeEach(() => {
const typeService = new TypesService();
typeServiceStart = typeService.start();
});
afterEach(() => {
jest.clearAllMocks();
});
test('invoking the compatibility function returns false when initialized with types that are not grouped as agg visualizations', async () => {
jest.spyOn(typeServiceStart, 'all').mockReturnValue([]);
const addAggVisualizationPanelAction = new AddAggVisualizationPanelAction(typeServiceStart);
expect(
await addAggVisualizationPanelAction.isCompatible({ embeddable: mockCompatibleEmbeddableAPI })
).toBe(false);
});
test('invoking the compatibility function returns true when the registered agg visualizations type does not have creation disabled', async () => {
jest.spyOn(typeServiceStart, 'all').mockReturnValue([
{
group: VisGroups.AGGBASED,
disableCreate: false,
name: 'test visualization',
} as BaseVisType,
]);
const addAggVisualizationPanelAction = new AddAggVisualizationPanelAction(typeServiceStart);
expect(
await addAggVisualizationPanelAction.isCompatible({ embeddable: mockCompatibleEmbeddableAPI })
).toBe(true);
});
});

View file

@ -1,72 +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 { i18n } from '@kbn/i18n';
import {
apiHasAppContext,
EmbeddableApiContext,
HasType,
HasAppContext,
} from '@kbn/presentation-publishing';
import { ADD_PANEL_LEGACY_GROUP } from '@kbn/embeddable-plugin/public';
import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
import { apiHasType } from '@kbn/presentation-publishing';
import { apiCanAddNewPanel, CanAddNewPanel } from '@kbn/presentation-containers';
import { VisGroups } from '../vis_types/vis_groups_enum';
import type { TypesStart } from '../vis_types/types_service';
import { showNewVisModal } from '../wizard/show_new_vis';
import { ADD_AGG_VIS_ACTION_ID } from './constants';
export type AddAggVisualizationPanelActionApi = HasType & CanAddNewPanel & HasAppContext;
const isApiCompatible = (api: unknown | null): api is AddAggVisualizationPanelActionApi => {
return apiHasType(api) && apiCanAddNewPanel(api) && apiHasAppContext(api);
};
export class AddAggVisualizationPanelAction implements Action<EmbeddableApiContext> {
public readonly type = ADD_AGG_VIS_ACTION_ID;
public readonly id = ADD_AGG_VIS_ACTION_ID;
public readonly grouping = [ADD_PANEL_LEGACY_GROUP];
private readonly aggVisualizationCreationEnabled: boolean;
public readonly order = 20;
constructor(visTypes: TypesStart) {
this.aggVisualizationCreationEnabled = visTypes.all().some((type) => {
return !type.disableCreate && type.group === VisGroups.AGGBASED;
});
}
public getIconType() {
return 'visualizeApp';
}
public getDisplayName() {
return i18n.translate('visualizations.uiAction.addAggVis.displayName', {
defaultMessage: 'Aggregation based',
});
}
public async isCompatible({ embeddable }: EmbeddableApiContext) {
// only mark this action as compatible in environments that have agg based visualizations creation enabled
return this.aggVisualizationCreationEnabled && isApiCompatible(embeddable);
}
public async execute({ embeddable }: EmbeddableApiContext): Promise<void> {
if (!isApiCompatible(embeddable)) {
throw new IncompatibleActionError();
}
showNewVisModal({
originatingApp: embeddable.getAppContext().currentAppId,
outsideVisualizeApp: true,
showAggsSelection: true,
});
}
}

View file

@ -7,5 +7,4 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
export const ADD_AGG_VIS_ACTION_ID = 'ADD_AGG_VIS';
export const ACTION_EDIT_IN_LENS = 'ACTION_EDIT_IN_LENS';

View file

@ -9,8 +9,8 @@
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { CONTEXT_MENU_TRIGGER } from '@kbn/embeddable-plugin/public';
import { ADD_PANEL_TRIGGER, type UiActionsStart } from '@kbn/ui-actions-plugin/public';
import { ACTION_EDIT_IN_LENS, ADD_AGG_VIS_ACTION_ID } from './constants';
import type { UiActionsStart } from '@kbn/ui-actions-plugin/public';
import { ACTION_EDIT_IN_LENS } from './constants';
import { TypesStart } from '../vis_types/types_service';
export function registerActions(
@ -22,9 +22,4 @@ export function registerActions(
const { EditInLensAction } = await import('./edit_in_lens_action');
return new EditInLensAction(data.query.timefilter.timefilter);
});
uiActions.addTriggerActionAsync(ADD_PANEL_TRIGGER, ADD_AGG_VIS_ACTION_ID, async () => {
const { AddAggVisualizationPanelAction } = await import('./add_agg_vis_action');
return new AddAggVisualizationPanelAction(types);
});
}

View file

@ -1,114 +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 expect from '@kbn/expect';
import { VisualizeConstants } from '@kbn/visualizations-plugin/common/constants';
import { FtrProviderContext } from '../../../ftr_provider_context';
export default function ({ getService, getPageObjects }: FtrProviderContext) {
const retry = getService('retry');
const { dashboard, header, visualize } = getPageObjects(['dashboard', 'header', 'visualize']);
const browser = getService('browser');
const kibanaServer = getService('kibanaServer');
const dashboardAddPanel = getService('dashboardAddPanel');
describe('create and add embeddables', () => {
before(async () => {
await kibanaServer.savedObjects.cleanStandardList();
await kibanaServer.importExport.load(
'test/functional/fixtures/kbn_archiver/dashboard/current/kibana'
);
await kibanaServer.uiSettings.replace({
defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c',
});
});
after(async () => {
await kibanaServer.savedObjects.cleanStandardList();
});
it('ensure toolbar popover closes on add', async () => {
await dashboard.navigateToApp();
await dashboard.clickNewDashboard();
await dashboard.switchToEditMode();
await dashboardAddPanel.clickEditorMenuButton();
await dashboardAddPanel.clickAddNewPanelFromUIActionLink('Monitors stats');
await dashboardAddPanel.expectEditorMenuClosed();
});
describe('add new visualization link', () => {
before(async () => {
await dashboard.navigateToApp();
await dashboard.preserveCrossAppState();
await dashboard.loadSavedDashboard('few panels');
});
it('adds new visualization via the top nav link', async () => {
const originalPanelCount = await dashboard.getPanelCount();
await dashboard.switchToEditMode();
await dashboardAddPanel.clickEditorMenuButton();
await dashboardAddPanel.clickAggBasedVisualizations();
await visualize.clickAreaChart();
await visualize.clickNewSearch();
await visualize.saveVisualizationExpectSuccess('visualization from top nav add new panel', {
redirectToOrigin: true,
});
await retry.try(async () => {
const panelCount = await dashboard.getPanelCount();
expect(panelCount).to.eql(originalPanelCount + 1);
});
await dashboard.waitForRenderComplete();
});
it('adds a new visualization', async () => {
const originalPanelCount = await dashboard.getPanelCount();
await dashboardAddPanel.clickEditorMenuButton();
await dashboardAddPanel.clickAggBasedVisualizations();
await visualize.clickAreaChart();
await visualize.clickNewSearch();
await visualize.saveVisualizationExpectSuccess('visualization from add new link', {
redirectToOrigin: true,
});
await retry.try(async () => {
const panelCount = await dashboard.getPanelCount();
expect(panelCount).to.eql(originalPanelCount + 1);
});
await dashboard.waitForRenderComplete();
});
it('adds a new timelion visualization', async () => {
// adding this case, as the timelion agg-based viz doesn't need the `clickNewSearch()` step
const originalPanelCount = await dashboard.getPanelCount();
await dashboardAddPanel.clickEditorMenuButton();
await dashboardAddPanel.clickAggBasedVisualizations();
await visualize.clickTimelion();
await visualize.saveVisualizationExpectSuccess('timelion visualization from add new link', {
redirectToOrigin: true,
});
await retry.try(async () => {
const panelCount = await dashboard.getPanelCount();
expect(panelCount).to.eql(originalPanelCount + 1);
});
await dashboard.waitForRenderComplete();
});
it('saves the listing page instead of the visualization to the app link', async () => {
await header.clickVisualize(true);
const currentUrl = await browser.getCurrentUrl();
expect(currentUrl).not.to.contain(VisualizeConstants.EDIT_PATH);
});
after(async () => {
await header.clickDashboard();
});
});
});
}

View file

@ -12,29 +12,17 @@ import expect from '@kbn/expect';
import { FtrProviderContext } from '../../../ftr_provider_context';
export default function ({ getService, getPageObjects }: FtrProviderContext) {
const { dashboard, header, visualize } = getPageObjects(['dashboard', 'header', 'visualize']);
const { dashboard, header } = getPageObjects(['dashboard', 'header']);
const kibanaServer = getService('kibanaServer');
const dashboardAddPanel = getService('dashboardAddPanel');
const dashboardPanelActions = getService('dashboardPanelActions');
let existingDashboardPanelCount = 0;
const dashboardTitle = 'few panels';
const originalPanelCount = 3; // dashboardTitle contains 3 panels
const unsavedDashboardTitle = 'New Dashboard';
const newDashboartTitle = 'A Wild Dashboard';
describe('dashboard unsaved listing', () => {
const addSomePanels = async () => {
// add an area chart by value
await dashboardAddPanel.clickEditorMenuButton();
await dashboardAddPanel.clickAggBasedVisualizations();
await visualize.clickAreaChart();
await visualize.clickNewSearch();
await visualize.saveVisualizationAndReturn();
// add a metric by reference
await dashboardAddPanel.addVisualization('Rendering-Test: metric');
};
before(async () => {
await kibanaServer.savedObjects.cleanStandardList();
await kibanaServer.importExport.load(
@ -54,8 +42,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
it('lists unsaved changes to existing dashboards', async () => {
await dashboard.loadSavedDashboard(dashboardTitle);
await dashboard.switchToEditMode();
await addSomePanels();
existingDashboardPanelCount = await dashboard.getPanelCount();
// change dashboard by adding panel
await dashboardAddPanel.addVisualization('Rendering-Test: metric');
await dashboard.gotoDashboardLandingPage();
await header.waitUntilLoadingHasFinished();
await dashboard.expectUnsavedChangesListingExists(dashboardTitle);
@ -65,14 +54,15 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await dashboard.clickUnsavedChangesContinueEditing(dashboardTitle);
await header.waitUntilLoadingHasFinished();
const currentPanelCount = await dashboard.getPanelCount();
expect(currentPanelCount).to.eql(existingDashboardPanelCount);
expect(currentPanelCount).to.eql(originalPanelCount + 1);
await dashboard.gotoDashboardLandingPage();
await header.waitUntilLoadingHasFinished();
});
it('lists unsaved changes to new dashboards', async () => {
await dashboard.clickNewDashboard();
await addSomePanels();
// change dashboard by adding panel
await dashboardAddPanel.addVisualization('Rendering-Test: metric');
await dashboard.gotoDashboardLandingPage();
await header.waitUntilLoadingHasFinished();
await dashboard.expectUnsavedChangesListingExists(unsavedDashboardTitle);
@ -81,7 +71,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
it('restores unsaved changes to new dashboards', async () => {
await dashboard.clickUnsavedChangesContinueEditing(unsavedDashboardTitle);
await header.waitUntilLoadingHasFinished();
expect(await dashboard.getPanelCount()).to.eql(2);
expect(await dashboard.getPanelCount()).to.eql(1);
await dashboard.gotoDashboardLandingPage();
await header.waitUntilLoadingHasFinished();
});
@ -89,7 +79,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
it('shows a warning on create new, and restores panels if continue is selected', async () => {
await dashboard.clickNewDashboard({ continueEditing: true, expectWarning: true });
await header.waitUntilLoadingHasFinished();
expect(await dashboard.getPanelCount()).to.eql(2);
expect(await dashboard.getPanelCount()).to.eql(1);
await dashboard.gotoDashboardLandingPage();
await header.waitUntilLoadingHasFinished();
});
@ -117,14 +107,15 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await dashboard.loadSavedDashboard(dashboardTitle);
await dashboard.switchToEditMode();
const currentPanelCount = await dashboard.getPanelCount();
expect(currentPanelCount).to.eql(existingDashboardPanelCount - 2);
expect(currentPanelCount).to.eql(originalPanelCount);
await dashboard.gotoDashboardLandingPage();
await header.waitUntilLoadingHasFinished();
});
it('loses unsaved changes to new dashboard upon saving', async () => {
await dashboard.clickNewDashboard();
await addSomePanels();
// change dashboard by adding panel
await dashboardAddPanel.addVisualization('Rendering-Test: metric');
// ensure that the unsaved listing exists first
await dashboard.gotoDashboardLandingPage();
@ -144,13 +135,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await dashboard.switchToEditMode();
// add another panel so we can delete it later
await dashboardAddPanel.clickEditorMenuButton();
await dashboardAddPanel.clickAggBasedVisualizations();
await visualize.clickAreaChart();
await visualize.clickNewSearch();
await visualize.saveVisualizationExpectSuccess('Wildvis', {
redirectToOrigin: true,
});
await dashboardAddPanel.addVisualization('Rendering-Test: heatmap');
await header.waitUntilLoadingHasFinished();
await dashboard.waitForRenderComplete();
@ -165,7 +150,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await header.waitUntilLoadingHasFinished();
// Remove the panel that was just added
await dashboardPanelActions.removePanelByTitle('Wildvis');
await dashboardPanelActions.removePanelByTitle('RenderingTest:heatmap');
await header.waitUntilLoadingHasFinished();
// Check that it now does not exist

View file

@ -98,32 +98,20 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
});
describe('edit mode state', () => {
const addPanels = async () => {
// add an area chart by value
await dashboardAddPanel.clickEditorMenuButton();
await dashboardAddPanel.clickAggBasedVisualizations();
await visualize.clickAreaChart();
await visualize.clickNewSearch();
await visualize.saveVisualizationAndReturn();
// add a metric by reference
await dashboardAddPanel.addVisualization('Rendering-Test: metric');
};
it('does not show unsaved changes badge when there are no unsaved changes', async () => {
await testSubjects.missingOrFail('dashboardUnsavedChangesBadge');
});
it('shows the unsaved changes badge after adding panels', async () => {
await dashboard.switchToEditMode();
await addPanels();
await dashboardAddPanel.addVisualization('Rendering-Test: metric');
await header.waitUntilLoadingHasFinished();
await testSubjects.existOrFail('dashboardUnsavedChangesBadge');
});
it('has correct number of panels', async () => {
unsavedPanelCount = await dashboard.getPanelCount();
expect(unsavedPanelCount).to.eql(originalPanelCount + 2);
expect(unsavedPanelCount).to.eql(originalPanelCount + 1);
});
it('retains unsaved panel count after navigating to listing page and back', async () => {
@ -150,7 +138,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
it('can discard changes', async () => {
unsavedPanelCount = await dashboard.getPanelCount();
expect(unsavedPanelCount).to.eql(originalPanelCount + 2);
expect(unsavedPanelCount).to.eql(originalPanelCount + 1);
await dashboard.clickDiscardChanges();
const currentPanelCount = await dashboard.getPanelCount();
@ -158,10 +146,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
});
it('resets to original panel count after switching to view mode and discarding changes', async () => {
await addPanels();
await dashboardAddPanel.addVisualization('Rendering-Test: metric');
await header.waitUntilLoadingHasFinished();
unsavedPanelCount = await dashboard.getPanelCount();
expect(unsavedPanelCount).to.eql(originalPanelCount + 2);
expect(unsavedPanelCount).to.eql(originalPanelCount + 1);
await dashboard.clickCancelOutOfEditMode();
await header.waitUntilLoadingHasFinished();
@ -172,7 +160,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
it('does not show unsaved changes badge after saving', async () => {
await dashboard.switchToEditMode();
await addPanels();
await dashboardAddPanel.addVisualization('Rendering-Test: metric');
await header.waitUntilLoadingHasFinished();
await dashboard.saveDashboard('Unsaved State Test');
await header.waitUntilLoadingHasFinished();

View file

@ -32,7 +32,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) {
loadTestFile(require.resolve('./url_field_formatter'));
loadTestFile(require.resolve('./embeddable_rendering'));
loadTestFile(require.resolve('./embeddable_data_grid'));
loadTestFile(require.resolve('./create_and_add_embeddables'));
loadTestFile(require.resolve('./edit_embeddable_redirects'));
loadTestFile(require.resolve('./dashboard_unsaved_state'));
loadTestFile(require.resolve('./dashboard_unsaved_listing'));

View file

@ -61,17 +61,16 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
panelGroupByOrder.set(order, panelGroupTitle);
}
expect(panelGroupByOrder.size).to.eql(4);
expect(panelGroupByOrder.size).to.eql(3);
expect([...panelGroupByOrder.values()]).to.eql([
'visualizationsGroup',
'annotation-and-navigationGroup',
'observabilityGroup',
'legacyGroup',
]);
// Any changes to the number of panels needs to be audited by @elastic/kibana-presentation
expect(panelTypes.length).to.eql(11);
expect(panelTypes.length).to.eql(9);
});
});
}

View file

@ -15,12 +15,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const queryBar = getService('queryBar');
const kibanaServer = getService('kibanaServer');
const dashboardAddPanel = getService('dashboardAddPanel');
const { dashboard, common, visualize, timePicker } = getPageObjects([
'dashboard',
'common',
'visualize',
'timePicker',
]);
const { dashboard, common, timePicker } = getPageObjects(['dashboard', 'common', 'timePicker']);
const dashboardName = 'dashboard with filter';
const copyOfDashboardName = `Copy of ${dashboardName}`;
const filterBar = getService('filterBar');
@ -156,29 +151,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
expect(hasFilter).to.be(true);
});
it('when a new vis is added', async function () {
const originalPanelCount = await dashboard.getPanelCount();
await dashboardAddPanel.clickEditorMenuButton();
await dashboardAddPanel.clickAggBasedVisualizations();
await visualize.clickAreaChart();
await visualize.clickNewSearch();
await visualize.saveVisualizationExpectSuccess('new viz panel', {
saveAsNew: false,
redirectToOrigin: true,
});
await dashboard.clickCancelOutOfEditMode(false);
// for this sleep see https://github.com/elastic/kibana/issues/22299
await common.sleep(500);
// confirm lose changes
await common.clickConfirmOnModal();
const panelCount = await dashboard.getPanelCount();
expect(panelCount).to.eql(originalPanelCount);
});
it('when an existing vis is added', async function () {
it('when a panel is added', async function () {
const originalPanelCount = await dashboard.getPanelCount();
await dashboardAddPanel.addVisualization('new viz panel');

View file

@ -238,4 +238,65 @@
],
"type": "visualization",
"version": "WzE1LDJd"
}
{
"id": "f88ce246-ec9b-4546-93b8-cc00dbfad8a7",
"type": "dashboard",
"namespaces": [
"default"
],
"updated_at": "2025-01-29T21:11:39.727Z",
"created_at": "2025-01-29T21:08:10.314Z",
"created_by": "u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0",
"updated_by": "u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0",
"version": "WzQ0LDFd",
"attributes": {
"version": 3,
"description": "",
"timeRestore": false,
"title": "legacy visualizations",
"controlGroupInput": {
"chainingSystem": "HIERARCHICAL",
"controlStyle": "oneLine",
"ignoreParentSettingsJSON": "{\"ignoreFilters\":false,\"ignoreQuery\":false,\"ignoreTimerange\":false,\"ignoreValidations\":false}",
"panelsJSON": "{}",
"showApplySelections": false
},
"optionsJSON": "{\"useMargins\":true,\"syncColors\":false,\"syncCursor\":true,\"syncTooltips\":false,\"hidePanelTitles\":false}",
"panelsJSON": "[{\"type\":\"visualization\",\"embeddableConfig\":{\"enhancements\":{\"dynamicActions\":{\"events\":[]}},\"savedVis\":{\"id\":\"\",\"title\":\"area\",\"description\":\"\",\"type\":\"area\",\"params\":{\"type\":\"area\",\"grid\":{\"categoryLines\":false},\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"type\":\"category\",\"position\":\"bottom\",\"show\":true,\"scale\":{\"type\":\"linear\"},\"labels\":{\"show\":true,\"filter\":true,\"truncate\":100},\"title\":{},\"style\":{}}],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"name\":\"LeftAxis-1\",\"type\":\"value\",\"position\":\"left\",\"show\":true,\"scale\":{\"type\":\"linear\",\"mode\":\"normal\"},\"labels\":{\"show\":true,\"rotate\":0,\"filter\":true,\"truncate\":100},\"title\":{\"text\":\"\"},\"style\":{}}],\"seriesParams\":[{\"show\":true,\"type\":\"area\",\"mode\":\"stacked\",\"data\":{\"label\":\"Count\",\"id\":\"1\"},\"drawLinesBetweenPoints\":true,\"lineWidth\":2,\"showCircles\":true,\"circlesRadius\":1,\"interpolate\":\"linear\",\"valueAxis\":\"ValueAxis-1\"}],\"addTooltip\":true,\"detailedTooltip\":true,\"palette\":{\"type\":\"palette\",\"name\":\"default\"},\"addLegend\":true,\"legendPosition\":\"right\",\"fittingFunction\":\"linear\",\"times\":[],\"addTimeMarker\":false,\"truncateLegend\":true,\"maxLegendLines\":1,\"radiusRatio\":9,\"thresholdLine\":{\"show\":false,\"value\":10,\"width\":1,\"style\":\"full\",\"color\":\"#E7664C\"},\"labels\":{}},\"uiState\":{},\"data\":{\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"params\":{\"emptyAsNull\":false},\"schema\":\"metric\"}],\"searchSource\":{\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}}}},\"panelIndex\":\"ff3ea662-cb07-457a-ad0c-2c500da24c16\",\"gridData\":{\"i\":\"ff3ea662-cb07-457a-ad0c-2c500da24c16\",\"y\":0,\"x\":0,\"w\":24,\"h\":15}},{\"type\":\"visualization\",\"embeddableConfig\":{\"enhancements\":{\"dynamicActions\":{\"events\":[]}},\"savedVis\":{\"id\":\"\",\"title\":\"timelion\",\"description\":\"\",\"type\":\"timelion\",\"params\":{\"expression\":\".es(*)\",\"interval\":\"auto\"},\"uiState\":{},\"data\":{\"aggs\":[],\"searchSource\":{}}}},\"panelIndex\":\"c56b1091-6b8b-4c8a-9296-83fcd909724a\",\"gridData\":{\"x\":24,\"y\":0,\"w\":24,\"h\":15,\"i\":\"c56b1091-6b8b-4c8a-9296-83fcd909724a\"}},{\"type\":\"visualization\",\"embeddableConfig\":{\"enhancements\":{\"dynamicActions\":{\"events\":[]}},\"savedVis\":{\"id\":\"\",\"title\":\"histogram\",\"description\":\"\",\"type\":\"histogram\",\"params\":{\"type\":\"histogram\",\"grid\":{\"categoryLines\":false},\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"type\":\"category\",\"position\":\"bottom\",\"show\":true,\"scale\":{\"type\":\"linear\"},\"labels\":{\"show\":true,\"filter\":true,\"truncate\":100},\"title\":{},\"style\":{}}],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"name\":\"LeftAxis-1\",\"type\":\"value\",\"position\":\"left\",\"show\":true,\"scale\":{\"type\":\"linear\",\"mode\":\"normal\"},\"labels\":{\"show\":true,\"rotate\":0,\"filter\":true,\"truncate\":100},\"title\":{\"text\":\"\"},\"style\":{}}],\"seriesParams\":[{\"show\":true,\"type\":\"histogram\",\"mode\":\"stacked\",\"data\":{\"label\":\"Count\",\"id\":\"1\"},\"interpolate\":\"linear\",\"valueAxis\":\"ValueAxis-1\",\"drawLinesBetweenPoints\":true,\"lineWidth\":2,\"showCircles\":true,\"circlesRadius\":1}],\"radiusRatio\":0,\"addTooltip\":true,\"detailedTooltip\":true,\"palette\":{\"type\":\"palette\",\"name\":\"default\"},\"addLegend\":true,\"legendPosition\":\"right\",\"times\":[],\"addTimeMarker\":false,\"truncateLegend\":true,\"maxLegendLines\":1,\"labels\":{\"show\":false},\"thresholdLine\":{\"show\":false,\"value\":10,\"width\":1,\"style\":\"full\",\"color\":\"#E7664C\"}},\"uiState\":{},\"data\":{\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"params\":{\"emptyAsNull\":false},\"schema\":\"metric\"}],\"searchSource\":{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[{\"$state\":{\"store\":\"appState\"},\"meta\":{\"alias\":null,\"disabled\":false,\"field\":\"geo.src\",\"key\":\"geo.src\",\"negate\":false,\"params\":{\"query\":\"CN\"},\"type\":\"phrase\",\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index\"},\"query\":{\"match_phrase\":{\"geo.src\":\"CN\"}}}],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}}}},\"panelIndex\":\"32825b63-a38d-4d5f-a171-bbc171623ee1\",\"gridData\":{\"x\":0,\"y\":15,\"w\":24,\"h\":15,\"i\":\"32825b63-a38d-4d5f-a171-bbc171623ee1\"}}]",
"kibanaSavedObjectMeta": {
"searchSourceJSON": "{\"filter\":[{\"meta\":{\"disabled\":false,\"negate\":false,\"alias\":null,\"key\":\"bytes\",\"field\":\"bytes\",\"params\":{\"gte\":\"3000\"},\"type\":\"range\",\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index\"},\"query\":{\"range\":{\"bytes\":{\"gte\":\"3000\"}}},\"$state\":{\"store\":\"appState\"}}],\"query\":{\"query\":\"\",\"language\":\"kuery\"}}"
}
},
"references": [
{
"name": "kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index",
"type": "index-pattern",
"id": "logstash-*"
},
{
"name": "ff3ea662-cb07-457a-ad0c-2c500da24c16:kibanaSavedObjectMeta.searchSourceJSON.index",
"type": "index-pattern",
"id": "logstash-*"
},
{
"name": "32825b63-a38d-4d5f-a171-bbc171623ee1:kibanaSavedObjectMeta.searchSourceJSON.index",
"type": "index-pattern",
"id": "logstash-*"
},
{
"name": "32825b63-a38d-4d5f-a171-bbc171623ee1:kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index",
"type": "index-pattern",
"id": "logstash-*"
},
{
"name": "kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index",
"type": "index-pattern",
"id": "logstash-*"
}
],
"managed": false,
"coreMigrationVersion": "8.8.0",
"typeMigrationVersion": "10.2.0"
}

View file

@ -64,11 +64,6 @@ export class DashboardAddPanelService extends FtrService {
await this.testSubjects.missingOrFail('dashboardPanelSelectionFlyout');
}
async clickAggBasedVisualizations() {
this.log.debug('DashboardAddPanel.clickEditorMenuAggBasedMenuItem');
await this.clickAddNewPanelFromUIActionLink('Aggregation based');
}
async clickVisType(visType: string) {
this.log.debug('DashboardAddPanel.clickVisType');
await this.testSubjects.click(`visType-${visType}`);

View file

@ -2756,7 +2756,6 @@
"embeddableApi.cellValueTrigger.description": "Les actions apparaissent dans les options de valeur de cellule dans la visualisation",
"embeddableApi.cellValueTrigger.title": "Valeur de cellule",
"embeddableApi.common.constants.grouping.annotations": "Annotations et Navigation",
"embeddableApi.common.constants.grouping.legacy": "Hérité",
"embeddableApi.common.constants.grouping.other": "Autre",
"embeddableApi.contextMenuTrigger.description": "Une nouvelle action sera ajoutée au menu contextuel du panneau",
"embeddableApi.contextMenuTrigger.title": "Menu contextuel",
@ -9639,7 +9638,6 @@
"visualizations.topNavMenu.shareVisualizationButtonAriaLabel": "Partager la visualisation",
"visualizations.topNavMenu.shareVisualizationButtonLabel": "partager",
"visualizations.topNavMenu.updatePanel": "Mettre à jour le panneau sur {originatingAppName}",
"visualizations.uiAction.addAggVis.displayName": "Basé sur une agrégation",
"visualizations.visualizationLoadingFailedErrorMessage": "Impossible de charger la visualisation",
"visualizations.visualizationTypeInvalidMessage": "Type de visualisation non valide \"{visType}\"",
"visualizations.visualizeDescription": "Créez des visualisations et des datastores agrégés dans vos index Elasticsearch.",

View file

@ -2751,7 +2751,6 @@
"embeddableApi.cellValueTrigger.description": "アクションはビジュアライゼーションのセル値オプションに表示されます",
"embeddableApi.cellValueTrigger.title": "セル値",
"embeddableApi.common.constants.grouping.annotations": "注釈とナビゲーション",
"embeddableApi.common.constants.grouping.legacy": "レガシー",
"embeddableApi.common.constants.grouping.other": "Other",
"embeddableApi.contextMenuTrigger.description": "新しいアクションがパネルのコンテキストメニューに追加されます",
"embeddableApi.contextMenuTrigger.title": "コンテキストメニュー",
@ -9514,7 +9513,6 @@
"visualizations.topNavMenu.shareVisualizationButtonAriaLabel": "ビジュアライゼーションを共有",
"visualizations.topNavMenu.shareVisualizationButtonLabel": "共有",
"visualizations.topNavMenu.updatePanel": "{originatingAppName}でパネルを更新",
"visualizations.uiAction.addAggVis.displayName": "アグリゲーションに基づく",
"visualizations.visualizationLoadingFailedErrorMessage": "ビジュアライゼーションを読み込めませんでした",
"visualizations.visualizationTypeInvalidMessage": "無効なビジュアライゼーションタイプ \"{visType}\"",
"visualizations.visualizeDescription": "ビジュアライゼーションを作成してElasticsearchインデックスに保存されたデータをアグリゲーションします。",

View file

@ -2745,7 +2745,6 @@
"embeddableApi.cellValueTrigger.description": "操作在可视化上的单元格值选项中显示",
"embeddableApi.cellValueTrigger.title": "单元格值",
"embeddableApi.common.constants.grouping.annotations": "标注和导航",
"embeddableApi.common.constants.grouping.legacy": "旧版",
"embeddableApi.common.constants.grouping.other": "其他",
"embeddableApi.contextMenuTrigger.description": "会将一个新操作添加到该面板的上下文菜单",
"embeddableApi.contextMenuTrigger.title": "上下文菜单",
@ -9368,7 +9367,6 @@
"visualizations.topNavMenu.shareVisualizationButtonAriaLabel": "共享可视化",
"visualizations.topNavMenu.shareVisualizationButtonLabel": "共享",
"visualizations.topNavMenu.updatePanel": "更新 {originatingAppName} 中的面板",
"visualizations.uiAction.addAggVis.displayName": "基于聚合",
"visualizations.visualizationLoadingFailedErrorMessage": "无法加载可视化",
"visualizations.visualizeDescription": "创建可视化并聚合在 Elasticsearch 索引中的数据存储。",
"visualizations.visualizeListingBreadcrumbsTitle": "Visualize 库",

View file

@ -59,18 +59,17 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
panelGroupByOrder.set(order, panelGroupTitle);
}
expect(panelGroupByOrder.size).to.eql(5);
expect(panelGroupByOrder.size).to.eql(4);
expect([...panelGroupByOrder.values()]).to.eql([
'visualizationsGroup',
'annotation-and-navigationGroup',
'mlGroup',
'observabilityGroup',
'legacyGroup',
]);
// Any changes to the number of panels needs to be audited by @elastic/kibana-presentation
expect(panelTypes.length).to.eql(21);
expect(panelTypes.length).to.eql(19);
});
});
}

View file

@ -15,79 +15,54 @@
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../../../ftr_provider_context';
const AREA_PANEL_INDEX = 0;
const TIMELION_PANEL_INDEX = 1;
const HISTOGRAM_PANEL_INDEX = 2;
export default function ({ getPageObjects, getService }: FtrProviderContext) {
const { lens, dashboard, canvas } = getPageObjects(['lens', 'dashboard', 'canvas']);
const { lens, dashboard } = getPageObjects(['lens', 'dashboard']);
const testSubjects = getService('testSubjects');
const retry = getService('retry');
const panelActions = getService('dashboardPanelActions');
const dashboardAddPanel = getService('dashboardAddPanel');
const filterBar = getService('filterBar');
describe('Convert to Lens action on dashboard', function describeIndexTests() {
before(async () => {
await dashboard.initTests();
await dashboard.gotoDashboardEditMode('legacy visualizations');
});
it('should show notification in context menu if visualization can be converted', async () => {
await dashboard.clickNewDashboard();
await dashboardAddPanel.clickEditorMenuButton();
await dashboardAddPanel.clickAggBasedVisualizations();
await dashboardAddPanel.clickVisType('area');
await testSubjects.click('savedObjectTitlelogstash-*');
await testSubjects.exists('visualizesaveAndReturnButton');
await testSubjects.click('visualizesaveAndReturnButton');
await dashboard.waitForRenderComplete();
// define a filter
await filterBar.addFilter({ field: 'geo.src', operation: 'is', value: 'CN' });
await dashboard.waitForRenderComplete();
expect(await dashboard.isNotificationExists(0)).to.be(true);
expect(await dashboard.isNotificationExists(AREA_PANEL_INDEX)).to.be(true);
});
it('should convert legacy visualization to lens by clicking "convert to lens" action', async () => {
const originalEmbeddableCount = await canvas.getEmbeddableCount();
await panelActions.convertToLens();
const panel = (await dashboard.getDashboardPanels())[AREA_PANEL_INDEX];
await panelActions.convertToLens(panel);
await lens.waitForVisualization('xyVisChart');
const lastBreadcrumbdcrumb = await testSubjects.getVisibleText('breadcrumb last');
expect(lastBreadcrumbdcrumb).to.be('Converting Area visualization');
expect(lastBreadcrumbdcrumb).to.be('Converting "area" visualization');
const filterCount = await filterBar.getFilterCount();
expect(filterCount).to.equal(0);
await lens.replaceInDashboard();
await retry.try(async () => {
const embeddableCount = await canvas.getEmbeddableCount();
expect(embeddableCount).to.eql(originalEmbeddableCount);
});
await dashboard.waitForRenderComplete();
const titles = await dashboard.getPanelTitles();
expect(titles[0]).to.be('Area visualization (converted)');
expect(titles[0]).to.be('area (converted)');
expect(await dashboard.isNotificationExists(0)).to.be(false);
expect(await dashboard.isNotificationExists(AREA_PANEL_INDEX)).to.be(false);
});
it('should not show notification in context menu if visualization can not be converted', async () => {
await dashboardAddPanel.clickEditorMenuButton();
await dashboardAddPanel.clickAggBasedVisualizations();
await dashboardAddPanel.clickVisType('timelion');
await testSubjects.exists('visualizesaveAndReturnButton');
await testSubjects.click('visualizesaveAndReturnButton');
await dashboard.waitForRenderComplete();
expect(await dashboard.isNotificationExists(1)).to.be(false);
expect(await dashboard.isNotificationExists(TIMELION_PANEL_INDEX)).to.be(false);
});
it('should carry the visualizations filters to Lens', async () => {
await dashboardAddPanel.clickEditorMenuButton();
await dashboardAddPanel.clickAggBasedVisualizations();
await dashboardAddPanel.clickVisType('histogram');
await testSubjects.click('savedObjectTitlelogstash-*');
await filterBar.addFilter({ field: 'geo.src', operation: 'is', value: 'CN' });
await testSubjects.exists('visualizesaveAndReturnButton');
await testSubjects.click('visualizesaveAndReturnButton');
await dashboard.waitForRenderComplete();
expect(await dashboard.isNotificationExists(2)).to.be(true);
const panel = (await dashboard.getDashboardPanels())[2];
expect(await dashboard.isNotificationExists(HISTOGRAM_PANEL_INDEX)).to.be(true);
const panel = (await dashboard.getDashboardPanels())[HISTOGRAM_PANEL_INDEX];
await panelActions.convertToLens(panel);
await lens.waitForVisualization('xyVisChart');
@ -96,7 +71,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
await lens.replaceInDashboard();
await dashboard.waitForRenderComplete();
expect(await dashboard.isNotificationExists(2)).to.be(false);
expect(await dashboard.isNotificationExists(HISTOGRAM_PANEL_INDEX)).to.be(false);
});
});
}