From b6cc7fef5f1fea82c45f3a9929acc36978c540ec Mon Sep 17 00:00:00 2001 From: Abdi Mo Date: Wed, 22 Apr 2026 16:34:57 +0200 Subject: [PATCH 01/35] replaced some old tables with dynamic-mat-table and removed column icons --- .../datafiles/datafiles.component.html | 23 +- .../datafiles/datafiles.component.scss | 3 +- .../datafiles/datafiles.component.spec.ts | 201 +++++------------- .../datasets/datafiles/datafiles.component.ts | 149 +++++-------- .../related-datasets.component.html | 27 +-- .../related-datasets.component.spec.ts | 60 ++++-- .../related-datasets.component.ts | 108 +++++++--- .../files-dashboard.component.ts | 7 - .../instruments-dashboard.component.ts | 2 - .../related-proposals.component.ts | 6 - .../sample-dashboard.component.ts | 5 - 11 files changed, 252 insertions(+), 339 deletions(-) diff --git a/src/app/datasets/datafiles/datafiles.component.html b/src/app/datasets/datafiles/datafiles.component.html index 50ec74edd9..b73f7745eb 100644 --- a/src/app/datasets/datafiles/datafiles.component.html +++ b/src/app/datasets/datafiles/datafiles.component.html @@ -13,7 +13,7 @@ - +
Selected: {{ selectedFileSize | filesize @@ -38,17 +38,16 @@

No files associated to this dataset

>
- - + diff --git a/src/app/datasets/datafiles/datafiles.component.scss b/src/app/datasets/datafiles/datafiles.component.scss index f0663d1d2d..4603154c33 100644 --- a/src/app/datasets/datafiles/datafiles.component.scss +++ b/src/app/datasets/datafiles/datafiles.component.scss @@ -8,7 +8,8 @@ mat-icon { .datafiles-header { margin-top: 1em; - height: 40px; + min-height: 40px; + display: flow-root; .nbr-of-files { font-size: larger; diff --git a/src/app/datasets/datafiles/datafiles.component.spec.ts b/src/app/datasets/datafiles/datafiles.component.spec.ts index c1c843e6d1..0edc325c55 100644 --- a/src/app/datasets/datafiles/datafiles.component.spec.ts +++ b/src/app/datasets/datafiles/datafiles.component.spec.ts @@ -6,14 +6,12 @@ import { MatTableModule } from "@angular/material/table"; import { PipesModule } from "shared/pipes/pipes.module"; import { RouterModule } from "@angular/router"; import { StoreModule } from "@ngrx/store"; -import { CheckboxEvent } from "shared/modules/table/table.component"; import { MockAuthService, MockDatafilesActionsComponent, MockMatDialogRef, MockUserApi, } from "shared/MockStubs"; -import { MatCheckboxChange } from "@angular/material/checkbox"; import { MatIconModule } from "@angular/material/icon"; import { MatButtonModule } from "@angular/material/button"; import { AppConfigService } from "app-config.service"; @@ -22,12 +20,15 @@ import { ConfigurableActionsComponent } from "shared/modules/configurable-action import { UsersService } from "@scicatproject/scicat-sdk-ts-angular"; import { AuthService } from "shared/services/auth/auth.service"; import { FileSizePipe } from "shared/pipes/filesize.pipe"; +import { RowEventType } from "shared/modules/dynamic-material-table/models/table-row.model"; describe("DatafilesComponent", () => { let component: DatafilesComponent; let fixture: ComponentFixture; const getConfig = () => ({ + fileDownloadEnabled: true, + multipleDownloadEnabled: true, datafilesActionsEnabled: true, datafilesActions: [ { @@ -92,6 +93,7 @@ describe("DatafilesComponent", () => { ], declarations: [DatafilesComponent], }); + TestBed.overrideComponent(DatafilesComponent, { set: { providers: [ @@ -107,20 +109,14 @@ describe("DatafilesComponent", () => { ], }, }); + TestBed.compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(DatafilesComponent); component = fixture.componentInstance; - fixture.detectChanges(); - }); - afterEach(() => { - fixture.destroy(); - }); - - beforeEach(() => { component.files = [ { path: "test1", @@ -145,182 +141,93 @@ describe("DatafilesComponent", () => { hash: "", }, ]; - component.tableData = component.files; + component.sourceFolder = "/test/"; fixture.detectChanges(); }); - it("should create", () => { - expect(component).toBeTruthy(); - }); - - describe("#getAreAllSelected()", () => { - it("should return 'false' if no file is selected", () => { - const areAllSelected = component.getAreAllSelected(); - - expect(areAllSelected).toEqual(false); - }); - - it("should return 'false' if only some files are selected", () => { - component.tableData[0].selected = true; - const areAllSelected = component.getAreAllSelected(); - - expect(areAllSelected).toEqual(false); - }); - - it("should return 'true' if all files are selected", () => { - component.tableData.forEach((file) => { - file.selected = true; - }); - const areAllSelected = component.getAreAllSelected(); - - expect(areAllSelected).toEqual(true); - }); + afterEach(() => { + fixture.destroy(); }); - describe("#getIsNoneSelected()", () => { - it("should return 'true' if no file is selected", () => { - const isNoneSelected = component.getIsNoneSelected(); - - expect(isNoneSelected).toEqual(true); - }); - - it("should return 'false' if some files are selected", () => { - component.tableData[0].selected = true; - const isNoneSelected = component.getIsNoneSelected(); - - expect(isNoneSelected).toEqual(false); - }); - - it("should return 'false' if all files are selected", () => { - component.tableData.forEach((file) => { - file.selected = true; - }); - const isNoneSelected = component.getIsNoneSelected(); - - expect(isNoneSelected).toEqual(false); - }); + it("should create", () => { + expect(component).toBeTruthy(); }); describe("#getAllFiles()", () => { - it("should return an array of file paths of files in table", () => { + it("should return an array of file paths from files", () => { const files = component.getAllFiles(); expect(Array.isArray(files)).toEqual(true); - expect(files.includes("test1")).toEqual(true); - expect(files.includes("test2")).toEqual(true); + expect(files).toEqual(["test1", "test2"]); }); }); describe("#getSelectedFiles()", () => { - it("should return an array of file paths from selected files in table", () => { - component.tableData[0].selected = true; + it("should return selected file paths", () => { + component.files[0].selected = true; const files = component.getSelectedFiles(); - expect(Array.isArray(files)).toEqual(true); - expect(files.includes("test1")).toEqual(true); + expect(files).toEqual(["test1"]); }); }); - describe("#updateSelectionStatus()", () => { - it("should set 'areAllSelected' to false and 'isNoneSelected' to true if no file is selected", () => { - component.updateSelectionStatus(); - - expect(component.areAllSelected).toEqual(false); - expect(component.isNoneSelected).toEqual(true); - }); - - it("should set both 'areAllSelected' and 'isNoneSelected' to false if some files are selected", () => { - component.tableData[0].selected = true; - component.updateSelectionStatus(); - - expect(component.areAllSelected).toEqual(false); - expect(component.isNoneSelected).toEqual(false); - }); + describe("#onRowEvent()", () => { + it("should select one row and update selectedFileSize", () => { + const row = component.files[0]; - it("should set 'areAllSelected' to true and 'isNoneSelected' to false if all files are selected", () => { - component.tableData.forEach((file) => { - file.selected = true; - }); - component.updateSelectionStatus(); + component.onRowEvent({ + event: RowEventType.RowSelectionChange, + sender: { row, checked: true }, + } as any); - expect(component.areAllSelected).toEqual(true); - expect(component.isNoneSelected).toEqual(false); + expect(component.files[0].selected).toEqual(true); + expect(component.selectedFileSize).toEqual(5000); }); - }); - describe("#onSelectOne()", () => { - it("should set 'selected' to true and add the size of the file to 'selectedFileSize'", () => { - const file = component.tableData[0]; - const event = new MatCheckboxChange(); - event.checked = true; - const checkboxEvent: CheckboxEvent = { event, row: file }; - component.onSelectOne(checkboxEvent); + it("should unselect one row and update selectedFileSize", () => { + const row = component.files[0]; + row.selected = true; + component.selectedFileSize = 5000; - expect(component.tableData[0].selected).toEqual(true); - expect(component.selectedFileSize).toEqual(file.size); - }); + component.onRowEvent({ + event: RowEventType.RowSelectionChange, + sender: { row, checked: false }, + } as any); - it("should set 'selected' of the provided file to false and subtract the size of the file from 'selectedFileSize'", () => { - const firstFile = component.tableData[0]; - const event = new MatCheckboxChange(); - event.checked = true; - const firstCheckboxEvent: CheckboxEvent = { event, row: firstFile }; - component.onSelectOne(firstCheckboxEvent); - - expect(component.tableData[0].selected).toEqual(true); - expect(component.selectedFileSize).toEqual(firstFile.size); - - const event2 = new MatCheckboxChange(); - event2.checked = false; - const secondCheckboxEvent: CheckboxEvent = { - event: event2, - row: firstFile, - }; - component.onSelectOne(secondCheckboxEvent); - - expect(component.tableData[0].selected).toEqual(false); + expect(component.files[0].selected).toEqual(false); expect(component.selectedFileSize).toEqual(0); }); - }); - describe("#onSelectAll()", () => { - it("should set 'selected' of all files to true if previously set to false and add the size of the files to 'selectedFileSize'", () => { - const event = { - checked: true, - } as MatCheckboxChange; - component.onSelectAll(event); + it("should apply master selection from selectionModel", () => { + const selectionModel = { + isSelected: (file) => file.path === "test1", + }; - component.tableData.forEach((file) => { - expect(file.selected).toEqual(true); - }); + component.onRowEvent({ + event: RowEventType.MasterSelectionChange, + sender: { selectionModel }, + } as any); - expect(component.selectedFileSize).toEqual(15000); + expect(component.files[0].selected).toEqual(true); + expect(component.files[1].selected).toEqual(false); + expect(component.selectedFileSize).toEqual(5000); }); - it("should set 'selected' of all files to false and subtract the size of the files from 'selectedFileSize'", () => { - const firstEvent = { - checked: true, - } as MatCheckboxChange; - component.onSelectAll(firstEvent); + it("should select all rows in master selection", () => { + const selectionModel = { + isSelected: () => true, + }; - component.tableData.forEach((file) => { - expect(file.selected).toEqual(true); - }); + component.onRowEvent({ + event: RowEventType.MasterSelectionChange, + sender: { selectionModel }, + } as any); + expect(component.files[0].selected).toEqual(true); + expect(component.files[1].selected).toEqual(true); expect(component.selectedFileSize).toEqual(15000); - - const secondEvent = { - checked: false, - } as MatCheckboxChange; - component.onSelectAll(secondEvent); - - component.tableData.forEach((file) => { - expect(file.selected).toEqual(false); - }); - - expect(component.selectedFileSize).toEqual(0); }); }); diff --git a/src/app/datasets/datafiles/datafiles.component.ts b/src/app/datasets/datafiles/datafiles.component.ts index 8766755f17..9bd78954c2 100644 --- a/src/app/datasets/datafiles/datafiles.component.ts +++ b/src/app/datasets/datafiles/datafiles.component.ts @@ -8,17 +8,12 @@ import { ViewChild, ElementRef, } from "@angular/core"; -import { Subscription } from "rxjs"; +import { BehaviorSubject, Subscription } from "rxjs"; import { Store } from "@ngrx/store"; import { selectCurrentOrigDatablocks, selectCurrentDataset, } from "state-management/selectors/datasets.selectors"; -import { - TableColumn, - PageChangeEvent, - CheckboxEvent, -} from "shared/modules/table/table.component"; import { selectIsLoading, selectIsLoggedIn, @@ -29,7 +24,6 @@ import { CreateJobDtoV3, } from "@scicatproject/scicat-sdk-ts-angular"; import { FileSizePipe } from "shared/pipes/filesize.pipe"; -import { MatCheckboxChange } from "@angular/material/checkbox"; import { MatDialog } from "@angular/material/dialog"; import { PublicDownloadDialogComponent } from "datasets/public-download-dialog/public-download-dialog.component"; import { submitJobAction } from "state-management/actions/jobs.actions"; @@ -41,6 +35,17 @@ import { ActionItems, } from "shared/modules/configurable-actions/configurable-action.interfaces"; import { AuthService } from "shared/services/auth/auth.service"; +import { TableField } from "shared/modules/dynamic-material-table/models/table-field.model"; +import { + TablePagination, + TablePaginationMode, +} from "shared/modules/dynamic-material-table/models/table-pagination.model"; +import { + IRowEvent, + RowEventType, + TableSelectionMode, +} from "shared/modules/dynamic-material-table/models/table-row.model"; +import { ITableSetting } from "shared/modules/dynamic-material-table/models/table-setting.model"; @Component({ selector: "datafiles", @@ -64,9 +69,6 @@ export class DatafilesComponent implements OnDestroy, OnInit, AfterViewChecked { totalFileSize = 0; selectedFileSize = 0; - areAllSelected = false; - isNoneSelected = true; - subscriptions: Subscription[] = []; files: Array = []; @@ -76,8 +78,6 @@ export class DatafilesComponent implements OnDestroy, OnInit, AfterViewChecked { }; count = 0; - pageSize = 25; - currentPage = 0; fileDownloadEnabled: boolean = this.appConfig.fileDownloadEnabled; multipleDownloadEnabled: boolean = this.appConfig.multipleDownloadEnabled; fileserverBaseURL: string | undefined = this.appConfig.fileserverBaseURL; @@ -94,29 +94,47 @@ export class DatafilesComponent implements OnDestroy, OnInit, AfterViewChecked { jwt: CreateUserJWT; auth_token: string; - tableColumns: TableColumn[] = [ + tableColumns: TableField[] = [ { name: "path", - icon: "save", - sort: false, - inList: true, + header: "Path", }, { name: "size", - icon: "save", - sort: false, - inList: true, - pipe: FileSizePipe, + header: "Size", + customRender: (_column, row: DataFiles_File) => + this.fileSizePipe.transform(row.size), }, { name: "time", - icon: "access_time", - sort: false, - inList: true, - dateFormat: "yyyy-MM-dd HH:mm", + header: "Time", + type: "date", + format: "yyyy-MM-dd HH:mm", }, ]; - tableData: DataFiles_File[] = []; + + setting: ITableSetting = { + rowStyle: { + "border-bottom": "1px solid #d2d2d2", + }, + }; + + dataSource: BehaviorSubject = new BehaviorSubject< + DataFiles_File[] + >([]); + + paginationMode: TablePaginationMode = "server-side"; + + pagination: TablePagination = { + pageSizeOptions: [10, 25, 50], + pageIndex: 0, + pageSize: 25, + length: 0, + }; + + rowSelectionMode: TableSelectionMode = this.fileDownloadEnabled + ? "multi" + : "none"; constructor( public appConfigService: AppConfigService, @@ -128,80 +146,28 @@ export class DatafilesComponent implements OnDestroy, OnInit, AfterViewChecked { private fileSizePipe: FileSizePipe, ) {} - onPageChange(event: PageChangeEvent) { - const { pageIndex, pageSize } = event; - this.currentPage = pageIndex; - this.pageSize = pageSize; - const skip = this.currentPage * this.pageSize; - const end = skip + this.pageSize; - this.tableData = this.files.slice(skip, end); - } - - getAreAllSelected() { - return this.tableData.reduce((accum, curr) => accum && curr.selected, true); - } - - getIsNoneSelected() { - return this.tableData.reduce( - (accum, curr) => accum && !curr.selected, - true, - ); - } - getAllFiles() { - if (!this.tableData) { - return []; - } - return this.tableData.map((file) => file.path); + return this.files.map((file) => file.path); } getSelectedFiles() { - if (!this.tableData) { - return []; - } - return this.tableData - .filter((file) => file.selected) - .map((file) => file.path); - } - - updateSelectionStatus() { - this.areAllSelected = this.getAreAllSelected(); - this.isNoneSelected = this.getIsNoneSelected(); - this.updateSelectedInFiles(); + return this.files.filter((file) => file.selected).map((file) => file.path); } - updateSelectedInFiles() { - const selected = this.tableData - .filter((item) => item.selected) - .map((item) => item.path); - const files = this.files.map((item) => { - item.selected = selected.includes(item.path); - return item; - }); - this.files = [...files]; - } + onRowEvent({ event, sender }: IRowEvent) { + if (event === RowEventType.RowSelectionChange && sender.row) { + sender.row.selected = sender.checked; + } - onSelectOne(checkboxEvent: CheckboxEvent) { - const { event, row } = checkboxEvent; - row.selected = event.checked; - if (event.checked) { - this.selectedFileSize += row.size; - } else { - this.selectedFileSize -= row.size; + if (event === RowEventType.MasterSelectionChange && sender.selectionModel) { + this.files.forEach((file) => { + file.selected = sender.selectionModel.isSelected(file); + }); } - this.updateSelectionStatus(); - } - onSelectAll(event: MatCheckboxChange) { - this.tableData.forEach((file) => { - file.selected = event.checked; - if (event.checked) { - this.selectedFileSize += file.size; - } else { - this.selectedFileSize = 0; - } - }); - this.updateSelectionStatus(); + this.selectedFileSize = this.files + .filter((file) => file.selected) + .reduce((sum, file) => sum + file.size, 0); } hasTooLargeFiles(files: any[]) { @@ -268,8 +234,9 @@ export class DatafilesComponent implements OnDestroy, OnInit, AfterViewChecked { }); }); this.count = files.length; - this.tableData = files.slice(0, this.pageSize); this.files = files; + this.dataSource.next(files); + this.pagination.length = files.length; this.tooLargeFile = this.hasTooLargeFiles(this.files); this.actionItems.datasets[0].files = files; } diff --git a/src/app/datasets/related-datasets/related-datasets.component.html b/src/app/datasets/related-datasets/related-datasets.component.html index fe0d5f231f..458faee54c 100644 --- a/src/app/datasets/related-datasets/related-datasets.component.html +++ b/src/app/datasets/related-datasets/related-datasets.component.html @@ -1,19 +1,14 @@
- - - - + [setting]="setting" + [dataSource]="dataSource" + [pagination]="pagination" + [pagingMode]="paginationMode" + [showGlobalTextSearch]="false" + [rowSelectionMode]="rowSelectionMode" + [emptyMessage]="'No related datasets available'" + (paginationChange)="onPaginationChange($event)" + (onRowEvent)="onRowEvent($event)" + >
diff --git a/src/app/datasets/related-datasets/related-datasets.component.spec.ts b/src/app/datasets/related-datasets/related-datasets.component.spec.ts index 71c4e47887..4a5bb93e1a 100644 --- a/src/app/datasets/related-datasets/related-datasets.component.spec.ts +++ b/src/app/datasets/related-datasets/related-datasets.component.spec.ts @@ -1,20 +1,27 @@ import { DatePipe } from "@angular/common"; import { ComponentFixture, TestBed } from "@angular/core/testing"; -import { Router } from "@angular/router"; +import { ActivatedRoute, Router } from "@angular/router"; import { Store } from "@ngrx/store"; import { provideMockStore } from "@ngrx/store/testing"; -import { PageChangeEvent } from "shared/modules/table/table.component"; import { changeRelatedDatasetsPageAction, fetchRelatedDatasetsAction, } from "state-management/actions/datasets.actions"; -import { selectRelatedDatasetsPageViewModel } from "state-management/selectors/datasets.selectors"; +import { + selectRelatedDatasetsCurrentPage, + selectRelatedDatasetsPageViewModel, + selectRelatedDatasetsPerPage, +} from "state-management/selectors/datasets.selectors"; import { RelatedDatasetsComponent } from "./related-datasets.component"; -import { TableModule } from "shared/modules/table/table.module"; -import { createMock } from "shared/MockStubs"; +import { MockActivatedRoute, createMock } from "shared/MockStubs"; import { DatasetClass } from "@scicatproject/scicat-sdk-ts-angular"; -import { EmptyContentModule } from "shared/modules/generic-empty-content/empty-content.module"; +import { RowEventType } from "shared/modules/dynamic-material-table/models/table-row.model"; +import { DynamicMatTableModule } from "shared/modules/dynamic-material-table/table/dynamic-mat-table.module"; +import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; +import { TranslateService } from "@ngx-translate/core"; +import { SharedScicatFrontendModule } from "shared/shared.module"; +import { TablePagination } from "shared/modules/dynamic-material-table/models/table-pagination.model"; describe("RelatedDatasetsComponent", () => { let component: RelatedDatasetsComponent; @@ -22,14 +29,19 @@ describe("RelatedDatasetsComponent", () => { const router = { navigateByUrl: jasmine.createSpy("navigateByUrl"), + navigate: jasmine.createSpy("navigate"), }; let store: Store; - let dispatchSpy; + let dispatchSpy: jasmine.Spy; beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [RelatedDatasetsComponent], - imports: [TableModule, EmptyContentModule], + imports: [ + BrowserAnimationsModule, + DynamicMatTableModule.forRoot({}), + SharedScicatFrontendModule, + ], providers: [ DatePipe, provideMockStore({ @@ -39,16 +51,21 @@ describe("RelatedDatasetsComponent", () => { value: { relatedDatasets: [], relatedDatasetsCount: 0, - relatedDatasetsFilters: { - skip: 0, - limit: 25, - sortField: "creationTime:desc", - }, }, }, + { + selector: selectRelatedDatasetsCurrentPage, + value: 0, + }, + { + selector: selectRelatedDatasetsPerPage, + value: 25, + }, ], }), { provide: Router, useValue: router }, + { provide: ActivatedRoute, useClass: MockActivatedRoute }, + { provide: TranslateService, useValue: { instant: (k: string) => k } }, ], }).compileComponents(); @@ -65,17 +82,17 @@ describe("RelatedDatasetsComponent", () => { expect(component).toBeTruthy(); }); - describe("#onPageChange", () => { - it("should dispatch a changeRelatedDatasetsPageAction and a fetchRelatedDatasetsAction", () => { + describe("#onPaginationChange()", () => { + it("should dispatch changeRelatedDatasetsPageAction and fetchRelatedDatasetsAction", () => { dispatchSpy = spyOn(store, "dispatch"); - const event: PageChangeEvent = { + const event: TablePagination = { pageIndex: 0, pageSize: 25, length: 25, }; - component.onPageChange(event); + component.onPaginationChange(event); expect(dispatchSpy).toHaveBeenCalledTimes(2); expect(dispatchSpy).toHaveBeenCalledWith( @@ -88,11 +105,14 @@ describe("RelatedDatasetsComponent", () => { }); }); - describe("#onRowClick()", () => { + describe("#onRowEvent()", () => { it("should navigate to a dataset", () => { - const dataset = createMock({}); + const dataset = createMock({ pid: "PID-123" }); - component.onRowClick(dataset); + component.onRowEvent({ + event: RowEventType.RowClick, + sender: { row: dataset }, + } as any); expect(router.navigateByUrl).toHaveBeenCalledOnceWith( "/datasets/" + encodeURIComponent(dataset.pid), diff --git a/src/app/datasets/related-datasets/related-datasets.component.ts b/src/app/datasets/related-datasets/related-datasets.component.ts index 750e0f5d09..3d8d5c971c 100644 --- a/src/app/datasets/related-datasets/related-datasets.component.ts +++ b/src/app/datasets/related-datasets/related-datasets.component.ts @@ -1,12 +1,8 @@ import { DatePipe } from "@angular/common"; -import { Component } from "@angular/core"; +import { Component, OnInit, OnDestroy } from "@angular/core"; import { Router } from "@angular/router"; import { Store } from "@ngrx/store"; import { map } from "rxjs/operators"; -import { - PageChangeEvent, - TableColumn, -} from "shared/modules/table/table.component"; import { DatasetClass, OutputDatasetObsoleteDto, @@ -20,6 +16,18 @@ import { selectRelatedDatasetsPageViewModel, selectRelatedDatasetsPerPage, } from "state-management/selectors/datasets.selectors"; +import { TableField } from "shared/modules/dynamic-material-table/models/table-field.model"; +import { BehaviorSubject, Subscription } from "rxjs"; +import { + TablePagination, + TablePaginationMode, +} from "shared/modules/dynamic-material-table/models/table-pagination.model"; +import { + IRowEvent, + RowEventType, + TableSelectionMode, +} from "shared/modules/dynamic-material-table/models/table-row.model"; +import { ITableSetting } from "shared/modules/dynamic-material-table/models/table-setting.model"; @Component({ selector: "app-related-datasets", @@ -27,7 +35,7 @@ import { styleUrls: ["./related-datasets.component.scss"], standalone: false, }) -export class RelatedDatasetsComponent { +export class RelatedDatasetsComponent implements OnInit, OnDestroy { vm$ = this.store.select(selectRelatedDatasetsPageViewModel).pipe( map((vm) => ({ ...vm, @@ -37,52 +45,86 @@ export class RelatedDatasetsComponent { currentPage$ = this.store.select(selectRelatedDatasetsCurrentPage); datasetsPerPage$ = this.store.select(selectRelatedDatasetsPerPage); - tablePaginate = true; - tableColumns: TableColumn[] = [ + subscriptions: Subscription[] = []; + + tableColumns: TableField[] = [ { name: "name", - icon: "portrait", - sort: true, - inList: true, + header: "Name", }, { name: "sourceFolder", - icon: "explore", - sort: true, - inList: true, + header: "Source Folder", }, { name: "size", - icon: "save", - sort: true, - inList: true, + header: "Size", }, { name: "type", - icon: "bubble_chart", - sort: true, - inList: true, + header: "Type", }, { name: "creationTime", - icon: "calendar_today", - sort: true, - inList: true, + header: "Creation Time", }, { name: "owner", - icon: "face", - sort: true, - inList: true, + header: "Owner", }, ]; + setting: ITableSetting = { + rowStyle: { + "border-bottom": "1px solid #d2d2d2", + }, + }; + + dataSource: BehaviorSubject = new BehaviorSubject([]); + + paginationMode: TablePaginationMode = "server-side"; + + pagination: TablePagination = { + pageSizeOptions: [5, 10, 25, 50, 100], + pageIndex: 0, + pageSize: 10, + length: 0, + }; + + rowSelectionMode: TableSelectionMode = "none"; + constructor( private datePipe: DatePipe, private router: Router, private store: Store, ) {} + ngOnInit(): void { + this.subscriptions.push( + this.vm$.subscribe((vm) => { + this.dataSource.next(vm.relatedDatasets); + this.pagination.length = vm.relatedDatasetsCount || 0; + }), + ); + + this.subscriptions.push( + this.datasetsPerPage$.subscribe((size) => { + const pageSize = size || 25; + this.pagination.pageSize = pageSize; + if (!this.pagination.pageSizeOptions.includes(pageSize)) { + this.pagination.pageSizeOptions = [ + ...this.pagination.pageSizeOptions, + pageSize, + ].sort((a, b) => a - b); + } + }), + ); + } + + ngOnDestroy(): void { + this.subscriptions.forEach((s) => s.unsubscribe()); + } + formatTableData( datasets: OutputDatasetObsoleteDto[], ): Record[] { @@ -104,18 +146,20 @@ export class RelatedDatasetsComponent { })); } - onPageChange(event: PageChangeEvent): void { + onPaginationChange({ pageIndex, pageSize }: TablePagination): void { this.store.dispatch( changeRelatedDatasetsPageAction({ - page: event.pageIndex, - limit: event.pageSize, + page: pageIndex, + limit: pageSize, }), ); this.store.dispatch(fetchRelatedDatasetsAction()); } - onRowClick(dataset: DatasetClass): void { - const pid = encodeURIComponent(dataset.pid); - this.router.navigateByUrl("/datasets/" + pid); + onRowEvent({ event, sender }: IRowEvent): void { + if (event === RowEventType.RowClick) { + const pid = encodeURIComponent(sender.row.pid); + this.router.navigateByUrl("/datasets/" + pid); + } } } diff --git a/src/app/files/files-dashboard/files-dashboard.component.ts b/src/app/files/files-dashboard/files-dashboard.component.ts index 5473fc45e2..b097853e20 100644 --- a/src/app/files/files-dashboard/files-dashboard.component.ts +++ b/src/app/files/files-dashboard/files-dashboard.component.ts @@ -73,7 +73,6 @@ export class FilesDashboardComponent implements OnInit, OnDestroy { columnSetting: [ { name: "dataFileList.path", - icon: "text_snippet", header: "Filename", customRender(column, row) { return get(row, column.name); @@ -81,7 +80,6 @@ export class FilesDashboardComponent implements OnInit, OnDestroy { }, { name: "dataFileList.size", - icon: "save", header: "Size", customRender(column, row) { return get(row, column.name); @@ -89,7 +87,6 @@ export class FilesDashboardComponent implements OnInit, OnDestroy { }, { name: "dataFileList.time", - icon: "access_time", header: "Created at", customRender: (column, row) => { return this.datePipe.transform(get(row, column.name)); @@ -97,7 +94,6 @@ export class FilesDashboardComponent implements OnInit, OnDestroy { }, { name: "dataFileList.uid", - icon: "person", header: "UID", customRender: (column, row) => { return get(row, column.name); @@ -105,7 +101,6 @@ export class FilesDashboardComponent implements OnInit, OnDestroy { }, { name: "dataFileList.gid", - icon: "group", header: "GID", customRender: (column, row) => { return get(row, column.name); @@ -113,12 +108,10 @@ export class FilesDashboardComponent implements OnInit, OnDestroy { }, { name: "ownerGroup", - icon: "group", header: "Owner Group", }, { name: "datasetId", - icon: "list", header: "Dataset PID", customRender: (column, row) => `${row[column.name]}`, diff --git a/src/app/instruments/instruments-dashboard/instruments-dashboard.component.ts b/src/app/instruments/instruments-dashboard/instruments-dashboard.component.ts index 5c7c418e07..b4a98e0923 100644 --- a/src/app/instruments/instruments-dashboard/instruments-dashboard.component.ts +++ b/src/app/instruments/instruments-dashboard/instruments-dashboard.component.ts @@ -37,12 +37,10 @@ const tableDefaultSettingsConfig: ITableSetting = { { name: "uniqueName", header: "Unique Name", - icon: "scanner", }, { name: "name", header: "Name", - icon: "fingerprint", }, ], }, diff --git a/src/app/proposals/related-proposals/related-proposals.component.ts b/src/app/proposals/related-proposals/related-proposals.component.ts index 638c566e78..05667ca7d5 100644 --- a/src/app/proposals/related-proposals/related-proposals.component.ts +++ b/src/app/proposals/related-proposals/related-proposals.component.ts @@ -39,27 +39,21 @@ const tableDefaultSettingsConfig: ITableSetting = { { name: "proposalId", header: "Proposal ID", - icon: "perm_device_information", }, { name: "relation", - icon: "compare_arrows", }, { name: "title", - icon: "description", }, { name: "abstract", - icon: "description", }, { name: "email", - icon: "badge", }, { name: "type", - icon: "text_format", }, ], }, diff --git a/src/app/samples/sample-dashboard/sample-dashboard.component.ts b/src/app/samples/sample-dashboard/sample-dashboard.component.ts index 326bbde27b..8e915e58cd 100644 --- a/src/app/samples/sample-dashboard/sample-dashboard.component.ts +++ b/src/app/samples/sample-dashboard/sample-dashboard.component.ts @@ -71,21 +71,17 @@ export class SampleDashboardComponent implements OnInit, OnDestroy { columnSetting: [ { name: "sampleId", - icon: "fingerprint", header: "Sample ID", }, { name: "description", - icon: "description", }, { name: "owner", - icon: "face", }, { name: "createdAt", header: "Creation time", - icon: "date_range", customRender: (column, row) => { return this.datePipe.transform(row[column.name]); }, @@ -93,7 +89,6 @@ export class SampleDashboardComponent implements OnInit, OnDestroy { { name: "ownerGroup", header: "Owner group", - icon: "group", }, ], }, From 961274c26ec3fe0b64038f04c62fa2c55a9f80d4 Mon Sep 17 00:00:00 2001 From: Abdi Mo Date: Mon, 27 Apr 2026 13:59:41 +0200 Subject: [PATCH 02/35] sourcery changes --- .../datafiles/datafiles.component.html | 1 + .../datasets/datafiles/datafiles.component.ts | 36 ++++++++++++++++--- .../related-datasets.component.ts | 9 +++-- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/src/app/datasets/datafiles/datafiles.component.html b/src/app/datasets/datafiles/datafiles.component.html index b73f7745eb..bfdb646436 100644 --- a/src/app/datasets/datafiles/datafiles.component.html +++ b/src/app/datasets/datafiles/datafiles.component.html @@ -48,6 +48,7 @@

No files associated to this dataset

[rowSelectionMode]="rowSelectionMode" [emptyMessage]="'No datafiles available'" (onRowEvent)="onRowEvent($event)" + (paginationChange)="onPaginationChange($event)" > diff --git a/src/app/datasets/datafiles/datafiles.component.ts b/src/app/datasets/datafiles/datafiles.component.ts index 9bd78954c2..4347f31700 100644 --- a/src/app/datasets/datafiles/datafiles.component.ts +++ b/src/app/datasets/datafiles/datafiles.component.ts @@ -123,10 +123,10 @@ export class DatafilesComponent implements OnDestroy, OnInit, AfterViewChecked { DataFiles_File[] >([]); - paginationMode: TablePaginationMode = "server-side"; + paginationMode: TablePaginationMode = "client-side"; pagination: TablePagination = { - pageSizeOptions: [10, 25, 50], + pageSizeOptions: [5, 10, 25, 50, 100], pageIndex: 0, pageSize: 25, length: 0, @@ -225,6 +225,7 @@ export class DatafilesComponent implements OnDestroy, OnInit, AfterViewChecked { this.subscriptions.push( this.datablocks$.subscribe((datablocks) => { if (datablocks) { + this.totalFileSize = 0; const files: DataFiles_File[] = []; datablocks.forEach((block) => { block.dataFileList.map((file: DataFiles_File) => { @@ -235,10 +236,19 @@ export class DatafilesComponent implements OnDestroy, OnInit, AfterViewChecked { }); this.count = files.length; this.files = files; - this.dataSource.next(files); - this.pagination.length = files.length; + + this.pagination = { + ...this.pagination, + pageIndex: 0, + length: files.length, + pageSize: this.pagination.pageSize || 25, + }; + + this.applyPagination(); this.tooLargeFile = this.hasTooLargeFiles(this.files); - this.actionItems.datasets[0].files = files; + if (this.actionItems.datasets.length > 0) { + this.actionItems.datasets[0].files = files; + } } }), ); @@ -266,6 +276,22 @@ export class DatafilesComponent implements OnDestroy, OnInit, AfterViewChecked { } } + applyPagination() { + const start = this.pagination.pageIndex * this.pagination.pageSize; + const end = start + this.pagination.pageSize; + this.dataSource.next(this.files.slice(start, end)); + } + + onPaginationChange({ pageIndex, pageSize }: TablePagination) { + this.pagination = { + ...this.pagination, + pageIndex, + pageSize, + length: this.files.length, + }; + this.applyPagination(); + } + ngOnDestroy() { this.subscriptions.forEach((subscription) => subscription.unsubscribe()); } diff --git a/src/app/datasets/related-datasets/related-datasets.component.ts b/src/app/datasets/related-datasets/related-datasets.component.ts index 3d8d5c971c..ee0a5fefe8 100644 --- a/src/app/datasets/related-datasets/related-datasets.component.ts +++ b/src/app/datasets/related-datasets/related-datasets.component.ts @@ -80,7 +80,9 @@ export class RelatedDatasetsComponent implements OnInit, OnDestroy { }, }; - dataSource: BehaviorSubject = new BehaviorSubject([]); + dataSource: BehaviorSubject = new BehaviorSubject< + OutputDatasetObsoleteDto[] + >([]); paginationMode: TablePaginationMode = "server-side"; @@ -127,12 +129,13 @@ export class RelatedDatasetsComponent implements OnInit, OnDestroy { formatTableData( datasets: OutputDatasetObsoleteDto[], - ): Record[] { + ): OutputDatasetObsoleteDto[] { if (!datasets) { return []; } return datasets.map((dataset) => ({ + ...dataset, pid: dataset.pid, name: dataset.datasetName, sourceFolder: dataset.sourceFolder, @@ -156,7 +159,7 @@ export class RelatedDatasetsComponent implements OnInit, OnDestroy { this.store.dispatch(fetchRelatedDatasetsAction()); } - onRowEvent({ event, sender }: IRowEvent): void { + onRowEvent({ event, sender }: IRowEvent): void { if (event === RowEventType.RowClick) { const pid = encodeURIComponent(sender.row.pid); this.router.navigateByUrl("/datasets/" + pid); From 669c7e5de94e8576c88da37876fac9e82ae99d9c Mon Sep 17 00:00:00 2001 From: Abdi Mo Date: Mon, 27 Apr 2026 14:09:24 +0200 Subject: [PATCH 03/35] test fix: added provider for HttpClient --- .../related-datasets/related-datasets.component.spec.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/app/datasets/related-datasets/related-datasets.component.spec.ts b/src/app/datasets/related-datasets/related-datasets.component.spec.ts index 4a5bb93e1a..53a32a6bc0 100644 --- a/src/app/datasets/related-datasets/related-datasets.component.spec.ts +++ b/src/app/datasets/related-datasets/related-datasets.component.spec.ts @@ -22,6 +22,8 @@ import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { TranslateService } from "@ngx-translate/core"; import { SharedScicatFrontendModule } from "shared/shared.module"; import { TablePagination } from "shared/modules/dynamic-material-table/models/table-pagination.model"; +import { provideHttpClient, withInterceptorsFromDi } from "@angular/common/http"; +import { provideHttpClientTesting } from "@angular/common/http/testing"; describe("RelatedDatasetsComponent", () => { let component: RelatedDatasetsComponent; @@ -66,6 +68,8 @@ describe("RelatedDatasetsComponent", () => { { provide: Router, useValue: router }, { provide: ActivatedRoute, useClass: MockActivatedRoute }, { provide: TranslateService, useValue: { instant: (k: string) => k } }, + provideHttpClient(withInterceptorsFromDi()), + provideHttpClientTesting(), ], }).compileComponents(); From fa9e3a2cb27c00b0c1bc3e0314bb315930ae57c7 Mon Sep 17 00:00:00 2001 From: Abdi Mo Date: Mon, 27 Apr 2026 14:12:58 +0200 Subject: [PATCH 04/35] eslint fix --- .../related-datasets/related-datasets.component.spec.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/app/datasets/related-datasets/related-datasets.component.spec.ts b/src/app/datasets/related-datasets/related-datasets.component.spec.ts index 53a32a6bc0..20eb7f6f1f 100644 --- a/src/app/datasets/related-datasets/related-datasets.component.spec.ts +++ b/src/app/datasets/related-datasets/related-datasets.component.spec.ts @@ -22,7 +22,10 @@ import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { TranslateService } from "@ngx-translate/core"; import { SharedScicatFrontendModule } from "shared/shared.module"; import { TablePagination } from "shared/modules/dynamic-material-table/models/table-pagination.model"; -import { provideHttpClient, withInterceptorsFromDi } from "@angular/common/http"; +import { + provideHttpClient, + withInterceptorsFromDi, +} from "@angular/common/http"; import { provideHttpClientTesting } from "@angular/common/http/testing"; describe("RelatedDatasetsComponent", () => { From b16bc405cc286abd794a53cfccc2ebd42952a0e6 Mon Sep 17 00:00:00 2001 From: Abdi Mo Date: Mon, 27 Apr 2026 14:21:56 +0200 Subject: [PATCH 05/35] test: removed unneeded config options --- src/app/datasets/datafiles/datafiles.component.spec.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/app/datasets/datafiles/datafiles.component.spec.ts b/src/app/datasets/datafiles/datafiles.component.spec.ts index 0edc325c55..499fc909ef 100644 --- a/src/app/datasets/datafiles/datafiles.component.spec.ts +++ b/src/app/datasets/datafiles/datafiles.component.spec.ts @@ -27,8 +27,6 @@ describe("DatafilesComponent", () => { let fixture: ComponentFixture; const getConfig = () => ({ - fileDownloadEnabled: true, - multipleDownloadEnabled: true, datafilesActionsEnabled: true, datafilesActions: [ { From e002fbd74dc09daa6e77345035d8ce67e1272449 Mon Sep 17 00:00:00 2001 From: Abdi Mo Date: Mon, 11 May 2026 10:45:05 +0200 Subject: [PATCH 06/35] added actionMenu for datafiles table --- .../datasets/datafiles/datafiles.component.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/app/datasets/datafiles/datafiles.component.ts b/src/app/datasets/datafiles/datafiles.component.ts index 4347f31700..260d6f3fff 100644 --- a/src/app/datasets/datafiles/datafiles.component.ts +++ b/src/app/datasets/datafiles/datafiles.component.ts @@ -46,6 +46,7 @@ import { TableSelectionMode, } from "shared/modules/dynamic-material-table/models/table-row.model"; import { ITableSetting } from "shared/modules/dynamic-material-table/models/table-setting.model"; +import { actionMenu } from "shared/modules/dynamic-material-table/utilizes/default-table-settings"; @Component({ selector: "datafiles", @@ -113,7 +114,20 @@ export class DatafilesComponent implements OnDestroy, OnInit, AfterViewChecked { }, ]; - setting: ITableSetting = { + setting: ITableSetting = {}; + + tableDefaultSettingsConfig: ITableSetting = { + visibleActionMenu: actionMenu, + saveSettingMode: "none", + settingList: [ + { + visibleActionMenu: actionMenu, + saveSettingMode: "none", + isDefaultSetting: true, + isCurrentSetting: true, + columnSetting: [], + }, + ], rowStyle: { "border-bottom": "1px solid #d2d2d2", }, @@ -215,6 +229,7 @@ export class DatafilesComponent implements OnDestroy, OnInit, AfterViewChecked { } //ngAfterViewInit() { ngOnInit() { + this.setting = this.tableDefaultSettingsConfig; this.subscriptions.push( this.dataset$.subscribe((dataset) => { if (dataset) { From 832a970773f486e62207f822f68d0879b44d0dde Mon Sep 17 00:00:00 2001 From: Abdi Mo Date: Mon, 11 May 2026 12:55:44 +0200 Subject: [PATCH 07/35] synced related datasets table with user column settings --- .../related-datasets.component.html | 3 +- .../related-datasets.component.ts | 117 ++++++++++++++---- 2 files changed, 96 insertions(+), 24 deletions(-) diff --git a/src/app/datasets/related-datasets/related-datasets.component.html b/src/app/datasets/related-datasets/related-datasets.component.html index 458faee54c..fd90610da2 100644 --- a/src/app/datasets/related-datasets/related-datasets.component.html +++ b/src/app/datasets/related-datasets/related-datasets.component.html @@ -1,6 +1,7 @@
[]; + + pending = true; + + setting: ITableSetting = {}; tableColumns: TableField[] = [ { @@ -74,7 +95,18 @@ export class RelatedDatasetsComponent implements OnInit, OnDestroy { }, ]; - setting: ITableSetting = { + tableDefaultSettingsConfig: ITableSetting = { + visibleActionMenu: actionMenu, + saveSettingMode: "none", + settingList: [ + { + visibleActionMenu: actionMenu, + saveSettingMode: "none", + isDefaultSetting: true, + isCurrentSetting: true, + columnSetting: [], + }, + ], rowStyle: { "border-bottom": "1px solid #d2d2d2", }, @@ -99,32 +131,71 @@ export class RelatedDatasetsComponent implements OnInit, OnDestroy { private datePipe: DatePipe, private router: Router, private store: Store, + private appConfigService: AppConfigService, + private tableConfigService: TableConfigService, + private datasetsListService: DatasetsListService, ) {} ngOnInit(): void { - this.subscriptions.push( - this.vm$.subscribe((vm) => { - this.dataSource.next(vm.relatedDatasets); - this.pagination.length = vm.relatedDatasetsCount || 0; - }), - ); + this.store.dispatch(fetchRelatedDatasetsAction()); - this.subscriptions.push( - this.datasetsPerPage$.subscribe((size) => { - const pageSize = size || 25; - this.pagination.pageSize = pageSize; - if (!this.pagination.pageSizeOptions.includes(pageSize)) { - this.pagination.pageSizeOptions = [ - ...this.pagination.pageSizeOptions, - pageSize, - ].sort((a, b) => a - b); - } - }), - ); + this.subscription = combineLatest([ + this.vm$, + this.selectColumnsWithFetchedSettings$.pipe(take(1)), + this.currentPage$, + this.datasetsPerPage$, + ]).subscribe(([vm, defaultTableColumns, currentPage, datasetsPerPage]) => { + this.dataSource.next(vm.relatedDatasets); + this.pending = false; + + const defaultConfigColumns = + this.appConfig?.defaultDatasetsListSettings?.columns || []; + + const userTableConfigColumns = + this.datasetsListService.convertSavedDatasetColumns( + defaultTableColumns.columns, + ); + + this.tableDefaultSettingsConfig.settingList[0].columnSetting = + this.datasetsListService.convertSavedDatasetColumns( + defaultConfigColumns as TableColumn[], + ); + + const tableSettingsConfig = + this.tableConfigService.getTableSettingsConfig( + this.tableName, + this.tableDefaultSettingsConfig, + userTableConfigColumns, + ); + + const paginationConfig = { + pageSizeOptions: [5, 10, 25, 100], + pageIndex: currentPage || 0, + pageSize: datasetsPerPage || 10, + length: vm.relatedDatasetsCount || 0, + }; + + if (tableSettingsConfig?.settingList.length) { + this.initTable(tableSettingsConfig, paginationConfig); + } + }); + } + + initTable( + settingConfig: ITableSetting, + paginationConfig: TablePagination, + ): void { + const currentColumnSetting = settingConfig.settingList.find( + (s) => s.isCurrentSetting, + )?.columnSetting; + + this.columns = currentColumnSetting; + this.setting = settingConfig; + this.pagination = paginationConfig; } ngOnDestroy(): void { - this.subscriptions.forEach((s) => s.unsubscribe()); + this.subscription.unsubscribe(); } formatTableData( From 0b41cfdd2ecc4eefc1dce88b7aaf4ba20fc07896 Mon Sep 17 00:00:00 2001 From: Abdi Mo Date: Mon, 11 May 2026 13:05:18 +0200 Subject: [PATCH 08/35] fixed tests --- .../related-datasets/related-datasets.component.spec.ts | 7 +++++++ .../related-datasets/related-datasets.component.ts | 4 +--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/app/datasets/related-datasets/related-datasets.component.spec.ts b/src/app/datasets/related-datasets/related-datasets.component.spec.ts index 20eb7f6f1f..2781776487 100644 --- a/src/app/datasets/related-datasets/related-datasets.component.spec.ts +++ b/src/app/datasets/related-datasets/related-datasets.component.spec.ts @@ -27,6 +27,8 @@ import { withInterceptorsFromDi, } from "@angular/common/http"; import { provideHttpClientTesting } from "@angular/common/http/testing"; +import { FileSizePipe } from "shared/pipes/filesize.pipe"; +import { selectColumnsWithHasFetchedSettings } from "state-management/selectors/user.selectors"; describe("RelatedDatasetsComponent", () => { let component: RelatedDatasetsComponent; @@ -49,6 +51,7 @@ describe("RelatedDatasetsComponent", () => { ], providers: [ DatePipe, + FileSizePipe, provideMockStore({ selectors: [ { @@ -66,6 +69,10 @@ describe("RelatedDatasetsComponent", () => { selector: selectRelatedDatasetsPerPage, value: 25, }, + { + selector: selectColumnsWithHasFetchedSettings, + value: { columns: [], hasFetchedSettings: true }, + }, ], }), { provide: Router, useValue: router }, diff --git a/src/app/datasets/related-datasets/related-datasets.component.ts b/src/app/datasets/related-datasets/related-datasets.component.ts index 012ef2d97d..2d90e7d185 100644 --- a/src/app/datasets/related-datasets/related-datasets.component.ts +++ b/src/app/datasets/related-datasets/related-datasets.component.ts @@ -3,9 +3,7 @@ import { Component, OnInit, OnDestroy } from "@angular/core"; import { Router } from "@angular/router"; import { Store } from "@ngrx/store"; import { map } from "rxjs/operators"; -import { - OutputDatasetObsoleteDto, -} from "@scicatproject/scicat-sdk-ts-angular"; +import { OutputDatasetObsoleteDto } from "@scicatproject/scicat-sdk-ts-angular"; import { changeRelatedDatasetsPageAction, fetchRelatedDatasetsAction, From 17936d2b704b9d74e8d1fbe6771c9268b0c88686 Mon Sep 17 00:00:00 2001 From: Abdi Mo Date: Mon, 11 May 2026 14:51:15 +0200 Subject: [PATCH 09/35] added pipe for test --- .../related-datasets/related-datasets.component.spec.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/app/datasets/related-datasets/related-datasets.component.spec.ts b/src/app/datasets/related-datasets/related-datasets.component.spec.ts index 2781776487..7e79a003aa 100644 --- a/src/app/datasets/related-datasets/related-datasets.component.spec.ts +++ b/src/app/datasets/related-datasets/related-datasets.component.spec.ts @@ -29,6 +29,7 @@ import { import { provideHttpClientTesting } from "@angular/common/http/testing"; import { FileSizePipe } from "shared/pipes/filesize.pipe"; import { selectColumnsWithHasFetchedSettings } from "state-management/selectors/user.selectors"; +import { JsonHeadPipe } from "shared/pipes/json-head.pipe"; describe("RelatedDatasetsComponent", () => { let component: RelatedDatasetsComponent; @@ -52,6 +53,7 @@ describe("RelatedDatasetsComponent", () => { providers: [ DatePipe, FileSizePipe, + JsonHeadPipe, provideMockStore({ selectors: [ { From a53e166a5707c2c5bf03a4a39cbe42b1209e6565 Mon Sep 17 00:00:00 2001 From: Abdi Mo Date: Mon, 11 May 2026 15:11:06 +0200 Subject: [PATCH 10/35] sample dashboard table now covers the white space horizontally --- .../samples/sample-dashboard/sample-dashboard.component.html | 2 +- .../samples/sample-dashboard/sample-dashboard.component.scss | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/app/samples/sample-dashboard/sample-dashboard.component.html b/src/app/samples/sample-dashboard/sample-dashboard.component.html index ba479e0cd2..818e1f7d7c 100644 --- a/src/app/samples/sample-dashboard/sample-dashboard.component.html +++ b/src/app/samples/sample-dashboard/sample-dashboard.component.html @@ -75,7 +75,7 @@
-
+
Date: Wed, 22 Apr 2026 16:34:57 +0200 Subject: [PATCH 11/35] replaced some old tables with dynamic-mat-table and removed column icons --- .../datafiles/datafiles.component.html | 23 +- .../datafiles/datafiles.component.scss | 3 +- .../datafiles/datafiles.component.spec.ts | 201 +++++------------- .../datasets/datafiles/datafiles.component.ts | 149 +++++-------- .../related-datasets.component.html | 27 +-- .../related-datasets.component.spec.ts | 60 ++++-- .../related-datasets.component.ts | 108 +++++++--- .../files-dashboard.component.ts | 7 - .../instruments-dashboard.component.ts | 2 - .../related-proposals.component.ts | 6 - .../sample-dashboard.component.ts | 5 - 11 files changed, 252 insertions(+), 339 deletions(-) diff --git a/src/app/datasets/datafiles/datafiles.component.html b/src/app/datasets/datafiles/datafiles.component.html index 50ec74edd9..b73f7745eb 100644 --- a/src/app/datasets/datafiles/datafiles.component.html +++ b/src/app/datasets/datafiles/datafiles.component.html @@ -13,7 +13,7 @@
- +
Selected: {{ selectedFileSize | filesize @@ -38,17 +38,16 @@

No files associated to this dataset

>
- - + diff --git a/src/app/datasets/datafiles/datafiles.component.scss b/src/app/datasets/datafiles/datafiles.component.scss index f0663d1d2d..4603154c33 100644 --- a/src/app/datasets/datafiles/datafiles.component.scss +++ b/src/app/datasets/datafiles/datafiles.component.scss @@ -8,7 +8,8 @@ mat-icon { .datafiles-header { margin-top: 1em; - height: 40px; + min-height: 40px; + display: flow-root; .nbr-of-files { font-size: larger; diff --git a/src/app/datasets/datafiles/datafiles.component.spec.ts b/src/app/datasets/datafiles/datafiles.component.spec.ts index c1c843e6d1..0edc325c55 100644 --- a/src/app/datasets/datafiles/datafiles.component.spec.ts +++ b/src/app/datasets/datafiles/datafiles.component.spec.ts @@ -6,14 +6,12 @@ import { MatTableModule } from "@angular/material/table"; import { PipesModule } from "shared/pipes/pipes.module"; import { RouterModule } from "@angular/router"; import { StoreModule } from "@ngrx/store"; -import { CheckboxEvent } from "shared/modules/table/table.component"; import { MockAuthService, MockDatafilesActionsComponent, MockMatDialogRef, MockUserApi, } from "shared/MockStubs"; -import { MatCheckboxChange } from "@angular/material/checkbox"; import { MatIconModule } from "@angular/material/icon"; import { MatButtonModule } from "@angular/material/button"; import { AppConfigService } from "app-config.service"; @@ -22,12 +20,15 @@ import { ConfigurableActionsComponent } from "shared/modules/configurable-action import { UsersService } from "@scicatproject/scicat-sdk-ts-angular"; import { AuthService } from "shared/services/auth/auth.service"; import { FileSizePipe } from "shared/pipes/filesize.pipe"; +import { RowEventType } from "shared/modules/dynamic-material-table/models/table-row.model"; describe("DatafilesComponent", () => { let component: DatafilesComponent; let fixture: ComponentFixture; const getConfig = () => ({ + fileDownloadEnabled: true, + multipleDownloadEnabled: true, datafilesActionsEnabled: true, datafilesActions: [ { @@ -92,6 +93,7 @@ describe("DatafilesComponent", () => { ], declarations: [DatafilesComponent], }); + TestBed.overrideComponent(DatafilesComponent, { set: { providers: [ @@ -107,20 +109,14 @@ describe("DatafilesComponent", () => { ], }, }); + TestBed.compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(DatafilesComponent); component = fixture.componentInstance; - fixture.detectChanges(); - }); - afterEach(() => { - fixture.destroy(); - }); - - beforeEach(() => { component.files = [ { path: "test1", @@ -145,182 +141,93 @@ describe("DatafilesComponent", () => { hash: "", }, ]; - component.tableData = component.files; + component.sourceFolder = "/test/"; fixture.detectChanges(); }); - it("should create", () => { - expect(component).toBeTruthy(); - }); - - describe("#getAreAllSelected()", () => { - it("should return 'false' if no file is selected", () => { - const areAllSelected = component.getAreAllSelected(); - - expect(areAllSelected).toEqual(false); - }); - - it("should return 'false' if only some files are selected", () => { - component.tableData[0].selected = true; - const areAllSelected = component.getAreAllSelected(); - - expect(areAllSelected).toEqual(false); - }); - - it("should return 'true' if all files are selected", () => { - component.tableData.forEach((file) => { - file.selected = true; - }); - const areAllSelected = component.getAreAllSelected(); - - expect(areAllSelected).toEqual(true); - }); + afterEach(() => { + fixture.destroy(); }); - describe("#getIsNoneSelected()", () => { - it("should return 'true' if no file is selected", () => { - const isNoneSelected = component.getIsNoneSelected(); - - expect(isNoneSelected).toEqual(true); - }); - - it("should return 'false' if some files are selected", () => { - component.tableData[0].selected = true; - const isNoneSelected = component.getIsNoneSelected(); - - expect(isNoneSelected).toEqual(false); - }); - - it("should return 'false' if all files are selected", () => { - component.tableData.forEach((file) => { - file.selected = true; - }); - const isNoneSelected = component.getIsNoneSelected(); - - expect(isNoneSelected).toEqual(false); - }); + it("should create", () => { + expect(component).toBeTruthy(); }); describe("#getAllFiles()", () => { - it("should return an array of file paths of files in table", () => { + it("should return an array of file paths from files", () => { const files = component.getAllFiles(); expect(Array.isArray(files)).toEqual(true); - expect(files.includes("test1")).toEqual(true); - expect(files.includes("test2")).toEqual(true); + expect(files).toEqual(["test1", "test2"]); }); }); describe("#getSelectedFiles()", () => { - it("should return an array of file paths from selected files in table", () => { - component.tableData[0].selected = true; + it("should return selected file paths", () => { + component.files[0].selected = true; const files = component.getSelectedFiles(); - expect(Array.isArray(files)).toEqual(true); - expect(files.includes("test1")).toEqual(true); + expect(files).toEqual(["test1"]); }); }); - describe("#updateSelectionStatus()", () => { - it("should set 'areAllSelected' to false and 'isNoneSelected' to true if no file is selected", () => { - component.updateSelectionStatus(); - - expect(component.areAllSelected).toEqual(false); - expect(component.isNoneSelected).toEqual(true); - }); - - it("should set both 'areAllSelected' and 'isNoneSelected' to false if some files are selected", () => { - component.tableData[0].selected = true; - component.updateSelectionStatus(); - - expect(component.areAllSelected).toEqual(false); - expect(component.isNoneSelected).toEqual(false); - }); + describe("#onRowEvent()", () => { + it("should select one row and update selectedFileSize", () => { + const row = component.files[0]; - it("should set 'areAllSelected' to true and 'isNoneSelected' to false if all files are selected", () => { - component.tableData.forEach((file) => { - file.selected = true; - }); - component.updateSelectionStatus(); + component.onRowEvent({ + event: RowEventType.RowSelectionChange, + sender: { row, checked: true }, + } as any); - expect(component.areAllSelected).toEqual(true); - expect(component.isNoneSelected).toEqual(false); + expect(component.files[0].selected).toEqual(true); + expect(component.selectedFileSize).toEqual(5000); }); - }); - describe("#onSelectOne()", () => { - it("should set 'selected' to true and add the size of the file to 'selectedFileSize'", () => { - const file = component.tableData[0]; - const event = new MatCheckboxChange(); - event.checked = true; - const checkboxEvent: CheckboxEvent = { event, row: file }; - component.onSelectOne(checkboxEvent); + it("should unselect one row and update selectedFileSize", () => { + const row = component.files[0]; + row.selected = true; + component.selectedFileSize = 5000; - expect(component.tableData[0].selected).toEqual(true); - expect(component.selectedFileSize).toEqual(file.size); - }); + component.onRowEvent({ + event: RowEventType.RowSelectionChange, + sender: { row, checked: false }, + } as any); - it("should set 'selected' of the provided file to false and subtract the size of the file from 'selectedFileSize'", () => { - const firstFile = component.tableData[0]; - const event = new MatCheckboxChange(); - event.checked = true; - const firstCheckboxEvent: CheckboxEvent = { event, row: firstFile }; - component.onSelectOne(firstCheckboxEvent); - - expect(component.tableData[0].selected).toEqual(true); - expect(component.selectedFileSize).toEqual(firstFile.size); - - const event2 = new MatCheckboxChange(); - event2.checked = false; - const secondCheckboxEvent: CheckboxEvent = { - event: event2, - row: firstFile, - }; - component.onSelectOne(secondCheckboxEvent); - - expect(component.tableData[0].selected).toEqual(false); + expect(component.files[0].selected).toEqual(false); expect(component.selectedFileSize).toEqual(0); }); - }); - describe("#onSelectAll()", () => { - it("should set 'selected' of all files to true if previously set to false and add the size of the files to 'selectedFileSize'", () => { - const event = { - checked: true, - } as MatCheckboxChange; - component.onSelectAll(event); + it("should apply master selection from selectionModel", () => { + const selectionModel = { + isSelected: (file) => file.path === "test1", + }; - component.tableData.forEach((file) => { - expect(file.selected).toEqual(true); - }); + component.onRowEvent({ + event: RowEventType.MasterSelectionChange, + sender: { selectionModel }, + } as any); - expect(component.selectedFileSize).toEqual(15000); + expect(component.files[0].selected).toEqual(true); + expect(component.files[1].selected).toEqual(false); + expect(component.selectedFileSize).toEqual(5000); }); - it("should set 'selected' of all files to false and subtract the size of the files from 'selectedFileSize'", () => { - const firstEvent = { - checked: true, - } as MatCheckboxChange; - component.onSelectAll(firstEvent); + it("should select all rows in master selection", () => { + const selectionModel = { + isSelected: () => true, + }; - component.tableData.forEach((file) => { - expect(file.selected).toEqual(true); - }); + component.onRowEvent({ + event: RowEventType.MasterSelectionChange, + sender: { selectionModel }, + } as any); + expect(component.files[0].selected).toEqual(true); + expect(component.files[1].selected).toEqual(true); expect(component.selectedFileSize).toEqual(15000); - - const secondEvent = { - checked: false, - } as MatCheckboxChange; - component.onSelectAll(secondEvent); - - component.tableData.forEach((file) => { - expect(file.selected).toEqual(false); - }); - - expect(component.selectedFileSize).toEqual(0); }); }); diff --git a/src/app/datasets/datafiles/datafiles.component.ts b/src/app/datasets/datafiles/datafiles.component.ts index 8766755f17..9bd78954c2 100644 --- a/src/app/datasets/datafiles/datafiles.component.ts +++ b/src/app/datasets/datafiles/datafiles.component.ts @@ -8,17 +8,12 @@ import { ViewChild, ElementRef, } from "@angular/core"; -import { Subscription } from "rxjs"; +import { BehaviorSubject, Subscription } from "rxjs"; import { Store } from "@ngrx/store"; import { selectCurrentOrigDatablocks, selectCurrentDataset, } from "state-management/selectors/datasets.selectors"; -import { - TableColumn, - PageChangeEvent, - CheckboxEvent, -} from "shared/modules/table/table.component"; import { selectIsLoading, selectIsLoggedIn, @@ -29,7 +24,6 @@ import { CreateJobDtoV3, } from "@scicatproject/scicat-sdk-ts-angular"; import { FileSizePipe } from "shared/pipes/filesize.pipe"; -import { MatCheckboxChange } from "@angular/material/checkbox"; import { MatDialog } from "@angular/material/dialog"; import { PublicDownloadDialogComponent } from "datasets/public-download-dialog/public-download-dialog.component"; import { submitJobAction } from "state-management/actions/jobs.actions"; @@ -41,6 +35,17 @@ import { ActionItems, } from "shared/modules/configurable-actions/configurable-action.interfaces"; import { AuthService } from "shared/services/auth/auth.service"; +import { TableField } from "shared/modules/dynamic-material-table/models/table-field.model"; +import { + TablePagination, + TablePaginationMode, +} from "shared/modules/dynamic-material-table/models/table-pagination.model"; +import { + IRowEvent, + RowEventType, + TableSelectionMode, +} from "shared/modules/dynamic-material-table/models/table-row.model"; +import { ITableSetting } from "shared/modules/dynamic-material-table/models/table-setting.model"; @Component({ selector: "datafiles", @@ -64,9 +69,6 @@ export class DatafilesComponent implements OnDestroy, OnInit, AfterViewChecked { totalFileSize = 0; selectedFileSize = 0; - areAllSelected = false; - isNoneSelected = true; - subscriptions: Subscription[] = []; files: Array = []; @@ -76,8 +78,6 @@ export class DatafilesComponent implements OnDestroy, OnInit, AfterViewChecked { }; count = 0; - pageSize = 25; - currentPage = 0; fileDownloadEnabled: boolean = this.appConfig.fileDownloadEnabled; multipleDownloadEnabled: boolean = this.appConfig.multipleDownloadEnabled; fileserverBaseURL: string | undefined = this.appConfig.fileserverBaseURL; @@ -94,29 +94,47 @@ export class DatafilesComponent implements OnDestroy, OnInit, AfterViewChecked { jwt: CreateUserJWT; auth_token: string; - tableColumns: TableColumn[] = [ + tableColumns: TableField[] = [ { name: "path", - icon: "save", - sort: false, - inList: true, + header: "Path", }, { name: "size", - icon: "save", - sort: false, - inList: true, - pipe: FileSizePipe, + header: "Size", + customRender: (_column, row: DataFiles_File) => + this.fileSizePipe.transform(row.size), }, { name: "time", - icon: "access_time", - sort: false, - inList: true, - dateFormat: "yyyy-MM-dd HH:mm", + header: "Time", + type: "date", + format: "yyyy-MM-dd HH:mm", }, ]; - tableData: DataFiles_File[] = []; + + setting: ITableSetting = { + rowStyle: { + "border-bottom": "1px solid #d2d2d2", + }, + }; + + dataSource: BehaviorSubject = new BehaviorSubject< + DataFiles_File[] + >([]); + + paginationMode: TablePaginationMode = "server-side"; + + pagination: TablePagination = { + pageSizeOptions: [10, 25, 50], + pageIndex: 0, + pageSize: 25, + length: 0, + }; + + rowSelectionMode: TableSelectionMode = this.fileDownloadEnabled + ? "multi" + : "none"; constructor( public appConfigService: AppConfigService, @@ -128,80 +146,28 @@ export class DatafilesComponent implements OnDestroy, OnInit, AfterViewChecked { private fileSizePipe: FileSizePipe, ) {} - onPageChange(event: PageChangeEvent) { - const { pageIndex, pageSize } = event; - this.currentPage = pageIndex; - this.pageSize = pageSize; - const skip = this.currentPage * this.pageSize; - const end = skip + this.pageSize; - this.tableData = this.files.slice(skip, end); - } - - getAreAllSelected() { - return this.tableData.reduce((accum, curr) => accum && curr.selected, true); - } - - getIsNoneSelected() { - return this.tableData.reduce( - (accum, curr) => accum && !curr.selected, - true, - ); - } - getAllFiles() { - if (!this.tableData) { - return []; - } - return this.tableData.map((file) => file.path); + return this.files.map((file) => file.path); } getSelectedFiles() { - if (!this.tableData) { - return []; - } - return this.tableData - .filter((file) => file.selected) - .map((file) => file.path); - } - - updateSelectionStatus() { - this.areAllSelected = this.getAreAllSelected(); - this.isNoneSelected = this.getIsNoneSelected(); - this.updateSelectedInFiles(); + return this.files.filter((file) => file.selected).map((file) => file.path); } - updateSelectedInFiles() { - const selected = this.tableData - .filter((item) => item.selected) - .map((item) => item.path); - const files = this.files.map((item) => { - item.selected = selected.includes(item.path); - return item; - }); - this.files = [...files]; - } + onRowEvent({ event, sender }: IRowEvent) { + if (event === RowEventType.RowSelectionChange && sender.row) { + sender.row.selected = sender.checked; + } - onSelectOne(checkboxEvent: CheckboxEvent) { - const { event, row } = checkboxEvent; - row.selected = event.checked; - if (event.checked) { - this.selectedFileSize += row.size; - } else { - this.selectedFileSize -= row.size; + if (event === RowEventType.MasterSelectionChange && sender.selectionModel) { + this.files.forEach((file) => { + file.selected = sender.selectionModel.isSelected(file); + }); } - this.updateSelectionStatus(); - } - onSelectAll(event: MatCheckboxChange) { - this.tableData.forEach((file) => { - file.selected = event.checked; - if (event.checked) { - this.selectedFileSize += file.size; - } else { - this.selectedFileSize = 0; - } - }); - this.updateSelectionStatus(); + this.selectedFileSize = this.files + .filter((file) => file.selected) + .reduce((sum, file) => sum + file.size, 0); } hasTooLargeFiles(files: any[]) { @@ -268,8 +234,9 @@ export class DatafilesComponent implements OnDestroy, OnInit, AfterViewChecked { }); }); this.count = files.length; - this.tableData = files.slice(0, this.pageSize); this.files = files; + this.dataSource.next(files); + this.pagination.length = files.length; this.tooLargeFile = this.hasTooLargeFiles(this.files); this.actionItems.datasets[0].files = files; } diff --git a/src/app/datasets/related-datasets/related-datasets.component.html b/src/app/datasets/related-datasets/related-datasets.component.html index fe0d5f231f..458faee54c 100644 --- a/src/app/datasets/related-datasets/related-datasets.component.html +++ b/src/app/datasets/related-datasets/related-datasets.component.html @@ -1,19 +1,14 @@
- - - - + [setting]="setting" + [dataSource]="dataSource" + [pagination]="pagination" + [pagingMode]="paginationMode" + [showGlobalTextSearch]="false" + [rowSelectionMode]="rowSelectionMode" + [emptyMessage]="'No related datasets available'" + (paginationChange)="onPaginationChange($event)" + (onRowEvent)="onRowEvent($event)" + >
diff --git a/src/app/datasets/related-datasets/related-datasets.component.spec.ts b/src/app/datasets/related-datasets/related-datasets.component.spec.ts index 71c4e47887..4a5bb93e1a 100644 --- a/src/app/datasets/related-datasets/related-datasets.component.spec.ts +++ b/src/app/datasets/related-datasets/related-datasets.component.spec.ts @@ -1,20 +1,27 @@ import { DatePipe } from "@angular/common"; import { ComponentFixture, TestBed } from "@angular/core/testing"; -import { Router } from "@angular/router"; +import { ActivatedRoute, Router } from "@angular/router"; import { Store } from "@ngrx/store"; import { provideMockStore } from "@ngrx/store/testing"; -import { PageChangeEvent } from "shared/modules/table/table.component"; import { changeRelatedDatasetsPageAction, fetchRelatedDatasetsAction, } from "state-management/actions/datasets.actions"; -import { selectRelatedDatasetsPageViewModel } from "state-management/selectors/datasets.selectors"; +import { + selectRelatedDatasetsCurrentPage, + selectRelatedDatasetsPageViewModel, + selectRelatedDatasetsPerPage, +} from "state-management/selectors/datasets.selectors"; import { RelatedDatasetsComponent } from "./related-datasets.component"; -import { TableModule } from "shared/modules/table/table.module"; -import { createMock } from "shared/MockStubs"; +import { MockActivatedRoute, createMock } from "shared/MockStubs"; import { DatasetClass } from "@scicatproject/scicat-sdk-ts-angular"; -import { EmptyContentModule } from "shared/modules/generic-empty-content/empty-content.module"; +import { RowEventType } from "shared/modules/dynamic-material-table/models/table-row.model"; +import { DynamicMatTableModule } from "shared/modules/dynamic-material-table/table/dynamic-mat-table.module"; +import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; +import { TranslateService } from "@ngx-translate/core"; +import { SharedScicatFrontendModule } from "shared/shared.module"; +import { TablePagination } from "shared/modules/dynamic-material-table/models/table-pagination.model"; describe("RelatedDatasetsComponent", () => { let component: RelatedDatasetsComponent; @@ -22,14 +29,19 @@ describe("RelatedDatasetsComponent", () => { const router = { navigateByUrl: jasmine.createSpy("navigateByUrl"), + navigate: jasmine.createSpy("navigate"), }; let store: Store; - let dispatchSpy; + let dispatchSpy: jasmine.Spy; beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [RelatedDatasetsComponent], - imports: [TableModule, EmptyContentModule], + imports: [ + BrowserAnimationsModule, + DynamicMatTableModule.forRoot({}), + SharedScicatFrontendModule, + ], providers: [ DatePipe, provideMockStore({ @@ -39,16 +51,21 @@ describe("RelatedDatasetsComponent", () => { value: { relatedDatasets: [], relatedDatasetsCount: 0, - relatedDatasetsFilters: { - skip: 0, - limit: 25, - sortField: "creationTime:desc", - }, }, }, + { + selector: selectRelatedDatasetsCurrentPage, + value: 0, + }, + { + selector: selectRelatedDatasetsPerPage, + value: 25, + }, ], }), { provide: Router, useValue: router }, + { provide: ActivatedRoute, useClass: MockActivatedRoute }, + { provide: TranslateService, useValue: { instant: (k: string) => k } }, ], }).compileComponents(); @@ -65,17 +82,17 @@ describe("RelatedDatasetsComponent", () => { expect(component).toBeTruthy(); }); - describe("#onPageChange", () => { - it("should dispatch a changeRelatedDatasetsPageAction and a fetchRelatedDatasetsAction", () => { + describe("#onPaginationChange()", () => { + it("should dispatch changeRelatedDatasetsPageAction and fetchRelatedDatasetsAction", () => { dispatchSpy = spyOn(store, "dispatch"); - const event: PageChangeEvent = { + const event: TablePagination = { pageIndex: 0, pageSize: 25, length: 25, }; - component.onPageChange(event); + component.onPaginationChange(event); expect(dispatchSpy).toHaveBeenCalledTimes(2); expect(dispatchSpy).toHaveBeenCalledWith( @@ -88,11 +105,14 @@ describe("RelatedDatasetsComponent", () => { }); }); - describe("#onRowClick()", () => { + describe("#onRowEvent()", () => { it("should navigate to a dataset", () => { - const dataset = createMock({}); + const dataset = createMock({ pid: "PID-123" }); - component.onRowClick(dataset); + component.onRowEvent({ + event: RowEventType.RowClick, + sender: { row: dataset }, + } as any); expect(router.navigateByUrl).toHaveBeenCalledOnceWith( "/datasets/" + encodeURIComponent(dataset.pid), diff --git a/src/app/datasets/related-datasets/related-datasets.component.ts b/src/app/datasets/related-datasets/related-datasets.component.ts index 750e0f5d09..3d8d5c971c 100644 --- a/src/app/datasets/related-datasets/related-datasets.component.ts +++ b/src/app/datasets/related-datasets/related-datasets.component.ts @@ -1,12 +1,8 @@ import { DatePipe } from "@angular/common"; -import { Component } from "@angular/core"; +import { Component, OnInit, OnDestroy } from "@angular/core"; import { Router } from "@angular/router"; import { Store } from "@ngrx/store"; import { map } from "rxjs/operators"; -import { - PageChangeEvent, - TableColumn, -} from "shared/modules/table/table.component"; import { DatasetClass, OutputDatasetObsoleteDto, @@ -20,6 +16,18 @@ import { selectRelatedDatasetsPageViewModel, selectRelatedDatasetsPerPage, } from "state-management/selectors/datasets.selectors"; +import { TableField } from "shared/modules/dynamic-material-table/models/table-field.model"; +import { BehaviorSubject, Subscription } from "rxjs"; +import { + TablePagination, + TablePaginationMode, +} from "shared/modules/dynamic-material-table/models/table-pagination.model"; +import { + IRowEvent, + RowEventType, + TableSelectionMode, +} from "shared/modules/dynamic-material-table/models/table-row.model"; +import { ITableSetting } from "shared/modules/dynamic-material-table/models/table-setting.model"; @Component({ selector: "app-related-datasets", @@ -27,7 +35,7 @@ import { styleUrls: ["./related-datasets.component.scss"], standalone: false, }) -export class RelatedDatasetsComponent { +export class RelatedDatasetsComponent implements OnInit, OnDestroy { vm$ = this.store.select(selectRelatedDatasetsPageViewModel).pipe( map((vm) => ({ ...vm, @@ -37,52 +45,86 @@ export class RelatedDatasetsComponent { currentPage$ = this.store.select(selectRelatedDatasetsCurrentPage); datasetsPerPage$ = this.store.select(selectRelatedDatasetsPerPage); - tablePaginate = true; - tableColumns: TableColumn[] = [ + subscriptions: Subscription[] = []; + + tableColumns: TableField[] = [ { name: "name", - icon: "portrait", - sort: true, - inList: true, + header: "Name", }, { name: "sourceFolder", - icon: "explore", - sort: true, - inList: true, + header: "Source Folder", }, { name: "size", - icon: "save", - sort: true, - inList: true, + header: "Size", }, { name: "type", - icon: "bubble_chart", - sort: true, - inList: true, + header: "Type", }, { name: "creationTime", - icon: "calendar_today", - sort: true, - inList: true, + header: "Creation Time", }, { name: "owner", - icon: "face", - sort: true, - inList: true, + header: "Owner", }, ]; + setting: ITableSetting = { + rowStyle: { + "border-bottom": "1px solid #d2d2d2", + }, + }; + + dataSource: BehaviorSubject = new BehaviorSubject([]); + + paginationMode: TablePaginationMode = "server-side"; + + pagination: TablePagination = { + pageSizeOptions: [5, 10, 25, 50, 100], + pageIndex: 0, + pageSize: 10, + length: 0, + }; + + rowSelectionMode: TableSelectionMode = "none"; + constructor( private datePipe: DatePipe, private router: Router, private store: Store, ) {} + ngOnInit(): void { + this.subscriptions.push( + this.vm$.subscribe((vm) => { + this.dataSource.next(vm.relatedDatasets); + this.pagination.length = vm.relatedDatasetsCount || 0; + }), + ); + + this.subscriptions.push( + this.datasetsPerPage$.subscribe((size) => { + const pageSize = size || 25; + this.pagination.pageSize = pageSize; + if (!this.pagination.pageSizeOptions.includes(pageSize)) { + this.pagination.pageSizeOptions = [ + ...this.pagination.pageSizeOptions, + pageSize, + ].sort((a, b) => a - b); + } + }), + ); + } + + ngOnDestroy(): void { + this.subscriptions.forEach((s) => s.unsubscribe()); + } + formatTableData( datasets: OutputDatasetObsoleteDto[], ): Record[] { @@ -104,18 +146,20 @@ export class RelatedDatasetsComponent { })); } - onPageChange(event: PageChangeEvent): void { + onPaginationChange({ pageIndex, pageSize }: TablePagination): void { this.store.dispatch( changeRelatedDatasetsPageAction({ - page: event.pageIndex, - limit: event.pageSize, + page: pageIndex, + limit: pageSize, }), ); this.store.dispatch(fetchRelatedDatasetsAction()); } - onRowClick(dataset: DatasetClass): void { - const pid = encodeURIComponent(dataset.pid); - this.router.navigateByUrl("/datasets/" + pid); + onRowEvent({ event, sender }: IRowEvent): void { + if (event === RowEventType.RowClick) { + const pid = encodeURIComponent(sender.row.pid); + this.router.navigateByUrl("/datasets/" + pid); + } } } diff --git a/src/app/files/files-dashboard/files-dashboard.component.ts b/src/app/files/files-dashboard/files-dashboard.component.ts index c739f50fb7..7141e008d6 100644 --- a/src/app/files/files-dashboard/files-dashboard.component.ts +++ b/src/app/files/files-dashboard/files-dashboard.component.ts @@ -73,7 +73,6 @@ export class FilesDashboardComponent implements OnInit, OnDestroy { columnSetting: [ { name: "dataFileList.path", - icon: "text_snippet", header: "Filename", customRender(column, row) { return get(row, column.name); @@ -81,7 +80,6 @@ export class FilesDashboardComponent implements OnInit, OnDestroy { }, { name: "dataFileList.size", - icon: "save", header: "Size", customRender(column, row) { return get(row, column.name); @@ -89,7 +87,6 @@ export class FilesDashboardComponent implements OnInit, OnDestroy { }, { name: "dataFileList.time", - icon: "access_time", header: "Created at", customRender: (column, row) => { return this.datePipe.transform(get(row, column.name)); @@ -97,7 +94,6 @@ export class FilesDashboardComponent implements OnInit, OnDestroy { }, { name: "dataFileList.uid", - icon: "person", header: "UID", customRender: (column, row) => { return get(row, column.name); @@ -105,7 +101,6 @@ export class FilesDashboardComponent implements OnInit, OnDestroy { }, { name: "dataFileList.gid", - icon: "group", header: "GID", customRender: (column, row) => { return get(row, column.name); @@ -113,12 +108,10 @@ export class FilesDashboardComponent implements OnInit, OnDestroy { }, { name: "ownerGroup", - icon: "group", header: "Owner Group", }, { name: "datasetId", - icon: "list", header: "Dataset PID", customRender: (column, row) => `${row[column.name]}`, diff --git a/src/app/instruments/instruments-dashboard/instruments-dashboard.component.ts b/src/app/instruments/instruments-dashboard/instruments-dashboard.component.ts index 1bb5085ef9..ea0752f431 100644 --- a/src/app/instruments/instruments-dashboard/instruments-dashboard.component.ts +++ b/src/app/instruments/instruments-dashboard/instruments-dashboard.component.ts @@ -37,12 +37,10 @@ const tableDefaultSettingsConfig: ITableSetting = { { name: "uniqueName", header: "Unique Name", - icon: "scanner", }, { name: "name", header: "Name", - icon: "fingerprint", }, ], }, diff --git a/src/app/proposals/related-proposals/related-proposals.component.ts b/src/app/proposals/related-proposals/related-proposals.component.ts index 638c566e78..05667ca7d5 100644 --- a/src/app/proposals/related-proposals/related-proposals.component.ts +++ b/src/app/proposals/related-proposals/related-proposals.component.ts @@ -39,27 +39,21 @@ const tableDefaultSettingsConfig: ITableSetting = { { name: "proposalId", header: "Proposal ID", - icon: "perm_device_information", }, { name: "relation", - icon: "compare_arrows", }, { name: "title", - icon: "description", }, { name: "abstract", - icon: "description", }, { name: "email", - icon: "badge", }, { name: "type", - icon: "text_format", }, ], }, diff --git a/src/app/samples/sample-dashboard/sample-dashboard.component.ts b/src/app/samples/sample-dashboard/sample-dashboard.component.ts index 3c71a004b4..bcb335b785 100644 --- a/src/app/samples/sample-dashboard/sample-dashboard.component.ts +++ b/src/app/samples/sample-dashboard/sample-dashboard.component.ts @@ -68,21 +68,17 @@ export class SampleDashboardComponent implements OnInit, OnDestroy { columnSetting: [ { name: "sampleId", - icon: "fingerprint", header: "Sample ID", }, { name: "description", - icon: "description", }, { name: "owner", - icon: "face", }, { name: "createdAt", header: "Creation time", - icon: "date_range", customRender: (column, row) => { return this.datePipe.transform(row[column.name]); }, @@ -90,7 +86,6 @@ export class SampleDashboardComponent implements OnInit, OnDestroy { { name: "ownerGroup", header: "Owner group", - icon: "group", }, ], }, From 638bd7c7ba91c241e3a298b7e7e99eb5dd25e91e Mon Sep 17 00:00:00 2001 From: Abdi Mo Date: Mon, 27 Apr 2026 13:59:41 +0200 Subject: [PATCH 12/35] sourcery changes --- .../datafiles/datafiles.component.html | 1 + .../datasets/datafiles/datafiles.component.ts | 36 ++++++++++++++++--- .../related-datasets.component.ts | 9 +++-- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/src/app/datasets/datafiles/datafiles.component.html b/src/app/datasets/datafiles/datafiles.component.html index b73f7745eb..bfdb646436 100644 --- a/src/app/datasets/datafiles/datafiles.component.html +++ b/src/app/datasets/datafiles/datafiles.component.html @@ -48,6 +48,7 @@

No files associated to this dataset

[rowSelectionMode]="rowSelectionMode" [emptyMessage]="'No datafiles available'" (onRowEvent)="onRowEvent($event)" + (paginationChange)="onPaginationChange($event)" > diff --git a/src/app/datasets/datafiles/datafiles.component.ts b/src/app/datasets/datafiles/datafiles.component.ts index 9bd78954c2..4347f31700 100644 --- a/src/app/datasets/datafiles/datafiles.component.ts +++ b/src/app/datasets/datafiles/datafiles.component.ts @@ -123,10 +123,10 @@ export class DatafilesComponent implements OnDestroy, OnInit, AfterViewChecked { DataFiles_File[] >([]); - paginationMode: TablePaginationMode = "server-side"; + paginationMode: TablePaginationMode = "client-side"; pagination: TablePagination = { - pageSizeOptions: [10, 25, 50], + pageSizeOptions: [5, 10, 25, 50, 100], pageIndex: 0, pageSize: 25, length: 0, @@ -225,6 +225,7 @@ export class DatafilesComponent implements OnDestroy, OnInit, AfterViewChecked { this.subscriptions.push( this.datablocks$.subscribe((datablocks) => { if (datablocks) { + this.totalFileSize = 0; const files: DataFiles_File[] = []; datablocks.forEach((block) => { block.dataFileList.map((file: DataFiles_File) => { @@ -235,10 +236,19 @@ export class DatafilesComponent implements OnDestroy, OnInit, AfterViewChecked { }); this.count = files.length; this.files = files; - this.dataSource.next(files); - this.pagination.length = files.length; + + this.pagination = { + ...this.pagination, + pageIndex: 0, + length: files.length, + pageSize: this.pagination.pageSize || 25, + }; + + this.applyPagination(); this.tooLargeFile = this.hasTooLargeFiles(this.files); - this.actionItems.datasets[0].files = files; + if (this.actionItems.datasets.length > 0) { + this.actionItems.datasets[0].files = files; + } } }), ); @@ -266,6 +276,22 @@ export class DatafilesComponent implements OnDestroy, OnInit, AfterViewChecked { } } + applyPagination() { + const start = this.pagination.pageIndex * this.pagination.pageSize; + const end = start + this.pagination.pageSize; + this.dataSource.next(this.files.slice(start, end)); + } + + onPaginationChange({ pageIndex, pageSize }: TablePagination) { + this.pagination = { + ...this.pagination, + pageIndex, + pageSize, + length: this.files.length, + }; + this.applyPagination(); + } + ngOnDestroy() { this.subscriptions.forEach((subscription) => subscription.unsubscribe()); } diff --git a/src/app/datasets/related-datasets/related-datasets.component.ts b/src/app/datasets/related-datasets/related-datasets.component.ts index 3d8d5c971c..ee0a5fefe8 100644 --- a/src/app/datasets/related-datasets/related-datasets.component.ts +++ b/src/app/datasets/related-datasets/related-datasets.component.ts @@ -80,7 +80,9 @@ export class RelatedDatasetsComponent implements OnInit, OnDestroy { }, }; - dataSource: BehaviorSubject = new BehaviorSubject([]); + dataSource: BehaviorSubject = new BehaviorSubject< + OutputDatasetObsoleteDto[] + >([]); paginationMode: TablePaginationMode = "server-side"; @@ -127,12 +129,13 @@ export class RelatedDatasetsComponent implements OnInit, OnDestroy { formatTableData( datasets: OutputDatasetObsoleteDto[], - ): Record[] { + ): OutputDatasetObsoleteDto[] { if (!datasets) { return []; } return datasets.map((dataset) => ({ + ...dataset, pid: dataset.pid, name: dataset.datasetName, sourceFolder: dataset.sourceFolder, @@ -156,7 +159,7 @@ export class RelatedDatasetsComponent implements OnInit, OnDestroy { this.store.dispatch(fetchRelatedDatasetsAction()); } - onRowEvent({ event, sender }: IRowEvent): void { + onRowEvent({ event, sender }: IRowEvent): void { if (event === RowEventType.RowClick) { const pid = encodeURIComponent(sender.row.pid); this.router.navigateByUrl("/datasets/" + pid); From e0636d4a60f1351faee43df7c07aee8c7e79b345 Mon Sep 17 00:00:00 2001 From: Abdi Mo Date: Mon, 27 Apr 2026 14:09:24 +0200 Subject: [PATCH 13/35] test fix: added provider for HttpClient --- .../related-datasets/related-datasets.component.spec.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/app/datasets/related-datasets/related-datasets.component.spec.ts b/src/app/datasets/related-datasets/related-datasets.component.spec.ts index 4a5bb93e1a..53a32a6bc0 100644 --- a/src/app/datasets/related-datasets/related-datasets.component.spec.ts +++ b/src/app/datasets/related-datasets/related-datasets.component.spec.ts @@ -22,6 +22,8 @@ import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { TranslateService } from "@ngx-translate/core"; import { SharedScicatFrontendModule } from "shared/shared.module"; import { TablePagination } from "shared/modules/dynamic-material-table/models/table-pagination.model"; +import { provideHttpClient, withInterceptorsFromDi } from "@angular/common/http"; +import { provideHttpClientTesting } from "@angular/common/http/testing"; describe("RelatedDatasetsComponent", () => { let component: RelatedDatasetsComponent; @@ -66,6 +68,8 @@ describe("RelatedDatasetsComponent", () => { { provide: Router, useValue: router }, { provide: ActivatedRoute, useClass: MockActivatedRoute }, { provide: TranslateService, useValue: { instant: (k: string) => k } }, + provideHttpClient(withInterceptorsFromDi()), + provideHttpClientTesting(), ], }).compileComponents(); From 36afc8b5934715c2ee795aeb5499467ace5d24c9 Mon Sep 17 00:00:00 2001 From: Abdi Mo Date: Mon, 27 Apr 2026 14:12:58 +0200 Subject: [PATCH 14/35] eslint fix --- .../related-datasets/related-datasets.component.spec.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/app/datasets/related-datasets/related-datasets.component.spec.ts b/src/app/datasets/related-datasets/related-datasets.component.spec.ts index 53a32a6bc0..20eb7f6f1f 100644 --- a/src/app/datasets/related-datasets/related-datasets.component.spec.ts +++ b/src/app/datasets/related-datasets/related-datasets.component.spec.ts @@ -22,7 +22,10 @@ import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { TranslateService } from "@ngx-translate/core"; import { SharedScicatFrontendModule } from "shared/shared.module"; import { TablePagination } from "shared/modules/dynamic-material-table/models/table-pagination.model"; -import { provideHttpClient, withInterceptorsFromDi } from "@angular/common/http"; +import { + provideHttpClient, + withInterceptorsFromDi, +} from "@angular/common/http"; import { provideHttpClientTesting } from "@angular/common/http/testing"; describe("RelatedDatasetsComponent", () => { From a23182b526621b9abe07ddb24ca795bc481fa258 Mon Sep 17 00:00:00 2001 From: Abdi Mo Date: Mon, 27 Apr 2026 14:21:56 +0200 Subject: [PATCH 15/35] test: removed unneeded config options --- src/app/datasets/datafiles/datafiles.component.spec.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/app/datasets/datafiles/datafiles.component.spec.ts b/src/app/datasets/datafiles/datafiles.component.spec.ts index 0edc325c55..499fc909ef 100644 --- a/src/app/datasets/datafiles/datafiles.component.spec.ts +++ b/src/app/datasets/datafiles/datafiles.component.spec.ts @@ -27,8 +27,6 @@ describe("DatafilesComponent", () => { let fixture: ComponentFixture; const getConfig = () => ({ - fileDownloadEnabled: true, - multipleDownloadEnabled: true, datafilesActionsEnabled: true, datafilesActions: [ { From 7245b79e4a96fe558600696892e9da33f2d9220a Mon Sep 17 00:00:00 2001 From: Abdi Mo Date: Mon, 11 May 2026 10:45:05 +0200 Subject: [PATCH 16/35] added actionMenu for datafiles table --- .../datasets/datafiles/datafiles.component.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/app/datasets/datafiles/datafiles.component.ts b/src/app/datasets/datafiles/datafiles.component.ts index 4347f31700..260d6f3fff 100644 --- a/src/app/datasets/datafiles/datafiles.component.ts +++ b/src/app/datasets/datafiles/datafiles.component.ts @@ -46,6 +46,7 @@ import { TableSelectionMode, } from "shared/modules/dynamic-material-table/models/table-row.model"; import { ITableSetting } from "shared/modules/dynamic-material-table/models/table-setting.model"; +import { actionMenu } from "shared/modules/dynamic-material-table/utilizes/default-table-settings"; @Component({ selector: "datafiles", @@ -113,7 +114,20 @@ export class DatafilesComponent implements OnDestroy, OnInit, AfterViewChecked { }, ]; - setting: ITableSetting = { + setting: ITableSetting = {}; + + tableDefaultSettingsConfig: ITableSetting = { + visibleActionMenu: actionMenu, + saveSettingMode: "none", + settingList: [ + { + visibleActionMenu: actionMenu, + saveSettingMode: "none", + isDefaultSetting: true, + isCurrentSetting: true, + columnSetting: [], + }, + ], rowStyle: { "border-bottom": "1px solid #d2d2d2", }, @@ -215,6 +229,7 @@ export class DatafilesComponent implements OnDestroy, OnInit, AfterViewChecked { } //ngAfterViewInit() { ngOnInit() { + this.setting = this.tableDefaultSettingsConfig; this.subscriptions.push( this.dataset$.subscribe((dataset) => { if (dataset) { From 9fa42fbc3bfc053b4c323a497017265aa81fc737 Mon Sep 17 00:00:00 2001 From: Abdi Mo Date: Mon, 11 May 2026 12:55:44 +0200 Subject: [PATCH 17/35] synced related datasets table with user column settings --- .../related-datasets.component.html | 3 +- .../related-datasets.component.ts | 117 ++++++++++++++---- 2 files changed, 96 insertions(+), 24 deletions(-) diff --git a/src/app/datasets/related-datasets/related-datasets.component.html b/src/app/datasets/related-datasets/related-datasets.component.html index 458faee54c..fd90610da2 100644 --- a/src/app/datasets/related-datasets/related-datasets.component.html +++ b/src/app/datasets/related-datasets/related-datasets.component.html @@ -1,6 +1,7 @@
[]; + + pending = true; + + setting: ITableSetting = {}; tableColumns: TableField[] = [ { @@ -74,7 +95,18 @@ export class RelatedDatasetsComponent implements OnInit, OnDestroy { }, ]; - setting: ITableSetting = { + tableDefaultSettingsConfig: ITableSetting = { + visibleActionMenu: actionMenu, + saveSettingMode: "none", + settingList: [ + { + visibleActionMenu: actionMenu, + saveSettingMode: "none", + isDefaultSetting: true, + isCurrentSetting: true, + columnSetting: [], + }, + ], rowStyle: { "border-bottom": "1px solid #d2d2d2", }, @@ -99,32 +131,71 @@ export class RelatedDatasetsComponent implements OnInit, OnDestroy { private datePipe: DatePipe, private router: Router, private store: Store, + private appConfigService: AppConfigService, + private tableConfigService: TableConfigService, + private datasetsListService: DatasetsListService, ) {} ngOnInit(): void { - this.subscriptions.push( - this.vm$.subscribe((vm) => { - this.dataSource.next(vm.relatedDatasets); - this.pagination.length = vm.relatedDatasetsCount || 0; - }), - ); + this.store.dispatch(fetchRelatedDatasetsAction()); - this.subscriptions.push( - this.datasetsPerPage$.subscribe((size) => { - const pageSize = size || 25; - this.pagination.pageSize = pageSize; - if (!this.pagination.pageSizeOptions.includes(pageSize)) { - this.pagination.pageSizeOptions = [ - ...this.pagination.pageSizeOptions, - pageSize, - ].sort((a, b) => a - b); - } - }), - ); + this.subscription = combineLatest([ + this.vm$, + this.selectColumnsWithFetchedSettings$.pipe(take(1)), + this.currentPage$, + this.datasetsPerPage$, + ]).subscribe(([vm, defaultTableColumns, currentPage, datasetsPerPage]) => { + this.dataSource.next(vm.relatedDatasets); + this.pending = false; + + const defaultConfigColumns = + this.appConfig?.defaultDatasetsListSettings?.columns || []; + + const userTableConfigColumns = + this.datasetsListService.convertSavedDatasetColumns( + defaultTableColumns.columns, + ); + + this.tableDefaultSettingsConfig.settingList[0].columnSetting = + this.datasetsListService.convertSavedDatasetColumns( + defaultConfigColumns as TableColumn[], + ); + + const tableSettingsConfig = + this.tableConfigService.getTableSettingsConfig( + this.tableName, + this.tableDefaultSettingsConfig, + userTableConfigColumns, + ); + + const paginationConfig = { + pageSizeOptions: [5, 10, 25, 100], + pageIndex: currentPage || 0, + pageSize: datasetsPerPage || 10, + length: vm.relatedDatasetsCount || 0, + }; + + if (tableSettingsConfig?.settingList.length) { + this.initTable(tableSettingsConfig, paginationConfig); + } + }); + } + + initTable( + settingConfig: ITableSetting, + paginationConfig: TablePagination, + ): void { + const currentColumnSetting = settingConfig.settingList.find( + (s) => s.isCurrentSetting, + )?.columnSetting; + + this.columns = currentColumnSetting; + this.setting = settingConfig; + this.pagination = paginationConfig; } ngOnDestroy(): void { - this.subscriptions.forEach((s) => s.unsubscribe()); + this.subscription.unsubscribe(); } formatTableData( From db46373756f79a0be752eaf39a694068053653b4 Mon Sep 17 00:00:00 2001 From: Abdi Mo Date: Mon, 11 May 2026 13:05:18 +0200 Subject: [PATCH 18/35] fixed tests --- .../related-datasets/related-datasets.component.spec.ts | 7 +++++++ .../related-datasets/related-datasets.component.ts | 4 +--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/app/datasets/related-datasets/related-datasets.component.spec.ts b/src/app/datasets/related-datasets/related-datasets.component.spec.ts index 20eb7f6f1f..2781776487 100644 --- a/src/app/datasets/related-datasets/related-datasets.component.spec.ts +++ b/src/app/datasets/related-datasets/related-datasets.component.spec.ts @@ -27,6 +27,8 @@ import { withInterceptorsFromDi, } from "@angular/common/http"; import { provideHttpClientTesting } from "@angular/common/http/testing"; +import { FileSizePipe } from "shared/pipes/filesize.pipe"; +import { selectColumnsWithHasFetchedSettings } from "state-management/selectors/user.selectors"; describe("RelatedDatasetsComponent", () => { let component: RelatedDatasetsComponent; @@ -49,6 +51,7 @@ describe("RelatedDatasetsComponent", () => { ], providers: [ DatePipe, + FileSizePipe, provideMockStore({ selectors: [ { @@ -66,6 +69,10 @@ describe("RelatedDatasetsComponent", () => { selector: selectRelatedDatasetsPerPage, value: 25, }, + { + selector: selectColumnsWithHasFetchedSettings, + value: { columns: [], hasFetchedSettings: true }, + }, ], }), { provide: Router, useValue: router }, diff --git a/src/app/datasets/related-datasets/related-datasets.component.ts b/src/app/datasets/related-datasets/related-datasets.component.ts index 012ef2d97d..2d90e7d185 100644 --- a/src/app/datasets/related-datasets/related-datasets.component.ts +++ b/src/app/datasets/related-datasets/related-datasets.component.ts @@ -3,9 +3,7 @@ import { Component, OnInit, OnDestroy } from "@angular/core"; import { Router } from "@angular/router"; import { Store } from "@ngrx/store"; import { map } from "rxjs/operators"; -import { - OutputDatasetObsoleteDto, -} from "@scicatproject/scicat-sdk-ts-angular"; +import { OutputDatasetObsoleteDto } from "@scicatproject/scicat-sdk-ts-angular"; import { changeRelatedDatasetsPageAction, fetchRelatedDatasetsAction, From dc7951d6bc697761a48c4b16bd2e69397f475f65 Mon Sep 17 00:00:00 2001 From: Abdi Mo Date: Mon, 11 May 2026 14:51:15 +0200 Subject: [PATCH 19/35] added pipe for test --- .../related-datasets/related-datasets.component.spec.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/app/datasets/related-datasets/related-datasets.component.spec.ts b/src/app/datasets/related-datasets/related-datasets.component.spec.ts index 2781776487..7e79a003aa 100644 --- a/src/app/datasets/related-datasets/related-datasets.component.spec.ts +++ b/src/app/datasets/related-datasets/related-datasets.component.spec.ts @@ -29,6 +29,7 @@ import { import { provideHttpClientTesting } from "@angular/common/http/testing"; import { FileSizePipe } from "shared/pipes/filesize.pipe"; import { selectColumnsWithHasFetchedSettings } from "state-management/selectors/user.selectors"; +import { JsonHeadPipe } from "shared/pipes/json-head.pipe"; describe("RelatedDatasetsComponent", () => { let component: RelatedDatasetsComponent; @@ -52,6 +53,7 @@ describe("RelatedDatasetsComponent", () => { providers: [ DatePipe, FileSizePipe, + JsonHeadPipe, provideMockStore({ selectors: [ { From 7bd8e5866b872225ac38e003826a2c1915b16197 Mon Sep 17 00:00:00 2001 From: Abdi Mo Date: Mon, 11 May 2026 15:11:06 +0200 Subject: [PATCH 20/35] sample dashboard table now covers the white space horizontally --- .../samples/sample-dashboard/sample-dashboard.component.html | 2 +- .../samples/sample-dashboard/sample-dashboard.component.scss | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/app/samples/sample-dashboard/sample-dashboard.component.html b/src/app/samples/sample-dashboard/sample-dashboard.component.html index ba479e0cd2..818e1f7d7c 100644 --- a/src/app/samples/sample-dashboard/sample-dashboard.component.html +++ b/src/app/samples/sample-dashboard/sample-dashboard.component.html @@ -75,7 +75,7 @@
-
+
Date: Fri, 22 May 2026 11:35:03 +0200 Subject: [PATCH 21/35] remove display:flow-root and fix root cause --- src/app/datasets/datafiles/datafiles.component.scss | 1 - .../configurable-actions/configurable-actions.component.scss | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/app/datasets/datafiles/datafiles.component.scss b/src/app/datasets/datafiles/datafiles.component.scss index 4603154c33..859458675a 100644 --- a/src/app/datasets/datafiles/datafiles.component.scss +++ b/src/app/datasets/datafiles/datafiles.component.scss @@ -9,7 +9,6 @@ mat-icon { .datafiles-header { margin-top: 1em; min-height: 40px; - display: flow-root; .nbr-of-files { font-size: larger; diff --git a/src/app/shared/modules/configurable-actions/configurable-actions.component.scss b/src/app/shared/modules/configurable-actions/configurable-actions.component.scss index a0d3feb528..6f42afefb1 100644 --- a/src/app/shared/modules/configurable-actions/configurable-actions.component.scss +++ b/src/app/shared/modules/configurable-actions/configurable-actions.component.scss @@ -1,4 +1,4 @@ .configurable-actions { - float: right; - //margin: 1em; + display: flex; + justify-content: flex-end; } From e78ad714d0788dcc4fbc0b879d507e833b11c391 Mon Sep 17 00:00:00 2001 From: Abdi Mo Date: Wed, 10 Jun 2026 10:21:21 +0200 Subject: [PATCH 22/35] addressed requested changes and changed the pagination for datafiles table from client-side to server-side --- .../datasets/datafiles/datafiles.component.ts | 60 ++++++++++--------- .../related-datasets.component.ts | 10 +++- .../sample-dashboard.component.scss | 4 +- .../actions/datasets.actions.ts | 2 +- .../effects/datasets.effects.ts | 46 ++++++++++---- .../reducers/datasets.reducer.ts | 13 ++-- .../selectors/datasets.selectors.ts | 16 +++++ .../state-management/state/datasets.store.ts | 4 ++ 8 files changed, 103 insertions(+), 52 deletions(-) diff --git a/src/app/datasets/datafiles/datafiles.component.ts b/src/app/datasets/datafiles/datafiles.component.ts index 260d6f3fff..91cba27989 100644 --- a/src/app/datasets/datafiles/datafiles.component.ts +++ b/src/app/datasets/datafiles/datafiles.component.ts @@ -13,6 +13,7 @@ import { Store } from "@ngrx/store"; import { selectCurrentOrigDatablocks, selectCurrentDataset, + selectDatafilesPageViewModel, } from "state-management/selectors/datasets.selectors"; import { selectIsLoading, @@ -47,6 +48,7 @@ import { } from "shared/modules/dynamic-material-table/models/table-row.model"; import { ITableSetting } from "shared/modules/dynamic-material-table/models/table-setting.model"; import { actionMenu } from "shared/modules/dynamic-material-table/utilizes/default-table-settings"; +import { fetchOrigDatablocksAction } from "state-management/actions/datasets.actions"; @Component({ selector: "datafiles", @@ -57,6 +59,7 @@ import { actionMenu } from "shared/modules/dynamic-material-table/utilizes/defau export class DatafilesComponent implements OnDestroy, OnInit, AfterViewChecked { @ViewChild("downloadAllForm") downloadAllFormElement: ElementRef; @ViewChild("downloadSelectedForm") downloadSelectedFormElement; + vm$ = this.store.select(selectDatafilesPageViewModel); datablocks$ = this.store.select(selectCurrentOrigDatablocks); dataset$ = this.store.select(selectCurrentDataset); loading$ = this.store.select(selectIsLoading); @@ -110,7 +113,7 @@ export class DatafilesComponent implements OnDestroy, OnInit, AfterViewChecked { name: "time", header: "Time", type: "date", - format: "yyyy-MM-dd HH:mm", + format: this.appConfig.dateFormat || "yyyy-MM-dd HH:mm", }, ]; @@ -137,7 +140,7 @@ export class DatafilesComponent implements OnDestroy, OnInit, AfterViewChecked { DataFiles_File[] >([]); - paginationMode: TablePaginationMode = "client-side"; + paginationMode: TablePaginationMode = "server-side"; pagination: TablePagination = { pageSizeOptions: [5, 10, 25, 50, 100], @@ -227,39 +230,45 @@ export class DatafilesComponent implements OnDestroy, OnInit, AfterViewChecked { return warning; } - //ngAfterViewInit() { + ngOnInit() { this.setting = this.tableDefaultSettingsConfig; this.subscriptions.push( - this.dataset$.subscribe((dataset) => { + this.vm$.subscribe(({ datablocks, totalCount, dataset }) => { if (dataset) { + this.datasetPid = dataset.pid; this.actionItems.datasets = [dataset]; + + if (!this.files.length) { + this.store.dispatch( + fetchOrigDatablocksAction({ + pid: dataset.pid, + filters: { skip: 0, limit: this.pagination.pageSize }, + }), + ); + } } - }), - ); - this.subscriptions.push( - this.datablocks$.subscribe((datablocks) => { + if (datablocks) { this.totalFileSize = 0; const files: DataFiles_File[] = []; datablocks.forEach((block) => { - block.dataFileList.map((file: DataFiles_File) => { - this.totalFileSize += file.size; - file.selected = false; + if (block.dataFileList && !Array.isArray(block.dataFileList)) { + const file = block.dataFileList as DataFiles_File; + this.totalFileSize += file.size || 0; files.push(file); - }); + } }); this.count = files.length; this.files = files; this.pagination = { ...this.pagination, - pageIndex: 0, - length: files.length, - pageSize: this.pagination.pageSize || 25, + length: totalCount, }; - this.applyPagination(); + this.dataSource.next(this.files); + this.tooLargeFile = this.hasTooLargeFiles(this.files); if (this.actionItems.datasets.length > 0) { this.actionItems.datasets[0].files = files; @@ -291,20 +300,13 @@ export class DatafilesComponent implements OnDestroy, OnInit, AfterViewChecked { } } - applyPagination() { - const start = this.pagination.pageIndex * this.pagination.pageSize; - const end = start + this.pagination.pageSize; - this.dataSource.next(this.files.slice(start, end)); - } - onPaginationChange({ pageIndex, pageSize }: TablePagination) { - this.pagination = { - ...this.pagination, - pageIndex, - pageSize, - length: this.files.length, - }; - this.applyPagination(); + this.store.dispatch( + fetchOrigDatablocksAction({ + pid: this.datasetPid, + filters: { skip: pageIndex * pageSize, limit: pageSize }, + }), + ); } ngOnDestroy() { diff --git a/src/app/datasets/related-datasets/related-datasets.component.ts b/src/app/datasets/related-datasets/related-datasets.component.ts index 2d90e7d185..5863e99283 100644 --- a/src/app/datasets/related-datasets/related-datasets.component.ts +++ b/src/app/datasets/related-datasets/related-datasets.component.ts @@ -151,7 +151,7 @@ export class RelatedDatasetsComponent implements OnInit, OnDestroy { const userTableConfigColumns = this.datasetsListService.convertSavedDatasetColumns( - defaultTableColumns.columns, + defaultTableColumns.columns ?? [], ); this.tableDefaultSettingsConfig.settingList[0].columnSetting = @@ -183,17 +183,21 @@ export class RelatedDatasetsComponent implements OnInit, OnDestroy { settingConfig: ITableSetting, paginationConfig: TablePagination, ): void { - const currentColumnSetting = settingConfig.settingList.find( + let currentColumnSetting = settingConfig.settingList.find( (s) => s.isCurrentSetting, )?.columnSetting; + if (!currentColumnSetting && settingConfig.settingList.length > 0) { + currentColumnSetting = settingConfig.settingList[0].columnSetting; + } + this.columns = currentColumnSetting; this.setting = settingConfig; this.pagination = paginationConfig; } ngOnDestroy(): void { - this.subscription.unsubscribe(); + this.subscription?.unsubscribe(); } formatTableData( diff --git a/src/app/samples/sample-dashboard/sample-dashboard.component.scss b/src/app/samples/sample-dashboard/sample-dashboard.component.scss index 60f41a2539..ae66679313 100644 --- a/src/app/samples/sample-dashboard/sample-dashboard.component.scss +++ b/src/app/samples/sample-dashboard/sample-dashboard.component.scss @@ -38,9 +38,7 @@ @media only screen and (max-width: 1279px) { .big-search { - display: { - display: block; - } + display: block; } .small-search { diff --git a/src/app/state-management/actions/datasets.actions.ts b/src/app/state-management/actions/datasets.actions.ts index 8a00c121a3..b81f2cc94b 100644 --- a/src/app/state-management/actions/datasets.actions.ts +++ b/src/app/state-management/actions/datasets.actions.ts @@ -78,7 +78,7 @@ export const fetchOrigDatablocksAction = createAction( ); export const fetchOrigDatablocksCompleteAction = createAction( "[Dataset] Fetch Origin Datablocks Complete", - props<{ origdatablocks: OrigDatablock[] }>(), + props<{ origdatablocks: OrigDatablock[]; totalCount?: number }>(), ); export const fetchOrigDatablocksFailedAction = createAction( "[Dataset] Fetch Origin Datablocks Failed", diff --git a/src/app/state-management/effects/datasets.effects.ts b/src/app/state-management/effects/datasets.effects.ts index 4d402ae286..4e3691bc5a 100644 --- a/src/app/state-management/effects/datasets.effects.ts +++ b/src/app/state-management/effects/datasets.effects.ts @@ -9,6 +9,7 @@ import { OrigDatablock, OutputDatasetObsoleteDto, UpdateAttachmentV3Dto, + OrigdatablocksV4Service, } from "@scicatproject/scicat-sdk-ts-angular"; import { Store } from "@ngrx/store"; import { @@ -27,7 +28,7 @@ import { tap, filter, } from "rxjs/operators"; -import { of } from "rxjs"; +import { of, forkJoin } from "rxjs"; import { selectCurrentUser } from "state-management/selectors/user.selectors"; import { logoutCompleteAction, @@ -176,9 +177,9 @@ export class DatasetEffects { fetchDatablocksOfDataset$ = createEffect(() => { return this.actions$.pipe( ofType(fromActions.fetchDatablocksAction), - switchMap(({ pid, filters }) => { + switchMap(({ pid }) => { return this.datasetsService - .datasetsControllerFindAllDatablocksV3(pid, filters) + .datasetsControllerFindAllDatablocksV3(pid) .pipe( map((datablocks: Datablock[]) => fromActions.fetchDatablocksCompleteAction({ datablocks }), @@ -192,15 +193,37 @@ export class DatasetEffects { fetchOrigDatablocksOfDataset$ = createEffect(() => { return this.actions$.pipe( ofType(fromActions.fetchOrigDatablocksAction), - switchMap(({ pid }) => { - return this.datasetsService - .datasetsControllerFindAllOrigDatablocksV3(pid) - .pipe( - map((origdatablocks: OrigDatablock[]) => - fromActions.fetchOrigDatablocksCompleteAction({ origdatablocks }), + switchMap(({ pid, filters }) => { + const paginatedQuery = { + where: { datasetId: pid }, + limits: { + skip: filters?.skip || 0, + limit: filters?.limit || 5, + }, + }; + + const allFilesQuery = { + where: { datasetId: pid }, + }; + + return forkJoin({ + paginated: + this.origdatablocksService.origDatablocksV4ControllerFindAllFilesV4( + JSON.stringify(paginatedQuery), ), - catchError(() => of(fromActions.fetchOrigDatablocksFailedAction())), - ); + all: this.origdatablocksService.origDatablocksV4ControllerFindAllFilesV4( + JSON.stringify(allFilesQuery), + ), + }).pipe( + map(({ paginated, all }) => { + const totalCount = all?.length || 0; + return fromActions.fetchOrigDatablocksCompleteAction({ + origdatablocks: paginated as any as OrigDatablock[], + totalCount, + }); + }), + catchError(() => of(fromActions.fetchOrigDatablocksFailedAction())), + ); }), ); }); @@ -524,6 +547,7 @@ export class DatasetEffects { constructor( private actions$: Actions, private datasetsService: DatasetsService, + private origdatablocksService: OrigdatablocksV4Service, private store: Store, private appConfigService: AppConfigService, ) {} diff --git a/src/app/state-management/reducers/datasets.reducer.ts b/src/app/state-management/reducers/datasets.reducer.ts index 160a2279a9..430ecc8590 100644 --- a/src/app/state-management/reducers/datasets.reducer.ts +++ b/src/app/state-management/reducers/datasets.reducer.ts @@ -53,13 +53,16 @@ const reducer = createReducer( on( fromActions.fetchOrigDatablocksCompleteAction, - (state, { origdatablocks }) => { + (state, { origdatablocks, totalCount }) => { return { ...state, - currentSet: { - ...state.currentSet, - origdatablocks, - }, + currentSet: state.currentSet + ? { + ...state.currentSet, + origdatablocks, + } + : state.currentSet, + origDatablocksCount: totalCount ?? 0, }; }, ), diff --git a/src/app/state-management/selectors/datasets.selectors.ts b/src/app/state-management/selectors/datasets.selectors.ts index d93373c325..c97af41298 100644 --- a/src/app/state-management/selectors/datasets.selectors.ts +++ b/src/app/state-management/selectors/datasets.selectors.ts @@ -315,3 +315,19 @@ export const selectRelatedDatasetsPerPage = createSelector( selectRelatedDatasetsFilters, (filters) => filters.limit, ); + +export const selectCurrentOrigDatablocksCount = createSelector( + selectDatasetState, + (state) => state.origDatablocksCount ?? 0, +); + +export const selectDatafilesPageViewModel = createSelector( + selectCurrentOrigDatablocks, + selectCurrentOrigDatablocksCount, + selectCurrentDataset, + (datablocks, totalCount, dataset) => ({ + datablocks, + totalCount, + dataset, + }), +); diff --git a/src/app/state-management/state/datasets.store.ts b/src/app/state-management/state/datasets.store.ts index d7f284baee..bf44bb08d5 100644 --- a/src/app/state-management/state/datasets.store.ts +++ b/src/app/state-management/state/datasets.store.ts @@ -42,6 +42,8 @@ export interface DatasetState { batch: OutputDatasetObsoleteDto[]; openwhiskResult: Record | undefined; + + origDatablocksCount?: number; } export const initialDatasetState: DatasetState = { @@ -87,4 +89,6 @@ export const initialDatasetState: DatasetState = { batch: [], openwhiskResult: undefined, + + origDatablocksCount: 0, }; From 6830ba43fc3d7d1b722ebe9c2a11cd5f40da974a Mon Sep 17 00:00:00 2001 From: Abdi Mo Date: Wed, 10 Jun 2026 10:35:01 +0200 Subject: [PATCH 23/35] fixed unit tests --- .../state-management/effects/datasets.effects.spec.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/app/state-management/effects/datasets.effects.spec.ts b/src/app/state-management/effects/datasets.effects.spec.ts index 3b4b783b74..2b6760ea1e 100644 --- a/src/app/state-management/effects/datasets.effects.spec.ts +++ b/src/app/state-management/effects/datasets.effects.spec.ts @@ -22,6 +22,7 @@ import { DatasetsControllerCreateV3Request, DatasetsService, OutputDatasetObsoleteDto, + OrigdatablocksV4Service, } from "@scicatproject/scicat-sdk-ts-angular"; import { TestObservable } from "jasmine-marbles/src/test-observables"; import { @@ -57,6 +58,7 @@ describe("DatasetEffects", () => { let actions: TestObservable; let effects: DatasetEffects; let datasetApi: jasmine.SpyObj; + let origdatablocksApi: jasmine.SpyObj; const getConfig = () => ({}); @@ -99,12 +101,19 @@ describe("DatasetEffects", () => { "datasetsControllerCountV3", ]), }, + { + provide: OrigdatablocksV4Service, + useValue: jasmine.createSpyObj("origdatablocksService", [ + "origDatablocksV4ControllerFindAllFilesV4", + ]), + }, { provide: AppConfigService, useValue: { getConfig } }, ], }); effects = TestBed.inject(DatasetEffects); datasetApi = injectedStub(DatasetsService); + origdatablocksApi = injectedStub(OrigdatablocksV4Service); }); const injectedStub = (service: Type): jasmine.SpyObj => From a07fdcc30f87f066cfb50365c8ec83453f4bea52 Mon Sep 17 00:00:00 2001 From: Abdi Mo Date: Wed, 10 Jun 2026 11:37:17 +0200 Subject: [PATCH 24/35] fixed cypress test --- cypress/e2e/datasets/datasets-datafiles.cy.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cypress/e2e/datasets/datasets-datafiles.cy.js b/cypress/e2e/datasets/datasets-datafiles.cy.js index 22656bb362..bb8cd457fd 100644 --- a/cypress/e2e/datasets/datasets-datafiles.cy.js +++ b/cypress/e2e/datasets/datasets-datafiles.cy.js @@ -217,6 +217,8 @@ describe("0040: Dataset datafiles", () => { cy.get(".mat-mdc-tab-link").contains("Datafiles").click(); + cy.finishedLoading(); + cy.get(".mdc-checkbox__native-control").eq(1).check(); cy.window().then((win) => { From 9f36a1eedbe5df6d8f69e3ab251ca3bd5f0c2b05 Mon Sep 17 00:00:00 2001 From: Abdi Mo Date: Wed, 10 Jun 2026 16:19:09 +0200 Subject: [PATCH 25/35] fixed cypress test x2 --- cypress/e2e/datasets/datasets-datafiles.cy.js | 2 -- src/app/datasets/datafiles/datafiles.component.ts | 9 --------- 2 files changed, 11 deletions(-) diff --git a/cypress/e2e/datasets/datasets-datafiles.cy.js b/cypress/e2e/datasets/datasets-datafiles.cy.js index bb8cd457fd..22656bb362 100644 --- a/cypress/e2e/datasets/datasets-datafiles.cy.js +++ b/cypress/e2e/datasets/datasets-datafiles.cy.js @@ -217,8 +217,6 @@ describe("0040: Dataset datafiles", () => { cy.get(".mat-mdc-tab-link").contains("Datafiles").click(); - cy.finishedLoading(); - cy.get(".mdc-checkbox__native-control").eq(1).check(); cy.window().then((win) => { diff --git a/src/app/datasets/datafiles/datafiles.component.ts b/src/app/datasets/datafiles/datafiles.component.ts index 91cba27989..6e055b2403 100644 --- a/src/app/datasets/datafiles/datafiles.component.ts +++ b/src/app/datasets/datafiles/datafiles.component.ts @@ -238,15 +238,6 @@ export class DatafilesComponent implements OnDestroy, OnInit, AfterViewChecked { if (dataset) { this.datasetPid = dataset.pid; this.actionItems.datasets = [dataset]; - - if (!this.files.length) { - this.store.dispatch( - fetchOrigDatablocksAction({ - pid: dataset.pid, - filters: { skip: 0, limit: this.pagination.pageSize }, - }), - ); - } } if (datablocks) { From 90f3512803f797c67d1e40d8987a64f0f18a6e05 Mon Sep 17 00:00:00 2001 From: Abdi Mo Date: Wed, 17 Jun 2026 10:36:08 +0200 Subject: [PATCH 26/35] eslint fix --- src/app/state-management/effects/datasets.effects.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/state-management/effects/datasets.effects.spec.ts b/src/app/state-management/effects/datasets.effects.spec.ts index e5557ca18c..b0c96aa27f 100644 --- a/src/app/state-management/effects/datasets.effects.spec.ts +++ b/src/app/state-management/effects/datasets.effects.spec.ts @@ -117,7 +117,7 @@ describe("DatasetEffects", () => { provide: MetadataKeysV4Service, useValue: jasmine.createSpyObj("metadataKeysApi", [ "metadataKeysV4ControllerFindAllV4", - ]), + ]), }, { provide: AppConfigService, useValue: { getConfig } }, provideHttpClient(withInterceptorsFromDi()), From 14f046991b299c8e6a765db58adcf0587d57815d Mon Sep 17 00:00:00 2001 From: Abdi Mo Date: Wed, 24 Jun 2026 14:25:24 +0200 Subject: [PATCH 27/35] removed fixed height of sample table --- src/app/samples/sample-dashboard/sample-dashboard.component.html | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/samples/sample-dashboard/sample-dashboard.component.html b/src/app/samples/sample-dashboard/sample-dashboard.component.html index 818e1f7d7c..344b980e0d 100644 --- a/src/app/samples/sample-dashboard/sample-dashboard.component.html +++ b/src/app/samples/sample-dashboard/sample-dashboard.component.html @@ -90,7 +90,6 @@ (onRowEvent)="onRowClick($event)" (settingChange)="onSettingChange($event)" (onTableEvent)="onTableEvent($event)" - style="height: 70vh" class="mat-elevation-z2" > From 97f5533933a227aad43a6c0e90e12e47e850e0dd Mon Sep 17 00:00:00 2001 From: Abdi Mo Date: Mon, 29 Jun 2026 10:58:04 +0200 Subject: [PATCH 28/35] added effect, actions and reducer for origdatablockcount, also added a count dispatch for datafiles component and dataset details dashboard inside fetchDataForTab --- .../datasets/datafiles/datafiles.component.ts | 9 ++- .../dataset-details-dashboard.component.ts | 11 +++ .../actions/datasets.actions.ts | 13 +++- .../effects/datasets.effects.ts | 68 +++++++++++++------ .../reducers/datasets.reducer.ts | 23 ++++--- 5 files changed, 93 insertions(+), 31 deletions(-) diff --git a/src/app/datasets/datafiles/datafiles.component.ts b/src/app/datasets/datafiles/datafiles.component.ts index 6e055b2403..b57d58cbc9 100644 --- a/src/app/datasets/datafiles/datafiles.component.ts +++ b/src/app/datasets/datafiles/datafiles.component.ts @@ -48,7 +48,7 @@ import { } from "shared/modules/dynamic-material-table/models/table-row.model"; import { ITableSetting } from "shared/modules/dynamic-material-table/models/table-setting.model"; import { actionMenu } from "shared/modules/dynamic-material-table/utilizes/default-table-settings"; -import { fetchOrigDatablocksAction } from "state-management/actions/datasets.actions"; +import { fetchOrigDatablocksAction, fetchOrigDatablocksCountAction } from "state-management/actions/datasets.actions"; @Component({ selector: "datafiles", @@ -145,7 +145,7 @@ export class DatafilesComponent implements OnDestroy, OnInit, AfterViewChecked { pagination: TablePagination = { pageSizeOptions: [5, 10, 25, 50, 100], pageIndex: 0, - pageSize: 25, + pageSize: 5, length: 0, }; @@ -298,6 +298,11 @@ export class DatafilesComponent implements OnDestroy, OnInit, AfterViewChecked { filters: { skip: pageIndex * pageSize, limit: pageSize }, }), ); + this.store.dispatch( + fetchOrigDatablocksCountAction({ + pid: this.datasetPid, + }), + ); } ngOnDestroy() { diff --git a/src/app/datasets/dataset-details-dashboard/dataset-details-dashboard.component.ts b/src/app/datasets/dataset-details-dashboard/dataset-details-dashboard.component.ts index 65cf86131b..bebce985bb 100644 --- a/src/app/datasets/dataset-details-dashboard/dataset-details-dashboard.component.ts +++ b/src/app/datasets/dataset-details-dashboard/dataset-details-dashboard.component.ts @@ -30,6 +30,7 @@ import { fetchDatablocksAction, fetchDatasetAction, fetchOrigDatablocksAction, + fetchOrigDatablocksCountAction, fetchRelatedDatasetsAction, } from "state-management/actions/datasets.actions"; import { @@ -260,6 +261,16 @@ export class DatasetDetailsDashboardComponent } } break; + case TAB.datafiles: + { + this.store.dispatch( + fetchOrigDatablocksAction(args as { pid: string }), + ); + this.store.dispatch( + fetchOrigDatablocksCountAction(args as { pid: string }), + ); + } + break; case TAB.logbook: { const { loaded } = this.fetchDataActions[TAB.logbook]; diff --git a/src/app/state-management/actions/datasets.actions.ts b/src/app/state-management/actions/datasets.actions.ts index 743b9b866a..f62107c76a 100644 --- a/src/app/state-management/actions/datasets.actions.ts +++ b/src/app/state-management/actions/datasets.actions.ts @@ -79,11 +79,22 @@ export const fetchOrigDatablocksAction = createAction( ); export const fetchOrigDatablocksCompleteAction = createAction( "[Dataset] Fetch Origin Datablocks Complete", - props<{ origdatablocks: OrigDatablock[]; totalCount?: number }>(), + props<{ origdatablocks: OrigDatablock[]; count?: number }>(), ); export const fetchOrigDatablocksFailedAction = createAction( "[Dataset] Fetch Origin Datablocks Failed", ); +export const fetchOrigDatablocksCountAction = createAction( + "[Dataset] Fetch Origin Datablocks Count", + props<{ pid: string; filters?: any }>(), +) +export const fetchOrigDatablocksCountCompleteAction = createAction( + "[Dataset] Fetch Origin Datablocks Count Complete", + props<{ count: number }>(), +); +export const fetchOrigDatablocksCountFailedAction = createAction( + "[Dataset] Fetch Origin Datablocks Count Failed", +); export const fetchAttachmentsAction = createAction( "[Dataset] Fetch Attachments", props<{ pid: string; filters?: any }>(), diff --git a/src/app/state-management/effects/datasets.effects.ts b/src/app/state-management/effects/datasets.effects.ts index b469246e44..dc78ab8c20 100644 --- a/src/app/state-management/effects/datasets.effects.ts +++ b/src/app/state-management/effects/datasets.effects.ts @@ -10,6 +10,7 @@ import { OutputDatasetObsoleteDto, UpdateAttachmentV3Dto, OrigdatablocksV4Service, + OrigdatablocksPublicV4Service, MetadataKeysV4Service, } from "@scicatproject/scicat-sdk-ts-angular"; import { Store } from "@ngrx/store"; @@ -29,7 +30,7 @@ import { tap, filter, } from "rxjs/operators"; -import { of, forkJoin } from "rxjs"; +import { of } from "rxjs"; import { selectCurrentUser } from "state-management/selectors/user.selectors"; import { logoutCompleteAction, @@ -209,8 +210,9 @@ export class DatasetEffects { fetchOrigDatablocksOfDataset$ = createEffect(() => { return this.actions$.pipe( ofType(fromActions.fetchOrigDatablocksAction), - switchMap(({ pid, filters }) => { - const paginatedQuery = { + concatLatestFrom(() => this.currentUser$), + switchMap(([{ pid, filters }, user]) => { + const filter = { where: { datasetId: pid }, limits: { skip: filters?.skip || 0, @@ -218,24 +220,18 @@ export class DatasetEffects { }, }; - const allFilesQuery = { - where: { datasetId: pid }, - }; + const apiCall$ = user + ? this.origdatablocksService.origDatablocksV4ControllerFindAllFilesV4( + JSON.stringify(filter), + ) + : this.origdatablocksPublicService.origDatablocksPublicV4ControllerFindAllFilesPublicV4( + JSON.stringify(filter), + ); - return forkJoin({ - paginated: - this.origdatablocksService.origDatablocksV4ControllerFindAllFilesV4( - JSON.stringify(paginatedQuery), - ), - all: this.origdatablocksService.origDatablocksV4ControllerFindAllFilesV4( - JSON.stringify(allFilesQuery), - ), - }).pipe( - map(({ paginated, all }) => { - const totalCount = all?.length || 0; + return apiCall$.pipe( + map((origdatablocks: OrigDatablock[]) => { return fromActions.fetchOrigDatablocksCompleteAction({ - origdatablocks: paginated as any as OrigDatablock[], - totalCount, + origdatablocks, }); }), catchError(() => of(fromActions.fetchOrigDatablocksFailedAction())), @@ -244,6 +240,39 @@ export class DatasetEffects { ); }); + fetchOrigDatablocksCount$ = createEffect(() => { + return this.actions$.pipe( + ofType(fromActions.fetchOrigDatablocksCountAction), + concatLatestFrom(() => this.currentUser$), + switchMap(([{ pid }, user]) => { + const filter = { + where: { + datasetId: pid, + }, + }; + + const apiCall$ = user + ? this.origdatablocksService.origDatablocksV4ControllerCountFilesV4( + JSON.stringify(filter), + ) + : this.origdatablocksPublicService.origDatablocksPublicV4ControllerCountFilesPublicV4( + JSON.stringify(filter), + ); + + return apiCall$.pipe( + map(({ count }) => + fromActions.fetchOrigDatablocksCountCompleteAction({ + count, + }), + ), + catchError(() => + of(fromActions.fetchOrigDatablocksCountFailedAction()), + ), + ); + }), + ); + }); + fetchAttachmentsOfDataset$ = createEffect(() => { return this.actions$.pipe( ofType(fromActions.fetchAttachmentsAction), @@ -564,6 +593,7 @@ export class DatasetEffects { private actions$: Actions, private datasetsService: DatasetsService, private origdatablocksService: OrigdatablocksV4Service, + private origdatablocksPublicService: OrigdatablocksPublicV4Service, private store: Store, private appConfigService: AppConfigService, private metadataKeysV4Service: MetadataKeysV4Service, diff --git a/src/app/state-management/reducers/datasets.reducer.ts b/src/app/state-management/reducers/datasets.reducer.ts index 14aa529589..05a2609d8e 100644 --- a/src/app/state-management/reducers/datasets.reducer.ts +++ b/src/app/state-management/reducers/datasets.reducer.ts @@ -70,20 +70,25 @@ const reducer = createReducer( on( fromActions.fetchOrigDatablocksCompleteAction, - (state, { origdatablocks, totalCount }) => { + (state, { origdatablocks }) => { return { ...state, - currentSet: state.currentSet - ? { - ...state.currentSet, - origdatablocks, - } - : state.currentSet, - origDatablocksCount: totalCount ?? 0, + currentSet: { + ...state.currentSet, + origdatablocks, + }, + }; + }, + ), + on( + fromActions.fetchOrigDatablocksCountCompleteAction, + (state, { count }) => { + return { + ...state, + origDatablocksCount: count, }; }, ), - on(fromActions.fetchAttachmentsCompleteAction, (state, { attachments }) => { return { ...state, From 867fbd8e2a3415131539f051c3a71eb03c647962 Mon Sep 17 00:00:00 2001 From: Abdi Mo Date: Mon, 29 Jun 2026 11:04:50 +0200 Subject: [PATCH 29/35] eslint & unit test fix --- src/app/datasets/datafiles/datafiles.component.ts | 5 ++++- .../state-management/actions/datasets.actions.ts | 2 +- .../effects/datasets.effects.spec.ts | 11 +++++++++++ .../state-management/reducers/datasets.reducer.ts | 15 ++++++--------- 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/app/datasets/datafiles/datafiles.component.ts b/src/app/datasets/datafiles/datafiles.component.ts index b57d58cbc9..26944d1e21 100644 --- a/src/app/datasets/datafiles/datafiles.component.ts +++ b/src/app/datasets/datafiles/datafiles.component.ts @@ -48,7 +48,10 @@ import { } from "shared/modules/dynamic-material-table/models/table-row.model"; import { ITableSetting } from "shared/modules/dynamic-material-table/models/table-setting.model"; import { actionMenu } from "shared/modules/dynamic-material-table/utilizes/default-table-settings"; -import { fetchOrigDatablocksAction, fetchOrigDatablocksCountAction } from "state-management/actions/datasets.actions"; +import { + fetchOrigDatablocksAction, + fetchOrigDatablocksCountAction, +} from "state-management/actions/datasets.actions"; @Component({ selector: "datafiles", diff --git a/src/app/state-management/actions/datasets.actions.ts b/src/app/state-management/actions/datasets.actions.ts index f62107c76a..91ed136796 100644 --- a/src/app/state-management/actions/datasets.actions.ts +++ b/src/app/state-management/actions/datasets.actions.ts @@ -87,7 +87,7 @@ export const fetchOrigDatablocksFailedAction = createAction( export const fetchOrigDatablocksCountAction = createAction( "[Dataset] Fetch Origin Datablocks Count", props<{ pid: string; filters?: any }>(), -) +); export const fetchOrigDatablocksCountCompleteAction = createAction( "[Dataset] Fetch Origin Datablocks Count Complete", props<{ count: number }>(), diff --git a/src/app/state-management/effects/datasets.effects.spec.ts b/src/app/state-management/effects/datasets.effects.spec.ts index b0c96aa27f..2b45380c02 100644 --- a/src/app/state-management/effects/datasets.effects.spec.ts +++ b/src/app/state-management/effects/datasets.effects.spec.ts @@ -23,6 +23,7 @@ import { DatasetsService, OutputDatasetObsoleteDto, OrigdatablocksV4Service, + OrigdatablocksPublicV4Service, MetadataKeysV4Service, } from "@scicatproject/scicat-sdk-ts-angular"; import { TestObservable } from "jasmine-marbles/src/test-observables"; @@ -64,6 +65,7 @@ describe("DatasetEffects", () => { let effects: DatasetEffects; let datasetApi: jasmine.SpyObj; let origdatablocksApi: jasmine.SpyObj; + let origdatablocksPublicApi: jasmine.SpyObj; let metadataKeysApi: jasmine.SpyObj; const getConfig = () => ({}); @@ -111,6 +113,14 @@ describe("DatasetEffects", () => { provide: OrigdatablocksV4Service, useValue: jasmine.createSpyObj("origdatablocksService", [ "origDatablocksV4ControllerFindAllFilesV4", + "origDatablocksV4ControllerCountFilesV4", + ]), + }, + { + provide: OrigdatablocksPublicV4Service, + useValue: jasmine.createSpyObj("origdatablocksPublicService", [ + "origDatablocksPublicV4ControllerFindAllFilesPublicV4", + "origDatablocksPublicV4ControllerCountFilesPublicV4", ]), }, { @@ -128,6 +138,7 @@ describe("DatasetEffects", () => { effects = TestBed.inject(DatasetEffects); datasetApi = injectedStub(DatasetsService); origdatablocksApi = injectedStub(OrigdatablocksV4Service); + origdatablocksPublicApi = injectedStub(OrigdatablocksPublicV4Service); metadataKeysApi = injectedStub(MetadataKeysV4Service); }); diff --git a/src/app/state-management/reducers/datasets.reducer.ts b/src/app/state-management/reducers/datasets.reducer.ts index 05a2609d8e..5c2e9fa5f9 100644 --- a/src/app/state-management/reducers/datasets.reducer.ts +++ b/src/app/state-management/reducers/datasets.reducer.ts @@ -80,15 +80,12 @@ const reducer = createReducer( }; }, ), - on( - fromActions.fetchOrigDatablocksCountCompleteAction, - (state, { count }) => { - return { - ...state, - origDatablocksCount: count, - }; - }, - ), + on(fromActions.fetchOrigDatablocksCountCompleteAction, (state, { count }) => { + return { + ...state, + origDatablocksCount: count, + }; + }), on(fromActions.fetchAttachmentsCompleteAction, (state, { attachments }) => { return { ...state, From 2f1f4755740fd38eee35b0a57739b0654d9b0d4d Mon Sep 17 00:00:00 2001 From: Abdi Mo Date: Mon, 29 Jun 2026 15:31:26 +0200 Subject: [PATCH 30/35] added loading spinner for datafiles & related datasets tables --- .../datasets/datafiles/datafiles.component.ts | 3 +- .../related-datasets.component.ts | 77 ++++++++++--------- .../actions/datasets.actions.ts | 4 + .../reducers/datasets.reducer.ts | 31 ++++++++ .../selectors/datasets.selectors.ts | 14 +++- .../state-management/state/datasets.store.ts | 4 + 6 files changed, 96 insertions(+), 37 deletions(-) diff --git a/src/app/datasets/datafiles/datafiles.component.ts b/src/app/datasets/datafiles/datafiles.component.ts index 26944d1e21..093e62088c 100644 --- a/src/app/datasets/datafiles/datafiles.component.ts +++ b/src/app/datasets/datafiles/datafiles.component.ts @@ -237,7 +237,7 @@ export class DatafilesComponent implements OnDestroy, OnInit, AfterViewChecked { ngOnInit() { this.setting = this.tableDefaultSettingsConfig; this.subscriptions.push( - this.vm$.subscribe(({ datablocks, totalCount, dataset }) => { + this.vm$.subscribe(({ datablocks, totalCount, dataset, isLoading }) => { if (dataset) { this.datasetPid = dataset.pid; this.actionItems.datasets = [dataset]; @@ -259,6 +259,7 @@ export class DatafilesComponent implements OnDestroy, OnInit, AfterViewChecked { this.pagination = { ...this.pagination, length: totalCount, + isLoading, }; this.dataSource.next(this.files); diff --git a/src/app/datasets/related-datasets/related-datasets.component.ts b/src/app/datasets/related-datasets/related-datasets.component.ts index 5863e99283..ccb158bc90 100644 --- a/src/app/datasets/related-datasets/related-datasets.component.ts +++ b/src/app/datasets/related-datasets/related-datasets.component.ts @@ -12,6 +12,7 @@ import { selectRelatedDatasetsCurrentPage, selectRelatedDatasetsPageViewModel, selectRelatedDatasetsPerPage, + selectRelatedDatasetsCountIsLoading, } from "state-management/selectors/datasets.selectors"; import { TableField } from "shared/modules/dynamic-material-table/models/table-field.model"; import { BehaviorSubject, Subscription, take, combineLatest } from "rxjs"; @@ -48,6 +49,8 @@ export class RelatedDatasetsComponent implements OnInit, OnDestroy { currentPage$ = this.store.select(selectRelatedDatasetsCurrentPage); datasetsPerPage$ = this.store.select(selectRelatedDatasetsPerPage); + isLoading$ = this.store.select(selectRelatedDatasetsCountIsLoading); + subscription: Subscription; relatedDatasets$ = this.store.select(selectRelatedDatasetsPageViewModel); @@ -142,41 +145,45 @@ export class RelatedDatasetsComponent implements OnInit, OnDestroy { this.selectColumnsWithFetchedSettings$.pipe(take(1)), this.currentPage$, this.datasetsPerPage$, - ]).subscribe(([vm, defaultTableColumns, currentPage, datasetsPerPage]) => { - this.dataSource.next(vm.relatedDatasets); - this.pending = false; - - const defaultConfigColumns = - this.appConfig?.defaultDatasetsListSettings?.columns || []; - - const userTableConfigColumns = - this.datasetsListService.convertSavedDatasetColumns( - defaultTableColumns.columns ?? [], - ); - - this.tableDefaultSettingsConfig.settingList[0].columnSetting = - this.datasetsListService.convertSavedDatasetColumns( - defaultConfigColumns as TableColumn[], - ); - - const tableSettingsConfig = - this.tableConfigService.getTableSettingsConfig( - this.tableName, - this.tableDefaultSettingsConfig, - userTableConfigColumns, - ); - - const paginationConfig = { - pageSizeOptions: [5, 10, 25, 100], - pageIndex: currentPage || 0, - pageSize: datasetsPerPage || 10, - length: vm.relatedDatasetsCount || 0, - }; - - if (tableSettingsConfig?.settingList.length) { - this.initTable(tableSettingsConfig, paginationConfig); - } - }); + this.isLoading$, + ]).subscribe( + ([vm, defaultTableColumns, currentPage, datasetsPerPage, isLoading]) => { + this.dataSource.next(vm.relatedDatasets); + this.pending = false; + + const defaultConfigColumns = + this.appConfig?.defaultDatasetsListSettings?.columns || []; + + const userTableConfigColumns = + this.datasetsListService.convertSavedDatasetColumns( + defaultTableColumns.columns ?? [], + ); + + this.tableDefaultSettingsConfig.settingList[0].columnSetting = + this.datasetsListService.convertSavedDatasetColumns( + defaultConfigColumns as TableColumn[], + ); + + const tableSettingsConfig = + this.tableConfigService.getTableSettingsConfig( + this.tableName, + this.tableDefaultSettingsConfig, + userTableConfigColumns, + ); + + const paginationConfig = { + pageSizeOptions: [5, 10, 25, 100], + pageIndex: currentPage || 0, + pageSize: datasetsPerPage || 10, + length: vm.relatedDatasetsCount || 0, + isLoading, + }; + + if (tableSettingsConfig?.settingList.length) { + this.initTable(tableSettingsConfig, paginationConfig); + } + }, + ); } initTable( diff --git a/src/app/state-management/actions/datasets.actions.ts b/src/app/state-management/actions/datasets.actions.ts index 91ed136796..25b67fd0a4 100644 --- a/src/app/state-management/actions/datasets.actions.ts +++ b/src/app/state-management/actions/datasets.actions.ts @@ -118,6 +118,10 @@ export const fetchRelatedDatasetsFailedAction = createAction( "[Datasets] Fetch Related Datasets Failed", ); +export const fetchRelatedDatasetsCountAction = createAction( + "[Dataset] Fetch Related Datasets Count", +); + export const fetchRelatedDatasetsCountCompleteAction = createAction( "[Dataset] Fetch Related Datasets Count Complete", props<{ count: number }>(), diff --git a/src/app/state-management/reducers/datasets.reducer.ts b/src/app/state-management/reducers/datasets.reducer.ts index 5c2e9fa5f9..6c7c874a42 100644 --- a/src/app/state-management/reducers/datasets.reducer.ts +++ b/src/app/state-management/reducers/datasets.reducer.ts @@ -80,12 +80,27 @@ const reducer = createReducer( }; }, ), + on( + fromActions.fetchOrigDatablocksCountAction, + (state): DatasetState => ({ + ...state, + origDatablocksCountIsLoading: true, + }), + ), on(fromActions.fetchOrigDatablocksCountCompleteAction, (state, { count }) => { return { ...state, origDatablocksCount: count, + origDatablocksCountIsLoading: false, }; }), + on( + fromActions.fetchOrigDatablocksCountFailedAction, + (state): DatasetState => ({ + ...state, + origDatablocksCountIsLoading: false, + }), + ), on(fromActions.fetchAttachmentsCompleteAction, (state, { attachments }) => { return { ...state, @@ -103,11 +118,27 @@ const reducer = createReducer( relatedDatasets, }), ), + on( + fromActions.fetchRelatedDatasetsAction, + (state): DatasetState => ({ + ...state, + relatedDatasetsCountIsLoading: true, + }), + ), on( fromActions.fetchRelatedDatasetsCountCompleteAction, (state, { count }): DatasetState => ({ ...state, relatedDatasetsCount: count, + relatedDatasetsCountIsLoading: false, + }), + ), + + on( + fromActions.fetchRelatedDatasetsCountFailedAction, + (state): DatasetState => ({ + ...state, + relatedDatasetsCountIsLoading: false, }), ), diff --git a/src/app/state-management/selectors/datasets.selectors.ts b/src/app/state-management/selectors/datasets.selectors.ts index 740fd55e96..8cbb8170e5 100644 --- a/src/app/state-management/selectors/datasets.selectors.ts +++ b/src/app/state-management/selectors/datasets.selectors.ts @@ -24,6 +24,16 @@ export const selectDatasetsFacetCountsIsLoading = createSelector( (state) => state.facetCountsIsLoading, ); +export const selectOrigDatablocksCountIsLoading = createSelector( + selectDatasetState, + (state) => state.origDatablocksCountIsLoading, +); + +export const selectRelatedDatasetsCountIsLoading = createSelector( + selectDatasetState, + (state) => state.relatedDatasetsCountIsLoading, +); + export const selectCurrentDataset = createSelector( selectDatasetState, (state) => state.currentSet, @@ -330,9 +340,11 @@ export const selectDatafilesPageViewModel = createSelector( selectCurrentOrigDatablocks, selectCurrentOrigDatablocksCount, selectCurrentDataset, - (datablocks, totalCount, dataset) => ({ + selectOrigDatablocksCountIsLoading, + (datablocks, totalCount, dataset, isLoading) => ({ datablocks, totalCount, dataset, + isLoading, }), ); diff --git a/src/app/state-management/state/datasets.store.ts b/src/app/state-management/state/datasets.store.ts index c6a1bac96c..210709f3af 100644 --- a/src/app/state-management/state/datasets.store.ts +++ b/src/app/state-management/state/datasets.store.ts @@ -26,6 +26,8 @@ export interface DatasetState { facetCounts: FacetCounts; facetCountsIsLoading: boolean; + origDatablocksCountIsLoading: boolean; + relatedDatasetsCountIsLoading: boolean; metadataKeys: string[]; hasPrefilledFilters: boolean; searchTerms: string; @@ -57,6 +59,8 @@ export const initialDatasetState: DatasetState = { facetCounts: {}, facetCountsIsLoading: false, + origDatablocksCountIsLoading: false, + relatedDatasetsCountIsLoading: false, metadataKeys: [], hasPrefilledFilters: false, searchTerms: "", From 66ca1b9f837fed9f0ca54ca18407978cfba0d5ad Mon Sep 17 00:00:00 2001 From: Abdi Mo Date: Mon, 29 Jun 2026 15:43:46 +0200 Subject: [PATCH 31/35] unit test fix --- src/app/state-management/selectors/datasets.selectors.spec.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/app/state-management/selectors/datasets.selectors.spec.ts b/src/app/state-management/selectors/datasets.selectors.spec.ts index 162d303e73..2fda04c5e9 100644 --- a/src/app/state-management/selectors/datasets.selectors.spec.ts +++ b/src/app/state-management/selectors/datasets.selectors.spec.ts @@ -14,6 +14,8 @@ const initialDatasetState: DatasetState = { facetCounts: {}, facetCountsIsLoading: false, + origDatablocksCountIsLoading: false, + relatedDatasetsCountIsLoading: false, metadataKeys: ["test"], hasPrefilledFilters: false, searchTerms: "run", From b8a2d39f73ec1b3c2839ee4c61f14898ff582a06 Mon Sep 17 00:00:00 2001 From: junjiequan Date: Thu, 9 Jul 2026 14:09:19 +0200 Subject: [PATCH 32/35] fix: related datasets show same item on different pages --- src/app/datasets/datasets.module.ts | 1 - .../datasets/related-datasets/related-datasets.component.ts | 2 -- src/app/state-management/effects/datasets.effects.ts | 3 ++- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/app/datasets/datasets.module.ts b/src/app/datasets/datasets.module.ts index 38c72c2888..69dbb15dab 100644 --- a/src/app/datasets/datasets.module.ts +++ b/src/app/datasets/datasets.module.ts @@ -89,7 +89,6 @@ import { IngestorModule } from "../ingestor/ingestor.module"; import { MatExpansionModule } from "@angular/material/expansion"; import { MatBadgeModule } from "@angular/material/badge"; import { TitleCasePipe } from "shared/pipes/title-case.pipe"; -import { ConfigurableActionsModule } from "shared/modules/configurable-actions/configurable-actions.module"; import { OverlayModule } from "@angular/cdk/overlay"; import { SharedConditionModule } from "shared/modules/shared-condition/shared-condition.module"; import { RelationshipsComponent } from "./relationships/relationships.component"; diff --git a/src/app/datasets/related-datasets/related-datasets.component.ts b/src/app/datasets/related-datasets/related-datasets.component.ts index ccb158bc90..f2224c7a4f 100644 --- a/src/app/datasets/related-datasets/related-datasets.component.ts +++ b/src/app/datasets/related-datasets/related-datasets.component.ts @@ -138,8 +138,6 @@ export class RelatedDatasetsComponent implements OnInit, OnDestroy { ) {} ngOnInit(): void { - this.store.dispatch(fetchRelatedDatasetsAction()); - this.subscription = combineLatest([ this.vm$, this.selectColumnsWithFetchedSettings$.pipe(take(1)), diff --git a/src/app/state-management/effects/datasets.effects.ts b/src/app/state-management/effects/datasets.effects.ts index dc78ab8c20..8c2c9760d0 100644 --- a/src/app/state-management/effects/datasets.effects.ts +++ b/src/app/state-management/effects/datasets.effects.ts @@ -297,12 +297,13 @@ export class DatasetEffects { this.relatedDatasetsFilters$, ]), switchMap(([, dataset, filters]) => { + const sortTieBreaker = "pid:asc"; const queryFilter = { where: {}, limits: { skip: filters.skip, limit: filters.limit, - order: filters.sortField, + order: filters.sortField + "," + sortTieBreaker, }, }; if (dataset.type === "raw") { From 997095dc31465246efd54c64374daa303e9b446e Mon Sep 17 00:00:00 2001 From: junjiequan Date: Thu, 9 Jul 2026 14:31:26 +0200 Subject: [PATCH 33/35] fix pagination infinite count loading icon for related datasets and set default page size to 10 --- src/app/datasets/datafiles/datafiles.component.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/app/datasets/datafiles/datafiles.component.ts b/src/app/datasets/datafiles/datafiles.component.ts index 093e62088c..3ca7c10162 100644 --- a/src/app/datasets/datafiles/datafiles.component.ts +++ b/src/app/datasets/datafiles/datafiles.component.ts @@ -148,7 +148,7 @@ export class DatafilesComponent implements OnDestroy, OnInit, AfterViewChecked { pagination: TablePagination = { pageSizeOptions: [5, 10, 25, 50, 100], pageIndex: 0, - pageSize: 5, + pageSize: 10, length: 0, }; @@ -242,7 +242,6 @@ export class DatafilesComponent implements OnDestroy, OnInit, AfterViewChecked { this.datasetPid = dataset.pid; this.actionItems.datasets = [dataset]; } - if (datablocks) { this.totalFileSize = 0; const files: DataFiles_File[] = []; @@ -256,12 +255,6 @@ export class DatafilesComponent implements OnDestroy, OnInit, AfterViewChecked { this.count = files.length; this.files = files; - this.pagination = { - ...this.pagination, - length: totalCount, - isLoading, - }; - this.dataSource.next(this.files); this.tooLargeFile = this.hasTooLargeFiles(this.files); @@ -269,6 +262,11 @@ export class DatafilesComponent implements OnDestroy, OnInit, AfterViewChecked { this.actionItems.datasets[0].files = files; } } + this.pagination = { + ...this.pagination, + length: totalCount, + isLoading, + }; }), ); } From 3a1360def1ca1013850a233136422a5e72083438 Mon Sep 17 00:00:00 2001 From: junjiequan Date: Thu, 9 Jul 2026 14:39:47 +0200 Subject: [PATCH 34/35] lint fix --- .../reducers/datasets.reducer.ts | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/src/app/state-management/reducers/datasets.reducer.ts b/src/app/state-management/reducers/datasets.reducer.ts index 34d27a2f81..ecdbc174af 100644 --- a/src/app/state-management/reducers/datasets.reducer.ts +++ b/src/app/state-management/reducers/datasets.reducer.ts @@ -74,13 +74,10 @@ const reducer = createReducer( }; }, ), - on( - fromActions.fetchOrigDatablocksCountAction, - (state): DatasetState => ({ - ...state, - origDatablocksCountIsLoading: true, - }), - ), + on(fromActions.fetchOrigDatablocksCountAction, (state): DatasetState => ({ + ...state, + origDatablocksCountIsLoading: true, + })), on(fromActions.fetchOrigDatablocksCountCompleteAction, (state, { count }) => { return { ...state, @@ -112,13 +109,10 @@ const reducer = createReducer( relatedDatasets, }), ), - on( - fromActions.fetchRelatedDatasetsAction, - (state): DatasetState => ({ - ...state, - relatedDatasetsCountIsLoading: true, - }), - ), + on(fromActions.fetchRelatedDatasetsAction, (state): DatasetState => ({ + ...state, + relatedDatasetsCountIsLoading: true, + })), on( fromActions.fetchRelatedDatasetsCountCompleteAction, (state, { count }): DatasetState => ({ From 93beb10320b12ee884befdd9a59af19ad854cd41 Mon Sep 17 00:00:00 2001 From: junjiequan Date: Thu, 9 Jul 2026 14:40:54 +0200 Subject: [PATCH 35/35] check user.id instead of user to prevent empty object matc --- src/app/state-management/effects/datasets.effects.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/state-management/effects/datasets.effects.ts b/src/app/state-management/effects/datasets.effects.ts index 8c2c9760d0..0d3030638a 100644 --- a/src/app/state-management/effects/datasets.effects.ts +++ b/src/app/state-management/effects/datasets.effects.ts @@ -220,7 +220,7 @@ export class DatasetEffects { }, }; - const apiCall$ = user + const apiCall$ = user?.id ? this.origdatablocksService.origDatablocksV4ControllerFindAllFilesV4( JSON.stringify(filter), ) @@ -251,7 +251,7 @@ export class DatasetEffects { }, }; - const apiCall$ = user + const apiCall$ = user?.id ? this.origdatablocksService.origDatablocksV4ControllerCountFilesV4( JSON.stringify(filter), )