fix(ui): expose all automation schedule filters (#126962)

* fix(ui): expose all automation schedule filters

Amp-Thread-ID: https://ampcode.com/threads/T-01a021f4-b547-7788-a916-d4a94cbd3e3b

* refactor(ui): derive cron filters from protocol

Amp-Thread-ID: https://ampcode.com/threads/T-01a021f4-b547-7788-a916-d4a94cbd3e3b

---------

Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
Peter Steinberger
2026-08-20 21:10:49 -07:00
committed by GitHub
parent e2a48d4b70
commit 2acfc47b7f
5 changed files with 33 additions and 26 deletions
+2 -15
View File
@@ -2,6 +2,7 @@
import { parseBoolean } from "@openclaw/normalization-core/boolean-coercion";
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
import {
type CronListParams,
ErrorCodes,
errorShape,
GatewayErrorDetailCodes,
@@ -488,21 +489,7 @@ export const cronHandlers: GatewayRequestHandlers = {
if (!assertValidParams(params, validateCronListParams, "cron.list", respond)) {
return;
}
const p = params as {
includeDisabled?: boolean;
limit?: number;
offset?: number;
query?: string;
enabled?: "all" | "enabled" | "disabled";
scheduleKind?: "all" | "at" | "every" | "cron";
lastRunStatus?: "all" | "ok" | "error" | "skipped" | "unknown";
trigger?: "all" | "conditional" | "unconditional";
sortBy?: "nextRunAtMs" | "updatedAtMs" | "name";
sortDir?: "asc" | "desc";
agentId?: string;
compact?: boolean;
includeDeliveryPreviews?: boolean;
};
const p = params as CronListParams;
const callerScope = readCronCallerScope(client);
const requestedAgentId = p.agentId ? normalizeAgentId(p.agentId) : undefined;
if (callerScope && requestedAgentId && requestedAgentId !== callerScope.agentId) {
+1
View File
@@ -555,6 +555,7 @@ export type {
export type CronRunStatus = NonNullable<ProtocolCronRunLogEntry["status"]>;
export type CronDeliveryStatus = NonNullable<ProtocolCronRunLogEntry["deliveryStatus"]>;
export type CronJobsEnabledFilter = NonNullable<CronListParams["enabled"]>;
export type CronJobsScheduleKindFilter = NonNullable<CronListParams["scheduleKind"]>;
export type CronJobsTriggerFilter = NonNullable<CronListParams["trigger"]>;
export type CronJobsSortBy = NonNullable<CronListParams["sortBy"]>;
export type CronRunScope = NonNullable<CronRunsParams["scope"]>;
+1 -1
View File
@@ -7,6 +7,7 @@ import type {
CronJob,
CronDeliveryStatus,
CronJobsEnabledFilter,
CronJobsScheduleKindFilter,
CronJobsTriggerFilter,
CronJobsListResult,
CronJobsSortBy,
@@ -191,7 +192,6 @@ export type CronFieldKey =
export type CronFieldErrors = Partial<Record<CronFieldKey, string>>;
export type CronJobsScheduleKindFilter = "all" | "at" | "every" | "cron" | "on-exit" | "stream";
export type CronJobsLastStatusFilter = "all" | CronRunStatus | "unknown";
type CronRunsLoadStatus = "ok" | "error" | "skipped";
+15 -3
View File
@@ -122,9 +122,21 @@ describe("cron view list pane", () => {
'[data-test-id="cron-jobs-schedule-filter"]',
HTMLSelectElement,
);
scheduleFilter.value = "cron";
scheduleFilter.dispatchEvent(new Event("change", { bubbles: true }));
expect(onJobsFiltersChange).toHaveBeenCalledWith({ cronJobsScheduleKindFilter: "cron" });
expect(Array.from(scheduleFilter.options, (option) => option.value)).toEqual([
"all",
"at",
"every",
"cron",
"on-exit",
"stream",
]);
for (const scheduleKind of ["on-exit", "stream"] as const) {
scheduleFilter.value = scheduleKind;
scheduleFilter.dispatchEvent(new Event("change", { bubbles: true }));
expect(onJobsFiltersChange).toHaveBeenCalledWith({
cronJobsScheduleKindFilter: scheduleKind,
});
}
const lastStatusFilter = getElement(
container,
+14 -7
View File
@@ -17,6 +17,7 @@ import type {
CronStatus,
CronDeliveryStatus,
CronJobsEnabledFilter,
CronJobsScheduleKindFilter,
CronJobsTriggerFilter,
CronRunsStatusValue,
CronJobsSortBy,
@@ -51,7 +52,6 @@ import type {
CronFieldKey,
CronFormState,
CronJobsLastStatusFilter,
CronJobsScheduleKindFilter,
} from "../../lib/cron/index.ts";
import { formatUiExternalText } from "../../lib/format-error.ts";
import { formatRelativeTimestamp, formatMs } from "../../lib/format.ts";
@@ -448,6 +448,15 @@ const ENABLED_TABS: Array<{ value: CronJobsEnabledFilter; labelKey: string }> =
{ value: "disabled", labelKey: "cron.tabs.paused" },
];
const SCHEDULE_KIND_FILTER_LABELS: Record<CronJobsScheduleKindFilter, string> = {
all: "cron.jobs.all",
at: "cron.form.at",
every: "cron.form.every",
cron: "cron.form.cronOption",
"on-exit": "cron.form.repeatOnExit",
stream: "cron.form.repeatStream",
};
function renderListView(props: CronProps) {
const hasAdvancedJobsFilters =
props.jobsScheduleKindFilter !== "all" ||
@@ -646,12 +655,10 @@ function renderJobsFilterPopover(props: CronProps, active: boolean) {
label: t("cron.jobs.schedule"),
value: props.jobsScheduleKindFilter,
testId: "cron-jobs-schedule-filter",
options: [
{ value: "all", label: t("cron.jobs.all") },
{ value: "at", label: t("cron.form.at") },
{ value: "every", label: t("cron.form.every") },
{ value: "cron", label: t("cron.form.cronOption") },
],
options: Object.entries(SCHEDULE_KIND_FILTER_LABELS).map(([value, labelKey]) => ({
value,
label: t(labelKey),
})),
})}
${renderJobsFilter(props, "cronJobsLastStatusFilter", {
label: t("cron.jobs.lastRun"),