mirror of
https://github.com/elastic/kibana.git
synced 2025-06-28 03:01:21 -04:00
[Logs+] Add Log Explorer profile deep link (#161939)
Co-authored-by: Marco Antonio Ghiani <marcoantonio.ghiani@elastic.co> Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: weltenwort <stuermer@weltenwort.de>
This commit is contained in:
parent
d19a295d38
commit
9bae853586
28 changed files with 535 additions and 219 deletions
|
@ -10,6 +10,7 @@ xpack.serverless.observability.enabled: true
|
||||||
|
|
||||||
## Configure plugins
|
## Configure plugins
|
||||||
xpack.infra.logs.app_target: discover
|
xpack.infra.logs.app_target: discover
|
||||||
|
xpack.discoverLogExplorer.featureFlags.deepLinkVisible: true
|
||||||
|
|
||||||
## Set the home route
|
## Set the home route
|
||||||
uiSettings.overrides.defaultRoute: /app/observability/landing
|
uiSettings.overrides.defaultRoute: /app/observability/landing
|
||||||
|
|
|
@ -74,7 +74,8 @@ export class DiscoverCustomizationExamplesPlugin implements Plugin {
|
||||||
isOptionsOpen = false;
|
isOptionsOpen = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
discover.customize('customization-examples', async ({ customizations, stateContainer }) => {
|
discover.registerCustomizationProfile('customization-examples', {
|
||||||
|
customize: async ({ customizations, stateContainer }) => {
|
||||||
customizations.set({
|
customizations.set({
|
||||||
id: 'top_nav',
|
id: 'top_nav',
|
||||||
defaultMenu: {
|
defaultMenu: {
|
||||||
|
@ -231,6 +232,7 @@ export class DiscoverCustomizationExamplesPlugin implements Plugin {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log('Cleaning up Logs explorer customizations');
|
console.log('Cleaning up Logs explorer customizations');
|
||||||
};
|
};
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -297,6 +297,12 @@ export type AppDeepLink<Id extends string = string> = {
|
||||||
navLinkStatus?: AppNavLinkStatus;
|
navLinkStatus?: AppNavLinkStatus;
|
||||||
/** Optional flag to determine if the link is searchable in the global search. Defaulting to `true` if `navLinkStatus` is `visible` or omitted */
|
/** Optional flag to determine if the link is searchable in the global search. Defaulting to `true` if `navLinkStatus` is `visible` or omitted */
|
||||||
searchable?: boolean;
|
searchable?: boolean;
|
||||||
|
/**
|
||||||
|
* Optional category to use instead of the parent app category.
|
||||||
|
* This property is added to customize the way a deep link is rendered in the global search.
|
||||||
|
* Any other feature that consumes the deep links (navigation tree, etc.) will not be affected by this addition.
|
||||||
|
*/
|
||||||
|
category?: AppCategory;
|
||||||
} & AppNavOptions &
|
} & AppNavOptions &
|
||||||
(
|
(
|
||||||
| {
|
| {
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
* Side Public License, v 1.
|
* Side Public License, v 1.
|
||||||
*/
|
*/
|
||||||
|
import { DISCOVER_APP_ID } from '@kbn/deeplinks-analytics';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
LOGS_APP_ID,
|
LOGS_APP_ID,
|
||||||
|
@ -27,6 +28,8 @@ export type AppId =
|
||||||
| ApmApp
|
| ApmApp
|
||||||
| MetricsApp;
|
| MetricsApp;
|
||||||
|
|
||||||
|
export type DiscoverLogExplorerId = `${typeof DISCOVER_APP_ID}:log-explorer`;
|
||||||
|
|
||||||
export type LogsLinkId = 'log-categories' | 'settings' | 'anomalies' | 'stream';
|
export type LogsLinkId = 'log-categories' | 'settings' | 'anomalies' | 'stream';
|
||||||
|
|
||||||
export type ObservabilityOverviewLinkId =
|
export type ObservabilityOverviewLinkId =
|
||||||
|
@ -52,6 +55,7 @@ export type LinkId = LogsLinkId | ObservabilityOverviewLinkId | MetricsLinkId |
|
||||||
|
|
||||||
export type DeepLinkId =
|
export type DeepLinkId =
|
||||||
| AppId
|
| AppId
|
||||||
|
| DiscoverLogExplorerId
|
||||||
| `${LogsApp}:${LogsLinkId}`
|
| `${LogsApp}:${LogsLinkId}`
|
||||||
| `${ObservabilityOverviewApp}:${ObservabilityOverviewLinkId}`
|
| `${ObservabilityOverviewApp}:${ObservabilityOverviewLinkId}`
|
||||||
| `${MetricsApp}:${MetricsLinkId}`
|
| `${MetricsApp}:${MetricsLinkId}`
|
||||||
|
|
|
@ -15,5 +15,7 @@
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"target/**/*"
|
"target/**/*"
|
||||||
],
|
],
|
||||||
"kbn_references": []
|
"kbn_references": [
|
||||||
|
"@kbn/deeplinks-analytics",
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -130,12 +130,12 @@ const profileRegistry = createProfileRegistry();
|
||||||
const callbacks = [jest.fn()];
|
const callbacks = [jest.fn()];
|
||||||
|
|
||||||
profileRegistry.set({
|
profileRegistry.set({
|
||||||
name: 'default',
|
id: 'default',
|
||||||
customizationCallbacks: callbacks,
|
customizationCallbacks: callbacks,
|
||||||
});
|
});
|
||||||
|
|
||||||
profileRegistry.set({
|
profileRegistry.set({
|
||||||
name: 'test',
|
id: 'test',
|
||||||
customizationCallbacks: callbacks,
|
customizationCallbacks: callbacks,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -6,25 +6,27 @@
|
||||||
* Side Public License, v 1.
|
* Side Public License, v 1.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createCustomizeFunction, createProfileRegistry } from './profile_registry';
|
import { App, AppDeepLink, AppUpdater } from '@kbn/core/public';
|
||||||
|
import { BehaviorSubject, combineLatest, map, take } from 'rxjs';
|
||||||
|
import { createRegisterCustomizationProfile, createProfileRegistry } from './profile_registry';
|
||||||
|
|
||||||
describe('createProfileRegistry', () => {
|
describe('createProfileRegistry', () => {
|
||||||
it('should allow registering profiles', () => {
|
it('should allow registering profiles', () => {
|
||||||
const registry = createProfileRegistry();
|
const registry = createProfileRegistry();
|
||||||
registry.set({
|
registry.set({
|
||||||
name: 'test',
|
id: 'test',
|
||||||
customizationCallbacks: [],
|
customizationCallbacks: [],
|
||||||
});
|
});
|
||||||
registry.set({
|
registry.set({
|
||||||
name: 'test2',
|
id: 'test2',
|
||||||
customizationCallbacks: [],
|
customizationCallbacks: [],
|
||||||
});
|
});
|
||||||
expect(registry.get('test')).toEqual({
|
expect(registry.get('test')).toEqual({
|
||||||
name: 'test',
|
id: 'test',
|
||||||
customizationCallbacks: [],
|
customizationCallbacks: [],
|
||||||
});
|
});
|
||||||
expect(registry.get('test2')).toEqual({
|
expect(registry.get('test2')).toEqual({
|
||||||
name: 'test2',
|
id: 'test2',
|
||||||
customizationCallbacks: [],
|
customizationCallbacks: [],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -32,20 +34,20 @@ describe('createProfileRegistry', () => {
|
||||||
it('should allow overriding profiles', () => {
|
it('should allow overriding profiles', () => {
|
||||||
const registry = createProfileRegistry();
|
const registry = createProfileRegistry();
|
||||||
registry.set({
|
registry.set({
|
||||||
name: 'test',
|
id: 'test',
|
||||||
customizationCallbacks: [],
|
customizationCallbacks: [],
|
||||||
});
|
});
|
||||||
expect(registry.get('test')).toEqual({
|
expect(registry.get('test')).toEqual({
|
||||||
name: 'test',
|
id: 'test',
|
||||||
customizationCallbacks: [],
|
customizationCallbacks: [],
|
||||||
});
|
});
|
||||||
const callback = jest.fn();
|
const callback = jest.fn();
|
||||||
registry.set({
|
registry.set({
|
||||||
name: 'test',
|
id: 'test',
|
||||||
customizationCallbacks: [callback],
|
customizationCallbacks: [callback],
|
||||||
});
|
});
|
||||||
expect(registry.get('test')).toEqual({
|
expect(registry.get('test')).toEqual({
|
||||||
name: 'test',
|
id: 'test',
|
||||||
customizationCallbacks: [callback],
|
customizationCallbacks: [callback],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -53,31 +55,80 @@ describe('createProfileRegistry', () => {
|
||||||
it('should be case insensitive', () => {
|
it('should be case insensitive', () => {
|
||||||
const registry = createProfileRegistry();
|
const registry = createProfileRegistry();
|
||||||
registry.set({
|
registry.set({
|
||||||
name: 'test',
|
id: 'test',
|
||||||
customizationCallbacks: [],
|
customizationCallbacks: [],
|
||||||
});
|
});
|
||||||
expect(registry.get('tEsT')).toEqual({
|
expect(registry.get('tEsT')).toEqual({
|
||||||
name: 'test',
|
id: 'test',
|
||||||
customizationCallbacks: [],
|
customizationCallbacks: [],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('createCustomizeFunction', () => {
|
describe('createRegisterCustomizationProfile', () => {
|
||||||
test('should add a customization callback to the registry', () => {
|
test('should add a customization callback to the registry', () => {
|
||||||
const registry = createProfileRegistry();
|
const registry = createProfileRegistry();
|
||||||
const customize = createCustomizeFunction(registry);
|
const registerCustomizationProfile = createRegisterCustomizationProfile(registry);
|
||||||
const callback = jest.fn();
|
const callback = jest.fn();
|
||||||
customize('test', callback);
|
registerCustomizationProfile('test', { customize: callback });
|
||||||
expect(registry.get('test')).toEqual({
|
expect(registry.get('test')).toEqual({
|
||||||
name: 'test',
|
id: 'test',
|
||||||
customizationCallbacks: [callback],
|
customizationCallbacks: [callback],
|
||||||
|
deepLinks: [],
|
||||||
});
|
});
|
||||||
const callback2 = jest.fn();
|
const callback2 = jest.fn();
|
||||||
customize('test', callback2);
|
registerCustomizationProfile('test', { customize: callback2 });
|
||||||
expect(registry.get('test')).toEqual({
|
expect(registry.get('test')).toEqual({
|
||||||
name: 'test',
|
id: 'test',
|
||||||
customizationCallbacks: [callback, callback2],
|
customizationCallbacks: [callback, callback2],
|
||||||
|
deepLinks: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('profile.getContributedAppState$ observable', () => {
|
||||||
|
test('should notify subscribers with new app updates when a profile is registered', (done) => {
|
||||||
|
const registry = createProfileRegistry();
|
||||||
|
const callback = jest.fn();
|
||||||
|
const appUpdater$ = new BehaviorSubject<AppUpdater>(() => ({}));
|
||||||
|
|
||||||
|
const mockDeepLink: AppDeepLink = {
|
||||||
|
id: 'test-deepLink',
|
||||||
|
title: 'Test deep link',
|
||||||
|
path: '/test-deep-link',
|
||||||
|
};
|
||||||
|
let mockApp: App = { id: 'test-app', title: 'Test App', mount: () => () => {} };
|
||||||
|
const expectedApp: App = { ...mockApp, deepLinks: [mockDeepLink] };
|
||||||
|
|
||||||
|
const appStateUpdater$ = combineLatest([appUpdater$, registry.getContributedAppState$()]).pipe(
|
||||||
|
map(
|
||||||
|
([appUpdater, registryContributor]): AppUpdater =>
|
||||||
|
(app) => ({ ...appUpdater(app), ...registryContributor(app) })
|
||||||
|
),
|
||||||
|
take(3)
|
||||||
|
);
|
||||||
|
|
||||||
|
appStateUpdater$.subscribe({
|
||||||
|
next: (updater) => {
|
||||||
|
mockApp = { ...mockApp, ...updater(mockApp) };
|
||||||
|
},
|
||||||
|
complete: () => {
|
||||||
|
expect(mockApp).toEqual(expectedApp);
|
||||||
|
done();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// First update, no deepLinks set
|
||||||
|
registry.set({
|
||||||
|
id: 'test',
|
||||||
|
customizationCallbacks: [callback],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Second update, deepLinks set to update app
|
||||||
|
registry.set({
|
||||||
|
id: 'test',
|
||||||
|
customizationCallbacks: [],
|
||||||
|
deepLinks: [mockDeepLink],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,34 +6,68 @@
|
||||||
* Side Public License, v 1.
|
* Side Public License, v 1.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { CustomizationCallback } from './types';
|
import type { AppDeepLink, AppUpdater } from '@kbn/core/public';
|
||||||
|
import { map, Observable, BehaviorSubject } from 'rxjs';
|
||||||
export interface DiscoverProfile {
|
import type { RegisterCustomizationProfile, DiscoverProfile, DiscoverProfileId } from './types';
|
||||||
name: string;
|
|
||||||
customizationCallbacks: CustomizationCallback[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DiscoverProfileRegistry {
|
export interface DiscoverProfileRegistry {
|
||||||
get(name: string): DiscoverProfile | undefined;
|
get(id: DiscoverProfileId): DiscoverProfile | undefined;
|
||||||
set(profile: DiscoverProfile): void;
|
set(profile: DiscoverProfile): void;
|
||||||
|
getContributedAppState$: () => Observable<AppUpdater>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createProfileRegistry = (): DiscoverProfileRegistry => {
|
export const createProfileRegistry = (): DiscoverProfileRegistry => {
|
||||||
const profiles = new Map<string, DiscoverProfile>();
|
const profiles = new Map<string, DiscoverProfile>([['default', createProfile('default')]]);
|
||||||
|
const profiles$ = new BehaviorSubject<DiscoverProfile[]>([...profiles.values()]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
get: (name) => profiles.get(name.toLowerCase()),
|
get: (id) => profiles.get(id.toLowerCase()),
|
||||||
set: (profile) => profiles.set(profile.name.toLowerCase(), profile),
|
set: (profile) => {
|
||||||
|
profiles.set(profile.id.toLowerCase(), profile);
|
||||||
|
profiles$.next([...profiles.values()]);
|
||||||
|
},
|
||||||
|
getContributedAppState$() {
|
||||||
|
return profiles$.pipe(
|
||||||
|
map((profilesList) => profilesList.flatMap((profile) => profile.deepLinks ?? [])),
|
||||||
|
map((profilesDeepLinks) => (app) => ({
|
||||||
|
deepLinks: getUniqueDeepLinks([...(app.deepLinks ?? []), ...profilesDeepLinks]),
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createCustomizeFunction =
|
export const createRegisterCustomizationProfile =
|
||||||
(profileRegistry: DiscoverProfileRegistry) =>
|
(profileRegistry: DiscoverProfileRegistry): RegisterCustomizationProfile =>
|
||||||
(profileName: string, callback: CustomizationCallback) => {
|
(id, options) => {
|
||||||
const profile = profileRegistry.get(profileName) ?? {
|
const profile = profileRegistry.get(id) ?? createProfile(id);
|
||||||
name: profileName,
|
|
||||||
customizationCallbacks: [],
|
const { customize, deepLinks } = options;
|
||||||
};
|
|
||||||
profile.customizationCallbacks.push(callback);
|
profile.customizationCallbacks.push(customize);
|
||||||
|
|
||||||
|
if (Array.isArray(deepLinks) && profile.deepLinks) {
|
||||||
|
profile.deepLinks = getUniqueDeepLinks([...profile.deepLinks, ...deepLinks]);
|
||||||
|
} else if (Array.isArray(deepLinks)) {
|
||||||
|
profile.deepLinks = getUniqueDeepLinks(deepLinks);
|
||||||
|
}
|
||||||
|
|
||||||
profileRegistry.set(profile);
|
profileRegistry.set(profile);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utils
|
||||||
|
*/
|
||||||
|
const createProfile = (id: DiscoverProfileId): DiscoverProfile => ({
|
||||||
|
id,
|
||||||
|
customizationCallbacks: [],
|
||||||
|
deepLinks: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const getUniqueDeepLinks = (deepLinks: AppDeepLink[]): AppDeepLink[] => {
|
||||||
|
const mapValues = deepLinks
|
||||||
|
.reduce((deepLinksMap, deepLink) => deepLinksMap.set(deepLink.id, deepLink), new Map())
|
||||||
|
.values();
|
||||||
|
|
||||||
|
return Array.from(mapValues);
|
||||||
|
};
|
||||||
|
|
|
@ -6,14 +6,33 @@
|
||||||
* Side Public License, v 1.
|
* Side Public License, v 1.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type { AppDeepLink } from '@kbn/core/public';
|
||||||
import type { DiscoverStateContainer } from '../application/main/services/discover_state';
|
import type { DiscoverStateContainer } from '../application/main/services/discover_state';
|
||||||
import type { DiscoverCustomizationService } from './customization_service';
|
import type { DiscoverCustomizationService } from './customization_service';
|
||||||
|
|
||||||
|
export type DiscoverProfileId = string;
|
||||||
|
|
||||||
|
export interface DiscoverProfile {
|
||||||
|
id: DiscoverProfileId;
|
||||||
|
customizationCallbacks: CustomizationCallback[];
|
||||||
|
deepLinks?: AppDeepLink[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface CustomizationCallbackContext {
|
export interface CustomizationCallbackContext {
|
||||||
customizations: DiscoverCustomizationService;
|
customizations: DiscoverCustomizationService;
|
||||||
stateContainer: DiscoverStateContainer;
|
stateContainer: DiscoverStateContainer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DiscoverProfileOptions {
|
||||||
|
customize: CustomizationCallback;
|
||||||
|
deepLinks?: AppDeepLink[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RegisterCustomizationProfile = (
|
||||||
|
id: DiscoverProfileId,
|
||||||
|
options: DiscoverProfileOptions
|
||||||
|
) => void;
|
||||||
|
|
||||||
export type CustomizationCallback = (
|
export type CustomizationCallback = (
|
||||||
options: CustomizationCallbackContext
|
options: CustomizationCallbackContext
|
||||||
) => void | (() => void) | Promise<void | (() => void)>;
|
) => void | (() => void) | Promise<void | (() => void)>;
|
||||||
|
|
|
@ -16,6 +16,11 @@ export function plugin(initializerContext: PluginInitializerContext) {
|
||||||
|
|
||||||
export type { ISearchEmbeddable, SearchInput } from './embeddable';
|
export type { ISearchEmbeddable, SearchInput } from './embeddable';
|
||||||
export type { DiscoverStateContainer } from './application/main/services/discover_state';
|
export type { DiscoverStateContainer } from './application/main/services/discover_state';
|
||||||
export type { CustomizationCallback } from './customizations';
|
export type {
|
||||||
|
CustomizationCallback,
|
||||||
|
DiscoverProfileId,
|
||||||
|
DiscoverProfileOptions,
|
||||||
|
RegisterCustomizationProfile,
|
||||||
|
} from './customizations';
|
||||||
export { SEARCH_EMBEDDABLE_TYPE, SEARCH_EMBEDDABLE_CELL_ACTIONS_TRIGGER_ID } from './embeddable';
|
export { SEARCH_EMBEDDABLE_TYPE, SEARCH_EMBEDDABLE_CELL_ACTIONS_TRIGGER_ID } from './embeddable';
|
||||||
export { loadSharingDataHelpers } from './utils';
|
export { loadSharingDataHelpers } from './utils';
|
||||||
|
|
|
@ -25,7 +25,7 @@ const createSetupContract = (): Setup => {
|
||||||
const createStartContract = (): Start => {
|
const createStartContract = (): Start => {
|
||||||
const startContract: Start = {
|
const startContract: Start = {
|
||||||
locator: sharePluginMock.createLocator(),
|
locator: sharePluginMock.createLocator(),
|
||||||
customize: jest.fn(),
|
registerCustomizationProfile: jest.fn(),
|
||||||
};
|
};
|
||||||
return startContract;
|
return startContract;
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { BehaviorSubject } from 'rxjs';
|
import { BehaviorSubject, combineLatest, map } from 'rxjs';
|
||||||
import {
|
import {
|
||||||
AppMountParameters,
|
AppMountParameters,
|
||||||
AppUpdater,
|
AppUpdater,
|
||||||
|
@ -72,8 +72,11 @@ import {
|
||||||
DiscoverSingleDocLocatorDefinition,
|
DiscoverSingleDocLocatorDefinition,
|
||||||
} from './application/doc/locator';
|
} from './application/doc/locator';
|
||||||
import { DiscoverAppLocator, DiscoverAppLocatorDefinition } from '../common';
|
import { DiscoverAppLocator, DiscoverAppLocatorDefinition } from '../common';
|
||||||
import type { CustomizationCallback } from './customizations';
|
import type { RegisterCustomizationProfile } from './customizations';
|
||||||
import { createCustomizeFunction, createProfileRegistry } from './customizations/profile_registry';
|
import {
|
||||||
|
createRegisterCustomizationProfile,
|
||||||
|
createProfileRegistry,
|
||||||
|
} from './customizations/profile_registry';
|
||||||
import { SEARCH_EMBEDDABLE_CELL_ACTIONS_TRIGGER } from './embeddable/constants';
|
import { SEARCH_EMBEDDABLE_CELL_ACTIONS_TRIGGER } from './embeddable/constants';
|
||||||
|
|
||||||
const DocViewerLegacyTable = React.lazy(
|
const DocViewerLegacyTable = React.lazy(
|
||||||
|
@ -159,7 +162,7 @@ export interface DiscoverStart {
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
readonly locator: undefined | DiscoverAppLocator;
|
readonly locator: undefined | DiscoverAppLocator;
|
||||||
readonly customize: (profileName: string, callback: CustomizationCallback) => void;
|
readonly registerCustomizationProfile: RegisterCustomizationProfile;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -305,10 +308,23 @@ export class DiscoverPlugin
|
||||||
stopUrlTracker();
|
stopUrlTracker();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const appStateUpdater$ = combineLatest([
|
||||||
|
this.appStateUpdater,
|
||||||
|
this.profileRegistry.getContributedAppState$(),
|
||||||
|
]).pipe(
|
||||||
|
map(
|
||||||
|
([urlAppStateUpdater, profileAppStateUpdater]): AppUpdater =>
|
||||||
|
(app) => ({
|
||||||
|
...urlAppStateUpdater(app),
|
||||||
|
...profileAppStateUpdater(app),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
core.application.register({
|
core.application.register({
|
||||||
id: PLUGIN_ID,
|
id: PLUGIN_ID,
|
||||||
title: 'Discover',
|
title: 'Discover',
|
||||||
updater$: this.appStateUpdater.asObservable(),
|
updater$: appStateUpdater$,
|
||||||
order: 1000,
|
order: 1000,
|
||||||
euiIconType: 'logoKibana',
|
euiIconType: 'logoKibana',
|
||||||
defaultPath: '#/',
|
defaultPath: '#/',
|
||||||
|
@ -416,7 +432,7 @@ export class DiscoverPlugin
|
||||||
|
|
||||||
return {
|
return {
|
||||||
locator: this.locator,
|
locator: this.locator,
|
||||||
customize: createCustomizeFunction(this.profileRegistry),
|
registerCustomizationProfile: createRegisterCustomizationProfile(this.profileRegistry),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -286,6 +286,10 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
|
||||||
'xpack.observability.unsafe.alertDetails.uptime.enabled (boolean)',
|
'xpack.observability.unsafe.alertDetails.uptime.enabled (boolean)',
|
||||||
'xpack.observability.unsafe.thresholdRule.enabled (boolean)',
|
'xpack.observability.unsafe.thresholdRule.enabled (boolean)',
|
||||||
'xpack.observability_onboarding.ui.enabled (boolean)',
|
'xpack.observability_onboarding.ui.enabled (boolean)',
|
||||||
|
/**
|
||||||
|
* xpack.discoverLogExplorer.featureFlags is conditional and will never resolve if used in non-serverless environment
|
||||||
|
*/
|
||||||
|
'xpack.discoverLogExplorer.featureFlags.deepLinkVisible (any)',
|
||||||
];
|
];
|
||||||
// We don't assert that actualExposedConfigKeys and expectedExposedConfigKeys are equal, because test failure messages with large
|
// We don't assert that actualExposedConfigKeys and expectedExposedConfigKeys are equal, because test failure messages with large
|
||||||
// arrays are hard to grok. Instead, we take the difference between the two arrays and assert them separately, that way it's
|
// arrays are hard to grok. Instead, we take the difference between the two arrays and assert them separately, that way it's
|
||||||
|
|
12
x-pack/plugins/discover_log_explorer/common/plugin_config.ts
Normal file
12
x-pack/plugins/discover_log_explorer/common/plugin_config.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface DiscoverLogExplorerConfig {
|
||||||
|
featureFlags: {
|
||||||
|
deepLinkVisible: boolean;
|
||||||
|
};
|
||||||
|
}
|
|
@ -5,9 +5,9 @@
|
||||||
"description": "This plugin exposes and registers Logs+ features.",
|
"description": "This plugin exposes and registers Logs+ features.",
|
||||||
"plugin": {
|
"plugin": {
|
||||||
"id": "discoverLogExplorer",
|
"id": "discoverLogExplorer",
|
||||||
"server": false,
|
"server": true,
|
||||||
"browser": true,
|
"browser": true,
|
||||||
"configPath": ["xpack", "discover_log_explorer"],
|
"configPath": ["xpack", "discoverLogExplorer"],
|
||||||
"requiredPlugins": ["discover", "fleet", "kibanaReact", "kibanaUtils"],
|
"requiredPlugins": ["discover", "fleet", "kibanaReact", "kibanaUtils"],
|
||||||
"optionalPlugins": [],
|
"optionalPlugins": [],
|
||||||
"requiredBundles": []
|
"requiredBundles": []
|
||||||
|
|
21
x-pack/plugins/discover_log_explorer/public/deep_links.ts
Normal file
21
x-pack/plugins/discover_log_explorer/public/deep_links.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { AppDeepLink, AppNavLinkStatus, DEFAULT_APP_CATEGORIES } from '@kbn/core/public';
|
||||||
|
import { i18n } from '@kbn/i18n';
|
||||||
|
import { LOG_EXPLORER_PROFILE_ID } from '../common/constants';
|
||||||
|
|
||||||
|
export const getLogExplorerDeepLink = ({ isVisible }: { isVisible: boolean }): AppDeepLink => ({
|
||||||
|
id: LOG_EXPLORER_PROFILE_ID,
|
||||||
|
title: i18n.translate('xpack.discoverLogExplorer.deepLink', {
|
||||||
|
defaultMessage: 'Logs Explorer',
|
||||||
|
}),
|
||||||
|
path: `#/p/${LOG_EXPLORER_PROFILE_ID}`,
|
||||||
|
category: DEFAULT_APP_CATEGORIES.observability,
|
||||||
|
euiIconType: 'logoObservability',
|
||||||
|
navLinkStatus: isVisible ? AppNavLinkStatus.visible : AppNavLinkStatus.default,
|
||||||
|
});
|
|
@ -5,8 +5,10 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { PluginInitializerContext } from '@kbn/core/public';
|
||||||
|
import { DiscoverLogExplorerConfig } from '../common/plugin_config';
|
||||||
import { DiscoverLogExplorerPlugin } from './plugin';
|
import { DiscoverLogExplorerPlugin } from './plugin';
|
||||||
|
|
||||||
export function plugin() {
|
export function plugin(context: PluginInitializerContext<DiscoverLogExplorerConfig>) {
|
||||||
return new DiscoverLogExplorerPlugin();
|
return new DiscoverLogExplorerPlugin(context);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,11 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { CoreStart, Plugin } from '@kbn/core/public';
|
import { CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/public';
|
||||||
import { LOG_EXPLORER_PROFILE_ID } from '../common/constants';
|
import { LOG_EXPLORER_PROFILE_ID } from '../common/constants';
|
||||||
|
import { DiscoverLogExplorerConfig } from '../common/plugin_config';
|
||||||
import { createLogExplorerProfileCustomizations } from './customizations/log_explorer_profile';
|
import { createLogExplorerProfileCustomizations } from './customizations/log_explorer_profile';
|
||||||
|
import { getLogExplorerDeepLink } from './deep_links';
|
||||||
import {
|
import {
|
||||||
DiscoverLogExplorerPluginSetup,
|
DiscoverLogExplorerPluginSetup,
|
||||||
DiscoverLogExplorerPluginStart,
|
DiscoverLogExplorerPluginStart,
|
||||||
|
@ -17,11 +19,20 @@ import {
|
||||||
export class DiscoverLogExplorerPlugin
|
export class DiscoverLogExplorerPlugin
|
||||||
implements Plugin<DiscoverLogExplorerPluginSetup, DiscoverLogExplorerPluginStart>
|
implements Plugin<DiscoverLogExplorerPluginSetup, DiscoverLogExplorerPluginStart>
|
||||||
{
|
{
|
||||||
|
private config: DiscoverLogExplorerConfig;
|
||||||
|
|
||||||
|
constructor(context: PluginInitializerContext<DiscoverLogExplorerConfig>) {
|
||||||
|
this.config = context.config.get();
|
||||||
|
}
|
||||||
|
|
||||||
public setup() {}
|
public setup() {}
|
||||||
|
|
||||||
public start(core: CoreStart, plugins: DiscoverLogExplorerStartDeps) {
|
public start(core: CoreStart, plugins: DiscoverLogExplorerStartDeps) {
|
||||||
const { discover } = plugins;
|
const { discover } = plugins;
|
||||||
|
|
||||||
discover.customize(LOG_EXPLORER_PROFILE_ID, createLogExplorerProfileCustomizations({ core }));
|
discover.registerCustomizationProfile(LOG_EXPLORER_PROFILE_ID, {
|
||||||
|
customize: createLogExplorerProfileCustomizations({ core }),
|
||||||
|
deepLinks: [getLogExplorerDeepLink({ isVisible: this.config.featureFlags.deepLinkVisible })],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
34
x-pack/plugins/discover_log_explorer/server/config.ts
Normal file
34
x-pack/plugins/discover_log_explorer/server/config.ts
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { schema } from '@kbn/config-schema';
|
||||||
|
import { PluginConfigDescriptor } from '@kbn/core/server';
|
||||||
|
|
||||||
|
import { DiscoverLogExplorerConfig } from '../common/plugin_config';
|
||||||
|
|
||||||
|
export const configSchema = schema.object({
|
||||||
|
featureFlags: schema.object({
|
||||||
|
deepLinkVisible: schema.conditional(
|
||||||
|
schema.contextRef('serverless'),
|
||||||
|
true,
|
||||||
|
schema.boolean(),
|
||||||
|
schema.never(),
|
||||||
|
{
|
||||||
|
defaultValue: false,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const config: PluginConfigDescriptor<DiscoverLogExplorerConfig> = {
|
||||||
|
schema: configSchema,
|
||||||
|
exposeToBrowser: {
|
||||||
|
featureFlags: {
|
||||||
|
deepLinkVisible: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
12
x-pack/plugins/discover_log_explorer/server/index.ts
Normal file
12
x-pack/plugins/discover_log_explorer/server/index.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { DiscoverLogExplorerServerPlugin } from './plugin';
|
||||||
|
|
||||||
|
export { config } from './config';
|
||||||
|
|
||||||
|
export const plugin = () => new DiscoverLogExplorerServerPlugin();
|
14
x-pack/plugins/discover_log_explorer/server/plugin.ts
Normal file
14
x-pack/plugins/discover_log_explorer/server/plugin.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Plugin } from '@kbn/core/server';
|
||||||
|
|
||||||
|
export class DiscoverLogExplorerServerPlugin implements Plugin {
|
||||||
|
setup() {}
|
||||||
|
|
||||||
|
start() {}
|
||||||
|
}
|
|
@ -3,7 +3,7 @@
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "target/types"
|
"outDir": "target/types"
|
||||||
},
|
},
|
||||||
"include": ["../../../typings/**/*", "common/**/*", "public/**/*", ".storybook/**/*.tsx"],
|
"include": ["../../../typings/**/*", "common/**/*", "public/**/*", "server/**/*", ".storybook/**/*.tsx"],
|
||||||
"kbn_references": [
|
"kbn_references": [
|
||||||
"@kbn/core",
|
"@kbn/core",
|
||||||
"@kbn/discover-plugin",
|
"@kbn/discover-plugin",
|
||||||
|
@ -13,6 +13,7 @@
|
||||||
"@kbn/io-ts-utils",
|
"@kbn/io-ts-utils",
|
||||||
"@kbn/data-views-plugin",
|
"@kbn/data-views-plugin",
|
||||||
"@kbn/rison",
|
"@kbn/rison",
|
||||||
|
"@kbn/config-schema",
|
||||||
],
|
],
|
||||||
"exclude": ["target/**/*"]
|
"exclude": ["target/**/*"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,6 +104,56 @@ describe('getAppResults', () => {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('deep links "category" and "icon" should take precedence over the same app properties', () => {
|
||||||
|
const apps = [
|
||||||
|
createApp({
|
||||||
|
euiIconType: 'logoKibana',
|
||||||
|
category: DEFAULT_APP_CATEGORIES.kibana,
|
||||||
|
deepLinks: [
|
||||||
|
{
|
||||||
|
id: 'sub-observability',
|
||||||
|
title: 'Sub Observability',
|
||||||
|
path: '/sub-observability',
|
||||||
|
deepLinks: [],
|
||||||
|
keywords: [],
|
||||||
|
navLinkStatus: AppNavLinkStatus.hidden,
|
||||||
|
searchable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'sub-security',
|
||||||
|
title: 'Sub Security',
|
||||||
|
path: '/sub-security',
|
||||||
|
deepLinks: [],
|
||||||
|
keywords: [],
|
||||||
|
navLinkStatus: AppNavLinkStatus.visible,
|
||||||
|
searchable: true,
|
||||||
|
euiIconType: 'logoSecurity',
|
||||||
|
category: DEFAULT_APP_CATEGORIES.security,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
keywords: [],
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
const results = getAppResults('App 1', apps);
|
||||||
|
const [appLink, observabilityLink, securityLink] = results;
|
||||||
|
expect(appLink).toMatchObject({
|
||||||
|
icon: 'logoKibana',
|
||||||
|
meta: { categoryId: 'kibana', categoryLabel: 'Analytics' },
|
||||||
|
title: 'App 1',
|
||||||
|
});
|
||||||
|
expect(observabilityLink).toMatchObject({
|
||||||
|
icon: 'logoKibana',
|
||||||
|
meta: { categoryId: 'kibana', categoryLabel: 'Analytics' },
|
||||||
|
title: 'App 1 / Sub Observability',
|
||||||
|
});
|
||||||
|
expect(securityLink).toMatchObject({
|
||||||
|
icon: 'logoSecurity',
|
||||||
|
meta: { categoryId: 'securitySolution', categoryLabel: 'Security' },
|
||||||
|
title: 'App 1 / Sub Security',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('only includes deepLinks when search term is non-empty', () => {
|
it('only includes deepLinks when search term is non-empty', () => {
|
||||||
const apps = [
|
const apps = [
|
||||||
createApp({
|
createApp({
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import levenshtein from 'js-levenshtein';
|
import levenshtein from 'js-levenshtein';
|
||||||
import { PublicAppInfo, PublicAppDeepLinkInfo } from '@kbn/core/public';
|
import { PublicAppInfo, PublicAppDeepLinkInfo, AppCategory } from '@kbn/core/public';
|
||||||
import { GlobalSearchProviderResult } from '@kbn/global-search-plugin/public';
|
import { GlobalSearchProviderResult } from '@kbn/global-search-plugin/public';
|
||||||
|
|
||||||
/** Type used internally to represent an application unrolled into its separate deepLinks */
|
/** Type used internally to represent an application unrolled into its separate deepLinks */
|
||||||
|
@ -16,6 +16,8 @@ export interface AppLink {
|
||||||
subLinkTitles: string[];
|
subLinkTitles: string[];
|
||||||
path: string;
|
path: string;
|
||||||
keywords: string[];
|
keywords: string[];
|
||||||
|
category?: AppCategory;
|
||||||
|
euiIconType?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** weighting factor for scoring keywords */
|
/** weighting factor for scoring keywords */
|
||||||
|
@ -107,11 +109,11 @@ export const appToResult = (appLink: AppLink, score: number): GlobalSearchProvid
|
||||||
// Concatenate title using slashes
|
// Concatenate title using slashes
|
||||||
title: titleParts.join(' / '),
|
title: titleParts.join(' / '),
|
||||||
type: 'application',
|
type: 'application',
|
||||||
icon: appLink.app.euiIconType,
|
icon: appLink.euiIconType ?? appLink.app.euiIconType,
|
||||||
url: appLink.path,
|
url: appLink.path,
|
||||||
meta: {
|
meta: {
|
||||||
categoryId: appLink.app.category?.id ?? null,
|
categoryId: appLink.category?.id ?? appLink.app.category?.id ?? null,
|
||||||
categoryLabel: appLink.app.category?.label ?? null,
|
categoryLabel: appLink.category?.label ?? appLink.app.category?.label ?? null,
|
||||||
},
|
},
|
||||||
score,
|
score,
|
||||||
};
|
};
|
||||||
|
@ -138,6 +140,7 @@ const flattenDeepLinks = (app: PublicAppInfo, deepLink?: PublicAppDeepLinkInfo):
|
||||||
...(deepLink.path && deepLink.searchable
|
...(deepLink.path && deepLink.searchable
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
|
...deepLink,
|
||||||
id: `${app.id}-${deepLink.id}`,
|
id: `${app.id}-${deepLink.id}`,
|
||||||
app,
|
app,
|
||||||
path: `${app.appRoute}${deepLink.path}`,
|
path: `${app.appRoute}${deepLink.path}`,
|
||||||
|
|
|
@ -31,7 +31,10 @@ const navigationTree: NavigationTreeDefinition = {
|
||||||
id: 'discover-dashboard-alerts-slos',
|
id: 'discover-dashboard-alerts-slos',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
link: 'discover',
|
title: i18n.translate('xpack.serverlessObservability.nav.discover', {
|
||||||
|
defaultMessage: 'Discover',
|
||||||
|
}),
|
||||||
|
link: 'discover:log-explorer',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18n.translate('xpack.serverlessObservability.nav.dashboards', {
|
title: i18n.translate('xpack.serverlessObservability.nav.dashboards', {
|
||||||
|
|
|
@ -4,11 +4,12 @@
|
||||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
import expect from '@kbn/expect';
|
||||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||||
|
|
||||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||||
const kibanaServer = getService('kibanaServer');
|
const kibanaServer = getService('kibanaServer');
|
||||||
const PageObjects = getPageObjects(['common']);
|
const PageObjects = getPageObjects(['common', 'navigationalSearch']);
|
||||||
const testSubjects = getService('testSubjects');
|
const testSubjects = getService('testSubjects');
|
||||||
|
|
||||||
describe('Customizations', () => {
|
describe('Customizations', () => {
|
||||||
|
@ -50,6 +51,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||||
await testSubjects.existOrFail('openInspectorButton');
|
await testSubjects.existOrFail('openInspectorButton');
|
||||||
await testSubjects.missingOrFail('discoverSaveButton');
|
await testSubjects.missingOrFail('discoverSaveButton');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should add a searchable deep link to the profile page', async () => {
|
||||||
|
await PageObjects.common.navigateToApp('home');
|
||||||
|
await PageObjects.navigationalSearch.searchFor('discover log explorer');
|
||||||
|
|
||||||
|
const results = await PageObjects.navigationalSearch.getDisplayedResults();
|
||||||
|
expect(results[0].label).to.eql('Discover / Logs Explorer');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,7 +87,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
||||||
describe('Applications provider', function () {
|
describe('Applications provider', function () {
|
||||||
it('can search for root-level applications', async () => {
|
it('can search for root-level applications', async () => {
|
||||||
const results = await findResultsWithApi('discover');
|
const results = await findResultsWithApi('discover');
|
||||||
expect(results.length).to.be(1);
|
expect(results.length).to.be(2);
|
||||||
expect(results[0].title).to.be('Discover');
|
expect(results[0].title).to.be('Discover');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -35,11 +35,12 @@ export default function ({ getPageObject, getService }: FtrProviderContext) {
|
||||||
});
|
});
|
||||||
await svlCommonNavigation.sidenav.expectSectionClosed('project_settings_project_nav');
|
await svlCommonNavigation.sidenav.expectSectionClosed('project_settings_project_nav');
|
||||||
|
|
||||||
// TODO: test something oblt project specific instead of generic discover
|
|
||||||
// navigate to discover
|
// navigate to discover
|
||||||
await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'discover' });
|
await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'discover:log-explorer' });
|
||||||
await svlCommonNavigation.sidenav.expectLinkActive({ deepLinkId: 'discover' });
|
await svlCommonNavigation.sidenav.expectLinkActive({ deepLinkId: 'discover:log-explorer' });
|
||||||
await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({ deepLinkId: 'discover' });
|
await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({
|
||||||
|
deepLinkId: 'discover:log-explorer',
|
||||||
|
});
|
||||||
await expect(await browser.getCurrentUrl()).contain('/app/discover');
|
await expect(await browser.getCurrentUrl()).contain('/app/discover');
|
||||||
|
|
||||||
// check the aiops subsection
|
// check the aiops subsection
|
||||||
|
@ -78,12 +79,11 @@ export default function ({ getPageObject, getService }: FtrProviderContext) {
|
||||||
|
|
||||||
it('navigate using search', async () => {
|
it('navigate using search', async () => {
|
||||||
await svlCommonNavigation.search.showSearch();
|
await svlCommonNavigation.search.showSearch();
|
||||||
// TODO: test something oblt project specific instead of generic discover
|
await svlCommonNavigation.search.searchFor('discover log explorer');
|
||||||
await svlCommonNavigation.search.searchFor('discover');
|
|
||||||
await svlCommonNavigation.search.clickOnOption(0);
|
await svlCommonNavigation.search.clickOnOption(0);
|
||||||
await svlCommonNavigation.search.hideSearch();
|
await svlCommonNavigation.search.hideSearch();
|
||||||
|
|
||||||
await expect(await browser.getCurrentUrl()).contain('/app/discover');
|
await expect(await browser.getCurrentUrl()).contain('/app/discover#/p/log-explorer');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue