v0.1.0-rc.2#281
Merged
Merged
Conversation
The console renders a "Sending" badge for a scheduled message whenever `has_pending_events` is true. That field was computed as "any unfired event exists", but recurring schedules pre-generate the next occurrence's event the moment they advance (anchor + N*interval). As a result every recurring schedule perpetually had an unfired future event and was shown as "Sending" indefinitely, even while idle months before its next run. Scope `has_pending_events` to events that are actually due (`fired_at IS NULL AND fire_at <= NOW()`) in ListUserSchedules and ListOrganizationSchedules. This matches the existing scan-due predicate and the "up to 1 minute to process" tooltip: the badge now reflects an event being actively delivered, not one queued for a future occurrence. No frontend change needed — the badge keys solely off this field.
Recurring schedules advanced to their next occurrence with a generate_series(1, 10000) scan from the anchor. That scan both capped how far a schedule could be advanced and was O(N) in the number of skipped occurrences: a schedule dormant for more than 10000 intervals (e.g. a 1-minute interval idle for ~7 days, or hourly for ~1 year) could never resume — the advance query returned no row and errored every cycle. Replace the scan with a next_schedule_occurrence SQL function that estimates the occurrence number in O(1) from the average interval length and then corrects it exactly with a couple of interval-arithmetic loops. The estimate only seeds the loops; the returned occurrence is exact for any strictly-increasing interval and there is no upper bound on the catch-up gap. Both the user and organization advance paths and computeNextOccurrence now call it, removing the duplicated calendar math. Tested: exact known cases (yearly, month-end clamping, boundary, ~470yr dormancy), a 640-case property grid asserting the defining properties across leap years / mixed calendar intervals / horizons to year 3025, and rejection of non-positive intervals.
The create button stayed disabled after enabling and then disabling the transactional toggle, even with a subscription selected. Toggling transactional on cleared subscription_id, and the auto-select effect restored a subscription via form.setValue without revalidating, leaving formState.isValid stale at false. Pass shouldValidate so the form revalidates when subscription_id is set programmatically.
…are usable
The gate (and other downstream) variable picker only surfaces a step's
event variables when that step has a `data_key` — the backend likewise
only exposes step state under `journey.<data_key>.*`. Entrance steps never
got a data_key assigned automatically (it was a manual, easily-missed
field), so the triggering "input" event's variables never appeared in the
picker; only the always-present "User" group showed.
Give entrances a readable, unique default data_key ("entrance",
"entrance_2", …):
- at creation, in both the drag-drop and trigger-setup paths, and
- as a non-destructive backfill on load for entrances that predate this,
persisted on the next manual save.
An exit step requires an `entrance_uuid` (the entrance it terminates), but it was always empty on drop, forcing a manual pick and failing validation until set. Default it to the first entrance when placed, so single-entrance journeys — the common case — need no extra configuration.
The Journey editor's Send node renders the email preview inside a small 250x200 thumbnail (size="small"), but EmailFrame always laid out at full scale: 22px subject, large avatar, generous padding, and a full row of Gmail action icons. Crammed into the thumbnail this wrapped the subject, sender name, and timestamp onto multiple lines and overlapped the reply icon, leaving the preview looking broken. Thread the existing `size` prop through to EmailFrame and add a compact layout for "small": tighter padding, smaller subject/sender/avatar type, truncation instead of wrapping, and hidden action icons. Full-size previews are unchanged.
Replace the previous compact-header approach. For the small thumbnail in the Journey Send node the Gmail-style header chrome (subject, sender, action icons) doesn't fit and looked broken. Instead, render the compiled email itself, clipped to the thumbnail height — a truer preview of what will be sent. EmailFrame is reverted to its original full-size layout; large previews are unchanged.
The generated oapi `HandlerWithOptions` wrapper composes middlewares with a forward-wrapping loop (`handler = mw(handler)`), so the LAST entry in the `Middlewares` slice becomes the outermost wrapper and executes FIRST. The slice was ordered `[Validator, RateLimit]`, so RateLimit actually ran BEFORE the validator's authentication. As a result `rbac.FromContext` was always nil when the limiter computed its key, and every request fell back to the `ip:` bucket. Behind a reverse proxy or ingress (and `TrustedProxyHops` defaults to 0, so `RemoteAddr` is used), that IP is the proxy's — identical for all traffic — collapsing the entire deployment into a single shared 600/min bucket. API-key ingestion and Clerk-authenticated console sessions then starved each other's budget instead of being keyed independently per auth method. List RateLimit before the validator on both surfaces so it runs *after* authentication and keys on the resolved actor, and add a regression test that composes the chain like the generated wrapper and asserts two actors from the same IP get independent budgets.
…e scheduling (#280) * feat(scheduled): address schedule assignments by id for multi-instance scheduling Schedule assignments are now addressed by their own id. Submitting with an existing assignment id upserts that row; omitting it creates a new instance, so a single subject can hold multiple assignments to the same named schedule (e.g. several reminders of the same kind, or rescheduling a specific one). - migration drops the unique (user_id, schedule_id) / (organization_id, schedule_id) indexes; the non-unique lookup indexes are kept - UpsertUserSchedule / UpsertOrganizationSchedule take an id and conflict on (id), guarded by user/schedule so a supplied id cannot hijack another subject's row (ErrScheduleOwnershipMismatch otherwise) - ScheduledMsg carries assignment_id; the client API exposes an optional id on the upsert requests and returns the assignment id in the 202 response; the management API exposes an optional id so the console can target an instance - journey schedule steps derive a deterministic assignment id from (user, schedule) so re-runs/retries stay idempotent (no behaviour change) - regenerated client + management Go oapi and the console TS types * feat(scheduled): journeys schedule a new instance per entry; client delete-by-id Follow-up to the id-based assignment change: - Journey schedule steps now create a NEW assignment per journey entry (a user's pass through the journey), so re-entering schedules an additional instance. The id is derived from (journey_entry_id, step_id) so a redelivered/retried step message stays idempotent instead of duplicating. - Client API delete now accepts an optional id to remove a single assignment (ownership-checked); without an id it still deletes every assignment by name. name is now optional (either id or name required); regenerated client oapi. * fix(scheduled): regenerate oapi with pinned oapi-codegen v2.7.0 The generated files were produced with v2.5.1 locally, which diverges from the CI-pinned v2.7.0 and broke the generate check. Regenerated with v2.7.0; the diff vs main is now scoped to the new schedule id fields only. * fix(scheduled): keep omitted-id messages idempotent; require delete identifier Addresses PR review: - Consumer derives a deterministic assignment id from (subject, schedule) when a ScheduledMsg omits assignment_id, so producers that don't set one (user/org anniversary schedules) stay idempotent under at-least-once redelivery instead of minting a fresh id per delivery and duplicating the assignment. The client API still sends an explicit id, so its create-new-per-submit behaviour is unchanged. - DeleteUserScheduledRequest now requires identifier in the schema (the handler already rejected requests without one); regenerated client oapi and updated the handler for the now non-pointer identifier. - Added a unit test for resolveAssignmentID. * docs(scheduled): update upsert doc comments for id-based semantics The UpsertUserSchedule / UpsertOrganizationSchedule doc comments still described the old (subject, schedule) conflict model. Updated to reflect that assignments are keyed by their own id (nil creates a new instance; an existing id updates in place, guarded by ErrScheduleOwnershipMismatch).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This pull request introduces several improvements and fixes across the codebase, focusing on three main areas: API enhancements for scheduled assignments, improved handling of entrance step data keys in the journey editor, and UI/UX refinements. The most significant changes are summarized below.
API and Type Improvements for Scheduled Assignments
idfields to scheduled assignment request and response schemas for both users and organizations, allowing updates and deletes by assignment instance, and clarified behavior in OpenAPI specs and generated Go types (internal/http/controllers/v1/client/oapi/resources.yml,console/src/oapi/management.generated.ts,internal/http/controllers/v1/client/oapi/resources_gen.go). [1] [2] [3] [4] [5] [6] [7] [8] [9] [10]Journey Editor: Entrance Data Key Auto-Assignment
defaultEntranceDataKeyutility and logic to automatically assign unique, readabledata_keyvalues to entrance steps, ensuring their trigger event data is referenceable downstream and backfilling legacy entrances without a key (console/src/views/journey/editor/JourneyEditor.utils.ts,console/src/views/journey/hooks/useJourneyEditorGraph.ts,console/src/views/journey/hooks/useJourneyFlowHandlers.ts). [1] [2] [3] [4] [5] [6] [7]console/src/views/journey/editor/JourneyEditor.utils.test.ts).UI and UX Refinements
console/src/components/preview.tsx).console/src/views/campaign/NewCampaign.tsx). [1] [2]console/src/views/settings/clients/store.ts).These changes collectively enhance API flexibility for scheduled resources, improve the developer and user experience in the journey editor, and address minor UI details.