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

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


The following commit(s) were added to refs/heads/main by this push:
     new 0f5f791e78 fix: route share-by-email link to correct dashboard path by 
type (#5168)
0f5f791e78 is described below

commit 0f5f791e78d144af4b0cc1b29f5d5bb3400f5169
Author: Meng Wang <[email protected]>
AuthorDate: Sat May 23 16:37:53 2026 -0700

    fix: route share-by-email link to correct dashboard path by type (#5168)
    
    ### What changes were proposed in this PR?
    
    `ShareAccessComponent.grantAccess` hard-coded the
    `dashboard/user/workflow/` path segment when constructing the
    share-notification email's "access the ... at ..." URL, regardless of
    which resource type the dialog was opened for. Because the dialog is
    opened with `type: "dataset"` / `type: "project"` / `type: "workflow"`
    from the respective list-item components, the link in the email for
    shared **datasets** and shared **projects** pointed at the workflow
    route and 404'd on click.
    
    Branch on `this.type` to pick the correct route from the existing
    `app-routing.constant.ts` constants:
    
    - `workflow` → `DASHBOARD_USER_WORKFLOW` (`/dashboard/user/workflow`)
    - `dataset` → `DASHBOARD_USER_DATASET` (`/dashboard/user/dataset`)
    - `project` → `DASHBOARD_USER_PROJECT` (`/dashboard/user/project`)
    
    `computing-unit` is unaffected — the existing `if (this.type !==
    "computing-unit")` guard already skips the URL line for that type.
    
    Note: the issue mentioned the dataset case only; the project case has
    the same defect and is fixed by the same change.
    
    ### Any related issues, documentation, discussions?
    
    Closes #5163.
    
    Follow-up to #4200 (which fixed the missing `/dashboard/user/` prefix on
    the same line per #3583); this fixes the remaining hard-coded `workflow`
    segment.
    
    ### How was this PR tested?
    
    Added `share-access.component.spec.ts` (Vitest + TestBed +
    `HttpClientTestingModule`) with four cases that exercise `grantAccess`:
    
    - `type=workflow`, id=11 → email message contains
    `/dashboard/user/workflow/11`
    - `type=dataset`, id=22 → email message contains
    `/dashboard/user/dataset/22`
    - `type=project`, id=33 → email message contains
    `/dashboard/user/project/33`
    - `type=computing-unit`, id=44 → email message does NOT contain
    `/dashboard/user/`
    
    Each case mocks the component's injected dependencies
    (`ShareAccessService`, `UserService`, `GmailService`,
    `NotificationService`, `NzMessageService`, `NzModalService`,
    `NzModalRef`, `WorkflowPersistService`, `DatasetService`,
    `WorkflowActionService`, `NZ_MODAL_DATA`), runs `grantAccess`, and
    inspects the message passed to `gmailService.sendEmail` to confirm the
    URL formatting.
    
    Verified locally:
    
    - `yarn test --include='**/share-access.component.spec.ts'` — 4 passed.
    - `yarn prettier --check
    
src/app/dashboard/component/user/share-access/share-access.component.{ts,spec.ts}`
    — clean.
    
    Manual verification path:
    
    1. Share a dataset via the dataset list dashboard, entering a real email
    address.
    2. Check the recipient's inbox: the link should now read
    `https://<host>/dashboard/user/dataset/<did>` and resolve to the dataset
    detail page on click.
    3. Repeat for a project: link is `/dashboard/user/project/<pid>`.
    4. Repeat for a workflow: link is `/dashboard/user/workflow/<wid>`
    (unchanged behavior).
    
    ### Was this PR authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Code (claude-opus-4-7)
    
    Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
---
 .../share-access/share-access.component.spec.ts    | 110 +++++++++++++++++++++
 .../user/share-access/share-access.component.ts    |  14 ++-
 2 files changed, 122 insertions(+), 2 deletions(-)

diff --git 
a/frontend/src/app/dashboard/component/user/share-access/share-access.component.spec.ts
 
b/frontend/src/app/dashboard/component/user/share-access/share-access.component.spec.ts
new file mode 100644
index 0000000000..b5c525956e
--- /dev/null
+++ 
b/frontend/src/app/dashboard/component/user/share-access/share-access.component.spec.ts
@@ -0,0 +1,110 @@
+/**
+ * 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 { TestBed } from "@angular/core/testing";
+import { HttpClientTestingModule } from "@angular/common/http/testing";
+import { NoopAnimationsModule } from "@angular/platform-browser/animations";
+import { of } from "rxjs";
+
+import { NZ_MODAL_DATA, NzModalRef, NzModalService } from 
"ng-zorro-antd/modal";
+import { NzMessageService } from "ng-zorro-antd/message";
+
+import { ShareAccessComponent } from "./share-access.component";
+import { ShareAccessService } from 
"../../../service/user/share-access/share-access.service";
+import { UserService } from "../../../../common/service/user/user.service";
+import { GmailService } from "../../../../common/service/gmail/gmail.service";
+import { NotificationService } from 
"../../../../common/service/notification/notification.service";
+import { DatasetService } from "../../../service/user/dataset/dataset.service";
+import { WorkflowPersistService } from 
"src/app/common/service/workflow-persist/workflow-persist.service";
+import { WorkflowActionService } from 
"src/app/workspace/service/workflow-graph/model/workflow-action.service";
+
+describe("ShareAccessComponent.grantAccess", () => {
+  let gmailSpy: { sendEmail: ReturnType<typeof vi.fn> };
+  let accessServiceSpy: {
+    grantAccess: ReturnType<typeof vi.fn>;
+    getAccessList: ReturnType<typeof vi.fn>;
+    getOwner: ReturnType<typeof vi.fn>;
+  };
+
+  function setupComponent(type: string, id: number): ShareAccessComponent {
+    TestBed.configureTestingModule({
+      imports: [HttpClientTestingModule, NoopAnimationsModule, 
ShareAccessComponent],
+      providers: [
+        { provide: NZ_MODAL_DATA, useValue: { type, id, allOwners: [], 
inWorkspace: false } },
+        { provide: ShareAccessService, useValue: accessServiceSpy },
+        {
+          provide: UserService,
+          useValue: { getCurrentUser: () => ({ email: "[email protected]" }) },
+        },
+        { provide: GmailService, useValue: gmailSpy },
+        { provide: NotificationService, useValue: { success: vi.fn(), error: 
vi.fn() } },
+        { provide: NzMessageService, useValue: { error: vi.fn() } },
+        { provide: NzModalService, useValue: {} },
+        { provide: NzModalRef, useValue: { close: vi.fn() } },
+        {
+          provide: WorkflowPersistService,
+          useValue: { getWorkflowIsPublished: 
vi.fn().mockReturnValue(of("Private")) },
+        },
+        {
+          provide: DatasetService,
+          useValue: {
+            getDataset: vi.fn().mockReturnValue(of({ dataset: { isPublic: 
false } })),
+          },
+        },
+        { provide: WorkflowActionService, useValue: {} },
+      ],
+    });
+    return TestBed.createComponent(ShareAccessComponent).componentInstance;
+  }
+
+  beforeEach(() => {
+    gmailSpy = { sendEmail: vi.fn() };
+    accessServiceSpy = {
+      grantAccess: vi.fn().mockReturnValue(of(null)),
+      getAccessList: vi.fn().mockReturnValue(of([])),
+      getOwner: vi.fn().mockReturnValue(of("[email protected]")),
+    };
+  });
+
+  function grantAndCaptureMessage(c: ShareAccessComponent): string {
+    c.emailTags = ["[email protected]"];
+    c.grantAccess();
+    return gmailSpy.sendEmail.mock.calls[0][1] as string;
+  }
+
+  it("uses the workflow dashboard path when sharing a workflow", () => {
+    const message = grantAndCaptureMessage(setupComponent("workflow", 11));
+    expect(message).toContain("/dashboard/user/workflow/11");
+  });
+
+  it("uses the dataset dashboard path when sharing a dataset", () => {
+    const message = grantAndCaptureMessage(setupComponent("dataset", 22));
+    expect(message).toContain("/dashboard/user/dataset/22");
+  });
+
+  it("uses the project dashboard path when sharing a project", () => {
+    const message = grantAndCaptureMessage(setupComponent("project", 33));
+    expect(message).toContain("/dashboard/user/project/33");
+  });
+
+  it("omits the access URL when sharing a computing-unit", () => {
+    const message = grantAndCaptureMessage(setupComponent("computing-unit", 
44));
+    expect(message).not.toContain("/dashboard/user/");
+  });
+});
diff --git 
a/frontend/src/app/dashboard/component/user/share-access/share-access.component.ts
 
b/frontend/src/app/dashboard/component/user/share-access/share-access.component.ts
index 576e104aed..b6b51b9709 100644
--- 
a/frontend/src/app/dashboard/component/user/share-access/share-access.component.ts
+++ 
b/frontend/src/app/dashboard/component/user/share-access/share-access.component.ts
@@ -27,6 +27,11 @@ import { GmailService } from 
"../../../../common/service/gmail/gmail.service";
 import { NZ_MODAL_DATA, NzModalRef, NzModalService } from 
"ng-zorro-antd/modal";
 import { NotificationService } from 
"../../../../common/service/notification/notification.service";
 import { HttpErrorResponse } from "@angular/common/http";
+import {
+  DASHBOARD_USER_DATASET,
+  DASHBOARD_USER_PROJECT,
+  DASHBOARD_USER_WORKFLOW,
+} from "../../../../app-routing.constant";
 import { NzMessageService } from "ng-zorro-antd/message";
 import { DatasetService } from "../../../service/user/dataset/dataset.service";
 import { WorkflowPersistService } from 
"src/app/common/service/workflow-persist/workflow-persist.service";
@@ -189,8 +194,13 @@ export class ShareAccessComponent implements OnInit, 
OnDestroy {
     if (this.emailTags.length > 0) {
       this.emailTags.forEach(email => {
         let message = `${this.userService.getCurrentUser()?.email} shared a 
${this.type} with you`;
-        if (this.type !== "computing-unit")
-          message += `, access the ${this.type} at 
${location.origin}/dashboard/user/workflow/${this.id}`;
+        if (this.type !== "computing-unit") {
+          let routePath = "";
+          if (this.type === "workflow") routePath = DASHBOARD_USER_WORKFLOW;
+          if (this.type === "dataset") routePath = DASHBOARD_USER_DATASET;
+          if (this.type === "project") routePath = DASHBOARD_USER_PROJECT;
+          message += `, access the ${this.type} at 
${location.origin}${routePath}/${this.id}`;
+        }
         this.accessService
           .grantAccess(this.type, this.id, email, 
this.validateForm.value.accessLevel)
           .pipe(untilDestroyed(this))

Reply via email to