mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
# Backport This will backport the following commits from `main` to `8.6`: - [[Actionable observability] Validate alert search bar query parameters (#145369)](https://github.com/elastic/kibana/pull/145369) <!--- Backport version: 8.9.7 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Maryam Saeidi","email":"maryam.saeidi@elastic.co"},"sourceCommit":{"committedDate":"2022-11-18T10:08:03Z","message":"[Actionable observability] Validate alert search bar query parameters (#145369)\n\nFixes #144911, #143640\r\n\r\n## 📝 Summary\r\n\r\n- Validate alert search bar query params using io-ts\r\n- Make sure that we only enter one history for changes related to the\r\nalert search bar\r\n- Make sure to not save tab changes on the rule details page in the\r\nhistory\r\n- Use `DatePickerContextProvider` only for `overview` page to fix 143640\r\n\r\n## 🧪 How to test\r\n- In the alerts page URL, you should not see\r\n`rangeFrom=now-15m&rangeTo=now` query parameters anymore\r\n- Changing the alerts' page URL to an invalid one should not break the\r\npage anymore\r\n - Example: `_a=(kuery:%27%27,rangeFrom:now-15m,rangeTo:12)`\r\n- By changing alert search bar information, you should not see new\r\nrecords added to the history\r\n- Same for rule details page (either changing tab or alert search bar\r\nparameters)","sha":"42a1062000ca4dd5e827a97aac0134a8ad8f895a","branchLabelMapping":{"^v8.7.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["bug","release_note:skip","Team: Actionable Observability","v8.6.0","v8.7.0"],"number":145369,"url":"https://github.com/elastic/kibana/pull/145369","mergeCommit":{"message":"[Actionable observability] Validate alert search bar query parameters (#145369)\n\nFixes #144911, #143640\r\n\r\n## 📝 Summary\r\n\r\n- Validate alert search bar query params using io-ts\r\n- Make sure that we only enter one history for changes related to the\r\nalert search bar\r\n- Make sure to not save tab changes on the rule details page in the\r\nhistory\r\n- Use `DatePickerContextProvider` only for `overview` page to fix 143640\r\n\r\n## 🧪 How to test\r\n- In the alerts page URL, you should not see\r\n`rangeFrom=now-15m&rangeTo=now` query parameters anymore\r\n- Changing the alerts' page URL to an invalid one should not break the\r\npage anymore\r\n - Example: `_a=(kuery:%27%27,rangeFrom:now-15m,rangeTo:12)`\r\n- By changing alert search bar information, you should not see new\r\nrecords added to the history\r\n- Same for rule details page (either changing tab or alert search bar\r\nparameters)","sha":"42a1062000ca4dd5e827a97aac0134a8ad8f895a"}},"sourceBranch":"main","suggestedTargetBranches":["8.6"],"targetPullRequestStates":[{"branch":"8.6","label":"v8.6.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.7.0","labelRegex":"^v8.7.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/145369","number":145369,"mergeCommit":{"message":"[Actionable observability] Validate alert search bar query parameters (#145369)\n\nFixes #144911, #143640\r\n\r\n## 📝 Summary\r\n\r\n- Validate alert search bar query params using io-ts\r\n- Make sure that we only enter one history for changes related to the\r\nalert search bar\r\n- Make sure to not save tab changes on the rule details page in the\r\nhistory\r\n- Use `DatePickerContextProvider` only for `overview` page to fix 143640\r\n\r\n## 🧪 How to test\r\n- In the alerts page URL, you should not see\r\n`rangeFrom=now-15m&rangeTo=now` query parameters anymore\r\n- Changing the alerts' page URL to an invalid one should not break the\r\npage anymore\r\n - Example: `_a=(kuery:%27%27,rangeFrom:now-15m,rangeTo:12)`\r\n- By changing alert search bar information, you should not see new\r\nrecords added to the history\r\n- Same for rule details page (either changing tab or alert search bar\r\nparameters)","sha":"42a1062000ca4dd5e827a97aac0134a8ad8f895a"}}]}] BACKPORT--> Co-authored-by: Maryam Saeidi <maryam.saeidi@elastic.co>
This commit is contained in:
parent
d9d8b15a90
commit
57318d2d7e
11 changed files with 136 additions and 28 deletions
8
x-pack/plugins/observability/common/constants.ts
Normal file
8
x-pack/plugins/observability/common/constants.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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 const ALERT_STATUS_ALL = 'all';
|
|
@ -4,10 +4,12 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import * as t from 'io-ts';
|
||||
import { ALERT_STATUS_ACTIVE, ALERT_STATUS_RECOVERED } from '@kbn/rule-data-utils';
|
||||
import { ALERT_STATUS_ALL } from './constants';
|
||||
|
||||
export type Maybe<T> = T | null | undefined;
|
||||
import { ALERT_STATUS_ACTIVE, ALERT_STATUS_RECOVERED } from '@kbn/rule-data-utils';
|
||||
|
||||
export const alertWorkflowStatusRt = t.keyof({
|
||||
open: null,
|
||||
|
@ -27,7 +29,10 @@ export interface ApmIndicesConfig {
|
|||
apmCustomLinkIndex: string;
|
||||
}
|
||||
|
||||
export type AlertStatus = typeof ALERT_STATUS_ACTIVE | typeof ALERT_STATUS_RECOVERED | '';
|
||||
export type AlertStatus =
|
||||
| typeof ALERT_STATUS_ACTIVE
|
||||
| typeof ALERT_STATUS_RECOVERED
|
||||
| typeof ALERT_STATUS_ALL;
|
||||
|
||||
export interface AlertStatusFilter {
|
||||
status: AlertStatus;
|
||||
|
|
|
@ -19,7 +19,6 @@ import {
|
|||
import { Storage } from '@kbn/kibana-utils-plugin/public';
|
||||
import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
|
||||
import type { LazyObservabilityPageTemplateProps } from '../components/shared/page_template/lazy_page_template';
|
||||
import { DatePickerContextProvider } from '../context/date_picker_context';
|
||||
import { HasDataContextProvider } from '../context/has_data_context';
|
||||
import { PluginContext } from '../context/plugin_context';
|
||||
import { useRouteParams } from '../hooks/use_route_params';
|
||||
|
@ -98,11 +97,9 @@ export const renderApp = ({
|
|||
<EuiThemeProvider darkMode={isDarkMode}>
|
||||
<i18nCore.Context>
|
||||
<RedirectAppLinks application={core.application} className={APP_WRAPPER_CLASS}>
|
||||
<DatePickerContextProvider>
|
||||
<HasDataContextProvider>
|
||||
<App />
|
||||
</HasDataContextProvider>
|
||||
</DatePickerContextProvider>
|
||||
<HasDataContextProvider>
|
||||
<App />
|
||||
</HasDataContextProvider>
|
||||
</RedirectAppLinks>
|
||||
</i18nCore.Context>
|
||||
</EuiThemeProvider>
|
||||
|
|
|
@ -20,7 +20,9 @@ import { buildEsQuery } from '../../../utils/build_es_query';
|
|||
import { AlertStatus } from '../../../../common/typings';
|
||||
|
||||
const getAlertStatusQuery = (status: string): Query[] => {
|
||||
return status ? [{ query: ALERT_STATUS_QUERY[status], language: 'kuery' }] : [];
|
||||
return ALERT_STATUS_QUERY[status]
|
||||
? [{ query: ALERT_STATUS_QUERY[status], language: 'kuery' }]
|
||||
: [];
|
||||
};
|
||||
|
||||
export function ObservabilityAlertSearchBar({
|
||||
|
|
|
@ -9,12 +9,13 @@ import { Query } from '@kbn/es-query';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { ALERT_STATUS_ACTIVE, ALERT_STATUS_RECOVERED, ALERT_STATUS } from '@kbn/rule-data-utils';
|
||||
import { AlertStatusFilter } from '../../../../common/typings';
|
||||
import { ALERT_STATUS_ALL } from '../../../../common/constants';
|
||||
|
||||
export const DEFAULT_QUERIES: Query[] = [];
|
||||
export const DEFAULT_QUERY_STRING = '';
|
||||
|
||||
export const ALL_ALERTS: AlertStatusFilter = {
|
||||
status: '',
|
||||
status: ALERT_STATUS_ALL,
|
||||
query: '',
|
||||
label: i18n.translate('xpack.observability.alerts.alertStatusFilter.showAll', {
|
||||
defaultMessage: 'Show all',
|
||||
|
|
|
@ -38,7 +38,7 @@ const defaultState: AlertSearchBarContainerState = {
|
|||
rangeFrom: 'now-15m',
|
||||
rangeTo: 'now',
|
||||
kuery: '',
|
||||
status: ALL_ALERTS.status as AlertStatus,
|
||||
status: ALL_ALERTS.status,
|
||||
};
|
||||
|
||||
const transitions: AlertSearchBarStateTransitions = {
|
||||
|
|
|
@ -5,9 +5,12 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { isRight } from 'fp-ts/Either';
|
||||
import { pipe } from 'fp-ts/pipeable';
|
||||
import * as t from 'io-ts';
|
||||
import { useEffect } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
import { ALERT_STATUS_ACTIVE, ALERT_STATUS_RECOVERED } from '@kbn/rule-data-utils';
|
||||
import { TimefilterContract } from '@kbn/data-plugin/public';
|
||||
import {
|
||||
createKbnUrlStateStorage,
|
||||
|
@ -15,6 +18,8 @@ import {
|
|||
IKbnUrlStateStorage,
|
||||
useContainerSelector,
|
||||
} from '@kbn/kibana-utils-plugin/public';
|
||||
import { datemathStringRT } from '../../../../utils/datemath';
|
||||
import { ALERT_STATUS_ALL } from '../../../../../common/constants';
|
||||
import { useTimefilterService } from '../../../../hooks/use_timefilter_service';
|
||||
|
||||
import {
|
||||
|
@ -24,6 +29,17 @@ import {
|
|||
AlertSearchBarContainerState,
|
||||
} from './state_container';
|
||||
|
||||
export const alertSearchBarState = t.partial({
|
||||
rangeFrom: datemathStringRT,
|
||||
rangeTo: datemathStringRT,
|
||||
kuery: t.string,
|
||||
status: t.union([
|
||||
t.literal(ALERT_STATUS_ACTIVE),
|
||||
t.literal(ALERT_STATUS_RECOVERED),
|
||||
t.literal(ALERT_STATUS_ALL),
|
||||
]),
|
||||
});
|
||||
|
||||
export function useAlertSearchBarStateContainer(urlStorageKey: string) {
|
||||
const stateContainer = useContainer();
|
||||
|
||||
|
@ -77,7 +93,7 @@ function useUrlStateSyncEffect(
|
|||
|
||||
function setupUrlStateSync(
|
||||
stateContainer: AlertSearchBarStateContainer,
|
||||
stateStorage: IKbnUrlStateStorage,
|
||||
urlStateStorage: IKbnUrlStateStorage,
|
||||
urlStorageKey: string
|
||||
) {
|
||||
// This handles filling the state when an incomplete URL set is provided
|
||||
|
@ -91,7 +107,11 @@ function setupUrlStateSync(
|
|||
...stateContainer,
|
||||
set: setWithDefaults,
|
||||
},
|
||||
stateStorage,
|
||||
stateStorage: {
|
||||
...urlStateStorage,
|
||||
set: <AlertSearchBarStateContainer,>(key: string, state: AlertSearchBarStateContainer) =>
|
||||
urlStateStorage.set(key, state, { replace: true }),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -101,12 +121,14 @@ function syncUrlStateWithInitialContainerState(
|
|||
urlStateStorage: IKbnUrlStateStorage,
|
||||
urlStorageKey: string
|
||||
) {
|
||||
const urlState = urlStateStorage.get<Partial<AlertSearchBarContainerState>>(urlStorageKey);
|
||||
const urlState = alertSearchBarState.decode(
|
||||
urlStateStorage.get<Partial<AlertSearchBarContainerState>>(urlStorageKey)
|
||||
);
|
||||
|
||||
if (urlState) {
|
||||
if (isRight(urlState)) {
|
||||
const newState = {
|
||||
...defaultState,
|
||||
...urlState,
|
||||
...pipe(urlState).right,
|
||||
};
|
||||
|
||||
stateContainer.set(newState);
|
||||
|
@ -125,5 +147,7 @@ function syncUrlStateWithInitialContainerState(
|
|||
stateContainer.set(defaultState);
|
||||
}
|
||||
|
||||
urlStateStorage.set(urlStorageKey, stateContainer.get());
|
||||
urlStateStorage.set(urlStorageKey, stateContainer.get(), {
|
||||
replace: true,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -92,9 +92,10 @@ export function RuleDetailsPage() {
|
|||
const { ruleTypes } = useLoadRuleTypes({
|
||||
filteredRuleTypes,
|
||||
});
|
||||
const [tabId, setTabId] = useState<TabId>(
|
||||
(toQuery(location.search)?.tabId as TabId) || EXECUTION_TAB
|
||||
);
|
||||
const [tabId, setTabId] = useState<TabId>(() => {
|
||||
const urlTabId = (toQuery(location.search)?.tabId as TabId) || EXECUTION_TAB;
|
||||
return [EXECUTION_TAB, ALERTS_TAB].includes(urlTabId) ? urlTabId : EXECUTION_TAB;
|
||||
});
|
||||
const [features, setFeatures] = useState<string>('');
|
||||
const [ruleType, setRuleType] = useState<RuleType<string, string>>();
|
||||
const [ruleToDelete, setRuleToDelete] = useState<string[]>([]);
|
||||
|
@ -107,12 +108,18 @@ export function RuleDetailsPage() {
|
|||
] as Query[]);
|
||||
|
||||
const updateUrl = (nextQuery: { tabId: TabId }) => {
|
||||
history.push({
|
||||
const newTabId = nextQuery.tabId;
|
||||
const nextSearch =
|
||||
newTabId === ALERTS_TAB
|
||||
? {
|
||||
...toQuery(location.search),
|
||||
...nextQuery,
|
||||
}
|
||||
: { tabId: EXECUTION_TAB };
|
||||
|
||||
history.replace({
|
||||
...location,
|
||||
search: fromQuery({
|
||||
...toQuery(location.search),
|
||||
...nextQuery,
|
||||
}),
|
||||
search: fromQuery(nextSearch),
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -223,7 +230,7 @@ export function RuleDetailsPage() {
|
|||
<EuiSpacer size="s" />
|
||||
<EuiFlexGroup style={{ minHeight: 450 }} direction={'column'}>
|
||||
<EuiFlexItem>
|
||||
{esQuery && (
|
||||
{esQuery && features && (
|
||||
<AlertsStateTable
|
||||
alertsTableConfigurationRegistry={alertsTableConfigurationRegistry}
|
||||
configurationId={observabilityFeatureId}
|
||||
|
|
|
@ -19,6 +19,7 @@ import { RulesPage } from '../pages/rules';
|
|||
import { RuleDetailsPage } from '../pages/rule_details';
|
||||
import { AlertingPages } from '../config';
|
||||
import { AlertDetails } from '../pages/alert_details';
|
||||
import { DatePickerContextProvider } from '../context/date_picker_context';
|
||||
|
||||
export type RouteParams<T extends keyof typeof routes> = DecodeParams<typeof routes[T]['params']>;
|
||||
|
||||
|
@ -56,7 +57,11 @@ export const routes = {
|
|||
},
|
||||
'/overview': {
|
||||
handler: ({ query }: any) => {
|
||||
return <OverviewPage />;
|
||||
return (
|
||||
<DatePickerContextProvider>
|
||||
<OverviewPage />
|
||||
</DatePickerContextProvider>
|
||||
);
|
||||
},
|
||||
params: {},
|
||||
exact: true,
|
||||
|
|
29
x-pack/plugins/observability/public/utils/datemath.test.ts
Normal file
29
x-pack/plugins/observability/public/utils/datemath.test.ts
Normal file
|
@ -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 { isValidDatemath } from './datemath';
|
||||
|
||||
describe('isValidDatemath()', () => {
|
||||
it('Returns `false` for empty strings', () => {
|
||||
expect(isValidDatemath('')).toBe(false);
|
||||
});
|
||||
|
||||
it('Returns `false` for invalid strings', () => {
|
||||
expect(isValidDatemath('wadus')).toBe(false);
|
||||
expect(isValidDatemath('nowww-')).toBe(false);
|
||||
expect(isValidDatemath('now-')).toBe(false);
|
||||
expect(isValidDatemath('now-1')).toBe(false);
|
||||
expect(isValidDatemath('now-1d/')).toBe(false);
|
||||
});
|
||||
|
||||
it('Returns `true` for valid strings', () => {
|
||||
expect(isValidDatemath('now')).toBe(true);
|
||||
expect(isValidDatemath('now-1d')).toBe(true);
|
||||
expect(isValidDatemath('now-1d/d')).toBe(true);
|
||||
expect(isValidDatemath('2022-11-09T09:37:10.481Z')).toBe(true);
|
||||
});
|
||||
});
|
30
x-pack/plugins/observability/public/utils/datemath.ts
Normal file
30
x-pack/plugins/observability/public/utils/datemath.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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 dateMath from '@kbn/datemath';
|
||||
import { chain } from 'fp-ts/Either';
|
||||
import { pipe } from 'fp-ts/pipeable';
|
||||
import * as r from 'io-ts';
|
||||
|
||||
// Copied from x-pack/plugins/infra/public/utils/datemath.ts
|
||||
export function isValidDatemath(value: string): boolean {
|
||||
const parsedValue = dateMath.parse(value);
|
||||
return !!(parsedValue && parsedValue.isValid());
|
||||
}
|
||||
|
||||
export const datemathStringRT = new r.Type<string, string, unknown>(
|
||||
'datemath',
|
||||
r.string.is,
|
||||
(value, context) =>
|
||||
pipe(
|
||||
r.string.validate(value, context),
|
||||
chain((stringValue) =>
|
||||
isValidDatemath(stringValue) ? r.success(stringValue) : r.failure(stringValue, context)
|
||||
)
|
||||
),
|
||||
String
|
||||
);
|
Loading…
Add table
Add a link
Reference in a new issue