diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 5eb41a0bc..176415f0b 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -27,7 +27,7 @@ jobs: with: persist-credentials: false - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 + uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 with: node-version: ${{ matrix.node-version }} - run: npm install --verbose diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml index d2f4dfcae..b0f31baf3 100644 --- a/.github/workflows/samples.yml +++ b/.github/workflows/samples.yml @@ -21,7 +21,7 @@ jobs: contents: read steps: - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 + uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 with: node-version: ${{ matrix.node-version }} - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 @@ -48,7 +48,7 @@ jobs: contents: read steps: - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 + uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 with: node-version: ${{ matrix.node-version }} - name: Checkout bolt-js diff --git a/docs/english/reference.md b/docs/english/reference.md index 01f51fe64..3cbf1a06d 100644 --- a/docs/english/reference.md +++ b/docs/english/reference.md @@ -21,7 +21,6 @@ Below is the current list of methods that accept listener functions. These metho | `app.action(actionId, fn);` | Listens for an action event from a Block Kit element, such as a user interaction with a button, select menu, or datepicker. The `actionId` identifier is a `string` that should match the unique `action_id` included when your app sends the element to a view. Note that a view can be a message, modal, or app home. Note that action elements included in an `input` block do not trigger any events. | `app.shortcut(callbackId, fn);` | Listens for global or message shortcut invocation. The `callbackId` is a `string` or `RegExp` that must match a shortcut `callback_id` specified within your app's configuration. | `app.view(callbackId, fn);` | Listens for `view_submission` and `view_closed` events. `view_submission` events are sent when a user submits a modal that your app opened. `view_closed` events are sent when a user closes the modal rather than submits it. -| `app.step(workflowStep)` | Listen and responds to steps from apps events using the callbacks passed in an instance of `WorkflowStep`. Callbacks include three callbacks: `edit`, `save`, and `execute`. More information on steps from apps can be found [in the documentation](/tools/bolt-js/legacy/steps-from-apps). | `app.command(commandName, fn);` | Listens for slash command invocations. The `commandName` is a `string` that must match a slash command specified in your app's configuration. Slash command names should be prefaced with a `/` (ex: `/helpdesk`). | `app.options(actionId, fn);` | Listens for options requests (from select menus with an external data source). This isn't often used, and shouldn't be mistaken with `app.action`. The `actionId` identifier is a `string` that matches the unique `action_id` included when you app sends a [select with an external data source](/reference/block-kit/block-elements/multi-select-menu-element/#external_multi_select). @@ -185,7 +184,6 @@ Bolt includes a set of error types to make errors easier to handle, with more sp | `ReceiverMultipleAckError` | Error thrown within Receiver when your app calls `ack()` when that request has previously been acknowledged. Currently only used in the default `HTTPReceiver`. | | `ReceiverAuthenticityError` | Error thrown when your app's request signature could not be verified. The error includes information on why it failed, such as an invalid timestamp, missing headers, or invalid signing secret. | `MultipleListenerError` | Thrown when multiple errors occur when processing multiple listeners for a single event. Includes an `originals` property with an array of the individual errors. | -| `WorkflowStepInitializationError` | Error thrown when configuration options are invalid or missing when instantiating a new `WorkflowStep` instance. This could be scenarios like not including a `callback_id`, or not including a configuration object. More information on steps from apps [can be found in the documentation](/tools/bolt-js/legacy/steps-from-apps). | | `UnknownError` | An error that was thrown inside the framework but does not have a specified error code. Contains an `original` property with more details. | :::info diff --git a/docs/japanese/reference.md b/docs/japanese/reference.md index b4ffe1c8b..1c6ed3cf9 100644 --- a/docs/japanese/reference.md +++ b/docs/japanese/reference.md @@ -19,7 +19,6 @@ Slack アプリは通常、Slack からのイベント情報を受け取った | `app.action(actionId, fn);` | Block Kit エレメントから送信される `action` イベントをリッスンします。このイベントにはユーザーのボタン操作、メニュー選択、日付ピッカーの操作などがあります。`actionId` は文字列型で、アプリがビュー内に含めたブロックエレメントに指定した一意の `action_id` の値と一致する必要があります。ここでいう「ビュー」とは、メッセージ、モーダル、アプリのホームタブのことを指します。アクションエレメントを `input` ブロックに配置した場合はイベントがトリガーされないことに注意してください。 | `app.shortcut(callbackId, fn);` | グローバルショートカットまたはメッセージショートカットの呼び出しをリッスンします。`callbackId` は文字列または正規表現で、アプリの設定で指定したショートカットの `callback_id` にマッチする必要があります。 | `app.view(callbackId, fn);` | `view_submission` イベントと `view_closed` イベントをリッスンします。`view_submission` イベントは、アプリが開いたモーダルでユーザーがデータ送信の操作をしたときに発生します。`view_closed` イベントは、ユーザーがデータ送信を実行せずにモーダルを閉じたときに発生します。 -| `app.step(workflowStep)` | `WorkflowStep` のインスタンスに渡されたコールバックを使用して、ワークフローステップイベントのリッスンと応答を行います。コールバックには `edit`、`save`、`execute` の 3 種類があります。ワークフローステップについて詳しくは、[ドキュメント](/tools/bolt-js/legacy/steps-from-apps)を参照してください。 | `app.command(commandName, fn);` | Slash コマンドの呼び出しをリッスンします。`commandName` は文字列型で、アプリの設定で指定したスラッシュコマンドと一致する必要があります。スラッシュコマンドの名前では `/` を最初に配置します(例 : `/helpdesk`)。 | `app.options(actionId, fn);` | 外部データソースを使用するセレクトメニューなどから送られる選択肢読み込みのリクエストをリッスンします。使う機会は多くありませんが、`app.action` と混同しないようにしましょう。`actionId` は文字列型で、アプリがビュー内に[外部データソースを使用するセレクトメニュー](/reference/block-kit/block-elements/multi-select-menu-element#external_multi_select)を含めるときに指定した`action_id` と一致する必要があります。 @@ -124,7 +123,6 @@ Bolt では、さまざまなエラーが定義されています。これらに | `ReceiverMultipleAckError` | Receiver 内で、すでに確認が済んでいるリクエストに対してアプリがさらに `ack()` を呼んだ場合にスローされるエラーです。現在、デフォルトの `HTTPReceiver` でのみ使用されます。 | | `ReceiverAuthenticityError` | アプリのリクエストの署名が検証できないときにスローされるエラーです。このエラーには、失敗した理由を示す情報が含まれます(例 : タイムスタンプが有効でない、ヘッダーに抜けがある、署名シークレットが有効でない)。 | `MultipleListenerError` | 単一のイベントに対して複数のリスナーでの処理中に複数のエラーが発生した場合にスローされるエラーです。個々のエラーを配列に収めた `originals` プロパティを持ちます。 | -| `WorkflowStepInitializationError` | 新しい `WorkflowStep` をインスタンス化する際に、設定オプションが無効な場合、または不足している場合にスローされるエラーです。原因として、`callback_id` が指定されていない、または設定オブジェクトが指定されていないことが考えられます。ワークフローステップについて詳しくは、[ドキュメント](/tools/bolt-js/legacy/steps-from-apps)を参照してください。 | | `UnknownError` | フレームワーク内でスローされる、特定のエラーコードを持たないエラーです。`original` プロパティで詳細を確認できます。 | > [errors.ts](https://github.com/slackapi/bolt-js/blob/main/src/errors.ts) のコードで、エラー定義の部分とコンストラクターの部分を読み、参考にしてみてください。 diff --git a/src/App.ts b/src/App.ts index a902f40b3..d1c182824 100644 --- a/src/App.ts +++ b/src/App.ts @@ -13,7 +13,6 @@ import { createFunctionComplete, createFunctionFail, } from './CustomFunction'; -import type { WorkflowStep } from './WorkflowStep'; import { type ConversationStore, MemoryStore, conversationContext } from './conversation-store'; import { AppInitializationError, @@ -84,7 +83,6 @@ import type { SlashCommand, ViewConstraints, ViewOutput, - WorkflowStepEdit, } from './types'; import { contextBuiltinKeys } from './types'; import { type StringIndexed, isRejected } from './types/utilities'; @@ -522,19 +520,6 @@ export default class App return this; } - /** - * Register WorkflowStep middleware - * - * @param workflowStep global workflow step middleware function - * @deprecated Steps from Apps are no longer supported and support for them will be removed in the next major bolt-js - * version. - */ - public step(workflowStep: WorkflowStep): this { - const m = workflowStep.getMiddleware(); - this.middleware.push(m); - return this; - } - /** * Register middleware for a workflow step. * @param callbackId Unique callback ID of a step. @@ -1022,11 +1007,9 @@ export default class App // Set body and payload // TODO: this value should eventually conform to AnyMiddlewareArgs - // TODO: remove workflow step stuff in bolt v5 // TODO: can we instead use type predicates in these switch cases to allow for narrowing of the body simultaneously? we have isEvent, isView, isShortcut, isAction already in types/utilities / helpers let payload: | DialogSubmitAction - | WorkflowStepEdit | SlackShortcut | KnownEventFromType | SlashCommand diff --git a/src/WorkflowStep.ts b/src/WorkflowStep.ts deleted file mode 100644 index 84f941517..000000000 --- a/src/WorkflowStep.ts +++ /dev/null @@ -1,432 +0,0 @@ -import type { WorkflowStepExecuteEvent } from '@slack/types'; -import type { - Block, - KnownBlock, - ViewsOpenResponse, - WorkflowsStepCompletedResponse, - WorkflowsStepFailedResponse, - WorkflowsUpdateStepResponse, -} from '@slack/web-api'; -import { WorkflowStepInitializationError } from './errors'; -import processMiddleware from './middleware/process'; -import type { - AllMiddlewareArgs, - AnyMiddlewareArgs, - Context, - Middleware, - SlackActionMiddlewareArgs, - SlackEventMiddlewareArgs, - SlackViewMiddlewareArgs, - ViewWorkflowStepSubmitAction, - WorkflowStepEdit, -} from './types'; - -/** Interfaces */ - -/** @deprecated Steps from Apps are no longer supported and support for them will be removed in the next major bolt-js - * version. - */ -export interface StepConfigureArguments { - blocks: (KnownBlock | Block)[]; - private_metadata?: string; - submit_disabled?: boolean; - external_id?: string; -} - -/** @deprecated Steps from Apps are no longer supported and support for them will be removed in the next major bolt-js - * version. - */ -export interface StepUpdateArguments { - inputs?: Record< - string, - { - // biome-ignore lint/suspicious/noExplicitAny: user-defined workflow inputs could be anything - value: any; - skip_variable_replacement?: boolean; - // biome-ignore lint/suspicious/noExplicitAny: user-defined workflow inputs could be anything - variables?: Record; - } - >; - outputs?: { - name: string; - type: string; - label: string; - }[]; - step_name?: string; - step_image_url?: string; -} - -/** @deprecated Steps from Apps are no longer supported and support for them will be removed in the next major bolt-js - * version. - */ -export interface StepCompleteArguments { - // biome-ignore lint/suspicious/noExplicitAny: user-defined workflow outputs could be anything - outputs?: Record; -} - -/** @deprecated Steps from Apps are no longer supported and support for them will be removed in the next major bolt-js - * version. - */ -export interface StepFailArguments { - error: { - message: string; - }; -} - -/** @deprecated Steps from Apps are no longer supported and support for them will be removed in the next major bolt-js - * version. - */ -export type StepConfigureFn = (params: StepConfigureArguments) => Promise; - -/** @deprecated Steps from Apps are no longer supported and support for them will be removed in the next major bolt-js - * version. - */ -export type StepUpdateFn = (params?: StepUpdateArguments) => Promise; - -/** @deprecated Steps from Apps are no longer supported and support for them will be removed in the next major bolt-js - * version. - */ -export type StepCompleteFn = (params?: StepCompleteArguments) => Promise; - -/** @deprecated Steps from Apps are no longer supported and support for them will be removed in the next major bolt-js - * version. - */ -export type StepFailFn = (params: StepFailArguments) => Promise; - -/** @deprecated Steps from Apps are no longer supported and support for them will be removed in the next major bolt-js - * version. - */ -export interface WorkflowStepConfig { - edit: WorkflowStepEditMiddleware | WorkflowStepEditMiddleware[]; - save: WorkflowStepSaveMiddleware | WorkflowStepSaveMiddleware[]; - execute: WorkflowStepExecuteMiddleware | WorkflowStepExecuteMiddleware[]; -} - -/** @deprecated Steps from Apps are no longer supported and support for them will be removed in the next major bolt-js - * version. - */ -export interface WorkflowStepEditMiddlewareArgs extends SlackActionMiddlewareArgs { - step: WorkflowStepEdit['workflow_step']; - configure: StepConfigureFn; -} - -/** @deprecated Steps from Apps are no longer supported and support for them will be removed in the next major bolt-js - * version. - */ -export interface WorkflowStepSaveMiddlewareArgs extends SlackViewMiddlewareArgs { - step: ViewWorkflowStepSubmitAction['workflow_step']; - update: StepUpdateFn; -} - -/** @deprecated Steps from Apps are no longer supported and support for them will be removed in the next major bolt-js - * version. - */ -export interface WorkflowStepExecuteMiddlewareArgs extends SlackEventMiddlewareArgs<'workflow_step_execute'> { - step: WorkflowStepExecuteEvent['workflow_step']; - complete: StepCompleteFn; - fail: StepFailFn; -} - -/** Types */ - -/** @deprecated Steps from Apps are no longer supported and support for them will be removed in the next major bolt-js - * version. - */ -export type SlackWorkflowStepMiddlewareArgs = - | WorkflowStepEditMiddlewareArgs - | WorkflowStepSaveMiddlewareArgs - | WorkflowStepExecuteMiddlewareArgs; - -/** @deprecated Steps from Apps are no longer supported and support for them will be removed in the next major bolt-js - * version. - */ -export type WorkflowStepEditMiddleware = Middleware; -/** @deprecated Steps from Apps are no longer supported and support for them will be removed in the next major bolt-js - * version. - */ -export type WorkflowStepSaveMiddleware = Middleware; -/** @deprecated Steps from Apps are no longer supported and support for them will be removed in the next major bolt-js - * version. - */ -export type WorkflowStepExecuteMiddleware = Middleware; - -/** @deprecated Steps from Apps are no longer supported and support for them will be removed in the next major bolt-js - * version. - */ -export type WorkflowStepMiddleware = - | WorkflowStepEditMiddleware[] - | WorkflowStepSaveMiddleware[] - | WorkflowStepExecuteMiddleware[]; - -/** @deprecated Steps from Apps are no longer supported and support for them will be removed in the next major bolt-js - * version. - */ -export type AllWorkflowStepMiddlewareArgs = - T & AllMiddlewareArgs; - -/** Constants */ - -const VALID_PAYLOAD_TYPES = new Set(['workflow_step_edit', 'workflow_step', 'workflow_step_execute']); - -/** @deprecated Steps from Apps are no longer supported and support for them will be removed in the next major bolt-js - * version. - */ -export class WorkflowStep { - /** Step callback_id */ - private callbackId: string; - - /** Step Add/Edit :: 'workflow_step_edit' action */ - private edit: WorkflowStepEditMiddleware[]; - - /** Step Config Save :: 'view_submission' */ - private save: WorkflowStepSaveMiddleware[]; - - /** Step Executed/Run :: 'workflow_step_execute' event */ - private execute: WorkflowStepExecuteMiddleware[]; - - public constructor(callbackId: string, config: WorkflowStepConfig) { - validate(callbackId, config); - - const { save, edit, execute } = config; - - this.callbackId = callbackId; - this.save = Array.isArray(save) ? save : [save]; - this.edit = Array.isArray(edit) ? edit : [edit]; - this.execute = Array.isArray(execute) ? execute : [execute]; - } - - public getMiddleware(): Middleware { - return async (args): Promise => { - if (isStepEvent(args) && this.matchesConstraints(args)) { - return this.processEvent(args); - } - return args.next(); - }; - } - - private matchesConstraints(args: SlackWorkflowStepMiddlewareArgs): boolean { - return args.payload.callback_id === this.callbackId; - } - - private async processEvent(args: AllWorkflowStepMiddlewareArgs): Promise { - const { payload } = args; - const stepArgs = prepareStepArgs(args); - const stepMiddleware = this.getStepMiddleware(payload); - return processStepMiddleware(stepArgs, stepMiddleware); - } - - private getStepMiddleware(payload: AllWorkflowStepMiddlewareArgs['payload']): WorkflowStepMiddleware { - switch (payload.type) { - case 'workflow_step_edit': - return this.edit; - case 'workflow_step': - return this.save; - case 'workflow_step_execute': - return this.execute; - default: - return []; - } - } -} - -/** Helper Functions */ - -/** @deprecated Steps from Apps are no longer supported and support for them will be removed in the next major bolt-js - * version. - */ -export function validate(callbackId: string, config: WorkflowStepConfig): void { - // Ensure callbackId is valid - if (typeof callbackId !== 'string') { - const errorMsg = 'WorkflowStep expects a callback_id as the first argument'; - throw new WorkflowStepInitializationError(errorMsg); - } - - // Ensure step config object is passed in - if (typeof config !== 'object') { - const errorMsg = 'WorkflowStep expects a configuration object as the second argument'; - throw new WorkflowStepInitializationError(errorMsg); - } - - // Check for missing required keys - const requiredKeys: (keyof WorkflowStepConfig)[] = ['save', 'edit', 'execute']; - const missingKeys: (keyof WorkflowStepConfig)[] = []; - for (const key of requiredKeys) { - if (config[key] === undefined) { - missingKeys.push(key); - } - } - - if (missingKeys.length > 0) { - const errorMsg = `WorkflowStep is missing required keys: ${missingKeys.join(', ')}`; - throw new WorkflowStepInitializationError(errorMsg); - } - - // Ensure a callback or an array of callbacks is present - const requiredFns: (keyof WorkflowStepConfig)[] = ['save', 'edit', 'execute']; - for (const fn of requiredFns) { - if (typeof config[fn] !== 'function' && !Array.isArray(config[fn])) { - const errorMsg = `WorkflowStep ${fn} property must be a function or an array of functions`; - throw new WorkflowStepInitializationError(errorMsg); - } - } -} - -/** - * `processStepMiddleware()` invokes each callback for lifecycle event - * @deprecated Steps from Apps are no longer supported and support for them will be removed in the next major bolt-js - * version. - * @param args workflow_step_edit action - */ -export async function processStepMiddleware( - args: AllWorkflowStepMiddlewareArgs, - middleware: WorkflowStepMiddleware, -): Promise { - const { context, client, logger } = args; - // TODO :: revisit type used below (look into contravariance) - const callbacks = [...middleware] as Middleware[]; - const lastCallback = callbacks.pop(); - - if (lastCallback !== undefined) { - await processMiddleware(callbacks, args, context, client, logger, async () => - lastCallback({ ...args, context, client, logger }), - ); - } -} - -/** @deprecated Steps from Apps are no longer supported and support for them will be removed in the next major bolt-js - * version. - */ -export function isStepEvent(args: AnyMiddlewareArgs): args is AllWorkflowStepMiddlewareArgs { - return VALID_PAYLOAD_TYPES.has(args.payload.type); -} - -function selectToken(context: Context): string | undefined { - return context.botToken !== undefined ? context.botToken : context.userToken; -} - -/** - * Factory for `configure()` utility - * @param args workflow_step_edit action - */ -function createStepConfigure(args: AllWorkflowStepMiddlewareArgs): StepConfigureFn { - const { - context, - client, - body: { callback_id, trigger_id }, - } = args; - const token = selectToken(context); - - return (params: Parameters[0]) => - client.views.open({ - token, - trigger_id, - view: { - callback_id, - type: 'workflow_step', - ...params, - }, - }); -} - -/** - * Factory for `update()` utility - * @param args view_submission event - */ -function createStepUpdate(args: AllWorkflowStepMiddlewareArgs): StepUpdateFn { - const { - context, - client, - body: { - workflow_step: { workflow_step_edit_id }, - }, - } = args; - const token = selectToken(context); - - return (params: Parameters[0] = {}) => - client.workflows.updateStep({ - token, - workflow_step_edit_id, - ...params, - }); -} - -/** - * Factory for `complete()` utility - * @param args workflow_step_execute event - */ -function createStepComplete(args: AllWorkflowStepMiddlewareArgs): StepCompleteFn { - const { - context, - client, - payload: { - workflow_step: { workflow_step_execute_id }, - }, - } = args; - const token = selectToken(context); - - return (params: Parameters[0] = {}) => - client.workflows.stepCompleted({ - token, - workflow_step_execute_id, - ...params, - }); -} - -/** - * Factory for `fail()` utility - * @param args workflow_step_execute event - */ -function createStepFail(args: AllWorkflowStepMiddlewareArgs): StepFailFn { - const { - context, - client, - payload: { - workflow_step: { workflow_step_execute_id }, - }, - } = args; - const token = selectToken(context); - - return (params: Parameters[0]) => { - const { error } = params; - return client.workflows.stepFailed({ - token, - workflow_step_execute_id, - error, - }); - }; -} - -/** - * `prepareStepArgs()` takes in a step's args and: - * 1. removes the next() passed in from App-level middleware processing - * - events will *not* continue down global middleware chain to subsequent listeners - * 2. augments args with step lifecycle-specific properties/utilities - * @deprecated Steps from Apps are no longer supported and support for them will be removed in the next major bolt-js - * version. - */ -// TODO :: refactor to incorporate a generic parameter -export function prepareStepArgs(args: AllWorkflowStepMiddlewareArgs): AllWorkflowStepMiddlewareArgs { - const { next: _next, ...stepArgs } = args; - // biome-ignore lint/suspicious/noExplicitAny: need to use any as the cases of the switch that follows dont narrow to the specific required args type. use type predicates for each workflow_step event args in the switch to get rid of this any. - const preparedArgs: any = { ...stepArgs }; - - switch (preparedArgs.payload.type) { - case 'workflow_step_edit': - preparedArgs.step = preparedArgs.action.workflow_step; - preparedArgs.configure = createStepConfigure(preparedArgs); - break; - case 'workflow_step': - preparedArgs.step = preparedArgs.body.workflow_step; - preparedArgs.update = createStepUpdate(preparedArgs); - break; - case 'workflow_step_execute': - preparedArgs.step = preparedArgs.event.workflow_step; - preparedArgs.complete = createStepComplete(preparedArgs); - preparedArgs.fail = createStepFail(preparedArgs); - break; - default: - break; - } - - return preparedArgs; -} diff --git a/src/errors.ts b/src/errors.ts index 582aea969..d021e3474 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -42,9 +42,6 @@ export enum ErrorCode { */ UnknownError = 'slack_bolt_unknown_error', - // TODO: remove workflow step stuff in bolt v5 - WorkflowStepInitializationError = 'slack_bolt_workflow_step_initialization_error', - CustomFunctionInitializationError = 'slack_bolt_custom_function_initialization_error', CustomFunctionCompleteSuccessError = 'slack_bolt_custom_function_complete_success_error', CustomFunctionCompleteFailError = 'slack_bolt_custom_function_complete_fail_error', @@ -156,13 +153,6 @@ export class MultipleListenerError extends Error implements CodedError { this.originals = originals; } } -/** - * @deprecated Steps from Apps are no longer supported and support for them will be removed in the next major bolt-js - * version. - */ -export class WorkflowStepInitializationError extends Error implements CodedError { - public code = ErrorCode.WorkflowStepInitializationError; -} export class CustomFunctionInitializationError extends Error implements CodedError { public code = ErrorCode.CustomFunctionInitializationError; diff --git a/src/helpers.ts b/src/helpers.ts index 07e55298e..cad693bd4 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -81,8 +81,7 @@ export function getTypeAndConversation(body: any): { type?: IncomingEventType; c conversationId: optionsBody.channel !== undefined ? optionsBody.channel.id : undefined, }; } - // TODO: remove workflow_step stuff in v5 - if (body.actions !== undefined || body.type === 'dialog_submission' || body.type === 'workflow_step_edit') { + if (body.actions !== undefined || body.type === 'dialog_submission') { const actionBody = body as SlackActionMiddlewareArgs['body']; return { type: IncomingEventType.Action, diff --git a/src/index.ts b/src/index.ts index 7418cfd47..9756d36e3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -60,14 +60,6 @@ export { AssistantUserMessageMiddleware, } from './Assistant'; -export { - WorkflowStep, - WorkflowStepConfig, - WorkflowStepEditMiddleware, - WorkflowStepSaveMiddleware, - WorkflowStepExecuteMiddleware, -} from './WorkflowStep'; - export { Installation, InstallURLOptions, diff --git a/src/types/actions/index.ts b/src/types/actions/index.ts index 9a9b120bf..4af823b14 100644 --- a/src/types/actions/index.ts +++ b/src/types/actions/index.ts @@ -4,13 +4,10 @@ import type { AckFn, RespondFn, SayArguments, SayFn } from '../utilities'; import type { BlockAction } from './block-action'; import type { DialogSubmitAction, DialogValidation } from './dialog-action'; import type { InteractiveMessage } from './interactive-message'; -import type { WorkflowStepEdit } from './workflow-step-edit'; export * from './block-action'; export * from './interactive-message'; export * from './dialog-action'; -// TODO: remove workflow step stuff in bolt v5 -export * from './workflow-step-edit'; /** * All known actions from Slack's Block Kit interactive components, message actions, dialogs, and legacy interactive @@ -26,8 +23,7 @@ export * from './workflow-step-edit'; * offered when no generic parameter is bound would be limited to BasicElementAction rather than the union of known * actions - ElementAction. */ -// TODO: remove workflow step stuff in bolt v5 -export type SlackAction = BlockAction | InteractiveMessage | DialogSubmitAction | WorkflowStepEdit; +export type SlackAction = BlockAction | InteractiveMessage | DialogSubmitAction; export interface ActionConstraints { type?: A['type']; @@ -66,8 +62,7 @@ export type SlackActionMiddlewareArgs complete?: FunctionCompleteFn; fail?: FunctionFailFn; inputs?: FunctionInputs; - // TODO: remove workflow step stuff in bolt v5 -} & (Action extends Exclude +} & (Action extends Exclude ? // all action types except dialog submission and steps from apps have a channel context // TODO: not exactly true: a block action could occur from a view. should improve this. { say: SayFn } diff --git a/src/types/actions/workflow-step-edit.ts b/src/types/actions/workflow-step-edit.ts deleted file mode 100644 index aa3c37bcd..000000000 --- a/src/types/actions/workflow-step-edit.ts +++ /dev/null @@ -1,54 +0,0 @@ -/** - * A Slack step from app action wrapped in the standard metadata. - * - * This describes the entire JSON-encoded body of a request from Slack step from app actions. - * @deprecated Steps from Apps are no longer supported and support for them will be removed in the next major bolt-js - * version. - */ -export interface WorkflowStepEdit { - type: 'workflow_step_edit'; - callback_id: string; - trigger_id: string; - user: { - id: string; - username: string; - team_id?: string; // undocumented - }; - team: { - id: string; - domain: string; - enterprise_id?: string; // undocumented - enterprise_name?: string; // undocumented - }; - channel?: { - id?: string; - name?: string; - }; - token: string; - action_ts: string; // undocumented - workflow_step: { - workflow_id: string; - step_id: string; - inputs: Record< - string, - { - // biome-ignore lint/suspicious/noExplicitAny: input parameters can accept anything - value: any; - } - >; - outputs: { - name: string; - type: string; - label: string; - }[]; - step_name?: string; - step_image_url?: string; - }; - - // exists for enterprise installs - is_enterprise_install?: boolean; - enterprise?: { - id: string; - name: string; - }; -} diff --git a/src/types/view/index.ts b/src/types/view/index.ts index 585f0918c..d530e7b01 100644 --- a/src/types/view/index.ts +++ b/src/types/view/index.ts @@ -5,11 +5,7 @@ import type { AckFn, RespondFn } from '../utilities'; /** * Known view action types */ -export type SlackViewAction = - | ViewSubmitAction - | ViewClosedAction - | ViewWorkflowStepSubmitAction // TODO: remove workflow step stuff in bolt v5 - | ViewWorkflowStepClosedAction; +export type SlackViewAction = ViewSubmitAction | ViewClosedAction; // // TODO: add a type parameter here, just like the other constraint interfaces have. export interface ViewConstraints { @@ -103,39 +99,6 @@ export interface ViewClosedAction { name: string; }; } - -/** - * A Slack view_submission step from app event - * - * This describes the additional JSON-encoded body details for a step's view_submission event - * @deprecated Steps from Apps are no longer supported and support for them will be removed in the next major bolt-js - * version. - */ -export interface ViewWorkflowStepSubmitAction extends ViewSubmitAction { - trigger_id: string; - response_urls?: ViewResponseUrl[]; - workflow_step: { - workflow_step_edit_id: string; - workflow_id: string; - step_id: string; - }; -} - -/** - * A Slack view_closed step from app event - * - * This describes the additional JSON-encoded body details for a step's view_closed event - * @deprecated Steps from Apps are no longer supported and support for them will be removed in the next major bolt-js - * version. - */ -export interface ViewWorkflowStepClosedAction extends ViewClosedAction { - workflow_step: { - workflow_step_edit_id: string; - workflow_id: string; - step_id: string; - }; -} - export interface ViewStateSelectedOption { text: PlainTextElement; value: string; diff --git a/test/unit/WorkflowStep.spec.ts b/test/unit/WorkflowStep.spec.ts deleted file mode 100644 index dab9cc663..000000000 --- a/test/unit/WorkflowStep.spec.ts +++ /dev/null @@ -1,388 +0,0 @@ -import path from 'node:path'; -import type { WebClient } from '@slack/web-api'; -import { assert } from 'chai'; -import sinon from 'sinon'; -import { - type AllWorkflowStepMiddlewareArgs, - type SlackWorkflowStepMiddlewareArgs, - WorkflowStep, - type WorkflowStepConfig, - type WorkflowStepEditMiddlewareArgs, - type WorkflowStepExecuteMiddlewareArgs, - type WorkflowStepMiddleware, - type WorkflowStepSaveMiddlewareArgs, -} from '../../src/WorkflowStep'; -import { WorkflowStepInitializationError } from '../../src/errors'; -import type { AllMiddlewareArgs, AnyMiddlewareArgs, Middleware, WorkflowStepEdit } from '../../src/types'; -import { type Override, noopVoid, proxyquire } from './helpers'; - -function importWorkflowStep(overrides: Override = {}): typeof import('../../src/WorkflowStep') { - const absolutePath = path.resolve(__dirname, '../../src/WorkflowStep'); - return proxyquire(absolutePath, overrides); -} - -const MOCK_CONFIG_SINGLE = { - edit: noopVoid, - save: noopVoid, - execute: noopVoid, -}; - -const MOCK_CONFIG_MULTIPLE = { - edit: [noopVoid, noopVoid], - save: [noopVoid], - execute: [noopVoid, noopVoid, noopVoid], -}; - -describe('WorkflowStep class', () => { - describe('constructor', () => { - it('should accept config as single functions', async () => { - const ws = new WorkflowStep('test_callback_id', MOCK_CONFIG_SINGLE); - assert.isNotNull(ws); - }); - - it('should accept config as multiple functions', async () => { - const ws = new WorkflowStep('test_callback_id', MOCK_CONFIG_MULTIPLE); - assert.isNotNull(ws); - }); - }); - - describe('getMiddleware', () => { - it('should not call next if a workflow step event', async () => { - const ws = new WorkflowStep('test_edit_callback_id', MOCK_CONFIG_SINGLE); - const middleware = ws.getMiddleware(); - const fakeEditArgs = createFakeStepEditAction() as unknown as SlackWorkflowStepMiddlewareArgs & AllMiddlewareArgs; - - const fakeNext = sinon.spy(); - fakeEditArgs.next = fakeNext; - - await middleware(fakeEditArgs); - - assert(fakeNext.notCalled); - }); - - it('should call next if valid workflow step with mismatched callback_id', async () => { - const ws = new WorkflowStep('bad_callback_id', MOCK_CONFIG_SINGLE); - const middleware = ws.getMiddleware(); - const fakeEditArgs = createFakeStepEditAction() as unknown as SlackWorkflowStepMiddlewareArgs & AllMiddlewareArgs; - - const fakeNext = sinon.spy(); - fakeEditArgs.next = fakeNext; - - await middleware(fakeEditArgs); - - assert(fakeNext.called); - }); - - it('should call next if not a workflow step event', async () => { - const ws = new WorkflowStep('test_view_callback_id', MOCK_CONFIG_SINGLE); - const middleware = ws.getMiddleware(); - const fakeViewArgs = createFakeViewEvent() as unknown as SlackWorkflowStepMiddlewareArgs & AllMiddlewareArgs; - - const fakeNext = sinon.spy(); - fakeViewArgs.next = fakeNext; - - await middleware(fakeViewArgs); - - assert(fakeNext.called); - }); - }); - - describe('validate', () => { - it('should throw an error if callback_id is not valid', async () => { - const { validate } = importWorkflowStep(); - - // intentionally casting to string to trigger failure - const badId = {} as string; - const validationFn = () => validate(badId, MOCK_CONFIG_SINGLE); - - const expectedMsg = 'WorkflowStep expects a callback_id as the first argument'; - assert.throws(validationFn, WorkflowStepInitializationError, expectedMsg); - }); - - it('should throw an error if config is not an object', async () => { - const { validate } = importWorkflowStep(); - - // intentionally casting to WorkflowStepConfig to trigger failure - const badConfig = '' as unknown as WorkflowStepConfig; - - const validationFn = () => validate('callback_id', badConfig); - const expectedMsg = 'WorkflowStep expects a configuration object as the second argument'; - assert.throws(validationFn, WorkflowStepInitializationError, expectedMsg); - }); - - it('should throw an error if required keys are missing', async () => { - const { validate } = importWorkflowStep(); - - // intentionally casting to WorkflowStepConfig to trigger failure - const badConfig = { - edit: async () => {}, - } as unknown as WorkflowStepConfig; - - const validationFn = () => validate('callback_id', badConfig); - const expectedMsg = 'WorkflowStep is missing required keys: save, execute'; - assert.throws(validationFn, WorkflowStepInitializationError, expectedMsg); - }); - - it('should throw an error if lifecycle props are not a single callback or an array of callbacks', async () => { - const { validate } = importWorkflowStep(); - - // intentionally casting to WorkflowStepConfig to trigger failure - const badConfig = { - edit: async () => {}, - save: {}, - execute: async () => {}, - } as unknown as WorkflowStepConfig; - - const validationFn = () => validate('callback_id', badConfig); - const expectedMsg = 'WorkflowStep save property must be a function or an array of functions'; - assert.throws(validationFn, WorkflowStepInitializationError, expectedMsg); - }); - }); - - describe('isStepEvent', () => { - it('should return true if recognized workflow step payload type', async () => { - const fakeEditArgs = createFakeStepEditAction() as unknown as SlackWorkflowStepMiddlewareArgs & AllMiddlewareArgs; - const fakeSaveArgs = createFakeStepSaveEvent() as unknown as SlackWorkflowStepMiddlewareArgs & AllMiddlewareArgs; - const fakeExecuteArgs = createFakeStepExecuteEvent() as unknown as SlackWorkflowStepMiddlewareArgs & - AllMiddlewareArgs; - - const { isStepEvent } = importWorkflowStep(); - - const editIsStepEvent = isStepEvent(fakeEditArgs); - const viewIsStepEvent = isStepEvent(fakeSaveArgs); - const executeIsStepEvent = isStepEvent(fakeExecuteArgs); - - assert.isTrue(editIsStepEvent); - assert.isTrue(viewIsStepEvent); - assert.isTrue(executeIsStepEvent); - }); - - it('should return false if not a recognized workflow step payload type', async () => { - const fakeEditArgs = createFakeStepEditAction() as unknown as AnyMiddlewareArgs; - fakeEditArgs.payload.type = 'invalid_type'; - - const { isStepEvent } = importWorkflowStep(); - const actionIsStepEvent = isStepEvent(fakeEditArgs); - - assert.isFalse(actionIsStepEvent); - }); - }); - - describe('prepareStepArgs', () => { - it('should remove next() from all original event args', async () => { - const fakeEditArgs = createFakeStepEditAction() as unknown as SlackWorkflowStepMiddlewareArgs & AllMiddlewareArgs; - const fakeSaveArgs = createFakeStepSaveEvent() as unknown as SlackWorkflowStepMiddlewareArgs & AllMiddlewareArgs; - const fakeExecuteArgs = createFakeStepExecuteEvent() as unknown as SlackWorkflowStepMiddlewareArgs & - AllMiddlewareArgs; - - const { prepareStepArgs } = importWorkflowStep(); - - const editStepArgs = prepareStepArgs(fakeEditArgs); - const viewStepArgs = prepareStepArgs(fakeSaveArgs); - const executeStepArgs = prepareStepArgs(fakeExecuteArgs); - - assert.notExists(editStepArgs.next); - assert.notExists(viewStepArgs.next); - assert.notExists(executeStepArgs.next); - }); - - it('should augment workflow_step_edit args with step and configure()', async () => { - const fakeArgs = createFakeStepEditAction(); - const { prepareStepArgs } = importWorkflowStep(); - // casting to returned type because prepareStepArgs isn't built to do so - const stepArgs = prepareStepArgs(fakeArgs as AllWorkflowStepMiddlewareArgs); - - assert.exists(stepArgs.step); - assert.property(stepArgs, 'configure'); - }); - - it('should augment view_submission with step and update()', async () => { - const fakeArgs = createFakeStepSaveEvent(); - const { prepareStepArgs } = importWorkflowStep(); - // casting to returned type because prepareStepArgs isn't built to do so - const stepArgs = prepareStepArgs( - fakeArgs as unknown as AllWorkflowStepMiddlewareArgs, - ); - - assert.exists(stepArgs.step); - assert.property(stepArgs, 'update'); - }); - - it('should augment workflow_step_execute with step, complete() and fail()', async () => { - const fakeArgs = createFakeStepExecuteEvent(); - const { prepareStepArgs } = importWorkflowStep(); - // casting to returned type because prepareStepArgs isn't built to do so - const stepArgs = prepareStepArgs( - fakeArgs as unknown as AllWorkflowStepMiddlewareArgs, - ); - - assert.exists(stepArgs.step); - assert.property(stepArgs, 'complete'); - assert.property(stepArgs, 'fail'); - }); - }); - - describe('step utility functions', () => { - it('configure should call views.open', async () => { - const fakeEditArgs = createFakeStepEditAction() as unknown as AllWorkflowStepMiddlewareArgs; - - const fakeClient = { views: { open: sinon.spy() } }; - fakeEditArgs.client = fakeClient as unknown as WebClient; - - const { prepareStepArgs } = importWorkflowStep(); - // casting to returned type because prepareStepArgs isn't built to do so - const editStepArgs = prepareStepArgs( - fakeEditArgs, - ) as AllWorkflowStepMiddlewareArgs; - - await editStepArgs.configure({ blocks: [] }); - - assert(fakeClient.views.open.called); - }); - - it('update should call workflows.updateStep', async () => { - const fakeSaveArgs = createFakeStepSaveEvent() as unknown as AllWorkflowStepMiddlewareArgs; - - const fakeClient = { workflows: { updateStep: sinon.spy() } }; - fakeSaveArgs.client = fakeClient as unknown as WebClient; - - const { prepareStepArgs } = importWorkflowStep(); - // casting to returned type because prepareStepArgs isn't built to do so - const saveStepArgs = prepareStepArgs( - fakeSaveArgs, - ) as AllWorkflowStepMiddlewareArgs; - - await saveStepArgs.update(); - - assert(fakeClient.workflows.updateStep.called); - }); - - it('complete should call workflows.stepCompleted', async () => { - const fakeExecuteArgs = createFakeStepExecuteEvent() as unknown as SlackWorkflowStepMiddlewareArgs & - AllMiddlewareArgs; // eslint-disable-line max-len - - const fakeClient = { workflows: { stepCompleted: sinon.spy() } }; - fakeExecuteArgs.client = fakeClient as unknown as WebClient; - - const { prepareStepArgs } = importWorkflowStep(); - // casting to returned type because prepareStepArgs isn't built to do so - const executeStepArgs = prepareStepArgs( - fakeExecuteArgs, - ) as AllWorkflowStepMiddlewareArgs; - - await executeStepArgs.complete(); - - assert(fakeClient.workflows.stepCompleted.called); - }); - - it('fail should call workflows.stepFailed', async () => { - const fakeExecuteArgs = createFakeStepExecuteEvent() as unknown as SlackWorkflowStepMiddlewareArgs & - AllMiddlewareArgs; // eslint-disable-line max-len - - const fakeClient = { workflows: { stepFailed: sinon.spy() } }; - fakeExecuteArgs.client = fakeClient as unknown as WebClient; - - const { prepareStepArgs } = importWorkflowStep(); - // casting to returned type because prepareStepArgs isn't built to do so - const executeStepArgs = prepareStepArgs( - fakeExecuteArgs, - ) as AllWorkflowStepMiddlewareArgs; - - await executeStepArgs.fail({ error: { message: 'Failed' } }); - - assert(fakeClient.workflows.stepFailed.called); - }); - }); - - describe('processStepMiddleware', () => { - it('should call each callback in user-provided middleware', async () => { - const { ...fakeArgs } = createFakeStepEditAction() as unknown as AllWorkflowStepMiddlewareArgs; - const { processStepMiddleware } = importWorkflowStep(); - - const fn1 = sinon.spy((async ({ next: continuation }) => { - await continuation(); - }) as Middleware); - const fn2 = sinon.spy(async () => {}); - const fakeMiddleware = [fn1, fn2] as WorkflowStepMiddleware; - - await processStepMiddleware(fakeArgs, fakeMiddleware); - - assert(fn1.called); - assert(fn2.called); - }); - }); -}); - -// TODO: need middleware test utilities like wrapping in AllMiddleWareArgs (creating say, respond, context) -// same for other kinds of middleware -// this stuff probably already exists -function createFakeStepEditAction() { - return { - body: { - callback_id: 'test_edit_callback_id', - trigger_id: 'test_edit_trigger_id', - }, - payload: { - type: 'workflow_step_edit', - callback_id: 'test_edit_callback_id', - }, - action: { - workflow_step: {}, - }, - context: {}, - }; -} - -function createFakeStepSaveEvent() { - return { - body: { - callback_id: 'test_save_callback_id', - trigger_id: 'test_save_trigger_id', - workflow_step: { - workflow_step_edit_id: '', - }, - }, - payload: { - type: 'workflow_step', - callback_id: 'test_save_callback_id', - }, - context: {}, - }; -} - -function createFakeStepExecuteEvent() { - return { - body: { - callback_id: 'test_execute_callback_id', - trigger_id: 'test_execute_trigger_id', - }, - event: { - workflow_step: {}, - }, - payload: { - type: 'workflow_step_execute', - callback_id: 'test_execute_callback_id', - workflow_step: { - workflow_step_execute_id: '', - }, - }, - context: {}, - }; -} - -function createFakeViewEvent() { - return { - body: { - callback_id: 'test_view_callback_id', - trigger_id: 'test_view_trigger_id', - workflow_step: { - workflow_step_edit_id: '', - }, - }, - payload: { - type: 'view_submission', - callback_id: 'test_view_callback_id', - }, - context: {}, - }; -}