From a964b4241876c5ee95d68de446d54b827c46cb5c Mon Sep 17 00:00:00 2001 From: Sofia Papacharalampous Date: Thu, 20 Jun 2024 16:12:23 +0300 Subject: [PATCH] description listing refactoring --- .../app/core/model/description/description.ts | 9 +- .../listing/description-listing.component.ts | 264 ++++++++++-------- 2 files changed, 147 insertions(+), 126 deletions(-) diff --git a/dmp-frontend/src/app/core/model/description/description.ts b/dmp-frontend/src/app/core/model/description/description.ts index bd941de0b..528d87922 100644 --- a/dmp-frontend/src/app/core/model/description/description.ts +++ b/dmp-frontend/src/app/core/model/description/description.ts @@ -8,10 +8,9 @@ import { Tag, TagPersist } from "../tag/tag"; import { User } from "../user/user"; import { AppPermission } from "@app/core/common/enum/permission.enum"; -export interface Description extends BaseEntity { +export interface Description extends BaseDescription { label?: string; properties?: DescriptionPropertyDefinition; - status?: DescriptionStatus; description?: string; createdBy?: User; finalizedAt?: Date; @@ -129,7 +128,7 @@ export interface DescriptionStatusPersist { // Public // -export interface PublicDescription extends BaseEntity { +export interface PublicDescription extends BaseDescription { label?: string; status?: DescriptionStatus; description?: string; @@ -159,3 +158,7 @@ export interface UpdateDescriptionTemplatePersist { id: Guid; hash?: string; } + +export interface BaseDescription extends BaseEntity { + status?: DescriptionStatus; +} diff --git a/dmp-frontend/src/app/ui/description/listing/description-listing.component.ts b/dmp-frontend/src/app/ui/description/listing/description-listing.component.ts index 37ae86eed..e2e14c79c 100644 --- a/dmp-frontend/src/app/ui/description/listing/description-listing.component.ts +++ b/dmp-frontend/src/app/ui/description/listing/description-listing.component.ts @@ -1,5 +1,3 @@ - -import { HttpClient } from '@angular/common/http'; import { Component, OnInit, ViewChild } from '@angular/core'; import { UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, Validators } from '@angular/forms'; import { MatDialog } from '@angular/material/dialog'; @@ -11,14 +9,13 @@ import { IsActive } from '@app/core/common/enum/is-active.enum'; import { AppPermission } from '@app/core/common/enum/permission.enum'; import { RecentActivityOrder } from '@app/core/common/enum/recent-activity-order'; import { DescriptionTemplate } from '@app/core/model/description-template/description-template'; -import { Description } from '@app/core/model/description/description'; +import { BaseDescription, Description } from '@app/core/model/description/description'; import { DmpBlueprint, DmpBlueprintDefinition, DmpBlueprintDefinitionSection } from '@app/core/model/dmp-blueprint/dmp-blueprint'; import { Dmp, DmpDescriptionTemplate, DmpUser } from '@app/core/model/dmp/dmp'; import { DmpReference } from '@app/core/model/dmp/dmp-reference'; import { ReferenceType } from '@app/core/model/reference-type/reference-type'; import { Reference } from '@app/core/model/reference/reference'; import { DescriptionFilter, DescriptionLookup } from '@app/core/query/description.lookup'; -import { DmpLookup } from '@app/core/query/dmp.lookup'; import { AuthService } from '@app/core/services/auth/auth.service'; import { DescriptionService } from '@app/core/services/description/description.service'; import { AnalyticsService } from '@app/core/services/matomo/analytics-service'; @@ -26,23 +23,28 @@ import { EnumUtils } from '@app/core/services/utilities/enum-utils.service'; import { GuidedTour, Orientation } from '@app/library/guided-tour/guided-tour.constants'; import { GuidedTourService } from '@app/library/guided-tour/guided-tour.service'; import { StartNewDmpDialogComponent } from '@app/ui/dmp/new/start-new-dmp-dialogue/start-new-dmp-dialog.component'; -import { BaseComponent } from '@common/base/base.component'; import { HttpErrorHandlingService } from '@common/modules/errors/error-handling/http-error-handling.service'; -import { SortDirection } from '@common/modules/hybrid-listing/hybrid-listing.component'; -import { Guid } from '@common/types/guid'; +import { PageLoadEvent, SortDirection } from '@common/modules/hybrid-listing/hybrid-listing.component'; import { TranslateService } from '@ngx-translate/core'; -import { debounceTime, map, takeUntil } from 'rxjs/operators'; +import { debounceTime, takeUntil, tap } from 'rxjs/operators'; import { nameof } from 'ts-simple-nameof'; import { StartNewDescriptionDialogComponent } from '../start-new-description-dialog/start-new-description-dialog.component'; import { RouterUtilsService } from '@app/core/services/router/router-utils.service'; import { DescriptionFilterDialogComponent } from './filtering/description-filter-dialogue/description-filter-dialog.component'; +import { BaseListingComponent } from '@common/base/base-listing-component'; +import { QueryResult } from '@common/model/query-result'; +import { Observable } from 'rxjs'; +import { QueryParamsService } from '@app/core/services/utilities/query-params.service'; +import { UiNotificationService } from '@app/core/services/notification/ui-notification-service'; @Component({ selector: 'app-description-listing-component', templateUrl: 'description-listing.component.html', styleUrls: ['./description-listing.component.scss'] }) -export class DescriptionListingComponent extends BaseComponent implements OnInit { +export class DescriptionListingComponent extends BaseListingComponent implements OnInit { + + userSettingsKey = { key: 'DescriptionListingUserSettings' }; @ViewChild(MatPaginator, { static: true }) _paginator: MatPaginator; @ViewChild(MatSort) sort: MatSort; @@ -58,8 +60,9 @@ export class DescriptionListingComponent extends BaseComponent implements OnInit isPublic: boolean = false; public isVisible = true + protected ITEMS_PER_PAGE = 5; pageSize: number = 5; - lookup: DescriptionLookup = new DescriptionLookup(); + lookup: DescriptionLookup; public formGroup = new UntypedFormBuilder().group({ like: new UntypedFormControl(), order: new UntypedFormControl() @@ -70,7 +73,7 @@ export class DescriptionListingComponent extends BaseComponent implements OnInit dmpText: string; descriptionText: string; - private _sortDirection: SortDirection = SortDirection.Descending; //SortDirection.Descending: "-" + private _sortDirection: SortDirection = SortDirection.Descending; set sortDirection(sortDirection: SortDirection) { this._sortDirection = sortDirection; } @@ -90,80 +93,98 @@ export class DescriptionListingComponent extends BaseComponent implements OnInit constructor( public routerUtils: RouterUtilsService, private descriptionService: DescriptionService, - private router: Router, - private route: ActivatedRoute, public dialog: MatDialog, private language: TranslateService, private authService: AuthService, public enumUtils: EnumUtils, - private authentication: AuthService, private guidedTourService: GuidedTourService, - private httpClient: HttpClient, private analyticsService: AnalyticsService, private fb: UntypedFormBuilder, - private httpErrorHandlingService: HttpErrorHandlingService, + protected httpErrorHandlingService: HttpErrorHandlingService, + protected router: Router, + protected route: ActivatedRoute, + protected uiNotificationService: UiNotificationService, + protected queryParamsService: QueryParamsService, ) { - super(); + super(router, route, uiNotificationService, httpErrorHandlingService, queryParamsService); } ngOnInit() { this.analyticsService.trackPageView(AnalyticsService.DescriptionListing); this.isPublic = this.route.snapshot.data['isPublic'] ?? false; - if (this.isPublic) { - //TODO: refactor - // this.formGroup.get('order').setValue(this.order.DATASETPUBLISHED); - } else { - this.formGroup.get('order').setValue(this.order.UpdatedAt); - } + if (!this.isPublic && !this.authService.currentAccountIsAuthenticated()) { this.router.navigateByUrl("/explore"); } - this.route.params + this.route.queryParamMap .pipe(takeUntil(this._destroyed)) .subscribe(async (params: Params) => { const queryParams = this.route.snapshot.queryParams; - this.dmpId = queryParams['dmpId']; - this.status = queryParams['status']; - this.lookup.page = { size: this.pageSize, offset: 0 }; - this.lookup.order = { items: ['-' + nameof(x => x.updatedAt)] }; - this.lookup.metadata = { countAll: true }; - this.lookup.isActive = [IsActive.Active]; - if (this.dmpId != null && Guid.isGuid(this.dmpId)) { - this.dmpSearchEnabled = false; - //const dmp = await this.dmpService.getSingle(this.dmpId).toPromise(); - this.lookup.dmpSubQuery = new DmpLookup(); - this.lookup.dmpSubQuery.ids = [Guid.parse(this.dmpId)]; - this.lookup.associatedDmpIds.push(Guid.parse(this.dmpId)); - if (params['dmpLabel'] !== undefined) { - this.titlePrefix = 'for ' + params['dmpLabel']; - } - } + if (!this.lookup) this.lookup = queryParams['lookup'] ? this._parseLookupFromParams(queryParams) : this.initializeLookup(); - if (this.status != null && this.status == DescriptionStatus.Draft) { //TODO: chack if actually used - this.lookup.statuses = [DescriptionStatus.Draft] + if ((this.formGroup.get('order')?.value == null || (!this.isAscending && !this.isDescending)) && this.lookup.order.items && this.lookup.order.items.length > 0) { + + let ordering = this.lookup.order.items[0]; + let sortBy = ordering.substring(1, ordering.length); + + this.formGroup.get('order').setValue(this._getRecentActivityOrder(sortBy)); + ordering.charAt(0) == '-' ? this.sortDirection = SortDirection.Descending : this.sortDirection = SortDirection.Ascending; } - this.refresh(this.lookup); + + this.onPageLoad({ offset: this.lookup.page.offset / this.lookup.page.size } as PageLoadEvent); }); this.formGroup.get('like').valueChanges .pipe(takeUntil(this._destroyed), debounceTime(500)) .subscribe(x => this.controlModified()); - this.formGroup.get('order').valueChanges - .pipe(takeUntil(this._destroyed)) - .subscribe(x => { - const fields: Array = [((this.formGroup.get('order').value === 'status') || (this.formGroup.get('order').value === 'label') ? '' : "-") + this.formGroup.get('order').value]; - this.lookup.order = { items: fields }; - this.lookup.page = { size: this.pageSize, offset: 0 }; - this.refresh(this.lookup); - }); } ngAfterContentChecked(): void { this.scrollbar = this.hasScrollbar(); } + protected loadListing(): Observable> { + if (this.isPublic) { + return this.descriptionService.publicQuery(this.lookup).pipe(takeUntil(this._destroyed)) + .pipe(tap(result => { + if (!result) { return []; } + this.totalCount = result.count; + if (this.lookup?.page?.offset === 0) this.listingItems = []; + this.listingItems.push(...result.items); + this.hasListingItems = true; + })); + } else { + return this.descriptionService.query(this.lookup).pipe(takeUntil(this._destroyed)) + .pipe(tap(result => { + if (!result) { return []; } + this.totalCount = result.count; + if (this.lookup?.page?.offset === 0) this.listingItems = []; + this.listingItems.push(...result.items); + this.hasListingItems = true; + })); + } + } + protected initializeLookup(): DescriptionLookup { + const lookup = new DescriptionLookup(); + lookup.metadata = { countAll: true }; + lookup.page = { offset: 0, size: this.ITEMS_PER_PAGE }; + lookup.statuses = [DescriptionStatus.Draft, DescriptionStatus.Finalized] + lookup.isActive = [IsActive.Active]; + lookup.order = { items: [this.toDescSortField(nameof(x => x.updatedAt))] }; + this.updateOrderUiFields(lookup.order); + + lookup.project = { + fields: this._lookupFields + }; + + return lookup; + } + + protected setupColumns() { + } + public dashboardTour: GuidedTour = { tourId: 'dmp-description-tour', useOrb: true, @@ -192,12 +213,13 @@ export class DescriptionListingComponent extends BaseComponent implements OnInit this.lookup.like = this.formGroup.get("like").value; this.lookup.page = { size: this.pageSize, offset: 0 }; - this.refresh(this.lookup); + this.loadListing(); } loadMore() { this.lookup.page = { size: this.pageSize, offset: this.lookup.page.offset + this.pageSize }; - this.refresh(this.lookup); + + this.filterChanged(this.lookup, true); } orderByChanged() { @@ -208,75 +230,7 @@ export class DescriptionListingComponent extends BaseComponent implements OnInit } else { this.lookup.order = { items: [this.sortingDirectionPrefix + nameof(x => x.updatedAt)] }; } - this.refresh(this.lookup); - } - - private refresh(lookup: DescriptionLookup) { - lookup.project = { - fields: [ - nameof(x => x.id), - nameof(x => x.label), - nameof(x => x.status), - nameof(x => x.updatedAt), - nameof(x => x.belongsToCurrentTenant), - nameof(x => x.finalizedAt), - - - [nameof(x => x.authorizationFlags), AppPermission.EditDescription].join('.'), - [nameof(x => x.authorizationFlags), AppPermission.DeleteDescription].join('.'), - [nameof(x => x.authorizationFlags), AppPermission.InviteDmpUsers].join('.'), - - [nameof(x => x.descriptionTemplate), nameof(x => x.id)].join('.'), - [nameof(x => x.descriptionTemplate), nameof(x => x.label)].join('.'), - [nameof(x => x.descriptionTemplate), nameof(x => x.groupId)].join('.'), - [nameof(x => x.dmp), nameof(x => x.id)].join('.'), - [nameof(x => x.dmp), nameof(x => x.label)].join('.'), - [nameof(x => x.dmp), nameof(x => x.status)].join('.'), - [nameof(x => x.dmp), nameof(x => x.accessType)].join('.'), - [nameof(x => x.dmp), nameof(x => x.blueprint), nameof(x => x.id)].join('.'), - [nameof(x => x.dmp), nameof(x => x.blueprint), nameof(x => x.label)].join('.'), - [nameof(x => x.dmp), nameof(x => x.blueprint), nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.id)].join('.'), - [nameof(x => x.dmp), nameof(x => x.blueprint), nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.label)].join('.'), - [nameof(x => x.dmp), nameof(x => x.blueprint), nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.hasTemplates)].join('.'), - [nameof(x => x.dmp), nameof(x => x.dmpUsers), nameof(x => x.id)].join('.'), - [nameof(x => x.dmp), nameof(x => x.dmpUsers), nameof(x => x.user.id)].join('.'), - [nameof(x => x.dmp), nameof(x => x.dmpUsers), nameof(x => x.role)].join('.'), - [nameof(x => x.dmp), nameof(x => x.dmpUsers), nameof(x => x.isActive)].join('.'), - [nameof(x => x.dmp), nameof(x => x.dmpReferences), nameof(x => x.id)].join('.'), - [nameof(x => x.dmp), nameof(x => x.dmpReferences), nameof(x => x.reference), nameof(x => x.id)].join('.'), - [nameof(x => x.dmp), nameof(x => x.dmpReferences), nameof(x => x.reference), nameof(x => x.label)].join('.'), - [nameof(x => x.dmp), nameof(x => x.dmpReferences), nameof(x => x.reference), nameof(x => x.type), nameof(x => x.id)].join('.'), - [nameof(x => x.dmp), nameof(x => x.dmpReferences), nameof(x => x.reference), nameof(x => x.reference)].join('.'), - [nameof(x => x.dmp), nameof(x => x.dmpReferences), nameof(x => x.isActive)].join('.'), - [nameof(x => x.dmpDescriptionTemplate), nameof(x => x.id)].join('.'), - [nameof(x => x.dmpDescriptionTemplate), nameof(x => x.dmp), nameof(x => x.id)].join('.'), - [nameof(x => x.dmpDescriptionTemplate), nameof(x => x.descriptionTemplateGroupId)].join('.'), - [nameof(x => x.dmpDescriptionTemplate), nameof(x => x.sectionId)].join('.'), - ] - }; - if (this.isPublic) { - this.descriptionService.publicQuery(lookup).pipe(takeUntil(this._destroyed)) - .subscribe(result => { - if (!result) { return []; } - this.totalCount = result.count; - if (lookup?.page?.offset === 0) this.listingItems = []; - this.listingItems.push(...result.items); - this.hasListingItems = true; - }, - (error) => this.httpErrorHandlingService.handleBackedRequestError(error)); - } else { - this.descriptionService.query(lookup).pipe(takeUntil(this._destroyed)) - .subscribe(result => { - if (!result) { return []; } - this.totalCount = result.count; - if (lookup?.page?.offset === 0) this.listingItems = []; - result.items.forEach(description => { - if (description.status != DescriptionStatus.Canceled) this.listingItems.push(description); - }) - this.hasListingItems = true; - }, - (error) => this.httpErrorHandlingService.handleBackedRequestError(error)); - } + this.filterChanged(this.lookup); } openFiltersDialog(): void { @@ -372,7 +326,71 @@ export class DescriptionListingComponent extends BaseComponent implements OnInit } toggleSortDirection(): void { - this.sortDirection = this.isAscending ? this.sortDirection = SortDirection.Descending : this.sortDirection = SortDirection.Ascending; + this.sortDirection = this.isAscending ? SortDirection.Descending : SortDirection.Ascending; this.orderByChanged(); } + + private _parseLookupFromParams(params: Params): DescriptionLookup { + let lookup: DescriptionLookup = JSON.parse(params['lookup']); + + if (!lookup) return this.initializeLookup(); + + const queryOffset = 0; + const querySize = (lookup.page?.offset ?? 0) + this.pageSize; + lookup.page = { size: querySize, offset: queryOffset }; + lookup.statuses = [DescriptionStatus.Draft, DescriptionStatus.Finalized]; + lookup.project = { fields: this._lookupFields }; + lookup.metadata = { countAll: true }; + return lookup; + } + + private _getRecentActivityOrder(recentActivityOrderValue: string): RecentActivityOrder { + switch (recentActivityOrderValue) { + case nameof(x => x.updatedAt): return RecentActivityOrder.UpdatedAt; + case nameof(x => x.label): return RecentActivityOrder.Label; + case nameof(x => x.status): return RecentActivityOrder.Status; + } + } + + private get _lookupFields(): string[] { + return [ + nameof(x => x.id), + nameof(x => x.label), + nameof(x => x.status), + nameof(x => x.updatedAt), + nameof(x => x.belongsToCurrentTenant), + nameof(x => x.finalizedAt), + + [nameof(x => x.authorizationFlags), AppPermission.EditDescription].join('.'), + [nameof(x => x.authorizationFlags), AppPermission.DeleteDescription].join('.'), + [nameof(x => x.authorizationFlags), AppPermission.InviteDmpUsers].join('.'), + + [nameof(x => x.descriptionTemplate), nameof(x => x.id)].join('.'), + [nameof(x => x.descriptionTemplate), nameof(x => x.label)].join('.'), + [nameof(x => x.descriptionTemplate), nameof(x => x.groupId)].join('.'), + [nameof(x => x.dmp), nameof(x => x.id)].join('.'), + [nameof(x => x.dmp), nameof(x => x.label)].join('.'), + [nameof(x => x.dmp), nameof(x => x.status)].join('.'), + [nameof(x => x.dmp), nameof(x => x.accessType)].join('.'), + [nameof(x => x.dmp), nameof(x => x.blueprint), nameof(x => x.id)].join('.'), + [nameof(x => x.dmp), nameof(x => x.blueprint), nameof(x => x.label)].join('.'), + [nameof(x => x.dmp), nameof(x => x.blueprint), nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.id)].join('.'), + [nameof(x => x.dmp), nameof(x => x.blueprint), nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.label)].join('.'), + [nameof(x => x.dmp), nameof(x => x.blueprint), nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.hasTemplates)].join('.'), + [nameof(x => x.dmp), nameof(x => x.dmpUsers), nameof(x => x.id)].join('.'), + [nameof(x => x.dmp), nameof(x => x.dmpUsers), nameof(x => x.user.id)].join('.'), + [nameof(x => x.dmp), nameof(x => x.dmpUsers), nameof(x => x.role)].join('.'), + [nameof(x => x.dmp), nameof(x => x.dmpUsers), nameof(x => x.isActive)].join('.'), + [nameof(x => x.dmp), nameof(x => x.dmpReferences), nameof(x => x.id)].join('.'), + [nameof(x => x.dmp), nameof(x => x.dmpReferences), nameof(x => x.reference), nameof(x => x.id)].join('.'), + [nameof(x => x.dmp), nameof(x => x.dmpReferences), nameof(x => x.reference), nameof(x => x.label)].join('.'), + [nameof(x => x.dmp), nameof(x => x.dmpReferences), nameof(x => x.reference), nameof(x => x.type), nameof(x => x.id)].join('.'), + [nameof(x => x.dmp), nameof(x => x.dmpReferences), nameof(x => x.reference), nameof(x => x.reference)].join('.'), + [nameof(x => x.dmp), nameof(x => x.dmpReferences), nameof(x => x.isActive)].join('.'), + [nameof(x => x.dmpDescriptionTemplate), nameof(x => x.id)].join('.'), + [nameof(x => x.dmpDescriptionTemplate), nameof(x => x.dmp), nameof(x => x.id)].join('.'), + [nameof(x => x.dmpDescriptionTemplate), nameof(x => x.descriptionTemplateGroupId)].join('.'), + [nameof(x => x.dmpDescriptionTemplate), nameof(x => x.sectionId)].join('.'), + ]; + } }