Skip to content

Commit 3ee0bf2

Browse files
streamichelasticmachinekertal
authored
Explore underlying data (#68496)
* 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>
1 parent effd504 commit 3ee0bf2

File tree

23 files changed

+963
-36
lines changed

23 files changed

+963
-36
lines changed

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
# App
66
/x-pack/plugins/dashboard_enhanced/ @elastic/kibana-app
7+
/x-pack/plugins/discover_enhanced/ @elastic/kibana-app
78
/x-pack/plugins/lens/ @elastic/kibana-app
89
/x-pack/plugins/graph/ @elastic/kibana-app
910
/src/legacy/core_plugins/kibana/public/local_application_service/ @elastic/kibana-app

test/functional/page_objects/discover_page.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,23 @@ export function DiscoverPageProvider({ getService, getPageObjects }: FtrProvider
324324
const nr = await el.getAttribute('data-fetch-counter');
325325
return Number(nr);
326326
}
327+
328+
/**
329+
* Check if Discover app is currently rendered on the screen.
330+
*/
331+
public async isDiscoverAppOnScreen(): Promise<boolean> {
332+
const result = await find.allByCssSelector('discover-app');
333+
return result.length === 1;
334+
}
335+
336+
/**
337+
* Wait until Discover app is rendered on the screen.
338+
*/
339+
public async waitForDiscoverAppOnScreen() {
340+
await retry.waitFor('Discover app on screen', async () => {
341+
return await this.isDiscoverAppOnScreen();
342+
});
343+
}
327344
}
328345

329346
return new DiscoverPage();

test/functional/page_objects/time_picker.ts

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,18 @@ import moment from 'moment';
2121
import { FtrProviderContext } from '../ftr_provider_context.d';
2222
import { WebElementWrapper } from '../services/lib/web_element_wrapper';
2323

24+
export type CommonlyUsed =
25+
| 'Today'
26+
| 'This_week'
27+
| 'Last_15 minutes'
28+
| 'Last_30 minutes'
29+
| 'Last_1 hour'
30+
| 'Last_24 hours'
31+
| 'Last_7 days'
32+
| 'Last_30 days'
33+
| 'Last_90 days'
34+
| 'Last_1 year';
35+
2436
export function TimePickerProvider({ getService, getPageObjects }: FtrProviderContext) {
2537
const log = getService('log');
2638
const retry = getService('retry');
@@ -30,18 +42,6 @@ export function TimePickerProvider({ getService, getPageObjects }: FtrProviderCo
3042
const { header, common } = getPageObjects(['header', 'common']);
3143
const kibanaServer = getService('kibanaServer');
3244

33-
type CommonlyUsed =
34-
| 'Today'
35-
| 'This_week'
36-
| 'Last_15 minutes'
37-
| 'Last_30 minutes'
38-
| 'Last_1 hour'
39-
| 'Last_24 hours'
40-
| 'Last_7 days'
41-
| 'Last_30 days'
42-
| 'Last_90 days'
43-
| 'Last_1 year';
44-
4545
class TimePicker {
4646
defaultStartTime = 'Sep 19, 2015 @ 06:31:44.000';
4747
defaultEndTime = 'Sep 23, 2015 @ 18:31:44.000';
@@ -227,6 +227,12 @@ export function TimePickerProvider({ getService, getPageObjects }: FtrProviderCo
227227
};
228228
}
229229

230+
public async getShowDatesButtonText() {
231+
const button = await testSubjects.find('superDatePickerShowDatesButton');
232+
const text = await button.getVisibleText();
233+
return text;
234+
}
235+
230236
public async getTimeDurationForSharing() {
231237
return await testSubjects.getAttribute(
232238
'dataSharedTimefilterDuration',

x-pack/.i18nrc.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"xpack.beatsManagement": ["legacy/plugins/beats_management", "plugins/beats_management"],
1010
"xpack.canvas": "plugins/canvas",
1111
"xpack.dashboard": "plugins/dashboard_enhanced",
12+
"xpack.discover": "plugins/discover_enhanced",
1213
"xpack.crossClusterReplication": "plugins/cross_cluster_replication",
1314
"xpack.dashboardMode": "legacy/plugins/dashboard_mode",
1415
"xpack.data": "plugins/data_enhanced",
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"id": "discoverEnhanced",
3+
"version": "8.0.0",
4+
"kibanaVersion": "kibana",
5+
"server": false,
6+
"ui": true,
7+
"requiredPlugins": ["uiActions", "embeddable", "discover"],
8+
"optionalPlugins": ["share"]
9+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
export * from './view_in_discover';
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import {
8+
ExploreDataContextMenuAction,
9+
ACTION_EXPLORE_DATA,
10+
Params,
11+
PluginDeps,
12+
} from './explore_data_context_menu_action';
13+
import { coreMock } from '../../../../../../src/core/public/mocks';
14+
import { UrlGeneratorContract } from '../../../../../../src/plugins/share/public';
15+
import { i18n } from '@kbn/i18n';
16+
import {
17+
VisualizeEmbeddableContract,
18+
VISUALIZE_EMBEDDABLE_TYPE,
19+
} from '../../../../../../src/plugins/visualizations/public';
20+
import { ViewMode } from '../../../../../../src/plugins/embeddable/public';
21+
22+
const i18nTranslateSpy = (i18n.translate as unknown) as jest.SpyInstance;
23+
24+
jest.mock('@kbn/i18n', () => ({
25+
i18n: {
26+
translate: jest.fn((key, options) => options.defaultMessage),
27+
},
28+
}));
29+
30+
afterEach(() => {
31+
i18nTranslateSpy.mockClear();
32+
});
33+
34+
const setup = () => {
35+
type UrlGenerator = UrlGeneratorContract<'DISCOVER_APP_URL_GENERATOR'>;
36+
37+
const core = coreMock.createStart();
38+
39+
const urlGenerator: UrlGenerator = ({
40+
id: ACTION_EXPLORE_DATA,
41+
createUrl: jest.fn(() => Promise.resolve('/xyz/app/discover/foo#bar')),
42+
} as unknown) as UrlGenerator;
43+
44+
const plugins: PluginDeps = {
45+
discover: {
46+
urlGenerator,
47+
},
48+
};
49+
50+
const params: Params = {
51+
start: () => ({
52+
plugins,
53+
self: {},
54+
core,
55+
}),
56+
};
57+
const action = new ExploreDataContextMenuAction(params);
58+
59+
const input = {
60+
viewMode: ViewMode.VIEW,
61+
};
62+
63+
const output = {
64+
indexPatterns: [
65+
{
66+
id: 'index-ptr-foo',
67+
},
68+
],
69+
};
70+
71+
const embeddable: VisualizeEmbeddableContract = ({
72+
type: VISUALIZE_EMBEDDABLE_TYPE,
73+
getInput: () => input,
74+
getOutput: () => output,
75+
} as unknown) as VisualizeEmbeddableContract;
76+
77+
const context = {
78+
embeddable,
79+
};
80+
81+
return { core, plugins, urlGenerator, params, action, input, output, embeddable, context };
82+
};
83+
84+
describe('"Explore underlying data" panel action', () => {
85+
test('action has Discover icon', () => {
86+
const { action } = setup();
87+
expect(action.getIconType()).toBe('discoverApp');
88+
});
89+
90+
test('title is "Explore underlying data"', () => {
91+
const { action } = setup();
92+
expect(action.getDisplayName()).toBe('Explore underlying data');
93+
});
94+
95+
test('translates title', () => {
96+
expect(i18nTranslateSpy).toHaveBeenCalledTimes(0);
97+
98+
setup().action.getDisplayName();
99+
100+
expect(i18nTranslateSpy).toHaveBeenCalledTimes(1);
101+
expect(i18nTranslateSpy.mock.calls[0][0]).toBe(
102+
'xpack.discover.FlyoutCreateDrilldownAction.displayName'
103+
);
104+
});
105+
106+
describe('isCompatible()', () => {
107+
test('returns true when all conditions are met', async () => {
108+
const { action, context } = setup();
109+
110+
const isCompatible = await action.isCompatible(context);
111+
112+
expect(isCompatible).toBe(true);
113+
});
114+
115+
test('returns false when URL generator is not present', async () => {
116+
const { action, plugins, context } = setup();
117+
(plugins.discover as any).urlGenerator = undefined;
118+
119+
const isCompatible = await action.isCompatible(context);
120+
121+
expect(isCompatible).toBe(false);
122+
});
123+
124+
test('returns false if embeddable is not Visualize embeddable', async () => {
125+
const { action, embeddable, context } = setup();
126+
(embeddable as any).type = 'NOT_VISUALIZE_EMBEDDABLE';
127+
128+
const isCompatible = await action.isCompatible(context);
129+
130+
expect(isCompatible).toBe(false);
131+
});
132+
133+
test('returns false if embeddable does not have index patterns', async () => {
134+
const { action, output, context } = setup();
135+
delete output.indexPatterns;
136+
137+
const isCompatible = await action.isCompatible(context);
138+
139+
expect(isCompatible).toBe(false);
140+
});
141+
142+
test('returns false if embeddable index patterns are empty', async () => {
143+
const { action, output, context } = setup();
144+
output.indexPatterns = [];
145+
146+
const isCompatible = await action.isCompatible(context);
147+
148+
expect(isCompatible).toBe(false);
149+
});
150+
151+
test('returns false if dashboard is in edit mode', async () => {
152+
const { action, input, context } = setup();
153+
input.viewMode = ViewMode.EDIT;
154+
155+
const isCompatible = await action.isCompatible(context);
156+
157+
expect(isCompatible).toBe(false);
158+
});
159+
});
160+
161+
describe('getHref()', () => {
162+
test('returns URL path generated by URL generator', async () => {
163+
const { action, context } = setup();
164+
165+
const href = await action.getHref(context);
166+
167+
expect(href).toBe('/xyz/app/discover/foo#bar');
168+
});
169+
170+
test('calls URL generator with right arguments', async () => {
171+
const { action, urlGenerator, context } = setup();
172+
173+
expect(urlGenerator.createUrl).toHaveBeenCalledTimes(0);
174+
175+
await action.getHref(context);
176+
177+
expect(urlGenerator.createUrl).toHaveBeenCalledTimes(1);
178+
expect(urlGenerator.createUrl).toHaveBeenCalledWith({
179+
indexPatternId: 'index-ptr-foo',
180+
});
181+
});
182+
});
183+
184+
describe('execute()', () => {
185+
test('calls platform SPA navigation method', async () => {
186+
const { action, context, core } = setup();
187+
188+
expect(core.application.navigateToApp).toHaveBeenCalledTimes(0);
189+
190+
await action.execute(context);
191+
192+
expect(core.application.navigateToApp).toHaveBeenCalledTimes(1);
193+
});
194+
195+
test('calls platform SPA navigation method with right arguments', async () => {
196+
const { action, context, core } = setup();
197+
198+
await action.execute(context);
199+
200+
expect(core.application.navigateToApp).toHaveBeenCalledTimes(1);
201+
expect(core.application.navigateToApp.mock.calls[0]).toEqual([
202+
'discover',
203+
{
204+
path: '/foo#bar',
205+
},
206+
]);
207+
});
208+
});
209+
});

0 commit comments

Comments
 (0)