import { getLinearApi } from "@automate.ax/integration-contracts/linear" import { LINEAR_PAGE_INFO_SCHEMA, LINEAR_PAGE_INPUT_SCHEMA, LINEAR_PROJECT_MILESTONE_SCHEMA, linearConnectionSchema, } from "@automate.ax/integration-contracts/linear" import * as z from "zod" import { defineAction } from "../../../automation/actions" import { LINEAR_PROJECT_MILESTONE_FIELDS } from "../lib/fragments" import { LINEAR_READ_ACCOUNT_OPTIONS, LINEAR_WRITE_ACCOUNT_OPTIONS, } from "../lib/scopes" const MILESTONE_RESPONSE_SCHEMA = z.object({ projectMilestone: LINEAR_PROJECT_MILESTONE_SCHEMA, }) const MILESTONE_CONNECTION_SCHEMA = linearConnectionSchema( LINEAR_PROJECT_MILESTONE_SCHEMA, ) const MILESTONE_PAGE_SCHEMA = z.object({ pageInfo: LINEAR_PAGE_INFO_SCHEMA, projectMilestones: LINEAR_PROJECT_MILESTONE_SCHEMA.array(), }) const MILESTONE_MUTATION_SCHEMA = z.object({ projectMilestoneMutation: z.object({ projectMilestone: LINEAR_PROJECT_MILESTONE_SCHEMA, success: z.literal(true), }), }) const MILESTONE_DELETE_SCHEMA = z.object({ projectMilestoneMutation: z.object({ entityId: z.string(), success: z.literal(true), }), }) const MILESTONE_FIELDS_INPUT_SCHEMA = z.object({ /** Markdown milestone description, or `null` to clear it. */ description: z.string().nullable().optional(), /** Milestone name. */ name: z.string().min(1).optional(), /** Project containing the milestone. */ projectId: z.string().min(1).optional(), /** Relative order inside the project. */ sortOrder: z.number().optional(), /** Planned completion date, or `null` to clear it. */ targetDate: z.iso.date().nullable().optional(), }) /** Lists Linear project milestones. */ export const listLinearProjectMilestones = defineAction( "List Linear project milestones", ) .describe("Lists a cursor-paginated page of Linear project milestones.") .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 containing returned milestones. */ projectId: z.string().min(1).optional(), }), ) .output(MILESTONE_PAGE_SCHEMA) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => { const { projectMilestones } = await getLinearApi(account.secret).graphql( `query LinearProjectMilestones( $first: Int! $after: String $includeArchived: Boolean $orderBy: PaginationOrderBy! $filter: ProjectMilestoneFilter ) { projectMilestones( first: $first after: $after includeArchived: $includeArchived orderBy: $orderBy filter: $filter ) { nodes { ${LINEAR_PROJECT_MILESTONE_FIELDS} } pageInfo { hasNextPage hasPreviousPage startCursor endCursor } } }`, { responseSchema: z.object({ projectMilestones: MILESTONE_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: projectMilestones.pageInfo!, projectMilestones: projectMilestones.nodes, } }) /** Gets one Linear project milestone by ID. */ export const getLinearProjectMilestone = defineAction( "Get Linear project milestone", ) .describe("Gets one Linear project milestone by ID.") .account("linear", LINEAR_READ_ACCOUNT_OPTIONS) .input(z.object({ projectMilestoneId: z.string().min(1) })) .output(LINEAR_PROJECT_MILESTONE_SCHEMA) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => getLinearApi(account.secret) .graphql( `query LinearProjectMilestone($id: String!) { projectMilestone(id: $id) { ${LINEAR_PROJECT_MILESTONE_FIELDS} } }`, { responseSchema: MILESTONE_RESPONSE_SCHEMA, variables: { id: input.projectMilestoneId }, }, ) .then(({ projectMilestone }) => projectMilestone), ) /** Creates a Linear project milestone. */ export const createLinearProjectMilestone = defineAction( "Create Linear project milestone", ) .describe("Creates a dated milestone inside a Linear project.") .account("linear", LINEAR_WRITE_ACCOUNT_OPTIONS) .input( MILESTONE_FIELDS_INPUT_SCHEMA.extend({ name: z.string().min(1), projectId: z.string().min(1), }), ) .output(LINEAR_PROJECT_MILESTONE_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { const { projectMilestoneMutation } = await getLinearApi( account.secret, ).graphql( `mutation LinearProjectMilestoneCreate( $input: ProjectMilestoneCreateInput! ) { projectMilestoneMutation: projectMilestoneCreate(input: $input) { success projectMilestone { ${LINEAR_PROJECT_MILESTONE_FIELDS} } } }`, { responseSchema: MILESTONE_MUTATION_SCHEMA, variables: { input } }, ) return projectMilestoneMutation.projectMilestone }) /** Updates a Linear project milestone. */ export const updateLinearProjectMilestone = defineAction( "Update Linear project milestone", ) .describe("Updates a Linear project milestone's details or placement.") .account("linear", LINEAR_WRITE_ACCOUNT_OPTIONS) .input( MILESTONE_FIELDS_INPUT_SCHEMA.extend({ projectMilestoneId: z.string().min(1), }), ) .output(LINEAR_PROJECT_MILESTONE_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { const { projectMilestoneId, ...update } = input const { projectMilestoneMutation } = await getLinearApi( account.secret, ).graphql( `mutation LinearProjectMilestoneUpdate( $id: String! $input: ProjectMilestoneUpdateInput! ) { projectMilestoneMutation: projectMilestoneUpdate( id: $id input: $input ) { success projectMilestone { ${LINEAR_PROJECT_MILESTONE_FIELDS} } } }`, { responseSchema: MILESTONE_MUTATION_SCHEMA, variables: { id: projectMilestoneId, input: update }, }, ) return projectMilestoneMutation.projectMilestone }) /** Permanently deletes a Linear project milestone. */ export const deleteLinearProjectMilestone = defineAction( "Delete Linear project milestone", ) .describe("Permanently deletes a Linear project milestone by ID.") .account("linear", LINEAR_WRITE_ACCOUNT_OPTIONS) .input(z.object({ projectMilestoneId: z.string().min(1) })) .output(z.object({ projectMilestoneId: z.string() })) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { const { projectMilestoneMutation } = await getLinearApi( account.secret, ).graphql( `mutation LinearProjectMilestoneDelete($id: String!) { projectMilestoneMutation: projectMilestoneDelete(id: $id) { success entityId } }`, { responseSchema: MILESTONE_DELETE_SCHEMA, variables: { id: input.projectMilestoneId }, }, ) return { projectMilestoneId: projectMilestoneMutation.entityId } })