Skip to content

Commit

Permalink
[Drilldowns] Dashboard to dashboard drilldown (#63108)
Browse files Browse the repository at this point in the history
* partial progress on async loading / searching of dashboard titles

* feat: 🎸 make combobox full width

* filtering combobox polish

* storybook fix

* implement navigating to dashboard, seems like a type problem

* try navToApp

* filter out current dashboard

* rough draft linking to a dashboard

* remove note

* typefix

* fix navigation from dashboard to dashboard

except for back button - that would be addressed separatly

* partial progress getting filters from action data

* fix issue with getIndexPatterns undefined

we can’t import those functions as static functions, instead we have to expose them on plugin contract because they are statefull

* fix filter / time passing into url

* typefix

* dashboard to dashboard drilldown functional test and back button fix

* documentation update

* chore clean-ups

fix type

* basic unit test for dashboard drilldown

* remove test todos

decided to skip those tests because not clear how to test due to EuiCombobox is using react-virtualized and options list is not rendered in jsdom env

* remove config

* improve back button with filter comparison tweak

* dashboard filters/date option off by default

* revert change to config/kibana.yml

* remove unneeded comments

* use default time range as appropriate

* fix type, add filter icon, add text

* fix test

* change how time range is restored and improve back button for drilldowns

* resolve conflicts

* fix async compile issue

* remove redundant test

* wip

* wip

* fix

* temp skip tests

* fix

* handle missing dashboard edge case

* fix api

* refactor action filter creation utils

* updating

* updating docs

* improve

* fix storybook

* post merge fixes

* fix payload emitted in brush event

* properly export createRange action

* improve tests

* add test

* post merge fixes

* improve

* fix

* improve

* fix build

* wip getHref support

* implement getHref()

* give proper name to a story

* use sync start services

* update text

* fix types

* fix ts

* fix docs

* move clone below drilldowns (near replace)

* remove redundant comments

* refactor action filter creation utils

* updating

* updating docs

* fix payload emitted in brush event

* properly export createRange action

* some more updates

* fixing types

* ...

* inline EventData

* fix typescript in lens and update docs

* improve filters types

* docs

* merge

* @mdefazio review

* adjust actions order

* docs

* @stacey-gammon review

Co-authored-by: Matt Kime <matt@mattki.me>
Co-authored-by: streamich <streamich@gmail.com>
Co-authored-by: ppisljar <peter.pisljar@gmail.com>
  • Loading branch information
4 people authored Apr 24, 2020
1 parent cd4693b commit 02524f1
Show file tree
Hide file tree
Showing 57 changed files with 1,504 additions and 227 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ esFilters: {
generateFilters: typeof generateFilters;
onlyDisabledFiltersChanged: (newFilters?: import("../common").Filter[] | undefined, oldFilters?: import("../common").Filter[] | undefined) => boolean;
changeTimeFilter: typeof changeTimeFilter;
convertRangeFilterToTimeRangeString: typeof convertRangeFilterToTimeRangeString;
mapAndFlattenFilters: (filters: import("../common").Filter[]) => import("../common").Filter[];
extractTimeFilter: typeof extractTimeFilter;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export interface ClonePanelActionContext {
export class ClonePanelAction implements ActionByType<typeof ACTION_CLONE_PANEL> {
public readonly type = ACTION_CLONE_PANEL;
public readonly id = ACTION_CLONE_PANEL;
public order = 11;
public order = 45;

constructor(private core: CoreStart) {}

Expand Down
1 change: 1 addition & 0 deletions src/plugins/data/public/actions/apply_filter_action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export function createFilterAction(
return createAction<typeof ACTION_GLOBAL_APPLY_FILTER>({
type: ACTION_GLOBAL_APPLY_FILTER,
id: ACTION_GLOBAL_APPLY_FILTER,
getIconType: () => 'filter',
getDisplayName: () => {
return i18n.translate('data.filter.applyFilterActionTitle', {
defaultMessage: 'Apply filter to current view',
Expand Down
2 changes: 2 additions & 0 deletions src/plugins/data/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import {
changeTimeFilter,
mapAndFlattenFilters,
extractTimeFilter,
convertRangeFilterToTimeRangeString,
} from './query';

// Filter helpers namespace:
Expand Down Expand Up @@ -96,6 +97,7 @@ export const esFilters = {
onlyDisabledFiltersChanged,

changeTimeFilter,
convertRangeFilterToTimeRangeString,
mapAndFlattenFilters,
extractTimeFilter,
};
Expand Down
98 changes: 50 additions & 48 deletions src/plugins/data/public/public.api.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/plugins/data/public/query/timefilter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@ export * from './types';
export { Timefilter, TimefilterContract } from './timefilter';
export { TimeHistory, TimeHistoryContract } from './time_history';
export { getTime } from './get_time';
export { changeTimeFilter } from './lib/change_time_filter';
export { changeTimeFilter, convertRangeFilterToTimeRangeString } from './lib/change_time_filter';
export { extractTimeFilter } from './lib/extract_time_filter';
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import moment from 'moment';
import { keys } from 'lodash';
import { TimefilterContract } from '../../timefilter';
import { RangeFilter } from '../../../../common';
import { RangeFilter, TimeRange } from '../../../../common';

export function convertRangeFilterToTimeRange(filter: RangeFilter) {
const key = keys(filter.range)[0];
Expand All @@ -32,6 +32,14 @@ export function convertRangeFilterToTimeRange(filter: RangeFilter) {
};
}

export function convertRangeFilterToTimeRangeString(filter: RangeFilter): TimeRange {
const { from, to } = convertRangeFilterToTimeRange(filter);
return {
from: from?.toISOString(),
to: to?.toISOString(),
};
}

export function changeTimeFilter(timeFilter: TimefilterContract, filter: RangeFilter) {
timeFilter.setTime(convertRangeFilterToTimeRange(filter));
}
2 changes: 2 additions & 0 deletions src/plugins/embeddable/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ export {
withEmbeddableSubscription,
SavedObjectEmbeddableInput,
isSavedObjectEmbeddableInput,
isRangeSelectTriggerContext,
isValueClickTriggerContext,
} from './lib';

export function plugin(initializerContext: PluginInitializerContext) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ function renderNotifications(
) {
return notifications.map(notification => (
<EuiNotificationBadge
data-test-subj={`embeddablePanelNotification-${notification.id}`}
key={notification.id}
style={{ marginTop: '4px', marginRight: '4px' }}
onClick={() => notification.execute({ embeddable })}
Expand Down
18 changes: 13 additions & 5 deletions src/plugins/embeddable/public/lib/triggers/triggers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,16 @@
* under the License.
*/

import { Trigger } from '../../../../ui_actions/public';
import { KibanaDatatable } from '../../../../expressions';
import { Trigger } from '../../../../ui_actions/public';
import { IEmbeddable } from '..';

export interface EmbeddableContext {
embeddable: IEmbeddable;
}

export interface ValueClickTriggerContext {
embeddable?: IEmbeddable;
export interface ValueClickTriggerContext<T extends IEmbeddable = IEmbeddable> {
embeddable?: T;
timeFieldName?: string;
data: {
data: Array<{
Expand All @@ -39,8 +39,12 @@ export interface ValueClickTriggerContext {
};
}

export interface RangeSelectTriggerContext {
embeddable?: IEmbeddable;
export const isValueClickTriggerContext = (
context: ValueClickTriggerContext | RangeSelectTriggerContext
): context is ValueClickTriggerContext => context.data && 'data' in context.data;

export interface RangeSelectTriggerContext<T extends IEmbeddable = IEmbeddable> {
embeddable?: T;
timeFieldName?: string;
data: {
table: KibanaDatatable;
Expand All @@ -49,6 +53,10 @@ export interface RangeSelectTriggerContext {
};
}

export const isRangeSelectTriggerContext = (
context: ValueClickTriggerContext | RangeSelectTriggerContext
): context is RangeSelectTriggerContext => context.data && 'range' in context.data;

export const CONTEXT_MENU_TRIGGER = 'CONTEXT_MENU_TRIGGER';
export const contextMenuTrigger: Trigger<'CONTEXT_MENU_TRIGGER'> = {
id: CONTEXT_MENU_TRIGGER,
Expand Down
1 change: 1 addition & 0 deletions src/plugins/kibana_utils/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export {
} from './state_sync';
export { removeQueryParam, redirectWhenMissing, ensureDefaultIndexPattern } from './history';
export { applyDiff } from './state_management/utils/diff_object';
export { createStartServicesGetter } from './core/create_start_service_getter';

/** dummy plugin, we just want kibanaUtils to have its own bundle */
export function plugin() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,11 @@ export function openContextMenu(
anchorPosition="downRight"
withTitle
>
<EuiContextMenu initialPanelId="mainMenu" panels={panels} />
<EuiContextMenu
initialPanelId="mainMenu"
panels={panels}
data-test-subj={props['data-test-subj']}
/>
</EuiPopover>,
container
);
Expand Down
4 changes: 3 additions & 1 deletion src/plugins/ui_actions/public/triggers/trigger_internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ export class TriggerInternal<T extends TriggerId> {
title: this.trigger.title,
closeMenu: () => session.close(),
});
const session = openContextMenu([panel]);
const session = openContextMenu([panel], {
'data-test-subj': 'multipleActionsContextMenu',
});
}
}
2 changes: 1 addition & 1 deletion src/plugins/ui_actions/public/util/configurable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* under the License.
*/

import { UiComponent } from 'src/plugins/kibana_utils/common';
import { UiComponent } from 'src/plugins/kibana_utils/public';

/**
* Represents something that can be configured by user using UI.
Expand Down
27 changes: 23 additions & 4 deletions test/functional/page_objects/dashboard_page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,16 +104,21 @@ export function DashboardPageProvider({ getService, getPageObjects }: FtrProvide

public async getDashboardIdFromCurrentUrl() {
const currentUrl = await browser.getCurrentUrl();
const urlSubstring = 'kibana#/dashboard/';
const startOfIdIndex = currentUrl.indexOf(urlSubstring) + urlSubstring.length;
const endIndex = currentUrl.indexOf('?');
const id = currentUrl.substring(startOfIdIndex, endIndex < 0 ? currentUrl.length : endIndex);
const id = this.getDashboardIdFromUrl(currentUrl);

log.debug(`Dashboard id extracted from ${currentUrl} is ${id}`);

return id;
}

public getDashboardIdFromUrl(url: string) {
const urlSubstring = 'kibana#/dashboard/';
const startOfIdIndex = url.indexOf(urlSubstring) + urlSubstring.length;
const endIndex = url.indexOf('?');
const id = url.substring(startOfIdIndex, endIndex < 0 ? url.length : endIndex);
return id;
}

/**
* Returns true if already on the dashboard landing page (that page doesn't have a link to itself).
* @returns {Promise<boolean>}
Expand Down Expand Up @@ -512,6 +517,20 @@ export function DashboardPageProvider({ getService, getPageObjects }: FtrProvide

return checkList.filter(viz => viz.isPresent === false).map(viz => viz.name);
}

public async getPanelDrilldownCount(panelIndex = 0): Promise<number> {
log.debug('getPanelDrilldownCount');
const panel = (await this.getDashboardPanels())[panelIndex];
try {
const count = await panel.findByTestSubject(
'embeddablePanelNotification-ACTION_PANEL_NOTIFICATIONS'
);
return Number.parseInt(await count.getVisibleText(), 10);
} catch (e) {
// if not found then this is 0 (we don't show badge with 0)
return 0;
}
}
}

return new DashboardPage();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ test('Pick and configure action', () => {
const screen = render(<Demo actionFactories={[dashboardFactory, urlFactory]} />);

// check that all factories are displayed to pick
expect(screen.getAllByTestId(TEST_SUBJ_ACTION_FACTORY_ITEM)).toHaveLength(2);
expect(screen.getAllByTestId(new RegExp(TEST_SUBJ_ACTION_FACTORY_ITEM))).toHaveLength(2);

// select URL one
fireEvent.click(screen.getByText(/Go to URL/i));
Expand All @@ -43,8 +43,8 @@ test('If only one actions factory is available then actionFactory selection is e
const screen = render(<Demo actionFactories={[urlFactory]} />);

// check that no factories are displayed to pick from
expect(screen.queryByTestId(TEST_SUBJ_ACTION_FACTORY_ITEM)).not.toBeInTheDocument();
expect(screen.queryByTestId(TEST_SUBJ_SELECTED_ACTION_FACTORY)).toBeInTheDocument();
expect(screen.queryByTestId(new RegExp(TEST_SUBJ_ACTION_FACTORY_ITEM))).not.toBeInTheDocument();
expect(screen.queryByTestId(new RegExp(TEST_SUBJ_SELECTED_ACTION_FACTORY))).toBeInTheDocument();

// Input url
const URL = 'https://elastic.co';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ interface SelectedActionFactoryProps {
onDeselect: () => void;
}

export const TEST_SUBJ_SELECTED_ACTION_FACTORY = 'selected-action-factory';
export const TEST_SUBJ_SELECTED_ACTION_FACTORY = 'selectedActionFactory';

const SelectedActionFactory: React.FC<SelectedActionFactoryProps> = ({
actionFactory,
Expand All @@ -118,7 +118,7 @@ const SelectedActionFactory: React.FC<SelectedActionFactoryProps> = ({
return (
<div
className="auaActionWizard__selectedActionFactoryContainer"
data-test-subj={TEST_SUBJ_SELECTED_ACTION_FACTORY}
data-test-subj={`${TEST_SUBJ_SELECTED_ACTION_FACTORY}-${actionFactory.id}`}
>
<header>
<EuiFlexGroup alignItems="center" gutterSize="s">
Expand Down Expand Up @@ -159,7 +159,7 @@ interface ActionFactorySelectorProps {
onActionFactorySelected: (actionFactory: ActionFactory) => void;
}

export const TEST_SUBJ_ACTION_FACTORY_ITEM = 'action-factory-item';
export const TEST_SUBJ_ACTION_FACTORY_ITEM = 'actionFactoryItem';

const ActionFactorySelector: React.FC<ActionFactorySelectorProps> = ({
actionFactories,
Expand All @@ -181,7 +181,7 @@ const ActionFactorySelector: React.FC<ActionFactorySelectorProps> = ({
<EuiKeyPadMenuItem
className="auaActionWizard__actionFactoryItem"
label={actionFactory.getDisplayName(context)}
data-test-subj={TEST_SUBJ_ACTION_FACTORY_ITEM}
data-test-subj={`${TEST_SUBJ_ACTION_FACTORY_ITEM}-${actionFactory.id}`}
onClick={() => onActionFactorySelected(actionFactory)}
>
{actionFactory.getIconType(context) && (
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/dashboard_enhanced/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
"version": "kibana",
"server": false,
"ui": true,
"requiredPlugins": ["uiActions", "embeddable", "dashboard", "drilldowns"],
"requiredPlugins": ["data","uiActions", "embeddable", "dashboard", "drilldowns", "share"],
"configPath": ["xpack", "dashboardEnhanced"]
}
5 changes: 0 additions & 5 deletions x-pack/plugins/dashboard_enhanced/public/components/README.md

This file was deleted.

5 changes: 5 additions & 0 deletions x-pack/plugins/dashboard_enhanced/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,26 @@
*/

import { CoreStart, CoreSetup, Plugin, PluginInitializerContext } from 'src/core/public';
import { SharePluginStart, SharePluginSetup } from '../../../../src/plugins/share/public';
import { UiActionsSetup, UiActionsStart } from '../../../../src/plugins/ui_actions/public';
import { EmbeddableSetup, EmbeddableStart } from '../../../../src/plugins/embeddable/public';
import { DashboardDrilldownsService } from './services';
import { DataPublicPluginStart } from '../../../../src/plugins/data/public';
import { DrilldownsSetup, DrilldownsStart } from '../../drilldowns/public';

export interface SetupDependencies {
drilldowns: DrilldownsSetup;
embeddable: EmbeddableSetup;
uiActions: UiActionsSetup;
share: SharePluginSetup;
}

export interface StartDependencies {
drilldowns: DrilldownsStart;
embeddable: EmbeddableStart;
uiActions: UiActionsStart;
share: SharePluginStart;
data: DataPublicPluginStart;
}

// eslint-disable-next-line
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ const drilldowns = drilldownsPluginMock.createStartContract();
const uiActions = uiActionsPluginMock.createStartContract();

const actionParams: OpenFlyoutAddDrilldownParams = {
drilldowns: () => Promise.resolve(drilldowns),
overlays: () => Promise.resolve(overlays),
drilldowns: () => drilldowns,
overlays: () => overlays,
};

test('should create', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import { EmbeddableContext } from '../../../../../../../../src/plugins/embeddabl
export const OPEN_FLYOUT_ADD_DRILLDOWN = 'OPEN_FLYOUT_ADD_DRILLDOWN';

export interface OpenFlyoutAddDrilldownParams {
overlays: () => Promise<CoreStart['overlays']>;
drilldowns: () => Promise<DrilldownsStart>;
overlays: () => CoreStart['overlays'];
drilldowns: () => DrilldownsStart;
}

export class FlyoutCreateDrilldownAction implements ActionByType<typeof OPEN_FLYOUT_ADD_DRILLDOWN> {
Expand Down Expand Up @@ -50,8 +50,8 @@ export class FlyoutCreateDrilldownAction implements ActionByType<typeof OPEN_FLY
}

public async execute(context: EmbeddableContext) {
const overlays = await this.params.overlays();
const drilldowns = await this.params.drilldowns();
const overlays = this.params.overlays();
const drilldowns = this.params.drilldowns();
const { embeddable } = context;

if (!isEnhancedEmbeddable(embeddable)) {
Expand All @@ -71,6 +71,7 @@ export class FlyoutCreateDrilldownAction implements ActionByType<typeof OPEN_FLY
),
{
ownFocus: true,
'data-test-subj': 'createDrilldownFlyout',
}
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ uiActionsPlugin.setup.registerActionFactory({
});

const actionParams: FlyoutEditDrilldownParams = {
drilldowns: () => Promise.resolve(drilldowns),
overlays: () => Promise.resolve(overlays),
drilldowns: () => drilldowns,
overlays: () => overlays,
};

test('should create', () => {
Expand Down
Loading

0 comments on commit 02524f1

Please sign in to comment.