Skip to content

Commit

Permalink
[RAM] Remove allow slack channels (#161674)
Browse files Browse the repository at this point in the history
## Summary

Remove allow slack channels feature for 8.9 until we have a better way
to deal with channels


### Checklist

- [X] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
  • Loading branch information
XavierM authored Jul 11, 2023
1 parent f4e9cd1 commit dd292b7
Show file tree
Hide file tree
Showing 10 changed files with 9 additions and 349 deletions.
4 changes: 1 addition & 3 deletions x-pack/plugins/stack_connectors/common/slack_api/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@ export const SlackApiSecretsSchema = schema.object({
token: schema.string({ minLength: 1 }),
});

export const SlackApiConfigSchema = schema.object({
allowedChannels: schema.maybe(schema.arrayOf(schema.string())),
});
export const SlackApiConfigSchema = schema.object({}, { defaultValue: {} });

export const GetChannelsParamsSchema = schema.object({
subAction: schema.literal('getChannels'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,34 +48,21 @@ describe('SlackActionFields renders', () => {
secrets: {
token: 'some token',
},
config: {
allowedChannels: ['foo', 'bar'],
},
config: {},
id: 'test',
actionTypeId: '.slack',
name: 'slack',
isDeprecated: false,
};

const { container } = render(
render(
<ConnectorFormTestProvider connector={actionConnector} onSubmit={onSubmit}>
<SlackActionFields readOnly={false} isEdit={false} registerPreSubmitValidator={() => {}} />
</ConnectorFormTestProvider>
);

expect(screen.getByTestId('secrets.token-input')).toBeInTheDocument();
expect(screen.getByTestId('secrets.token-input')).toHaveValue('some token');
expect(screen.getByTestId('config.allowedChannels-input')).toBeInTheDocument();
const allowedChannels: string[] = [];
container
.querySelectorAll('[data-test-subj="config.allowedChannels-input"] .euiBadge')
.forEach((node) => {
const channel = node.getAttribute('title');
if (channel) {
allowedChannels.push(channel);
}
});
expect(allowedChannels).toEqual(['foo', 'bar']);
});

it('connector validation succeeds when connector config is valid for Web API type', async () => {
Expand Down Expand Up @@ -105,9 +92,6 @@ describe('SlackActionFields renders', () => {
secrets: {
token: 'some token',
},
config: {
allowedChannels: [],
},
id: 'test',
actionTypeId: '.slack',
name: 'slack',
Expand All @@ -116,62 +100,4 @@ describe('SlackActionFields renders', () => {
isValid: true,
});
});

it('Allowed Channels combobox should be disable when there is NO token', async () => {
const actionConnector = {
secrets: {
token: '',
},
config: {
allowedChannels: ['foo', 'bar'],
},
id: 'test',
actionTypeId: '.slack',
name: 'slack',
isDeprecated: false,
};

const { container } = render(
<ConnectorFormTestProvider connector={actionConnector} onSubmit={onSubmit}>
<SlackActionFields readOnly={false} isEdit={false} registerPreSubmitValidator={() => {}} />
</ConnectorFormTestProvider>
);
expect(
container.querySelector(
'[data-test-subj="config.allowedChannels-input"].euiComboBox-isDisabled'
)
).toBeInTheDocument();
});

it('Allowed Channels combobox should NOT be disable when there is token', async () => {
const actionConnector = {
secrets: {
token: 'qwertyuiopasdfghjklzxcvbnm',
},
config: {
allowedChannels: ['foo', 'bar'],
},
id: 'test',
actionTypeId: '.slack',
name: 'slack',
isDeprecated: false,
};

(useFetchChannels as jest.Mock).mockImplementation(() => ({
channels: [{ label: 'foo' }, { label: 'bar' }, { label: 'hello' }, { label: 'world' }],
isLoading: false,
}));

const { container } = render(
<ConnectorFormTestProvider connector={actionConnector} onSubmit={onSubmit}>
<SlackActionFields readOnly={false} isEdit={false} registerPreSubmitValidator={() => {}} />
</ConnectorFormTestProvider>
);

expect(
container.querySelector(
'[data-test-subj="config.allowedChannels-input"].euiComboBox-isDisabled'
)
).not.toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,20 @@
* 2.0.
*/

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

import { useFormContext, useFormData } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib';
import { debounce, isEmpty } from 'lodash';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import * as i18n from './translations';
import { useFetchChannels } from './use_fetch_channels';

/** wait this many ms after the user completes typing before applying the filter input */
const INPUT_TIMEOUT = 250;

const getSecretsFormSchema = (docLinks: DocLinksStart): SecretsFieldSchema[] => [
{
Expand All @@ -42,31 +36,6 @@ const getSecretsFormSchema = (docLinks: DocLinksStart): SecretsFieldSchema[] =>
},
];

const getConfigFormSchemaAfterSecrets = (
options: EuiComboBoxOptionOption[],
isLoading: boolean,
isDisabled: boolean
): ConfigFieldSchema[] => [
{
id: 'allowedChannels',
isRequired: false,
label: i18n.ALLOWED_CHANNELS,
helpText: (
<FormattedMessage
id="xpack.stackConnectors.components.slack_api.allowedChannelsText"
defaultMessage="By default, the connector can access all channels within the scope of the Slack app."
/>
),
type: 'COMBO_BOX',
euiFieldProps: {
isDisabled,
isLoading,
noSuggestions: false,
options,
},
},
];

const NO_SCHEMA: ConfigFieldSchema[] = [];

export const SlackActionFieldsComponents: React.FC<ActionConnectorFieldsProps> = ({
Expand All @@ -75,39 +44,12 @@ export const SlackActionFieldsComponents: React.FC<ActionConnectorFieldsProps> =
}) => {
const { docLinks } = useKibana().services;

const form = useFormContext();
const { setFieldValue } = form;
const [formData] = useFormData({ form });
const [authToken, setAuthToken] = useState('');

const { channels, isLoading } = useFetchChannels({ authToken });
const configFormSchemaAfterSecrets = useMemo(
() => getConfigFormSchemaAfterSecrets(channels, isLoading, channels.length === 0),
[channels, isLoading]
);

const debounceSetToken = debounce(setAuthToken, INPUT_TIMEOUT);
useEffect(() => {
if (formData.secrets && formData.secrets.token !== authToken) {
debounceSetToken(formData.secrets.token);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [formData.secrets]);

useEffect(() => {
if (isEmpty(authToken) && channels.length > 0) {
setFieldValue('config.allowedChannels', []);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [authToken]);

return (
<SimpleConnectorForm
isEdit={isEdit}
readOnly={readOnly}
configFormSchema={NO_SCHEMA}
secretsFormSchema={getSecretsFormSchema(docLinks)}
configFormSchemaAfterSecrets={configFormSchemaAfterSecrets}
/>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,23 +55,9 @@ describe('validate config', () => {
}).toThrowErrorMatchingInlineSnapshot(
`"error validating action type config: [message]: definition for this key is missing"`
);

expect(() => {
validateConfig(connectorType, { allowedChannels: 'foo' }, { configurationUtilities });
}).toThrowErrorMatchingInlineSnapshot(
`"error validating action type config: [allowedChannels]: could not parse array value from json input"`
);
});

test('should validate when config are valid', () => {
expect(() => {
validateConfig(
connectorType,
{ allowedChannels: ['foo', 'bar'] },
{ configurationUtilities }
);
}).not.toThrow();

expect(() => {
validateConfig(connectorType, {}, { configurationUtilities });
}).not.toThrow();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ const slackApiExecutor = async ({

const externalService = createExternalService(
{
config,
secrets,
},
logger,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,50 +177,5 @@ describe('Slack API service', () => {
status: 'error',
});
});

test('should NOT by pass allowed channels when present', async () => {
service = createExternalService(
{
secrets: { token: 'token' },
config: { allowedChannels: ['foo', 'bar'] },
},
logger,
configurationUtilities
);

expect(
await service.postMessage({ channels: ['general', 'privat'], text: 'a message' })
).toEqual({
actionId: SLACK_API_CONNECTOR_ID,
serviceMessage:
'The channel "general,privat" is not included in the allowed channels list "foo,bar"',
message: 'error posting slack message',
status: 'error',
});
});

test('should allowed channels to be persisted', async () => {
service = createExternalService(
{
secrets: { token: 'token' },
config: { allowedChannels: ['foo', 'bar', 'general', 'privat'] },
},
logger,
configurationUtilities
);
requestMock.mockImplementation(() => postMessageResponse);

await service.postMessage({ channels: ['general', 'privat'], text: 'a message' });

expect(requestMock).toHaveBeenCalledTimes(1);
expect(requestMock).toHaveBeenNthCalledWith(1, {
axios,
logger,
configurationUtilities,
method: 'post',
url: 'chat.postMessage',
data: { channel: 'general', text: 'a message' },
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,11 @@ const buildSlackExecutorSuccessResponse = <T extends SlackAPiResponse>({
};

export const createExternalService = (
{ config, secrets }: { config?: { allowedChannels?: string[] }; secrets: { token: string } },
{ secrets }: { secrets: { token: string } },
logger: Logger,
configurationUtilities: ActionsConfigurationUtilities
): SlackApiService => {
const { token } = secrets;
const { allowedChannels } = config || { allowedChannels: [] };
if (!token) {
throw Error(`[Action][${SLACK_CONNECTOR_NAME}]: Wrong configuration.`);
}
Expand Down Expand Up @@ -170,23 +169,6 @@ export const createExternalService = (
}
result.data.channels = channels;
const responseData = result.data;
if ((allowedChannels ?? []).length > 0) {
const allowedChannelsList = channels.filter((channel: ChannelsResponse) =>
allowedChannels?.includes(channel.name)
);
allowedChannels?.forEach((ac) => {
if (!allowedChannelsList.find((c: ChannelsResponse) => c.name === ac)) {
allowedChannelsList.push({
id: '-1',
name: ac,
is_channel: true,
is_archived: false,
is_private: false,
});
}
});
responseData.channels = allowedChannelsList;
}

return buildSlackExecutorSuccessResponse<GetChannelsResponse>({
slackApiResponseData: responseData,
Expand All @@ -201,19 +183,6 @@ export const createExternalService = (
text,
}: PostMessageSubActionParams): Promise<ConnectorTypeExecutorResult<unknown>> => {
try {
if (
allowedChannels &&
allowedChannels.length > 0 &&
!channels.every((c) => allowedChannels?.includes(c))
) {
return buildSlackExecutorErrorResponse({
slackApiError: {
message: `The channel "${channels.join()}" is not included in the allowed channels list "${allowedChannels.join()}"`,
},
logger,
});
}

const result: AxiosResponse<PostMessageResponse> = await request({
axios: axiosInstance,
method: 'post',
Expand Down
11 changes: 3 additions & 8 deletions x-pack/plugins/stack_connectors/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
* 2.0.
*/

import { PluginInitializerContext, Plugin, CoreSetup, Logger } from '@kbn/core/server';
import { PluginInitializerContext, Plugin, CoreSetup } from '@kbn/core/server';
import { PluginSetupContract as ActionsPluginSetupContract } from '@kbn/actions-plugin/server';
import { registerConnectorTypes } from './connector_types';
import { getSlackApiChannelsRoute, getWellKnownEmailServiceRoute } from './routes';
import { getWellKnownEmailServiceRoute } from './routes';
export interface ConnectorsPluginsSetup {
actions: ActionsPluginSetupContract;
}
Expand All @@ -18,18 +18,13 @@ export interface ConnectorsPluginsStart {
}

export class StackConnectorsPlugin implements Plugin<void, void> {
private readonly logger: Logger;

constructor(context: PluginInitializerContext) {
this.logger = context.logger.get();
}
constructor(context: PluginInitializerContext) {}

public setup(core: CoreSetup<ConnectorsPluginsStart>, plugins: ConnectorsPluginsSetup) {
const router = core.http.createRouter();
const { actions } = plugins;

getWellKnownEmailServiceRoute(router);
getSlackApiChannelsRoute(router, actions.getActionsConfigurationUtilities(), this.logger);

registerConnectorTypes({
actions,
Expand Down
Loading

0 comments on commit dd292b7

Please sign in to comment.