Repository navigation
Scheduled maintenance: the calendar as its own page under a Calendar section, external .ics calendars drawn on it, and Calendar Settings (#5084) - #5119
Merged
Conversation
…links, worked out off the main thread Calendars from outside the project (issue #5084) - public holidays, a change freeze, another project's calendar link - are read by the Workers through the egress guard, cut down to the properties read, and expanded by ical.js (RRULE, RDATE, EXDATE, RECURRENCE-ID, VTIMEZONE) in a worker thread that is terminated past its time limit: an impossible RRULE never returns from ical.js. Their events replace the old ones in one transaction. The link is encrypted and write-only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…red and kept out of the project's own links The parser on Exchange, Google and OneUptime calendars (RRULE, EXDATE, RECURRENCE-ID, VTIMEZONE across DST, all-day, floating and IANA times, limits, an impossible rule stopped off the main thread), the linear pre-scan with 400,000-character worst cases, the fetch against a real server (size, deadline, gzip, redirects re-checked by the egress guard), the services through the member write pipeline, the refresher's claim and reads, Read now, both tables, and the team's link holding its own events alone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… and their SQL against Postgres Generated against a fully migrated database (no drift after it). The opt-in Postgres suite runs the bulk replace, the read claim and the cascade on a clone of the migrated tables, wired into the Postgres Schema Drift workflow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…endar section, external calendars drawn on it, and Calendar Settings Scheduled Maintenance gets a Calendar section in its side menu, right after Overview, with the Maintenance Calendar: the calendar that used to be the Calendar half of a List | Calendar switch on All Events, which is a plain list again (a reader who had left it on Calendar is taken to the new page once). Calendar Settings, in the folded Settings section, holds the calendars from outside the project: name and link, colour and refresh under More fields, status, Read now, Change link, Turn on/off. Their events are drawn on the calendar as striped, dashed blocks in their colour, each calendar a toggle under the grid that this reader's browser remembers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lendars, Calendar Settings and the side menu The side menu's Calendar section and Calendar Settings, the everyday-section guard, the overlay logic (hidden calendars per project, all-day dates in every zone, timed events in the reader's), the calendar drawing and toggling external calendars for real, the status words, and the Calendar Settings page's contract. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lendars and Calendar Settings in all 17 languages 64 new strings, worded in each of the 16 other languages, and four words the new pages show that most languages lacked (Busy, Not yet, the kept-events count). The two link descriptions are translation keys so the extractor reads them despite the link scheme in them, and three sentences are plainer before they are translated (redirects, the name field, color). MaintenanceCalendarInLocale holds every sentence to its page and to every language, and renders the toggles in German, Russian, Japanese and Persian. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…endars, in all 17 languages Maintenance Calendar: the calendar page under the side menu's Calendar section, a note for the readers whose All Events was left on the old switch, and a section on external calendars - adding one on Calendar Settings, what it keeps, the actions, the roles, the addresses OneUptime will not read, and why one is not working. The overview names the new Calendar section (open, like Overview) and Calendar Settings, and the settings page points at it. Every language line for line, its labels as its Dashboard draws them, anchors localized. Calendar Settings now sits right before Numbering, which stays every product's last Settings entry (NumberingPagesWiring). The overlay limit reads the shared EXTERNAL_CALENDAR_VIEW_LIMIT. The docs facts hold the pages to the product: the side menu sections, the one-time redirect's key, the form's fields and fold, the limits, the permission lists equal to scheduled maintenance's, the egress guard, and the team and status page calendars never reading external calendars. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…endar Settings' rows A row action's own completion only clears its spinner (RowActions), so a calendar turned off went on saying Working until the page was reloaded. Every action that changes a row now reloads the table itself, with a count so two reloads in one millisecond stay two; a refused action reloads nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ars and Calendar Settings in a real browser The offline suite drives the Maintenance Calendar under the side menu's Calendar section, All Events as the list alone, the one-time move of a reader the old switch left on Calendar, the external calendars drawn beside the events (lighter, dashed, opening nothing) with a toggle each, and Calendar Settings: status, adding one (Color and Refresh folded), a refused link, Change link, Turn off / Turn on, a refused Read now, a viewer's locked Add External Calendar - at 1280 and 390, light and dark, German and Japanese. The List | Calendar switch's slot at the end of the filter bar (filterBarEnd) had no other user and goes with it, as do the comments that still named the switch. The schema-drift workflow's heartbeat comment is back above its step. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…calendar-section The locale files merged both sides through npm run i18n:extract. The external calendars' migration moves to 1805120000000, past master's newest (1805000000000-AddExternalMcpServers, which took the same timestamp). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… a backlog, batch after batch for 45 seconds One batch of 20 a minute read at most 1,200 calendars an hour across every project, so a backlog larger than that only grew. A run now goes on reading due calendars, oldest first, until none is left or 45 seconds are up; the read under way when the time is up is finished. Reads stay one at a time, so a run's memory never grows, each calendar is tried once a run (a claim another reader won is not retried), and each claim's lease runs from when it is made. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…in compile-tests compile-tests (tsconfig.tests.rest.json) refused four lines: a TypeORM index read through a propertyName it does not have (a property's @Index() is kept as columns: [its name]), a calendar built with nextReadAt: undefined under exactOptionalPropertyTypes, and two spies annotated with @types/jest's SpiedFunction in a suite whose jest comes from @jest/globals (jest-mock's SpyInstance now). The migration test reads a class name's 13-digit timestamp without a pattern that could take polynomial time. Dashboard Offline UI group 6's comment says what Maintenance Calendar's 22 new specs add to it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…endar Settings
Suites that read the files this branch changes, run after merging master:
- PageSearchIndexCoversMenus / CoversRoutes: the side menu's new Calendar
section (Maintenance Calendar) and Calendar Settings are in the page search
index, with the words people type for them (subscribe to calendar, ical
feed; external calendars, public holidays, change freeze).
- CustomFieldSavedViewTables: the calendar page passed a saveFilterProps
table id it never saved views under (saved views are the list's
ModelTable's); it passes none, and its filter chips still live in the page's
address.
- OnCallCalendarFeedWiring: the rotated-out link's two sentences are found by
what tells them apart, as the test's comment says ("empty calendar"),
not by naming an old link and a calendar - Change link's dialog for an
external calendar names both.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… Prettier and ESLint write them Formatting only, plus two lint findings in tests: an object cast typed as Record<string, unknown>, and the docs facts' side-menu section pattern named and written with counted spaces (no-regex-spaces). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…calendar-section The migration index keeps master's AddInvestigationRuleWait (1805030000000) and its indexes (1805040000000) before the external calendars' (1805120000000), still the newest. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…calendar-section The locale files merged both sides through npm run i18n:extract. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…and Terraform describe them Built from the real OpenAPI generator: the calendar's six routes, a link taken on create and on update (Change link) but never in what is read back, nothing the Workers write taken on create or update, how the last read went read back without the content digest, and the events left out of the reference. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Author
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
…lendars', its header as All Events', Add External Calendar opening its dialog UI review of the Maintenance Calendar and Calendar Settings (#5084): - A busy calendar from outside the project folded the page's own maintenance into "+N more": react-big-calendar fills a month cell's rows by length and start time. The shared Calendar now lays the page's own events out first and the overlays in the rows they leave (getCalendarLocalizer), and a keyboard no longer stops on an overlay's block in the week and day views (it opens nothing). - The calendar card uses the toolbar header All Events uses: the title was broken into a column a word wide at tablet widths, and the description wrapped on a laptop. - Add External Calendar in the calendar's menu opens Calendar Settings with its Add dialog open (?open=add-external-calendar, taken back out of the address), instead of a list where the same button had to be found again. - Each external calendar's toggle is named by its calendar and says whether it is shown with aria-pressed; a calendar not read lately is marked inside its own toggle, the reason its description. - Calendar Settings' description is one short sentence; the breadcrumb names the page Maintenance Calendar, as its menu entry and card do. - Docs: the add step names the menu path first, in all 17 languages. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…a line on a phone, as on All Events The calendar card's header drew Create Scheduled Maintenance Event across the whole width of a phone and pushed the menu onto a line of its own; the list holds the two as one group, the button wrapping its words beside the menu. The calendar now does the same. The offline suite also checks that a busy external calendar never folds the project's own events (a long one included) into +N more, and the overlay tests cover a change freeze across the week. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…calendar-section-ui-review
…nalPropertyTypes, and the new tests as Prettier writes them Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…calendar-section-ui-review # Conflicts: # packages/App/FeatureSet/Dashboard/src/Locales/da.json # packages/App/FeatureSet/Dashboard/src/Locales/de.json # packages/App/FeatureSet/Dashboard/src/Locales/en.json # packages/App/FeatureSet/Dashboard/src/Locales/es.json # packages/App/FeatureSet/Dashboard/src/Locales/fa.json # packages/App/FeatureSet/Dashboard/src/Locales/fr.json # packages/App/FeatureSet/Dashboard/src/Locales/hi.json # packages/App/FeatureSet/Dashboard/src/Locales/it.json # packages/App/FeatureSet/Dashboard/src/Locales/ja.json # packages/App/FeatureSet/Dashboard/src/Locales/ko.json # packages/App/FeatureSet/Dashboard/src/Locales/nl.json # packages/App/FeatureSet/Dashboard/src/Locales/no.json # packages/App/FeatureSet/Dashboard/src/Locales/pt.json # packages/App/FeatureSet/Dashboard/src/Locales/ru.json # packages/App/FeatureSet/Dashboard/src/Locales/sv.json # packages/App/FeatureSet/Dashboard/src/Locales/zh-CN.json # packages/App/FeatureSet/Dashboard/src/Locales/zh-TW.json
simlarsen
pushed a commit
that referenced
this pull request
Oct 21, 2026
…he TypeScript project js-lint has failed on master since #5119 (the maintenance calendar section): ESLint's typed config could not parse ExternalCalendarExpansionWorker.cjs ("was not found by the project service"). The file is plain CommonJS on purpose - a worker thread starts a fresh Node, which loads JavaScript, not the TypeScript the server runs as - so, like the Probe's CommonJS fixtures and FakeTcpdump.cjs, it is listed in the ignores with the reason, rather than pulled into a TypeScript project. ESLint passes on the ExternalCalendar directory. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
The problem
The maintainer asked (task doc, block 27): "Can you please have calendar view as separate page in side menu with its own side menu section called 'Calendar'. We can then also implement some of these ideas: #5084, where we can overlay external feeds (You can create a new page in Settings called Calendar Settings so all of these options are advanced and can be hidden)".
Until now the scheduled maintenance calendar was the Calendar half of a List | Calendar switch on All Events (#4942), and a project could publish
.icsfeeds but not read anyone else's (#5084: public holidays, a change freeze kept in Exchange, another project's maintenance).What changed
The calendar is a page of its own, under a Calendar section
/dashboard/:projectId/scheduled-maintenance-events/calendar). It is the calendar Scheduled maintenance: a calendar view, the team's calendar link, and a status page's maintenance calendar (iCal) #4942 built - month, week, day, filters, the + to schedule on a day, Subscribe to calendar - with its own title, breadcrumb and side-menu highlight./scheduled-maintenance-eventsstill opens All Events. A reader whose browser had left the old switch on Calendar is taken to Maintenance Calendar once (replace, so Back is not trapped); the stored choice is then forgotten and All Events is the list from then on.External calendars (#5084), set up on Calendar Settings
https://,http://orwebcal://). Color (one the project's other calendars do not use) and Refresh (every hour; 15/30 min, 6 h, a day) are folded under More fields. It is read at once and its row says Working with the events kept, Waiting for the first read, Off, or Not working with the reason and what to do. Row ⋯: Read now, Change link, Turn off / Turn on; Edit changes its name, colour and refresh; Delete. Each action reloads the list, so the row says what happened.How the calendars are read (server)
ScheduledMaintenanceExternalCalendar(CRUD at/scheduled-maintenance-external-calendar, in the API reference and so Terraform) andScheduledMaintenanceExternalCalendarEvent(read-only to everyone; written by the Workers alone). Migration1805120000000-AddScheduledMaintenanceExternalCalendars, generated against a fully migrated database, no drift after it.read: []): never returned by any API or page; only its host is shown. Change link replaces it and forgets the old link's events.ScheduledMaintenance:RefreshExternalCalendars, every minute) reads each enabled calendar whose next read is due, one at a time, batch after batch for at most 45 seconds per run; a calendar is claimed with a compare-and-set first, so two Workers never read one calendar. Read now (POST .../:id/read-now, for whoever may update it) is refused within 30 s of the last read or while it is off.DataSourceEgressGuard, like the data sources and threat-intel feeds): the host is resolved and checked before a socket opens and the socket is pinned to the checked addresses; redirects are re-checked (at most 3); 5 MB cap (decompressed), 20 s for the whole read. Never loopback or a metadata address; OneUptime Cloud also refuses private networks.ical.js(Mozilla's parser) in a worker thread with a heap cap, a recurrence step budget and a 10 s limit after which it is terminated (an RRULE written never to match makes ical.js loop forever - measured). Before it, a one-pass linear pre-scan drops every property never read. RRULE, RDATE, EXDATE, RECURRENCE-ID overrides and cancellations, VTIMEZONE (as Exchange writes it), IANA and old Mozilla TZIDs, floating times (X-WR-TIMEZONE) and all-day dates are handled. Kept: 90 days back to a year ahead, up to 2,000 events (nearest today), the same window the team's link serves. An unchanged calendar is not worked out or written again (content digest).Small things around it
filterBarEndinuseResourceOwners) only ever held the old switch, so it goes; comments that still named the switch are updated (ViewSwitch, RememberedView, CalendarToolbar, the LLM Usage page).OnCallCalendarFeedWiringfound the rotated-out link's two sentences by "old link" + "calendar"; Change link's dialog names both, so it now looks for "empty calendar", as its own comment says.What changes for existing customers
Left out
packages/E2E/Tests: the deployed e2e stack has no calendar server its egress guard would let OneUptime read; the reads are covered by the server suites, the Postgres suite and the offline browser suite below.Tests (what each proves)
Common:
ExternalCalendarExpansion.test.ts- the parser on Exchange, Google and OneUptime calendars: weekly and yearly RRULEs, an ended rule, RDATE, EXDATE, RECURRENCE-ID moves and cancellations, VTIMEZONE across DST, IANA/Mozilla/floating times, all-day dates, private events' titles, the window, the 2,000 cap, step budgets, malformed input, and a rule that never matches stopped at the time limit off the main thread.ExternalCalendarText.test.ts- the linear pre-scan, with 400,000-character worst cases under 2 s.ExternalCalendarFetch.test.ts- against a real local server: size cap (plain and gzip), deadline, a server that drips bytes, redirects re-checked by the guard, SSRF refusal, statuses mapped to reasons that never name the link.ExternalCalendarRefresher.test.ts- the claim, what a read writes, a failed read keeping the last good events, the unchanged calendar, a link replaced mid-read, Read now's refusals, and the job: batches until a short one, each calendar tried once a run, the 45 s budget.ScheduledMaintenanceExternalCalendarService/EventService/API/Modelstests - write-only encrypted link, normalisation, colour picking, 20-per-project limit, resets on Change link, permissions, Read now routing.ScheduledMaintenanceExternalCalendarPostgres.test.ts(opt-in, wired into Postgres Schema Drift) - bulk replace, the claim race and the cascade on the migrated tables.AddScheduledMaintenanceExternalCalendarsMigration.test.ts- two tables and nothing else, indexes, FKs, down().ExternalCalendarApiSpec.test.ts- from the real OpenAPI generator (what Terraform is built from): the six routes, the link taken on create and update but never read back, nothing the Workers write taken, the events left out of the reference.OwnCalendarLinksLeaveOverlaysOut.test.ts- the team's and status pages' feeds never read external events.App:
ScheduledMaintenanceCalendarWiring(routes, page, the switch gone, the one-time redirect),ScheduledMaintenanceCalendarDarkMode,MaintenanceCalendarDocsFacts/ScheduledMaintenanceDocsFacts(docs held to the product: menus, fields, limits, permissions, egress guard).Offline browser suite (
test-maintenance-calendar-ui, 64 tests, 22 new): the Calendar section and page, All Events alone, the one-time move, overlays drawn/toggled/hidden/failing/truncated, Calendar Settings (status, add with folded fields, refused link, Change link, Turn off/on, refused Read now, a viewer's locked Add External Calendar), at 1280 and 390, light and dark, German and Japanese. It adds about 45 s locally (about 2 min 10 s with its bundle) to Dashboard Offline UI group 6, which lost Custom Roles (about 3 min) to group 7 today.Run locally
test-maintenance-calendar-ui): 64/64 green, about 2 min 10 s with its bundle.SsoInCommunityEdition"Invalid Plan", the WebAuthn suites' "Invalid URL" without HOST,McpAuthorizePage's/apipath), and two from master's own recent merges -TestSendRoutesAskTheRule(ExternalMcpServerAPI/test is not listed) andDashboardStorageArrayListComponents(the Incident template's extra MTTA query).npx jest Guardandnpx jest Localein Common,npx jest Localeand the root-level tests in App, DashboardLocalesGuard,i18n:check,i18n:validate,docs:check-anchors, repo-rootTests/Ops, and this change's suites once more with ee/ hidden.CheckSchemaDrift.sh(no drift), then the opt-in external calendar suite against it.npm run compilein Common, App, the Dashboard and E2E,compile-testsin Common, thetsc --listFilescheck (nothing from another package), prettier and eslint on every changed file, and the ReDoS checker (no pattern that is not linear).UI review
Looked at, on the real pages in the offline fixture (the product's theme, Tailwind and Inter) at 1280, 1024, 768 and 390 px, light and dark, in English, German, Japanese, French and Russian: the Maintenance Calendar (month, week, day; a busy month; calendars failing, hidden, never read; a viewer), the external calendars' toggles, the calendar's ⋯ menu (owner and viewer), Calendar Settings (rows, empty, waiting, failing, off; Read now refused; Turn off), the Add dialog with More fields open, Edit, Change link, the row menu, All Events, and the docs page's External calendars section (en, de, ja at 1280 and 390).
Changed:
getCalendarLocalizer: a sort that puts overlays after, and adaySpanthat never counts an overlay among the multi-day blocks laid out first). Pages without overlays (the on-call schedules) keep the calendar's one localizer.CalendarEventWrappertakes overlays out of the tab order; a screen reader still reads them where they are.?open=add-external-calendar, read once, for whoever may add one, then taken out of the address). It used to open the list, where the same button had to be found again. The docs' add step says so, in all 17 languages.aria-pressed; its name used to flip between "Hide X on the calendar" and "Show X on the calendar" while also pressed and unpressed ("Hide Public holidays, pressed"). What a click does is now the pointer's hint. A calendar not read lately has its mark inside its own chip (it sat between two chips, belonging to neither), and the reason is the toggle's description. A hidden chip's text is gray-500 (gray-400 was 2.5:1).Left alone (shared, not this change): "Name is required." shows once More fields is opened before the name is typed (BasicForm marks a field touched on blur); the side menu's 224px column truncates "Maintenance Calendar" between 768 and 1023 px; a required dropdown (Refresh) keeps its clear button.
Tests added or changed in the review:
UI/Components/Calendar/CalendarOverlayEvents.test.tsx(new): own events sort before overlays, an overlay never counts as multi-day, the localizer copy keeps the calendar's settings; month cells laid out own-first (same day, a long own event against a long holiday that began a day earlier, a change freeze across the week); week and day blocks of overlays out of the tab order, month blocks untouched.ScheduledMaintenanceCalendarExternalCalendars.test.tsx: toggles named by their calendar with aria-pressed and hints, the mark inside its toggle with the reason as its description, own events before overlays, the toolbar header.ScheduledMaintenanceCalendarSettingsPage.test.tsx: the Add dialog opened by the address (not otherwise, never for a viewer), the ask removed and the rest of the address kept; the new description.ExternalCalendarAddLink.test.ts(new): the ask and what does not count as one.MaintenanceCalendarInLocale.test.tsx: the toggles' names, hints and description in German, Russian, Japanese and Persian.ScheduledMaintenanceCalendarWiring(the trail's name, the ⋯ item's wiring to the dialog) andMaintenanceCalendarDocsFacts(the reworded add step and the behaviour behind it).test-maintenance-calendar-ui, 69 tests, 5 new, about 2 min 20 s with its bundle, about 10 s more): the header at 1280, 1024 and 768; a busy month keeps the project's events visible, a long one included; a keyboard in the week skips overlays; Add External Calendar opens the dialog, once; on a phone Create and the ⋯ share a line as on All Events. The toggle tests follow the new names.Run after merging master (344bfa3, the Code redesign; its locale conflict resolved with i18n:extract): the changed suites and every suite reading the changed files (the Calendar suites, the on-call schedule calendar suites, breadcrumbs, the readers of ScheduledMaintenanceTable, App's dark mode suites),
npx jest Guard(Common 154 suites, App 41),npx jest Localein Common and App, the general docs suites with the scheduled maintenance and calendar docs suites and DocsCriteriaConditions, App's root tests, RelationSelectColumnsWiring, Tests/Ops/E2ESuitesRunInCi, the changed suites with ee/ hidden,i18n:check,i18n:extract(nothing new),i18n:validate,docs:check-anchors,npm run compilein Common, App, the Dashboard and E2E,compile-tests, thetsc --listFilescheck, prettier and eslint on every changed file, the ReDoS checker (no new pattern), and the whole offline suite (69/69). Not run: a full-stack e2e stack.🤖 Generated with Claude Code