Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for wildcard/unscoped channel/workspace event trigger definitions #98

Merged
merged 2 commits into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/maintainers_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
<https://deno.land/x/deno_slack_api>.
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

Expand Down
65 changes: 35 additions & 30 deletions src/typed-method-types/workflows/triggers/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>;
// deno-lint-ignore no-explicit-any
[otherOptions: string]: any;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was moved down to BaseEvent

};
& (ChannelEvent | MetadataChannelEvent | MessagePostedEvent) // controls `event_type` and `filter`
& (ChannelUnscopedEvent | ChannelScopedEvent); // controls event scoping: `channel_ids` and `all_resources`

/**
* Event that is unscoped and not 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: true`. */
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: true`. */
channel_ids: PopulatedArray<string>;
/** @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<ChannelTypes, MessageMetadataTypes>;
};

type MetadataChannelEvent =
& BaseEvent
& {
/** @description The type of event */
event_type: Extract<ChannelTypes, MessageMetadataTypes>;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed this line by extending from ChannelEvent instead of BaseEvent

/** @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 =
Expand All @@ -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<string>;
// deno-lint-ignore no-explicit-any
[otherOptions: string]: any;
};

type BaseWorkspaceEvent = BaseEvent & {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merged BaseWorkspaceEvent into WorkspaceEvents, since they are only used here.

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<string>;
};

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<string, any>;

export type EventTrigger<WorkflowDefinition extends WorkflowSchema> =
& BaseTrigger<WorkflowDefinition>
Expand Down Expand Up @@ -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<string, any>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this 💯

188 changes: 152 additions & 36 deletions src/typed-method-types/workflows/triggers/tests/event_test.ts
Original file line number Diff line number Diff line change
@@ -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/[email protected]/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<ExampleWorkflow> = {
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<ExampleWorkflow> = {
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<ExampleWorkflow> = {
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<ExampleWorkflow> = {
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<ExampleWorkflow> = {
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<ExampleWorkflow> = {
type: TriggerTypes.Event,
name: "test",
workflow: "#/workflows/example",
inputs: {},
event: {
event_type: TriggerEventTypes.ReactionAdded,
channel_ids: ["C1234"],
},
};
const _event2: EventTrigger<ExampleWorkflow> = {
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<ExampleWorkflow> = {
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<ExampleWorkflow> = {
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<ExampleWorkflow> = {
type: TriggerTypes.Event,
name: "test",
workflow: "#/workflows/example",
inputs: {},
// @ts-expect-error requires one of `all_resources:true` or `channel_ids`
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using @ts-expect-error, we can test for the error path, which I think is very helpful for maintaining types. If we have a large enough set of these type tests, we will be able to more easily refactor the types with less worry about forgetting something.

event: {
event_type: TriggerEventTypes.ReactionAdded,
},
};
const _event2: EventTrigger<ExampleWorkflow> = {
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<ExampleWorkflow> = {
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<ExampleWorkflow> = {
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) => {
Expand Down
Loading