Skip to content
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
6 changes: 6 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
Copy link
Member

Choose a reason for hiding this comment

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

📣 For sample apps this is an interesting choice that seems helpful for the current maintenance requirements across all projects of @slack-samples.

8 changes: 5 additions & 3 deletions .github/workflows/deno.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ name: Deno app build and testing

on:
push:
branches: [ main ]
branches:
- main
pull_request:
branches: [ main ]
branches:
- main

jobs:
deno:
Expand All @@ -18,7 +20,7 @@ jobs:
- name: Setup Deno
uses: denoland/setup-deno@v1
with:
deno-version: v1.x
deno-version: v2.x

- name: Verify formatting
run: deno fmt --check
Expand Down
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
dist
package
.DS_Store
.slack/apps.dev.json
.env
2 changes: 2 additions & 0 deletions .slack/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
apps.dev.json
cache/
File renamed without changes.
13 changes: 3 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,11 +198,9 @@ $ slack activity --tail
Contains `apps.dev.json` and `apps.json`, which include installation details for
development and deployed apps.

### `datastores/`

[Datastores](https://api.slack.com/automation/datastores) securely store data
for your application on Slack infrastructure. Required scopes to use datastores
include `datastore:write` and `datastore:read`.
Contains `hooks.json` used by the CLI to interact with the project's SDK
dependencies. It contains script hooks that are executed by the CLI and
implemented by the SDK.

### `functions/`

Expand Down Expand Up @@ -230,11 +228,6 @@ continuing to the next step.
The [app manifest](https://api.slack.com/automation/manifest) contains the app's
configuration. This file defines attributes like app name and description.

### `slack.json`

Used by the CLI to interact with the project's SDK dependencies. It contains
script hooks that are executed by the CLI and implemented by the SDK.

## Resources

To learn more about developing automations on Slack, visit the following:
Expand Down
11 changes: 8 additions & 3 deletions deno.jsonc
Copy link
Member

Choose a reason for hiding this comment

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

Changes to this file might be reverted from automations of slack-samples/sample-app-configurations but I realize the changed imports perhaps make that automation not useful now?

Either before or after this pull request we should update these workflows! 🤖

Copy link
Member

Choose a reason for hiding this comment

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

Rambling now, but if we do expect these files to diverge I think it'd be best to remove paths that don't exist within a specific sample and perhaps revisit the lock file? 🔏

I'm also noticing sections in the README.md exist for paths not found in this sample. It might be best to hold off on making these files more sample specific than import updates within these changes...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yess I agree 💯 and I was also thinking of updating slack-samples/sample-app-configurations to use imports rather then import maps

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$schema": "https://deno.land/x/deno/cli/schemas/config-file.v1.json",
"$schema": "https://raw.githubusercontent.com/denoland/deno/main/cli/schemas/config-file.v1.json",
"fmt": {
"include": [
"README.md",
Expand All @@ -13,7 +13,6 @@
"workflows"
]
},
"importMap": "import_map.json",
"lint": {
"include": [
"datastores",
Expand All @@ -28,6 +27,12 @@
},
"lock": false,
"tasks": {
"test": "deno fmt --check && deno lint && deno test --allow-read --allow-none"
"test": "deno fmt --check && deno lint && deno test --allow-read"
},
"imports": {
"deno-slack-sdk/": "https://deno.land/x/deno_slack_sdk@2.15.0/",
"deno-slack-api/": "https://deno.land/x/deno_slack_api@2.8.0/",
"@std/testing": "jsr:@std/testing@^1.0.12",
"@std/assert": "jsr:@std/assert@^1.0.13"
}
}
2 changes: 1 addition & 1 deletion functions/detect_lang_test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { SlackFunctionTester } from "deno-slack-sdk/mod.ts";
import { assertEquals } from "std/testing/asserts.ts";
import { assertEquals } from "@std/assert";
import handler from "./detect_lang.ts";

const { createContext } = SlackFunctionTester("my-function");
Expand Down
26 changes: 13 additions & 13 deletions functions/translate.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { DefineFunction, Schema, SlackFunction } from "deno-slack-sdk/mod.ts";
import { SlackAPIClient } from "deno-slack-sdk/types.ts";
import type { SlackAPIClient } from "deno-slack-sdk/types.ts";
import { isDebugMode } from "./internals/debug_mode.ts";

export const def = DefineFunction({
Expand Down Expand Up @@ -52,7 +52,7 @@ export default SlackFunction(def, async ({ inputs, client, env }) => {
return { error };
}

if (translationTargetResponse.messages.length == 0) {
if (translationTargetResponse.messages.length === 0) {
console.log("No message found");
return emptyOutputs; // this is not an error
}
Expand Down Expand Up @@ -81,7 +81,7 @@ export default SlackFunction(def, async ({ inputs, client, env }) => {
if (match.match(/^[#@].*$/)) {
const matched = match.match(/^([#@].*)$/);
if (matched != null) {
return "<mrkdwn>" + matched[1] + "</mrkdwn>";
return `<mrkdwn>${matched[1]}</mrkdwn>`;
}
return "";
}
Expand All @@ -93,32 +93,32 @@ export default SlackFunction(def, async ({ inputs, client, env }) => {
if (match.match(/^!date.*$/)) {
const matched = match.match(/^(!date.*)$/);
if (matched != null) {
return "<mrkdwn>" + matched[1] + "</mrkdwn>";
return `<mrkdwn>${matched[1]}</mrkdwn>`;
}
return "";
}
// match special mention
if (match.match(/^!.*$/)) {
const matched = match.match(/^!(.*?)(?:\|.*)?$/);
if (matched != null) {
return "<ignore>@" + matched[1] + "</ignore>";
return `<ignore>@${matched[1]}</ignore>`;
}
return "<ignore>@[special mention]</ignore>";
}
// match formatted link
if (match.match(/^.*?\|.*$/)) {
const matched = match.match(/^(.*?)\|(.*)$/);
if (matched != null) {
return '<a href="' + matched[1] + '">' + matched[2] + "</a>";
return `<a href="${matched[1]}">${matched[2]}</a>`;
}
return "";
}
// fallback (raw link or unforeseen formatting)
return "<mrkdwn>" + match + "</mrkdwn>";
return `<mrkdwn>${match}</mrkdwn>`;
// match emoji
})
.replace(/:([a-z0-9_-]+):/g, (_: unknown, match: string) => {
return "<emoji>" + match + "</emoji>";
return `<emoji>${match}</emoji>`;
});
body.append("text", targetText);
body.append("tag_handling", "xml");
Expand All @@ -133,8 +133,8 @@ export default SlackFunction(def, async ({ inputs, client, env }) => {
},
body,
});
if (deeplResponse.status != 200) {
if (deeplResponse.status == 403) {
if (deeplResponse.status !== 200) {
if (deeplResponse.status === 403) {
// If the status code is 403, the given auth key is not valid
const error =
`Translating a message failed! Please make sure if the DEEPL_AUTH_KEY is correct. - (status: ${deeplResponse.status}, target text: ${
Expand Down Expand Up @@ -170,19 +170,19 @@ export default SlackFunction(def, async ({ inputs, client, env }) => {
const translatedText = translationResult.translations[0].text
// Parse encoding tags to restore the original special syntax
.replace(/<emoji>([a-z0-9_-]+)<\/emoji>/g, (_: unknown, match: string) => {
return ":" + match + ":";
return `:${match}:`;
// match <mrkdwn>...</mrkdwn>
})
.replace(/<mrkdwn>(.*?)<\/mrkdwn>/g, (_: unknown, match: string) => {
return "<" + match + ">";
return `<${match}>`;
// match <a href="...">...</a>
})
.replace(
/(<a href="(?:.*?)">(?:.*?)<\/a>)/g,
(_: unknown, match: string) => {
const matched = match.match(/<a href="(.*?)">(.*?)<\/a>/);
if (matched != null) {
return "<" + matched[1] + "|" + matched[2] + ">";
return `<${matched[1]}|${matched[2]}>`;
}
return "";
// match <ignore>...</ignore>
Expand Down
155 changes: 88 additions & 67 deletions functions/translate_test.ts
Original file line number Diff line number Diff line change
@@ -1,81 +1,100 @@
import * as mf from "mock-fetch/mod.ts";
import { SlackFunctionTester } from "deno-slack-sdk/mod.ts";
import { assertEquals } from "std/testing/asserts.ts";
import { assertEquals } from "@std/assert";
import { stub } from "@std/testing/mock";
import handler from "./translate.ts";

// Replaces globalThis.fetch with the mocked copy
mf.install();
function stubFetch() {
// Replaces globalThis.fetch with the mocked copy
return stub(
globalThis,
"fetch",
async (url: string | URL | Request, options?: RequestInit) => {
const req = url instanceof Request ? url : new Request(url, options);

mf.mock("POST@/api/conversations.replies", (args) => {
const authHeader = args.headers.get("Authorization");
if (authHeader === "Bearer empty-response") {
return new Response(JSON.stringify({ ok: true, messages: [] }), {
status: 200,
});
}
return new Response(
JSON.stringify({
"ok": true,
"oldest": "1670566778.964519",
"messages": [
{
"type": "message",
"text":
"Make work life simpler, more pleasant and more productive.\n\nSlack is the collaboration hub that brings the right people, information, and tools together to get work done. From Fortune 100 companies to corner markets, millions of people around the world use Slack to connect their teams, unify their systems, and drive their business forward.",
"user": "U03E94MK0",
"ts": "1670566778.964519",
"team": "T03E94MJU",
"thread_ts": "1670566778.964519",
"reply_count": 3,
"reply_users_count": 1,
"latest_reply": "1670570301.090889",
"reply_users": ["U04EJMQQEFN"],
"is_locked": false,
"subscribed": false,
"reactions": [{ "name": "jp", "users": ["U03E94MK0"], "count": 1 }],
},
],
"has_more": false,
"pin_count": 0,
"channel_actions_ts": null,
"channel_actions_count": 0,
}),
{
status: 200,
},
);
});
assertEquals(req.method, "POST");

mf.mock("POST@/api/chat.postMessage", () => {
return new Response(JSON.stringify({ ok: true, ts: "1111.2222" }), {
status: 200,
});
});

mf.mock("POST@/v2/translate", async (args) => {
const body = await args.formData();
if (body.get("auth_key") !== "valid") {
return new Response("", { status: 403 });
}
return new Response(
JSON.stringify({
translations: [
{
detected_source_language: "EN",
text:
"ワークライフをよりシンプルに、より快適に、より生産的にする。\n\nSlack は、適切な人、情報、ツールを集めて仕事を成し遂げるためのコラボレーション ハブです。フォーチュン100の企業から片隅の市場ま...",
},
],
}),
{
status: 200,
switch (req.url) {
case "https://slack.com/api/conversations.replies":
if (req.headers.get("Authorization") === "Bearer empty-response") {
return new Response(JSON.stringify({ ok: true, messages: [] }), {
status: 200,
});
}
return new Response(
JSON.stringify({
"ok": true,
"oldest": "1670566778.964519",
"messages": [
{
"type": "message",
"text":
"Make work life simpler, more pleasant and more productive.\n\nSlack is the collaboration hub that brings the right people, information, and tools together to get work done. From Fortune 100 companies to corner markets, millions of people around the world use Slack to connect their teams, unify their systems, and drive their business forward.",
"user": "U03E94MK0",
"ts": "1670566778.964519",
"team": "T03E94MJU",
"thread_ts": "1670566778.964519",
"reply_count": 3,
"reply_users_count": 1,
"latest_reply": "1670570301.090889",
"reply_users": ["U04EJMQQEFN"],
"is_locked": false,
"subscribed": false,
"reactions": [{
"name": "jp",
"users": ["U03E94MK0"],
"count": 1,
}],
},
],
"has_more": false,
"pin_count": 0,
"channel_actions_ts": null,
"channel_actions_count": 0,
}),
{
status: 200,
},
);
case "https://slack.com/api/chat.postMessage": {
return new Response(JSON.stringify({ ok: true, ts: "1111.2222" }), {
status: 200,
});
}
case "https://api.deepl.com/v2/translate": {
const body = await req.formData();
if (body.get("auth_key") === "valid") {
return new Response(
JSON.stringify({
translations: [
{
detected_source_language: "EN",
text:
"ワークライフをよりシンプルに、より快適に、より生産的にする。\n\nSlack は、適切な人、情報、ツールを集めて仕事を成し遂げるためのコラボレーション ハブです。フォーチュン100の企業から片隅の市場ま...",
},
],
}),
{
status: 200,
},
);
}
return new Response("", { status: 403 });
}
default:
throw Error(
`No stub found for ${req.method} ${req.url}\nHeaders: ${
JSON.stringify(Object.fromEntries(req.headers.entries()))
}`,
);
}
},
);
});
}

const { createContext } = SlackFunctionTester("my-function");

Deno.test("No message found", async () => {
using _stubFetch = stubFetch();
const inputs = {
channelId: "C111",
messageTs: "1670566778.964519",
Expand All @@ -88,6 +107,7 @@ Deno.test("No message found", async () => {
});

Deno.test("Translate a message successfully", async () => {
using _stubFetch = stubFetch();
const inputs = {
channelId: "C123",
messageTs: "1670566778.964519",
Expand All @@ -100,6 +120,7 @@ Deno.test("Translate a message successfully", async () => {
});

Deno.test("Fail to translate with an invalid auth key", async () => {
using _stubFetch = stubFetch();
const inputs = {
channelId: "C111",
messageTs: "1670566778.964519",
Expand Down
8 changes: 0 additions & 8 deletions import_map.json

This file was deleted.

2 changes: 1 addition & 1 deletion triggers/reaction_added_trigger.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Trigger } from "deno-slack-sdk/types.ts";
import type { Trigger } from "deno-slack-sdk/types.ts";
Copy link
Member

Choose a reason for hiding this comment

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

👁️‍🗨️ Good catch!

import {
TriggerContextData,
TriggerEventTypes,
Expand Down