Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions components/timecamp/actions/common/constants.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export default {
BUDGET_UNITS: [
{
label: "Hours",
value: "hours",
},
{
label: "Fee",
value: "fee",
},
],
};
70 changes: 70 additions & 0 deletions components/timecamp/actions/create-task/create-task.mjs
Original file line number Diff line number Diff line change
@@ -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;
},
};
Original file line number Diff line number Diff line change
@@ -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;
},
};
21 changes: 21 additions & 0 deletions components/timecamp/package.json
Original file line number Diff line number Diff line change
@@ -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 <support@pipedream.com> (https://pipedream.com/)",
"license": "MIT",
"gitHead": "e12480b94cc03bed4808ebc6b13e7fdb3a1ba535",
"publishConfig": {
"access": "public"
},
"dependencies": {
"@pipedream/platform": "^1.1.0",
"dayjs": "^1.11.5"
}
}
42 changes: 42 additions & 0 deletions components/timecamp/sources/new-task/new-task.mjs
Original file line number Diff line number Diff line change
@@ -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();
},
};
65 changes: 65 additions & 0 deletions components/timecamp/sources/new-time-entry/new-time-entry.mjs
Original file line number Diff line number Diff line change
@@ -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);
},
};
67 changes: 63 additions & 4 deletions components/timecamp/timecamp.app.mjs
Original file line number Diff line number Diff line change
@@ -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,
});
},
},
};
15 changes: 15 additions & 0 deletions pnpm-lock.yaml

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