Skip to content

Commit

Permalink
Add a client instance to function handler params (#101)
Browse files Browse the repository at this point in the history
* setting up client as new param passed into all function handlers

* adding enrichContext to centralize where it's done across handlers

* updating api version for testing

* using published version of api

* updating types

* removing type

* new deno version fmt adjustments

* updating tests, adding client to unhandledEvent handlers

* checking for client in unhandledEvent handler

* adding enrichContext tests

* updating exported SlackFunctionHandler type to represent runtime context and not enriched SlackFunction() handler context

* switching to ts-expect-error

* export SlackAPI

* adding some api exports
  • Loading branch information
selfcontained committed Sep 13, 2022
1 parent c25b335 commit c5fa357
Show file tree
Hide file tree
Showing 19 changed files with 519 additions and 238 deletions.
2 changes: 1 addition & 1 deletion build_npm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ await build({
});

// post build steps
Deno.copyFileSync("README.md", "npm/README.md");
Deno.copyFileSync("README.md", "npm/README.md");
72 changes: 33 additions & 39 deletions docs/functions-action-handlers.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,49 +89,44 @@ subdirectory in your app) that posts a message with two buttons: an approval but
and a deny button:

```typescript
import type { SlackFunctionHandler } from "deno-slack-sdk/types.ts";
import { SlackAPI } from "deno-slack-api/mod.ts";
import { SlackFunction } from "deno-slack-sdk/mod.ts";
// ApprovalFunction is the function we defined in the previous section
import { ApprovalFunction } from "./definition.ts";

const approval: SlackFunctionHandler<typeof ApprovalFunction.definition> =
async ({ inputs, token }) => {
console.log('Incoming approval!');
// A Slack API client, so that we can make API calls to Slack
const client = SlackAPI(token);

await client.chat.postMessage({
channel: inputs.approval_channel_id,
blocks: [{
"type": "actions",
"block_id": "mah-buttons",
"elements": [{
type: "button",
text: {
type: "plain_text",
text: "Approve",
},
action_id: "approve_request",
style: "primary",
export default SlackFunction(ApprovalFunction, async ({ inputs, client }) => {
console.log('Incoming approval!');

await client.chat.postMessage({
channel: inputs.approval_channel_id,
blocks: [{
"type": "actions",
"block_id": "mah-buttons",
"elements": [{
type: "button",
text: {
type: "plain_text",
text: "Approve",
},
{
type: "button",
text: {
type: "plain_text",
text: "Deny",
},
action_id: "deny_request",
style: "danger",
}],
action_id: "approve_request",
style: "primary",
},
{
type: "button",
text: {
type: "plain_text",
text: "Deny",
},
action_id: "deny_request",
style: "danger",
}],
});
// Important to set completed: false! We will set the function's complete
// status later - in our action handler
return {
completed: false,
};
}],
});
// Important to set completed: false! We will set the function's complete
// status later - in our action handler
return {
completed: false,
};
export default approval;
});
```

The key bit of information we need to remember before moving on to adding an
Expand Down Expand Up @@ -161,9 +156,8 @@ const ActionsRouter = BlockActionsRouter(ApprovalFunction);
export const blockActions = ActionsRouter.addHandler(
['approve_request', 'deny_request'], // The first argument to addHandler can accept an array of action_id strings, among many other formats!
// Check the API reference at the end of this document for the full list of supported options
async ({ action, body, token }) => { // The second argument is the handler function itself
async ({ action, body, client }) => { // The second argument is the handler function itself
console.log('Incoming action handler invocation', action);
const client = SlackAPI(token);

const outputs = {
reviewer: body.user.id,
Expand Down
18 changes: 5 additions & 13 deletions docs/functions-view-handlers.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,17 +99,12 @@ type `Schema.slack.types.interactivity` under the `interactivity_pointer` proper
Check out the code below for an example:

```typescript
import type { SlackFunctionHandler } from "deno-slack-sdk/types.ts";
import { SlackAPI } from "deno-slack-api/mod.ts";
import { SlackFunction } from "deno-slack-sdk/mod.ts";
// DiaryFunction is the function we defined in the previous section
import { DiaryFunction } from "./definition.ts";

const diary: SlackFunctionHandler<
typeof DiaryFunction.definition
> = async ({ inputs, token }) => {
export default SlackFunction(DiaryFunction, async ({ inputs, client }) => {
console.log('Someone might want to write a diary entry...');
// A Slack API client, so that we can make API calls to Slack
const client = SlackAPI(token);

await client.views.open({
trigger_id: inputs.interactivity.interactivity_pointer,
Expand Down Expand Up @@ -182,15 +177,13 @@ You can use the value of `body.trigger_id` within an action handler to open a
view, like so:
```typescript
import type { SlackFunctionHandler } from "deno-slack-sdk/types.ts";
import { SlackAPI } from "deno-slack-api/mod.ts";
import { BlockActionsRouter } from "deno-slack-sdk/mod.ts";
// DiaryFunction is the function we defined in the previous section
import { DiaryFunction } from "./definition.ts";

export const blockActions = BlockActionsRouter(DiaryFunction).addHandler(
'deny_request',
async ({ action, body, token }) => {
const client = SlackAPI(token);
async ({ action, body, client }) => {
await client.views.open({
trigger_id: body.trigger_id,
view: { /* your view object goes here */ },
Expand Down Expand Up @@ -252,8 +245,7 @@ As an example, consider the following two code snippets. They yield identical be
```typescript
export const { viewSubmission, viewClosed } = ViewRouter
// A view submission handler that pushes a new view using the API
.addSubmissionHandler(/view/, async ({ token, body }) => {
const client = SlackAPI(token);
.addSubmissionHandler(/view/, async ({ client, body }) => {
await client.views.push({
trigger_id: body.trigger_id,
view: { /* your view object goes here */ },
Expand Down
5 changes: 5 additions & 0 deletions src/deps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export { SlackAPI } from "https://deno.land/x/deno_slack_api@1.1.0/mod.ts";
export type {
SlackAPIClient,
Trigger,
} from "https://deno.land/x/deno_slack_api@1.1.0/types.ts";
62 changes: 35 additions & 27 deletions src/functions/base_function_handler_type_test.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { assertEquals } from "../dev_deps.ts";
import { SlackFunctionTester } from "./tester/mod.ts";
import { BaseSlackFunctionHandler } from "./types.ts";
import { BaseRuntimeSlackFunctionHandler } from "./types.ts";

// These tests are to ensure our Function Handler types are supporting the use cases we want to
// Any "failures" here will most likely be reflected in Type errors

Deno.test("BaseSlackFunctionHandler types", () => {
Deno.test("BaseRuntimeSlackFunctionHandler types", () => {
type Inputs = {
in: string;
};
type Outputs = {
out: string;
};
const handler: BaseSlackFunctionHandler<Inputs, Outputs> = (
const handler: BaseRuntimeSlackFunctionHandler<Inputs, Outputs> = (
{ inputs },
) => {
return {
Expand All @@ -27,10 +27,10 @@ Deno.test("BaseSlackFunctionHandler types", () => {
assertEquals(result.outputs?.out, inputs.in);
});

Deno.test("BaseSlackFunctionHandler with empty inputs and empty outputs", () => {
Deno.test("BaseRuntimeSlackFunctionHandler with empty inputs and empty outputs", () => {
type Inputs = Record<never, never>;
type Outputs = Record<never, never>;
const handler: BaseSlackFunctionHandler<Inputs, Outputs> = () => {
const handler: BaseRuntimeSlackFunctionHandler<Inputs, Outputs> = () => {
return {
outputs: {},
};
Expand All @@ -40,10 +40,10 @@ Deno.test("BaseSlackFunctionHandler with empty inputs and empty outputs", () =>
assertEquals(result.outputs, {});
});

Deno.test("BaseSlackFunctionHandler with undefined inputs and outputs", () => {
Deno.test("BaseRuntimeSlackFunctionHandler with undefined inputs and outputs", () => {
type Inputs = undefined;
type Outputs = undefined;
const handler: BaseSlackFunctionHandler<Inputs, Outputs> = () => {
const handler: BaseRuntimeSlackFunctionHandler<Inputs, Outputs> = () => {
return {
outputs: {},
};
Expand All @@ -53,12 +53,14 @@ Deno.test("BaseSlackFunctionHandler with undefined inputs and outputs", () => {
assertEquals(result.outputs, {});
});

Deno.test("BaseSlackFunctionHandler with inputs and empty outputs", () => {
Deno.test("BaseRuntimeSlackFunctionHandler with inputs and empty outputs", () => {
type Inputs = {
in: string;
};
type Outputs = Record<never, never>;
const handler: BaseSlackFunctionHandler<Inputs, Outputs> = ({ inputs }) => {
const handler: BaseRuntimeSlackFunctionHandler<Inputs, Outputs> = (
{ inputs },
) => {
const _test = inputs.in;

return {
Expand All @@ -71,12 +73,12 @@ Deno.test("BaseSlackFunctionHandler with inputs and empty outputs", () => {
assertEquals(result.outputs, {});
});

Deno.test("BaseSlackFunctionHandler with empty inputs and outputs", () => {
Deno.test("BaseRuntimeSlackFunctionHandler with empty inputs and outputs", () => {
type Inputs = Record<never, never>;
type Outputs = {
out: string;
};
const handler: BaseSlackFunctionHandler<Inputs, Outputs> = () => {
const handler: BaseRuntimeSlackFunctionHandler<Inputs, Outputs> = () => {
return {
outputs: {
out: "test",
Expand All @@ -88,9 +90,9 @@ Deno.test("BaseSlackFunctionHandler with empty inputs and outputs", () => {
assertEquals(result.outputs?.out, "test");
});

Deno.test("BaseSlackFunctionHandler with any inputs and any outputs", () => {
Deno.test("BaseRuntimeSlackFunctionHandler with any inputs and any outputs", () => {
// deno-lint-ignore no-explicit-any
const handler: BaseSlackFunctionHandler<any, any> = ({ inputs }) => {
const handler: BaseRuntimeSlackFunctionHandler<any, any> = ({ inputs }) => {
return {
outputs: {
out: inputs.in,
Expand All @@ -103,36 +105,40 @@ Deno.test("BaseSlackFunctionHandler with any inputs and any outputs", () => {
assertEquals(result.outputs?.out, inputs.in);
});

Deno.test("BaseSlackFunctionHandler with no inputs and error output", () => {
Deno.test("BaseRuntimeSlackFunctionHandler with no inputs and error output", () => {
// deno-lint-ignore no-explicit-any
const handler: BaseSlackFunctionHandler<any, { example: string }> = () => {
return {
error: "error",
const handler: BaseRuntimeSlackFunctionHandler<any, { example: string }> =
() => {
return {
error: "error",
};
};
};
const { createContext } = SlackFunctionTester("test");
const result = handler(createContext({ inputs: {} }));
assertEquals(result.error, "error");
});

Deno.test("BaseSlackFunctionHandler with no inputs and completed false output", () => {
Deno.test("BaseRuntimeSlackFunctionHandler with no inputs and completed false output", () => {
// deno-lint-ignore no-explicit-any
const handler: BaseSlackFunctionHandler<any, { example: boolean }> = () => {
return {
completed: false,
const handler: BaseRuntimeSlackFunctionHandler<any, { example: boolean }> =
() => {
return {
completed: false,
};
};
};
const { createContext } = SlackFunctionTester("test");
const result = handler(createContext({ inputs: {} }));
assertEquals(result.completed, false);
});

Deno.test("BaseSlackFunctionHandler with set inputs and any outputs", () => {
Deno.test("BaseRuntimeSlackFunctionHandler with set inputs and any outputs", () => {
type Inputs = {
in: string;
};
// deno-lint-ignore no-explicit-any
const handler: BaseSlackFunctionHandler<Inputs, any> = ({ inputs }) => {
const handler: BaseRuntimeSlackFunctionHandler<Inputs, any> = (
{ inputs },
) => {
return {
outputs: {
out: inputs.in,
Expand All @@ -145,7 +151,7 @@ Deno.test("BaseSlackFunctionHandler with set inputs and any outputs", () => {
assertEquals(result.outputs?.out, inputs.in);
});

Deno.test("BaseSlackFunctionHandler with input and output objects", () => {
Deno.test("BaseRuntimeSlackFunctionHandler with input and output objects", () => {
type Inputs = {
anObject: {
in: string;
Expand All @@ -156,7 +162,9 @@ Deno.test("BaseSlackFunctionHandler with input and output objects", () => {
out: string;
};
};
const handler: BaseSlackFunctionHandler<Inputs, Outputs> = ({ inputs }) => {
const handler: BaseRuntimeSlackFunctionHandler<Inputs, Outputs> = (
{ inputs },
) => {
return {
outputs: {
anObject: { out: inputs.anObject.in },
Expand Down
22 changes: 22 additions & 0 deletions src/functions/enrich-context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { SlackAPI } from "../deps.ts";
import {
BaseRuntimeFunctionContext,
FunctionContextEnrichment,
} from "./types.ts";

export const enrichContext = (
// deno-lint-ignore no-explicit-any
context: BaseRuntimeFunctionContext<any>,
): typeof context & FunctionContextEnrichment => {
const token = context.token;
const slackApiUrl = (context.env || {})["SLACK_API_URL"];

const client = SlackAPI(token, {
slackApiUrl: slackApiUrl ? slackApiUrl : undefined,
});

return {
...context,
client,
};
};
33 changes: 33 additions & 0 deletions src/functions/enrich-context_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { assertExists } from "../dev_deps.ts";
import { enrichContext } from "./enrich-context.ts";
import { BaseRuntimeFunctionContext } from "./types.ts";

Deno.test("enrichContext with no env.SLACK_API_URL", () => {
// deno-lint-ignore no-explicit-any
const ctx: BaseRuntimeFunctionContext<any> = {
env: {},
inputs: {},
team_id: "team",
token: "token",
};

const newContext = enrichContext(ctx);

assertExists(newContext.client);
});

Deno.test("enrichContext with env.SLACK_API_URL", () => {
// deno-lint-ignore no-explicit-any
const ctx: BaseRuntimeFunctionContext<any> = {
env: {
"SLACK_API_URL": "https://something.slack.com/api",
},
inputs: {},
team_id: "team",
token: "token",
};

const newContext = enrichContext(ctx);

assertExists(newContext.client);
});
Loading

0 comments on commit c5fa357

Please sign in to comment.