Skip to content

Commit

Permalink
Explore underlying data (#68496)
Browse files Browse the repository at this point in the history
* feat: ๐ŸŽธ stub discover_enhanced plugin

* feat: ๐ŸŽธ improve view in discover action

* feat: ๐ŸŽธ add URL generator to "View in Discover" action

* feat: ๐ŸŽธ implement navigation and getHref in view raw logs actio

* fix: ๐Ÿ› disable action in "edit" mode

* refactor: ๐Ÿ’ก renamce context menu view in discover action

* feat: ๐ŸŽธ rename action to "explore data"

* fix: ๐Ÿ› correctly generate action path

* feat: ๐ŸŽธ add internationalization to "explore action"

* fix: ๐Ÿ› correctly parse generated Discover URL path

* test: ๐Ÿ’ setup basic functional tests

* refactor: ๐Ÿ’ก modularize url generation logic

* feat: ๐ŸŽธ export CommonlyUsed type

* test: ๐Ÿ’ add test subjects to panel custom time range modal

* test: ๐Ÿ’ add index patterna and time range functional tests

* refactor: ๐Ÿ’ก rename action file

* refactor: ๐Ÿ’ก use URL generator from Discover plugin's contract

* test: ๐Ÿ’ add "Explore raw data" action unit tests

* fix: ๐Ÿ› import share plugin to check if it is enabled

* Update x-pack/plugins/discover_enhanced/public/actions/view_in_discover/explore_data_context_menu_action.ts

Co-authored-by: Matthias Wilhelm <ankertal@gmail.com>

* chore: ๐Ÿค– add discover_enhanced to KibanaApp codeowners

* test: ๐Ÿ’ improve "Explore underlying data" functional tests

* test: ๐Ÿ’ improve <a> link assertion

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
Co-authored-by: Matthias Wilhelm <ankertal@gmail.com>
  • Loading branch information
3 people authored Jun 17, 2020
1 parent effd504 commit 3ee0bf2
Show file tree
Hide file tree
Showing 23 changed files with 963 additions and 36 deletions.
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules โ€ฆ
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

# App
/x-pack/plugins/dashboard_enhanced/ @elastic/kibana-app
/x-pack/plugins/discover_enhanced/ @elastic/kibana-app
/x-pack/plugins/lens/ @elastic/kibana-app
/x-pack/plugins/graph/ @elastic/kibana-app
/src/legacy/core_plugins/kibana/public/local_application_service/ @elastic/kibana-app
Expand Down
17 changes: 17 additions & 0 deletions test/functional/page_objects/discover_page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,23 @@ export function DiscoverPageProvider({ getService, getPageObjects }: FtrProvider
const nr = await el.getAttribute('data-fetch-counter');
return Number(nr);
}

/**
* Check if Discover app is currently rendered on the screen.
*/
public async isDiscoverAppOnScreen(): Promise<boolean> {
const result = await find.allByCssSelector('discover-app');
return result.length === 1;
}

/**
* Wait until Discover app is rendered on the screen.
*/
public async waitForDiscoverAppOnScreen() {
await retry.waitFor('Discover app on screen', async () => {
return await this.isDiscoverAppOnScreen();
});
}
}

return new DiscoverPage();
Expand Down
30 changes: 18 additions & 12 deletions test/functional/page_objects/time_picker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,18 @@ import moment from 'moment';
import { FtrProviderContext } from '../ftr_provider_context.d';
import { WebElementWrapper } from '../services/lib/web_element_wrapper';

export type CommonlyUsed =
| 'Today'
| 'This_week'
| 'Last_15 minutes'
| 'Last_30 minutes'
| 'Last_1 hour'
| 'Last_24 hours'
| 'Last_7 days'
| 'Last_30 days'
| 'Last_90 days'
| 'Last_1 year';

export function TimePickerProvider({ getService, getPageObjects }: FtrProviderContext) {
const log = getService('log');
const retry = getService('retry');
Expand All @@ -30,18 +42,6 @@ export function TimePickerProvider({ getService, getPageObjects }: FtrProviderCo
const { header, common } = getPageObjects(['header', 'common']);
const kibanaServer = getService('kibanaServer');

type CommonlyUsed =
| 'Today'
| 'This_week'
| 'Last_15 minutes'
| 'Last_30 minutes'
| 'Last_1 hour'
| 'Last_24 hours'
| 'Last_7 days'
| 'Last_30 days'
| 'Last_90 days'
| 'Last_1 year';

class TimePicker {
defaultStartTime = 'Sep 19, 2015 @ 06:31:44.000';
defaultEndTime = 'Sep 23, 2015 @ 18:31:44.000';
Expand Down Expand Up @@ -227,6 +227,12 @@ export function TimePickerProvider({ getService, getPageObjects }: FtrProviderCo
};
}

public async getShowDatesButtonText() {
const button = await testSubjects.find('superDatePickerShowDatesButton');
const text = await button.getVisibleText();
return text;
}

public async getTimeDurationForSharing() {
return await testSubjects.getAttribute(
'dataSharedTimefilterDuration',
Expand Down
1 change: 1 addition & 0 deletions x-pack/.i18nrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"xpack.beatsManagement": ["legacy/plugins/beats_management", "plugins/beats_management"],
"xpack.canvas": "plugins/canvas",
"xpack.dashboard": "plugins/dashboard_enhanced",
"xpack.discover": "plugins/discover_enhanced",
"xpack.crossClusterReplication": "plugins/cross_cluster_replication",
"xpack.dashboardMode": "legacy/plugins/dashboard_mode",
"xpack.data": "plugins/data_enhanced",
Expand Down
9 changes: 9 additions & 0 deletions x-pack/plugins/discover_enhanced/kibana.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"id": "discoverEnhanced",
"version": "8.0.0",
"kibanaVersion": "kibana",
"server": false,
"ui": true,
"requiredPlugins": ["uiActions", "embeddable", "discover"],
"optionalPlugins": ["share"]
}
7 changes: 7 additions & 0 deletions x-pack/plugins/discover_enhanced/public/actions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

export * from './view_in_discover';
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import {
ExploreDataContextMenuAction,
ACTION_EXPLORE_DATA,
Params,
PluginDeps,
} from './explore_data_context_menu_action';
import { coreMock } from '../../../../../../src/core/public/mocks';
import { UrlGeneratorContract } from '../../../../../../src/plugins/share/public';
import { i18n } from '@kbn/i18n';
import {
VisualizeEmbeddableContract,
VISUALIZE_EMBEDDABLE_TYPE,
} from '../../../../../../src/plugins/visualizations/public';
import { ViewMode } from '../../../../../../src/plugins/embeddable/public';

const i18nTranslateSpy = (i18n.translate as unknown) as jest.SpyInstance;

jest.mock('@kbn/i18n', () => ({
i18n: {
translate: jest.fn((key, options) => options.defaultMessage),
},
}));

afterEach(() => {
i18nTranslateSpy.mockClear();
});

const setup = () => {
type UrlGenerator = UrlGeneratorContract<'DISCOVER_APP_URL_GENERATOR'>;

const core = coreMock.createStart();

const urlGenerator: UrlGenerator = ({
id: ACTION_EXPLORE_DATA,
createUrl: jest.fn(() => Promise.resolve('/xyz/app/discover/foo#bar')),
} as unknown) as UrlGenerator;

const plugins: PluginDeps = {
discover: {
urlGenerator,
},
};

const params: Params = {
start: () => ({
plugins,
self: {},
core,
}),
};
const action = new ExploreDataContextMenuAction(params);

const input = {
viewMode: ViewMode.VIEW,
};

const output = {
indexPatterns: [
{
id: 'index-ptr-foo',
},
],
};

const embeddable: VisualizeEmbeddableContract = ({
type: VISUALIZE_EMBEDDABLE_TYPE,
getInput: () => input,
getOutput: () => output,
} as unknown) as VisualizeEmbeddableContract;

const context = {
embeddable,
};

return { core, plugins, urlGenerator, params, action, input, output, embeddable, context };
};

describe('"Explore underlying data" panel action', () => {
test('action has Discover icon', () => {
const { action } = setup();
expect(action.getIconType()).toBe('discoverApp');
});

test('title is "Explore underlying data"', () => {
const { action } = setup();
expect(action.getDisplayName()).toBe('Explore underlying data');
});

test('translates title', () => {
expect(i18nTranslateSpy).toHaveBeenCalledTimes(0);

setup().action.getDisplayName();

expect(i18nTranslateSpy).toHaveBeenCalledTimes(1);
expect(i18nTranslateSpy.mock.calls[0][0]).toBe(
'xpack.discover.FlyoutCreateDrilldownAction.displayName'
);
});

describe('isCompatible()', () => {
test('returns true when all conditions are met', async () => {
const { action, context } = setup();

const isCompatible = await action.isCompatible(context);

expect(isCompatible).toBe(true);
});

test('returns false when URL generator is not present', async () => {
const { action, plugins, context } = setup();
(plugins.discover as any).urlGenerator = undefined;

const isCompatible = await action.isCompatible(context);

expect(isCompatible).toBe(false);
});

test('returns false if embeddable is not Visualize embeddable', async () => {
const { action, embeddable, context } = setup();
(embeddable as any).type = 'NOT_VISUALIZE_EMBEDDABLE';

const isCompatible = await action.isCompatible(context);

expect(isCompatible).toBe(false);
});

test('returns false if embeddable does not have index patterns', async () => {
const { action, output, context } = setup();
delete output.indexPatterns;

const isCompatible = await action.isCompatible(context);

expect(isCompatible).toBe(false);
});

test('returns false if embeddable index patterns are empty', async () => {
const { action, output, context } = setup();
output.indexPatterns = [];

const isCompatible = await action.isCompatible(context);

expect(isCompatible).toBe(false);
});

test('returns false if dashboard is in edit mode', async () => {
const { action, input, context } = setup();
input.viewMode = ViewMode.EDIT;

const isCompatible = await action.isCompatible(context);

expect(isCompatible).toBe(false);
});
});

describe('getHref()', () => {
test('returns URL path generated by URL generator', async () => {
const { action, context } = setup();

const href = await action.getHref(context);

expect(href).toBe('/xyz/app/discover/foo#bar');
});

test('calls URL generator with right arguments', async () => {
const { action, urlGenerator, context } = setup();

expect(urlGenerator.createUrl).toHaveBeenCalledTimes(0);

await action.getHref(context);

expect(urlGenerator.createUrl).toHaveBeenCalledTimes(1);
expect(urlGenerator.createUrl).toHaveBeenCalledWith({
indexPatternId: 'index-ptr-foo',
});
});
});

describe('execute()', () => {
test('calls platform SPA navigation method', async () => {
const { action, context, core } = setup();

expect(core.application.navigateToApp).toHaveBeenCalledTimes(0);

await action.execute(context);

expect(core.application.navigateToApp).toHaveBeenCalledTimes(1);
});

test('calls platform SPA navigation method with right arguments', async () => {
const { action, context, core } = setup();

await action.execute(context);

expect(core.application.navigateToApp).toHaveBeenCalledTimes(1);
expect(core.application.navigateToApp.mock.calls[0]).toEqual([
'discover',
{
path: '/foo#bar',
},
]);
});
});
});
Loading

0 comments on commit 3ee0bf2

Please sign in to comment.