From 6b116d5c31da1ccaffd321cf9cb9a4f28da57598 Mon Sep 17 00:00:00 2001 From: Filip Maj Date: Fri, 12 Apr 2024 10:25:42 -0400 Subject: [PATCH] Add support for wildcard/unscoped channel event trigger definitions. Fixes #222. --- .github/maintainers_guide.md | 4 +- .../workflows/triggers/event.ts | 65 +++--- .../workflows/triggers/tests/event_test.ts | 188 ++++++++++++++---- 3 files changed, 189 insertions(+), 68 deletions(-) diff --git a/.github/maintainers_guide.md b/.github/maintainers_guide.md index 7356e13..59b740f 100644 --- a/.github/maintainers_guide.md +++ b/.github/maintainers_guide.md @@ -105,8 +105,8 @@ To create a new release: 7. Publish the release by clicking the "Publish release" button! 8. After a few minutes, the corresponding version will be available on . -9. Don't forget to also bump this library's version in the deno-slack-sdk's `deps.ts` - file! +9. Don't forget to also bump this library's version in the deno-slack-sdk's + `deps.ts` file! ## Workflow diff --git a/src/typed-method-types/workflows/triggers/event.ts b/src/typed-method-types/workflows/triggers/event.ts index fea395c..602fa43 100644 --- a/src/typed-method-types/workflows/triggers/event.ts +++ b/src/typed-method-types/workflows/triggers/event.ts @@ -56,27 +56,38 @@ type WorkspaceTypes = ObjectValueUnion< >; type ChannelEvents = - & (ChannelEvent | MetadataChannelEvent | MessagePostedEvent) - & { - /** @description The channel id's that this event listens on */ - channel_ids: PopulatedArray; - // deno-lint-ignore no-explicit-any - [otherOptions: string]: any; - }; + & (ChannelEvent | MetadataChannelEvent | MessagePostedEvent) // controls `event_type` and `filter` + & (ChannelUnscopedEvent | ChannelScopedEvent); // controls event scoping: `channel_ids` and `all_resources` + +/** + * Event that is unscoped (limited) to a specific channel + */ +type ChannelUnscopedEvent = { + /** @description If set to `true`, will trigger in all channels. `false` by default and mutually exclusive with `channel_ids`. */ + all_resources: true; + /** @description The channel ids that this event listens on. Mutually exclusive with `all_resources`. */ + channel_ids?: never; +}; + +/** + * Event that is scoped to specific channel ID(s) + */ +type ChannelScopedEvent = { + /** @description The channel ids that this event listens on. Mutually exclusive with `all_resources`. */ + channel_ids: PopulatedArray; + /** @description If set to `true`, will trigger in all channels. `false` by default and mutually exclusive with `channel_ids`. */ + all_resources?: false; +}; type ChannelEvent = BaseEvent & { /** @description The type of event */ event_type: Exclude; }; -type MetadataChannelEvent = - & BaseEvent - & { - /** @description The type of event */ - event_type: Extract; - /** @description User defined description for the metadata event type */ - metadata_event_type: string; - }; +type MetadataChannelEvent = ChannelEvent & { + /** @description User defined description for the metadata event type */ + metadata_event_type: string; +}; // The only event that currently requires a filter type MessagePostedEvent = @@ -87,26 +98,20 @@ type MessagePostedEvent = event_type: MessagePostedEventType; }; -type WorkspaceEvents = - & BaseWorkspaceEvent - & { - /** @description The team id's that this event listens on */ - team_ids?: PopulatedArray; - // deno-lint-ignore no-explicit-any - [otherOptions: string]: any; - }; - -type BaseWorkspaceEvent = BaseEvent & { +type WorkspaceEvents = BaseEvent & { /** @description The type of event */ event_type: WorkspaceTypes; + /** @description The team IDs that this event should listen on. Must be included when used on Enterprise Grid and working with workspace-based event triggers. */ + team_ids?: PopulatedArray; }; type BaseEvent = { + // TODO: (breaking change) filter should not be optional here, but explicitly chosen for the events that accept it; + // could use similar technique as we do to manage messagemetadata-specific properties (above) /** @description Defines the condition in which this event trigger should execute the workflow */ filter?: FilterType; // deno-lint-ignore no-explicit-any - [otherOptions: string]: any; -}; +} & Record; export type EventTrigger = & BaseTrigger @@ -138,6 +143,6 @@ export type EventTriggerResponseObject< * @description The type of event specified for the event trigger */ event_type?: string; - // deno-lint-ignore no-explicit-any - [otherOptions: string]: any; - }; + } + // deno-lint-ignore no-explicit-any + & Record; diff --git a/src/typed-method-types/workflows/triggers/tests/event_test.ts b/src/typed-method-types/workflows/triggers/tests/event_test.ts index 0054ae7..77baa94 100644 --- a/src/typed-method-types/workflows/triggers/tests/event_test.ts +++ b/src/typed-method-types/workflows/triggers/tests/event_test.ts @@ -1,50 +1,166 @@ import { assertEquals } from "../../../../dev_deps.ts"; -import { TriggerTypes } from "../mod.ts"; -import { SlackAPI } from "../../../../mod.ts"; +import { SlackAPI, TriggerEventTypes, TriggerTypes } from "../../../../mod.ts"; import * as mf from "https://deno.land/x/mock_fetch@0.3.0/mod.ts"; import { event_response } from "./fixtures/sample_responses.ts"; import { ExampleWorkflow } from "./fixtures/workflows.ts"; import { EventTrigger } from "../event.ts"; -Deno.test("Event triggers can set the type using the string", () => { - const event: EventTrigger = { - type: "event", - name: "test", - workflow: "#/workflows/example", - inputs: {}, - event: { - event_type: "slack#/events/reaction_added", - channel_ids: ["C013ZG3K41Z"], +Deno.test("Event trigger type tests", async (t) => { + await t.step("Event triggers can set the type using the string", () => { + const event: EventTrigger = { + type: "event", + name: "test", + workflow: "#/workflows/example", + inputs: {}, + event: { + event_type: "slack#/events/reaction_added", + channel_ids: ["C013ZG3K41Z"], + }, + }; + assertEquals(event.type, TriggerTypes.Event); + }); + + await t.step( + "Event triggers can set the type using the TriggerTypes object", + () => { + const event: EventTrigger = { + type: TriggerTypes.Event, + name: "test", + workflow: "#/workflows/example", + inputs: {}, + event: { + event_type: "slack#/events/reaction_added", + channel_ids: ["C013ZG3K41Z"], + }, + }; + assertEquals(event.type, TriggerTypes.Event); }, - }; - assertEquals(event.type, TriggerTypes.Event); -}); + ); -Deno.test("Event triggers can set the type using the TriggerTypes object", () => { - const event: EventTrigger = { - type: TriggerTypes.Event, - name: "test", - workflow: "#/workflows/example", - inputs: {}, - event: { - event_type: "slack#/events/reaction_added", - channel_ids: ["C013ZG3K41Z"], + await t.step( + "shared_channel_invite_* event triggers do not require channel_ids", + () => { + const event: EventTrigger = { + type: TriggerTypes.Event, + name: "test", + workflow: "#/workflows/example", + inputs: {}, + event: { + event_type: TriggerEventTypes.SharedChannelInviteAccepted, + }, + }; + assertEquals(event.type, TriggerTypes.Event); }, - }; - assertEquals(event.type, TriggerTypes.Event); -}); + ); + + await t.step( + "can define a channel-scoped event with `channel_ids`", + () => { + const event: EventTrigger = { + type: TriggerTypes.Event, + name: "test", + workflow: "#/workflows/example", + inputs: {}, + event: { + event_type: TriggerEventTypes.ReactionAdded, + channel_ids: ["C1234"], + }, + }; + const _event2: EventTrigger = { + type: TriggerTypes.Event, + name: "test", + workflow: "#/workflows/example", + inputs: {}, + event: { + event_type: TriggerEventTypes.ReactionAdded, + channel_ids: ["C1234"], + all_resources: false, + }, + }; + assertEquals(event.type, TriggerTypes.Event); + }, + ); -Deno.test("shared_channel_invite_* event triggers do not require channel_ids", () => { - const event: EventTrigger = { - type: TriggerTypes.Event, - name: "test", - workflow: "#/workflows/example", - inputs: {}, - event: { - event_type: "slack#/events/shared_channel_invite_accepted", + await t.step( + "can define a channel-unscoped event with `all_resources`", + () => { + const event: EventTrigger = { + type: TriggerTypes.Event, + name: "test", + workflow: "#/workflows/example", + inputs: {}, + event: { + event_type: TriggerEventTypes.ReactionAdded, + all_resources: true, + }, + }; + assertEquals(event.type, TriggerTypes.Event); + }, + ); + + await t.step( + "channel event types must provide one of `all_resources:true` or `channel_ids`", + () => { + const event: EventTrigger = { + type: TriggerTypes.Event, + name: "test", + workflow: "#/workflows/example", + inputs: {}, + // @ts-expect-error requires one of `all_resources:true` or `channel_ids` + event: { + event_type: TriggerEventTypes.ReactionAdded, + }, + }; + const _event2: EventTrigger = { + type: TriggerTypes.Event, + name: "test", + workflow: "#/workflows/example", + inputs: {}, + // @ts-expect-error requires one of `all_resources:true` or `channel_ids` + event: { + event_type: TriggerEventTypes.ReactionAdded, + all_resources: false, + }, + }; + assertEquals(event.type, TriggerTypes.Event); + }, + ); + + await t.step( + "channel event types must not provide both `all_resources:true` and `channel_ids`", + () => { + const event: EventTrigger = { + type: TriggerTypes.Event, + name: "test", + workflow: "#/workflows/example", + inputs: {}, + // @ts-expect-error cannot provide both `all_resources` and `channel_ids` + event: { + event_type: TriggerEventTypes.ReactionAdded, + all_resources: true, + channel_ids: ["C1234"], + }, + }; + assertEquals(event.type, TriggerTypes.Event); + }, + ); + + await t.step( + "can define a workspace-scoped event with `team_ids`", + () => { + const event: EventTrigger = { + type: TriggerTypes.Event, + name: "test", + workflow: "#/workflows/example", + inputs: {}, + event: { + event_type: TriggerEventTypes.UserJoinedTeam, + team_ids: ["T1234"], + }, + }; + assertEquals(event.type, TriggerTypes.Event); }, - }; - assertEquals(event.type, TriggerTypes.Event); + ); }); Deno.test("Mock call for event", async (t) => {