diff --git a/packages/backend/src/apps/slack/actions.ts b/packages/backend/src/apps/slack/actions.ts index 7e44ebc4..e4063b76 100644 --- a/packages/backend/src/apps/slack/actions.ts +++ b/packages/backend/src/apps/slack/actions.ts @@ -1,12 +1,15 @@ import SendMessageToChannel from './actions/send-message-to-channel'; +import FindMessage from './actions/find-message'; import SlackClient from './client'; export default class Actions { client: SlackClient; sendMessageToChannel: SendMessageToChannel; + findMessage: FindMessage; constructor(client: SlackClient) { this.client = client; this.sendMessageToChannel = new SendMessageToChannel(client); + this.findMessage = new FindMessage(client); } } diff --git a/packages/backend/src/apps/slack/actions/find-message.ts b/packages/backend/src/apps/slack/actions/find-message.ts new file mode 100644 index 00000000..4185d7b5 --- /dev/null +++ b/packages/backend/src/apps/slack/actions/find-message.ts @@ -0,0 +1,26 @@ +import SlackClient from '../client'; + +export default class FindMessage { + client: SlackClient; + + constructor(client: SlackClient) { + this.client = client; + } + + async run() { + const parameters = this.client.step.parameters; + const query = parameters.query as string; + const sortBy = parameters.sortBy as string; + const sortDirection = parameters.sortDirection as string; + const count = 1; + + const messages = await this.client.findMessages.run( + query, + sortBy, + sortDirection, + count, + ); + + return messages; + } +} diff --git a/packages/backend/src/apps/slack/client/endpoints/find-messages.ts b/packages/backend/src/apps/slack/client/endpoints/find-messages.ts new file mode 100644 index 00000000..50d99819 --- /dev/null +++ b/packages/backend/src/apps/slack/client/endpoints/find-messages.ts @@ -0,0 +1,44 @@ +import SlackClient from '../index'; + +export default class FindMessages { + client: SlackClient; + + constructor(client: SlackClient) { + this.client = client; + } + + async run(query: string, sortBy: string, sortDirection: string, count = 1) { + const headers = { + Authorization: `Bearer ${this.client.connection.formattedData.accessToken}`, + }; + + const params = { + query, + sort: sortBy, + sort_dir: sortDirection, + count, + }; + + const response = await this.client.httpClient.get('/search.messages', { + headers, + params, + }); + + const data = response.data; + + if (!data.ok) { + if (data.error === 'missing_scope') { + throw new Error( + `Error occured while finding messages; ${data.error}: ${data.needed}` + ); + } + + throw new Error(`Error occured while finding messages; ${data.error}`); + } + + const messages = data.messages.matches; + const message = messages?.[0]; + + return message; + } +} diff --git a/packages/backend/src/apps/slack/client/index.ts b/packages/backend/src/apps/slack/client/index.ts index b3e9e17d..ba47be53 100644 --- a/packages/backend/src/apps/slack/client/index.ts +++ b/packages/backend/src/apps/slack/client/index.ts @@ -2,6 +2,7 @@ import { IFlow, IStep, IConnection } from '@automatisch/types'; import HttpClient from '../../../helpers/http-client'; import VerifyAccessToken from './endpoints/verify-access-token'; import PostMessageToChannel from './endpoints/post-message-to-channel'; +import FindMessages from './endpoints/find-messages'; export default class SlackClient { flow: IFlow; @@ -11,6 +12,7 @@ export default class SlackClient { verifyAccessToken: VerifyAccessToken; postMessageToChannel: PostMessageToChannel; + findMessages: FindMessages; static baseUrl = 'https://slack.com/api'; @@ -22,5 +24,6 @@ export default class SlackClient { this.httpClient = new HttpClient({ baseURL: SlackClient.baseUrl }); this.verifyAccessToken = new VerifyAccessToken(this); this.postMessageToChannel = new PostMessageToChannel(this); + this.findMessages = new FindMessages(this); } } diff --git a/packages/backend/src/apps/slack/info.json b/packages/backend/src/apps/slack/info.json index 4e5cd4f8..d2ccc565 100644 --- a/packages/backend/src/apps/slack/info.json +++ b/packages/backend/src/apps/slack/info.json @@ -204,6 +204,73 @@ "name": "Test action" } ] + }, + { + "name": "Find message", + "key": "findMessage", + "description": "Find a Slack message using the Slack Search feature.", + "substeps": [ + { + "key": "chooseConnection", + "name": "Choose connection" + }, + { + "key": "setupAction", + "name": "Set up action", + "arguments": [ + { + "label": "Search Query", + "key": "query", + "type": "string", + "required": true, + "description": "Search query to use for finding matching messages. See the Slack Search Documentation for more information on constructing a query.", + "variables": true + }, + { + "label": "Sort by", + "key": "sortBy", + "type": "dropdown", + "description": "Sort messages by their match strength or by their date. Default is score.", + "required": true, + "value": "score", + "variables": false, + "options": [ + { + "label": "Match strength", + "value": "score" + }, + { + "label": "Message date time", + "value": "timestamp" + } + ] + }, + { + "label": "Sort direction", + "key": "sortDirection", + "type": "dropdown", + "description": "Sort matching messages in ascending or descending order. Default is descending.", + "required": true, + "value": "desc", + "variables": false, + "options": [ + { + "label": "Descending (newest or best match first)", + "value": "desc" + }, + { + "label": "Ascending (oldest or worst match first)", + "value": "asc" + } + ] + } + ] + }, + { + "key": "testStep", + "name": "Test action" + } + ] } ] } diff --git a/packages/backend/src/graphql/schema.graphql b/packages/backend/src/graphql/schema.graphql index 4ec57ecc..d26c9bf9 100644 --- a/packages/backend/src/graphql/schema.graphql +++ b/packages/backend/src/graphql/schema.graphql @@ -73,6 +73,7 @@ type ActionSubstepArgument { description: String required: Boolean variables: Boolean + options: [ActionSubstepArgumentOption] source: ActionSubstepArgumentSource dependsOn: [String] } @@ -83,6 +84,11 @@ type ActionSubstepArgumentSource { arguments: [ActionSubstepArgumentSourceArgument] } +type ActionSubstepArgumentOption { + label: String + value: JSONObject +} + type ActionSubstepArgumentSourceArgument { name: String value: String diff --git a/packages/web/src/components/TestSubstep/index.tsx b/packages/web/src/components/TestSubstep/index.tsx index de921f6e..6eb22e7b 100644 --- a/packages/web/src/components/TestSubstep/index.tsx +++ b/packages/web/src/components/TestSubstep/index.tsx @@ -73,7 +73,7 @@ function TestSubstep(props: TestSubstepProps): React.ReactElement { {error?.graphQLErrors.map((error) => (<>{error.message}
))} } - {called && !response && ( + {called && !loading && !error && !response && ( {formatMessage('flowEditor.noTestDataTitle')} diff --git a/packages/web/src/graphql/queries/get-apps.ts b/packages/web/src/graphql/queries/get-apps.ts index a09cdf26..82ff70ec 100644 --- a/packages/web/src/graphql/queries/get-apps.ts +++ b/packages/web/src/graphql/queries/get-apps.ts @@ -95,6 +95,10 @@ export const GET_APPS = gql` description variables dependsOn + options { + label + value + } source { type name