diff --git a/components/timecamp/actions/common/constants.mjs b/components/timecamp/actions/common/constants.mjs new file mode 100644 index 0000000000000..b1a38857cc490 --- /dev/null +++ b/components/timecamp/actions/common/constants.mjs @@ -0,0 +1,12 @@ +export default { + BUDGET_UNITS: [ + { + label: "Hours", + value: "hours", + }, + { + label: "Fee", + value: "fee", + }, + ], +}; diff --git a/components/timecamp/actions/create-task/create-task.mjs b/components/timecamp/actions/create-task/create-task.mjs new file mode 100644 index 0000000000000..0be59ac0ee518 --- /dev/null +++ b/components/timecamp/actions/create-task/create-task.mjs @@ -0,0 +1,70 @@ +import timecamp from "../../timecamp.app.mjs"; +import constants from "../common/constants.mjs"; + +export default { + name: "Create Task", + version: "0.0.1", + key: "timecamp-create-task", + description: "Creates a task. [See docs here](https://developer.timecamp.com/docs/timecamp-api/b3A6NTg5ODUxMA-create-new-task)", + type: "action", + props: { + timecamp, + name: { + label: "Name", + description: "The name of the task", + type: "string", + }, + note: { + label: "Description", + description: "The description of the task", + type: "string", + optional: true, + }, + tags: { + label: "Tags", + description: "The tags of the task. E.g. `IT, R&D`", + type: "string", + optional: true, + }, + billable: { + label: "Billable", + description: "The task is billable", + type: "boolean", + optional: true, + }, + budgetUnit: { + label: "Budget Unit", + description: "The budget unit of the task", + type: "string", + options: constants.BUDGET_UNITS, + optional: true, + }, + parentId: { + label: "Parent Task ID", + propDefinition: [ + timecamp, + "taskId", + ], + optional: true, + }, + }, + async run({ $ }) { + const response = await this.timecamp.createTask({ + $, + data: { + name: this.name, + note: this.note, + tags: this.tags, + billable: this.billable, + budget_unit: this.budgetUnit, + parent_id: this.parentId, + }, + }); + + if (response) { + $.export("$summary", `Successfully created task with id ${Object.values(response)[0].task_id}`); + } + + return response; + }, +}; diff --git a/components/timecamp/actions/create-time-entry/create-time-entry.mjs b/components/timecamp/actions/create-time-entry/create-time-entry.mjs new file mode 100644 index 0000000000000..f5596344f9ec9 --- /dev/null +++ b/components/timecamp/actions/create-time-entry/create-time-entry.mjs @@ -0,0 +1,73 @@ +import { ConfigurationError } from "@pipedream/platform"; +import timecamp from "../../timecamp.app.mjs"; + +export default { + name: "Create Time Entry", + version: "0.1.3", + key: "timecamp-create-timeentry", + description: "Creates a time entry. [See docs here](https://developer.timecamp.com/docs/timecamp-api/b3A6NTY2NDIyOQ-create-time-entry)", + type: "action", + props: { + timecamp, + date: { + label: "Date", + description: "The date will be entry create. E.g. `2021-03-02`", + type: "string", + optional: true, + }, + start: { + label: "Start Time", + description: "The entry start time. E.g. `2020-02-02 13:05:47`", + type: "string", + optional: true, + }, + end: { + label: "End Time", + description: "The entry end time. E.g. `2020-02-02 15:21:31`", + type: "string", + optional: true, + }, + duration: { + label: "Duration", + description: "The time entry duration in seconds if don't have `Start` and `End`. E.g. `7200`", + type: "integer", + optional: true, + }, + note: { + label: "Description", + description: "The description of the time entry", + type: "string", + optional: true, + }, + taskId: { + propDefinition: [ + timecamp, + "taskId", + ], + optional: true, + }, + }, + async run({ $ }) { + if (!this.duration && (!this.start || !this.end)) { + throw new ConfigurationError("Is needed `Start` and `End` or `Duration`"); + } + + const response = await this.timecamp.createTimeEntry({ + $, + data: { + date: this.date, + start: this.start, + end: this.end, + duration: this.duration, + note: this.note, + task_id: this.taskId, + }, + }); + + if (response) { + $.export("$summary", `Successfully created time entry with id ${response.entry_id}`); + } + + return response; + }, +}; diff --git a/components/timecamp/package.json b/components/timecamp/package.json new file mode 100644 index 0000000000000..c27f122b87a45 --- /dev/null +++ b/components/timecamp/package.json @@ -0,0 +1,21 @@ +{ + "name": "@pipedream/timecamp", + "version": "0.0.1", + "description": "Pipedream TimeCamp Components", + "main": "timecamp.app.mjs", + "keywords": [ + "pipedream", + "timecamp" + ], + "homepage": "https://pipedream.com/apps/timecamp", + "author": "Pipedream (https://pipedream.com/)", + "license": "MIT", + "gitHead": "e12480b94cc03bed4808ebc6b13e7fdb3a1ba535", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^1.1.0", + "dayjs": "^1.11.5" + } +} diff --git a/components/timecamp/sources/new-task/new-task.mjs b/components/timecamp/sources/new-task/new-task.mjs new file mode 100644 index 0000000000000..060aeaea7d678 --- /dev/null +++ b/components/timecamp/sources/new-task/new-task.mjs @@ -0,0 +1,42 @@ +import timecamp from "../../timecamp.app.mjs"; + +export default { + name: "New Task", + version: "0.0.1", + key: "timecamp-new-task", + description: "Emit new event on each created task.", + type: "source", + dedupe: "unique", + props: { + timecamp, + db: "$.service.db", + timer: { + type: "$.interface.timer", + static: { + intervalSeconds: 15 * 60, // 15 minutes + }, + }, + }, + methods: { + emitEvent(data) { + this.$emit(data, { + id: data.task_id, + summary: `New task with id ${data.task_id}`, + ts: Date.parse(data.add_date), + }); + }, + async emitAllTasks() { + const tasks = await this.timecamp.getTasks({}); + + tasks.reverse().forEach(this.emitEvent); + }, + }, + hooks: { + async deploy() { + await this.emitAllTasks(); + }, + }, + async run() { + await this.emitAllTasks(); + }, +}; diff --git a/components/timecamp/sources/new-time-entry/new-time-entry.mjs b/components/timecamp/sources/new-time-entry/new-time-entry.mjs new file mode 100644 index 0000000000000..4ff135b93d073 --- /dev/null +++ b/components/timecamp/sources/new-time-entry/new-time-entry.mjs @@ -0,0 +1,65 @@ +import timecamp from "../../timecamp.app.mjs"; +import dayjs from "dayjs"; + +export default { + name: "New Time Entry", + version: "0.0.1", + key: "timecamp-new-time-entry", + description: "Emit new event on each created time entry.", + type: "source", + dedupe: "unique", + props: { + timecamp, + db: "$.service.db", + timer: { + type: "$.interface.timer", + static: { + intervalSeconds: 15 * 60, // 15 minutes + }, + }, + }, + methods: { + _getLastSyncDate() { + return this.db.get("lastSyncDate"); + }, + _setLastSyncDate(lastSyncDate) { + return this.db.set("lastSyncDate", lastSyncDate); + }, + emitEvent(data) { + this.$emit(data, { + id: data.id, + summary: `New time entry with id ${data.id}`, + ts: Date.parse(data.add_date), + }); + }, + async emitTimeEntries() { + const lastSyncDate = this._getLastSyncDate(); + + const timeEntries = await this.timecamp.getTimeEntries({ + params: { + from: lastSyncDate, + to: dayjs().format("YYYY-MM-DD"), + }, + }); + + timeEntries.reverse().forEach(this.emitEvent); + }, + }, + hooks: { + async deploy() { + const lastSyncDate = dayjs().subtract(1, "month") + .format("YYYY-MM-DD"); + + this._setLastSyncDate(lastSyncDate); + + await this.emitTimeEntries(); + }, + }, + async run() { + await this.emitTimeEntries(); + + const lastSyncDate = dayjs().format("YYYY-MM-DD"); + + this._setLastSyncDate(lastSyncDate); + }, +}; diff --git a/components/timecamp/timecamp.app.mjs b/components/timecamp/timecamp.app.mjs index 3c260e481b07e..ff5a56ec01e54 100644 --- a/components/timecamp/timecamp.app.mjs +++ b/components/timecamp/timecamp.app.mjs @@ -1,11 +1,70 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "timecamp", - propDefinitions: {}, + propDefinitions: { + taskId: { + label: "Task ID", + description: "The task ID", + type: "string", + async options() { + const tasks = await this.getTasks(); + + return tasks.map((task) => ({ + label: task.name, + value: task.task_id, + })); + }, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _apiToken() { + return this.$auth.api_token; + }, + _apiUrl() { + return "https://www.timecamp.com/third_party/api"; + }, + async _makeRequest({ + $ = this, path, ...args + }) { + return axios($, { + url: `${this._apiUrl()}${path}`, + headers: { + Authorization: this._apiToken(), + }, + ...args, + }); + }, + async getTasks({ ...args } = {}) { + const response = await this._makeRequest({ + path: "/tasks", + ...args, + }); + + return Object.values(response); + }, + async getTimeEntries({ ...args } = {}) { + const response = await this._makeRequest({ + path: "/entries", + ...args, + }); + + return Object.values(response); + }, + async createTask({ ...args } = {}) { + return this._makeRequest({ + path: "/tasks", + method: "post", + ...args, + }); + }, + async createTimeEntry({ ...args } = {}) { + return this._makeRequest({ + path: "/entries", + method: "post", + ...args, + }); }, }, }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f539d98a16842..45413743b2af9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -830,6 +830,9 @@ importers: components/grist: specifiers: {} + components/growsurf: + specifiers: {} + components/guru: specifiers: {} @@ -1827,6 +1830,14 @@ importers: dependencies: '@pipedream/platform': 0.10.0 + components/timecamp: + specifiers: + '@pipedream/platform': ^1.1.0 + dayjs: ^1.11.5 + dependencies: + '@pipedream/platform': 1.1.1 + dayjs: 1.11.5 + components/tmetric: specifiers: {} @@ -10134,6 +10145,10 @@ packages: resolution: {integrity: sha512-xxwlswWOlGhzgQ4TKzASQkUhqERI3egRNqgV4ScR8wlANA/A9tZ7miXa44vTTKEq5l7vWoL5G57bG3zA+Kow0A==} dev: false + /dayjs/1.11.5: + resolution: {integrity: sha512-CAdX5Q3YW3Gclyo5Vpqkgpj8fSdLQcRuzfX6mC6Phy0nfJ0eGYOeS7m4mt2plDWLAtA4TqTakvbboHvUxfe4iA==} + dev: false + /debug/2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: