This is an automated email from the ASF dual-hosted git repository.
github-merge-queue[bot] pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/texera.git
The following commit(s) were added to refs/heads/main by this push:
new f24dab0030 chore(frontend): remove the unused
UserWorkflowListItemComponent (#8463)
f24dab0030 is described below
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: