This is an automated email from the ASF dual-hosted git repository. github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/main/pr-8463-2e261e47dea226aaec304fb6edbd815d14cec93a in repository https://gitbox.apache.org/repos/asf/texera.git
commit f24dab0030bf4ee0620562ff0d186b3b04de7d51 Author: Xinyuan Lin <[email protected]> AuthorDate: Thu Sep 24 04:00:11 2026 +0000 chore(frontend): remove the unused UserWorkflowListItemComponent (#8463) ### What changes were proposed in this PR? Deletes `UserWorkflowListItemComponent`, which is declared in `AppModule` but whose selector `texera-user-workflow-list-item` appears in no template — so it is never rendered. `HighlightSearchTermsPipe` goes with it, because the component's own template is the pipe's last remaining consumer. So does the CSS that only that template ever matched. Pure deletion, no behaviour change: **−1,201 lines**. ### History | | | | --- | --- | | **Introduced by** | #1937 (2023-05-30) — "Refactored each workflow list item into its own component" | | **Usage removed by** | #2793 (2024-08-20) — "Generalize List Item" replaced it with the generic `ListItemComponent`, deleting the last two `texera-user-workflow-list-item` uses from `search-results.component.html` | Dead for about two years. > Reviewer note — why the pipe is in the same PR. `HighlightSearchTermsPipe` arrived in #2386 and kept a second consumer until #7463 (2026-08-29, "remove the deprecated project feature") dropped `entry.name | highlightSearchTerms` from that template. Since then its only use is this dead component's template (L43/L106). Removing the component alone would leave the pipe dead in the same commit, so they ship together; splitting them would just create a second PR for code this one already orphans. > Reviewer note — the CSS. The component's stylesheet `@use`d `section-style.scss`, and its template was the only one producing `.workflow-list-item` (plus every class nested under it) and `.metadata-container`. Those rules go, along with a second `.metadata-container` copy in `search-results.component.scss` that no template ever matched. The pipe was the only producer of the global `.highlight-search-terms` rule in `styles.scss`, so that goes too. The `itemSize` comment in `search-results.component.html` cited `.workflow-list-item`, and now cites `.list-item-card`, the rule that sizes each row today. > Nothing else needs cleaning: `WorkflowExecutionHistoryComponent`, `ShareAccessComponent`, `DownloadService`, `GuiConfigService` and `WorkflowPersistService` all keep other consumers. ### Any related issues, documentation, discussions? Closes #8460 ### How was this PR tested? Existing tests only — this PR removes a component, a pipe, their two specs, and CSS that no remaining template matches. From `frontend/`: - `npx ng build` — **success**. This is the gate that matters here: removing an `AppModule` declaration can only be proven safe by an AOT build, since template diagnostics are AOT-only. A leftover use of either selector, or an `@extend` of a removed CSS rule, would fail it. - `npx ng test --watch=false --include='**/search-results.component.spec.ts' --include='**/user-workflow.component.spec.ts'` — 107 tests pass (2 files). - `yarn --cwd frontend format:ci` — clean. Verification, re-runnable by a reviewer (all three return nothing): ``` git grep -n "texera-user-workflow-list-item\|UserWorkflowListItemComponent" git grep -n "highlightSearchTerms\|HighlightSearchTermsPipe" git grep -n "workflow-list-item\|metadata-container\|highlight-search-terms" ``` ### Was this PR authored or co-authored using generative AI tooling? Generated-by: Claude Code (Claude Opus 5) --- frontend/src/app/app.module.ts | 4 - .../src/app/dashboard/component/section-style.scss | 84 ---- .../search-results/search-results.component.html | 4 +- .../search-results/search-results.component.scss | 6 - .../highlight-search-terms.pipe.spec.ts | 67 --- .../highlight-search-terms.pipe.ts | 40 -- .../user-workflow-list-item.component.html | 210 -------- .../user-workflow-list-item.component.scss | 21 - .../user-workflow-list-item.component.spec.ts | 559 --------------------- .../user-workflow-list-item.component.ts | 201 -------- .../user-workflow/user-workflow.component.spec.ts | 2 - frontend/src/styles.scss | 5 - 12 files changed, 2 insertions(+), 1201 deletions(-) diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index 6c96ab6ade..ca45b08289 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -120,7 +120,6 @@ import { MarkdownModule } from "ngx-markdown"; import { FileSaverService } from "./dashboard/service/user/file/file-saver.service"; 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 { 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"; @@ -139,7 +138,6 @@ import { TimeTravelComponent } from "./workspace/component/left-panel/time-trave import { NzModalModule } from "ng-zorro-antd/modal"; import { NzDescriptionsModule } from "ng-zorro-antd/descriptions"; import { OverlayModule } from "@angular/cdk/overlay"; -import { HighlightSearchTermsPipe } from "./dashboard/component/user/user-workflow/user-workflow-list-item/highlight-search-terms.pipe"; import { en_US, provideNzI18n } from "ng-zorro-antd/i18n"; import { FilesUploaderComponent } from "./dashboard/component/user/files-uploader/files-uploader.component"; import { ConflictingFileModalContentComponent } from "./dashboard/component/user/files-uploader/conflicting-file-modal-content/conflicting-file-modal-content.component"; @@ -331,7 +329,6 @@ registerLocaleData(en); CollabWrapperComponent, AboutComponent, TexeraLoginComponent, - UserWorkflowListItemComponent, SortButtonComponent, FiltersComponent, FiltersInstructionsComponent, @@ -339,7 +336,6 @@ registerLocaleData(en); PortPropertyEditFrameComponent, WorkflowRuntimeStatisticsComponent, FlarumComponent, - HighlightSearchTermsPipe, SearchBarComponent, ListItemComponent, CardItemComponent, diff --git a/frontend/src/app/dashboard/component/section-style.scss b/frontend/src/app/dashboard/component/section-style.scss index 784815fb4a..446187f502 100644 --- a/frontend/src/app/dashboard/component/section-style.scss +++ b/frontend/src/app/dashboard/component/section-style.scss @@ -94,7 +94,6 @@ $dashboard-navigation-height: 76px; /** * the third grid of the workflow/files list. - * including css shared by workflow/file item **/ .section-list-container { grid-row-start: 3; @@ -119,83 +118,6 @@ $dashboard-navigation-height: 76px; border: 1px solid #e8e8e8; border-radius: 4px; } - - .metadata-container { - span { - margin: 0 1rem 0 0; - } - } -} - -/** - * css specifically for each workflow item in the list (not shared by file items) - **/ -.workflow-list-item { - margin-bottom: 10px; - min-height: 70px; - padding: 5px 0 5px 0; - - .workflow-item-checkbox { - margin: 8px; - } - - .workflow-item-meta-title { - display: flex; - align-items: center; - - .workflow-name { - font-size: 20px; - font-family: -apple-system, BlinkMacSystemFont, sans-serif; - text-align: center; - margin-bottom: 0; - color: inherit; - text-decoration: none; - } - - .workflow-name:hover { - cursor: pointer; - } - - i { - position: relative; - font-size: 17px; - } - - i.workflow-is-owner-icon { - margin-left: 7px; - } - } - - .workflow-item-meta-description { - display: flex; - align-items: center; - padding: 2px 8px 2px 10px; - margin-bottom: 5px; - - .workflow-description { - font-size: 13px; - font-family: -apple-system, BlinkMacSystemFont, sans-serif; - - display: inline-block; - min-width: 17ch; - border: 0 none; - outline: none; - - &:hover { - cursor: pointer; - box-shadow: 0 0 0 1px rgb(202, 202, 202); - } - } - - .workflow-editable-description { - margin-bottom: 5px; - display: inline-block; - min-width: 17ch; - border: 0 none; - outline: none; - box-shadow: 0 0 0 2px #007bff; - } - } } // Shared look of one resource row (the computing-unit and warehouse list @@ -241,9 +163,3 @@ $dashboard-navigation-height: 76px; margin-left: 5px; margin-right: 5px; } - -.metadata-container { - span { - margin: 0 1rem 0 0; // add space to the right - } -} diff --git a/frontend/src/app/dashboard/component/user/search-results/search-results.component.html b/frontend/src/app/dashboard/component/user/search-results/search-results.component.html index 084ccb0d9f..13ff93847e 100644 --- a/frontend/src/app/dashboard/component/user/search-results/search-results.component.html +++ b/frontend/src/app/dashboard/component/user/search-results/search-results.component.html @@ -23,8 +23,8 @@ <!-- LIST VIEW --> <ng-container *ngIf="viewMode === 'list'"> <!-- itemSize: the height (px) of each list item, - this MUST be approximately the same as list item size set in CSS, - .workflow-list-item sets the item size to be 70px, with additional paddings/margins it's approximately 80px + this MUST be approximately the same as the list item height set in CSS + (.list-item-card in list-item.component.scss) --> <cdk-virtual-scroll-viewport itemSize="70" diff --git a/frontend/src/app/dashboard/component/user/search-results/search-results.component.scss b/frontend/src/app/dashboard/component/user/search-results/search-results.component.scss index 32f4de9513..a2c66114a9 100644 --- a/frontend/src/app/dashboard/component/user/search-results/search-results.component.scss +++ b/frontend/src/app/dashboard/component/user/search-results/search-results.component.scss @@ -125,12 +125,6 @@ nz-content { margin-right: 5px; } -.metadata-container { - span { - margin: 0 1rem 0 0; // add space to the right - } -} - .card-scroll-container { height: 100%; overflow-y: auto; diff --git a/frontend/src/app/dashboard/component/user/user-workflow/user-workflow-list-item/highlight-search-terms.pipe.spec.ts b/frontend/src/app/dashboard/component/user/user-workflow/user-workflow-list-item/highlight-search-terms.pipe.spec.ts deleted file mode 100644 index 653cab0b54..0000000000 --- a/frontend/src/app/dashboard/component/user/user-workflow/user-workflow-list-item/highlight-search-terms.pipe.spec.ts +++ /dev/null @@ -1,67 +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 { TestBed } from "@angular/core/testing"; -import { SafeHtml } from "@angular/platform-browser"; - -import { HighlightSearchTermsPipe } from "./highlight-search-terms.pipe"; - -describe("HighlightSearchTermsPipe", () => { - let pipe: HighlightSearchTermsPipe; - - // Recover the raw HTML string that was wrapped by bypassSecurityTrustHtml. - const raw = (safe: SafeHtml): string => (safe as any).changingThisBreaksApplicationSecurity; - - beforeEach(() => { - TestBed.configureTestingModule({ providers: [HighlightSearchTermsPipe] }); - pipe = TestBed.inject(HighlightSearchTermsPipe); - }); - - it("returns an empty string when the value is undefined", () => { - expect(raw(pipe.transform(undefined, ["x"]))).toBe(""); - }); - - it("returns the value unchanged when there are no terms to highlight", () => { - expect(raw(pipe.transform("hello", []))).toBe("hello"); - }); - - it("wraps a single matching token in a highlight span", () => { - expect(raw(pipe.transform("hello world", ["world"]))).toBe( - 'hello <span class="highlight-search-terms">world</span>' - ); - }); - - it("matches case-insensitively and preserves the original casing inside the span", () => { - expect(raw(pipe.transform("Hello", ["hello"]))).toBe('<span class="highlight-search-terms">Hello</span>'); - }); - - it("wraps every occurrence of a token (global flag)", () => { - expect(raw(pipe.transform("a a a", ["a"]))).toBe( - '<span class="highlight-search-terms">a</span> ' + - '<span class="highlight-search-terms">a</span> ' + - '<span class="highlight-search-terms">a</span>' - ); - }); - - it("wraps each token independently when multiple terms are given", () => { - expect(raw(pipe.transform("hello world", ["hello", "world"]))).toBe( - '<span class="highlight-search-terms">hello</span> ' + '<span class="highlight-search-terms">world</span>' - ); - }); -}); diff --git a/frontend/src/app/dashboard/component/user/user-workflow/user-workflow-list-item/highlight-search-terms.pipe.ts b/frontend/src/app/dashboard/component/user/user-workflow/user-workflow-list-item/highlight-search-terms.pipe.ts deleted file mode 100644 index 44953b1f8a..0000000000 --- a/frontend/src/app/dashboard/component/user/user-workflow/user-workflow-list-item/highlight-search-terms.pipe.ts +++ /dev/null @@ -1,40 +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 { Pipe, PipeTransform } from "@angular/core"; -import { DomSanitizer, SafeHtml } from "@angular/platform-browser"; - -@Pipe({ name: "highlightSearchTerms" }) -export class HighlightSearchTermsPipe implements PipeTransform { - constructor(private sanitizer: DomSanitizer) {} - - transform(value: string | undefined, terms: string[]): SafeHtml { - if (!terms || !terms.length || !value) { - // Return the original value if there's nothing to highlight or if the value is undefined - return this.sanitizer.bypassSecurityTrustHtml(value || ""); - } - - // Escape the terms to be used in a RegExp - const regex = new RegExp(`(${terms.join("|")})`, "gi"); - - const highlightedString = value.replace(regex, '<span class="highlight-search-terms">$1</span>'); - // Use the sanitizer to avoid security risks - return this.sanitizer.bypassSecurityTrustHtml(highlightedString); - } -} diff --git a/frontend/src/app/dashboard/component/user/user-workflow/user-workflow-list-item/user-workflow-list-item.component.html b/frontend/src/app/dashboard/component/user/user-workflow/user-workflow-list-item/user-workflow-list-item.component.html deleted file mode 100644 index 273cb55d3a..0000000000 --- a/frontend/src/app/dashboard/component/user/user-workflow/user-workflow-list-item/user-workflow-list-item.component.html +++ /dev/null @@ -1,210 +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. ---> - -<nz-list-item class="workflow-list-item"> - <nz-list-item-meta> - <!-- Margin need only when the checkbox is not visible --> - <nz-list-item-meta-avatar [ngStyle]="{ 'margin-left': editable ? 'none' : '16px' }"> - <label - *ngIf="editable" - class="workflow-item-checkbox" - id="{{workflow.wid}}" - ngDefaultControl - [(ngModel)]="entry.checked"></label> - <nz-avatar - [ngStyle]="{ 'background-color': 'grey', 'vertical-align': 'middle' }" - [nzGap]="4" - [nzText]="'' + workflow.wid" - nzSize="default"></nz-avatar> - </nz-list-item-meta-avatar> - - <!-- editable name of saved workflow --> - <nz-list-item-meta-title class="meta-title-container"> - <div class="workflow-item-meta-title"> - <div - *ngIf="!editingName; else customWorkflowTitle " - [routerLink]="USER_WORKSPACE + '/' + workflow.wid" - [innerHTML]="workflow.name | highlightSearchTerms : keywords" - class="workflow-name"></div> - <ng-template #customWorkflowTitle> - <input - #customName - (focusout)="confirmUpdateWorkflowCustomName(customName.value)" - (keyup.enter)="confirmUpdateWorkflowCustomName(customName.value)" - placeholder="{{ workflow.name }}" - value="{{ workflow.name }}" /> - </ng-template> - <button - *ngIf="editable" - (click)="editingName = true" - nz-button - nz-tooltip="Customize Workflow Name" - nzSize="small" - nzTooltipPlacement="bottom" - nzType="text"> - <i - nz-icon - nzTheme="outline" - nzType="edit"></i> - </button> - <button - *ngIf="editable" - (click)="editingDescription = true" - nz-button - nz-tooltip="Add Description" - nzSize="small" - nzTooltipPlacement="bottom" - nzType="text" - class="add-description-btn"> - <i - nz-icon - nzTheme="outline" - nzType="plus-square"></i> - </button> - <i - class="workflow-is-owner-icon" - *ngIf="entry.workflow.isOwner" - nz-tooltip="You are the owner" - nzTooltipPlacement="bottom" - nz-icon - nzTheme="outline" - nzType="user"></i> - <i - *ngIf="!entry.workflow.isOwner" - nz-tooltip="{{ - entry.workflow.accessLevel - }} access shared by {{ entry.workflow.ownerName }}" - nzTooltipPlacement="bottom" - nz-icon - nzTheme="outline" - nzType="team"></i> - </div> - </nz-list-item-meta-title> - - <!-- editable description of saved workflow --> - <nz-list-item-meta-description> - <div class="workflow-item-meta-description"> - <label - *ngIf="!editingDescription; else customWorkflowDescription " - (click)="editingDescription = editable" - [innerHTML]="workflow.description | highlightSearchTerms : keywords" - class="workflow-description"> - </label> - <ng-template #customWorkflowDescription> - <input - title="Description" - #customDescription - (focusout)="confirmUpdateWorkflowCustomDescription(customDescription.value)" - (keyup.enter)="confirmUpdateWorkflowCustomDescription(customDescription.value)" - class="workflow-editable-description" - value="{{ workflow.description }}" - maxlength="500" /> - </ng-template> - </div> - </nz-list-item-meta-description> - - <!-- last access and created date of saved workflow --> - <nz-list-item-meta-description class="metadata-container"> - <span>Last Modified: {{ workflow.lastModifiedTime | date: "yyyy-MM-dd HH:mm" }}</span> - <span>Created: {{ workflow.creationTime | date: "yyyy-MM-dd HH:mm" }}</span> - </nz-list-item-meta-description> - </nz-list-item-meta> - - <ul nz-list-item-actions> - <nz-list-item-action> - <button - (click)="onClickOpenShareAccess()" - nz-button - nz-tooltip="Share the workflow {{ - workflow.name - }} to others" - nzTooltipPlacement="bottom" - type="button"> - <i - nz-icon - nzTheme="outline" - nzType="share-alt"></i> - </button> - </nz-list-item-action> - <nz-list-item-action *ngIf="editable"> - <button - (click)="duplicated.emit()" - class="duplicate-workflow-btn" - nz-button - nz-tooltip="Duplicate the workflow {{ - workflow.name - }}" - nzTooltipPlacement="bottom" - type="button"> - <i - nz-icon - nzTheme="outline" - nzType="copy"></i> - </button> - </nz-list-item-action> - <nz-list-item-action> - <button - (click)="onClickDownloadWorkfllow()" - nz-button - nz-tooltip="Download the workflow {{ - workflow.name - }}" - nzTooltipPlacement="bottom" - type="button"> - <i - nz-icon - nzType="cloud-download" - nzTheme="outline"></i> - </button> - </nz-list-item-action> - <nz-list-item-action *ngIf="editable"> - <button - (nzOnConfirm)="deleted.emit()" - nz-popconfirm - nzPopconfirmTitle="Confirm to delete this workflow." - [disabled]="!entry.workflow.isOwner" - class="delete-workflow-btn" - nz-button - nz-tooltip="Delete the workflow {{ - workflow.name - }}" - nzTooltipPlacement="bottom"> - <i - nz-icon - nzTheme="outline" - nzType="delete"></i> - </button> - </nz-list-item-action> - <nz-list-item-action *ngIf="this.config.env.workflowExecutionsTrackingEnabled"> - <button - (click)="onClickGetWorkflowExecutions()" - nz-button - nz-tooltip="Executions of the workflow {{ - workflow.name - }}" - nzTooltipPlacement="bottom" - type="button"> - <i - nz-icon - nzTheme="outline" - nzType="history"></i> - </button> - </nz-list-item-action> - </ul> -</nz-list-item> diff --git a/frontend/src/app/dashboard/component/user/user-workflow/user-workflow-list-item/user-workflow-list-item.component.scss b/frontend/src/app/dashboard/component/user/user-workflow/user-workflow-list-item/user-workflow-list-item.component.scss deleted file mode 100644 index 630cd34d91..0000000000 --- a/frontend/src/app/dashboard/component/user/user-workflow/user-workflow-list-item/user-workflow-list-item.component.scss +++ /dev/null @@ -1,21 +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. - */ - -@use "../../../section-style" as *; -@use "../../../dashboard.component.scss" as *; diff --git a/frontend/src/app/dashboard/component/user/user-workflow/user-workflow-list-item/user-workflow-list-item.component.spec.ts b/frontend/src/app/dashboard/component/user/user-workflow/user-workflow-list-item/user-workflow-list-item.component.spec.ts deleted file mode 100644 index ad3d296445..0000000000 --- a/frontend/src/app/dashboard/component/user/user-workflow/user-workflow-list-item/user-workflow-list-item.component.spec.ts +++ /dev/null @@ -1,559 +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 { Component, ViewChild } from "@angular/core"; -import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; -import { UserWorkflowListItemComponent } from "./user-workflow-list-item.component"; -import { FileSaverService } from "../../../../service/user/file/file-saver.service"; -import { testWorkflow1, testWorkflowEntries } from "../../../user-dashboard-test-fixtures"; -import { By } from "@angular/platform-browser"; -import { StubWorkflowPersistService } from "../../../../../common/service/workflow-persist/stub-workflow-persist.service"; -import { - DEFAULT_WORKFLOW_NAME, - WorkflowPersistService, -} from "../../../../../common/service/workflow-persist/workflow-persist.service"; -import { DownloadService } from "../../../../service/user/download/download.service"; -import { WorkflowExecutionHistoryComponent } from "../ngbd-modal-workflow-executions/workflow-execution-history.component"; -import { ShareAccessComponent } from "../../share-access/share-access.component"; -import { Workflow } from "../../../../../common/type/workflow"; -import { of } from "rxjs"; -import { NzListComponent } from "ng-zorro-antd/list"; -import { NzModalModule, NzModalService } from "ng-zorro-antd/modal"; -import { HttpClientTestingModule } from "@angular/common/http/testing"; -import { provideRouter } from "@angular/router"; -import { DashboardEntry } from "../../../../type/dashboard-entry"; -import { NzTooltipDirective, NzTooltipModule } from "ng-zorro-antd/tooltip"; -import { commonTestProviders } from "../../../../../common/testing/test-utils"; -import type { Mocked } from "vitest"; -import { GuiConfigService } from "../../../../../common/service/gui-config.service"; -import { MockGuiConfigService } from "../../../../../common/service/gui-config.service.mock"; - -// UserWorkflowListItemComponent is rooted at <nz-list-item>; instantiating it -// outside an <nz-list> host throws "No provider found for NzListComponent". -@Component({ - standalone: true, - imports: [NzListComponent, UserWorkflowListItemComponent], - template: ` - <nz-list> - <texera-user-workflow-list-item - [entry]="entry" - [editable]="editable"></texera-user-workflow-list-item> - </nz-list> - `, -}) -class TestHostComponent { - entry!: DashboardEntry; - editable = true; - @ViewChild(UserWorkflowListItemComponent, { static: true }) inner!: UserWorkflowListItemComponent; -} - -// A fresh DashboardEntry per call so methods that mutate the workflow (rename) -// cannot leak into the shared testWorkflowEntries fixture. -function makeWorkflowEntry(workflowOverrides: Partial<Workflow> = {}): DashboardEntry { - return new DashboardEntry({ - workflow: { ...testWorkflow1, ...workflowOverrides }, - isOwner: true, - ownerName: "Texera", - accessLevel: "Write", - ownerId: 1, - coverImage: null, - }); -} - -describe("UserWorkflowListItemComponent", () => { - let component: UserWorkflowListItemComponent; - let fixture: ComponentFixture<TestHostComponent>; - const fileSaverServiceSpy = { saveAs: vi.fn() } as unknown as Mocked<FileSaverService>; - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [TestHostComponent, NzModalModule, HttpClientTestingModule, NzTooltipModule], - providers: [ - { provide: WorkflowPersistService, useValue: new StubWorkflowPersistService(testWorkflowEntries) }, - { provide: FileSaverService, useValue: fileSaverServiceSpy }, - provideRouter([]), - ...commonTestProviders, - ], - }).compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(TestHostComponent); - fixture.componentInstance.entry = testWorkflowEntries[0]; - fixture.componentInstance.editable = true; - fixture.detectChanges(); - component = fixture.componentInstance.inner; - }); - - it("should create", () => { - expect(component).toBeTruthy(); - }); - - it("sends http request to backend to retrieve export json", () => { - // Test the workflow download button. - component.onClickDownloadWorkfllow(); - expect(fileSaverServiceSpy.saveAs).toHaveBeenCalledExactlyOnceWith( - new Blob([JSON.stringify(testWorkflowEntries[0].workflow.workflow.content)], { - type: "text/plain;charset=utf-8", - }), - "workflow 1.json" - ); - }); - - it("adding a workflow description adds a description to the workflow", waitForAsync(() => { - fixture.whenStable().then(() => { - let addWorkflowDescriptionBtn = fixture.debugElement.query(By.css(".add-description-btn")); - expect(addWorkflowDescriptionBtn).toBeTruthy(); - addWorkflowDescriptionBtn.triggerEventHandler("click", null); - fixture.detectChanges(); - let editableDescriptionInput = fixture.debugElement.nativeElement.querySelector(".workflow-editable-description"); - expect(editableDescriptionInput).toBeTruthy(); - vi.spyOn(component, "confirmUpdateWorkflowCustomDescription"); - sendInput(editableDescriptionInput, "dummy description added by focusing out the input element.").then(() => { - fixture.detectChanges(); - editableDescriptionInput.dispatchEvent(new Event("focusout")); - fixture.detectChanges(); - expect(component.confirmUpdateWorkflowCustomDescription).toHaveBeenCalledTimes(1); - }); - }); - })); - - it("Editing a workflow description edits a description to the workflow", waitForAsync(() => { - fixture.whenStable().then(() => { - const workflowDescriptionLabel = fixture.debugElement.query(By.css(".workflow-description")); - expect(workflowDescriptionLabel).toBeTruthy(); - workflowDescriptionLabel.triggerEventHandler("click", null); - fixture.detectChanges(); - let editableDescriptionInput1 = fixture.debugElement.nativeElement.querySelector( - ".workflow-editable-description" - ); - expect(editableDescriptionInput1).toBeTruthy(); - vi.spyOn(component, "confirmUpdateWorkflowCustomDescription"); - sendInput(editableDescriptionInput1, "dummy description added by focusing out the input element.").then(() => { - fixture.detectChanges(); - editableDescriptionInput1.dispatchEvent(new Event("focusout")); - fixture.detectChanges(); - expect(component.confirmUpdateWorkflowCustomDescription).toHaveBeenCalledTimes(1); - }); - }); - })); - - describe("method coverage", () => { - afterEach(() => { - vi.restoreAllMocks(); - }); - - describe("confirmUpdateWorkflowCustomName", () => { - it("persists the new name, updates the workflow, and stops editing", () => { - const persist = TestBed.inject(WorkflowPersistService); - // The stub lacks updateWorkflowName; give this fresh-per-test instance a spy. - const spy = ((persist as any).updateWorkflowName = vi.fn().mockReturnValue(of(undefined))); - component.entry = makeWorkflowEntry({ wid: 5, name: "old" }); - component.editingName = true; - - component.confirmUpdateWorkflowCustomName("new name"); - - expect(spy).toHaveBeenCalledWith(5, "new name"); - expect(component.workflow.name).toBe("new name"); - expect(component.editingName).toBe(false); - }); - - it("falls back to the default name when the input is empty", () => { - const persist = TestBed.inject(WorkflowPersistService); - const spy = ((persist as any).updateWorkflowName = vi.fn().mockReturnValue(of(undefined))); - component.entry = makeWorkflowEntry({ wid: 5 }); - - component.confirmUpdateWorkflowCustomName(""); - - expect(spy).toHaveBeenCalledWith(5, DEFAULT_WORKFLOW_NAME); - expect(component.workflow.name).toBe(DEFAULT_WORKFLOW_NAME); - }); - - it("is a no-op when the workflow has no id", () => { - const persist = TestBed.inject(WorkflowPersistService); - const spy = ((persist as any).updateWorkflowName = vi.fn()); - component.entry = makeWorkflowEntry({ wid: undefined }); - - component.confirmUpdateWorkflowCustomName("x"); - - expect(spy).not.toHaveBeenCalled(); - }); - }); - - describe("confirmUpdateWorkflowCustomDescription", () => { - it("persists the new description and stops editing", () => { - const persist = TestBed.inject(WorkflowPersistService); - const spy = ((persist as any).updateWorkflowDescription = vi.fn().mockReturnValue(of(undefined))); - component.entry = makeWorkflowEntry({ wid: 5 }); - component.editingDescription = true; - - component.confirmUpdateWorkflowCustomDescription("new desc"); - - expect(spy).toHaveBeenCalledWith(5, "new desc"); - expect(component.workflow.description).toBe("new desc"); - expect(component.editingDescription).toBe(false); - }); - - it("is a no-op when the workflow has no id", () => { - const persist = TestBed.inject(WorkflowPersistService); - const spy = ((persist as any).updateWorkflowDescription = vi.fn()); - component.entry = makeWorkflowEntry({ wid: undefined }); - - component.confirmUpdateWorkflowCustomDescription("x"); - - expect(spy).not.toHaveBeenCalled(); - }); - }); - - it("onClickGetWorkflowExecutions opens the execution-history modal for the workflow", () => { - const modal = TestBed.inject(NzModalService); - const spy = vi.spyOn(modal, "create").mockReturnValue({} as any); - component.entry = makeWorkflowEntry({ wid: 9, name: "wf" }); - - component.onClickGetWorkflowExecutions(); - - expect(spy).toHaveBeenCalledWith( - expect.objectContaining({ - nzContent: WorkflowExecutionHistoryComponent, - nzData: { wid: 9 }, - nzTitle: "Execution results of Workflow: wf", - }) - ); - }); - - describe("onClickDownloadWorkfllow", () => { - it("delegates to the download service with the workflow id and name", () => { - const download = TestBed.inject(DownloadService); - const spy = vi.spyOn(download, "downloadWorkflow").mockReturnValue(of(undefined) as any); - component.entry = makeWorkflowEntry({ wid: 9, name: "wf" }); - - component.onClickDownloadWorkfllow(); - - expect(spy).toHaveBeenCalledWith(9, "wf"); - }); - - it("does nothing when the workflow has no id", () => { - const download = TestBed.inject(DownloadService); - const spy = vi.spyOn(download, "downloadWorkflow"); - component.entry = makeWorkflowEntry({ wid: undefined }); - - component.onClickDownloadWorkfllow(); - - expect(spy).not.toHaveBeenCalled(); - }); - }); - - it("opens the share modal for this row's workflow, carrying its write access and the owner list", async () => { - const entry = makeWorkflowEntry({ wid: 21, name: "wf" }); - entry.workflow.accessLevel = "WRITE"; - component.entry = entry; - const persist = TestBed.inject(WorkflowPersistService); - // The owner list is the autocomplete the dialog exists to offer; it is the one awaited - // value in the method, so nothing else proves it is resolved before the modal opens. - vi.spyOn(persist, "retrieveOwners").mockReturnValue(of(["[email protected]", "[email protected]"])); - const modal = TestBed.inject(NzModalService); - const spy = vi.spyOn(modal, "create").mockReturnValue({} as any); - - await component.onClickOpenShareAccess(); - - expect(spy).toHaveBeenCalledWith( - expect.objectContaining({ - nzContent: ShareAccessComponent, - nzData: expect.objectContaining({ - writeAccess: true, - type: "workflow", - id: 21, - allOwners: ["[email protected]", "[email protected]"], - }), - }) - ); - }); - - it("marks the share modal read-only for a row the viewer can only read", async () => { - const entry = makeWorkflowEntry({ wid: 21 }); - entry.workflow.accessLevel = "READ"; - component.entry = entry; - const modal = TestBed.inject(NzModalService); - const spy = vi.spyOn(modal, "create").mockReturnValue({} as any); - - await component.onClickOpenShareAccess(); - - expect(spy).toHaveBeenCalledWith( - expect.objectContaining({ nzData: expect.objectContaining({ writeAccess: false }) }) - ); - }); - - describe("mis-wired inputs", () => { - // Both accessors are read from the template on every change-detection pass, so a silent - // undefined would surface as an unrelated crash deep in ng-zorro instead of here. - it("refuses to read the entry before one has been provided", () => { - component.entry = undefined as any; - - expect(() => component.entry).toThrowError("entry property must be provided to UserWorkflowListItemComponent."); - }); - - it("refuses to read a workflow off an entry that carries no workflow payload", () => { - // The guard tests for the payload, not for entry.type: an entry of any kind that does - // carry a workflow passes it, so this must not claim to be a kind check. - component.entry = { name: "ds" } as unknown as DashboardEntry; - - expect(() => component.workflow).toThrowError(/Entry must be workflow/); - }); - }); - }); - - function sendInput(editableDescriptionInput: HTMLInputElement, text: string) { - // Helper function to change the workflow description textbox. - editableDescriptionInput.value = text; - editableDescriptionInput.dispatchEvent(new Event("input")); - fixture.detectChanges(); - return fixture.whenStable(); - } -}); -/** - * The list item's template carries decisions the class does not: which control a click reaches, what - * value an edit forwards, whether a read-only row may still be edited inline, and which actions the - * row offers. The suite above calls the component's methods directly, so none of that was rendered. - */ -describe("UserWorkflowListItemComponent rendering", () => { - let fixture: ComponentFixture<TestHostComponent>; - let component: UserWorkflowListItemComponent; - let persistService: { updateWorkflowName: ReturnType<typeof vi.fn> }; - - async function setup(opts: { executionsTracking?: boolean } = {}) { - // StubWorkflowPersistService does not declare updateWorkflowName, so it cannot be spied on. - persistService = { updateWorkflowName: vi.fn(() => of({} as Response)) }; - TestBed.resetTestingModule(); - await TestBed.configureTestingModule({ - imports: [TestHostComponent, NzModalModule, HttpClientTestingModule, NzTooltipModule], - providers: [ - { provide: WorkflowPersistService, useValue: persistService }, - { provide: FileSaverService, useValue: { saveAs: vi.fn() } }, - provideRouter([]), - ...commonTestProviders, - ], - }).compileComponents(); - if (opts.executionsTracking) { - (TestBed.inject(GuiConfigService) as unknown as MockGuiConfigService).setConfig({ - workflowExecutionsTrackingEnabled: true, - }); - } - } - - /** Renders one row. */ - function render(entry: DashboardEntry, editable = true): HTMLElement { - fixture = TestBed.createComponent(TestHostComponent); - fixture.componentInstance.entry = entry; - fixture.componentInstance.editable = editable; - fixture.detectChanges(); - component = fixture.componentInstance.inner; - return fixture.nativeElement as HTMLElement; - } - - /** - * Tooltip titles on the row, read off the directive: ng-zorro consumes the nz-tooltip attribute, - * and an interpolated title is a property binding that never appears in the DOM at all. - */ - function tooltipTitles(): unknown[] { - return fixture.debugElement - .queryAll(By.directive(NzTooltipDirective)) - .map(d => (d.injector.get(NzTooltipDirective) as NzTooltipDirective).directiveTitle); - } - - /** The elements whose tooltip title satisfies the predicate, in document order. */ - function byTooltip(pred: (title: string) => boolean): HTMLElement[] { - return fixture.debugElement - .queryAll(By.directive(NzTooltipDirective)) - .filter(d => { - const t = (d.injector.get(NzTooltipDirective) as NzTooltipDirective).directiveTitle; - return typeof t === "string" && pred(t); - }) - .map(d => d.nativeElement as HTMLElement); - } - - /** - * The one element whose tooltip title satisfies the predicate. Indexing byTooltip() directly - * reports a missing or duplicated action as a TypeError on `undefined.click()` — or, worse, - * silently clicks the first of several; asserting the match is unique names the real problem. - */ - function onlyByTooltip(pred: (title: string) => boolean): HTMLElement { - const matches = byTooltip(pred); - expect(matches).toHaveLength(1); - return matches[0]; - } - - beforeEach(async () => { - await setup(); - }); - - describe("renaming", () => { - it("forwards the text that was typed, not the name it started with", () => { - // The input is seeded with the current name, so binding workflow.name instead of the input's - // value would still look right on screen while silently discarding every rename. - render(makeWorkflowEntry({ wid: 7, name: "before" })); - - component.editingName = true; - fixture.detectChanges(); - - const input = (fixture.nativeElement as HTMLElement).querySelector<HTMLInputElement>("input")!; - expect(input.value).toBe("before"); - input.value = "after"; - input.dispatchEvent(new Event("focusout")); - - expect(persistService.updateWorkflowName).toHaveBeenCalledWith(7, "after"); - }); - }); - - describe("inline description editing", () => { - it("opens for a viewer who may edit", () => { - const el = render(makeWorkflowEntry(), true); - - el.querySelector<HTMLElement>(".workflow-description")?.click(); - fixture.detectChanges(); - - expect(component.editingDescription).toBe(true); - }); - - it("stays shut for a read-only viewer", () => { - // The gate is the template expression editingDescription = editable; without it a shared - // read-only row opens an editor whose save the backend then rejects. - const el = render(makeWorkflowEntry(), false); - - el.querySelector<HTMLElement>(".workflow-description")?.click(); - fixture.detectChanges(); - - expect(component.editingDescription).toBe(false); - }); - }); - - describe("ownership", () => { - it("tells a non-owner what access they have and who shared it", () => { - const entry = makeWorkflowEntry(); - entry.workflow.isOwner = false; - entry.workflow.accessLevel = "READ"; - entry.workflow.ownerName = "Bob"; - render(entry); - - // Pins the order of the two interpolations. - expect(tooltipTitles()).toContain("READ access shared by Bob"); - }); - - it("shows no shared-access marker to the owner", () => { - const entry = makeWorkflowEntry(); - entry.workflow.isOwner = true; - render(entry); - - expect(tooltipTitles().some(t => typeof t === "string" && t.includes("access shared by"))).toBe(false); - }); - - it("disables deleting a workflow the viewer does not own", () => { - const entry = makeWorkflowEntry(); - entry.workflow.isOwner = false; - const el = render(entry); - - expect(el.querySelector<HTMLButtonElement>("button[nz-popconfirm]")?.disabled).toBe(true); - }); - }); - - describe("row actions", () => { - it("withholds the executions action while execution tracking is off", () => { - render(makeWorkflowEntry()); - - expect(tooltipTitles().some(t => typeof t === "string" && t.startsWith("Executions of the workflow"))).toBe( - false - ); - }); - - it("offers the executions action once execution tracking is on", async () => { - await setup({ executionsTracking: true }); - render(makeWorkflowEntry()); - - expect(tooltipTitles().some(t => typeof t === "string" && t.startsWith("Executions of the workflow"))).toBe(true); - }); - - it("keeps duplicate and delete on their own outputs", () => { - // Adjacent icon buttons; emitting the wrong one of these would be destructive. - render(makeWorkflowEntry()); - const duplicated = vi.fn(); - const deleted = vi.fn(); - component.duplicated.subscribe(duplicated); - component.deleted.subscribe(deleted); - - const dup = byTooltip(t => t.startsWith("Duplicate")); - expect(dup.length).toBe(1); - dup[0].click(); - expect(duplicated).toHaveBeenCalledTimes(1); - expect(deleted).not.toHaveBeenCalled(); - - fixture.debugElement.query(By.css("button[nz-popconfirm]")).triggerEventHandler("nzOnConfirm", null); - expect(deleted).toHaveBeenCalledTimes(1); - expect(duplicated).toHaveBeenCalledTimes(1); - }); - - it("opens the executions modal from the history action", async () => { - await setup({ executionsTracking: true }); - render(makeWorkflowEntry({ wid: 11, name: "wf" })); - const modal = TestBed.inject(NzModalService); - const create = vi.spyOn(modal, "create").mockReturnValue({} as any); - - onlyByTooltip(t => t.startsWith("Executions of the workflow")).click(); - - expect(create).toHaveBeenCalledWith( - expect.objectContaining({ nzContent: WorkflowExecutionHistoryComponent, nzData: { wid: 11 } }) - ); - }); - }); - - describe("inline name and description editors", () => { - it("puts name editing behind the pencil and description editing behind the plus", () => { - // Two adjacent icon buttons on the same toolbar; swapping them would open the wrong editor. - render(makeWorkflowEntry()); - expect(component.editingName).toBe(false); - expect(component.editingDescription).toBe(false); - - onlyByTooltip(t => t === "Customize Workflow Name").click(); - - expect(component.editingName).toBe(true); - expect(component.editingDescription).toBe(false); - - onlyByTooltip(t => t === "Add Description").click(); - - expect(component.editingDescription).toBe(true); - }); - }); - - describe("selection checkbox", () => { - it("indents the avatar only when the checkbox is absent", () => { - const withBox = render(makeWorkflowEntry(), true).querySelector<HTMLElement>("nz-list-item-meta-avatar")!; - expect(withBox.style.marginLeft).not.toBe("16px"); - - const withoutBox = render(makeWorkflowEntry(), false).querySelector<HTMLElement>("nz-list-item-meta-avatar")!; - expect(withoutBox.style.marginLeft).toBe("16px"); - expect(withoutBox.querySelector(".workflow-item-checkbox")).toBeNull(); - }); - - it("records the selection on the entry", () => { - const entry = makeWorkflowEntry(); - render(entry, true); - - fixture.debugElement.query(By.css(".workflow-item-checkbox")).triggerEventHandler("ngModelChange", true); - - expect(entry.checked).toBe(true); - }); - }); -}); diff --git a/frontend/src/app/dashboard/component/user/user-workflow/user-workflow-list-item/user-workflow-list-item.component.ts b/frontend/src/app/dashboard/component/user/user-workflow/user-workflow-list-item/user-workflow-list-item.component.ts deleted file mode 100644 index f16ae54da9..0000000000 --- a/frontend/src/app/dashboard/component/user/user-workflow/user-workflow-list-item/user-workflow-list-item.component.ts +++ /dev/null @@ -1,201 +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 { Component, EventEmitter, Input, Output } from "@angular/core"; -import { NzModalService } from "ng-zorro-antd/modal"; -import { WorkflowExecutionHistoryComponent } from "../ngbd-modal-workflow-executions/workflow-execution-history.component"; -import { - DEFAULT_WORKFLOW_NAME, - WorkflowPersistService, -} from "../../../../../common/service/workflow-persist/workflow-persist.service"; -import { UntilDestroy, untilDestroyed } from "@ngneat/until-destroy"; -import { ShareAccessComponent } from "../../share-access/share-access.component"; -import { Workflow } from "../../../../../common/type/workflow"; -import { DashboardEntry } from "../../../../type/dashboard-entry"; -import { firstValueFrom } from "rxjs"; -import { DownloadService } from "src/app/dashboard/service/user/download/download.service"; -import { USER_WORKSPACE } from "../../../../../app-routing.constant"; -import { GuiConfigService } from "../../../../../common/service/gui-config.service"; -import { - NzListItemComponent, - NzListItemMetaComponent, - NzListItemMetaAvatarComponent, - NzListItemMetaTitleComponent, - NzListItemMetaDescriptionComponent, - NzListItemActionsComponent, - NzListItemActionComponent, -} from "ng-zorro-antd/list"; -import { NgStyle, NgIf, DatePipe } from "@angular/common"; -import { FormsModule } from "@angular/forms"; -import { NzAvatarComponent } from "ng-zorro-antd/avatar"; -import { RouterLink } from "@angular/router"; -import { NzSpaceCompactItemDirective } from "ng-zorro-antd/space"; -import { NzButtonComponent } from "ng-zorro-antd/button"; -import { ɵNzTransitionPatchDirective } from "ng-zorro-antd/core/transition-patch"; -import { NzTooltipDirective } from "ng-zorro-antd/tooltip"; -import { NzIconDirective } from "ng-zorro-antd/icon"; -import { NzWaveDirective } from "ng-zorro-antd/core/wave"; -import { NzPopconfirmDirective } from "ng-zorro-antd/popconfirm"; -import { HighlightSearchTermsPipe } from "./highlight-search-terms.pipe"; - -@UntilDestroy() -@Component({ - selector: "texera-user-workflow-list-item", - templateUrl: "./user-workflow-list-item.component.html", - styleUrls: ["./user-workflow-list-item.component.scss"], - imports: [ - NzListItemComponent, - NzListItemMetaComponent, - NzListItemMetaAvatarComponent, - NgStyle, - NgIf, - FormsModule, - NzAvatarComponent, - NzListItemMetaTitleComponent, - RouterLink, - NzSpaceCompactItemDirective, - NzButtonComponent, - ɵNzTransitionPatchDirective, - NzTooltipDirective, - NzIconDirective, - NzListItemMetaDescriptionComponent, - NzListItemActionsComponent, - NzListItemActionComponent, - NzWaveDirective, - NzPopconfirmDirective, - DatePipe, - HighlightSearchTermsPipe, - ], -}) -export class UserWorkflowListItemComponent { - protected readonly USER_WORKSPACE = USER_WORKSPACE; - private _entry?: DashboardEntry; - @Input() public keywords: string[] = []; - - @Input() - get entry(): DashboardEntry { - if (!this._entry) { - throw new Error("entry property must be provided to UserWorkflowListItemComponent."); - } - return this._entry; - } - - set entry(value: DashboardEntry) { - this._entry = value; - } - - get workflow(): Workflow { - if (!this.entry.workflow) { - throw new Error( - "Incorrect type of DashboardEntry provided to UserWorkflowListItemComponent. Entry must be workflow." - ); - } - return this.entry.workflow.workflow; - } - - @Input() editable = false; - @Output() deleted = new EventEmitter<void>(); - @Output() duplicated = new EventEmitter<void>(); - - editingName = false; - editingDescription = false; - - constructor( - private workflowPersistService: WorkflowPersistService, - private modalService: NzModalService, - protected config: GuiConfigService, - private downloadService: DownloadService - ) {} - - /** - * open the workflow executions page - */ - public onClickGetWorkflowExecutions(): void { - this.modalService.create({ - nzContent: WorkflowExecutionHistoryComponent, - nzData: { wid: this.workflow.wid }, - nzTitle: "Execution results of Workflow: " + this.workflow.name, - nzFooter: null, - nzWidth: "80%", - nzCentered: true, - }); - } - - public confirmUpdateWorkflowCustomName(name: string): void { - if (this.workflow.wid === undefined) { - return; - } - - this.workflowPersistService - .updateWorkflowName(this.workflow.wid, name || DEFAULT_WORKFLOW_NAME) - .pipe(untilDestroyed(this)) - .subscribe(() => { - this.workflow.name = name || DEFAULT_WORKFLOW_NAME; - }) - .add(() => { - this.editingName = false; - }); - } - - public confirmUpdateWorkflowCustomDescription(description: string): void { - if (this.workflow.wid === undefined) { - return; - } - - this.workflowPersistService - .updateWorkflowDescription(this.workflow.wid, description) - .pipe(untilDestroyed(this)) - .subscribe(() => { - this.workflow.description = description; - }) - .add(() => { - this.editingDescription = false; - }); - } - - /** - * open the Modal based on the workflow clicked on - */ - public async onClickOpenShareAccess(): Promise<void> { - this.modalService.create({ - nzContent: ShareAccessComponent, - nzData: { - writeAccess: this.entry.workflow.accessLevel === "WRITE", - type: "workflow", - id: this.workflow.wid, - allOwners: await firstValueFrom(this.workflowPersistService.retrieveOwners()), - }, - nzFooter: null, - nzTitle: "Share this workflow with others", - nzCentered: true, - }); - } - - /** - * Download the workflow as a json file - */ - public onClickDownloadWorkfllow(): void { - if (this.workflow.wid) { - this.downloadService - .downloadWorkflow(this.workflow.wid, this.workflow.name) - .pipe(untilDestroyed(this)) - .subscribe(); - } - } -} 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 764b4bdc11..cd6ecc446c 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 @@ -57,7 +57,6 @@ import { testWorkflowFileNameConflictEntries, } from "../../user-dashboard-test-fixtures"; import { FiltersComponent } from "../filters/filters.component"; -import { UserWorkflowListItemComponent } from "./user-workflow-list-item/user-workflow-list-item.component"; import { SearchService } from "../../../service/user/search.service"; import { StubSearchService } from "../../../service/user/stub-search.service"; import { SearchResultsComponent } from "../search-results/search-results.component"; @@ -104,7 +103,6 @@ describe("SavedWorkflowSectionComponent", () => { UserWorkflowComponent, ShareAccessComponent, FiltersComponent, - UserWorkflowListItemComponent, SearchResultsComponent, FormsModule, RouterTestingModule, diff --git a/frontend/src/styles.scss b/frontend/src/styles.scss index 129826fa3d..3625505f95 100644 --- a/frontend/src/styles.scss +++ b/frontend/src/styles.scss @@ -65,11 +65,6 @@ hr { border: 0; } -// due to innerHTML, this rule has to be global -.highlight-search-terms { - color: blue; -} - .box { border-radius: 5px; box-shadow:
