Skip to content

fix(frontend): sort the admin user columns ascending under the ascend caret - #7825

Open
aglinxinyuan wants to merge 2 commits into
apache:mainfrom
aglinxinyuan:fix/admin-user-sort-direction
Open

fix(frontend): sort the admin user columns ascending under the ascend caret#7825
aglinxinyuan wants to merge 2 commits into
apache:mainfrom
aglinxinyuan:fix/admin-user-sort-direction

Conversation

@aglinxinyuan

@aglinxinyuan aglinxinyuan commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

What changes were proposed in this PR?

Seven sortable columns in the admin user table rendered backwards relative to the caret they lit. sortByID, sortByName, sortByEmail, sortByAffiliation, sortByJoiningReason, sortByComment and sortByRole all compared with reversed operands.

ng-zorro uses an NzTableSortFn's result as-is for ascend and negates it for descend (ng-zorro-antd-table.mjs:822). Every one of these seven headers declares nzSortDirections="['ascend','descend']" — no null member — so the first click is always ascend, and reversed operands render Z→A under a lit up-caret.

All seven were verified individually against their own <th> before being touched; none was already correct and none started at descend:

Comparator <th> nzSortDirections
sortByID line 41, ['ascend','descend']
sortByName line 46, ['ascend','descend']
sortByEmail line 60, ['ascend','descend']
sortByAffiliation line 74, ['ascend','descend']
sortByJoiningReason line 79, ['ascend','descend']
sortByComment line 84, ['ascend','descend']
sortByRole line 99 (nzSortFn at 105), ['ascend','descend']

Same defect and same signature as sortBySize in #7806. Two corroborations inside this file: the tie-breaker in all six string comparators is the contract-correct ascending a.uid - b.uid, and sortByAccountCreation already uses ascending form under an identical header.

Why sortByID is in scope after all

An earlier revision of this PR left sortByID alone, on the theory that "highest uid first" might be a deliberate newest-accounts-first default rather than the same defect. It isn't, for two reasons:

  1. No column in this template sets nzSortOrder (grep -c nzSortOrder over admin-user.component.html returns 0). The table has no default sort at all — it renders in the order rows arrive until someone clicks a header. So the reversal cannot express a "newest first" default, because the comparator does not run until a click. Its only observable effect was that clicking the up caret on ID rendered highest-uid-first.
  2. The column that actually encodes recency is already ascending. sortByAccountCreation is (a - b), i.e. oldest-first under the up caret. If newest-first were the product stance, that is the column that would be reversed. It isn't, so ID was the lone inversion in the table.

sortByActive was examined for the same signature and deliberately not changed, only documented. Its key is a boolean, so there is no natural A→Z to reverse; ng-zorro's contract asks a comparator to declare its own ascending order, and "active before inactive" is that order — it is also what makes the first click useful. The existing unit test already pins both directions plus the uid tiebreak.

The existing spec was cementing the defect

Its "column sort comparators" block asserted the reversed behaviour directly — e.g. sortByName(Alice, Bob) > 0, and sortByID had a bare it("orders by descending uid"). Those assertions are corrected here, which is why they appear in the failing-before set below rather than being untouched.

Failing before, passing after

production reverted with fix
admin-user.component.spec.ts 14 failed, 41 passed 55 passed

14 is exactly 7 comparators × 2 tests each. The rendered-header failures show the #7806 signature — the caret assertions passed and only the order failed, e.g. sorts User Role A-to-Z…expected [3, 2, 1, 4] to deeply equal [4, 1, 2, 3], and for the ID column expected [4, 3, 2, 1] to deeply equal [1, 2, 3, 4].

Test design, which matters more here than the fix

The fix is seven operand swaps; the risk is a test that cannot tell a correct comparator from one inverted the other way. Two layers, both covering all seven fields individually:

  1. Corrected per-comparator unit assertions (direction only; the existing null-value uid-tiebreak assertions are unaffected and kept).
  2. A describe("sorted columns (rendered header)"), table-driven over ASCENDING_BY_COLUMN and generating one it() per column so each gets a fresh fixture — these headers have no null state, so a shared fixture would leave earlier columns sorted as secondary keys. Each case clicks the real rendered <th>, waits a macrotask (nz-table republishes its sort operators on delay(0), so a bare detectChanges() reads the previous ordering), and pins the uid order and which caret carries .active, in both directions.

The four fixture rows carry a distinct value in every sorted field, so no comparator reaches its uid tiebreak and any row exchange is visible. The seven per-field orders were chosen so all fourteen sequences (7 ascending + 7 descending) are distinct across the table and none equals the supplied order — no column's expectation can be satisfied by another column's sort, or by the table not sorting at all.

Adding the ID column tightened that last constraint, since uid is what the assertions are written in. The rows were supplied 1,2,3,4, which is exactly ID's ascending expectation — that leg would have passed against a table that never sorted. They are now supplied 4,2,1,3, checked against all 24 permutations to collide with none of the fourteen asserted sequences, and the invariant is recorded in a comment so a future reshuffle re-checks it.

Three mutation checks confirm both halves are load-bearing:

  • Re-inverting only sortByID: 2 failed | 53 passed — its unit test and its rendered test, nothing else.
  • Re-inverting only sortByAffiliation: 2 failed | 53 passed. So no test passes while six of seven are still broken.
  • Keeping affiliation inverted but flipping its header to ['descend','ascend'] — the tempting wrong "fix" that yields the right order with the wrong caret — still fails, on expected false to be true from the caret assertion.

Verification

  • admin-user.component.spec.ts 55/55; sibling user-quota.component.spec.ts 25/25, unaffected.
  • No other file in frontend/src references these comparators (user-project.component.ts's sortByNameAsc/Desc are explicit-direction buttons, not NzTableSortFn).
  • yarn format:ci exits 0; no junit.xml left behind.

Any related issues, documentation, discussions?

Closes #7824

How was this PR tested?

npx ng test --watch=false --include="**/admin-user.component.spec.ts"
 Test Files  1 passed (1)
      Tests  55 passed (55)

Was this PR authored or co-authored using generative AI tooling?

Generated-by: Claude Code (Opus 5)

Copilot AI lite review requested due to automatic review settings August 22, 2026 00:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@Yicong-Huang Yicong-Huang added the release/v1.2 back porting to release/v1.2 label Aug 22, 2026
@github-actions
github-actions Bot requested a review from xuang7 August 22, 2026 00:17
@github-actions

Copy link
Copy Markdown
Contributor

Automated Reviewer Suggestions

Based on the git blame history of the changed files, we recommend the following reviewers:

  • Contributors with relevant context: @mengw15, @xuang7
    You can notify them by mentioning @mengw15, @xuang7 in a comment.

@github-actions

github-actions Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Backport auto-label report

This fix: PR was checked against each actively-supported release branch. release/* labels drive the post-merge backport, so add or remove one to change where this fix lands.

Release branch Analysis
release/v1.2 Already labeled — this fix is queued to backport here.

Auto-label run.

@github-actions github-actions Bot added fix frontend Changes related to the frontend GUI labels Aug 22, 2026
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.50%. Comparing base (2edbdf9) to head (80ffe34).

Additional details and impacted files
@@            Coverage Diff            @@
##               main    #7825   +/-   ##
=========================================
  Coverage     91.50%   91.50%           
  Complexity     4501     4501           
=========================================
  Files          1177     1177           
  Lines         47489    47489           
  Branches       5324     5324           
=========================================
  Hits          43457    43457           
  Misses         2366     2366           
  Partials       1666     1666           
Flag Coverage Δ *Carryforward flag
access-control-service 81.00% <ø> (ø) Carriedforward from 2edbdf9
agent-service 98.62% <ø> (ø) Carriedforward from 2edbdf9
amber 88.04% <ø> (ø) Carriedforward from 2edbdf9
computing-unit-managing-service 73.67% <ø> (ø) Carriedforward from 2edbdf9
config-service 86.73% <ø> (ø) Carriedforward from 2edbdf9
file-service 75.74% <ø> (ø) Carriedforward from 2edbdf9
frontend 93.24% <100.00%> (ø)
notebook-migration-service 79.13% <ø> (ø) Carriedforward from 2edbdf9
pyamber 97.57% <ø> (ø) Carriedforward from 2edbdf9
workflow-compiling-service 77.19% <ø> (ø) Carriedforward from 2edbdf9

*This pull request uses carry forward flags. 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:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

…nd caret

nz-table applies an NzTableSortFn's result as-is for 'ascend' and negates it
for 'descend', so sortByID's reversed operands rendered the highest uid first
under the up caret. The reversal was not a "newest accounts first" default: no
column sets nzSortOrder, so the comparator only runs once a header is clicked,
and Account Creation Time, the column that actually encodes recency, already
sorts oldest-first.

Extend the rendered-header table with the ID column so it pins the row order
and the active caret in both directions, and supply the rows in an order that
matches no column's ascending or descending expectation, so no leg can pass
against a table that does not sort. Also note why sortByActive's
active-before-inactive ranking is its ascending order rather than a reversal.
@aglinxinyuan
aglinxinyuan requested a review from mengw15 August 22, 2026 06:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fix frontend Changes related to the frontend GUI release/v1.2 back porting to release/v1.2

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Six admin user table columns sort backwards under their own caret

4 participants