From 36eac0c9b26b3995c518771038fcdee1418c612c Mon Sep 17 00:00:00 2001 From: Olivier Sambourg Date: Thu, 11 Jul 2024 16:11:35 +0200 Subject: [PATCH] feat(todoist): add support for natural language due dates --- .../pieces/community/todoist/package.json | 4 +- .../src/lib/actions/create-task-action.ts | 122 +++---- .../src/lib/actions/update-task.action.ts | 106 +++--- .../src/lib/common/client/rest-client.ts | 305 ++++++++++-------- .../todoist/src/lib/common/models.ts | 7 +- 5 files changed, 292 insertions(+), 252 deletions(-) diff --git a/packages/pieces/community/todoist/package.json b/packages/pieces/community/todoist/package.json index d3dbd499dc..2d3333a69c 100644 --- a/packages/pieces/community/todoist/package.json +++ b/packages/pieces/community/todoist/package.json @@ -1,4 +1,4 @@ { "name": "@activepieces/piece-todoist", - "version": "0.3.8" -} \ No newline at end of file + "version": "0.3.9" +} diff --git a/packages/pieces/community/todoist/src/lib/actions/create-task-action.ts b/packages/pieces/community/todoist/src/lib/actions/create-task-action.ts index 2f0d7411a9..45214d3165 100644 --- a/packages/pieces/community/todoist/src/lib/actions/create-task-action.ts +++ b/packages/pieces/community/todoist/src/lib/actions/create-task-action.ts @@ -1,65 +1,77 @@ import { createAction, Property } from '@activepieces/pieces-framework'; import { assertNotNullOrUndefined } from '@activepieces/shared'; import { todoistRestClient } from '../common/client/rest-client'; -import { todoistProjectIdDropdown, todoistSectionIdDropdown } from '../common/props'; +import { + todoistProjectIdDropdown, + todoistSectionIdDropdown, +} from '../common/props'; import { TodoistCreateTaskRequest } from '../common/models'; import { todoistAuth } from '../..'; export const todoistCreateTaskAction = createAction({ - auth: todoistAuth, - name: 'create_task', - displayName: 'Create Task', - description: 'Create task', - props: { - project_id: todoistProjectIdDropdown( - "Task project ID. If not set, task is put to user's Inbox.", - ), - content: Property.LongText({ - displayName: 'content', - description: "The task's content. It may contain some markdown-formatted text and hyperlinks", - required: true, - }), - description: Property.LongText({ - displayName: 'Description', - description: - 'A description for the task. This value may contain some markdown-formatted text and hyperlinks.', - required: false, - }), - labels: Property.Array({ - displayName: 'Labels', - required: false, - description: - "The task's labels (a list of names that may represent either personal or shared labels)", - }), - priority: Property.Number({ - displayName: 'Priority', - description: 'Task priority from 1 (normal) to 4 (urgent)', - required: false, - }), - due_date: Property.ShortText({ - displayName: 'Due date', - description: "Specific date in YYYY-MM-DD format relative to user's timezone", - required: false, - }), - section_id: todoistSectionIdDropdown, - }, + auth: todoistAuth, + name: 'create_task', + displayName: 'Create Task', + description: 'Create task', + props: { + project_id: todoistProjectIdDropdown( + "Task project ID. If not set, task is put to user's Inbox." + ), + content: Property.LongText({ + displayName: 'content', + description: + "The task's content. It may contain some markdown-formatted text and hyperlinks", + required: true, + }), + description: Property.LongText({ + displayName: 'Description', + description: + 'A description for the task. This value may contain some markdown-formatted text and hyperlinks.', + required: false, + }), + labels: Property.Array({ + displayName: 'Labels', + required: false, + description: + "The task's labels (a list of names that may represent either personal or shared labels)", + }), + priority: Property.Number({ + displayName: 'Priority', + description: 'Task priority from 1 (normal) to 4 (urgent)', + required: false, + }), + due_date: Property.ShortText({ + displayName: 'Due date', + description: + "Can be either a specific date in YYYY-MM-DD format relative to user's timezone, a specific date and time in RFC3339 format, or a human defined date (e.g. 'next Monday') using local time", + required: false, + }), + section_id: todoistSectionIdDropdown, + }, - async run({ auth, propsValue }) { - const token = auth.access_token; - const { project_id, content, description, labels, priority, due_date, section_id } = - propsValue as TodoistCreateTaskRequest; + async run({ auth, propsValue }) { + const token = auth.access_token; + const { + project_id, + content, + description, + labels, + priority, + due_date, + section_id, + } = propsValue as TodoistCreateTaskRequest; - assertNotNullOrUndefined(token, 'token'); - assertNotNullOrUndefined(content, 'content'); - return await todoistRestClient.tasks.create({ - token, - project_id, - content, - description, - labels, - priority, - due_date, - section_id, - }); - }, + assertNotNullOrUndefined(token, 'token'); + assertNotNullOrUndefined(content, 'content'); + return await todoistRestClient.tasks.create({ + token, + project_id, + content, + description, + labels, + priority, + due_date, + section_id, + }); + }, }); diff --git a/packages/pieces/community/todoist/src/lib/actions/update-task.action.ts b/packages/pieces/community/todoist/src/lib/actions/update-task.action.ts index 5dced83b92..aede34a987 100644 --- a/packages/pieces/community/todoist/src/lib/actions/update-task.action.ts +++ b/packages/pieces/community/todoist/src/lib/actions/update-task.action.ts @@ -4,58 +4,60 @@ import { todoistRestClient } from '../common/client/rest-client'; import { todoistAuth } from '../..'; export const todoistUpdateTaskAction = createAction({ - auth: todoistAuth, - name: 'update_task', - displayName: 'Update Task', - description: 'Updates an existing task.', - props: { - task_id: Property.ShortText({ - displayName: 'Task ID', - required: true, - }), - content: Property.LongText({ - displayName: 'content', - description: "The task's content. It may contain some markdown-formatted text and hyperlinks", - required: false, - }), - description: Property.LongText({ - displayName: 'Description', - description: - 'A description for the task. This value may contain some markdown-formatted text and hyperlinks.', - required: false, - }), - labels: Property.Array({ - displayName: 'Labels', - required: false, - description: - "The task's labels (a list of names that may represent either personal or shared labels)", - }), - priority: Property.Number({ - displayName: 'Priority', - description: 'Task priority from 1 (normal) to 4 (urgent)', - required: false, - }), - due_date: Property.ShortText({ - displayName: 'Due date', - description: "Specific date in YYYY-MM-DD format relative to user's timezone", - required: false, - }), - }, + auth: todoistAuth, + name: 'update_task', + displayName: 'Update Task', + description: 'Updates an existing task.', + props: { + task_id: Property.ShortText({ + displayName: 'Task ID', + required: true, + }), + content: Property.LongText({ + displayName: 'content', + description: + "The task's content. It may contain some markdown-formatted text and hyperlinks", + required: false, + }), + description: Property.LongText({ + displayName: 'Description', + description: + 'A description for the task. This value may contain some markdown-formatted text and hyperlinks.', + required: false, + }), + labels: Property.Array({ + displayName: 'Labels', + required: false, + description: + "The task's labels (a list of names that may represent either personal or shared labels)", + }), + priority: Property.Number({ + displayName: 'Priority', + description: 'Task priority from 1 (normal) to 4 (urgent)', + required: false, + }), + due_date: Property.ShortText({ + displayName: 'Due date', + description: + "Can be either a specific date in YYYY-MM-DD format relative to user's timezone, a specific date and time in RFC3339 format, or a human defined date (e.g. 'next Monday') using local time", + required: false, + }), + }, - async run({ auth, propsValue }) { - const token = auth.access_token; - const { task_id, content, description, priority, due_date } = propsValue; - const labels = propsValue.labels as string[]; + async run({ auth, propsValue }) { + const token = auth.access_token; + const { task_id, content, description, priority, due_date } = propsValue; + const labels = propsValue.labels as string[]; - assertNotNullOrUndefined(token, 'token'); - return await todoistRestClient.tasks.update({ - token, - task_id, - content, - description, - labels, - priority, - due_date, - }); - }, + assertNotNullOrUndefined(token, 'token'); + return await todoistRestClient.tasks.update({ + token, + task_id, + content, + description, + labels, + priority, + due_date, + }); + }, }); diff --git a/packages/pieces/community/todoist/src/lib/common/client/rest-client.ts b/packages/pieces/community/todoist/src/lib/common/client/rest-client.ts index fdd6d4b411..3390b738f4 100644 --- a/packages/pieces/community/todoist/src/lib/common/client/rest-client.ts +++ b/packages/pieces/community/todoist/src/lib/common/client/rest-client.ts @@ -1,166 +1,189 @@ import { - HttpRequest, - HttpMethod, - AuthenticationType, - httpClient, + HttpRequest, + HttpMethod, + AuthenticationType, + httpClient, } from '@activepieces/pieces-common'; import { isNotUndefined, pickBy } from '@activepieces/shared'; import { - TodoistCreateTaskRequest, - TodoistProject, - TodoistSection, - TodoistTask, - TodoistUpdateTaskRequest, + TodoistCreateTaskRequest, + TodoistProject, + TodoistSection, + TodoistTask, + TodoistUpdateTaskRequest, } from '../models'; const API = 'https://api.todoist.com/rest/v2'; export const todoistRestClient = { - projects: { - async list({ token }: ProjectsListParams): Promise { - const request: HttpRequest = { - method: HttpMethod.GET, - url: `${API}/projects`, - authentication: { - type: AuthenticationType.BEARER_TOKEN, - token, - }, - }; - - const response = await httpClient.sendRequest(request); - return response.body; - }, - }, - - sections: { - async list(params: SectionsListPrams): Promise { - const qs: Record = {}; - if (params.project_id) qs['project_id'] = params.project_id; - - const request: HttpRequest = { - method: HttpMethod.GET, - url: `${API}/sections`, - authentication: { - type: AuthenticationType.BEARER_TOKEN, - token: params.token, - }, - queryParams: qs, - }; - - const response = await httpClient.sendRequest(request); - return response.body; - }, - }, - - tasks: { - async create({ - token, - project_id, - content, - description, - labels, - priority, - due_date, - section_id, - }: TasksCreateParams): Promise { - const request: HttpRequest = { - method: HttpMethod.POST, - url: `${API}/tasks`, - authentication: { - type: AuthenticationType.BEARER_TOKEN, - token, - }, - body: { - content, - project_id, - description, - labels, - priority, - due_date, - section_id, - }, - }; - - const response = await httpClient.sendRequest(request); - return response.body; - }, - - async update(params: TasksUpdateParams): Promise { - const request: HttpRequest = { - method: HttpMethod.POST, - url: `${API}/tasks/${params.task_id}`, - authentication: { - type: AuthenticationType.BEARER_TOKEN, - token: params.token, - }, - body: { - content: params.content, - description: params.description, - labels: params.labels?.length === 0 ? undefined : params.labels, - priority: params.priority, - due_date: params.due_date, - }, - }; - - const response = await httpClient.sendRequest(request); - return response.body; - }, - - async list({ token, project_id, filter }: TasksListParams): Promise { - const queryParams = { - filter, - project_id, - }; - - const request: HttpRequest = { - method: HttpMethod.GET, - url: `${API}/tasks`, - authentication: { - type: AuthenticationType.BEARER_TOKEN, - token, - }, - queryParams: pickBy(queryParams, isNotUndefined), - }; - - const response = await httpClient.sendRequest(request); - return response.body; - }, - - async close({ token, task_id }: { token: string; task_id: string }) { - const request: HttpRequest = { - method: HttpMethod.POST, - url: `${API}/tasks/${task_id}/close`, - authentication: { - type: AuthenticationType.BEARER_TOKEN, - token, - }, - }; - - const response = await httpClient.sendRequest(request); - return response.body; - }, - }, + projects: { + async list({ token }: ProjectsListParams): Promise { + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${API}/projects`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token, + }, + }; + + const response = await httpClient.sendRequest(request); + return response.body; + }, + }, + + sections: { + async list(params: SectionsListPrams): Promise { + const qs: Record = {}; + if (params.project_id) qs['project_id'] = params.project_id; + + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${API}/sections`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: params.token, + }, + queryParams: qs, + }; + + const response = await httpClient.sendRequest(request); + return response.body; + }, + }, + + tasks: { + async create({ + token, + project_id, + content, + description, + labels, + priority, + due_date, + section_id, + }: TasksCreateParams): Promise { + const body: TodoistCreateTaskRequest = { + content, + project_id, + description, + labels, + priority, + section_id, + ...dueDateParams(due_date), + }; + + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${API}/tasks`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token, + }, + body, + }; + + const response = await httpClient.sendRequest(request); + return response.body; + }, + + async update(params: TasksUpdateParams): Promise { + const body: TodoistUpdateTaskRequest = { + content: params.content, + description: params.description, + labels: params.labels?.length === 0 ? undefined : params.labels, + priority: params.priority, + ...dueDateParams(params.due_date), + }; + + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${API}/tasks/${params.task_id}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: params.token, + }, + body, + }; + + const response = await httpClient.sendRequest(request); + return response.body; + }, + + async list({ + token, + project_id, + filter, + }: TasksListParams): Promise { + const queryParams = { + filter, + project_id, + }; + + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${API}/tasks`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token, + }, + queryParams: pickBy(queryParams, isNotUndefined), + }; + + const response = await httpClient.sendRequest(request); + return response.body; + }, + + async close({ token, task_id }: { token: string; task_id: string }) { + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${API}/tasks/${task_id}/close`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token, + }, + }; + + const response = await httpClient.sendRequest(request); + return response.body; + }, + }, }; type ProjectsListParams = { - token: string; + token: string; }; type SectionsListPrams = { - token: string; - project_id?: string; + token: string; + project_id?: string; }; type TasksCreateParams = { - token: string; + token: string; } & TodoistCreateTaskRequest; type TasksUpdateParams = { - token: string; + token: string; + task_id: string; } & TodoistUpdateTaskRequest; type TasksListParams = { - token: string; - project_id?: string | undefined; - filter?: string | undefined; + token: string; + project_id?: string | undefined; + filter?: string | undefined; +}; + +const dueDateParams = (dueDate?: string) => { + if (dueDate) { + const parsedDate = Date.parse(dueDate); + if (isNaN(parsedDate)) { + return { due_string: dueDate }; + } else if (/\d{4}-\d{2}-\d{2}/.test(dueDate)) { + return { due_date: dueDate }; + } else { + return { due_datetime: new Date(parsedDate).toISOString() }; + } + } + return {}; }; diff --git a/packages/pieces/community/todoist/src/lib/common/models.ts b/packages/pieces/community/todoist/src/lib/common/models.ts index c428ce1773..d4b7f6acaa 100644 --- a/packages/pieces/community/todoist/src/lib/common/models.ts +++ b/packages/pieces/community/todoist/src/lib/common/models.ts @@ -17,16 +17,19 @@ export type TodoistCreateTaskRequest = { labels?: Array | undefined; priority?: number | undefined; due_date?: string | undefined; + due_string?: string | undefined; + due_datetime?: string | undefined; section_id?: string | undefined; }; export type TodoistUpdateTaskRequest = { - task_id: string; content?: string; description?: string; labels?: Array; priority?: number; - due_date?: string; + due_date?: string | undefined; + due_string?: string | undefined; + due_datetime?: string | undefined; }; type TodoistTaskDue = {