import { getLinearApi } from "@automate.ax/integration-contracts/linear" import { LINEAR_PAGE_INFO_SCHEMA, LINEAR_PAGE_INPUT_SCHEMA, LINEAR_PROJECT_UPDATE_HEALTH_SCHEMA, LINEAR_PROJECT_UPDATE_SCHEMA, linearConnectionSchema, } from "@automate.ax/integration-contracts/linear" import * as z from "zod" import { defineAction } from "../../../automation/actions" import { LINEAR_PROJECT_UPDATE_FIELDS } from "../lib/fragments" import { LINEAR_READ_ACCOUNT_OPTIONS, LINEAR_WRITE_ACCOUNT_OPTIONS, } from "../lib/scopes" const PROJECT_UPDATE_RESPONSE_SCHEMA = z.object({ projectUpdate: LINEAR_PROJECT_UPDATE_SCHEMA, }) const PROJECT_UPDATE_CONNECTION_SCHEMA = linearConnectionSchema( LINEAR_PROJECT_UPDATE_SCHEMA, ) const PROJECT_UPDATE_PAGE_SCHEMA = z.object({ pageInfo: LINEAR_PAGE_INFO_SCHEMA, projectUpdates: LINEAR_PROJECT_UPDATE_SCHEMA.array(), }) const PROJECT_UPDATE_MUTATION_SCHEMA = z.object({ projectUpdateMutation: z.object({ projectUpdate: LINEAR_PROJECT_UPDATE_SCHEMA, success: z.literal(true), }), }) const PROJECT_UPDATE_ARCHIVE_SCHEMA = z.object({ projectUpdateMutation: z.object({ entity: LINEAR_PROJECT_UPDATE_SCHEMA.nullable(), success: z.literal(true), }), }) const PROJECT_UPDATE_FIELDS_INPUT_SCHEMA = z.object({ /** Markdown project-update body. */ body: z.string().optional(), /** Project health at the time of the update. */ health: LINEAR_PROJECT_UPDATE_HEALTH_SCHEMA.optional(), }) /** Lists Linear project updates, optionally limited to one project. */ export const listLinearProjectUpdates = defineAction( "List Linear project updates", ) .describe("Lists a cursor-paginated page of Linear project updates.") .account("linear", LINEAR_READ_ACCOUNT_OPTIONS) .input( LINEAR_PAGE_INPUT_SCHEMA.extend({ /** Sort by creation time or most recent update. */ orderBy: z.enum(["createdAt", "updatedAt"]).prefault("updatedAt"), /** Project whose updates should be returned. */ projectId: z.string().min(1).optional(), }), ) .output(PROJECT_UPDATE_PAGE_SCHEMA) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => { const { projectUpdates } = await getLinearApi(account.secret).graphql( `query LinearProjectUpdates( $first: Int! $after: String $includeArchived: Boolean $orderBy: PaginationOrderBy! $filter: ProjectUpdateFilter ) { projectUpdates( first: $first after: $after includeArchived: $includeArchived orderBy: $orderBy filter: $filter ) { nodes { ${LINEAR_PROJECT_UPDATE_FIELDS} } pageInfo { hasNextPage hasPreviousPage startCursor endCursor } } }`, { responseSchema: z.object({ projectUpdates: PROJECT_UPDATE_CONNECTION_SCHEMA, }), variables: { first: input.limit, orderBy: input.orderBy, ...(input.after && { after: input.after }), ...(input.includeArchived !== undefined && { includeArchived: input.includeArchived, }), ...(input.projectId && { filter: { project: { id: { eq: input.projectId } } }, }), }, }, ) return { pageInfo: projectUpdates.pageInfo!, projectUpdates: projectUpdates.nodes, } }) /** Gets one Linear project update by ID or project-local slug. */ export const getLinearProjectUpdate = defineAction("Get Linear project update") .describe("Gets one Linear project update by ID or project-local slug.") .account("linear", LINEAR_READ_ACCOUNT_OPTIONS) .input( z.object({ /** Project ID when `projectUpdateId` is a URL slug. */ projectId: z.string().min(1).optional(), /** Stable update ID or URL slug. */ projectUpdateId: z.string().min(1), }), ) .output(LINEAR_PROJECT_UPDATE_SCHEMA) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => getLinearApi(account.secret) .graphql( `query LinearProjectUpdate($id: String!, $projectId: String) { projectUpdate(id: $id, projectId: $projectId) { ${LINEAR_PROJECT_UPDATE_FIELDS} } }`, { responseSchema: PROJECT_UPDATE_RESPONSE_SCHEMA, variables: { id: input.projectUpdateId, ...(input.projectId && { projectId: input.projectId }), }, }, ) .then(({ projectUpdate }) => projectUpdate), ) /** Creates a Markdown status update on a Linear project. */ export const createLinearProjectUpdate = defineAction( "Create Linear project update", ) .describe("Posts a status and health update to a Linear project.") .account("linear", LINEAR_WRITE_ACCOUNT_OPTIONS) .input( PROJECT_UPDATE_FIELDS_INPUT_SCHEMA.extend({ /** Hide the computed diff from the preceding project update. */ isDiffHidden: z.boolean().optional(), projectId: z.string().min(1), }), ) .output(LINEAR_PROJECT_UPDATE_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { const { projectUpdateMutation } = await getLinearApi( account.secret, ).graphql( `mutation LinearProjectUpdateCreate($input: ProjectUpdateCreateInput!) { projectUpdateMutation: projectUpdateCreate(input: $input) { success projectUpdate { ${LINEAR_PROJECT_UPDATE_FIELDS} } } }`, { responseSchema: PROJECT_UPDATE_MUTATION_SCHEMA, variables: { input } }, ) return projectUpdateMutation.projectUpdate }) /** Updates the body or health of a Linear project update. */ export const updateLinearProjectUpdate = defineAction( "Update Linear project update", ) .describe("Updates a Linear project update's body or health.") .account("linear", LINEAR_WRITE_ACCOUNT_OPTIONS) .input( PROJECT_UPDATE_FIELDS_INPUT_SCHEMA.extend({ projectUpdateId: z.string().min(1), }), ) .output(LINEAR_PROJECT_UPDATE_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { const { projectUpdateId, ...update } = input const { projectUpdateMutation } = await getLinearApi( account.secret, ).graphql( `mutation LinearProjectUpdateUpdate( $id: String! $input: ProjectUpdateUpdateInput! ) { projectUpdateMutation: projectUpdateUpdate(id: $id, input: $input) { success projectUpdate { ${LINEAR_PROJECT_UPDATE_FIELDS} } } }`, { responseSchema: PROJECT_UPDATE_MUTATION_SCHEMA, variables: { id: projectUpdateId, input: update }, }, ) return projectUpdateMutation.projectUpdate }) /** Archives a Linear project update. */ export const archiveLinearProjectUpdate = defineAction( "Archive Linear project update", ) .describe("Archives a Linear project update by ID.") .account("linear", LINEAR_WRITE_ACCOUNT_OPTIONS) .input(z.object({ projectUpdateId: z.string().min(1) })) .output(LINEAR_PROJECT_UPDATE_SCHEMA.nullable()) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => archiveProjectUpdate( account.secret, "projectUpdateArchive", input.projectUpdateId, ), ) /** Restores an archived Linear project update. */ export const unarchiveLinearProjectUpdate = defineAction( "Unarchive Linear project update", ) .describe("Restores an archived Linear project update.") .account("linear", LINEAR_WRITE_ACCOUNT_OPTIONS) .input(z.object({ projectUpdateId: z.string().min(1) })) .output(LINEAR_PROJECT_UPDATE_SCHEMA.nullable()) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => archiveProjectUpdate( account.secret, "projectUpdateUnarchive", input.projectUpdateId, ), ) /** * Executes one project-update archive lifecycle mutation. * * @param secret - Resolved Linear credential. * @param mutation - Provider archive lifecycle mutation. * @param projectUpdateId - Project update to mutate. */ async function archiveProjectUpdate( secret: Record, mutation: "projectUpdateArchive" | "projectUpdateUnarchive", projectUpdateId: string, ) { const { projectUpdateMutation } = await getLinearApi(secret).graphql( mutation === "projectUpdateArchive" ? `mutation LinearProjectUpdateArchive($id: String!) { projectUpdateMutation: projectUpdateArchive(id: $id) { success entity { ${LINEAR_PROJECT_UPDATE_FIELDS} } } }` : `mutation LinearProjectUpdateUnarchive($id: String!) { projectUpdateMutation: projectUpdateUnarchive(id: $id) { success entity { ${LINEAR_PROJECT_UPDATE_FIELDS} } } }`, { responseSchema: PROJECT_UPDATE_ARCHIVE_SCHEMA, variables: { id: projectUpdateId }, }, ) return projectUpdateMutation.entity }