feat(sync): derive shared-schedule labels for Zoom and Google Calendar from the linked family - #552
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
A family served by one Zoom meeting now names each mode with its own days
("... - Hybrid Mon-Fri - Zoom Only Sat"), and the recurrence union skips members
that never meet online. Single-schedule meetings keep the exact name they have
today, and a pinned zoomTopic is still returned verbatim.
The create/update/retry-sync routes and the monthly scan now load the family (linkedToMid, unioned with the rows sharing the zid) instead of zid siblings alone, so an In-Person member reaches the Zoom name while split children and uncovered legacy zid groups still reach the union schedule.
…nkedFamily The lookup is a family query, not a Zoom API call, so keeping it in services/zoom.ts would have hidden it behind that module's jest mock in every route integration test. The retry-sync assertion now names the whole family it receives rather than siblings alone.
…Google Calendar Both services named a family's schedules with their own copy of the segment logic and mode labels; the builder now lives in util/meetings/linkedSchedules.ts, so a family's Zoom topic and its calendar event titles can never drift apart. Each service keeps its own single-schedule suffix map, since a Zoom topic never says "In Person" and a calendar event always has.
…whole family The three routes that already load the linked family for Zoom now reuse that one lookup for their Google Calendar writes, so a family's calendar events and its Zoom meeting always carry the same name. Two integration tests that never drained their background sync are drained explicitly -- the extra lookup made them leak calls into the next test's assertions.
Locks in the calendar event title's lone-meeting suffix byte-for-byte alongside the family name every member's event now carries, and covers the shared builder and resolveFamilyRows directly rather than only through the two services.
… name
A "this occurrence" split-off inherits the parent's zid, so it reaches the family the shared
label is built from; edited to a different mode afterwards it added a bogus segment
("... - Zoom Only One-time") to every sharing row's Zoom topic and calendar title. The
predicate the retrieve route already filtered divergence by is now shared and applied to the
label only -- buildZoomRecurrence must keep seeing those rows to judge whether Zoom's schedule
can be represented at all.
…d scoped delete Suspend-then-resume, a scheduled resume's pre-created series, and a scoped delete's surviving parent each rewrote a linked family member's Google Calendar event from its own mode alone, undoing the union title and leaving it wrong until that row was next fully edited. All three now load the family the same way the edit paths do.
…ckground syncs The parent and split-child syncs start concurrently and read a near-identical row set, so they now share the request's loader -- which caches its in-flight promise rather than the resolved value, or concurrent callers would each pay for the query. Also states the calendar's single-schedule suffix map at its call site, as the Zoom side already did, and records the missing family-wide calendar fan-out as a TODO.
bd6b8b9 to
209dc3e
Compare
@coderabbitai summary
Description
Second PR of the linked meeting modes work, and the first consumer of PR 1's
linkedToMidmodel. A meeting the group runs as two co-existing weekly schedules now carries one name across both external services —"One Day at a Time - Hybrid Mon-Fri - Zoom Only Sat"— on its shared Zoom meeting and on every family member's Google Calendar event. The label is not Zoom-specific, so it is built once and both services call it, rather than the segment logic living twice.Behavior-neutral for every existing single-schedule meeting (the almost-all case is asserted byte-for-byte in both services' tests), and immediately correct for the three legacy pairs PR 1 just backfilled.
buildLinkedScheduleLabel(baseTitle, meeting, family, singleScheduleSuffix?)— pure text, no knowledge of Zoom, Google orzid. Segments followLINKED_SCHEDULE_MODES' fixed[Hybrid, In Person, Remote]order (never row-creation order, so the name is stable no matter which member triggered the write), each rendered as${modeLabel} ${formatDayColumn(pattern)}reusing the exports' own Day-column formatter.LINKED_SCHEDULE_MODE_LABELis the one mode→label map both services now share.resolveFamilyRows(meeting, family)— reconciles the in-flight row with its pre-edit copy still sitting in the DB-loaded family, so a caller may pass either the whole family or only the siblings.isDetachedSplitChild(row)— a "this occurrence" split-off child is a one-off, not a schedule; it is excluded from the label, so one detached child whose mode was later edited can't inject a bogus"- Zoom Only One-time"segment into every sharing row's name.getZoomScheduleFamily(tx, mid, zid)— the rows the family's single Zoom meeting must account for, unioned by mid from two sources that are each incomplete alone: thelinkedToMidfamily (the only way to reach a Zoom-free In-Person member) and every other live row sharing thezid(scoped-edit split children and any legacy zid group the backfill didn't adopt — dropping them would re-narrow the union schedule fixed in Editing one row of a shared Zoom meeting narrows the unioned Zoom recurrence #513).linkedFamilyLoader(tx, mid)— a once-per-request reader caching the in-flight promise (a scoped edit runs its parent and childafter()syncs concurrently), so the Zoom write and the Google Calendar writes in one request read the same rows and cannot disagree.zoomTopicFor(meeting, family)keeps its pinned-zoomTopicearly return and otherwise delegates to the shared builder withZOOM_SINGLE_TOPIC_SUFFIX(Hybrid/Remote only — an in-person meeting has no Zoom meeting to name).buildZoomMeetingBody/updateZoomMeeting/createZoomMeetingtake the full family instead of "sibling rows sharing this zid", andbuildZoomRecurrence/nextOccurrenceStartfilter it throughisZoomBearinginternally — a standalone correctness hardening, since unioning an In-Person member's weekdays intoweekly_dayswould advertise Zoom occurrences for a schedule that never meets online. Nothing writes the derived topic back intoMeeting.zoomTopic: a null column keeps meaning "auto, recompute from the current family", which is what makes the topic recomputable rather than pin-once.buildEventTitle(meeting, family)calls the same builder, retiring the privatemodeTitleSuffixmap.buildEventBody/createCalendarEvent/updateCalendarEvent/reconcileMeetingCalendarsgain a trailing, defaultedfamilyparameter — trailing because most call sites (resume, suspension, delete rewrites) have no family concept and an extra positional argument they'd all pass[]to is only a chance to get the order wrong. Google has nozoomTopicequivalent, so there is no verbatim-name escape hatch; every member's event gets the full union title, mirroring the one shared Zoom meeting even though each schedule keeps its own event with its own dates and RRULE.linkedFamilyLoaderper request, replacing the twozid-onlyfindManycalls, and its result feeds both the Zoom write and the calType/Zoom-Room calendar writes in that same request.handleScopedEditshares a single loader betweensyncScopedParentCalendarandsyncSplitMeeting, which start concurrently.createPendingResumeSeriesuses the lazy loader so its "no upcoming occurrence" early returns stay genuine no-ops, query included.zoomScheduleDiverged's inline detached-child filter now callsisDetachedSplitChild, so the divergence signal and the family label can never disagree about what counts as a detached one-off. Same predicate, no behavior change.Dailycollapsing, the in-flight-vs-stored row reconciliation, detached split children excluded but recurring tail splits kept, the In-Person exclusion from Zoom's recurrence union, pinnedzoomTopicshort-circuit, and single-schedule output asserted byte-identical to today in both services. Integration suites' Zoom/Calendar mocks and fixtures updated for the new signatures.Known gaps, deliberately deferred to PR 3
Both are carried forward from this branch's review and marked
TODO(linked-schedules PR3)atbuildEventTitle:after(...)must republish every member's events alongside its family-wideupdateZoomMeeting.titleas its base, so two members' names agree only while theirtitlecolumns do. PR 3 derives the linked row's title from the anchor and decides there whether a direct edit of a linked row's title is rejected, propagated, or accepted.Testing
cd frontend && yarn lint— clean.cd frontend && yarn typecheck— clean (this is what catches every un-updated call site of the six changed signatures).cd frontend && yarn test:unit— green, including the 29 new label/topic/title tests.cd frontend && yarn test:integration— green; the route suites exercise the new family lookups against a real Postgres.pr_convention.mdsweep):tests/unit/zoom.test.ts's suffix-only topic expectations,write-meeting-route.test.ts'screateZoomMeetingcall assertions, and theupdateZoomMeetingsibling-array assertions inupdate-meeting-route.test.ts/update-meeting-sync-route.test.tsall updated to the family signature rather than left asserting the old one.git diff feat/linked-schedules-model...feat/linked-zoom-topic -- frontend/services— confirm no code path writes a derived topic back intoMeeting.zoomTopic, which is what keepsNULLmeaning "auto".Area(s) Touched
Product areas
Integrations
Engineering
Pre-merge Checklist
yarn lint).