[Alerting UI] Make connector reducer as generic type. (#86857)
* - * fixed failing tests * fixed typescript checks * fixed typescript checks * fixed failing build * fixed typescript checks * removed typo cast * fixed failing test * fixed faling build
This commit is contained in:
parent
52e3371c39
commit
1c30525d46
|
@ -17,9 +17,7 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { ReducerAction } from './connector_reducer';
|
||||
import {
|
||||
ActionConnector,
|
||||
IErrorObject,
|
||||
ActionTypeRegistryContract,
|
||||
UserConfiguredActionConnector,
|
||||
|
@ -28,8 +26,11 @@ import {
|
|||
import { hasSaveActionsCapability } from '../../lib/capabilities';
|
||||
import { useKibana } from '../../../common/lib/kibana';
|
||||
import { SectionLoading } from '../../components/section_loading';
|
||||
import { ConnectorReducerAction } from './connector_reducer';
|
||||
|
||||
export function validateBaseProperties(actionObject: ActionConnector) {
|
||||
export function validateBaseProperties<ConnectorConfig, ConnectorSecrets>(
|
||||
actionObject: UserConfiguredActionConnector<ConnectorConfig, ConnectorSecrets>
|
||||
) {
|
||||
const validationResult = { errors: {} };
|
||||
const verrors = {
|
||||
name: new Array<string>(),
|
||||
|
@ -78,14 +79,14 @@ interface ActionConnectorProps<
|
|||
ConnectorSecrets = Record<string, any>
|
||||
> {
|
||||
connector: UserConfiguredActionConnector<ConnectorConfig, ConnectorSecrets>;
|
||||
dispatch: React.Dispatch<ReducerAction>;
|
||||
actionTypeName: string;
|
||||
serverError?: {
|
||||
body: { message: string; error: string };
|
||||
};
|
||||
dispatch: React.Dispatch<ConnectorReducerAction<ConnectorConfig, ConnectorSecrets>>;
|
||||
errors: IErrorObject;
|
||||
actionTypeRegistry: ActionTypeRegistryContract;
|
||||
consumer?: string;
|
||||
actionTypeName?: string;
|
||||
serverError?: {
|
||||
body: { message: string; error: string };
|
||||
};
|
||||
}
|
||||
|
||||
export const ActionConnectorForm = ({
|
||||
|
@ -103,15 +104,31 @@ export const ActionConnectorForm = ({
|
|||
} = useKibana().services;
|
||||
const canSave = hasSaveActionsCapability(capabilities);
|
||||
|
||||
const setActionProperty = (key: string, value: any) => {
|
||||
const setActionProperty = <
|
||||
Key extends keyof UserConfiguredActionConnector<
|
||||
Record<string, unknown>,
|
||||
Record<string, unknown>
|
||||
>
|
||||
>(
|
||||
key: Key,
|
||||
value:
|
||||
| UserConfiguredActionConnector<Record<string, unknown>, Record<string, unknown>>[Key]
|
||||
| null
|
||||
) => {
|
||||
dispatch({ command: { type: 'setProperty' }, payload: { key, value } });
|
||||
};
|
||||
|
||||
const setActionConfigProperty = (key: string, value: any) => {
|
||||
const setActionConfigProperty = <Key extends keyof Record<string, unknown>>(
|
||||
key: Key,
|
||||
value: Record<string, unknown>[Key]
|
||||
) => {
|
||||
dispatch({ command: { type: 'setConfigProperty' }, payload: { key, value } });
|
||||
};
|
||||
|
||||
const setActionSecretsProperty = (key: string, value: any) => {
|
||||
const setActionSecretsProperty = <Key extends keyof Record<string, unknown>>(
|
||||
key: Key,
|
||||
value: Record<string, unknown>[Key]
|
||||
) => {
|
||||
dispatch({ command: { type: 'setSecretsProperty' }, payload: { key, value } });
|
||||
};
|
||||
|
||||
|
@ -135,7 +152,7 @@ export const ActionConnectorForm = ({
|
|||
id="xpack.triggersActionsUI.sections.actionConnectorForm.actions.actionConfigurationWarningDescriptionText"
|
||||
defaultMessage="To create this connector, you must configure at least one {actionType} account. {docLink}"
|
||||
values={{
|
||||
actionType: actionTypeName,
|
||||
actionType: actionTypeName ?? connector.actionTypeId,
|
||||
docLink: (
|
||||
<EuiLink
|
||||
href={`${docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${docLinks.DOC_LINK_VERSION}/action-types.html`}
|
||||
|
|
|
@ -24,12 +24,17 @@ import { HttpSetup } from 'kibana/public';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { ActionTypeMenu } from './action_type_menu';
|
||||
import { ActionConnectorForm, getConnectorErrors } from './action_connector_form';
|
||||
import { ActionType, ActionConnector, ActionTypeRegistryContract } from '../../../types';
|
||||
import { connectorReducer } from './connector_reducer';
|
||||
import {
|
||||
ActionType,
|
||||
ActionConnector,
|
||||
ActionTypeRegistryContract,
|
||||
UserConfiguredActionConnector,
|
||||
} from '../../../types';
|
||||
import { hasSaveActionsCapability } from '../../lib/capabilities';
|
||||
import { createActionConnector } from '../../lib/action_connector_api';
|
||||
import { VIEW_LICENSE_OPTIONS_LINK } from '../../../common/constants';
|
||||
import { useKibana } from '../../../common/lib/kibana';
|
||||
import { createConnectorReducer, InitialConnector, ConnectorReducer } from './connector_reducer';
|
||||
import { getConnectorWithInvalidatedFields } from '../../lib/value_validators';
|
||||
|
||||
export interface ConnectorAddFlyoutProps {
|
||||
|
@ -59,15 +64,32 @@ const ConnectorAddFlyout: React.FunctionComponent<ConnectorAddFlyoutProps> = ({
|
|||
const [hasActionsUpgradeableByTrial, setHasActionsUpgradeableByTrial] = useState<boolean>(false);
|
||||
|
||||
// hooks
|
||||
const initialConnector = {
|
||||
const initialConnector: InitialConnector<Record<string, unknown>, Record<string, unknown>> = {
|
||||
actionTypeId: actionType?.id ?? '',
|
||||
config: {},
|
||||
secrets: {},
|
||||
} as ActionConnector;
|
||||
const [{ connector }, dispatch] = useReducer(connectorReducer, { connector: initialConnector });
|
||||
const setActionProperty = (key: string, value: any) => {
|
||||
};
|
||||
|
||||
const reducer: ConnectorReducer<
|
||||
Record<string, unknown>,
|
||||
Record<string, unknown>
|
||||
> = createConnectorReducer<Record<string, unknown>, Record<string, unknown>>();
|
||||
const [{ connector }, dispatch] = useReducer(reducer, {
|
||||
connector: initialConnector as UserConfiguredActionConnector<
|
||||
Record<string, unknown>,
|
||||
Record<string, unknown>
|
||||
>,
|
||||
});
|
||||
|
||||
const setActionProperty = <Key extends keyof ActionConnector>(
|
||||
key: Key,
|
||||
value:
|
||||
| UserConfiguredActionConnector<Record<string, unknown>, Record<string, unknown>>[Key]
|
||||
| null
|
||||
) => {
|
||||
dispatch({ command: { type: 'setProperty' }, payload: { key, value } });
|
||||
};
|
||||
|
||||
const setConnector = (value: any) => {
|
||||
dispatch({ command: { type: 'setConnector' }, payload: { key: 'connector', value } });
|
||||
};
|
||||
|
|
|
@ -18,11 +18,16 @@ import { EuiButtonEmpty } from '@elastic/eui';
|
|||
import { EuiOverlayMask } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ActionConnectorForm, getConnectorErrors } from './action_connector_form';
|
||||
import { connectorReducer } from './connector_reducer';
|
||||
import { createConnectorReducer, InitialConnector, ConnectorReducer } from './connector_reducer';
|
||||
import { createActionConnector } from '../../lib/action_connector_api';
|
||||
import './connector_add_modal.scss';
|
||||
import { hasSaveActionsCapability } from '../../lib/capabilities';
|
||||
import { ActionType, ActionConnector, ActionTypeRegistryContract } from '../../../types';
|
||||
import {
|
||||
ActionType,
|
||||
ActionConnector,
|
||||
ActionTypeRegistryContract,
|
||||
UserConfiguredActionConnector,
|
||||
} from '../../../types';
|
||||
import { useKibana } from '../../../common/lib/kibana';
|
||||
import { getConnectorWithInvalidatedFields } from '../../lib/value_validators';
|
||||
|
||||
|
@ -47,7 +52,10 @@ export const ConnectorAddModal = ({
|
|||
application: { capabilities },
|
||||
} = useKibana().services;
|
||||
let hasErrors = false;
|
||||
const initialConnector = useMemo(
|
||||
const initialConnector: InitialConnector<
|
||||
Record<string, unknown>,
|
||||
Record<string, unknown>
|
||||
> = useMemo(
|
||||
() => ({
|
||||
actionTypeId: actionType.id,
|
||||
config: {},
|
||||
|
@ -58,7 +66,16 @@ export const ConnectorAddModal = ({
|
|||
const [isSaving, setIsSaving] = useState<boolean>(false);
|
||||
const canSave = hasSaveActionsCapability(capabilities);
|
||||
|
||||
const [{ connector }, dispatch] = useReducer(connectorReducer, { connector: initialConnector });
|
||||
const reducer: ConnectorReducer<
|
||||
Record<string, unknown>,
|
||||
Record<string, unknown>
|
||||
> = createConnectorReducer<Record<string, unknown>, Record<string, unknown>>();
|
||||
const [{ connector }, dispatch] = useReducer(reducer, {
|
||||
connector: initialConnector as UserConfiguredActionConnector<
|
||||
Record<string, unknown>,
|
||||
Record<string, unknown>
|
||||
>,
|
||||
});
|
||||
const setConnector = (value: any) => {
|
||||
dispatch({ command: { type: 'setConnector' }, payload: { key: 'connector', value } });
|
||||
};
|
||||
|
|
|
@ -26,8 +26,12 @@ import { i18n } from '@kbn/i18n';
|
|||
import { Option, none, some } from 'fp-ts/lib/Option';
|
||||
import { ActionConnectorForm, getConnectorErrors } from './action_connector_form';
|
||||
import { TestConnectorForm } from './test_connector_form';
|
||||
import { ActionConnector, ActionTypeRegistryContract } from '../../../types';
|
||||
import { connectorReducer } from './connector_reducer';
|
||||
import {
|
||||
ActionConnector,
|
||||
ActionTypeRegistryContract,
|
||||
UserConfiguredActionConnector,
|
||||
} from '../../../types';
|
||||
import { ConnectorReducer, createConnectorReducer } from './connector_reducer';
|
||||
import { updateActionConnector, executeAction } from '../../lib/action_connector_api';
|
||||
import { hasSaveActionsCapability } from '../../lib/capabilities';
|
||||
import {
|
||||
|
@ -66,17 +70,29 @@ export const ConnectorEditFlyout = ({
|
|||
docLinks,
|
||||
application: { capabilities },
|
||||
} = useKibana().services;
|
||||
const getConnectorWithoutSecrets = () => ({
|
||||
...(initialConnector as UserConfiguredActionConnector<
|
||||
Record<string, unknown>,
|
||||
Record<string, unknown>
|
||||
>),
|
||||
secrets: {},
|
||||
});
|
||||
const canSave = hasSaveActionsCapability(capabilities);
|
||||
|
||||
const [{ connector }, dispatch] = useReducer(connectorReducer, {
|
||||
connector: { ...initialConnector, secrets: {} },
|
||||
const reducer: ConnectorReducer<
|
||||
Record<string, unknown>,
|
||||
Record<string, unknown>
|
||||
> = createConnectorReducer<Record<string, unknown>, Record<string, unknown>>();
|
||||
const [{ connector }, dispatch] = useReducer(reducer, {
|
||||
connector: getConnectorWithoutSecrets(),
|
||||
});
|
||||
const [isSaving, setIsSaving] = useState<boolean>(false);
|
||||
const [selectedTab, setTab] = useState<EditConectorTabs>(tab);
|
||||
|
||||
const [hasChanges, setHasChanges] = useState<boolean>(false);
|
||||
const setConnector = (key: string, value: any) => {
|
||||
dispatch({ command: { type: 'setConnector' }, payload: { key, value } });
|
||||
|
||||
const setConnector = (value: any) => {
|
||||
dispatch({ command: { type: 'setConnector' }, payload: { key: 'connector', value } });
|
||||
};
|
||||
|
||||
const [testExecutionActionParams, setTestExecutionActionParams] = useState<
|
||||
|
@ -101,7 +117,7 @@ export const ConnectorEditFlyout = ({
|
|||
);
|
||||
|
||||
const closeFlyout = useCallback(() => {
|
||||
setConnector('connector', { ...initialConnector, secrets: {} });
|
||||
setConnector(getConnectorWithoutSecrets());
|
||||
setHasChanges(false);
|
||||
setTestExecutionResult(none);
|
||||
onClose();
|
||||
|
@ -220,7 +236,6 @@ export const ConnectorEditFlyout = ({
|
|||
const onSaveClicked = async (closeAfterSave: boolean = true) => {
|
||||
if (hasErrors) {
|
||||
setConnector(
|
||||
'connector',
|
||||
getConnectorWithInvalidatedFields(
|
||||
connector,
|
||||
configErrors,
|
||||
|
@ -282,7 +297,6 @@ export const ConnectorEditFlyout = ({
|
|||
<ActionConnectorForm
|
||||
connector={connector}
|
||||
errors={connectorErrors}
|
||||
actionTypeName={connector.actionType}
|
||||
dispatch={(changes) => {
|
||||
setHasChanges(true);
|
||||
// if the user changes the connector, "forget" the last execution
|
||||
|
|
|
@ -3,11 +3,14 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { connectorReducer } from './connector_reducer';
|
||||
import { ActionConnector } from '../../../types';
|
||||
import { UserConfiguredActionConnector } from '../../../types';
|
||||
import { createConnectorReducer, ConnectorReducer } from './connector_reducer';
|
||||
|
||||
describe('connector reducer', () => {
|
||||
let initialConnector: ActionConnector;
|
||||
let initialConnector: UserConfiguredActionConnector<
|
||||
Record<string, unknown>,
|
||||
Record<string, unknown>
|
||||
>;
|
||||
beforeAll(() => {
|
||||
initialConnector = {
|
||||
secrets: {},
|
||||
|
@ -20,6 +23,11 @@ describe('connector reducer', () => {
|
|||
};
|
||||
});
|
||||
|
||||
const connectorReducer: ConnectorReducer<
|
||||
Record<string, unknown>,
|
||||
Record<string, unknown>
|
||||
> = createConnectorReducer<Record<string, unknown>, Record<string, unknown>>();
|
||||
|
||||
test('if property name was changed', () => {
|
||||
const updatedConnector = connectorReducer(
|
||||
{ connector: initialConnector },
|
||||
|
|
|
@ -4,30 +4,69 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { isEqual } from 'lodash';
|
||||
import { Reducer } from 'react';
|
||||
import { UserConfiguredActionConnector } from '../../../types';
|
||||
|
||||
interface CommandType {
|
||||
type: 'setConnector' | 'setProperty' | 'setConfigProperty' | 'setSecretsProperty';
|
||||
export type InitialConnector<Config, Secrets> = Partial<
|
||||
UserConfiguredActionConnector<Config, Secrets>
|
||||
> &
|
||||
Pick<UserConfiguredActionConnector<Config, Secrets>, 'actionTypeId' | 'config' | 'secrets'>;
|
||||
|
||||
interface CommandType<
|
||||
T extends 'setConnector' | 'setProperty' | 'setConfigProperty' | 'setSecretsProperty'
|
||||
> {
|
||||
type: T;
|
||||
}
|
||||
|
||||
export interface ActionState {
|
||||
connector: any;
|
||||
interface Payload<Keys, Value> {
|
||||
key: Keys;
|
||||
value: Value;
|
||||
}
|
||||
|
||||
export interface ReducerAction {
|
||||
command: CommandType;
|
||||
payload: {
|
||||
key: string;
|
||||
value: any;
|
||||
};
|
||||
interface TPayload<T, Key extends keyof T> {
|
||||
key: Key;
|
||||
value: T[Key] | null;
|
||||
}
|
||||
|
||||
export const connectorReducer = (state: ActionState, action: ReducerAction) => {
|
||||
const { command, payload } = action;
|
||||
export type ConnectorReducerAction<Config, Secrets> =
|
||||
| {
|
||||
command: CommandType<'setConnector'>;
|
||||
payload: Payload<'connector', InitialConnector<Config, Secrets>>;
|
||||
}
|
||||
| {
|
||||
command: CommandType<'setProperty'>;
|
||||
payload: TPayload<
|
||||
UserConfiguredActionConnector<Config, Secrets>,
|
||||
keyof UserConfiguredActionConnector<Config, Secrets>
|
||||
>;
|
||||
}
|
||||
| {
|
||||
command: CommandType<'setConfigProperty'>;
|
||||
payload: TPayload<Config, keyof Config>;
|
||||
}
|
||||
| {
|
||||
command: CommandType<'setSecretsProperty'>;
|
||||
payload: TPayload<Secrets, keyof Secrets>;
|
||||
};
|
||||
|
||||
export type ConnectorReducer<Config, Secrets> = Reducer<
|
||||
{ connector: UserConfiguredActionConnector<Config, Secrets> },
|
||||
ConnectorReducerAction<Config, Secrets>
|
||||
>;
|
||||
|
||||
export const createConnectorReducer = <Config, Secrets>() => <
|
||||
ConnectorPhase extends
|
||||
| InitialConnector<Config, Secrets>
|
||||
| UserConfiguredActionConnector<Config, Secrets>
|
||||
>(
|
||||
state: { connector: ConnectorPhase },
|
||||
action: ConnectorReducerAction<Config, Secrets>
|
||||
) => {
|
||||
const { connector } = state;
|
||||
|
||||
switch (command.type) {
|
||||
switch (action.command.type) {
|
||||
case 'setConnector': {
|
||||
const { key, value } = payload;
|
||||
const { key, value } = action.payload as Payload<'connector', ConnectorPhase>;
|
||||
if (key === 'connector') {
|
||||
return {
|
||||
...state,
|
||||
|
@ -38,7 +77,10 @@ export const connectorReducer = (state: ActionState, action: ReducerAction) => {
|
|||
}
|
||||
}
|
||||
case 'setProperty': {
|
||||
const { key, value } = payload;
|
||||
const { key, value } = action.payload as TPayload<
|
||||
UserConfiguredActionConnector<Config, Secrets>,
|
||||
keyof UserConfiguredActionConnector<Config, Secrets>
|
||||
>;
|
||||
if (isEqual(connector[key], value)) {
|
||||
return state;
|
||||
} else {
|
||||
|
@ -52,7 +94,7 @@ export const connectorReducer = (state: ActionState, action: ReducerAction) => {
|
|||
}
|
||||
}
|
||||
case 'setConfigProperty': {
|
||||
const { key, value } = payload;
|
||||
const { key, value } = action.payload as TPayload<Config, keyof Config>;
|
||||
if (isEqual(connector.config[key], value)) {
|
||||
return state;
|
||||
} else {
|
||||
|
@ -61,7 +103,7 @@ export const connectorReducer = (state: ActionState, action: ReducerAction) => {
|
|||
connector: {
|
||||
...connector,
|
||||
config: {
|
||||
...connector.config,
|
||||
...(connector.config as Config),
|
||||
[key]: value,
|
||||
},
|
||||
},
|
||||
|
@ -69,7 +111,7 @@ export const connectorReducer = (state: ActionState, action: ReducerAction) => {
|
|||
}
|
||||
}
|
||||
case 'setSecretsProperty': {
|
||||
const { key, value } = payload;
|
||||
const { key, value } = action.payload as TPayload<Secrets, keyof Secrets>;
|
||||
if (isEqual(connector.secrets[key], value)) {
|
||||
return state;
|
||||
} else {
|
||||
|
@ -78,7 +120,7 @@ export const connectorReducer = (state: ActionState, action: ReducerAction) => {
|
|||
connector: {
|
||||
...connector,
|
||||
secrets: {
|
||||
...connector.secrets,
|
||||
...(connector.secrets as Secrets),
|
||||
[key]: value,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -43,15 +43,16 @@ export { ActionType };
|
|||
|
||||
export type ActionTypeIndex = Record<string, ActionType>;
|
||||
export type AlertTypeIndex = Map<string, AlertType>;
|
||||
export type ActionTypeRegistryContract<ActionConnector = any, ActionParams = any> = PublicMethodsOf<
|
||||
TypeRegistry<ActionTypeModel<ActionConnector, ActionParams>>
|
||||
>;
|
||||
export type ActionTypeRegistryContract<
|
||||
ActionConnector = unknown,
|
||||
ActionParams = unknown
|
||||
> = PublicMethodsOf<TypeRegistry<ActionTypeModel<ActionConnector, ActionParams>>>;
|
||||
export type AlertTypeRegistryContract = PublicMethodsOf<TypeRegistry<AlertTypeModel>>;
|
||||
|
||||
export interface ActionConnectorFieldsProps<TActionConnector> {
|
||||
action: TActionConnector;
|
||||
editActionConfig: (property: string, value: any) => void;
|
||||
editActionSecrets: (property: string, value: any) => void;
|
||||
editActionConfig: (property: string, value: unknown) => void;
|
||||
editActionSecrets: (property: string, value: unknown) => void;
|
||||
errors: IErrorObject;
|
||||
readOnly: boolean;
|
||||
consumer?: string;
|
||||
|
@ -128,13 +129,13 @@ export type UserConfiguredActionConnector<Config, Secrets> = ActionConnectorProp
|
|||
isPreconfigured: false;
|
||||
};
|
||||
|
||||
export type ActionConnector<Config = Record<string, any>, Secrets = Record<string, any>> =
|
||||
export type ActionConnector<Config = Record<string, unknown>, Secrets = Record<string, unknown>> =
|
||||
| PreConfiguredActionConnector
|
||||
| UserConfiguredActionConnector<Config, Secrets>;
|
||||
|
||||
export type ActionConnectorWithoutId<
|
||||
Config = Record<string, any>,
|
||||
Secrets = Record<string, any>
|
||||
Config = Record<string, unknown>,
|
||||
Secrets = Record<string, unknown>
|
||||
> = Omit<UserConfiguredActionConnector<Config, Secrets>, 'id'>;
|
||||
|
||||
export type ActionConnectorTableItem = ActionConnector & {
|
||||
|
@ -186,7 +187,7 @@ export interface AlertTableItem extends Alert {
|
|||
|
||||
export interface AlertTypeParamsExpressionProps<
|
||||
Params extends AlertTypeParams = AlertTypeParams,
|
||||
MetaData = Record<string, any>,
|
||||
MetaData = Record<string, unknown>,
|
||||
ActionGroupIds extends string = string
|
||||
> {
|
||||
alertParams: Params;
|
||||
|
|
Loading…
Reference in a new issue