Skip to content

Commit

Permalink
Merge branch 'master' into pipeline-tasks-redux
Browse files Browse the repository at this point in the history
  • Loading branch information
elasticmachine authored Aug 3, 2020
2 parents e15d78f + 8c6d655 commit 03f6826
Show file tree
Hide file tree
Showing 44 changed files with 985 additions and 367 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -64,35 +64,33 @@ describe('DatePicker', () => {
});

beforeEach(() => {
jest.clearAllMocks();
jest.resetAllMocks();
});

it('should set default query params in the URL', () => {
it('sets default query params in the URL', () => {
mountDatePicker();
expect(mockHistoryPush).toHaveBeenCalledTimes(1);
expect(mockHistoryPush).toHaveBeenCalledWith(
expect.objectContaining({
search:
'rangeFrom=now-15m&rangeTo=now&refreshPaused=false&refreshInterval=10000',
search: 'rangeFrom=now-15m&rangeTo=now',
})
);
});

it('should add missing default value', () => {
it('adds missing default value', () => {
mountDatePicker({
rangeTo: 'now',
refreshInterval: 5000,
});
expect(mockHistoryPush).toHaveBeenCalledTimes(1);
expect(mockHistoryPush).toHaveBeenCalledWith(
expect.objectContaining({
search:
'rangeFrom=now-15m&rangeTo=now&refreshInterval=5000&refreshPaused=false',
search: 'rangeFrom=now-15m&rangeTo=now&refreshInterval=5000',
})
);
});

it('should not set default query params in the URL when values already defined', () => {
it('does not set default query params in the URL when values already defined', () => {
mountDatePicker({
rangeFrom: 'now-1d',
rangeTo: 'now',
Expand All @@ -102,7 +100,7 @@ describe('DatePicker', () => {
expect(mockHistoryPush).toHaveBeenCalledTimes(0);
});

it('should update the URL when the date range changes', () => {
it('updates the URL when the date range changes', () => {
const datePicker = mountDatePicker();
datePicker.find(EuiSuperDatePicker).props().onTimeChange({
start: 'updated-start',
Expand All @@ -113,13 +111,12 @@ describe('DatePicker', () => {
expect(mockHistoryPush).toHaveBeenCalledTimes(2);
expect(mockHistoryPush).toHaveBeenLastCalledWith(
expect.objectContaining({
search:
'rangeFrom=updated-start&rangeTo=updated-end&refreshInterval=5000&refreshPaused=false',
search: 'rangeFrom=updated-start&rangeTo=updated-end',
})
);
});

it('should auto-refresh when refreshPaused is false', async () => {
it('enables auto-refresh when refreshPaused is false', async () => {
jest.useFakeTimers();
const wrapper = mountDatePicker({
refreshPaused: false,
Expand All @@ -132,7 +129,7 @@ describe('DatePicker', () => {
wrapper.unmount();
});

it('should NOT auto-refresh when refreshPaused is true', async () => {
it('disables auto-refresh when refreshPaused is true', async () => {
jest.useFakeTimers();
mountDatePicker({ refreshPaused: true, refreshInterval: 1000 });
expect(mockRefreshTimeRange).not.toHaveBeenCalled();
Expand Down
16 changes: 1 addition & 15 deletions x-pack/plugins/apm/public/components/shared/DatePicker/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,7 @@ import { useUrlParams } from '../../../hooks/useUrlParams';
import { clearCache } from '../../../services/rest/callApi';
import { useApmPluginContext } from '../../../hooks/useApmPluginContext';
import { UI_SETTINGS } from '../../../../../../../src/plugins/data/common';
import {
TimePickerQuickRange,
TimePickerTimeDefaults,
TimePickerRefreshInterval,
} from './typings';
import { TimePickerQuickRange, TimePickerTimeDefaults } from './typings';

function removeUndefinedAndEmptyProps<T extends object>(obj: T): Partial<T> {
return pickBy(obj, (value) => value !== undefined && !isEmpty(String(value)));
Expand All @@ -36,19 +32,9 @@ export function DatePicker() {
UI_SETTINGS.TIMEPICKER_TIME_DEFAULTS
);

const timePickerRefreshIntervalDefaults = core.uiSettings.get<
TimePickerRefreshInterval
>(UI_SETTINGS.TIMEPICKER_REFRESH_INTERVAL_DEFAULTS);

const DEFAULT_VALUES = {
rangeFrom: timePickerTimeDefaults.from,
rangeTo: timePickerTimeDefaults.to,
refreshPaused: timePickerRefreshIntervalDefaults.pause,
/*
* Must be replaced by timePickerRefreshIntervalDefaults.value when this issue is fixed.
* https://github.com/elastic/kibana/issues/70562
*/
refreshInterval: 10000,
};

const commonlyUsedRanges = timePickerQuickRanges.map(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,19 @@ describe('ES search strategy', () => {
expect(method).toBe('POST');
expect(path).toBe('/foo-%E7%A8%8B/_rollup_search');
});

it('sets wait_for_completion_timeout and keep_alive in the request', async () => {
mockApiCaller.mockResolvedValueOnce(mockAsyncResponse);

const params = { index: 'foo-*', body: {} };
const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$, mockLogger);

await esSearch.search((mockContext as unknown) as RequestHandlerContext, { params });

expect(mockApiCaller).toBeCalled();
expect(mockApiCaller.mock.calls[0][0]).toBe('transport.request');
const { query } = mockApiCaller.mock.calls[0][1];
expect(query).toHaveProperty('wait_for_completion_timeout');
expect(query).toHaveProperty('keep_alive');
});
});
11 changes: 9 additions & 2 deletions x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,15 @@ async function asyncSearch(
const method = request.id ? 'GET' : 'POST';
const path = encodeURI(request.id ? `/_async_search/${request.id}` : `/${index}/_async_search`);

// Wait up to 1s for the response to return
const query = toSnakeCase({ waitForCompletionTimeout: '100ms', ...queryParams });
// Only report partial results every 64 shards; this should be reduced when we actually display partial results
const batchedReduceSize = request.id ? undefined : 64;

const query = toSnakeCase({
waitForCompletionTimeout: '100ms', // Wait up to 100ms for the response to return
keepAlive: '1m', // Extend the TTL for this search request by one minute
...(batchedReduceSize && { batchedReduceSize }),
...queryParams,
});

const { id, response, is_partial, is_running } = (await caller(
'transport.request',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
* 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, { FC, useState, useEffect, useMemo, useCallback } from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import {
EuiFlexGroup,
EuiFlexItem,
EuiTitle,
EuiSpacer,
EuiLoadingSpinner,
EuiButton,
} from '@elastic/eui';

import { CombinedJob } from '../../../../../../../../common/types/anomaly_detection_jobs';
import { MLJobEditor } from '../../../../../jobs_list/components/ml_job_editor';
import { mlJobService } from '../../../../../../services/job_service';
import { ML_DATA_PREVIEW_COUNT } from '../../../../../../../../common/util/job_utils';

export const DatafeedPreview: FC<{
combinedJob: CombinedJob | null;
heightOffset?: number;
}> = ({ combinedJob, heightOffset = 0 }) => {
// the ace editor requires a fixed height
const editorHeight = useMemo(() => `${window.innerHeight - 230 - heightOffset}px`, [
heightOffset,
]);
const [loading, setLoading] = useState(false);
const [previewJsonString, setPreviewJsonString] = useState('');
const [outOfDate, setOutOfDate] = useState(false);
const [combinedJobString, setCombinedJobString] = useState('');

useEffect(() => {
try {
if (combinedJob !== null) {
if (combinedJobString === '') {
// first time, set the string and load the preview
loadDataPreview();
} else {
setOutOfDate(JSON.stringify(combinedJob) !== combinedJobString);
}
}
} catch (error) {
// fail silently
}
}, [combinedJob]);

const loadDataPreview = useCallback(async () => {
setPreviewJsonString('');
if (combinedJob === null) {
return;
}

setLoading(true);
setCombinedJobString(JSON.stringify(combinedJob));

if (combinedJob.datafeed_config && combinedJob.datafeed_config.indices.length) {
try {
const resp = await mlJobService.searchPreview(combinedJob);
const data = resp.aggregations
? resp.aggregations.buckets.buckets.slice(0, ML_DATA_PREVIEW_COUNT)
: resp.hits.hits;

setPreviewJsonString(JSON.stringify(data, null, 2));
} catch (error) {
setPreviewJsonString(JSON.stringify(error, null, 2));
}
setLoading(false);
setOutOfDate(false);
} else {
const errorText = i18n.translate(
'xpack.ml.newJob.wizard.datafeedPreviewFlyout.datafeedDoesNotExistLabel',
{
defaultMessage: 'Datafeed does not exist',
}
);
setPreviewJsonString(errorText);
}
}, [combinedJob]);

return (
<EuiFlexItem>
<EuiFlexGroup>
<EuiFlexItem>
<EuiTitle size="s">
<h5>
<FormattedMessage
id="xpack.ml.newJob.wizard.datafeedPreviewFlyout.title"
defaultMessage="Datafeed preview"
/>
</h5>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
{outOfDate && (
<EuiButton size="s" onClick={loadDataPreview} iconType="refresh">
Refresh
</EuiButton>
)}
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="s" />
{loading === true ? (
<EuiFlexGroup justifyContent="spaceAround">
<EuiFlexItem grow={false}>
<EuiSpacer size="xxl" />
<EuiLoadingSpinner size="l" />
</EuiFlexItem>
</EuiFlexGroup>
) : (
<MLJobEditor value={previewJsonString} height={editorHeight} readOnly={true} />
)}
</EuiFlexItem>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,20 @@
* you may not use this file except in compliance with the Elastic License.
*/

import React, { Fragment, FC, useState, useContext, useEffect } from 'react';
import { i18n } from '@kbn/i18n';
import React, { Fragment, FC, useState, useContext } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import {
EuiFlyout,
EuiFlyoutFooter,
EuiFlexGroup,
EuiFlexItem,
EuiButtonEmpty,
EuiTitle,
EuiFlyoutBody,
EuiSpacer,
EuiLoadingSpinner,
} from '@elastic/eui';
import { CombinedJob } from '../../../../../../../../common/types/anomaly_detection_jobs';
import { MLJobEditor } from '../../../../../jobs_list/components/ml_job_editor';

import { JobCreatorContext } from '../../job_creator_context';
import { mlJobService } from '../../../../../../services/job_service';
import { ML_DATA_PREVIEW_COUNT } from '../../../../../../../../common/util/job_utils';
import { DatafeedPreview } from './datafeed_preview';

const EDITOR_HEIGHT = '800px';
export enum EDITOR_MODE {
HIDDEN,
READONLY,
Expand All @@ -36,63 +29,23 @@ interface Props {
export const DatafeedPreviewFlyout: FC<Props> = ({ isDisabled }) => {
const { jobCreator } = useContext(JobCreatorContext);
const [showFlyout, setShowFlyout] = useState(false);
const [previewJsonString, setPreviewJsonString] = useState('');
const [loading, setLoading] = useState(false);

function toggleFlyout() {
setShowFlyout(!showFlyout);
}

useEffect(() => {
if (showFlyout === true) {
loadDataPreview();
}
}, [showFlyout]);

async function loadDataPreview() {
setLoading(true);
setPreviewJsonString('');
const combinedJob: CombinedJob = {
...jobCreator.jobConfig,
datafeed_config: jobCreator.datafeedConfig,
};

if (combinedJob.datafeed_config && combinedJob.datafeed_config.indices.length) {
try {
const resp = await mlJobService.searchPreview(combinedJob);
const data = resp.aggregations
? resp.aggregations.buckets.buckets.slice(0, ML_DATA_PREVIEW_COUNT)
: resp.hits.hits;

setPreviewJsonString(JSON.stringify(data, null, 2));
} catch (error) {
setPreviewJsonString(JSON.stringify(error, null, 2));
}
setLoading(false);
} else {
const errorText = i18n.translate(
'xpack.ml.newJob.wizard.datafeedPreviewFlyout.datafeedDoesNotExistLabel',
{
defaultMessage: 'Datafeed does not exist',
}
);
setPreviewJsonString(errorText);
}
}

return (
<Fragment>
<FlyoutButton onClick={toggleFlyout} isDisabled={isDisabled} />

{showFlyout === true && isDisabled === false && (
<EuiFlyout onClose={() => setShowFlyout(false)} hideCloseButton size="m">
<EuiFlyoutBody>
<Contents
title={i18n.translate('xpack.ml.newJob.wizard.datafeedPreviewFlyout.title', {
defaultMessage: 'Datafeed preview',
})}
value={previewJsonString}
loading={loading}
<DatafeedPreview
combinedJob={{
...jobCreator.jobConfig,
datafeed_config: jobCreator.datafeedConfig,
}}
/>
</EuiFlyoutBody>
<EuiFlyoutFooter>
Expand Down Expand Up @@ -127,28 +80,3 @@ const FlyoutButton: FC<{ isDisabled: boolean; onClick(): void }> = ({ isDisabled
</EuiButtonEmpty>
);
};

const Contents: FC<{
title: string;
value: string;
loading: boolean;
}> = ({ title, value, loading }) => {
return (
<EuiFlexItem>
<EuiTitle size="s">
<h5>{title}</h5>
</EuiTitle>
<EuiSpacer size="s" />
{loading === true ? (
<EuiFlexGroup justifyContent="spaceAround">
<EuiFlexItem grow={false}>
<EuiSpacer size="xxl" />
<EuiLoadingSpinner size="l" />
</EuiFlexItem>
</EuiFlexGroup>
) : (
<MLJobEditor value={value} height={EDITOR_HEIGHT} readOnly={true} />
)}
</EuiFlexItem>
);
};
Loading

0 comments on commit 03f6826

Please sign in to comment.