From dd292b70b7fc121dcef4fad84b979d730b6c6f6e Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau Date: Tue, 11 Jul 2023 13:25:20 -0400 Subject: [PATCH] [RAM] Remove allow slack channels (#161674) ## 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 --- .../common/slack_api/schema.ts | 4 +- .../slack_api/slack_connectors.test.tsx | 78 +------------ .../slack_api/slack_connectors.tsx | 62 +--------- .../connector_types/slack_api/index.test.ts | 14 --- .../server/connector_types/slack_api/index.ts | 1 - .../connector_types/slack_api/service.test.ts | 45 -------- .../connector_types/slack_api/service.ts | 33 +----- .../plugins/stack_connectors/server/plugin.ts | 11 +- .../server/routes/get_slack_api_channels.ts | 109 ------------------ .../stack_connectors/server/routes/index.ts | 1 - 10 files changed, 9 insertions(+), 349 deletions(-) delete mode 100644 x-pack/plugins/stack_connectors/server/routes/get_slack_api_channels.ts diff --git a/x-pack/plugins/stack_connectors/common/slack_api/schema.ts b/x-pack/plugins/stack_connectors/common/slack_api/schema.ts index 3a96528ba2801..4f121fd92389a 100644 --- a/x-pack/plugins/stack_connectors/common/slack_api/schema.ts +++ b/x-pack/plugins/stack_connectors/common/slack_api/schema.ts @@ -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'), diff --git a/x-pack/plugins/stack_connectors/public/connector_types/slack_api/slack_connectors.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/slack_api/slack_connectors.test.tsx index 8346e4b07c697..8c255bce003fe 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/slack_api/slack_connectors.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/slack_api/slack_connectors.test.tsx @@ -48,16 +48,14 @@ describe('SlackActionFields renders', () => { secrets: { token: 'some token', }, - config: { - allowedChannels: ['foo', 'bar'], - }, + config: {}, id: 'test', actionTypeId: '.slack', name: 'slack', isDeprecated: false, }; - const { container } = render( + render( {}} /> @@ -65,17 +63,6 @@ describe('SlackActionFields renders', () => { 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 () => { @@ -105,9 +92,6 @@ describe('SlackActionFields renders', () => { secrets: { token: 'some token', }, - config: { - allowedChannels: [], - }, id: 'test', actionTypeId: '.slack', name: 'slack', @@ -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( - - {}} /> - - ); - 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( - - {}} /> - - ); - - expect( - container.querySelector( - '[data-test-subj="config.allowedChannels-input"].euiComboBox-isDisabled' - ) - ).not.toBeInTheDocument(); - }); }); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/slack_api/slack_connectors.tsx b/x-pack/plugins/stack_connectors/public/connector_types/slack_api/slack_connectors.tsx index 2caf8bff0b611..71a262954d2d1 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/slack_api/slack_connectors.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/slack_api/slack_connectors.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useEffect, useMemo, useState } from 'react'; +import React from 'react'; import { ActionConnectorFieldsProps, ConfigFieldSchema, @@ -13,18 +13,12 @@ import { 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[] => [ { @@ -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: ( - - ), - type: 'COMBO_BOX', - euiFieldProps: { - isDisabled, - isLoading, - noSuggestions: false, - options, - }, - }, -]; - const NO_SCHEMA: ConfigFieldSchema[] = []; export const SlackActionFieldsComponents: React.FC = ({ @@ -75,39 +44,12 @@ export const SlackActionFieldsComponents: React.FC = }) => { 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 ( ); }; diff --git a/x-pack/plugins/stack_connectors/server/connector_types/slack_api/index.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/slack_api/index.test.ts index 2b4022285dea8..c4922020cdce7 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/slack_api/index.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/slack_api/index.test.ts @@ -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(); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/slack_api/index.ts b/x-pack/plugins/stack_connectors/server/connector_types/slack_api/index.ts index bc3128dc666b8..ffb952f457956 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/slack_api/index.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/slack_api/index.ts @@ -105,7 +105,6 @@ const slackApiExecutor = async ({ const externalService = createExternalService( { - config, secrets, }, logger, diff --git a/x-pack/plugins/stack_connectors/server/connector_types/slack_api/service.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/slack_api/service.test.ts index 2f22944ff6480..85df189c72d29 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/slack_api/service.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/slack_api/service.test.ts @@ -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' }, - }); - }); }); }); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/slack_api/service.ts b/x-pack/plugins/stack_connectors/server/connector_types/slack_api/service.ts index a2b6ff8989880..2db25dc52f223 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/slack_api/service.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/slack_api/service.ts @@ -106,12 +106,11 @@ const buildSlackExecutorSuccessResponse = ({ }; 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.`); } @@ -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({ slackApiResponseData: responseData, @@ -201,19 +183,6 @@ export const createExternalService = ( text, }: PostMessageSubActionParams): Promise> => { 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 = await request({ axios: axiosInstance, method: 'post', diff --git a/x-pack/plugins/stack_connectors/server/plugin.ts b/x-pack/plugins/stack_connectors/server/plugin.ts index 3e76b9adbb083..ce1795b4eb7fb 100644 --- a/x-pack/plugins/stack_connectors/server/plugin.ts +++ b/x-pack/plugins/stack_connectors/server/plugin.ts @@ -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; } @@ -18,18 +18,13 @@ export interface ConnectorsPluginsStart { } export class StackConnectorsPlugin implements Plugin { - private readonly logger: Logger; - - constructor(context: PluginInitializerContext) { - this.logger = context.logger.get(); - } + constructor(context: PluginInitializerContext) {} public setup(core: CoreSetup, plugins: ConnectorsPluginsSetup) { const router = core.http.createRouter(); const { actions } = plugins; getWellKnownEmailServiceRoute(router); - getSlackApiChannelsRoute(router, actions.getActionsConfigurationUtilities(), this.logger); registerConnectorTypes({ actions, diff --git a/x-pack/plugins/stack_connectors/server/routes/get_slack_api_channels.ts b/x-pack/plugins/stack_connectors/server/routes/get_slack_api_channels.ts deleted file mode 100644 index dac35c0503cbe..0000000000000 --- a/x-pack/plugins/stack_connectors/server/routes/get_slack_api_channels.ts +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { schema } from '@kbn/config-schema'; -import { - IRouter, - RequestHandlerContext, - KibanaRequest, - IKibanaResponse, - KibanaResponseFactory, - Logger, -} from '@kbn/core/server'; -import axios, { AxiosResponse } from 'axios'; -import { request } from '@kbn/actions-plugin/server/lib/axios_utils'; -import { ActionsConfigurationUtilities } from '@kbn/actions-plugin/server/actions_config'; -import { INTERNAL_BASE_STACK_CONNECTORS_API_PATH } from '../../common'; -import { SLACK_URL } from '../../common/slack_api/constants'; -import { ChannelsResponse, GetChannelsResponse } from '../../common/slack_api/types'; - -const bodySchema = schema.object({ - authToken: schema.string(), -}); - -const RE_TRY = 5; -const LIMIT = 1000; - -export const getSlackApiChannelsRoute = ( - router: IRouter, - configurationUtilities: ActionsConfigurationUtilities, - logger: Logger -) => { - router.post( - { - path: `${INTERNAL_BASE_STACK_CONNECTORS_API_PATH}/_slack_api/channels`, - validate: { - body: bodySchema, - }, - }, - handler - ); - - async function handler( - ctx: RequestHandlerContext, - req: KibanaRequest, - res: KibanaResponseFactory - ): Promise { - const { authToken } = req.body; - - const axiosInstance = axios.create({ - baseURL: SLACK_URL, - headers: { - Authorization: `Bearer ${authToken}`, - 'Content-type': 'application/json; charset=UTF-8', - }, - }); - - const fetchChannels = (cursor: string = ''): Promise> => { - return request({ - axios: axiosInstance, - configurationUtilities, - logger, - method: 'get', - url: `conversations.list?exclude_archived=true&types=public_channel,private_channel&limit=${LIMIT}${ - cursor.length > 0 ? `&cursor=${cursor}` : '' - }`, - }); - }; - - let numberOfFetch = 0; - let cursor = ''; - const channels: ChannelsResponse[] = []; - let result: AxiosResponse = { - data: { ok: false, channels }, - status: 0, - statusText: '', - headers: {}, - config: {}, - }; - - while (numberOfFetch < RE_TRY) { - result = await fetchChannels(cursor); - if (result.data.ok && (result.data?.channels ?? []).length > 0) { - channels.push(...(result.data?.channels ?? [])); - } - if ( - result.data.ok && - result.data.response_metadata && - result.data.response_metadata.next_cursor && - result.data.response_metadata.next_cursor.length > 0 - ) { - numberOfFetch += 1; - cursor = result.data.response_metadata.next_cursor; - } else { - break; - } - } - - return res.ok({ - body: { - ...result.data, - channels, - }, - }); - } -}; diff --git a/x-pack/plugins/stack_connectors/server/routes/index.ts b/x-pack/plugins/stack_connectors/server/routes/index.ts index df48f18480252..2766b99679845 100644 --- a/x-pack/plugins/stack_connectors/server/routes/index.ts +++ b/x-pack/plugins/stack_connectors/server/routes/index.ts @@ -6,4 +6,3 @@ */ export { getWellKnownEmailServiceRoute } from './get_well_known_email_service'; -export { getSlackApiChannelsRoute } from './get_slack_api_channels';