Merge remote-tracking branch 'origin/master' into feature/merge-code

This commit is contained in:
Fuyao Zhao 2019-01-15 13:31:06 -08:00
commit f11e08068f
133 changed files with 1209 additions and 1020 deletions

View file

@ -103,3 +103,16 @@ The port is now protocol dependent: https ports will use 443, and http ports wil
`server.ssl.supportedProtocols`
*Impact:* Users relying upon TLSv1 will be unable to use Kibana unless `server.ssl.supportedProtocols` is explicitly set.
[float]
=== kibana.yml setting `server.ssl.cert` is no longer valid
*Details:* This deprecated setting has been removed and `server.ssl.certificate` should be used instead.
*Impact:* Users with `server.ssl.cert` set should use `server.ssl.certificate` instead
[float]
=== kibana.yml `server.ssl.enabled` must be set to `true` to enable SSL
*Details:* Previously, if `server.ssl.certificate` and `server.ssl.key` were set, SSL would automatically be enabled.
It's now required that the user sets `server.ssl.enabled` to true for this to occur.
*Impact:* Users with both `server.ssl.certificate` and `server.ssl.key` set must now also set `server.ssl.enabled` to enable SSL.

View file

@ -95,7 +95,7 @@
},
"dependencies": {
"@elastic/datemath": "5.0.2",
"@elastic/eui": "6.0.1",
"@elastic/eui": "6.3.1",
"@elastic/filesaver": "1.1.2",
"@elastic/good": "8.1.1-kibana2",
"@elastic/numeral": "2.3.2",

View file

@ -45,7 +45,7 @@ exports.downloadSnapshot = async function installSnapshot({
log = defaultLog,
}) {
const fileName = getFilename(license, version);
const url = `https://snapshots.elastic.co/downloads/elasticsearch/${fileName}`;
const url = getUrl(fileName);
const dest = path.resolve(basePath, 'cache', fileName);
log.info('version: %s', chalk.bold(version));
@ -150,3 +150,13 @@ function getFilename(license, version) {
return `${basename}-SNAPSHOT.${extension}`;
}
function getUrl(fileName) {
if (process.env.TEST_ES_SNAPSHOT_VERSION) {
return `https://snapshots.elastic.co/${
process.env.TEST_ES_SNAPSHOT_VERSION
}/downloads/elasticsearch/${fileName}`;
} else {
return `https://snapshots.elastic.co/downloads/elasticsearch/${fileName}`;
}
}

View file

@ -2944,8 +2944,7 @@ main {
/* 1 */
padding: 10px;
height: 40px;
background-color: #ffffff;
border: 1px solid #D3DAE6; }
background-color: #ffffff; }
.kuiToolBarFooterSection {
display: -webkit-box;

View file

@ -37,6 +37,7 @@ export interface Brand {
export interface Breadcrumb {
text: string;
href?: string;
'data-test-subj'?: string;
}
export class ChromeService {

View file

@ -17,40 +17,6 @@ Object {
}
`;
exports[`#get correctly handles server config.: deprecated missing ssl.enabled 1`] = `
Object {
"autoListen": true,
"basePath": "/abc",
"cors": false,
"host": "host",
"maxPayload": 1000,
"port": 1234,
"rewriteBasePath": false,
"ssl": Object {
"certificate": "cert",
"enabled": true,
"key": "key",
},
}
`;
exports[`#get correctly handles server config.: deprecated ssl.cert 1`] = `
Object {
"autoListen": true,
"basePath": "/abc",
"cors": false,
"host": "host",
"maxPayload": 1000,
"port": 1234,
"rewriteBasePath": false,
"ssl": Object {
"certificate": "deprecated-cert",
"enabled": true,
"key": "key",
},
}
`;
exports[`#get correctly handles server config.: disabled ssl 1`] = `
Object {
"autoListen": true,

View file

@ -90,40 +90,8 @@ describe('#get', () => {
},
});
const configAdapterWithCert = new LegacyObjectToConfigAdapter({
server: {
autoListen: true,
basePath: '/abc',
cors: false,
host: 'host',
maxPayloadBytes: 1000,
port: 1234,
rewriteBasePath: false,
ssl: { enabled: true, cert: 'deprecated-cert', key: 'key' },
someNotSupportedValue: 'val',
},
});
const configAdapterWithoutSSLEnabled = new LegacyObjectToConfigAdapter({
server: {
autoListen: true,
basePath: '/abc',
cors: false,
host: 'host',
maxPayloadBytes: 1000,
port: 1234,
rewriteBasePath: false,
ssl: { certificate: 'cert', key: 'key' },
someNotSupportedValue: 'val',
},
});
expect(configAdapter.get('server')).toMatchSnapshot('default');
expect(configAdapterWithDisabledSSL.get('server')).toMatchSnapshot('disabled ssl');
expect(configAdapterWithCert.get('server')).toMatchSnapshot('deprecated ssl.cert');
expect(configAdapterWithoutSSLEnabled.get('server')).toMatchSnapshot(
'deprecated missing ssl.enabled'
);
});
});

View file

@ -67,26 +67,10 @@ export class LegacyObjectToConfigAdapter extends ObjectToConfigAdapter {
maxPayload: configValue.maxPayloadBytes,
port: configValue.port,
rewriteBasePath: configValue.rewriteBasePath,
ssl: configValue.ssl && LegacyObjectToConfigAdapter.transformSSL(configValue.ssl),
ssl: configValue.ssl,
};
}
private static transformSSL(configValue: Record<string, any>) {
// `server.ssl.cert` is deprecated, legacy platform will issue deprecation warning.
if (configValue.cert) {
configValue.certificate = configValue.cert;
delete configValue.cert;
}
// Enabling ssl by only specifying server.ssl.certificate and server.ssl.key is deprecated,
// legacy platform will issue deprecation warning.
if (typeof configValue.enabled !== 'boolean' && configValue.certificate && configValue.key) {
configValue.enabled = true;
}
return configValue;
}
private static transformPlugins(configValue: Record<string, any>) {
// This property is the only one we use from the existing `plugins` config node
// since `scanDirs` and `paths` aren't respected by new platform plugin discovery.

View file

@ -29,9 +29,11 @@ fi
export KIBANA_DIR="$dir"
export XPACK_DIR="$KIBANA_DIR/x-pack"
export PARENT_DIR="$(cd "$KIBANA_DIR/.."; pwd)"
export TEST_ES_SNAPSHOT_VERSION="7.0.0-fbd1a09d"
echo "-> KIBANA_DIR $KIBANA_DIR"
echo "-> XPACK_DIR $XPACK_DIR"
echo "-> PARENT_DIR $PARENT_DIR"
echo "-> TEST_ES_SNAPSHOT_VERSION $TEST_ES_SNAPSHOT_VERSION"
###
### download node

View file

@ -93,6 +93,7 @@ exports[`Table should render normally 1`] = `
},
]
}
executeQueryOptions={Object {}}
items={
Array [
Object {

View file

@ -55,6 +55,7 @@ exports[`Table should render normally 1`] = `
},
]
}
executeQueryOptions={Object {}}
items={
Array [
Object {

View file

@ -86,6 +86,7 @@ exports[`Table should render normally 1`] = `
},
]
}
executeQueryOptions={Object {}}
items={
Array [
Object {

View file

@ -50,6 +50,7 @@ exports[`ObjectsTable delete should show a confirm modal 1`] = `
},
]
}
executeQueryOptions={Object {}}
items={
Array [
Object {

View file

@ -93,6 +93,7 @@ exports[`Flyout conflicts should allow conflict resolution 1`] = `
},
]
}
executeQueryOptions={Object {}}
items={
Array [
Object {

View file

@ -95,6 +95,7 @@ exports[`Relationships should render dashboards normally 1`] = `
},
]
}
executeQueryOptions={Object {}}
items={
Array [
Object {
@ -302,6 +303,7 @@ exports[`Relationships should render searches normally 1`] = `
},
]
}
executeQueryOptions={Object {}}
items={
Array [
Object {
@ -369,6 +371,7 @@ exports[`Relationships should render searches normally 1`] = `
},
]
}
executeQueryOptions={Object {}}
items={
Array [
Object {
@ -481,6 +484,7 @@ exports[`Relationships should render visualizations normally 1`] = `
},
]
}
executeQueryOptions={Object {}}
items={
Array [
Object {

View file

@ -119,7 +119,7 @@ export class EMSClientV66 {
if (!i18nObject) {
return '';
}
return i18nObject[this._language] ? i18nObject[this._language] : i18nObject[DEFAULT_LANGUAGE];
return i18nObject[this._language] ? i18nObject[this._language] : i18nObject[DEFAULT_LANGUAGE];
}
/**
@ -139,9 +139,7 @@ export class EMSClientV66 {
}
throw new Error(`Unable to retrieve manifest from ${manifestUrl}: ${e.message}`);
} finally {
return result
? await result.json()
: null;
return result ? await result.json() : null;
}
}

View file

@ -27,6 +27,17 @@ export class FileLayer {
this._emsClient = emsClient;
}
getAttributions() {
const attributions = this._config.attribution.map(attribution => {
const url = this._emsClient.getValueInLanguage(attribution.url);
const label = this._emsClient.getValueInLanguage(attribution.label);
return {
url: url,
label: label
};
});
return attributions;
}
getHTMLAttribution() {
const attributions = this._config.attribution.map(attribution => {

View file

@ -34,6 +34,10 @@ export class TMSService {
return this._emsClient.sanitizeMarkdown(this._config.attribution);
}
getMarkdownAttribution() {
return this._config.attribution;
}
getMinZoom() {
return this._config.minZoom;
}

View file

@ -17,22 +17,12 @@
* under the License.
*/
import _, { partial, set } from 'lodash';
import _, { set } from 'lodash';
import { createTransform, Deprecations } from '../../deprecation';
import { unset } from '../../utils';
const { rename, unused } = Deprecations;
const serverSslEnabled = (settings, log) => {
const has = partial(_.has, settings);
const set = partial(_.set, settings);
if (!has('server.ssl.enabled') && has('server.ssl.certificate') && has('server.ssl.key')) {
set('server.ssl.enabled', true);
log('Enabling ssl by only specifying server.ssl.certificate and server.ssl.key is deprecated. Please set server.ssl.enabled to true');
}
};
const savedObjectsIndexCheckTimeout = (settings, log) => {
if (_.has(settings, 'savedObjects.indexCheckTimeout')) {
log('savedObjects.indexCheckTimeout is no longer necessary.');
@ -67,7 +57,6 @@ const loggingTimezone = (settings, log) => {
const deprecations = [
//server
rename('server.ssl.cert', 'server.ssl.certificate'),
unused('server.xsrf.token'),
unused('uiSettings.enabled'),
rename('optimize.lazy', 'optimize.watch'),
@ -76,7 +65,6 @@ const deprecations = [
rename('optimize.lazyPrebuild', 'optimize.watchPrebuild'),
rename('optimize.lazyProxyTimeout', 'optimize.watchProxyTimeout'),
rename('i18n.defaultLocale', 'i18n.locale'),
serverSslEnabled,
savedObjectsIndexCheckTimeout,
rewriteBasePath,
loggingTimezone,

View file

@ -22,59 +22,6 @@ import { transformDeprecations } from './transform_deprecations';
describe('server/config', function () {
describe('transformDeprecations', function () {
describe('server.ssl.enabled', function () {
it('sets enabled to true when certificate and key are set', function () {
const settings = {
server: {
ssl: {
certificate: '/cert.crt',
key: '/key.key'
}
}
};
const result = transformDeprecations(settings);
expect(result.server.ssl.enabled).toBe(true);
});
it('logs a message when automatically setting enabled to true', function () {
const settings = {
server: {
ssl: {
certificate: '/cert.crt',
key: '/key.key'
}
}
};
const log = sinon.spy();
transformDeprecations(settings, log);
expect(log.calledOnce).toBe(true);
});
it(`doesn't set enabled when key and cert aren't set`, function () {
const settings = {
server: {
ssl: {}
}
};
const result = transformDeprecations(settings);
expect(result.server.ssl.enabled).toBe(undefined);
});
it(`doesn't log a message when not automatically setting enabled`, function () {
const settings = {
server: {
ssl: {}
}
};
const log = sinon.spy();
transformDeprecations(settings, log);
expect(log.called).toBe(false);
});
});
describe('savedObjects.indexCheckTimeout', () => {
it('removes the indexCheckTimeout and savedObjects properties', () => {

View file

@ -67,6 +67,7 @@ export interface PutMappingOpts {
body: DocMapping;
index: string;
type: string;
include_type_name?: boolean;
}
export interface PutTemplateOpts {
@ -87,6 +88,7 @@ export interface IndexOpts {
export interface IndexCreationOpts {
index: string;
include_type_name?: boolean;
body?: {
mappings?: IndexMapping;
settings?: {

View file

@ -222,7 +222,12 @@ export async function migrationsUpToDate(
* @param {IndexMapping} mappings
*/
export function putMappings(callCluster: CallCluster, index: string, mappings: IndexMapping) {
return callCluster('indices.putMapping', { body: mappings.doc, index, type: ROOT_TYPE });
return callCluster('indices.putMapping', {
body: mappings.doc,
index,
type: ROOT_TYPE,
include_type_name: true,
});
}
export async function createIndex(
@ -230,7 +235,11 @@ export async function createIndex(
index: string,
mappings?: IndexMapping
) {
await callCluster('indices.create', { body: { mappings, settings }, index });
await callCluster('indices.create', {
body: { mappings, settings },
index,
include_type_name: true,
});
}
export async function deleteIndex(callCluster: CallCluster, index: string) {

View file

@ -53,6 +53,7 @@ describe('IndexMigrator', () => {
},
},
index: '.kibana_1',
include_type_name: true,
type: ROOT_TYPE,
});
});
@ -89,6 +90,7 @@ describe('IndexMigrator', () => {
settings: { number_of_shards: 1, auto_expand_replicas: '0-1' },
},
index: '.kibana_1',
include_type_name: true,
});
});
@ -175,6 +177,7 @@ describe('IndexMigrator', () => {
},
},
},
include_type_name: true,
});
await new IndexMigrator(opts).migrate();
@ -201,6 +204,7 @@ describe('IndexMigrator', () => {
settings: { number_of_shards: 1, auto_expand_replicas: '0-1' },
},
index: '.kibana_2',
include_type_name: true,
});
});

View file

@ -38,6 +38,7 @@ export function createSavedObjectsService(server, schema, serializer, migrator)
const index = server.config().get('kibana.index');
await adminCluster.callWithInternalUser('indices.putTemplate', {
name: `kibana_index_template:${index}`,
include_type_name: true,
body: {
template: index,
settings: {

View file

@ -68,6 +68,7 @@ export function initAngularApi(chrome, internals) {
})
.run(internals.capture$httpLoadingCount)
.run(internals.$setupBreadcrumbsAutoClear)
.run(internals.$initNavLinksDeepWatch)
.run(($location, $rootScope, Private, config) => {
chrome.getFirstPathSegment = () => {
return $location.path().split('/')[1];

View file

@ -17,15 +17,33 @@
* under the License.
*/
import * as Rx from 'rxjs';
import { mapTo } from 'rxjs/operators';
import { remove } from 'lodash';
import { relativeToAbsolute } from '../../url/relative_to_absolute';
import { absoluteToParsedUrl } from '../../url/absolute_to_parsed_url';
export function initChromeNavApi(chrome, internals) {
const navUpdate$ = new Rx.BehaviorSubject(undefined);
chrome.getNavLinks = function () {
return internals.nav;
};
chrome.getNavLinks$ = function () {
return navUpdate$.pipe(mapTo(internals.nav));
};
// track navLinks with $rootScope.$watch like the old nav used to, necessary
// as long as random parts of the app are directly mutating the navLinks
internals.$initNavLinksDeepWatch = function ($rootScope) {
$rootScope.$watch(
() => internals.nav,
() => navUpdate$.next(),
true
);
};
chrome.navLinkExists = (id) => {
return !!internals.nav.find(link => link.id === id);
};

View file

@ -4,6 +4,7 @@ exports[`HeaderBreadcrumbs renders updates to the breadcrumbs$ observable 1`] =
<span
aria-current="page"
className="euiBreadcrumb euiBreadcrumb--last"
data-test-subj="breadcrumb first last"
title="First"
>
First
@ -15,11 +16,13 @@ Array [
<EuiLink
className="euiBreadcrumb"
color="subdued"
data-test-subj="breadcrumb first"
title="First"
type="button"
>
<button
className="euiLink euiLink--subdued euiBreadcrumb"
data-test-subj="breadcrumb first"
title="First"
type="button"
>
@ -28,6 +31,7 @@ Array [
</EuiLink>,
<button
className="euiLink euiLink--subdued euiBreadcrumb"
data-test-subj="breadcrumb first"
title="First"
type="button"
>
@ -36,6 +40,7 @@ Array [
<span
aria-current="page"
className="euiBreadcrumb euiBreadcrumb--last"
data-test-subj="breadcrumb last"
title="Second"
>
Second

View file

@ -46,7 +46,7 @@ interface Props {
breadcrumbs$: Rx.Observable<Breadcrumb[]>;
homeHref: string;
isVisible: boolean;
navLinks: NavLink[];
navLinks$: Rx.Observable<NavLink[]>;
navControls: ChromeHeaderNavControlsRegistry;
intl: InjectedIntl;
}
@ -67,7 +67,7 @@ class HeaderUI extends Component<Props> {
}
public render() {
const { appTitle, breadcrumbs$, isVisible, navControls, navLinks } = this.props;
const { appTitle, breadcrumbs$, isVisible, navControls, navLinks$ } = this.props;
if (!isVisible) {
return null;
@ -90,7 +90,7 @@ class HeaderUI extends Component<Props> {
<HeaderNavControls navControls={rightNavControls} />
<EuiHeaderSectionItem>
<HeaderAppMenu navLinks={navLinks} />
<HeaderAppMenu navLinks$={navLinks$} />
</EuiHeaderSectionItem>
</EuiHeaderSection>
</EuiHeader>

View file

@ -18,6 +18,7 @@
*/
import React, { Component } from 'react';
import * as Rx from 'rxjs';
import {
// TODO: add type annotations
@ -36,25 +37,45 @@ import { InjectedIntl, injectI18n } from '@kbn/i18n/react';
import { NavLink } from '../';
interface Props {
navLinks: NavLink[];
navLinks$: Rx.Observable<NavLink[]>;
intl: InjectedIntl;
}
interface State {
isOpen: boolean;
navLinks: NavLink[];
}
class HeaderAppMenuUI extends Component<Props, State> {
private subscription?: Rx.Subscription;
constructor(props: Props) {
super(props);
this.state = {
isOpen: false,
navLinks: [],
};
}
public componentDidMount() {
this.subscription = this.props.navLinks$.subscribe({
next: navLinks => {
this.setState({ navLinks });
},
});
}
public componentWillUnmount() {
if (this.subscription) {
this.subscription.unsubscribe();
this.subscription = undefined;
}
}
public render() {
const { navLinks = [], intl } = this.props;
const { intl } = this.props;
const { navLinks } = this.state;
const button = (
<EuiHeaderSectionItemButton
@ -103,7 +124,7 @@ class HeaderAppMenuUI extends Component<Props, State> {
private renderNavLink = (navLink: NavLink) => (
<EuiKeyPadMenuItem
label={navLink.title}
href={navLink.url}
href={navLink.active || !navLink.lastSubUrl ? navLink.url : navLink.lastSubUrl}
key={navLink.id}
onClick={this.closeMenu}
>

View file

@ -17,6 +17,7 @@
* under the License.
*/
import classNames from 'classnames';
import React, { Component } from 'react';
import * as Rx from 'rxjs';
@ -63,13 +64,9 @@ export class HeaderBreadcrumbs extends Component<Props, State> {
}
public render() {
let breadcrumbs = this.state.breadcrumbs;
if (breadcrumbs.length === 0 && this.props.appTitle) {
breadcrumbs = [{ text: this.props.appTitle }];
}
return <EuiHeaderBreadcrumbs breadcrumbs={breadcrumbs} />;
return (
<EuiHeaderBreadcrumbs breadcrumbs={this.getBreadcrumbs()} data-test-subj="breadcrumbs" />
);
}
private subscribe() {
@ -86,4 +83,22 @@ export class HeaderBreadcrumbs extends Component<Props, State> {
delete this.subscription;
}
}
private getBreadcrumbs() {
let breadcrumbs = this.state.breadcrumbs;
if (breadcrumbs.length === 0 && this.props.appTitle) {
breadcrumbs = [{ text: this.props.appTitle }];
}
return breadcrumbs.map((breadcrumb, i) => ({
...breadcrumb,
'data-test-subj': classNames(
'breadcrumb',
breadcrumb['data-test-subj'],
i === 0 && 'first',
i === breadcrumbs.length - 1 && 'last'
),
}));
}
}

View file

@ -27,7 +27,6 @@ const module = uiModules.get('kibana');
module.directive('headerGlobalNav', (reactDirective, chrome, Private) => {
const navControls = Private(chromeHeaderNavControlsRegistry);
const navLinks = chrome.getNavLinks();
const homeHref = chrome.addBasePath('/app/kibana#/home');
return reactDirective(injectI18nProvider(Header), [
@ -39,7 +38,7 @@ module.directive('headerGlobalNav', (reactDirective, chrome, Private) => {
// angular injected React props
{
breadcrumbs$: chrome.breadcrumbs.get$(),
navLinks,
navLinks$: chrome.getNavLinks$(),
navControls,
homeHref
});

View file

@ -37,4 +37,6 @@ export interface NavLink {
url: string;
id: string;
euiIconType: IconType;
lastSubUrl?: string;
active: boolean;
}

View file

@ -74,11 +74,11 @@
var ticks = [],
end = ceilAsLog10(axis.max),
start = floorAsLog10(axis.datamin),
start = floorAsLog10(axis.min),
tick = Number.NaN,
i = 0;
if (axis.datamin === null || axis.datamin <= 0) {
if (axis.min === null || axis.min <= 0) {
// Bad minimum, make ticks from 1 (10**0) to max
start = 0;
axis.min = 0.6;

View file

@ -22,6 +22,7 @@ import expect from 'expect.js';
export default function ({ getService, getPageObjects }) {
const PageObjects = getPageObjects(['dashboard', 'header']);
const dashboardExpect = getService('dashboardExpect');
const pieChart = getService('pieChart');
const browser = getService('browser');
const log = getService('log');
const queryBar = getService('queryBar');
@ -67,7 +68,7 @@ export default function ({ getService, getPageObjects }) {
const query = await queryBar.getQueryString();
expect(query).to.equal('memory:>220000');
await dashboardExpect.pieSliceCount(5);
await pieChart.expectPieSliceCount(5);
await dashboardExpect.panelCount(2);
await dashboardExpect.selectedLegendColorCount('#F9D9F9', 5);
});
@ -83,7 +84,7 @@ export default function ({ getService, getPageObjects }) {
const query = await queryBar.getQueryString();
expect(query).to.equal('memory:>220000');
await dashboardExpect.pieSliceCount(5);
await pieChart.expectPieSliceCount(5);
await dashboardExpect.panelCount(2);
await dashboardExpect.selectedLegendColorCount('#F9D9F9', 5);
});

View file

@ -24,6 +24,7 @@ export default function ({ getService, getPageObjects }) {
const dashboardAddPanel = getService('dashboardAddPanel');
const testSubjects = getService('testSubjects');
const filterBar = getService('filterBar');
const pieChart = getService('pieChart');
const PageObjects = getPageObjects(['dashboard', 'header', 'visualize']);
describe('dashboard filter bar', async () => {
@ -92,11 +93,11 @@ export default function ({ getService, getPageObjects }) {
it('are added when a pie chart slice is clicked', async function () {
await dashboardAddPanel.addVisualization('Rendering Test: pie');
await PageObjects.dashboard.waitForRenderComplete();
await PageObjects.dashboard.filterOnPieSlice('4,886');
await pieChart.filterOnPieSlice('4,886');
const filters = await PageObjects.dashboard.getFilters();
expect(filters.length).to.equal(1);
await dashboardExpect.pieSliceCount(1);
await pieChart.expectPieSliceCount(1);
});
it('are preserved after saving a dashboard', async () => {
@ -106,7 +107,7 @@ export default function ({ getService, getPageObjects }) {
const filters = await PageObjects.dashboard.getFilters();
expect(filters.length).to.equal(1);
await dashboardExpect.pieSliceCount(1);
await pieChart.expectPieSliceCount(1);
});
it('are preserved after opening a dashboard saved with filters', async () => {
@ -117,7 +118,7 @@ export default function ({ getService, getPageObjects }) {
const filters = await PageObjects.dashboard.getFilters();
expect(filters.length).to.equal(1);
await dashboardExpect.pieSliceCount(1);
await pieChart.expectPieSliceCount(1);
});
});
});

View file

@ -25,6 +25,7 @@ import expect from 'expect.js';
*/
export default function ({ getService, getPageObjects }) {
const dashboardExpect = getService('dashboardExpect');
const pieChart = getService('pieChart');
const queryBar = getService('queryBar');
const dashboardAddPanel = getService('dashboardAddPanel');
const renderable = getService('renderable');
@ -57,7 +58,7 @@ export default function ({ getService, getPageObjects }) {
});
it('filters on pie charts', async () => {
await dashboardExpect.pieSliceCount(0);
await pieChart.expectPieSliceCount(0);
});
it('area, bar and heatmap charts filtered', async () => {
@ -119,7 +120,7 @@ export default function ({ getService, getPageObjects }) {
});
it('filters on pie charts', async () => {
await dashboardExpect.pieSliceCount(0);
await pieChart.expectPieSliceCount(0);
});
it('area, bar and heatmap charts filtered', async () => {
@ -177,7 +178,7 @@ export default function ({ getService, getPageObjects }) {
});
it('pie charts', async () => {
await dashboardExpect.pieSliceCount(5);
await pieChart.expectPieSliceCount(5);
});
it('area, bar and heatmap charts', async () => {
@ -238,7 +239,7 @@ export default function ({ getService, getPageObjects }) {
await dashboardAddPanel.addVisualization('Rendering-Test:-animal-sounds-pie');
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.dashboard.waitForRenderComplete();
await dashboardExpect.pieSliceCount(5);
await pieChart.expectPieSliceCount(5);
await dashboardPanelActions.openContextMenu();
await dashboardPanelActions.clickEdit();
@ -250,13 +251,13 @@ export default function ({ getService, getPageObjects }) {
// We are on the visualize page, not dashboard, so can't use "PageObjects.dashboard.waitForRenderComplete();"
// as that expects an item with the `data-shared-items-count` tag.
await renderable.waitForRender();
await dashboardExpect.pieSliceCount(3);
await pieChart.expectPieSliceCount(3);
await PageObjects.visualize.saveVisualizationExpectSuccess('Rendering Test: animal sounds pie');
await PageObjects.header.clickDashboard();
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.dashboard.waitForRenderComplete();
await dashboardExpect.pieSliceCount(3);
await pieChart.expectPieSliceCount(3);
});
it('Nested visualization filter pills filters data as expected', async () => {
@ -264,14 +265,14 @@ export default function ({ getService, getPageObjects }) {
await dashboardPanelActions.clickEdit();
await PageObjects.header.waitUntilLoadingHasFinished();
await renderable.waitForRender();
await PageObjects.dashboard.filterOnPieSlice('grr');
await pieChart.filterOnPieSlice('grr');
await PageObjects.header.waitUntilLoadingHasFinished();
await dashboardExpect.pieSliceCount(1);
await pieChart.expectPieSliceCount(1);
await PageObjects.visualize.saveVisualizationExpectSuccess('animal sounds pie');
await PageObjects.header.clickDashboard();
await dashboardExpect.pieSliceCount(1);
await pieChart.expectPieSliceCount(1);
});
it('Removing filter pills and query unfiters data as expected', async () => {
@ -283,17 +284,17 @@ export default function ({ getService, getPageObjects }) {
await queryBar.submitQuery();
await filterBar.removeFilter('sound.keyword');
await PageObjects.header.waitUntilLoadingHasFinished();
await dashboardExpect.pieSliceCount(5);
await pieChart.expectPieSliceCount(5);
await PageObjects.visualize.saveVisualization('Rendering Test: animal sounds pie');
await PageObjects.header.clickDashboard();
await dashboardExpect.pieSliceCount(5);
await pieChart.expectPieSliceCount(5);
});
it('Pie chart linked to saved search filters data', async () => {
await dashboardAddPanel.addVisualization('Filter Test: animals: linked to search with filter');
await dashboardExpect.pieSliceCount(7);
await pieChart.expectPieSliceCount(7);
});
it('Pie chart linked to saved search filters shows no data with conflicting dashboard query', async () => {
@ -301,7 +302,7 @@ export default function ({ getService, getPageObjects }) {
await queryBar.submitQuery();
await PageObjects.dashboard.waitForRenderComplete();
await dashboardExpect.pieSliceCount(5);
await pieChart.expectPieSliceCount(5);
});
});
});

View file

@ -21,7 +21,7 @@ import expect from 'expect.js';
export default function ({ getService, getPageObjects }) {
const esArchiver = getService('esArchiver');
const dashboardExpect = getService('dashboardExpect');
const pieChart = getService('pieChart');
const queryBar = getService('queryBar');
const PageObjects = getPageObjects(['dashboard', 'discover']);
@ -37,7 +37,7 @@ export default function ({ getService, getPageObjects }) {
const headers = await PageObjects.discover.getColumnHeaders();
expect(headers.length).to.be(0);
await dashboardExpect.pieSliceCount(0);
await pieChart.expectPieSliceCount(0);
});
});
}

View file

@ -29,6 +29,8 @@ export default function ({ getService, getPageObjects }) {
const testSubjects = getService('testSubjects');
const browser = getService('browser');
const queryBar = getService('queryBar');
const pieChart = getService('pieChart');
const inspector = getService('inspector');
const retry = getService('retry');
const dashboardPanelActions = getService('dashboardPanelActions');
const dashboardAddPanel = getService('dashboardAddPanel');
@ -140,8 +142,8 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.dashboard.saveDashboard('No local edits');
await dashboardPanelActions.openInspector();
const tileMapData = await PageObjects.visualize.getInspectorTableData();
await PageObjects.visualize.closeInspector();
const tileMapData = await inspector.getTableData();
await inspector.close();
await PageObjects.dashboard.switchToEditMode();
await dashboardPanelActions.openContextMenu();
@ -157,8 +159,8 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.header.clickDashboard();
await dashboardPanelActions.openInspector();
const changedTileMapData = await PageObjects.visualize.getInspectorTableData();
await PageObjects.visualize.closeInspector();
const changedTileMapData = await inspector.getTableData();
await inspector.close();
expect(changedTileMapData.length).to.not.equal(tileMapData.length);
});
@ -228,7 +230,7 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.header.waitUntilLoadingHasFinished();
await retry.try(async () => {
const allPieSlicesColor = await PageObjects.visualize.getAllPieSliceStyles('80,000');
const allPieSlicesColor = await pieChart.getAllPieSliceStyles('80,000');
let whitePieSliceCounts = 0;
allPieSlicesColor.forEach(style => {
if (style.indexOf('rgb(255, 255, 255)') > 0) {
@ -255,7 +257,7 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.header.waitUntilLoadingHasFinished();
await retry.try(async () => {
const pieSliceStyle = await PageObjects.visualize.getPieSliceStyle('80,000');
const pieSliceStyle = await pieChart.getPieSliceStyle('80,000');
// The default green color that was stored with the visualization before any dashboard overrides.
expect(pieSliceStyle.indexOf('rgb(87, 193, 123)')).to.be.greaterThan(0);
});

View file

@ -21,6 +21,7 @@ import { PIE_CHART_VIS_NAME } from '../../page_objects/dashboard_page';
export default function ({ getService, getPageObjects }) {
const dashboardExpect = getService('dashboardExpect');
const pieChart = getService('pieChart');
const dashboardVisualizations = getService('dashboardVisualizations');
const PageObjects = getPageObjects(['dashboard', 'header', 'visualize']);
@ -41,10 +42,10 @@ export default function ({ getService, getPageObjects }) {
it('Visualization updated when time picker changes', async () => {
await PageObjects.dashboard.clickNewDashboard();
await PageObjects.dashboard.addVisualizations([PIE_CHART_VIS_NAME]);
await dashboardExpect.pieSliceCount(0);
await pieChart.expectPieSliceCount(0);
await PageObjects.dashboard.setTimepickerInHistoricalDataRange();
await dashboardExpect.pieSliceCount(10);
await pieChart.expectPieSliceCount(10);
});
it('Saved search updated when time picker changes', async () => {

View file

@ -30,12 +30,13 @@ import expect from 'expect.js';
export default function ({ getService, getPageObjects }) {
const find = getService('find');
const browser = getService('browser');
const pieChart = getService('pieChart');
const dashboardExpect = getService('dashboardExpect');
const dashboardAddPanel = getService('dashboardAddPanel');
const PageObjects = getPageObjects(['common', 'dashboard', 'header', 'visualize', 'discover']);
const expectAllDataRenders = async () => {
await dashboardExpect.pieSliceCount(16);
await pieChart.expectPieSliceCount(16);
await dashboardExpect.seriesElementCount(19);
await dashboardExpect.dataTableRowCount(5);
await dashboardExpect.savedSearchRowCount(50);
@ -57,7 +58,7 @@ export default function ({ getService, getPageObjects }) {
};
const expectNoDataRenders = async () => {
await dashboardExpect.pieSliceCount(0);
await pieChart.expectPieSliceCount(0);
await dashboardExpect.seriesElementCount(0);
await dashboardExpect.dataTableRowCount(0);
await dashboardExpect.savedSearchRowCount(0);

View file

@ -21,7 +21,7 @@ import path from 'path';
import expect from 'expect.js';
export default function ({ getService, getPageObjects }) {
const dashboardExpect = getService('dashboardExpect');
const pieChart = getService('pieChart');
const PageObjects = getPageObjects(['dashboard', 'header', 'settings', 'common']);
describe('dashboard time zones', () => {
@ -43,7 +43,7 @@ export default function ({ getService, getPageObjects }) {
it('Exported dashboard adjusts EST time to UTC', async () => {
const timeRange = await PageObjects.header.getPrettyDuration();
expect(timeRange).to.be('April 10th 2018, 03:00:00.000 to April 10th 2018, 04:00:00.000');
await dashboardExpect.pieSliceCount(4);
await pieChart.expectPieSliceCount(4);
});
it('Changing timezone changes dashboard timestamp and shows the same data', async () => {
@ -54,7 +54,7 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.dashboard.loadSavedDashboard('time zone test');
const timeRange = await PageObjects.header.getPrettyDuration();
expect(timeRange).to.be('April 9th 2018, 22:00:00.000 to April 9th 2018, 23:00:00.000');
await dashboardExpect.pieSliceCount(4);
await pieChart.expectPieSliceCount(4);
});
});
}

View file

@ -23,6 +23,7 @@ export default function ({ getService, getPageObjects }) {
const PageObjects = getPageObjects(['common', 'header', 'visualize']);
const esArchiver = getService('esArchiver');
const kibanaServer = getService('kibanaServer');
const inspector = getService('inspector');
const STATS_ROW_NAME_INDEX = 0;
const STATS_ROW_VALUE_INDEX = 1;
@ -47,12 +48,12 @@ export default function ({ getService, getPageObjects }) {
});
afterEach(async () => {
await PageObjects.visualize.closeInspector();
await inspector.close();
});
it('should display request stats with no results', async () => {
await PageObjects.visualize.openInspector();
const requestStats = await PageObjects.visualize.getInspectorTableData();
await inspector.open();
const requestStats = await inspector.getTableData();
expect(getHitCount(requestStats)).to.be('0');
});
@ -60,8 +61,8 @@ export default function ({ getService, getPageObjects }) {
it('should display request stats with results', async () => {
await PageObjects.header.setAbsoluteRange('2015-09-19 06:31:44.000', '2015-09-23 18:31:44.000');
await PageObjects.visualize.openInspector();
const requestStats = await PageObjects.visualize.getInspectorTableData();
await inspector.open();
const requestStats = await inspector.getTableData();
expect(getHitCount(requestStats)).to.be('14004');
});

View file

@ -22,6 +22,7 @@ import expect from 'expect.js';
export default function ({ getService, getPageObjects }) {
const retry = getService('retry');
const find = getService('find');
const pieChart = getService('pieChart');
const dashboardExpect = getService('dashboardExpect');
const PageObjects = getPageObjects(['common', 'header', 'home', 'dashboard']);
@ -91,7 +92,7 @@ export default function ({ getService, getPageObjects }) {
it.skip('pie charts rendered', async () => {
await dashboardExpect.pieSliceCount(4);
await pieChart.expectPieSliceCount(4);
});
it.skip('area, bar and heatmap charts rendered', async () => {

View file

@ -36,6 +36,7 @@ export default function ({ getService, getPageObjects }) {
const log = getService('log');
const browser = getService('browser');
const retry = getService('retry');
const inspector = getService('inspector');
const testSubjects = getService('testSubjects');
const PageObjects = getPageObjects(['common', 'header', 'settings', 'visualize', 'discover']);
@ -131,13 +132,9 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.discover.clickFieldListItemVisualize(scriptedPainlessFieldName);
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.visualize.waitForVisualization();
await PageObjects.visualize.openInspector();
await PageObjects.visualize.setInspectorTablePageSize(50);
const data = await PageObjects.visualize.getInspectorTableData();
await log.debug('getDataTableData = ' + data);
await log.debug('data=' + data);
await log.debug('data.length=' + data.length);
expect(data).to.eql(expectedChartValues);
await inspector.open();
await inspector.setTablePageSize(50);
await inspector.expectTableData(expectedChartValues);
});
});
@ -195,12 +192,8 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.discover.clickFieldListItemVisualize(scriptedPainlessFieldName2);
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.visualize.waitForVisualization();
await PageObjects.visualize.openInspector();
const data = await PageObjects.visualize.getInspectorTableData();
await log.debug('getDataTableData = ' + data);
await log.debug('data=' + data);
await log.debug('data.length=' + data.length);
expect(data).to.eql([
await inspector.open();
await inspector.expectTableData([
['good', '359'],
['bad', '27']
]);
@ -261,12 +254,8 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.discover.clickFieldListItemVisualize(scriptedPainlessFieldName2);
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.visualize.waitForVisualization();
await PageObjects.visualize.openInspector();
const data = await PageObjects.visualize.getInspectorTableData();
await log.debug('getDataTableData = ' + data);
await log.debug('data=' + data);
await log.debug('data.length=' + data.length);
expect(data).to.eql([
await inspector.open();
await inspector.expectTableData([
['true', '359'],
['false', '27']
]);
@ -327,13 +316,9 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.discover.clickFieldListItemVisualize(scriptedPainlessFieldName2);
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.visualize.waitForVisualization();
await PageObjects.visualize.openInspector();
await PageObjects.visualize.setInspectorTablePageSize(50);
const data = await PageObjects.visualize.getInspectorTableData();
await log.debug('getDataTableData = ' + data);
await log.debug('data=' + data);
await log.debug('data.length=' + data.length);
expect(data).to.eql([
await inspector.open();
await inspector.setTablePageSize(50);
await inspector.expectTableData([
['2015-09-17 20:00', '1'],
['2015-09-17 21:00', '1'],
['2015-09-17 23:00', '1'],

View file

@ -21,6 +21,7 @@ import expect from 'expect.js';
export default function ({ getService, getPageObjects }) {
const log = getService('log');
const inspector = getService('inspector');
const browser = getService('browser');
const retry = getService('retry');
const PageObjects = getPageObjects(['common', 'visualize', 'header', 'settings']);
@ -81,12 +82,10 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.visualize.waitForVisualizationSavedToastGone();
await PageObjects.visualize.loadSavedVisualization(vizName1);
await PageObjects.visualize.waitForVisualization();
return PageObjects.common.sleep(2000);
});
it('should have inspector enabled', async function () {
const spyToggleExists = await PageObjects.visualize.isInspectorButtonEnabled();
expect(spyToggleExists).to.be(true);
await inspector.expectIsEnabled();
});
it('should show correct chart', async function () {
@ -140,11 +139,9 @@ export default function ({ getService, getPageObjects }) {
['2015-09-22 21:00', '29']
];
await PageObjects.visualize.openInspector();
await PageObjects.visualize.setInspectorTablePageSize(50);
const data = await PageObjects.visualize.getInspectorTableData();
log.debug('getDataTableData = ' + data);
expect(data).to.eql(expectedTableData);
await inspector.open();
await inspector.setTablePageSize(50);
await inspector.expectTableData(expectedTableData);
});
it('should hide side editor if embed is set to true in url', async () => {

View file

@ -21,6 +21,7 @@ import expect from 'expect.js';
export default function ({ getService, getPageObjects }) {
const log = getService('log');
const inspector = getService('inspector');
const retry = getService('retry');
const filterBar = getService('filterBar');
const renderable = getService('renderable');
@ -77,8 +78,7 @@ export default function ({ getService, getPageObjects }) {
});
it('should have inspector enabled', async function () {
const spyToggleExists = await PageObjects.visualize.isInspectorButtonEnabled();
expect(spyToggleExists).to.be(true);
await inspector.expectIsEnabled();
});
it('should show correct data', function () {
@ -96,11 +96,9 @@ export default function ({ getService, getPageObjects }) {
];
return retry.try(async function () {
await PageObjects.visualize.openInspector();
const data = await PageObjects.visualize.getInspectorTableData();
await PageObjects.visualize.closeInspector();
log.debug(data);
expect(data).to.eql(expectedChartData);
await inspector.open();
await inspector.expectTableData(expectedChartData);
await inspector.close();
});
});

View file

@ -21,6 +21,7 @@ import expect from 'expect.js';
export default function ({ getService, getPageObjects }) {
const log = getService('log');
const inspector = getService('inspector');
const retry = getService('retry');
const filterBar = getService('filterBar');
const renderable = getService('renderable');
@ -72,8 +73,7 @@ export default function ({ getService, getPageObjects }) {
});
it('should have inspector enabled', async function () {
const spyToggleExists = await PageObjects.visualize.isInspectorButtonEnabled();
expect(spyToggleExists).to.be(true);
await inspector.expectIsEnabled();
});
it('should show correct data', function () {
@ -91,11 +91,9 @@ export default function ({ getService, getPageObjects }) {
];
return retry.try(async function () {
await PageObjects.visualize.openInspector();
const data = await PageObjects.visualize.getInspectorTableData();
await PageObjects.visualize.closeInspector();
log.debug(data);
expect(data).to.eql(expectedChartData);
await inspector.open();
await inspector.expectTableData(expectedChartData);
await inspector.close();
});
});

View file

@ -22,6 +22,7 @@ import expect from 'expect.js';
export default function ({ getService, getPageObjects }) {
const log = getService('log');
const retry = getService('retry');
const inspector = getService('inspector');
const PageObjects = getPageObjects(['common', 'visualize', 'header']);
describe('gauge chart', function indexPatternCreation() {
@ -40,8 +41,7 @@ export default function ({ getService, getPageObjects }) {
it('should have inspector enabled', async function () {
const spyToggleExists = await PageObjects.visualize.isInspectorButtonEnabled();
expect(spyToggleExists).to.be(true);
await inspector.expectIsEnabled();
});
it('should show Count', function () {

View file

@ -21,6 +21,7 @@ import expect from 'expect.js';
export default function ({ getService, getPageObjects }) {
const log = getService('log');
const inspector = getService('inspector');
const PageObjects = getPageObjects(['common', 'visualize', 'header']);
describe('heatmap chart', function indexPatternCreation() {
@ -69,8 +70,7 @@ export default function ({ getService, getPageObjects }) {
});
it('should have inspector enabled', async function () {
const spyToggleExists = await PageObjects.visualize.isInspectorButtonEnabled();
expect(spyToggleExists).to.be(true);
await inspector.expectIsEnabled();
});
it('should show correct data', async function () {
@ -99,11 +99,9 @@ export default function ({ getService, getPageObjects }) {
];
await PageObjects.visualize.openInspector();
const data = await PageObjects.visualize.getInspectorTableData();
log.debug(data);
expect(data).to.eql(expectedChartData);
await PageObjects.visualize.closeInspector();
await inspector.open();
await inspector.expectTableData(expectedChartData);
await inspector.close();
});
it('should show 4 color ranges as default colorNumbers param', async function () {

View file

@ -23,6 +23,7 @@ export default function ({ getService, getPageObjects }) {
const filterBar = getService('filterBar');
const PageObjects = getPageObjects(['common', 'visualize', 'header']);
const testSubjects = getService('testSubjects');
const inspector = getService('inspector');
const find = getService('find');
const comboBox = getService('comboBox');
@ -44,8 +45,7 @@ export default function ({ getService, getPageObjects }) {
it('should not have inspector enabled', async function () {
const spyToggleExists = await PageObjects.visualize.isInspectorButtonEnabled();
expect(spyToggleExists).to.be(false);
await inspector.expectIsNotEnabled();
});
describe('updateFiltersOnChange is false', () => {

View file

@ -17,10 +17,10 @@
* under the License.
*/
import expect from 'expect.js';
export default function ({ getService, getPageObjects }) {
const log = getService('log');
const inspector = getService('inspector');
const filterBar = getService('filterBar');
const PageObjects = getPageObjects(['common', 'visualize', 'header']);
@ -39,9 +39,8 @@ export default function ({ getService, getPageObjects }) {
describe('inspector table', function indexPatternCreation() {
it('should update table header when columns change', async function () {
await PageObjects.visualize.openInspector();
let headers = await PageObjects.visualize.getInspectorTableHeaders();
expect(headers).to.eql(['Count']);
await inspector.open();
await inspector.expectTableHeaders(['Count']);
log.debug('Add Average Metric on machine.ram field');
await PageObjects.visualize.clickAddMetric();
@ -49,10 +48,8 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.visualize.selectAggregation('Average', 'metrics');
await PageObjects.visualize.selectField('machine.ram', 'metrics');
await PageObjects.visualize.clickGo();
await PageObjects.visualize.openInspector();
headers = await PageObjects.visualize.getInspectorTableHeaders();
expect(headers).to.eql(['Count', 'Average machine.ram']);
await inspector.open();
await inspector.expectTableHeaders(['Count', 'Average machine.ram']);
});
describe('filtering on inspector table values', function () {
@ -67,33 +64,30 @@ export default function ({ getService, getPageObjects }) {
});
beforeEach(async function () {
await PageObjects.visualize.openInspector();
await inspector.open();
});
afterEach(async function () {
await PageObjects.visualize.closeInspector();
await inspector.close();
await filterBar.removeFilter('machine.os.raw');
});
it('should allow filtering for values', async function () {
let data = await PageObjects.visualize.getInspectorTableData();
expect(data).to.eql([
await inspector.expectTableData([
['win 8', '2,904', '13,031,579,645.108'],
['win xp', '2,858', '13,073,190,186.423'],
['Other', '6,920', '13,123,599,766.011'],
]);
await PageObjects.visualize.filterForInspectorTableCell(1, 1);
data = await PageObjects.visualize.getInspectorTableData();
expect(data).to.eql([
await inspector.filterForTableCell(1, 1);
await inspector.expectTableData([
['win 8', '2,904', '13,031,579,645.108'],
]);
});
it('should allow filtering out values', async function () {
await PageObjects.visualize.filterOutInspectorTableCell(1, 1);
const data = await PageObjects.visualize.getInspectorTableData();
expect(data).to.eql([
await inspector.filterOutTableCell(1, 1);
await inspector.expectTableData([
['win xp', '2,858', '13,073,190,186.423'],
['win 7', '2,814', '13,186,695,551.251'],
['Other', '4,106', '13,080,420,659.354'],
@ -101,9 +95,8 @@ export default function ({ getService, getPageObjects }) {
});
it('should allow filtering for other values', async function () {
await PageObjects.visualize.filterForInspectorTableCell(1, 3);
const data = await PageObjects.visualize.getInspectorTableData();
expect(data).to.eql([
await inspector.filterForTableCell(1, 3);
await inspector.expectTableData([
['win 7', '2,814', '13,186,695,551.251'],
['ios', '2,784', '13,009,497,206.823'],
['Other', '1,322', '13,228,964,670.613'],
@ -111,9 +104,8 @@ export default function ({ getService, getPageObjects }) {
});
it('should allow filtering out other values', async function () {
await PageObjects.visualize.filterOutInspectorTableCell(1, 3);
const data = await PageObjects.visualize.getInspectorTableData();
expect(data).to.eql([
await inspector.filterOutTableCell(1, 3);
await inspector.expectTableData([
['win 8', '2,904', '13,031,579,645.108'],
['win xp', '2,858', '13,073,190,186.423'],
]);

View file

@ -21,6 +21,7 @@ import expect from 'expect.js';
export default function ({ getService, getPageObjects }) {
const log = getService('log');
const inspector = getService('inspector');
const retry = getService('retry');
const PageObjects = getPageObjects(['common', 'visualize', 'header']);
@ -52,7 +53,7 @@ export default function ({ getService, getPageObjects }) {
before(initLineChart);
afterEach(async () => {
await PageObjects.visualize.closeInspector();
await inspector.close();
});
it('should show correct chart', async function () {
@ -77,8 +78,7 @@ export default function ({ getService, getPageObjects }) {
it('should have inspector enabled', async function () {
const spyToggleExists = await PageObjects.visualize.isInspectorButtonEnabled();
expect(spyToggleExists).to.be(true);
await inspector.expectIsEnabled();
});
it('should show correct chart order by Term', async function () {
@ -107,10 +107,8 @@ export default function ({ getService, getPageObjects }) {
const expectedChartData = [['png', '1,373'], ['php', '445'], ['jpg', '9,109'], ['gif', '918'], ['css', '2,159']];
await PageObjects.visualize.openInspector();
const data = await PageObjects.visualize.getInspectorTableData();
log.debug(data);
expect(data).to.eql(expectedChartData);
await inspector.open();
await inspector.expectTableData(expectedChartData);
});
it('should be able to save and load', async function () {

View file

@ -22,6 +22,7 @@ import expect from 'expect.js';
export default function ({ getPageObjects, getService }) {
const PageObjects = getPageObjects(['common', 'visualize', 'header']);
const find = getService('find');
const inspector = getService('inspector');
const markdown = `
# Heading 1
@ -39,8 +40,7 @@ export default function ({ getPageObjects, getService }) {
describe('markdown vis', async () => {
it('should not have inspector enabled', async function () {
const spyToggleExists = await PageObjects.visualize.isInspectorButtonEnabled();
expect(spyToggleExists).to.be(false);
await inspector.expectIsNotEnabled();
});
it('should render markdown as html', async function () {

View file

@ -22,6 +22,7 @@ import expect from 'expect.js';
export default function ({ getService, getPageObjects }) {
const log = getService('log');
const retry = getService('retry');
const inspector = getService('inspector');
const PageObjects = getPageObjects(['common', 'visualize', 'header']);
describe('metric chart', function () {
@ -39,8 +40,7 @@ export default function ({ getService, getPageObjects }) {
});
it('should have inspector enabled', async function () {
const spyToggleExists = await PageObjects.visualize.isInspectorButtonEnabled();
expect(spyToggleExists).to.be(true);
await inspector.expectIsEnabled();
});
it('should show Count', async function () {

View file

@ -22,6 +22,8 @@ import expect from 'expect.js';
export default function ({ getService, getPageObjects }) {
const log = getService('log');
const filterBar = getService('filterBar');
const pieChart = getService('pieChart');
const inspector = getService('inspector');
const PageObjects = getPageObjects(['common', 'visualize', 'header', 'settings']);
const fromTime = '2015-09-19 06:31:44.000';
const toTime = '2015-09-23 18:31:44.000';
@ -58,22 +60,15 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.visualize.waitForVisualizationSavedToastGone();
await PageObjects.visualize.loadSavedVisualization(vizName1);
await PageObjects.visualize.waitForVisualization();
// sleep a bit before trying to get the pie chart data below
await PageObjects.common.sleep(2000);
});
it('should have inspector enabled', async function () {
const spyToggleExists = await PageObjects.visualize.isInspectorButtonEnabled();
expect(spyToggleExists).to.be(true);
await inspector.expectIsEnabled();
});
it('should show 10 slices in pie chart', async function () {
const expectedPieChartSliceCount = 10;
const pieData = await PageObjects.visualize.getPieChartData();
log.debug('pieData.length = ' + pieData.length);
expect(pieData.length).to.be(expectedPieChartSliceCount);
pieChart.expectPieSliceCount(10);
});
it('should show correct data', async function () {
@ -81,11 +76,9 @@ export default function ({ getService, getPageObjects }) {
['160,000', '44'], ['200,000', '40'], ['240,000', '46'], ['280,000', '39'], ['320,000', '40'], ['360,000', '47']
];
await PageObjects.visualize.openInspector();
await PageObjects.visualize.setInspectorTablePageSize(50);
const data = await PageObjects.visualize.getInspectorTableData();
log.debug(data);
expect(data).to.eql(expectedTableData);
await inspector.open();
await inspector.setTablePageSize(50);
await inspector.expectTableData(expectedTableData);
});
describe('other bucket', () => {
@ -108,35 +101,29 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.visualize.toggleMissingBucket();
log.debug('clickGo');
await PageObjects.visualize.clickGo();
const pieData = await PageObjects.visualize.getPieChartLabels();
log.debug(`pieData.length = ${pieData.length}`);
expect(pieData).to.eql(expectedTableData);
await pieChart.expectPieChartLabels(expectedTableData);
});
// FLAKY: https://github.com/elastic/kibana/issues/25955
it.skip('should apply correct filter on other bucket', async () => {
it('should apply correct filter on other bucket', async () => {
const expectedTableData = [ 'Missing', 'osx' ];
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.visualize.filterPieSlice('Other');
await pieChart.filterOnPieSlice('Other');
await PageObjects.visualize.waitForVisualization();
const pieData = await PageObjects.visualize.getPieChartLabels();
log.debug(`pieData.length = ${pieData.length}`);
expect(pieData).to.eql(expectedTableData);
await pieChart.expectPieChartLabels(expectedTableData);
await filterBar.removeFilter('machine.os.raw');
await PageObjects.visualize.waitForVisualization();
});
// FLAKY: https://github.com/elastic/kibana/issues/26323
it.skip('should apply correct filter on other bucket by clicking on a legend', async () => {
it('should apply correct filter on other bucket by clicking on a legend', async () => {
const expectedTableData = [ 'Missing', 'osx' ];
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.visualize.filterLegend('Other');
await PageObjects.visualize.waitForVisualization();
const pieData = await PageObjects.visualize.getPieChartLabels();
log.debug(`pieData.length = ${pieData.length}`);
expect(pieData).to.eql(expectedTableData);
await pieChart.expectPieChartLabels(expectedTableData);
await filterBar.removeFilter('machine.os.raw');
await PageObjects.visualize.waitForVisualization();
});
it('should show two levels of other buckets', async () => {
@ -155,9 +142,7 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.visualize.toggleMissingBucket();
log.debug('clickGo');
await PageObjects.visualize.clickGo();
const pieData = await PageObjects.visualize.getPieChartLabels();
log.debug(`pieData.length = ${pieData.length}`);
expect(pieData).to.eql(expectedTableData);
await pieChart.expectPieChartLabels(expectedTableData);
});
});
@ -179,9 +164,7 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.visualize.toggleDisabledAgg(2);
await PageObjects.visualize.clickGo();
const pieData = await PageObjects.visualize.getPieChartLabels();
log.debug('pieData.length = ' + pieData.length);
expect(pieData).to.eql(expectedTableData);
await pieChart.expectPieChartLabels(expectedTableData);
});
it('should correctly save disabled agg', async () => {
@ -194,9 +177,7 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.visualize.waitForVisualization();
const expectedTableData = [ 'win 8', 'win xp', 'win 7', 'ios', 'osx' ];
const pieData = await PageObjects.visualize.getPieChartLabels();
log.debug('pieData.length = ' + pieData.length);
expect(pieData).to.eql(expectedTableData);
await pieChart.expectPieChartLabels(expectedTableData);
});
it('should show correct result when agg is re-enabled', async () => {
@ -209,9 +190,8 @@ export default function ({ getService, getPageObjects }) {
'win 8', 'ios', 'win 7', 'win xp', 'osx', '200,000', 'win 8', 'ios', 'win xp', 'win 7', 'osx', '240,000',
'ios', 'win 7', 'win xp', 'win 8', 'osx', '280,000', 'win xp', 'win 8', 'win 7', 'ios', 'osx', '320,000',
'win xp', 'win 7', 'ios', 'win 8', 'osx', '360,000', 'win 7', 'win xp', 'ios', 'win 8', 'osx' ];
const pieData = await PageObjects.visualize.getPieChartLabels();
log.debug('pieData.length = ' + pieData.length);
expect(pieData).to.eql(expectedTableData);
await pieChart.expectPieChartLabels(expectedTableData);
});
});
@ -239,7 +219,7 @@ export default function ({ getService, getPageObjects }) {
log.debug('Switch to a different time range from \"' + emptyFromTime + '\" to \"' + emptyToTime + '\"');
await PageObjects.header.setAbsoluteRange(emptyFromTime, emptyToTime);
await PageObjects.visualize.waitForVisualization();
await PageObjects.visualize.expectPieChartError();
await PageObjects.visualize.expectError();
});
});
describe('multi series slice', () => {
@ -323,12 +303,10 @@ export default function ({ getService, getPageObjects }) {
[ 'osx', '1,322', 'ID', '56' ],
[ 'osx', '1,322', 'BR', '30' ]
];
await PageObjects.visualize.openInspector();
await PageObjects.visualize.setInspectorTablePageSize(50);
const data = await PageObjects.visualize.getInspectorTableData();
await PageObjects.visualize.closeInspector();
log.debug(data);
expect(data).to.eql(expectedTableData);
await inspector.open();
await inspector.setTablePageSize(50);
await inspector.expectTableData(expectedTableData);
await inspector.close();
});
it ('correctly applies filter', async () => {
@ -336,12 +314,10 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.visualize.filterLegend('CN');
await PageObjects.visualize.applyFilters();
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.visualize.openInspector();
await PageObjects.visualize.setInspectorTablePageSize(50);
const data = await PageObjects.visualize.getInspectorTableData();
await PageObjects.visualize.closeInspector();
log.debug(data);
expect(data).to.eql(expectedTableData);
await inspector.open();
await inspector.setTablePageSize(50);
await inspector.expectTableData(expectedTableData);
await inspector.close();
});
});
});

View file

@ -26,6 +26,7 @@ export default function ({ getService, getPageObjects }) {
const fromTime = '2015-09-19 06:31:44.000';
const toTime = '2015-09-23 18:31:44.000';
const inspector = getService('inspector');
const log = getService('log');
const find = getService('find');
const PageObjects = getPageObjects(['common', 'visualize', 'header', 'settings']);
@ -51,15 +52,13 @@ export default function ({ getService, getPageObjects }) {
describe('vector map', function indexPatternCreation() {
it('should have inspector enabled', async function () {
const spyToggleExists = await PageObjects.visualize.isInspectorButtonEnabled();
expect(spyToggleExists).to.be(true);
await inspector.expectIsEnabled();
});
it('should show results after clicking play (join on states)', async function () {
const expectedData = [['CN', '2,592'], ['IN', '2,373'], ['US', '1,194'], ['ID', '489'], ['BR', '415']];
await PageObjects.visualize.openInspector();
const data = await PageObjects.visualize.getInspectorTableData();
expect(data).to.eql(expectedData);
await inspector.open();
await inspector.expectTableData(expectedData);
});
it('should change results after changing layer to world', async function () {
@ -76,8 +75,8 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.visualize.selectFieldById('ISO 3166-1 alpha-2 code', 'joinField');
await PageObjects.common.sleep(2000);//need some time for the data to load
await PageObjects.visualize.openInspector();
const actualData = await PageObjects.visualize.getInspectorTableData();
await inspector.open();
const actualData = await inspector.getTableData();
const expectedData = [['CN', '2,592'], ['IN', '2,373'], ['US', '1,194'], ['ID', '489'], ['BR', '415']];
expect(actualData).to.eql(expectedData);
});

View file

@ -22,6 +22,7 @@ import expect from 'expect.js';
export default function ({ getService, getPageObjects }) {
const filterBar = getService('filterBar');
const log = getService('log');
const inspector = getService('inspector');
const browser = getService('browser');
const retry = getService('retry');
const find = getService('find');
@ -55,8 +56,7 @@ export default function ({ getService, getPageObjects }) {
it('should have inspector enabled', async function () {
const spyToggleExists = await PageObjects.visualize.isInspectorButtonEnabled();
expect(spyToggleExists).to.be(true);
await inspector.expectIsEnabled();
});
it('should show correct tag cloud data', async function () {
@ -127,11 +127,9 @@ export default function ({ getService, getPageObjects }) {
[ '18,253,611,008', '679' ]
];
await PageObjects.visualize.openInspector();
await await PageObjects.visualize.setInspectorTablePageSize('50');
const data = await PageObjects.visualize.getInspectorTableData();
log.debug(data);
expect(data).to.eql(expectedTableData);
await inspector.open();
await await inspector.setTablePageSize('50');
await inspector.expectTableData(expectedTableData);
});
describe('formatted field', function () {

View file

@ -22,6 +22,7 @@ import expect from 'expect.js';
export default function ({ getService, getPageObjects }) {
const log = getService('log');
const retry = getService('retry');
const inspector = getService('inspector');
const find = getService('find');
const testSubjects = getService('testSubjects');
const browser = getService('browser');
@ -104,8 +105,7 @@ export default function ({ getService, getPageObjects }) {
describe('tile map chart', function indexPatternCreation() {
it('should have inspector enabled', async function () {
const spyToggleExists = await PageObjects.visualize.isInspectorButtonEnabled();
expect(spyToggleExists).to.be(true);
await inspector.expectIsEnabled();
});
it('should show correct tile map data on default zoom level', async function () {
@ -122,10 +122,10 @@ export default function ({ getService, getPageObjects }) {
//level 0
await PageObjects.visualize.clickMapZoomOut();
await PageObjects.visualize.openInspector();
await PageObjects.visualize.setInspectorTablePageSize(50);
const actualTableData = await PageObjects.visualize.getInspectorTableData();
await PageObjects.visualize.closeInspector();
await inspector.open();
await inspector.setTablePageSize(50);
const actualTableData = await inspector.getTableData();
await inspector.close();
compareTableData(actualTableData, expectedTableData);
});
@ -161,9 +161,9 @@ export default function ({ getService, getPageObjects }) {
];
await PageObjects.visualize.clickMapFitDataBounds();
await PageObjects.visualize.openInspector();
const data = await PageObjects.visualize.getInspectorTableData();
await PageObjects.visualize.closeInspector();
await inspector.open();
const data = await inspector.getTableData();
await inspector.close();
compareTableData(data, expectedPrecision2DataTable);
});
@ -174,13 +174,13 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.visualize.clickMapZoomIn();
const mapBounds = await PageObjects.visualize.getMapBounds();
await PageObjects.visualize.closeInspector();
await inspector.close();
await PageObjects.visualize.saveVisualizationExpectSuccess(vizName1);
const afterSaveMapBounds = await PageObjects.visualize.getMapBounds();
await PageObjects.visualize.closeInspector();
await inspector.close();
// For some reason the values are slightly different, so we can't check that they are equal. But we did
// have a bug where after the save, there were _no_ map bounds. So this checks for the later case, but
// until we figure out how to make sure the map center is always the exact same, we can't comparison check.
@ -195,20 +195,18 @@ export default function ({ getService, getPageObjects }) {
it('when checked adds filters to aggregation', async () => {
const vizName1 = 'Visualization TileMap';
await PageObjects.visualize.loadSavedVisualization(vizName1);
await PageObjects.visualize.openInspector();
const tableHeaders = await PageObjects.visualize.getInspectorTableHeaders();
await PageObjects.visualize.closeInspector();
expect(tableHeaders).to.eql(['filter', 'geohash_grid', 'Count', 'Geo Centroid']);
await inspector.open();
await inspector.expectTableHeaders(['filter', 'geohash_grid', 'Count', 'Geo Centroid']);
await inspector.close();
});
it('when not checked does not add filters to aggregation', async () => {
await PageObjects.visualize.toggleOpenEditor(2);
await PageObjects.visualize.toggleIsFilteredByCollarCheckbox();
await PageObjects.visualize.clickGo();
await PageObjects.visualize.openInspector();
const tableHeaders = await PageObjects.visualize.getInspectorTableHeaders();
await PageObjects.visualize.closeInspector();
expect(tableHeaders).to.eql(['geohash_grid', 'Count', 'Geo Centroid']);
await inspector.open();
await inspector.expectTableHeaders(['geohash_grid', 'Count', 'Geo Centroid']);
await inspector.close();
});
after(async () => {

View file

@ -22,6 +22,7 @@ import expect from 'expect.js';
export default function ({ getService, getPageObjects }) {
const esArchiver = getService('esArchiver');
const log = getService('log');
const inspector = getService('inspector');
const retry = getService('retry');
const PageObjects = getPageObjects(['common', 'visualize', 'header', 'settings', 'visualBuilder']);
@ -70,8 +71,7 @@ export default function ({ getService, getPageObjects }) {
});
it('should not have inspector enabled', async function () {
const spyToggleExists = await PageObjects.visualize.isInspectorButtonEnabled();
expect(spyToggleExists).to.be(false);
await inspector.expectIsNotEnabled();
});
it('should show correct data', async function () {
@ -91,8 +91,7 @@ export default function ({ getService, getPageObjects }) {
});
it('should not have inspector enabled', async function () {
const spyToggleExists = await PageObjects.visualize.isInspectorButtonEnabled();
expect(spyToggleExists).to.be(false);
await inspector.expectIsNotEnabled();
});
it('should show correct data', async function () {

View file

@ -21,6 +21,7 @@ import expect from 'expect.js';
export default function ({ getService, getPageObjects }) {
const log = getService('log');
const inspector = getService('inspector');
const PageObjects = getPageObjects(['common', 'visualize', 'header']);
describe('visualize app', () => {
@ -33,8 +34,7 @@ export default function ({ getService, getPageObjects }) {
describe('vega chart', () => {
it('should not have inspector enabled', async function () {
const spyToggleExists = await PageObjects.visualize.isInspectorButtonEnabled();
expect(spyToggleExists).to.be(false);
await inspector.expectIsNotEnabled();
});
it.skip('should have some initial vega spec text', async function () {

View file

@ -22,6 +22,7 @@ import expect from 'expect.js';
export default function ({ getService, getPageObjects }) {
const log = getService('log');
const retry = getService('retry');
const inspector = getService('inspector');
const PageObjects = getPageObjects(['common', 'visualize', 'header']);
describe('vertical bar chart', function () {
@ -64,8 +65,7 @@ export default function ({ getService, getPageObjects }) {
});
it('should have inspector enabled', async function () {
const spyToggleExists = await PageObjects.visualize.isInspectorButtonEnabled();
expect(spyToggleExists).to.be(true);
await inspector.expectIsEnabled();
});
it('should show correct chart', async function () {
@ -109,11 +109,9 @@ export default function ({ getService, getPageObjects }) {
[ '2015-09-22 09:00', '1,408' ],
];
await PageObjects.visualize.openInspector();
const data = await PageObjects.visualize.getInspectorTableData();
await PageObjects.visualize.closeInspector();
log.debug(data);
expect(data).to.eql(expectedChartData);
await inspector.open();
await inspector.expectTableData(expectedChartData);
await inspector.close();
});
it('should have `drop partial buckets` option', async () => {

View file

@ -22,6 +22,7 @@ import expect from 'expect.js';
export default function ({ getService, getPageObjects }) {
const log = getService('log');
const retry = getService('retry');
const inspector = getService('inspector');
const PageObjects = getPageObjects(['common', 'visualize', 'header']);
describe.skip('vertical bar chart with index without time filter', function () {
@ -61,8 +62,7 @@ export default function ({ getService, getPageObjects }) {
});
it('should have inspector enabled', async function () {
const spyToggleExists = await PageObjects.visualize.isInspectorButtonEnabled();
expect(spyToggleExists).to.be(true);
await inspector.expectIsEnabled();
});
it('should show correct chart', async function () {
@ -106,10 +106,8 @@ export default function ({ getService, getPageObjects }) {
[ '2015-09-22 09:00', '1,408' ],
];
await PageObjects.visualize.openInspector();
const data = await PageObjects.visualize.getInspectorTableData();
log.debug(data);
expect(data).to.eql(expectedChartData);
await inspector.open();
await inspector.expectTableData(expectedChartData);
});
describe.skip('switch between Y axis scale types', () => {

View file

@ -55,6 +55,8 @@ import {
RenderableProvider,
TableProvider,
BrowserProvider,
InspectorProvider,
PieChartProvider,
} from './services';
export default async function ({ readConfigFile }) {
@ -115,6 +117,8 @@ export default async function ({ readConfigFile }) {
renderable: RenderableProvider,
table: TableProvider,
browser: BrowserProvider,
pieChart: PieChartProvider,
inspector: InspectorProvider,
},
servers: commonConfig.get('servers'),

View file

@ -125,7 +125,9 @@ export function DashboardPageProvider({ getService, getPageObjects }) {
*/
async onDashboardLandingPage() {
log.debug(`onDashboardLandingPage`);
return await testSubjects.exists('dashboardLandingPage', 5000);
return await testSubjects.exists('dashboardLandingPage', {
timeout: 5000
});
}
async expectExistsDashboardLandingPage() {
@ -560,28 +562,6 @@ export function DashboardPageProvider({ getService, getPageObjects }) {
return _.map(filters, async (filter) => await filter.getVisibleText());
}
async getPieSliceCount(timeout) {
log.debug('getPieSliceCount');
return await retry.try(async () => {
const slices = await find.allByCssSelector('svg > g > g.arcs > path.slice', timeout);
return slices.length;
});
}
async filterOnPieSlice(sliceValue) {
log.debug(`Filtering on a pie slice with optional value ${sliceValue}`);
if (sliceValue) {
await testSubjects.click(`pieSlice-${sliceValue}`);
} else {
// If no pie slice has been provided, find the first one available.
await retry.try(async () => {
const slices = await find.allByCssSelector('svg > g > g.arcs > path.slice');
log.debug('Slices found:' + slices.length);
return slices[0].click();
});
}
}
async getSharedItemsCount() {
log.debug('in getSharedItemsCount');
const attributeName = 'data-shared-items-count';

View file

@ -254,8 +254,10 @@ export function HeaderPageProvider({ getService, getPageObjects }) {
}
async awaitGlobalLoadingIndicatorHidden() {
log.debug('awaitGlobalLoadingIndicatorHidden');
await testSubjects.find('globalLoadingIndicator-hidden', defaultFindTimeout * 10);
await testSubjects.existOrFail('globalLoadingIndicator-hidden', {
allowHidden: true,
timeout: defaultFindTimeout * 10
});
}
async awaitKibanaChrome() {

View file

@ -90,7 +90,9 @@ export function HomePageProvider({ getService }) {
async loadSavedObjects() {
await retry.try(async () => {
await testSubjects.click('loadSavedObjects');
const successMsgExists = await testSubjects.exists('loadSavedObjects_success', 5000);
const successMsgExists = await testSubjects.exists('loadSavedObjects_success', {
timeout: 5000
});
if (!successMsgExists) {
throw new Error('Failed to load saved objects');
}

View file

@ -28,7 +28,7 @@ export function VisualizePageProvider({ getService, getPageObjects }) {
const retry = getService('retry');
const find = getService('find');
const log = getService('log');
const flyout = getService('flyout');
const inspector = getService('inspector');
const renderable = getService('renderable');
const table = getService('table');
const PageObjects = getPageObjects(['common', 'header']);
@ -289,41 +289,10 @@ export function VisualizePageProvider({ getService, getPageObjects }) {
await options[optionIndex].click();
}
async isInspectorButtonEnabled() {
const button = await testSubjects.find('openInspectorButton');
const ariaDisabled = await button.getAttribute('aria-disabled');
return ariaDisabled !== 'true';
}
async getSideEditorExists() {
return await find.existsByCssSelector('.collapsible-sidebar');
}
async openInspector() {
log.debug('Open Inspector');
const isOpen = await testSubjects.exists('inspectorPanel');
if (!isOpen) {
await retry.try(async () => {
await testSubjects.click('openInspectorButton');
await testSubjects.find('inspectorPanel');
});
}
}
async closeInspector() {
log.debug('Close Inspector');
let isOpen = await testSubjects.exists('inspectorPanel');
if (isOpen) {
await retry.try(async () => {
await flyout.close('inspectorPanel');
isOpen = await testSubjects.exists('inspectorPanel');
if (isOpen) {
throw new Error('Failed to close inspector');
}
});
}
}
async setInspectorTablePageSize(size) {
const panel = await testSubjects.find('inspectorPanel');
await find.clickByButtonText('Rows per page: 20', panel);
@ -531,10 +500,8 @@ export function VisualizePageProvider({ getService, getPageObjects }) {
}
async getInterval() {
const select = await find.byCssSelector('select[ng-model="agg.params.interval"]');
const selectedIndex = await select.getProperty('selectedIndex');
const intervalElement = await find.byCssSelector(
`select[ng-model="agg.params.interval"] option:nth-child(${(selectedIndex + 1)})`);
`select[ng-model="agg.params.interval"] option[selected]`);
return await intervalElement.getProperty('label');
}
@ -650,11 +617,11 @@ export function VisualizePageProvider({ getService, getPageObjects }) {
const table = await testSubjects.find('heatmapCustomRangesTable');
const lastRow = await table.findByCssSelector('tr:last-child');
const fromCell = await lastRow.findByCssSelector('td:first-child input');
fromCell.clearValue();
fromCell.type(`${from}`);
await fromCell.clearValue();
await fromCell.type(`${from}`);
const toCell = await lastRow.findByCssSelector('td:nth-child(2) input');
toCell.clearValue();
toCell.type(`${to}`);
await toCell.clearValue();
await toCell.type(`${to}`);
}
async clickYAxisOptions(axisId) {
@ -719,13 +686,17 @@ export function VisualizePageProvider({ getService, getPageObjects }) {
async saveVisualizationExpectSuccess(vizName, { saveAsNew = false } = {}) {
await this.saveVisualization(vizName, { saveAsNew });
const successToast = await testSubjects.exists('saveVisualizationSuccess', defaultFindTimeout);
const successToast = await testSubjects.exists('saveVisualizationSuccess', {
timeout: defaultFindTimeout
});
expect(successToast).to.be(true);
}
async saveVisualizationExpectFail(vizName, { saveAsNew = false } = {}) {
await this.saveVisualization(vizName, { saveAsNew });
const errorToast = await testSubjects.exists('saveVisualizationError', defaultFindTimeout);
const errorToast = await testSubjects.exists('saveVisualizationError', {
timeout: defaultFindTimeout
});
expect(errorToast).to.be(true);
}
@ -879,25 +850,7 @@ export function VisualizePageProvider({ getService, getPageObjects }) {
return await Promise.all(getChartTypesPromises);
}
async getPieChartData() {
const chartTypes = await find.allByCssSelector('path.slice', defaultFindTimeout * 2);
const getChartTypesPromises = chartTypes.map(async chart => await chart.getAttribute('d'));
return await Promise.all(getChartTypesPromises);
}
async getPieChartLabels() {
// Outer retry is because because of stale element references getting thrown on grabbing the data-label.
// I suspect it's due to a rendering bug with pie charts not emitting the render-complete flag
// when actually done rendering.
return await retry.try(async () => {
const chartTypes = await find.allByCssSelector('path.slice', defaultFindTimeout * 2);
const getChartTypesPromises = chartTypes.map(async chart => await chart.getAttribute('data-label'));
return await Promise.all(getChartTypesPromises);
});
}
async expectPieChartError() {
async expectError() {
return await testSubjects.existOrFail('visLibVisualizeError');
}
@ -951,32 +904,6 @@ export function VisualizePageProvider({ getService, getPageObjects }) {
});
}
async getInspectorTableData() {
// TODO: we should use datat-test-subj=inspectorTable as soon as EUI supports it
const inspectorPanel = await testSubjects.find('inspectorPanel');
const tableBody = await retry.try(async () => inspectorPanel.findByTagName('tbody'));
// Convert the data into a nested array format:
// [ [cell1_in_row1, cell2_in_row1], [cell1_in_row2, cell2_in_row2] ]
const rows = await tableBody.findAllByTagName('tr');
return await Promise.all(rows.map(async row => {
const cells = await row.findAllByTagName('td');
return await Promise.all(cells.map(async cell => cell.getVisibleText()));
}));
}
async getInspectorTableHeaders() {
// TODO: we should use datat-test-subj=inspectorTable as soon as EUI supports it
const dataTableHeader = await retry.try(async () => {
const inspectorPanel = await testSubjects.find('inspectorPanel');
return await inspectorPanel.findByTagName('thead');
});
const cells = await dataTableHeader.findAllByTagName('th');
return await Promise.all(cells.map(async (cell) => {
const untrimmed = await cell.getVisibleText();
return untrimmed.trim();
}));
}
async toggleIsFilteredByCollarCheckbox() {
await testSubjects.click('isFilteredByCollarCheckbox');
}
@ -1014,7 +941,7 @@ export function VisualizePageProvider({ getService, getPageObjects }) {
async getVisualizationRequest() {
log.debug('getVisualizationRequest');
await this.openInspector();
await inspector.open();
await testSubjects.click('inspectorViewChooser');
await testSubjects.click('inspectorViewChooserRequests');
await testSubjects.click('inspectorRequestDetailRequest');
@ -1023,7 +950,7 @@ export function VisualizePageProvider({ getService, getPageObjects }) {
async getVisualizationResponse() {
log.debug('getVisualizationResponse');
await this.openInspector();
await inspector.open();
await testSubjects.click('inspectorViewChooser');
await testSubjects.click('inspectorViewChooserRequests');
await testSubjects.click('inspectorRequestDetailResponse');
@ -1129,28 +1056,6 @@ export function VisualizePageProvider({ getService, getPageObjects }) {
});
}
async filterForInspectorTableCell(column, row) {
await retry.try(async () => {
const table = await testSubjects.find('inspectorTable');
const cell = await table.findByCssSelector(`tbody tr:nth-child(${row}) td:nth-child(${column})`);
await browser.moveMouseTo(cell);
const filterBtn = await testSubjects.findDescendant('filterForInspectorCellValue', cell);
await filterBtn.click();
});
await renderable.waitForRender();
}
async filterOutInspectorTableCell(column, row) {
await retry.try(async () => {
const table = await testSubjects.find('inspectorTable');
const cell = await table.findByCssSelector(`tbody tr:nth-child(${row}) td:nth-child(${column})`);
await browser.moveMouseTo(cell);
const filterBtn = await testSubjects.findDescendant('filterOutInspectorCellValue', cell);
await filterBtn.click();
});
await renderable.waitForRender();
}
async toggleLegend(show = true) {
await retry.try(async () => {
const isVisible = find.byCssSelector('vislib-legend');
@ -1188,33 +1093,6 @@ export function VisualizePageProvider({ getService, getPageObjects }) {
return await bucketType.click();
}
async filterPieSlice(name) {
const slice = await this.getPieSlice(name);
// Since slice is an SVG element we can't simply use .click() for it
await browser.moveMouseTo(slice);
await browser.clickMouseButton();
}
async getPieSlice(name) {
return await testSubjects.find(`pieSlice-${name.split(' ').join('-')}`);
}
async getAllPieSlices(name) {
return await testSubjects.findAll(`pieSlice-${name.split(' ').join('-')}`);
}
async getPieSliceStyle(name) {
log.debug(`VisualizePage.getPieSliceStyle(${name})`);
const pieSlice = await this.getPieSlice(name);
return await pieSlice.getAttribute('style');
}
async getAllPieSliceStyles(name) {
log.debug(`VisualizePage.getAllPieSliceStyles(${name})`);
const pieSlices = await this.getAllPieSlices(name);
return await Promise.all(pieSlices.map(async pieSlice => await pieSlice.getAttribute('style')));
}
async getBucketErrorMessage() {
const error = await find.byCssSelector('.visEditorAggParam__error');
const errorMessage = await error.getProperty('innerText');

View file

@ -28,13 +28,6 @@ export function DashboardExpectProvider({ getService, getPageObjects }) {
const PageObjects = getPageObjects(['dashboard', 'visualize']);
return new class DashboardExpect {
async pieSliceCount(expectedCount) {
log.debug(`DashboardExpect.expectPieSliceCount(${expectedCount})`);
await retry.try(async () => {
const slicesCount = await PageObjects.dashboard.getPieSliceCount();
expect(slicesCount).to.be(expectedCount);
});
}
async panelCount(expectedCount) {
log.debug(`DashboardExpect.panelCount(${expectedCount})`);

View file

@ -32,5 +32,7 @@ export { ComboBoxProvider } from './combo_box';
export { RenderableProvider } from './renderable';
export { TableProvider } from './table';
export { BrowserProvider } from './browser';
export { InspectorProvider } from './inspector';
export * from './visualizations';
export * from './dashboard';

View file

@ -0,0 +1,149 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import expect from 'expect.js';
export function InspectorProvider({ getService }) {
const log = getService('log');
const retry = getService('retry');
const browser = getService('browser');
const renderable = getService('renderable');
const flyout = getService('flyout');
const testSubjects = getService('testSubjects');
const find = getService('find');
return new class Inspector {
async getIsEnabled() {
const button = await testSubjects.find('openInspectorButton');
const ariaDisabled = await button.getAttribute('aria-disabled');
return ariaDisabled !== 'true';
}
async expectIsEnabled() {
await retry.try(async () => {
const isEnabled = await this.getIsEnabled();
expect(isEnabled).to.be(true);
});
}
async expectIsNotEnabled() {
await retry.try(async () => {
const isEnabled = await this.getIsEnabled();
expect(isEnabled).to.be(false);
});
}
async open() {
log.debug('Inspector.open');
const isOpen = await testSubjects.exists('inspectorPanel');
if (!isOpen) {
await retry.try(async () => {
await testSubjects.click('openInspectorButton');
await testSubjects.find('inspectorPanel');
});
}
}
async close() {
log.debug('Close Inspector');
let isOpen = await testSubjects.exists('inspectorPanel');
if (isOpen) {
await retry.try(async () => {
await flyout.close('inspectorPanel');
isOpen = await testSubjects.exists('inspectorPanel');
if (isOpen) {
throw new Error('Failed to close inspector');
}
});
}
}
async expectTableData(expectedData) {
await log.debug(`Inspector.expectTableData(${expectedData.join(',')})`);
const data = await this.getTableData();
expect(data).to.eql(expectedData);
}
async setTablePageSize(size) {
const panel = await testSubjects.find('inspectorPanel');
await find.clickByButtonText('Rows per page: 20', panel);
// The buttons for setting table page size are in a popover element. This popover
// element appears as if it's part of the inspectorPanel but it's really attached
// to the body element by a portal.
const tableSizesPopover = await find.byCssSelector('.euiPanel');
await find.clickByButtonText(`${size} rows`, tableSizesPopover);
}
async getTableData() {
// TODO: we should use datat-test-subj=inspectorTable as soon as EUI supports it
const inspectorPanel = await testSubjects.find('inspectorPanel');
const tableBody = await retry.try(async () => inspectorPanel.findByTagName('tbody'));
// Convert the data into a nested array format:
// [ [cell1_in_row1, cell2_in_row1], [cell1_in_row2, cell2_in_row2] ]
const rows = await tableBody.findAllByTagName('tr');
return await Promise.all(rows.map(async row => {
const cells = await row.findAllByTagName('td');
return await Promise.all(cells.map(async cell => cell.getVisibleText()));
}));
}
async getTableHeaders() {
log.debug('Inspector.getTableHeaders');
// TODO: we should use datat-test-subj=inspectorTable as soon as EUI supports it
const dataTableHeader = await retry.try(async () => {
const inspectorPanel = await testSubjects.find('inspectorPanel');
return await inspectorPanel.findByTagName('thead');
});
const cells = await dataTableHeader.findAllByTagName('th');
return await Promise.all(cells.map(async (cell) => {
const untrimmed = await cell.getVisibleText();
return untrimmed.trim();
}));
}
async expectTableHeaders(expected) {
await retry.try(async () => {
const headers = await this.getTableHeaders();
expect(headers).to.eql(expected);
});
}
async filterForTableCell(column, row) {
await retry.try(async () => {
const table = await testSubjects.find('inspectorTable');
const cell = await table.findByCssSelector(`tbody tr:nth-child(${row}) td:nth-child(${column})`);
await browser.moveMouseTo(cell);
const filterBtn = await testSubjects.findDescendant('filterForInspectorCellValue', cell);
await filterBtn.click();
});
await renderable.waitForRender();
}
async filterOutTableCell(column, row) {
await retry.try(async () => {
const table = await testSubjects.find('inspectorTable');
const cell = await table.findByCssSelector(`tbody tr:nth-child(${row}) td:nth-child(${column})`);
await browser.moveMouseTo(cell);
const filterBtn = await testSubjects.findDescendant('filterOutInspectorCellValue', cell);
await filterBtn.click();
});
await renderable.waitForRender();
}
};
}

View file

@ -17,7 +17,6 @@
* under the License.
*/
import expect from 'expect.js';
import testSubjSelector from '@kbn/test-subj-selector';
import {
filter as filterAsync,
@ -32,28 +31,34 @@ export function TestSubjectsProvider({ getService }) {
const config = getService('config');
const FIND_TIME = config.get('timeouts.find');
const TRY_TIME = config.get('timeouts.try');
const WAIT_FOR_EXISTS_TIME = config.get('timeouts.waitForExists');
class TestSubjects {
async exists(selector, timeout = WAIT_FOR_EXISTS_TIME) {
async exists(selector, options = {}) {
const {
timeout = WAIT_FOR_EXISTS_TIME,
allowHidden = false,
} = options;
log.debug(`TestSubjects.exists(${selector})`);
return await find.existsByDisplayedByCssSelector(testSubjSelector(selector), timeout);
return await (
allowHidden
? find.existsByCssSelector(testSubjSelector(selector), timeout)
: find.existsByDisplayedByCssSelector(testSubjSelector(selector), timeout)
);
}
async existOrFail(selector, timeout = WAIT_FOR_EXISTS_TIME) {
await retry.try(async () => {
log.debug(`TestSubjects.existOrFail(${selector})`);
const doesExist = await this.exists(selector, timeout);
// Verify element exists, or else fail the test consuming this.
expect(doesExist).to.be(true);
});
async existOrFail(selector, existsOptions) {
if (!await this.exists(selector, { timeout: TRY_TIME, ...existsOptions })) {
throw new Error(`expected testSubject(${selector}) to exist`);
}
}
async missingOrFail(selector, timeout = WAIT_FOR_EXISTS_TIME) {
log.debug(`TestSubjects.missingOrFail(${selector})`);
const doesExist = await this.exists(selector, timeout);
// Verify element is missing, or else fail the test consuming this.
expect(doesExist).to.be(false);
async missingOrFail(selector, existsOptions) {
if (await this.exists(selector, existsOptions)) {
throw new Error(`expected testSubject(${selector}) to not exist`);
}
}

View file

@ -0,0 +1,20 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export { PieChartProvider } from './pie_chart';

View file

@ -0,0 +1,111 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import expect from 'expect.js';
export function PieChartProvider({ getService }) {
const log = getService('log');
const retry = getService('retry');
const config = getService('config');
const inspector = getService('inspector');
const testSubjects = getService('testSubjects');
const find = getService('find');
const defaultFindTimeout = config.get('timeouts.find');
return new class PieChart {
async filterOnPieSlice(name) {
log.debug(`PieChart.filterOnPieSlice(${name})`);
if (name) {
await testSubjects.click(`pieSlice-${name.split(' ').join('-')}`);
} else {
// If no pie slice has been provided, find the first one available.
await retry.try(async () => {
const slices = await find.allByCssSelector('svg > g > g.arcs > path.slice');
log.debug('Slices found:' + slices.length);
return slices[0].click();
});
}
}
async getPieSlice(name) {
return await testSubjects.find(`pieSlice-${name.split(' ').join('-')}`);
}
async getAllPieSlices(name) {
return await testSubjects.findAll(`pieSlice-${name.split(' ').join('-')}`);
}
async getPieSliceStyle(name) {
log.debug(`VisualizePage.getPieSliceStyle(${name})`);
const pieSlice = await this.getPieSlice(name);
return await pieSlice.getAttribute('style');
}
async getAllPieSliceStyles(name) {
log.debug(`VisualizePage.getAllPieSliceStyles(${name})`);
const pieSlices = await this.getAllPieSlices(name);
return await Promise.all(pieSlices.map(async pieSlice => await pieSlice.getAttribute('style')));
}
async getPieChartData() {
const chartTypes = await find.allByCssSelector('path.slice', defaultFindTimeout * 2);
const getChartTypesPromises = chartTypes.map(async chart => await chart.getAttribute('d'));
return await Promise.all(getChartTypesPromises);
}
async expectPieChartTableData(expectedTableData) {
await inspector.open();
await inspector.setTablePageSize(50);
await inspector.expectTableData(expectedTableData);
}
async getPieChartLabels() {
const chartTypes = await find.allByCssSelector('path.slice', defaultFindTimeout * 2);
const getChartTypesPromises = chartTypes.map(async chart => await chart.getAttribute('data-label'));
return await Promise.all(getChartTypesPromises);
}
async getPieSliceCount() {
log.debug('PieChart.getPieSliceCount');
return await retry.try(async () => {
const slices = await find.allByCssSelector('svg > g > g.arcs > path.slice');
return slices.length;
});
}
async expectPieSliceCount(expectedCount) {
log.debug(`PieChart.expectPieSliceCount(${expectedCount})`);
await retry.try(async () => {
const slicesCount = await this.getPieSliceCount();
expect(slicesCount).to.be(expectedCount);
});
}
async expectPieChartLabels(expectedLabels) {
log.debug(`PieChart.expectPieChartLabels(${expectedLabels.join(',')})`);
await retry.try(async () => {
const pieData = await this.getPieChartLabels();
expect(pieData).to.eql(expectedLabels);
});
}
};
}

View file

@ -139,7 +139,7 @@
},
"dependencies": {
"@elastic/datemath": "5.0.2",
"@elastic/eui": "6.0.1",
"@elastic/eui": "6.3.1",
"@elastic/javascript-typescript-langserver": "^0.1.9",
"@elastic/lsp-extension": "^0.1.1",
"@elastic/node-crypto": "0.1.2",

View file

@ -12,6 +12,7 @@ import {
EuiTitle
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import idx from 'idx';
import { first, get } from 'lodash';
import React from 'react';
import { RRRRenderResponse } from 'react-redux-request';
@ -122,7 +123,10 @@ export function DetailView({ errorGroup, urlParams, location }: Props) {
{
fieldName: REQUEST_URL_FULL,
label: 'URL',
val: get(error, REQUEST_URL_FULL, notAvailableLabel),
val:
idx(error, _ => _.context.page.url) ||
idx(transaction, _ => _.context.request.url.full) ||
notAvailableLabel,
truncated: true,
width: '50%'
},

View file

@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import idx from 'idx';
import { get } from 'lodash';
import React from 'react';
import {
@ -29,7 +30,11 @@ export function StickyTransactionProperties({
totalDuration
}: Props) {
const timestamp = transaction['@timestamp'];
const url = get(transaction, REQUEST_URL_FULL, 'N/A');
const url =
idx(transaction, _ => _.context.page.url) ||
idx(transaction, _ => _.context.request.url) ||
'N/A';
const duration = transaction.transaction.duration.us;
const stickyProperties: IStickyProperty[] = [
{

View file

@ -29,6 +29,9 @@ interface Context {
service: ContextService;
system?: ContextSystem;
request?: ContextRequest;
page?: {
url: string;
};
[key: string]: unknown;
}

View file

@ -27,6 +27,9 @@ interface Context {
username?: string;
email?: string;
};
page?: {
url: string;
};
[key: string]: unknown;
}

View file

@ -51,4 +51,7 @@ build
public/style/index.css
# Don't commit built plugin files
canvas_plugin/*
canvas_plugin/*
# Don't commit the Webpack statistics
webpack_stats.json

View file

@ -25,6 +25,8 @@ export const WorkpadTemplates = compose(
// Clone workpad given an id
cloneWorkpad: props => workpad => {
workpad.id = getId('workpad');
workpad.name = `Untitled Workpad - ${workpad.name}`;
workpad.tags = undefined;
return workpadService
.create(workpad)
.then(() => props.router.navigateTo('loadWorkpad', { id: workpad.id, page: 1 }))

View file

@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
const { writeFileSync } = require('fs');
const path = require('path');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const {
@ -13,12 +14,11 @@ const {
const sourceDir = path.resolve(__dirname, '../../canvas_plugin_src');
const buildDir = path.resolve(__dirname, '../../canvas_plugin');
export function getWebpackConfig({ devtool, watch } = {}) {
export function getWebpackConfig({ devtool, watch, production } = {}) {
return {
watch,
devtool,
mode: 'none',
mode: production ? 'production' : 'none',
entry: {
'elements/all': path.join(sourceDir, 'elements/register.js'),
'renderers/all': path.join(sourceDir, 'renderers/register.js'),
@ -94,6 +94,16 @@ export function getWebpackConfig({ devtool, watch } = {}) {
ignore: '**/__tests__/**',
},
]),
function canvasStatsGenerator() {
if (!process.env.CANVAS_GENERATE_STATS) {
return;
}
this.hooks.done.tap('canvas_stats', stats => {
const content = JSON.stringify(stats.toJson());
writeFileSync(path.resolve(__dirname, '../../webpack_stats.json'), content);
});
},
],
module: {

View file

@ -40,6 +40,6 @@ export default function pluginsTasks(gulp, { log, colors }) {
});
gulp.task('canvas:plugins:build-prod', function(done) {
del(buildDir).then(() => webpack(getWebpackConfig(), onComplete(done)));
del(buildDir).then(() => webpack(getWebpackConfig({ production: true }), onComplete(done)));
});
}

View file

@ -43,14 +43,18 @@ map-listing, .gisListingPage {
.gisWidgetOverlay {
position: absolute;
z-index: $euiZLevel1;
min-width: 17rem;
max-width: 24rem;
top: $euiSizeM;
right: $euiSizeM;
bottom: $euiSizeM;
// left: $euiSizeM;
pointer-events: none; /* 1 */
}
.gisWidgetOverlay__rightSide {
min-width: 17rem;
max-width: 24rem;
}
.gisWidgetControl {
max-height: 100%;
overflow: hidden;
@ -70,6 +74,21 @@ map-listing, .gisListingPage {
}
}
.gisAttributionControl {
padding: 0 $euiSizeXS;
}
.gisViewControl__coordinates {
padding: $euiSizeXS $euiSizeS;
justify-content: center;
pointer-events: none;
}
.gisViewControl__gotoButton {
min-width: 0;
pointer-events: all; /* 1 */
}
.gisWidgetControl__tocHolder {
@include euiScrollBar;
overflow-y: auto;
@ -173,9 +192,12 @@ map-listing, .gisListingPage {
overflow-y: auto;
@include euiScrollBar;
> * {
flex: 0 0 auto;
}
> *:not(:last-child) {
margin-bottom: $euiSize;
flex: 0 0 auto;
}
}

View file

@ -296,20 +296,22 @@ export function updateSourceProp(layerId, propName, value) {
propName,
value,
});
dispatch(syncDataForLayer(layerId));
};
}
export function syncDataForLayer(layerId) {
return (dispatch, getState) => {
return async (dispatch, getState) => {
const targetLayer = getLayerList(getState()).find(layer => {
return layer.getId() === layerId;
});
if (targetLayer) {
const dataFilters = getDataFilters(getState());
const loadingFunctions = getLayerLoadingCallbacks(dispatch, layerId);
targetLayer.syncData({ ...loadingFunctions, dataFilters });
await targetLayer.syncData({
...loadingFunctions,
dataFilters
});
}
};
}

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import React, { Component, Fragment } from 'react';
import { ALL_SOURCES } from '../../shared/layers/sources/all_sources';
import {
EuiSpacer,
@ -14,63 +14,48 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiTitle,
EuiForm,
EuiFormRow,
EuiSuperSelect,
EuiPanel,
EuiCard,
EuiIcon,
} from '@elastic/eui';
export class AddLayerPanel extends React.Component {
export class AddLayerPanel extends Component {
constructor() {
super();
this.state = {
label: '',
sourceType: '',
minZoom: 0,
maxZoom: 24,
alphaValue: 1
sourceType: null,
};
}
componentDidUpdate() {
if (this.layer && this.state.alphaValue === null) {
const defaultAlphaValue = this.layer._descriptor.type === 'TILE' ? 1 : 1;
if (this.state.alphaValue !== defaultAlphaValue) {
this.setState({
alphaValue: defaultAlphaValue
});
}
}
}
_previewLayer = (source) => {
this.layer = source.createDefaultLayer({
temporary: true,
label: this.state.label,
minZoom: this.state.minZoom,
maxZoom: this.state.maxZoom,
});
this.props.previewLayer(this.layer);
};
_onSourceTypeChange = (sourceType) => {
this.setState({
sourceType,
});
_clearSource = () => {
this.setState({ sourceType: null });
if (this.layer) {
this.props.removeLayer(this.layer.getId());
}
}
_onSourceTypeChange = (sourceType) => {
this.setState({ sourceType });
}
_renderNextBtn() {
if (!this.state.sourceType) {
return null;
}
const { layerLoading, temporaryLayers, nextAction } = this.props;
const addToMapBtnText = 'Next';
return (
<EuiButton
style={{ width: '9rem' }}
disabled={!temporaryLayers || layerLoading}
isLoading={layerLoading}
iconSide="right"
@ -82,42 +67,41 @@ export class AddLayerPanel extends React.Component {
}}
fill
>
{addToMapBtnText}
Create layer
</EuiButton>
);
}
_renderSourceSelect() {
const sourceOptions = ALL_SOURCES.map(Source => {
return {
value: Source.type,
inputDisplay: Source.typeDisplayName,
dropdownDisplay: Source.renderDropdownDisplayOption()
};
});
return (
<EuiFormRow label="Data source">
<EuiSuperSelect
itemClassName="sourceSelectItem"
options={sourceOptions}
valueOfSelected={this.state.sourceType}
onChange={this._onSourceTypeChange}
itemLayoutAlign="top"
hasDividers
_renderSourceCards() {
return ALL_SOURCES.map(Source => {
const icon = Source.icon
? <EuiIcon type={Source.icon} size="xl" />
: null;
return (
<EuiCard
key={Source.type}
title={Source.title}
icon={icon}
onClick={() => this._onSourceTypeChange(Source.type)}
description={Source.description}
layout="horizontal"
/>
</EuiFormRow>
);
});
}
_renderSourceSelect() {
return (
<Fragment>
<EuiTitle size="xs">
<h2>Choose data source</h2>
</EuiTitle>
{this._renderSourceCards()}
</Fragment>
);
}
_renderSourceEditor() {
if (!this.state.sourceType) {
return;
}
const editorProperties = {
onPreviewSource: this._previewLayer,
dataSourcesMeta: this.props.dataSourcesMeta
@ -130,16 +114,28 @@ export class AddLayerPanel extends React.Component {
throw new Error(`Unexepected source type: ${this.state.sourceType}`);
}
return Source.renderEditor(editorProperties);
return (
<Fragment>
<EuiButtonEmpty
contentProps={{ style: { justifyContent: 'left' } }}
onClick={this._clearSource}
iconType="arrowLeft"
>
Change data source
</EuiButtonEmpty>
<EuiPanel>
{Source.renderEditor(editorProperties)}
</EuiPanel>
</Fragment>
);
}
_renderAddLayerForm() {
return (
<EuiForm>
{this._renderSourceSelect()}
{this._renderSourceEditor()}
</EuiForm>
);
if (!this.state.sourceType) {
return this._renderSourceSelect();
}
return this._renderSourceEditor();
}
_renderFlyout() {
@ -157,9 +153,7 @@ export class AddLayerPanel extends React.Component {
</EuiFlexItem>
<EuiFlexItem className="gisViewPanel__body">
<EuiPanel>
{this._renderAddLayerForm()}
</EuiPanel>
{this._renderAddLayerForm()}
</EuiFlexItem>
<EuiFlexItem grow={false} className="gisViewPanel__footer">

View file

@ -12,7 +12,7 @@ import {
mapDestroyed,
setMouseCoordinates,
clearMouseCoordinates,
clearGoto,
clearGoto
} from '../../../actions/store_actions';
import { getLayerList, getMapReady, getGoto } from "../../../selectors/map_selectors";
@ -20,7 +20,7 @@ function mapStateToProps(state = {}) {
return {
isMapReady: getMapReady(state),
layerList: getLayerList(state),
goto: getGoto(state),
goto: getGoto(state)
};
}

View file

@ -10,6 +10,7 @@ import mapboxgl from 'mapbox-gl';
export async function createMbMapInstance(node, initialView) {
return new Promise((resolve) => {
const options = {
attributionControl: false,
container: node,
style: {
version: 8,

View file

@ -174,7 +174,7 @@ export class MBMapContainer extends React.Component {
lng: goto.lon,
lat: goto.lat
});
}
};
_syncMbMapWithLayerList = () => {
const {
@ -190,7 +190,7 @@ export class MBMapContainer extends React.Component {
layer.syncLayerWithMB(this._mbMap);
});
syncLayerOrder(this._mbMap, layerList);
}
};
_syncMbMapWithInspector = () => {
if (!this.props.isMapReady) {
@ -206,7 +206,7 @@ export class MBMapContainer extends React.Component {
stats,
style: this._mbMap.getStyle(),
});
}
};
render() {
// do not debounce syncing zoom and center

View file

@ -0,0 +1,22 @@
/*
* 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 { connect } from 'react-redux';
import { AttributionControl } from './view';
import { getLayerList } from "../../../selectors/map_selectors";
function mapStateToProps(state = {}) {
return {
layerList: getLayerList(state)
};
}
function mapDispatchToProps() {
return {};
}
const connectedViewControl = connect(mapStateToProps, mapDispatchToProps)(AttributionControl);
export { connectedViewControl as AttributionControl };

View file

@ -0,0 +1,88 @@
/*
* 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 React, { Fragment } from 'react';
import _ from 'lodash';
import {
EuiText,
EuiPanel,
EuiLink,
} from '@elastic/eui';
export class AttributionControl extends React.Component {
constructor() {
super();
this.state = {
uniqueAttributions: []
};
}
componentDidMount() {
this._isMounted = true;
this._syncMbMapWithAttribution();
}
componentWillUnmount() {
this._isMounted = false;
}
componentDidUpdate() {
this._syncMbMapWithAttribution();
}
_syncMbMapWithAttribution = async () => {
const attributionPromises = this.props.layerList.map(layer => {
return layer.getAttributions();
});
const attributions = await Promise.all(attributionPromises);
if (!this._isMounted) {
return;
}
const uniqueAttributions = [];
for (let i = 0; i < attributions.length; i++) {
for (let j = 0; j < attributions[i].length; j++) {
const testAttr = attributions[i][j];
const attr = uniqueAttributions.find((added) => {
return (added.url === testAttr.url && added.label === testAttr.label);
});
if (!attr) {
uniqueAttributions.push(testAttr);
}
}
}
if (!_.isEqual(this.state.uniqueAttributions, uniqueAttributions)) {
this.setState({ uniqueAttributions });
}
};
_renderAttributions() {
return this.state.uniqueAttributions.map((attribution, index) => {
return (
<Fragment key={index}>
<EuiLink color="subdued" href={attribution.url} target="_blank">{attribution.label}</EuiLink>
{index < (this.state.uniqueAttributions.length - 1) && ', '}
</Fragment>
);
});
}
render() {
if (this.state.uniqueAttributions.length === 0) {
return null;
}
return (
<EuiPanel className="gisWidgetControl gisAttributionControl" paddingSize="none" grow={false}>
<EuiText color="subdued" size="xs">
<small>{this._renderAttributions()}</small>
</EuiText>
</EuiPanel>
);
}
}

View file

@ -4,12 +4,12 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React, { Fragment } from 'react';
import React from 'react';
import {
EuiFlexGroup,
EuiFlexItem,
EuiPanel,
EuiButtonEmpty,
EuiButton,
EuiPopover,
EuiText,
} from '@elastic/eui';
@ -26,15 +26,17 @@ export function ViewControl({ isSetViewOpen, closeSetView, openSetView, mouseCoo
};
const setView = (
<EuiPopover
anchorPosition="upRight"
button={(
<EuiButtonEmpty
flush="right"
size="xs"
<EuiButton
className="gisViewControl__gotoButton"
fill
size="s"
onClick={toggleSetViewVisibility}
data-test-subj="toggleSetViewVisibilityButton"
>
Goto
</EuiButtonEmpty>)}
</EuiButton>)}
isOpen={isSetViewOpen}
closePopover={closeSetView}
>
@ -44,39 +46,30 @@ export function ViewControl({ isSetViewOpen, closeSetView, openSetView, mouseCoo
function renderMouseCoordinates() {
return (
<Fragment>
<EuiFlexItem grow={false}>
<EuiText size="xs">
<p>
<strong>lat:</strong> {mouseCoordinates && mouseCoordinates.lat}
</p>
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="xs">
<p>
<strong>long:</strong> {mouseCoordinates && mouseCoordinates.lon}
</p>
</EuiText>
</EuiFlexItem>
</Fragment>
<EuiPanel className="gisWidgetControl gisViewControl__coordinates" paddingSize="none">
<EuiText size="xs">
<p>
<strong>lat:</strong> {mouseCoordinates && mouseCoordinates.lat},{' '}
<strong>lon:</strong> {mouseCoordinates && mouseCoordinates.lon}
</p>
</EuiText>
</EuiPanel>
);
}
return (
<EuiPanel className="gisWidgetControl" hasShadow paddingSize="s">
<EuiFlexGroup
justifyContent="spaceBetween"
alignItems="center"
gutterSize="s"
>
<EuiFlexGroup
justifyContent="spaceBetween"
gutterSize="s"
responsive={false}
>
<EuiFlexItem>
{mouseCoordinates && renderMouseCoordinates()}
</EuiFlexItem>
{renderMouseCoordinates()}
<EuiFlexItem grow={false}>
{setView}
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
<EuiFlexItem grow={false}>
{setView}
</EuiFlexItem>
</EuiFlexGroup>
);
}

View file

@ -11,19 +11,28 @@ import {
} from '@elastic/eui';
import { LayerControl } from './layer_control';
import { ViewControl } from './view_control';
import { AttributionControl } from './attribution_control';
export function WidgetOverlay() {
return (
<EuiFlexGroup
className="gisWidgetOverlay"
direction="column"
justifyContent="spaceBetween"
>
<EuiFlexGroup className="gisWidgetOverlay" responsive={false} direction="column" alignItems="flexEnd" gutterSize="s">
<EuiFlexItem>
<LayerControl/>
<EuiFlexGroup
className="gisWidgetOverlay__rightSide"
direction="column"
justifyContent="spaceBetween"
responsive={false}
>
<EuiFlexItem>
<LayerControl/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<ViewControl/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<ViewControl/>
<AttributionControl/>
</EuiFlexItem>
</EuiFlexGroup>
);

View file

@ -19,7 +19,7 @@ const AGG_OPTIONS = [
export const METRIC_AGGREGATION_VALUES = AGG_OPTIONS.map(({ value }) => { return value; });
export function MetricSelect({ value, onChange }) {
export function MetricSelect({ value, onChange, metricsFilter }) {
function onAggChange(selectedOptions) {
if (selectedOptions.length === 0) {
@ -30,12 +30,14 @@ export function MetricSelect({ value, onChange }) {
onChange(aggType);
}
const options = metricsFilter ? AGG_OPTIONS.filter(metricsFilter) : AGG_OPTIONS;
return (
<EuiComboBox
placeholder="Select aggregation"
singleSelection={true}
isClearable={false}
options={AGG_OPTIONS}
options={options}
selectedOptions={AGG_OPTIONS.filter(option => {
return value === option.value;
})}
@ -45,6 +47,7 @@ export function MetricSelect({ value, onChange }) {
}
MetricSelect.propTypes = {
metricsFilter: PropTypes.func,
value: PropTypes.oneOf(METRIC_AGGREGATION_VALUES),
onChange: PropTypes.func.isRequired,
};

View file

@ -20,7 +20,7 @@ import {
} from './metric_select';
import { SingleFieldSelect } from './single_field_select';
export function MetricsEditor({ fields, metrics, onChange }) {
export function MetricsEditor({ fields, metrics, onChange, allowMultipleMetrics, metricsFilter }) {
function onMetricChange(metric, index) {
onChange([
@ -91,6 +91,7 @@ export function MetricsEditor({ fields, metrics, onChange }) {
<MetricSelect
onChange={onAggChange}
value={metric.type}
metricsFilter={metricsFilter}
/>
</EuiFlexItem>
@ -110,6 +111,21 @@ export function MetricsEditor({ fields, metrics, onChange }) {
]);
}
function renderAddMetricButton() {
if (!allowMultipleMetrics) {
return null;
}
return (<EuiButtonIcon
iconType="plusInCircle"
onClick={addMetric}
aria-label="Add metric"
title="Add metric"
/>);
}
return (
<Fragment>
<EuiFlexGroup alignItems="center">
@ -119,12 +135,7 @@ export function MetricsEditor({ fields, metrics, onChange }) {
</EuiFormLabel>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonIcon
iconType="plusInCircle"
onClick={addMetric}
aria-label="Add metric"
title="Add metric"
/>
{renderAddMetricButton()}
</EuiFlexItem>
</EuiFlexGroup>
@ -140,6 +151,8 @@ MetricsEditor.propTypes = {
})),
fields: PropTypes.object, // indexPattern.fields IndexedArray object
onChange: PropTypes.func.isRequired,
allowMultipleMetrics: PropTypes.bool,
metricsFilter: PropTypes.func,
};
MetricsEditor.defaultProps = {

View file

@ -11,6 +11,8 @@ import { EuiIcon } from '@elastic/eui';
import { HeatmapStyle } from './styles/heatmap_style';
import { ZOOM_TO_PRECISION } from '../utils/zoom_to_precision';
const SCALED_PROPERTY_NAME = '__kbn_heatmap_weight__';//unique name to store scaled value for weighting
export class HeatmapLayer extends ALayer {
static type = "HEATMAP";
@ -39,6 +41,11 @@ export class HeatmapLayer extends ALayer {
return this._source.getIndexPatternIds();
}
_getPropKeyOfSelectedMetric() {
const metricfields = this._source.getMetricFields();
return metricfields[0].propertyKey;
}
syncLayerWithMB(mbMap) {
const mbSource = mbMap.getSource(this.getId());
@ -67,22 +74,21 @@ export class HeatmapLayer extends ALayer {
return;
}
const scaledPropertyName = '__kbn_heatmap_weight__';
const propertyName = 'doc_count';
const propertyKey = this._getPropKeyOfSelectedMetric();
const dataBoundToMap = ALayer.getBoundDataForSource(mbMap, this.getId());
if (featureCollection !== dataBoundToMap) {
let max = 0;
for (let i = 0; i < featureCollection.features.length; i++) {
max = Math.max(featureCollection.features[i].properties[propertyName], max);
max = Math.max(featureCollection.features[i].properties[propertyKey], max);
}
for (let i = 0; i < featureCollection.features.length; i++) {
featureCollection.features[i].properties[scaledPropertyName] = featureCollection.features[i].properties[propertyName] / max;
featureCollection.features[i].properties[SCALED_PROPERTY_NAME] = featureCollection.features[i].properties[propertyKey] / max;
}
mbSourceAfter.setData(featureCollection);
}
mbMap.setLayoutProperty(heatmapLayerId, 'visibility', this.isVisible() ? 'visible' : 'none');
this._style.setMBPaintProperties(mbMap, heatmapLayerId, scaledPropertyName);
this._style.setMBPaintProperties(mbMap, heatmapLayerId, SCALED_PROPERTY_NAME);
mbMap.setLayerZoomRange(heatmapLayerId, this._descriptor.minZoom, this._descriptor.maxZoom);
}
@ -112,19 +118,24 @@ export class HeatmapLayer extends ALayer {
const updateDueToQuery = dataFilters.query
&& !_.isEqual(dataMeta.query, dataFilters.query);
const metricPropertyKey = this._getPropKeyOfSelectedMetric();
const updateDueToMetricChange = !_.isEqual(dataMeta.metric, metricPropertyKey);
if (isSamePrecision
&& isSameTime
&& !updateDueToExtent
&& !updateDueToRefreshTimer
&& !updateDueToQuery) {
&& !updateDueToQuery
&& !updateDueToMetricChange) {
return;
}
const newDataMeta = {
...dataFilters,
precision: targetPrecision
precision: targetPrecision,
metric: metricPropertyKey
};
return this._fetchNewData({ startLoading, stopLoading, onLoadError, dataMeta: newDataMeta });
await this._fetchNewData({ startLoading, stopLoading, onLoadError, dataMeta: newDataMeta });
}
async _fetchNewData({ startLoading, stopLoading, onLoadError, dataMeta }) {
@ -133,7 +144,7 @@ export class HeatmapLayer extends ALayer {
startLoading('source', requestToken, dataMeta);
try {
const layerName = await this.getDisplayName();
const data = await this._source.getGeoJsonPointsWithTotalCount({
const data = await this._source.getGeoJsonPoints({
precision,
extent: buffer,
timeFilters,

View file

@ -64,6 +64,10 @@ export class ALayer {
return (await this._source.getDisplayName()) || `Layer ${this._descriptor.id}`;
}
async getAttributions() {
return await this._source.getAttributions();
}
getLabel() {
return this._descriptor.label ? this._descriptor.label : '';
}

View file

@ -5,13 +5,12 @@
*/
import { VectorSource } from './vector_source';
import React, { Fragment } from 'react';
import React from 'react';
import {
EuiLink,
EuiText,
EuiSelect,
EuiFormRow,
EuiSpacer
} from '@elastic/eui';
import { GIS_API_PATH } from '../../../../common/constants';
@ -20,7 +19,9 @@ import { emsServiceSettings } from '../../../kibana_services';
export class EMSFileSource extends VectorSource {
static type = 'EMS_FILE';
static typeDisplayName = 'Elastic Maps Service vector shapes';
static title = 'Elastic Maps Service vector shapes';
static description = 'Vector shapes of administrative boundaries from Elastic Maps Service';
static icon = 'emsApp';
static createDescriptor(id) {
return {
@ -54,20 +55,6 @@ export class EMSFileSource extends VectorSource {
);
}
static renderDropdownDisplayOption() {
return (
<Fragment>
<strong>{EMSFileSource.typeDisplayName}</strong>
<EuiSpacer size="xs" />
<EuiText size="s" color="subdued">
<p className="euiTextColor--subdued">
Vector shapes of administrative boundaries from Elastic Maps Service
</p>
</EuiText>
</Fragment>
);
}
constructor(descriptor, { emsFileLayers }) {
super(descriptor);
this._emsFiles = emsFileLayers;
@ -100,6 +87,13 @@ export class EMSFileSource extends VectorSource {
const fileSource = this._emsFiles.find((source => source.id === this._descriptor.id));
return fileSource.name;
}
async getAttributions() {
const fileSource = this._emsFiles.find((source => source.id === this._descriptor.id));
return fileSource.attributions;
}
async getStringFields() {
//todo: use map/service-settings instead.
const fileSource = this._emsFiles.find((source => source.id === this._descriptor.id));

View file

@ -3,14 +3,13 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React, { Fragment } from 'react';
import { TMSSource } from './tms_source';
import { TileLayer } from '../tile_layer';
import React from 'react';
import { TMSSource } from '../tms_source';
import { TileLayer } from '../../tile_layer';
import {
EuiText,
EuiSelect,
EuiFormRow,
EuiSpacer
} from '@elastic/eui';
import _ from 'lodash';
@ -18,7 +17,9 @@ import _ from 'lodash';
export class EMSTMSSource extends TMSSource {
static type = 'EMS_TMS';
static typeDisplayName = 'Elastic Maps Service tiles';
static title = 'Elastic Maps Service tiles';
static description = 'Map tiles from Elastic Maps Service';
static icon = 'emsApp';
static createDescriptor(serviceId) {
return {
@ -52,20 +53,6 @@ export class EMSTMSSource extends TMSSource {
);
}
static renderDropdownDisplayOption() {
return (
<Fragment>
<strong>{EMSTMSSource.typeDisplayName}</strong>
<EuiSpacer size="xs" />
<EuiText size="s" color="subdued">
<p className="euiTextColor--subdued">
Map tiles from Elastic Maps Service
</p>
</EuiText>
</Fragment>
);
}
constructor(descriptor, { emsTmsServices }) {
super(descriptor);
this._emsTileServices = emsTmsServices;
@ -107,6 +94,22 @@ export class EMSTMSSource extends TMSSource {
return this._descriptor.id;
}
async getAttributions() {
const service = this._getTMSOptions();
const attributions = service.attributionMarkdown.split('|');
return attributions.map((attribution) => {
attribution = attribution.trim();
//this assumes attribution is plain markdown link
const extractLink = /\[(.*)\]\((.*)\)/;
const result = extractLink.exec(attribution);
return {
label: result ? result[1] : null,
url: result ? result[2] : null
};
});
}
getUrlTemplate() {
const service = this._getTMSOptions();
return service.url;

View file

@ -0,0 +1,44 @@
/*
* 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 {
EMSTMSSource,
} from './ems_tms_source';
describe('EMSTMSSource', () => {
it('should get attribution from markdown (tiles v2 legacy format)', async () => {
const emsTmsSource = new EMSTMSSource({
id: 'road_map'
}, {
emsTmsServices: [
{
id: 'road_map',
attributionMarkdown: '[foobar](http://foobar.org) | [foobaz](http://foobaz.org)'
}, {
id: 'satellite',
attributionMarkdown: '[satellite](http://satellite.org)'
}
]
});
const attributions = await emsTmsSource.getAttributions();
expect(attributions).toEqual([
{
label: 'foobar',
url: 'http://foobar.org'
}, {
label: 'foobaz',
url: 'http://foobaz.org'
}
]);
});
});

Some files were not shown because too many files have changed in this diff Show more