Skip to content

Add a permissions warning on contributor merge - #1576

Open
danlamanna wants to merge 3 commits into
masterfrom
contributor-merge-warning
Open

Add a permissions warning on contributor merge#1576
danlamanna wants to merge 3 commits into
masterfrom
contributor-merge-warning

Conversation

@danlamanna

@danlamanna danlamanna commented Aug 7, 2026

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features

    • Added a contributor merge impact preview for staff.
    • Displays affected users, access changes, contributor accession and image counts, and engagement-profile updates.
    • Shows future-upload access implications before completing a merge.
    • Added clearer loading, validation, and no-impact messaging.
  • Bug Fixes

    • Prevents impact checks when the same contributor is selected for both sides.
    • Clears outdated impact information when selections are incomplete or changed.
  • Tests

    • Added coverage for permissions, access changes, profile updates, and browser-visible merge messaging.

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds contributor merge-impact computation, a staff-only API endpoint, autocomplete selection events, Alpine form integration, impact messaging, and unit, API, and browser tests.

Changes

Contributor merge impact

Layer / File(s) Summary
Merge impact computation
isic/ingest/services/contributor/__init__.py, isic/ingest/tests/test_merge.py
The service computes affected users, contributor accession and image counts, engagement-profile status, and profiles that will be repointed.
Staff API contract and endpoint
isic/ingest/api.py, isic/ingest/tests/test_api_contributor.py
The API returns merge-impact data, rejects identical contributor IDs with HTTP 400, and restricts access to staff clients.
Selection events and impact loading
isic/ingest/static/ingest/autocomplete.js, isic/ingest/static/ingest/contributor_merge_impact.js, isic/ingest/templates/ingest/contributor_merge.html, isic/ingest/templates/ingest/partials/autocomplete_field.html
Autocomplete fields emit selection events. The Alpine component validates selections, requests impact data, and manages loading state.
Impact alert rendering and browser validation
isic/ingest/templates/ingest/partials/contributor_merge_impact.html, isic/ingest/templates/ingest/partials/merge_impact_users.html, isic/ingest/tests/test_merge_contributors_browser.py
The merge form displays affected users, counts, future-upload access, and profile repointing details. Browser tests validate selection and clearing behavior.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Staff
  participant autocompleteInput
  participant contributorMergeImpact
  participant contributor_merge_impact
  participant compute_contributor_merge_impact
  Staff->>autocompleteInput: select destination and source contributors
  autocompleteInput->>contributorMergeImpact: dispatch autocomplete-selection
  contributorMergeImpact->>contributor_merge_impact: request merge impact
  contributor_merge_impact->>compute_contributor_merge_impact: compute contributor effects
  compute_contributor_merge_impact-->>contributor_merge_impact: return impact data
  contributor_merge_impact-->>contributorMergeImpact: return API response
  contributorMergeImpact-->>Staff: display merge-impact alert
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: displaying a permissions warning during contributor merges.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch contributor-merge-warning

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@isic/ingest/static/ingest/contributor_merge_impact.js`:
- Around line 16-34: Update refresh() to clear impact when starting every valid
request and track the request or selection identity so responses from superseded
fetches cannot update impact. Preserve loading cleanup for the active request,
and ensure changing or clearing selections leaves stale results cleared. Add a
browser test covering a selection change or clear before the first response
resolves.
🪄 Autofix

✅ Autofix completed


ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 59388345-19d1-475e-bcdd-713c6be18ffa

📥 Commits

Reviewing files that changed from the base of the PR and between 2f83b38 and afa899b.

📒 Files selected for processing (11)
  • isic/ingest/api.py
  • isic/ingest/services/contributor/__init__.py
  • isic/ingest/static/ingest/autocomplete.js
  • isic/ingest/static/ingest/contributor_merge_impact.js
  • isic/ingest/templates/ingest/contributor_merge.html
  • isic/ingest/templates/ingest/partials/autocomplete_field.html
  • isic/ingest/templates/ingest/partials/contributor_merge_impact.html
  • isic/ingest/templates/ingest/partials/merge_impact_users.html
  • isic/ingest/tests/test_api_contributor.py
  • isic/ingest/tests/test_merge.py
  • isic/ingest/tests/test_merge_contributors_browser.py

Comment on lines +16 to +34
async refresh() {
const dest = this.selections[destField] || '';
const src = this.selections[srcField] || '';

// merging a contributor into itself is rejected by the form, so there's nothing to warn about
if (!dest || !src || dest === src) {
this.impact = null;
this.loading = false;
return;
}

this.loading = true;
const params = new URLSearchParams({ dest_contributor: dest, src_contributor: src });
try {
const response = await fetch(`${impactUrl}?${params}`);
this.impact = response.ok ? await response.json() : null;
} finally {
this.loading = false;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Prevent stale merge-impact responses from updating the current selection.

refresh() does not associate a response with the selection that started the request. If a user changes or clears a contributor while fetch() is pending, an older response can set impact after the current selection has changed. The warning can then show incorrect access effects.

Clear impact when a new valid request starts. Ignore results from superseded requests. Add a browser test that changes or clears a selection before the first response completes.

Proposed fix
 function contributorMergeImpact({ impactUrl, destField, srcField }) {
   return {
     selections: {},
     impact: null,
     loading: false,
+    refreshVersion: 0,

     async refresh() {
+      const refreshVersion = ++this.refreshVersion;
       const dest = this.selections[destField] || '';
       const src = this.selections[srcField] || '';

       if (!dest || !src || dest === src) {
         this.impact = null;
         this.loading = false;
         return;
       }

       this.loading = true;
+      this.impact = null;
       const params = new URLSearchParams({ dest_contributor: dest, src_contributor: src });
       try {
         const response = await fetch(`${impactUrl}?${params}`);
-        this.impact = response.ok ? await response.json() : null;
+        const impact = response.ok ? await response.json() : null;
+        if (refreshVersion === this.refreshVersion) {
+          this.impact = impact;
+        }
       } finally {
-        this.loading = false;
+        if (refreshVersion === this.refreshVersion) {
+          this.loading = false;
+        }
       }
     },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async refresh() {
const dest = this.selections[destField] || '';
const src = this.selections[srcField] || '';
// merging a contributor into itself is rejected by the form, so there's nothing to warn about
if (!dest || !src || dest === src) {
this.impact = null;
this.loading = false;
return;
}
this.loading = true;
const params = new URLSearchParams({ dest_contributor: dest, src_contributor: src });
try {
const response = await fetch(`${impactUrl}?${params}`);
this.impact = response.ok ? await response.json() : null;
} finally {
this.loading = false;
}
async refresh() {
const refreshVersion = ++this.refreshVersion;
const dest = this.selections[destField] || '';
const src = this.selections[srcField] || '';
// merging a contributor into itself is rejected by the form, so there's nothing to warn about
if (!dest || !src || dest === src) {
this.impact = null;
this.loading = false;
return;
}
this.loading = true;
this.impact = null;
const params = new URLSearchParams({ dest_contributor: dest, src_contributor: src });
try {
const response = await fetch(`${impactUrl}?${params}`);
const impact = response.ok ? await response.json() : null;
if (refreshVersion === this.refreshVersion) {
this.impact = impact;
}
} finally {
if (refreshVersion === this.refreshVersion) {
this.loading = false;
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@isic/ingest/static/ingest/contributor_merge_impact.js` around lines 16 - 34,
Update refresh() to clear impact when starting every valid request and track the
request or selection identity so responses from superseded fetches cannot update
impact. Preserve loading cleanup for the active request, and ensure changing or
clearing selections leaves stale results cleared. Add a browser test covering a
selection change or clear before the first response resolves.

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Cannot run autofix: This PR has merge conflicts.

Please resolve the conflicts with the base branch and try again.

Alternatively, use @coderabbitai resolve merge conflict to automatically resolve the conflicts.

@danlamanna

Copy link
Copy Markdown
Member Author

@coderabbitai resolve merge conflict

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Merge conflicts resolved successfully!

Resolved 2 conflict file(s). Commit: 38d4de30909d7ea53c4e9c51fd118eeed7042a93 pushed to contributor-merge-warning.

11 file operation(s)
  • isic/ingest/api.py (update)
  • isic/ingest/services/contributor/__init__.py (update)
  • isic/ingest/static/ingest/autocomplete.js (update)
  • isic/ingest/static/ingest/contributor_merge_impact.js (update)
  • isic/ingest/templates/ingest/contributor_merge.html (update)
  • isic/ingest/templates/ingest/partials/autocomplete_field.html (update)
  • isic/ingest/templates/ingest/partials/contributor_merge_impact.html (update)
  • isic/ingest/templates/ingest/partials/merge_impact_users.html (update)
  • isic/ingest/tests/test_api_contributor.py (update)
  • isic/ingest/tests/test_merge.py (update)
  • isic/ingest/tests/test_merge_contributors_browser.py (update)
View agent analysis

Resolved conflicts in:
- isic/ingest/services/contributor/__init__.py (content)
- isic/ingest/static/ingest/autocomplete.js (content)

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Fixes Applied Successfully

Fixed 2 file(s) based on 1 unresolved review comment.

Files modified:

  • isic/ingest/static/ingest/contributor_merge_impact.js
  • isic/ingest/tests/test_merge_contributors_browser.py

Commit: b68f3d8db30d4b9cc8ca8e4efaade020a67b2ba5

The changes have been pushed to the contributor-merge-warning branch.

Time taken: 4m 6s

Fixed 2 file(s) based on 1 unresolved review comment.

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
@danlamanna

Copy link
Copy Markdown
Member Author

@coderabbitai fix-ci

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

CI fix PR created: #1577

Follow #1577 for fix progress and CI status. If CI still fails, re-run the fix-ci command to try again.

Note: Fixing CI failures is a beta feature and may encounter errors. Expect some limitations and changes as we gather feedback and continue to improve it.

ℹ️ Local verification skipped — no lint, format, or test checks were found in this PR's CI config.

Note: If your CI is configured to only run on PRs targeting specific branches (e.g., main), it may not trigger on the fix PR. You can merge the fix into your branch and CI will validate on the original PR.

3 PR-caused check(s)

Showing 2 errors from 2 check run(s) out of ~3 total. Deferred 1 check run(s) (GitHub Actions: ci / 0_test.txt — test). These may resolve after fixing the shown errors, or may need a follow-up run.

  • GitHub Actions: ci / test
  • GitHub Actions: ci / test
  • GitHub Actions: ci / 0_test.txt
1 file(s) modified
  • isic/ingest/tests/test_merge_contributors_browser.py
View agent analysis
This test asserted `owner_a` (the destination contributor's owner) would not be visible in the merge-impact display. But per `compute_contributor_merge_impact` (isic/ingest/services/contributor/__init__.py:32-127) and its dedicated unit tests in `test_merge.py`, a destination-only owner legitimately gains access to the source contributor's data and is included in `users_gaining_access_to_src` — this is by design, not a stale-request artifact. Only `owner_b` (from the superseded contributor_b selection) should be absent.

I fixed the test assertion in `isic/ingest/tests/test_merge_contributors_browser.py:225-229` to expect `owner_a` visible (correct final impact) and only assert `owner_b` is hidden (the actual stale-data check the test is meant to verify).

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.

1 participant