NMS-20108: On-Call Roles management via versioned REST API and PrimeVue UI - #8715
NMS-20108: On-Call Roles management via versioned REST API and PrimeVue UI#8715joseanesONMS wants to merge 15 commits into
Conversation
Adds an admin-only versioned /api/v2/on-call-roles API over the existing groups.xml role storage (list/get/create/update/rename/delete plus a computed month calendar from GroupManager.getRoleScheduleEntries), and a PrimeVue Manage On-Call Roles page with a schedule calendar and one-off coverage editor. The admin menu entry now points at the new page. Roles stay in groups.xml: hand-edited entries (including recurring weekly, daily and monthly schedules) are picked up on read via GroupManager.update(), round-trip unmodified, and remain editable; schedule validation applies only to entries added through the API. New overnight duty entries (begin after end) are rejected because DutySchedule never matches them at runtime.
Review fixes for the on-call roles API and page. New schedule entries are canonicalized to the exact forms the scheduler dispatches on (zero-padded widths, JVM-default-locale month names, lowercase weekly days, unpadded monthly days), closing the gap where the API accepted entries notifd silently ignored. The optional time id attribute now round-trips, partial updates can change supervisor or membership-group independently, and a role whose schedule cannot be evaluated no longer breaks the whole list response. The calendar reports the server time zone and the dialog renders and labels all times in it, so what is entered matches what notifd evaluates. Coverage edits re-read the stored role before mutating and match removals by content, narrowing the window where a concurrent edit could be overwritten; removing a recurring (hand-edited) entry asks for confirmation since this editor cannot recreate one. Dialogs show API rejections inline like the users and groups pages.
…rver locale Formatting with the default locale made specific-schedule writes fail only in months whose abbreviation breaks the 20-character width (janv. but not mai), and stored strings the API itself could not re-parse. Dates are now always stored in the fixed-width Locale.ROOT form; if the default locale cannot parse that form back, the write is rejected up front with a clear message, since notifd would silently ignore the entry at runtime.
A role whose stored schedule the runtime cannot evaluate now reports a schedule-error field instead of silently showing an empty currently-on-call list, and the roles table renders it as a warning tag — an unevaluable rota is a notifd outage, not an idle one. The name regex scans for forbidden characters instead of a newline-bypassable full-string match.
|
@marshallmassengill could you take a review pass when you get a chance? |
… in MenuHeaderIT The menu entry now lands on the Vue page, so the smoke test waits for its page title instead of the legacy JSP breadcrumb.
bc553c8 to
8e55125
Compare
Swap direct PrimeVue for the Onms-XXX seam wrappers across the roles table, editor/rename dialogs and the schedule calendar: Button->OnmsButton (text/outlined mapped to variant), Dialog->OnmsDialog, InputText->OnmsInputText, Select->OnmsSelect, DatePicker->OnmsDatePicker, DataTable->OnmsTable, Column->OnmsColumn, Tag->OnmsTag. Message and IftaLabel have no wrapper yet and stay on PrimeVue. No behaviour change.
|
@synqotik @marshallmassengill — ready for review. This page now uses the |
…sDialog width prop Month nav and remove-coverage buttons now use OnmsIconButton with icon components instead of a fallthrough icon="pi ..."; dialog sizing routes through the OnmsDialog width prop (min(Npx, 95vw)); drop maximizable, which the dialog seam does not expose. Removes reliance on $attrs riding into PrimeVue.
marshallmassengill
left a comment
There was a problem hiding this comment.
Couple blockers:
- "Specific" one-off schedules are rejected on most server locales, and on en_GB only in September. runtimeDateTimeString formats the date with Locale.ROOT, then requires Locale.getDefault() to parse that string back, else it throws. Running the exact check against JDK CLDR data:
de-DE 0/12 fr-FR 0/12 pt-BR 0/12 sv-SE 0/12 ja-JP 0/12 zh-CN 0/12 it-IT 4/12 es-ES 7/12 id-ID 8/12 nl-NL 9/12 en-GB 11/12 (Sep fails: en-GB abbreviates "Sept")
So on a plain LANG=en_GB server, adding one-off coverage in September fails with a message blaming the server locale while every other month works; on a German or Japanese server it never works. The javadoc's claim that "the check is locale-level, not month-level, so the same request never flips between accept and reject" is wrong, because it formats the actual date. The root cause is BasicScheduleUtils.setOutCalTime doing new SimpleDateFormat(FORMAT1) with no locale, while WebSchedEntry:58 has written Locale.ROOT strings all along: passing Locale.ROOT there fixes both this guard and the pre-existing silent-ignore bug this PR correctly diagnosed. The ITs never pin Locale.getDefault(), so the guard is untested and the suite's outcome depends on the JVM locale plus the fixture month (June, which en_GB happens to accept).
- Changing a role's membership group orphans its schedules. validateDtoFields checks schedule users against the group only when dto.getSchedules() != null, and RoleEditorDialog.save() sends membership-group while deliberately omitting schedule. GroupManager.getUserSchedulesForRole and getRoleScheduleEntries never filter by membership, so after a group change notifd keeps paging users who are no longer in the group and the new group's members get nothing, silently. testPartialIdentityUpdates changes the group on a role that has no schedules, so it
misses this.
Address the review blockers on On-Call Roles: - "Specific" one-off schedules were dropped on servers whose default locale abbreviates months differently from Locale.ROOT (the locale WebSchedEntry writes with). BasicScheduleUtils.setOutCalTime parsed with the JVM default locale, so a round-trip through it failed on e.g. a German or Japanese server and silently discarded the entry. Parse with Locale.ROOT to match the writer; a locale-pinned unit test covers it. - Changing a role's membership group orphaned its schedules. validateDtoFields only checked schedule users against the group when the request carried schedules, but the editor changes the group without resubmitting them, so retained schedules kept paging users no longer in the group. Re-validate the retained schedules against the new group (and drop the "already on the role" exemption when the group changes), rejecting the change with a clear message. Covered by a new IT. Also bring the On-Call Roles dialogs onto the current @opennms/onms-ui seam: drop primevue IftaLabel for FormField's label/for, replace the primevue Message banners with a themed role=alert element, and bind OnmsSelect via its inputId prop. No page imports an un-wrapped PrimeVue primitive; pnpm lint clean.
|
@marshallmassengill — both blockers addressed: "Specific" one-offs dropped on most server locales. Fixed at the root you pointed to: Membership-group change orphaned schedules.
|
|
@marshallmassengill friendly ping — both blockers are pushed: the Locale.ROOT schedule parsing fix (with a locale-pinned test) and the membership-group guard that rejects a change orphaning retained schedules. |
marshallmassengill
left a comment
There was a problem hiding this comment.
One blocker: The locale guard contradicts this PR's own fix and rejects valid requests. runtimeDateTimeString re-parses the stored timestamp with Locale.getDefault() and 400s if that fails, justified by a javadoc saying "the runtime parses stored dd-MMM-yyyy strings with the JVM default locale" — which this same PR changes to Locale.ROOT (and adds testSetOutCalTimeParsesRootLocaleMonthUnderForeignDefaultLocale to prove it). OpenNMS doesn't pin user.language, so on a de_DE server every specific schedule in March, May, October and December is rejected, and on fr_FR essentially all of them — with a message ("specific schedules would be ignored at runtime") that is no longer true. The guard's own comment claims it is "locale-level, not month-level"; it is month-level in effect.
Deleting it is the preferred path I think.
# Conflicts: # opennms-webapp/src/main/webapp/WEB-INF/applicationContext-spring-security.xml # ui/src/main/router/index.ts # ui/src/services/index.ts
|
@marshallmassengill @synqotik — rebased on current develop (the shared router/services/security-config unions plus the icon imports develop migrated to the |
marshallmassengill
left a comment
There was a problem hiding this comment.
The locale guard runtimeDateTimeString (OnCallRolesRestService.java:601) is unfixed, and commit 6e7d152 made it worse. That commit changed BasicScheduleUtils.setOutCalTime to parse with Locale.ROOT — the correct fix — but left the guard that formats with Locale.ROOT and then demands Locale.getDefault() parse it back.
- The guard now rejects requests the runtime handles perfectly, with a message ("specific schedules would be ignored at runtime") that commit 6e7d152 itself made false. The javadoc on both runtimeDateTimeString and validateTime still asserts "parses with the JVM default locale", contradicting BasicScheduleUtils.java:174 in the same PR.
- OnCallRolesRestServiceIT now fails on 8 of the 12 locales above. Every fixture uses June (15-Jun-2093, 5-Jun-2093), and no test pins Locale.getDefault(), so testCreateAndGetRole, testCalendar, testCanonicalization, testMembershipChangeRejected… and friends 400 instead of 201 on any de/fr/it/pt/ja/zh/sv/ru JVM. CI passes only because the agents are en_US.
Delete runtimeDateTimeString and format with Locale.ROOT directly; the BasicScheduleUtils change is the whole fix. Then add a locale-pinned IT so the ROOT contract is actually covered on the API side, not just in BasicScheduleUtilsTest.
So still one blocker with this one.
The previous smoke-test-core run timed out with zero test failures (infrastructure); re-trigger the full pipeline with smoke.
…x !smoke runtimeDateTimeString formatted with Locale.ROOT and then demanded the JVM default locale parse the result back — after BasicScheduleUtils moved to Locale.ROOT parsing, the guard only rejected requests the runtime handles fine, on any non-English JVM, with a message that fix made false. Specific schedules now canonicalize with Locale.ROOT directly, the javadoc says so, and a locale-pinned IT (Locale.GERMANY) covers the ROOT contract on the API side rather than only in BasicScheduleUtilsTest.
|
@marshallmassengill you were right — the guard was the bug, not the safety net. |
marshallmassengill
left a comment
There was a problem hiding this comment.
Still a few more items:
- OnCallRolesRestService.java:140 — the calendar GET returns 400 with an internal message. TimeInterval's constructor throws IllegalArgumentException("start (…) must come strictly before end (…)") when start >= end, and getInterval builds both endpoints from one reference day, so any stored overnight entry throws out of getRoleScheduleEntries. serverError maps all IllegalArgumentException to 400, so a valid GET is blamed on the caller and the raw message is returned. toDto handles the same exception properly via scheduleError — the pattern was recognized for the list and missed for the calendar.
- OnCallRolesRestService.java:464 — a year outside 1000–2999 passes validation, then breaks the marshal. groups.xsd pins begins/ends to [0-9]{2}-[A-Za-z]{3}-[12][0-9]{3}…, @ValidateUsing("groups.xsd") is on Groupinfo/Time, and getMarshallerFor sets that schema. 01-Jan-3000 clears every service check and then fails saveGroups(). Neither OnmsDatePicker sets maxDate, so it's UI-reachable.
- OnCallRolesRestService.java::84 — INVALID_NAME doesn't block C0 control chars. Java \s is only [ \t\n\x0B\f\r], so \u0001 passes validateName and then fails the XML marshal.
- OnCallRolesRestService.java::138 — one calendar request re-checks groups.xml 28–31 times, because getRoleScheduleEntries starts with its own update(). A mid-request hand-edit also splits the month across two configs, and a role removed mid-loop NPEs at GroupManager.java:676.
- OnCallRolesRestService.java::495 — overnight shifts are unexpressible with no hint. The rejection is correct (the runtime throws rather than wrapping), but a 20:00–06:00 night shift is the canonical on-call case and the message never says to split it in two, unlike the sibling destination-path validator.
- The non-admin test skips PUT, /rename, and /calendar — the three most sensitive methods.
…coverage !smoke The calendar endpoint now loads the role once and resolves every day from that snapshot (new GroupManager overload skipping the per-call update()), and an entry the interval math cannot evaluate - e.g. a hand-edited overnight window - marks the response with schedule-error instead of turning a valid GET into a 400 with an internal message. The dialog shows that as a warning banner. Validation now rejects years outside groups.xsd's 1000-2999 before the marshal can fail, blocks C0 control characters in role names, and the overnight-shift rejection explains splitting into two entries. The date pickers pin the same range. The non-admin IT covers PUT, rename, and calendar; new ITs cover the overnight tolerance, the year guard, the control-character guard, and the split hint. 22 ITs green locally.
|
@marshallmassengill all six addressed in
|
…a/NMS-20108-oncall-roles-pr # Conflicts: # opennms-webapp/src/main/webapp/WEB-INF/applicationContext-spring-security.xml # ui/src/main/router/index.ts # ui/src/services/index.ts
Rewrites Admin → On-Call Roles as a PrimeVue page backed by a new versioned REST API, keeping groups.xml as the system of record.
Adds admin-only
/api/v2/on-call-roles: list/get/create/update/rename/delete plus a computed month calendar that reuses the exact interval resolution notifd uses;time-zoneis reported so clients render times in server time.Hand-edited roles round-trip untouched and stay editable: file changes are picked up on read, schedule entries already stored skip new-entry validation, the optional time
idattribute is preserved, and a role whose schedule the runtime cannot evaluate reportsschedule-errorinstead of failing the list.New entries are validated and canonicalized to the exact forms
BasicScheduleUtilsdispatches on (zero-padded widths, English month names, lowercase weekday, unpadded monthly day); overnightbegins>endsentries are rejected because the runtime never matches them.The page shows the legacy list columns plus an explicit supervisor-fallback indicator, a month calendar with one-off coverage add/remove (content-matched removal, refetch-before-mutate, confirmation before removing recurring entries), rename/delete with referential warnings, and inline field validation with API errors shown in the dialogs.
The admin menu entry now points at the new page. 16 integration tests cover the API; store, validation and dialog behavior are covered by vitest.
Replaces fork PR NMS-20108: On-Call Roles management via versioned REST API and PrimeVue UI #8702 with an in-repo branch so CI runs.