diff --git a/cypress/e2e/published-data/published-data.cy.js b/cypress/e2e/published-data/published-data.cy.js index 97fc09f388..6c3be903c7 100644 --- a/cypress/e2e/published-data/published-data.cy.js +++ b/cypress/e2e/published-data/published-data.cy.js @@ -173,7 +173,8 @@ describe("Datasets general", () => { cy.finishedLoading(); - cy.get('input[formcontrolname="globalSearch"]').clear().type(title); + cy.get('[data-cy="text-search"]').clear().type(title); + cy.get('[data-cy="search-button"]').click(); cy.isLoading(); @@ -192,7 +193,8 @@ describe("Datasets general", () => { cy.finishedLoading(); - cy.get('input[formcontrolname="globalSearch"]').clear().type(title); + cy.get('[data-cy="text-search"]').clear().type(title); + cy.get('[data-cy="search-button"]').click(); cy.isLoading(); @@ -221,7 +223,8 @@ describe("Datasets general", () => { cy.finishedLoading(); - cy.get('input[formcontrolname="globalSearch"]').clear().type(title); + cy.get('[data-cy="text-search"]').clear().type(title); + cy.get('[data-cy="search-button"]').click(); cy.isLoading(); @@ -298,7 +301,8 @@ describe("Datasets general", () => { cy.finishedLoading(); - cy.get('input[formcontrolname="globalSearch"]').clear().type(title); + cy.get('[data-cy="text-search"]').clear().type(title); + cy.get('[data-cy="search-button"]').click(); cy.isLoading(); @@ -349,7 +353,8 @@ describe("Datasets general", () => { cy.finishedLoading(); - cy.get('input[formcontrolname="globalSearch"]').clear().type(title); + cy.get('[data-cy="text-search"]').clear().type(title); + cy.get('[data-cy="search-button"]').click(); cy.isLoading(); @@ -374,7 +379,8 @@ describe("Datasets general", () => { cy.finishedLoading(); - cy.get('input[formcontrolname="globalSearch"]').clear().type(title); + cy.get('[data-cy="text-search"]').clear().type(title); + cy.get('[data-cy="search-button"]').click(); cy.isLoading(); @@ -398,7 +404,8 @@ describe("Datasets general", () => { cy.finishedLoading(); - cy.get('input[formcontrolname="globalSearch"]').clear().type(title); + cy.get('[data-cy="text-search"]').clear().type(title); + cy.get('[data-cy="search-button"]').click(); cy.isLoading(); @@ -417,7 +424,8 @@ describe("Datasets general", () => { cy.finishedLoading(); - cy.get('input[formcontrolname="globalSearch"]').clear().type(title); + cy.get('[data-cy="text-search"]').clear().type(title); + cy.get('[data-cy="search-button"]').click(); cy.isLoading(); @@ -534,10 +542,12 @@ describe("Datasets general", () => { cy.finishedLoading(); - cy.get('input[formcontrolname="globalSearch"]') + cy.get('[data-cy="text-search"]') .clear() .type(userPublishedDataTitle); + cy.get('[data-cy="search-button"]').click(); + cy.isLoading(); cy.get("app-publisheddata-dashboard mat-table mat-row") diff --git a/src/app/publisheddata/publisheddata-dashboard/publisheddata-dashboard.component.html b/src/app/publisheddata/publisheddata-dashboard/publisheddata-dashboard.component.html index c5870c83e5..162ca51a1c 100644 --- a/src/app/publisheddata/publisheddata-dashboard/publisheddata-dashboard.component.html +++ b/src/app/publisheddata/publisheddata-dashboard/publisheddata-dashboard.component.html @@ -1,11 +1,21 @@ - - + diff --git a/src/app/publisheddata/publisheddata-dashboard/publisheddata-dashboard.component.spec.ts b/src/app/publisheddata/publisheddata-dashboard/publisheddata-dashboard.component.spec.ts index 7024155b90..792202cef3 100644 --- a/src/app/publisheddata/publisheddata-dashboard/publisheddata-dashboard.component.spec.ts +++ b/src/app/publisheddata/publisheddata-dashboard/publisheddata-dashboard.component.spec.ts @@ -10,18 +10,13 @@ import { MockStore, createMock, mockPublishedData } from "shared/MockStubs"; import { NO_ERRORS_SCHEMA } from "@angular/core"; import { StoreModule, Store } from "@ngrx/store"; import { Router } from "@angular/router"; -import { CheckboxEvent } from "shared/modules/table/table.component"; -import { MatCheckboxChange } from "@angular/material/checkbox"; -import { of } from "rxjs"; -import { Message, MessageType } from "state-management/models"; -import { showMessageAction } from "state-management/actions/user.actions"; import { FlexLayoutModule } from "@ngbracket/ngx-layout"; import { MatButtonModule } from "@angular/material/button"; import { MatIconModule } from "@angular/material/icon"; import { AppConfigService } from "app-config.service"; import { ScicatDataService } from "shared/services/scicat-data-service"; import { ExportExcelService } from "shared/services/export-excel.service"; -import { PublishedData } from "@scicatproject/scicat-sdk-ts-angular"; +import { RowEventType } from "shared/modules/dynamic-material-table/models/table-row.model"; const getConfig = () => ({}); @@ -83,31 +78,15 @@ describe("PublisheddataDashboardComponent", () => { expect(component).toBeTruthy(); }); - describe("#onShareClick()", () => { - it("should copy the selected DOI's to the users clipboard and dispatch a showMessageAction", () => { - const commandSpy = spyOn(document, "execCommand"); - dispatchSpy = spyOn(store, "dispatch"); - - const message = new Message( - "The selected DOI's have been copied to your clipboard", - MessageType.Success, - 5000, - ); - - component.onShareClick(); - - expect(commandSpy).toHaveBeenCalledTimes(1); - expect(commandSpy).toHaveBeenCalledWith("copy"); - expect(dispatchSpy).toHaveBeenCalledTimes(1); - expect(dispatchSpy).toHaveBeenCalledWith(showMessageAction({ message })); - }); - }); - - describe("#onRowClick", () => { + describe("#onRowEvent", () => { it("should navigate to a Published Dataset", () => { const published = mockPublishedData; const id = encodeURIComponent(published.doi); - component.onRowClick(published); + + component.onRowEvent({ + event: RowEventType.RowClick, + sender: { row: published }, + } as any); expect(router.navigateByUrl).toHaveBeenCalledTimes(1); expect(router.navigateByUrl).toHaveBeenCalledWith( @@ -115,86 +94,4 @@ describe("PublisheddataDashboardComponent", () => { ); }); }); - - describe("#onSelectAll()", () => { - it("should add all DOI's to selectedDOIs if checked is true", () => { - const published = createMock({ - doi: "test", - title: "test", - abstract: "test", - datasetPids: [], - createdAt: "", - registeredTime: "", - status: PublishedData.StatusEnum.private, - updatedAt: "", - metadata: { - creators: ["test creator"], - publisher: { name: "test" }, - publicationYear: 2021, - resourceType: "test", - }, - }); - - spyOn(component.vm$, "pipe").and.returnValue( - of({ publishedData: [published] }), - ); - - const event = { - event: { - checked: true, - }, - selection: { selected: [published] }, - }; - - component.onSelectAll(event as any); - - expect(component.selectedDOIs.length).toEqual(1); - }); - - it("should remove all DOI's from selectedDOIs if checked is false", () => { - component.selectedDOIs.push( - component.doiBaseUrl + "test1", - component.doiBaseUrl + "test2", - ); - - const event = { - event: { - checked: false, - }, - selection: [], - }; - - component.onSelectAll(event as any); - - expect(component.selectedDOIs.length).toEqual(0); - }); - }); - - describe("#onSelectOne()", () => { - it("should add the selected DOI to selectedDOIs if checked is true", () => { - const checkboxEvent: CheckboxEvent = { - event: { checked: true } as MatCheckboxChange, - row: { doi: "test" }, - }; - - component.onSelectOne(checkboxEvent); - - expect(component.selectedDOIs).toContain( - component.doiBaseUrl + checkboxEvent.row.doi, - ); - }); - - it("should remove the deselected DOI from selectedDOIs if checked is false", () => { - const checkboxEvent: CheckboxEvent = { - event: { checked: false } as MatCheckboxChange, - row: { doi: "test" }, - }; - - component.selectedDOIs.push(component.doiBaseUrl + checkboxEvent.row.doi); - - component.onSelectOne(checkboxEvent); - - expect(component.selectedDOIs.length).toEqual(0); - }); - }); }); diff --git a/src/app/publisheddata/publisheddata-dashboard/publisheddata-dashboard.component.ts b/src/app/publisheddata/publisheddata-dashboard/publisheddata-dashboard.component.ts index 57f7c7dd3e..f66ffb2ef8 100644 --- a/src/app/publisheddata/publisheddata-dashboard/publisheddata-dashboard.component.ts +++ b/src/app/publisheddata/publisheddata-dashboard/publisheddata-dashboard.component.ts @@ -1,21 +1,29 @@ -import { Component, OnInit, OnDestroy, Inject } from "@angular/core"; +import { Component, OnInit, OnDestroy } from "@angular/core"; import { Store } from "@ngrx/store"; import { PublishedData } from "@scicatproject/scicat-sdk-ts-angular"; import { Router } from "@angular/router"; import { selectPublishedDataDashboardPageViewModel } from "state-management/selectors/published-data.selectors"; -import { CheckboxEvent } from "shared/modules/table/table.component"; -import { Subscription } from "rxjs"; - -import { MatCheckboxChange } from "@angular/material/checkbox"; -import { DOCUMENT } from "@angular/common"; -import { Message, MessageType } from "state-management/models"; -import { showMessageAction } from "state-management/actions/user.actions"; -import { Column } from "shared/modules/shared-table/shared-table.module"; -import { SciCatDataSource } from "shared/services/scicat.datasource"; +import { BehaviorSubject, Subscription, take } from "rxjs"; import { AppConfigService } from "app-config.service"; import { ScicatDataService } from "shared/services/scicat-data-service"; import { ExportExcelService } from "shared/services/export-excel.service"; -import { SelectionModel } from "@angular/cdk/collections"; +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 { + ITableSetting, + TableSettingEventType, +} from "shared/modules/dynamic-material-table/models/table-setting.model"; +import { actionMenu } from "shared/modules/dynamic-material-table/utilizes/default-table-settings"; +import { SciCatDataSource } from "shared/services/scicat.datasource"; +import { + IRowEvent, + RowEventType, +} from "shared/modules/dynamic-material-table/models/table-row.model"; +import { TableConfigService } from "shared/services/table-config.service"; +import { updateUserSettingsAction } from "state-management/actions/user.actions"; @Component({ selector: "app-publisheddata-dashboard", @@ -25,78 +33,68 @@ import { SelectionModel } from "@angular/cdk/collections"; }) export class PublisheddataDashboardComponent implements OnInit, OnDestroy { public vm$ = this.store.select(selectPublishedDataDashboardPageViewModel); - columns: Column[] = [ - { - id: "doi", - label: "DOI", - canSort: true, - icon: "fingerprint", - matchMode: "contains", - hideOrder: 0, - }, - { - id: "title", - label: "Title", - icon: "description", - canSort: true, - matchMode: "contains", - hideOrder: 1, - }, - { - id: "creator", - label: "Creator", - icon: "face", - canSort: true, - matchMode: "contains", - hideOrder: 2, - }, - { - id: "status", - label: "Status", - icon: "face", - canSort: true, - matchMode: "contains", - hideOrder: 3, - }, - { - id: "createdBy", - icon: "account_circle", - label: "Created by", - canSort: true, - matchMode: "contains", - hideOrder: 4, - }, - { - id: "createdAt", - icon: "date_range", - label: "Created at", - format: "date", - canSort: true, - matchMode: "between", - hideOrder: 5, + + columns: TableField[] = []; + setting: ITableSetting = {}; + + tableName = "publishedDataTable"; + rowSelectionMode: "single" | "multi" | "none" = "none"; + paginationMode: TablePaginationMode = "server-side"; + pending = false; + globalTextSearch = ""; + + tableDefaultSettingsConfig: ITableSetting = { + visibleActionMenu: actionMenu, + settingList: [ + { + visibleActionMenu: actionMenu, + isDefaultSetting: true, + isCurrentSetting: true, + columnSetting: [ + { name: "doi", header: "DOI", index: 0 }, + { name: "title", header: "Title", index: 1 }, + { name: "creator", header: "Creator", index: 2 }, + { name: "status", header: "Status", index: 3 }, + { name: "createdBy", header: "Created by", index: 4 }, + { name: "createdAt", header: "Created at", index: 5 }, + ], + }, + ], + rowStyle: { + "border-bottom": "1px solid #d2d2d2", }, - ]; + }; + + pagination: TablePagination = { + pageSize: 5, + pageIndex: 0, + pageSizeOptions: [5, 10, 25, 100], + length: 0, + }; + tableDefinition = { collection: "publishedData", columns: this.columns, apiVersion: "v4", }; - dataSource: SciCatDataSource; - select = true; - doiBaseUrl = "https://doi.org/"; - selectedDOIs: string[] = []; - filtersSubscription: Subscription = new Subscription(); + dataSource: BehaviorSubject = new BehaviorSubject< + PublishedData[] + >([]); + scicatDataSource: SciCatDataSource; + + subscriptions: Subscription[] = []; + currentFilters: any = {}; constructor( - @Inject(DOCUMENT) private document: Document, private router: Router, private store: Store, private appConfigService: AppConfigService, private dataService: ScicatDataService, private exportService: ExportExcelService, + private tableConfigService: TableConfigService, ) { - this.dataSource = new SciCatDataSource( + this.scicatDataSource = new SciCatDataSource( this.appConfigService, this.dataService, this.exportService, @@ -104,64 +102,129 @@ export class PublisheddataDashboardComponent implements OnInit, OnDestroy { ); } - onShareClick() { - const selectionBox = this.document.createElement("textarea"); - selectionBox.style.position = "fixed"; - selectionBox.style.left = "0"; - selectionBox.style.top = "0"; - selectionBox.style.opacity = "0"; - selectionBox.value = this.selectedDOIs.join("\n"); - this.document.body.appendChild(selectionBox); - selectionBox.focus(); - selectionBox.select(); - this.document.execCommand("copy"); - this.document.body.removeChild(selectionBox); - - const message = new Message( - "The selected DOI's have been copied to your clipboard", - MessageType.Success, - 5000, + ngOnInit() { + this.subscriptions.push( + this.vm$.pipe(take(1)).subscribe((vm) => { + this.currentFilters = vm.filters; + const { skip, limit } = vm.filters; + const pageIndex = skip / limit; + + this.loadData(vm.filters, pageIndex, limit); + + const tableSettingsConfig = + this.tableConfigService.getTableSettingsConfig( + this.tableName, + this.tableDefaultSettingsConfig, + vm.tablesSettings?.columns || [], + ); + + const currentColumnSetting = tableSettingsConfig.settingList.find( + (s) => s.isCurrentSetting, + )?.columnSetting; + + this.columns = currentColumnSetting; + this.setting = tableSettingsConfig; + + this.pagination = { + ...this.pagination, + length: vm.count, + pageIndex, + pageSize: limit, + }; + }), + ); + + this.subscriptions.push( + this.scicatDataSource.connect().subscribe((data) => { + this.dataSource.next(data); + }), + ); + + this.subscriptions.push( + this.scicatDataSource.count$.subscribe((count) => { + this.pagination = { ...this.pagination, length: count }; + }), ); - this.store.dispatch(showMessageAction({ message })); } - onRowClick(published: PublishedData) { - const id = encodeURIComponent(published.doi); - this.router.navigateByUrl("/publishedDatasets/" + id); + onRowEvent(event: IRowEvent) { + if (event?.event === RowEventType.RowClick) { + const id = encodeURIComponent(event.sender.row.doi); + this.router.navigateByUrl("/publishedDatasets/" + id); + } + } + + onPaginationChange(pagination: TablePagination) { + const pageIndex = pagination.pageIndex; + const pageSize = pagination.pageSize; + const newFilters = { + ...this.currentFilters, + skip: pageIndex * pageSize, + limit: pageSize, + }; + + this.loadData(newFilters, pageIndex, pageSize); + } + + saveTableSettings(setting: ITableSetting) { + const columnsSetting = setting.columnSetting.map((column, index) => { + const { name, display, width } = column; + + return { name, display, order: index, width }; + }); + + this.store.dispatch( + updateUserSettingsAction({ + property: { fe_publisheddata_table_columns: columnsSetting }, + }), + ); } - onSelectAll(event: { - event: MatCheckboxChange; - selection: SelectionModel; + onSettingChange(event: { + type: TableSettingEventType; + setting: ITableSetting; }) { - if (event.event.checked) { - this.selectedDOIs = event.selection.selected.map( - ({ doi }) => this.doiBaseUrl + encodeURIComponent(doi), - ); - } else { - this.selectedDOIs = []; + if ( + event.type === TableSettingEventType.save || + event.type === TableSettingEventType.create + ) { + this.saveTableSettings(event.setting); } } - onSelectOne(checkboxEvent: CheckboxEvent) { - const { event, row } = checkboxEvent; - const doiUrl = this.doiBaseUrl + encodeURIComponent(row.doi); - if (event.checked) { - this.selectedDOIs.push(doiUrl); - } else { - this.selectedDOIs.splice(this.selectedDOIs.indexOf(doiUrl), 1); - } + onGlobalTextSearchChange(text: string) { + this.globalTextSearch = text; } - ngOnInit() { - this.filtersSubscription = this.vm$.subscribe((vm) => { - this.router.navigate(["/publishedDatasets"], { - queryParams: { args: JSON.stringify(vm.filters) }, - }); - }); + onGlobalTextSearchAction() { + const newFilters = { + ...this.currentFilters, + skip: 0, + limit: this.pagination.pageSize, + globalSearch: this.globalTextSearch || undefined, + }; + + this.loadData(newFilters, 0, this.pagination.pageSize); + this.currentFilters = newFilters; + } + + getSort(filters: any) { + const sortField = filters?.sortField; + return sortField ? sortField.split(" ") : ["", "asc"]; + } + + loadData(filters: any, pageIndex: number, pageSize: number) { + const [field, direction] = this.getSort(filters); + this.scicatDataSource.loadAllData( + filters, + field, + direction, + pageIndex, + pageSize, + ); } ngOnDestroy() { - this.filtersSubscription.unsubscribe(); + this.subscriptions.forEach((sub) => sub.unsubscribe()); } } diff --git a/src/app/state-management/effects/user.effects.spec.ts b/src/app/state-management/effects/user.effects.spec.ts index 6e2df86840..95100a7983 100644 --- a/src/app/state-management/effects/user.effects.spec.ts +++ b/src/app/state-management/effects/user.effects.spec.ts @@ -619,6 +619,7 @@ describe("UserEffects", () => { fe_sample_table_conditions: [], fe_instrument_table_columns: [], fe_file_table_columns: [], + fe_publisheddata_table_columns: [], }, } as unknown as UserSettings; const action = fromActions.fetchUserSettingsAction({ id }); diff --git a/src/app/state-management/models/index.ts b/src/app/state-management/models/index.ts index 16ce256ff8..99b02a59b9 100644 --- a/src/app/state-management/models/index.ts +++ b/src/app/state-management/models/index.ts @@ -19,6 +19,7 @@ export interface Settings { fe_sample_table_conditions?: ConditionConfig[]; fe_instrument_table_columns?: TableColumn[]; fe_file_table_columns?: TableColumn[]; + fe_publisheddata_table_columns?: TableColumn[]; } export interface TableColumn { @@ -220,6 +221,11 @@ export const SETTINGS_CONFIG = [ configKey: "columns", }, { key: "fe_file_table_columns", scope: "file", configKey: "columns" }, + { + key: "fe_publisheddata_table_columns", + scope: "publisheddata", + configKey: "columns", + }, ]; export type SettingScope = @@ -227,7 +233,8 @@ export type SettingScope = | "proposal" | "sample" | "instrument" - | "file"; + | "file" + | "publisheddata"; export type SettingKind = "columns" | "filters" | "conditions"; export const getSettingKey = ( diff --git a/src/app/state-management/selectors/published-data.selectors.spec.ts b/src/app/state-management/selectors/published-data.selectors.spec.ts index 0d9aa26381..00b3763aa0 100644 --- a/src/app/state-management/selectors/published-data.selectors.spec.ts +++ b/src/app/state-management/selectors/published-data.selectors.spec.ts @@ -3,6 +3,7 @@ import { PublishedDataState } from "state-management/state/published-data.store" import * as fromSelectors from "./published-data.selectors"; import { createMock } from "shared/MockStubs"; import { PublishedData } from "@scicatproject/scicat-sdk-ts-angular"; +import { initialUserState } from "./user.selectors.spec"; const publishedData = createMock({ doi: "testDOI", @@ -108,6 +109,7 @@ describe("Published Data Selectors", () => { fromSelectors.selectPage.projector(initialPublishedDataState.filters), initialPublishedDataState.filters.limit, initialPublishedDataState.filters, + initialUserState.settings, ), ).toEqual({ publishedData: [], @@ -119,6 +121,9 @@ describe("Published Data Selectors", () => { skip: 0, limit: 25, }, + tablesSettings: { + columns: initialUserState.settings.fe_publisheddata_table_columns, + }, }); }); }); diff --git a/src/app/state-management/selectors/published-data.selectors.ts b/src/app/state-management/selectors/published-data.selectors.ts index cd9dc71c46..d5e9c2f0ee 100644 --- a/src/app/state-management/selectors/published-data.selectors.ts +++ b/src/app/state-management/selectors/published-data.selectors.ts @@ -1,5 +1,6 @@ import { createFeatureSelector, createSelector } from "@ngrx/store"; import { PublishedDataState } from "state-management/state/published-data.store"; +import { selectSettings } from "./user.selectors"; const selectPublishedDataState = createFeatureSelector("publishedData"); @@ -45,12 +46,23 @@ export const selectPublishedDataDashboardPageViewModel = createSelector( selectPage, selectPublishedDataPerPage, selectFilters, - (publishedData, count, currentPage, publishedDataPerPage, filters) => ({ + selectSettings, + ( publishedData, count, currentPage, publishedDataPerPage, filters, + settings, + ) => ({ + publishedData, + count, + currentPage, + publishedDataPerPage, + filters, + tablesSettings: { + columns: settings.fe_publisheddata_table_columns, + }, }), ); diff --git a/src/app/state-management/state/user.store.ts b/src/app/state-management/state/user.store.ts index 9e01a3c661..a9dbdc3a67 100644 --- a/src/app/state-management/state/user.store.ts +++ b/src/app/state-management/state/user.store.ts @@ -79,6 +79,7 @@ export const initialUserState: UserState = { fe_sample_table_conditions: [], fe_instrument_table_columns: [], fe_file_table_columns: [], + fe_publisheddata_table_columns: [], }, // TODO sync with server settings? message: undefined,