Skip to content

Enable save and delete on a freshly created entity (#274, #268) - #281

Open
p-hoffmann wants to merge 2 commits into
developfrom
p-hoffmann/fix-274-ownership-on-create
Open

Enable save and delete on a freshly created entity (#274, #268)#281
p-hoffmann wants to merge 2 commits into
developfrom
p-hoffmann/fix-274-ownership-on-create

Conversation

@p-hoffmann

@p-hoffmann p-hoffmann commented Aug 21, 2026

Copy link
Copy Markdown
Member

Problem

An entity you create during a session cannot be edited or deleted until you refresh the page. Save and Delete are disabled on the thing you just made (#274), and the cohort definition Delete button behaves the same way (#268). After a refresh, both work.

Cause

WebAPI reports per-entity permissions only through /user/me, which the client requests once at startup. An entity created later in the session is not in that snapshot, so the client finds no permission for it and disables writing. Refreshing re-requests /user/me, the permission appears, and the buttons come back to life.

What changes

When a create succeeds, the client now records you as the owner of the new entity straight away — the server accepting the request already establishes that — so Save and Delete work immediately, without a refresh.

This covers cohort definitions, concept sets, characterizations, feature analyses, pathways and incidence rates, including copying an incidence rate.

Fixes #274
Fixes #268

Per-entity grants are only delivered by /user/me at startup, so an entity
created during the session had no grant until the next page load.
useEntityAccess therefore denied write on the thing the user had just
made, leaving Save and Delete disabled in the editor until a refresh.

Record ownership locally when a create succeeds. The server accepting the
POST already establishes the user as the creator, so this matches what
/user/me reports after a reload.
@codecov

codecov Bot commented Aug 21, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.84%. Comparing base (77a7c15) to head (28d8844).
⚠️ Report is 7 commits behind head on develop.

Additional details and impacted files
@@             Coverage Diff             @@
##           develop     #281      +/-   ##
===========================================
+ Coverage    94.79%   94.84%   +0.04%     
===========================================
  Files          488      491       +3     
  Lines        66605    66977     +372     
  Branches     13627    13755     +128     
===========================================
+ Hits         63136    63521     +385     
+ Misses        3469     3456      -13     
Flag Coverage Δ
unit 94.84% <100.00%> (+0.04%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@chrisknoll

Copy link
Copy Markdown
Collaborator

this might be worth taking a look at the 2.x codebase for how we synchronize client-side permissions with server side.

If i understand the code correctly, you're doing something related to 'register ownership' but the ownership grant is handled on the server side, and you would get your updated permissions from the server (and at this time you can get all the other perms that may have changed). Sounds inefficient (why get everything when you know which permissions changed?), but the reality is that many permissions may change (like you import a Characterization design where new cohorts have been created in the result) so it's just more canonical to refresh the entire set of permissions after a request is performed that may alter user permissions. We also should be periodically refreshing user permissions (but that can be a potentially separate issue, but could be handled here because the crux of this issue is permissions became stale).

I'm going to provide a detailed description about how Atlas 2.x handles this in a follow up, if you would like to wait to address this. My CoPilot is going through the code and producing a summary.

@chrisknoll

chrisknoll commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

@p-hoffmann : here's what I got, its a pretty good summary and includes a suggested bridge strategy:

Atlas2 Permission Refresh Flow

Atlas2 keeps client-side permissions aligned with the server by treating the server as the source of truth and reloading the current user’s authorization snapshot whenever the app enters a state where permissions may have changed. The core refresh path is in js/services/AuthAPI.js: loadUserInfo() calls GET user/me, stores the returned authorization payload in the client permissions observable, and updates the current user identity. For write flows, Atlas wraps the underlying API call in executeWithRefresh(...), which waits for the service method to complete and then immediately re-runs loadUserInfo() so permission changes caused by saves are reflected in the UI. You can see that pattern in entity mutation paths such as js/services/CohortDefinition.js and js/services/ConceptSet.js.

There are two additional refresh triggers. First, js/services/AuthAPI.js also starts a periodic poll that re-calls loadUserInfo() on an interval, which acts as a fallback for long-lived sessions. Second, navigation changes refresh permissions in js/pages/Router.js: setCurrentView(...) calls authApi.loadUserInfo() whenever the user moves to a new route. The overall strategy is simple: refresh after permission-affecting writes, refresh on navigation, and refresh periodically as a safety net.

Atlas3 Bridge Strategy

Atlas3 already has the same ingredients, but split across composables, stores, and service modules instead of one monolithic auth service. The server round-trip lives in src/services/auth/authService.ts, where fetchUserInfo() calls user/me and refreshToken() renews the JWT. Reactive auth state lives in src/stores/auth.ts, while permission checks and cache invalidation live in src/services/auth/permissions.ts. The important bridge point is setUser(...) in the auth store: it clears the permission cache before swapping the current subject, which is the Atlas3 equivalent of reloading the observable permissions snapshot in Atlas2.

To recreate Atlas2 behavior, the Atlas3 equivalent of executeWithRefresh(...) should be a shared orchestration step used by mutation services: await the write, then call a single refresh action that re-fetches user/me and updates the auth store. That gives you the same post-save permission sync for cohort, concept set, characterization, and similar entity writes, without spreading user/me calls through components. For route changes, the refresh should be wired at the router layer in src/router/index.ts, so navigation can trigger the same refresh action after the route settles. Periodic refresh and cross-tab sync are already separated into src/services/auth/tokenRefresh.ts and src/services/auth/sessionSync.ts, so the migration goal is mostly to reuse one shared “refresh current user context” action from all three triggers: write completion, navigation, and timer/sync events.

Recording the creator as owner client-side fixes the disabled Save and
Delete buttons, but it only covers the entity whose id came back. A create
can grant more than that: importing a design creates cohorts and concept
sets of its own, and none of those ids are visible to the client. It also
guesses at an answer the server already has, so a WebAPI that does not grant
the creator write would leave the buttons wrongly enabled.

Re-read user/me after a create and let it replace the local grant. The
optimistic grant stays as the first step so the editor unblocks without
waiting on the round trip, and as the fallback when user/me cannot be
reached. Rename registerCreatedEntity to syncAccessAfterCreate now that it
does both.

Also register the three import endpoints, which create an entity server-side
and were never covered, so #274 still reproduced through Import.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Edit/Update/Delete - Not working for new items (cohort definition, characterization, concept set) Deletion - Can't delete the cohort definition

2 participants