Skip to content

Commit

Permalink
add trello to @dandori/ui
Browse files Browse the repository at this point in the history
  • Loading branch information
hiroki0525 committed Jan 14, 2024
1 parent 16a4192 commit a230b82
Show file tree
Hide file tree
Showing 8 changed files with 276 additions and 0 deletions.
2 changes: 2 additions & 0 deletions packages/ui/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}
Expand Down
1 change: 1 addition & 0 deletions packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [],
Expand Down
136 changes: 136 additions & 0 deletions packages/ui/src/__tests__/trello.test.ts
Original file line number Diff line number Diff line change
@@ -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();
});
});
});
1 change: 1 addition & 0 deletions packages/ui/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./miro";
export * from "./notion";
export * from "./trello";
39 changes: 39 additions & 0 deletions packages/ui/src/trello/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
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;
}

getLists(boardId: string): Promise<ListResponse[]> {
return fetch(
`https://api.trello.com/1/boards/${boardId}/lists?key=${this.apiKey}&token=${this.apiToken}`,
).then((res) => res.json());
}

createCard(param: { listId: string; name: string }): Promise<void> {
return fetch(
`https://api.trello.com/1/cards?key=${this.apiKey}&token=${this.apiToken}&idList=${param.listId}&name=${param.name}`,
{
method: "POST",
},
).then((res) => res.json());
}
}
1 change: 1 addition & 0 deletions packages/ui/src/trello/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./trello";
77 changes: 77 additions & 0 deletions packages/ui/src/trello/trello.ts
Original file line number Diff line number Diff line change
@@ -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<void> {
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<DandoriTaskStatus, string | undefined> = {
todo: listIds[0],
doing: listIds[1],
done: listIds[2],
};
const createCards: (() => Promise<void>)[] = [];
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");
}
19 changes: 19 additions & 0 deletions packages/ui/templates/generateDandoriTrelloCards.ts
Original file line number Diff line number Diff line change
@@ -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,
});

0 comments on commit a230b82

Please sign in to comment.