import { CLICKUP_TIME_ENTRY_SCHEMA } from "@automate.ax/integration-contracts/clickup" import * as z from "zod" import { defineAction } from "../../../automation/actions" import { CLICKUP_ACCOUNT, CLICKUP_ACCOUNT_REQUIREMENT, CLICKUP_ID_SCHEMA, CLICKUP_TIMESTAMP_SCHEMA, CLICKUP_VOID_RESPONSE_SCHEMA, compactClickUpInput, getClickUpApi, } from "./lib" const TIME_ENTRY_RESPONSE_SCHEMA = z.object({ data: CLICKUP_TIME_ENTRY_SCHEMA }) const DELETED_TIME_ENTRY_RESPONSE_SCHEMA = z.object({ data: z .union([ CLICKUP_TIME_ENTRY_SCHEMA, z.tuple([CLICKUP_TIME_ENTRY_SCHEMA], CLICKUP_TIME_ENTRY_SCHEMA), ]) .transform((data) => (Array.isArray(data) ? data[0] : data)), }) const TIME_ENTRIES_RESPONSE_SCHEMA = z.object({ data: CLICKUP_TIME_ENTRY_SCHEMA.array(), }) const TIME_ENTRY_TAG_SCHEMA = z.object({ name: z.string().min(1), tagBg: z.string(), tagFg: z.string(), }) const TIME_ENTRY_CREATE_FIELDS = { assignee: z.number().int().optional(), billable: z.boolean().optional(), description: z.string().optional(), duration: z.number().int(), end: CLICKUP_TIMESTAMP_SCHEMA.optional(), start: CLICKUP_TIMESTAMP_SCHEMA.optional(), tags: TIME_ENTRY_TAG_SCHEMA.array().optional(), taskId: CLICKUP_ID_SCHEMA.optional(), } as const const CREATED_TIME_ENTRY_SCHEMA = z.object({ assignee: z.number().int().optional(), billable: z.boolean().optional(), description: z.string().optional(), duration: z.coerce.string().optional(), start: z.coerce.string(), tags: TIME_ENTRY_TAG_SCHEMA.array().prefault([]), tid: z.string().optional(), }) const CREATED_TIME_ENTRY_RESPONSE_SCHEMA = CREATED_TIME_ENTRY_SCHEMA.extend({ start: z.coerce.string().optional(), }) /** Lists time entries in a ClickUp Workspace. */ export const listClickUpTimeEntries = defineAction("List ClickUp time entries") .describe("Lists time entries in a date range and optional location.") .account(CLICKUP_ACCOUNT, CLICKUP_ACCOUNT_REQUIREMENT) .input( z .object({ assignee: z.number().int().optional(), endDate: CLICKUP_TIMESTAMP_SCHEMA.optional(), folderId: CLICKUP_ID_SCHEMA.optional(), includeLocationNames: z.boolean().prefault(true), includeTaskTags: z.boolean().prefault(true), listId: CLICKUP_ID_SCHEMA.optional(), spaceId: CLICKUP_ID_SCHEMA.optional(), startDate: CLICKUP_TIMESTAMP_SCHEMA.optional(), taskId: CLICKUP_ID_SCHEMA.optional(), workspaceId: CLICKUP_ID_SCHEMA, }) .refine( ({ folderId, listId, spaceId, taskId }) => [folderId, listId, spaceId, taskId].filter(Boolean).length <= 1, { message: "Use at most one time-entry location filter." }, ), ) .output(z.object({ entries: CLICKUP_TIME_ENTRY_SCHEMA.array() })) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => { const { workspaceId, ...query } = input return { entries: ( await getClickUpApi(account).request( `team/${workspaceId}/time_entries`, { query, responseSchema: TIME_ENTRIES_RESPONSE_SCHEMA }, ) ).data, } }) /** Gets one ClickUp time entry. */ export const getClickUpTimeEntry = defineAction("Get ClickUp time entry") .describe("Gets one time entry from a ClickUp Workspace.") .account(CLICKUP_ACCOUNT, CLICKUP_ACCOUNT_REQUIREMENT) .input( z.object({ entryId: CLICKUP_ID_SCHEMA, workspaceId: CLICKUP_ID_SCHEMA }), ) .output(CLICKUP_TIME_ENTRY_SCHEMA) .retry({ replaySafety: "safe" }) .handler( async ({ account, input }) => ( await getClickUpApi(account).request( `team/${input.workspaceId}/time_entries/${input.entryId}`, { responseSchema: TIME_ENTRY_RESPONSE_SCHEMA }, ) ).data, ) /** Creates a ClickUp time entry. */ export const createClickUpTimeEntry = defineAction("Create ClickUp time entry") .describe("Creates a completed time entry in a ClickUp Workspace.") .account(CLICKUP_ACCOUNT, CLICKUP_ACCOUNT_REQUIREMENT) .input( z.object({ ...TIME_ENTRY_CREATE_FIELDS, start: CLICKUP_TIMESTAMP_SCHEMA, workspaceId: CLICKUP_ID_SCHEMA, }), ) .output(CREATED_TIME_ENTRY_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { const { taskId, workspaceId, ...body } = input const result = await getClickUpApi(account).request( `team/${workspaceId}/time_entries`, { body: compactClickUpInput({ ...body, tid: taskId }), method: "POST", responseSchema: CREATED_TIME_ENTRY_RESPONSE_SCHEMA, }, ) return { ...result, duration: result.duration ?? String(input.duration), start: result.start ?? String(input.start), } }) /** Updates a ClickUp time entry. */ export const updateClickUpTimeEntry = defineAction("Update ClickUp time entry") .describe("Updates a completed ClickUp time entry.") .account(CLICKUP_ACCOUNT, CLICKUP_ACCOUNT_REQUIREMENT) .input( z .object({ billable: z.boolean().optional(), description: z.string().optional(), duration: z.number().int().optional(), end: CLICKUP_TIMESTAMP_SCHEMA.optional(), entryId: CLICKUP_ID_SCHEMA, start: CLICKUP_TIMESTAMP_SCHEMA.optional(), tagAction: z.enum(["add", "remove", "replace"]).optional(), tags: TIME_ENTRY_TAG_SCHEMA.array(), taskId: CLICKUP_ID_SCHEMA.optional(), workspaceId: CLICKUP_ID_SCHEMA, }) .refine( ({ start, end }) => (start === undefined) === (end === undefined), { message: "Provide start and end together when changing either timestamp.", }, ) .refine( ({ entryId: _entryId, workspaceId: _workspaceId, ...body }) => Object.keys(body).length > 0, { message: "Provide at least one time-entry field to update." }, ), ) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => { const { entryId, taskId, workspaceId, ...body } = input return getClickUpApi(account).request( `team/${workspaceId}/time_entries/${entryId}`, { body: compactClickUpInput({ ...body, tid: taskId }), method: "PUT", responseSchema: CLICKUP_VOID_RESPONSE_SCHEMA, }, ) }) /** Deletes a ClickUp time entry. */ export const deleteClickUpTimeEntry = defineAction("Delete ClickUp time entry") .describe("Deletes a time entry from a ClickUp Workspace.") .account(CLICKUP_ACCOUNT, CLICKUP_ACCOUNT_REQUIREMENT) .input( z.object({ entryId: CLICKUP_ID_SCHEMA, workspaceId: CLICKUP_ID_SCHEMA }), ) .output(CLICKUP_TIME_ENTRY_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler( async ({ account, input }) => ( await getClickUpApi(account).request( `team/${input.workspaceId}/time_entries/${input.entryId}`, { method: "DELETE", responseSchema: DELETED_TIME_ENTRY_RESPONSE_SCHEMA, }, ) ).data, ) /** Gets the timer currently running for the connected user. */ export const getRunningClickUpTimeEntry = defineAction( "Get running ClickUp time entry", ) .describe("Gets the timer currently running for the connected user.") .account(CLICKUP_ACCOUNT, CLICKUP_ACCOUNT_REQUIREMENT) .input(z.object({ workspaceId: CLICKUP_ID_SCHEMA })) .output(CLICKUP_TIME_ENTRY_SCHEMA.nullable()) .retry({ replaySafety: "safe" }) .handler( async ({ account, input }) => ( await getClickUpApi(account).request( `team/${input.workspaceId}/time_entries/current`, { responseSchema: z.object({ data: CLICKUP_TIME_ENTRY_SCHEMA.nullable(), }), }, ) ).data, ) /** Starts a ClickUp timer for the connected user. */ export const startClickUpTimeEntry = defineAction("Start ClickUp time entry") .describe("Starts a timer for the connected ClickUp user.") .account(CLICKUP_ACCOUNT, CLICKUP_ACCOUNT_REQUIREMENT) .input( z.object({ billable: z.boolean().optional(), description: z.string().optional(), tags: z .object({ name: z.string().min(1) }) .array() .optional(), taskId: CLICKUP_ID_SCHEMA.optional(), workspaceId: CLICKUP_ID_SCHEMA, }), ) .output(CLICKUP_TIME_ENTRY_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { const { taskId, workspaceId, ...body } = input return ( await getClickUpApi(account).request( `team/${workspaceId}/time_entries/start`, { body: compactClickUpInput({ ...body, tid: taskId }), method: "POST", responseSchema: TIME_ENTRY_RESPONSE_SCHEMA, }, ) ).data }) /** Stops the timer currently running for the connected user. */ export const stopClickUpTimeEntry = defineAction("Stop ClickUp time entry") .describe("Stops the timer currently running for the connected ClickUp user.") .account(CLICKUP_ACCOUNT, CLICKUP_ACCOUNT_REQUIREMENT) .input(z.object({ workspaceId: CLICKUP_ID_SCHEMA })) .output(CLICKUP_TIME_ENTRY_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler( async ({ account, input }) => ( await getClickUpApi(account).request( `team/${input.workspaceId}/time_entries/stop`, { method: "POST", responseSchema: TIME_ENTRY_RESPONSE_SCHEMA }, ) ).data, )