[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:
Yuliia Naumenko 2021-01-07 11:50:48 -08:00 committed by GitHub
parent 52e3371c39
commit 1c30525d46
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 183 additions and 62 deletions

View file

@ -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`}

View file

@ -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 } });
};

View file

@ -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 } });
};

View file

@ -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

View file

@ -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 },

View file

@ -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,
},
},

View file

@ -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;