Skip to content

Commit

Permalink
[Uptime] Search made easy (elastic#88581)
Browse files Browse the repository at this point in the history
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
shahzad31 and kibanamachine committed Feb 24, 2021
1 parent a12ae72 commit 679a697
Show file tree
Hide file tree
Showing 40 changed files with 814 additions and 314 deletions.
1 change: 0 additions & 1 deletion x-pack/plugins/translations/translations/ja-JP.json
Original file line number Diff line number Diff line change
Expand Up @@ -21781,7 +21781,6 @@
"xpack.uptime.filterPopout.searchMessage.ariaLabel": "{title} を検索",
"xpack.uptime.integrationLink.missingDataMessage": "この統合に必要なデータが見つかりませんでした。",
"xpack.uptime.kueryBar.indexPatternMissingWarningMessage": "インデックスパターンの取得中にエラーが発生しました。",
"xpack.uptime.kueryBar.searchPlaceholder": "モニター ID、名前、プロトコルタイプなどを検索…",
"xpack.uptime.locationAvailabilityViewToggleLegend": "トグルを表示",
"xpack.uptime.locationMap.locations.missing.message": "重要な位置情報構成がありません。{codeBlock}フィールドを使用して、アップタイムチェック用に一意の地域を作成できます。",
"xpack.uptime.locationMap.locations.missing.message1": "詳細については、ドキュメンテーションを参照してください。",
Expand Down
1 change: 0 additions & 1 deletion x-pack/plugins/translations/translations/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -21831,7 +21831,6 @@
"xpack.uptime.filterPopout.searchMessage.ariaLabel": "搜索 {title}",
"xpack.uptime.integrationLink.missingDataMessage": "未找到此集成的所需数据。",
"xpack.uptime.kueryBar.indexPatternMissingWarningMessage": "检索索引模式时出错。",
"xpack.uptime.kueryBar.searchPlaceholder": "搜索监测 ID、名称和协议类型......",
"xpack.uptime.locationAvailabilityViewToggleLegend": "视图切换",
"xpack.uptime.locationMap.locations.missing.message": "重要的地理位置配置缺失。您可以使用 {codeBlock} 字段为您的运行时间检查创建独特的地理区域。",
"xpack.uptime.locationMap.locations.missing.message1": "在我们的文档中获取更多的信息。",
Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/uptime/common/constants/ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,5 @@ export enum CERT_STATUS {
EXPIRED = 'EXPIRED',
TOO_OLD = 'TOO_OLD',
}

export const KQL_SYNTAX_LOCAL_STORAGE = 'xpack.uptime.kql.syntax';
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export const FetchMonitorStatesQueryArgsType = t.intersection([
pagination: t.string,
filters: t.string,
statusFilter: t.string,
query: t.string,
}),
t.type({
dateRangeStart: t.string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export interface GetPingHistogramParams {
filters?: string;
monitorId?: string;
bucketSize?: string;
query?: string;
}

export interface HistogramResult {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ interface Props {

const Container: React.FC<Props & ResponsiveWrapperProps> = ({ height }) => {
const {
query,
absoluteDateRangeStart,
absoluteDateRangeEnd,
dateRangeStart: dateStart,
Expand All @@ -37,8 +38,8 @@ const Container: React.FC<Props & ResponsiveWrapperProps> = ({ height }) => {
const { loading, pingHistogram: data } = useSelector(selectPingHistogram);

useEffect(() => {
dispatch(getPingHistogram({ monitorId, dateStart, dateEnd, filters: esKuery }));
}, [dateStart, dateEnd, monitorId, lastRefresh, esKuery, dispatch]);
dispatch(getPingHistogram.get({ monitorId, dateStart, dateEnd, query, filters: esKuery }));
}, [dateStart, dateEnd, monitorId, lastRefresh, esKuery, dispatch, query]);
return (
<PingHistogramComponent
data={data}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,11 @@ export const AlertMonitorStatus: React.FC<Props> = ({
);
useEffect(() => {
dispatch(
getSnapshotCountAction({ dateRangeStart: 'now-24h', dateRangeEnd: 'now', filters: esKuery })
getSnapshotCountAction.get({
dateRangeStart: 'now-24h',
dateRangeEnd: 'now',
filters: esKuery,
})
);
}, [dispatch, esKuery]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export function KueryBar({
let currentRequestCheck: string;

const [getUrlParams, updateUrlParams] = useUrlParams();
const { search: kuery } = getUrlParams();
const { search: kuery, query } = getUrlParams();

useEffect(() => {
updateSearchText(kuery);
Expand Down Expand Up @@ -155,7 +155,7 @@ export function KueryBar({
dataTestSubj={dataTestSubj}
disabled={indexPatternMissing}
isLoading={isLoadingSuggestions || loading}
initialValue={defaultKuery || kuery}
initialValue={defaultKuery || kuery || query}
onChange={onChange}
onSubmit={onSubmit}
suggestions={state.suggestions.slice(0, suggestionLimit)}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React from 'react';
import { fireEvent } from '@testing-library/react';
import { render } from '../../../../../lib/helper/rtl_helpers';
import { SearchType } from './search_type';

describe('Kuery bar search type', () => {
it('can change from simple to kq;', () => {
let kqlSyntax = false;
const setKqlSyntax = jest.fn((val: boolean) => {
kqlSyntax = val;
});

const { getByTestId } = render(
<SearchType kqlSyntax={kqlSyntax} setKqlSyntax={setKqlSyntax} />
);

// open popover to change
fireEvent.click(getByTestId('syntaxChangeToKql'));

// change syntax
fireEvent.click(getByTestId('toggleKqlSyntax'));

expect(setKqlSyntax).toHaveBeenCalledWith(true);
expect(setKqlSyntax).toHaveBeenCalledTimes(1);
});

it('can change from kql to simple;', () => {
let kqlSyntax = false;
const setKqlSyntax = jest.fn((val: boolean) => {
kqlSyntax = val;
});

const { getByTestId } = render(
<SearchType kqlSyntax={kqlSyntax} setKqlSyntax={setKqlSyntax} />
);

fireEvent.click(getByTestId('syntaxChangeToKql'));

fireEvent.click(getByTestId('toggleKqlSyntax'));

expect(setKqlSyntax).toHaveBeenCalledWith(true);
expect(setKqlSyntax).toHaveBeenCalledTimes(1);
});

it('clears the query on change to kql', () => {
const setKqlSyntax = jest.fn();

const { history } = render(<SearchType kqlSyntax={true} setKqlSyntax={setKqlSyntax} />, {
url: '/app/uptime?query=test',
});

expect(history?.location.search).toBe('');
});

it('clears the search param on change to simple syntax', () => {
const setKqlSyntax = jest.fn();

const { history } = render(<SearchType kqlSyntax={false} setKqlSyntax={setKqlSyntax} />, {
url: '/app/uptime?search=test',
});

expect(history?.location.search).toBe('');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React, { useEffect, useState } from 'react';
import {
EuiPopover,
EuiFormRow,
EuiSwitch,
EuiButtonEmpty,
EuiPopoverTitle,
EuiText,
EuiSpacer,
EuiLink,
EuiButtonIcon,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { useKibana } from '../../../../../../../../../src/plugins/kibana_react/public';
import { euiStyled } from '../../../../../../../../../src/plugins/kibana_react/common';
import { useUrlParams } from '../../../../../hooks';
import {
CHANGE_SEARCH_BAR_SYNTAX,
CHANGE_SEARCH_BAR_SYNTAX_SIMPLE,
SYNTAX_OPTIONS_LABEL,
} from '../translations';

const BoxesVerticalIcon = euiStyled(EuiButtonIcon)`
padding: 10px 8px 0 8px;
border-radius: 0;
height: 38px;
width: 32px;
background-color: ${(props) => props.theme.eui.euiColorLightestShade};
padding-top: 8px;
padding-bottom: 8px;
cursor: pointer;
`;

interface Props {
kqlSyntax: boolean;
setKqlSyntax: (val: boolean) => void;
}

export const SearchType = ({ kqlSyntax, setKqlSyntax }: Props) => {
const {
services: { docLinks },
} = useKibana();

const [getUrlParams, updateUrlParams] = useUrlParams();

const { query, search } = getUrlParams();

const [isPopoverOpen, setIsPopoverOpen] = useState(false);

const onButtonClick = () => setIsPopoverOpen((prevState) => !prevState);

const closePopover = () => setIsPopoverOpen(false);

useEffect(() => {
if (kqlSyntax && query) {
updateUrlParams({ query: '' });
}

if (!kqlSyntax && search) {
updateUrlParams({ search: '' });
}
}, [kqlSyntax, query, search, updateUrlParams]);

const button = kqlSyntax ? (
<EuiButtonEmpty
data-test-subj="syntaxChangeToSimple"
onClick={onButtonClick}
aria-label={CHANGE_SEARCH_BAR_SYNTAX_SIMPLE}
title={CHANGE_SEARCH_BAR_SYNTAX_SIMPLE}
>
KQL
</EuiButtonEmpty>
) : (
<BoxesVerticalIcon
color="text"
iconType="boxesVertical"
onClick={onButtonClick}
data-test-subj="syntaxChangeToKql"
aria-label={CHANGE_SEARCH_BAR_SYNTAX}
title={CHANGE_SEARCH_BAR_SYNTAX}
/>
);

return (
<EuiPopover
button={button}
isOpen={isPopoverOpen}
closePopover={closePopover}
ownFocus={true}
anchorPosition="downRight"
>
<div style={{ width: '360px' }}>
<EuiPopoverTitle>{SYNTAX_OPTIONS_LABEL}</EuiPopoverTitle>
<EuiText>
<p>
<KqlDescription href={docLinks!.links.query.kueryQuerySyntax} />
</p>
</EuiText>
<EuiSpacer />
<EuiFormRow label={KIBANA_QUERY_LANGUAGE} hasChildLabel={false}>
<EuiSwitch
name="switch"
label={kqlSyntax ? 'On' : 'Off'}
checked={kqlSyntax}
onChange={() => setKqlSyntax(!kqlSyntax)}
data-test-subj="toggleKqlSyntax"
/>
</EuiFormRow>
</div>
</EuiPopover>
);
};

const KqlDescription = ({ href }: { href: string }) => {
return (
<FormattedMessage
id="xpack.uptime.queryBar.syntaxOptionsDescription"
defaultMessage="The {docsLink} (KQL) offers a simplified query
syntax and support for scripted fields. KQL also provides autocomplete if you have
a Basic license or above. If you turn off KQL, Uptime
uses simple wildcard search against {searchField} fields."
values={{
docsLink: (
<EuiLink href={href} target="_blank" external>
{KIBANA_QUERY_LANGUAGE}
</EuiLink>
),
searchField: <strong>Monitor Name, ID, Url</strong>,
}}
/>
);
};

const KIBANA_QUERY_LANGUAGE = i18n.translate('xpack.uptime.query.queryBar.kqlFullLanguageName', {
defaultMessage: 'Kibana Query Language',
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { i18n } from '@kbn/i18n';

export const KQL_PLACE_HOLDER = i18n.translate('xpack.uptime.kueryBar.searchPlaceholder.kql', {
defaultMessage:
'Search using kql syntax for monitor IDs, names and type etc (E.g monitor.type: "http" AND tags: "dev")',
});

export const SIMPLE_SEARCH_PLACEHOLDER = i18n.translate(
'xpack.uptime.kueryBar.searchPlaceholder.simple',
{
defaultMessage: 'Search by monitor ID, name, or url (E.g. http:// )',
}
);

export const CHANGE_SEARCH_BAR_SYNTAX = i18n.translate(
'xpack.uptime.kueryBar.options.syntax.changeLabel',
{
defaultMessage: 'Change search bar syntax to use Kibana Query Language',
}
);

export const CHANGE_SEARCH_BAR_SYNTAX_SIMPLE = i18n.translate(
'xpack.uptime.kueryBar.options.syntax.simple',
{
defaultMessage: 'Change search bar syntax to not use Kibana Query Language',
}
);

export const SYNTAX_OPTIONS_LABEL = i18n.translate('xpack.uptime.kueryBar.options.syntax', {
defaultMessage: 'SYNTAX OPTIONS',
});
Original file line number Diff line number Diff line change
@@ -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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React from 'react';
import { fireEvent } from '@testing-library/react';
import { Typeahead } from './typehead';
import { render } from '../../../../lib/helper/rtl_helpers';

describe('Type head', () => {
jest.useFakeTimers();

it('it sets initial value', () => {
const { getByTestId, getByDisplayValue, history } = render(
<Typeahead
ariaLabel={'Search for data'}
dataTestSubj={'kueryBar'}
disabled={false}
isLoading={false}
initialValue={'elastic'}
onChange={jest.fn()}
onSubmit={() => {}}
suggestions={[]}
loadMore={() => {}}
queryExample=""
/>
);

const input = getByTestId('uptimeKueryBarInput');

expect(input).toBeInTheDocument();
expect(getByDisplayValue('elastic')).toBeInTheDocument();

fireEvent.change(input, { target: { value: 'kibana' } });

// to check if it updateds the query params, needed for debounce wait
jest.advanceTimersByTime(250);

expect(history.location.search).toBe('?query=kibana');
});
});
Loading

0 comments on commit 679a697

Please sign in to comment.