diff --git a/.gitignore b/.gitignore index 2c81080d953..bce6f530372 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Logs logs +!packages/cli/src/commands/automations/logs *.log npm-debug.log* yarn-debug.log* diff --git a/packages/cli/src/commands/automations/create/command.ts b/packages/cli/src/commands/automations/create/command.ts index 60eb08c1e7d..bdfc157b3b1 100644 --- a/packages/cli/src/commands/automations/create/command.ts +++ b/packages/cli/src/commands/automations/create/command.ts @@ -75,8 +75,8 @@ export default command({ throw new Error("Provide --prompt or --prompt-file "); } - if (!options.project) { - throw new Error("Provide --project (required)"); + if (!options.project && !options.workspace) { + throw new Error("Provide --project or --workspace"); } const agentConfig = options.agentConfigFile @@ -88,8 +88,8 @@ export default command({ prompt, agentConfig, targetHostId: options.host ?? null, - v2ProjectId: options.project, - v2WorkspaceId: options.workspace ?? null, + v2ProjectId: options.project ?? undefined, + v2WorkspaceId: options.workspace ?? undefined, rrule: options.rrule, dtstart: options.dtstart ? new Date(options.dtstart) : undefined, timezone: options.timezone ?? DEFAULT_TIMEZONE, diff --git a/packages/cli/src/commands/automations/logs/command.ts b/packages/cli/src/commands/automations/logs/command.ts new file mode 100644 index 00000000000..7a7dceaf9a3 --- /dev/null +++ b/packages/cli/src/commands/automations/logs/command.ts @@ -0,0 +1,41 @@ +import { number, positional, table } from "@superset/cli-framework"; +import { command } from "../../../lib/command"; +import { formatAutomationDate } from "../format"; + +export default command({ + description: "List recent runs of an automation", + args: [positional("id").required().desc("Automation id")], + options: { + limit: number() + .int() + .min(1) + .max(100) + .default(20) + .desc("Max runs to return (1-100)"), + }, + run: async ({ ctx, args, options }) => { + const automationId = args.id as string; + return ctx.api.automation.listRuns.query({ + automationId, + limit: options.limit, + }); + }, + display: (data) => + table( + (data as Record[]).map((row) => ({ + id: row.id, + status: row.status, + scheduledFor: formatAutomationDate( + row.scheduledFor as Date | string | null | undefined, + null, + ), + dispatchedAt: formatAutomationDate( + row.dispatchedAt as Date | string | null | undefined, + null, + ), + host: row.hostId ?? "—", + })), + ["id", "status", "scheduledFor", "dispatchedAt", "host"], + ["RUN ID", "STATUS", "SCHEDULED", "DISPATCHED", "HOST"], + ), +}); diff --git a/packages/cli/src/commands/automations/update/command.ts b/packages/cli/src/commands/automations/update/command.ts index ad6f2d042cb..12a7be13f84 100644 --- a/packages/cli/src/commands/automations/update/command.ts +++ b/packages/cli/src/commands/automations/update/command.ts @@ -51,6 +51,9 @@ export default command({ "Path to a JSON file with a full ResolvedAgentConfig (overrides --agent)", ), host: string().desc("New target host id"), + project: string().desc("New v2 project id"), + workspace: string().desc("New v2 workspace id"), + mcpScope: string().desc("Comma-separated MCP scope strings"), enabled: boolean().desc("Enable or pause the automation"), }, run: async ({ ctx, args, options }) => { @@ -69,6 +72,14 @@ export default command({ ? resolveDefaultAgentConfig(options.agent) : undefined; + const mcpScope = + options.mcpScope !== undefined + ? options.mcpScope + .split(",") + .map((s) => s.trim()) + .filter(Boolean) + : undefined; + const result = await ctx.api.automation.update.mutate({ id, name: options.name, @@ -77,6 +88,13 @@ export default command({ dtstart: options.dtstart ? new Date(options.dtstart) : undefined, agentConfig, ...(options.host !== undefined ? { targetHostId: options.host } : {}), + ...(options.project !== undefined + ? { v2ProjectId: options.project } + : {}), + ...(options.workspace !== undefined + ? { v2WorkspaceId: options.workspace } + : {}), + ...(mcpScope !== undefined ? { mcpScope } : {}), }); return { diff --git a/packages/cli/src/commands/tasks/update/command.ts b/packages/cli/src/commands/tasks/update/command.ts index fc739b897f1..3f8f0406b20 100644 --- a/packages/cli/src/commands/tasks/update/command.ts +++ b/packages/cli/src/commands/tasks/update/command.ts @@ -1,4 +1,5 @@ -import { CLIError, positional, string } from "@superset/cli-framework"; +import { CLIError, number, positional, string } from "@superset/cli-framework"; +import { isValid, parseISO } from "date-fns"; import { command } from "../../../lib/command"; export default command({ @@ -11,18 +12,47 @@ export default command({ .enum("urgent", "high", "medium", "low", "none") .desc("Priority"), assignee: string().desc("Assignee user ID"), + statusId: string().desc("Status ID"), + prUrl: string().desc("Linked PR URL"), + estimate: number().int().min(1).desc("Story-point estimate"), + dueDate: string().desc("Due date (ISO 8601)"), + labels: string().desc("Comma-separated labels"), }, run: async ({ ctx, args, options }) => { const idOrSlug = args.idOrSlug as string; const task = await ctx.api.task.byIdOrSlug.query(idOrSlug); if (!task) throw new CLIError(`Task not found: ${idOrSlug}`); + let dueDate: Date | undefined; + if (options.dueDate !== undefined) { + const parsed = parseISO(options.dueDate); + if (!isValid(parsed)) { + throw new CLIError( + `--due-date: invalid ISO 8601 date "${options.dueDate}"`, + ); + } + dueDate = parsed; + } + + const labels = + options.labels !== undefined + ? options.labels + .split(",") + .map((label) => label.trim()) + .filter(Boolean) + : undefined; + const result = await ctx.api.task.update.mutate({ id: task.id, title: options.title ?? undefined, description: options.description ?? undefined, priority: options.priority ?? undefined, assigneeId: options.assignee ?? undefined, + statusId: options.statusId ?? undefined, + prUrl: options.prUrl ?? undefined, + estimate: options.estimate ?? undefined, + dueDate, + labels, }); return {