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/07/09 02:06:08 UTC

(texera) branch gh-readonly-queue/main/pr-6285-2b9c7afabb89474ce988358c797f8949e058f158 created (now d2a928fd4d)

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-6285-2b9c7afabb89474ce988358c797f8949e058f158
in repository https://gitbox.apache.org/repos/asf/texera.git


      at d2a928fd4d fix(frontend): virtualize dataset version file tree (#6285)

This branch includes the following new commits:

     new d2a928fd4d fix(frontend): virtualize dataset version file tree (#6285)

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): virtualize dataset version file tree (#6285)

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-6285-2b9c7afabb89474ce988358c797f8949e058f158
in repository https://gitbox.apache.org/repos/asf/texera.git

commit d2a928fd4d1be1d640f99fc453d393932e73363c
Author: Kunwoo (Chris) <14...@users.noreply.github.com>
AuthorDate: Wed Jul 8 22:05:45 2026 -0400

    fix(frontend): virtualize dataset version file tree (#6285)
    
    ### What changes were proposed in this PR?
    
    This PR fixes the frontend issue where opening a dataset whose selected
    version contains hundreds of files freezes the frontend for tens of
    seconds to minutes.
    
    Root cause: the version file tree renders every file as a full tree-node
    component (`@ali-hm/angular-tree-component` with virtualization
    disabled), and each row's `nz-button` re-reads layout after every
    change-detection pass.
    
    This PR enables the tree's `useVirtualScroll` with a fixed `nodeHeight`,
    gives the tree container a definite height (200px, matching the previous
    `max-height`) so the viewport can size itself, and pins the row height
    in SCSS to match. Only the ~28 visible rows exist in the DOM regardless
    of file count, which also bounds the per-row button layout reads.
    
    Measured on the local dev stack (`ng serve`, Chrome, Long Tasks API),
    datasets of N one-byte files:
    
    | N files | Page usable after (before → after) | Main thread blocked
    (before → after) | DOM rows (before → after) |
    |---|---|---|---|
    | 400 | 24.5 s → 1.3 s | 22.2 s → 0.2 s | 400 → 28 |
    
    ### Any related issues, documentation, discussions?
    
    Closes #6281. Related to #5586 (same unbounded-rendering pattern in the
    upload lists).
    
    ### How was this PR tested?
    
    
    https://github.com/user-attachments/assets/702b6ffa-7ac3-479e-9ac0-f1f186aa4739
    
    ### Was this PR authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Code, Claude Fable 5
---
 .../user-dataset-version-filetree.component.scss   | 24 +++++-
 ...user-dataset-version-filetree.component.spec.ts | 89 ++++++++++++++++++++++
 .../user-dataset-version-filetree.component.ts     |  9 +++
 3 files changed, 118 insertions(+), 4 deletions(-)

diff --git a/frontend/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/user-dataset-version-filetree/user-dataset-version-filetree.component.scss b/frontend/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/user-dataset-version-filetree/user-dataset-version-filetree.component.scss
index ee81e4c13b..2468bffd31 100644
--- a/frontend/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/user-dataset-version-filetree/user-dataset-version-filetree.component.scss
+++ b/frontend/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/user-dataset-version-filetree/user-dataset-version-filetree.component.scss
@@ -32,11 +32,27 @@
   user-select: text;
 }
 
-/* Styles for the file tree container */
+/* Styles for the file tree container. The tree's virtual scroll needs a
+   definite viewport height: the container is fixed at 200px, tree-root fills
+   it, and the lib's tree-viewport (height: 100%, overflow: auto) scrolls. */
 .file-tree-container {
-  max-height: 200px; /* Adjust the max-height as needed */
-  overflow-y: auto; /* Enables vertical scrolling when content exceeds max-height */
-  overflow-x: auto; /* Prevents horizontal scrolling */
+  height: 200px;
+  overflow: hidden;
+
+  tree-root {
+    display: block;
+    height: 100%;
+  }
+}
+
+/* Each row must stay exactly TREE_NODE_HEIGHT_PX tall so the virtual scroll
+   positions rows correctly. */
+:host ::ng-deep .file-tree-container .node-content-wrapper {
+  height: 24px;
+  padding-top: 0;
+  padding-bottom: 0;
+  display: flex;
+  align-items: center;
 }
 
 //tree-root .fa-file {
diff --git a/frontend/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/user-dataset-version-filetree/user-dataset-version-filetree.component.spec.ts b/frontend/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/user-dataset-version-filetree/user-dataset-version-filetree.component.spec.ts
new file mode 100644
index 0000000000..2efc1a1aa8
--- /dev/null
+++ b/frontend/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/user-dataset-version-filetree/user-dataset-version-filetree.component.spec.ts
@@ -0,0 +1,89 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { ComponentFixture, TestBed } from "@angular/core/testing";
+import { UserDatasetVersionFiletreeComponent } from "./user-dataset-version-filetree.component";
+import { DatasetFileNode } from "../../../../../../common/type/datasetVersionFileTree";
+
+describe("UserDatasetVersionFiletreeComponent", () => {
+  let fixture: ComponentFixture<UserDatasetVersionFiletreeComponent>;
+  let component: UserDatasetVersionFiletreeComponent;
+
+  const FILE_COUNT = 1000;
+  function makeFlatFileNodes(count: number): DatasetFileNode[] {
+    return Array.from({ length: count }, (_, i) => ({
+      name: `file_${String(i + 1).padStart(4, "0")}.txt`,
+      type: "file" as const,
+      parentDir: "/owner/dataset/v1",
+      size: 1,
+    }));
+  }
+
+  beforeEach(() => {
+    TestBed.configureTestingModule({
+      imports: [UserDatasetVersionFiletreeComponent],
+    });
+    fixture = TestBed.createComponent(UserDatasetVersionFiletreeComponent);
+    component = fixture.componentInstance;
+  });
+
+  // Regression tests for the frontend freeze when a dataset version has
+  // hundreds of files: the tree must virtualize its rows instead of creating
+  // one component per file.
+  it("enables virtual scrolling with a fixed node height", () => {
+    expect(component.fileTreeDisplayOptions.useVirtualScroll).toBe(true);
+    expect(typeof component.fileTreeDisplayOptions.nodeHeight).toBe("number");
+  });
+
+  it("keeps the full tree in the model without one DOM row per file", async () => {
+    component.fileTreeNodes = makeFlatFileNodes(FILE_COUNT);
+
+    fixture.detectChanges();
+    await fixture.whenStable();
+    fixture.detectChanges();
+
+    expect(component.tree.treeModel.roots.length).toBe(FILE_COUNT);
+    const renderedRows = fixture.nativeElement.querySelectorAll("tree-node").length;
+    expect(renderedRows).toBeLessThan(FILE_COUNT / 5);
+  });
+
+  it("emits selectedTreeNode when a leaf node is clicked", () => {
+    component.fileTreeNodes = makeFlatFileNodes(1);
+    const emitted: DatasetFileNode[] = [];
+    component.selectedTreeNode.subscribe((n: DatasetFileNode) => emitted.push(n));
+
+    // Invoke the tree's real click handler with a stub leaf node; the handler
+    // only reads hasChildren and data, so tree and $event are unused.
+    const onClick = component.fileTreeDisplayOptions.actionMapping!.mouse!.click!;
+    const leafNode = { hasChildren: false, data: component.fileTreeNodes[0] } as never;
+    onClick(undefined as never, leafNode, undefined as never);
+
+    expect(emitted).toEqual([component.fileTreeNodes[0]]);
+  });
+
+  it("emits deletedTreeNode when a node deletion is requested", () => {
+    component.fileTreeNodes = makeFlatFileNodes(1);
+    const emitted: DatasetFileNode[] = [];
+    component.deletedTreeNode.subscribe((n: DatasetFileNode) => emitted.push(n));
+
+    component.onNodeDeleted(component.fileTreeNodes[0]);
+
+    expect(emitted).toEqual([component.fileTreeNodes[0]]);
+  });
+});
diff --git a/frontend/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/user-dataset-version-filetree/user-dataset-version-filetree.component.ts b/frontend/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/user-dataset-version-filetree/user-dataset-version-filetree.component.ts
index 2eda4b53bf..9147fd1fcb 100644
--- a/frontend/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/user-dataset-version-filetree/user-dataset-version-filetree.component.ts
+++ b/frontend/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/user-dataset-version-filetree/user-dataset-version-filetree.component.ts
@@ -63,9 +63,18 @@ export class UserDatasetVersionFiletreeComponent implements AfterViewInit {
   @Output()
   setCoverImage = new EventEmitter<string>();
 
+  // Row height (px) used by the tree's virtual scroll to position rows; must
+  // match the row height pinned in the SCSS.
+  public readonly TREE_NODE_HEIGHT_PX = 24;
+
+  // useVirtualScroll keeps only the visible rows in the DOM; without it a
+  // version with hundreds of files renders one component per file and every
+  // change-detection pass freezes the page for seconds to minutes.
   public fileTreeDisplayOptions: ITreeOptions = {
     displayField: "name",
     hasChildrenField: "children",
+    useVirtualScroll: true,
+    nodeHeight: this.TREE_NODE_HEIGHT_PX,
     actionMapping: {
       mouse: {
         click: (tree: any, node: any, $event: any) => {