mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
* Translate dashboard state to URL conditionally
* Add functional tests
* Fix typo in functional test descriptions
* Remove deprecated references
* Rename useUrl to be more specific
(cherry picked from commit 17a997cccb
)
# Conflicts:
# src/plugins/dashboard/public/index.ts
This commit is contained in:
parent
7e2bf92f90
commit
3a6bf011d4
8 changed files with 123 additions and 64 deletions
|
@ -25,7 +25,11 @@ export type {
|
|||
|
||||
export type { DashboardUrlGeneratorState } from './url_generator';
|
||||
export { DASHBOARD_APP_URL_GENERATOR, createDashboardUrlGenerator } from './url_generator';
|
||||
export type { DashboardAppLocator, DashboardAppLocatorParams } from './locator';
|
||||
export {
|
||||
type DashboardAppLocator,
|
||||
type DashboardAppLocatorParams,
|
||||
cleanEmptyKeys,
|
||||
} from './locator';
|
||||
|
||||
export type { DashboardSavedObject } from './saved_dashboards';
|
||||
export type { SavedDashboardPanel, DashboardContainerInput } from './types';
|
||||
|
|
|
@ -22,7 +22,7 @@ import { DashboardConstants } from './dashboard_constants';
|
|||
*/
|
||||
const getSerializableRecord: <O>(o: O) => O & SerializableRecord = flow(JSON.stringify, JSON.parse);
|
||||
|
||||
const cleanEmptyKeys = (stateObj: Record<string, unknown>) => {
|
||||
export const cleanEmptyKeys = (stateObj: Record<string, unknown>) => {
|
||||
Object.keys(stateObj).forEach((key) => {
|
||||
if (stateObj[key] === undefined) {
|
||||
delete stateObj[key];
|
||||
|
|
|
@ -202,6 +202,18 @@ export class WebElementWrapper {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* If possible, opens 'href' of this element directly through the URL
|
||||
*
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public async openHref() {
|
||||
const href = await this.getAttribute('href');
|
||||
if (href) {
|
||||
await this.driver.get(href);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if webelement wrapper has a specific class.
|
||||
*
|
||||
|
|
|
@ -45,7 +45,11 @@ export abstract class AbstractDashboardDrilldown<Context extends object = object
|
|||
|
||||
public abstract readonly supportedTriggers: () => string[];
|
||||
|
||||
protected abstract getLocation(config: Config, context: Context): Promise<KibanaLocation>;
|
||||
protected abstract getLocation(
|
||||
config: Config,
|
||||
context: Context,
|
||||
useUrlForState: boolean
|
||||
): Promise<KibanaLocation>;
|
||||
|
||||
public readonly order = 100;
|
||||
|
||||
|
@ -73,7 +77,7 @@ export abstract class AbstractDashboardDrilldown<Context extends object = object
|
|||
};
|
||||
|
||||
public readonly getHref = async (config: Config, context: Context): Promise<string> => {
|
||||
const { app, path } = await this.getLocation(config, context);
|
||||
const { app, path } = await this.getLocation(config, context, true);
|
||||
const url = await this.params.start().core.application.getUrlForApp(app, {
|
||||
path,
|
||||
absolute: true,
|
||||
|
@ -82,7 +86,7 @@ export abstract class AbstractDashboardDrilldown<Context extends object = object
|
|||
};
|
||||
|
||||
public readonly execute = async (config: Config, context: Context) => {
|
||||
const { app, path, state } = await this.getLocation(config, context);
|
||||
const { app, path, state } = await this.getLocation(config, context, false);
|
||||
await this.params.start().core.application.navigateToApp(app, {
|
||||
path,
|
||||
state,
|
||||
|
|
|
@ -5,20 +5,12 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { Filter, RangeFilter } from '@kbn/es-query';
|
||||
import { EmbeddableToDashboardDrilldown } from './embeddable_to_dashboard_drilldown';
|
||||
import { AbstractDashboardDrilldownConfig as Config } from '../abstract_dashboard_drilldown';
|
||||
import { savedObjectsServiceMock } from '../../../../../../../src/core/public/mocks';
|
||||
import {
|
||||
Filter,
|
||||
FilterStateStore,
|
||||
Query,
|
||||
RangeFilter,
|
||||
TimeRange,
|
||||
} from '../../../../../../../src/plugins/data/common';
|
||||
import {
|
||||
ApplyGlobalFilterActionContext,
|
||||
esFilters,
|
||||
} from '../../../../../../../src/plugins/data/public';
|
||||
import { FilterStateStore, Query, TimeRange } from '../../../../../../../src/plugins/data/common';
|
||||
import { ApplyGlobalFilterActionContext } from '../../../../../../../src/plugins/data/public';
|
||||
import {
|
||||
DashboardAppLocatorDefinition,
|
||||
DashboardAppLocatorParams,
|
||||
|
@ -318,7 +310,7 @@ describe('.execute() & getHref', () => {
|
|||
function getFilter(isPinned: boolean, queryKey: string): Filter {
|
||||
return {
|
||||
$state: {
|
||||
store: isPinned ? esFilters.FilterStateStore.GLOBAL_STATE : FilterStateStore.APP_STATE,
|
||||
store: isPinned ? FilterStateStore.GLOBAL_STATE : FilterStateStore.APP_STATE,
|
||||
},
|
||||
meta: {
|
||||
index: 'logstash-*',
|
||||
|
|
|
@ -4,19 +4,21 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { type Filter, isFilters, isFilterPinned } from '@kbn/es-query';
|
||||
import type { KibanaLocation } from 'src/plugins/share/public';
|
||||
import { DashboardAppLocatorParams } from '../../../../../../../src/plugins/dashboard/public';
|
||||
import {
|
||||
DashboardAppLocatorParams,
|
||||
cleanEmptyKeys,
|
||||
} from '../../../../../../../src/plugins/dashboard/public';
|
||||
import { setStateToKbnUrl } from '../../../../../../../src/plugins/kibana_utils/public';
|
||||
import {
|
||||
ApplyGlobalFilterActionContext,
|
||||
APPLY_FILTER_TRIGGER,
|
||||
esFilters,
|
||||
Filter,
|
||||
isFilters,
|
||||
isQuery,
|
||||
isTimeRange,
|
||||
Query,
|
||||
TimeRange,
|
||||
extractTimeRange,
|
||||
} from '../../../../../../../src/plugins/data/public';
|
||||
import { IEmbeddable, EmbeddableInput } from '../../../../../../../src/plugins/embeddable/public';
|
||||
import {
|
||||
|
@ -49,7 +51,11 @@ export class EmbeddableToDashboardDrilldown extends AbstractDashboardDrilldown<C
|
|||
|
||||
public readonly supportedTriggers = () => [APPLY_FILTER_TRIGGER];
|
||||
|
||||
protected async getLocation(config: Config, context: Context): Promise<KibanaLocation> {
|
||||
protected async getLocation(
|
||||
config: Config,
|
||||
context: Context,
|
||||
useUrlForState: boolean
|
||||
): Promise<KibanaLocation> {
|
||||
const params: DashboardAppLocatorParams = {
|
||||
dashboardId: config.dashboardId,
|
||||
};
|
||||
|
@ -70,11 +76,13 @@ export class EmbeddableToDashboardDrilldown extends AbstractDashboardDrilldown<C
|
|||
if (isFilters(input.filters))
|
||||
params.filters = config.useCurrentFilters
|
||||
? input.filters
|
||||
: input.filters?.filter((f) => esFilters.isFilterPinned(f));
|
||||
: input.filters?.filter((f) => isFilterPinned(f));
|
||||
}
|
||||
|
||||
const { restOfFilters: filtersFromEvent, timeRange: timeRangeFromEvent } =
|
||||
esFilters.extractTimeRange(context.filters, context.timeFieldName);
|
||||
const { restOfFilters: filtersFromEvent, timeRange: timeRangeFromEvent } = extractTimeRange(
|
||||
context.filters,
|
||||
context.timeFieldName
|
||||
);
|
||||
|
||||
if (filtersFromEvent) {
|
||||
params.filters = [...(params.filters ?? []), ...filtersFromEvent];
|
||||
|
@ -85,10 +93,27 @@ export class EmbeddableToDashboardDrilldown extends AbstractDashboardDrilldown<C
|
|||
}
|
||||
|
||||
const location = await this.locator.getLocation(params);
|
||||
if (useUrlForState) {
|
||||
this.useUrlForState(location);
|
||||
}
|
||||
|
||||
return location;
|
||||
}
|
||||
|
||||
private useUrlForState(location: KibanaLocation<DashboardAppLocatorParams>) {
|
||||
const state = location.state;
|
||||
location.path = setStateToKbnUrl(
|
||||
'_a',
|
||||
cleanEmptyKeys({
|
||||
query: state.query,
|
||||
filters: state.filters?.filter((f) => !isFilterPinned(f)),
|
||||
savedQuery: state.savedQuery,
|
||||
}),
|
||||
{ useHash: false, storeInHashQuery: true },
|
||||
location.path
|
||||
);
|
||||
}
|
||||
|
||||
public readonly inject = createInject({ drilldownId: this.id });
|
||||
|
||||
public readonly extract = createExtract({ drilldownId: this.id });
|
||||
|
|
|
@ -47,7 +47,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await security.testUser.restoreDefaults();
|
||||
});
|
||||
|
||||
it('should create dashboard to dashboard drilldown, use it, and then delete it', async () => {
|
||||
it('create dashboard to dashboard drilldown', async () => {
|
||||
await PageObjects.dashboard.gotoDashboardEditMode(
|
||||
dashboardDrilldownsManage.DASHBOARD_WITH_PIE_CHART_NAME
|
||||
);
|
||||
|
@ -76,47 +76,22 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
exitFromEditMode: true,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// trigger drilldown action by clicking on a pie and picking drilldown action by it's name
|
||||
await pieChart.clickOnPieSlice('40,000');
|
||||
await dashboardDrilldownPanelActions.expectMultipleActionsMenuOpened();
|
||||
|
||||
const href = await dashboardDrilldownPanelActions.getActionHrefByText(
|
||||
DRILLDOWN_TO_AREA_CHART_NAME
|
||||
it('use dashboard to dashboard drilldown via onClick action', async () => {
|
||||
await testDashboardDrilldown(
|
||||
dashboardDrilldownPanelActions.clickActionByText.bind(dashboardDrilldownPanelActions) // preserve 'this'
|
||||
);
|
||||
expect(typeof href).to.be('string'); // checking that action has a href
|
||||
const dashboardIdFromHref = PageObjects.dashboard.getDashboardIdFromUrl(href);
|
||||
});
|
||||
|
||||
await navigateWithinDashboard(async () => {
|
||||
await dashboardDrilldownPanelActions.clickActionByText(DRILLDOWN_TO_AREA_CHART_NAME);
|
||||
});
|
||||
// checking that href is at least pointing to the same dashboard that we are navigated to by regular click
|
||||
expect(dashboardIdFromHref).to.be(
|
||||
await PageObjects.dashboard.getDashboardIdFromCurrentUrl()
|
||||
it('use dashboard to dashboard drilldown via getHref action', async () => {
|
||||
await filterBar.removeAllFilters();
|
||||
await testDashboardDrilldown(
|
||||
dashboardDrilldownPanelActions.openHrefByText.bind(dashboardDrilldownPanelActions) // preserve 'this'
|
||||
);
|
||||
});
|
||||
|
||||
// check that we drilled-down with filter from pie chart
|
||||
expect(await filterBar.getFilterCount()).to.be(1);
|
||||
|
||||
const originalTimeRangeDurationHours =
|
||||
await PageObjects.timePicker.getTimeDurationInHours();
|
||||
|
||||
// brush area chart and drilldown back to pie chat dashboard
|
||||
await brushAreaChart();
|
||||
await dashboardDrilldownPanelActions.expectMultipleActionsMenuOpened();
|
||||
|
||||
await navigateWithinDashboard(async () => {
|
||||
await dashboardDrilldownPanelActions.clickActionByText(DRILLDOWN_TO_PIE_CHART_NAME);
|
||||
});
|
||||
|
||||
// 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);
|
||||
|
||||
it('delete dashboard to dashboard drilldown', async () => {
|
||||
// delete drilldown
|
||||
await PageObjects.dashboard.switchToEditMode();
|
||||
await dashboardPanelActions.openContextMenu();
|
||||
|
@ -127,7 +102,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await dashboardDrilldownsManage.deleteDrilldownsByTitles([DRILLDOWN_TO_AREA_CHART_NAME]);
|
||||
await dashboardDrilldownsManage.closeFlyout();
|
||||
|
||||
// check that drilldown notification badge is shown
|
||||
// check that drilldown notification badge is not shown
|
||||
expect(await PageObjects.dashboard.getPanelDrilldownCount()).to.be(0);
|
||||
});
|
||||
|
||||
|
@ -154,6 +129,48 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
originalTimeRangeDurationHours
|
||||
);
|
||||
});
|
||||
|
||||
async function testDashboardDrilldown(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();
|
||||
|
||||
const href = await dashboardDrilldownPanelActions.getActionHrefByText(
|
||||
DRILLDOWN_TO_AREA_CHART_NAME
|
||||
);
|
||||
expect(typeof href).to.be('string'); // checking that action has a href
|
||||
const dashboardIdFromHref = PageObjects.dashboard.getDashboardIdFromUrl(href);
|
||||
|
||||
await navigateWithinDashboard(async () => {
|
||||
await drilldownAction(DRILLDOWN_TO_AREA_CHART_NAME);
|
||||
});
|
||||
// checking that href is at least pointing to the same dashboard that we are navigated to by regular click
|
||||
expect(dashboardIdFromHref).to.be(
|
||||
await PageObjects.dashboard.getDashboardIdFromCurrentUrl()
|
||||
);
|
||||
|
||||
// check that we drilled-down with filter from pie chart
|
||||
expect(await filterBar.getFilterCount()).to.be(1);
|
||||
|
||||
const originalTimeRangeDurationHours =
|
||||
await PageObjects.timePicker.getTimeDurationInHours();
|
||||
|
||||
// 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);
|
||||
});
|
||||
|
||||
// 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);
|
||||
}
|
||||
});
|
||||
|
||||
describe('Copy to space', () => {
|
||||
|
|
|
@ -64,6 +64,11 @@ export function DashboardDrilldownPanelActionsProvider({ getService }: FtrProvid
|
|||
return item.getAttribute('href');
|
||||
}
|
||||
|
||||
async openHrefByText(text: string) {
|
||||
log.debug(`openHref: "${text}"`);
|
||||
(await this.getActionWebElementByText(text)).openHref();
|
||||
}
|
||||
|
||||
async getActionWebElementByText(text: string): Promise<WebElementWrapper> {
|
||||
log.debug(`getActionWebElement: "${text}"`);
|
||||
const menu = await testSubjects.find('multipleActionsContextMenu');
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue