From 2f64df252d10c9cd5758194594a255e9231adff3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laure-H=C3=A9l=C3=A8ne=20Bruneton?= Date: Wed, 17 Jul 2024 12:05:35 +0200 Subject: [PATCH] feat(editor): add record action menu to duplicate --- .../app/records/records-list.component.html | 1 + .../records/records-list.component.spec.ts | 14 ++++++ .../src/app/records/records-list.component.ts | 4 ++ .../search-records-list.component.html | 1 + .../search-records-list.component.spec.ts | 14 ++++++ .../search-records-list.component.ts | 4 ++ .../results-table-container.component.html | 1 + .../results-table-container.component.spec.ts | 47 ++++++++++++++----- .../results-table-container.component.ts | 5 ++ .../results-table.component.html | 34 ++++++++++++-- .../results-table.component.spec.ts | 41 ++++++++++++---- .../results-table/results-table.component.ts | 30 +++++++----- 12 files changed, 159 insertions(+), 37 deletions(-) diff --git a/apps/metadata-editor/src/app/records/records-list.component.html b/apps/metadata-editor/src/app/records/records-list.component.html index 7b214aa574..710f8cfa65 100644 --- a/apps/metadata-editor/src/app/records/records-list.component.html +++ b/apps/metadata-editor/src/app/records/records-list.component.html @@ -53,6 +53,7 @@

>
diff --git a/apps/metadata-editor/src/app/records/records-list.component.spec.ts b/apps/metadata-editor/src/app/records/records-list.component.spec.ts index 579cf33780..4cd15fdd74 100644 --- a/apps/metadata-editor/src/app/records/records-list.component.spec.ts +++ b/apps/metadata-editor/src/app/records/records-list.component.spec.ts @@ -22,6 +22,7 @@ const totalPages = 25 }) export class ResultsTableContainerComponent { @Output() recordClick = new EventEmitter() + @Output() duplicateRecord = new EventEmitter() } @Component({ @@ -136,6 +137,19 @@ describe('RecordsListComponent', () => { expect(router.navigate).toHaveBeenCalledWith(['/edit', 123]) }) }) + describe('when asking for record duplication', () => { + const uniqueIdentifier = 123 + const singleRecord = { + ...DATASET_RECORDS[0], + uniqueIdentifier, + } + beforeEach(() => { + table.duplicateRecord.emit(singleRecord) + }) + it('routes to record duplication', () => { + expect(router.navigate).toHaveBeenCalledWith(['/duplicate', 123]) + }) + }) describe('when click on pagination', () => { beforeEach(() => { pagination.newCurrentPageEvent.emit(3) diff --git a/apps/metadata-editor/src/app/records/records-list.component.ts b/apps/metadata-editor/src/app/records/records-list.component.ts index a9f4b9801f..5ef8dc82b3 100644 --- a/apps/metadata-editor/src/app/records/records-list.component.ts +++ b/apps/metadata-editor/src/app/records/records-list.component.ts @@ -67,6 +67,10 @@ export class RecordsListComponent { this.router.navigate(['/edit', record.uniqueIdentifier]) } + duplicateRecord(record: CatalogRecord) { + this.router.navigate(['/duplicate', record.uniqueIdentifier]) + } + showUsers() { this.router.navigate(['/users/my-org']) } diff --git a/apps/metadata-editor/src/app/records/search-records/search-records-list.component.html b/apps/metadata-editor/src/app/records/search-records/search-records-list.component.html index 3ad6cb07f9..d07c2c50ef 100644 --- a/apps/metadata-editor/src/app/records/search-records/search-records-list.component.html +++ b/apps/metadata-editor/src/app/records/search-records/search-records-list.component.html @@ -43,6 +43,7 @@

diff --git a/apps/metadata-editor/src/app/records/search-records/search-records-list.component.spec.ts b/apps/metadata-editor/src/app/records/search-records/search-records-list.component.spec.ts index 600317c1ff..bbbe974ba2 100644 --- a/apps/metadata-editor/src/app/records/search-records/search-records-list.component.spec.ts +++ b/apps/metadata-editor/src/app/records/search-records/search-records-list.component.spec.ts @@ -30,6 +30,7 @@ const totalPages = 25 }) export class ResultsTableContainerComponent { @Output() recordClick = new EventEmitter() + @Output() duplicateRecord = new EventEmitter() } @Component({ @@ -152,6 +153,19 @@ describe('SearchRecordsComponent', () => { expect(router.navigate).toHaveBeenCalledWith(['/edit', 123]) }) }) + describe('when asking for record duplication', () => { + const uniqueIdentifier = 123 + const singleRecord = { + ...DATASET_RECORDS[0], + uniqueIdentifier, + } + beforeEach(() => { + table.duplicateRecord.emit(singleRecord) + }) + it('routes to record duplication', () => { + expect(router.navigate).toHaveBeenCalledWith(['/duplicate', 123]) + }) + }) describe('when click on pagination', () => { beforeEach(() => { pagination.newCurrentPageEvent.emit(3) diff --git a/apps/metadata-editor/src/app/records/search-records/search-records-list.component.ts b/apps/metadata-editor/src/app/records/search-records/search-records-list.component.ts index 7bd7ee44b8..1a6b4ade82 100644 --- a/apps/metadata-editor/src/app/records/search-records/search-records-list.component.ts +++ b/apps/metadata-editor/src/app/records/search-records/search-records-list.component.ts @@ -48,6 +48,10 @@ export class SearchRecordsComponent { this.router.navigate(['/edit', record.uniqueIdentifier]) } + duplicateRecord(record: CatalogRecord) { + this.router.navigate(['/duplicate', record.uniqueIdentifier]) + } + createRecord() { this.router.navigate(['/create']) } diff --git a/libs/feature/search/src/lib/results-table/results-table-container.component.html b/libs/feature/search/src/lib/results-table/results-table-container.component.html index 0853ac710e..b0aeb07004 100644 --- a/libs/feature/search/src/lib/results-table/results-table-container.component.html +++ b/libs/feature/search/src/lib/results-table/results-table-container.component.html @@ -4,6 +4,7 @@ [selectedRecordsIdentifiers]="selectedRecords$ | async" [sortOrder]="sortBy$ | async" (recordClick)="handleRecordClick($event)" + (duplicateRecord)="handleDuplicateRecord($event)" (recordsSelectedChange)="handleRecordsSelectedChange($event[0], $event[1])" (sortByChange)="handleSortByChange($event[0], $event[1])" > diff --git a/libs/feature/search/src/lib/results-table/results-table-container.component.spec.ts b/libs/feature/search/src/lib/results-table/results-table-container.component.spec.ts index 0c663abd04..08bb59e223 100644 --- a/libs/feature/search/src/lib/results-table/results-table-container.component.spec.ts +++ b/libs/feature/search/src/lib/results-table/results-table-container.component.spec.ts @@ -1,14 +1,15 @@ import { ComponentFixture, TestBed } from '@angular/core/testing' -import { DATASET_RECORDS } from '@geonetwork-ui/common/fixtures' -import { ResultsTableContainerComponent } from './results-table-container.component' -import { CatalogRecord } from '@geonetwork-ui/common/domain/model/record' import { By } from '@angular/platform-browser' +import { NoopAnimationsModule } from '@angular/platform-browser/animations' +import { SelectionService } from '@geonetwork-ui/api/repository' +import { CatalogRecord } from '@geonetwork-ui/common/domain/model/record' +import { RecordsRepositoryInterface } from '@geonetwork-ui/common/domain/repository/records-repository.interface' +import { DATASET_RECORDS } from '@geonetwork-ui/common/fixtures' +import { TranslateModule } from '@ngx-translate/core' import { BehaviorSubject } from 'rxjs' import { SearchFacade } from '../state/search.facade' import { SearchService } from '../utils/service/search.service' -import { SelectionService } from '@geonetwork-ui/api/repository' -import { TranslateModule } from '@ngx-translate/core' -import { RecordsRepositoryInterface } from '@geonetwork-ui/common/domain/repository/records-repository.interface' +import { ResultsTableContainerComponent } from './results-table-container.component' class SearchFacadeMock { results$ = new BehaviorSubject(DATASET_RECORDS) @@ -40,7 +41,7 @@ describe('ResultsTableContainerComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [TranslateModule.forRoot()], + imports: [TranslateModule.forRoot(), NoopAnimationsModule], providers: [ { provide: SearchFacade, @@ -106,7 +107,7 @@ describe('ResultsTableContainerComponent', () => { }) }) - describe('clicking on a dataset', () => { + describe('clicking on a title', () => { let clickedRecord: CatalogRecord beforeEach(() => { @@ -115,14 +116,36 @@ describe('ResultsTableContainerComponent', () => { }) it('emits a recordClick event', () => { - const tableRow = fixture.debugElement.queryAll( - By.css('.table-row-cell') - )[1].nativeElement as HTMLDivElement - tableRow.parentElement.click() + const titleCell = fixture.debugElement.query( + By.css('[data-test="record-title-cell"]') + ).nativeElement as HTMLDivElement + titleCell.click() expect(clickedRecord).toEqual(DATASET_RECORDS[0]) }) }) + describe('duplicating a dataset', () => { + let recordToBeDuplicated: CatalogRecord + + beforeEach(() => { + recordToBeDuplicated = null + component.duplicateRecord.subscribe((r) => (recordToBeDuplicated = r)) + }) + + it('emits a duplicateRecord event', () => { + const menuButton = fixture.debugElement.query( + By.css('[data-test="record-menu-button"]') + ).nativeElement as HTMLButtonElement + menuButton.click() + fixture.detectChanges() + const duplicateButton = fixture.debugElement.query( + By.css('[data-test="record-menu-duplicate-button"]') + ).nativeElement as HTMLButtonElement + duplicateButton.click() + expect(recordToBeDuplicated).toEqual(DATASET_RECORDS[0]) + }) + }) + describe('#hasDraft', () => { it('calls the repository service', () => { const record = DATASET_RECORDS[0] diff --git a/libs/feature/search/src/lib/results-table/results-table-container.component.ts b/libs/feature/search/src/lib/results-table/results-table-container.component.ts index 10c076e4c4..6b1cf8ebce 100644 --- a/libs/feature/search/src/lib/results-table/results-table-container.component.ts +++ b/libs/feature/search/src/lib/results-table/results-table-container.component.ts @@ -16,6 +16,7 @@ import { CommonModule } from '@angular/common' }) export class ResultsTableContainerComponent { @Output() recordClick = new EventEmitter() + @Output() duplicateRecord = new EventEmitter() records$ = this.searchFacade.results$ selectedRecords$ = this.selectionService.selectedRecordsIdentifiers$ @@ -35,6 +36,10 @@ export class ResultsTableContainerComponent { this.recordClick.emit(item as CatalogRecord) } + handleDuplicateRecord(item: unknown) { + this.duplicateRecord.emit(item as CatalogRecord) + } + handleSortByChange(col: string, order: 'asc' | 'desc') { this.searchService.setSortBy([order, col]) } diff --git a/libs/ui/search/src/lib/results-table/results-table.component.html b/libs/ui/search/src/lib/results-table/results-table.component.html index 1b0e3d1225..8ca4f64814 100644 --- a/libs/ui/search/src/lib/results-table/results-table.component.html +++ b/libs/ui/search/src/lib/results-table/results-table.component.html @@ -1,7 +1,4 @@ - + @@ -34,7 +31,11 @@ record.metadata.title -
+
{{ item.title }} + + + + + + + + + + + diff --git a/libs/ui/search/src/lib/results-table/results-table.component.spec.ts b/libs/ui/search/src/lib/results-table/results-table.component.spec.ts index d940a4a614..d484cbd6e7 100644 --- a/libs/ui/search/src/lib/results-table/results-table.component.spec.ts +++ b/libs/ui/search/src/lib/results-table/results-table.component.spec.ts @@ -1,9 +1,10 @@ import { ComponentFixture, TestBed } from '@angular/core/testing' -import { DATASET_RECORDS } from '@geonetwork-ui/common/fixtures' -import { ResultsTableComponent } from './results-table.component' -import { CatalogRecord } from '@geonetwork-ui/common/domain/model/record' import { By } from '@angular/platform-browser' +import { NoopAnimationsModule } from '@angular/platform-browser/animations' +import { CatalogRecord } from '@geonetwork-ui/common/domain/model/record' +import { DATASET_RECORDS } from '@geonetwork-ui/common/fixtures' import { TranslateModule } from '@ngx-translate/core' +import { ResultsTableComponent } from './results-table.component' describe('ResultsTableComponent', () => { let component: ResultsTableComponent @@ -11,7 +12,7 @@ describe('ResultsTableComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [TranslateModule.forRoot()], + imports: [TranslateModule.forRoot(), NoopAnimationsModule], }).compileComponents() fixture = TestBed.createComponent(ResultsTableComponent) @@ -133,7 +134,7 @@ describe('ResultsTableComponent', () => { }) }) - describe('clicking on a dataset', () => { + describe('clicking on a title', () => { let clickedRecord: CatalogRecord beforeEach(() => { @@ -142,11 +143,33 @@ describe('ResultsTableComponent', () => { }) it('emits a recordClick event', () => { - const tableRow = fixture.debugElement.queryAll( - By.css('.table-row-cell') - )[1].nativeElement as HTMLDivElement - tableRow.parentElement.click() + const tableRow = fixture.debugElement.query( + By.css('[data-test="record-title-cell"]') + ).nativeElement as HTMLDivElement + tableRow.click() expect(clickedRecord).toEqual(DATASET_RECORDS[0]) }) }) + + describe('duplicating a dataset', () => { + let recordToBeDuplicated: CatalogRecord + + beforeEach(() => { + recordToBeDuplicated = null + component.duplicateRecord.subscribe((r) => (recordToBeDuplicated = r)) + }) + + it('emits a duplicateRecord event', () => { + const menuButton = fixture.debugElement.query( + By.css('[data-test="record-menu-button"]') + ).nativeElement as HTMLButtonElement + menuButton.click() + fixture.detectChanges() + const duplicateButton = fixture.debugElement.query( + By.css('[data-test="record-menu-duplicate-button"]') + ).nativeElement as HTMLButtonElement + duplicateButton.click() + expect(recordToBeDuplicated).toEqual(DATASET_RECORDS[0]) + }) + }) }) diff --git a/libs/ui/search/src/lib/results-table/results-table.component.ts b/libs/ui/search/src/lib/results-table/results-table.component.ts index 6752d70fdc..ec3307df0c 100644 --- a/libs/ui/search/src/lib/results-table/results-table.component.ts +++ b/libs/ui/search/src/lib/results-table/results-table.component.ts @@ -1,23 +1,24 @@ +import { CommonModule } from '@angular/common' import { Component, EventEmitter, Input, Output } from '@angular/core' +import { MatIconModule } from '@angular/material/icon' +import { MatMenuModule } from '@angular/material/menu' import { CatalogRecord } from '@geonetwork-ui/common/domain/model/record' import { - FileFormat, - getBadgeColor, - getFileFormat, - getFormatPriority, -} from '@geonetwork-ui/util/shared' + FieldSort, + SortByField, +} from '@geonetwork-ui/common/domain/model/search' import { BadgeComponent, UiInputsModule } from '@geonetwork-ui/ui/inputs' import { InteractiveTableColumnComponent, InteractiveTableComponent, } from '@geonetwork-ui/ui/layout' -import { MatIconModule } from '@angular/material/icon' -import { TranslateModule } from '@ngx-translate/core' -import { CommonModule } from '@angular/common' import { - FieldSort, - SortByField, -} from '@geonetwork-ui/common/domain/model/search' + FileFormat, + getBadgeColor, + getFileFormat, + getFormatPriority, +} from '@geonetwork-ui/util/shared' +import { TranslateModule } from '@ngx-translate/core' @Component({ selector: 'gn-ui-results-table', @@ -32,6 +33,7 @@ import { MatIconModule, TranslateModule, BadgeComponent, + MatMenuModule, ], }) export class ResultsTableComponent { @@ -43,6 +45,7 @@ export class ResultsTableComponent { // emits the column (field) as well as the order @Output() sortByChange = new EventEmitter<[string, 'asc' | 'desc']>() @Output() recordClick = new EventEmitter() + @Output() duplicateRecord = new EventEmitter() @Output() recordsSelectedChange = new EventEmitter< [CatalogRecord[], boolean] >() @@ -89,6 +92,11 @@ export class ResultsTableComponent { this.recordClick.emit(item as CatalogRecord) } + handleDuplicateClick(event: Event, item: unknown) { + event.stopPropagation() + this.duplicateRecord.emit(item as CatalogRecord) + } + setSortBy(col: string, order: 'asc' | 'desc') { this.sortByChange.emit([col, order]) }