Merge branch 'w2p-63669_Edit-Col/Com-Tabs' into w2p-65272_Edit-collection-item-template

This commit is contained in:
Kristof De Langhe
2019-10-03 14:41:15 +02:00
42 changed files with 879 additions and 128 deletions

View File

@@ -130,6 +130,15 @@
"collection.delete.text": "Are you sure you want to delete collection \"{{ dso }}\"",
"collection.edit.delete": "Delete this collection",
"collection.edit.head": "Edit Collection",
"collection.edit.return": "Return",
"collection.edit.tabs.curate.head": "Curate",
"collection.edit.tabs.curate.title": "Collection Edit - Curate",
"collection.edit.tabs.metadata.head": "Edit Metadata",
"collection.edit.tabs.metadata.title": "Collection Edit - Metadata",
"collection.edit.tabs.roles.head": "Assign Roles",
"collection.edit.tabs.roles.title": "Collection Edit - Roles",
"collection.edit.tabs.source.head": "Content Source",
"collection.edit.tabs.source.title": "Collection Edit - Content Source",
"collection.form.abstract": "Short Description",
"collection.form.description": "Introductory text (HTML)",
"collection.form.errors.title.required": "Please enter a collection name",
@@ -153,6 +162,13 @@
"community.delete.text": "Are you sure you want to delete community \"{{ dso }}\"",
"community.edit.delete": "Delete this community",
"community.edit.head": "Edit Community",
"community.edit.return": "Return",
"community.edit.tabs.curate.head": "Curate",
"community.edit.tabs.curate.title": "Community Edit - Curate",
"community.edit.tabs.metadata.head": "Edit Metadata",
"community.edit.tabs.metadata.title": "Community Edit - Metadata",
"community.edit.tabs.roles.head": "Assign Roles",
"community.edit.tabs.roles.title": "Community Edit - Roles",
"community.form.abstract": "Short Description",
"community.form.description": "Introductory text (HTML)",
"community.form.errors.title.required": "Please enter a community name",

View File

@@ -5,7 +5,6 @@ import { CollectionPageComponent } from './collection-page.component';
import { CollectionPageResolver } from './collection-page.resolver';
import { CreateCollectionPageComponent } from './create-collection-page/create-collection-page.component';
import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
import { EditCollectionPageComponent } from './edit-collection-page/edit-collection-page.component';
import { CreateCollectionPageGuard } from './create-collection-page/create-collection-page.guard';
import { DeleteCollectionPageComponent } from './delete-collection-page/delete-collection-page.component';
import { URLCombiner } from '../core/url-combiner/url-combiner';
@@ -38,12 +37,8 @@ const COLLECTION_EDIT_PATH = ':id/edit';
},
{
path: COLLECTION_EDIT_PATH,
pathMatch: 'full',
component: EditCollectionPageComponent,
canActivate: [AuthenticatedGuard],
resolve: {
dso: CollectionPageResolver
}
loadChildren: './edit-collection-page/edit-collection-page.module#EditCollectionPageModule',
canActivate: [AuthenticatedGuard]
},
{
path: ':id/delete',

View File

@@ -7,7 +7,6 @@ import { CollectionPageComponent } from './collection-page.component';
import { CollectionPageRoutingModule } from './collection-page-routing.module';
import { CreateCollectionPageComponent } from './create-collection-page/create-collection-page.component';
import { CollectionFormComponent } from './collection-form/collection-form.component';
import { EditCollectionPageComponent } from './edit-collection-page/edit-collection-page.component';
import { DeleteCollectionPageComponent } from './delete-collection-page/delete-collection-page.component';
import { SearchService } from '../+search-page/search-service/search.service';
@@ -20,10 +19,12 @@ import { SearchService } from '../+search-page/search-service/search.service';
declarations: [
CollectionPageComponent,
CreateCollectionPageComponent,
EditCollectionPageComponent,
DeleteCollectionPageComponent,
CollectionFormComponent
],
exports: [
CollectionFormComponent
],
providers: [
SearchService
]

View File

@@ -0,0 +1,12 @@
import { Component } from '@angular/core';
/**
* Component for managing a collection's curation tasks
*/
@Component({
selector: 'ds-collection-curate',
templateUrl: './collection-curate.component.html',
})
export class CollectionCurateComponent {
/* TODO: Implement Collection Edit - Curate */
}

View File

@@ -0,0 +1,4 @@
<ds-collection-form (submitForm)="onSubmit($event)" [dso]="(dsoRD$ | async)?.payload"></ds-collection-form>
<a class="btn btn-danger"
[routerLink]="'/collections/' + (dsoRD$ | async)?.payload.uuid + '/delete'">{{'collection.edit.delete'
| translate}}</a>

View File

@@ -0,0 +1,39 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TranslateModule } from '@ngx-translate/core';
import { SharedModule } from '../../../shared/shared.module';
import { CommonModule } from '@angular/common';
import { RouterTestingModule } from '@angular/router/testing';
import { CollectionDataService } from '../../../core/data/collection-data.service';
import { ActivatedRoute } from '@angular/router';
import { of as observableOf } from 'rxjs/internal/observable/of';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { CollectionMetadataComponent } from './collection-metadata.component';
describe('CollectionMetadataComponent', () => {
let comp: CollectionMetadataComponent;
let fixture: ComponentFixture<CollectionMetadataComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot(), SharedModule, CommonModule, RouterTestingModule],
declarations: [CollectionMetadataComponent],
providers: [
{ provide: CollectionDataService, useValue: {} },
{ provide: ActivatedRoute, useValue: { parent: { data: observableOf({ dso: { payload: {} } }) } } },
],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CollectionMetadataComponent);
comp = fixture.componentInstance;
fixture.detectChanges();
});
describe('frontendURL', () => {
it('should have the right frontendURL set', () => {
expect((comp as any).frontendURL).toEqual('/collections/');
})
});
});

View File

@@ -0,0 +1,24 @@
import { Component } from '@angular/core';
import { ComcolMetadataComponent } from '../../../shared/comcol-forms/edit-comcol-page/comcol-metadata/comcol-metadata.component';
import { Collection } from '../../../core/shared/collection.model';
import { CollectionDataService } from '../../../core/data/collection-data.service';
import { ActivatedRoute, Router } from '@angular/router';
/**
* Component for editing a collection's metadata
*/
@Component({
selector: 'ds-collection-metadata',
templateUrl: './collection-metadata.component.html',
})
export class CollectionMetadataComponent extends ComcolMetadataComponent<Collection> {
protected frontendURL = '/collections/';
public constructor(
protected collectionDataService: CollectionDataService,
protected router: Router,
protected route: ActivatedRoute
) {
super(collectionDataService, router, route);
}
}

View File

@@ -0,0 +1,12 @@
import { Component } from '@angular/core';
/**
* Component for managing a collection's roles
*/
@Component({
selector: 'ds-collection-roles',
templateUrl: './collection-roles.component.html',
})
export class CollectionRolesComponent {
/* TODO: Implement Collection Edit - Roles */
}

View File

@@ -0,0 +1,12 @@
import { Component } from '@angular/core';
/**
* Component for managing the content source of the collection
*/
@Component({
selector: 'ds-collection-source',
templateUrl: './collection-source.component.html',
})
export class CollectionSourceComponent {
/* TODO: Implement Collection Edit - Content Source */
}

View File

@@ -1,11 +0,0 @@
<div class="container">
<div class="row">
<div class="col-12 pb-4">
<h2 id="header" class="border-bottom pb-2">{{ 'collection.edit.head' | translate }}</h2>
<ds-collection-form (submitForm)="onSubmit($event)" [dso]="(dsoRD$ | async)?.payload"></ds-collection-form>
<a class="btn btn-danger"
[routerLink]="'/collections/' + (dsoRD$ | async)?.payload.uuid + '/delete'">{{'collection.edit.delete'
| translate}}</a>
</div>
</div>
</div>

View File

@@ -13,13 +13,29 @@ describe('EditCollectionPageComponent', () => {
let comp: EditCollectionPageComponent;
let fixture: ComponentFixture<EditCollectionPageComponent>;
const routeStub = {
data: observableOf({
dso: { payload: {} }
}),
routeConfig: {
children: []
},
snapshot: {
firstChild: {
routeConfig: {
path: 'mockUrl'
}
}
}
};
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot(), SharedModule, CommonModule, RouterTestingModule],
declarations: [EditCollectionPageComponent],
providers: [
{ provide: CollectionDataService, useValue: {} },
{ provide: ActivatedRoute, useValue: { data: observableOf({ dso: { payload: {} } }) } },
{ provide: ActivatedRoute, useValue: routeStub },
],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
@@ -31,9 +47,9 @@ describe('EditCollectionPageComponent', () => {
fixture.detectChanges();
});
describe('frontendURL', () => {
it('should have the right frontendURL set', () => {
expect((comp as any).frontendURL).toEqual('/collections/');
describe('type', () => {
it('should have the right type set', () => {
expect((comp as any).type).toEqual('collection');
})
});
});

View File

@@ -2,24 +2,30 @@ import { Component } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { EditComColPageComponent } from '../../shared/comcol-forms/edit-comcol-page/edit-comcol-page.component';
import { Collection } from '../../core/shared/collection.model';
import { CollectionDataService } from '../../core/data/collection-data.service';
import { getCollectionPageRoute } from '../collection-page-routing.module';
/**
* Component that represents the page where a user can edit an existing Collection
*/
@Component({
selector: 'ds-edit-collection',
styleUrls: ['./edit-collection-page.component.scss'],
templateUrl: './edit-collection-page.component.html'
templateUrl: '../../shared/comcol-forms/edit-comcol-page/edit-comcol-page.component.html'
})
export class EditCollectionPageComponent extends EditComColPageComponent<Collection> {
protected frontendURL = '/collections/';
protected type = 'collection';
public constructor(
protected collectionDataService: CollectionDataService,
protected router: Router,
protected route: ActivatedRoute
) {
super(collectionDataService, router, route);
super(router, route);
}
/**
* Get the collection page url
* @param collection The collection for which the url is requested
*/
getPageUrl(collection: Collection): string {
return getCollectionPageRoute(collection.id)
}
}

View File

@@ -0,0 +1,32 @@
import { NgModule } from '@angular/core';
import { EditCollectionPageComponent } from './edit-collection-page.component';
import { CommonModule } from '@angular/common';
import { SharedModule } from '../../shared/shared.module';
import { EditCollectionPageRoutingModule } from './edit-collection-page.routing.module';
import { CollectionMetadataComponent } from './collection-metadata/collection-metadata.component';
import { CollectionPageModule } from '../collection-page.module';
import { CollectionRolesComponent } from './collection-roles/collection-roles.component';
import { CollectionCurateComponent } from './collection-curate/collection-curate.component';
import { CollectionSourceComponent } from './collection-source/collection-source.component';
/**
* Module that contains all components related to the Edit Collection page administrator functionality
*/
@NgModule({
imports: [
CommonModule,
SharedModule,
EditCollectionPageRoutingModule,
CollectionPageModule
],
declarations: [
EditCollectionPageComponent,
CollectionMetadataComponent,
CollectionRolesComponent,
CollectionCurateComponent,
CollectionSourceComponent
]
})
export class EditCollectionPageModule {
}

View File

@@ -0,0 +1,61 @@
import { RouterModule } from '@angular/router';
import { NgModule } from '@angular/core';
import { EditCollectionPageComponent } from './edit-collection-page.component';
import { CollectionPageResolver } from '../collection-page.resolver';
import { CollectionMetadataComponent } from './collection-metadata/collection-metadata.component';
import { CollectionRolesComponent } from './collection-roles/collection-roles.component';
import { CollectionSourceComponent } from './collection-source/collection-source.component';
import { CollectionCurateComponent } from './collection-curate/collection-curate.component';
/**
* Routing module that handles the routing for the Edit Collection page administrator functionality
*/
@NgModule({
imports: [
RouterModule.forChild([
{
path: '',
component: EditCollectionPageComponent,
resolve: {
dso: CollectionPageResolver
},
children: [
{
path: '',
redirectTo: 'metadata',
pathMatch: 'full'
},
{
path: 'metadata',
component: CollectionMetadataComponent,
data: {
title: 'collection.edit.tabs.metadata.title',
hideReturnButton: true
}
},
{
path: 'roles',
component: CollectionRolesComponent,
data: { title: 'collection.edit.tabs.roles.title' }
},
{
path: 'source',
component: CollectionSourceComponent,
data: { title: 'collection.edit.tabs.source.title' }
},
{
path: 'curate',
component: CollectionCurateComponent,
data: { title: 'collection.edit.tabs.curate.title' }
}
]
}
])
],
providers: [
CollectionPageResolver,
]
})
export class EditCollectionPageRoutingModule {
}

View File

@@ -5,7 +5,6 @@ import { CommunityPageComponent } from './community-page.component';
import { CommunityPageResolver } from './community-page.resolver';
import { CreateCommunityPageComponent } from './create-community-page/create-community-page.component';
import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
import { EditCommunityPageComponent } from './edit-community-page/edit-community-page.component';
import { CreateCommunityPageGuard } from './create-community-page/create-community-page.guard';
import { DeleteCommunityPageComponent } from './delete-community-page/delete-community-page.component';
import { URLCombiner } from '../core/url-combiner/url-combiner';
@@ -38,12 +37,8 @@ const COMMUNITY_EDIT_PATH = ':id/edit';
},
{
path: COMMUNITY_EDIT_PATH,
pathMatch: 'full',
component: EditCommunityPageComponent,
canActivate: [AuthenticatedGuard],
resolve: {
dso: CommunityPageResolver
}
loadChildren: './edit-community-page/edit-community-page.module#EditCommunityPageModule',
canActivate: [AuthenticatedGuard]
},
{
path: ':id/delete',

View File

@@ -9,7 +9,6 @@ import { CommunityPageRoutingModule } from './community-page-routing.module';
import {CommunityPageSubCommunityListComponent} from './sub-community-list/community-page-sub-community-list.component';
import { CreateCommunityPageComponent } from './create-community-page/create-community-page.component';
import { CommunityFormComponent } from './community-form/community-form.component';
import { EditCommunityPageComponent } from './edit-community-page/edit-community-page.component';
import { DeleteCommunityPageComponent } from './delete-community-page/delete-community-page.component';
@NgModule({
@@ -23,9 +22,11 @@ import { DeleteCommunityPageComponent } from './delete-community-page/delete-com
CommunityPageSubCollectionListComponent,
CommunityPageSubCommunityListComponent,
CreateCommunityPageComponent,
EditCommunityPageComponent,
DeleteCommunityPageComponent,
CommunityFormComponent
],
exports: [
CommunityFormComponent
]
})

View File

@@ -0,0 +1,12 @@
import { Component } from '@angular/core';
/**
* Component for managing a community's curation tasks
*/
@Component({
selector: 'ds-community-curate',
templateUrl: './community-curate.component.html',
})
export class CommunityCurateComponent {
/* TODO: Implement Community Edit - Curate */
}

View File

@@ -0,0 +1,4 @@
<ds-community-form (submitForm)="onSubmit($event)" [dso]="(dsoRD$ | async)?.payload"></ds-community-form>
<a class="btn btn-danger"
[routerLink]="'/communities/' + (dsoRD$ | async)?.payload.uuid + '/delete'">{{'community.edit.delete'
| translate}}</a>

View File

@@ -0,0 +1,39 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TranslateModule } from '@ngx-translate/core';
import { SharedModule } from '../../../shared/shared.module';
import { CommonModule } from '@angular/common';
import { RouterTestingModule } from '@angular/router/testing';
import { ActivatedRoute } from '@angular/router';
import { of as observableOf } from 'rxjs/internal/observable/of';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { CommunityMetadataComponent } from './community-metadata.component';
import { CommunityDataService } from '../../../core/data/community-data.service';
describe('CommunityMetadataComponent', () => {
let comp: CommunityMetadataComponent;
let fixture: ComponentFixture<CommunityMetadataComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot(), SharedModule, CommonModule, RouterTestingModule],
declarations: [CommunityMetadataComponent],
providers: [
{ provide: CommunityDataService, useValue: {} },
{ provide: ActivatedRoute, useValue: { parent: { data: observableOf({ dso: { payload: {} } }) } } },
],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CommunityMetadataComponent);
comp = fixture.componentInstance;
fixture.detectChanges();
});
describe('frontendURL', () => {
it('should have the right frontendURL set', () => {
expect((comp as any).frontendURL).toEqual('/communities/');
})
});
});

View File

@@ -0,0 +1,24 @@
import { Component } from '@angular/core';
import { ComcolMetadataComponent } from '../../../shared/comcol-forms/edit-comcol-page/comcol-metadata/comcol-metadata.component';
import { ActivatedRoute, Router } from '@angular/router';
import { Community } from '../../../core/shared/community.model';
import { CommunityDataService } from '../../../core/data/community-data.service';
/**
* Component for editing a community's metadata
*/
@Component({
selector: 'ds-community-metadata',
templateUrl: './community-metadata.component.html',
})
export class CommunityMetadataComponent extends ComcolMetadataComponent<Community> {
protected frontendURL = '/communities/';
public constructor(
protected communityDataService: CommunityDataService,
protected router: Router,
protected route: ActivatedRoute
) {
super(communityDataService, router, route);
}
}

View File

@@ -0,0 +1,12 @@
import { Component } from '@angular/core';
/**
* Component for managing a community's roles
*/
@Component({
selector: 'ds-community-roles',
templateUrl: './community-roles.component.html',
})
export class CommunityRolesComponent {
/* TODO: Implement Community Edit - Roles */
}

View File

@@ -1,12 +0,0 @@
<div class="container">
<div class="row">
<div class="col-12 pb-4">
<h2 id="header" class="border-bottom pb-2">{{ 'community.edit.head' | translate }}</h2>
<ds-community-form (submitForm)="onSubmit($event)"
[dso]="(dsoRD$ | async)?.payload"></ds-community-form>
<a class="btn btn-danger"
[routerLink]="'/communities/' + (dsoRD$ | async)?.payload.uuid + '/delete'">{{'community.edit.delete'
| translate}}</a>
</div>
</div>
</div>

View File

@@ -13,13 +13,29 @@ describe('EditCommunityPageComponent', () => {
let comp: EditCommunityPageComponent;
let fixture: ComponentFixture<EditCommunityPageComponent>;
const routeStub = {
data: observableOf({
dso: { payload: {} }
}),
routeConfig: {
children: []
},
snapshot: {
firstChild: {
routeConfig: {
path: 'mockUrl'
}
}
}
};
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot(), SharedModule, CommonModule, RouterTestingModule],
declarations: [EditCommunityPageComponent],
providers: [
{ provide: CommunityDataService, useValue: {} },
{ provide: ActivatedRoute, useValue: { data: observableOf({ dso: { payload: {} } }) } },
{ provide: ActivatedRoute, useValue: routeStub },
],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
@@ -31,9 +47,9 @@ describe('EditCommunityPageComponent', () => {
fixture.detectChanges();
});
describe('frontendURL', () => {
it('should have the right frontendURL set', () => {
expect((comp as any).frontendURL).toEqual('/communities/');
describe('type', () => {
it('should have the right type set', () => {
expect((comp as any).type).toEqual('community');
})
});
});

View File

@@ -1,25 +1,31 @@
import { Component } from '@angular/core';
import { Community } from '../../core/shared/community.model';
import { CommunityDataService } from '../../core/data/community-data.service';
import { ActivatedRoute, Router } from '@angular/router';
import { EditComColPageComponent } from '../../shared/comcol-forms/edit-comcol-page/edit-comcol-page.component';
import { getCommunityPageRoute } from '../community-page-routing.module';
/**
* Component that represents the page where a user can edit an existing Community
*/
@Component({
selector: 'ds-edit-community',
styleUrls: ['./edit-community-page.component.scss'],
templateUrl: './edit-community-page.component.html'
templateUrl: '../../shared/comcol-forms/edit-comcol-page/edit-comcol-page.component.html'
})
export class EditCommunityPageComponent extends EditComColPageComponent<Community> {
protected frontendURL = '/communities/';
protected type = 'community';
public constructor(
protected communityDataService: CommunityDataService,
protected router: Router,
protected route: ActivatedRoute
) {
super(communityDataService, router, route);
super(router, route);
}
/**
* Get the community page url
* @param community The community for which the url is requested
*/
getPageUrl(community: Community): string {
return getCommunityPageRoute(community.id)
}
}

View File

@@ -0,0 +1,30 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SharedModule } from '../../shared/shared.module';
import { EditCommunityPageRoutingModule } from './edit-community-page.routing.module';
import { CommunityPageModule } from '../community-page.module';
import { EditCommunityPageComponent } from './edit-community-page.component';
import { CommunityCurateComponent } from './community-curate/community-curate.component';
import { CommunityMetadataComponent } from './community-metadata/community-metadata.component';
import { CommunityRolesComponent } from './community-roles/community-roles.component';
/**
* Module that contains all components related to the Edit Community page administrator functionality
*/
@NgModule({
imports: [
CommonModule,
SharedModule,
EditCommunityPageRoutingModule,
CommunityPageModule
],
declarations: [
EditCommunityPageComponent,
CommunityCurateComponent,
CommunityMetadataComponent,
CommunityRolesComponent
]
})
export class EditCommunityPageModule {
}

View File

@@ -0,0 +1,52 @@
import { CommunityPageResolver } from '../community-page.resolver';
import { EditCommunityPageComponent } from './edit-community-page.component';
import { RouterModule } from '@angular/router';
import { NgModule } from '@angular/core';
import { CommunityMetadataComponent } from './community-metadata/community-metadata.component';
import { CommunityRolesComponent } from './community-roles/community-roles.component';
import { CommunityCurateComponent } from './community-curate/community-curate.component';
/**
* Routing module that handles the routing for the Edit Community page administrator functionality
*/
@NgModule({
imports: [
RouterModule.forChild([
{
path: '',
component: EditCommunityPageComponent,
resolve: {
dso: CommunityPageResolver
},
children: [
{
path: '',
redirectTo: 'metadata',
pathMatch: 'full'
},
{
path: 'metadata',
component: CommunityMetadataComponent,
data: { title: 'community.edit.tabs.metadata.title' }
},
{
path: 'roles',
component: CommunityRolesComponent,
data: { title: 'community.edit.tabs.roles.title' }
},
{
path: 'curate',
component: CommunityCurateComponent,
data: { title: 'community.edit.tabs.curate.title' }
}
]
}
])
],
providers: [
CommunityPageResolver,
]
})
export class EditCommunityPageRoutingModule {
}

View File

@@ -182,9 +182,9 @@ export class RelationshipService {
map(([leftItems, rightItems, relTypesCurrentPage]) => {
return relTypesCurrentPage.map((type, index) => {
if (leftItems[index].uuid === item.uuid) {
return type.leftLabel;
return type.leftwardType;
} else {
return type.rightLabel;
return type.rightwardType;
}
});
}),

View File

@@ -0,0 +1,108 @@
import { DSpaceObject } from '../../../../core/shared/dspace-object.model';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CommunityDataService } from '../../../../core/data/community-data.service';
import { ActivatedRoute, Router } from '@angular/router';
import { Community } from '../../../../core/shared/community.model';
import { of as observableOf } from 'rxjs/internal/observable/of';
import { RemoteData } from '../../../../core/data/remote-data';
import { TranslateModule } from '@ngx-translate/core';
import { SharedModule } from '../../../shared.module';
import { CommonModule } from '@angular/common';
import { RouterTestingModule } from '@angular/router/testing';
import { DataService } from '../../../../core/data/data.service';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { ComcolMetadataComponent } from './comcol-metadata.component';
describe('ComColMetadataComponent', () => {
let comp: ComcolMetadataComponent<DSpaceObject>;
let fixture: ComponentFixture<ComcolMetadataComponent<DSpaceObject>>;
let dsoDataService: CommunityDataService;
let router: Router;
let community;
let newCommunity;
let communityDataServiceStub;
let routerStub;
let routeStub;
function initializeVars() {
community = Object.assign(new Community(), {
uuid: 'a20da287-e174-466a-9926-f66b9300d347',
metadata: [{
key: 'dc.title',
value: 'test community'
}]
});
newCommunity = Object.assign(new Community(), {
uuid: '1ff59938-a69a-4e62-b9a4-718569c55d48',
metadata: [{
key: 'dc.title',
value: 'new community'
}]
});
communityDataServiceStub = {
update: (com, uuid?) => observableOf(new RemoteData(false, false, true, undefined, newCommunity))
};
routerStub = {
navigate: (commands) => commands
};
routeStub = {
parent: {
data: observableOf(community)
}
};
}
beforeEach(async(() => {
initializeVars();
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot(), SharedModule, CommonModule, RouterTestingModule],
providers: [
{ provide: DataService, useValue: communityDataServiceStub },
{ provide: Router, useValue: routerStub },
{ provide: ActivatedRoute, useValue: routeStub },
],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ComcolMetadataComponent);
comp = fixture.componentInstance;
fixture.detectChanges();
dsoDataService = (comp as any).dsoDataService;
router = (comp as any).router;
});
describe('onSubmit', () => {
let data;
beforeEach(() => {
data = Object.assign(new Community(), {
metadata: [{
key: 'dc.title',
value: 'test'
}]
});
});
it('should navigate when successful', () => {
spyOn(router, 'navigate');
comp.onSubmit(data);
fixture.detectChanges();
expect(router.navigate).toHaveBeenCalled();
});
it('should not navigate on failure', () => {
spyOn(router, 'navigate');
spyOn(dsoDataService, 'update').and.returnValue(observableOf(new RemoteData(true, true, false, undefined, newCommunity)));
comp.onSubmit(data);
fixture.detectChanges();
expect(router.navigate).not.toHaveBeenCalled();
});
});
});

View File

@@ -0,0 +1,48 @@
import { Component, OnInit } from '@angular/core';
import { DSpaceObject } from '../../../../core/shared/dspace-object.model';
import { Observable } from 'rxjs/internal/Observable';
import { RemoteData } from '../../../../core/data/remote-data';
import { ActivatedRoute, Router } from '@angular/router';
import { first, map } from 'rxjs/operators';
import { getSucceededRemoteData } from '../../../../core/shared/operators';
import { isNotUndefined } from '../../../empty.util';
import { DataService } from '../../../../core/data/data.service';
@Component({
selector: 'ds-comcol-metadata',
template: ''
})
export class ComcolMetadataComponent<TDomain extends DSpaceObject> implements OnInit {
/**
* Frontend endpoint for this type of DSO
*/
protected frontendURL: string;
public dsoRD$: Observable<RemoteData<TDomain>>;
public constructor(
protected dsoDataService: DataService<TDomain>,
protected router: Router,
protected route: ActivatedRoute
) {
}
ngOnInit(): void {
this.dsoRD$ = this.route.parent.data.pipe(first(), map((data) => data.dso));
}
/**
* @param {TDomain} dso The updated version of the DSO
* Updates an existing DSO based on the submitted user data and navigates to the edited object's home page
*/
onSubmit(dso: TDomain) {
this.dsoDataService.update(dso)
.pipe(getSucceededRemoteData())
.subscribe((dsoRD: RemoteData<TDomain>) => {
if (isNotUndefined(dsoRD)) {
const newUUID = dsoRD.payload.uuid;
this.router.navigate([this.frontendURL + newUUID]);
}
});
}
}

View File

@@ -0,0 +1,24 @@
<div class="container">
<div class="row">
<div class="col-12">
<h2 class="border-bottom">{{ type + '.edit.head' | translate }}</h2>
<div class="pt-2">
<ul class="nav nav-tabs justify-content-start mb-2">
<li *ngFor="let page of pages" class="nav-item">
<a class="nav-link"
[ngClass]="{'active' : page === currentPage}"
[routerLink]="['./' + page]">
{{ type + '.edit.tabs.' + page + '.head' | translate}}
</a>
</li>
</ul>
<div class="tab-pane active">
<div class="mb-4">
<router-outlet></router-outlet>
</div>
<a *ngIf="!hideReturnButton" [routerLink]="getPageUrl((dsoRD$ | async)?.payload)" class="btn btn-outline-secondary">{{ type + '.edit.return' | translate }}</a>
</div>
</div>
</div>
</div>
</div>

View File

@@ -1,5 +1,4 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CommunityDataService } from '../../../core/data/community-data.service';
import { ActivatedRoute, Router } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core';
import { of as observableOf } from 'rxjs';
@@ -10,21 +9,13 @@ import { RouterTestingModule } from '@angular/router/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
import { EditComColPageComponent } from './edit-comcol-page.component';
import { DataService } from '../../../core/data/data.service';
import {
createFailedRemoteDataObject$,
createSuccessfulRemoteDataObject$
} from '../../testing/utils';
describe('EditComColPageComponent', () => {
let comp: EditComColPageComponent<DSpaceObject>;
let fixture: ComponentFixture<EditComColPageComponent<DSpaceObject>>;
let dsoDataService: CommunityDataService;
let router: Router;
let community;
let newCommunity;
let communityDataServiceStub;
let routerStub;
let routeStub;
@@ -37,25 +28,26 @@ describe('EditComColPageComponent', () => {
}]
});
newCommunity = Object.assign(new Community(), {
uuid: '1ff59938-a69a-4e62-b9a4-718569c55d48',
metadata: [{
key: 'dc.title',
value: 'new community'
}]
});
communityDataServiceStub = {
update: (com, uuid?) => createSuccessfulRemoteDataObject$(newCommunity)
};
routerStub = {
navigate: (commands) => commands
navigate: (commands) => commands,
events: observableOf({}),
url: 'mockUrl'
};
routeStub = {
data: observableOf(community)
data: observableOf({
dso: community
}),
routeConfig: {
children: []
},
snapshot: {
firstChild: {
routeConfig: {
path: 'mockUrl'
}
}
}
};
}
@@ -65,7 +57,6 @@ describe('EditComColPageComponent', () => {
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot(), SharedModule, CommonModule, RouterTestingModule],
providers: [
{ provide: DataService, useValue: communityDataServiceStub },
{ provide: Router, useValue: routerStub },
{ provide: ActivatedRoute, useValue: routeStub },
],
@@ -77,33 +68,16 @@ describe('EditComColPageComponent', () => {
fixture = TestBed.createComponent(EditComColPageComponent);
comp = fixture.componentInstance;
fixture.detectChanges();
dsoDataService = (comp as any).dsoDataService;
router = (comp as any).router;
});
describe('onSubmit', () => {
let data;
describe('getPageUrl', () => {
let url;
beforeEach(() => {
data = Object.assign(new Community(), {
metadata: [{
key: 'dc.title',
value: 'test'
}]
});
url = comp.getPageUrl(community);
});
it('should navigate when successful', () => {
spyOn(router, 'navigate');
comp.onSubmit(data);
fixture.detectChanges();
expect(router.navigate).toHaveBeenCalled();
});
it('should not navigate on failure', () => {
spyOn(router, 'navigate');
spyOn(dsoDataService, 'update').and.returnValue(createFailedRemoteDataObject$(newCommunity));
comp.onSubmit(data);
fixture.detectChanges();
expect(router.navigate).not.toHaveBeenCalled();
it('should return the current url as a fallback', () => {
expect(url).toEqual(routerStub.url);
});
});
});

View File

@@ -2,7 +2,7 @@ import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { ActivatedRoute, Router } from '@angular/router';
import { RemoteData } from '../../../core/data/remote-data';
import { isNotUndefined } from '../../empty.util';
import { isNotEmpty, isNotUndefined } from '../../empty.util';
import { first, map } from 'rxjs/operators';
import { getSucceededRemoteData } from '../../../core/shared/operators';
import { DataService } from '../../../core/data/data.service';
@@ -17,37 +17,54 @@ import { DSpaceObject } from '../../../core/shared/dspace-object.model';
})
export class EditComColPageComponent<TDomain extends DSpaceObject> implements OnInit {
/**
* Frontend endpoint for this type of DSO
* The type of DSpaceObject (used to create i18n messages)
*/
protected frontendURL: string;
protected type: string;
/**
* The initial DSO object
* The current page outlet string
*/
public currentPage: string;
/**
* All possible page outlet strings
*/
public pages: string[];
/**
* The DSO to render the edit page for
*/
public dsoRD$: Observable<RemoteData<TDomain>>;
/**
* Hide the default return button?
*/
public hideReturnButton: boolean;
public constructor(
protected dsoDataService: DataService<TDomain>,
protected router: Router,
protected route: ActivatedRoute
) {
this.router.events.subscribe(() => {
this.currentPage = this.route.snapshot.firstChild.routeConfig.path;
this.hideReturnButton = this.route.routeConfig.children
.find((child: any) => child.path === this.currentPage).data.hideReturnButton;
});
}
ngOnInit(): void {
this.pages = this.route.routeConfig.children
.map((child: any) => child.path)
.filter((path: string) => isNotEmpty(path)); // ignore reroutes
this.dsoRD$ = this.route.data.pipe(first(), map((data) => data.dso));
}
/**
* @param {TDomain} dso The updated version of the DSO
* Updates an existing DSO based on the submitted user data and navigates to the edited object's home page
* Get the dso's page url
* This method is expected to be overridden in the edit community/collection page components
* @param dso The DSpaceObject for which the url is requested
*/
onSubmit(dso: TDomain) {
this.dsoDataService.update(dso)
.pipe(getSucceededRemoteData())
.subscribe((dsoRD: RemoteData<TDomain>) => {
if (isNotUndefined(dsoRD)) {
const newUUID = dsoRD.payload.uuid;
this.router.navigate([this.frontendURL + newUUID]);
}
});
getPageUrl(dso: TDomain): string {
return this.router.url;
}
}

View File

@@ -138,6 +138,8 @@ import { RoleDirective } from './roles/role.directive';
import { UserMenuComponent } from './auth-nav-menu/user-menu/user-menu.component';
import { ClaimedTaskActionsReturnToPoolComponent } from './mydspace-actions/claimed-task/return-to-pool/claimed-task-actions-return-to-pool.component';
import { ItemDetailPreviewFieldComponent } from './object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview-field/item-detail-preview-field.component';
import { AbstractTrackableComponent } from './trackable/abstract-trackable.component';
import { ComcolMetadataComponent } from './comcol-forms/edit-comcol-page/comcol-metadata/comcol-metadata.component';
import { FilterInputSuggestionsComponent } from './input-suggestions/filter-suggestions/filter-input-suggestions.component';
import { DsoInputSuggestionsComponent } from './input-suggestions/dso-input-suggestions/dso-input-suggestions.component';
import { TypedItemSearchResultGridElementComponent } from './object-grid/item-grid-element/item-types/typed-item-search-result-grid-element.component';
@@ -266,6 +268,8 @@ const COMPONENTS = [
TypedItemSearchResultGridElementComponent,
ItemTypeSwitcherComponent,
BrowseByComponent,
AbstractTrackableComponent,
ComcolMetadataComponent,
ItemTypeBadgeComponent
];
@@ -321,6 +325,7 @@ const SHARED_ITEM_PAGE_COMPONENTS = [
const PROVIDERS = [
TruncatableService,
MockAdminGuard,
AbstractTrackableComponent,
{
provide: DYNAMIC_FORM_CONTROL_MAP_FN,
useValue: dsDynamicFormControlMapFn

View File

@@ -0,0 +1,101 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { AbstractTrackableComponent } from './abstract-trackable.component';
import { INotification, Notification } from '../notifications/models/notification.model';
import { NotificationType } from '../notifications/models/notification-type';
import { of as observableOf } from 'rxjs';
import { TranslateModule } from '@ngx-translate/core';
import { ObjectUpdatesService } from '../../core/data/object-updates/object-updates.service';
import { NotificationsService } from '../notifications/notifications.service';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { TestScheduler } from 'rxjs/testing';
import { getTestScheduler } from 'jasmine-marbles';
describe('AbstractTrackableComponent', () => {
let comp: AbstractTrackableComponent;
let fixture: ComponentFixture<AbstractTrackableComponent>;
let objectUpdatesService;
let scheduler: TestScheduler;
const infoNotification: INotification = new Notification('id', NotificationType.Info, 'info');
const warningNotification: INotification = new Notification('id', NotificationType.Warning, 'warning');
const successNotification: INotification = new Notification('id', NotificationType.Success, 'success');
const notificationsService = jasmine.createSpyObj('notificationsService',
{
info: infoNotification,
warning: warningNotification,
success: successNotification
}
);
const url = 'http://test-url.com/test-url';
beforeEach(async(() => {
objectUpdatesService = jasmine.createSpyObj('objectUpdatesService',
{
saveAddFieldUpdate: {},
discardFieldUpdates: {},
reinstateFieldUpdates: observableOf(true),
initialize: {},
hasUpdates: observableOf(true),
isReinstatable: observableOf(false), // should always return something --> its in ngOnInit
isValidPage: observableOf(true)
}
);
scheduler = getTestScheduler();
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot()],
declarations: [AbstractTrackableComponent],
providers: [
{provide: ObjectUpdatesService, useValue: objectUpdatesService},
{provide: NotificationsService, useValue: notificationsService},
], schemas: [
NO_ERRORS_SCHEMA
]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AbstractTrackableComponent);
comp = fixture.componentInstance;
comp.url = url;
fixture.detectChanges();
});
it('should discard object updates', () => {
comp.discard();
expect(objectUpdatesService.discardFieldUpdates).toHaveBeenCalledWith(url, infoNotification);
});
it('should undo the discard of object updates', () => {
comp.reinstate();
expect(objectUpdatesService.reinstateFieldUpdates).toHaveBeenCalledWith(url);
});
describe('isReinstatable', () => {
beforeEach(() => {
objectUpdatesService.isReinstatable.and.returnValue(observableOf(true));
});
it('should return an observable that emits true', () => {
const expected = '(a|)';
scheduler.expectObservable(comp.isReinstatable()).toBe(expected, {a: true});
});
});
describe('hasChanges', () => {
beforeEach(() => {
objectUpdatesService.hasUpdates.and.returnValue(observableOf(true));
});
it('should return an observable that emits true', () => {
const expected = '(a|)';
scheduler.expectObservable(comp.hasChanges()).toBe(expected, {a: true});
});
});
});

View File

@@ -0,0 +1,78 @@
import { ObjectUpdatesService } from '../../core/data/object-updates/object-updates.service';
import { NotificationsService } from '../notifications/notifications.service';
import { TranslateService } from '@ngx-translate/core';
import { Observable } from 'rxjs';
import { Component } from '@angular/core';
/**
* Abstract Component that is able to track changes made in the inheriting component using the ObjectUpdateService
*/
@Component({
selector: 'ds-abstract-trackable',
template: ''
})
export class AbstractTrackableComponent {
/**
* The time span for being able to undo discarding changes
*/
public discardTimeOut: number;
public message: string;
public url: string;
public notificationsPrefix = 'static-pages.form.notification';
constructor(
public objectUpdatesService: ObjectUpdatesService,
public notificationsService: NotificationsService,
public translateService: TranslateService,
) {
}
/**
* Request the object updates service to discard all current changes to this item
* Shows a notification to remind the user that they can undo this
*/
discard() {
const undoNotification = this.notificationsService.info(this.getNotificationTitle('discarded'), this.getNotificationContent('discarded'), {timeOut: this.discardTimeOut});
this.objectUpdatesService.discardFieldUpdates(this.url, undoNotification);
}
/**
* Request the object updates service to undo discarding all changes to this item
*/
reinstate() {
this.objectUpdatesService.reinstateFieldUpdates(this.url);
}
/**
* Checks whether or not the object is currently reinstatable
*/
isReinstatable(): Observable<boolean> {
return this.objectUpdatesService.isReinstatable(this.url);
}
/**
* Checks whether or not there are currently updates for this object
*/
hasChanges(): Observable<boolean> {
return this.objectUpdatesService.hasUpdates(this.url);
}
/**
* Get translated notification title
* @param key
*/
private getNotificationTitle(key: string) {
return this.translateService.instant(this.notificationsPrefix + key + '.title');
}
/**
* Get translated notification content
* @param key
*/
private getNotificationContent(key: string) {
return this.translateService.instant(this.notificationsPrefix + key + '.content');
}
}