From 6da735adc4fd4f8defd9563a379fb17fc93aecde Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Sun, 9 Aug 2026 06:18:01 -0700 Subject: [PATCH] chore(frontend): remove the deprecated project feature --- frontend/README.md | 2 +- frontend/src/app/app-routing.constant.ts | 1 - frontend/src/app/app-routing.module.ts | 10 - frontend/src/app/app.module.ts | 12 - .../workflow-persist.service.spec.ts | 12 +- .../workflow-persist.service.ts | 4 +- frontend/src/app/common/type/gui-config.ts | 1 - .../settings/admin-settings.component.html | 9 - .../settings/admin-settings.component.spec.ts | 7 +- .../settings/admin-settings.component.ts | 1 - .../component/dashboard.component.html | 13 - .../component/dashboard.component.spec.ts | 9 +- .../component/dashboard.component.ts | 3 - .../component/user-dashboard-test-fixtures.ts | 15 - .../filters-instructions.component.html | 1 - .../user/filters/filters.component.html | 34 -- .../user/filters/filters.component.spec.ts | 71 +-- .../user/filters/filters.component.ts | 77 +-- .../card-item/card-item.component.spec.ts | 24 +- .../card-item/card-item.component.ts | 4 - .../list-item/list-item.component.spec.ts | 7 - .../user/list-item/list-item.component.ts | 4 - .../search-bar/search-bar.component.spec.ts | 18 - .../user/search-bar/search-bar.component.ts | 3 - .../user/search/search.component.html | 11 - .../component/user/search/search.component.ts | 6 +- .../share-access.component.spec.ts | 7 +- .../share-access/share-access.component.ts | 3 +- .../public-project.component.html | 57 -- .../public-project.component.spec.ts | 187 ------- .../public-project.component.ts | 105 ---- .../user-project-list-item.component.html | 206 ------- .../user-project-list-item.component.scss | 45 -- .../user-project-list-item.component.spec.ts | 502 ------------------ .../user-project-list-item.component.ts | 226 -------- ...-modal-add-project-workflow.component.html | 70 --- ...-modal-add-project-workflow.component.scss | 46 -- ...dal-add-project-workflow.component.spec.ts | 114 ---- ...bd-modal-add-project-workflow.component.ts | 121 ----- ...dal-remove-project-workflow.component.html | 70 --- ...dal-remove-project-workflow.component.scss | 46 -- ...-remove-project-workflow.component.spec.ts | 208 -------- ...modal-remove-project-workflow.component.ts | 100 ---- .../user-project-section.component.html | 87 --- .../user-project-section.component.scss | 142 ----- .../user-project-section.component.ts | 202 ------- .../user-project/user-project.component.html | 97 ---- .../user-project/user-project.component.scss | 25 - .../user-project.component.spec.ts | 262 --------- .../user-project/user-project.component.ts | 159 ------ .../user-workflow-list-item.component.html | 29 - .../user-workflow-list-item.component.scss | 47 -- .../user-workflow-list-item.component.spec.ts | 82 +-- .../user-workflow-list-item.component.ts | 41 +- .../user-workflow.component.html | 34 +- .../user-workflow.component.spec.ts | 150 +----- .../user-workflow/user-workflow.component.ts | 125 +---- .../user/project/stub-user-project.service.ts | 120 ----- .../user/project/user-project.service.spec.ts | 232 -------- .../user/project/user-project.service.ts | 170 ------ .../public-project/public-project.service.ts | 53 -- .../service/user/search.service.spec.ts | 36 +- .../dashboard/service/user/search.service.ts | 28 +- .../service/user/stub-search.service.ts | 6 +- .../dashboard/type/dashboard-entry.spec.ts | 36 +- .../src/app/dashboard/type/dashboard-entry.ts | 36 +- .../type/dashboard-project.interface.ts | 35 -- .../type/dashboard-workflow.interface.ts | 1 - .../type/search-filter-parameters.spec.ts | 28 +- .../type/search-filter-parameters.ts | 15 +- .../src/app/dashboard/type/search-result.ts | 4 +- .../dashboard/type/type-predicates.spec.ts | 44 -- .../src/app/dashboard/type/type-predicates.ts | 5 - .../browse-section.component.spec.ts | 2 +- .../hub-search-result.component.spec.ts | 28 +- .../hub-search-result.component.ts | 6 - .../app/hub/component/hub.component.spec.ts | 1 - frontend/src/app/hub/service/hub.service.ts | 1 - .../component/menu/menu.component.ts | 9 +- .../component/workspace.component.html | 1 - .../component/workspace.component.spec.ts | 12 - .../component/workspace.component.ts | 14 - 82 files changed, 79 insertions(+), 4798 deletions(-) delete mode 100644 frontend/src/app/dashboard/component/user/user-project/public-project/public-project.component.html delete mode 100644 frontend/src/app/dashboard/component/user/user-project/public-project/public-project.component.spec.ts delete mode 100644 frontend/src/app/dashboard/component/user/user-project/public-project/public-project.component.ts delete mode 100644 frontend/src/app/dashboard/component/user/user-project/user-project-list-item/user-project-list-item.component.html delete mode 100644 frontend/src/app/dashboard/component/user/user-project/user-project-list-item/user-project-list-item.component.scss delete mode 100644 frontend/src/app/dashboard/component/user/user-project/user-project-list-item/user-project-list-item.component.spec.ts delete mode 100644 frontend/src/app/dashboard/component/user/user-project/user-project-list-item/user-project-list-item.component.ts delete mode 100644 frontend/src/app/dashboard/component/user/user-project/user-project-section/ngbd-modal-add-project-workflow/ngbd-modal-add-project-workflow.component.html delete mode 100644 frontend/src/app/dashboard/component/user/user-project/user-project-section/ngbd-modal-add-project-workflow/ngbd-modal-add-project-workflow.component.scss delete mode 100644 frontend/src/app/dashboard/component/user/user-project/user-project-section/ngbd-modal-add-project-workflow/ngbd-modal-add-project-workflow.component.spec.ts delete mode 100644 frontend/src/app/dashboard/component/user/user-project/user-project-section/ngbd-modal-add-project-workflow/ngbd-modal-add-project-workflow.component.ts delete mode 100644 frontend/src/app/dashboard/component/user/user-project/user-project-section/ngbd-modal-remove-project-workflow/ngbd-modal-remove-project-workflow.component.html delete mode 100644 frontend/src/app/dashboard/component/user/user-project/user-project-section/ngbd-modal-remove-project-workflow/ngbd-modal-remove-project-workflow.component.scss delete mode 100644 frontend/src/app/dashboard/component/user/user-project/user-project-section/ngbd-modal-remove-project-workflow/ngbd-modal-remove-project-workflow.component.spec.ts delete mode 100644 frontend/src/app/dashboard/component/user/user-project/user-project-section/ngbd-modal-remove-project-workflow/ngbd-modal-remove-project-workflow.component.ts delete mode 100644 frontend/src/app/dashboard/component/user/user-project/user-project-section/user-project-section.component.html delete mode 100644 frontend/src/app/dashboard/component/user/user-project/user-project-section/user-project-section.component.scss delete mode 100644 frontend/src/app/dashboard/component/user/user-project/user-project-section/user-project-section.component.ts delete mode 100644 frontend/src/app/dashboard/component/user/user-project/user-project.component.html delete mode 100644 frontend/src/app/dashboard/component/user/user-project/user-project.component.scss delete mode 100644 frontend/src/app/dashboard/component/user/user-project/user-project.component.spec.ts delete mode 100644 frontend/src/app/dashboard/component/user/user-project/user-project.component.ts delete mode 100644 frontend/src/app/dashboard/service/user/project/stub-user-project.service.ts delete mode 100644 frontend/src/app/dashboard/service/user/project/user-project.service.spec.ts delete mode 100644 frontend/src/app/dashboard/service/user/project/user-project.service.ts delete mode 100644 frontend/src/app/dashboard/service/user/public-project/public-project.service.ts delete mode 100644 frontend/src/app/dashboard/type/dashboard-project.interface.ts diff --git a/frontend/README.md b/frontend/README.md index 8fd8e9378d2..d1f97bbcd3d 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -58,7 +58,7 @@ The full testing reference (Vitest stack, recipes, anti-patterns, coverage troub | Path | What lives here | | ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | | `src/app/workspace/` | Workflow editor — operator graph, property panel, result panel, code editor. | -| `src/app/dashboard/` | User dashboard — workflows, datasets, projects, computing units, admin. | +| `src/app/dashboard/` | User dashboard — workflows, datasets, computing units, admin. | | `src/app/hub/` | Public hub — discover and share workflows. | | `src/app/common/` | Cross-cutting services, types, formly extensions, and shared test helpers (`common/testing/test-utils.ts`). | | `src/app/workspace/service/operator-metadata/` | Operator metadata service + the `Stub…Service` test doubles other specs reuse. | diff --git a/frontend/src/app/app-routing.constant.ts b/frontend/src/app/app-routing.constant.ts index f5a01300394..f6a427ca7ee 100644 --- a/frontend/src/app/app-routing.constant.ts +++ b/frontend/src/app/app-routing.constant.ts @@ -29,7 +29,6 @@ export const HUB_DATASET_RESULT = `${HUB_DATASET}/result`; export const HUB_DATASET_RESULT_DETAIL = `${HUB_DATASET_RESULT}/detail`; export const USER = "/user"; -export const USER_PROJECT = `${USER}/project`; export const USER_WORKSPACE = `${USER}/workflow`; export const USER_WORKFLOW = `${USER}/workflow`; export const USER_DATASET = `${USER}/dataset`; diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index 58f90143006..49017313bd2 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -22,8 +22,6 @@ import { RouterModule, Routes } from "@angular/router"; import { DashboardComponent } from "./dashboard/component/dashboard.component"; import { UserWorkflowComponent } from "./dashboard/component/user/user-workflow/user-workflow.component"; import { UserQuotaComponent } from "./dashboard/component/user/user-quota/user-quota.component"; -import { UserProjectSectionComponent } from "./dashboard/component/user/user-project/user-project-section/user-project-section.component"; -import { UserProjectComponent } from "./dashboard/component/user/user-project/user-project.component"; import { UserComputingUnitComponent } from "./dashboard/component/user/user-computing-unit/user-computing-unit.component"; import { UserVenvComponent } from "./dashboard/component/user/user-venv/user-venv.component"; import { WorkspaceComponent } from "./workspace/component/workspace.component"; @@ -98,14 +96,6 @@ routes.push({ path: "user", canActivate: [AuthGuardService], children: [ - { - path: "project", - component: UserProjectComponent, - }, - { - path: "project/:pid", - component: UserProjectSectionComponent, - }, { path: "workflow", component: UserWorkflowComponent, diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index fef2fd5aa9a..d4cfb5042de 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -89,10 +89,6 @@ import { VersionsListComponent } from "./workspace/component/left-panel/versions import { NzPaginationModule } from "ng-zorro-antd/pagination"; import { JwtModule } from "@auth0/angular-jwt"; import { AuthService } from "./common/service/user/auth.service"; -import { UserProjectComponent } from "./dashboard/component/user/user-project/user-project.component"; -import { UserProjectSectionComponent } from "./dashboard/component/user/user-project/user-project-section/user-project-section.component"; -import { NgbdModalAddProjectWorkflowComponent } from "./dashboard/component/user/user-project/user-project-section/ngbd-modal-add-project-workflow/ngbd-modal-add-project-workflow.component"; -import { NgbdModalRemoveProjectWorkflowComponent } from "./dashboard/component/user/user-project/user-project-section/ngbd-modal-remove-project-workflow/ngbd-modal-remove-project-workflow.component"; import { PresetWrapperComponent } from "./common/formly/preset-wrapper/preset-wrapper.component"; import { MarkdownDescriptionComponent } from "./dashboard/component/user/markdown-description/markdown-description.component"; import { NzModalCommentBoxComponent } from "./workspace/component/workflow-editor/comment-box-modal/nz-modal-comment-box.component"; @@ -125,7 +121,6 @@ import { FileSaverService } from "./dashboard/service/user/file/file-saver.servi import { DragDropModule } from "@angular/cdk/drag-drop"; import { ScrollingModule } from "@angular/cdk/scrolling"; import { UserWorkflowListItemComponent } from "./dashboard/component/user/user-workflow/user-workflow-list-item/user-workflow-list-item.component"; -import { UserProjectListItemComponent } from "./dashboard/component/user/user-project/user-project-list-item/user-project-list-item.component"; import { SortButtonComponent } from "./dashboard/component/user/sort-button/sort-button.component"; import { FiltersComponent } from "./dashboard/component/user/filters/filters.component"; import { FiltersInstructionsComponent } from "./dashboard/component/user/filters-instructions/filters-instructions.component"; @@ -133,7 +128,6 @@ import { SearchComponent } from "./dashboard/component/user/search/search.compon import { SearchResultsComponent } from "./dashboard/component/user/search-results/search-results.component"; import { PortPropertyEditFrameComponent } from "./workspace/component/property-editor/port-property-edit-frame/port-property-edit-frame.component"; import { AdminGmailComponent } from "./dashboard/component/admin/gmail/admin-gmail.component"; -import { PublicProjectComponent } from "./dashboard/component/user/user-project/public-project/public-project.component"; import { FormlyNgZorroAntdModule } from "@ngx-formly/ng-zorro-antd"; import { FlarumComponent } from "./dashboard/component/user/flarum/flarum.component"; import { NzAlertModule } from "ng-zorro-antd/alert"; @@ -274,7 +268,6 @@ registerLocaleData(en); ScrollingModule, UiUdfParametersComponent, AdminGmailComponent, - PublicProjectComponent, WorkspaceComponent, MenuComponent, OperatorMenuComponent, @@ -313,10 +306,6 @@ registerLocaleData(en); ErrorFrameComponent, ResultTableFrameComponent, OperatorPropertyEditFrameComponent, - UserProjectComponent, - UserProjectSectionComponent, - NgbdModalAddProjectWorkflowComponent, - NgbdModalRemoveProjectWorkflowComponent, FilesUploaderComponent, ConflictingFileModalContentComponent, UserDatasetComponent, @@ -344,7 +333,6 @@ registerLocaleData(en); CollabWrapperComponent, AboutComponent, UserWorkflowListItemComponent, - UserProjectListItemComponent, SortButtonComponent, FiltersComponent, FiltersInstructionsComponent, diff --git a/frontend/src/app/common/service/workflow-persist/workflow-persist.service.spec.ts b/frontend/src/app/common/service/workflow-persist/workflow-persist.service.spec.ts index 007f42f99fe..6638aa8a6e5 100644 --- a/frontend/src/app/common/service/workflow-persist/workflow-persist.service.spec.ts +++ b/frontend/src/app/common/service/workflow-persist/workflow-persist.service.spec.ts @@ -146,7 +146,6 @@ describe("WorkflowPersistService", () => { owners: [], ids: [], operators: [], - projectIds: [], }; const keywords = ["test"]; const entry = { workflow: { wid: 1, name: "w", content: '{"operators":[]}' } } as unknown as DashboardWorkflow; @@ -277,28 +276,19 @@ describe("WorkflowPersistService", () => { expect(emitted).toBe(false); }); - it("duplicateWorkflow POSTs only wids when no pid is provided", () => { + it("duplicateWorkflow POSTs the wids", () => { let result: DashboardWorkflow[] | undefined; service.duplicateWorkflow([3, 4]).subscribe(r => (result = r)); const req = httpTestingController.expectOne(`${API}/${WORKFLOW_DUPLICATE_URL}`); expect(req.request.method).toBe("POST"); expect(req.request.body).toEqual({ wids: [3, 4] }); - expect(req.request.body).not.toHaveProperty("pid"); const dup = [{ workflow: { wid: 10 } }] as unknown as DashboardWorkflow[]; req.flush(dup); expect(result).toEqual(dup); }); - it("duplicateWorkflow includes pid in the body when provided", () => { - service.duplicateWorkflow([5], 42).subscribe(); - - const req = httpTestingController.expectOne(`${API}/${WORKFLOW_DUPLICATE_URL}`); - expect(req.request.body).toEqual({ wids: [5], pid: 42 }); - req.flush([{ workflow: { wid: 11 } }]); - }); - it("duplicateWorkflow filters out an empty-array response", () => { let emitted = false; service.duplicateWorkflow([6]).subscribe(() => (emitted = true)); diff --git a/frontend/src/app/common/service/workflow-persist/workflow-persist.service.ts b/frontend/src/app/common/service/workflow-persist/workflow-persist.service.ts index 5e738c5add2..0e3c8d34c24 100644 --- a/frontend/src/app/common/service/workflow-persist/workflow-persist.service.ts +++ b/frontend/src/app/common/service/workflow-persist/workflow-persist.service.ts @@ -107,13 +107,11 @@ export class WorkflowPersistService { /** * creates a workflow and insert it to backend database and return its information * @param targetWids - * @param pid */ - public duplicateWorkflow(targetWids: number[], pid?: number): Observable { + public duplicateWorkflow(targetWids: number[]): Observable { return this.http .post(`${AppSettings.getApiEndpoint()}/${WORKFLOW_DUPLICATE_URL}`, { wids: targetWids, - ...(pid !== undefined && { pid }), }) .pipe(filter((createdWorkflows: DashboardWorkflow[]) => createdWorkflows != null && createdWorkflows.length > 0)); } diff --git a/frontend/src/app/common/type/gui-config.ts b/frontend/src/app/common/type/gui-config.ts index 5da549acd96..df38cdd9c0f 100644 --- a/frontend/src/app/common/type/gui-config.ts +++ b/frontend/src/app/common/type/gui-config.ts @@ -52,7 +52,6 @@ export interface SidebarTabs { workflow_enabled: boolean; dataset_enabled: boolean; your_work_enabled: boolean; - projects_enabled: boolean; workflows_enabled: boolean; compute_enabled: boolean; datasets_enabled: boolean; diff --git a/frontend/src/app/dashboard/component/admin/settings/admin-settings.component.html b/frontend/src/app/dashboard/component/admin/settings/admin-settings.component.html index fd2fea7f753..a482f90973c 100644 --- a/frontend/src/app/dashboard/component/admin/settings/admin-settings.component.html +++ b/frontend/src/app/dashboard/component/admin/settings/admin-settings.component.html @@ -163,15 +163,6 @@

General Settings

- - diff --git a/frontend/src/app/dashboard/component/user/user-workflow/user-workflow.component.spec.ts b/frontend/src/app/dashboard/component/user/user-workflow/user-workflow.component.spec.ts index 7b71ead98c8..a85e206e5db 100644 --- a/frontend/src/app/dashboard/component/user/user-workflow/user-workflow.component.spec.ts +++ b/frontend/src/app/dashboard/component/user/user-workflow/user-workflow.component.spec.ts @@ -31,8 +31,6 @@ import { StubWorkflowPersistService } from "../../../../common/service/workflow- import { NotificationService } from "../../../../common/service/notification/notification.service"; import { DashboardEntry } from "../../../type/dashboard-entry"; import { DashboardWorkflow } from "../../../type/dashboard-workflow.interface"; -import { NgbdModalAddProjectWorkflowComponent } from "../user-project/user-project-section/ngbd-modal-add-project-workflow/ngbd-modal-add-project-workflow.component"; -import { NgbdModalRemoveProjectWorkflowComponent } from "../user-project/user-project-section/ngbd-modal-remove-project-workflow/ngbd-modal-remove-project-workflow.component"; import { ShareAccessComponent } from "../share-access/share-access.component"; import { ShareAccessService } from "../../../service/user/share-access/share-access.service"; import { UserService } from "../../../../common/service/user/user.service"; @@ -60,8 +58,6 @@ import { } from "../../user-dashboard-test-fixtures"; import { FiltersComponent } from "../filters/filters.component"; import { UserWorkflowListItemComponent } from "./user-workflow-list-item/user-workflow-list-item.component"; -import { UserProjectService } from "../../../service/user/project/user-project.service"; -import { StubUserProjectService } from "../../../service/user/project/stub-user-project.service"; import { SearchService } from "../../../service/user/search.service"; import { StubSearchService } from "../../../service/user/stub-search.service"; import { SearchResultsComponent } from "../search-results/search-results.component"; @@ -87,7 +83,6 @@ describe("SavedWorkflowSectionComponent", () => { providers: [ NzModalService, { provide: WorkflowPersistService, useValue: new StubWorkflowPersistService(testWorkflowEntries) }, - { provide: UserProjectService, useValue: new StubUserProjectService() }, ShareAccessService, { provide: OperatorMetadataService, useClass: StubOperatorMetadataService }, { provide: NZ_I18N, useValue: en_US }, @@ -198,23 +193,6 @@ describe("SavedWorkflowSectionComponent", () => { expect(component.filters.masterFilterList).toEqual(["id: 1", "id: 2", "id: 3"]); }); - it("searchByProjects", async () => { - component.filters.userProjectsDropdown = [ - { pid: 1, name: "Project1", checked: false }, - { pid: 2, name: "Project2", checked: false }, - { pid: 3, name: "Project3", checked: false }, - ]; - - // If the project filter is applied, only those workflows belonging to those projects should be returned. - component.filters.userProjectsDropdown[0].checked = true; - component.filters.updateSelectedProjects(); - await waitForLoading(); - expect(component.searchResultsComponent.loading).toBe(false); - const SortedCase = component.searchResultsComponent.entries.map(workflow => workflow.name); - expect(SortedCase).toEqual(["workflow 1", "workflow 2", "workflow 3"]); - expect(component.filters.masterFilterList).toEqual(["project: Project1"]); - }); - it("searchByCreationTime", async () => { // If the creation time filter is applied, only those workflows matching the date range should be returned. component.filters.selectedCtime = [new Date(1970, 0, 3), new Date(1981, 2, 13)]; @@ -277,29 +255,22 @@ describe("SavedWorkflowSectionComponent", () => { }); it("searchByManyParameters", async () => { - // Apply the project, ID, owner, and operator filter all at once. + // Apply the ID, owner, and operator filter all at once. component.filters.masterFilterList = ["1"]; const operatorGroup = component.filters.operators.get("Analysis"); if (operatorGroup) { operatorGroup[3].checked = true; // Aggregation operator component.filters.updateSelectedOperators(); - component.filters.userProjectsDropdown = [ - { pid: 1, name: "Project1", checked: false }, - { pid: 2, name: "Project2", checked: false }, - { pid: 3, name: "Project3", checked: false }, - ]; component.filters.owners[0].checked = true; //Texera component.filters.owners[1].checked = true; //Angular component.filters.wids[0].checked = true; component.filters.wids[1].checked = true; component.filters.wids[2].checked = true; //id 1,2,3 - component.filters.userProjectsDropdown[0].checked = true; //Project 1 component.filters.selectedCtime = [new Date(1970, 0, 1), new Date(1973, 2, 11)]; component.filters.selectedMtime = [new Date(1970, 0, 1), new Date(1982, 3, 14)]; //add/select new search parameter here - component.filters.updateSelectedProjects(); component.filters.updateSelectedIDs(); component.filters.updateSelectedOwners(); } @@ -316,7 +287,6 @@ describe("SavedWorkflowSectionComponent", () => { "id: 2", "id: 3", "operator: Aggregation", - "project: Project1", "ctime: 1970-01-01 ~ 1973-03-11", "mtime: 1970-01-01 ~ 1982-04-14", ]) @@ -331,7 +301,6 @@ describe("SavedWorkflowSectionComponent", () => { // StubWorkflowPersistService doesn't define createWorkflow — assign the // method here so the component's call resolves to a controlled observable. persist.createWorkflow = vi.fn().mockReturnValue(of({ workflow: { wid: 99 } })); - component.pid = undefined; component.onClickCreateNewWorkflowFromDashboard(); @@ -386,7 +355,6 @@ describe("SavedWorkflowSectionComponent", () => { isOwner: true, ownerName: "Texera", accessLevel: "Write", - projectIDs: [], ownerId: 1, coverImage: null, }); @@ -430,12 +398,11 @@ describe("SavedWorkflowSectionComponent", () => { }); describe("onClickDuplicateSelectedWorkflows", () => { - it("duplicates checked wids without a pid and prepends the new entries", () => { + it("duplicates checked wids and prepends the new entries", () => { const persist = TestBed.inject(WorkflowPersistService) as any; persist.duplicateWorkflow = vi .fn() .mockReturnValue(of([makeDashboardWorkflow(101, "dup a"), makeDashboardWorkflow(102, "dup b")])); - component.pid = undefined; setEntries([makeEntry(1, "wf 1", true), makeEntry(2, "wf 2", true), makeEntry(3, "wf 3", false)]); component.onClickDuplicateSelectedWorkflows(); @@ -450,17 +417,6 @@ describe("SavedWorkflowSectionComponent", () => { ]); }); - it("passes the pid to duplicateWorkflow when the section belongs to a project", () => { - const persist = TestBed.inject(WorkflowPersistService) as any; - persist.duplicateWorkflow = vi.fn().mockReturnValue(of([makeDashboardWorkflow(101, "dup a")])); - component.pid = 9; - setEntries([makeEntry(1, "wf 1", true), makeEntry(2, "wf 2", true)]); - - component.onClickDuplicateSelectedWorkflows(); - - expect(persist.duplicateWorkflow).toHaveBeenCalledWith([1, 2], 9); - }); - it("early-returns without calling the service when a checked entry has no wid", () => { const persist = TestBed.inject(WorkflowPersistService) as any; persist.duplicateWorkflow = vi.fn(); @@ -475,7 +431,6 @@ describe("SavedWorkflowSectionComponent", () => { const persist = TestBed.inject(WorkflowPersistService) as any; persist.duplicateWorkflow = vi.fn().mockReturnValue(throwError(() => "boom")); const alertSpy = vi.spyOn(window, "alert").mockImplementation(() => {}); - component.pid = undefined; setEntries([makeEntry(1, "wf 1", true)]); component.onClickDuplicateSelectedWorkflows(); @@ -681,44 +636,6 @@ describe("SavedWorkflowSectionComponent", () => { }); }); - describe("project workflow modals", () => { - it("opens the add-to-project modal and re-searches after it closes", () => { - const modalService = TestBed.inject(NzModalService); - const searchSpy = vi.spyOn(component, "search").mockResolvedValue(undefined); - const createSpy = vi.spyOn(modalService, "create").mockReturnValue({ afterClose: of(undefined) } as any); - component.pid = 7; - - component.onClickOpenAddWorkflow(); - - expect(createSpy).toHaveBeenCalledWith( - expect.objectContaining({ - nzContent: NgbdModalAddProjectWorkflowComponent, - nzData: { projectId: 7 }, - nzTitle: "Add Workflows To Project", - }) - ); - expect(searchSpy).toHaveBeenCalledWith(true); - }); - - it("opens the remove-from-project modal and re-searches after it closes", () => { - const modalService = TestBed.inject(NzModalService); - const searchSpy = vi.spyOn(component, "search").mockResolvedValue(undefined); - const createSpy = vi.spyOn(modalService, "create").mockReturnValue({ afterClose: of(undefined) } as any); - component.pid = 3; - - component.onClickOpenRemoveWorkflow(); - - expect(createSpy).toHaveBeenCalledWith( - expect.objectContaining({ - nzContent: NgbdModalRemoveProjectWorkflowComponent, - nzData: { projectId: 3 }, - nzTitle: "Remove Workflows From Project", - }) - ); - expect(searchSpy).toHaveBeenCalledWith(true); - }); - }); - describe("uncovered branch coverage", () => { // A FileReader whose result is intentionally not a string, so handleFileUploads // exercises its "file is not a string" guard. readAsText fires onload on the next @@ -790,32 +707,15 @@ describe("SavedWorkflowSectionComponent", () => { }); }); - describe("search", () => { - it("forces the section's pid into the search filter parameters", async () => { - const searchService = TestBed.inject(SearchService) as any; - const execSpy = vi.spyOn(searchService, "executeSearch"); - component.pid = 3; - - await component.search(true); - - expect(execSpy).toHaveBeenCalled(); - expect((execSpy.mock.calls[0][1] as any).projectIds).toEqual([3]); - }); - }); - describe("onClickCreateNewWorkflowFromDashboard", () => { - it("adds the new workflow to the project before navigating when a pid is set", () => { + it("navigates to the created workflow", () => { const router = TestBed.inject(Router); const navigateSpy = vi.spyOn(router, "navigate").mockResolvedValue(true); const persist = TestBed.inject(WorkflowPersistService) as any; persist.createWorkflow = vi.fn().mockReturnValue(of({ workflow: { wid: 55 } })); - const projectService = TestBed.inject(UserProjectService) as any; - const addSpy = vi.spyOn(projectService, "addWorkflowToProject").mockReturnValue(of({} as any)); - component.pid = 8; component.onClickCreateNewWorkflowFromDashboard(); - expect(addSpy).toHaveBeenCalledWith(8, 55); expect(navigateSpy).toHaveBeenCalledWith([USER_WORKSPACE, 55]); }); @@ -826,7 +726,6 @@ describe("SavedWorkflowSectionComponent", () => { .spyOn(TestBed.inject(NotificationService), "error") .mockImplementation(() => undefined as any); const navigateSpy = vi.spyOn(TestBed.inject(Router), "navigate").mockResolvedValue(true); - component.pid = undefined; component.onClickCreateNewWorkflowFromDashboard(); @@ -843,7 +742,6 @@ describe("SavedWorkflowSectionComponent", () => { .mockReturnValue(of([{ ...makeDashboardWorkflow(201, "dup"), ownerId: 2 }])); const searchService = TestBed.inject(SearchService) as any; const getUserInfoSpy = vi.spyOn(searchService, "getUserInfo"); - component.pid = undefined; component.currentUid = 1; setEntries([makeEntry(9, "existing")]); @@ -858,19 +756,6 @@ describe("SavedWorkflowSectionComponent", () => { expect(entries[0].accessibleUserIds).toEqual([1]); }); - it("passes the section pid to duplicateWorkflow when inside a project", async () => { - const persist = TestBed.inject(WorkflowPersistService) as any; - persist.duplicateWorkflow = vi - .fn() - .mockReturnValue(of([{ ...makeDashboardWorkflow(202, "dp"), ownerId: 2 }])); - component.pid = 9; - setEntries([]); - - await component.onClickDuplicateWorkflow(makeEntry(5, "orig")); - - expect(persist.duplicateWorkflow).toHaveBeenCalledWith([5], 9); - }); - it("skips the user-info lookup and access grant when there is no owner or current user", async () => { const persist = TestBed.inject(WorkflowPersistService) as any; persist.duplicateWorkflow = vi @@ -878,7 +763,6 @@ describe("SavedWorkflowSectionComponent", () => { .mockReturnValue(of([{ ...makeDashboardWorkflow(203, "no owner"), ownerId: undefined } as any])); const searchService = TestBed.inject(SearchService) as any; const getUserInfoSpy = vi.spyOn(searchService, "getUserInfo"); - component.pid = undefined; component.currentUid = undefined; setEntries([]); @@ -897,7 +781,6 @@ describe("SavedWorkflowSectionComponent", () => { .mockReturnValue(of([{ ...makeDashboardWorkflow(205, "na"), ownerId: 2 }])); const searchService = TestBed.inject(SearchService) as any; searchService.getUserInfo = vi.fn().mockReturnValue(of({ 2: { userName: "NoAvatar" } })); - component.pid = undefined; setEntries([]); await component.onClickDuplicateWorkflow(makeEntry(5, "orig")); @@ -921,7 +804,6 @@ describe("SavedWorkflowSectionComponent", () => { persist.duplicateWorkflow = vi.fn().mockReturnValue(throwError(() => ({ error: "dup error" }))); const alertSpy = vi.spyOn(window, "alert").mockImplementation(() => {}); vi.spyOn(console, "log").mockImplementation(() => {}); - component.pid = undefined; setEntries([]); await component.onClickDuplicateWorkflow(makeEntry(5, "orig")); @@ -940,19 +822,6 @@ describe("SavedWorkflowSectionComponent", () => { expect(persist.duplicateWorkflow).not.toHaveBeenCalled(); }); - - it("alerts on a duplication error in the project (pid) branch", () => { - const persist = TestBed.inject(WorkflowPersistService) as any; - persist.duplicateWorkflow = vi.fn().mockReturnValue(throwError(() => "pidboom")); - const alertSpy = vi.spyOn(window, "alert").mockImplementation(() => {}); - component.pid = 4; - setEntries([makeEntry(1, "a", true)]); - - component.onClickDuplicateSelectedWorkflows(); - - expect(persist.duplicateWorkflow).toHaveBeenCalledWith([1], 4); - expect(alertSpy).toHaveBeenCalledWith("pidboom"); - }); }); describe("handleConfirmDeleteSelectedWorkflows", () => { @@ -1091,19 +960,6 @@ describe("SavedWorkflowSectionComponent", () => { expect(spy).toHaveBeenCalled(); }); - it("shows and wires the project add/remove buttons when a pid is set", () => { - component.pid = 1; - fixture.detectChanges(); - const addSpy = vi.spyOn(component, "onClickOpenAddWorkflow").mockImplementation(() => {}); - const removeSpy = vi.spyOn(component, "onClickOpenRemoveWorkflow").mockImplementation(() => {}); - - q('[title="Add workflow(s) to project"]').triggerEventHandler("click", null); - q('[title="Remove workflow(s) from project"]').triggerEventHandler("click", null); - - expect(addSpy).toHaveBeenCalled(); - expect(removeSpy).toHaveBeenCalled(); - }); - it("switches the view type through the List/Card buttons", () => { component.viewType = "card"; fixture.detectChanges(); diff --git a/frontend/src/app/dashboard/component/user/user-workflow/user-workflow.component.ts b/frontend/src/app/dashboard/component/user/user-workflow/user-workflow.component.ts index 0c5149eab9f..e33aae3cacb 100644 --- a/frontend/src/app/dashboard/component/user/user-workflow/user-workflow.component.ts +++ b/frontend/src/app/dashboard/component/user/user-workflow/user-workflow.component.ts @@ -17,16 +17,13 @@ * under the License. */ -import { AfterViewInit, Component, Input, ViewChild } from "@angular/core"; +import { AfterViewInit, Component, ViewChild } from "@angular/core"; import { Router } from "@angular/router"; -import { NzModalService } from "ng-zorro-antd/modal"; import { firstValueFrom, from, lastValueFrom, Observable, of } from "rxjs"; import { DEFAULT_WORKFLOW_NAME, WorkflowPersistService, } from "../../../../common/service/workflow-persist/workflow-persist.service"; -import { NgbdModalAddProjectWorkflowComponent } from "../user-project/user-project-section/ngbd-modal-add-project-workflow/ngbd-modal-add-project-workflow.component"; -import { NgbdModalRemoveProjectWorkflowComponent } from "../user-project/user-project-section/ngbd-modal-remove-project-workflow/ngbd-modal-remove-project-workflow.component"; import { DashboardEntry, UserInfo } from "../../../type/dashboard-entry"; import { UserService } from "../../../../common/service/user/user.service"; import { UntilDestroy, untilDestroyed } from "@ngneat/until-destroy"; @@ -39,9 +36,7 @@ import { SearchResultsComponent } from "../search-results/search-results.compone import { CardItemComponent } from "../list-item/card-item/card-item.component"; import { SearchService } from "../../../service/user/search.service"; import { SortMethod } from "../../../type/sort-method"; -import { isDefined } from "../../../../common/util/predicate"; -import { UserProjectService } from "../../../service/user/project/user-project.service"; -import { map, mergeMap, switchMap, tap } from "rxjs/operators"; +import { map, switchMap, tap } from "rxjs/operators"; import { DashboardWorkflow } from "../../../type/dashboard-workflow.interface"; import { DownloadService } from "../../../service/user/download/download.service"; import { USER_WORKSPACE } from "../../../../app-routing.constant"; @@ -62,7 +57,7 @@ import { FormsModule } from "@angular/forms"; /** * Saved-workflow-section component contains information and functionality - * of the saved workflows section and is re-used in the user projects section when a project is clicked + * of the saved workflows section * * This component: * - displays the workflows the user has access to @@ -140,9 +135,6 @@ export class UserWorkflowComponent implements AfterViewInit { } private masterFilterList: ReadonlyArray | null = null; - // receive input from parent components (UserProjectSection), if any - @Input() public pid?: number = undefined; - @Input() public accessLevel?: string = undefined; public sortMethod = SortMethod.EditTimeDesc; public viewType: "list" | "card" = localStorage.getItem(UserWorkflowComponent.VIEW_MODE_STORAGE_KEY) === "card" ? "card" : "list"; @@ -151,9 +143,7 @@ export class UserWorkflowComponent implements AfterViewInit { constructor( private userService: UserService, private workflowPersistService: WorkflowPersistService, - private userProjectService: UserProjectService, private notificationService: NotificationService, - private modalService: NzModalService, private router: Router, private downloadService: DownloadService, private searchService: SearchService, @@ -199,34 +189,6 @@ export class UserWorkflowComponent implements AfterViewInit { .subscribe(() => this.search()); } - /** - * open the Modal to add workflow(s) to project - */ - public onClickOpenAddWorkflow() { - const modalRef = this.modalService.create({ - nzContent: NgbdModalAddProjectWorkflowComponent, - nzData: { projectId: this.pid }, - nzFooter: null, - nzTitle: "Add Workflows To Project", - nzCentered: true, - }); - modalRef.afterClose.pipe(untilDestroyed(this)).subscribe(() => this.search(true)); - } - - /** - * open the Modal to remove workflow(s) from project - */ - public onClickOpenRemoveWorkflow() { - const modalRef = this.modalService.create({ - nzContent: NgbdModalRemoveProjectWorkflowComponent, - nzData: { projectId: this.pid }, - nzFooter: null, - nzTitle: "Remove Workflows From Project", - nzCentered: true, - }); - modalRef.afterClose.pipe(untilDestroyed(this)).subscribe(() => this.search(true)); - } - /** * Searches workflows with keywords and filters given in the masterFilterList. * @returns @@ -243,10 +205,6 @@ export class UserWorkflowComponent implements AfterViewInit { this.lastSortMethod = this.sortMethod; this.masterFilterList = this.filters.masterFilterList; let filterParams = this.filters.getSearchFilterParameters(); - if (isDefined(this.pid)) { - // force the project id in the search query to be the current pid. - filterParams.projectIds = [this.pid]; - } this.searchResultsComponent.reset((start, count) => { return firstValueFrom( this.searchService @@ -280,7 +238,6 @@ export class UserWorkflowComponent implements AfterViewInit { executionMode: this.config.env.defaultExecutionMode, }, }; - let localPid = this.pid; this.workflowPersistService .createWorkflow(emptyWorkflowContent, DEFAULT_WORKFLOW_NAME) .pipe( @@ -289,18 +246,7 @@ export class UserWorkflowComponent implements AfterViewInit { throw new Error("Workflow creation failed."); } }), - mergeMap(createdWorkflow => { - // Check if localPid is defined; if so, add the workflow to the project - if (localPid) { - return this.userProjectService.addWorkflowToProject(localPid, createdWorkflow.workflow.wid!).pipe( - // Regardless of the project addition outcome, pass the wid downstream - map(() => createdWorkflow.workflow.wid) - ); - } else { - // If there's no localPid, skip adding to the project and directly pass the wid downstream - return of(createdWorkflow.workflow.wid); - } - }), + map(createdWorkflow => createdWorkflow.workflow.wid), untilDestroyed(this) ) .subscribe({ @@ -315,24 +261,13 @@ export class UserWorkflowComponent implements AfterViewInit { /** * duplicate the current workflow. A new record will appear in frontend * workflow list and backend database. - * - * for workflow components inside a project-section, it will also add - * the workflow to the project */ public async onClickDuplicateWorkflow(entry: DashboardEntry): Promise { if (entry.workflow.workflow.wid) { try { - let duplicatedWorkflowsInfo: DashboardWorkflow[] = []; - if (!isDefined(this.pid)) { - duplicatedWorkflowsInfo = await firstValueFrom( - this.workflowPersistService.duplicateWorkflow([entry.workflow.workflow.wid]) - ); - } else { - const localPid = this.pid; - duplicatedWorkflowsInfo = await firstValueFrom( - this.workflowPersistService.duplicateWorkflow([entry.workflow.workflow.wid], localPid) - ); - } + const duplicatedWorkflowsInfo: DashboardWorkflow[] = await firstValueFrom( + this.workflowPersistService.duplicateWorkflow([entry.workflow.workflow.wid]) + ); const userIds = new Set(); duplicatedWorkflowsInfo.forEach(workflow => { @@ -514,38 +449,20 @@ export class UserWorkflowComponent implements AfterViewInit { } if (targetWids.length > 0) { - if (!isDefined(this.pid)) { - this.workflowPersistService - .duplicateWorkflow(targetWids) - .pipe(untilDestroyed(this)) - .subscribe({ - next: duplicatedWorkflowsInfo => { - this.searchResultsComponent.entries = [ - ...duplicatedWorkflowsInfo.map(duplicatedWorkflowInfo => new DashboardEntry(duplicatedWorkflowInfo)), - ...this.searchResultsComponent.entries, - ]; - - // this.searchResultsComponent.clearAllSelections(); - }, // TODO: fix this with notification component - error: (err: unknown) => alert(err), - }); - } else { - const localPid = this.pid; - this.workflowPersistService - .duplicateWorkflow(targetWids, localPid) - .pipe(untilDestroyed(this)) - .subscribe({ - next: duplicatedWorkflowsInfo => { - this.searchResultsComponent.entries = [ - ...duplicatedWorkflowsInfo.map(duplicatedWorkflowInfo => new DashboardEntry(duplicatedWorkflowInfo)), - ...this.searchResultsComponent.entries, - ]; - - // this.searchResultsComponent.clearAllSelections(); - }, // TODO: fix this with notification component - error: (err: unknown) => alert(err), - }); - } + this.workflowPersistService + .duplicateWorkflow(targetWids) + .pipe(untilDestroyed(this)) + .subscribe({ + next: duplicatedWorkflowsInfo => { + this.searchResultsComponent.entries = [ + ...duplicatedWorkflowsInfo.map(duplicatedWorkflowInfo => new DashboardEntry(duplicatedWorkflowInfo)), + ...this.searchResultsComponent.entries, + ]; + + // this.searchResultsComponent.clearAllSelections(); + }, // TODO: fix this with notification component + error: (err: unknown) => alert(err), + }); } } diff --git a/frontend/src/app/dashboard/service/user/project/stub-user-project.service.ts b/frontend/src/app/dashboard/service/user/project/stub-user-project.service.ts deleted file mode 100644 index 8689cd326ed..00000000000 --- a/frontend/src/app/dashboard/service/user/project/stub-user-project.service.ts +++ /dev/null @@ -1,120 +0,0 @@ -/** - * 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 { Observable } from "rxjs"; -import { DashboardProject } from "../../../type/dashboard-project.interface"; -import { DashboardWorkflow } from "../../../type/dashboard-workflow.interface"; -import { DashboardFile } from "../../../type/dashboard-file.interface"; -import { UserProjectService } from "./user-project.service"; -import { testUserProjects } from "../../../component/user-dashboard-test-fixtures"; - -export class StubUserProjectService { - public getProjectList(): Observable { - return new Observable(observer => observer.next(testUserProjects.slice())); - } - - public retrieveWorkflowsOfProject(pid: number): Observable { - throw new Error("Not implemented."); - } - - public retrieveFilesOfProject(pid: number): Observable { - throw new Error("Not implemented."); - } - - public getProjectFiles(): ReadonlyArray { - throw new Error("Not implemented."); - } - - public refreshFilesOfProject(pid: number): void { - throw new Error("Not implemented."); - } - - public retrieveProject(pid: number): Observable { - throw new Error("Not implemented."); - } - - public updateProjectName(pid: number, name: string): Observable { - throw new Error("Not implemented."); - } - - public updateProjectDescription(pid: number, description: string): Observable { - throw new Error("Not implemented."); - } - - public deleteProject(pid: number): Observable { - throw new Error("Not implemented."); - } - - public createProject(name: string): Observable { - throw new Error("Not implemented."); - } - - public addWorkflowToProject(pid: number, wid: number): Observable { - throw new Error("Not implemented."); - } - - public removeWorkflowFromProject(pid: number, wid: number): Observable { - throw new Error("Not implemented."); - } - - public addFileToProject(pid: number, fid: number): Observable { - throw new Error("Not implemented."); - } - - public updateProjectColor(pid: number, colorHex: string): Observable { - throw new Error("Not implemented."); - } - - public deleteProjectColor(pid: number): Observable { - throw new Error("Not implemented."); - } - - public removeFileFromProject(pid: number, fid: number): Observable { - throw new Error("Not implemented."); - } - - /** - * same as UserFileService"s deleteDashboardUserFileEntry method, except - * it is modified to refresh the project"s list of files - */ - public deleteDashboardUserFileEntry(pid: number, targetUserFileEntry: DashboardFile): void { - throw new Error("Not implemented."); - } - - /** - * Helper function to determine if a project color is light - * or dark, which can be helpful for styling decisions - * - * @param color (HEX formatted color string) - * @returns boolean indicating whether color is "light" or "dark" - */ - public isLightColor(color: string): boolean { - return UserProjectService.isLightColor(color); - } - - /** - * Helper function to validate if a project color is in HEX format - * - * @param color - * @returns boolean indicating whether color is in valid HEX format - */ - public isInvalidColorFormat(color: string): boolean { - return UserProjectService.isInvalidColorFormat(color); - } -} diff --git a/frontend/src/app/dashboard/service/user/project/user-project.service.spec.ts b/frontend/src/app/dashboard/service/user/project/user-project.service.spec.ts deleted file mode 100644 index 8254a09e9cc..00000000000 --- a/frontend/src/app/dashboard/service/user/project/user-project.service.spec.ts +++ /dev/null @@ -1,232 +0,0 @@ -/** - * 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 { - CREATE_PROJECT_URL, - DELETE_PROJECT_URL, - USER_FILE_DELETE_URL, - USER_PROJECT_BASE_URL, - USER_PROJECT_LIST_URL, - UserProjectService, -} from "./user-project.service"; -import { HttpClientTestingModule, HttpTestingController } from "@angular/common/http/testing"; -import { TestBed } from "@angular/core/testing"; -import { NotificationService } from "../../../../common/service/notification/notification.service"; -import { DashboardProject } from "../../../type/dashboard-project.interface"; -import { DashboardWorkflow } from "../../../type/dashboard-workflow.interface"; -import { DashboardFile } from "../../../type/dashboard-file.interface"; - -const mockProject: DashboardProject = { - pid: 1, - name: "proj", - description: "desc", - ownerId: 7, - creationTime: 0, - color: null, - accessLevel: "WRITE", -}; - -const mockFile: DashboardFile = { - ownerEmail: "owner@test.com", - accessLevel: "READ", - file: { ownerUid: 7, fid: 3, size: 10, name: "data.csv", path: "/data.csv", description: "", uploadTime: 0 }, -}; - -const mockWorkflows = [{ projectIDs: [1], accessLevel: "READ" }] as unknown as DashboardWorkflow[]; -const mockResponse = { ok: true } as unknown as Response; - -describe("UserProjectService", () => { - let service: UserProjectService; - let httpMock: HttpTestingController; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], - // UserProjectService never calls NotificationService; stub it so TestBed - // does not have to construct the ng-zorro message/notification services. - providers: [UserProjectService, { provide: NotificationService, useValue: {} }], - }); - service = TestBed.inject(UserProjectService); - httpMock = TestBed.inject(HttpTestingController); - }); - - afterEach(() => { - httpMock.verify(); - }); - - it("getProjectList issues GET to the list url", () => { - let result: DashboardProject[] | undefined; - service.getProjectList().subscribe(res => (result = res)); - const req = httpMock.expectOne(USER_PROJECT_LIST_URL); - expect(req.request.method).toBe("GET"); - req.flush([mockProject]); - expect(result).toEqual([mockProject]); - }); - - it("retrieveWorkflowsOfProject issues GET to the project workflows url", () => { - let result: DashboardWorkflow[] | undefined; - service.retrieveWorkflowsOfProject(1).subscribe(res => (result = res)); - const req = httpMock.expectOne(`${USER_PROJECT_BASE_URL}/1/workflows`); - expect(req.request.method).toBe("GET"); - req.flush(mockWorkflows); - expect(result).toEqual(mockWorkflows); - }); - - it("retrieveFilesOfProject issues GET to the project files url", () => { - let result: DashboardFile[] | undefined; - service.retrieveFilesOfProject(1).subscribe(res => (result = res)); - const req = httpMock.expectOne(`${USER_PROJECT_BASE_URL}/1/files`); - expect(req.request.method).toBe("GET"); - req.flush([mockFile]); - expect(result).toEqual([mockFile]); - }); - - it("retrieveProject issues GET to the project url", () => { - let result: DashboardProject | undefined; - service.retrieveProject(1).subscribe(res => (result = res)); - const req = httpMock.expectOne(`${USER_PROJECT_BASE_URL}/1`); - expect(req.request.method).toBe("GET"); - req.flush(mockProject); - expect(result).toEqual(mockProject); - }); - - it("createProject issues POST to the create url with the name in the path", () => { - let result: DashboardProject | undefined; - service.createProject("proj").subscribe(res => (result = res)); - const req = httpMock.expectOne(`${CREATE_PROJECT_URL}/proj`); - expect(req.request.method).toBe("POST"); - expect(req.request.body).toEqual({}); - req.flush(mockProject); - expect(result).toEqual(mockProject); - }); - - it("updateProjectName issues POST to the rename url", () => { - service.updateProjectName(1, "renamed").subscribe(); - const req = httpMock.expectOne(`${USER_PROJECT_BASE_URL}/1/rename/renamed`); - expect(req.request.method).toBe("POST"); - expect(req.request.body).toEqual({}); - req.flush(mockResponse); - }); - - it("updateProjectDescription issues POST with the description as the raw body", () => { - service.updateProjectDescription(1, "a new description").subscribe(); - const req = httpMock.expectOne(`${USER_PROJECT_BASE_URL}/1/update/description`); - expect(req.request.method).toBe("POST"); - expect(req.request.body).toBe("a new description"); - req.flush(mockResponse); - }); - - it("deleteProject issues DELETE to the delete url", () => { - service.deleteProject(1).subscribe(); - const req = httpMock.expectOne(`${DELETE_PROJECT_URL}/1`); - expect(req.request.method).toBe("DELETE"); - req.flush(mockResponse); - }); - - it("addWorkflowToProject issues POST to the add-workflow url", () => { - service.addWorkflowToProject(1, 2).subscribe(); - const req = httpMock.expectOne(`${USER_PROJECT_BASE_URL}/1/workflow/2/add`); - expect(req.request.method).toBe("POST"); - expect(req.request.body).toEqual({}); - req.flush(mockResponse); - }); - - it("removeWorkflowFromProject issues DELETE to the remove-workflow url", () => { - service.removeWorkflowFromProject(1, 2).subscribe(); - const req = httpMock.expectOne(`${USER_PROJECT_BASE_URL}/1/workflow/2/delete`); - expect(req.request.method).toBe("DELETE"); - req.flush(mockResponse); - }); - - it("addFileToProject issues POST to the add-file url", () => { - service.addFileToProject(1, 3).subscribe(); - const req = httpMock.expectOne(`${USER_PROJECT_BASE_URL}/1/user-file/3/add`); - expect(req.request.method).toBe("POST"); - expect(req.request.body).toEqual({}); - req.flush(mockResponse); - }); - - it("updateProjectColor issues POST to the color-add url", () => { - service.updateProjectColor(1, "ff0000").subscribe(); - const req = httpMock.expectOne(`${USER_PROJECT_BASE_URL}/1/color/ff0000/add`); - expect(req.request.method).toBe("POST"); - expect(req.request.body).toEqual({}); - req.flush(mockResponse); - }); - - it("deleteProjectColor issues POST to the color-delete url", () => { - service.deleteProjectColor(1).subscribe(); - const req = httpMock.expectOne(`${USER_PROJECT_BASE_URL}/1/color/delete`); - expect(req.request.method).toBe("POST"); - expect(req.request.body).toEqual({}); - req.flush(mockResponse); - }); - - it("removeFileFromProject issues DELETE to the remove-file url", () => { - service.removeFileFromProject(1, 3).subscribe(); - const req = httpMock.expectOne(`${USER_PROJECT_BASE_URL}/1/user-file/3/delete`); - expect(req.request.method).toBe("DELETE"); - req.flush(mockResponse); - }); - - it("getProjectFiles returns an empty list before any fetch", () => { - expect(service.getProjectFiles()).toEqual([]); - }); - - it("refreshFilesOfProject fetches files and caches them for getProjectFiles", () => { - service.refreshFilesOfProject(1); - const req = httpMock.expectOne(`${USER_PROJECT_BASE_URL}/1/files`); - expect(req.request.method).toBe("GET"); - req.flush([mockFile]); - expect(service.getProjectFiles()).toEqual([mockFile]); - }); - - it("deleteDashboardUserFileEntry deletes the file then refreshes the cached project files", () => { - service.deleteDashboardUserFileEntry(1, mockFile); - - const deleteReq = httpMock.expectOne(`${USER_FILE_DELETE_URL}/${mockFile.file.name}/${mockFile.ownerEmail}`); - expect(deleteReq.request.method).toBe("DELETE"); - deleteReq.flush({}); - - const refreshReq = httpMock.expectOne(`${USER_PROJECT_BASE_URL}/1/files`); - expect(refreshReq.request.method).toBe("GET"); - refreshReq.flush([mockFile]); - - expect(service.getProjectFiles()).toEqual([mockFile]); - }); -}); - -describe("UserProjectService color helpers", () => { - it("isInvalidColorFormat flags null, wrong-length, and non-hex strings", () => { - expect(UserProjectService.isInvalidColorFormat(null as unknown as string)).toBe(true); - expect(UserProjectService.isInvalidColorFormat("ff")).toBe(true); // too short - expect(UserProjectService.isInvalidColorFormat("ffff")).toBe(true); // length 4 - expect(UserProjectService.isInvalidColorFormat("gggggg")).toBe(true); // non-hex - expect(UserProjectService.isInvalidColorFormat("fff")).toBe(false); // 3-digit hex - expect(UserProjectService.isInvalidColorFormat("ffffff")).toBe(false); // 6-digit hex - expect(UserProjectService.isInvalidColorFormat("1A2b3C")).toBe(false); // mixed case - }); - - it("isLightColor is true for light colors and false for dark or invalid ones", () => { - expect(UserProjectService.isLightColor("ffffff")).toBe(true); // white - expect(UserProjectService.isLightColor("fff")).toBe(true); // 3-digit white expands to ffffff - expect(UserProjectService.isLightColor("000000")).toBe(false); // black - expect(UserProjectService.isLightColor("zz")).toBe(false); // invalid format -> dark default - }); -}); diff --git a/frontend/src/app/dashboard/service/user/project/user-project.service.ts b/frontend/src/app/dashboard/service/user/project/user-project.service.ts deleted file mode 100644 index dbac0ae2603..00000000000 --- a/frontend/src/app/dashboard/service/user/project/user-project.service.ts +++ /dev/null @@ -1,170 +0,0 @@ -/** - * 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 { HttpClient } from "@angular/common/http"; -import { Injectable } from "@angular/core"; -import { Observable } from "rxjs"; -import { AppSettings } from "../../../../common/app-setting"; -import { DashboardWorkflow } from "../../../type/dashboard-workflow.interface"; -import { DashboardFile } from "../../../type/dashboard-file.interface"; -import { DashboardProject } from "../../../type/dashboard-project.interface"; -import { NotificationService } from "../../../../common/service/notification/notification.service"; - -export const USER_PROJECT_BASE_URL = `${AppSettings.getApiEndpoint()}/project`; -export const USER_PROJECT_LIST_URL = `${USER_PROJECT_BASE_URL}/list`; -export const DELETE_PROJECT_URL = `${USER_PROJECT_BASE_URL}/delete`; -export const CREATE_PROJECT_URL = `${USER_PROJECT_BASE_URL}/create`; -export const USER_FILE_BASE_URL = `${AppSettings.getApiEndpoint()}/user/file`; -export const USER_FILE_DELETE_URL = `${USER_FILE_BASE_URL}/delete`; - -@Injectable({ - providedIn: "root", -}) -export class UserProjectService { - private files: ReadonlyArray = []; - - constructor( - private http: HttpClient, - private notificationService: NotificationService - ) {} - - public getProjectList(): Observable { - return this.http.get(`${USER_PROJECT_LIST_URL}`); - } - - public retrieveWorkflowsOfProject(pid: number): Observable { - return this.http.get(`${USER_PROJECT_BASE_URL}/${pid}/workflows`); - } - - public retrieveFilesOfProject(pid: number): Observable { - return this.http.get(`${USER_PROJECT_BASE_URL}/${pid}/files`); - } - - public getProjectFiles(): ReadonlyArray { - return this.files; - } - - public refreshFilesOfProject(pid: number): void { - this.retrieveFilesOfProject(pid).subscribe(files => { - this.files = files; - }); - } - - public retrieveProject(pid: number): Observable { - return this.http.get(`${USER_PROJECT_BASE_URL}/${pid}`); - } - - public updateProjectName(pid: number, name: string): Observable { - return this.http.post(`${USER_PROJECT_BASE_URL}/${pid}/rename/${name}`, {}); - } - - public updateProjectDescription(pid: number, description: string): Observable { - return this.http.post(`${USER_PROJECT_BASE_URL}/${pid}/update/description`, `${description}`); - } - - public deleteProject(pid: number): Observable { - return this.http.delete(`${DELETE_PROJECT_URL}/` + pid); - } - - public createProject(name: string): Observable { - return this.http.post(`${CREATE_PROJECT_URL}/` + name, {}); - } - - public addWorkflowToProject(pid: number, wid: number): Observable { - return this.http.post(`${USER_PROJECT_BASE_URL}/${pid}/workflow/${wid}/add`, {}); - } - - public removeWorkflowFromProject(pid: number, wid: number): Observable { - return this.http.delete(`${USER_PROJECT_BASE_URL}/${pid}/workflow/${wid}/delete`, {}); - } - - public addFileToProject(pid: number, fid: number): Observable { - return this.http.post(`${USER_PROJECT_BASE_URL}/${pid}/user-file/${fid}/add`, {}); - } - - public updateProjectColor(pid: number, colorHex: string): Observable { - return this.http.post(`${USER_PROJECT_BASE_URL}/${pid}/color/${colorHex}/add`, {}); - } - - public deleteProjectColor(pid: number): Observable { - return this.http.post(`${USER_PROJECT_BASE_URL}/${pid}/color/delete`, {}); - } - - public removeFileFromProject(pid: number, fid: number): Observable { - return this.http.delete(`${USER_PROJECT_BASE_URL}/${pid}/user-file/${fid}/delete`, {}); - } - - /** - * same as UserFileService"s deleteDashboardUserFileEntry method, except - * it is modified to refresh the project"s list of files - */ - public deleteDashboardUserFileEntry(pid: number, targetUserFileEntry: DashboardFile): void { - this.http - .delete(`${USER_FILE_DELETE_URL}/${targetUserFileEntry.file.name}/${targetUserFileEntry.ownerEmail}`) - .subscribe({ - next: () => { - this.refreshFilesOfProject(pid); // refresh files within project - }, - // @ts-ignore // TODO: fix this with notification component - error: (err: unknown) => alert("Cannot delete the file entry: " + err.error), - }); - } - - /** - * Helper function to determine if a project color is light - * or dark, which can be helpful for styling decisions - * - * @param color (HEX formatted color string) - * @returns boolean indicating whether color is "light" or "dark" - */ - public static isLightColor(color: string): boolean { - if (this.isInvalidColorFormat(color)) { - return false; // default color is dark - } - - // ensure format is in 6 digit HEX - if (color.length == 3) { - color = color - .split("") - .map(s => s + s) - .join(""); - } - - // convert to RGB form - let colorRGB: number = +("0x" + color); - - let r: number = colorRGB >> 16; - let g: number = (colorRGB >> 8) & 255; - let b: number = colorRGB & 255; - - // estimate HSV value - let hsv: number = Math.sqrt(0.299 * (r * r) + 0.587 * (g * g) + 0.114 * (b * b)); - return hsv > 200; - } - - /** - * Helper function to validate if a project color is in HEX format - * - * @param color - * @returns boolean indicating whether color is in valid HEX format - */ - public static isInvalidColorFormat(color: string) { - return color == null || (color.length != 6 && color.length != 3) || !/^([0-9A-Fa-f]{3}){1,2}$/.test(color); - } -} diff --git a/frontend/src/app/dashboard/service/user/public-project/public-project.service.ts b/frontend/src/app/dashboard/service/user/public-project/public-project.service.ts deleted file mode 100644 index a6d35f610d7..00000000000 --- a/frontend/src/app/dashboard/service/user/public-project/public-project.service.ts +++ /dev/null @@ -1,53 +0,0 @@ -/** - * 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 { HttpClient } from "@angular/common/http"; -import { Injectable } from "@angular/core"; -import { Observable } from "rxjs"; -import { AppSettings } from "../../../../common/app-setting"; -import { PublicProject } from "../../../type/dashboard-project.interface"; - -export const USER_BASE_URL = `${AppSettings.getApiEndpoint()}/public/project`; - -@Injectable({ - providedIn: "root", -}) -export class PublicProjectService { - constructor(private http: HttpClient) {} - - public getType(pid: number): Observable { - return this.http.get(`${USER_BASE_URL}/type/${pid}`, { responseType: "text" }); - } - - public makePublic(pid: number): Observable { - return this.http.put(`${USER_BASE_URL}/public/${pid}`, null); - } - - public makePrivate(pid: number): Observable { - return this.http.put(`${USER_BASE_URL}/private/${pid}`, null); - } - - public getPublicProjects(): Observable { - return this.http.get(`${USER_BASE_URL}/list`); - } - - public addPublicProjects(CheckedId: number[]): Observable { - return this.http.put(`${USER_BASE_URL}/add`, CheckedId); - } -} diff --git a/frontend/src/app/dashboard/service/user/search.service.spec.ts b/frontend/src/app/dashboard/service/user/search.service.spec.ts index 994169a1ee4..624fa3de4ab 100644 --- a/frontend/src/app/dashboard/service/user/search.service.spec.ts +++ b/frontend/src/app/dashboard/service/user/search.service.spec.ts @@ -30,7 +30,6 @@ import { SearchFilterParameters } from "../../type/search-filter-parameters"; import { SortMethod } from "../../type/sort-method"; import { SearchResult, SearchResultItem } from "../../type/search-result"; import { DashboardWorkflow } from "../../type/dashboard-workflow.interface"; -import { DashboardProject } from "../../type/dashboard-project.interface"; import { DashboardDataset } from "../../type/dashboard-dataset.interface"; const API = "api"; @@ -44,7 +43,6 @@ function makeEmptyFilter(): SearchFilterParameters { owners: [], ids: [], operators: [], - projectIds: [], }; } @@ -68,7 +66,6 @@ function makeWorkflowItem(wid: number, ownerId: number): SearchResultItem { settings: { dataTransferBatchSize: 400, executionMode: "PIPELINED" as any }, }, }, - projectIDs: [], accessLevel: "WRITE", ownerId, coverImage: null, @@ -76,19 +73,6 @@ function makeWorkflowItem(wid: number, ownerId: number): SearchResultItem { return { resourceType: "workflow", workflow }; } -function makeProjectItem(pid: number, ownerId: number): SearchResultItem { - const project: DashboardProject = { - pid, - name: `proj-${pid}`, - description: "", - ownerId, - creationTime: 0, - color: null, - accessLevel: "WRITE", - }; - return { resourceType: "project", project }; -} - function makeDatasetItem(did: number, ownerUid: number): SearchResultItem { const dataset: DashboardDataset = { isOwner: true, @@ -277,22 +261,6 @@ describe("SearchService", () => { expect(persistSpy.getSizes).not.toHaveBeenCalled(); }); - it("uses Project entity routing for project items", async () => { - const proj = makeProjectItem(20, 8); - vi.spyOn(service, "getUserInfo").mockReturnValue(of({ 8: { userName: "bob" } })); - hubSpy.getCounts.mockReturnValue( - of([{ entityId: 20, entityType: EntityType.Project, counts: { [ActionType.Clone]: 2 } }]) - ); - - const [entry] = await firstValueFrom(service.extendSearchResultsWithHubActivityInfo([proj], true)); - - const [types, ids] = hubSpy.getCounts.mock.calls[0]; - expect(types).toEqual([EntityType.Project]); - expect(ids).toEqual([20]); - expect(entry.cloneCount).toBe(2); - expect(entry.ownerName).toBe("bob"); - }); - it("uses Dataset entity routing and pulls ownerUid for dataset items", async () => { const ds = makeDatasetItem(30, 9); const userInfoSpy = vi.spyOn(service, "getUserInfo").mockReturnValue(of({ 9: { userName: "carol" } })); @@ -307,10 +275,10 @@ describe("SearchService", () => { }); it("does not request sizes when there are no workflow items", async () => { - const proj = makeProjectItem(20, 8); + const ds = makeDatasetItem(20, 8); vi.spyOn(service, "getUserInfo").mockReturnValue(of({} as any)); - await firstValueFrom(service.extendSearchResultsWithHubActivityInfo([proj], true)); + await firstValueFrom(service.extendSearchResultsWithHubActivityInfo([ds], true)); expect(persistSpy.getSizes).not.toHaveBeenCalled(); }); diff --git a/frontend/src/app/dashboard/service/user/search.service.ts b/frontend/src/app/dashboard/service/user/search.service.ts index a48ff8991d6..90b5f14f72a 100644 --- a/frontend/src/app/dashboard/service/user/search.service.ts +++ b/frontend/src/app/dashboard/service/user/search.service.ts @@ -59,7 +59,7 @@ export class SearchService { * @param params - Additional search filter parameters. * @param start - The starting index for paginated results. * @param count - The number of results to retrieve. - * @param type - The type of resource to search for ("workflow", "project", "dataset", "file", or null (all resource type)). + * @param type - The type of resource to search for ("workflow", "dataset", "file", or null (all resource type)). * @param orderBy - Specifies the sorting method. * @param isLogin - Indicates if the user is logged in. * - `isLogin = true`: Use the authenticated search endpoint, retrieving both user-accessible and public resources based on `includePublic`. @@ -73,7 +73,7 @@ export class SearchService { params: SearchFilterParameters, start: number, count: number, - type: "workflow" | "project" | "file" | "dataset" | null, + type: "workflow" | "file" | "dataset" | null, orderBy: SortMethod, isLogin: boolean, includePublic: boolean = false @@ -102,7 +102,7 @@ export class SearchService { * This method: * - Dispatches a paginated search request (authenticated or public) via `this.search(...)`. * - Filters out null or mismatched datasets when `type === 'dataset'` and sets `hasMismatch`. - * - Fetches owner information (name, Google avatar) in batch for workflows, projects, and datasets. + * - Fetches owner information (name, Google avatar) in batch for workflows and datasets. * - Aggregates view/clone/like counts via the batch counts API. * - Constructs `DashboardEntry` instances and attaches owner info and counts. * @@ -110,7 +110,7 @@ export class SearchService { * @param params Additional search filter parameters. * @param start The starting index for paginated results. * @param count The number of results to retrieve. - * @param type The type of resource to search for ("workflow", "project", "dataset", "file", or null (all resource type)). + * @param type The type of resource to search for ("workflow", "dataset", "file", or null (all resource type)). * @param orderBy Specifies the sorting method. * @param isLogin Indicates if the user is logged in. * @param includePublic Specifies whether to include public resources in the search results. @@ -125,7 +125,7 @@ export class SearchService { params: SearchFilterParameters, start: number, count: number, - type: "workflow" | "project" | "dataset" | "file" | null, + type: "workflow" | "dataset" | "file" | null, orderBy: SortMethod, isLogin: boolean, includePublic: boolean @@ -170,8 +170,7 @@ export class SearchService { const userIds = new Set(); items.forEach(i => { - if (i.project) userIds.add(i.project.ownerId); - else if (i.workflow) userIds.add(i.workflow.ownerId); + if (i.workflow) userIds.add(i.workflow.ownerId); else if (i.dataset?.dataset?.ownerUid != null) userIds.add(i.dataset.dataset.ownerUid); }); const userInfo$ = userIds.size ? this.getUserInfo(Array.from(userIds)) : of({} as Record); @@ -182,9 +181,6 @@ export class SearchService { if (i.workflow?.workflow?.wid != null) { entityTypes.push(EntityType.Workflow); entityIds.push(i.workflow.workflow.wid); - } else if (i.project) { - entityTypes.push(EntityType.Project); - entityIds.push(i.project.pid); } else if (i.dataset?.dataset?.did != null) { entityTypes.push(EntityType.Dataset); entityIds.push(i.dataset.dataset.did); @@ -220,18 +216,10 @@ export class SearchService { access.forEach(r => (accessMap[`${r.entityType}:${r.entityId}`] = r.userIds)); return items.map(i => { - const entry = i.workflow - ? new DashboardEntry(i.workflow) - : i.project - ? new DashboardEntry(i.project) - : new DashboardEntry(i.dataset!); + const entry = i.workflow ? new DashboardEntry(i.workflow) : new DashboardEntry(i.dataset!); const key = `${entry.type}:${entry.id}`; - const ownerId = i.workflow - ? i.workflow.ownerId - : i.project - ? i.project.ownerId - : i.dataset!.dataset!.ownerUid!; + const ownerId = i.workflow ? i.workflow.ownerId : i.dataset!.dataset!.ownerUid!; const ui = (userMap as any)[ownerId]; if (ui) { entry.setOwnerName(ui.userName); diff --git a/frontend/src/app/dashboard/service/user/stub-search.service.ts b/frontend/src/app/dashboard/service/user/stub-search.service.ts index 6ab1628626f..441f095bc6a 100644 --- a/frontend/src/app/dashboard/service/user/stub-search.service.ts +++ b/frontend/src/app/dashboard/service/user/stub-search.service.ts @@ -43,7 +43,7 @@ export class StubSearchService { params: SearchFilterParameters, start: number, count: number, - type: "workflow" | "project" | "file" | "dataset" | null, + type: "workflow" | "file" | "dataset" | null, orderBy: SortMethod, isLogin: boolean = true, includePublic: boolean = false @@ -54,7 +54,6 @@ export class StubSearchService { results: searchTestEntries(keywords, params, this.testEntries, type).map(i => ({ resourceType: i.type, workflow: i.type === "workflow" ? i.workflow : undefined, - project: i.type === "project" ? i.project : undefined, })), more: false, }); @@ -80,7 +79,7 @@ export class StubSearchService { params: SearchFilterParameters, start: number, count: number, - type: "workflow" | "project" | "dataset" | "file" | null, + type: "workflow" | "dataset" | "file" | null, orderBy: SortMethod, isLogin: boolean, includePublic: boolean @@ -95,7 +94,6 @@ export class StubSearchService { i => this.testEntries.find(e => { if (i.workflow && e.type === "workflow" && e.workflow === i.workflow) return true; - if (i.project && e.type === "project" && e.project === i.project) return true; if (i.dataset && e.type === "dataset" && e.dataset === i.dataset) return true; return false; })! diff --git a/frontend/src/app/dashboard/type/dashboard-entry.spec.ts b/frontend/src/app/dashboard/type/dashboard-entry.spec.ts index 2d6621ecbf6..6212c458587 100644 --- a/frontend/src/app/dashboard/type/dashboard-entry.spec.ts +++ b/frontend/src/app/dashboard/type/dashboard-entry.spec.ts @@ -20,7 +20,6 @@ import { DashboardEntry } from "./dashboard-entry"; import { EntityType } from "../../hub/service/hub.service"; import { DashboardWorkflow } from "./dashboard-workflow.interface"; -import { DashboardProject } from "./dashboard-project.interface"; import { DashboardFile } from "./dashboard-file.interface"; import { DashboardDataset } from "./dashboard-dataset.interface"; import { DashboardWorkflowComputingUnit } from "../../common/type/workflow-computing-unit"; @@ -51,25 +50,12 @@ function makeWorkflow(coverImage: string | null = "http://example.com/cover.png" isPublished: 0, readonly: false, }, - projectIDs: [1, 2], accessLevel: "WRITE", ownerId: 10, coverImage, }; } -function makeProject(): DashboardProject { - return { - pid: 202, - name: "My Project", - description: "A sample project", - ownerId: 20, - creationTime: 1700000002000, - color: "#ff0000", - accessLevel: "READ", - }; -} - function makeFile(): DashboardFile { return { ownerEmail: "file-owner@example.com", @@ -164,20 +150,6 @@ describe("DashboardEntry", () => { expect(entry.coverImageUrl).toBeUndefined(); }); - it("maps a DashboardProject to the Project entity with empty description and creationTime as both timestamps", () => { - const entry = new DashboardEntry(makeProject()); - - expect(entry.type).toBe(EntityType.Project); - expect(entry.id).toBe(202); - expect(entry.name).toBe("My Project"); - expect(entry.description).toBe(""); - expect(entry.creationTime).toBe(1700000002000); - expect(entry.lastModifiedTime).toBe(1700000002000); - expect(entry.accessLevel).toBe("READ"); - expect(entry.ownerId).toBe(20); - expect(entry.coverImageUrl).toBeUndefined(); - }); - it("maps a DashboardFile to the File entity and copies file fields", () => { const entry = new DashboardEntry(makeFile()); @@ -286,13 +258,7 @@ describe("DashboardEntry", () => { it("workflow getter returns the value for a workflow entry and throws for others", () => { const workflowValue = makeWorkflow(); expect(new DashboardEntry(workflowValue).workflow).toBe(workflowValue); - expect(() => new DashboardEntry(makeProject()).workflow).toThrowError("Value is not of type DashboardWorkflow."); - }); - - it("project getter returns the value for a project entry and throws for others", () => { - const projectValue = makeProject(); - expect(new DashboardEntry(projectValue).project).toBe(projectValue); - expect(() => new DashboardEntry(makeWorkflow()).project).toThrowError("Value is not of type DashboardProject."); + expect(() => new DashboardEntry(makeFile()).workflow).toThrowError("Value is not of type DashboardWorkflow."); }); it("file getter returns the value for a file entry and throws for others", () => { diff --git a/frontend/src/app/dashboard/type/dashboard-entry.ts b/frontend/src/app/dashboard/type/dashboard-entry.ts index 71c9a1515f2..632cfff135a 100644 --- a/frontend/src/app/dashboard/type/dashboard-entry.ts +++ b/frontend/src/app/dashboard/type/dashboard-entry.ts @@ -19,13 +19,11 @@ import { DashboardFile } from "./dashboard-file.interface"; import { DashboardWorkflow } from "./dashboard-workflow.interface"; -import { DashboardProject } from "./dashboard-project.interface"; import { DashboardDataset } from "./dashboard-dataset.interface"; import { DashboardWorkflowComputingUnit } from "../../common/type/workflow-computing-unit"; import { isDashboardDataset, isDashboardFile, - isDashboardProject, isDashboardWorkflow, isDashboardWorkflowComputingUnit, } from "./type-predicates"; @@ -57,14 +55,7 @@ export class DashboardEntry { accessibleUserIds: number[]; coverImageUrl?: string; - constructor( - public value: - | DashboardWorkflow - | DashboardProject - | DashboardFile - | DashboardDataset - | DashboardWorkflowComputingUnit - ) { + constructor(public value: DashboardWorkflow | DashboardFile | DashboardDataset | DashboardWorkflowComputingUnit) { if (isDashboardWorkflow(value)) { this.type = EntityType.Workflow; this.id = value.workflow.wid; @@ -84,24 +75,6 @@ export class DashboardEntry { this.isLiked = false; this.accessibleUserIds = []; this.coverImageUrl = value.coverImage ?? undefined; - } else if (isDashboardProject(value)) { - this.type = EntityType.Project; - this.id = value.pid; - this.name = value.name; - this.description = ""; - this.creationTime = value.creationTime; - this.lastModifiedTime = value.creationTime; - this.accessLevel = value.accessLevel; - this.ownerName = ""; - this.ownerEmail = ""; - this.ownerGoogleAvatar = ""; - this.ownerId = value.ownerId; - this.size = 0; - this.viewCount = 0; - this.cloneCount = 0; - this.likeCount = 0; - this.isLiked = false; - this.accessibleUserIds = []; } else if (isDashboardFile(value)) { this.type = EntityType.File; this.id = value.file.fid; @@ -184,13 +157,6 @@ export class DashboardEntry { this.size = size; } - get project(): DashboardProject { - if (!isDashboardProject(this.value)) { - throw new Error("Value is not of type DashboardProject."); - } - return this.value; - } - get workflow(): DashboardWorkflow { if (!isDashboardWorkflow(this.value)) { throw new Error("Value is not of type DashboardWorkflow."); diff --git a/frontend/src/app/dashboard/type/dashboard-project.interface.ts b/frontend/src/app/dashboard/type/dashboard-project.interface.ts deleted file mode 100644 index 4e84bb16c0b..00000000000 --- a/frontend/src/app/dashboard/type/dashboard-project.interface.ts +++ /dev/null @@ -1,35 +0,0 @@ -/** - * 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. - */ - -export interface DashboardProject { - pid: number; - name: string; - description: string; - ownerId: number; - creationTime: number; - color: string | null; - accessLevel: string; -} - -export interface PublicProject { - pid: number; - name: string; - owner: string; - creationTime: number; -} diff --git a/frontend/src/app/dashboard/type/dashboard-workflow.interface.ts b/frontend/src/app/dashboard/type/dashboard-workflow.interface.ts index e984f29ac62..1c8f68328b0 100644 --- a/frontend/src/app/dashboard/type/dashboard-workflow.interface.ts +++ b/frontend/src/app/dashboard/type/dashboard-workflow.interface.ts @@ -23,7 +23,6 @@ export interface DashboardWorkflow { isOwner: boolean; ownerName: string | undefined; workflow: Workflow; - projectIDs: number[]; accessLevel: string; ownerId: number; coverImage: string | null; diff --git a/frontend/src/app/dashboard/type/search-filter-parameters.spec.ts b/frontend/src/app/dashboard/type/search-filter-parameters.spec.ts index fcf1250f933..7cf70ce56d3 100644 --- a/frontend/src/app/dashboard/type/search-filter-parameters.spec.ts +++ b/frontend/src/app/dashboard/type/search-filter-parameters.spec.ts @@ -34,7 +34,6 @@ function makeEmptyFilter(): SearchFilterParameters { owners: [], ids: [], operators: [], - projectIds: [], }; } @@ -90,13 +89,6 @@ describe("toQueryStrings", () => { ); }); - it("should stringify numeric projectIds and keep projectId 0", () => { - const filter = makeEmptyFilter(); - filter.projectIds = [0, 42]; - - expect(toQueryStrings([], filter)).toBe("projectId=0&projectId=42"); - }); - it("should URL-encode filter values", () => { const filter = makeEmptyFilter(); filter.owners = ["a+b@x.com"]; @@ -147,14 +139,13 @@ describe("toQueryStrings", () => { owners: ["alice"], ids: ["7"], operators: ["CSVFileScan"], - projectIds: [42], }; expect(toQueryStrings(["alpha"], filter, 10, 20, "workflow", SortMethod.CreateTimeDesc)).toBe( "query=alpha" + "&createDateStart=2024-01-15&createDateEnd=2024-02-20" + "&modifiedDateStart=2024-03-05&modifiedDateEnd=2024-04-10" + - "&owner=alice&id=7&operator=CSVFileScan&projectId=42" + + "&owner=alice&id=7&operator=CSVFileScan" + "&start=10&count=20&resourceType=workflow&orderBy=CreateTimeDesc" ); }); @@ -176,7 +167,6 @@ interface WorkflowEntryOverrides { creationTime?: number; lastModifiedTime?: number; operatorTypes?: string[]; - projectIDs?: number[]; } function makeWorkflowEntry(overrides: WorkflowEntryOverrides = {}): DashboardEntry { @@ -205,7 +195,6 @@ function makeWorkflowEntry(overrides: WorkflowEntryOverrides = {}): DashboardEnt isPublished: 0, readonly: false, }, - projectIDs: overrides.projectIDs ?? [], accessLevel: "WRITE", ownerId: 10, coverImage: null, @@ -313,17 +302,8 @@ describe("searchTestEntries", () => { expect(result).toEqual([hasCsv]); }); - it("filters by projectId membership", () => { - const inProject = makeWorkflowEntry({ name: "in", projectIDs: [1, 2] }); - const notInProject = makeWorkflowEntry({ name: "out", projectIDs: [3] }); - const filter = makeEmptyFilter(); - filter.projectIds = [2]; - const result = searchTestEntries([], filter, [inProject, notInProject], null); - expect(result).toEqual([inProject]); - }); - it("excludes non-workflow entries when a workflow-only filter is applied", () => { - // owners/ids/operators/projectIds all gate on e.type === "workflow". + // owners/ids/operators all gate on e.type === "workflow". const workflow = makeWorkflowEntry({ name: "wf", ownerName: "alice" }); const dataset = makeDatasetEntry("ds"); const filter = makeEmptyFilter(); @@ -347,7 +327,6 @@ describe("searchTestEntries", () => { creationTime: new Date(2024, 0, 15, 12).getTime(), lastModifiedTime: new Date(2024, 0, 16, 12).getTime(), operatorTypes: ["CSVFileScan"], - projectIDs: [42], }); const wrongOwner = makeWorkflowEntry({ name: "alpha-pipeline", @@ -356,7 +335,6 @@ describe("searchTestEntries", () => { creationTime: new Date(2024, 0, 15, 12).getTime(), lastModifiedTime: new Date(2024, 0, 16, 12).getTime(), operatorTypes: ["CSVFileScan"], - projectIDs: [42], }); const wrongName = makeWorkflowEntry({ name: "beta-pipeline", @@ -365,7 +343,6 @@ describe("searchTestEntries", () => { creationTime: new Date(2024, 0, 15, 12).getTime(), lastModifiedTime: new Date(2024, 0, 16, 12).getTime(), operatorTypes: ["CSVFileScan"], - projectIDs: [42], }); const filter: SearchFilterParameters = { createDateStart: new Date(2024, 0, 10), @@ -375,7 +352,6 @@ describe("searchTestEntries", () => { owners: ["alice"], ids: ["7"], operators: ["CSVFileScan"], - projectIds: [42], }; const result = searchTestEntries(["alpha"], filter, [match, wrongOwner, wrongName], "workflow"); expect(result).toEqual([match]); diff --git a/frontend/src/app/dashboard/type/search-filter-parameters.ts b/frontend/src/app/dashboard/type/search-filter-parameters.ts index 8cf39fc5a8c..a573d63d092 100644 --- a/frontend/src/app/dashboard/type/search-filter-parameters.ts +++ b/frontend/src/app/dashboard/type/search-filter-parameters.ts @@ -28,7 +28,6 @@ export interface SearchFilterParameters { owners: string[]; ids: string[]; operators: string[]; - projectIds: number[]; } export const toQueryStrings = ( @@ -36,7 +35,7 @@ export const toQueryStrings = ( params: SearchFilterParameters, start?: number, count?: number, - type?: "workflow" | "project" | "file" | "dataset" | null, + type?: "workflow" | "file" | "dataset" | null, orderBy?: SortMethod ): string => { function* getQueryParameters(): Iterable<[name: string, value: string]> { @@ -62,9 +61,6 @@ export const toQueryStrings = ( for (const operator of params.operators) { yield ["operator", operator]; } - for (const id of params.projectIds) { - yield ["projectId", id.toString()]; - } } const concatenateQueryStrings = (queryStrings: ReturnType): string => [ @@ -85,7 +81,7 @@ export const searchTestEntries = ( keywords: string[], params: SearchFilterParameters, testEntries: DashboardEntry[], - type: "workflow" | "project" | "file" | "dataset" | null + type: "workflow" | "file" | "dataset" | null ): DashboardEntry[] => { const endOfDay = (date: Date) => { date.setHours(23); @@ -130,13 +126,6 @@ export const searchTestEntries = ( ) ); } - if (params.projectIds.length > 0) { - testEntries = testEntries.filter( - e => - e.type === "workflow" && - e.workflow.projectIDs.some(id => params.projectIds.some(projectIdToFilterBy => projectIdToFilterBy == id)) - ); - } if (type) { testEntries = testEntries.filter(e => e.type === type); } diff --git a/frontend/src/app/dashboard/type/search-result.ts b/frontend/src/app/dashboard/type/search-result.ts index 8fc14361b17..07c6077369e 100644 --- a/frontend/src/app/dashboard/type/search-result.ts +++ b/frontend/src/app/dashboard/type/search-result.ts @@ -19,14 +19,12 @@ import { DashboardFile } from "./dashboard-file.interface"; import { DashboardWorkflow } from "./dashboard-workflow.interface"; -import { DashboardProject } from "./dashboard-project.interface"; import { DashboardDataset } from "./dashboard-dataset.interface"; import { DashboardEntry } from "./dashboard-entry"; export interface SearchResultItem { - resourceType: "workflow" | "project" | "file" | "dataset" | "computing-unit"; + resourceType: "workflow" | "file" | "dataset" | "computing-unit"; workflow?: DashboardWorkflow; - project?: DashboardProject; file?: DashboardFile; dataset?: DashboardDataset; } diff --git a/frontend/src/app/dashboard/type/type-predicates.spec.ts b/frontend/src/app/dashboard/type/type-predicates.spec.ts index d98afd8b9fa..93512341153 100644 --- a/frontend/src/app/dashboard/type/type-predicates.spec.ts +++ b/frontend/src/app/dashboard/type/type-predicates.spec.ts @@ -20,12 +20,10 @@ import { isDashboardDataset, isDashboardFile, - isDashboardProject, isDashboardWorkflow, isDashboardWorkflowComputingUnit, } from "./type-predicates"; import { DashboardWorkflow } from "./dashboard-workflow.interface"; -import { DashboardProject } from "./dashboard-project.interface"; import { DashboardFile } from "./dashboard-file.interface"; import { DashboardDataset } from "./dashboard-dataset.interface"; import { DashboardWorkflowComputingUnit } from "../../common/type/workflow-computing-unit"; @@ -53,22 +51,11 @@ const workflowFixture: DashboardWorkflow = { isPublished: 0, readonly: false, }, - projectIDs: [1, 2], accessLevel: "WRITE", ownerId: 10, coverImage: null, }; -const projectFixture: DashboardProject = { - pid: 5, - name: "My Project", - description: "A sample project", - ownerId: 10, - creationTime: 1700000000000, - color: "#ff0000", - accessLevel: "WRITE", -}; - const fileFixture: DashboardFile = { ownerEmail: "alice@example.com", accessLevel: "READ", @@ -154,35 +141,6 @@ describe("isDashboardWorkflow", () => { }); }); -describe("isDashboardProject", () => { - it("should return true for a realistic DashboardProject", () => { - expect(isDashboardProject(projectFixture)).toBe(true); - }); - - it("should return false for null and undefined", () => { - expect(isDashboardProject(null)).toBe(false); - expect(isDashboardProject(undefined)).toBe(false); - }); - - it("should return false for an object without a name field", () => { - expect(isDashboardProject({})).toBe(false); - }); - - it("should return false when name is not a string", () => { - expect(isDashboardProject({ name: 42 })).toBe(false); - }); - - it("should return false when a workflow field is also present", () => { - expect(isDashboardProject({ name: "x", workflow: workflowFixture.workflow })).toBe(false); - }); - - it("should return true when name is a string and workflow is null", () => { - // Intentional: a null workflow field is treated as "no workflow", so the - // exclusion branch `!value.workflow` still classifies the object as a project. - expect(isDashboardProject({ name: "x", workflow: null })).toBe(true); - }); -}); - describe("isDashboardFile", () => { it("should return true for a realistic DashboardFile", () => { expect(isDashboardFile(fileFixture)).toBe(true); @@ -269,7 +227,6 @@ describe("isDashboardWorkflowComputingUnit", () => { describe("type predicate cross-classification", () => { const fixtures: ReadonlyArray<[string, unknown, string]> = [ ["DashboardWorkflow fixture", workflowFixture, "isDashboardWorkflow"], - ["DashboardProject fixture", projectFixture, "isDashboardProject"], ["DashboardFile fixture", fileFixture, "isDashboardFile"], ["DashboardDataset fixture", datasetFixture, "isDashboardDataset"], ["DashboardWorkflowComputingUnit fixture", computingUnitFixture, "isDashboardWorkflowComputingUnit"], @@ -277,7 +234,6 @@ describe("type predicate cross-classification", () => { const predicates: ReadonlyArray<[string, (value: unknown) => boolean]> = [ ["isDashboardWorkflow", isDashboardWorkflow], - ["isDashboardProject", isDashboardProject], ["isDashboardFile", isDashboardFile], ["isDashboardDataset", isDashboardDataset], ["isDashboardWorkflowComputingUnit", isDashboardWorkflowComputingUnit], diff --git a/frontend/src/app/dashboard/type/type-predicates.ts b/frontend/src/app/dashboard/type/type-predicates.ts index 8c128b2c113..4ff5e2ad945 100644 --- a/frontend/src/app/dashboard/type/type-predicates.ts +++ b/frontend/src/app/dashboard/type/type-predicates.ts @@ -18,7 +18,6 @@ */ import { DashboardWorkflow } from "./dashboard-workflow.interface"; -import { DashboardProject } from "./dashboard-project.interface"; import { DashboardFile } from "./dashboard-file.interface"; import { DashboardDataset } from "./dashboard-dataset.interface"; import { DashboardWorkflowComputingUnit } from "../../common/type/workflow-computing-unit"; @@ -28,10 +27,6 @@ export function isDashboardWorkflow(value: any): value is DashboardWorkflow { return !!value && isNonNullObject(value.workflow); } -export function isDashboardProject(value: any): value is DashboardProject { - return !!value && typeof value.name === "string" && !value.workflow; -} - export function isDashboardFile(value: any): value is DashboardFile { return !!value && typeof value.ownerEmail === "string" && isNonNullObject(value.file); } diff --git a/frontend/src/app/hub/component/browse-section/browse-section.component.spec.ts b/frontend/src/app/hub/component/browse-section/browse-section.component.spec.ts index 0be21b86fe9..68f4e4539c0 100644 --- a/frontend/src/app/hub/component/browse-section/browse-section.component.spec.ts +++ b/frontend/src/app/hub/component/browse-section/browse-section.component.spec.ts @@ -95,7 +95,7 @@ describe("BrowseSectionComponent", () => { }); it("throws on an unexpected entity type", () => { - const bad = { id: 7, type: "project", accessibleUserIds: [] } as unknown as DashboardEntry; + const bad = { id: 7, type: "not-a-real-type", accessibleUserIds: [] } as unknown as DashboardEntry; expect(() => (component as any).initializeEntry(bad)).toThrowError("Unexpected type in DashboardEntry."); }); }); diff --git a/frontend/src/app/hub/component/hub-search-result/hub-search-result.component.spec.ts b/frontend/src/app/hub/component/hub-search-result/hub-search-result.component.spec.ts index 59aa576d101..ffa557904e9 100644 --- a/frontend/src/app/hub/component/hub-search-result/hub-search-result.component.spec.ts +++ b/frontend/src/app/hub/component/hub-search-result/hub-search-result.component.spec.ts @@ -195,7 +195,7 @@ describe("HubSearchResultComponent", () => { }); it("keeps the default 'workflow' searchType when the url matches neither branch", () => { - build("/dashboard/project"); + build("/dashboard/other"); expect(component.searchType).toBe("workflow"); expect(component.sortMethod).toBe(SortMethod.EditTimeDesc); }); @@ -329,31 +329,7 @@ describe("HubSearchResultComponent", () => { expect(results.reset).toHaveBeenCalledTimes(2); }); - it("passes projectIds=[pid] into the executeSearch loader when pid is set", async () => { - build("/dashboard/workflow"); - component.pid = 42; - const filters = makeFiltersMock(); - const results = makeSearchResultsMock(); - attachChildren(filters, results); - - await component.search(); - - const loader = results.reset.mock.calls[0][0] as (start: number, count: number) => Promise; - await loader(0, 20); - - expect(searchServiceMock.executeSearch).toHaveBeenCalledWith( - [""], - expect.objectContaining({ projectIds: [42] }), - 0, - 20, - "workflow", - SortMethod.EditTimeDesc, - false, - true - ); - }); - - it("does not inject projectIds when pid is undefined", async () => { + it("forwards the filter parameters into the executeSearch loader", async () => { build("/dashboard/workflow"); const filters = makeFiltersMock(); const results = makeSearchResultsMock(); diff --git a/frontend/src/app/hub/component/hub-search-result/hub-search-result.component.ts b/frontend/src/app/hub/component/hub-search-result/hub-search-result.component.ts index 15f576bb012..9fa474598ef 100644 --- a/frontend/src/app/hub/component/hub-search-result/hub-search-result.component.ts +++ b/frontend/src/app/hub/component/hub-search-result/hub-search-result.component.ts @@ -33,7 +33,6 @@ import { UntilDestroy, untilDestroyed } from "@ngneat/until-destroy"; import { SortMethod } from "../../../dashboard/type/sort-method"; import { UserService } from "../../../common/service/user/user.service"; import { SearchService } from "../../../dashboard/service/user/search.service"; -import { isDefined } from "../../../common/util/predicate"; import { firstValueFrom } from "rxjs"; import { map } from "rxjs/operators"; import { SortButtonComponent } from "../../../dashboard/component/user/sort-button/sort-button.component"; @@ -88,7 +87,6 @@ export class HubSearchResultComponent implements OnInit, AfterViewInit { } private masterFilterList: ReadonlyArray | null = null; - @Input() public pid?: number = undefined; @Input() public accessLevel?: string = undefined; public sortMethod = SortMethod.EditTimeDesc; lastSortMethod: SortMethod | null = null; @@ -147,10 +145,6 @@ export class HubSearchResultComponent implements OnInit, AfterViewInit { this.masterFilterList = this.filters.masterFilterList; this.searchKeywords = this.filters.getSearchKeywords(); let filterParams = this.filters.getSearchFilterParameters(); - if (isDefined(this.pid)) { - // force the project id in the search query to be the current pid. - filterParams.projectIds = [this.pid]; - } this.searchResultsComponent.reset((start, count) => { return firstValueFrom( diff --git a/frontend/src/app/hub/component/hub.component.spec.ts b/frontend/src/app/hub/component/hub.component.spec.ts index 5a883606394..61d85f60ac2 100644 --- a/frontend/src/app/hub/component/hub.component.spec.ts +++ b/frontend/src/app/hub/component/hub.component.spec.ts @@ -40,7 +40,6 @@ function makeSidebarTabs(overrides: Partial = {}): SidebarTabs { workflow_enabled: false, dataset_enabled: false, your_work_enabled: false, - projects_enabled: false, workflows_enabled: false, compute_enabled: false, datasets_enabled: false, diff --git a/frontend/src/app/hub/service/hub.service.ts b/frontend/src/app/hub/service/hub.service.ts index 4562ae9c8c8..46bd8763a0b 100644 --- a/frontend/src/app/hub/service/hub.service.ts +++ b/frontend/src/app/hub/service/hub.service.ts @@ -29,7 +29,6 @@ export const WORKFLOW_BASE_URL = `${AppSettings.getApiEndpoint()}/workflow`; export enum EntityType { Workflow = "workflow", Dataset = "dataset", - Project = "project", File = "file", ComputingUnit = "computing-unit", } diff --git a/frontend/src/app/workspace/component/menu/menu.component.ts b/frontend/src/app/workspace/component/menu/menu.component.ts index 8930d9d0bba..8f8dfd86eb8 100644 --- a/frontend/src/app/workspace/component/menu/menu.component.ts +++ b/frontend/src/app/workspace/component/menu/menu.component.ts @@ -33,17 +33,15 @@ import { WorkflowActionService } from "../../service/workflow-graph/model/workfl import { ExecutionState } from "../../types/execute-workflow.interface"; import { WorkflowWebsocketService } from "../../service/workflow-websocket/workflow-websocket.service"; import { WorkflowResultExportService } from "../../service/workflow-result-export/workflow-result-export.service"; -import { catchError, debounceTime, filter, mergeMap, switchMap, tap } from "rxjs/operators"; +import { catchError, debounceTime, switchMap, tap } from "rxjs/operators"; import { UntilDestroy, untilDestroyed } from "@ngneat/until-destroy"; import { WorkflowUtilService } from "../../service/workflow-graph/util/workflow-util.service"; import { WorkflowVersionService } from "../../../dashboard/service/user/workflow-version/workflow-version.service"; -import { UserProjectService } from "../../../dashboard/service/user/project/user-project.service"; import { saveAs } from "file-saver"; import { NotificationService } from "src/app/common/service/notification/notification.service"; import { OperatorMenuService } from "../../service/operator-menu/operator-menu.service"; import { CoeditorPresenceService } from "../../service/workflow-graph/model/coeditor-presence.service"; import { EMPTY, firstValueFrom, of, timer, map } from "rxjs"; -import { isDefined } from "../../../common/util/predicate"; import { NzModalService } from "ng-zorro-antd/modal"; import { ResultExportationComponent } from "../result-exportation/result-exportation.component"; import { ReportGenerationService } from "../../service/report-generation/report-generation.service"; @@ -149,7 +147,6 @@ export class MenuComponent implements OnInit, OnDestroy { protected readonly USER_WORKFLOW = USER_WORKFLOW; @Input() public writeAccess: boolean = false; - @Input() public pid?: number = undefined; @Input() public autoSaveState: string = ""; @Input() public currentWorkflowName: string = ""; // reset workflowName @Input() public currentExecutionName: string = ""; // reset executionName @@ -189,7 +186,6 @@ export class MenuComponent implements OnInit, OnDestroy { private datePipe: DatePipe, public workflowResultExportService: WorkflowResultExportService, public workflowUtilService: WorkflowUtilService, - private userProjectService: UserProjectService, private notificationService: NotificationService, public operatorMenu: OperatorMenuService, public coeditorPresenceService: CoeditorPresenceService, @@ -888,15 +884,12 @@ export class MenuComponent implements OnInit, OnDestroy { public persistWorkflow(): void { this.isSaving = true; - let localPid = this.pid; this.workflowPersistService .persistWorkflow(this.workflowActionService.getWorkflow()) .pipe( tap((updatedWorkflow: Workflow) => { this.workflowActionService.setWorkflowMetadata(updatedWorkflow); }), - filter(workflow => isDefined(localPid) && isDefined(workflow.wid)), - mergeMap(workflow => this.userProjectService.addWorkflowToProject(localPid!, workflow.wid!)), untilDestroyed(this) ) .subscribe({ diff --git a/frontend/src/app/workspace/component/workspace.component.html b/frontend/src/app/workspace/component/workspace.component.html index 78155285d4d..cbab3748b04 100644 --- a/frontend/src/app/workspace/component/workspace.component.html +++ b/frontend/src/app/workspace/component/workspace.component.html @@ -44,7 +44,6 @@ diff --git a/frontend/src/app/workspace/component/workspace.component.spec.ts b/frontend/src/app/workspace/component/workspace.component.spec.ts index d930af601fd..dbad94fe9b5 100644 --- a/frontend/src/app/workspace/component/workspace.component.spec.ts +++ b/frontend/src/app/workspace/component/workspace.component.spec.ts @@ -204,18 +204,6 @@ describe("WorkspaceComponent", () => { } describe("ngOnInit", () => { - it("parses numeric pid from route query params", async () => { - await createFixture(configureRoute({}, { pid: "13" })); - component.ngOnInit(); - expect(component.pid).toBe(13); - }); - - it("treats non-numeric pid as undefined", async () => { - await createFixture(configureRoute({}, { pid: "not-a-number" })); - component.ngOnInit(); - expect(component.pid).toBeUndefined(); - }); - it("enables highlighting on the workflow action service", async () => { await createFixture(); component.ngOnInit(); diff --git a/frontend/src/app/workspace/component/workspace.component.ts b/frontend/src/app/workspace/component/workspace.component.ts index bdc0cdb8a9b..359797a145b 100644 --- a/frontend/src/app/workspace/component/workspace.component.ts +++ b/frontend/src/app/workspace/component/workspace.component.ts @@ -90,7 +90,6 @@ export const SAVE_DEBOUNCE_TIME_IN_MS = 5000; ], }) export class WorkspaceComponent implements AfterViewInit, OnInit, OnDestroy { - public pid?: number = undefined; public writeAccess: boolean = false; public isLoading: boolean = false; // variable to track whether we are waiting for AI to finish generating (whether a loading icon should show) @@ -140,19 +139,6 @@ export class WorkspaceComponent implements AfterViewInit, OnInit, OnDestroy { ) {} ngOnInit() { - /** - * On initialization of the workspace, there are two possibilities regarding which component has - * routed to this component: - * - * 1. Routed to this component from within UserProjectSection component - * - track the pid identifying that project - * - upon persisting of a workflow, must also ensure it is also added to the project - * - * 2. Routed to this component from SavedWorkflowSection component - * - there is no related project, parseInt will return NaN. - * - NaN || undefined will result in undefined. - */ - this.pid = parseInt(this.route.snapshot.queryParams.pid) || undefined; this.workflowActionService.setHighlightingEnabled(true); // Clear session state when the user switches computing units in-canvas, so // the previous unit's status/console/results don't linger.