You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@texera.apache.org by gi...@apache.org on 2026/10/05 22:03:45 UTC

(texera) branch gh-readonly-queue/main/pr-8752-e848f945ca8b315b3bc3419e1354bf5928cd71fc created (now e2e0225263)

This is an automated email from the ASF dual-hosted git repository.

github-merge-queue[bot] pushed a change to branch gh-readonly-queue/main/pr-8752-e848f945ca8b315b3bc3419e1354bf5928cd71fc
in repository https://gitbox.apache.org/repos/asf/texera.git


      at e2e0225263 fix(frontend): restore the hub workflow view-count toggle (#8752)

This branch includes the following new commits:

     new e2e0225263 fix(frontend): restore the hub workflow view-count toggle (#8752)

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(texera) 01/01: fix(frontend): restore the hub workflow view-count toggle (#8752)

Posted by gi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/main/pr-8752-e848f945ca8b315b3bc3419e1354bf5928cd71fc
in repository https://gitbox.apache.org/repos/asf/texera.git

commit e2e0225263bf5400b5f6d2d6c99475b9e836cc04
Author: Xinyuan Lin <xi...@uci.edu>
AuthorDate: Mon Oct 5 21:53:13 2026 +0000

    fix(frontend): restore the hub workflow view-count toggle (#8752)
    
    ### What changes were proposed in this PR?
    
    Clicking the eye ("View") button on the hub workflow detail page
    switches the view count between the compact form (`1.2k`) and the exact
    count (`1,234`) again. The button has done nothing since #3028.
    
    **Root cause.** Only one method read `displayPreciseViewCount`:
    `formatViewCount`. #3028 deleted it as a duplicate of the like-count
    formatter, but the two were not the same. `formatViewCount` skipped the
    `k` abbreviation while the flag was set. The button, its `(click)`
    handler and the flag were all kept, so each click flipped a flag that
    nothing read.
    
    ```
    Before:  click eye -> "1.2k";  click again -> "1.2k"
    After:   click eye -> "1,234"; click again -> "1.2k"
    ```
    
    `formatViewCount` comes back as a thin wrapper over the shared
    `formatCount`, and the view button's label uses it again. The exact
    count is grouped by thousands with the app's `LOCALE_ID`, the same
    formatting as the `number` pipe used elsewhere in the UI. The app sets
    no `LOCALE_ID`, so it shows `1,234`. The like and clone counts still go
    through `formatCount` and stay compact.
    
    | | Default | After one click |
    | --- | --- | --- |
    | **Before** | ![before,
    default](https://raw.githubusercontent.com/aglinxinyuan/texera/630604d0570603be76e7caac68436e82575d6d1c/before-default.png)
    | ![before,
    clicked](https://raw.githubusercontent.com/aglinxinyuan/texera/630604d0570603be76e7caac68436e82575d6d1c/before-clicked.png)
    |
    | **After** | ![after,
    default](https://raw.githubusercontent.com/aglinxinyuan/texera/630604d0570603be76e7caac68436e82575d6d1c/after-default.png)
    | ![after,
    clicked](https://raw.githubusercontent.com/aglinxinyuan/texera/630604d0570603be76e7caac68436e82575d6d1c/after-clicked.png)
    |
    
    ### History
    
    | | |
    | --- | --- |
    | **Introduced by** | #2930 (2024-10-28), "Add view count to workflow":
    the eye button, `changeViewDisplayStyle()`, `displayPreciseViewCount`,
    and `formatViewCount`, which read the flag. The PR's demo shows the
    exact count after a click |
    | **Usage removed by** | #3028 (2024-11-11), "Remove duplicate and
    unnecessary code": deleted `formatViewCount` and switched the label to
    `formatCount` |
    
    The toggle worked for two weeks and has done nothing for almost two
    years since. The dataset detail page's copy of the button is a different
    case: #3269 added it after #3028, so it never worked there, and #8746
    removes it.
    
    ### Any related issues, documentation, discussions?
    
    Closes #8751
    
    ### How was this PR tested?
    
    The old spec case only checked that the flag flipped, and it passed
    while the button did nothing. It is replaced by:
    
    | Surface | Pinned |
    | --- | --- |
    | `formatViewCount` | Compact by default. Exact (`1,234`) after one
    toggle, compact again after a second. `1000` shows as `1.0k` / `1,000`.
    `0`, `1` and `999` look the same in both styles. The compact style of
    `1234567` equals `formatCount`, and the exact style is `1,234,567`. With
    `LOCALE_ID` set to `de`, the exact style is `1.234` |
    | Rendered view button (real template) | Shows `1.2k` for 1234 views. A
    click shows `1,234`, a second click `1.2k`. Same when the page opens as
    a modal. The like count stays `2.5k` after the view button is clicked |
    
    The rendered-button cases were run against `main`'s component first and
    fail there with `expected '1.2k' to be '1,234'`.
    
    From `frontend/`:
    
    - `npx ng test --watch=false
    --include='**/hub-workflow-detail.component.spec.ts'`: 52 tests, all
    pass.
    - `yarn format:ci`: clean.
    - `npx ng build`: success.
    
    Screenshots: `ng serve` against a stub API that reports 1234 views, 56
    likes and 12 clones. Headless Chromium at 1280×720 via Playwright, with
    the button row captured before and after one click on the eye.
    **Before** is the same page running `main`'s component.
    
    ### Was this PR authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Code (Claude Opus 5.5)
    
    🤖 Generated with [Claude Code](https://claude.com/claude-code)
---
 .../detail/hub-workflow-detail.component.html      |   2 +-
 .../detail/hub-workflow-detail.component.spec.ts   | 101 +++++++++++++++++++--
 .../detail/hub-workflow-detail.component.ts        |  11 ++-
 3 files changed, 101 insertions(+), 13 deletions(-)

diff --git a/frontend/src/app/hub/component/workflow/detail/hub-workflow-detail.component.html b/frontend/src/app/hub/component/workflow/detail/hub-workflow-detail.component.html
index aed7848aac..d3c41ab3c9 100644
--- a/frontend/src/app/hub/component/workflow/detail/hub-workflow-detail.component.html
+++ b/frontend/src/app/hub/component/workflow/detail/hub-workflow-detail.component.html
@@ -48,7 +48,7 @@
             [nzType]="'eye'"
             class="like-icon">
           </i>
-          <span>{{ formatCount(viewCount) }}</span>
+          <span>{{ formatViewCount(viewCount) }}</span>
         </button>
 
         <button
diff --git a/frontend/src/app/hub/component/workflow/detail/hub-workflow-detail.component.spec.ts b/frontend/src/app/hub/component/workflow/detail/hub-workflow-detail.component.spec.ts
index 25e91bbc82..a97678eb51 100644
--- a/frontend/src/app/hub/component/workflow/detail/hub-workflow-detail.component.spec.ts
+++ b/frontend/src/app/hub/component/workflow/detail/hub-workflow-detail.component.spec.ts
@@ -17,7 +17,9 @@
  * under the License.
  */
 
-import { Component, Input } from "@angular/core";
+import { Component, Input, LOCALE_ID } from "@angular/core";
+import { registerLocaleData } from "@angular/common";
+import localeDe from "@angular/common/locales/de";
 import { ComponentFixture, TestBed } from "@angular/core/testing";
 import { ActivatedRoute, Router } from "@angular/router";
 import { NzIconModule } from "ng-zorro-antd/icon";
@@ -524,14 +526,52 @@ describe("HubWorkflowDetailComponent", () => {
     });
   });
 
-  describe("changeViewDisplayStyle", () => {
-    it("toggles displayPreciseViewCount", () => {
+  // Asserted on the formatted string rather than on displayPreciseViewCount: the flag flipped on
+  // every click even while nothing read it, so a flag assertion passed with the toggle inert.
+  describe("formatViewCount", () => {
+    it("returns the compact count before the view style is changed", () => {
       build({ modalData: { wid: 1 } });
-      expect(component.displayPreciseViewCount).toBe(false);
+      expect(component.formatViewCount(1234)).toBe("1.2k");
+    });
+
+    it("returns the grouped exact count after one change and the compact count after a second", () => {
+      build({ modalData: { wid: 1 } });
+      component.changeViewDisplayStyle();
+      expect(component.formatViewCount(1234)).toBe("1,234");
+      component.changeViewDisplayStyle();
+      expect(component.formatViewCount(1234)).toBe("1.2k");
+    });
+
+    it("abbreviates 1000 only in the compact style", () => {
+      build({ modalData: { wid: 1 } });
+      expect(component.formatViewCount(1000)).toBe("1.0k");
+      component.changeViewDisplayStyle();
+      expect(component.formatViewCount(1000)).toBe("1,000");
+    });
+
+    it("renders counts below 1000 the same way in both styles", () => {
+      build({ modalData: { wid: 1 } });
+      expect([0, 1, 999].map(count => component.formatViewCount(count))).toEqual(["0", "1", "999"]);
       component.changeViewDisplayStyle();
-      expect(component.displayPreciseViewCount).toBe(true);
+      expect([0, 1, 999].map(count => component.formatViewCount(count))).toEqual(["0", "1", "999"]);
+    });
+
+    it("keeps every digit of a large count in the exact style", () => {
+      build({ modalData: { wid: 1 } });
+      expect(component.formatViewCount(1234567)).toBe(component.formatCount(1234567));
+      component.changeViewDisplayStyle();
+      expect(component.formatViewCount(1234567)).toBe("1,234,567");
+    });
+
+    it("groups the exact count by the app locale", () => {
+      // The separator comes from LOCALE_ID, like the `number` pipe the rest of the UI uses, rather
+      // than a hard-coded "en-US"; the app sets no LOCALE_ID, so it renders "1,234" today.
+      registerLocaleData(localeDe);
+      configure({ modalData: { wid: 1 } });
+      TestBed.overrideProvider(LOCALE_ID, { useValue: "de" });
+      component = TestBed.createComponent(HubWorkflowDetailComponent).componentInstance;
       component.changeViewDisplayStyle();
-      expect(component.displayPreciseViewCount).toBe(false);
+      expect(component.formatViewCount(1234)).toBe("1.234");
     });
   });
 });
@@ -553,7 +593,7 @@ describe("HubWorkflowDetailComponent rendered with its real children", () => {
   // goBack() chains .catch() onto the navigation result, so this has to be a real promise.
   let renderedRouter: { navigateByUrl: ReturnType<typeof vi.fn>; navigate: ReturnType<typeof vi.fn> };
 
-  function render(opts: { isHub: boolean }): void {
+  function render(opts: { isHub: boolean; viewCount?: number; likeCount?: number }): void {
     renderedRouter = { navigateByUrl: vi.fn().mockResolvedValue(true), navigate: vi.fn().mockResolvedValue(true) };
     TestBed.resetTestingModule();
     TestBed.configureTestingModule({
@@ -579,8 +619,8 @@ describe("HubWorkflowDetailComponent rendered with its real children", () => {
         {
           provide: HubService,
           useValue: {
-            getCounts: () => of([{ entityId: 5, entityType: EntityType.Workflow, counts: {} }]),
-            postView: () => of(7),
+            getCounts: () => of([{ entityId: 5, entityType: EntityType.Workflow, counts: { like: opts.likeCount } }]),
+            postView: () => of(opts.viewCount ?? 7),
             isLiked: () => of([]),
             postLike: () => of(true),
             postUnlike: () => of(true),
@@ -647,4 +687,47 @@ describe("HubWorkflowDetailComponent rendered with its real children", () => {
 
     expect((fixture.nativeElement as HTMLElement).querySelector(".go-back-button")).toBeNull();
   });
+
+  describe("the view button", () => {
+    function countShownBy(title: string): string {
+      const button = (fixture.nativeElement as HTMLElement).querySelector(`button[title='${title}']`)!;
+      return button.querySelector(":scope > span")!.textContent!.trim();
+    }
+
+    function clickView(): void {
+      (fixture.nativeElement as HTMLElement).querySelector<HTMLButtonElement>("button[title='View']")!.click();
+      fixture.detectChanges();
+    }
+
+    it("shows the compact view count until it is clicked", () => {
+      render({ isHub: true, viewCount: 1234 });
+
+      expect(countShownBy("View")).toBe("1.2k");
+    });
+
+    it("shows the exact view count on a click and the compact count on a second click", () => {
+      render({ isHub: true, viewCount: 1234 });
+
+      clickView();
+      expect(countShownBy("View")).toBe("1,234");
+      clickView();
+      expect(countShownBy("View")).toBe("1.2k");
+    });
+
+    it("works the same when the page is opened as a modal", () => {
+      render({ isHub: false, viewCount: 1234 });
+
+      clickView();
+      expect(countShownBy("View")).toBe("1,234");
+    });
+
+    it("leaves the like count compact", () => {
+      // The toggle belongs to the view count alone; the like count shares the formatter, so a
+      // style change that leaked into formatCount would show up here.
+      render({ isHub: true, viewCount: 1234, likeCount: 2500 });
+
+      clickView();
+      expect(countShownBy("Like")).toBe("2.5k");
+    });
+  });
 });
diff --git a/frontend/src/app/hub/component/workflow/detail/hub-workflow-detail.component.ts b/frontend/src/app/hub/component/workflow/detail/hub-workflow-detail.component.ts
index 83e82cf288..16bf16668e 100644
--- a/frontend/src/app/hub/component/workflow/detail/hub-workflow-detail.component.ts
+++ b/frontend/src/app/hub/component/workflow/detail/hub-workflow-detail.component.ts
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-import { AfterViewInit, Component, HostListener, Inject, OnDestroy, OnInit, Optional } from "@angular/core";
+import { AfterViewInit, Component, HostListener, Inject, LOCALE_ID, OnDestroy, OnInit, Optional } from "@angular/core";
 import { UntilDestroy, untilDestroyed } from "@ngneat/until-destroy";
 import { ActivatedRoute, Router } from "@angular/router";
 import { UserService } from "../../../../common/service/user/user.service";
@@ -31,7 +31,7 @@ import { NotificationService } from "../../../../common/service/notification/not
 import { WorkflowPersistService } from "../../../../common/service/workflow-persist/workflow-persist.service";
 import { NZ_MODAL_DATA } from "ng-zorro-antd/modal";
 import { HUB_WORKFLOW_RESULT, USER_WORKSPACE } from "../../../../app-routing.constant";
-import { NgIf, NgClass } from "@angular/common";
+import { formatNumber, NgIf, NgClass } from "@angular/common";
 import { NzSpaceCompactItemDirective } from "ng-zorro-antd/space";
 import { NzButtonComponent } from "ng-zorro-antd/button";
 import { ɵNzTransitionPatchDirective } from "ng-zorro-antd/core/transition-patch";
@@ -85,7 +85,8 @@ export class HubWorkflowDetailComponent implements AfterViewInit, OnDestroy, OnI
     private notificationService: NotificationService,
     private hubService: HubService,
     private workflowPersistService: WorkflowPersistService,
-    @Optional() @Inject(NZ_MODAL_DATA) public input: { wid: number } | undefined
+    @Optional() @Inject(NZ_MODAL_DATA) public input: { wid: number } | undefined,
+    @Inject(LOCALE_ID) private locale: string
   ) {
     this.wid = input?.wid; //Accessing from the pop up. getting wid from the @Input
     if (!isDefined(this.wid)) {
@@ -269,6 +270,10 @@ export class HubWorkflowDetailComponent implements AfterViewInit, OnDestroy, OnI
 
   formatCount = formatCount;
 
+  formatViewCount(count: number): string {
+    return this.displayPreciseViewCount ? formatNumber(count, this.locale) : formatCount(count);
+  }
+
   changeViewDisplayStyle() {
     this.displayPreciseViewCount = !this.displayPreciseViewCount;
   }