Skip to content

Commit

Permalink
add notion (#45)
Browse files Browse the repository at this point in the history
  • Loading branch information
hiroki0525 authored Nov 8, 2023
1 parent 52ae95f commit 0180a38
Show file tree
Hide file tree
Showing 10 changed files with 234 additions and 65 deletions.
6 changes: 6 additions & 0 deletions .changeset/serious-bobcats-compare.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@dandori/core": patch
"@dandori/ui": patch
---

add notion
11 changes: 10 additions & 1 deletion packages/core/src/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import generateDandoriTasks, {
DandoriTaskProperty,
DandoriTaskRequiredProperty,
DandoriTaskOptionalAllProperty,
FunctionCallValue,
} from "../index";
import { describe, beforeEach, afterEach, it, vi, expect, Mock } from "vitest";
import OpenAI from "openai";
Expand Down Expand Up @@ -85,7 +86,10 @@ describe("generateDandoriTasks", () => {
const excludePropertyPrompt =
"If not provided, this property shouldn't be included.";
const generateIdPrompt = "If not provided, return generated unique ID.";
const functionCallTaskProperties = {
const functionCallTaskProperties: Record<
DandoriTaskProperty,
FunctionCallValue
> = {
id: {
type: "string",
description: `The task ID. ${generateIdPrompt}`,
Expand Down Expand Up @@ -123,6 +127,11 @@ describe("generateDandoriTasks", () => {
type: "string",
},
},
status: {
type: "string",
description:
'The task status which value is only allowed "todo", "doing" and "done". If not provided, the default value is "todo"',
},
} as const;
const requiredProperties: DandoriTaskRequiredProperty[] = [
"id",
Expand Down
10 changes: 9 additions & 1 deletion packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ export type ChatGPTFunctionCallModel = "gpt-3.5-turbo-0613" | "gpt-4-0613";
const defaultChatGPTFunctionCallModel: ChatGPTFunctionCallModel =
"gpt-3.5-turbo-0613";

export type DandoriTaskStatus = "todo" | "doing" | "done";

export type DandoriTask = {
id: string;
name: string;
Expand All @@ -16,6 +18,7 @@ export type DandoriTask = {
id: string;
name: string;
};
status?: DandoriTaskStatus;
fromTaskIdList: string[];
};

Expand Down Expand Up @@ -48,7 +51,7 @@ export type GenerateDandoriTasksOptions = {
optionalTaskProps?: OptionalTaskPropsOption;
};

type FunctionCallValue = {
export type FunctionCallValue = {
type: "string" | "array" | "object";
description?: string;
items?: FunctionCallValue;
Expand Down Expand Up @@ -106,6 +109,11 @@ const optionalFunctionCallTaskProperties: Record<
},
},
},
status: {
type: "string",
description:
'The task status which value is only allowed "todo", "doing" and "done". If not provided, the default value is "todo"',
},
};

const requiredProperties: readonly DandoriTaskRequiredProperty[] = Object.keys(
Expand Down
1 change: 1 addition & 0 deletions packages/ui/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ declare module "process" {
namespace NodeJS {
interface ProcessEnv {
MIRO_ACCESS_TOKEN: string;
NOTION_TOKEN: string;
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,16 @@
"type": "module",
"scripts": {
"build": "tsup --config ../../tsup.config.ts",
"dev": "tsx ./scripts/generateDandoriMiroCards.ts",
"dev:miro": "tsx ./scripts/generateDandoriMiroCards.ts",
"dev:notion": "tsx ./scripts/generateDandoriNotionPages.ts",
"test": "vitest run"
},
"keywords": [],
"author": "Hiroki Miyaji",
"license": "MIT",
"dependencies": {
"@mirohq/miro-api": "^2.0.2",
"@notionhq/client": "^2.2.13",
"flat-to-nested": "^1.1.1",
"tree-model": "^1.0.7"
},
Expand Down
64 changes: 2 additions & 62 deletions packages/ui/scripts/generateDandoriMiroCards.ts
Original file line number Diff line number Diff line change
@@ -1,72 +1,12 @@
import { DandoriTask } from "@dandori/core";
import { loadEnvFile } from "@dandori/libs";
import { generateDandoriMiroCards } from "@dandori/ui";
import { tasks } from "./mock";

// set environment variables like miro access token
// set environment variables like access token
loadEnvFile();

const boardId = "Your board ID";

// customize tasks as you like
const tasks: DandoriTask[] = [
{
id: "1",
name: "task1",
deadline: "2021-01-01",
description: "task1-description",
fromTaskIdList: [],
},
{
id: "2",
name: "task2",
deadline: "2021-01-02",
description: "task2-description",
fromTaskIdList: ["1"],
},
{
id: "3",
name: "task3",
deadline: "2021-01-03",
description: "task3-description",
fromTaskIdList: ["1"],
},
{
id: "4",
name: "task4",
deadline: "2021-01-04",
description: "task4-description",
fromTaskIdList: ["2", "3"],
},
{
id: "5",
name: "task5",
deadline: "2021-01-05",
description: "task5-description",
fromTaskIdList: [],
},
{
id: "6",
name: "task6",
deadline: "2021-01-06",
description: "task6-description",
fromTaskIdList: ["5"],
},
{
id: "7",
name: "task7",
deadline: "2021-01-07",
description: "task7-description",
fromTaskIdList: ["5"],
},
{
id: "8",
name: "task8",
deadline: "2021-01-08",
description: "task8-description",
fromTaskIdList: ["6", "7"],
},
];

await generateDandoriMiroCards(tasks, {
boardId,
});
22 changes: 22 additions & 0 deletions packages/ui/scripts/generateDandoriNotionPages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { loadEnvFile } from "@dandori/libs";
import { tasks } from "./mock";
import generateDandoriNotionPages from "../src/notion";

// set environment variables like access token
loadEnvFile();

// set notion settings
const databaseId = "";
const databasePropertiesMap = {
deadline: "",
description: "",
status: "",
"status.todo": "",
"status.doing": "",
"status.done": "",
};

await generateDandoriNotionPages(tasks, {
databaseId,
databasePropertiesMap,
});
68 changes: 68 additions & 0 deletions packages/ui/scripts/mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { DandoriTask } from "@dandori/core/src";

// customize tasks as you like
export const tasks: DandoriTask[] = [
{
id: "1",
name: "task1",
deadline: "2021-01-01",
description: "task1-description",
fromTaskIdList: [],
status: "done",
},
{
id: "2",
name: "task2",
deadline: "2021-01-02",
description: "task2-description",
fromTaskIdList: ["1"],
status: "doing",
},
{
id: "3",
name: "task3",
deadline: "2021-01-03",
description: "task3-description",
fromTaskIdList: ["1"],
status: "doing",
},
{
id: "4",
name: "task4",
deadline: "2021-01-04",
description: "task4-description",
fromTaskIdList: ["2", "3"],
status: "todo",
},
{
id: "5",
name: "task5",
deadline: "2021-01-05",
description: "task5-description",
fromTaskIdList: [],
},
{
id: "6",
name: "task6",
deadline: "2021-01-06",
description: "task6-description",
fromTaskIdList: ["5"],
status: "doing",
},
{
id: "7",
name: "task7",
deadline: "2021-01-07",
description: "task7-description",
fromTaskIdList: ["5"],
status: "doing",
},
{
id: "8",
name: "task8",
deadline: "2021-01-08",
description: "task8-description",
fromTaskIdList: ["6", "7"],
status: "done",
},
];
100 changes: 100 additions & 0 deletions packages/ui/src/notion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import {
DandoriTask,
DandoriTaskOptionalProperty,
DandoriTaskRequiredProperty,
DandoriTaskStatus,
} from "@dandori/core";
import { Client } from "@notionhq/client";
import { runPromisesSequentially } from "@dandori/libs";
import { CreatePageParameters } from "@notionhq/client/build/src/api-endpoints";

type SupportNotionTaskOptionalProperty =
| Exclude<DandoriTaskOptionalProperty, "assignee">
| `${Extract<DandoriTaskOptionalProperty, "status">}.${DandoriTaskStatus}`;

type DatabasePropertiesMap = {
[key in SupportNotionTaskOptionalProperty]?: string;
} & { [key in Extract<DandoriTaskRequiredProperty, "name">]?: string };

export type GenerateDandoriNotionDatabaseItemsOptions = {
databaseId: string;
databasePropertiesMap: DatabasePropertiesMap;
};

const createPageParams = (
task: DandoriTask,
options: GenerateDandoriNotionDatabaseItemsOptions,
): CreatePageParameters => {
const propsMap = options.databasePropertiesMap;
const { deadline, description, status, name } = task;
const {
deadline: deadlineProp,
description: descriptionProp,
status: statusProp,
name: nameProp = "Name",
} = propsMap;
const pageProperties: Record<string, any> = {
[nameProp]: {
title: [
{
text: {
content: name,
},
},
],
},
};
if (descriptionProp) {
pageProperties[descriptionProp] = {
rich_text: [
{
text: {
content: description,
},
},
],
};
}
if (deadline && deadlineProp) {
// start date must be before end date in Notion
const today = new Date();
if (today < new Date(deadline)) {
pageProperties[deadlineProp] = {
date: {
start: today.toISOString(),
end: deadline,
},
};
}
}
if (status && statusProp) {
const statusName = propsMap[`status.${status}`];
if (statusName) {
pageProperties[statusProp] = {
select: {
name: statusName,
},
};
}
}
return {
parent: {
database_id: options.databaseId,
},
properties: pageProperties,
};
};

export default async function generateDandoriNotionPages(
tasks: DandoriTask[],
options: GenerateDandoriNotionDatabaseItemsOptions,
): Promise<void> {
const client = new Client({
auth: process.env.NOTION_TOKEN,
});
const createPages = tasks.map((task) => () => {
const pageParams = createPageParams(task, options);
return client.pages.create(pageParams);
});
await runPromisesSequentially(createPages, "Creating Notion Pages");
}
13 changes: 13 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 0180a38

Please sign in to comment.