Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ describe('deserializer', () => {
...data,
config: {
...data.config,
allowedChannels: ['#general', '#random'],
allowedChannels: [{ name: '#general' }, { name: '#random' }],
},
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ export const deserializer = (data: ConnectorFormSchema): InternalConnectorForm =
const formattedChannels =
allowedChannels.map((channel) => {
if (channel.name.startsWith('#')) {
return channel.name;
return channel;
}

return `#${channel.name}`;
return { ...channel, name: `#${channel.name}` };
}) ?? [];

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ describe('serializer', () => {
isDeprecated: false,
isSystemAction: false,
actionTypeId: CONNECTOR_ID,
config: { allowedChannels: ['#general', '#random'] },
config: { allowedChannels: [{ name: 'general' }, { name: '#random' }] },
secrets: {},
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,20 @@
*/

import type { ConnectorFormSchema, InternalConnectorForm } from '@kbn/alerts-ui-shared';
import type { SlackApiConfig } from '@kbn/connector-schemas/slack_api';

export const serializer = (data: InternalConnectorForm): ConnectorFormSchema => {
const formAllowedChannels = (data.config?.allowedChannels as string[]) ?? [];
const allowedChannels = formAllowedChannels.map((option) => ({ name: option })) ?? [];
const formAllowedChannels =
(data.config?.allowedChannels as SlackApiConfig['allowedChannels']) ?? [];

const allowedChannels =
formAllowedChannels.map((channel) => {
if (channel.name.startsWith('#')) {
return channel;
}

return { ...channel, name: `#${channel.name}` };
}) ?? [];

return {
...data,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ describe('SlackActionFields renders', () => {
expect(screen.getByText('#test')).toBeInTheDocument();
});

it('changes the values correctly', async () => {
it('changes the values correctly and preserve the id of the channel', async () => {
render(
<ConnectorFormTestProvider
connector={actionConnector}
Expand All @@ -232,7 +232,6 @@ describe('SlackActionFields renders', () => {
await userEvent.click(screen.getByTestId('secrets.token-input'));
await userEvent.paste('token updated');

await userEvent.click(screen.getByTestId('comboBoxClearButton'));
await userEvent.click(screen.getByTestId('comboBoxSearchInput'));
await userEvent.type(screen.getByTestId('comboBoxSearchInput'), '#new-channel{enter}');

Expand All @@ -247,7 +246,7 @@ describe('SlackActionFields renders', () => {
token: 'token updated',
},
config: {
allowedChannels: [{ name: '#new-channel' }],
allowedChannels: [{ id: 'channel-id', name: '#test' }, { name: '#new-channel' }],
},
},
isValid: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,32 @@
* 2.0.
*/

import React from 'react';
import React, { useCallback } from 'react';
import type {
ActionConnectorFieldsProps,
ConfigFieldSchema,
SecretsFieldSchema,
} from '@kbn/triggers-actions-ui-plugin/public';
import { SimpleConnectorForm, useKibana } from '@kbn/triggers-actions-ui-plugin/public';
import type { EuiComboBoxOptionOption } from '@elastic/eui';
import { EuiLink, EuiText } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import type { DocLinksStart } from '@kbn/core/public';

import type { FieldValidateResponse } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib';
import {
useFormContext,
useFormData,
VALIDATION_TYPES,
} from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib';
import type { SlackApiConfig } from '@kbn/connector-schemas/slack_api';
import * as i18n from './translations';

interface AllowedChannels {
id: string;
name: string;
}

const getSecretsFormSchema = (docLinks: DocLinksStart): SecretsFieldSchema[] => [
{
id: 'token',
Expand All @@ -34,59 +47,113 @@ const getSecretsFormSchema = (docLinks: DocLinksStart): SecretsFieldSchema[] =>
},
];

const getConfigFormSchema = (): ConfigFieldSchema<string>[] => [
{
id: 'allowedChannels',
isRequired: false,
label: i18n.ALLOWED_CHANNELS,
type: 'COMBO_BOX',
validations: [
{
isBlocking: true,
validator: (args) => {
const valueAsArray = Array.isArray(args.value) ? args.value : [args.value];

if (valueAsArray.length === 0) {
return;
}

const areAllValid = valueAsArray.every((value) => value.startsWith('#'));

if (areAllValid) {
return;
}

return {
code: 'ERR_FIELD_FORMAT',
formatType: 'COMBO_BOX',
message: i18n.CHANNEL_NAME_ERROR,
};
},
const areChannelsValid = (channels: AllowedChannels[]) => {
if (channels.length === 0) {
return true;
}

const areAllValid = channels.every((channel) => channel.name.startsWith('#'));

if (areAllValid) {
return true;
}

return false;
};

const getAllowedChannlesConfigSchema = (
euiFieldProps: ConfigFieldSchema['euiFieldProps']
): ConfigFieldSchema<AllowedChannels[]> => ({
id: 'allowedChannels',
isRequired: false,
label: i18n.ALLOWED_CHANNELS,
type: 'COMBO_BOX',
validations: [
{
isBlocking: true,
validator: (args) => {
const isValid = areChannelsValid(args.value);

if (isValid) {
return;
}

return {
code: 'ERR_FIELD_FORMAT',
formatType: 'COMBO_BOX',
message: i18n.CHANNEL_NAME_ERROR,
};
},
],
euiFieldProps: {
noSuggestions: true,
autoComplete: 'off',
append: (
<EuiText size="xs" color="subdued">
{i18n.OPTIONAL_LABEL}
</EuiText>
),
},
],
euiFieldProps: {
...euiFieldProps,
noSuggestions: true,
autoComplete: 'off',
append: (
<EuiText size="xs" color="subdued">
{i18n.OPTIONAL_LABEL}
</EuiText>
),
},
];
});

export const SlackActionFieldsComponents: React.FC<ActionConnectorFieldsProps> = ({
readOnly,
isEdit,
}) => {
const { docLinks } = useKibana().services;
const { getFields } = useFormContext();

const [{ config }] = useFormData({
watch: ['config.allowedChannels'],
});

const { allowedChannels = [] }: SlackApiConfig = config ?? {};

const selectedOptions = allowedChannels.map((channel) => ({
label: channel.name,
value: channel,
}));

const fields = getFields();

const allowedChannelsField = fields['config.allowedChannels'];

const onCreateOption = useCallback(
(value: string) => {
const finalValue = { name: value };

const { isValid } = allowedChannelsField.validate({
value: [finalValue],
validationType: VALIDATION_TYPES.ARRAY_ITEM,
}) as FieldValidateResponse;

if (!isValid) {
return false;
}

allowedChannelsField.setValue([...allowedChannels, finalValue]);
},
[allowedChannels, allowedChannelsField]
);

const onChange = useCallback(
(options: EuiComboBoxOptionOption<AllowedChannels>[]) => {
allowedChannelsField.setValue(
options.map((option) => ({ name: option.value?.name, id: option.value?.id }))
);
},
[allowedChannelsField]
);

return (
<SimpleConnectorForm
isEdit={isEdit}
readOnly={readOnly}
configFormSchema={getConfigFormSchema()}
configFormSchema={[
getAllowedChannlesConfigSchema({ onChange, onCreateOption, selectedOptions }),
]}
secretsFormSchema={getSecretsFormSchema(docLinks)}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ import { FIELD_TYPES, getUseField } from '@kbn/es-ui-shared-plugin/static/forms/
import { fieldValidators } from '@kbn/es-ui-shared-plugin/static/forms/helpers';
import { i18n } from '@kbn/i18n';

type Validations<T = string> = Array<ValidationConfig<FormData, string, T>>;
type Validations<T = any> = Array<ValidationConfig<FormData, string, T>>;

export interface CommonFieldSchema<T = string> {
export interface CommonFieldSchema<T = any> {
id: string;
label: string;
helpText?: string | ReactNode;
Expand All @@ -30,7 +30,7 @@ export interface CommonFieldSchema<T = string> {
validations?: Validations<T>;
}

export interface ConfigFieldSchema<T = string> extends CommonFieldSchema<T> {
export interface ConfigFieldSchema<T = any> extends CommonFieldSchema<T> {
isUrlField?: boolean;
requireTld?: boolean;
defaultValue?: T;
Expand All @@ -53,7 +53,7 @@ const UseTextField = getUseField({ component: Field });
const UseComboBoxField = getUseField({ component: ComboBoxField });
const { emptyField, urlField } = fieldValidators;

const getFieldConfig = <T extends string | string[] = string>({
const getFieldConfig = <T,>({
label,
isRequired = true,
isUrlField = false,
Expand Down