mirror of
https://github.com/Graylog2/graylog2-server.git
synced 2026-03-13 09:32:21 +08:00
Improve reusability of event definition wizard. (#18731)
* Unify event definition type. * Remove redundant event definition type. * Create component for event definition form buttons. * Make event definition form controls pluggable. * Fixing linter hint. * Default to first step when in security mode * Fix duplicate in event definition form (#18722) * register event definition export globally - EventDefinitionsBindings, EventNotificationsBindings, FieldValueProvidersBindings * fix types * Fix search query being erased while typing * fix test * fix types * Make initial step configurable. * Only set URL query param `step` when necessary. * Refactor from controls. * Update props: * Reimplement `onChange` in `FilterForm` to fix problem with params. * make embryonic only constrained to FilterForm * Fixing linter hints. --------- Co-authored-by: Simon Huang <simon.huang@graylog.com> Co-authored-by: Ousmane SAMBA <ousmane@graylog.com> Co-authored-by: Ousmane Samba <ousmanesamba@gmail.com>
This commit is contained in:
@@ -137,13 +137,30 @@ type DataTiering = {
|
||||
}>,
|
||||
}
|
||||
|
||||
type FieldValueProvider = {
|
||||
type: string,
|
||||
displayName: string,
|
||||
formComponent: React.ComponentType,
|
||||
summaryComponent: React.ComponentType,
|
||||
defaultConfig: {
|
||||
template?: string,
|
||||
table_name?: string,
|
||||
key_field?: string,
|
||||
},
|
||||
requiredFields: {
|
||||
template?: string,
|
||||
table_name?: string,
|
||||
key_field?: string,
|
||||
},
|
||||
}
|
||||
declare module 'graylog-web-plugin/plugin' {
|
||||
interface PluginExports {
|
||||
navigation?: Array<PluginNavigation>;
|
||||
dataTiering?: Array<DataTiering>
|
||||
defaultNavigation?: Array<PluginNavigation>;
|
||||
navigationItems?: Array<PluginNavigationItems>;
|
||||
globalNotifications?: Array<GlobalNotification>
|
||||
globalNotifications?: Array<GlobalNotification>;
|
||||
fieldValueProviders?:Array<FieldValueProvider>;
|
||||
// Global context providers allow to fetch and process data once
|
||||
// and provide the result for all components in your plugin.
|
||||
globalContextProviders?: Array<React.ComponentType<React.PropsWithChildrean<{}>>>,
|
||||
|
||||
@@ -25,10 +25,10 @@ import { Select } from 'components/common';
|
||||
import { Clearfix, Col, ControlLabel, FormGroup, HelpBlock, Row } from 'components/bootstrap';
|
||||
import { HelpPanel } from 'components/event-definitions/common/HelpPanel';
|
||||
import type User from 'logic/users/User';
|
||||
import { getPathnameWithoutId } from 'util/URLUtils';
|
||||
import useSendTelemetry from 'logic/telemetry/useSendTelemetry';
|
||||
import useLocation from 'routing/useLocation';
|
||||
import { TELEMETRY_EVENT_TYPE } from 'logic/telemetry/Constants';
|
||||
import { getPathnameWithoutId } from 'util/URLUtils';
|
||||
|
||||
import styles from './EventConditionForm.css';
|
||||
|
||||
@@ -143,9 +143,10 @@ const EventConditionForm = ({ action, entityTypes, eventDefinition, validation,
|
||||
Configure how Graylog should create Events of this kind. You can later use those Events as input on other
|
||||
Conditions, making it possible to build powerful Conditions based on others.
|
||||
</p>
|
||||
<FormGroup controlId="event-definition-priority" validationState={validation.errors.config ? 'error' : null}>
|
||||
<ControlLabel>Condition Type</ControlLabel>
|
||||
<FormGroup validationState={validation.errors.config ? 'error' : null}>
|
||||
<ControlLabel htmlFor="event-condition-type-select">Condition Type</ControlLabel>
|
||||
<Select placeholder="Select a Condition Type"
|
||||
inputId="event-condition-type-select"
|
||||
options={formattedEventDefinitionTypes()}
|
||||
value={eventDefinition.config.type}
|
||||
onChange={handleEventDefinitionTypeChange}
|
||||
|
||||
@@ -15,26 +15,22 @@
|
||||
* <http://www.mongodb.com/licensing/server-side-public-license>.
|
||||
*/
|
||||
import type { SyntheticEvent } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import * as React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import last from 'lodash/last';
|
||||
import defaultTo from 'lodash/defaultTo';
|
||||
import { PluginStore } from 'graylog-web-plugin/plugin';
|
||||
import styled from 'styled-components';
|
||||
import URI from 'urijs';
|
||||
import QS from 'qs';
|
||||
|
||||
import { getPathnameWithoutId } from 'util/URLUtils';
|
||||
import { Button, Col, Row } from 'components/bootstrap';
|
||||
import { ModalSubmit, Wizard } from 'components/common';
|
||||
import { Col, Row } from 'components/bootstrap';
|
||||
import { Wizard } from 'components/common';
|
||||
import type { EventNotification } from 'stores/event-notifications/EventNotificationsStore';
|
||||
import type { EventDefinition } from 'components/event-definitions/event-definitions-types';
|
||||
import type { EventDefinition, EventDefinitionFormControlsProps } from 'components/event-definitions/event-definitions-types';
|
||||
import type User from 'logic/users/User';
|
||||
import useQuery from 'routing/useQuery';
|
||||
import useHistory from 'routing/useHistory';
|
||||
import useSendTelemetry from 'logic/telemetry/useSendTelemetry';
|
||||
import useLocation from 'routing/useLocation';
|
||||
import { TELEMETRY_EVENT_TYPE } from 'logic/telemetry/Constants';
|
||||
import EventDefinitionFormControls from 'components/event-definitions/event-definition-form/EventDefinitionFormControls';
|
||||
|
||||
import EventDetailsForm from './EventDetailsForm';
|
||||
import EventConditionForm from './EventConditionForm';
|
||||
@@ -42,7 +38,10 @@ import FieldsForm from './FieldsForm';
|
||||
import NotificationsForm from './NotificationsForm';
|
||||
import EventDefinitionSummary from './EventDefinitionSummary';
|
||||
|
||||
const STEP_KEYS = ['event-details', 'condition', 'fields', 'notifications', 'summary'];
|
||||
const WizardContainer = styled.div`
|
||||
margin-bottom: 10px;
|
||||
`;
|
||||
export const STEP_KEYS = ['event-details', 'condition', 'fields', 'notifications', 'summary'];
|
||||
const STEP_TELEMETRY_KEYS = [
|
||||
TELEMETRY_EVENT_TYPE.EVENTDEFINITION_DETAILS.STEP_CLICKED,
|
||||
TELEMETRY_EVENT_TYPE.EVENTDEFINITION_CONDITION.STEP_CLICKED,
|
||||
@@ -59,11 +58,8 @@ const getConditionPlugin = (type: string | undefined) => {
|
||||
return PluginStore.exports('eventDefinitionTypes').find((edt) => edt.type === type) || {};
|
||||
};
|
||||
|
||||
const WizardContainer = styled.div`
|
||||
margin-bottom: 10px;
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
activeStep: string,
|
||||
action: 'edit' | 'create',
|
||||
eventDefinition: EventDefinition,
|
||||
currentUser: User,
|
||||
@@ -77,48 +73,40 @@ type Props = {
|
||||
notifications: Array<EventNotification>,
|
||||
defaults: { default_backlog_size: number },
|
||||
onChange: (key: string, value: unknown) => void,
|
||||
onChangeStep: (step: string) => void,
|
||||
onCancel: () => void,
|
||||
onSubmit: () => void
|
||||
canEdit: boolean,
|
||||
formControls?: React.ComponentType<EventDefinitionFormControlsProps>
|
||||
}
|
||||
|
||||
const EventDefinitionForm = ({
|
||||
action,
|
||||
eventDefinition,
|
||||
currentUser,
|
||||
validation,
|
||||
entityTypes,
|
||||
notifications,
|
||||
defaults,
|
||||
onChange,
|
||||
onCancel,
|
||||
onSubmit,
|
||||
activeStep,
|
||||
canEdit,
|
||||
currentUser,
|
||||
defaults,
|
||||
entityTypes,
|
||||
eventDefinition,
|
||||
formControls: FormControls,
|
||||
notifications,
|
||||
onCancel,
|
||||
onChange,
|
||||
onChangeStep,
|
||||
onSubmit,
|
||||
validation,
|
||||
}: Props) => {
|
||||
const { step } = useQuery();
|
||||
const [activeStep, setActiveStep] = useState(step as string || STEP_KEYS[0]);
|
||||
const history = useHistory();
|
||||
const { pathname } = useLocation();
|
||||
const sendTelemetry = useSendTelemetry();
|
||||
|
||||
useEffect(() => {
|
||||
const currentUrl = new URI(window.location.href);
|
||||
const queryParameters = QS.parse(currentUrl.query());
|
||||
|
||||
if (queryParameters.step !== activeStep) {
|
||||
const newUrl = currentUrl.removeSearch('step').addQuery('step', activeStep);
|
||||
history.replace(newUrl.resource());
|
||||
}
|
||||
}, [activeStep, history]);
|
||||
const activeStepIndex = STEP_KEYS.indexOf(activeStep);
|
||||
|
||||
const handleSubmit = (event: SyntheticEvent) => {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
if (activeStep === last(STEP_KEYS)) {
|
||||
onSubmit();
|
||||
}
|
||||
onSubmit();
|
||||
};
|
||||
|
||||
const defaultStepProps = {
|
||||
@@ -170,7 +158,7 @@ const EventDefinitionForm = ({
|
||||
},
|
||||
];
|
||||
|
||||
const handleStepChange = (nextStep) => {
|
||||
const handleStepChange = (nextStep: string) => {
|
||||
sendTelemetry(STEP_TELEMETRY_KEYS[STEP_KEYS.indexOf(nextStep)], {
|
||||
app_pathname: getPathnameWithoutId(pathname),
|
||||
app_section: (action === 'create') ? 'new-event-definition' : 'edit-event-definition',
|
||||
@@ -178,59 +166,31 @@ const EventDefinitionForm = ({
|
||||
current_step: steps[STEP_KEYS.indexOf(activeStep)].title,
|
||||
});
|
||||
|
||||
setActiveStep(nextStep);
|
||||
onChangeStep(nextStep);
|
||||
};
|
||||
|
||||
const renderButtons = () => {
|
||||
if (activeStep === last(STEP_KEYS)) {
|
||||
return (
|
||||
<ModalSubmit onCancel={onCancel}
|
||||
onSubmit={handleSubmit}
|
||||
submitButtonText={`${eventDefinition.id ? 'Update' : 'Create'} event definition`} />
|
||||
);
|
||||
}
|
||||
const openPrevPage = () => {
|
||||
sendTelemetry(TELEMETRY_EVENT_TYPE.EVENTDEFINITION_PREVIOUS_CLICKED, {
|
||||
app_pathname: getPathnameWithoutId(pathname),
|
||||
app_section: (action === 'create') ? 'new-event-definition' : 'edit-event-definition',
|
||||
app_action_value: 'previous-button',
|
||||
current_step: steps[activeStepIndex].title,
|
||||
});
|
||||
|
||||
const activeStepIndex = STEP_KEYS.indexOf(activeStep);
|
||||
const previousStep = activeStepIndex > 0 ? STEP_KEYS[activeStepIndex - 1] : undefined;
|
||||
onChangeStep(previousStep);
|
||||
};
|
||||
|
||||
const handlePreviousClick = () => {
|
||||
sendTelemetry(TELEMETRY_EVENT_TYPE.EVENTDEFINITION_PREVIOUS_CLICKED, {
|
||||
app_pathname: getPathnameWithoutId(pathname),
|
||||
app_section: (action === 'create') ? 'new-event-definition' : 'edit-event-definition',
|
||||
app_action_value: 'previous-button',
|
||||
current_step: steps[activeStepIndex].title,
|
||||
});
|
||||
const openNextPage = () => {
|
||||
sendTelemetry(TELEMETRY_EVENT_TYPE.EVENTDEFINITION_NEXT_CLICKED, {
|
||||
app_pathname: getPathnameWithoutId(pathname),
|
||||
app_section: (action === 'create') ? 'new-event-definition' : 'edit-event-definition',
|
||||
app_action_value: 'next-button',
|
||||
current_step: steps[activeStepIndex].title,
|
||||
});
|
||||
|
||||
const previousStep = activeStepIndex > 0 ? STEP_KEYS[activeStepIndex - 1] : undefined;
|
||||
setActiveStep(previousStep);
|
||||
};
|
||||
|
||||
const handleNextClick = () => {
|
||||
sendTelemetry(TELEMETRY_EVENT_TYPE.EVENTDEFINITION_NEXT_CLICKED, {
|
||||
app_pathname: getPathnameWithoutId(pathname),
|
||||
app_section: (action === 'create') ? 'new-event-definition' : 'edit-event-definition',
|
||||
app_action_value: 'next-button',
|
||||
current_step: steps[activeStepIndex].title,
|
||||
});
|
||||
|
||||
const nextStep = STEP_KEYS[activeStepIndex + 1];
|
||||
setActiveStep(nextStep);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Button bsStyle="info"
|
||||
onClick={handlePreviousClick}
|
||||
disabled={activeStepIndex === 0}>
|
||||
Previous
|
||||
</Button>
|
||||
<div className="pull-right">
|
||||
<Button bsStyle="info"
|
||||
onClick={handleNextClick}>
|
||||
Next
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
const nextStep = STEP_KEYS[activeStepIndex + 1];
|
||||
onChangeStep(nextStep);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -245,7 +205,13 @@ const EventDefinitionForm = ({
|
||||
containerClassName=""
|
||||
hidePreviousNextButtons />
|
||||
</WizardContainer>
|
||||
{renderButtons()}
|
||||
<FormControls activeStepIndex={activeStepIndex}
|
||||
action={action}
|
||||
onOpenPrevPage={openPrevPage}
|
||||
onOpenNextPage={openNextPage}
|
||||
steps={steps}
|
||||
onSubmit={handleSubmit}
|
||||
onCancel={onCancel} />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
@@ -266,6 +232,7 @@ EventDefinitionForm.propTypes = {
|
||||
|
||||
EventDefinitionForm.defaultProps = {
|
||||
action: 'edit',
|
||||
formControls: EventDefinitionFormControls,
|
||||
};
|
||||
|
||||
export default EventDefinitionForm;
|
||||
|
||||
@@ -194,6 +194,12 @@ jest.mock('logic/telemetry/useSendTelemetry');
|
||||
jest.mock('hooks/useScopePermissions');
|
||||
jest.mock('hooks/useCurrentUser');
|
||||
|
||||
jest.mock('components/perspectives/hooks/useActivePerspective', () => () => ({
|
||||
id: 'security',
|
||||
title: 'Security',
|
||||
welcomeRoute: '/security',
|
||||
}));
|
||||
|
||||
describe('EventDefinitionFormContainer', () => {
|
||||
beforeEach(() => {
|
||||
asMock(useLocation).mockImplementation(() => ({ pathname: '/event-definitions', search: '', hash: '', state: null, key: 'mock-key' }));
|
||||
@@ -226,18 +232,18 @@ describe('EventDefinitionFormContainer', () => {
|
||||
|
||||
it('should render Filters & Aggregation form enabled', async () => {
|
||||
render(<EventDefinitionFormContainer action="edit" eventDefinition={mockAggregationEventDefinition} />);
|
||||
|
||||
const tab = await screen.findByRole('button', { name: /filter & aggregation/i });
|
||||
const tab = await screen.findByRole('button', { name: /condition/i });
|
||||
userEvent.click(tab);
|
||||
const conditionTypeSelect = await screen.findByLabelText('Condition Type');
|
||||
|
||||
expect(screen.getByRole('textbox', { name: /search query/i })).toBeEnabled();
|
||||
expect(conditionTypeSelect).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Filters & Aggregation should not be accessible for immutable entities', async () => {
|
||||
asMock(useScopePermissions).mockImplementation(() => exampleEntityScopeImmutable);
|
||||
render(<EventDefinitionFormContainer action="edit" eventDefinition={mockAggregationEventDefinition} />);
|
||||
|
||||
const tab = await screen.findByRole('button', { name: /filter & aggregation/i });
|
||||
const tab = await screen.findByRole('button', { name: /condition/i });
|
||||
userEvent.click(tab);
|
||||
|
||||
expect(screen.getByText(/cannot be edited/i)).toBeVisible();
|
||||
|
||||
@@ -16,11 +16,9 @@
|
||||
*/
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { PluginStore } from 'graylog-web-plugin/plugin';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
|
||||
import Routes from 'routing/Routes';
|
||||
import { useStore } from 'stores/connect';
|
||||
import EventDefinitionPriorityEnum from 'logic/alerts/EventDefinitionPriorityEnum';
|
||||
import { ConfirmLeaveDialog, Spinner } from 'components/common';
|
||||
@@ -28,8 +26,7 @@ import { AvailableEventDefinitionTypesStore } from 'stores/event-definitions/Ava
|
||||
import { ConfigurationsActions } from 'stores/configurations/ConfigurationsStore';
|
||||
import { EventDefinitionsActions } from 'stores/event-definitions/EventDefinitionsStore';
|
||||
import { EventNotificationsActions, EventNotificationsStore } from 'stores/event-notifications/EventNotificationsStore';
|
||||
import 'components/event-notifications/event-notification-types';
|
||||
import type { EventDefinition } from 'components/event-definitions/event-definitions-types';
|
||||
import type { EventDefinition, EventDefinitionFormControlsProps } from 'components/event-definitions/event-definitions-types';
|
||||
import useCurrentUser from 'hooks/useCurrentUser';
|
||||
import useEventDefinitionConfigFromLocalStorage from 'components/event-definitions/hooks/useEventDefinitionConfigFromLocalStorage';
|
||||
import { getPathnameWithoutId } from 'util/URLUtils';
|
||||
@@ -38,7 +35,7 @@ import useLocation from 'routing/useLocation';
|
||||
import { TELEMETRY_EVENT_TYPE } from 'logic/telemetry/Constants';
|
||||
import useScopePermissions from 'hooks/useScopePermissions';
|
||||
|
||||
import EventDefinitionForm from './EventDefinitionForm';
|
||||
import EventDefinitionForm, { STEP_KEYS } from './EventDefinitionForm';
|
||||
|
||||
const fetchNotifications = () => {
|
||||
EventNotificationsActions.listAll();
|
||||
@@ -47,7 +44,12 @@ const fetchNotifications = () => {
|
||||
type Props = {
|
||||
action: 'edit' | 'create',
|
||||
eventDefinition: EventDefinition,
|
||||
formControls?: React.ComponentType<EventDefinitionFormControlsProps>,
|
||||
initialStep?: string,
|
||||
onCancel?: () => void
|
||||
onChangeStep?: (step: string) => void,
|
||||
onEventDefinitionChange: (nextEventDefinition: EventDefinition) => void,
|
||||
onSubmit?: () => void,
|
||||
}
|
||||
|
||||
const getConditionPlugin = (edType): any => {
|
||||
@@ -58,7 +60,17 @@ const getConditionPlugin = (edType): any => {
|
||||
return PluginStore.exports('eventDefinitionTypes').find((eventDefinitionType) => eventDefinitionType.type === edType) || {};
|
||||
};
|
||||
|
||||
const EventDefinitionFormContainer = ({ action, eventDefinition: eventDefinitionInitial, onEventDefinitionChange }: Props) => {
|
||||
const EventDefinitionFormContainer = ({
|
||||
action,
|
||||
eventDefinition: eventDefinitionInitial,
|
||||
formControls,
|
||||
initialStep,
|
||||
onCancel,
|
||||
onChangeStep,
|
||||
onEventDefinitionChange,
|
||||
onSubmit,
|
||||
}: Props) => {
|
||||
const [activeStep, setActiveStep] = useState(initialStep);
|
||||
const [eventDefinition, setEventDefinition] = useState(eventDefinitionInitial);
|
||||
const [validation, setValidation] = useState({ errors: {} });
|
||||
const [eventsClusterConfig, setEventsClusterConfig] = useState(undefined);
|
||||
@@ -69,7 +81,6 @@ const EventDefinitionFormContainer = ({ action, eventDefinition: eventDefinition
|
||||
const entityTypes = useStore(AvailableEventDefinitionTypesStore);
|
||||
const notifications = useStore(EventNotificationsStore);
|
||||
const currentUser = useCurrentUser();
|
||||
const navigate = useNavigate();
|
||||
const { pathname } = useLocation();
|
||||
const sendTelemetry = useSendTelemetry();
|
||||
|
||||
@@ -111,7 +122,13 @@ const EventDefinitionFormContainer = ({ action, eventDefinition: eventDefinition
|
||||
|
||||
const handleSubmitSuccessResponse = () => {
|
||||
setIsDirty(false);
|
||||
navigate(Routes.ALERTS.DEFINITIONS.LIST);
|
||||
|
||||
onSubmit();
|
||||
};
|
||||
|
||||
const showValidationErrors = (errors: { errors: unknown }) => {
|
||||
setValidation(errors);
|
||||
setActiveStep(STEP_KEYS[STEP_KEYS.length - 1]);
|
||||
};
|
||||
|
||||
const handleSubmitFailureResponse = (errorResponse) => {
|
||||
@@ -119,7 +136,7 @@ const EventDefinitionFormContainer = ({ action, eventDefinition: eventDefinition
|
||||
|
||||
if (errorResponse.status === 400) {
|
||||
if (body && body.failed) {
|
||||
setValidation(body);
|
||||
showValidationErrors(body);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -128,7 +145,7 @@ const EventDefinitionFormContainer = ({ action, eventDefinition: eventDefinition
|
||||
if (body.message.includes('org.graylog.events.conditions.Expression')
|
||||
|| body.message.includes('org.graylog.events.conditions.Expr')
|
||||
|| body.message.includes('org.graylog.events.processor.aggregation.AggregationSeries')) {
|
||||
setValidation({
|
||||
showValidationErrors({
|
||||
errors: { conditions: ['Aggregation condition is not valid'] },
|
||||
});
|
||||
|
||||
@@ -136,7 +153,7 @@ const EventDefinitionFormContainer = ({ action, eventDefinition: eventDefinition
|
||||
}
|
||||
|
||||
if (body.message.includes('embryonic')) {
|
||||
setValidation({
|
||||
showValidationErrors({
|
||||
errors: { query_parameters: ['Query parameters must be declared'] },
|
||||
});
|
||||
}
|
||||
@@ -173,7 +190,12 @@ const EventDefinitionFormContainer = ({ action, eventDefinition: eventDefinition
|
||||
app_action_value: 'cancel-button',
|
||||
});
|
||||
|
||||
navigate(Routes.ALERTS.DEFINITIONS.LIST);
|
||||
onCancel();
|
||||
};
|
||||
|
||||
const changeStep = (step: string) => {
|
||||
onChangeStep?.(step);
|
||||
setActiveStep(step);
|
||||
};
|
||||
|
||||
if (isLoading || loadingScopePermissions) {
|
||||
@@ -186,16 +208,19 @@ const EventDefinitionFormContainer = ({ action, eventDefinition: eventDefinition
|
||||
<ConfirmLeaveDialog question="Do you really want to abandon this page and lose your changes? This action cannot be undone." />
|
||||
)}
|
||||
<EventDefinitionForm action={action}
|
||||
eventDefinition={eventDefinition}
|
||||
canEdit={scopePermissions.is_mutable}
|
||||
currentUser={currentUser}
|
||||
validation={validation}
|
||||
entityTypes={entityTypes}
|
||||
notifications={notifications.all}
|
||||
defaults={defaults}
|
||||
onChange={handleChange}
|
||||
activeStep={activeStep}
|
||||
entityTypes={entityTypes}
|
||||
eventDefinition={eventDefinition}
|
||||
formControls={formControls}
|
||||
notifications={notifications.all}
|
||||
onCancel={handleCancel}
|
||||
onChange={handleChange}
|
||||
onChangeStep={changeStep}
|
||||
onSubmit={handleSubmit}
|
||||
canEdit={scopePermissions.is_mutable} />
|
||||
validation={validation} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -223,6 +248,11 @@ EventDefinitionFormContainer.defaultProps = {
|
||||
notifications: [],
|
||||
alert: false,
|
||||
},
|
||||
formControls: undefined,
|
||||
initialStep: STEP_KEYS[0],
|
||||
onCancel: undefined,
|
||||
onSubmit: undefined,
|
||||
onChangeStep: undefined,
|
||||
onEventDefinitionChange: () => {},
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Graylog, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the Server Side Public License, version 1,
|
||||
* as published by MongoDB, Inc.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* Server Side Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the Server Side Public License
|
||||
* along with this program. If not, see
|
||||
* <http://www.mongodb.com/licensing/server-side-public-license>.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { ModalSubmit } from 'components/common';
|
||||
import { Button } from 'components/bootstrap';
|
||||
import type { EventDefinitionFormControlsProps } from 'components/event-definitions/event-definitions-types';
|
||||
|
||||
const EventDefinitionFormControls = ({
|
||||
action,
|
||||
activeStepIndex,
|
||||
onCancel,
|
||||
onOpenNextPage,
|
||||
onOpenPrevPage,
|
||||
onSubmit,
|
||||
steps,
|
||||
}: EventDefinitionFormControlsProps) => {
|
||||
if (activeStepIndex === steps.length - 1) {
|
||||
return (
|
||||
<ModalSubmit onCancel={onCancel}
|
||||
onSubmit={onSubmit}
|
||||
submitButtonText={`${action === 'edit' ? 'Update' : 'Create'} event definition`} />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Button bsStyle="info"
|
||||
onClick={onOpenPrevPage}
|
||||
disabled={activeStepIndex === 0}>
|
||||
Previous
|
||||
</Button>
|
||||
<div className="pull-right">
|
||||
<Button bsStyle="info"
|
||||
onClick={onOpenNextPage}>
|
||||
Next
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EventDefinitionFormControls;
|
||||
@@ -35,10 +35,6 @@ import type { EventDefinition } from '../event-definitions-types';
|
||||
import commonStyles from '../common/commonStyles.css';
|
||||
import { SYSTEM_EVENT_DEFINITION_TYPE } from '../constants';
|
||||
|
||||
// Import built-in plugins
|
||||
import 'components/event-definitions/event-definition-types';
|
||||
import 'components/event-notifications/event-notification-types';
|
||||
|
||||
type Props = {
|
||||
eventDefinition: Omit<EventDefinition, 'id'>,
|
||||
notifications: Array<any>,
|
||||
|
||||
@@ -29,9 +29,6 @@ import HoverForHelp from 'components/common/HoverForHelp';
|
||||
import FieldForm from './FieldForm';
|
||||
import FieldsList from './FieldsList';
|
||||
|
||||
// Import built-in Field Value Providers
|
||||
import './field-value-providers';
|
||||
|
||||
import type { EventDefinition } from '../event-definitions-types';
|
||||
import commonStyles from '../common/commonStyles.css';
|
||||
|
||||
|
||||
@@ -14,15 +14,13 @@
|
||||
* along with this program. If not, see
|
||||
* <http://www.mongodb.com/licensing/server-side-public-license>.
|
||||
*/
|
||||
import { PluginManifest, PluginStore } from 'graylog-web-plugin/plugin';
|
||||
|
||||
import TemplateFieldValueProviderForm from './TemplateFieldValueProviderForm';
|
||||
import TemplateFieldValueProviderSummary from './TemplateFieldValueProviderSummary';
|
||||
import LookupTableFieldValueProviderForm from './LookupTableFieldValueProviderForm';
|
||||
import LookupTableFieldValueProviderFormContainer from './LookupTableFieldValueProviderFormContainer';
|
||||
import LookupTableFieldValueProviderSummary from './LookupTableFieldValueProviderSummary';
|
||||
|
||||
PluginStore.register(new PluginManifest({}, {
|
||||
export default {
|
||||
fieldValueProviders: [
|
||||
{
|
||||
type: TemplateFieldValueProviderForm.type,
|
||||
@@ -41,4 +39,4 @@ PluginStore.register(new PluginManifest({}, {
|
||||
requiredFields: LookupTableFieldValueProviderForm.requiredFields,
|
||||
},
|
||||
],
|
||||
}));
|
||||
};
|
||||
@@ -14,6 +14,7 @@
|
||||
* along with this program. If not, see
|
||||
* <http://www.mongodb.com/licensing/server-side-public-license>.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { useCallback, useContext, useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
@@ -30,6 +31,10 @@ import union from 'lodash/union';
|
||||
import moment from 'moment';
|
||||
import { OrderedMap } from 'immutable';
|
||||
|
||||
import { getPathnameWithoutId } from 'util/URLUtils';
|
||||
import { isPermitted } from 'util/PermissionsMixin';
|
||||
import * as FormsUtils from 'util/FormsUtils';
|
||||
import { naturalSortIgnoreCase } from 'util/SortUtils';
|
||||
import FormWarningsContext from 'contexts/FormWarningsContext';
|
||||
import { useStore } from 'stores/connect';
|
||||
import Store from 'logic/local-storage/Store';
|
||||
@@ -40,21 +45,18 @@ import Search from 'views/logic/search/Search';
|
||||
import { extractDurationAndUnit } from 'components/common/TimeUnitInput';
|
||||
import { Alert, ButtonToolbar, ControlLabel, FormGroup, HelpBlock, Input } from 'components/bootstrap';
|
||||
import RelativeTime from 'components/common/RelativeTime';
|
||||
import { naturalSortIgnoreCase } from 'util/SortUtils';
|
||||
import * as FormsUtils from 'util/FormsUtils';
|
||||
import { isPermitted } from 'util/PermissionsMixin';
|
||||
import type { LookupTableParameterJson } from 'views/logic/parameters/LookupTableParameter';
|
||||
import LookupTableParameter from 'views/logic/parameters/LookupTableParameter';
|
||||
import { LookupTablesActions, LookupTablesStore } from 'stores/lookup-tables/LookupTablesStore';
|
||||
import validateQuery from 'views/components/searchbar/queryvalidation/validateQuery';
|
||||
import generateId from 'logic/generateId';
|
||||
import parseSearch from 'views/logic/slices/parseSearch';
|
||||
import useLocation from 'routing/useLocation';
|
||||
import { getPathnameWithoutId } from 'util/URLUtils';
|
||||
import { TELEMETRY_EVENT_TYPE } from 'logic/telemetry/Constants';
|
||||
import useSendTelemetry from 'logic/telemetry/useSendTelemetry';
|
||||
import type User from 'logic/users/User';
|
||||
import useUserDateTime from 'hooks/useUserDateTime';
|
||||
import type { EventDefinition } from 'components/event-definitions/event-definition-types';
|
||||
import type { EventDefinition } from 'components/event-definitions/event-definitions-types';
|
||||
import type { Stream } from 'stores/streams/StreamsStore';
|
||||
import type { QueryValidationState } from 'views/components/searchbar/queryvalidation/types';
|
||||
import { indicesInWarmTier, isSearchingWarmTier } from 'views/components/searchbar/queryvalidation/warmTierValidation';
|
||||
@@ -64,18 +66,19 @@ import commonStyles from '../common/commonStyles.css';
|
||||
|
||||
export const PLUGGABLE_CONTROLS_HIDDEN_KEY = 'pluggableSearchBarControlsAreHidden';
|
||||
export const TIME_UNITS = ['HOURS', 'MINUTES', 'SECONDS'];
|
||||
|
||||
type LookupTableParameterJsonEmbryonic = Partial<LookupTableParameterJson> & {
|
||||
embryonic?: boolean,
|
||||
}
|
||||
const LOOKUP_PERMISSIONS = [
|
||||
'lookuptables:read',
|
||||
];
|
||||
|
||||
const buildNewParameter = (name: string) => ({
|
||||
const buildNewParameter = (name: string): LookupTableParameterJsonEmbryonic => ({
|
||||
name: name,
|
||||
embryonic: true,
|
||||
type: 'lut-parameter-v1',
|
||||
data_type: 'any',
|
||||
title: 'new title',
|
||||
// has no binding, no need to set binding property
|
||||
});
|
||||
|
||||
type Props = {
|
||||
@@ -353,7 +356,7 @@ const FilterForm = ({
|
||||
const parameterButtons = queryParameters.map((queryParam) => (
|
||||
<EditQueryParameterModal key={queryParam.name}
|
||||
queryParameter={LookupTableParameter.fromJSON(queryParam)}
|
||||
embryonic={!!queryParam.embryonic}
|
||||
embryonic={!!(queryParam as LookupTableParameterJsonEmbryonic).embryonic}
|
||||
queryParameters={queryParameters}
|
||||
lookupTables={Object.values(tables)}
|
||||
onChange={onChangeQueryParameters} />
|
||||
@@ -363,7 +366,7 @@ const FilterForm = ({
|
||||
return null;
|
||||
}
|
||||
|
||||
const hasEmbryonicParameters = !isEmpty(queryParameters.filter((param) => (param.embryonic)));
|
||||
const hasEmbryonicParameters = !isEmpty(queryParameters.filter((param : LookupTableParameterJsonEmbryonic) => (param.embryonic)));
|
||||
|
||||
return (
|
||||
<FormGroup validationState={validation.errors.query_parameters ? 'error' : null}>
|
||||
|
||||
@@ -14,13 +14,12 @@
|
||||
* along with this program. If not, see
|
||||
* <http://www.mongodb.com/licensing/server-side-public-license>.
|
||||
*/
|
||||
import { PluginManifest, PluginStore } from 'graylog-web-plugin/plugin';
|
||||
|
||||
import FilterAggregationFormContainer from './FilterAggregationFormContainer';
|
||||
import FilterAggregationForm from './FilterAggregationForm';
|
||||
import FilterAggregationSummary from './FilterAggregationSummary';
|
||||
|
||||
PluginStore.register(new PluginManifest({}, {
|
||||
export default {
|
||||
eventDefinitionTypes: [
|
||||
{
|
||||
type: 'aggregation-v1',
|
||||
@@ -33,4 +32,4 @@ PluginStore.register(new PluginManifest({}, {
|
||||
defaultConfig: FilterAggregationForm.defaultConfig,
|
||||
},
|
||||
],
|
||||
}));
|
||||
};
|
||||
@@ -14,6 +14,10 @@
|
||||
* along with this program. If not, see
|
||||
* <http://www.mongodb.com/licensing/server-side-public-license>.
|
||||
*/
|
||||
import { type SyntheticEvent } from 'react';
|
||||
|
||||
import type { Steps } from 'components/common/Wizard';
|
||||
import type { LookupTableParameterJson } from 'views/logic/parameters/LookupTableParameter';
|
||||
|
||||
type Provider = {
|
||||
type: string,
|
||||
@@ -29,8 +33,9 @@ type FieldSpec = {
|
||||
};
|
||||
|
||||
type Notification = {
|
||||
notification_id: number,
|
||||
};
|
||||
notification_id: string,
|
||||
notification_parameters: string
|
||||
}
|
||||
|
||||
export type Scheduler = {
|
||||
data: {
|
||||
@@ -57,29 +62,51 @@ export type SearchFilter = {
|
||||
};
|
||||
|
||||
export type EventDefinition = {
|
||||
_scope: string,
|
||||
id: string,
|
||||
config?: {
|
||||
type: string,
|
||||
execute_every_ms?: number,
|
||||
search_within_ms?: number,
|
||||
event_limit?: number,
|
||||
sigma_rule_id?: string,
|
||||
streams?: Array<string>
|
||||
filters?: Array<SearchFilter>,
|
||||
},
|
||||
title: string,
|
||||
description?: string,
|
||||
matched_at?: string,
|
||||
priority?: number,
|
||||
key_spec?: Array<string>
|
||||
field_spec?: FieldSpec,
|
||||
notification_settings?: {
|
||||
backlog_size: number,
|
||||
grace_period_ms: number,
|
||||
}
|
||||
notifications?: Array<Notification>,
|
||||
_scope?: string,
|
||||
scheduler?: Scheduler,
|
||||
description: string,
|
||||
priority: number,
|
||||
alert: boolean,
|
||||
state?: 'ENABLED' | 'DISABLED',
|
||||
config: {
|
||||
type: string,
|
||||
query: string,
|
||||
query_parameters: LookupTableParameterJson[],
|
||||
filters: SearchFilter[],
|
||||
streams: string[],
|
||||
group_by: string[],
|
||||
_is_scheduled: boolean,
|
||||
series: Array<{field: string, id: string, type: string}>,
|
||||
conditions: {
|
||||
expression: string | null | {},
|
||||
},
|
||||
search_within_ms: number,
|
||||
execute_every_ms: number,
|
||||
},
|
||||
field_spec: FieldSpec,
|
||||
key_spec: string[],
|
||||
notification_settings: {
|
||||
grace_period_ms: number,
|
||||
backlog_size: number,
|
||||
},
|
||||
notifications: Array<Notification>,
|
||||
remediation_steps?: string,
|
||||
};
|
||||
storage: Array<{
|
||||
type: string,
|
||||
streams: number[] | string[],
|
||||
}>,
|
||||
updated_at: string | null,
|
||||
matched_at?: string,
|
||||
scheduler?: Scheduler,
|
||||
}
|
||||
|
||||
export type EventDefinitionFormControlsProps = {
|
||||
action: 'edit' | 'create',
|
||||
activeStepIndex: number,
|
||||
onCancel: () => void,
|
||||
onOpenNextPage: () => void,
|
||||
onOpenPrevPage: () => void,
|
||||
onSubmit: (event: SyntheticEvent) => void,
|
||||
steps: Steps,
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import { useState, useMemo, useCallback } from 'react';
|
||||
import { EntityDataTable, NoSearchResult, PaginatedList, QueryHelper, RelativeTime, SearchForm, Spinner } from 'components/common';
|
||||
import type { Sort } from 'stores/PaginationTypes';
|
||||
import usePaginationQueryParameter from 'hooks/usePaginationQueryParameter';
|
||||
import 'components/event-definitions/event-definition-types';
|
||||
import { Link } from 'components/common/router';
|
||||
import Routes from 'routing/Routes';
|
||||
import type { ColumnRenderers } from 'components/common/EntityDataTable';
|
||||
|
||||
@@ -26,10 +26,6 @@ import withHistory from 'routing/withHistory';
|
||||
|
||||
import EventNotificationForm from './EventNotificationForm';
|
||||
|
||||
// Import built-in Event Notification Types
|
||||
|
||||
import '../event-notification-types';
|
||||
|
||||
const initialValidation = {
|
||||
errors: {},
|
||||
};
|
||||
|
||||
@@ -14,8 +14,6 @@
|
||||
* along with this program. If not, see
|
||||
* <http://www.mongodb.com/licensing/server-side-public-license>.
|
||||
*/
|
||||
import { PluginManifest, PluginStore } from 'graylog-web-plugin/plugin';
|
||||
|
||||
import EmailNotificationFormContainer from './EmailNotificationFormContainer';
|
||||
import EmailNotificationForm from './EmailNotificationForm';
|
||||
import EmailNotificationSummary from './EmailNotificationSummary';
|
||||
@@ -32,7 +30,7 @@ import HttpNotificationDetails from '../event-notification-details/HttpNotificat
|
||||
import LegacyNotificationDetails from '../event-notification-details/LegacyNotificationDetails';
|
||||
import HttpNotificationDetailsV2 from '../event-notification-details/HttpNotificationDetailsV2';
|
||||
|
||||
PluginStore.register(new PluginManifest({}, {
|
||||
export default {
|
||||
eventNotificationTypes: [
|
||||
{
|
||||
type: 'email-notification-v1',
|
||||
@@ -67,4 +65,4 @@ PluginStore.register(new PluginManifest({}, {
|
||||
defaultConfig: LegacyNotificationForm.defaultConfig,
|
||||
},
|
||||
],
|
||||
}));
|
||||
};
|
||||
@@ -38,9 +38,6 @@ import BulkActions from './BulkActions';
|
||||
import useEventNotifications from '../hooks/useEventNotifications';
|
||||
import useNotificationTest from '../hooks/useNotificationTest';
|
||||
|
||||
// Import built-in Event Notification Types
|
||||
import '../event-notification-types';
|
||||
|
||||
const customColumnRenderers = (testResults: TestResults): ColumnRenderers<EventNotification> => ({
|
||||
attributes: {
|
||||
title: {
|
||||
|
||||
@@ -30,8 +30,6 @@ import withPaginationQueryParameter from 'components/common/withPaginationQueryP
|
||||
|
||||
import Events, { PAGE_SIZES, EVENTS_MAX_OFFSET_LIMIT } from './Events';
|
||||
|
||||
import 'components/event-definitions/event-definition-types';
|
||||
|
||||
const LOCAL_STORAGE_ITEM = 'events-last-search';
|
||||
const SEARCH_DEBOUNCE_THRESHOLD = 500;
|
||||
|
||||
|
||||
@@ -16,10 +16,10 @@
|
||||
*/
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import type { EventDefinition } from 'logic/alerts/types';
|
||||
import fetch from 'logic/rest/FetchProvider';
|
||||
import { qualifyUrl } from 'util/URLUtils';
|
||||
import UserNotification from 'preflight/util/UserNotification';
|
||||
import type { EventDefinition } from 'components/event-definitions/event-definitions-types';
|
||||
|
||||
export const fetchEventDefinitionDetails = async (eventDefinitionId: string): Promise<EventDefinition> => (
|
||||
fetch('GET', qualifyUrl(`/events/definitions/${eventDefinitionId}`))
|
||||
|
||||
@@ -23,7 +23,7 @@ import Routes from 'routing/Routes';
|
||||
import useParams from 'routing/useParams';
|
||||
import type { Event } from 'components/events/events/types';
|
||||
import type { EventDefinitionAggregation } from 'hooks/useEventDefinition';
|
||||
import type { EventDefinition } from 'logic/alerts/types';
|
||||
import type { EventDefinition } from 'components/event-definitions/event-definitions-types';
|
||||
|
||||
const useAlertAndEventDefinitionData = () => {
|
||||
const { pathname: path } = useLocation();
|
||||
|
||||
@@ -21,7 +21,7 @@ import isEqual from 'lodash/isEqual';
|
||||
import UserNotification from 'util/UserNotification';
|
||||
import fetch from 'logic/rest/FetchProvider';
|
||||
import { qualifyUrl } from 'util/URLUtils';
|
||||
import type { EventDefinition } from 'logic/alerts/types';
|
||||
import type { EventDefinition } from 'components/event-definitions/event-definitions-types';
|
||||
import type FetchError from 'logic/errors/FetchError';
|
||||
|
||||
export type ValueExpr = '>' | '<' | '>=' | '<=' | '==';
|
||||
|
||||
@@ -28,6 +28,9 @@ import ViewsBindings from 'views/bindings';
|
||||
import ThreatIntelBindings from 'threatintel/bindings';
|
||||
import AwsBindings from 'aws/bindings';
|
||||
import IntegrationsBindings from 'integrations/bindings';
|
||||
import EventDefinitionsBindings from 'components/event-definitions/event-definition-types/bindings';
|
||||
import EventNotificationsBindings from 'components/event-notifications/event-notification-types/bindings';
|
||||
import FieldValueProvidersBindings from 'components/event-definitions/event-definition-form/field-value-providers/bindings';
|
||||
import CancellablePromise from 'logic/rest/CancellablePromise';
|
||||
import TelemetryInit from 'logic/telemetry/TelemetryInit';
|
||||
import LoginQueryClientProvider from 'contexts/LoginQueryClientProvider';
|
||||
@@ -44,6 +47,9 @@ PluginStore.register(new PluginManifest({}, ViewsBindings));
|
||||
PluginStore.register(new PluginManifest({}, ThreatIntelBindings));
|
||||
PluginStore.register(new PluginManifest({}, AwsBindings));
|
||||
PluginStore.register(new PluginManifest({}, IntegrationsBindings));
|
||||
PluginStore.register(new PluginManifest({}, EventDefinitionsBindings));
|
||||
PluginStore.register(new PluginManifest({}, EventNotificationsBindings));
|
||||
PluginStore.register(new PluginManifest({}, FieldValueProvidersBindings));
|
||||
PluginStore.register(new PluginManifest({}, PerspectivesBindings));
|
||||
PluginStore.register(new PluginManifest({}, NavigationBindings));
|
||||
PluginStore.register(new PluginManifest({}, SecurityBindings));
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Graylog, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the Server Side Public License, version 1,
|
||||
* as published by MongoDB, Inc.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* Server Side Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the Server Side Public License
|
||||
* along with this program. If not, see
|
||||
* <http://www.mongodb.com/licensing/server-side-public-license>.
|
||||
*/
|
||||
|
||||
import type { ParameterJson } from 'views/logic/parameters/Parameter';
|
||||
import type { SearchFilter } from 'components/event-definitions/event-definitions-types';
|
||||
|
||||
export type EventDefinition = {
|
||||
_scope: string,
|
||||
id: string,
|
||||
title: string,
|
||||
description: string,
|
||||
priority: number,
|
||||
alert: boolean,
|
||||
config: {
|
||||
type: string,
|
||||
query: string,
|
||||
query_parameters: ParameterJson[],
|
||||
filters: SearchFilter[],
|
||||
streams: string[],
|
||||
group_by: string[],
|
||||
series: Array<{field: string, id: string, type: string}>,
|
||||
conditions: {
|
||||
expression: string | null | {},
|
||||
},
|
||||
search_within_ms: number,
|
||||
execute_every_ms: number,
|
||||
},
|
||||
field_spec: {},
|
||||
key_spec: string[],
|
||||
notification_settings: {
|
||||
grace_period_ms: number,
|
||||
backlog_size: number,
|
||||
},
|
||||
notifications: Array<{ notification_id: string, notification_parameters: string}>,
|
||||
storage: [
|
||||
{
|
||||
type: string,
|
||||
streams: number[] | string[],
|
||||
}
|
||||
],
|
||||
updated_at: string | null
|
||||
}
|
||||
@@ -40,6 +40,10 @@ const CreateEventDefinitionPage = () => {
|
||||
|
||||
const pageTitle = useMemo(() => (eventDefinitionTitle ? `New Event Definition "${eventDefinitionTitle}"` : 'New Event Definition'), [eventDefinitionTitle]);
|
||||
|
||||
const goToOverview = useCallback(() => {
|
||||
navigate(Routes.ALERTS.DEFINITIONS.LIST);
|
||||
}, [navigate]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isPermitted(currentUser.permissions, 'eventdefinitions:create')) {
|
||||
navigate(Routes.NOTFOUND);
|
||||
@@ -62,7 +66,10 @@ const CreateEventDefinitionPage = () => {
|
||||
|
||||
<Row className="content">
|
||||
<Col md={12}>
|
||||
<EventDefinitionFormContainer action="create" onEventDefinitionChange={handleEventDefinitionChange} />
|
||||
<EventDefinitionFormContainer action="create"
|
||||
onEventDefinitionChange={handleEventDefinitionChange}
|
||||
onSubmit={goToOverview}
|
||||
onCancel={goToOverview} />
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
|
||||
@@ -14,8 +14,9 @@
|
||||
* along with this program. If not, see
|
||||
* <http://www.mongodb.com/licensing/server-side-public-license>.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import URI from 'urijs';
|
||||
|
||||
import EventsPageNavigation from 'components/events/EventsPageNavigation';
|
||||
import { Col, Row } from 'components/bootstrap';
|
||||
@@ -28,14 +29,21 @@ import useCurrentUser from 'hooks/useCurrentUser';
|
||||
import { EventDefinitionsActions } from 'stores/event-definitions/EventDefinitionsStore';
|
||||
import type { EventDefinition } from 'components/event-definitions/event-definitions-types';
|
||||
import useHistory from 'routing/useHistory';
|
||||
import useQuery from 'routing/useQuery';
|
||||
|
||||
import StreamPermissionErrorPage from './StreamPermissionErrorPage';
|
||||
|
||||
const EditEventDefinitionPage = () => {
|
||||
const params = useParams<{definitionId?: string}>();
|
||||
const { step } = useQuery();
|
||||
const currentUser = useCurrentUser();
|
||||
const [eventDefinition, setEventDefinition] = React.useState<EventDefinition>(undefined);
|
||||
const history = useHistory();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const goToOverview = useCallback(() => {
|
||||
navigate(Routes.ALERTS.DEFINITIONS.LIST);
|
||||
}, [navigate]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isPermitted(currentUser.permissions, `eventdefinitions:edit:${params.definitionId}`)) {
|
||||
@@ -71,6 +79,11 @@ const EditEventDefinitionPage = () => {
|
||||
|
||||
const missingStreams = streamsWithMissingPermissions();
|
||||
|
||||
const updateURLStepQueryParam = (newStep: string) => {
|
||||
const newUrl = new URI(window.location.href).removeSearch('step').addQuery('step', newStep);
|
||||
history.replace(newUrl.resource());
|
||||
};
|
||||
|
||||
if (missingStreams.length > 0) {
|
||||
return <StreamPermissionErrorPage error={null} missingStreamIds={missingStreams} />;
|
||||
}
|
||||
@@ -101,7 +114,12 @@ const EditEventDefinitionPage = () => {
|
||||
</PageHeader>
|
||||
<Row className="content">
|
||||
<Col md={12}>
|
||||
<EventDefinitionFormContainer action="edit" eventDefinition={eventDefinition} />
|
||||
<EventDefinitionFormContainer action="edit"
|
||||
initialStep={step as string}
|
||||
onChangeStep={updateURLStepQueryParam}
|
||||
eventDefinition={eventDefinition}
|
||||
onSubmit={goToOverview}
|
||||
onCancel={goToOverview} />
|
||||
</Col>
|
||||
</Row>
|
||||
</DocumentTitle>
|
||||
|
||||
@@ -29,7 +29,6 @@ import EventNotificationDetails from 'components/event-notifications/event-notif
|
||||
import EventNotificationActionLinks from 'components/event-notifications/event-notification-details/EventNotificationActionLinks';
|
||||
import type { EventNotification } from 'stores/event-notifications/EventNotificationsStore';
|
||||
import { EventNotificationsActions } from 'stores/event-notifications/EventNotificationsStore';
|
||||
import 'components/event-notifications/event-notification-types';
|
||||
import EventsPageNavigation from 'components/events/EventsPageNavigation';
|
||||
|
||||
import useHistory from '../routing/useHistory';
|
||||
|
||||
@@ -21,7 +21,7 @@ import uniq from 'lodash/uniq';
|
||||
import View from 'views/logic/views/View';
|
||||
import type { AbsoluteTimeRange, ElasticsearchQueryString, RelativeTimeRangeStartOnly } from 'views/logic/queries/Query';
|
||||
import type { Event } from 'components/events/events/types';
|
||||
import type { EventDefinition } from 'logic/alerts/types';
|
||||
import type { EventDefinition, SearchFilter } from 'components/event-definitions/event-definitions-types';
|
||||
import QueryGenerator from 'views/logic/queries/QueryGenerator';
|
||||
import Search from 'views/logic/search/Search';
|
||||
import { matchesDecoratorStream } from 'views/logic/views/ViewStateGenerator';
|
||||
@@ -46,7 +46,6 @@ import { concatQueryStrings, escape } from 'views/logic/queries/QueryHelper';
|
||||
import HighlightingRule, { randomColor } from 'views/logic/views/formatting/highlighting/HighlightingRule';
|
||||
import { exprToConditionMapper } from 'views/logic/ExpressionConditionMappers';
|
||||
import FormattingSettings from 'views/logic/views/formatting/FormattingSettings';
|
||||
import type { SearchFilter } from 'components/event-definitions/event-definitions-types';
|
||||
|
||||
const AGGREGATION_WIDGET_HEIGHT = 3;
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import type { EventDefinition } from 'logic/alerts/types';
|
||||
import type { EventDefinition } from 'components/event-definitions/event-definitions-types';
|
||||
import type { EventDefinitionAggregation } from 'hooks/useEventDefinition';
|
||||
import type { ElasticsearchQueryString, RelativeTimeRangeStartOnly } from 'views/logic/queries/Query';
|
||||
import { ViewGenerator } from 'views/logic/views/UseCreateViewForEvent';
|
||||
|
||||
@@ -29,7 +29,9 @@ export const simpleEventDefinition = {
|
||||
streams: ['stream-id-1'],
|
||||
type: 'aggregation-v1',
|
||||
filters: [],
|
||||
_is_scheduled: true,
|
||||
},
|
||||
updated_at: '2024-02-26T15:32:24.666Z',
|
||||
description: '',
|
||||
field_spec: {},
|
||||
id: 'event-definition-1-id',
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import Immutable from 'immutable';
|
||||
|
||||
import type { Event } from 'components/events/events/types';
|
||||
import type { EventDefinition } from 'logic/alerts/types';
|
||||
import type { EventDefinition } from 'components/event-definitions/event-definitions-types';
|
||||
import type { EventDefinitionAggregation } from 'hooks/useEventDefinition';
|
||||
import QueryGenerator from 'views/logic/queries/QueryGenerator';
|
||||
import Search from 'views/logic/search/Search';
|
||||
@@ -84,6 +84,7 @@ export const mockEventDefinitionTwoAggregations:EventDefinition = {
|
||||
type: 'aggregation-v1',
|
||||
query: 'http_method: GET',
|
||||
query_parameters: [],
|
||||
_is_scheduled: true,
|
||||
streams: [
|
||||
'001',
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user