From a8fe1352ce442465f4c7071d6d22e9a8961e2a32 Mon Sep 17 00:00:00 2001 From: martintrajanovski Date: Thu, 20 Mar 2025 15:40:16 +0100 Subject: [PATCH 1/7] refactor proposal-dashboard table before replacing the instruments table --- .../proposal-dashboard.component.html | 7 - .../proposal-dashboard.component.ts | 185 +++++++----------- .../cores/table.core.directive.ts | 8 +- 3 files changed, 77 insertions(+), 123 deletions(-) diff --git a/src/app/proposals/proposal-dashboard/proposal-dashboard.component.html b/src/app/proposals/proposal-dashboard/proposal-dashboard.component.html index fc59f5856..aec823181 100644 --- a/src/app/proposals/proposal-dashboard/proposal-dashboard.component.html +++ b/src/app/proposals/proposal-dashboard/proposal-dashboard.component.html @@ -1,19 +1,12 @@ []; - - direction: Direction = "ltr"; - - showReloadData = true; - - rowHeight = 50; + columns: TableField[]; pending = true; @@ -121,26 +113,20 @@ export class ProposalDashboardComponent implements OnInit, OnDestroy { paginationMode: TablePaginationMode = "server-side"; - showNoData = true; - dataSource: BehaviorSubject = new BehaviorSubject< ProposalClass[] >([]); pagination: TablePagination = {}; - stickyHeader = true; - - printConfig: PrintConfig = {}; - - showProgress = true; - rowSelectionMode: TableSelectionMode = "none"; globalTextSearch = ""; defaultPageSize = 10; + defaultPageSizeOptions = [5, 10, 25, 100]; + tablesSettings: object; constructor( @@ -150,59 +136,78 @@ export class ProposalDashboardComponent implements OnInit, OnDestroy { ) {} ngOnInit(): void { - const initialQueryParams = this.route.snapshot.queryParams; - if (initialQueryParams.textSearch) { - this.globalTextSearch = initialQueryParams.textSearch; - } - - this.store.dispatch( - fetchProposalsAction({ - limit: initialQueryParams.pageSize || this.defaultPageSize, - skip: initialQueryParams.pageIndex * initialQueryParams.pageSize, - search: initialQueryParams.textSearch, - sortColumn: initialQueryParams.sortColumn, - sortDirection: initialQueryParams.sortDirection, - }), + this.subscriptions.push( + this.proposalsWithCountAndTableSettings$.subscribe( + ({ proposals, count, tablesSettings }) => { + this.tablesSettings = tablesSettings; + this.dataSource.next(proposals); + this.pending = false; + + const savedTableConfigColumns = + tablesSettings?.[this.tableName]?.columns; + const tableSort = this.getTableSort(); + const pagginationConfig = this.getTablePaginationConfig(count); + + const tableSettingsConfig = getTableSettingsConfig( + this.tableName, + tableDefaultSettingsConfig, + savedTableConfigColumns, + tableSort, + ); + + if (tableSettingsConfig?.settingList.length) { + this.initTable(tableSettingsConfig, pagginationConfig); + } + }, + ), ); - this.subscription = this.proposalsWithCountAndTableSettings$.subscribe( - ({ proposals, count, tablesSettings }) => { - const queryParams = this.route.snapshot.queryParams; + this.subscriptions.push( + this.route.queryParams.subscribe((queryParams) => { + this.pending = true; + const limit = queryParams.pageSize + ? +queryParams.pageSize + : this.defaultPageSize; + const skip = queryParams.pageIndex ? +queryParams.pageIndex * limit : 0; if (queryParams.textSearch) { this.globalTextSearch = queryParams.textSearch; } - this.tablesSettings = tablesSettings; - this.dataSource.next(proposals); - this.pending = false; - let tableSort: ITableSetting["tableSort"]; - if (queryParams.sortDirection && queryParams.sortColumn) { - tableSort = { + this.store.dispatch( + fetchProposalsAction({ + limit: limit, + skip: skip, + search: queryParams.textSearch, sortColumn: queryParams.sortColumn, sortDirection: queryParams.sortDirection, - }; - } - const savedTableConfig = tablesSettings?.[this.tableName]; - - const tableSettingsConfig = getTableSettingsConfig( - this.tableName, - tableDefaultSettingsConfig, - savedTableConfig?.columns, - tableSort, + }), ); + }), + ); + } - const pagginationConfig = { - pageSizeOptions: [5, 10, 25, 100], - pageIndex: queryParams.pageIndex, - pageSize: queryParams.pageSize || this.defaultPageSize, - length: count, - }; + getTableSort(): ITableSetting["tableSort"] { + const { queryParams } = this.route.snapshot; - if (tableSettingsConfig?.settingList.length) { - this.initTable(tableSettingsConfig, pagginationConfig); - } - }, - ); + if (queryParams.sortDirection && queryParams.sortColumn) { + return { + sortColumn: queryParams.sortColumn, + sortDirection: queryParams.sortDirection, + }; + } + + return null; + } + + getTablePaginationConfig(dataCount = 0): TablePagination { + const { queryParams } = this.route.snapshot; + + return { + pageSizeOptions: this.defaultPageSizeOptions, + pageIndex: queryParams.pageIndex, + pageSize: queryParams.pageSize || this.defaultPageSize, + length: dataCount, + }; } initTable( @@ -219,36 +224,16 @@ export class ProposalDashboardComponent implements OnInit, OnDestroy { } onPaginationChange(pagination: TablePagination) { - this.pending = true; - const queryParams: Record = { - pageIndex: pagination.pageIndex, - pageSize: pagination.pageSize, - }; - const { textSearch, sortColumn, sortDirection } = - this.route.snapshot.queryParams; - - if (textSearch) { - queryParams.textSearch = textSearch; - } this.router.navigate([], { - queryParams, + queryParams: { + pageIndex: pagination.pageIndex, + pageSize: pagination.pageSize, + }, queryParamsHandling: "merge", }); - - this.store.dispatch( - fetchProposalsAction({ - limit: pagination.pageSize, - skip: pagination.pageIndex * pagination.pageSize, - search: queryParams.textSearch as string, - sortColumn: sortColumn, - sortDirection: sortDirection, - }), - ); } onGlobalTextSearchChange(text: string) { - this.pending = true; - this.pagination.pageIndex = 0; this.router.navigate([], { queryParams: { textSearch: text || undefined, @@ -256,18 +241,6 @@ export class ProposalDashboardComponent implements OnInit, OnDestroy { }, queryParamsHandling: "merge", }); - - const { sortColumn, sortDirection } = this.route.snapshot.queryParams; - - this.store.dispatch( - fetchProposalsAction({ - limit: this.pagination.pageSize, - skip: this.pagination.pageIndex * this.pagination.pageSize, - search: text, - sortColumn: sortColumn, - sortDirection: sortDirection, - }), - ); } saveTableSettings(setting: ITableSetting) { @@ -316,7 +289,7 @@ export class ProposalDashboardComponent implements OnInit, OnDestroy { onTableEvent({ event, sender }: ITableEvent) { if (event === TableEventType.SortChanged) { const { active: sortColumn, direction: sortDirection } = sender as Sort; - this.pending = true; + this.router.navigate([], { queryParams: { pageIndex: 0, @@ -325,24 +298,12 @@ export class ProposalDashboardComponent implements OnInit, OnDestroy { }, queryParamsHandling: "merge", }); - - const queryParams = this.route.snapshot.queryParams; - - this.store.dispatch( - fetchProposalsAction({ - limit: queryParams.pageSize, - skip: - queryParams.pageIndex * - (queryParams.pageSize || this.defaultPageSize), - search: queryParams.textSearch, - sortColumn: sortColumn, - sortDirection: sortDirection, - }), - ); } } ngOnDestroy() { - this.subscription.unsubscribe(); + this.subscriptions.forEach((sub) => { + sub.unsubscribe(); + }); } } diff --git a/src/app/shared/modules/dynamic-material-table/cores/table.core.directive.ts b/src/app/shared/modules/dynamic-material-table/cores/table.core.directive.ts index 5e7cc5562..f85714f50 100644 --- a/src/app/shared/modules/dynamic-material-table/cores/table.core.directive.ts +++ b/src/app/shared/modules/dynamic-material-table/cores/table.core.directive.ts @@ -59,15 +59,15 @@ export class TableCoreDirective { @Input() defaultWidth: number = null; @Input() minWidth = 120; @Input() printConfig: PrintConfig = {}; - @Input() sticky: boolean; + @Input() sticky = true; @Input() pending: boolean; - @Input() rowHeight = 48; + @Input() rowHeight = 50; @Input() headerHeight = 56; @Input() footerHeight = 48; @Input() headerEnable = true; @Input() footerEnable = false; - @Input() showNoData: boolean; - @Input() showReload: boolean; + @Input() showNoData = true; + @Input() showReload = true; @Input() showGlobalTextSearch = true; @Input() globalTextSearch = ""; @Input() globalTextSearchPlaceholder = "Search"; From 16d74d7741c3f733cfc500db72b7801def9a65df Mon Sep 17 00:00:00 2001 From: martintrajanovski Date: Fri, 21 Mar 2025 08:58:58 +0100 Subject: [PATCH 2/7] fix typo --- .../proposal-dashboard/proposal-dashboard.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/proposals/proposal-dashboard/proposal-dashboard.component.ts b/src/app/proposals/proposal-dashboard/proposal-dashboard.component.ts index 0a9a015cc..b8bebc078 100644 --- a/src/app/proposals/proposal-dashboard/proposal-dashboard.component.ts +++ b/src/app/proposals/proposal-dashboard/proposal-dashboard.component.ts @@ -146,7 +146,7 @@ export class ProposalDashboardComponent implements OnInit, OnDestroy { const savedTableConfigColumns = tablesSettings?.[this.tableName]?.columns; const tableSort = this.getTableSort(); - const pagginationConfig = this.getTablePaginationConfig(count); + const paginationConfig = this.getTablePaginationConfig(count); const tableSettingsConfig = getTableSettingsConfig( this.tableName, @@ -156,7 +156,7 @@ export class ProposalDashboardComponent implements OnInit, OnDestroy { ); if (tableSettingsConfig?.settingList.length) { - this.initTable(tableSettingsConfig, pagginationConfig); + this.initTable(tableSettingsConfig, paginationConfig); } }, ), From c85d4e92d7dbcff39083e3d82d33db753fd6f1dd Mon Sep 17 00:00:00 2001 From: martintrajanovski Date: Fri, 21 Mar 2025 15:57:54 +0100 Subject: [PATCH 3/7] feat: replace instruments table with the new dynamic material table --- .../instruments-dashboard.component.html | 35 +-- .../instruments-dashboard.component.spec.ts | 115 ++++---- .../instruments-dashboard.component.ts | 271 +++++++++++++++--- .../proposal-dashboard.component.spec.ts | 2 - .../actions/instruments.actions.spec.ts | 30 +- .../actions/instruments.actions.ts | 17 +- .../effects/instruments.effects.spec.ts | 111 +------ .../effects/instruments.effects.ts | 39 ++- .../effects/proposals.effects.ts | 2 +- .../reducers/instruments.reducer.spec.ts | 26 -- .../reducers/instruments.reducer.ts | 18 -- .../selectors/instruments.selectors.spec.ts | 16 +- .../selectors/instruments.selectors.ts | 19 +- .../selectors/user.selectors.spec.ts | 2 +- 14 files changed, 364 insertions(+), 339 deletions(-) diff --git a/src/app/instruments/instruments-dashboard/instruments-dashboard.component.html b/src/app/instruments/instruments-dashboard/instruments-dashboard.component.html index 4fda02df8..b23412ca6 100644 --- a/src/app/instruments/instruments-dashboard/instruments-dashboard.component.html +++ b/src/app/instruments/instruments-dashboard/instruments-dashboard.component.html @@ -1,17 +1,18 @@ - -
-
- -
-
-
+ + diff --git a/src/app/instruments/instruments-dashboard/instruments-dashboard.component.spec.ts b/src/app/instruments/instruments-dashboard/instruments-dashboard.component.spec.ts index 251df08d1..246a6113d 100644 --- a/src/app/instruments/instruments-dashboard/instruments-dashboard.component.spec.ts +++ b/src/app/instruments/instruments-dashboard/instruments-dashboard.component.spec.ts @@ -1,28 +1,20 @@ -import { - ComponentFixture, - TestBed, - inject, - waitForAsync, -} from "@angular/core/testing"; +import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; import { InstrumentsDashboardComponent } from "./instruments-dashboard.component"; -import { MockStore, mockInstrument } from "shared/MockStubs"; +import { MockActivatedRoute, mockInstrument } from "shared/MockStubs"; import { NO_ERRORS_SCHEMA } from "@angular/core"; import { provideMockStore } from "@ngrx/store/testing"; -import { selectInstrumentsDashboardPageViewModel } from "state-management/selectors/instruments.selectors"; -import { Store } from "@ngrx/store"; +import { selectInstrumentsWithCountAndTableSettings } from "state-management/selectors/instruments.selectors"; import { SharedScicatFrontendModule } from "shared/shared.module"; -import { JsonHeadPipe } from "shared/pipes/json-head.pipe"; -import { - PageChangeEvent, - SortChangeEvent, -} from "shared/modules/table/table.component"; -import { - changePageAction, - sortByColumnAction, -} from "state-management/actions/instruments.actions"; -import { Router } from "@angular/router"; +import { SortChangeEvent } from "shared/modules/table/table.component"; +import { ActivatedRoute, Router } from "@angular/router"; import { FlexLayoutModule } from "@ngbracket/ngx-layout"; +import { + RowEventType, + TableEventType, +} from "shared/modules/dynamic-material-table/models/table-row.model"; +import { TablePagination } from "shared/modules/dynamic-material-table/models/table-pagination.model"; +import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; describe("InstrumentsDashboardComponent", () => { let component: InstrumentsDashboardComponent; @@ -30,26 +22,27 @@ describe("InstrumentsDashboardComponent", () => { const router = { navigateByUrl: jasmine.createSpy("navigateByUrl"), + navigate: jasmine.createSpy("navigate"), }; - let store: MockStore; - let dispatchSpy; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ schemas: [NO_ERRORS_SCHEMA], declarations: [InstrumentsDashboardComponent], - imports: [FlexLayoutModule, SharedScicatFrontendModule], + imports: [ + FlexLayoutModule, + SharedScicatFrontendModule, + BrowserAnimationsModule, + ], providers: [ - JsonHeadPipe, provideMockStore({ selectors: [ { - selector: selectInstrumentsDashboardPageViewModel, + selector: selectInstrumentsWithCountAndTableSettings, value: { instruments: [], - currentPage: 0, - instrumentsCount: 100, - instrumentsPerPage: 25, + count: 100, + tableSettings: {}, }, }, ], @@ -58,7 +51,10 @@ describe("InstrumentsDashboardComponent", () => { }); TestBed.overrideComponent(InstrumentsDashboardComponent, { set: { - providers: [{ provide: Router, useValue: router }], + providers: [ + { provide: Router, useValue: router }, + { provide: ActivatedRoute, useClass: MockActivatedRoute }, + ], }, }); TestBed.compileComponents(); @@ -70,10 +66,6 @@ describe("InstrumentsDashboardComponent", () => { fixture.detectChanges(); }); - beforeEach(inject([Store], (mockStore: MockStore) => { - store = mockStore; - })); - afterEach(() => { fixture.destroy(); }); @@ -83,43 +75,53 @@ describe("InstrumentsDashboardComponent", () => { }); describe("#onPageChange()", () => { - it("should dispatch a changePageAction", () => { - dispatchSpy = spyOn(store, "dispatch"); - - const event: PageChangeEvent = { + it("should dispatch a fetchInstrumentsAction", () => { + router.navigate.calls.reset(); + const event: TablePagination = { pageIndex: 0, pageSize: 25, length: 100, }; - const { pageIndex: page, pageSize: limit } = event; + const { pageIndex, pageSize } = event; - component.onPageChange(event); + component.onPaginationChange(event); - expect(dispatchSpy).toHaveBeenCalledTimes(1); - expect(dispatchSpy).toHaveBeenCalledWith( - changePageAction({ page, limit }), - ); + expect(router.navigate).toHaveBeenCalledTimes(1); + expect(router.navigate).toHaveBeenCalledWith([], { + queryParams: { + pageIndex, + pageSize, + }, + queryParamsHandling: "merge", + }); }); }); - describe("#onSortChange()", () => { - it("should dispatch a sortByColumnAction", () => { - dispatchSpy = spyOn(store, "dispatch"); - + describe("#onTableEvent()", () => { + it("should dispatch a fetchInstrumentsAction", () => { + router.navigate.calls.reset(); const event: SortChangeEvent = { active: "test", direction: "asc", }; - const { active: column, direction } = event; - - component.onSortChange(event); - - expect(dispatchSpy).toHaveBeenCalledTimes(1); - expect(dispatchSpy).toHaveBeenCalledWith( - sortByColumnAction({ column, direction }), - ); + const { active: sortColumn, direction: sortDirection } = event; + + component.onTableEvent({ + event: TableEventType.SortChanged, + sender: event, + }); + + expect(router.navigate).toHaveBeenCalledTimes(1); + expect(router.navigate).toHaveBeenCalledWith([], { + queryParams: { + pageIndex: 0, + sortDirection: sortDirection || undefined, + sortColumn: sortDirection ? sortColumn : undefined, + }, + queryParamsHandling: "merge", + }); }); }); @@ -128,7 +130,10 @@ describe("InstrumentsDashboardComponent", () => { const instrument = mockInstrument; const pid = encodeURIComponent(instrument.pid); - component.onRowClick(instrument); + component.onRowClick({ + event: RowEventType.RowClick, + sender: { row: instrument }, + }); expect(router.navigateByUrl).toHaveBeenCalledTimes(1); expect(router.navigateByUrl).toHaveBeenCalledWith("/instruments/" + pid); diff --git a/src/app/instruments/instruments-dashboard/instruments-dashboard.component.ts b/src/app/instruments/instruments-dashboard/instruments-dashboard.component.ts index 53f0f2d69..639f95924 100644 --- a/src/app/instruments/instruments-dashboard/instruments-dashboard.component.ts +++ b/src/app/instruments/instruments-dashboard/instruments-dashboard.component.ts @@ -1,65 +1,256 @@ -import { Component, OnInit } from "@angular/core"; +import { Component, OnDestroy, OnInit } from "@angular/core"; import { Store } from "@ngrx/store"; import { Instrument } from "@scicatproject/scicat-sdk-ts-angular"; +import { ActivatedRoute, Router } from "@angular/router"; import { - fetchInstrumentsAction, - changePageAction, - sortByColumnAction, -} from "state-management/actions/instruments.actions"; -import { selectInstrumentsDashboardPageViewModel } from "state-management/selectors/instruments.selectors"; + ITableSetting, + TableSettingEventType, +} from "shared/modules/dynamic-material-table/models/table-setting.model"; import { - TableColumn, - PageChangeEvent, - SortChangeEvent, -} from "shared/modules/table/table.component"; -import { map } from "rxjs/operators"; -import { JsonHeadPipe } from "shared/pipes/json-head.pipe"; -import { Router } from "@angular/router"; + actionMenu, + getTableSettingsConfig, +} from "shared/modules/dynamic-material-table/utilizes/default-table-config"; +import { BehaviorSubject, Subscription } from "rxjs"; +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, + ITableEvent, + RowEventType, + TableEventType, + TableSelectionMode, +} from "shared/modules/dynamic-material-table/models/table-row.model"; +import { fetchInstrumentsAction } from "state-management/actions/instruments.actions"; +import { updateUserSettingsAction } from "state-management/actions/user.actions"; +import { Sort } from "@angular/material/sort"; +import { selectInstrumentsWithCountAndTableSettings } from "state-management/selectors/instruments.selectors"; + +const tableDefaultSettingsConfig: ITableSetting = { + visibleActionMenu: actionMenu, + settingList: [ + { + visibleActionMenu: actionMenu, + isDefaultSetting: true, + isCurrentSetting: true, + columnSetting: [ + { + name: "uniqueName", + icon: "scanner", + }, + { + name: "name", + icon: "fingerprint", + }, + ], + }, + ], + rowStyle: { + "border-bottom": "1px solid #d2d2d2", + }, +}; @Component({ selector: "app-instruments-dashboard", templateUrl: "./instruments-dashboard.component.html", styleUrls: ["./instruments-dashboard.component.scss"], }) -export class InstrumentsDashboardComponent implements OnInit { - vm$ = this.store.select(selectInstrumentsDashboardPageViewModel).pipe( - map((vm) => ({ - ...vm, - instruments: vm.instruments.map((instrument) => ({ - ...instrument, - customMetadata: this.jsonHeadPipe.transform(instrument.customMetadata), - })), - })), +export class InstrumentsDashboardComponent implements OnInit, OnDestroy { + instrumentsWithCountAndTableSettings$ = this.store.select( + selectInstrumentsWithCountAndTableSettings, + ); + + subscriptions: Subscription[] = []; + + tableName = "instrumentsTable"; + + columns: TableField[]; + + pending = true; + + setting: ITableSetting = {}; + + paginationMode: TablePaginationMode = "server-side"; + + dataSource: BehaviorSubject = new BehaviorSubject( + [], ); - tablePaginate = true; - tableColumns: TableColumn[] = [ - { name: "uniqueName", icon: "scanner", sort: true, inList: true }, - { name: "name", icon: "fingerprint", sort: true, inList: true }, - ]; + pagination: TablePagination = {}; + + rowSelectionMode: TableSelectionMode = "none"; + + showGlobalTextSearch = false; + + defaultPageSize = 10; + + defaultPageSizeOptions = [5, 10, 25, 100]; + + tablesSettings: object; constructor( - private jsonHeadPipe: JsonHeadPipe, - private router: Router, private store: Store, + private router: Router, + private route: ActivatedRoute, ) {} - onPageChange(event: PageChangeEvent): void { - const { pageIndex: page, pageSize: limit } = event; - this.store.dispatch(changePageAction({ page, limit })); + ngOnInit(): void { + this.subscriptions.push( + this.instrumentsWithCountAndTableSettings$.subscribe( + ({ instruments, count, tablesSettings }) => { + this.tablesSettings = tablesSettings; + this.dataSource.next(instruments); + this.pending = false; + + const savedTableConfigColumns = + tablesSettings?.[this.tableName]?.columns; + const tableSort = this.getTableSort(); + const paginationConfig = this.getTablePaginationConfig(count); + + const tableSettingsConfig = getTableSettingsConfig( + this.tableName, + tableDefaultSettingsConfig, + savedTableConfigColumns, + tableSort, + ); + + if (tableSettingsConfig?.settingList.length) { + this.initTable(tableSettingsConfig, paginationConfig); + } + }, + ), + ); + + this.subscriptions.push( + this.route.queryParams.subscribe((queryParams) => { + this.pending = true; + const limit = queryParams.pageSize + ? +queryParams.pageSize + : this.defaultPageSize; + const skip = queryParams.pageIndex ? +queryParams.pageIndex * limit : 0; + + this.store.dispatch( + fetchInstrumentsAction({ + limit: limit, + skip: skip, + sortColumn: queryParams.sortColumn, + sortDirection: queryParams.sortDirection, + }), + ); + }), + ); + } + + getTableSort(): ITableSetting["tableSort"] { + const { queryParams } = this.route.snapshot; + + if (queryParams.sortDirection && queryParams.sortColumn) { + return { + sortColumn: queryParams.sortColumn, + sortDirection: queryParams.sortDirection, + }; + } + + return null; + } + + getTablePaginationConfig(dataCount = 0): TablePagination { + const { queryParams } = this.route.snapshot; + + return { + pageSizeOptions: this.defaultPageSizeOptions, + pageIndex: queryParams.pageIndex, + pageSize: queryParams.pageSize || this.defaultPageSize, + length: dataCount, + }; + } + + initTable( + settingConfig: ITableSetting, + paginationConfig: TablePagination, + ): void { + const currentColumnSetting = settingConfig.settingList.find( + (s) => s.isCurrentSetting, + )?.columnSetting; + + this.columns = currentColumnSetting; + this.setting = settingConfig; + this.pagination = paginationConfig; } - onSortChange(event: SortChangeEvent): void { - const { active: column, direction } = event; - this.store.dispatch(sortByColumnAction({ column, direction })); + onPaginationChange(pagination: TablePagination) { + this.router.navigate([], { + queryParams: { + pageIndex: pagination.pageIndex, + pageSize: pagination.pageSize, + }, + queryParamsHandling: "merge", + }); } - onRowClick(instrument: Instrument): void { - const pid = encodeURIComponent(instrument.pid); - this.router.navigateByUrl("/instruments/" + pid); + saveTableSettings(setting: ITableSetting) { + this.pending = true; + const columnsSetting = setting.columnSetting.map((column) => { + const { name, display, index, width } = column; + + return { name, display, index, width }; + }); + + const tablesSettings = { + ...this.tablesSettings, + [setting.settingName || this.tableName]: { + columns: columnsSetting, + }, + }; + + this.store.dispatch( + updateUserSettingsAction({ + property: { + tablesSettings, + }, + }), + ); + } + + onSettingChange(event: { + type: TableSettingEventType; + setting: ITableSetting; + }) { + if ( + event.type === TableSettingEventType.save || + event.type === TableSettingEventType.create + ) { + this.saveTableSettings(event.setting); + } + } + + onRowClick(event: IRowEvent) { + if (event.event === RowEventType.RowClick) { + const id = encodeURIComponent(event.sender.row.pid); + this.router.navigateByUrl("/instruments/" + id); + } + } + + onTableEvent({ event, sender }: ITableEvent) { + if (event === TableEventType.SortChanged) { + const { active: sortColumn, direction: sortDirection } = sender as Sort; + + this.router.navigate([], { + queryParams: { + pageIndex: 0, + sortDirection: sortDirection || undefined, + sortColumn: sortDirection ? sortColumn : undefined, + }, + queryParamsHandling: "merge", + }); + } } - ngOnInit() { - this.store.dispatch(fetchInstrumentsAction()); + ngOnDestroy() { + this.subscriptions.forEach((sub) => { + sub.unsubscribe(); + }); } } diff --git a/src/app/proposals/proposal-dashboard/proposal-dashboard.component.spec.ts b/src/app/proposals/proposal-dashboard/proposal-dashboard.component.spec.ts index e04620099..c2eba8c9b 100644 --- a/src/app/proposals/proposal-dashboard/proposal-dashboard.component.spec.ts +++ b/src/app/proposals/proposal-dashboard/proposal-dashboard.component.spec.ts @@ -3,9 +3,7 @@ import { ActivatedRoute, Router } from "@angular/router"; import { AppConfigService } from "app-config.service"; import { MockActivatedRoute, - MockAppConfigService, MockRouter, - MockScicatDataSource, MockStore, MockDatasetApi, MockHttp, diff --git a/src/app/state-management/actions/instruments.actions.spec.ts b/src/app/state-management/actions/instruments.actions.spec.ts index ee65a919f..312af5b77 100644 --- a/src/app/state-management/actions/instruments.actions.spec.ts +++ b/src/app/state-management/actions/instruments.actions.spec.ts @@ -6,7 +6,7 @@ describe("Instrument Actions", () => { describe("fetchInstrumentsAction", () => { it("should create an action", () => { - const action = fromActions.fetchInstrumentsAction(); + const action = fromActions.fetchInstrumentsAction({}); expect({ ...action }).toEqual({ type: "[Instrument] Fetch Instruments" }); }); @@ -139,34 +139,6 @@ describe("Instrument Actions", () => { }); }); - describe("changePageAction", () => { - it("should create an action", () => { - const page = 0; - const limit = 25; - const action = fromActions.changePageAction({ page, limit }); - - expect({ ...action }).toEqual({ - type: "[Instrument] Change Page", - page, - limit, - }); - }); - }); - - describe("changePageAction", () => { - it("should create an action", () => { - const column = "test"; - const direction = "desc"; - const action = fromActions.sortByColumnAction({ column, direction }); - - expect({ ...action }).toEqual({ - type: "[Instrument] Sort By Column", - column, - direction, - }); - }); - }); - describe("clearInstrumentsStateAction", () => { it("should create an action", () => { const action = fromActions.clearInstrumentsStateAction(); diff --git a/src/app/state-management/actions/instruments.actions.ts b/src/app/state-management/actions/instruments.actions.ts index c10782629..4320d41d2 100644 --- a/src/app/state-management/actions/instruments.actions.ts +++ b/src/app/state-management/actions/instruments.actions.ts @@ -3,6 +3,13 @@ import { Instrument } from "@scicatproject/scicat-sdk-ts-angular"; export const fetchInstrumentsAction = createAction( "[Instrument] Fetch Instruments", + props<{ + skip?: number; + limit?: number; + search?: string; + sortDirection?: string; + sortColumn?: string; + }>(), ); export const fetchInstrumentsCompleteAction = createAction( "[Instrument] Fetch Instruments Complete", @@ -45,16 +52,6 @@ export const saveCustomMetadataFailedAction = createAction( "[Instrument] Save Custom Metadata Failed", ); -export const changePageAction = createAction( - "[Instrument] Change Page", - props<{ page: number; limit: number }>(), -); - -export const sortByColumnAction = createAction( - "[Instrument] Sort By Column", - props<{ column: string; direction: string }>(), -); - export const clearInstrumentsStateAction = createAction( "[Instrument] Clear State", ); diff --git a/src/app/state-management/effects/instruments.effects.spec.ts b/src/app/state-management/effects/instruments.effects.spec.ts index dea4a768e..b64469c01 100644 --- a/src/app/state-management/effects/instruments.effects.spec.ts +++ b/src/app/state-management/effects/instruments.effects.spec.ts @@ -38,6 +38,7 @@ describe("InstrumentEffects", () => { provide: InstrumentsService, useValue: jasmine.createSpyObj("instrumentApi", [ "instrumentsControllerFindAll", + "instrumentsControllerCount", "instrumentsControllerFindById", "instrumentsControllerUpdate", ]), @@ -55,7 +56,7 @@ describe("InstrumentEffects", () => { describe("fetchInstruments$", () => { describe("ofType fetchInstrumentAction", () => { it("should result in a fetchInstrumentsCompleteAction and a fetchCountAction", () => { - const action = fromActions.fetchInstrumentsAction(); + const action = fromActions.fetchInstrumentsAction({}); const outcome1 = fromActions.fetchInstrumentsCompleteAction({ instruments, }); @@ -70,102 +71,7 @@ describe("InstrumentEffects", () => { }); it("should result in a fetchInstrumentsFailedAction", () => { - const action = fromActions.fetchInstrumentsAction(); - const outcome = fromActions.fetchInstrumentsFailedAction(); - - actions = hot("-a", { a: action }); - const response = cold("-#", {}); - instrumentApi.instrumentsControllerFindAll.and.returnValue(response); - - const expected = cold("--b", { b: outcome }); - expect(effects.fetchInstruments$).toBeObservable(expected); - }); - }); - - describe("ofType changePageAction", () => { - const page = 0; - const limit = 25; - - it("should result in a fetchInstrumentsCompleteAction and a fetchCountAction", () => { - const action = fromActions.changePageAction({ page, limit }); - const outcome1 = fromActions.fetchInstrumentsCompleteAction({ - instruments, - }); - const outcome2 = fromActions.fetchCountAction(); - - actions = hot("-a", { a: action }); - const response = cold("-a|", { a: instruments }); - instrumentApi.instrumentsControllerFindAll.and.returnValue(response); - - const expected = cold("--(bc)", { b: outcome1, c: outcome2 }); - expect(effects.fetchInstruments$).toBeObservable(expected); - }); - - it("should result in a fetchInstrumentsFailedAction", () => { - const action = fromActions.changePageAction({ page, limit }); - const outcome = fromActions.fetchInstrumentsFailedAction(); - - actions = hot("-a", { a: action }); - const response = cold("-#", {}); - instrumentApi.instrumentsControllerFindAll.and.returnValue(response); - - const expected = cold("--b", { b: outcome }); - expect(effects.fetchInstruments$).toBeObservable(expected); - }); - }); - describe("ofType changePageAction", () => { - const page = 0; - const limit = 25; - - it("should result in a fetchInstrumentsCompleteAction and a fetchCountAction", () => { - const action = fromActions.changePageAction({ page, limit }); - const outcome1 = fromActions.fetchInstrumentsCompleteAction({ - instruments, - }); - const outcome2 = fromActions.fetchCountAction(); - - actions = hot("-a", { a: action }); - const response = cold("-a|", { a: instruments }); - instrumentApi.instrumentsControllerFindAll.and.returnValue(response); - - const expected = cold("--(bc)", { b: outcome1, c: outcome2 }); - expect(effects.fetchInstruments$).toBeObservable(expected); - }); - - it("should result in a fetchInstrumentsFailedAction", () => { - const action = fromActions.changePageAction({ page, limit }); - const outcome = fromActions.fetchInstrumentsFailedAction(); - - actions = hot("-a", { a: action }); - const response = cold("-#", {}); - instrumentApi.instrumentsControllerFindAll.and.returnValue(response); - - const expected = cold("--b", { b: outcome }); - expect(effects.fetchInstruments$).toBeObservable(expected); - }); - }); - - describe("ofType sortByColumnAction", () => { - const column = "test"; - const direction = "asc"; - - it("should result in a fetchInstrumentsCompleteAction and a fetchCountAction", () => { - const action = fromActions.sortByColumnAction({ column, direction }); - const outcome1 = fromActions.fetchInstrumentsCompleteAction({ - instruments, - }); - const outcome2 = fromActions.fetchCountAction(); - - actions = hot("-a", { a: action }); - const response = cold("-a|", { a: instruments }); - instrumentApi.instrumentsControllerFindAll.and.returnValue(response); - - const expected = cold("--(bc)", { b: outcome1, c: outcome2 }); - expect(effects.fetchInstruments$).toBeObservable(expected); - }); - - it("should result in a fetchInstrumentsFailedAction", () => { - const action = fromActions.sortByColumnAction({ column, direction }); + const action = fromActions.fetchInstrumentsAction({}); const outcome = fromActions.fetchInstrumentsFailedAction(); actions = hot("-a", { a: action }); @@ -180,14 +86,15 @@ describe("InstrumentEffects", () => { describe("fetchCount$", () => { it("should result in a fetchCountCompleteAction", () => { + const count = 1; const action = fromActions.fetchCountAction(); const outcome = fromActions.fetchCountCompleteAction({ - count: instruments.length, + count, }); actions = hot("-a", { a: action }); - const response = cold("-a|", { a: instruments }); - instrumentApi.instrumentsControllerFindAll.and.returnValue(response); + const response = cold("-a|", { a: { count } }); + instrumentApi.instrumentsControllerCount.and.returnValue(response); const expected = cold("--b", { b: outcome }); expect(effects.fetchCount$).toBeObservable(expected); @@ -199,7 +106,7 @@ describe("InstrumentEffects", () => { actions = hot("-a", { a: action }); const response = cold("-#", {}); - instrumentApi.instrumentsControllerFindAll.and.returnValue(response); + instrumentApi.instrumentsControllerCount.and.returnValue(response); const expected = cold("--b", { b: outcome }); expect(effects.fetchCount$).toBeObservable(expected); @@ -274,7 +181,7 @@ describe("InstrumentEffects", () => { describe("loading$", () => { describe("ofType fetchInstrumentsAction", () => { it("should dispatch a loadingAction", () => { - const action = fromActions.fetchInstrumentsAction(); + const action = fromActions.fetchInstrumentsAction({}); const outcome = loadingAction(); actions = hot("-a", { a: action }); diff --git a/src/app/state-management/effects/instruments.effects.ts b/src/app/state-management/effects/instruments.effects.ts index 795a29b32..06c26a533 100644 --- a/src/app/state-management/effects/instruments.effects.ts +++ b/src/app/state-management/effects/instruments.effects.ts @@ -1,5 +1,5 @@ import { Injectable } from "@angular/core"; -import { Actions, createEffect, ofType, concatLatestFrom } from "@ngrx/effects"; +import { Actions, createEffect, ofType } from "@ngrx/effects"; import { Instrument, InstrumentsService, @@ -8,7 +8,6 @@ import * as fromActions from "state-management/actions/instruments.actions"; import { switchMap, map, catchError, mergeMap } from "rxjs/operators"; import { of } from "rxjs"; import { Store } from "@ngrx/store"; -import { selectFilters } from "state-management/selectors/instruments.selectors"; import { loadingAction, loadingCompleteAction, @@ -16,28 +15,30 @@ import { @Injectable() export class InstrumentEffects { - filters$ = this.store.select(selectFilters); - fetchInstruments$ = createEffect(() => { return this.actions$.pipe( - ofType( - fromActions.fetchInstrumentsAction, - fromActions.changePageAction, - fromActions.sortByColumnAction, - ), - concatLatestFrom(() => this.filters$), - map(([action, filters]) => filters), - switchMap(({ sortField: order, skip, limit }) => - this.instrumentsService - .instrumentsControllerFindAll(JSON.stringify({ order, limit, skip })) + ofType(fromActions.fetchInstrumentsAction), + switchMap(({ limit, skip, sortColumn, sortDirection }) => { + const limitsParam = { + skip: skip, + limit: limit, + order: undefined, + }; + + if (sortColumn && sortDirection) { + limitsParam.order = `${sortColumn}:${sortDirection}`; + } + + return this.instrumentsService + .instrumentsControllerFindAll(JSON.stringify(limitsParam)) .pipe( mergeMap((instruments: Instrument[]) => [ fromActions.fetchInstrumentsCompleteAction({ instruments }), fromActions.fetchCountAction(), ]), catchError(() => of(fromActions.fetchInstrumentsFailedAction())), - ), - ), + ); + }), ); }); @@ -45,10 +46,8 @@ export class InstrumentEffects { return this.actions$.pipe( ofType(fromActions.fetchCountAction), switchMap(() => - this.instrumentsService.instrumentsControllerFindAll().pipe( - map((instruments: Instrument[]) => - fromActions.fetchCountCompleteAction({ count: instruments.length }), - ), + this.instrumentsService.instrumentsControllerCount().pipe( + map(({ count }) => fromActions.fetchCountCompleteAction({ count })), catchError(() => of(fromActions.fetchCountFailedAction())), ), ), diff --git a/src/app/state-management/effects/proposals.effects.ts b/src/app/state-management/effects/proposals.effects.ts index 439bcb9f3..863827426 100644 --- a/src/app/state-management/effects/proposals.effects.ts +++ b/src/app/state-management/effects/proposals.effects.ts @@ -30,7 +30,7 @@ export class ProposalEffects { fetchProposals$ = createEffect(() => { return this.actions$.pipe( ofType(fromActions.fetchProposalsAction), - mergeMap(({ skip, limit, search, sortColumn, sortDirection }) => { + switchMap(({ skip, limit, search, sortColumn, sortDirection }) => { const limitsParam = { skip: skip, limit: limit, diff --git a/src/app/state-management/reducers/instruments.reducer.spec.ts b/src/app/state-management/reducers/instruments.reducer.spec.ts index bd21ce69e..eff9e95d2 100644 --- a/src/app/state-management/reducers/instruments.reducer.spec.ts +++ b/src/app/state-management/reducers/instruments.reducer.spec.ts @@ -48,32 +48,6 @@ describe("InstrumentsReducer", () => { }); }); - describe("on changePageAction", () => { - it("should set skip and limit filters", () => { - const page = 1; - const limit = 25; - const skip = page * limit; - const action = fromActions.changePageAction({ page, limit }); - const state = instrumentsReducer(initialInstrumentState, action); - - expect(state.filters.skip).toEqual(skip); - expect(state.filters.limit).toEqual(limit); - }); - }); - - describe("on sortByColumnAction", () => { - it("should set sortField filter and set skip filter to 0", () => { - const column = "test"; - const direction = "asc"; - const sortField = column + ":" + direction; - const action = fromActions.sortByColumnAction({ column, direction }); - const state = instrumentsReducer(initialInstrumentState, action); - - expect(state.filters.sortField).toEqual(sortField); - expect(state.filters.skip).toEqual(0); - }); - }); - describe("on clearInstrumentsStateAction", () => { it("should set instrument state to initialInstrumentState", () => { const action = fromActions.clearInstrumentsStateAction(); diff --git a/src/app/state-management/reducers/instruments.reducer.ts b/src/app/state-management/reducers/instruments.reducer.ts index a692bec1a..f296f18b5 100644 --- a/src/app/state-management/reducers/instruments.reducer.ts +++ b/src/app/state-management/reducers/instruments.reducer.ts @@ -39,24 +39,6 @@ const reducer = createReducer( }), ), - on( - fromActions.changePageAction, - (state, { page, limit }): InstrumentState => { - const skip = page * limit; - const filters = { ...state.filters, skip, limit }; - return { ...state, filters }; - }, - ), - - on( - fromActions.sortByColumnAction, - (state, { column, direction }): InstrumentState => { - const sortField = column + (direction ? ":" + direction : ""); - const filters = { ...state.filters, sortField, skip: 0 }; - return { ...state, filters }; - }, - ), - on( fromActions.clearInstrumentsStateAction, (): InstrumentState => ({ diff --git a/src/app/state-management/selectors/instruments.selectors.spec.ts b/src/app/state-management/selectors/instruments.selectors.spec.ts index f8f9d6fc5..10699290b 100644 --- a/src/app/state-management/selectors/instruments.selectors.spec.ts +++ b/src/app/state-management/selectors/instruments.selectors.spec.ts @@ -1,7 +1,9 @@ import { InstrumentState } from "state-management/state/instruments.store"; import * as fromSelectors from "./instruments.selectors"; +import * as fromUserSelectors from "./user.selectors"; import { GenericFilters } from "state-management/models"; import { mockInstrument as instrument } from "shared/MockStubs"; +import { initialUserState } from "./user.selectors.spec"; const instrumentFilters: GenericFilters = { sortField: "name desc", @@ -71,24 +73,20 @@ describe("Instrument Selectors", () => { }); }); - describe("selectInstrumentsDashboardPageViewModel", () => { + describe("selectInstrumentsWithCountAndTableSettings", () => { it("should select the instruments dashboard page view model", () => { expect( - fromSelectors.selectInstrumentsDashboardPageViewModel.projector( + fromSelectors.selectInstrumentsWithCountAndTableSettings.projector( fromSelectors.selectInstruments.projector(initialInstrumentState), - fromSelectors.selectPage.projector(initialInstrumentState.filters), fromSelectors.selectInstrumentsCount.projector( initialInstrumentState, ), - fromSelectors.selectInstrumentsPerPage.projector( - initialInstrumentState.filters, - ), + fromUserSelectors.selectTablesSettings.projector(initialUserState), ), ).toEqual({ instruments: [], - currentPage: 0, - instrumentsCount: 0, - instrumentsPerPage: 25, + count: 0, + tablesSettings: {}, }); }); }); diff --git a/src/app/state-management/selectors/instruments.selectors.ts b/src/app/state-management/selectors/instruments.selectors.ts index 79e3697bd..984064259 100644 --- a/src/app/state-management/selectors/instruments.selectors.ts +++ b/src/app/state-management/selectors/instruments.selectors.ts @@ -1,5 +1,6 @@ import { createFeatureSelector, createSelector } from "@ngrx/store"; import { InstrumentState } from "state-management/state/instruments.store"; +import { selectTablesSettings } from "./user.selectors"; const selectInstrumentState = createFeatureSelector("instruments"); @@ -34,15 +35,15 @@ export const selectInstrumentsPerPage = createSelector( (filters) => filters.limit, ); -export const selectInstrumentsDashboardPageViewModel = createSelector( +export const selectInstrumentsWithCountAndTableSettings = createSelector( selectInstruments, - selectPage, selectInstrumentsCount, - selectInstrumentsPerPage, - (instruments, currentPage, instrumentsCount, instrumentsPerPage) => ({ - instruments, - currentPage, - instrumentsCount, - instrumentsPerPage, - }), + selectTablesSettings, + (instruments, count, tablesSettings) => { + return { + instruments, + count, + tablesSettings, + }; + }, ); diff --git a/src/app/state-management/selectors/user.selectors.spec.ts b/src/app/state-management/selectors/user.selectors.spec.ts index b2c163aa1..e9f528728 100644 --- a/src/app/state-management/selectors/user.selectors.spec.ts +++ b/src/app/state-management/selectors/user.selectors.spec.ts @@ -54,7 +54,7 @@ const settings: Settings = { darkTheme: false, }; -const initialUserState: UserState = { +export const initialUserState: UserState = { currentUser: user, profile: userIdentity.profile, accountType: "testType", From a7d4572365a95a2384557c15c1537b025c3ed86b Mon Sep 17 00:00:00 2001 From: martintrajanovski Date: Tue, 25 Mar 2025 15:00:14 +0100 Subject: [PATCH 4/7] add e2e tests for instruments table --- .../e2e/instruments/instruments-general.cy.js | 206 ++++++++++++++++++ cypress/fixtures/testData.js | 9 + cypress/support/commands.js | 63 ++++++ .../instrument-details.component.html | 5 +- .../effects/instruments.effects.ts | 4 +- 5 files changed, 283 insertions(+), 4 deletions(-) create mode 100644 cypress/e2e/instruments/instruments-general.cy.js diff --git a/cypress/e2e/instruments/instruments-general.cy.js b/cypress/e2e/instruments/instruments-general.cy.js new file mode 100644 index 000000000..3f90cd995 --- /dev/null +++ b/cypress/e2e/instruments/instruments-general.cy.js @@ -0,0 +1,206 @@ +const path = require("path"); + +import { testData } from "../../fixtures/testData"; +import { testConfig } from "../../fixtures/testData"; +import { getFormattedFileNamingDate, mergeConfig } from "../../support/utils"; + +describe("Instruments general", () => { + beforeEach(() => { + cy.login(Cypress.env("username"), Cypress.env("password")); + }); + + after(() => { + cy.removeInstruments(); + }); + + describe("Instruments table and details", () => { + it("should be able to see instrument in a table and visit the instrument details page", () => { + const instrument = { + ...testData.instrument, + name: "Cypress test instrument", + uniqueName: `Instrument-${Math.floor(1000 + Math.random() * 9000).toString()}`, + }; + cy.createInstrument(instrument); + + cy.visit("/instruments"); + + cy.get("mat-table mat-header-row").should("exist"); + + cy.finishedLoading(); + + cy.get("mat-table mat-row").should("contain", instrument.uniqueName); + + cy.get("mat-cell") + .contains(instrument.uniqueName) + .closest("mat-row") + .contains(instrument.name) + .click(); + + cy.url().should("include", `/instruments`); + + cy.contains(instrument.uniqueName); + }); + + it("proposal should have metadata and if not it should be able to add", () => { + const metadataName = "Instrument Metadata Name"; + const metadataValue = "instrument metadata value"; + const instrument = { + ...testData.instrument, + name: "Cypress test instrument", + uniqueName: `Instrument-${Math.floor(1000 + Math.random() * 9000).toString()}`, + }; + cy.createInstrument(instrument); + + cy.visit("/instruments"); + + cy.finishedLoading(); + + cy.get("mat-cell") + .contains(instrument.uniqueName) + .closest("mat-row") + .contains(instrument.name) + .click(); + + cy.finishedLoading(); + + cy.get('[data-cy="instrument-metadata-card"]').should("exist"); + + cy.get('[data-cy="instrument-metadata-card"] [role="tab"]') + .contains("Edit") + .click(); + + cy.get('[data-cy="add-new-row"]').click(); + + // simulate click event on the drop down + cy.get("mat-select[data-cy=field-type-input]").last().click(); // opens the drop down + + // simulate click event on the drop down item (mat-option) + cy.get("mat-option") + .contains("string") + .then((option) => { + option[0].click(); + }); + + cy.get("[data-cy=metadata-name-input]") + .last() + .focus() + .type(`${metadataName}{enter}`); + cy.get("[data-cy=metadata-value-input]") + .last() + .focus() + .type(`${metadataValue}{enter}`); + + cy.get("button[data-cy=save-changes-button]").click(); + + cy.finishedLoading(); + + cy.reload(); + + cy.finishedLoading(); + + cy.contains(instrument.name); + + cy.get('[data-cy="instrument-metadata-card"]').contains(metadataName, { + matchCase: true, + }); + cy.get('[data-cy="instrument-metadata-card"]').contains(metadataValue, { + matchCase: true, + }); + }); + }); + + describe("Proposals dynamic material table", () => { + it("should be able to sort for proposal in the column sort", () => { + const newInstrument = { + ...testData.instrument, + name: "000 Cypress test instrument", + uniqueName: `Instrument-${Math.floor(1000 + Math.random() * 9000).toString()}`, + }; + + const newInstrument2 = { + ...testData.instrument, + name: "001 Cypress test instrument", + uniqueName: `Instrument-${Math.floor(1000 + Math.random() * 9000).toString()}`, + }; + + cy.createInstrument(newInstrument2); + cy.createInstrument(newInstrument); + + cy.visit("/instruments"); + + cy.get("mat-table mat-row") + .first() + .should("not.contain", newInstrument.name); + + cy.get(".mat-sort-header-container").contains("Name").click(); + + cy.get("mat-table mat-row").first().should("contain", newInstrument.name); + + cy.reload(); + + cy.get("mat-table mat-row").first().should("contain", newInstrument.name); + }); + + it("should be able to download table data as a json", () => { + const instrument = { + ...testData.instrument, + uniqueName: `Instrument-${Math.floor(1000 + Math.random() * 9000).toString()}`, + }; + + cy.createInstrument(instrument); + + cy.visit("/instruments"); + + cy.get("dynamic-mat-table table-menu button").click(); + + cy.get('[role="menu"] button').contains("Save data").click(); + + cy.get('[role="menu"] button').contains("Json file").click(); + + const downloadsFolder = Cypress.config("downloadsFolder"); + const tableName = "instrumentsTable"; + + cy.readFile( + path.join( + downloadsFolder, + `${tableName}${getFormattedFileNamingDate()}.json`, + ), + ).then((actualExport) => { + const foundInstrument = actualExport.find( + (instrument) => instrument.uniqueName === instrument.uniqueName, + ); + + expect(foundInstrument).to.exist; + }); + }); + + it("should be able to download table data as a csv", () => { + const instrument = { + ...testData.instrument, + uniqueName: `Instrument-${Math.floor(1000 + Math.random() * 9000).toString()}`, + }; + + cy.createInstrument(instrument); + + cy.visit("/instruments"); + + cy.get("dynamic-mat-table table-menu button").click(); + + cy.get('[role="menu"] button').contains("Save data").click(); + + cy.get('[role="menu"] button').contains("CSV file").click(); + + const downloadsFolder = Cypress.config("downloadsFolder"); + const tableName = "instrumentsTable"; + + cy.readFile( + path.join( + downloadsFolder, + `${tableName}${getFormattedFileNamingDate()}.csv`, + ), + ).then((actualExport) => { + expect(actualExport).to.contain(instrument.uniqueName); + }); + }); + }); +}); diff --git a/cypress/fixtures/testData.js b/cypress/fixtures/testData.js index eab20af1f..5def968c1 100644 --- a/cypress/fixtures/testData.js +++ b/cypress/fixtures/testData.js @@ -61,6 +61,15 @@ export const testData = { ownerGroup: "20170251-group", accessGroups: [], }, + instrument: { + uniqueName: "ESS1-1", + name: "ESS1", + customMetadata: { + institute: "An immaginary intitution #1", + department: "An immaginary department #1", + main_user: "ESS", + }, + }, rawDataset: { principalInvestigator: "string", endTime: "2019-10-31T14:44:46.143Z", diff --git a/cypress/support/commands.js b/cypress/support/commands.js index a4f267f4c..922816b77 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -191,6 +191,29 @@ Cypress.Commands.add("createProposal", (proposal) => { }); }); }); + +Cypress.Commands.add("createInstrument", (instrument) => { + return cy.getCookie("user").then((userCookie) => { + const user = JSON.parse(decodeURIComponent(userCookie.value)); + + cy.getToken().then((token) => { + cy.log("Instrument: " + JSON.stringify(instrument, null, 2)); + cy.log("User: " + JSON.stringify(user, null, 2)); + + cy.request({ + method: "POST", + url: lbBaseUrl + "/Instruments", + headers: { + Authorization: token, + Accept: "application/json", + "Content-Type": "application/json", + }, + body: instrument, + }); + }); + }); +}); + Cypress.Commands.add("updateProposal", (proposalId, updateProposalDto) => { return cy.getCookie("user").then((userCookie) => { const user = JSON.parse(decodeURIComponent(userCookie.value)); @@ -310,6 +333,46 @@ Cypress.Commands.add("removeProposals", () => { }); }); +Cypress.Commands.add("removeInstruments", () => { + cy.login(Cypress.env("username"), Cypress.env("password")); + cy.getToken().then((token) => { + cy.request({ + method: "GET", + url: lbBaseUrl + "/instruments", + headers: { + Authorization: token, + Accept: "application/json", + "Content-Type": "application/json", + }, + }) + .its("body") + .as("instruments"); + + cy.get("@instruments").then((instruments) => { + cy.login( + Cypress.env("secondaryUsername"), + Cypress.env("secondaryPassword"), + ); + cy.getToken().then((token) => { + instruments.forEach((instrument) => { + cy.request({ + method: "DELETE", + url: + lbBaseUrl + + "/instruments/" + + encodeURIComponent(instrument.pid), + headers: { + Authorization: token, + Accept: "application/json", + "Content-Type": "application/json", + }, + }); + }); + }); + }); + }); +}); + Cypress.Commands.add("removeSamples", () => { cy.login(Cypress.env("username"), Cypress.env("password")); cy.getToken().then((token) => { diff --git a/src/app/instruments/instrument-details/instrument-details.component.html b/src/app/instruments/instrument-details/instrument-details.component.html index d478b7203..bdaba26a5 100644 --- a/src/app/instruments/instrument-details/instrument-details.component.html +++ b/src/app/instruments/instrument-details/instrument-details.component.html @@ -28,7 +28,10 @@ - +