diff --git a/backend/src/main/resources/i18n/messages.properties b/backend/src/main/resources/i18n/messages.properties index 1c207ee38..7cadee855 100644 --- a/backend/src/main/resources/i18n/messages.properties +++ b/backend/src/main/resources/i18n/messages.properties @@ -384,6 +384,18 @@ label.email=Email label.role=Role label.delete=Delete? +label.metadata-resolver-history=Metadata resolver history +label.metadata-version-history=Metadata Version History +label.select-version=Select Version +label.version=Version +label.save-date=Save Date +label.changed-by=Changed By +label.actions=Actions +label.check-to-select=Check to select +label.current=Current +label.restore=Restore +label.compare-selected=Compare Selected + message.delete-user-title=Delete User? message.delete-user-body=You are requesting to delete a user. If you complete this process the user will be removed. This cannot be undone. Do you wish to continue? diff --git a/ui/src/app/metadata/manager/component/entity-item.component.ts b/ui/src/app/metadata/manager/component/entity-item.component.ts index f9d1745c3..274b6ddf1 100644 --- a/ui/src/app/metadata/manager/component/entity-item.component.ts +++ b/ui/src/app/metadata/manager/component/entity-item.component.ts @@ -19,4 +19,5 @@ export class EntityItemComponent { @Output() toggle = new EventEmitter(); @Output() preview = new EventEmitter(); @Output() delete = new EventEmitter(); + @Output() history = new EventEmitter(); } diff --git a/ui/src/app/metadata/manager/component/resolver-item.component.html b/ui/src/app/metadata/manager/component/resolver-item.component.html index 0524c9d10..915a51906 100644 --- a/ui/src/app/metadata/manager/component/resolver-item.component.html +++ b/ui/src/app/metadata/manager/component/resolver-item.component.html @@ -39,6 +39,13 @@ aria-label="Review the XML of the selected service provider"> + \ No newline at end of file diff --git a/ui/src/app/metadata/version/component/history-list.component.spec.ts b/ui/src/app/metadata/version/component/history-list.component.spec.ts new file mode 100644 index 000000000..4cb6770eb --- /dev/null +++ b/ui/src/app/metadata/version/component/history-list.component.spec.ts @@ -0,0 +1,81 @@ +import { Component, ViewChild } from '@angular/core'; +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { MockI18nModule } from '../../../../testing/i18n.stub'; +import { HistoryListComponent } from './history-list.component'; +import { MetadataHistory } from '../model/history'; +import { MetadataVersion } from '../model/version'; + +export const TestData = { + versions: [ + { + versionNumber: 1, + saveDate: new Date(), + changedBy: 'admin', + actions: [] + } + ] +}; + +@Component({ + template: `` +}) +class TestHostComponent { + @ViewChild(HistoryListComponent) + public componentUnderTest: HistoryListComponent; + + history: MetadataHistory = TestData; + + compare(versions: MetadataVersion[]): void {} + restore(version: MetadataVersion): void {} +} + +describe('Metadata History List Component', () => { + let fixture: ComponentFixture; + let instance: TestHostComponent; + let table: HistoryListComponent; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [], + imports: [ + MockI18nModule + ], + declarations: [ + HistoryListComponent, + TestHostComponent + ], + }); + + fixture = TestBed.createComponent(TestHostComponent); + instance = fixture.componentInstance; + table = instance.componentUnderTest; + fixture.detectChanges(); + }); + + it('should compile', () => { + expect(table).toBeDefined(); + }); + + describe('compare selected', () => { + it('should allow the user to toggle selected versions for comparison', () => { + table.toggleVersionSelected(TestData.versions[0]); + expect(table.selected.length).toBe(1); + }); + + it('should emit an event with the selected values when the Compare Selected button is clicked', () => { + spyOn(instance, 'compare'); + const selected = TestData.versions; + table.compareSelected(selected); + fixture.detectChanges(); + expect(instance.compare).toHaveBeenCalledWith(selected); + }); + + it('should emit an event with the selected version when the Restore button is clicked', () => { + spyOn(instance, 'restore'); + const selected = TestData.versions[0]; + table.restoreVersion(selected); + fixture.detectChanges(); + expect(instance.restore).toHaveBeenCalledWith(selected); + }); + }); +}); diff --git a/ui/src/app/metadata/version/component/history-list.component.ts b/ui/src/app/metadata/version/component/history-list.component.ts new file mode 100644 index 000000000..34696a98e --- /dev/null +++ b/ui/src/app/metadata/version/component/history-list.component.ts @@ -0,0 +1,35 @@ +import { Component, Input, EventEmitter, ChangeDetectionStrategy, Output } from '@angular/core'; +import { MetadataHistory } from '../model/history'; +import { MetadataVersion } from '../model/version'; + +@Component({ + selector: 'history-list', + changeDetection: ChangeDetectionStrategy.OnPush, + templateUrl: './history-list.component.html', + styleUrls: [] +}) +export class HistoryListComponent { + @Input() history: MetadataHistory; + @Output() compare: EventEmitter = new EventEmitter(); + @Output() restore: EventEmitter = new EventEmitter(); + + selected: MetadataVersion[] = []; + + constructor() {} + + toggleVersionSelected(version: MetadataVersion): void { + if (this.selected.indexOf(version) > -1) { + this.selected = this.selected.filter(s => s !== version); + } else { + this.selected = [...this.selected, version]; + } + } + + compareSelected(selected: MetadataVersion[]): void { + this.compare.emit(selected); + } + + restoreVersion(version: MetadataVersion): void { + this.restore.emit(version); + } +} diff --git a/ui/src/app/metadata/version/container/version-history.component.html b/ui/src/app/metadata/version/container/version-history.component.html new file mode 100644 index 000000000..099841308 --- /dev/null +++ b/ui/src/app/metadata/version/container/version-history.component.html @@ -0,0 +1,18 @@ +
+
+
+
+
+ + + Metadata resolver history + +
+
+
+
+ +
+
+
diff --git a/ui/src/app/metadata/version/container/version-history.component.spec.ts b/ui/src/app/metadata/version/container/version-history.component.spec.ts new file mode 100644 index 000000000..4398b5128 --- /dev/null +++ b/ui/src/app/metadata/version/container/version-history.component.spec.ts @@ -0,0 +1,63 @@ +import { Component, ViewChild, Input, EventEmitter, Output } from '@angular/core'; +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { MockI18nModule } from '../../../../testing/i18n.stub'; +import { VersionHistoryComponent } from './version-history.component'; +import { MetadataHistory } from '../model/history'; +import { MetadataVersion } from '../model/version'; +import { HistoryService } from '../service/history.service'; +import { of } from 'rxjs'; + +export const TestData = { + versions: [ + { + versionNumber: 1, + saveDate: new Date(), + changedBy: 'admin', + actions: [] + } + ] +}; + +@Component({ + selector: 'history-list', + template: `` +}) +class MockHistoryListComponent { + @Input() history: MetadataHistory; + @Output() compare: EventEmitter = new EventEmitter(); + @Output() restore: EventEmitter = new EventEmitter(); +} + +const MockHistoryService = { + query: () => of(TestData) +}; + +describe('Metadata Version History Component', () => { + let fixture: ComponentFixture; + let instance: VersionHistoryComponent; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + { + provide: HistoryService, useValue: MockHistoryService + } + ], + imports: [ + MockI18nModule + ], + declarations: [ + MockHistoryListComponent, + VersionHistoryComponent + ], + }); + + fixture = TestBed.createComponent(VersionHistoryComponent); + instance = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should compile', () => { + expect(instance).toBeDefined(); + }); +}); diff --git a/ui/src/app/metadata/version/container/version-history.component.ts b/ui/src/app/metadata/version/container/version-history.component.ts new file mode 100644 index 000000000..dc26aa1fa --- /dev/null +++ b/ui/src/app/metadata/version/container/version-history.component.ts @@ -0,0 +1,21 @@ +import { Component, ChangeDetectionStrategy } from '@angular/core'; +import { MetadataHistory } from '../model/history'; +import { HistoryService } from '../service/history.service'; +import { Observable } from 'rxjs'; + +@Component({ + selector: 'version-history', + changeDetection: ChangeDetectionStrategy.OnPush, + templateUrl: './version-history.component.html', + styleUrls: [] +}) +export class VersionHistoryComponent { + + history$: Observable; + + constructor( + private historyService: HistoryService + ) { + this.history$ = this.historyService.query(); + } +} diff --git a/ui/src/app/metadata/version/model/history.ts b/ui/src/app/metadata/version/model/history.ts new file mode 100644 index 000000000..2fec132da --- /dev/null +++ b/ui/src/app/metadata/version/model/history.ts @@ -0,0 +1,5 @@ +import { MetadataVersion } from './version'; + +export interface MetadataHistory { + versions: MetadataVersion[]; +} diff --git a/ui/src/app/metadata/version/model/version.ts b/ui/src/app/metadata/version/model/version.ts new file mode 100644 index 000000000..85d1c9c7d --- /dev/null +++ b/ui/src/app/metadata/version/model/version.ts @@ -0,0 +1,6 @@ +export interface MetadataVersion { + versionNumber: Number; + saveDate: Date; + changedBy: String; + actions: string[]; +} diff --git a/ui/src/app/metadata/version/service/history.service.spec.ts b/ui/src/app/metadata/version/service/history.service.spec.ts new file mode 100644 index 000000000..71e6ec960 --- /dev/null +++ b/ui/src/app/metadata/version/service/history.service.spec.ts @@ -0,0 +1,28 @@ +import { TestBed, async, inject } from '@angular/core/testing'; +import { HttpClientModule } from '@angular/common/http'; +import { HttpTestingController, HttpClientTestingModule } from '@angular/common/http/testing'; +import { HistoryService } from './history.service'; + +describe(`Attributes Service`, () => { + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + HttpClientModule, + HttpClientTestingModule + ], + providers: [ + HistoryService + ] + }); + }); + + describe('query method', () => { + it(`should return a MetadataHistory`, async(inject([HistoryService, HttpTestingController], + (service: HistoryService) => { + service.query().subscribe(history => { + expect(history).toBeDefined(); + }); + } + ))); + }); +}); diff --git a/ui/src/app/metadata/version/service/history.service.ts b/ui/src/app/metadata/version/service/history.service.ts new file mode 100644 index 000000000..acf22b2ff --- /dev/null +++ b/ui/src/app/metadata/version/service/history.service.ts @@ -0,0 +1,34 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable, of } from 'rxjs'; +import { MetadataHistory } from '../model/history'; + + +@Injectable() +export class HistoryService { + + readonly base = '/api'; + + constructor( + private http: HttpClient + ) { } + + query(): Observable { + return of({ + versions: [ + { + versionNumber: 1, + saveDate: new Date(), + changedBy: 'admin', + actions: [] + }, + { + versionNumber: 2, + saveDate: new Date(), + changedBy: 'admin', + actions: ['restore'] + } + ] + }); + } +} diff --git a/ui/src/app/metadata/version/version.module.ts b/ui/src/app/metadata/version/version.module.ts new file mode 100644 index 000000000..2e3d36fb5 --- /dev/null +++ b/ui/src/app/metadata/version/version.module.ts @@ -0,0 +1,42 @@ +import { NgModule, ModuleWithProviders } from '@angular/core'; +import { StoreModule } from '@ngrx/store'; +import { EffectsModule } from '@ngrx/effects'; +import { HistoryListComponent } from './component/history-list.component'; +import { CommonModule } from '@angular/common'; +import { VersionHistoryComponent } from './container/version-history.component'; +import { HistoryService } from './service/history.service'; +import { I18nModule } from '../../i18n/i18n.module'; + +@NgModule({ + declarations: [ + HistoryListComponent, + VersionHistoryComponent + ], + entryComponents: [], + imports: [ + CommonModule, + I18nModule + ], + exports: [], + providers: [] +}) +export class MetadataVersionModule { + static forRoot(): ModuleWithProviders { + return { + ngModule: RootMetadataVersionModule, + providers: [] + }; + } +} + +@NgModule({ + imports: [ + MetadataVersionModule, + // StoreModule.forFeature('resolver', fromResolver.reducers), + // EffectsModule.forFeature([]) + ], + providers: [ + HistoryService + ] +}) +export class RootMetadataVersionModule { } diff --git a/ui/src/app/metadata/version/version.routing.ts b/ui/src/app/metadata/version/version.routing.ts new file mode 100644 index 000000000..dfb76468d --- /dev/null +++ b/ui/src/app/metadata/version/version.routing.ts @@ -0,0 +1,9 @@ +import { Routes } from '@angular/router'; +import { VersionHistoryComponent } from './container/version-history.component'; + +export const VersionRoutes: Routes = [ + { + path: ':type/:id/versions', + component: VersionHistoryComponent + } +];