From 2712a7f2be3407afda7e11adc96337fda6175a26 Mon Sep 17 00:00:00 2001 From: Hiroki Miyaji <40714517+hiroki0525@users.noreply.github.com> Date: Sun, 14 Jan 2024 21:40:17 +0900 Subject: [PATCH] Feat add support trello (#101) --- .changeset/swift-bugs-appear.md | 6 + README.md | 15 ++ media/trello_example.png | Bin 0 -> 46235 bytes packages/cli/README.md | 27 ++++ packages/cli/package.json | 8 +- .../cli/src/notion/__tests__/index.test.ts | 16 +-- .../cli/src/trello/__tests__/index.test.ts | 123 ++++++++++++++++ packages/cli/src/trello/cli.ts | 6 + packages/cli/src/trello/index.ts | 26 ++++ packages/ui/README.md | 95 +++++++++++- packages/ui/global.d.ts | 2 + packages/ui/package.json | 1 + packages/ui/src/__tests__/trello.test.ts | 136 ++++++++++++++++++ packages/ui/src/index.ts | 1 + packages/ui/src/trello/client.ts | 41 ++++++ packages/ui/src/trello/index.ts | 1 + packages/ui/src/trello/trello.ts | 77 ++++++++++ .../templates/generateDandoriTrelloCards.ts | 19 +++ 18 files changed, 587 insertions(+), 13 deletions(-) create mode 100644 .changeset/swift-bugs-appear.md create mode 100644 media/trello_example.png create mode 100644 packages/cli/src/trello/__tests__/index.test.ts create mode 100644 packages/cli/src/trello/cli.ts create mode 100644 packages/cli/src/trello/index.ts create mode 100644 packages/ui/src/__tests__/trello.test.ts create mode 100644 packages/ui/src/trello/client.ts create mode 100644 packages/ui/src/trello/index.ts create mode 100644 packages/ui/src/trello/trello.ts create mode 100644 packages/ui/templates/generateDandoriTrelloCards.ts diff --git a/.changeset/swift-bugs-appear.md b/.changeset/swift-bugs-appear.md new file mode 100644 index 0000000..b4d8674 --- /dev/null +++ b/.changeset/swift-bugs-appear.md @@ -0,0 +1,6 @@ +--- +"@dandori/cli": patch +"@dandori/ui": patch +--- + +Add trello diff --git a/README.md b/README.md index 04622b5..2d11c3f 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,21 @@ Today's My Tasks notion output example +## Example 3 + +### Input + +```text +Today's My Tasks +* [todo] Send Email to John +* [doing] Write a blog +* [done] Report to Boss +``` + +### Output(Trello) + +notion output example + ## Usage This project is monorepo. You can choose the following ways to use it. diff --git a/media/trello_example.png b/media/trello_example.png new file mode 100644 index 0000000000000000000000000000000000000000..dc50d7b9aefc0ba4b3f36082a6fa49fe451d8f70 GIT binary patch literal 46235 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 [options] + +Options: + -V, --version output the version number + -e, --env-file env file path + -m, --model Chat GPT model which supports function_calling + -o, --optional-task-props optional output task props which delimiter is a comma + -b, --board-id trello board id + --status-todo trello list status todo name + --status-doing trello list status doing name + --status-done trello list status done name + -h, --help display help for command +``` + +#### Example of the command + +```bash +pnpm --package=@dandori/cli dlx dandori-trello your_tasks.txt -d your_board_id -o status --status-todo 'Todo' --status-doing 'Doing' --status-done 'Done' ``` \ No newline at end of file diff --git a/packages/cli/package.json b/packages/cli/package.json index a4b9ab2..68462b4 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -34,13 +34,15 @@ "build": "tsup --config ../../tsup.config.ts", "test": "vitest run", "dev:core": "tsx src/core/cli.ts", - "dev:miro": "tsx src/miro/cli.ts ./tmp/tmp.txt", - "dev:notion": "tsx src/notion/cli.ts" + "dev:miro": "tsx src/miro/cli.ts", + "dev:notion": "tsx src/notion/cli.ts", + "dev:trello": "tsx src/trello/cli.ts -h" }, "bin": { "dandori-core": "./dist/core/cli.js", "dandori-miro": "./dist/miro/cli.js", - "dandori-notion": "./dist/notion/cli.js" + "dandori-notion": "./dist/notion/cli.js", + "dandori-trello": "./dist/trello/cli.js" }, "keywords": [], "author": "Hiroki Miyaji", diff --git a/packages/cli/src/notion/__tests__/index.test.ts b/packages/cli/src/notion/__tests__/index.test.ts index 59d02b8..40d23a0 100644 --- a/packages/cli/src/notion/__tests__/index.test.ts +++ b/packages/cli/src/notion/__tests__/index.test.ts @@ -11,7 +11,7 @@ import { } from "vitest"; import { DandoriTask } from "@dandori/core"; import { generateDandoriNotionPages } from "@dandori/ui"; -import DandoriMiroCli from "../index"; +import DandoriNotionCli from "../index"; import { rm, writeFile } from "fs/promises"; const tasks: DandoriTask[] = [ @@ -60,7 +60,7 @@ describe("DandoriNotionCli", () => { beforeEach(async () => { loadProcessArgv(["-d", databaseId]); - await new DandoriMiroCli().run(); + await new DandoriNotionCli().run(); }); it("call generateDandoriNotionPages with database id", () => { @@ -75,7 +75,7 @@ describe("DandoriNotionCli", () => { beforeEach(async () => { loadProcessArgv(["--name", name]); - await new DandoriMiroCli().run(); + await new DandoriNotionCli().run(); }); it("call generateDandoriNotionPages with databasePropertiesMap.name", () => { @@ -92,7 +92,7 @@ describe("DandoriNotionCli", () => { beforeEach(async () => { loadProcessArgv(["--deadline", deadline]); - await new DandoriMiroCli().run(); + await new DandoriNotionCli().run(); }); it("call generateDandoriNotionPages with databasePropertiesMap.deadline", () => { @@ -109,7 +109,7 @@ describe("DandoriNotionCli", () => { beforeEach(async () => { loadProcessArgv(["--status", status]); - await new DandoriMiroCli().run(); + await new DandoriNotionCli().run(); }); it("call generateDandoriNotionPages with databasePropertiesMap.status", () => { @@ -126,7 +126,7 @@ describe("DandoriNotionCli", () => { beforeEach(async () => { loadProcessArgv(["--status-todo", statusTodo]); - await new DandoriMiroCli().run(); + await new DandoriNotionCli().run(); }); it("call generateDandoriNotionPages with databasePropertiesMap.status.todo", () => { @@ -143,7 +143,7 @@ describe("DandoriNotionCli", () => { beforeEach(async () => { loadProcessArgv(["--status-doing", statusDoing]); - await new DandoriMiroCli().run(); + await new DandoriNotionCli().run(); }); it("call generateDandoriNotionPages with databasePropertiesMap.status.doing", () => { @@ -160,7 +160,7 @@ describe("DandoriNotionCli", () => { beforeEach(async () => { loadProcessArgv(["--status-done", statusDone]); - await new DandoriMiroCli().run(); + await new DandoriNotionCli().run(); }); it("call generateDandoriNotionPages with databasePropertiesMap.status.done", () => { diff --git a/packages/cli/src/trello/__tests__/index.test.ts b/packages/cli/src/trello/__tests__/index.test.ts new file mode 100644 index 0000000..64af7b9 --- /dev/null +++ b/packages/cli/src/trello/__tests__/index.test.ts @@ -0,0 +1,123 @@ +import { + describe, + beforeEach, + afterEach, + vi, + expect, + it, + beforeAll, + afterAll, + Mock, +} from "vitest"; +import { DandoriTask } from "@dandori/core"; +import { generateDandoriTrelloCards } from "@dandori/ui"; +import DandoriTrelloCli from "../index"; +import { rm, writeFile } from "fs/promises"; + +const tasks: DandoriTask[] = [ + { + id: "1", + name: "task1", + deadline: "2021-01-01", + description: "task1-description", + fromTaskIdList: [], + status: "todo", + }, +]; + +vi.mock("@dandori/core", () => ({ + default: vi.fn(() => tasks), +})); + +vi.mock("@dandori/ui", () => ({ + generateDandoriTrelloCards: vi.fn(), +})); + +const mockGenerateDandoriTrelloCards = generateDandoriTrelloCards as Mock; + +describe("DandoriTrelloCli", () => { + const inputFileName = "DandoriTrelloCli.txt"; + const inputFileText = "DandoriTrelloCli"; + const loadProcessArgv = (options: string[]) => { + process.argv = ["node", "cli.js", inputFileName, ...options]; + }; + + beforeAll(async () => { + await writeFile(inputFileName, inputFileText); + }); + + afterAll(async () => { + await rm(inputFileName); + }); + + afterEach(() => { + process.argv = []; + vi.clearAllMocks(); + }); + + describe("with -b option", () => { + const boardId = "boardId"; + + beforeEach(async () => { + loadProcessArgv(["-b", boardId]); + await new DandoriTrelloCli().run(); + }); + + it("call generateDandoriTrelloCards with board id", () => { + expect(mockGenerateDandoriTrelloCards.mock.lastCall[1]).toMatchObject({ + boardId, + }); + }); + }); + + describe("with --status-todo option", () => { + const statusTodo = "ToDo"; + + beforeEach(async () => { + loadProcessArgv(["--status-todo", statusTodo]); + await new DandoriTrelloCli().run(); + }); + + it("call generateDandoriTrelloCards with trelloListPropertiesMap.status.todo", () => { + expect( + mockGenerateDandoriTrelloCards.mock.lastCall[1].trelloListPropertiesMap, + ).toMatchObject({ + "status.todo": statusTodo, + }); + }); + }); + + describe("with --status-doing option", () => { + const statusDoing = "Doing"; + + beforeEach(async () => { + loadProcessArgv(["--status-doing", statusDoing]); + await new DandoriTrelloCli().run(); + }); + + it("call generateDandoriTrelloCards with trelloListPropertiesMap.status.doing", () => { + expect( + mockGenerateDandoriTrelloCards.mock.lastCall[1].trelloListPropertiesMap, + ).toMatchObject({ + "status.doing": statusDoing, + }); + }); + }); + + describe("with --status-done option", () => { + const statusDone = "Done"; + + beforeEach(async () => { + loadProcessArgv(["--status-done", statusDone]); + await new DandoriTrelloCli().run(); + }); + + it("call generateDandoriTrelloCards with trelloListPropertiesMap.status.done", () => { + expect( + mockGenerateDandoriTrelloCards.mock.lastCall[1].trelloListPropertiesMap, + ).toMatchObject({ + "status.done": statusDone, + }); + }); + }); +}); diff --git a/packages/cli/src/trello/cli.ts b/packages/cli/src/trello/cli.ts new file mode 100644 index 0000000..e672ca6 --- /dev/null +++ b/packages/cli/src/trello/cli.ts @@ -0,0 +1,6 @@ +#!/usr/bin/env node + +import DandoriTrelloCli from "./index"; + +const cli = new DandoriTrelloCli(); +void cli.run(); diff --git a/packages/cli/src/trello/index.ts b/packages/cli/src/trello/index.ts new file mode 100644 index 0000000..f9c8b6c --- /dev/null +++ b/packages/cli/src/trello/index.ts @@ -0,0 +1,26 @@ +import { generateDandoriTrelloCards } from "@dandori/ui"; +import DandoriCoreCli from "../core"; + +export default class DandoriTrelloCli extends DandoriCoreCli { + override async run(): Promise { + const tasks = await this.generateDandoriTasks(); + const opts = this.program.opts(); + await generateDandoriTrelloCards(tasks, { + boardId: opts.boardId, + trelloListPropertiesMap: { + "status.todo": opts.statusTodo, + "status.doing": opts.statusDoing, + "status.done": opts.statusDone, + }, + }); + } + + protected override buildCommand() { + return super + .buildCommand() + .option("-b, --board-id ", "trello board id") + .option("--status-todo ", "trello list status todo name") + .option("--status-doing ", "trello list status doing name") + .option("--status-done ", "trello list status done name"); + } +} diff --git a/packages/ui/README.md b/packages/ui/README.md index a45dc66..0a4e834 100644 --- a/packages/ui/README.md +++ b/packages/ui/README.md @@ -39,6 +39,7 @@ await generateDandoriMiroCards(tasks, { * [Miro](https://miro.com/) * [Notion](https://www.notion.so/) +* [Trello](https://trello.com/) ## API @@ -116,7 +117,7 @@ The tasks which are generated by `generateDandoriTasks` of `@dandori/core`. interface GenerateDandoriNotionPagesOptions { databaseId: string; databasePropertiesMap?: DatabasePropertiesMap; - apuKey?: string; + apiKey?: string; } ``` @@ -181,4 +182,94 @@ For more details about database properties, please see [Notion API](https://deve * apiKey -The api key of miro. You can also set `NOTION_API_KEY` environment variable instead of this option. \ No newline at end of file +The api key of notion. You can also set `NOTION_API_KEY` environment variable instead of this option. + +### generateDandoriTrelloCards + +```ts +async function generateDandoriTrelloCards( + tasks: DandoriTask[], + options?: GenerateDandoriTrelloCardsOptions, +): Promise {} +``` + +`generateDandoriTrelloCards` creates trello cards from `generateDandoriTasks` result. + +trello output example + +#### Parameters + +##### tasks + +The tasks which are generated by `generateDandoriTasks` of `@dandori/core`. + +##### options + +```ts +interface GenerateDandoriTrelloCardsOptions { + boardId: string; + trelloListPropertiesMap?: TrelloListPropertiesMap; + apiKey?: string; + apiToken?: string; +} +``` + +* boardId + +The existing board id of trello. + +You can get the board id from your trello url. + +For example, if the url is `https://trello.com/b/ABCDE/boardTitle`, the board id is `ABCDE`. + +* trelloListPropertiesMap + +The map which key is defined by dandori and value is your trello list properties. + +You can set the key like belows. + +```ts +const trelloListPropertiesMap = { + "status.todo": "", + "status.doing": "", + "status.done": "", +}; +``` + +```ts +import generateDandoriTasks from '@dandori/core'; +import { generateDandoriTrelloCards } from "@dandori/ui"; + +const text = ` +Today's My Tasks +* Send Email to John +* Send Email to Mary +* Report to Boss after sending emails +`; + +const trelloListPropertiesMap = { + "status.todo": "Todo", + "status.doing": "Doing", + "status.done": "Done", +}; + +const tasks = await generateDandoriTasks(text); +await generateDandoriNotionPages(tasks, { + boardId: 'ABCDE', + trelloListPropertiesMap, +}); +``` + +This is an example. In this case, the output is like belows. + +notion output example + +For more details about database properties, please see [Notion API](https://developers.notion.com/reference/page#page-property-value). + +* apiKey + +The api key of trello. You can also set `TRELLO_API_KEY` environment variable instead of this option. + +* apiToken + +The api token of trello which you can get through OAuth. You can also set `TRELLO_API_TOKEN` environment variable instead of this option. \ No newline at end of file diff --git a/packages/ui/global.d.ts b/packages/ui/global.d.ts index c5c4d67..0b90496 100644 --- a/packages/ui/global.d.ts +++ b/packages/ui/global.d.ts @@ -4,6 +4,8 @@ declare module "process" { interface ProcessEnv { MIRO_API_KEY: string; NOTION_API_KEY: string; + TRELLO_API_KEY: string; + TRELLO_API_TOKEN: string; } } } diff --git a/packages/ui/package.json b/packages/ui/package.json index 45fdb4d..abf25d9 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -35,6 +35,7 @@ "init": "tsx init.ts", "dev:miro": "tsx scripts/generateDandoriMiroCards.ts", "dev:notion": "tsx scripts/generateDandoriNotionPages.ts", + "dev:trello": "tsx scripts/generateDandoriTrelloCards.ts", "test": "vitest run" }, "keywords": [], diff --git a/packages/ui/src/__tests__/trello.test.ts b/packages/ui/src/__tests__/trello.test.ts new file mode 100644 index 0000000..5d7edb9 --- /dev/null +++ b/packages/ui/src/__tests__/trello.test.ts @@ -0,0 +1,136 @@ +import { describe, beforeEach, afterEach, vi, expect, it, Mock } from "vitest"; +import { DandoriTask } from "@dandori/core"; +import { generateDandoriTrelloCards } from "../index"; +import { runPromisesSequentially } from "@dandori/libs"; +import { TrelloClient } from "../trello/client"; + +vi.mock("../trello/client", () => { + const TrelloClient = vi.fn(); + TrelloClient.prototype = { + getLists: vi.fn(() => [ + { + id: "1", + name: "TODO", + }, + { + id: "2", + name: "DOING", + }, + { + id: "3", + name: "done", + }, + ]), + createCard: vi.fn(), + }; + return { TrelloClient }; +}); + +vi.mock("@dandori/libs", () => { + return { + runPromisesSequentially: vi.fn((runPromises, _runningLogPrefix) => + Promise.all(runPromises.map((runPromise: () => any) => runPromise())), + ), + checkApiKey: vi.fn(), + }; +}); + +const mockRunPromisesSequentially = runPromisesSequentially as Mock; + +describe("generateDandoriTrelloCards", () => { + let client: TrelloClient; + + const todoWithStatus = "TODO"; + const noStatus = "DOING"; + const doneWithStatusButInvalidTrelloListName = "DONE"; + + const tasks: DandoriTask[] = [ + { + id: "1", + name: todoWithStatus, + status: "todo", + fromTaskIdList: [], + }, + { + id: "2", + name: noStatus, + fromTaskIdList: ["1"], + }, + { + id: "3", + name: doneWithStatusButInvalidTrelloListName, + status: "done", + fromTaskIdList: ["2"], + }, + ]; + const boardId = "boardId"; + + const findPagePropertiesMockParam = (taskName: string) => { + const params = (client.createCard as Mock).mock.calls.flat(); + return params.find(({ name }) => name === taskName); + }; + + beforeEach(() => { + client = new TrelloClient("key", "token"); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + describe("with status.todo mapping", () => { + beforeEach(async () => { + await generateDandoriTrelloCards(tasks, { + boardId, + trelloListPropertiesMap: { + "status.todo": todoWithStatus, + }, + }); + }); + + it("runPromisesSequentially called with creating pages log", () => { + expect(mockRunPromisesSequentially.mock.calls[0][1]).toBe( + "Creating Trello Cards", + ); + }); + + it("called valid arguments", () => { + expect(findPagePropertiesMockParam(todoWithStatus)).toMatchObject({ + name: todoWithStatus, + listId: "1", + }); + }); + }); + + describe("with status.doing mapping", () => { + beforeEach(async () => { + await generateDandoriTrelloCards(tasks, { + boardId, + trelloListPropertiesMap: { + "status.doing": noStatus, + }, + }); + }); + + it("no called valid arguments", () => { + expect(findPagePropertiesMockParam(noStatus)).toBeUndefined(); + }); + }); + + describe("with status.done mapping", () => { + beforeEach(async () => { + await generateDandoriTrelloCards(tasks, { + boardId, + trelloListPropertiesMap: { + "status.done": doneWithStatusButInvalidTrelloListName, + }, + }); + }); + + it("no called valid arguments", () => { + expect( + findPagePropertiesMockParam(doneWithStatusButInvalidTrelloListName), + ).toBeUndefined(); + }); + }); +}); diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 97c12c1..9819ae0 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -1,2 +1,3 @@ export * from "./miro"; export * from "./notion"; +export * from "./trello"; diff --git a/packages/ui/src/trello/client.ts b/packages/ui/src/trello/client.ts new file mode 100644 index 0000000..ff2454c --- /dev/null +++ b/packages/ui/src/trello/client.ts @@ -0,0 +1,41 @@ +type ListResponse = { + id: string; + name: string; + closed: boolean; + pos: number; + softLimit: string; + idBoard: string; + subscribed: boolean; + limits: { + attachments: { + perBoard: any; + }; + }; +}; + +export class TrelloClient { + private readonly apiKey: string; + private readonly apiToken: string; + + constructor(apiKey: string, apiToken: string) { + this.apiKey = apiKey; + this.apiToken = apiToken; + } + + async getLists(boardId: string): Promise { + const res = await fetch( + `https://api.trello.com/1/boards/${boardId}/lists?key=${this.apiKey}&token=${this.apiToken}`, + ); + return res.json(); + } + + async createCard(param: { listId: string; name: string }): Promise { + const res = await fetch( + `https://api.trello.com/1/cards?key=${this.apiKey}&token=${this.apiToken}&idList=${param.listId}&name=${param.name}`, + { + method: "POST", + }, + ); + return res.json(); + } +} diff --git a/packages/ui/src/trello/index.ts b/packages/ui/src/trello/index.ts new file mode 100644 index 0000000..e398b32 --- /dev/null +++ b/packages/ui/src/trello/index.ts @@ -0,0 +1 @@ +export * from "./trello"; diff --git a/packages/ui/src/trello/trello.ts b/packages/ui/src/trello/trello.ts new file mode 100644 index 0000000..0995751 --- /dev/null +++ b/packages/ui/src/trello/trello.ts @@ -0,0 +1,77 @@ +import { DandoriTask, DandoriTaskStatus } from "@dandori/core"; +import { checkApiKey, runPromisesSequentially } from "@dandori/libs"; +import { TrelloClient } from "./client"; + +type TrelloListPropertiesMap = + | { + "status.todo": string; + "status.doing"?: string; + "status.done"?: string; + } + | { + "status.todo"?: string; + "status.doing": string; + "status.done"?: string; + } + | { + "status.todo"?: string; + "status.doing"?: string; + "status.done": string; + }; + +export type GenerateDandoriTrelloCardsOptions = { + boardId: string; + apiKey?: string; + apiToken?: string; + trelloListPropertiesMap: TrelloListPropertiesMap; +}; + +const targetName = "trello"; + +export async function generateDandoriTrelloCards( + tasks: DandoriTask[], + options: GenerateDandoriTrelloCardsOptions, +): Promise { + const key = checkApiKey( + `${targetName} api key`, + process.env.TRELLO_API_KEY, + options.apiKey, + ); + const token = checkApiKey( + `${targetName} api token`, + process.env.TRELLO_API_TOKEN, + options.apiToken, + ); + const trello = new TrelloClient(key, token); + const lists = await trello.getLists(options.boardId); + const { trelloListPropertiesMap } = options; + const statusNames = [ + trelloListPropertiesMap["status.todo"], + trelloListPropertiesMap["status.doing"], + trelloListPropertiesMap["status.done"], + ]; + const listIds = statusNames.map((statusName) => { + const list = lists.find((list) => list.name === statusName); + if (list) { + return list.id; + } + }); + const dandoriTaskListIdMap: Record = { + todo: listIds[0], + doing: listIds[1], + done: listIds[2], + }; + const createCards: (() => Promise)[] = []; + tasks.forEach((task) => { + const taskStatus = task.status; + if (!taskStatus) { + return; + } + const listId = dandoriTaskListIdMap[taskStatus]; + if (!listId) { + return; + } + createCards.push(() => trello.createCard({ listId, name: task.name })); + }); + await runPromisesSequentially(createCards, "Creating Trello Cards"); +} diff --git a/packages/ui/templates/generateDandoriTrelloCards.ts b/packages/ui/templates/generateDandoriTrelloCards.ts new file mode 100644 index 0000000..7116c82 --- /dev/null +++ b/packages/ui/templates/generateDandoriTrelloCards.ts @@ -0,0 +1,19 @@ +import { loadEnvFile } from "@dandori/libs"; +import { tasks } from "./mock"; +import { generateDandoriTrelloCards } from "../src/trello/trello"; + +// set environment variables like access token +loadEnvFile(); + +// set trello settings +const boardId = ""; +const trelloListPropertiesMap = { + "status.todo": "", + "status.doing": "", + "status.done": "", +}; + +await generateDandoriTrelloCards(tasks, { + boardId, + trelloListPropertiesMap, +});