Skip to content

Commit

Permalink
Merge branch 'master' into expressions-server-side
Browse files Browse the repository at this point in the history
  • Loading branch information
elasticmachine authored Feb 13, 2020
2 parents 25869b7 + 83cce37 commit 6851a8d
Show file tree
Hide file tree
Showing 13 changed files with 179 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@

## ApplicationStart.getUrlForApp() method

Returns a relative URL to a given app, including the global base path.
Returns an URL to a given app, including the global base path. By default, the URL is relative (/basePath/app/my-app). Use the `absolute` option to generate an absolute url (http://host:port/basePath/app/my-app)

Note that when generating absolute urls, the protocol, host and port are determined from the browser location.

<b>Signature:</b>

```typescript
getUrlForApp(appId: string, options?: {
path?: string;
absolute?: boolean;
}): string;
```

Expand All @@ -19,7 +22,7 @@ getUrlForApp(appId: string, options?: {
| Parameter | Type | Description |
| --- | --- | --- |
| appId | <code>string</code> | |
| options | <code>{</code><br/><code> path?: string;</code><br/><code> }</code> | |
| options | <code>{</code><br/><code> path?: string;</code><br/><code> absolute?: boolean;</code><br/><code> }</code> | |

<b>Returns:</b>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export interface ApplicationStart

| Method | Description |
| --- | --- |
| [getUrlForApp(appId, options)](./kibana-plugin-public.applicationstart.geturlforapp.md) | Returns a relative URL to a given app, including the global base path. |
| [getUrlForApp(appId, options)](./kibana-plugin-public.applicationstart.geturlforapp.md) | Returns an URL to a given app, including the global base path. By default, the URL is relative (/basePath/app/my-app). Use the <code>absolute</code> option to generate an absolute url (http://host:port/basePath/app/my-app)<!-- -->Note that when generating absolute urls, the protocol, host and port are determined from the browser location. |
| [navigateToApp(appId, options)](./kibana-plugin-public.applicationstart.navigatetoapp.md) | Navigate to a given app |
| [registerMountContext(contextName, provider)](./kibana-plugin-public.applicationstart.registermountcontext.md) | Register a context provider for application mounting. Will only be available to applications that depend on the plugin that registered this context. Deprecated, use [CoreSetup.getStartServices()](./kibana-plugin-public.coresetup.getstartservices.md)<!-- -->. |

11 changes: 10 additions & 1 deletion src/core/public/application/application_service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -580,14 +580,23 @@ describe('#start()', () => {

it('creates URLs with path parameter', async () => {
service.setup(setupDeps);

const { getUrlForApp } = await service.start(startDeps);

expect(getUrlForApp('app1', { path: 'deep/link' })).toBe('/base-path/app/app1/deep/link');
expect(getUrlForApp('app1', { path: '/deep//link/' })).toBe('/base-path/app/app1/deep/link');
expect(getUrlForApp('app1', { path: '//deep/link//' })).toBe('/base-path/app/app1/deep/link');
expect(getUrlForApp('app1', { path: 'deep/link///' })).toBe('/base-path/app/app1/deep/link');
});

it('creates absolute URLs when `absolute` parameter is true', async () => {
service.setup(setupDeps);
const { getUrlForApp } = await service.start(startDeps);

expect(getUrlForApp('app1', { absolute: true })).toBe('http://localhost/base-path/app/app1');
expect(getUrlForApp('app2', { path: 'deep/link', absolute: true })).toBe(
'http://localhost/base-path/app/app2/deep/link'
);
});
});

describe('navigateToApp', () => {
Expand Down
16 changes: 14 additions & 2 deletions src/core/public/application/application_service.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -272,8 +272,13 @@ export class ApplicationService {
takeUntil(this.stop$)
),
registerMountContext: this.mountContext.registerContext,
getUrlForApp: (appId, { path }: { path?: string } = {}) =>
http.basePath.prepend(getAppUrl(availableMounters, appId, path)),
getUrlForApp: (
appId,
{ path, absolute = false }: { path?: string; absolute?: boolean } = {}
) => {
const relUrl = http.basePath.prepend(getAppUrl(availableMounters, appId, path));
return absolute ? relativeToAbsolute(relUrl) : relUrl;
},
navigateToApp: async (appId, { path, state }: { path?: string; state?: any } = {}) => {
if (await this.shouldNavigate(overlays)) {
this.appLeaveHandlers.delete(this.currentAppId$.value!);
Expand Down Expand Up @@ -364,3 +369,10 @@ const updateStatus = <T extends AppBase>(app: T, statusUpdaters: AppUpdaterWrapp
...changes,
};
};

function relativeToAbsolute(url: string) {
// convert all link urls to absolute urls
const a = document.createElement('a');
a.setAttribute('href', url);
return a.href;
}
10 changes: 8 additions & 2 deletions src/core/public/application/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -593,11 +593,17 @@ export interface ApplicationStart {
navigateToApp(appId: string, options?: { path?: string; state?: any }): Promise<void>;

/**
* Returns a relative URL to a given app, including the global base path.
* Returns an URL to a given app, including the global base path.
* By default, the URL is relative (/basePath/app/my-app).
* Use the `absolute` option to generate an absolute url (http://host:port/basePath/app/my-app)
*
* Note that when generating absolute urls, the protocol, host and port are determined from the browser location.
*
* @param appId
* @param options.path - optional path inside application to deep link to
* @param options.absolute - if true, will returns an absolute url instead of a relative one
*/
getUrlForApp(appId: string, options?: { path?: string }): string;
getUrlForApp(appId: string, options?: { path?: string; absolute?: boolean }): string;

/**
* Register a context provider for application mounting. Will only be available to applications that depend on the
Expand Down
1 change: 1 addition & 0 deletions src/core/public/public.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ export interface ApplicationStart {
currentAppId$: Observable<string | undefined>;
getUrlForApp(appId: string, options?: {
path?: string;
absolute?: boolean;
}): string;
navigateToApp(appId: string, options?: {
path?: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,47 +4,44 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { HOSTS_PAGE } from '../../../urls/navigation';
import {
waitForAllHostsToBeLoaded,
dragAndDropFirstHostToTimeline,
dragFirstHostToTimeline,
dragFirstHostToEmptyTimelineDataProviders,
} from '../../../tasks/hosts/all_hosts';
import { HOSTS_NAMES } from '../../../screens/hosts/all_hosts';
import { DEFAULT_TIMEOUT, loginAndWaitForPage } from '../../../tasks/login';
import { openTimeline, createNewTimeline } from '../../../tasks/timeline/main';
import {
TIMELINE_DATA_PROVIDERS_EMPTY,
TIMELINE_DATA_PROVIDERS,
TIMELINE_DROPPED_DATA_PROVIDERS,
TIMELINE_DATA_PROVIDERS_EMPTY,
} from '../../lib/timeline/selectors';
import {
createNewTimeline,
dragFromAllHostsToTimeline,
toggleTimelineVisibility,
} from '../../lib/timeline/helpers';
import { ALL_HOSTS_WIDGET_DRAGGABLE_HOSTS } from '../../lib/hosts/selectors';
import { HOSTS_PAGE } from '../../lib/urls';
import { waitForAllHostsWidget } from '../../lib/hosts/helpers';
import { DEFAULT_TIMEOUT, loginAndWaitForPage } from '../../lib/util/helpers';
import { drag, dragWithoutDrop } from '../../lib/drag_n_drop/helpers';
} from '../../../screens/timeline/main';

describe('timeline data providers', () => {
before(() => {
loginAndWaitForPage(HOSTS_PAGE);
waitForAllHostsWidget();
waitForAllHostsToBeLoaded();
});

beforeEach(() => {
toggleTimelineVisibility();
openTimeline();
});

afterEach(() => {
createNewTimeline();
});

it('renders the data provider of a host dragged from the All Hosts widget on the hosts page', () => {
dragFromAllHostsToTimeline();
dragAndDropFirstHostToTimeline();

cy.get(TIMELINE_DROPPED_DATA_PROVIDERS, {
timeout: DEFAULT_TIMEOUT + 10 * 1000,
})
cy.get(TIMELINE_DROPPED_DATA_PROVIDERS, { timeout: DEFAULT_TIMEOUT })
.first()
.invoke('text')
.then(dataProviderText => {
// verify the data provider displays the same `host.name` as the host dragged from the `All Hosts` widget
cy.get(ALL_HOSTS_WIDGET_DRAGGABLE_HOSTS)
cy.get(HOSTS_NAMES)
.first()
.invoke('text')
.should(hostname => {
Expand All @@ -54,9 +51,7 @@ describe('timeline data providers', () => {
});

it('sets the background to euiColorSuccess with a 10% alpha channel when the user starts dragging a host, but is not hovering over the data providers', () => {
cy.get(ALL_HOSTS_WIDGET_DRAGGABLE_HOSTS)
.first()
.then(host => drag(host));
dragFirstHostToTimeline();

cy.get(TIMELINE_DATA_PROVIDERS).should(
'have.css',
Expand All @@ -65,30 +60,14 @@ describe('timeline data providers', () => {
);
});

it('sets the background to euiColorSuccess with a 20% alpha channel when the user starts dragging a host AND is hovering over the data providers', () => {
cy.get(ALL_HOSTS_WIDGET_DRAGGABLE_HOSTS)
.first()
.then(host => drag(host));

cy.get(TIMELINE_DATA_PROVIDERS_EMPTY).then(dataProvidersDropArea =>
dragWithoutDrop(dataProvidersDropArea)
);
it('sets the background to euiColorSuccess with a 20% alpha channel and renders the dashed border color as euiColorSuccess when the user starts dragging a host AND is hovering over the data providers', () => {
dragFirstHostToEmptyTimelineDataProviders();

cy.get(TIMELINE_DATA_PROVIDERS_EMPTY).should(
'have.css',
'background',
'rgba(1, 125, 115, 0.2) none repeat scroll 0% 0% / auto padding-box border-box'
);
});

it('renders the dashed border color as euiColorSuccess when hovering over the data providers', () => {
cy.get(ALL_HOSTS_WIDGET_DRAGGABLE_HOSTS)
.first()
.then(host => drag(host));

cy.get(TIMELINE_DATA_PROVIDERS_EMPTY).then(dataProvidersDropArea =>
dragWithoutDrop(dataProvidersDropArea)
);

cy.get(TIMELINE_DATA_PROVIDERS).should(
'have.css',
Expand Down
9 changes: 9 additions & 0 deletions x-pack/legacy/plugins/siem/cypress/screens/hosts/all_hosts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* 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 const ALL_HOSTS_TABLE = '[data-test-subj="table-allHosts-loading-false"]';

export const HOSTS_NAMES = '[data-test-subj="draggable-content-host.name"]';
12 changes: 12 additions & 0 deletions x-pack/legacy/plugins/siem/cypress/screens/timeline/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,15 @@ export const SERVER_SIDE_EVENT_COUNT = '[data-test-subj="server-side-event-count
export const TIMELINE_SETTINGS_ICON = '[data-test-subj="settings-gear"]';

export const TIMELINE_INSPECT_BUTTON = '[data-test-subj="inspect-empty-button"]';

export const CLOSE_TIMELINE_BTN = '[data-test-subj="close-timeline"]';

export const CREATE_NEW_TIMELINE = '[data-test-subj="timeline-new"]';

export const TIMELINE_DATA_PROVIDERS = '[data-test-subj="dataProviders"]';

export const TIMELINE_DATA_PROVIDERS_EMPTY =
'[data-test-subj="dataProviders"] [data-test-subj="empty"]';

export const TIMELINE_DROPPED_DATA_PROVIDERS =
'[data-test-subj="dataProviders"] [data-test-subj="providerContainer"]';
50 changes: 50 additions & 0 deletions x-pack/legacy/plugins/siem/cypress/tasks/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* 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.
*/

const primaryButton = 0;

/**
* To overcome the React Beautiful DND sloppy click detection threshold:
* https://github.com/atlassian/react-beautiful-dnd/blob/67b96c8d04f64af6b63ae1315f74fc02b5db032b/docs/sensors/mouse.md#sloppy-clicks-and-click-prevention-
*/
const dndSloppyClickDetectionThreshold = 5;

/** Starts dragging the subject */
export const drag = (subject: JQuery<HTMLElement>) => {
const subjectLocation = subject[0].getBoundingClientRect();

cy.wrap(subject)
.trigger('mousedown', {
button: primaryButton,
clientX: subjectLocation.left,
clientY: subjectLocation.top,
force: true,
})
.wait(1)
.trigger('mousemove', {
button: primaryButton,
clientX: subjectLocation.left + dndSloppyClickDetectionThreshold,
clientY: subjectLocation.top,
force: true,
})
.wait(1);
};

/** "Drops" the subject being dragged on the specified drop target */
export const drop = (dropTarget: JQuery<HTMLElement>) => {
cy.wrap(dropTarget)
.trigger('mousemove', { button: primaryButton, force: true })
.wait(1)
.trigger('mouseup', { force: true })
.wait(1);
};

/** Drags the subject being dragged on the specified drop target, but does not drop it */
export const dragWithoutDrop = (dropTarget: JQuery<HTMLElement>) => {
cy.wrap(dropTarget).trigger('mousemove', 'center', {
button: primaryButton,
});
};
40 changes: 40 additions & 0 deletions x-pack/legacy/plugins/siem/cypress/tasks/hosts/all_hosts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* 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 { ALL_HOSTS_TABLE, HOSTS_NAMES } from '../../screens/hosts/all_hosts';
import {
TIMELINE_DATA_PROVIDERS,
TIMELINE_DATA_PROVIDERS_EMPTY,
} from '../../screens/timeline/main';
import { DEFAULT_TIMEOUT } from '../../tasks/login';
import { drag, drop, dragWithoutDrop } from '../../tasks/common';

export const waitForAllHostsToBeLoaded = () => {
cy.get(ALL_HOSTS_TABLE, { timeout: DEFAULT_TIMEOUT }).should('exist');
};

export const dragAndDropFirstHostToTimeline = () => {
cy.get(HOSTS_NAMES)
.first()
.then(firstHost => drag(firstHost));
cy.get(TIMELINE_DATA_PROVIDERS).then(dataProvidersDropArea => drop(dataProvidersDropArea));
};

export const dragFirstHostToTimeline = () => {
cy.get(HOSTS_NAMES)
.first()
.then(host => drag(host));
};

export const dragFirstHostToEmptyTimelineDataProviders = () => {
cy.get(HOSTS_NAMES)
.first()
.then(host => drag(host));

cy.get(TIMELINE_DATA_PROVIDERS_EMPTY).then(dataProvidersDropArea =>
dragWithoutDrop(dataProvidersDropArea)
);
};
8 changes: 8 additions & 0 deletions x-pack/legacy/plugins/siem/cypress/tasks/timeline/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
SERVER_SIDE_EVENT_COUNT,
TIMELINE_SETTINGS_ICON,
TIMELINE_INSPECT_BUTTON,
CREATE_NEW_TIMELINE,
CLOSE_TIMELINE_BTN,
} from '../../screens/timeline/main';

export const hostExistsQuery = 'host.name: *';
Expand Down Expand Up @@ -44,3 +46,9 @@ export const openTimelineInspectButton = () => {
cy.get(TIMELINE_INSPECT_BUTTON, { timeout: DEFAULT_TIMEOUT }).should('not.be.disabled');
cy.get(TIMELINE_INSPECT_BUTTON).trigger('click', { force: true });
};

export const createNewTimeline = () => {
cy.get(TIMELINE_SETTINGS_ICON).click({ force: true });
cy.get(CREATE_NEW_TIMELINE).click();
cy.get(CLOSE_TIMELINE_BTN).click({ force: true });
};
1 change: 1 addition & 0 deletions x-pack/legacy/plugins/siem/cypress/urls/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

export const TIMELINES_PAGE = '/app/siem#/timelines';
export const OVERVIEW_PAGE = '/app/siem#/overview';
export const HOSTS_PAGE = '/app/siem#/hosts/allHosts';
export const HOSTS_PAGE_TAB_URLS = {
allHosts: '/app/siem#/hosts/allHosts',
anomalies: '/app/siem#/hosts/anomalies',
Expand Down

0 comments on commit 6851a8d

Please sign in to comment.