From f7b01593b72133626171db3ff96dc75a8bec8a56 Mon Sep 17 00:00:00 2001 From: michelle0927 Date: Mon, 1 Jul 2024 15:16:07 -0400 Subject: [PATCH 1/3] taskade init --- .../actions/create-task/create-task.mjs | 47 ++++++++ components/taskade/package.json | 2 +- .../task-due-instant/task-due-instant.mjs | 73 ++++++++++++ components/taskade/taskade.app.mjs | 107 +++++++++++++++++- 4 files changed, 224 insertions(+), 5 deletions(-) create mode 100644 components/taskade/actions/create-task/create-task.mjs create mode 100644 components/taskade/sources/task-due-instant/task-due-instant.mjs diff --git a/components/taskade/actions/create-task/create-task.mjs b/components/taskade/actions/create-task/create-task.mjs new file mode 100644 index 0000000000000..4de4e75cbb4de --- /dev/null +++ b/components/taskade/actions/create-task/create-task.mjs @@ -0,0 +1,47 @@ +import taskade from "../../taskade.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "taskade-create-task", + name: "Create Task", + description: "Creates a new task in Taskade. [See the documentation](https://developers.taskade.com/docs/api/tasks/create)", + version: "0.0.{{ts}}", + type: "action", + props: { + taskade, + taskTitle: { + type: "string", + label: "Task Title", + description: "The title of the task to be created", + required: true, + }, + workspace: { + type: "string", + label: "Workspace", + description: "The workspace where the task should be created", + required: true, + }, + dueDate: { + type: "string", + label: "Due Date", + description: "The due date for the task", + optional: true, + }, + assignees: { + type: "string[]", + label: "Assignees", + description: "The assignees for the task", + optional: true, + }, + }, + async run({ $ }) { + const task = await this.taskade.createTask( + this.taskTitle, + this.workspace, + this.dueDate, + this.assignees, + ); + $.export("$summary", `Successfully created task ${this.taskTitle}`); + return task; + }, +}; diff --git a/components/taskade/package.json b/components/taskade/package.json index 0d73684f741a9..a90a09ca789b3 100644 --- a/components/taskade/package.json +++ b/components/taskade/package.json @@ -12,4 +12,4 @@ "publishConfig": { "access": "public" } -} \ No newline at end of file +} diff --git a/components/taskade/sources/task-due-instant/task-due-instant.mjs b/components/taskade/sources/task-due-instant/task-due-instant.mjs new file mode 100644 index 0000000000000..88c7bdb976437 --- /dev/null +++ b/components/taskade/sources/task-due-instant/task-due-instant.mjs @@ -0,0 +1,73 @@ +import taskade from "../../taskade.app.mjs"; + +export default { + key: "taskade-task-due-instant", + name: "Task Due Instant", + description: "Emit new event when a task's due date is reached", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + taskade, + task_id: { + propDefinition: [ + taskade, + "task_id", + ], + }, + assignee_id: { + propDefinition: [ + taskade, + "assignee_id", + ], + optional: true, + }, + http: { + type: "$.interface.http", + customResponse: false, + }, + db: "$.service.db", + }, + hooks: { + async deploy() { + const task = await this.taskade.getTask(this.task_id); + if (!task.item[0].dueDate) { + console.log("This task does not have a due date"); + return; + } + const dueDate = new Date(task.item[0].dueDate.start.date); + if (dueDate < new Date()) { + console.log("The due date for this task has already passed"); + return; + } + this.db.set("dueDate", dueDate.getTime()); + }, + async activate() { + const intervalId = setInterval(() => { + const dueDate = new Date(this.db.get("dueDate")); + if (dueDate <= new Date()) { + clearInterval(intervalId); + this.$emit( + { + task_id: this.task_id, + }, + { + id: this.task_id, + summary: `Task ${this.task_id} due date reached`, + ts: Date.now(), + }, + ); + } + }, 60 * 1000); // check every minute + this.db.set("intervalId", intervalId); + }, + async deactivate() { + clearInterval(this.db.get("intervalId")); + }, + }, + async run(event) { + this.http.respond({ + status: 200, + }); + }, +}; diff --git a/components/taskade/taskade.app.mjs b/components/taskade/taskade.app.mjs index 44d2adaa8699f..73e999d7ee946 100644 --- a/components/taskade/taskade.app.mjs +++ b/components/taskade/taskade.app.mjs @@ -1,11 +1,110 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "taskade", - propDefinitions: {}, + propDefinitions: { + task_id: { + type: "string", + label: "Task ID", + description: "The ID of the task to track due date for", + required: true, + }, + assignee_id: { + type: "string", + label: "Assignee ID", + description: "The ID of the assignee to filter by", + optional: true, + }, + task_title: { + type: "string", + label: "Task Title", + description: "The title of the task to be created", + required: true, + }, + workspace: { + type: "string", + label: "Workspace", + description: "The workspace where the task should be created", + required: true, + }, + due_date: { + type: "string", + label: "Due Date", + description: "The due date for the task", + optional: true, + }, + assignees: { + type: "string[]", + label: "Assignees", + description: "The assignees for the task", + optional: true, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://www.taskade.com/api/v1"; + }, + async _makeRequest(opts = {}) { + const { + $ = this, + method = "GET", + path, + headers, + ...otherOpts + } = opts; + return axios($, { + ...otherOpts, + method, + url: this._baseUrl() + path, + headers: { + ...headers, + "Content-Type": "application/json", + "Authorization": `Bearer ${this.$auth.oauth_access_token}`, + }, + }); + }, + async createTask(task_title, workspace, due_date, assignees) { + const project = await this._makeRequest({ + method: "POST", + path: `/projects/${workspace}/tasks/`, + data: { + tasks: [ + { + content: task_title, + }, + ], + }, + }); + + if (due_date) { + await this._makeRequest({ + method: "PUT", + path: `/projects/${workspace}/tasks/${project.item[0].id}/date`, + data: { + start: { + date: due_date, + }, + }, + }); + } + + if (assignees) { + await this._makeRequest({ + method: "PUT", + path: `/projects/${workspace}/tasks/${project.item[0].id}/assignees`, + data: { + handles: assignees, + }, + }); + } + + return project; + }, + async getTask(task_id) { + return await this._makeRequest({ + path: `/tasks/${task_id}`, + }); }, }, }; From 8bfabe4babec8cac04a70c9137bc0da458008c8e Mon Sep 17 00:00:00 2001 From: michelle0927 Date: Tue, 2 Jul 2024 11:59:58 -0400 Subject: [PATCH 2/3] new-components --- .../actions/create-task/create-task.mjs | 72 +++++--- components/taskade/package.json | 5 +- .../new-task-created/new-task-created.mjs | 77 +++++++++ .../sources/new-task-created/test-event.mjs | 5 + .../task-due-instant/task-due-instant.mjs | 73 -------- components/taskade/taskade.app.mjs | 162 +++++++++--------- 6 files changed, 221 insertions(+), 173 deletions(-) create mode 100644 components/taskade/sources/new-task-created/new-task-created.mjs create mode 100644 components/taskade/sources/new-task-created/test-event.mjs delete mode 100644 components/taskade/sources/task-due-instant/task-due-instant.mjs diff --git a/components/taskade/actions/create-task/create-task.mjs b/components/taskade/actions/create-task/create-task.mjs index 4de4e75cbb4de..8503d903a0609 100644 --- a/components/taskade/actions/create-task/create-task.mjs +++ b/components/taskade/actions/create-task/create-task.mjs @@ -1,47 +1,75 @@ import taskade from "../../taskade.app.mjs"; -import { axios } from "@pipedream/platform"; export default { key: "taskade-create-task", name: "Create Task", description: "Creates a new task in Taskade. [See the documentation](https://developers.taskade.com/docs/api/tasks/create)", - version: "0.0.{{ts}}", + version: "0.0.1", type: "action", props: { taskade, - taskTitle: { + projectId: { + propDefinition: [ + taskade, + "projectId", + ], + }, + content: { type: "string", - label: "Task Title", - description: "The title of the task to be created", - required: true, + label: "Content", + description: "Content of the task", }, - workspace: { + contentType: { type: "string", - label: "Workspace", - description: "The workspace where the task should be created", - required: true, + label: "Content Type", + description: "The type of content", + options: [ + "text/markdown", + "text/plain", + ], }, - dueDate: { + placement: { type: "string", - label: "Due Date", - description: "The due date for the task", - optional: true, + label: "Placement", + description: "Placement of the task", + options: [ + "afterbegin", + "beforeend", + ], }, assignees: { type: "string[]", label: "Assignees", - description: "The assignees for the task", + description: "An array of user handles to assign to the task", optional: true, }, }, async run({ $ }) { - const task = await this.taskade.createTask( - this.taskTitle, - this.workspace, - this.dueDate, - this.assignees, - ); - $.export("$summary", `Successfully created task ${this.taskTitle}`); + const task = await this.taskade.createTask({ + $, + projectId: this.projectId, + data: { + tasks: [ + { + content: this.content, + contentType: this.contentType, + placement: this.placement, + }, + ], + }, + }); + const taskId = task.item[0].id; + if (this.assignees?.length) { + await this.taskade.assignTask({ + $, + projectId: this.projectId, + taskId, + data: { + handles: this.assignees, + }, + }); + } + $.export("$summary", `Successfully created task with ID ${taskId}`); return task; }, }; diff --git a/components/taskade/package.json b/components/taskade/package.json index a90a09ca789b3..853933b0fd7c4 100644 --- a/components/taskade/package.json +++ b/components/taskade/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/taskade", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Taskade Components", "main": "taskade.app.mjs", "keywords": [ @@ -11,5 +11,8 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.0" } } diff --git a/components/taskade/sources/new-task-created/new-task-created.mjs b/components/taskade/sources/new-task-created/new-task-created.mjs new file mode 100644 index 0000000000000..df0961d45fa6d --- /dev/null +++ b/components/taskade/sources/new-task-created/new-task-created.mjs @@ -0,0 +1,77 @@ +import taskade from "../../taskade.app.mjs"; +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; +import sampleEmit from "./test-event.mjs"; + +export default { + key: "taskade-new-task-created", + name: "New Task Created", + description: "Emit new event when a new task is created in Taskade", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + taskade, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + projectId: { + propDefinition: [ + taskade, + "projectId", + ], + }, + }, + hooks: { + async deploy() { + await this.processEvent(25); + }, + }, + methods: { + _getPreviousIds() { + return this.db.get("previousIds") || {}; + }, + _setPreviousIds(previousIds) { + this.db.set("previousIds", previousIds); + }, + emitEvent(task) { + const meta = this.generateMeta(task); + this.$emit(task, meta); + }, + generateMeta(task) { + return { + id: task.id, + summary: `New Task ID: ${task.id}`, + ts: Date.now(), + }; + }, + async processEvent(max) { + const tasks = []; + const items = this.taskade.paginate({ + resourceFn: this.taskade.listTasks, + args: { + projectId: this.projectId, + }, + resourceType: "items", + }); + for await (const item of items) { + tasks.push(item); + } + let previousIds = this._getPreviousIds(); + let newTasks = tasks.filter(({ id }) => !previousIds[id]); + newTasks.forEach(({ id }) => previousIds[id] = true); + this._setPreviousIds(previousIds); + newTasks = max + ? newTasks.slice(0, max) + : newTasks; + newTasks.forEach((task) => this.emitEvent(task)); + }, + }, + async run() { + await this.processEvent(); + }, + sampleEmit, +}; diff --git a/components/taskade/sources/new-task-created/test-event.mjs b/components/taskade/sources/new-task-created/test-event.mjs new file mode 100644 index 0000000000000..fa8da75002090 --- /dev/null +++ b/components/taskade/sources/new-task-created/test-event.mjs @@ -0,0 +1,5 @@ +export default { + "id": "85e89694-3ca9-49ac-8393-4595543d4643", + "text": "Getting Started", + "completed": false +} \ No newline at end of file diff --git a/components/taskade/sources/task-due-instant/task-due-instant.mjs b/components/taskade/sources/task-due-instant/task-due-instant.mjs deleted file mode 100644 index 88c7bdb976437..0000000000000 --- a/components/taskade/sources/task-due-instant/task-due-instant.mjs +++ /dev/null @@ -1,73 +0,0 @@ -import taskade from "../../taskade.app.mjs"; - -export default { - key: "taskade-task-due-instant", - name: "Task Due Instant", - description: "Emit new event when a task's due date is reached", - version: "0.0.{{ts}}", - type: "source", - dedupe: "unique", - props: { - taskade, - task_id: { - propDefinition: [ - taskade, - "task_id", - ], - }, - assignee_id: { - propDefinition: [ - taskade, - "assignee_id", - ], - optional: true, - }, - http: { - type: "$.interface.http", - customResponse: false, - }, - db: "$.service.db", - }, - hooks: { - async deploy() { - const task = await this.taskade.getTask(this.task_id); - if (!task.item[0].dueDate) { - console.log("This task does not have a due date"); - return; - } - const dueDate = new Date(task.item[0].dueDate.start.date); - if (dueDate < new Date()) { - console.log("The due date for this task has already passed"); - return; - } - this.db.set("dueDate", dueDate.getTime()); - }, - async activate() { - const intervalId = setInterval(() => { - const dueDate = new Date(this.db.get("dueDate")); - if (dueDate <= new Date()) { - clearInterval(intervalId); - this.$emit( - { - task_id: this.task_id, - }, - { - id: this.task_id, - summary: `Task ${this.task_id} due date reached`, - ts: Date.now(), - }, - ); - } - }, 60 * 1000); // check every minute - this.db.set("intervalId", intervalId); - }, - async deactivate() { - clearInterval(this.db.get("intervalId")); - }, - }, - async run(event) { - this.http.respond({ - status: 200, - }); - }, -}; diff --git a/components/taskade/taskade.app.mjs b/components/taskade/taskade.app.mjs index 73e999d7ee946..5fda3702c4a67 100644 --- a/components/taskade/taskade.app.mjs +++ b/components/taskade/taskade.app.mjs @@ -4,107 +4,115 @@ export default { type: "app", app: "taskade", propDefinitions: { - task_id: { + projectId: { type: "string", - label: "Task ID", - description: "The ID of the task to track due date for", - required: true, - }, - assignee_id: { - type: "string", - label: "Assignee ID", - description: "The ID of the assignee to filter by", - optional: true, - }, - task_title: { - type: "string", - label: "Task Title", - description: "The title of the task to be created", - required: true, - }, - workspace: { - type: "string", - label: "Workspace", - description: "The workspace where the task should be created", - required: true, - }, - due_date: { - type: "string", - label: "Due Date", - description: "The due date for the task", - optional: true, - }, - assignees: { - type: "string[]", - label: "Assignees", - description: "The assignees for the task", - optional: true, + label: "Project ID", + description: "The identifier of a project", + async options({ page }) { + const { items } = await this.listProjects({ + params: { + page: page + 1, + }, + }); + return items?.map(({ + id: value, name: label, + }) => ({ + value, + label, + })) || []; + }, }, }, methods: { _baseUrl() { return "https://www.taskade.com/api/v1"; }, - async _makeRequest(opts = {}) { + _makeRequest(opts = {}) { const { $ = this, - method = "GET", path, - headers, ...otherOpts } = opts; return axios($, { ...otherOpts, - method, - url: this._baseUrl() + path, + url: `${this._baseUrl()}${path}`, headers: { - ...headers, "Content-Type": "application/json", "Authorization": `Bearer ${this.$auth.oauth_access_token}`, }, }); }, - async createTask(task_title, workspace, due_date, assignees) { - const project = await this._makeRequest({ + listProjects(opts = {}) { + return this._makeRequest({ + path: "/me/projects", + ...opts, + }); + }, + listTasks({ + projectId, ...opts + }) { + return this._makeRequest({ + path: `/projects/${projectId}/tasks`, + ...opts, + }); + }, + createTask({ + projectId, ...opts + }) { + return this._makeRequest({ method: "POST", - path: `/projects/${workspace}/tasks/`, - data: { - tasks: [ - { - content: task_title, - }, - ], - }, + path: `/projects/${projectId}/tasks`, + ...opts, + }); + }, + assignTask({ + projectId, taskId, ...opts + }) { + return this._makeRequest({ + method: "PUT", + path: `/projects/${projectId}/tasks/${taskId}/assignees`, + ...opts, }); - - if (due_date) { - await this._makeRequest({ - method: "PUT", - path: `/projects/${workspace}/tasks/${project.item[0].id}/date`, - data: { - start: { - date: due_date, - }, - }, - }); - } - - if (assignees) { - await this._makeRequest({ - method: "PUT", - path: `/projects/${workspace}/tasks/${project.item[0].id}/assignees`, - data: { - handles: assignees, - }, - }); - } - - return project; }, - async getTask(task_id) { - return await this._makeRequest({ - path: `/tasks/${task_id}`, + createOrUpdateDueDate({ + projectId, taskId, ...opts + }) { + return this._makeRequest({ + method: "PUT", + path: `/projects/${projectId}/tasks/${taskId}/date`, + ...opts, }); }, + async *paginate({ + resourceFn, + args, + resourceType, + max, + }) { + args = { + ...args, + params: { + ...args.params, + }, + }; + let count = 0; + do { + const results = await resourceFn(args); + const items = resourceType + ? results[resourceType] + : results; + if (!items?.length) { + return; + } + for (const item of items) { + yield item; + count++; + if (max && max >= count) { + return; + } + } + args.params.after = items[items.length - 1].id; + } while (args.params.after); + }, }, }; From 1aa1f2ef9c3303fcdbb5f6c56a0b43d439b4a8ce Mon Sep 17 00:00:00 2001 From: michelle0927 Date: Tue, 2 Jul 2024 12:01:33 -0400 Subject: [PATCH 3/3] pnpm-lock.yaml --- pnpm-lock.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 71982f6f3c02e..1d701720d6e9b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8950,7 +8950,10 @@ importers: '@pipedream/platform': 1.6.2 components/taskade: - specifiers: {} + specifiers: + '@pipedream/platform': ^3.0.0 + dependencies: + '@pipedream/platform': 3.0.0 components/tave: specifiers: {}