import * as z from "zod" import { defineAction } from "../../../automation/actions" import { getAsanaApi } from "@automate.ax/integration-contracts/asana" import { ASANA_PAGE_INFO_SCHEMA, ASANA_PROVIDER_PAGE_SCHEMA, ASANA_PROVIDER_STORY_SCHEMA, ASANA_STORY_SCHEMA, toAsanaPageInfo, toAsanaStory, } from "@automate.ax/integration-contracts/asana" import { ASANA_ACCOUNT, ASANA_DEFAULT_SCOPE } from "./scopes" const STORY_FIELDS = "gid,resource_type,resource_subtype,created_at,created_by.gid,created_by.name,created_by.resource_type,html_text,is_edited,is_pinned,text" const STORY_RESPONSE_SCHEMA = z.looseObject({ data: ASANA_PROVIDER_STORY_SCHEMA, }) const STORY_PAGE_RESPONSE_SCHEMA = ASANA_PROVIDER_PAGE_SCHEMA.extend({ data: ASANA_PROVIDER_STORY_SCHEMA.array(), }) const EMPTY_RESPONSE_SCHEMA = z.looseObject({ data: z.looseObject({}) }) const COMMENT_CONTENT_SCHEMA = z.object({ /** Rich-text comment using Asana-supported HTML. */ htmlText: z.string().min(1).optional(), /** Plain-text comment. */ text: z.string().min(1).optional(), }) const CREATE_COMMENT_INPUT_SCHEMA = COMMENT_CONTENT_SCHEMA.safeExtend({ /** Whether to pin the comment to the top of the task activity. */ isPinned: z.boolean().optional(), /** Task GID. */ taskId: z.string().min(1), }).refine( ({ htmlText, text }) => (htmlText === undefined) !== (text === undefined), { message: "Provide exactly one of text or htmlText.", path: ["text"] }, ) const UPDATE_COMMENT_INPUT_SCHEMA = COMMENT_CONTENT_SCHEMA.safeExtend({ /** Whether the comment should be pinned. */ isPinned: z.boolean().optional(), /** Comment story GID. */ storyId: z.string().min(1), }) .refine( ({ htmlText, text }) => htmlText === undefined || text === undefined, { message: "Provide only one of text or htmlText.", path: ["text"], }, ) .refine( ({ htmlText, isPinned, text }) => htmlText !== undefined || text !== undefined || isPinned !== undefined, "Provide text, htmlText, or isPinned.", ) /** Lists activity and comments for an Asana task. */ export const listAsanaTaskStories = defineAction("List Asana task stories") .describe("Lists activity stories and comments for an Asana task.") .account(ASANA_ACCOUNT, ASANA_DEFAULT_SCOPE) .input( z.object({ /** Maximum stories to return. */ limit: z.number().int().min(1).max(100).prefault(100), /** Opaque token returned by the previous page. */ offset: z.string().min(1).optional(), /** Task GID. */ taskId: z.string().min(1), }), ) .output( z.object({ pageInfo: ASANA_PAGE_INFO_SCHEMA, stories: ASANA_STORY_SCHEMA.array(), }), ) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => { const result = await getAsanaApi(account.secret).call( `/tasks/${input.taskId}/stories`, { query: { limit: input.limit, offset: input.offset, opt_fields: STORY_FIELDS, }, responseSchema: STORY_PAGE_RESPONSE_SCHEMA, }, ) return { pageInfo: toAsanaPageInfo(result), stories: result.data.map(toAsanaStory), } }) /** Adds a plain-text or rich-text comment to an Asana task. */ export const addAsanaTaskComment = defineAction("Add Asana task comment") .describe("Adds a plain-text or rich-text comment to an Asana task.") .account(ASANA_ACCOUNT, ASANA_DEFAULT_SCOPE) .input(CREATE_COMMENT_INPUT_SCHEMA) .output(ASANA_STORY_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => toAsanaStory( ( await getAsanaApi(account.secret).call( `/tasks/${input.taskId}/stories`, { body: { ...(input.htmlText !== undefined && { html_text: input.htmlText, }), ...(input.isPinned !== undefined && { is_pinned: input.isPinned, }), ...(input.text !== undefined && { text: input.text }), }, query: { opt_fields: STORY_FIELDS }, responseSchema: STORY_RESPONSE_SCHEMA, }, ) ).data, ), ) /** Updates an editable user comment on an Asana task. */ export const updateAsanaTaskComment = defineAction("Update Asana task comment") .describe("Updates the content or pinned state of an editable Asana comment.") .account(ASANA_ACCOUNT, ASANA_DEFAULT_SCOPE) .input(UPDATE_COMMENT_INPUT_SCHEMA) .output(ASANA_STORY_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => toAsanaStory( ( await getAsanaApi(account.secret).call(`/stories/${input.storyId}`, { body: { ...(input.htmlText !== undefined && { html_text: input.htmlText, }), ...(input.isPinned !== undefined && { is_pinned: input.isPinned, }), ...(input.text !== undefined && { text: input.text }), }, httpMethod: "PUT", query: { opt_fields: STORY_FIELDS }, responseSchema: STORY_RESPONSE_SCHEMA, }) ).data, ), ) /** Deletes a task comment created by the connected Asana user. */ export const deleteAsanaTaskComment = defineAction("Delete Asana task comment") .describe("Deletes a task comment created by the connected Asana user.") .account(ASANA_ACCOUNT, ASANA_DEFAULT_SCOPE) .input( z.object({ /** Comment story GID. */ storyId: z.string().min(1), }), ) .output(z.void()) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { await getAsanaApi(account.secret).call(`/stories/${input.storyId}`, { httpMethod: "DELETE", responseSchema: EMPTY_RESPONSE_SCHEMA, }) })