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 { parseBoolean } from "@openclaw/normalization-core/boolean-coercion";
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
import { import {
type CronListParams,
ErrorCodes, ErrorCodes,
errorShape, errorShape,
GatewayErrorDetailCodes, GatewayErrorDetailCodes,
@@ -488,21 +489,7 @@ export const cronHandlers: GatewayRequestHandlers = {
if (!assertValidParams(params, validateCronListParams, "cron.list", respond)) { if (!assertValidParams(params, validateCronListParams, "cron.list", respond)) {
return; return;
} }
const p = params as { const p = params as CronListParams;
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 callerScope = readCronCallerScope(client); const callerScope = readCronCallerScope(client);
const requestedAgentId = p.agentId ? normalizeAgentId(p.agentId) : undefined; const requestedAgentId = p.agentId ? normalizeAgentId(p.agentId) : undefined;
if (callerScope && requestedAgentId && requestedAgentId !== callerScope.agentId) { if (callerScope && requestedAgentId && requestedAgentId !== callerScope.agentId) {
+1
View File
@@ -555,6 +555,7 @@ export type {
export type CronRunStatus = NonNullable<ProtocolCronRunLogEntry["status"]>; export type CronRunStatus = NonNullable<ProtocolCronRunLogEntry["status"]>;
export type CronDeliveryStatus = NonNullable<ProtocolCronRunLogEntry["deliveryStatus"]>; export type CronDeliveryStatus = NonNullable<ProtocolCronRunLogEntry["deliveryStatus"]>;
export type CronJobsEnabledFilter = NonNullable<CronListParams["enabled"]>; export type CronJobsEnabledFilter = NonNullable<CronListParams["enabled"]>;
export type CronJobsScheduleKindFilter = NonNullable<CronListParams["scheduleKind"]>;
export type CronJobsTriggerFilter = NonNullable<CronListParams["trigger"]>; export type CronJobsTriggerFilter = NonNullable<CronListParams["trigger"]>;
export type CronJobsSortBy = NonNullable<CronListParams["sortBy"]>; export type CronJobsSortBy = NonNullable<CronListParams["sortBy"]>;
export type CronRunScope = NonNullable<CronRunsParams["scope"]>; export type CronRunScope = NonNullable<CronRunsParams["scope"]>;
+1 -1
View File
@@ -7,6 +7,7 @@ import type {
CronJob, CronJob,
CronDeliveryStatus, CronDeliveryStatus,
CronJobsEnabledFilter, CronJobsEnabledFilter,
CronJobsScheduleKindFilter,
CronJobsTriggerFilter, CronJobsTriggerFilter,
CronJobsListResult, CronJobsListResult,
CronJobsSortBy, CronJobsSortBy,
@@ -191,7 +192,6 @@ export type CronFieldKey =
export type CronFieldErrors = Partial<Record<CronFieldKey, string>>; export type CronFieldErrors = Partial<Record<CronFieldKey, string>>;
export type CronJobsScheduleKindFilter = "all" | "at" | "every" | "cron" | "on-exit" | "stream";
export type CronJobsLastStatusFilter = "all" | CronRunStatus | "unknown"; export type CronJobsLastStatusFilter = "all" | CronRunStatus | "unknown";
type CronRunsLoadStatus = "ok" | "error" | "skipped"; 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"]', '[data-test-id="cron-jobs-schedule-filter"]',
HTMLSelectElement, HTMLSelectElement,
); );
scheduleFilter.value = "cron"; expect(Array.from(scheduleFilter.options, (option) => option.value)).toEqual([
scheduleFilter.dispatchEvent(new Event("change", { bubbles: true })); "all",
expect(onJobsFiltersChange).toHaveBeenCalledWith({ cronJobsScheduleKindFilter: "cron" }); "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( const lastStatusFilter = getElement(
container, container,
+14 -7
View File
@@ -17,6 +17,7 @@ import type {
CronStatus, CronStatus,
CronDeliveryStatus, CronDeliveryStatus,
CronJobsEnabledFilter, CronJobsEnabledFilter,
CronJobsScheduleKindFilter,
CronJobsTriggerFilter, CronJobsTriggerFilter,
CronRunsStatusValue, CronRunsStatusValue,
CronJobsSortBy, CronJobsSortBy,
@@ -51,7 +52,6 @@ import type {
CronFieldKey, CronFieldKey,
CronFormState, CronFormState,
CronJobsLastStatusFilter, CronJobsLastStatusFilter,
CronJobsScheduleKindFilter,
} from "../../lib/cron/index.ts"; } from "../../lib/cron/index.ts";
import { formatUiExternalText } from "../../lib/format-error.ts"; import { formatUiExternalText } from "../../lib/format-error.ts";
import { formatRelativeTimestamp, formatMs } from "../../lib/format.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" }, { 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) { function renderListView(props: CronProps) {
const hasAdvancedJobsFilters = const hasAdvancedJobsFilters =
props.jobsScheduleKindFilter !== "all" || props.jobsScheduleKindFilter !== "all" ||
@@ -646,12 +655,10 @@ function renderJobsFilterPopover(props: CronProps, active: boolean) {
label: t("cron.jobs.schedule"), label: t("cron.jobs.schedule"),
value: props.jobsScheduleKindFilter, value: props.jobsScheduleKindFilter,
testId: "cron-jobs-schedule-filter", testId: "cron-jobs-schedule-filter",
options: [ options: Object.entries(SCHEDULE_KIND_FILTER_LABELS).map(([value, labelKey]) => ({
{ value: "all", label: t("cron.jobs.all") }, value,
{ value: "at", label: t("cron.form.at") }, label: t(labelKey),
{ value: "every", label: t("cron.form.every") }, })),
{ value: "cron", label: t("cron.form.cronOption") },
],
})} })}
${renderJobsFilter(props, "cronJobsLastStatusFilter", { ${renderJobsFilter(props, "cronJobsLastStatusFilter", {
label: t("cron.jobs.lastRun"), label: t("cron.jobs.lastRun"),