mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Dashboard] Fix time range and filter state comparisons for dashboard-to-dashboard drilldowns (#124278)
* Remove deprecated references. * Add timeRange to DashboardState and timeRestore to DashboardContainerInput * Ignore filters.$state during dashboard diff * Undo remove deprecated references. * Conditionally exclude filter state from comparison * Inject filter.$state for context filters * Trigger apply filters * Add save to functional tests * Remove unused variable Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
c94d5fdd9e
commit
6f1a963d1d
11 changed files with 60 additions and 28 deletions
|
@ -82,6 +82,7 @@ const initialInput: DashboardContainerInput = {
|
|||
to: 'now',
|
||||
from: 'now-1d',
|
||||
},
|
||||
timeRestore: false,
|
||||
title: 'test',
|
||||
query: {
|
||||
query: '',
|
||||
|
|
|
@ -11,6 +11,7 @@ import type { KibanaExecutionContext } from 'src/core/public';
|
|||
import { DashboardSavedObject } from '../../saved_dashboards';
|
||||
import { getTagsFromSavedDashboard, migrateAppState } from '.';
|
||||
import { EmbeddablePackageState, ViewMode } from '../../services/embeddable';
|
||||
import { TimeRange } from '../../services/data';
|
||||
import { convertPanelStateToSavedDashboardPanel } from '../../../common/embeddable/embeddable_saved_object_converters';
|
||||
import {
|
||||
DashboardState,
|
||||
|
@ -74,7 +75,9 @@ export const savedObjectToDashboardState = ({
|
|||
version,
|
||||
usageCollection
|
||||
);
|
||||
|
||||
if (rawState.timeRestore) {
|
||||
rawState.timeRange = { from: savedDashboard.timeFrom, to: savedDashboard.timeTo } as TimeRange;
|
||||
}
|
||||
rawState.controlGroupInput = deserializeControlGroupFromDashboardSavedObject(
|
||||
savedDashboard
|
||||
) as ControlGroupInput;
|
||||
|
@ -106,6 +109,7 @@ export const stateToDashboardContainerInput = ({
|
|||
panels,
|
||||
query,
|
||||
title,
|
||||
timeRestore,
|
||||
} = dashboardState;
|
||||
|
||||
return {
|
||||
|
@ -127,6 +131,7 @@ export const stateToDashboardContainerInput = ({
|
|||
timeRange: {
|
||||
..._.cloneDeep(timefilter.getTime()),
|
||||
},
|
||||
timeRestore,
|
||||
executionContext,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -15,13 +15,7 @@ import { controlGroupInputIsEqual } from './dashboard_control_group';
|
|||
import { DashboardOptions, DashboardPanelMap, DashboardState } from '../../types';
|
||||
import { IEmbeddable } from '../../services/embeddable';
|
||||
|
||||
const stateKeystoIgnore = [
|
||||
'expandedPanelId',
|
||||
'fullScreenMode',
|
||||
'savedQuery',
|
||||
'viewMode',
|
||||
'tags',
|
||||
] as const;
|
||||
const stateKeystoIgnore = ['expandedPanelId', 'fullScreenMode', 'savedQuery', 'viewMode', 'tags'];
|
||||
type DashboardStateToCompare = Omit<DashboardState, typeof stateKeystoIgnore[number]>;
|
||||
|
||||
const inputKeystoIgnore = ['searchSessionId', 'lastReloadRequestTime', 'executionContext'] as const;
|
||||
|
@ -60,6 +54,9 @@ export const diffDashboardState = async ({
|
|||
newState: DashboardState;
|
||||
getEmbeddable: (id: string) => Promise<IEmbeddable>;
|
||||
}): Promise<Partial<DashboardState>> => {
|
||||
if (!newState.timeRestore) {
|
||||
stateKeystoIgnore.push('timeRange');
|
||||
}
|
||||
const {
|
||||
controlGroupInput: originalControlGroupInput,
|
||||
options: originalOptions,
|
||||
|
|
|
@ -20,6 +20,7 @@ import {
|
|||
setFullScreenMode,
|
||||
setPanels,
|
||||
setQuery,
|
||||
setTimeRange,
|
||||
} from '../state';
|
||||
import { diffDashboardContainerInput } from './diff_dashboard_state';
|
||||
import { replaceUrlHashQuery } from '../../../../kibana_utils/public';
|
||||
|
@ -111,6 +112,10 @@ export const applyContainerChangesToState = ({
|
|||
dispatchDashboardStateChange(setQuery(input.query));
|
||||
}
|
||||
|
||||
if (input.timeRestore && !_.isEqual(input.timeRange, latestState.timeRange)) {
|
||||
dispatchDashboardStateChange(setTimeRange(input.timeRange));
|
||||
}
|
||||
|
||||
if (!_.isEqual(input.expandedPanelId, latestState.expandedPanelId)) {
|
||||
dispatchDashboardStateChange(setExpandedPanelId(input.expandedPanelId));
|
||||
}
|
||||
|
|
|
@ -81,6 +81,7 @@ export const syncDashboardFilterState = ({
|
|||
set: ({ filters, query }) => {
|
||||
intermediateFilterState.filters = cleanFiltersForSerialize(filters ?? []) || [];
|
||||
intermediateFilterState.query = query || queryString.getDefaultQuery();
|
||||
applyFilters(intermediateFilterState.query, intermediateFilterState.filters);
|
||||
dispatchDashboardStateChange(setFiltersAndQuery(intermediateFilterState));
|
||||
},
|
||||
state$: $onDashboardStateChange.pipe(
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
|
||||
import { Filter, Query } from '../../services/data';
|
||||
import { Filter, Query, TimeRange } from '../../services/data';
|
||||
import { ViewMode } from '../../services/embeddable';
|
||||
import type { DashboardControlGroupInput } from '../lib/dashboard_control_group';
|
||||
import { DashboardOptions, DashboardPanelMap, DashboardState } from '../../types';
|
||||
|
@ -72,6 +72,9 @@ export const dashboardStateSlice = createSlice({
|
|||
setTimeRestore: (state, action: PayloadAction<boolean>) => {
|
||||
state.timeRestore = action.payload;
|
||||
},
|
||||
setTimeRange: (state, action: PayloadAction<TimeRange>) => {
|
||||
state.timeRange = action.payload;
|
||||
},
|
||||
setDescription: (state, action: PayloadAction<string>) => {
|
||||
state.description = action.payload;
|
||||
},
|
||||
|
@ -109,6 +112,7 @@ export const {
|
|||
setSavedQueryId,
|
||||
setDescription,
|
||||
setTimeRestore,
|
||||
setTimeRange,
|
||||
setSyncColors,
|
||||
setUseMargins,
|
||||
setViewMode,
|
||||
|
|
|
@ -28,6 +28,7 @@ export function getSampleDashboardInput(
|
|||
to: 'now',
|
||||
from: 'now-15m',
|
||||
},
|
||||
timeRestore: false,
|
||||
viewMode: ViewMode.VIEW,
|
||||
panels: {},
|
||||
...overrides,
|
||||
|
|
|
@ -69,6 +69,7 @@ export interface DashboardState {
|
|||
expandedPanelId?: string;
|
||||
options: DashboardOptions;
|
||||
panels: DashboardPanelMap;
|
||||
timeRange?: TimeRange;
|
||||
|
||||
controlGroupInput?: DashboardControlGroupInput;
|
||||
}
|
||||
|
@ -86,6 +87,7 @@ export interface DashboardContainerInput extends ContainerInput {
|
|||
isFullScreenMode: boolean;
|
||||
expandedPanelId?: string;
|
||||
timeRange: TimeRange;
|
||||
timeRestore: boolean;
|
||||
description?: string;
|
||||
useMargins: boolean;
|
||||
syncColors?: boolean;
|
||||
|
|
|
@ -291,6 +291,24 @@ export class DashboardPageObject extends FtrService {
|
|||
});
|
||||
}
|
||||
|
||||
public async clearUnsavedChanges() {
|
||||
this.log.debug('clearUnsavedChanges');
|
||||
let switchMode = false;
|
||||
if (await this.getIsInViewMode()) {
|
||||
await this.switchToEditMode();
|
||||
switchMode = true;
|
||||
}
|
||||
await this.retry.try(async () => {
|
||||
// avoid flaky test by surrounding in retry
|
||||
await this.testSubjects.existOrFail('dashboardUnsavedChangesBadge');
|
||||
await this.clickQuickSave();
|
||||
await this.testSubjects.missingOrFail('dashboardUnsavedChangesBadge');
|
||||
});
|
||||
if (switchMode) {
|
||||
await this.clickCancelOutOfEditMode();
|
||||
}
|
||||
}
|
||||
|
||||
public async clickNewDashboard(continueEditing = false) {
|
||||
const discardButtonExists = await this.testSubjects.exists('discardDashboardPromptButton');
|
||||
if (!continueEditing && discardButtonExists) {
|
||||
|
|
|
@ -45,8 +45,16 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
|
||||
after(async () => {
|
||||
await security.testUser.restoreDefaults();
|
||||
await clearFilters(dashboardDrilldownsManage.DASHBOARD_WITH_PIE_CHART_NAME);
|
||||
await clearFilters(dashboardDrilldownsManage.DASHBOARD_WITH_AREA_CHART_NAME);
|
||||
});
|
||||
|
||||
const clearFilters = async (dashboardName: string) => {
|
||||
await PageObjects.dashboard.gotoDashboardEditMode(dashboardName);
|
||||
await filterBar.removeAllFilters();
|
||||
await PageObjects.dashboard.clearUnsavedChanges();
|
||||
};
|
||||
|
||||
it('create dashboard to dashboard drilldown', async () => {
|
||||
await PageObjects.dashboard.gotoDashboardEditMode(
|
||||
dashboardDrilldownsManage.DASHBOARD_WITH_PIE_CHART_NAME
|
||||
|
@ -68,6 +76,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
expect(await PageObjects.dashboard.getPanelDrilldownCount()).to.be(1);
|
||||
|
||||
// save dashboard, navigate to view mode
|
||||
await testSubjects.existOrFail('dashboardUnsavedChangesBadge');
|
||||
await PageObjects.dashboard.saveDashboard(
|
||||
dashboardDrilldownsManage.DASHBOARD_WITH_PIE_CHART_NAME,
|
||||
{
|
||||
|
@ -76,6 +85,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
exitFromEditMode: true,
|
||||
}
|
||||
);
|
||||
await testSubjects.missingOrFail('dashboardUnsavedChangesBadge');
|
||||
});
|
||||
|
||||
it('use dashboard to dashboard drilldown via onClick action', async () => {
|
||||
|
@ -85,7 +95,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
it('use dashboard to dashboard drilldown via getHref action', async () => {
|
||||
await filterBar.removeAllFilters();
|
||||
await testDashboardDrilldown(
|
||||
dashboardDrilldownPanelActions.openHrefByText.bind(dashboardDrilldownPanelActions) // preserve 'this'
|
||||
);
|
||||
|
@ -130,7 +139,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
);
|
||||
});
|
||||
|
||||
async function testDashboardDrilldown(drilldownAction: (text: string) => Promise<void>) {
|
||||
const testDashboardDrilldown = async (drilldownAction: (text: string) => Promise<void>) => {
|
||||
// trigger drilldown action by clicking on a pie and picking drilldown action by it's name
|
||||
await pieChart.clickOnPieSlice('40,000');
|
||||
await dashboardDrilldownPanelActions.expectMultipleActionsMenuOpened();
|
||||
|
@ -151,14 +160,13 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
|
||||
// check that we drilled-down with filter from pie chart
|
||||
expect(await filterBar.getFilterCount()).to.be(1);
|
||||
|
||||
const originalTimeRangeDurationHours =
|
||||
await PageObjects.timePicker.getTimeDurationInHours();
|
||||
await PageObjects.dashboard.clearUnsavedChanges();
|
||||
|
||||
// brush area chart and drilldown back to pie chat dashboard
|
||||
await brushAreaChart();
|
||||
await dashboardDrilldownPanelActions.expectMultipleActionsMenuOpened();
|
||||
|
||||
await navigateWithinDashboard(async () => {
|
||||
await drilldownAction(DRILLDOWN_TO_PIE_CHART_NAME);
|
||||
});
|
||||
|
@ -166,11 +174,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
// because filters are preserved during navigation, we expect that only one slice is displayed (filter is still applied)
|
||||
expect(await filterBar.getFilterCount()).to.be(1);
|
||||
await pieChart.expectPieSliceCount(1);
|
||||
|
||||
// check that new time range duration was applied
|
||||
const newTimeRangeDurationHours = await PageObjects.timePicker.getTimeDurationInHours();
|
||||
expect(newTimeRangeDurationHours).to.be.lessThan(originalTimeRangeDurationHours);
|
||||
}
|
||||
await PageObjects.dashboard.clearUnsavedChanges();
|
||||
};
|
||||
});
|
||||
|
||||
describe('Copy to space', () => {
|
||||
|
|
|
@ -11,7 +11,6 @@ import { FtrProviderContext } from '../../ftr_provider_context';
|
|||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const esArchiver = getService('esArchiver');
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
const testSubjects = getService('testSubjects');
|
||||
const retry = getService('retry');
|
||||
const dashboardPanelActions = getService('dashboardPanelActions');
|
||||
const PageObjects = getPageObjects([
|
||||
|
@ -43,15 +42,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
describe('panel titles - by value', () => {
|
||||
const clearUnsavedChanges = async () => {
|
||||
await retry.try(async () => {
|
||||
// avoid flaky test by surrounding in retry
|
||||
await testSubjects.existOrFail('dashboardUnsavedChangesBadge');
|
||||
await PageObjects.dashboard.clickQuickSave();
|
||||
await testSubjects.missingOrFail('dashboardUnsavedChangesBadge');
|
||||
});
|
||||
};
|
||||
|
||||
it('new panel by value has empty title', async () => {
|
||||
await PageObjects.lens.createAndAddLensFromDashboard({});
|
||||
const newPanelTitle = (await PageObjects.dashboard.getPanelTitles())[0];
|
||||
|
@ -60,14 +50,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
|
||||
it('saving new panel with blank title clears "unsaved changes" badge', async () => {
|
||||
await dashboardPanelActions.setCustomPanelTitle('');
|
||||
await clearUnsavedChanges();
|
||||
await PageObjects.dashboard.clearUnsavedChanges();
|
||||
});
|
||||
|
||||
it('custom title causes unsaved changes and saving clears it', async () => {
|
||||
await dashboardPanelActions.setCustomPanelTitle(CUSTOM_TITLE);
|
||||
const panelTitle = (await PageObjects.dashboard.getPanelTitles())[0];
|
||||
expect(panelTitle).to.equal(CUSTOM_TITLE);
|
||||
await clearUnsavedChanges();
|
||||
await PageObjects.dashboard.clearUnsavedChanges();
|
||||
});
|
||||
|
||||
it('resetting title on a by value panel sets it to the empty string', async () => {
|
||||
|
@ -77,7 +67,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await dashboardPanelActions.resetCustomPanelTitle();
|
||||
const panelTitle = (await PageObjects.dashboard.getPanelTitles())[0];
|
||||
expect(panelTitle).to.equal(EMPTY_TITLE);
|
||||
await clearUnsavedChanges();
|
||||
await PageObjects.dashboard.clearUnsavedChanges();
|
||||
});
|
||||
|
||||
it('blank titles are hidden in view mode', async () => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue