-
Notifications
You must be signed in to change notification settings - Fork 5.5k
[Components] tmetric #13315 #19287
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
[Components] tmetric #13315 #19287
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| import app from "../../tmetric.app.mjs"; | ||
|
|
||
| export default { | ||
| key: "tmetric-create-time-entry", | ||
| name: "Create Time Entry", | ||
| description: "Create a new Time Entry. [See the documentation](https://app.tmetric.com/api-docs/?_gl=1*n76hcy*_gcl_aw*R0NMLjE3NjQwOTU2MDguQ2owS0NRaUF4SlhKQmhEX0FSSXNBSF9KR2pncU8zZzgxcHp3VUhQWGdjazEyUWFTaThXeE00ZTBUZ1hvak5ma1AzeG10a1pGYWJuek8wOGFBbC1KRUFMd193Y0I.*_gcl_au*MjU2MzU4NTI3LjE3NjQwOTU2MDg.*_ga*ODE4ODUyMDE2LjE3NjQwOTU2MDM.*_ga_66EFKVJKC5*czE3NjQwOTU2MDMkbzEkZzEkdDE3NjQwOTYyODkkajQzJGwwJGgw#/Time%20Entries/post-accounts-accountId-timeentries)", | ||
| version: "0.0.1", | ||
| annotations: { | ||
| destructiveHint: false, | ||
| openWorldHint: true, | ||
| readOnlyHint: false, | ||
| }, | ||
| type: "action", | ||
| props: { | ||
| app, | ||
| accountId: { | ||
| propDefinition: [ | ||
| app, | ||
| "accountId", | ||
| ], | ||
| }, | ||
| startTime: { | ||
| propDefinition: [ | ||
| app, | ||
| "startTime", | ||
| ], | ||
| }, | ||
| endTime: { | ||
| propDefinition: [ | ||
| app, | ||
| "endTime", | ||
| ], | ||
| }, | ||
| note: { | ||
| propDefinition: [ | ||
| app, | ||
| "note", | ||
| ], | ||
| }, | ||
| isBillable: { | ||
| propDefinition: [ | ||
| app, | ||
| "isBillable", | ||
| ], | ||
| }, | ||
| isInvoiced: { | ||
| propDefinition: [ | ||
| app, | ||
| "isInvoiced", | ||
| ], | ||
| }, | ||
| }, | ||
| async run({ $ }) { | ||
| const response = await this.app.createTimeEntry({ | ||
| $, | ||
| accountId: this.accountId, | ||
| data: { | ||
| startTime: this.startTime ?? "", | ||
| endTime: this.endTime ?? "", | ||
| note: this.note, | ||
| isBillable: this.isBillable, | ||
| isInvoiced: this.isInvoiced, | ||
| }, | ||
| }); | ||
| $.export("$summary", "Successfully created time entry with ID: " + response[0].id); | ||
| return response; | ||
|
Comment on lines
+65
to
+66
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Defensively handle the createTimeEntry response shape when building the summary The summary assumes You can support both shapes with a small adjustment: - $.export("$summary", "Successfully created time entry with ID: " + response[0].id);
- return response;
+ const timeEntry = Array.isArray(response) ? response[0] : response;
+ $.export("$summary", `Successfully created time entry with ID: ${timeEntry.id}`);
+ return response;This keeps the summary robust even if the API or client behavior changes. 🤖 Prompt for AI Agents |
||
| }, | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| import app from "../../tmetric.app.mjs"; | ||
|
|
||
| export default { | ||
| key: "tmetric-delete-time-entry", | ||
| name: "Delete Time Entry", | ||
| description: "Delete the specified Time Entry. [See the documentation](https://app.tmetric.com/api-docs/?_gl=1*n76hcy*_gcl_aw*R0NMLjE3NjQwOTU2MDguQ2owS0NRaUF4SlhKQmhEX0FSSXNBSF9KR2pncU8zZzgxcHp3VUhQWGdjazEyUWFTaThXeE00ZTBUZ1hvak5ma1AzeG10a1pGYWJuek8wOGFBbC1KRUFMd193Y0I.*_gcl_au*MjU2MzU4NTI3LjE3NjQwOTU2MDg.*_ga*ODE4ODUyMDE2LjE3NjQwOTU2MDM.*_ga_66EFKVJKC5*czE3NjQwOTU2MDMkbzEkZzEkdDE3NjQwOTYyODkkajQzJGwwJGgw#/Time%20Entries/delete-accounts-accountId-timeentries-timeEntryId)", | ||
| version: "0.0.1", | ||
| annotations: { | ||
| destructiveHint: true, | ||
| openWorldHint: true, | ||
| readOnlyHint: false, | ||
| }, | ||
| type: "action", | ||
| props: { | ||
| app, | ||
| accountId: { | ||
| propDefinition: [ | ||
| app, | ||
| "accountId", | ||
| ], | ||
| }, | ||
| timeEntryId: { | ||
| propDefinition: [ | ||
| app, | ||
| "timeEntryId", | ||
| (c) => ({ | ||
| accountId: c.accountId, | ||
| }), | ||
| ], | ||
| }, | ||
| }, | ||
| async run({ $ }) { | ||
| const response = await this.app.deleteTimeEntry({ | ||
| $, | ||
| data: { | ||
| accountId: this.accountId, | ||
| timeEntryId: this.timeEntryId, | ||
| }, | ||
| }); | ||
|
Comment on lines
+32
to
+39
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix
You should pass the IDs at the top level (and you don't need them in the body): - const response = await this.app.deleteTimeEntry({
- $,
- data: {
- accountId: this.accountId,
- timeEntryId: this.timeEntryId,
- },
- });
+ const response = await this.app.deleteTimeEntry({
+ $,
+ accountId: this.accountId,
+ timeEntryId: this.timeEntryId,
+ });🤖 Prompt for AI Agents |
||
| $.export("$summary", "Successfully deleted time entry with ID: " + this.timeEntryId); | ||
| return response; | ||
| }, | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| import app from "../../tmetric.app.mjs"; | ||
|
|
||
| export default { | ||
| key: "tmetric-modify-time-entry", | ||
| name: "Modify Time Entry", | ||
| description: "Modify the specified Time Entry. [See the documentation](https://app.tmetric.com/api-docs/?_gl=1*n76hcy*_gcl_aw*R0NMLjE3NjQwOTU2MDguQ2owS0NRaUF4SlhKQmhEX0FSSXNBSF9KR2pncU8zZzgxcHp3VUhQWGdjazEyUWFTaThXeE00ZTBUZ1hvak5ma1AzeG10a1pGYWJuek8wOGFBbC1KRUFMd193Y0I.*_gcl_au*MjU2MzU4NTI3LjE3NjQwOTU2MDg.*_ga*ODE4ODUyMDE2LjE3NjQwOTU2MDM.*_ga_66EFKVJKC5*czE3NjQwOTU2MDMkbzEkZzEkdDE3NjQwOTYyODkkajQzJGwwJGgw#/Time%20Entries/put-accounts-accountId-timeentries-timeEntryId)", | ||
| version: "0.0.1", | ||
| annotations: { | ||
| destructiveHint: false, | ||
| openWorldHint: true, | ||
| readOnlyHint: false, | ||
| }, | ||
| type: "action", | ||
| props: { | ||
| app, | ||
| accountId: { | ||
| propDefinition: [ | ||
| app, | ||
| "accountId", | ||
| ], | ||
| }, | ||
| timeEntryId: { | ||
| propDefinition: [ | ||
| app, | ||
| "timeEntryId", | ||
| (c) => ({ | ||
| accountId: c.accountId, | ||
| }), | ||
| ], | ||
| }, | ||
| startTime: { | ||
| propDefinition: [ | ||
| app, | ||
| "startTime", | ||
| ], | ||
| }, | ||
| endTime: { | ||
| propDefinition: [ | ||
| app, | ||
| "endTime", | ||
| ], | ||
| }, | ||
| note: { | ||
| propDefinition: [ | ||
| app, | ||
| "note", | ||
| ], | ||
| }, | ||
| isBillable: { | ||
| propDefinition: [ | ||
| app, | ||
| "isBillable", | ||
| ], | ||
| }, | ||
| isInvoiced: { | ||
| propDefinition: [ | ||
| app, | ||
| "isInvoiced", | ||
| ], | ||
| }, | ||
| }, | ||
| async run({ $ }) { | ||
| const response = await this.app.modifyTimeEntry({ | ||
| $, | ||
| accountId: this.accountId, | ||
| timeEntryId: this.timeEntryId, | ||
| data: { | ||
| startTime: this.startTime ?? "", | ||
| endTime: this.endTime ?? "", | ||
| note: this.note, | ||
| isBillable: this.isBillable, | ||
| isInvoiced: this.isInvoiced, | ||
| }, | ||
| }); | ||
| $.export("$summary", "Successfully modified the time entry with ID: " + this.timeEntryId); | ||
| return response; | ||
| }, | ||
| }; |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,11 +1,132 @@ | ||||||||||||||||||||||||||||||||||||||||||
| import { axios } from "@pipedream/platform"; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| export default { | ||||||||||||||||||||||||||||||||||||||||||
| type: "app", | ||||||||||||||||||||||||||||||||||||||||||
| app: "tmetric", | ||||||||||||||||||||||||||||||||||||||||||
| propDefinitions: {}, | ||||||||||||||||||||||||||||||||||||||||||
| propDefinitions: { | ||||||||||||||||||||||||||||||||||||||||||
| accountId: { | ||||||||||||||||||||||||||||||||||||||||||
| type: "string", | ||||||||||||||||||||||||||||||||||||||||||
| label: "Account ID", | ||||||||||||||||||||||||||||||||||||||||||
| description: "Identifier of the account", | ||||||||||||||||||||||||||||||||||||||||||
| async options() { | ||||||||||||||||||||||||||||||||||||||||||
| const response = await this.getUser(); | ||||||||||||||||||||||||||||||||||||||||||
| const accounts = response.accounts; | ||||||||||||||||||||||||||||||||||||||||||
| return accounts.map(({ | ||||||||||||||||||||||||||||||||||||||||||
| id, name, | ||||||||||||||||||||||||||||||||||||||||||
| }) => ({ | ||||||||||||||||||||||||||||||||||||||||||
| value: id, | ||||||||||||||||||||||||||||||||||||||||||
| label: name, | ||||||||||||||||||||||||||||||||||||||||||
| })); | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| timeEntryId: { | ||||||||||||||||||||||||||||||||||||||||||
| type: "string", | ||||||||||||||||||||||||||||||||||||||||||
| label: "Time Entry ID", | ||||||||||||||||||||||||||||||||||||||||||
| description: "Identifier of the time entry", | ||||||||||||||||||||||||||||||||||||||||||
| async options({ accountId }) { | ||||||||||||||||||||||||||||||||||||||||||
| const response = await this.getTimeEntries({ | ||||||||||||||||||||||||||||||||||||||||||
| accountId, | ||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||
| return response.map(({ | ||||||||||||||||||||||||||||||||||||||||||
| id, note, | ||||||||||||||||||||||||||||||||||||||||||
| }) => ({ | ||||||||||||||||||||||||||||||||||||||||||
| value: id, | ||||||||||||||||||||||||||||||||||||||||||
| label: note, | ||||||||||||||||||||||||||||||||||||||||||
| })); | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| startTime: { | ||||||||||||||||||||||||||||||||||||||||||
| type: "string", | ||||||||||||||||||||||||||||||||||||||||||
| label: "Start Time", | ||||||||||||||||||||||||||||||||||||||||||
| description: "Start timestamp in ISO format, e.g. `2025-11-26T14:16:00`; leave null to start the timer at the current moment", | ||||||||||||||||||||||||||||||||||||||||||
| optional: true, | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| endTime: { | ||||||||||||||||||||||||||||||||||||||||||
| type: "string", | ||||||||||||||||||||||||||||||||||||||||||
| label: "End Time", | ||||||||||||||||||||||||||||||||||||||||||
| description: "End timestamp in ISO format, e.g. `2025-11-26T15:16:00`; leave null to keep the timer running indefinitely", | ||||||||||||||||||||||||||||||||||||||||||
| optional: true, | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+39
to
+48
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Clarify “leave null” phrasing in The descriptions say “leave null …”, but in the UI users will typically just leave these fields blank. Consider rephrasing to something like “leave blank to …” to better match how props are actually set: - description: "Start timestamp in ISO format, e.g. `2025-11-26T14:16:00`; leave null to start the timer at the current moment",
+ description: "Start timestamp in ISO format, e.g. `2025-11-26T14:16:00`; leave blank to start the timer at the current moment",
...
- description: "End timestamp in ISO format, e.g. `2025-11-26T15:16:00`; leave null to keep the timer running indefinitely",
+ description: "End timestamp in ISO format, e.g. `2025-11-26T15:16:00`; leave blank to keep the timer running indefinitely",📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| note: { | ||||||||||||||||||||||||||||||||||||||||||
| type: "string", | ||||||||||||||||||||||||||||||||||||||||||
| label: "Note", | ||||||||||||||||||||||||||||||||||||||||||
| description: "Additional note for the time entry", | ||||||||||||||||||||||||||||||||||||||||||
| optional: true, | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| isBillable: { | ||||||||||||||||||||||||||||||||||||||||||
| type: "boolean", | ||||||||||||||||||||||||||||||||||||||||||
| label: "Is Billable", | ||||||||||||||||||||||||||||||||||||||||||
| description: "Whether the time entry is billable", | ||||||||||||||||||||||||||||||||||||||||||
| optional: true, | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| isInvoiced: { | ||||||||||||||||||||||||||||||||||||||||||
| type: "boolean", | ||||||||||||||||||||||||||||||||||||||||||
| label: "Is Invoiced", | ||||||||||||||||||||||||||||||||||||||||||
| description: "Whether the time entry has been invoiced", | ||||||||||||||||||||||||||||||||||||||||||
| optional: true, | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| methods: { | ||||||||||||||||||||||||||||||||||||||||||
| // this.$auth contains connected account data | ||||||||||||||||||||||||||||||||||||||||||
| authKeys() { | ||||||||||||||||||||||||||||||||||||||||||
| console.log(Object.keys(this.$auth)); | ||||||||||||||||||||||||||||||||||||||||||
| _baseUrl() { | ||||||||||||||||||||||||||||||||||||||||||
| return "https://app.tmetric.com/api/v3"; | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| async _makeRequest(opts = {}) { | ||||||||||||||||||||||||||||||||||||||||||
| const { | ||||||||||||||||||||||||||||||||||||||||||
| $ = this, | ||||||||||||||||||||||||||||||||||||||||||
| path, | ||||||||||||||||||||||||||||||||||||||||||
| headers, | ||||||||||||||||||||||||||||||||||||||||||
| ...otherOpts | ||||||||||||||||||||||||||||||||||||||||||
| } = opts; | ||||||||||||||||||||||||||||||||||||||||||
| return axios($, { | ||||||||||||||||||||||||||||||||||||||||||
| ...otherOpts, | ||||||||||||||||||||||||||||||||||||||||||
| url: this._baseUrl() + path, | ||||||||||||||||||||||||||||||||||||||||||
| headers: { | ||||||||||||||||||||||||||||||||||||||||||
| Authorization: `Bearer ${this.$auth.api_token}`, | ||||||||||||||||||||||||||||||||||||||||||
| ...headers, | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| async createTimeEntry({ | ||||||||||||||||||||||||||||||||||||||||||
| accountId, ...args | ||||||||||||||||||||||||||||||||||||||||||
| }) { | ||||||||||||||||||||||||||||||||||||||||||
| return this._makeRequest({ | ||||||||||||||||||||||||||||||||||||||||||
| path: `/accounts/${accountId}/timeentries`, | ||||||||||||||||||||||||||||||||||||||||||
| method: "post", | ||||||||||||||||||||||||||||||||||||||||||
| ...args, | ||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| async deleteTimeEntry({ | ||||||||||||||||||||||||||||||||||||||||||
| accountId, timeEntryId, ...args | ||||||||||||||||||||||||||||||||||||||||||
| }) { | ||||||||||||||||||||||||||||||||||||||||||
| return this._makeRequest({ | ||||||||||||||||||||||||||||||||||||||||||
| path: `/accounts/${accountId}/timeentries/${timeEntryId}`, | ||||||||||||||||||||||||||||||||||||||||||
| method: "delete", | ||||||||||||||||||||||||||||||||||||||||||
| ...args, | ||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| async modifyTimeEntry({ | ||||||||||||||||||||||||||||||||||||||||||
| accountId, timeEntryId, ...args | ||||||||||||||||||||||||||||||||||||||||||
| }) { | ||||||||||||||||||||||||||||||||||||||||||
| return this._makeRequest({ | ||||||||||||||||||||||||||||||||||||||||||
| path: `/accounts/${accountId}/timeentries/${timeEntryId}`, | ||||||||||||||||||||||||||||||||||||||||||
| method: "put", | ||||||||||||||||||||||||||||||||||||||||||
| ...args, | ||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| async getTimeEntries({ | ||||||||||||||||||||||||||||||||||||||||||
| accountId, ...args | ||||||||||||||||||||||||||||||||||||||||||
| }) { | ||||||||||||||||||||||||||||||||||||||||||
| return this._makeRequest({ | ||||||||||||||||||||||||||||||||||||||||||
| path: `/accounts/${accountId}/timeentries`, | ||||||||||||||||||||||||||||||||||||||||||
| ...args, | ||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| async getUser(args = {}) { | ||||||||||||||||||||||||||||||||||||||||||
| return this._makeRequest({ | ||||||||||||||||||||||||||||||||||||||||||
| path: "/user", | ||||||||||||||||||||||||||||||||||||||||||
| ...args, | ||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
🏁 Script executed:
Repository: PipedreamHQ/pipedream
Length of output: 464
🏁 Script executed:
Repository: PipedreamHQ/pipedream
Length of output: 2263
🏁 Script executed:
Repository: PipedreamHQ/pipedream
Length of output: 99
🏁 Script executed:
Repository: PipedreamHQ/pipedream
Length of output: 4184
🏁 Script executed:
Repository: PipedreamHQ/pipedream
Length of output: 131
🏁 Script executed:
Repository: PipedreamHQ/pipedream
Length of output: 2555
Avoid coercing missing
startTime/endTimeto empty stringsUsing
this.startTime ?? ""andthis.endTime ?? ""forces empty strings into the payload when the user leaves these props blank. This conflicts with the API documentation in propDefinitions ("leave null to start the timer at the current moment" and "leave null to keep the timer running indefinitely") and prevents relying on the platform client's behavior of omitting undefined fields.You can pass the values directly and let undefined be dropped from the request:
Apply this same adjustment to the modify-time-entry action (lines 68-69) for consistency.
📝 Committable suggestion
🤖 Prompt for AI Agents