mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Uptime] Migrate uptime query bar to global kuery bar (#93889)
This commit is contained in:
parent
d815403953
commit
6f7a7e4755
80 changed files with 566 additions and 1996 deletions
|
@ -415,6 +415,34 @@
|
|||
"signature": [
|
||||
"string | undefined"
|
||||
]
|
||||
},
|
||||
{
|
||||
"tags": [],
|
||||
"id": "def-public.QueryStringInputProps.autoSubmit",
|
||||
"type": "CompoundType",
|
||||
"label": "autoSubmit",
|
||||
"description": [],
|
||||
"source": {
|
||||
"path": "src/plugins/data/public/ui/query_string_input/query_string_input.tsx",
|
||||
"lineNumber": 72
|
||||
},
|
||||
"signature": [
|
||||
"boolean | undefined"
|
||||
]
|
||||
},
|
||||
{
|
||||
"tags": [],
|
||||
"id": "def-public.QueryStringInputProps.storageKey",
|
||||
"type": "string",
|
||||
"label": "storageKey",
|
||||
"description": [],
|
||||
"source": {
|
||||
"path": "src/plugins/data/public/ui/query_string_input/query_string_input.tsx",
|
||||
"lineNumber": 76
|
||||
},
|
||||
"signature": [
|
||||
"string | undefined"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": {
|
||||
|
@ -460,7 +488,7 @@
|
|||
"section": "def-public.SearchBarProps",
|
||||
"text": "SearchBarProps"
|
||||
},
|
||||
", \"filters\" | \"query\" | \"intl\" | \"indexPatterns\" | \"isLoading\" | \"customSubmitButton\" | \"screenTitle\" | \"dataTestSubj\" | \"showQueryBar\" | \"showQueryInput\" | \"showFilterBar\" | \"showDatePicker\" | \"showAutoRefreshOnly\" | \"isRefreshPaused\" | \"refreshInterval\" | \"dateRangeFrom\" | \"dateRangeTo\" | \"showSaveQuery\" | \"savedQuery\" | \"onQueryChange\" | \"onQuerySubmit\" | \"onSaved\" | \"onSavedQueryUpdated\" | \"onClearSavedQuery\" | \"onRefresh\" | \"indicateNoData\" | \"placeholder\" | \"isClearable\" | \"iconType\" | \"nonKqlMode\" | \"nonKqlModeHelpText\" | \"timeHistory\" | \"onFiltersUpdated\" | \"onRefreshChange\">, \"filters\" | \"query\" | \"indexPatterns\" | \"isLoading\" | \"customSubmitButton\" | \"screenTitle\" | \"dataTestSubj\" | \"showQueryBar\" | \"showQueryInput\" | \"showFilterBar\" | \"showDatePicker\" | \"showAutoRefreshOnly\" | \"isRefreshPaused\" | \"refreshInterval\" | \"dateRangeFrom\" | \"dateRangeTo\" | \"showSaveQuery\" | \"savedQuery\" | \"onQueryChange\" | \"onQuerySubmit\" | \"onSaved\" | \"onSavedQueryUpdated\" | \"onClearSavedQuery\" | \"onRefresh\" | \"indicateNoData\" | \"placeholder\" | \"isClearable\" | \"iconType\" | \"nonKqlMode\" | \"nonKqlModeHelpText\" | \"timeHistory\" | \"onFiltersUpdated\" | \"onRefreshChange\">, any> & { WrappedComponent: React.ComponentType<Pick<",
|
||||
", \"filters\" | \"query\" | \"intl\" | \"indexPatterns\" | \"isLoading\" | \"customSubmitButton\" | \"screenTitle\" | \"dataTestSubj\" | \"showQueryBar\" | \"showQueryInput\" | \"showFilterBar\" | \"showDatePicker\" | \"showAutoRefreshOnly\" | \"isRefreshPaused\" | \"refreshInterval\" | \"dateRangeFrom\" | \"dateRangeTo\" | \"showSaveQuery\" | \"savedQuery\" | \"onQueryChange\" | \"onQuerySubmit\" | \"onSaved\" | \"onSavedQueryUpdated\" | \"onClearSavedQuery\" | \"onRefresh\" | \"indicateNoData\" | \"placeholder\" | \"isClearable\" | \"iconType\" | \"nonKqlMode\" | \"nonKqlModeHelpText\" | \"storageKey\" | \"disableLanguageSwitcher\" | \"isInvalid\" | \"autoSubmit\" | \"timeHistory\" | \"onFiltersUpdated\" | \"onRefreshChange\">, \"filters\" | \"query\" | \"indexPatterns\" | \"isLoading\" | \"customSubmitButton\" | \"screenTitle\" | \"dataTestSubj\" | \"showQueryBar\" | \"showQueryInput\" | \"showFilterBar\" | \"showDatePicker\" | \"showAutoRefreshOnly\" | \"isRefreshPaused\" | \"refreshInterval\" | \"dateRangeFrom\" | \"dateRangeTo\" | \"showSaveQuery\" | \"savedQuery\" | \"onQueryChange\" | \"onQuerySubmit\" | \"onSaved\" | \"onSavedQueryUpdated\" | \"onClearSavedQuery\" | \"onRefresh\" | \"indicateNoData\" | \"placeholder\" | \"isClearable\" | \"iconType\" | \"nonKqlMode\" | \"nonKqlModeHelpText\" | \"storageKey\" | \"disableLanguageSwitcher\" | \"isInvalid\" | \"autoSubmit\" | \"timeHistory\" | \"onFiltersUpdated\" | \"onRefreshChange\">, any> & { WrappedComponent: React.ComponentType<Pick<",
|
||||
{
|
||||
"pluginId": "data",
|
||||
"scope": "public",
|
||||
|
@ -468,7 +496,7 @@
|
|||
"section": "def-public.SearchBarProps",
|
||||
"text": "SearchBarProps"
|
||||
},
|
||||
", \"filters\" | \"query\" | \"intl\" | \"indexPatterns\" | \"isLoading\" | \"customSubmitButton\" | \"screenTitle\" | \"dataTestSubj\" | \"showQueryBar\" | \"showQueryInput\" | \"showFilterBar\" | \"showDatePicker\" | \"showAutoRefreshOnly\" | \"isRefreshPaused\" | \"refreshInterval\" | \"dateRangeFrom\" | \"dateRangeTo\" | \"showSaveQuery\" | \"savedQuery\" | \"onQueryChange\" | \"onQuerySubmit\" | \"onSaved\" | \"onSavedQueryUpdated\" | \"onClearSavedQuery\" | \"onRefresh\" | \"indicateNoData\" | \"placeholder\" | \"isClearable\" | \"iconType\" | \"nonKqlMode\" | \"nonKqlModeHelpText\" | \"timeHistory\" | \"onFiltersUpdated\" | \"onRefreshChange\"> & ReactIntl.InjectedIntlProps>; }"
|
||||
", \"filters\" | \"query\" | \"intl\" | \"indexPatterns\" | \"isLoading\" | \"customSubmitButton\" | \"screenTitle\" | \"dataTestSubj\" | \"showQueryBar\" | \"showQueryInput\" | \"showFilterBar\" | \"showDatePicker\" | \"showAutoRefreshOnly\" | \"isRefreshPaused\" | \"refreshInterval\" | \"dateRangeFrom\" | \"dateRangeTo\" | \"showSaveQuery\" | \"savedQuery\" | \"onQueryChange\" | \"onQuerySubmit\" | \"onSaved\" | \"onSavedQueryUpdated\" | \"onClearSavedQuery\" | \"onRefresh\" | \"indicateNoData\" | \"placeholder\" | \"isClearable\" | \"iconType\" | \"nonKqlMode\" | \"nonKqlModeHelpText\" | \"storageKey\" | \"disableLanguageSwitcher\" | \"isInvalid\" | \"autoSubmit\" | \"timeHistory\" | \"onFiltersUpdated\" | \"onRefreshChange\"> & ReactIntl.InjectedIntlProps>; }"
|
||||
],
|
||||
"initialIsOpen": false
|
||||
},
|
||||
|
@ -480,7 +508,7 @@
|
|||
"description": [],
|
||||
"source": {
|
||||
"path": "src/plugins/data/public/ui/search_bar/search_bar.tsx",
|
||||
"lineNumber": 80
|
||||
"lineNumber": 84
|
||||
},
|
||||
"signature": [
|
||||
"SearchBarOwnProps & SearchBarInjectedDeps"
|
||||
|
@ -521,4 +549,4 @@
|
|||
"misc": [],
|
||||
"objects": []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -482,7 +482,7 @@
|
|||
},
|
||||
"signature": [
|
||||
"SearchBarOwnProps",
|
||||
" & { appName: string; useDefaultBehaviors?: boolean | undefined; savedQueryId?: string | undefined; onSavedQueryIdChange?: ((savedQueryId?: string | undefined) => void) | undefined; } & Pick<SearchBarProps, \"filters\" | \"query\" | \"indexPatterns\" | \"isLoading\" | \"customSubmitButton\" | \"screenTitle\" | \"dataTestSubj\" | \"showQueryBar\" | \"showQueryInput\" | \"showFilterBar\" | \"showDatePicker\" | \"showAutoRefreshOnly\" | \"isRefreshPaused\" | \"refreshInterval\" | \"dateRangeFrom\" | \"dateRangeTo\" | \"showSaveQuery\" | \"savedQuery\" | \"onQueryChange\" | \"onQuerySubmit\" | \"onSaved\" | \"onSavedQueryUpdated\" | \"onClearSavedQuery\" | \"onRefresh\" | \"indicateNoData\" | \"placeholder\" | \"isClearable\" | \"iconType\" | \"nonKqlMode\" | \"nonKqlModeHelpText\" | \"onFiltersUpdated\" | \"onRefreshChange\"> & { config?: TopNavMenuData[] | undefined; badges?: (({ iconType?: string | React.ComponentClass<{}, any> | React.FunctionComponent<{}> | undefined; iconSide?: \"left\" | \"right\" | undefined; color?: string | undefined; isDisabled?: boolean | undefined; closeButtonProps?: Partial<",
|
||||
" & { appName: string; useDefaultBehaviors?: boolean | undefined; savedQueryId?: string | undefined; onSavedQueryIdChange?: ((savedQueryId?: string | undefined) => void) | undefined; } & Pick<SearchBarProps, \"filters\" | \"query\" | \"indexPatterns\" | \"isLoading\" | \"customSubmitButton\" | \"screenTitle\" | \"dataTestSubj\" | \"showQueryBar\" | \"showQueryInput\" | \"showFilterBar\" | \"showDatePicker\" | \"showAutoRefreshOnly\" | \"isRefreshPaused\" | \"refreshInterval\" | \"dateRangeFrom\" | \"dateRangeTo\" | \"showSaveQuery\" | \"savedQuery\" | \"onQueryChange\" | \"onQuerySubmit\" | \"onSaved\" | \"onSavedQueryUpdated\" | \"onClearSavedQuery\" | \"onRefresh\" | \"indicateNoData\" | \"placeholder\" | \"isClearable\" | \"iconType\" | \"nonKqlMode\" | \"nonKqlModeHelpText\" | \"storageKey\" | \"disableLanguageSwitcher\" | \"isInvalid\" | \"autoSubmit\" | \"onFiltersUpdated\" | \"onRefreshChange\"> & { config?: TopNavMenuData[] | undefined; badges?: (({ iconType?: string | React.ComponentClass<{}, any> | React.FunctionComponent<{}> | undefined; iconSide?: \"left\" | \"right\" | undefined; color?: string | undefined; isDisabled?: boolean | undefined; closeButtonProps?: Partial<",
|
||||
"EuiIconProps",
|
||||
"> | undefined; } & ",
|
||||
"CommonProps",
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QueryStringInputProps](./kibana-plugin-plugins-data-public.querystringinputprops.md) > [autoSubmit](./kibana-plugin-plugins-data-public.querystringinputprops.autosubmit.md)
|
||||
|
||||
## QueryStringInputProps.autoSubmit property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
autoSubmit?: boolean;
|
||||
```
|
|
@ -14,6 +14,7 @@ export interface QueryStringInputProps
|
|||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [autoSubmit](./kibana-plugin-plugins-data-public.querystringinputprops.autosubmit.md) | <code>boolean</code> | |
|
||||
| [bubbleSubmitEvent](./kibana-plugin-plugins-data-public.querystringinputprops.bubblesubmitevent.md) | <code>boolean</code> | |
|
||||
| [className](./kibana-plugin-plugins-data-public.querystringinputprops.classname.md) | <code>string</code> | |
|
||||
| [dataTestSubj](./kibana-plugin-plugins-data-public.querystringinputprops.datatestsubj.md) | <code>string</code> | |
|
||||
|
@ -36,5 +37,6 @@ export interface QueryStringInputProps
|
|||
| [query](./kibana-plugin-plugins-data-public.querystringinputprops.query.md) | <code>Query</code> | |
|
||||
| [screenTitle](./kibana-plugin-plugins-data-public.querystringinputprops.screentitle.md) | <code>string</code> | |
|
||||
| [size](./kibana-plugin-plugins-data-public.querystringinputprops.size.md) | <code>SuggestionsListSize</code> | |
|
||||
| [storageKey](./kibana-plugin-plugins-data-public.querystringinputprops.storagekey.md) | <code>string</code> | |
|
||||
| [submitOnBlur](./kibana-plugin-plugins-data-public.querystringinputprops.submitonblur.md) | <code>boolean</code> | |
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QueryStringInputProps](./kibana-plugin-plugins-data-public.querystringinputprops.md) > [storageKey](./kibana-plugin-plugins-data-public.querystringinputprops.storagekey.md)
|
||||
|
||||
## QueryStringInputProps.storageKey property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
storageKey?: string;
|
||||
```
|
|
@ -7,7 +7,7 @@
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
SearchBar: React.ComponentClass<Pick<Pick<SearchBarProps, "query" | "placeholder" | "isLoading" | "iconType" | "indexPatterns" | "filters" | "dataTestSubj" | "isClearable" | "intl" | "refreshInterval" | "nonKqlMode" | "nonKqlModeHelpText" | "screenTitle" | "onRefresh" | "onRefreshChange" | "showQueryInput" | "showDatePicker" | "showAutoRefreshOnly" | "dateRangeFrom" | "dateRangeTo" | "isRefreshPaused" | "customSubmitButton" | "timeHistory" | "indicateNoData" | "onFiltersUpdated" | "savedQuery" | "showSaveQuery" | "onClearSavedQuery" | "showQueryBar" | "showFilterBar" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated">, "query" | "placeholder" | "isLoading" | "iconType" | "indexPatterns" | "filters" | "dataTestSubj" | "isClearable" | "refreshInterval" | "nonKqlMode" | "nonKqlModeHelpText" | "screenTitle" | "onRefresh" | "onRefreshChange" | "showQueryInput" | "showDatePicker" | "showAutoRefreshOnly" | "dateRangeFrom" | "dateRangeTo" | "isRefreshPaused" | "customSubmitButton" | "timeHistory" | "indicateNoData" | "onFiltersUpdated" | "savedQuery" | "showSaveQuery" | "onClearSavedQuery" | "showQueryBar" | "showFilterBar" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated">, any> & {
|
||||
WrappedComponent: React.ComponentType<Pick<SearchBarProps, "query" | "placeholder" | "isLoading" | "iconType" | "indexPatterns" | "filters" | "dataTestSubj" | "isClearable" | "intl" | "refreshInterval" | "nonKqlMode" | "nonKqlModeHelpText" | "screenTitle" | "onRefresh" | "onRefreshChange" | "showQueryInput" | "showDatePicker" | "showAutoRefreshOnly" | "dateRangeFrom" | "dateRangeTo" | "isRefreshPaused" | "customSubmitButton" | "timeHistory" | "indicateNoData" | "onFiltersUpdated" | "savedQuery" | "showSaveQuery" | "onClearSavedQuery" | "showQueryBar" | "showFilterBar" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated"> & ReactIntl.InjectedIntlProps>;
|
||||
SearchBar: React.ComponentClass<Pick<Pick<SearchBarProps, "query" | "placeholder" | "isLoading" | "iconType" | "indexPatterns" | "filters" | "dataTestSubj" | "isClearable" | "isInvalid" | "storageKey" | "intl" | "refreshInterval" | "nonKqlMode" | "nonKqlModeHelpText" | "screenTitle" | "disableLanguageSwitcher" | "autoSubmit" | "onRefresh" | "onRefreshChange" | "showQueryInput" | "showDatePicker" | "showAutoRefreshOnly" | "dateRangeFrom" | "dateRangeTo" | "isRefreshPaused" | "customSubmitButton" | "timeHistory" | "indicateNoData" | "onFiltersUpdated" | "savedQuery" | "showSaveQuery" | "onClearSavedQuery" | "showQueryBar" | "showFilterBar" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated">, "query" | "placeholder" | "isLoading" | "iconType" | "indexPatterns" | "filters" | "dataTestSubj" | "isClearable" | "isInvalid" | "storageKey" | "refreshInterval" | "nonKqlMode" | "nonKqlModeHelpText" | "screenTitle" | "disableLanguageSwitcher" | "autoSubmit" | "onRefresh" | "onRefreshChange" | "showQueryInput" | "showDatePicker" | "showAutoRefreshOnly" | "dateRangeFrom" | "dateRangeTo" | "isRefreshPaused" | "customSubmitButton" | "timeHistory" | "indicateNoData" | "onFiltersUpdated" | "savedQuery" | "showSaveQuery" | "onClearSavedQuery" | "showQueryBar" | "showFilterBar" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated">, any> & {
|
||||
WrappedComponent: React.ComponentType<Pick<SearchBarProps, "query" | "placeholder" | "isLoading" | "iconType" | "indexPatterns" | "filters" | "dataTestSubj" | "isClearable" | "isInvalid" | "storageKey" | "intl" | "refreshInterval" | "nonKqlMode" | "nonKqlModeHelpText" | "screenTitle" | "disableLanguageSwitcher" | "autoSubmit" | "onRefresh" | "onRefreshChange" | "showQueryInput" | "showDatePicker" | "showAutoRefreshOnly" | "dateRangeFrom" | "dateRangeTo" | "isRefreshPaused" | "customSubmitButton" | "timeHistory" | "indicateNoData" | "onFiltersUpdated" | "savedQuery" | "showSaveQuery" | "onClearSavedQuery" | "showQueryBar" | "showFilterBar" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated"> & ReactIntl.InjectedIntlProps>;
|
||||
}
|
||||
```
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
*/
|
||||
|
||||
export const DEFAULT_QUERY_LANGUAGE = 'kuery';
|
||||
export const KIBANA_USER_QUERY_LANGUAGE_KEY = 'kibana.userQueryLanguage';
|
||||
|
||||
export const UI_SETTINGS = {
|
||||
META_FIELDS: 'metaFields',
|
||||
|
|
|
@ -2022,6 +2022,8 @@ export const QueryStringInput: (props: QueryStringInputProps) => JSX.Element;
|
|||
//
|
||||
// @public (undocumented)
|
||||
export interface QueryStringInputProps {
|
||||
// (undocumented)
|
||||
autoSubmit?: boolean;
|
||||
// (undocumented)
|
||||
bubbleSubmitEvent?: boolean;
|
||||
// (undocumented)
|
||||
|
@ -2071,6 +2073,8 @@ export interface QueryStringInputProps {
|
|||
// (undocumented)
|
||||
size?: SuggestionsListSize;
|
||||
// (undocumented)
|
||||
storageKey?: string;
|
||||
// (undocumented)
|
||||
submitOnBlur?: boolean;
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import { skip } from 'rxjs/operators';
|
|||
import { PublicMethodsOf } from '@kbn/utility-types';
|
||||
import { CoreStart } from 'kibana/public';
|
||||
import { IStorageWrapper } from 'src/plugins/kibana_utils/public';
|
||||
import { Query, UI_SETTINGS } from '../../../common';
|
||||
import { KIBANA_USER_QUERY_LANGUAGE_KEY, Query, UI_SETTINGS } from '../../../common';
|
||||
|
||||
export class QueryStringManager {
|
||||
private query$: BehaviorSubject<Query>;
|
||||
|
@ -25,7 +25,7 @@ export class QueryStringManager {
|
|||
|
||||
private getDefaultLanguage() {
|
||||
return (
|
||||
this.storage.get('kibana.userQueryLanguage') ||
|
||||
this.storage.get(KIBANA_USER_QUERY_LANGUAGE_KEY) ||
|
||||
this.uiSettings.get(UI_SETTINGS.SEARCH_QUERY_LANGUAGE)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ import { QueryLanguageSwitcher } from './language_switcher';
|
|||
import { PersistedLog, getQueryLog, matchPairs, toUser, fromUser } from '../../query';
|
||||
import { SuggestionsListSize } from '../typeahead/suggestions_component';
|
||||
import { SuggestionsComponent } from '..';
|
||||
import { KIBANA_USER_QUERY_LANGUAGE_KEY } from '../../../common';
|
||||
|
||||
export interface QueryStringInputProps {
|
||||
indexPatterns: Array<IIndexPattern | string>;
|
||||
|
@ -67,6 +68,14 @@ export interface QueryStringInputProps {
|
|||
*/
|
||||
nonKqlMode?: 'lucene' | 'text';
|
||||
nonKqlModeHelpText?: string;
|
||||
/**
|
||||
* @param autoSubmit if user selects a value, in that case kuery will be auto submitted
|
||||
*/
|
||||
autoSubmit?: boolean;
|
||||
/**
|
||||
* @param storageKey this key is used to use user preference between kql and non-kql mode
|
||||
*/
|
||||
storageKey?: string;
|
||||
}
|
||||
|
||||
interface Props extends QueryStringInputProps {
|
||||
|
@ -99,6 +108,10 @@ const KEY_CODES = {
|
|||
// Needed for React.lazy
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default class QueryStringInputUI extends Component<Props, State> {
|
||||
static defaultProps = {
|
||||
storageKey: KIBANA_USER_QUERY_LANGUAGE_KEY,
|
||||
};
|
||||
|
||||
public state: State = {
|
||||
isSuggestionsVisible: false,
|
||||
index: null,
|
||||
|
@ -218,7 +231,7 @@ export default class QueryStringInputUI extends Component<Props, State> {
|
|||
const recentSearches = this.persistedLog.get();
|
||||
const matchingRecentSearches = recentSearches.filter((recentQuery) => {
|
||||
const recentQueryString = typeof recentQuery === 'object' ? toUser(recentQuery) : recentQuery;
|
||||
return recentQueryString.includes(query);
|
||||
return recentQueryString !== '' && recentQueryString.includes(query);
|
||||
});
|
||||
return matchingRecentSearches.map((recentSearch) => {
|
||||
const text = toUser(recentSearch);
|
||||
|
@ -393,8 +406,13 @@ export default class QueryStringInputUI extends Component<Props, State> {
|
|||
selectionStart: start + (cursorIndex ? cursorIndex : text.length),
|
||||
selectionEnd: start + (cursorIndex ? cursorIndex : text.length),
|
||||
});
|
||||
const isTypeRecentSearch = type === QuerySuggestionTypes.RecentSearch;
|
||||
|
||||
if (type === QuerySuggestionTypes.RecentSearch) {
|
||||
const isAutoSubmitAndValid =
|
||||
this.props.autoSubmit &&
|
||||
(type === QuerySuggestionTypes.Value || [':*', ': *'].includes(value.trim()));
|
||||
|
||||
if (isTypeRecentSearch || isAutoSubmitAndValid) {
|
||||
this.setState({ isSuggestionsVisible: false, index: null });
|
||||
this.onSubmit({ query: newQueryString, language: this.props.query.language });
|
||||
}
|
||||
|
@ -488,12 +506,16 @@ export default class QueryStringInputUI extends Component<Props, State> {
|
|||
body: JSON.stringify({ opt_in: language === 'kuery' }),
|
||||
});
|
||||
|
||||
this.services.storage.set('kibana.userQueryLanguage', language);
|
||||
const storageKey = this.props.storageKey;
|
||||
this.services.storage.set(storageKey!, language);
|
||||
|
||||
const newQuery = { query: '', language };
|
||||
this.onChange(newQuery);
|
||||
this.onSubmit(newQuery);
|
||||
this.reportUiCounter?.(METRIC_TYPE.LOADED, `query_string:language:${language}`);
|
||||
this.reportUiCounter?.(
|
||||
METRIC_TYPE.LOADED,
|
||||
storageKey ? `${storageKey}:language:${language}` : `query_string:language:${language}`
|
||||
);
|
||||
};
|
||||
|
||||
private onOutsideClick = () => {
|
||||
|
@ -756,6 +778,9 @@ export default class QueryStringInputUI extends Component<Props, State> {
|
|||
})}
|
||||
onClick={() => {
|
||||
this.onQueryStringChange('');
|
||||
if (this.props.autoSubmit) {
|
||||
this.onSubmit({ query: '', language: this.props.query.language });
|
||||
}
|
||||
}}
|
||||
>
|
||||
<EuiIcon className="euiFormControlLayoutClearButton__icon" type="cross" />
|
||||
|
|
|
@ -22933,7 +22933,6 @@
|
|||
"xpack.uptime.filterPopout.searchMessage.ariaLabel": "{title} を検索",
|
||||
"xpack.uptime.filterPopover.filterItem.label": "{title} {item}でフィルタリングします。",
|
||||
"xpack.uptime.integrationLink.missingDataMessage": "この統合に必要なデータが見つかりませんでした。",
|
||||
"xpack.uptime.kueryBar.indexPatternMissingWarningMessage": "インデックスパターンの取得中にエラーが発生しました。",
|
||||
"xpack.uptime.locationAvailabilityViewToggleLegend": "トグルを表示",
|
||||
"xpack.uptime.locationMap.locations.missing.message": "重要な位置情報構成がありません。{codeBlock}フィールドを使用して、アップタイムチェック用に一意の地域を作成できます。",
|
||||
"xpack.uptime.locationMap.locations.missing.message1": "詳細については、ドキュメンテーションを参照してください。",
|
||||
|
@ -23073,9 +23072,6 @@
|
|||
"xpack.uptime.overviewPageLink.disabled.ariaLabel": "無効になったページ付けボタンです。モニターリストがこれ以上ナビゲーションできないことを示しています。",
|
||||
"xpack.uptime.overviewPageLink.next.ariaLabel": "次の結果ページ",
|
||||
"xpack.uptime.overviewPageLink.prev.ariaLabel": "前の結果ページ",
|
||||
"xpack.uptime.overviewPageParsingErrorCallout.content": "フィルタークエリの解析中にエラーが発生しました。{content}",
|
||||
"xpack.uptime.overviewPageParsingErrorCallout.noMessage": "エラーメッセージはありませんでした",
|
||||
"xpack.uptime.overviewPageParsingErrorCallout.title": "エラーを解析中",
|
||||
"xpack.uptime.page_header.settingsLink": "設定",
|
||||
"xpack.uptime.pingist.durationSecondsColumnFormatting": "{seconds}秒",
|
||||
"xpack.uptime.pingist.durationSecondsColumnFormatting.singular": "{seconds}秒",
|
||||
|
|
|
@ -23292,7 +23292,6 @@
|
|||
"xpack.uptime.filterPopout.searchMessage.ariaLabel": "搜索 {title}",
|
||||
"xpack.uptime.filterPopover.filterItem.label": "按 {title} {item} 筛选。",
|
||||
"xpack.uptime.integrationLink.missingDataMessage": "未找到此集成的所需数据。",
|
||||
"xpack.uptime.kueryBar.indexPatternMissingWarningMessage": "检索索引模式时出错。",
|
||||
"xpack.uptime.locationAvailabilityViewToggleLegend": "视图切换",
|
||||
"xpack.uptime.locationMap.locations.missing.message": "重要的地理位置配置缺失。您可以使用 {codeBlock} 字段为您的运行时间检查创建独特的地理区域。",
|
||||
"xpack.uptime.locationMap.locations.missing.message1": "在我们的文档中获取更多的信息。",
|
||||
|
@ -23432,9 +23431,6 @@
|
|||
"xpack.uptime.overviewPageLink.disabled.ariaLabel": "禁用的分页按钮表示在监测列表中无法进行进一步导航。",
|
||||
"xpack.uptime.overviewPageLink.next.ariaLabel": "下页结果",
|
||||
"xpack.uptime.overviewPageLink.prev.ariaLabel": "上页结果",
|
||||
"xpack.uptime.overviewPageParsingErrorCallout.content": "解析筛选查询时出错。{content}",
|
||||
"xpack.uptime.overviewPageParsingErrorCallout.noMessage": "没有错误消息",
|
||||
"xpack.uptime.overviewPageParsingErrorCallout.title": "解析错误",
|
||||
"xpack.uptime.page_header.settingsLink": "设置",
|
||||
"xpack.uptime.pingist.durationSecondsColumnFormatting": "{seconds} 秒",
|
||||
"xpack.uptime.pingist.durationSecondsColumnFormatting.singular": "{seconds} 秒",
|
||||
|
|
|
@ -14,12 +14,5 @@
|
|||
"server": true,
|
||||
"ui": true,
|
||||
"version": "8.0.0",
|
||||
"requiredBundles": [
|
||||
"observability",
|
||||
"kibanaReact",
|
||||
"home",
|
||||
"data",
|
||||
"ml",
|
||||
"maps"
|
||||
]
|
||||
"requiredBundles": ["observability", "kibanaReact", "kibanaUtils", "home", "data", "ml", "maps"]
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ import {
|
|||
import { alertTypeInitializers } from '../lib/alert_types';
|
||||
import { FetchDataParams, ObservabilityPublicSetup } from '../../../observability/public';
|
||||
import { PLUGIN } from '../../common/constants/plugin';
|
||||
import { IStorageWrapper } from '../../../../../src/plugins/kibana_utils/public';
|
||||
|
||||
export interface ClientPluginsSetup {
|
||||
data: DataPublicPluginSetup;
|
||||
|
@ -43,6 +44,13 @@ export interface ClientPluginsStart {
|
|||
triggersActionsUi: TriggersAndActionsUIPublicPluginStart;
|
||||
}
|
||||
|
||||
export interface UptimePluginServices extends Partial<CoreStart> {
|
||||
embeddable: EmbeddableStart;
|
||||
data: DataPublicPluginStart;
|
||||
triggersActionsUi: TriggersAndActionsUIPublicPluginStart;
|
||||
storage: IStorageWrapper;
|
||||
}
|
||||
|
||||
export type ClientSetup = void;
|
||||
export type ClientStart = void;
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ import { store } from '../state';
|
|||
import { kibanaService } from '../state/kibana_service';
|
||||
import { ActionMenu } from '../components/common/header/action_menu';
|
||||
import { EuiThemeProvider } from '../../../../../src/plugins/kibana_react/common';
|
||||
import { Storage } from '../../../../../src/plugins/kibana_utils/public';
|
||||
|
||||
export interface UptimeAppColors {
|
||||
danger: string;
|
||||
|
@ -96,12 +97,20 @@ const Application = (props: UptimeAppProps) => {
|
|||
|
||||
store.dispatch(setBasePath(basePath));
|
||||
|
||||
const storage = new Storage(window.localStorage);
|
||||
|
||||
return (
|
||||
<EuiErrorBoundary>
|
||||
<i18nCore.Context>
|
||||
<ReduxProvider store={store}>
|
||||
<KibanaContextProvider
|
||||
services={{ ...core, ...plugins, triggersActionsUi: startPlugins.triggersActionsUi }}
|
||||
services={{
|
||||
...core,
|
||||
...plugins,
|
||||
storage,
|
||||
data: startPlugins.data,
|
||||
triggersActionsUi: startPlugins.triggersActionsUi,
|
||||
}}
|
||||
>
|
||||
<Router history={appMountParameters.history}>
|
||||
<EuiThemeProvider darkMode={darkMode}>
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`OverviewPageParsingErrorCallout renders without errors when a valid error is provided 1`] = `
|
||||
<EuiCallOut
|
||||
color="danger"
|
||||
iconType="alert"
|
||||
style={
|
||||
Object {
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
title="Parsing error"
|
||||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
defaultMessage="There was an error parsing the filter query. {content}"
|
||||
id="xpack.uptime.overviewPageParsingErrorCallout.content"
|
||||
values={
|
||||
Object {
|
||||
"content": <EuiCodeBlock>
|
||||
Unable to convert to Elasticsearch query, invalid syntax.
|
||||
</EuiCodeBlock>,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
`;
|
||||
|
||||
exports[`OverviewPageParsingErrorCallout renders without errors when an error with no message is provided 1`] = `
|
||||
<EuiCallOut
|
||||
color="danger"
|
||||
iconType="alert"
|
||||
style={
|
||||
Object {
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
title="Parsing error"
|
||||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
defaultMessage="There was an error parsing the filter query. {content}"
|
||||
id="xpack.uptime.overviewPageParsingErrorCallout.content"
|
||||
values={
|
||||
Object {
|
||||
"content": <EuiCodeBlock>
|
||||
There was no error message
|
||||
</EuiCodeBlock>,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
`;
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* 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 { EuiFlexItem } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { QueryStringInput } from '../../../../../../../../src/plugins/data/public';
|
||||
import { useIndexPattern } from '../../query_bar/use_index_pattern';
|
||||
import { isValidKuery } from '../../query_bar/query_bar';
|
||||
import * as labels from '../translations';
|
||||
import { useGetUrlParams } from '../../../../hooks';
|
||||
|
||||
interface Props {
|
||||
query: string;
|
||||
onChange: (query: string) => void;
|
||||
}
|
||||
|
||||
export const AlertQueryBar = ({ query, onChange }: Props) => {
|
||||
const { index_pattern: indexPattern } = useIndexPattern();
|
||||
|
||||
const { search } = useGetUrlParams();
|
||||
|
||||
const [inputVal, setInputVal] = useState<string>(search ?? '');
|
||||
|
||||
useEffect(() => {
|
||||
onChange(search);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<EuiFlexItem grow={1} style={{ flexBasis: 485 }}>
|
||||
<QueryStringInput
|
||||
indexPatterns={indexPattern ? [indexPattern] : []}
|
||||
iconType="search"
|
||||
isClearable={true}
|
||||
onChange={(queryN) => {
|
||||
setInputVal(queryN?.query as string);
|
||||
if (isValidKuery(queryN?.query as string)) {
|
||||
// we want to submit when user clears or paste a complete kuery
|
||||
onChange(queryN.query as string);
|
||||
}
|
||||
}}
|
||||
onSubmit={(queryN) => {
|
||||
if (queryN) onChange(queryN.query as string);
|
||||
}}
|
||||
query={{ query: inputVal, language: 'kuery' }}
|
||||
aria-label={labels.ALERT_KUERY_BAR_ARIA}
|
||||
data-test-subj="xpack.uptime.alerts.monitorStatus.filterBar"
|
||||
autoSubmit={true}
|
||||
disableLanguageSwitcher={true}
|
||||
isInvalid={!!(inputVal && !query)}
|
||||
placeholder={i18n.translate('xpack.uptime.alerts.searchPlaceholder.kql', {
|
||||
defaultMessage: 'Filter using kql syntax',
|
||||
})}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
};
|
|
@ -5,30 +5,18 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useMemo, useEffect } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { isRight } from 'fp-ts/lib/Either';
|
||||
import {
|
||||
selectMonitorStatusAlert,
|
||||
overviewFiltersSelector,
|
||||
snapshotDataSelector,
|
||||
esKuerySelector,
|
||||
selectedFiltersSelector,
|
||||
} from '../../../../state/selectors';
|
||||
import { AlertMonitorStatusComponent } from '../index';
|
||||
import {
|
||||
fetchOverviewFilters,
|
||||
setSearchTextAction,
|
||||
setEsKueryString,
|
||||
getSnapshotCountAction,
|
||||
} from '../../../../state/actions';
|
||||
import { overviewFiltersSelector, selectedFiltersSelector } from '../../../../state/selectors';
|
||||
import { AlertMonitorStatusComponent } from '../monitor_status_alert/alert_monitor_status';
|
||||
import { fetchOverviewFilters, setSearchTextAction } from '../../../../state/actions';
|
||||
import {
|
||||
AtomicStatusCheckParamsType,
|
||||
GetMonitorAvailabilityParamsType,
|
||||
} from '../../../../../common/runtime_types';
|
||||
import { useIndexPattern } from '../../kuery_bar/use_index_pattern';
|
||||
import { useUpdateKueryString } from '../../../../hooks';
|
||||
|
||||
import { useSnapShotCount } from './use_snap_shot';
|
||||
|
||||
interface Props {
|
||||
alertParams: { [key: string]: any };
|
||||
|
@ -63,27 +51,17 @@ export const AlertMonitorStatus: React.FC<Props> = ({
|
|||
}, [alertParams, dispatch]);
|
||||
|
||||
const overviewFilters = useSelector(overviewFiltersSelector);
|
||||
const { locations } = useSelector(selectMonitorStatusAlert);
|
||||
|
||||
useEffect(() => {
|
||||
if (alertParams.search) {
|
||||
dispatch(setSearchTextAction(alertParams.search));
|
||||
}
|
||||
}, [alertParams, dispatch]);
|
||||
|
||||
const { index_pattern: indexPattern } = useIndexPattern();
|
||||
|
||||
const { count, loading } = useSelector(snapshotDataSelector);
|
||||
const esKuery = useSelector(esKuerySelector);
|
||||
const [esFilters] = useUpdateKueryString(
|
||||
indexPattern,
|
||||
alertParams.search,
|
||||
alertParams.filters === undefined || typeof alertParams.filters === 'string'
|
||||
? ''
|
||||
: JSON.stringify(Array.from(Object.entries(alertParams.filters)))
|
||||
);
|
||||
useEffect(() => {
|
||||
dispatch(setEsKueryString(esFilters ?? ''));
|
||||
}, [dispatch, esFilters]);
|
||||
const { count, loading } = useSnapShotCount({
|
||||
query: alertParams.search,
|
||||
filters: alertParams.filters,
|
||||
});
|
||||
|
||||
const isOldAlert = React.useMemo(
|
||||
() =>
|
||||
|
@ -92,15 +70,6 @@ export const AlertMonitorStatus: React.FC<Props> = ({
|
|||
!isRight(GetMonitorAvailabilityParamsType.decode(alertParams)),
|
||||
[alertParams]
|
||||
);
|
||||
useEffect(() => {
|
||||
dispatch(
|
||||
getSnapshotCountAction.get({
|
||||
dateRangeStart: 'now-24h',
|
||||
dateRangeEnd: 'now',
|
||||
filters: esKuery,
|
||||
})
|
||||
);
|
||||
}, [dispatch, esKuery]);
|
||||
|
||||
const selectedFilters = useSelector(selectedFiltersSelector);
|
||||
useEffect(() => {
|
||||
|
@ -118,19 +87,14 @@ export const AlertMonitorStatus: React.FC<Props> = ({
|
|||
}
|
||||
}, [alertParams, setAlertParams, selectedFilters]);
|
||||
|
||||
const { pathname } = useLocation();
|
||||
const shouldUpdateUrl = useMemo(() => pathname.indexOf('app/uptime') !== -1, [pathname]);
|
||||
|
||||
return (
|
||||
<AlertMonitorStatusComponent
|
||||
alertParams={alertParams}
|
||||
enabled={enabled}
|
||||
hasFilters={!!overviewFilters?.filters}
|
||||
isOldAlert={isOldAlert}
|
||||
locations={locations || []}
|
||||
numTimes={numTimes}
|
||||
setAlertParams={setAlertParams}
|
||||
shouldUpdateUrl={shouldUpdateUrl}
|
||||
snapshotCount={count.total}
|
||||
snapshotLoading={loading}
|
||||
timerange={timerange}
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export { AlertMonitorStatus } from './alert_monitor_status';
|
||||
export {
|
||||
ToggleAlertFlyoutButton,
|
||||
ToggleAlertFlyoutButtonProps,
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useUpdateKueryString } from '../../../../hooks';
|
||||
import { useIndexPattern } from '../../query_bar/use_index_pattern';
|
||||
import { useFetcher } from '../../../../../../observability/public';
|
||||
import { fetchSnapshotCount } from '../../../../state/api';
|
||||
|
||||
export const useSnapShotCount = ({ query, filters }: { query: string; filters: [] | string }) => {
|
||||
const parsedFilters =
|
||||
filters === undefined || typeof filters === 'string'
|
||||
? ''
|
||||
: JSON.stringify(Array.from(Object.entries(filters)));
|
||||
|
||||
const { index_pattern: indexPattern } = useIndexPattern();
|
||||
|
||||
const [esKuery, error] = useUpdateKueryString(indexPattern, query, parsedFilters);
|
||||
|
||||
const { data, loading } = useFetcher(
|
||||
() =>
|
||||
fetchSnapshotCount({
|
||||
dateRangeStart: 'now-24h',
|
||||
dateRangeEnd: 'now',
|
||||
filters: error ? undefined : esKuery,
|
||||
}),
|
||||
[esKuery, query]
|
||||
);
|
||||
|
||||
return { count: data || { total: 0, up: 0, down: 0 }, loading };
|
||||
};
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export { AlertMonitorStatusComponent } from './alert_monitor_status';
|
||||
export { AlertMonitorStatusComponent } from './monitor_status_alert/alert_monitor_status';
|
||||
export { ToggleAlertFlyoutButtonComponent } from './toggle_alert_flyout_button';
|
||||
export { UptimeAlertsFlyoutWrapperComponent } from './uptime_alerts_flyout_wrapper';
|
||||
export * from './alerts_containers';
|
||||
|
|
|
@ -32,17 +32,10 @@ describe('FiltersExpressionSelect', () => {
|
|||
tags: [],
|
||||
}}
|
||||
setAlertParams={jest.fn()}
|
||||
setUpdatedFieldValues={jest.fn()}
|
||||
shouldUpdateUrl={false}
|
||||
/>
|
||||
);
|
||||
expect(component).toMatchInlineSnapshot(`
|
||||
<Fragment>
|
||||
<EuiSpacer
|
||||
size="xs"
|
||||
/>
|
||||
</Fragment>
|
||||
`);
|
||||
expect(component).toMatchInlineSnapshot(`<Fragment />`);
|
||||
});
|
||||
|
||||
it.each([
|
||||
|
@ -71,7 +64,6 @@ describe('FiltersExpressionSelect', () => {
|
|||
locations: [],
|
||||
}}
|
||||
setAlertParams={jest.fn()}
|
||||
setUpdatedFieldValues={jest.fn()}
|
||||
shouldUpdateUrl={false}
|
||||
/>
|
||||
);
|
||||
|
@ -99,7 +91,6 @@ describe('FiltersExpressionSelect', () => {
|
|||
locations: ['nyc'],
|
||||
}}
|
||||
setAlertParams={setAlertParamsMock}
|
||||
setUpdatedFieldValues={jest.fn()}
|
||||
shouldUpdateUrl={false}
|
||||
/>
|
||||
);
|
||||
|
@ -194,7 +185,6 @@ describe('FiltersExpressionSelect', () => {
|
|||
onRemoveFilter={jest.fn()}
|
||||
filters={filters}
|
||||
setAlertParams={jest.fn()}
|
||||
setUpdatedFieldValues={jest.fn()}
|
||||
shouldUpdateUrl={false}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -13,13 +13,7 @@ import { alertFilterLabels, filterAriaLabels } from './translations';
|
|||
import { FilterExpressionsSelectProps } from './filters_expression_select_container';
|
||||
import { OverviewFiltersState } from '../../../../state/reducers/overview_filters';
|
||||
|
||||
type FilterFieldUpdate = (updateTarget: { fieldName: string; values: string[] }) => void;
|
||||
|
||||
interface OwnProps {
|
||||
setUpdatedFieldValues: FilterFieldUpdate;
|
||||
}
|
||||
|
||||
type Props = FilterExpressionsSelectProps & Pick<OverviewFiltersState, 'filters'> & OwnProps;
|
||||
type Props = FilterExpressionsSelectProps & Pick<OverviewFiltersState, 'filters'>;
|
||||
|
||||
export const FiltersExpressionsSelect: React.FC<Props> = ({
|
||||
alertParams,
|
||||
|
@ -27,13 +21,15 @@ export const FiltersExpressionsSelect: React.FC<Props> = ({
|
|||
newFilters,
|
||||
onRemoveFilter,
|
||||
setAlertParams,
|
||||
setUpdatedFieldValues,
|
||||
}) => {
|
||||
const { tags, ports, schemes, locations } = overviewFilters;
|
||||
const selectedPorts = alertParams?.filters?.['url.port'] ?? [];
|
||||
const selectedLocations = alertParams?.filters?.['observer.geo.name'] ?? [];
|
||||
const selectedSchemes = alertParams?.filters?.['monitor.type'] ?? [];
|
||||
const selectedTags = alertParams?.filters?.tags ?? [];
|
||||
|
||||
const alertFilters = alertParams?.filters;
|
||||
|
||||
const selectedPorts = alertFilters?.['url.port'] ?? [];
|
||||
const selectedLocations = alertFilters?.['observer.geo.name'] ?? [];
|
||||
const selectedSchemes = alertFilters?.['monitor.type'] ?? [];
|
||||
const selectedTags = alertFilters?.tags ?? [];
|
||||
|
||||
const onFilterFieldChange = (fieldName: string, values: string[]) => {
|
||||
// the `filters` field is no longer a string
|
||||
|
@ -54,7 +50,6 @@ export const FiltersExpressionsSelect: React.FC<Props> = ({
|
|||
)
|
||||
);
|
||||
}
|
||||
setUpdatedFieldValues({ fieldName, values });
|
||||
};
|
||||
|
||||
const monitorFilters = [
|
||||
|
@ -162,12 +157,9 @@ export const FiltersExpressionsSelect: React.FC<Props> = ({
|
|||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiSpacer size="xs" />
|
||||
</EuiFlexGroup>
|
||||
))}
|
||||
|
||||
<EuiSpacer size="xs" />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -5,11 +5,10 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { FiltersExpressionsSelect } from './filters_expression_select';
|
||||
import { overviewFiltersSelector } from '../../../../state/selectors';
|
||||
import { useFilterUpdate } from '../../../../hooks/use_filter_update';
|
||||
|
||||
export interface FilterExpressionsSelectProps {
|
||||
alertParams: { [key: string]: any };
|
||||
|
@ -20,20 +19,7 @@ export interface FilterExpressionsSelectProps {
|
|||
}
|
||||
|
||||
export const FiltersExpressionSelectContainer: React.FC<FilterExpressionsSelectProps> = (props) => {
|
||||
const [updatedFieldValues, setUpdatedFieldValues] = useState<{
|
||||
fieldName: string;
|
||||
values: string[];
|
||||
}>({ fieldName: '', values: [] });
|
||||
|
||||
useFilterUpdate(updatedFieldValues.fieldName, updatedFieldValues.values, props.shouldUpdateUrl);
|
||||
|
||||
const overviewFilters = useSelector(overviewFiltersSelector);
|
||||
|
||||
return (
|
||||
<FiltersExpressionsSelect
|
||||
{...overviewFilters}
|
||||
{...props}
|
||||
setUpdatedFieldValues={setUpdatedFieldValues}
|
||||
/>
|
||||
);
|
||||
return <FiltersExpressionsSelect {...overviewFilters} {...props} />;
|
||||
};
|
||||
|
|
|
@ -22,8 +22,10 @@ describe('AddFilterButton component', () => {
|
|||
<EuiButtonEmpty
|
||||
data-test-subj="uptimeCreateAlertAddFilter"
|
||||
disabled={false}
|
||||
flush="left"
|
||||
iconType="plusInCircleFilled"
|
||||
onClick={[Function]}
|
||||
size="s"
|
||||
>
|
||||
Add filter
|
||||
</EuiButtonEmpty>
|
||||
|
@ -86,8 +88,10 @@ describe('AddFilterButton component', () => {
|
|||
<EuiButtonEmpty
|
||||
data-test-subj="uptimeCreateAlertAddFilter"
|
||||
disabled={false}
|
||||
flush="left"
|
||||
iconType="plusInCircleFilled"
|
||||
onClick={[Function]}
|
||||
size="s"
|
||||
>
|
||||
Add filter
|
||||
</EuiButtonEmpty>
|
||||
|
@ -137,8 +141,10 @@ describe('AddFilterButton component', () => {
|
|||
<EuiButtonEmpty
|
||||
data-test-subj="uptimeCreateAlertAddFilter"
|
||||
disabled={true}
|
||||
flush="left"
|
||||
iconType="plusInCircleFilled"
|
||||
onClick={[Function]}
|
||||
size="s"
|
||||
>
|
||||
Add filter
|
||||
</EuiButtonEmpty>
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import React, { useState } from 'react';
|
||||
import { EuiButtonEmpty, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } from '@elastic/eui';
|
||||
import * as labels from './translations';
|
||||
import * as labels from '../translations';
|
||||
|
||||
interface Props {
|
||||
newFilters: string[];
|
||||
|
@ -60,6 +60,8 @@ export const AddFilterButton: React.FC<Props> = ({ newFilters, onNewFilter, aler
|
|||
disabled={items.length === 0}
|
||||
iconType="plusInCircleFilled"
|
||||
onClick={onButtonClick}
|
||||
size="s"
|
||||
flush="left"
|
||||
>
|
||||
{labels.ADD_FILTER}
|
||||
</EuiButtonEmpty>
|
|
@ -21,8 +21,6 @@ describe('alert monitor status component', () => {
|
|||
enabled: true,
|
||||
hasFilters: false,
|
||||
isOldAlert: true,
|
||||
locations: [],
|
||||
shouldUpdateUrl: false,
|
||||
snapshotCount: 0,
|
||||
snapshotLoading: false,
|
||||
numTimes: 14,
|
||||
|
@ -37,15 +35,30 @@ describe('alert monitor status component', () => {
|
|||
<OldAlertCallOut
|
||||
isOldAlert={true}
|
||||
/>
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
<EuiCallOut
|
||||
iconType="iInCircle"
|
||||
size="s"
|
||||
title={
|
||||
<span>
|
||||
<FormattedMessage
|
||||
defaultMessage="This alert will apply to approximately {snapshotCount} monitors."
|
||||
id="xpack.uptime.alerts.monitorStatus.monitorCallOut.title"
|
||||
values={
|
||||
Object {
|
||||
"snapshotCount": 0,
|
||||
}
|
||||
}
|
||||
/>
|
||||
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
<KueryBar
|
||||
aria-label="Input that allows filtering criteria for the monitor status alert"
|
||||
data-test-subj="xpack.uptime.alerts.monitorStatus.filterBar"
|
||||
defaultKuery="monitor.id: foo"
|
||||
shouldUpdateUrl={false}
|
||||
updateDefaultKuery={[Function]}
|
||||
<EuiSpacer
|
||||
size="s"
|
||||
/>
|
||||
<AlertQueryBar
|
||||
onChange={[Function]}
|
||||
query="monitor.id: foo"
|
||||
/>
|
||||
<EuiSpacer
|
||||
size="s"
|
||||
|
@ -94,24 +107,6 @@ describe('alert monitor status component', () => {
|
|||
isOldAlert={true}
|
||||
setAlertParams={[MockFunction]}
|
||||
/>
|
||||
<EuiSpacer
|
||||
size="l"
|
||||
/>
|
||||
<EuiCallOut
|
||||
iconType="iInCircle"
|
||||
size="s"
|
||||
title={
|
||||
<FormattedMessage
|
||||
defaultMessage="This alert will apply to approximately {snapshotCount} monitors."
|
||||
id="xpack.uptime.alerts.monitorStatus.monitorCallOut.title"
|
||||
values={
|
||||
Object {
|
||||
"snapshotCount": 0,
|
||||
}
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
/>
|
|
@ -6,26 +6,23 @@
|
|||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { EuiCallOut, EuiSpacer, EuiHorizontalRule } from '@elastic/eui';
|
||||
import { EuiCallOut, EuiSpacer, EuiHorizontalRule, EuiLoadingSpinner } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import * as labels from './translations';
|
||||
import { FiltersExpressionSelectContainer, StatusExpressionSelect } from './monitor_expressions';
|
||||
import { FiltersExpressionSelectContainer, StatusExpressionSelect } from '../monitor_expressions';
|
||||
import { AddFilterButton } from './add_filter_btn';
|
||||
import { OldAlertCallOut } from './old_alert_call_out';
|
||||
import { AvailabilityExpressionSelect } from './monitor_expressions/availability_expression_select';
|
||||
import { KueryBar } from '..';
|
||||
import { AvailabilityExpressionSelect } from '../monitor_expressions/availability_expression_select';
|
||||
import { AlertQueryBar } from '../alert_query_bar/query_bar';
|
||||
|
||||
export interface AlertMonitorStatusProps {
|
||||
alertParams: { [key: string]: any };
|
||||
enabled: boolean;
|
||||
hasFilters: boolean;
|
||||
isOldAlert: boolean;
|
||||
locations: string[];
|
||||
snapshotCount: number;
|
||||
snapshotLoading: boolean;
|
||||
snapshotLoading?: boolean;
|
||||
numTimes: number;
|
||||
setAlertParams: (key: string, value: any) => void;
|
||||
shouldUpdateUrl: boolean;
|
||||
timerange: {
|
||||
from: string;
|
||||
to: string;
|
||||
|
@ -38,7 +35,6 @@ export const AlertMonitorStatusComponent: React.FC<AlertMonitorStatusProps> = (p
|
|||
hasFilters,
|
||||
isOldAlert,
|
||||
setAlertParams,
|
||||
shouldUpdateUrl,
|
||||
snapshotCount,
|
||||
snapshotLoading,
|
||||
} = props;
|
||||
|
@ -52,14 +48,26 @@ export const AlertMonitorStatusComponent: React.FC<AlertMonitorStatusProps> = (p
|
|||
<>
|
||||
<OldAlertCallOut isOldAlert={isOldAlert} />
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
<EuiCallOut
|
||||
size="s"
|
||||
title={
|
||||
<span>
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.alerts.monitorStatus.monitorCallOut.title"
|
||||
defaultMessage="This alert will apply to approximately {snapshotCount} monitors."
|
||||
values={{ snapshotCount: snapshotLoading ? '...' : snapshotCount }}
|
||||
/>{' '}
|
||||
{snapshotLoading && <EuiLoadingSpinner />}
|
||||
</span>
|
||||
}
|
||||
iconType="iInCircle"
|
||||
/>
|
||||
|
||||
<KueryBar
|
||||
aria-label={labels.ALERT_KUERY_BAR_ARIA}
|
||||
defaultKuery={alertParams.search}
|
||||
shouldUpdateUrl={shouldUpdateUrl}
|
||||
updateDefaultKuery={(value: string) => setAlertParams('search', value)}
|
||||
data-test-subj="xpack.uptime.alerts.monitorStatus.filterBar"
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<AlertQueryBar
|
||||
query={alertParams.search || ''}
|
||||
onChange={(value: string) => setAlertParams('search', value)}
|
||||
/>
|
||||
|
||||
<EuiSpacer size="s" />
|
||||
|
@ -81,7 +89,7 @@ export const AlertMonitorStatusComponent: React.FC<AlertMonitorStatusProps> = (p
|
|||
}
|
||||
}}
|
||||
setAlertParams={setAlertParams}
|
||||
shouldUpdateUrl={shouldUpdateUrl}
|
||||
shouldUpdateUrl={false}
|
||||
/>
|
||||
|
||||
<EuiHorizontalRule />
|
||||
|
@ -100,20 +108,6 @@ export const AlertMonitorStatusComponent: React.FC<AlertMonitorStatusProps> = (p
|
|||
setAlertParams={setAlertParams}
|
||||
/>
|
||||
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
<EuiCallOut
|
||||
size="s"
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.alerts.monitorStatus.monitorCallOut.title"
|
||||
defaultMessage="This alert will apply to approximately {snapshotCount} monitors."
|
||||
values={{ snapshotCount: snapshotLoading ? '...' : snapshotCount }}
|
||||
/>
|
||||
}
|
||||
iconType="iInCircle"
|
||||
/>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
</>
|
||||
);
|
|
@ -10,6 +10,3 @@ export * from './empty_state';
|
|||
export * from './filter_group';
|
||||
export * from './alerts';
|
||||
export * from './snapshot';
|
||||
export * from './kuery_bar';
|
||||
|
||||
export { ParsingErrorCallout } from './parsing_error_callout';
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export { KueryBar } from './kuery_bar';
|
|
@ -1,185 +0,0 @@
|
|||
/*
|
||||
* 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, { useState, useEffect } from 'react';
|
||||
import { EuiCallOut, htmlIdGenerator } from '@elastic/eui';
|
||||
import styled from 'styled-components';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { Typeahead } from './typeahead';
|
||||
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
|
||||
import { useSearchText, useUrlParams } from '../../../hooks';
|
||||
import {
|
||||
esKuery,
|
||||
IIndexPattern,
|
||||
QuerySuggestion,
|
||||
DataPublicPluginStart,
|
||||
} from '../../../../../../../src/plugins/data/public';
|
||||
import { useIndexPattern } from './use_index_pattern';
|
||||
|
||||
const Container = styled.div`
|
||||
margin-bottom: 4px;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
interface State {
|
||||
suggestions: QuerySuggestion[];
|
||||
isLoadingIndexPattern: boolean;
|
||||
}
|
||||
|
||||
function convertKueryToEsQuery(kuery: string, indexPattern: IIndexPattern) {
|
||||
const ast = esKuery.fromKueryExpression(kuery);
|
||||
return esKuery.toElasticsearchQuery(ast, indexPattern);
|
||||
}
|
||||
|
||||
interface Props {
|
||||
'aria-label': string;
|
||||
defaultKuery?: string;
|
||||
'data-test-subj': string;
|
||||
shouldUpdateUrl?: boolean;
|
||||
updateDefaultKuery?: (value: string) => void;
|
||||
}
|
||||
|
||||
export function KueryBar({
|
||||
'aria-label': ariaLabel,
|
||||
defaultKuery,
|
||||
'data-test-subj': dataTestSubj,
|
||||
shouldUpdateUrl,
|
||||
updateDefaultKuery,
|
||||
}: Props) {
|
||||
const { loading, index_pattern: indexPattern } = useIndexPattern();
|
||||
const { updateSearchText } = useSearchText();
|
||||
|
||||
const {
|
||||
services: {
|
||||
data: { autocomplete },
|
||||
},
|
||||
} = useKibana<{ data: DataPublicPluginStart }>();
|
||||
|
||||
const [state, setState] = useState<State>({
|
||||
suggestions: [],
|
||||
isLoadingIndexPattern: true,
|
||||
});
|
||||
const [suggestionLimit, setSuggestionLimit] = useState(15);
|
||||
const [isLoadingSuggestions, setIsLoadingSuggestions] = useState<boolean>(false);
|
||||
let currentRequestCheck: string;
|
||||
|
||||
const [getUrlParams, updateUrlParams] = useUrlParams();
|
||||
const { search: kuery, query } = getUrlParams();
|
||||
|
||||
useEffect(() => {
|
||||
updateSearchText(kuery);
|
||||
}, [kuery, updateSearchText]);
|
||||
|
||||
useEffect(() => {
|
||||
if (updateDefaultKuery && kuery) {
|
||||
updateDefaultKuery(kuery);
|
||||
} else if (defaultKuery && updateDefaultKuery) {
|
||||
updateDefaultKuery(defaultKuery);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const indexPatternMissing = loading && !indexPattern;
|
||||
|
||||
async function onChange(inputValue: string, selectionStart: number | null) {
|
||||
if (!indexPattern) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoadingSuggestions(true);
|
||||
setState({ ...state, suggestions: [] });
|
||||
setSuggestionLimit(15);
|
||||
|
||||
const currentRequest = htmlIdGenerator()();
|
||||
currentRequestCheck = currentRequest;
|
||||
|
||||
try {
|
||||
const suggestions = (
|
||||
(await autocomplete.getQuerySuggestions({
|
||||
language: 'kuery',
|
||||
indexPatterns: [indexPattern],
|
||||
query: inputValue,
|
||||
selectionStart: selectionStart || 0,
|
||||
selectionEnd: selectionStart || 0,
|
||||
useTimeRange: true,
|
||||
})) || []
|
||||
).filter((suggestion: QuerySuggestion) => !suggestion.text.startsWith('span.'));
|
||||
if (currentRequest !== currentRequestCheck) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoadingSuggestions(false);
|
||||
setState({ ...state, suggestions });
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Error while fetching suggestions', e);
|
||||
}
|
||||
}
|
||||
|
||||
function onSubmit(inputValue: string) {
|
||||
if (indexPattern === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = convertKueryToEsQuery(inputValue, indexPattern);
|
||||
if (!res) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (shouldUpdateUrl !== false) {
|
||||
updateUrlParams({ search: inputValue.trim() });
|
||||
}
|
||||
updateSearchText(inputValue);
|
||||
if (updateDefaultKuery) {
|
||||
updateDefaultKuery(inputValue);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('Invalid kuery syntax'); // eslint-disable-line no-console
|
||||
}
|
||||
}
|
||||
|
||||
const increaseLimit = () => {
|
||||
setSuggestionLimit(suggestionLimit + 15);
|
||||
};
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Typeahead
|
||||
ariaLabel={ariaLabel}
|
||||
dataTestSubj={dataTestSubj}
|
||||
disabled={indexPatternMissing}
|
||||
isLoading={isLoadingSuggestions || loading}
|
||||
initialValue={defaultKuery || kuery || query}
|
||||
onChange={onChange}
|
||||
onSubmit={onSubmit}
|
||||
suggestions={state.suggestions.slice(0, suggestionLimit)}
|
||||
loadMore={increaseLimit}
|
||||
queryExample=""
|
||||
/>
|
||||
|
||||
{indexPatternMissing && !loading && (
|
||||
<EuiCallOut
|
||||
style={{ display: 'inline-block', marginTop: '10px' }}
|
||||
title={
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.kueryBar.indexPatternMissingWarningMessage"
|
||||
// TODO: we need to determine the best instruction to provide if the index pattern is missing
|
||||
defaultMessage="There was an error retrieving the index pattern."
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
color="warning"
|
||||
iconType="alert"
|
||||
size="s"
|
||||
/>
|
||||
)}
|
||||
</Container>
|
||||
);
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export { Typeahead } from './typehead';
|
|
@ -1,71 +0,0 @@
|
|||
/*
|
||||
* 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('');
|
||||
});
|
||||
});
|
|
@ -1,144 +0,0 @@
|
|||
/*
|
||||
* 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',
|
||||
});
|
|
@ -1,89 +0,0 @@
|
|||
/*
|
||||
* 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, { useRef, useEffect, RefObject } from 'react';
|
||||
import { EuiSuggestItem } from '@elastic/eui';
|
||||
|
||||
import { QuerySuggestion } from '../../../../../../../../src/plugins/data/public';
|
||||
import { euiStyled } from '../../../../../../../../src/plugins/kibana_react/common';
|
||||
|
||||
const SuggestionItem = euiStyled.div<{ selected: boolean }>`
|
||||
background: ${(props) => (props.selected ? props.theme.eui.euiColorLightestShade : 'initial')};
|
||||
`;
|
||||
|
||||
function getIconColor(type: string) {
|
||||
switch (type) {
|
||||
case 'field':
|
||||
return 'tint5';
|
||||
case 'value':
|
||||
return 'tint0';
|
||||
case 'operator':
|
||||
return 'tint1';
|
||||
case 'conjunction':
|
||||
return 'tint3';
|
||||
case 'recentSearch':
|
||||
return 'tint10';
|
||||
default:
|
||||
return 'tint5';
|
||||
}
|
||||
}
|
||||
|
||||
function getEuiIconType(type: string) {
|
||||
switch (type) {
|
||||
case 'field':
|
||||
return 'kqlField';
|
||||
case 'value':
|
||||
return 'kqlValue';
|
||||
case 'recentSearch':
|
||||
return 'search';
|
||||
case 'conjunction':
|
||||
return 'kqlSelector';
|
||||
case 'operator':
|
||||
return 'kqlOperand';
|
||||
default:
|
||||
throw new Error(`Unknown type ${type}`);
|
||||
}
|
||||
}
|
||||
|
||||
interface SuggestionProps {
|
||||
onClick: (sug: QuerySuggestion) => void;
|
||||
onMouseEnter: () => void;
|
||||
selected: boolean;
|
||||
suggestion: QuerySuggestion;
|
||||
innerRef: (node: any) => void;
|
||||
}
|
||||
|
||||
export const Suggestion: React.FC<SuggestionProps> = ({
|
||||
innerRef,
|
||||
selected,
|
||||
suggestion,
|
||||
onClick,
|
||||
onMouseEnter,
|
||||
}) => {
|
||||
const childNode: RefObject<HTMLDivElement> = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (childNode.current) {
|
||||
innerRef(childNode.current);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [childNode]);
|
||||
|
||||
return (
|
||||
<SuggestionItem ref={childNode} selected={selected}>
|
||||
<EuiSuggestItem
|
||||
type={{ iconType: getEuiIconType(suggestion.type), color: getIconColor(suggestion.type) }}
|
||||
label={suggestion.text}
|
||||
onClick={() => onClick(suggestion)}
|
||||
onMouseEnter={onMouseEnter}
|
||||
// @ts-ignore
|
||||
description={suggestion.description}
|
||||
/>
|
||||
</SuggestionItem>
|
||||
);
|
||||
};
|
|
@ -1,146 +0,0 @@
|
|||
/*
|
||||
* 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, { useRef, useState, useEffect } from 'react';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { rgba } from 'polished';
|
||||
import { Suggestion } from './suggestion';
|
||||
import { QuerySuggestion } from '../../../../../../../../src/plugins/data/public';
|
||||
import { euiStyled } from '../../../../../../../../src/plugins/kibana_react/common';
|
||||
|
||||
export const unit = 16;
|
||||
|
||||
export const units = {
|
||||
unit,
|
||||
eighth: unit / 8,
|
||||
quarter: unit / 4,
|
||||
half: unit / 2,
|
||||
minus: unit * 0.75,
|
||||
plus: unit * 1.5,
|
||||
double: unit * 2,
|
||||
triple: unit * 3,
|
||||
quadruple: unit * 4,
|
||||
};
|
||||
|
||||
export function px(value: number): string {
|
||||
return `${value}px`;
|
||||
}
|
||||
|
||||
const List = euiStyled.ul`
|
||||
width: 100%;
|
||||
border: 1px solid ${(props) => props.theme.eui.euiColorLightShade};
|
||||
border-radius: ${px(units.quarter)};
|
||||
background-color: ${(props) => props.theme.eui.euiColorEmptyShade};
|
||||
z-index: 10;
|
||||
max-height: ${px(unit * 20)};
|
||||
overflow: auto;
|
||||
position: absolute;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
height: ${({ theme }) => theme.eui.euiScrollBar};
|
||||
width: ${({ theme }) => theme.eui.euiScrollBar};
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-clip: content-box;
|
||||
background-color: ${({ theme }) => rgba(theme.eui.euiColorDarkShade, 0.5)};
|
||||
border: ${({ theme }) => theme.eui.euiScrollBarCorner} solid transparent;
|
||||
}
|
||||
&::-webkit-scrollbar-corner,
|
||||
&::-webkit-scrollbar-track {
|
||||
background-color: transparent;
|
||||
}
|
||||
`;
|
||||
|
||||
interface SuggestionsProps {
|
||||
index: number;
|
||||
onClick: (sug: QuerySuggestion) => void;
|
||||
onMouseEnter: (index: number) => void;
|
||||
show?: boolean;
|
||||
suggestions: QuerySuggestion[];
|
||||
loadMore: () => void;
|
||||
}
|
||||
|
||||
export const Suggestions: React.FC<SuggestionsProps> = ({
|
||||
show,
|
||||
index,
|
||||
onClick,
|
||||
suggestions,
|
||||
onMouseEnter,
|
||||
loadMore,
|
||||
}) => {
|
||||
const [childNodes, setChildNodes] = useState<HTMLDivElement[]>([]);
|
||||
|
||||
const parentNode = useRef<HTMLUListElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const scrollIntoView = () => {
|
||||
const parent = parentNode.current;
|
||||
const child = childNodes[index];
|
||||
|
||||
if (index == null || !parent || !child) {
|
||||
return;
|
||||
}
|
||||
|
||||
const scrollTop = Math.max(
|
||||
Math.min(parent.scrollTop, child.offsetTop),
|
||||
child.offsetTop + child.offsetHeight - parent.offsetHeight
|
||||
);
|
||||
|
||||
parent.scrollTop = scrollTop;
|
||||
};
|
||||
scrollIntoView();
|
||||
}, [index, childNodes]);
|
||||
|
||||
if (!show || isEmpty(suggestions)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleScroll = () => {
|
||||
const parent = parentNode.current;
|
||||
|
||||
if (!loadMore || !parent) {
|
||||
return;
|
||||
}
|
||||
|
||||
const position = parent.scrollTop + parent.offsetHeight;
|
||||
const height = parent.scrollHeight;
|
||||
const remaining = height - position;
|
||||
const margin = 50;
|
||||
|
||||
if (!height || !position) {
|
||||
return;
|
||||
}
|
||||
if (remaining <= margin) {
|
||||
loadMore();
|
||||
}
|
||||
};
|
||||
|
||||
const suggestionsNodes = suggestions.map((suggestion, currIndex) => {
|
||||
const key = suggestion + '_' + currIndex;
|
||||
return (
|
||||
<Suggestion
|
||||
innerRef={(node) => {
|
||||
const nodes = childNodes;
|
||||
nodes[currIndex] = node;
|
||||
setChildNodes([...nodes]);
|
||||
}}
|
||||
selected={currIndex === index}
|
||||
suggestion={suggestion}
|
||||
onClick={onClick}
|
||||
onMouseEnter={() => onMouseEnter(currIndex)}
|
||||
key={key}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<List ref={parentNode} onScroll={handleScroll}>
|
||||
{suggestionsNodes}
|
||||
</List>
|
||||
);
|
||||
};
|
|
@ -1,44 +0,0 @@
|
|||
/*
|
||||
* 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');
|
||||
});
|
||||
});
|
|
@ -1,210 +0,0 @@
|
|||
/*
|
||||
* 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, { ChangeEvent, MouseEvent, useState, useRef, useEffect } from 'react';
|
||||
import { EuiFieldSearch, EuiProgress, EuiOutsideClickDetector } from '@elastic/eui';
|
||||
import { Suggestions } from './suggestions';
|
||||
import { QuerySuggestion } from '../../../../../../../../src/plugins/data/public';
|
||||
import { SearchType } from './search_type/search_type';
|
||||
import { useKqlSyntax } from './use_kql_syntax';
|
||||
import { useKeyEvents } from './use_key_events';
|
||||
import { KQL_PLACE_HOLDER, SIMPLE_SEARCH_PLACEHOLDER } from './translations';
|
||||
import { useSimpleQuery } from './use_simple_kuery';
|
||||
|
||||
interface TypeaheadProps {
|
||||
onChange: (inputValue: string, selectionStart: number | null) => void;
|
||||
onSubmit: (inputValue: string) => void;
|
||||
suggestions: QuerySuggestion[];
|
||||
queryExample: string;
|
||||
initialValue?: string;
|
||||
isLoading?: boolean;
|
||||
disabled?: boolean;
|
||||
dataTestSubj: string;
|
||||
ariaLabel: string;
|
||||
loadMore: () => void;
|
||||
}
|
||||
|
||||
export const Typeahead: React.FC<TypeaheadProps> = ({
|
||||
initialValue,
|
||||
suggestions,
|
||||
onChange,
|
||||
onSubmit,
|
||||
dataTestSubj,
|
||||
ariaLabel,
|
||||
disabled,
|
||||
isLoading,
|
||||
loadMore,
|
||||
}) => {
|
||||
const [value, setValue] = useState('');
|
||||
const [index, setIndex] = useState<number | null>(null);
|
||||
const [isSuggestionsVisible, setIsSuggestionsVisible] = useState(false);
|
||||
|
||||
const [selected, setSelected] = useState<QuerySuggestion | null>(null);
|
||||
const [inputIsPristine, setInputIsPristine] = useState(true);
|
||||
const [lastSubmitted, setLastSubmitted] = useState('');
|
||||
|
||||
const { kqlSyntax, setKqlSyntax } = useKqlSyntax({ setValue });
|
||||
|
||||
const inputRef = useRef<HTMLInputElement>();
|
||||
|
||||
const { setQuery } = useSimpleQuery();
|
||||
|
||||
useEffect(() => {
|
||||
if (inputIsPristine && initialValue) {
|
||||
setValue(initialValue);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [initialValue]);
|
||||
|
||||
const selectSuggestion = (suggestion: QuerySuggestion) => {
|
||||
const nextInputValue =
|
||||
value.substr(0, suggestion.start) + suggestion.text + value.substr(suggestion.end);
|
||||
|
||||
setValue(nextInputValue);
|
||||
setSelected(suggestion);
|
||||
setIndex(null);
|
||||
|
||||
onChange(nextInputValue, nextInputValue.length);
|
||||
};
|
||||
|
||||
const { onKeyDown, onKeyUp } = useKeyEvents({
|
||||
index,
|
||||
value,
|
||||
isSuggestionsVisible,
|
||||
setIndex,
|
||||
setIsSuggestionsVisible,
|
||||
suggestions,
|
||||
selectSuggestion,
|
||||
onChange,
|
||||
onSubmit,
|
||||
});
|
||||
|
||||
const onClickOutside = () => {
|
||||
if (isSuggestionsVisible) {
|
||||
setIsSuggestionsVisible(false);
|
||||
onSuggestionSubmit();
|
||||
}
|
||||
};
|
||||
|
||||
const onChangeInputValue = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
const { value: valueN, selectionStart } = event.target;
|
||||
const hasValue = Boolean(valueN.trim());
|
||||
|
||||
setValue(valueN);
|
||||
|
||||
setInputIsPristine(false);
|
||||
setIndex(null);
|
||||
|
||||
if (!kqlSyntax) {
|
||||
setQuery(valueN);
|
||||
return;
|
||||
}
|
||||
|
||||
setIsSuggestionsVisible(hasValue);
|
||||
|
||||
if (!hasValue) {
|
||||
onSubmit(valueN);
|
||||
}
|
||||
onChange(valueN, selectionStart!);
|
||||
};
|
||||
|
||||
const onClickInput = (event: MouseEvent<HTMLInputElement> & ChangeEvent<HTMLInputElement>) => {
|
||||
if (kqlSyntax) {
|
||||
event.stopPropagation();
|
||||
const { selectionStart } = event.target;
|
||||
onChange(value, selectionStart!);
|
||||
}
|
||||
};
|
||||
|
||||
const onFocus = () => {
|
||||
if (kqlSyntax) {
|
||||
setIsSuggestionsVisible(true);
|
||||
}
|
||||
};
|
||||
|
||||
const onClickSuggestion = (suggestion: QuerySuggestion) => {
|
||||
selectSuggestion(suggestion);
|
||||
if (inputRef.current) inputRef.current.focus();
|
||||
};
|
||||
|
||||
const onMouseEnterSuggestion = (indexN: number) => {
|
||||
setIndex(indexN);
|
||||
};
|
||||
|
||||
const onSuggestionSubmit = () => {
|
||||
if (
|
||||
lastSubmitted !== value &&
|
||||
selected &&
|
||||
(selected.type === 'value' || selected.text.trim() === ': *')
|
||||
) {
|
||||
onSubmit(value);
|
||||
|
||||
setLastSubmitted(value);
|
||||
setSelected(null);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiOutsideClickDetector onOutsideClick={onClickOutside}>
|
||||
<span>
|
||||
<div data-test-subj={dataTestSubj} style={{ position: 'relative' }}>
|
||||
<EuiFieldSearch
|
||||
aria-label={ariaLabel}
|
||||
fullWidth
|
||||
style={
|
||||
kqlSyntax
|
||||
? {
|
||||
backgroundImage: 'none',
|
||||
}
|
||||
: {}
|
||||
}
|
||||
placeholder={kqlSyntax ? KQL_PLACE_HOLDER : SIMPLE_SEARCH_PLACEHOLDER}
|
||||
inputRef={(node) => {
|
||||
if (node) {
|
||||
inputRef.current = node;
|
||||
}
|
||||
}}
|
||||
disabled={disabled}
|
||||
value={value}
|
||||
onKeyDown={kqlSyntax ? onKeyDown : undefined}
|
||||
onKeyUp={kqlSyntax ? onKeyUp : undefined}
|
||||
onFocus={onFocus}
|
||||
onChange={onChangeInputValue}
|
||||
onClick={onClickInput}
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
data-test-subj={'uptimeKueryBarInput'}
|
||||
append={<SearchType kqlSyntax={kqlSyntax} setKqlSyntax={setKqlSyntax} />}
|
||||
/>
|
||||
|
||||
{isLoading && (
|
||||
<EuiProgress
|
||||
size="xs"
|
||||
color="accent"
|
||||
position="absolute"
|
||||
style={{
|
||||
bottom: 0,
|
||||
top: 'initial',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{kqlSyntax && (
|
||||
<Suggestions
|
||||
show={isSuggestionsVisible}
|
||||
suggestions={suggestions}
|
||||
index={index!}
|
||||
onClick={onClickSuggestion}
|
||||
onMouseEnter={onMouseEnterSuggestion}
|
||||
loadMore={loadMore}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
</EuiOutsideClickDetector>
|
||||
);
|
||||
};
|
|
@ -1,113 +0,0 @@
|
|||
/*
|
||||
* 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 { ChangeEvent, KeyboardEvent } from 'react';
|
||||
import * as React from 'react';
|
||||
import { QuerySuggestion } from '../../../../../../../../src/plugins/data/public';
|
||||
|
||||
const KEY_CODES = {
|
||||
LEFT: 37,
|
||||
UP: 38,
|
||||
RIGHT: 39,
|
||||
DOWN: 40,
|
||||
ENTER: 13,
|
||||
ESC: 27,
|
||||
TAB: 9,
|
||||
};
|
||||
|
||||
interface Props {
|
||||
value: string;
|
||||
index: number | null;
|
||||
isSuggestionsVisible: boolean;
|
||||
setIndex: React.Dispatch<React.SetStateAction<number | null>>;
|
||||
setIsSuggestionsVisible: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
suggestions: QuerySuggestion[];
|
||||
selectSuggestion: (suggestion: QuerySuggestion) => void;
|
||||
onChange: (inputValue: string, selectionStart: number | null) => void;
|
||||
onSubmit: (inputValue: string) => void;
|
||||
}
|
||||
|
||||
export const useKeyEvents = ({
|
||||
value,
|
||||
index,
|
||||
isSuggestionsVisible,
|
||||
setIndex,
|
||||
setIsSuggestionsVisible,
|
||||
suggestions,
|
||||
selectSuggestion,
|
||||
onChange,
|
||||
onSubmit,
|
||||
}: Props) => {
|
||||
const incrementIndex = (currentIndex: number) => {
|
||||
let nextIndex = currentIndex + 1;
|
||||
if (currentIndex === null || nextIndex >= suggestions.length) {
|
||||
nextIndex = 0;
|
||||
}
|
||||
|
||||
setIndex(nextIndex);
|
||||
};
|
||||
|
||||
const decrementIndex = (currentIndex: number) => {
|
||||
let previousIndex: number | null = currentIndex - 1;
|
||||
if (previousIndex < 0) {
|
||||
previousIndex = null;
|
||||
}
|
||||
setIndex(previousIndex);
|
||||
};
|
||||
|
||||
const onKeyUp = (event: KeyboardEvent<HTMLInputElement> & ChangeEvent<HTMLInputElement>) => {
|
||||
const { selectionStart } = event.target;
|
||||
switch (event.keyCode) {
|
||||
case KEY_CODES.LEFT:
|
||||
setIsSuggestionsVisible(true);
|
||||
onChange(value, selectionStart);
|
||||
break;
|
||||
case KEY_CODES.RIGHT:
|
||||
setIsSuggestionsVisible(true);
|
||||
onChange(value, selectionStart);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const onKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
|
||||
switch (event.keyCode) {
|
||||
case KEY_CODES.DOWN:
|
||||
event.preventDefault();
|
||||
if (isSuggestionsVisible) {
|
||||
incrementIndex(index!);
|
||||
} else {
|
||||
setIndex(0);
|
||||
setIsSuggestionsVisible(true);
|
||||
}
|
||||
break;
|
||||
case KEY_CODES.UP:
|
||||
event.preventDefault();
|
||||
if (isSuggestionsVisible) {
|
||||
decrementIndex(index!);
|
||||
}
|
||||
break;
|
||||
case KEY_CODES.ENTER:
|
||||
event.preventDefault();
|
||||
if (isSuggestionsVisible && suggestions[index!]) {
|
||||
selectSuggestion(suggestions[index!]);
|
||||
} else {
|
||||
setIsSuggestionsVisible(false);
|
||||
onSubmit(value);
|
||||
}
|
||||
break;
|
||||
case KEY_CODES.ESC:
|
||||
event.preventDefault();
|
||||
setIsSuggestionsVisible(false);
|
||||
break;
|
||||
case KEY_CODES.TAB:
|
||||
setIsSuggestionsVisible(false);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
return { onKeyUp, onKeyDown };
|
||||
};
|
|
@ -1,56 +0,0 @@
|
|||
/*
|
||||
* 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 { KQL_SYNTAX_LOCAL_STORAGE } from '../../../../../common/constants';
|
||||
import { useUrlParams } from '../../../../hooks';
|
||||
|
||||
interface Props {
|
||||
setValue: React.Dispatch<React.SetStateAction<string>>;
|
||||
}
|
||||
|
||||
export const useKqlSyntax = ({ setValue }: Props) => {
|
||||
const [kqlSyntax, setKqlSyntax] = useState(
|
||||
localStorage.getItem(KQL_SYNTAX_LOCAL_STORAGE) === 'true'
|
||||
);
|
||||
|
||||
const [getUrlParams] = useUrlParams();
|
||||
|
||||
const { query, search } = getUrlParams();
|
||||
|
||||
useEffect(() => {
|
||||
setValue(query || '');
|
||||
}, [query, setValue]);
|
||||
|
||||
useEffect(() => {
|
||||
setValue(search || '');
|
||||
}, [search, setValue]);
|
||||
|
||||
useEffect(() => {
|
||||
if (query || search) {
|
||||
// if url has query or params we will give them preference on load
|
||||
// for selecting syntax type
|
||||
if (query) {
|
||||
setKqlSyntax(false);
|
||||
}
|
||||
if (search) {
|
||||
setKqlSyntax(true);
|
||||
}
|
||||
} else {
|
||||
setKqlSyntax(localStorage.getItem(KQL_SYNTAX_LOCAL_STORAGE) === 'true');
|
||||
}
|
||||
// This part is meant to run only when component loads
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem(KQL_SYNTAX_LOCAL_STORAGE, String(kqlSyntax));
|
||||
setValue('');
|
||||
}, [kqlSyntax, setValue]);
|
||||
|
||||
return { kqlSyntax, setKqlSyntax };
|
||||
};
|
|
@ -1,32 +0,0 @@
|
|||
/*
|
||||
* 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 { useEffect, useState } from 'react';
|
||||
import useDebounce from 'react-use/lib/useDebounce';
|
||||
import { useUrlParams } from '../../../../hooks';
|
||||
|
||||
export const useSimpleQuery = () => {
|
||||
const [getUrlParams, updateUrlParams] = useUrlParams();
|
||||
|
||||
const { query } = getUrlParams();
|
||||
|
||||
const [debouncedValue, setDebouncedValue] = useState(query ?? '');
|
||||
|
||||
useEffect(() => {
|
||||
setDebouncedValue(query ?? '');
|
||||
}, [query]);
|
||||
|
||||
useDebounce(
|
||||
() => {
|
||||
updateUrlParams({ query: debouncedValue });
|
||||
},
|
||||
250,
|
||||
[debouncedValue]
|
||||
);
|
||||
|
||||
return { query, setQuery: setDebouncedValue };
|
||||
};
|
|
@ -8,7 +8,7 @@
|
|||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { getMonitorList } from '../../../state/actions';
|
||||
import { monitorListSelector } from '../../../state/selectors';
|
||||
import { esKuerySelector, monitorListSelector } from '../../../state/selectors';
|
||||
import { MonitorListComponent } from './monitor_list';
|
||||
import { useUrlParams } from '../../../hooks';
|
||||
import { UptimeRefreshContext } from '../../../contexts';
|
||||
|
@ -28,7 +28,7 @@ const getPageSizeValue = () => {
|
|||
};
|
||||
|
||||
export const MonitorList: React.FC<MonitorListProps> = (props) => {
|
||||
const { filters } = props;
|
||||
const filters = useSelector(esKuerySelector);
|
||||
|
||||
const [pageSize, setPageSize] = useState<number>(getPageSizeValue);
|
||||
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
/*
|
||||
* 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 { useDispatch, useSelector } from 'react-redux';
|
||||
import React, { useCallback } from 'react';
|
||||
import { OverviewPageComponent } from '../../pages/overview';
|
||||
import { selectIndexPattern } from '../../state/selectors';
|
||||
import { setEsKueryString } from '../../state/actions';
|
||||
|
||||
export const OverviewPage: React.FC = (props) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const setEsKueryFilters = useCallback(
|
||||
(esFilters: string) => dispatch(setEsKueryString(esFilters)),
|
||||
[dispatch]
|
||||
);
|
||||
const { index_pattern: indexPattern, loading } = useSelector(selectIndexPattern);
|
||||
|
||||
return (
|
||||
<OverviewPageComponent
|
||||
setEsKueryFilters={setEsKueryFilters}
|
||||
indexPattern={indexPattern}
|
||||
loading={loading}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -1,27 +0,0 @@
|
|||
/*
|
||||
* 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 { shallowWithIntl } from '@kbn/test/jest';
|
||||
import { ParsingErrorCallout } from './parsing_error_callout';
|
||||
|
||||
describe('OverviewPageParsingErrorCallout', () => {
|
||||
it('renders without errors when a valid error is provided', () => {
|
||||
expect(
|
||||
shallowWithIntl(
|
||||
<ParsingErrorCallout
|
||||
error={{ message: 'Unable to convert to Elasticsearch query, invalid syntax.' }}
|
||||
/>
|
||||
)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders without errors when an error with no message is provided', () => {
|
||||
const error: any = {};
|
||||
expect(shallowWithIntl(<ParsingErrorCallout error={error} />)).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -1,48 +0,0 @@
|
|||
/*
|
||||
* 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 { EuiCallOut, EuiCodeBlock } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React from 'react';
|
||||
|
||||
interface HasMessage {
|
||||
message: string;
|
||||
}
|
||||
|
||||
interface ParsingErrorCalloutProps {
|
||||
error: HasMessage;
|
||||
}
|
||||
|
||||
export const ParsingErrorCallout = ({ error }: ParsingErrorCalloutProps) => (
|
||||
<EuiCallOut
|
||||
title={i18n.translate('xpack.uptime.overviewPageParsingErrorCallout.title', {
|
||||
defaultMessage: 'Parsing error',
|
||||
})}
|
||||
color="danger"
|
||||
iconType="alert"
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.overviewPageParsingErrorCallout.content"
|
||||
defaultMessage="There was an error parsing the filter query. {content}"
|
||||
values={{
|
||||
content: (
|
||||
<EuiCodeBlock>
|
||||
{error.message
|
||||
? error.message
|
||||
: i18n.translate('xpack.uptime.overviewPageParsingErrorCallout.noMessage', {
|
||||
defaultMessage: 'There was no error message',
|
||||
})}
|
||||
</EuiCodeBlock>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
);
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* 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, { useState } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiFlexItem } from '@elastic/eui';
|
||||
import { QueryStringInput } from '../../../../../../../src/plugins/data/public/';
|
||||
import { useIndexPattern } from './use_index_pattern';
|
||||
import { SyntaxType, useQueryBar } from './use_query_bar';
|
||||
import { KQL_PLACE_HOLDER, SIMPLE_SEARCH_PLACEHOLDER } from './translations';
|
||||
import { useGetUrlParams } from '../../../hooks';
|
||||
|
||||
const SYNTAX_STORAGE = 'uptime:queryBarSyntax';
|
||||
|
||||
export const isValidKuery = (query: string) => {
|
||||
if (query === '') {
|
||||
return true;
|
||||
}
|
||||
const listOfOperators = [':', '>=', '=>', '>', '<'];
|
||||
for (let i = 0; i < listOfOperators.length; i++) {
|
||||
const operator = listOfOperators[i];
|
||||
const qParts = query.trim().split(operator);
|
||||
if (query.includes(operator) && qParts.length > 1 && qParts[1]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const QueryBar = () => {
|
||||
const { index_pattern: indexPattern } = useIndexPattern();
|
||||
|
||||
const { search: urlValue } = useGetUrlParams();
|
||||
|
||||
const { query, setQuery } = useQueryBar();
|
||||
|
||||
const [inputVal, setInputVal] = useState<string>(query.query);
|
||||
|
||||
const isInValid = () => {
|
||||
if (query.language === SyntaxType.text) {
|
||||
return false;
|
||||
}
|
||||
return inputVal?.trim() !== urlValue?.trim();
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiFlexItem grow={1} style={{ flexBasis: 485 }}>
|
||||
<QueryStringInput
|
||||
indexPatterns={indexPattern ? [indexPattern] : []}
|
||||
nonKqlMode="text"
|
||||
iconType="search"
|
||||
isClearable={true}
|
||||
onChange={(queryN) => {
|
||||
if (queryN?.language === SyntaxType.text) {
|
||||
setQuery({ query: queryN.query as string, language: queryN.language });
|
||||
}
|
||||
if (queryN?.language === SyntaxType.kuery && isValidKuery(queryN?.query as string)) {
|
||||
// we want to submit when user clears or paste a complete kuery
|
||||
setQuery({ query: queryN.query as string, language: queryN.language });
|
||||
}
|
||||
setInputVal(queryN?.query as string);
|
||||
}}
|
||||
onSubmit={(queryN) => {
|
||||
if (queryN) setQuery({ query: queryN.query as string, language: queryN.language });
|
||||
}}
|
||||
query={{ ...query, query: inputVal }}
|
||||
aria-label={i18n.translate('xpack.uptime.filterBar.ariaLabel', {
|
||||
defaultMessage: 'Input filter criteria for the overview page',
|
||||
})}
|
||||
data-test-subj="uptimeSearchBarInput"
|
||||
autoSubmit={true}
|
||||
storageKey={SYNTAX_STORAGE}
|
||||
placeholder={
|
||||
query.language === SyntaxType.kuery ? KQL_PLACE_HOLDER : SIMPLE_SEARCH_PLACEHOLDER
|
||||
}
|
||||
isInvalid={isInValid()}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
};
|
|
@ -18,21 +18,3 @@ export const SIMPLE_SEARCH_PLACEHOLDER = i18n.translate(
|
|||
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',
|
||||
});
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* 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 { useCallback, useEffect, useState } from 'react';
|
||||
import { useDebounce } from 'react-use';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useGetUrlParams, useUpdateKueryString, useUrlParams } from '../../../hooks';
|
||||
import { setEsKueryString } from '../../../state/actions';
|
||||
import { useIndexPattern } from './use_index_pattern';
|
||||
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
|
||||
import { UptimePluginServices } from '../../../apps/plugin';
|
||||
|
||||
export enum SyntaxType {
|
||||
text = 'text',
|
||||
kuery = 'kuery',
|
||||
}
|
||||
const SYNTAX_STORAGE = 'uptime:queryBarSyntax';
|
||||
|
||||
export const useQueryBar = () => {
|
||||
const { index_pattern: indexPattern } = useIndexPattern();
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const { absoluteDateRangeStart, absoluteDateRangeEnd, ...params } = useGetUrlParams();
|
||||
const { search, query: queryParam, filters: paramFilters } = params;
|
||||
|
||||
const {
|
||||
services: { storage },
|
||||
} = useKibana<UptimePluginServices>();
|
||||
|
||||
const [query, setQuery] = useState(
|
||||
queryParam
|
||||
? {
|
||||
query: queryParam,
|
||||
language: SyntaxType.text,
|
||||
}
|
||||
: search
|
||||
? { query: search, language: SyntaxType.kuery }
|
||||
: {
|
||||
query: '',
|
||||
language: storage.get(SYNTAX_STORAGE) ?? SyntaxType.text,
|
||||
}
|
||||
);
|
||||
|
||||
const updateUrlParams = useUrlParams()[1];
|
||||
|
||||
const [esFilters, error] = useUpdateKueryString(
|
||||
indexPattern,
|
||||
query.language === SyntaxType.kuery ? (query.query as string) : undefined,
|
||||
paramFilters
|
||||
);
|
||||
|
||||
const setEsKueryFilters = useCallback(
|
||||
(esFiltersN: string) => dispatch(setEsKueryString(esFiltersN)),
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setEsKueryFilters(esFilters ?? '');
|
||||
}, [esFilters, setEsKueryFilters]);
|
||||
|
||||
useDebounce(
|
||||
() => {
|
||||
if (query.language === SyntaxType.text && queryParam !== query.query) {
|
||||
updateUrlParams({ query: query.query as string });
|
||||
}
|
||||
if (query.language === SyntaxType.kuery) {
|
||||
updateUrlParams({ query: '' });
|
||||
}
|
||||
},
|
||||
350,
|
||||
[query]
|
||||
);
|
||||
|
||||
useDebounce(
|
||||
() => {
|
||||
if (query.language === SyntaxType.kuery && !error && esFilters) {
|
||||
updateUrlParams({ search: query.query as string });
|
||||
}
|
||||
if (query.language === SyntaxType.text) {
|
||||
updateUrlParams({ search: '' });
|
||||
}
|
||||
if (query.language === SyntaxType.kuery && query.query === '') {
|
||||
updateUrlParams({ search: '' });
|
||||
}
|
||||
},
|
||||
250,
|
||||
[esFilters, error]
|
||||
);
|
||||
|
||||
return { query, setQuery };
|
||||
};
|
|
@ -6,4 +6,3 @@
|
|||
*/
|
||||
|
||||
export { SnapshotComponent } from './snapshot';
|
||||
export { Snapshot } from './snapshot_container';
|
||||
|
|
|
@ -7,8 +7,9 @@
|
|||
|
||||
import React from 'react';
|
||||
import { shallowWithIntl } from '@kbn/test/jest';
|
||||
import { Snapshot } from '../../../common/runtime_types';
|
||||
import { SnapshotComponent } from './snapshot/snapshot';
|
||||
import { SnapshotComponent } from './snapshot';
|
||||
import { Snapshot } from '../../../../common/runtime_types/snapshot';
|
||||
import * as hook from './use_snap_shot';
|
||||
|
||||
describe('Snapshot component', () => {
|
||||
const snapshot: Snapshot = {
|
||||
|
@ -18,7 +19,9 @@ describe('Snapshot component', () => {
|
|||
};
|
||||
|
||||
it('renders without errors', () => {
|
||||
const wrapper = shallowWithIntl(<SnapshotComponent count={snapshot} loading={false} />);
|
||||
jest.spyOn(hook, 'useSnapShotCount').mockReturnValue({ count: snapshot, loading: false });
|
||||
|
||||
const wrapper = shallowWithIntl(<SnapshotComponent />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -10,13 +10,11 @@ import React from 'react';
|
|||
import { DonutChart } from '../../common/charts';
|
||||
import { ChartWrapper } from '../../common/charts/chart_wrapper';
|
||||
import { SnapshotHeading } from './snapshot_heading';
|
||||
import { Snapshot as SnapshotType } from '../../../../common/runtime_types';
|
||||
import { useSnapShotCount } from './use_snap_shot';
|
||||
|
||||
const SNAPSHOT_CHART_HEIGHT = 144;
|
||||
|
||||
interface SnapshotComponentProps {
|
||||
count: SnapshotType;
|
||||
loading: boolean;
|
||||
height?: string;
|
||||
}
|
||||
|
||||
|
@ -25,10 +23,14 @@ interface SnapshotComponentProps {
|
|||
* glean the status of their uptime environment.
|
||||
* @param props the props required by the component
|
||||
*/
|
||||
export const SnapshotComponent: React.FC<SnapshotComponentProps> = ({ count, height, loading }) => (
|
||||
<ChartWrapper loading={loading} height={height}>
|
||||
<SnapshotHeading total={count.total} />
|
||||
<EuiSpacer size="xs" />
|
||||
<DonutChart up={count.up} down={count.down} height={SNAPSHOT_CHART_HEIGHT} />
|
||||
</ChartWrapper>
|
||||
);
|
||||
export const SnapshotComponent: React.FC<SnapshotComponentProps> = ({ height }) => {
|
||||
const { count, loading } = useSnapShotCount();
|
||||
|
||||
return (
|
||||
<ChartWrapper loading={loading} height={height}>
|
||||
<SnapshotHeading total={count.total} />
|
||||
<EuiSpacer size="xs" />
|
||||
<DonutChart up={count.up} down={count.down} height={SNAPSHOT_CHART_HEIGHT} />
|
||||
</ChartWrapper>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
/*
|
||||
* 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, { useContext, useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useGetUrlParams } from '../../../hooks';
|
||||
import { getSnapshotCountAction } from '../../../state/actions';
|
||||
import { SnapshotComponent } from './snapshot';
|
||||
import { esKuerySelector, snapshotDataSelector } from '../../../state/selectors';
|
||||
import { UptimeRefreshContext } from '../../../contexts';
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
* Height is needed, since by default charts takes height of 100%
|
||||
*/
|
||||
height?: string;
|
||||
}
|
||||
|
||||
export const Snapshot: React.FC<Props> = ({ height }: Props) => {
|
||||
const { dateRangeStart, dateRangeEnd, query } = useGetUrlParams();
|
||||
|
||||
const { lastRefresh } = useContext(UptimeRefreshContext);
|
||||
|
||||
const { count, loading } = useSelector(snapshotDataSelector);
|
||||
const esKuery = useSelector(esKuerySelector);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getSnapshotCountAction.get({ query, dateRangeStart, dateRangeEnd, filters: esKuery }));
|
||||
}, [dateRangeStart, dateRangeEnd, esKuery, lastRefresh, dispatch, query]);
|
||||
return <SnapshotComponent count={count} height={height} loading={loading} />;
|
||||
};
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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 { useContext } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useGetUrlParams } from '../../../hooks';
|
||||
import { esKuerySelector } from '../../../state/selectors';
|
||||
import { UptimeRefreshContext } from '../../../contexts';
|
||||
import { useFetcher } from '../../../../../observability/public';
|
||||
import { fetchSnapshotCount } from '../../../state/api';
|
||||
|
||||
export const useSnapShotCount = () => {
|
||||
const { dateRangeStart, dateRangeEnd, query } = useGetUrlParams();
|
||||
|
||||
const { lastRefresh } = useContext(UptimeRefreshContext);
|
||||
|
||||
const esKuery = useSelector(esKuerySelector);
|
||||
|
||||
const { data, loading } = useFetcher(
|
||||
() => fetchSnapshotCount({ query, dateRangeStart, dateRangeEnd, filters: esKuery }),
|
||||
[dateRangeStart, dateRangeEnd, esKuery, lastRefresh, query]
|
||||
);
|
||||
|
||||
return { count: data || { total: 0, up: 0, down: 0 }, loading };
|
||||
};
|
|
@ -8,7 +8,7 @@
|
|||
import React from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui';
|
||||
import { PingHistogram } from '../monitor';
|
||||
import { Snapshot } from './snapshot/snapshot_container';
|
||||
import { SnapshotComponent } from './snapshot';
|
||||
|
||||
const STATUS_CHART_HEIGHT = '160px';
|
||||
|
||||
|
@ -16,7 +16,7 @@ export const StatusPanel = ({}) => (
|
|||
<EuiPanel>
|
||||
<EuiFlexGroup gutterSize="l">
|
||||
<EuiFlexItem grow={2}>
|
||||
<Snapshot height={STATUS_CHART_HEIGHT} />
|
||||
<SnapshotComponent height={STATUS_CHART_HEIGHT} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={10}>
|
||||
<PingHistogram height={STATUS_CHART_HEIGHT} isResponsive={true} />
|
||||
|
|
|
@ -209,7 +209,7 @@ exports[`useUrlParams deletes keys that do not have truthy values 1`] = `
|
|||
}
|
||||
>
|
||||
<div>
|
||||
{"pagination":"foo","absoluteDateRangeStart":20,"absoluteDateRangeEnd":20,"autorefreshInterval":60000,"autorefreshIsPaused":false,"dateRangeStart":"now-12","dateRangeEnd":"now","filters":"","search":"","statusFilter":"","focusConnectorField":false}
|
||||
{"pagination":"foo","absoluteDateRangeStart":20,"absoluteDateRangeEnd":20,"autorefreshInterval":60000,"autorefreshIsPaused":false,"dateRangeStart":"now-12","dateRangeEnd":"now","filters":"","search":"","statusFilter":"","focusConnectorField":false,"query":""}
|
||||
</div>
|
||||
<button
|
||||
id="setUrlParams"
|
||||
|
@ -433,7 +433,7 @@ exports[`useUrlParams gets the expected values using the context 1`] = `
|
|||
hook={[Function]}
|
||||
>
|
||||
<div>
|
||||
{"absoluteDateRangeStart":20,"absoluteDateRangeEnd":20,"autorefreshInterval":60000,"autorefreshIsPaused":false,"dateRangeStart":"now-15m","dateRangeEnd":"now","filters":"","search":"","statusFilter":"","focusConnectorField":false}
|
||||
{"absoluteDateRangeStart":20,"absoluteDateRangeEnd":20,"autorefreshInterval":60000,"autorefreshIsPaused":false,"dateRangeStart":"now-15m","dateRangeEnd":"now","filters":"","search":"","statusFilter":"","focusConnectorField":false,"query":""}
|
||||
</div>
|
||||
<button
|
||||
id="setUrlParams"
|
||||
|
|
|
@ -33,15 +33,6 @@ export const mockState: AppState = {
|
|||
loading: false,
|
||||
errors: [],
|
||||
},
|
||||
snapshot: {
|
||||
count: {
|
||||
up: 2,
|
||||
down: 0,
|
||||
total: 2,
|
||||
},
|
||||
errors: [],
|
||||
loading: false,
|
||||
},
|
||||
ui: {
|
||||
alertFlyoutVisible: false,
|
||||
basePath: 'yyz',
|
||||
|
|
|
@ -11,8 +11,8 @@ import { CoreStart } from 'kibana/public';
|
|||
import { store } from '../../../state';
|
||||
import { KibanaContextProvider } from '../../../../../../../src/plugins/kibana_react/public';
|
||||
import { ClientPluginsStart } from '../../../apps/plugin';
|
||||
import { AlertMonitorStatus } from '../../../components/overview/alerts/alerts_containers';
|
||||
import { kibanaService } from '../../../state/kibana_service';
|
||||
import { AlertMonitorStatus } from '../../../components/overview/alerts/alerts_containers/alert_monitor_status';
|
||||
|
||||
interface Props {
|
||||
core: CoreStart;
|
||||
|
|
|
@ -11,7 +11,7 @@ Object {
|
|||
"filters": "",
|
||||
"focusConnectorField": false,
|
||||
"pagination": undefined,
|
||||
"query": undefined,
|
||||
"query": "",
|
||||
"search": "",
|
||||
"statusFilter": "",
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ Object {
|
|||
"filters": "",
|
||||
"focusConnectorField": false,
|
||||
"pagination": undefined,
|
||||
"query": undefined,
|
||||
"query": "",
|
||||
"search": "",
|
||||
"statusFilter": "",
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ Object {
|
|||
"filters": "",
|
||||
"focusConnectorField": false,
|
||||
"pagination": undefined,
|
||||
"query": undefined,
|
||||
"query": "",
|
||||
"search": "monitor.status: down",
|
||||
"statusFilter": "",
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ Object {
|
|||
"filters": "",
|
||||
"focusConnectorField": false,
|
||||
"pagination": undefined,
|
||||
"query": undefined,
|
||||
"query": "",
|
||||
"search": "",
|
||||
"statusFilter": "",
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ Object {
|
|||
"filters": "",
|
||||
"focusConnectorField": false,
|
||||
"pagination": undefined,
|
||||
"query": undefined,
|
||||
"query": "",
|
||||
"search": "",
|
||||
"statusFilter": "",
|
||||
}
|
||||
|
|
|
@ -63,6 +63,7 @@ describe('getSupportedUrlParams', () => {
|
|||
pagination: undefined,
|
||||
search: SEARCH,
|
||||
statusFilter: STATUS_FILTER,
|
||||
query: '',
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -80,7 +80,6 @@ export const getSupportedUrlParams = (params: {
|
|||
} = filteredParams;
|
||||
|
||||
return {
|
||||
query,
|
||||
pagination,
|
||||
absoluteDateRangeStart: parseAbsoluteDate(
|
||||
dateRangeStart || DATE_RANGE_START,
|
||||
|
@ -99,5 +98,6 @@ export const getSupportedUrlParams = (params: {
|
|||
search: search || SEARCH,
|
||||
statusFilter: statusFilter || STATUS_FILTER,
|
||||
focusConnectorField: !!focusConnectorField,
|
||||
query: query || '',
|
||||
};
|
||||
};
|
||||
|
|
|
@ -85,107 +85,7 @@ exports[`MonitorPage shallow renders expected elements for valid props 1`] = `
|
|||
}
|
||||
}
|
||||
>
|
||||
<Memo()
|
||||
indexPattern={
|
||||
Object {
|
||||
"fields": Array [
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"esTypes": Array [
|
||||
"date",
|
||||
],
|
||||
"name": "@timestamp",
|
||||
"readFromDocValues": true,
|
||||
"searchable": true,
|
||||
"type": "date",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"esTypes": Array [
|
||||
"keyword",
|
||||
],
|
||||
"name": "monitor.check_group",
|
||||
"readFromDocValues": true,
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"esTypes": Array [
|
||||
"long",
|
||||
],
|
||||
"name": "monitor.duration.us",
|
||||
"readFromDocValues": true,
|
||||
"searchable": true,
|
||||
"type": "number",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"esTypes": Array [
|
||||
"keyword",
|
||||
],
|
||||
"name": "monitor.id",
|
||||
"readFromDocValues": true,
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"esTypes": Array [
|
||||
"ip",
|
||||
],
|
||||
"name": "monitor.ip",
|
||||
"readFromDocValues": true,
|
||||
"searchable": true,
|
||||
"type": "ip",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"esTypes": Array [
|
||||
"keyword",
|
||||
],
|
||||
"name": "monitor.name",
|
||||
"readFromDocValues": true,
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"esTypes": Array [
|
||||
"keyword",
|
||||
],
|
||||
"name": "monitor.status",
|
||||
"readFromDocValues": true,
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"esTypes": Array [
|
||||
"date_range",
|
||||
],
|
||||
"name": "monitor.timespan",
|
||||
"readFromDocValues": true,
|
||||
"searchable": true,
|
||||
"type": "unknown",
|
||||
},
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"esTypes": Array [
|
||||
"keyword",
|
||||
],
|
||||
"name": "monitor.type",
|
||||
"readFromDocValues": true,
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
],
|
||||
"title": "heartbeat-8*",
|
||||
}
|
||||
}
|
||||
loading={false}
|
||||
setEsKueryFilters={[MockFunction]}
|
||||
/>
|
||||
<OverviewPageComponent />
|
||||
</ContextProvider>
|
||||
</ContextProvider>
|
||||
`;
|
||||
|
|
|
@ -10,94 +10,7 @@ import { OverviewPageComponent } from './overview';
|
|||
import { shallowWithRouter } from '../lib';
|
||||
|
||||
describe('MonitorPage', () => {
|
||||
const indexPattern = {
|
||||
fields: [
|
||||
{
|
||||
name: '@timestamp',
|
||||
type: 'date',
|
||||
esTypes: ['date'],
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
readFromDocValues: true,
|
||||
},
|
||||
|
||||
{
|
||||
name: 'monitor.check_group',
|
||||
type: 'string',
|
||||
esTypes: ['keyword'],
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
readFromDocValues: true,
|
||||
},
|
||||
{
|
||||
name: 'monitor.duration.us',
|
||||
type: 'number',
|
||||
esTypes: ['long'],
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
readFromDocValues: true,
|
||||
},
|
||||
{
|
||||
name: 'monitor.id',
|
||||
type: 'string',
|
||||
esTypes: ['keyword'],
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
readFromDocValues: true,
|
||||
},
|
||||
{
|
||||
name: 'monitor.ip',
|
||||
type: 'ip',
|
||||
esTypes: ['ip'],
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
readFromDocValues: true,
|
||||
},
|
||||
{
|
||||
name: 'monitor.name',
|
||||
type: 'string',
|
||||
esTypes: ['keyword'],
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
readFromDocValues: true,
|
||||
},
|
||||
{
|
||||
name: 'monitor.status',
|
||||
type: 'string',
|
||||
esTypes: ['keyword'],
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
readFromDocValues: true,
|
||||
},
|
||||
{
|
||||
name: 'monitor.timespan',
|
||||
type: 'unknown',
|
||||
esTypes: ['date_range'],
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
readFromDocValues: true,
|
||||
},
|
||||
{
|
||||
name: 'monitor.type',
|
||||
type: 'string',
|
||||
esTypes: ['keyword'],
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
readFromDocValues: true,
|
||||
},
|
||||
],
|
||||
title: 'heartbeat-8*',
|
||||
};
|
||||
|
||||
it('shallow renders expected elements for valid props', () => {
|
||||
expect(
|
||||
shallowWithRouter(
|
||||
<OverviewPageComponent
|
||||
indexPattern={indexPattern}
|
||||
setEsKueryFilters={jest.fn()}
|
||||
loading={false}
|
||||
/>
|
||||
)
|
||||
).toMatchSnapshot();
|
||||
expect(shallowWithRouter(<OverviewPageComponent />)).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,24 +8,16 @@
|
|||
import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
|
||||
import React, { useEffect } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useGetUrlParams } from '../hooks';
|
||||
import { IIndexPattern } from '../../../../../src/plugins/data/public';
|
||||
import { useUpdateKueryString } from '../hooks';
|
||||
|
||||
import { useBreadcrumbs } from '../hooks/use_breadcrumbs';
|
||||
import { useTrackPageview } from '../../../observability/public';
|
||||
import { MonitorList } from '../components/overview/monitor_list/monitor_list_container';
|
||||
import { EmptyState, FilterGroup, KueryBar, ParsingErrorCallout } from '../components/overview';
|
||||
import { EmptyState, FilterGroup } from '../components/overview';
|
||||
import { StatusPanel } from '../components/overview/status_panel';
|
||||
import { getConnectorsAction, getMonitorAlertsAction } from '../state/alerts/alerts';
|
||||
import { useInitApp } from '../hooks/use_init_app';
|
||||
|
||||
interface Props {
|
||||
loading: boolean;
|
||||
indexPattern: IIndexPattern | null;
|
||||
setEsKueryFilters: (esFilters: string) => void;
|
||||
}
|
||||
import { QueryBar } from '../components/overview/query_bar/query_bar';
|
||||
|
||||
const EuiFlexItemStyled = styled(EuiFlexItem)`
|
||||
&& {
|
||||
|
@ -39,54 +31,33 @@ const EuiFlexItemStyled = styled(EuiFlexItem)`
|
|||
}
|
||||
`;
|
||||
|
||||
export const OverviewPageComponent = React.memo(
|
||||
({ indexPattern, setEsKueryFilters, loading }: Props) => {
|
||||
const { absoluteDateRangeStart, absoluteDateRangeEnd, ...params } = useGetUrlParams();
|
||||
const { search, filters: urlFilters } = params;
|
||||
export const OverviewPageComponent = () => {
|
||||
useTrackPageview({ app: 'uptime', path: 'overview' });
|
||||
useTrackPageview({ app: 'uptime', path: 'overview', delay: 15000 });
|
||||
|
||||
useTrackPageview({ app: 'uptime', path: 'overview' });
|
||||
useTrackPageview({ app: 'uptime', path: 'overview', delay: 15000 });
|
||||
useInitApp();
|
||||
|
||||
useInitApp();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const [esFilters, error] = useUpdateKueryString(indexPattern, search, urlFilters);
|
||||
useEffect(() => {
|
||||
dispatch(getConnectorsAction.get());
|
||||
dispatch(getMonitorAlertsAction.get());
|
||||
}, [dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
setEsKueryFilters(esFilters ?? '');
|
||||
}, [esFilters, setEsKueryFilters]);
|
||||
useBreadcrumbs([]); // No extra breadcrumbs on overview
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getConnectorsAction.get());
|
||||
dispatch(getMonitorAlertsAction.get());
|
||||
}, [dispatch]);
|
||||
|
||||
useBreadcrumbs([]); // No extra breadcrumbs on overview
|
||||
|
||||
return (
|
||||
<>
|
||||
<EmptyState>
|
||||
<EuiFlexGroup gutterSize="xs" wrap responsive={false}>
|
||||
<EuiFlexItem grow={1} style={{ flexBasis: 485 }}>
|
||||
<KueryBar
|
||||
aria-label={i18n.translate('xpack.uptime.filterBar.ariaLabel', {
|
||||
defaultMessage: 'Input filter criteria for the overview page',
|
||||
})}
|
||||
data-test-subj="xpack.uptime.filterBar"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItemStyled grow={true}>
|
||||
<FilterGroup esFilters={esFilters} />
|
||||
</EuiFlexItemStyled>
|
||||
{error && !loading && <ParsingErrorCallout error={error} />}
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="xs" />
|
||||
<StatusPanel />
|
||||
<EuiSpacer size="s" />
|
||||
<MonitorList filters={esFilters} />
|
||||
</EmptyState>
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
return (
|
||||
<EmptyState>
|
||||
<EuiFlexGroup gutterSize="xs" wrap responsive={false}>
|
||||
<QueryBar />
|
||||
<EuiFlexItemStyled grow={true}>
|
||||
<FilterGroup />
|
||||
</EuiFlexItemStyled>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="xs" />
|
||||
<StatusPanel />
|
||||
<EuiSpacer size="s" />
|
||||
<MonitorList />
|
||||
</EmptyState>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
import React, { FC, useEffect } from 'react';
|
||||
import { Route, RouteComponentProps, Switch } from 'react-router-dom';
|
||||
import { OverviewPage } from './components/overview/overview_container';
|
||||
import { Props as PageHeaderProps, PageHeader } from './components/common/header/page_header';
|
||||
import {
|
||||
CERTIFICATES_ROUTE,
|
||||
|
@ -20,6 +19,7 @@ import {
|
|||
import { MonitorPage, StepDetailPage, NotFoundPage, SettingsPage } from './pages';
|
||||
import { CertificatesPage } from './pages/certificates';
|
||||
import { UptimePage, useUptimeTelemetry } from './hooks';
|
||||
import { OverviewPageComponent } from './pages/overview';
|
||||
import { SyntheticsCheckSteps } from './pages/synthetics/synthetics_checks';
|
||||
|
||||
interface RouteProps {
|
||||
|
@ -83,7 +83,7 @@ const Routes: RouteProps[] = [
|
|||
{
|
||||
title: baseTitle,
|
||||
path: OVERVIEW_ROUTE,
|
||||
component: OverviewPage,
|
||||
component: OverviewPageComponent,
|
||||
dataTestSubj: 'uptimeOverviewPage',
|
||||
telemetryId: UptimePage.Overview,
|
||||
headerProps: {
|
||||
|
|
|
@ -30,5 +30,3 @@ export const fetchOverviewFiltersFail = createAction<Error>('FETCH_OVERVIEW_FILT
|
|||
export const fetchOverviewFiltersSuccess = createAction<OverviewFilters>(
|
||||
'FETCH_OVERVIEW_FILTERS_SUCCESS'
|
||||
);
|
||||
|
||||
export const setOverviewFilters = createAction<OverviewFilters>('SET_OVERVIEW_FILTERS');
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
import { fork } from 'redux-saga/effects';
|
||||
import { fetchMonitorDetailsEffect } from './monitor';
|
||||
import { fetchOverviewFiltersEffect } from './overview_filters';
|
||||
import { fetchSnapshotCountEffect } from './snapshot';
|
||||
import { fetchMonitorListEffect } from './monitor_list';
|
||||
import { fetchMonitorStatusEffect } from './monitor_status';
|
||||
import { fetchDynamicSettingsEffect, setDynamicSettingsEffect } from './dynamic_settings';
|
||||
|
@ -24,7 +23,6 @@ import { fetchNetworkEventsEffect } from './network_events';
|
|||
|
||||
export function* rootEffect() {
|
||||
yield fork(fetchMonitorDetailsEffect);
|
||||
yield fork(fetchSnapshotCountEffect);
|
||||
yield fork(fetchOverviewFiltersEffect);
|
||||
yield fork(fetchMonitorListEffect);
|
||||
yield fork(fetchMonitorStatusEffect);
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
/*
|
||||
* 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 { takeLatest } from 'redux-saga/effects';
|
||||
import { getSnapshotCountAction } from '../actions';
|
||||
import { fetchSnapshotCount } from '../api';
|
||||
import { fetchEffectFactory } from './fetch_effect';
|
||||
|
||||
export function* fetchSnapshotCountEffect() {
|
||||
yield takeLatest(
|
||||
getSnapshotCountAction.get,
|
||||
fetchEffectFactory(
|
||||
fetchSnapshotCount,
|
||||
getSnapshotCountAction.success,
|
||||
getSnapshotCountAction.fail
|
||||
)
|
||||
);
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`snapshot reducer appends a current error to existing errors list 1`] = `
|
||||
Object {
|
||||
"count": Object {
|
||||
"down": 0,
|
||||
"total": 0,
|
||||
"up": 0,
|
||||
},
|
||||
"errors": Array [
|
||||
[Error: I couldn't get your data because the server denied the request],
|
||||
],
|
||||
"loading": false,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`snapshot reducer changes the count when a snapshot fetch succeeds 1`] = `
|
||||
Object {
|
||||
"count": Object {
|
||||
"down": 15,
|
||||
"total": 25,
|
||||
"up": 10,
|
||||
},
|
||||
"errors": Array [],
|
||||
"loading": false,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`snapshot reducer sets the state's status to loading during a fetch 1`] = `
|
||||
Object {
|
||||
"count": Object {
|
||||
"down": 0,
|
||||
"total": 0,
|
||||
"up": 0,
|
||||
},
|
||||
"errors": Array [],
|
||||
"loading": true,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`snapshot reducer updates existing state 1`] = `
|
||||
Object {
|
||||
"count": Object {
|
||||
"down": 1,
|
||||
"total": 4,
|
||||
"up": 3,
|
||||
},
|
||||
"errors": Array [],
|
||||
"loading": true,
|
||||
}
|
||||
`;
|
|
@ -8,7 +8,6 @@
|
|||
import { combineReducers } from 'redux';
|
||||
import { monitorReducer } from './monitor';
|
||||
import { overviewFiltersReducer } from './overview_filters';
|
||||
import { snapshotReducer } from './snapshot';
|
||||
import { uiReducer } from './ui';
|
||||
import { monitorStatusReducer } from './monitor_status';
|
||||
import { monitorListReducer } from './monitor_list';
|
||||
|
@ -28,7 +27,6 @@ import { networkEventsReducer } from './network_events';
|
|||
export const rootReducer = combineReducers({
|
||||
monitor: monitorReducer,
|
||||
overviewFilters: overviewFiltersReducer,
|
||||
snapshot: snapshotReducer,
|
||||
ui: uiReducer,
|
||||
monitorList: monitorListReducer,
|
||||
monitorStatus: monitorStatusReducer,
|
||||
|
|
|
@ -11,7 +11,6 @@ import {
|
|||
fetchOverviewFilters,
|
||||
fetchOverviewFiltersFail,
|
||||
fetchOverviewFiltersSuccess,
|
||||
setOverviewFilters,
|
||||
GetOverviewFiltersPayload,
|
||||
OverviewFiltersPayload,
|
||||
} from '../actions';
|
||||
|
@ -51,11 +50,6 @@ export const overviewFiltersReducer = handleActions<OverviewFiltersState, Overvi
|
|||
errors: [...state.errors, action.payload],
|
||||
loading: false,
|
||||
}),
|
||||
|
||||
[String(setOverviewFilters)]: (state, action: Action<OverviewFilters>) => ({
|
||||
...state,
|
||||
filters: action.payload,
|
||||
}),
|
||||
},
|
||||
initialState
|
||||
);
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
/*
|
||||
* 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 { snapshotReducer } from './snapshot';
|
||||
import { getSnapshotCountAction } from '../actions';
|
||||
import { IHttpFetchError } from '../../../../../../src/core/public';
|
||||
|
||||
describe('snapshot reducer', () => {
|
||||
it('updates existing state', () => {
|
||||
const action = getSnapshotCountAction.get({
|
||||
dateRangeStart: 'now-15m',
|
||||
dateRangeEnd: 'now',
|
||||
filters: 'foo: bar',
|
||||
});
|
||||
expect(
|
||||
snapshotReducer(
|
||||
{
|
||||
count: { down: 1, total: 4, up: 3 },
|
||||
errors: [],
|
||||
loading: false,
|
||||
},
|
||||
action
|
||||
)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it(`sets the state's status to loading during a fetch`, () => {
|
||||
const action = getSnapshotCountAction.get({
|
||||
dateRangeStart: 'now-15m',
|
||||
dateRangeEnd: 'now',
|
||||
});
|
||||
expect(snapshotReducer(undefined, action)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('changes the count when a snapshot fetch succeeds', () => {
|
||||
const action = getSnapshotCountAction.success({
|
||||
up: 10,
|
||||
down: 15,
|
||||
total: 25,
|
||||
});
|
||||
|
||||
expect(snapshotReducer(undefined, action)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('appends a current error to existing errors list', () => {
|
||||
const action = getSnapshotCountAction.fail(
|
||||
new Error(`I couldn't get your data because the server denied the request`) as IHttpFetchError
|
||||
);
|
||||
|
||||
expect(snapshotReducer(undefined, action)).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -1,49 +0,0 @@
|
|||
/*
|
||||
* 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 { Action } from 'redux-actions';
|
||||
import { Snapshot } from '../../../common/runtime_types';
|
||||
import { getSnapshotCountAction } from '../actions';
|
||||
|
||||
export interface SnapshotState {
|
||||
count: Snapshot;
|
||||
errors: any[];
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
const initialState: SnapshotState = {
|
||||
count: {
|
||||
down: 0,
|
||||
total: 0,
|
||||
up: 0,
|
||||
},
|
||||
errors: [],
|
||||
loading: false,
|
||||
};
|
||||
|
||||
export function snapshotReducer(state = initialState, action: Action<any>): SnapshotState {
|
||||
switch (action.type) {
|
||||
case String(getSnapshotCountAction.get):
|
||||
return {
|
||||
...state,
|
||||
loading: true,
|
||||
};
|
||||
case String(getSnapshotCountAction.success):
|
||||
return {
|
||||
...state,
|
||||
count: action.payload,
|
||||
loading: false,
|
||||
};
|
||||
case String(getSnapshotCountAction.fail):
|
||||
return {
|
||||
...state,
|
||||
errors: [...state.errors, action.payload],
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
|
@ -31,15 +31,6 @@ describe('state selectors', () => {
|
|||
loading: false,
|
||||
errors: [],
|
||||
},
|
||||
snapshot: {
|
||||
count: {
|
||||
up: 2,
|
||||
down: 0,
|
||||
total: 2,
|
||||
},
|
||||
errors: [],
|
||||
loading: false,
|
||||
},
|
||||
ui: {
|
||||
alertFlyoutVisible: false,
|
||||
basePath: 'yyz',
|
||||
|
|
|
@ -35,8 +35,6 @@ export const selectPingHistogram = ({ ping }: AppState) => ping;
|
|||
|
||||
export const selectPingList = ({ pingList }: AppState) => pingList;
|
||||
|
||||
export const snapshotDataSelector = ({ snapshot }: AppState) => snapshot;
|
||||
|
||||
export const mlCapabilitiesSelector = (state: AppState) => state.ml.mlCapabilities;
|
||||
|
||||
export const hasMLFeatureSelector = createSelector(
|
||||
|
|
|
@ -188,9 +188,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
});
|
||||
|
||||
it('can change query syntax to kql', async () => {
|
||||
await testSubjects.click('syntaxChangeToKql');
|
||||
await testSubjects.click('toggleKqlSyntax');
|
||||
await testSubjects.exists('syntaxChangeToSimple');
|
||||
await testSubjects.click('switchQueryLanguageButton');
|
||||
await testSubjects.click('languageToggle');
|
||||
});
|
||||
|
||||
it('runs filter query without issues', async () => {
|
||||
|
|
|
@ -42,7 +42,7 @@ export function UptimeCommonProvider({ getService, getPageObjects }: FtrProvider
|
|||
await browser.pressKeys(browser.keys.ENTER);
|
||||
},
|
||||
async setFilterText(filterQuery: string) {
|
||||
await this.setKueryBarText('xpack.uptime.filterBar', filterQuery);
|
||||
await this.setKueryBarText('queryInput', filterQuery);
|
||||
},
|
||||
async goToNextPage() {
|
||||
await testSubjects.click('xpack.uptime.monitorList.nextButton', 5000);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue