feat (acp): custom methods for managing scheduler (#9972)

This commit is contained in:
Lifei Zhou
2026-06-25 09:11:35 +10:00
committed by GitHub
parent 09998b12cf
commit eea698945c
28 changed files with 2050 additions and 484 deletions
@@ -991,8 +991,8 @@ export default function RecipesView() {
id: scheduleRecipeManifest.id,
source: '',
cron: scheduleRecipeManifest.schedule_cron,
last_run: null,
currently_running: false,
lastRun: null,
currentlyRunning: false,
paused: false,
}
: null
@@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react';
import { ScheduledJob } from '../../schedule';
import type { ScheduledJobDto } from '@aaif/goose-sdk';
import { errorMessage } from '../../utils/conversionUtils';
import { defineMessages, useIntl } from '../../i18n';
import {
@@ -61,7 +61,7 @@ const i18n = defineMessages({
});
interface CronPickerProps {
schedule: ScheduledJob | null;
schedule: ScheduledJobDto | null;
onChange: (cron: string) => void;
isValid: (valid: boolean) => void;
}
@@ -1,29 +1,28 @@
import React, { useState, useEffect, useCallback } from 'react';
import type { ScheduledJobDto, SessionInfo } from '@aaif/goose-sdk';
import { Button } from '../ui/button';
import { ScrollArea } from '../ui/scroll-area';
import BackButton from '../ui/BackButton';
import { Card } from '../ui/card';
import {
getScheduleSessions,
runScheduleNow,
pauseSchedule,
unpauseSchedule,
updateSchedule,
listSchedules,
killRunningJob,
inspectRunningJob,
ScheduledJob,
} from '../../schedule';
import SessionHistoryView from '../sessions/SessionHistoryView';
acpListScheduleSessions,
acpRunScheduleNow,
acpPauseSchedule,
acpUnpauseSchedule,
acpUpdateSchedule,
acpListSchedules,
acpKillRunningJob,
acpInspectRunningJob,
} from '../../acp/schedules';
import { ScheduleModal, NewSchedulePayload } from './ScheduleModal';
import { toastError, toastSuccess } from '../../toasts';
import { Loader2, Pause, Play, Edit, Square, Eye } from 'lucide-react';
import cronstrue from 'cronstrue';
import { formatToLocalDateWithTimezone } from '../../utils/date';
import { getSession, Session } from '../../api';
import { trackScheduleRunNow, getErrorType } from '../../utils/analytics';
import { errorMessage } from '../../utils/conversionUtils';
import { defineMessages, useIntl } from '../../i18n';
import { useNavigation } from '../../hooks/useNavigation';
const i18n = defineMessages({
scheduleNotFound: { id: 'scheduleDetailView.scheduleNotFound', defaultMessage: 'Schedule Not Found' },
@@ -57,12 +56,11 @@ const i18n = defineMessages({
created: { id: 'scheduleDetailView.created', defaultMessage: 'Created: {date}' },
messages: { id: 'scheduleDetailView.messages', defaultMessage: 'Messages: {count}' },
dir: { id: 'scheduleDetailView.dir', defaultMessage: 'Dir: {path}' },
tokens: { id: 'scheduleDetailView.tokens', defaultMessage: 'Tokens: {count}' },
idLabel: { id: 'scheduleDetailView.idLabel', defaultMessage: 'ID:' },
jobCancelled: { id: 'scheduleDetailView.jobCancelled', defaultMessage: 'Job Cancelled' },
jobCancelledMsg: { id: 'scheduleDetailView.jobCancelledMsg', defaultMessage: 'The job was cancelled while starting up.' },
scheduleTriggered: { id: 'scheduleDetailView.scheduleTriggered', defaultMessage: 'Schedule Triggered' },
newSession: { id: 'scheduleDetailView.newSession', defaultMessage: 'New session: {sessionId}' },
scheduleCompleted: { id: 'scheduleDetailView.scheduleCompleted', defaultMessage: 'Run completed' },
completedSession: { id: 'scheduleDetailView.completedSession', defaultMessage: 'Session: {sessionId}' },
runScheduleError: { id: 'scheduleDetailView.runScheduleError', defaultMessage: 'Run Schedule Error' },
scheduleUnpaused: { id: 'scheduleDetailView.scheduleUnpaused', defaultMessage: 'Schedule Unpaused' },
unpausedMsg: { id: 'scheduleDetailView.unpausedMsg', defaultMessage: 'Unpaused "{id}"' },
@@ -77,53 +75,48 @@ const i18n = defineMessages({
scheduleUpdated: { id: 'scheduleDetailView.scheduleUpdated', defaultMessage: 'Schedule Updated' },
updatedMsg: { id: 'scheduleDetailView.updatedMsg', defaultMessage: 'Updated "{id}"' },
updateScheduleError: { id: 'scheduleDetailView.updateScheduleError', defaultMessage: 'Update Schedule Error' },
failedToLoadSession: { id: 'scheduleDetailView.failedToLoadSession', defaultMessage: 'Failed to load session' },
scheduleNotFoundError: { id: 'scheduleDetailView.scheduleNotFoundError', defaultMessage: 'Schedule not found' },
});
interface ScheduleSessionMeta {
id: string;
name: string;
createdAt: string;
workingDir?: string;
scheduleId?: string | null;
messageCount?: number;
totalTokens?: number | null;
inputTokens?: number | null;
outputTokens?: number | null;
accumulatedTotalTokens?: number | null;
accumulatedInputTokens?: number | null;
accumulatedOutputTokens?: number | null;
}
interface ScheduleDetailViewProps {
scheduleId: string | null;
onNavigateBack: () => void;
}
function sessionMeta(session: SessionInfo): Record<string, unknown> {
return typeof session._meta === 'object' && session._meta !== null ? session._meta : {};
}
function metaString(session: SessionInfo, key: string): string | undefined {
const value = sessionMeta(session)[key];
return typeof value === 'string' ? value : undefined;
}
function metaNumber(session: SessionInfo, key: string): number | undefined {
const value = sessionMeta(session)[key];
return typeof value === 'number' ? value : undefined;
}
const ScheduleDetailView: React.FC<ScheduleDetailViewProps> = ({ scheduleId, onNavigateBack }) => {
const intl = useIntl();
const [sessions, setSessions] = useState<ScheduleSessionMeta[]>([]);
const setView = useNavigation();
const [sessions, setSessions] = useState<SessionInfo[]>([]);
const [isLoadingSessions, setIsLoadingSessions] = useState(false);
const [sessionsError, setSessionsError] = useState<string | null>(null);
const [scheduleDetails, setScheduleDetails] = useState<ScheduledJob | null>(null);
const [scheduleDetails, setScheduleDetails] = useState<ScheduledJobDto | null>(null);
const [isLoadingSchedule, setIsLoadingSchedule] = useState(false);
const [scheduleError, setScheduleError] = useState<string | null>(null);
const [isActionLoading, setIsActionLoading] = useState(false);
const [selectedSession, setSelectedSession] = useState<Session | null>(null);
const [isLoadingSession, setIsLoadingSession] = useState(false);
const [sessionError, setSessionError] = useState<string | null>(null);
const [isModalOpen, setIsModalOpen] = useState(false);
const fetchSessions = async (sId: string) => {
setIsLoadingSessions(true);
setSessionsError(null);
try {
const data = await getScheduleSessions(sId, 20);
const data = await acpListScheduleSessions(sId, 20);
setSessions(data);
} catch (err) {
setSessionsError(errorMessage(err, 'Failed to fetch sessions'));
@@ -136,7 +129,7 @@ const ScheduleDetailView: React.FC<ScheduleDetailViewProps> = ({ scheduleId, onN
setIsLoadingSchedule(true);
setScheduleError(null);
try {
const allSchedules = await listSchedules();
const allSchedules = await acpListSchedules();
const schedule = allSchedules.find((s) => s.id === sId);
if (schedule) {
setScheduleDetails(schedule);
@@ -151,22 +144,27 @@ const ScheduleDetailView: React.FC<ScheduleDetailViewProps> = ({ scheduleId, onN
}, [intl]);
useEffect(() => {
if (scheduleId && !selectedSession) {
if (scheduleId) {
fetchSessions(scheduleId);
fetchSchedule(scheduleId);
}
}, [scheduleId, selectedSession, fetchSchedule]);
}, [scheduleId, fetchSchedule]);
const openSession = useCallback((sessionId: string) => {
setView('pair', {
disableAnimation: true,
resumeSessionId: sessionId,
});
}, [setView]);
const handleRunNow = async () => {
if (!scheduleId) return;
setIsActionLoading(true);
try {
const newSessionId = await runScheduleNow(scheduleId);
const result = await acpRunScheduleNow(scheduleId);
trackScheduleRunNow(true);
if (newSessionId === 'CANCELLED') {
toastSuccess({ title: intl.formatMessage(i18n.jobCancelled), msg: intl.formatMessage(i18n.jobCancelledMsg) });
} else {
toastSuccess({ title: intl.formatMessage(i18n.scheduleTriggered), msg: intl.formatMessage(i18n.newSession, { sessionId: newSessionId }) });
if (result.status === 'completed' && result.sessionId) {
toastSuccess({ title: intl.formatMessage(i18n.scheduleCompleted), msg: intl.formatMessage(i18n.completedSession, { sessionId: result.sessionId }) });
}
await fetchSessions(scheduleId);
await fetchSchedule(scheduleId);
@@ -187,10 +185,10 @@ const ScheduleDetailView: React.FC<ScheduleDetailViewProps> = ({ scheduleId, onN
setIsActionLoading(true);
try {
if (scheduleDetails.paused) {
await unpauseSchedule(scheduleId);
await acpUnpauseSchedule(scheduleId);
toastSuccess({ title: intl.formatMessage(i18n.scheduleUnpaused), msg: intl.formatMessage(i18n.unpausedMsg, { id: scheduleId }) });
} else {
await pauseSchedule(scheduleId);
await acpPauseSchedule(scheduleId);
toastSuccess({ title: intl.formatMessage(i18n.schedulePaused), msg: intl.formatMessage(i18n.pausedMsg, { id: scheduleId }) });
}
await fetchSchedule(scheduleId);
@@ -209,7 +207,7 @@ const ScheduleDetailView: React.FC<ScheduleDetailViewProps> = ({ scheduleId, onN
if (!scheduleId) return;
setIsActionLoading(true);
try {
const result = await killRunningJob(scheduleId);
const result = await acpKillRunningJob(scheduleId);
toastSuccess({ title: intl.formatMessage(i18n.jobKilled), msg: result.message });
await fetchSchedule(scheduleId);
} catch (err) {
@@ -227,7 +225,7 @@ const ScheduleDetailView: React.FC<ScheduleDetailViewProps> = ({ scheduleId, onN
if (!scheduleId) return;
setIsActionLoading(true);
try {
const result = await inspectRunningJob(scheduleId);
const result = await acpInspectRunningJob(scheduleId);
if (result.sessionId) {
const duration = result.runningDurationSeconds
? `${Math.floor(result.runningDurationSeconds / 60)}m ${result.runningDurationSeconds % 60}s`
@@ -254,7 +252,7 @@ const ScheduleDetailView: React.FC<ScheduleDetailViewProps> = ({ scheduleId, onN
if (!scheduleId) return;
setIsActionLoading(true);
try {
await updateSchedule(scheduleId, payload as string);
await acpUpdateSchedule(scheduleId, payload as string);
toastSuccess({ title: intl.formatMessage(i18n.scheduleUpdated), msg: intl.formatMessage(i18n.updatedMsg, { id: scheduleId }) });
await fetchSchedule(scheduleId);
setIsModalOpen(false);
@@ -269,37 +267,6 @@ const ScheduleDetailView: React.FC<ScheduleDetailViewProps> = ({ scheduleId, onN
}
};
const loadSession = async (sessionId: string) => {
setIsLoadingSession(true);
setSessionError(null);
try {
const response = await getSession<true>({
path: { session_id: sessionId },
throwOnError: true,
});
setSelectedSession(response.data);
} catch (err) {
const msg = errorMessage(err, 'Failed to load session');
setSessionError(msg);
toastError({ title: intl.formatMessage(i18n.failedToLoadSession), msg });
} finally {
setIsLoadingSession(false);
}
};
if (selectedSession) {
return (
<SessionHistoryView
session={selectedSession}
isLoading={isLoadingSession}
error={sessionError}
onBack={() => setSelectedSession(null)}
onRetry={() => loadSession(selectedSession.id)}
showActionButtons={true}
/>
);
}
if (!scheduleId) {
return (
<div className="h-screen w-full flex flex-col items-center justify-center bg-white dark:bg-gray-900 text-text-primary p-8">
@@ -352,7 +319,7 @@ const ScheduleDetailView: React.FC<ScheduleDetailViewProps> = ({ scheduleId, onN
{scheduleDetails.id}
</h3>
<div className="mt-2 md:mt-0 flex items-center gap-2">
{scheduleDetails.currently_running && (
{scheduleDetails.currentlyRunning && (
<div className="text-sm text-green-500 dark:text-green-400 font-semibold flex items-center">
<span className="inline-block w-2 h-2 bg-green-500 dark:bg-green-400 rounded-full mr-1 animate-pulse"></span>
{intl.formatMessage(i18n.currentlyRunning)}
@@ -377,18 +344,18 @@ const ScheduleDetailView: React.FC<ScheduleDetailViewProps> = ({ scheduleId, onN
</p>
<p className="text-sm text-text-primary">
<span className="font-semibold">{intl.formatMessage(i18n.lastRun)}</span>{' '}
{formatToLocalDateWithTimezone(scheduleDetails.last_run)}
{formatToLocalDateWithTimezone(scheduleDetails.lastRun)}
</p>
{scheduleDetails.currently_running && scheduleDetails.current_session_id && (
{scheduleDetails.currentlyRunning && scheduleDetails.currentSessionId && (
<p className="text-sm text-text-primary">
<span className="font-semibold">{intl.formatMessage(i18n.currentSession)}</span>{' '}
{scheduleDetails.current_session_id}
{scheduleDetails.currentSessionId}
</p>
)}
{scheduleDetails.currently_running && scheduleDetails.process_start_time && (
{scheduleDetails.currentlyRunning && scheduleDetails.jobStartTime && (
<p className="text-sm text-text-primary">
<span className="font-semibold">{intl.formatMessage(i18n.processStarted)}</span>{' '}
{formatToLocalDateWithTimezone(scheduleDetails.process_start_time)}
{formatToLocalDateWithTimezone(scheduleDetails.jobStartTime)}
</p>
)}
</div>
@@ -401,13 +368,13 @@ const ScheduleDetailView: React.FC<ScheduleDetailViewProps> = ({ scheduleId, onN
<div className="flex flex-col md:flex-row gap-2">
<Button
onClick={handleRunNow}
disabled={isActionLoading || scheduleDetails?.currently_running}
disabled={isActionLoading || scheduleDetails?.currentlyRunning}
className="w-full md:w-auto"
>
{intl.formatMessage(i18n.runScheduleNow)}
</Button>
{scheduleDetails && !scheduleDetails.currently_running && (
{scheduleDetails && !scheduleDetails.currentlyRunning && (
<>
<Button
onClick={() => setIsModalOpen(true)}
@@ -443,7 +410,7 @@ const ScheduleDetailView: React.FC<ScheduleDetailViewProps> = ({ scheduleId, onN
</>
)}
{scheduleDetails?.currently_running && (
{scheduleDetails?.currentlyRunning && (
<>
<Button
onClick={handleInspect}
@@ -467,7 +434,7 @@ const ScheduleDetailView: React.FC<ScheduleDetailViewProps> = ({ scheduleId, onN
)}
</div>
{scheduleDetails?.currently_running && (
{scheduleDetails?.currentlyRunning && (
<p className="text-sm text-amber-600 dark:text-amber-400 mt-2">
{intl.formatMessage(i18n.cannotModifyRunning)}
</p>
@@ -496,45 +463,46 @@ const ScheduleDetailView: React.FC<ScheduleDetailViewProps> = ({ scheduleId, onN
{sessions.length > 0 && (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{sessions.map((session) => (
<Card
key={session.id}
className="p-4 bg-background-primary shadow cursor-pointer hover:shadow-lg transition-shadow duration-200"
onClick={() => loadSession(session.id)}
>
<h3
className="text-sm font-semibold text-text-primary truncate"
title={session.name || session.id}
{sessions.map((session) => {
const sessionId = String(session.sessionId);
const sessionName = session.title ?? sessionId;
const createdAt = metaString(session, 'createdAt') ?? session.updatedAt;
const messageCount = metaNumber(session, 'messageCount');
return (
<Card
key={sessionId}
className="p-4 bg-background-primary shadow cursor-pointer hover:shadow-lg transition-shadow duration-200"
onClick={() => openSession(sessionId)}
>
{session.name || intl.formatMessage(i18n.sessionId, { id: session.id })}
</h3>
<p className="text-xs text-text-secondary mt-1">
{intl.formatMessage(i18n.created, { date: session.createdAt ? formatToLocalDateWithTimezone(session.createdAt) : 'N/A' })}
</p>
{session.messageCount !== undefined && (
<p className="text-xs text-text-secondary mt-1">
{intl.formatMessage(i18n.messages, { count: session.messageCount })}
</p>
)}
{session.workingDir && (
<p
className="text-xs text-text-secondary mt-1 truncate"
title={session.workingDir}
<h3
className="text-sm font-semibold text-text-primary truncate"
title={sessionName}
>
{intl.formatMessage(i18n.dir, { path: session.workingDir })}
{sessionName || intl.formatMessage(i18n.sessionId, { id: sessionId })}
</h3>
<p className="text-xs text-text-secondary mt-1">
{intl.formatMessage(i18n.created, { date: createdAt ? formatToLocalDateWithTimezone(createdAt) : 'N/A' })}
</p>
)}
{session.accumulatedTotalTokens !== undefined &&
session.accumulatedTotalTokens !== null && (
{messageCount !== undefined && (
<p className="text-xs text-text-secondary mt-1">
{intl.formatMessage(i18n.tokens, { count: session.accumulatedTotalTokens })}
{intl.formatMessage(i18n.messages, { count: messageCount })}
</p>
)}
<p className="text-xs text-text-secondary mt-1">
{intl.formatMessage(i18n.idLabel)} <span className="font-mono">{session.id}</span>
</p>
</Card>
))}
{session.cwd && (
<p
className="text-xs text-text-secondary mt-1 truncate"
title={session.cwd}
>
{intl.formatMessage(i18n.dir, { path: session.cwd })}
</p>
)}
<p className="text-xs text-text-secondary mt-1">
{intl.formatMessage(i18n.idLabel)} <span className="font-mono">{sessionId}</span>
</p>
</Card>
);
})}
</div>
)}
</section>
@@ -1,8 +1,8 @@
import React, { useState, useEffect, FormEvent, useCallback } from 'react';
import type { ScheduledJobDto } from '@aaif/goose-sdk';
import { Card } from '../ui/card';
import { Button } from '../ui/button';
import { Input } from '../ui/input';
import { ScheduledJob } from '../../schedule';
import { CronPicker } from './CronPicker';
import { Recipe, parseDeeplink, parseRecipeFromFile } from '../../recipe';
import { getStorageDirectory } from '../../recipe/recipe_management';
@@ -47,7 +47,7 @@ interface ScheduleModalProps {
isOpen: boolean;
onClose: () => void;
onSubmit: (payload: NewSchedulePayload | string) => Promise<void>;
schedule: ScheduledJob | null;
schedule: ScheduledJobDto | null;
isLoadingExternally: boolean;
apiErrorExternally: string | null;
initialDeepLink: string | null;
@@ -1,16 +1,16 @@
import React, { useState, useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import type { ScheduledJobDto } from '@aaif/goose-sdk';
import {
listSchedules,
createSchedule,
deleteSchedule,
pauseSchedule,
unpauseSchedule,
updateSchedule,
killRunningJob,
inspectRunningJob,
ScheduledJob,
} from '../../schedule';
acpListSchedules,
acpCreateSchedule,
acpDeleteSchedule,
acpPauseSchedule,
acpUnpauseSchedule,
acpUpdateSchedule,
acpKillRunningJob,
acpInspectRunningJob,
} from '../../acp/schedules';
import { ScrollArea } from '../ui/scroll-area';
import { Card } from '../ui/card';
import { Button } from '../ui/button';
@@ -67,9 +67,9 @@ interface SchedulesViewProps {
}
const ScheduleCard: React.FC<{
job: ScheduledJob;
job: ScheduledJobDto;
onNavigateToDetail: (id: string) => void;
onEdit: (job: ScheduledJob) => void;
onEdit: (job: ScheduledJobDto) => void;
onPause: (id: string) => void;
onUnpause: (id: string) => void;
onKill: (id: string) => void;
@@ -95,7 +95,7 @@ const ScheduleCard: React.FC<{
readableCron = job.cron;
}
const formattedLastRun = formatToLocalDateWithTimezone(job.last_run);
const formattedLastRun = formatToLocalDateWithTimezone(job.lastRun);
return (
<Card
@@ -108,7 +108,7 @@ const ScheduleCard: React.FC<{
<h3 className="text-base truncate max-w-[50vw]" title={job.id}>
{job.id}
</h3>
{job.currently_running && (
{job.currentlyRunning && (
<span className="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300">
<span className="inline-block w-2 h-2 bg-green-500 rounded-full mr-1 animate-pulse"></span>
{intl.formatMessage(i18n.running)}
@@ -130,7 +130,7 @@ const ScheduleCard: React.FC<{
</div>
<div className="flex items-center gap-2 shrink-0">
{!job.currently_running && (
{!job.currentlyRunning && (
<>
<Button
onClick={(e) => {
@@ -173,7 +173,7 @@ const ScheduleCard: React.FC<{
</Button>
</>
)}
{job.currently_running && (
{job.currentlyRunning && (
<>
<Button
onClick={(e) => {
@@ -224,13 +224,13 @@ const ScheduleCard: React.FC<{
const SchedulesView: React.FC<SchedulesViewProps> = ({ onClose: _onClose }) => {
const intl = useIntl();
const location = useLocation();
const [schedules, setSchedules] = useState<ScheduledJob[]>([]);
const [schedules, setSchedules] = useState<ScheduledJobDto[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const [apiError, setApiError] = useState<string | null>(null);
const [submitApiError, setSubmitApiError] = useState<string | null>(null);
const [isModalOpen, setIsModalOpen] = useState(false);
const [editingSchedule, setEditingSchedule] = useState<ScheduledJob | null>(null);
const [editingSchedule, setEditingSchedule] = useState<ScheduledJobDto | null>(null);
const [isRefreshing, setIsRefreshing] = useState(false);
const [pendingDeepLink, setPendingDeepLink] = useState<string | null>(null);
const [actionsInProgress, setActionsInProgress] = useState<Set<string>>(new Set());
@@ -240,7 +240,7 @@ const SchedulesView: React.FC<SchedulesViewProps> = ({ onClose: _onClose }) => {
setIsLoading(true);
setApiError(null);
try {
const fetchedSchedules = await listSchedules();
const fetchedSchedules = await acpListSchedules();
setSchedules(fetchedSchedules);
} catch (error) {
console.error('Failed to fetch schedules:', error);
@@ -289,14 +289,14 @@ const SchedulesView: React.FC<SchedulesViewProps> = ({ onClose: _onClose }) => {
setSubmitApiError(null);
try {
if (editingSchedule) {
await updateSchedule(editingSchedule.id, payload as string);
await acpUpdateSchedule(editingSchedule.id, payload as string);
toastSuccess({
title: intl.formatMessage(i18n.scheduleUpdated),
msg: intl.formatMessage(i18n.scheduleUpdatedMsg, { id: editingSchedule.id }),
});
} else {
const newPayload = payload as NewSchedulePayload;
await createSchedule(newPayload);
await acpCreateSchedule(newPayload);
const sourceType = pendingDeepLink ? 'deeplink' : 'file';
trackScheduleCreated(sourceType, true);
}
@@ -325,7 +325,7 @@ const SchedulesView: React.FC<SchedulesViewProps> = ({ onClose: _onClose }) => {
setApiError(null);
try {
await deleteSchedule(id);
await acpDeleteSchedule(id);
trackScheduleDeleted(true);
await fetchSchedules();
} catch (error) {
@@ -347,7 +347,7 @@ const SchedulesView: React.FC<SchedulesViewProps> = ({ onClose: _onClose }) => {
setApiError(null);
try {
await pauseSchedule(id);
await acpPauseSchedule(id);
toastSuccess({
title: intl.formatMessage(i18n.schedulePaused),
msg: intl.formatMessage(i18n.schedulePausedMsg, { id }),
@@ -375,7 +375,7 @@ const SchedulesView: React.FC<SchedulesViewProps> = ({ onClose: _onClose }) => {
setApiError(null);
try {
await unpauseSchedule(id);
await acpUnpauseSchedule(id);
toastSuccess({
title: intl.formatMessage(i18n.scheduleUnpaused),
msg: intl.formatMessage(i18n.scheduleUnpausedMsg, { id }),
@@ -403,7 +403,7 @@ const SchedulesView: React.FC<SchedulesViewProps> = ({ onClose: _onClose }) => {
setApiError(null);
try {
const result = await killRunningJob(id);
const result = await acpKillRunningJob(id);
toastSuccess({
title: intl.formatMessage(i18n.jobKilled),
msg: result.message,
@@ -431,7 +431,7 @@ const SchedulesView: React.FC<SchedulesViewProps> = ({ onClose: _onClose }) => {
setApiError(null);
try {
const result = await inspectRunningJob(id);
const result = await acpInspectRunningJob(id);
if (result.sessionId) {
const duration = result.runningDurationSeconds
? `${Math.floor(result.runningDurationSeconds / 60)}m ${result.runningDurationSeconds % 60}s`
@@ -1,9 +1,9 @@
import { render, screen, type RenderOptions, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { describe, expect, it, vi } from 'vitest';
import type { ScheduledJobDto } from '@aaif/goose-sdk';
import { IntlTestWrapper } from '../../../i18n/test-utils';
import { CronPicker } from '../CronPicker';
import type { ScheduledJob } from '../../../schedule';
const renderWithIntl = (ui: React.ReactElement, options?: RenderOptions) =>
render(ui, { wrapper: IntlTestWrapper, ...options });
@@ -13,10 +13,12 @@ const getLastCron = (onChange: ReturnType<typeof vi.fn>) => {
return calls[calls.length - 1]?.[0];
};
const scheduledJob = (cron: string): ScheduledJob => ({
const scheduledJob = (cron: string): ScheduledJobDto => ({
id: 'quarterly-report',
source: 'dummy.yaml',
cron,
currentlyRunning: false,
paused: false,
});
describe('CronPicker', () => {