forked from hazza/dspace-angular
Merge with main
This commit is contained in:
6
.github/pull_request_template.md
vendored
6
.github/pull_request_template.md
vendored
@@ -1,7 +1,7 @@
|
|||||||
## References
|
## References
|
||||||
_Add references/links to any related tickets or PRs. These may include:_
|
_Add references/links to any related issues or PRs. These may include:_
|
||||||
* Link to [Angular issue or PR](https://github.com/DSpace/dspace-angular/issues) related to this PR, if any
|
* Fixes [GitHub issue](https://github.com/DSpace/dspace-angular/issues), if any
|
||||||
* Link to [JIRA](https://jira.lyrasis.org/projects/DS/summary) ticket(s), if any
|
* Requires [REST API PR](https://github.com/DSpace/DSpace/pulls), if any
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
Short summary of changes (1-2 sentences).
|
Short summary of changes (1-2 sentences).
|
||||||
|
@@ -0,0 +1,4 @@
|
|||||||
|
<div class="container">
|
||||||
|
<h2>{{'admin.curation-tasks.header' |translate }}</h2>
|
||||||
|
<ds-curation-form></ds-curation-form>
|
||||||
|
</div>
|
@@ -0,0 +1,28 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { AdminCurationTasksComponent } from './admin-curation-tasks.component';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||||
|
|
||||||
|
describe('AdminCurationTasksComponent', () => {
|
||||||
|
let comp: AdminCurationTasksComponent;
|
||||||
|
let fixture: ComponentFixture<AdminCurationTasksComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot()],
|
||||||
|
declarations: [AdminCurationTasksComponent],
|
||||||
|
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(AdminCurationTasksComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
});
|
||||||
|
describe('init', () => {
|
||||||
|
it('should initialise the comp', () => {
|
||||||
|
expect(comp).toBeDefined();
|
||||||
|
expect(fixture.debugElement.nativeElement.innerHTML).toContain('ds-curation-form');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,12 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component responsible for rendering the system wide Curation Task UI
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-admin-curation-task',
|
||||||
|
templateUrl: './admin-curation-tasks.component.html',
|
||||||
|
})
|
||||||
|
export class AdminCurationTasksComponent {
|
||||||
|
|
||||||
|
}
|
@@ -6,6 +6,7 @@ import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.reso
|
|||||||
import { AdminWorkflowPageComponent } from './admin-workflow-page/admin-workflow-page.component';
|
import { AdminWorkflowPageComponent } from './admin-workflow-page/admin-workflow-page.component';
|
||||||
import { I18nBreadcrumbsService } from '../core/breadcrumbs/i18n-breadcrumbs.service';
|
import { I18nBreadcrumbsService } from '../core/breadcrumbs/i18n-breadcrumbs.service';
|
||||||
import { URLCombiner } from '../core/url-combiner/url-combiner';
|
import { URLCombiner } from '../core/url-combiner/url-combiner';
|
||||||
|
import { AdminCurationTasksComponent } from './admin-curation-tasks/admin-curation-tasks.component';
|
||||||
|
|
||||||
const REGISTRIES_MODULE_PATH = 'registries';
|
const REGISTRIES_MODULE_PATH = 'registries';
|
||||||
export const ACCESS_CONTROL_MODULE_PATH = 'access-control';
|
export const ACCESS_CONTROL_MODULE_PATH = 'access-control';
|
||||||
@@ -41,6 +42,12 @@ export function getAccessControlModulePath() {
|
|||||||
component: AdminWorkflowPageComponent,
|
component: AdminWorkflowPageComponent,
|
||||||
data: { title: 'admin.workflow.title', breadcrumbKey: 'admin.workflow' }
|
data: { title: 'admin.workflow.title', breadcrumbKey: 'admin.workflow' }
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'curation-tasks',
|
||||||
|
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
||||||
|
component: AdminCurationTasksComponent,
|
||||||
|
data: { title: 'admin.curation-tasks.title', breadcrumbKey: 'admin.curation-tasks' }
|
||||||
|
},
|
||||||
])
|
])
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
|
@@ -469,7 +469,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
model: {
|
model: {
|
||||||
type: MenuItemType.LINK,
|
type: MenuItemType.LINK,
|
||||||
text: 'menu.section.curation_task',
|
text: 'menu.section.curation_task',
|
||||||
link: ''
|
link: 'admin/curation-tasks'
|
||||||
} as LinkMenuItemModel,
|
} as LinkMenuItemModel,
|
||||||
icon: 'filter',
|
icon: 'filter',
|
||||||
index: 7
|
index: 7
|
||||||
|
@@ -16,6 +16,7 @@ import { WorkflowItemSearchResultAdminWorkflowGridElementComponent } from './adm
|
|||||||
import { WorkflowItemAdminWorkflowActionsComponent } from './admin-workflow-page/admin-workflow-search-results/workflow-item-admin-workflow-actions.component';
|
import { WorkflowItemAdminWorkflowActionsComponent } from './admin-workflow-page/admin-workflow-search-results/workflow-item-admin-workflow-actions.component';
|
||||||
import { WorkflowItemSearchResultAdminWorkflowListElementComponent } from './admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-list-element/workflow-item/workflow-item-search-result-admin-workflow-list-element.component';
|
import { WorkflowItemSearchResultAdminWorkflowListElementComponent } from './admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-list-element/workflow-item/workflow-item-search-result-admin-workflow-list-element.component';
|
||||||
import { AdminWorkflowPageComponent } from './admin-workflow-page/admin-workflow-page.component';
|
import { AdminWorkflowPageComponent } from './admin-workflow-page/admin-workflow-page.component';
|
||||||
|
import { AdminCurationTasksComponent } from './admin-curation-tasks/admin-curation-tasks.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -35,6 +36,7 @@ import { AdminWorkflowPageComponent } from './admin-workflow-page/admin-workflow
|
|||||||
CommunityAdminSearchResultGridElementComponent,
|
CommunityAdminSearchResultGridElementComponent,
|
||||||
CollectionAdminSearchResultGridElementComponent,
|
CollectionAdminSearchResultGridElementComponent,
|
||||||
ItemAdminSearchResultActionsComponent,
|
ItemAdminSearchResultActionsComponent,
|
||||||
|
AdminCurationTasksComponent,
|
||||||
|
|
||||||
WorkflowItemSearchResultAdminWorkflowListElementComponent,
|
WorkflowItemSearchResultAdminWorkflowListElementComponent,
|
||||||
WorkflowItemSearchResultAdminWorkflowGridElementComponent,
|
WorkflowItemSearchResultAdminWorkflowGridElementComponent,
|
||||||
|
@@ -0,0 +1,6 @@
|
|||||||
|
<div class="container">
|
||||||
|
<h3>{{'collection.curate.header' |translate:{collection: (collectionName$ |async)} }}</h3>
|
||||||
|
<ds-curation-form
|
||||||
|
[dsoHandle]="(dsoRD$|async)?.payload.handle"
|
||||||
|
></ds-curation-form>
|
||||||
|
</div>
|
||||||
|
@@ -0,0 +1,69 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { CUSTOM_ELEMENTS_SCHEMA, DebugElement } from '@angular/core';
|
||||||
|
import { CollectionCurateComponent } from './collection-curate.component';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils';
|
||||||
|
import { Collection } from '../../../core/shared/collection.model';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { DSONameService } from '../../../core/breadcrumbs/dso-name.service';
|
||||||
|
|
||||||
|
describe('CollectionCurateComponent', () => {
|
||||||
|
let comp: CollectionCurateComponent;
|
||||||
|
let fixture: ComponentFixture<CollectionCurateComponent>;
|
||||||
|
let debugEl: DebugElement;
|
||||||
|
|
||||||
|
let routeStub;
|
||||||
|
let dsoNameService;
|
||||||
|
|
||||||
|
const collection = Object.assign(new Collection(), {
|
||||||
|
handle: '123456789/1', metadata: {'dc.title': ['Collection Name']}
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
routeStub = {
|
||||||
|
parent: {
|
||||||
|
data: observableOf({
|
||||||
|
dso: createSuccessfulRemoteDataObject(collection)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
dsoNameService = jasmine.createSpyObj('dsoNameService', {
|
||||||
|
getName: 'Collection Name'
|
||||||
|
});
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot()],
|
||||||
|
declarations: [CollectionCurateComponent],
|
||||||
|
providers: [
|
||||||
|
{provide: ActivatedRoute, useValue: routeStub},
|
||||||
|
{provide: DSONameService, useValue: dsoNameService}
|
||||||
|
],
|
||||||
|
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(CollectionCurateComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
debugEl = fixture.debugElement;
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
describe('init', () => {
|
||||||
|
it('should initialise the comp', () => {
|
||||||
|
expect(comp).toBeDefined();
|
||||||
|
expect(debugEl.nativeElement.innerHTML).toContain('ds-curation-form');
|
||||||
|
});
|
||||||
|
it('should contain the collection information provided in the route', () => {
|
||||||
|
comp.dsoRD$.subscribe((value) => {
|
||||||
|
expect(value.payload.handle
|
||||||
|
).toEqual('123456789/1');
|
||||||
|
});
|
||||||
|
comp.collectionName$.subscribe((value) => {
|
||||||
|
expect(value).toEqual('Collection Name');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -1,4 +1,11 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
|
import { filter, map, take } from 'rxjs/operators';
|
||||||
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { DSONameService } from '../../../core/breadcrumbs/dso-name.service';
|
||||||
|
import { Collection } from '../../../core/shared/collection.model';
|
||||||
|
import { hasValue } from '../../../shared/empty.util';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component for managing a collection's curation tasks
|
* Component for managing a collection's curation tasks
|
||||||
@@ -8,5 +15,26 @@ import { Component } from '@angular/core';
|
|||||||
templateUrl: './collection-curate.component.html',
|
templateUrl: './collection-curate.component.html',
|
||||||
})
|
})
|
||||||
export class CollectionCurateComponent {
|
export class CollectionCurateComponent {
|
||||||
/* TODO: Implement Collection Edit - Curate */
|
dsoRD$: Observable<RemoteData<Collection>>;
|
||||||
|
collectionName$: Observable<string>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private dsoNameService: DSONameService,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.dsoRD$ = this.route.parent.data.pipe(
|
||||||
|
take(1),
|
||||||
|
map((data) => data.dso),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.collectionName$ = this.dsoRD$.pipe(
|
||||||
|
filter((rd: RemoteData<Collection>) => hasValue(rd)),
|
||||||
|
map((rd: RemoteData<Collection>) => {
|
||||||
|
return this.dsoNameService.getName(rd.payload);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -30,24 +30,27 @@ describe('CollectionRolesComponent', () => {
|
|||||||
undefined,
|
undefined,
|
||||||
Object.assign(new Collection(), {
|
Object.assign(new Collection(), {
|
||||||
_links: {
|
_links: {
|
||||||
'irrelevant': {
|
irrelevant: {
|
||||||
href: 'irrelevant link',
|
href: 'irrelevant link',
|
||||||
},
|
},
|
||||||
'adminGroup': {
|
adminGroup: {
|
||||||
href: 'adminGroup link',
|
href: 'adminGroup link',
|
||||||
},
|
},
|
||||||
'submittersGroup': {
|
submittersGroup: {
|
||||||
href: 'submittersGroup link',
|
href: 'submittersGroup link',
|
||||||
},
|
},
|
||||||
'itemReadGroup': {
|
itemReadGroup: {
|
||||||
href: 'itemReadGroup link',
|
href: 'itemReadGroup link',
|
||||||
},
|
},
|
||||||
'bitstreamReadGroup': {
|
bitstreamReadGroup: {
|
||||||
href: 'bitstreamReadGroup link',
|
href: 'bitstreamReadGroup link',
|
||||||
},
|
},
|
||||||
'workflowGroups/test': {
|
workflowGroups: [
|
||||||
|
{
|
||||||
|
name: 'test',
|
||||||
href: 'test workflow group link',
|
href: 'test workflow group link',
|
||||||
},
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
@@ -5,7 +5,7 @@ import { first, map } from 'rxjs/operators';
|
|||||||
import { RemoteData } from '../../../core/data/remote-data';
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
import { Collection } from '../../../core/shared/collection.model';
|
import { Collection } from '../../../core/shared/collection.model';
|
||||||
import { getRemoteDataPayload, getSucceededRemoteData } from '../../../core/shared/operators';
|
import { getRemoteDataPayload, getSucceededRemoteData } from '../../../core/shared/operators';
|
||||||
import { ComcolRole } from '../../../shared/comcol-forms/edit-comcol-page/comcol-role/comcol-role';
|
import { HALLink } from '../../../core/shared/hal-link.model';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component for managing a collection's roles
|
* Component for managing a collection's roles
|
||||||
@@ -31,19 +31,27 @@ export class CollectionRolesComponent implements OnInit {
|
|||||||
/**
|
/**
|
||||||
* The different roles for the collection, as an observable.
|
* The different roles for the collection, as an observable.
|
||||||
*/
|
*/
|
||||||
getComcolRoles(): Observable<ComcolRole[]> {
|
getComcolRoles(): Observable<HALLink[]> {
|
||||||
return this.collection$.pipe(
|
return this.collection$.pipe(
|
||||||
map((collection) =>
|
map((collection) => [
|
||||||
[
|
{
|
||||||
ComcolRole.COLLECTION_ADMIN,
|
name: 'collection-admin',
|
||||||
ComcolRole.SUBMITTERS,
|
href: collection._links.adminGroup.href,
|
||||||
ComcolRole.ITEM_READ,
|
},
|
||||||
ComcolRole.BITSTREAM_READ,
|
{
|
||||||
...Object.keys(collection._links)
|
name: 'submitters',
|
||||||
.filter((link) => link.startsWith('workflowGroups/'))
|
href: collection._links.submittersGroup.href,
|
||||||
.map((link) => new ComcolRole(link.substr('workflowGroups/'.length), link)),
|
},
|
||||||
]
|
{
|
||||||
),
|
name: 'item_read',
|
||||||
|
href: collection._links.itemReadGroup.href,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'bitstream_read',
|
||||||
|
href: collection._links.bitstreamReadGroup.href,
|
||||||
|
},
|
||||||
|
...collection._links.workflowGroups,
|
||||||
|
]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -0,0 +1,6 @@
|
|||||||
|
<div class="container">
|
||||||
|
<h3>{{'community.curate.header' |translate:{community: (communityName$ |async)} }}</h3>
|
||||||
|
<ds-curation-form
|
||||||
|
[dsoHandle]="(dsoRD$|async)?.payload.handle"
|
||||||
|
></ds-curation-form>
|
||||||
|
</div>
|
||||||
|
@@ -0,0 +1,69 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { CUSTOM_ELEMENTS_SCHEMA, DebugElement } from '@angular/core';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { DSONameService } from '../../../core/breadcrumbs/dso-name.service';
|
||||||
|
import { CommunityCurateComponent } from './community-curate.component';
|
||||||
|
import { Community } from '../../../core/shared/community.model';
|
||||||
|
|
||||||
|
describe('CommunityCurateComponent', () => {
|
||||||
|
let comp: CommunityCurateComponent;
|
||||||
|
let fixture: ComponentFixture<CommunityCurateComponent>;
|
||||||
|
let debugEl: DebugElement;
|
||||||
|
|
||||||
|
let routeStub;
|
||||||
|
let dsoNameService;
|
||||||
|
|
||||||
|
const community = Object.assign(new Community(), {
|
||||||
|
handle: '123456789/1', metadata: {'dc.title': ['Community Name']}
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
routeStub = {
|
||||||
|
parent: {
|
||||||
|
data: observableOf({
|
||||||
|
dso: createSuccessfulRemoteDataObject(community)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
dsoNameService = jasmine.createSpyObj('dsoNameService', {
|
||||||
|
getName: 'Community Name'
|
||||||
|
});
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot()],
|
||||||
|
declarations: [CommunityCurateComponent],
|
||||||
|
providers: [
|
||||||
|
{provide: ActivatedRoute, useValue: routeStub},
|
||||||
|
{provide: DSONameService, useValue: dsoNameService}
|
||||||
|
],
|
||||||
|
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(CommunityCurateComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
debugEl = fixture.debugElement;
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
describe('init', () => {
|
||||||
|
it('should initialise the comp', () => {
|
||||||
|
expect(comp).toBeDefined();
|
||||||
|
expect(debugEl.nativeElement.innerHTML).toContain('ds-curation-form');
|
||||||
|
});
|
||||||
|
it('should contain the community information provided in the route', () => {
|
||||||
|
comp.dsoRD$.subscribe((value) => {
|
||||||
|
expect(value.payload.handle
|
||||||
|
).toEqual('123456789/1');
|
||||||
|
});
|
||||||
|
comp.communityName$.subscribe((value) => {
|
||||||
|
expect(value).toEqual('Community Name');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -1,4 +1,12 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { Community } from '../../../core/shared/community.model';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { map, take } from 'rxjs/operators';
|
||||||
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { DSONameService } from '../../../core/breadcrumbs/dso-name.service';
|
||||||
|
import { hasValue } from '../../../shared/empty.util';
|
||||||
|
import { filter } from 'rxjs/internal/operators/filter';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component for managing a community's curation tasks
|
* Component for managing a community's curation tasks
|
||||||
@@ -7,6 +15,29 @@ import { Component } from '@angular/core';
|
|||||||
selector: 'ds-community-curate',
|
selector: 'ds-community-curate',
|
||||||
templateUrl: './community-curate.component.html',
|
templateUrl: './community-curate.component.html',
|
||||||
})
|
})
|
||||||
export class CommunityCurateComponent {
|
export class CommunityCurateComponent implements OnInit {
|
||||||
/* TODO: Implement Community Edit - Curate */
|
|
||||||
|
dsoRD$: Observable<RemoteData<Community>>;
|
||||||
|
communityName$: Observable<string>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private dsoNameService: DSONameService,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.dsoRD$ = this.route.parent.data.pipe(
|
||||||
|
take(1),
|
||||||
|
map((data) => data.dso),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.communityName$ = this.dsoRD$.pipe(
|
||||||
|
filter((rd: RemoteData<Community>) => hasValue(rd)),
|
||||||
|
map((rd: RemoteData<Community>) => {
|
||||||
|
return this.dsoNameService.getName(rd.payload);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<ds-comcol-role
|
<ds-comcol-role
|
||||||
*ngFor="let comcolRole of getComcolRoles()"
|
*ngFor="let comcolRole of getComcolRoles$() | async"
|
||||||
[dso]="community$ | async"
|
[dso]="community$ | async"
|
||||||
[comcolRole]="comcolRole"
|
[comcolRole]="comcolRole"
|
||||||
>
|
>
|
||||||
|
@@ -4,8 +4,8 @@ import { Observable } from 'rxjs';
|
|||||||
import { first, map } from 'rxjs/operators';
|
import { first, map } from 'rxjs/operators';
|
||||||
import { Community } from '../../../core/shared/community.model';
|
import { Community } from '../../../core/shared/community.model';
|
||||||
import { getRemoteDataPayload, getSucceededRemoteData } from '../../../core/shared/operators';
|
import { getRemoteDataPayload, getSucceededRemoteData } from '../../../core/shared/operators';
|
||||||
import { ComcolRole } from '../../../shared/comcol-forms/edit-comcol-page/comcol-role/comcol-role';
|
|
||||||
import { RemoteData } from '../../../core/data/remote-data';
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
|
import { HALLink } from '../../../core/shared/hal-link.model';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component for managing a community's roles
|
* Component for managing a community's roles
|
||||||
@@ -31,10 +31,15 @@ export class CommunityRolesComponent implements OnInit {
|
|||||||
/**
|
/**
|
||||||
* The different roles for the community.
|
* The different roles for the community.
|
||||||
*/
|
*/
|
||||||
getComcolRoles(): ComcolRole[] {
|
getComcolRoles$(): Observable<HALLink[]> {
|
||||||
return [
|
return this.community$.pipe(
|
||||||
ComcolRole.COMMUNITY_ADMIN,
|
map((community) => [
|
||||||
];
|
{
|
||||||
|
name: 'community-admin',
|
||||||
|
href: community._links.adminGroup.href,
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@@ -0,0 +1,11 @@
|
|||||||
|
<div>
|
||||||
|
<div class="modal-header">{{'dso-selector.create.submission.head' | translate}}
|
||||||
|
<button type="button" class="close" (click)="close()" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<ds-collection-dropdown (selectionChange)="selectObject($event.collection)">
|
||||||
|
</ds-collection-dropdown>
|
||||||
|
</div>
|
||||||
|
</div>
|
@@ -0,0 +1,164 @@
|
|||||||
|
import { async, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { CollectionSelectorComponent } from './collection-selector.component';
|
||||||
|
import { CollectionDropdownComponent } from 'src/app/shared/collection-dropdown/collection-dropdown.component';
|
||||||
|
import { Collection } from 'src/app/core/shared/collection.model';
|
||||||
|
import { of, Observable } from 'rxjs';
|
||||||
|
import { RemoteData } from 'src/app/core/data/remote-data';
|
||||||
|
import { Community } from 'src/app/core/shared/community.model';
|
||||||
|
import { FindListOptions } from 'src/app/core/data/request.models';
|
||||||
|
import { FollowLinkConfig } from 'src/app/shared/utils/follow-link-config.model';
|
||||||
|
import { PaginatedList } from 'src/app/core/data/paginated-list';
|
||||||
|
import { createSuccessfulRemoteDataObject } from 'src/app/shared/remote-data.utils';
|
||||||
|
import { PageInfo } from 'src/app/core/shared/page-info.model';
|
||||||
|
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
|
||||||
|
import { TranslateLoaderMock } from 'src/app/shared/mocks/translate-loader.mock';
|
||||||
|
import { CollectionDataService } from 'src/app/core/data/collection-data.service';
|
||||||
|
import { ChangeDetectorRef, ElementRef, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { hot } from 'jasmine-marbles';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
|
||||||
|
describe('CollectionSelectorComponent', () => {
|
||||||
|
let component: CollectionSelectorComponent;
|
||||||
|
let fixture: ComponentFixture<CollectionSelectorComponent>;
|
||||||
|
const modal = jasmine.createSpyObj('modal', ['close', 'dismiss']);
|
||||||
|
|
||||||
|
const community: Community = Object.assign(new Community(), {
|
||||||
|
id: 'ce64f48e-2c9b-411a-ac36-ee429c0e6a88',
|
||||||
|
uuid: 'ce64f48e-2c9b-411a-ac36-ee429c0e6a88',
|
||||||
|
name: 'Community 1'
|
||||||
|
});
|
||||||
|
|
||||||
|
const collections: Collection[] = [
|
||||||
|
Object.assign(new Collection(), {
|
||||||
|
id: 'ce64f48e-2c9b-411a-ac36-ee429c0e6a88',
|
||||||
|
name: 'Collection 1',
|
||||||
|
metadata: [
|
||||||
|
{
|
||||||
|
key: 'dc.title',
|
||||||
|
language: 'en_US',
|
||||||
|
value: 'Community 1-Collection 1'
|
||||||
|
}],
|
||||||
|
parentCommunity: of(
|
||||||
|
new RemoteData(false, false, true, undefined, community, 200)
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
Object.assign(new Collection(), {
|
||||||
|
id: '59ee713b-ee53-4220-8c3f-9860dc84fe33',
|
||||||
|
name: 'Collection 2',
|
||||||
|
metadata: [
|
||||||
|
{
|
||||||
|
key: 'dc.title',
|
||||||
|
language: 'en_US',
|
||||||
|
value: 'Community 1-Collection 2'
|
||||||
|
}],
|
||||||
|
parentCommunity: of(
|
||||||
|
new RemoteData(false, false, true, undefined, community, 200)
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
Object.assign(new Collection(), {
|
||||||
|
id: 'e9dbf393-7127-415f-8919-55be34a6e9ed',
|
||||||
|
name: 'Collection 3',
|
||||||
|
metadata: [
|
||||||
|
{
|
||||||
|
key: 'dc.title',
|
||||||
|
language: 'en_US',
|
||||||
|
value: 'Community 1-Collection 3'
|
||||||
|
}],
|
||||||
|
parentCommunity: of(
|
||||||
|
new RemoteData(false, false, true, undefined, community, 200)
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
Object.assign(new Collection(), {
|
||||||
|
id: '59da2ff0-9bf4-45bf-88be-e35abd33f304',
|
||||||
|
name: 'Collection 4',
|
||||||
|
metadata: [
|
||||||
|
{
|
||||||
|
key: 'dc.title',
|
||||||
|
language: 'en_US',
|
||||||
|
value: 'Community 1-Collection 4'
|
||||||
|
}],
|
||||||
|
parentCommunity: of(
|
||||||
|
new RemoteData(false, false, true, undefined, community, 200)
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
Object.assign(new Collection(), {
|
||||||
|
id: 'a5159760-f362-4659-9e81-e3253ad91ede',
|
||||||
|
name: 'Collection 5',
|
||||||
|
metadata: [
|
||||||
|
{
|
||||||
|
key: 'dc.title',
|
||||||
|
language: 'en_US',
|
||||||
|
value: 'Community 1-Collection 5'
|
||||||
|
}],
|
||||||
|
parentCommunity: of(
|
||||||
|
new RemoteData(false, false, true, undefined, community, 200)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
|
// tslint:disable-next-line: max-classes-per-file
|
||||||
|
const collectionDataServiceMock = {
|
||||||
|
getAuthorizedCollection(query: string, options: FindListOptions = {}, ...linksToFollow: Array<FollowLinkConfig<Collection>>): Observable<RemoteData<PaginatedList<Collection>>> {
|
||||||
|
return hot( 'a|', {
|
||||||
|
a: createSuccessfulRemoteDataObject(
|
||||||
|
new PaginatedList(new PageInfo(), collections)
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
TranslateModule.forRoot({
|
||||||
|
loader: {
|
||||||
|
provide: TranslateLoader,
|
||||||
|
useClass: TranslateLoaderMock
|
||||||
|
}
|
||||||
|
})
|
||||||
|
],
|
||||||
|
declarations: [ CollectionSelectorComponent, CollectionDropdownComponent ],
|
||||||
|
providers: [
|
||||||
|
{provide: CollectionDataService, useValue: collectionDataServiceMock},
|
||||||
|
{provide: ChangeDetectorRef, useValue: {}},
|
||||||
|
{provide: ElementRef, userValue: {}},
|
||||||
|
{provide: NgbActiveModal, useValue: modal},
|
||||||
|
{provide: ActivatedRoute, useValue: {}}
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(CollectionSelectorComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call selectObject', fakeAsync(() => {
|
||||||
|
spyOn(component, 'selectObject');
|
||||||
|
fixture.detectChanges();
|
||||||
|
tick();
|
||||||
|
fixture.whenStable().then(() => {
|
||||||
|
const collectionItem = fixture.debugElement.query(By.css('.collection-item:nth-child(2)'));
|
||||||
|
collectionItem.triggerEventHandler('click', {
|
||||||
|
preventDefault: () => {/**/
|
||||||
|
}
|
||||||
|
});
|
||||||
|
expect(component.selectObject).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should close the dialog', () => {
|
||||||
|
component.close();
|
||||||
|
expect((component as any).activeModal.close).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,33 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
||||||
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This component displays the dialog that shows the list of selectable collections
|
||||||
|
* on the MyDSpace page
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-collection-selector',
|
||||||
|
templateUrl: './collection-selector.component.html',
|
||||||
|
styleUrls: ['./collection-selector.component.scss']
|
||||||
|
})
|
||||||
|
export class CollectionSelectorComponent {
|
||||||
|
|
||||||
|
constructor(protected activeModal: NgbActiveModal) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method called when an element has been selected from collection list.
|
||||||
|
* Its close the active modal and send selected value to the component container
|
||||||
|
* @param dso The selected DSpaceObject
|
||||||
|
*/
|
||||||
|
selectObject(dso: DSpaceObject) {
|
||||||
|
this.activeModal.close(dso);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the modal
|
||||||
|
*/
|
||||||
|
close() {
|
||||||
|
this.activeModal.close();
|
||||||
|
}
|
||||||
|
}
|
@@ -3,7 +3,8 @@
|
|||||||
<ds-uploader *ngIf="uploadFilesOptions.url !== ''"
|
<ds-uploader *ngIf="uploadFilesOptions.url !== ''"
|
||||||
[uploadFilesOptions]="uploadFilesOptions"
|
[uploadFilesOptions]="uploadFilesOptions"
|
||||||
(onCompleteItem)="onCompleteItem($event)"
|
(onCompleteItem)="onCompleteItem($event)"
|
||||||
(onUploadError)="onUploadError()"></ds-uploader>
|
(onUploadError)="onUploadError($event)"
|
||||||
|
(onFileSelected)="afterFileLoaded($event)"></ds-uploader>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="add">
|
<div class="add">
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { ChangeDetectorRef, Component, NO_ERRORS_SCHEMA } from '@angular/core';
|
import { ChangeDetectorRef, Component, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { async, ComponentFixture, inject, TestBed, tick, fakeAsync } from '@angular/core/testing';
|
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
|
||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
|
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
@@ -15,7 +15,6 @@ import { createTestComponent } from '../../shared/testing/utils.test';
|
|||||||
import { MyDSpaceNewSubmissionComponent } from './my-dspace-new-submission.component';
|
import { MyDSpaceNewSubmissionComponent } from './my-dspace-new-submission.component';
|
||||||
import { AppState } from '../../app.reducer';
|
import { AppState } from '../../app.reducer';
|
||||||
import { TranslateLoaderMock } from '../../shared/mocks/translate-loader.mock';
|
import { TranslateLoaderMock } from '../../shared/mocks/translate-loader.mock';
|
||||||
import { getMockTranslateService } from '../../shared/mocks/translate.service.mock';
|
|
||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub';
|
import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub';
|
||||||
import { SharedModule } from '../../shared/shared.module';
|
import { SharedModule } from '../../shared/shared.module';
|
||||||
@@ -25,10 +24,25 @@ import { By } from '@angular/platform-browser';
|
|||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { HostWindowService } from '../../shared/host-window.service';
|
import { HostWindowService } from '../../shared/host-window.service';
|
||||||
import { HostWindowServiceStub } from '../../shared/testing/host-window-service.stub';
|
import { HostWindowServiceStub } from '../../shared/testing/host-window-service.stub';
|
||||||
|
import { UploaderComponent } from 'src/app/shared/uploader/uploader.component';
|
||||||
|
|
||||||
describe('MyDSpaceNewSubmissionComponent test', () => {
|
fdescribe('MyDSpaceNewSubmissionComponent test', () => {
|
||||||
|
|
||||||
|
const translateService: TranslateService = jasmine.createSpyObj('translateService', {
|
||||||
|
get: (key: string): any => { observableOf(key) },
|
||||||
|
instant: jasmine.createSpy('instant')
|
||||||
|
});
|
||||||
|
|
||||||
|
const uploader: any = jasmine.createSpyObj('uploader', {
|
||||||
|
clearQueue: jasmine.createSpy('clearQueue')
|
||||||
|
});
|
||||||
|
|
||||||
|
const modalService = {
|
||||||
|
open: () => {
|
||||||
|
return { result: new Promise((res, rej) => {/****/}) };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const translateService: any = getMockTranslateService();
|
|
||||||
const store: Store<AppState> = jasmine.createSpyObj('store', {
|
const store: Store<AppState> = jasmine.createSpyObj('store', {
|
||||||
/* tslint:disable:no-empty */
|
/* tslint:disable:no-empty */
|
||||||
dispatch: {},
|
dispatch: {},
|
||||||
@@ -58,11 +72,7 @@ describe('MyDSpaceNewSubmissionComponent test', () => {
|
|||||||
{ provide: ScrollToService, useValue: getMockScrollToService() },
|
{ provide: ScrollToService, useValue: getMockScrollToService() },
|
||||||
{ provide: Store, useValue: store },
|
{ provide: Store, useValue: store },
|
||||||
{ provide: TranslateService, useValue: translateService },
|
{ provide: TranslateService, useValue: translateService },
|
||||||
{
|
{ provide: NgbModal, useValue: modalService },
|
||||||
provide: NgbModal, useValue: {
|
|
||||||
open: () => {/*comment*/}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ChangeDetectorRef,
|
ChangeDetectorRef,
|
||||||
MyDSpaceNewSubmissionComponent,
|
MyDSpaceNewSubmissionComponent,
|
||||||
UploaderService,
|
UploaderService,
|
||||||
@@ -103,6 +113,10 @@ describe('MyDSpaceNewSubmissionComponent test', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(MyDSpaceNewSubmissionComponent);
|
fixture = TestBed.createComponent(MyDSpaceNewSubmissionComponent);
|
||||||
comp = fixture.componentInstance;
|
comp = fixture.componentInstance;
|
||||||
|
comp.uploadFilesOptions.authToken = 'user-auth-token';
|
||||||
|
comp.uploadFilesOptions.url = 'https://fake.upload-api.url';
|
||||||
|
comp.uploaderComponent = TestBed.createComponent(UploaderComponent).componentInstance;
|
||||||
|
comp.uploaderComponent.uploader = uploader;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call app.openDialog', () => {
|
it('should call app.openDialog', () => {
|
||||||
@@ -114,6 +128,12 @@ describe('MyDSpaceNewSubmissionComponent test', () => {
|
|||||||
});
|
});
|
||||||
expect(comp.openDialog).toHaveBeenCalled();
|
expect(comp.openDialog).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should show a collection selector if only one file are uploaded', () => {
|
||||||
|
spyOn((comp as any).modalService, 'open').and.returnValue({ result: new Promise((res, rej) => {/****/}) });
|
||||||
|
comp.afterFileLoaded(['']);
|
||||||
|
expect((comp as any).modalService.open).toHaveBeenCalled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -1,13 +1,10 @@
|
|||||||
import { ChangeDetectorRef, Component, EventEmitter, OnDestroy, OnInit, Output } from '@angular/core';
|
import { ChangeDetectorRef, Component, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@angular/core';
|
||||||
import { Router } from '@angular/router';
|
|
||||||
|
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
import { first } from 'rxjs/operators';
|
import { first } from 'rxjs/operators';
|
||||||
import { Store } from '@ngrx/store';
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
|
||||||
import { SubmissionState } from '../../submission/submission.reducers';
|
|
||||||
import { AuthService } from '../../core/auth/auth.service';
|
import { AuthService } from '../../core/auth/auth.service';
|
||||||
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
@@ -18,6 +15,9 @@ import { NotificationType } from '../../shared/notifications/models/notification
|
|||||||
import { hasValue } from '../../shared/empty.util';
|
import { hasValue } from '../../shared/empty.util';
|
||||||
import { SearchResult } from '../../shared/search/search-result.model';
|
import { SearchResult } from '../../shared/search/search-result.model';
|
||||||
import { CreateItemParentSelectorComponent } from '../../shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component';
|
import { CreateItemParentSelectorComponent } from '../../shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component';
|
||||||
|
import { CollectionSelectorComponent } from '../collection-selector/collection-selector.component';
|
||||||
|
import { UploaderComponent } from '../../shared/uploader/uploader.component';
|
||||||
|
import { UploaderError } from '../../shared/uploader/uploader-error.model';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component represents the whole mydspace page header
|
* This component represents the whole mydspace page header
|
||||||
@@ -43,6 +43,11 @@ export class MyDSpaceNewSubmissionComponent implements OnDestroy, OnInit {
|
|||||||
*/
|
*/
|
||||||
private sub: Subscription;
|
private sub: Subscription;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to uploaderComponent
|
||||||
|
*/
|
||||||
|
@ViewChild(UploaderComponent, { static: false }) uploaderComponent: UploaderComponent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize instance variables
|
* Initialize instance variables
|
||||||
*
|
*
|
||||||
@@ -59,9 +64,7 @@ export class MyDSpaceNewSubmissionComponent implements OnDestroy, OnInit {
|
|||||||
private changeDetectorRef: ChangeDetectorRef,
|
private changeDetectorRef: ChangeDetectorRef,
|
||||||
private halService: HALEndpointService,
|
private halService: HALEndpointService,
|
||||||
private notificationsService: NotificationsService,
|
private notificationsService: NotificationsService,
|
||||||
private store: Store<SubmissionState>,
|
|
||||||
private translate: TranslateService,
|
private translate: TranslateService,
|
||||||
private router: Router,
|
|
||||||
private modalService: NgbModal) {
|
private modalService: NgbModal) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,6 +72,7 @@ export class MyDSpaceNewSubmissionComponent implements OnDestroy, OnInit {
|
|||||||
* Initialize url and Bearer token
|
* Initialize url and Bearer token
|
||||||
*/
|
*/
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
this.uploadFilesOptions.autoUpload = false;
|
||||||
this.sub = this.halService.getEndpoint('workspaceitems').pipe(first()).subscribe((url) => {
|
this.sub = this.halService.getEndpoint('workspaceitems').pipe(first()).subscribe((url) => {
|
||||||
this.uploadFilesOptions.url = url;
|
this.uploadFilesOptions.url = url;
|
||||||
this.uploadFilesOptions.authToken = this.authService.buildAuthHeader();
|
this.uploadFilesOptions.authToken = this.authService.buildAuthHeader();
|
||||||
@@ -108,8 +112,12 @@ export class MyDSpaceNewSubmissionComponent implements OnDestroy, OnInit {
|
|||||||
/**
|
/**
|
||||||
* Method called on file upload error
|
* Method called on file upload error
|
||||||
*/
|
*/
|
||||||
public onUploadError() {
|
public onUploadError(error: UploaderError) {
|
||||||
this.notificationsService.error(null, this.translate.get('mydspace.upload.upload-failed'));
|
let errorMessageKey = 'mydspace.upload.upload-failed';
|
||||||
|
if (hasValue(error.status) && error.status === 422) {
|
||||||
|
errorMessageKey = 'mydspace.upload.upload-failed-manyentries';
|
||||||
|
}
|
||||||
|
this.notificationsService.error(null, this.translate.get(errorMessageKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -120,6 +128,28 @@ export class MyDSpaceNewSubmissionComponent implements OnDestroy, OnInit {
|
|||||||
this.modalService.open(CreateItemParentSelectorComponent);
|
this.modalService.open(CreateItemParentSelectorComponent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method invoked after all file are loaded from upload plugin
|
||||||
|
*/
|
||||||
|
afterFileLoaded(items) {
|
||||||
|
const uploader = this.uploaderComponent.uploader;
|
||||||
|
if (hasValue(items) && items.length > 1) {
|
||||||
|
this.notificationsService.error(null, this.translate.get('mydspace.upload.upload-failed-moreonefile'));
|
||||||
|
uploader.clearQueue();
|
||||||
|
this.changeDetectorRef.detectChanges();
|
||||||
|
} else {
|
||||||
|
const modalRef = this.modalService.open(CollectionSelectorComponent);
|
||||||
|
// When the dialog are closes its takes the collection selected and
|
||||||
|
// uploads choosed file after adds owningCollection parameter
|
||||||
|
modalRef.result.then( (result) => {
|
||||||
|
uploader.onBuildItemForm = (fileItem: any, form: any) => {
|
||||||
|
form.append('owningCollection', result.uuid);
|
||||||
|
};
|
||||||
|
uploader.uploadAll();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unsubscribe from the subscription
|
* Unsubscribe from the subscription
|
||||||
*/
|
*/
|
||||||
|
@@ -20,6 +20,7 @@ import { SearchResultListElementComponent } from '../shared/object-list/search-r
|
|||||||
import { ItemSearchResultListElementSubmissionComponent } from '../shared/object-list/my-dspace-result-list-element/item-search-result/item-search-result-list-element-submission.component';
|
import { ItemSearchResultListElementSubmissionComponent } from '../shared/object-list/my-dspace-result-list-element/item-search-result/item-search-result-list-element-submission.component';
|
||||||
import { WorkflowItemSearchResultListElementComponent } from '../shared/object-list/my-dspace-result-list-element/workflow-item-search-result/workflow-item-search-result-list-element.component';
|
import { WorkflowItemSearchResultListElementComponent } from '../shared/object-list/my-dspace-result-list-element/workflow-item-search-result/workflow-item-search-result-list-element.component';
|
||||||
import { PoolSearchResultDetailElementComponent } from '../shared/object-detail/my-dspace-result-detail-element/pool-search-result/pool-search-result-detail-element.component';
|
import { PoolSearchResultDetailElementComponent } from '../shared/object-detail/my-dspace-result-detail-element/pool-search-result/pool-search-result-detail-element.component';
|
||||||
|
import { CollectionSelectorComponent } from './collection-selector/collection-selector.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -40,7 +41,8 @@ import { PoolSearchResultDetailElementComponent } from '../shared/object-detail/
|
|||||||
ClaimedTaskSearchResultDetailElementComponent,
|
ClaimedTaskSearchResultDetailElementComponent,
|
||||||
PoolSearchResultDetailElementComponent,
|
PoolSearchResultDetailElementComponent,
|
||||||
MyDSpaceNewSubmissionComponent,
|
MyDSpaceNewSubmissionComponent,
|
||||||
ItemSearchResultListElementSubmissionComponent
|
ItemSearchResultListElementSubmissionComponent,
|
||||||
|
CollectionSelectorComponent
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
MyDSpaceGuard,
|
MyDSpaceGuard,
|
||||||
@@ -57,7 +59,8 @@ import { PoolSearchResultDetailElementComponent } from '../shared/object-detail/
|
|||||||
WorkflowItemSearchResultDetailElementComponent,
|
WorkflowItemSearchResultDetailElementComponent,
|
||||||
ClaimedTaskSearchResultDetailElementComponent,
|
ClaimedTaskSearchResultDetailElementComponent,
|
||||||
PoolSearchResultDetailElementComponent,
|
PoolSearchResultDetailElementComponent,
|
||||||
ItemSearchResultListElementSubmissionComponent
|
ItemSearchResultListElementSubmissionComponent,
|
||||||
|
CollectionSelectorComponent
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@@ -160,6 +160,8 @@ import { SubmissionCcLicenseDataService } from './submission/submission-cc-licen
|
|||||||
import { SubmissionCcLicence } from './submission/models/submission-cc-license.model';
|
import { SubmissionCcLicence } from './submission/models/submission-cc-license.model';
|
||||||
import { SubmissionCcLicenceUrl } from './submission/models/submission-cc-license-url.model';
|
import { SubmissionCcLicenceUrl } from './submission/models/submission-cc-license-url.model';
|
||||||
import { SubmissionCcLicenseUrlDataService } from './submission/submission-cc-license-url-data.service';
|
import { SubmissionCcLicenseUrlDataService } from './submission/submission-cc-license-url-data.service';
|
||||||
|
import { ConfigurationDataService } from './data/configuration-data.service';
|
||||||
|
import { ConfigurationProperty } from './shared/configuration-property.model';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When not in production, endpoint responses can be mocked for testing purposes
|
* When not in production, endpoint responses can be mocked for testing purposes
|
||||||
@@ -245,6 +247,7 @@ const PROVIDERS = [
|
|||||||
UploaderService,
|
UploaderService,
|
||||||
FileService,
|
FileService,
|
||||||
DSpaceObjectDataService,
|
DSpaceObjectDataService,
|
||||||
|
ConfigurationDataService,
|
||||||
DSOChangeAnalyzer,
|
DSOChangeAnalyzer,
|
||||||
DefaultChangeAnalyzer,
|
DefaultChangeAnalyzer,
|
||||||
ArrayMoveChangeAnalyzer,
|
ArrayMoveChangeAnalyzer,
|
||||||
@@ -350,7 +353,8 @@ export const models =
|
|||||||
TemplateItem,
|
TemplateItem,
|
||||||
Feature,
|
Feature,
|
||||||
Authorization,
|
Authorization,
|
||||||
Registration
|
Registration,
|
||||||
|
ConfigurationProperty
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
87
src/app/core/data/configuration-data.service.spec.ts
Normal file
87
src/app/core/data/configuration-data.service.spec.ts
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import { cold, getTestScheduler } from 'jasmine-marbles';
|
||||||
|
import { TestScheduler } from 'rxjs/testing';
|
||||||
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
|
import { FindByIDRequest } from './request.models';
|
||||||
|
import { RequestService } from './request.service';
|
||||||
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { ConfigurationDataService } from './configuration-data.service';
|
||||||
|
import { ConfigurationProperty } from '../shared/configuration-property.model';
|
||||||
|
|
||||||
|
describe('ConfigurationDataService', () => {
|
||||||
|
let scheduler: TestScheduler;
|
||||||
|
let service: ConfigurationDataService;
|
||||||
|
let halService: HALEndpointService;
|
||||||
|
let requestService: RequestService;
|
||||||
|
let rdbService: RemoteDataBuildService;
|
||||||
|
let objectCache: ObjectCacheService;
|
||||||
|
const testObject = {
|
||||||
|
uuid: 'test-property',
|
||||||
|
name: 'test-property',
|
||||||
|
values: ['value-1', 'value-2']
|
||||||
|
} as ConfigurationProperty;
|
||||||
|
const configLink = 'https://rest.api/rest/api/config/properties';
|
||||||
|
const requestURL = `https://rest.api/rest/api/config/properties/${testObject.name}`;
|
||||||
|
const requestUUID = 'test-property';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
scheduler = getTestScheduler();
|
||||||
|
|
||||||
|
halService = jasmine.createSpyObj('halService', {
|
||||||
|
getEndpoint: cold('a', {a: configLink})
|
||||||
|
});
|
||||||
|
requestService = jasmine.createSpyObj('requestService', {
|
||||||
|
generateRequestId: requestUUID,
|
||||||
|
configure: true
|
||||||
|
});
|
||||||
|
rdbService = jasmine.createSpyObj('rdbService', {
|
||||||
|
buildSingle: cold('a', {
|
||||||
|
a: {
|
||||||
|
payload: testObject
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
objectCache = {} as ObjectCacheService;
|
||||||
|
const notificationsService = {} as NotificationsService;
|
||||||
|
const http = {} as HttpClient;
|
||||||
|
const comparator = {} as any;
|
||||||
|
|
||||||
|
service = new ConfigurationDataService(
|
||||||
|
requestService,
|
||||||
|
rdbService,
|
||||||
|
objectCache,
|
||||||
|
halService,
|
||||||
|
notificationsService,
|
||||||
|
http,
|
||||||
|
comparator
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('findById', () => {
|
||||||
|
it('should call HALEndpointService with the path to the properties endpoint', () => {
|
||||||
|
scheduler.schedule(() => service.findByPropertyName(testObject.name));
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect(halService.getEndpoint).toHaveBeenCalledWith('properties');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should configure the proper FindByIDRequest', () => {
|
||||||
|
scheduler.schedule(() => service.findByPropertyName(testObject.name));
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect(requestService.configure).toHaveBeenCalledWith(new FindByIDRequest(requestUUID, requestURL, testObject.name));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a RemoteData<ConfigurationProperty> for the object with the given name', () => {
|
||||||
|
const result = service.findByPropertyName(testObject.name);
|
||||||
|
const expected = cold('a', {
|
||||||
|
a: {
|
||||||
|
payload: testObject
|
||||||
|
}
|
||||||
|
});
|
||||||
|
expect(result).toBeObservable(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
62
src/app/core/data/configuration-data.service.ts
Normal file
62
src/app/core/data/configuration-data.service.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
|
import { dataService } from '../cache/builders/build-decorators';
|
||||||
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
|
import { CoreState } from '../core.reducers';
|
||||||
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
|
import { DataService } from './data.service';
|
||||||
|
import { RemoteData } from './remote-data';
|
||||||
|
import { RequestService } from './request.service';
|
||||||
|
import { ConfigurationProperty } from '../shared/configuration-property.model';
|
||||||
|
import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
|
||||||
|
import { CONFIG_PROPERTY } from '../shared/config-property.resource-type';
|
||||||
|
|
||||||
|
/* tslint:disable:max-classes-per-file */
|
||||||
|
class DataServiceImpl extends DataService<ConfigurationProperty> {
|
||||||
|
protected linkPath = 'properties';
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected requestService: RequestService,
|
||||||
|
protected rdbService: RemoteDataBuildService,
|
||||||
|
protected store: Store<CoreState>,
|
||||||
|
protected objectCache: ObjectCacheService,
|
||||||
|
protected halService: HALEndpointService,
|
||||||
|
protected notificationsService: NotificationsService,
|
||||||
|
protected http: HttpClient,
|
||||||
|
protected comparator: DefaultChangeAnalyzer<ConfigurationProperty>) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
@dataService(CONFIG_PROPERTY)
|
||||||
|
/**
|
||||||
|
* Data Service responsible for retrieving Configuration properties
|
||||||
|
*/
|
||||||
|
export class ConfigurationDataService {
|
||||||
|
protected linkPath = 'properties';
|
||||||
|
private dataService: DataServiceImpl;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected requestService: RequestService,
|
||||||
|
protected rdbService: RemoteDataBuildService,
|
||||||
|
protected objectCache: ObjectCacheService,
|
||||||
|
protected halService: HALEndpointService,
|
||||||
|
protected notificationsService: NotificationsService,
|
||||||
|
protected http: HttpClient,
|
||||||
|
protected comparator: DefaultChangeAnalyzer<ConfigurationProperty>) {
|
||||||
|
this.dataService = new DataServiceImpl(requestService, rdbService, null, objectCache, halService, notificationsService, http, comparator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds a configuration property by name
|
||||||
|
* @param name
|
||||||
|
*/
|
||||||
|
findByPropertyName(name: string): Observable<RemoteData<ConfigurationProperty>> {
|
||||||
|
return this.dataService.findById(name);
|
||||||
|
}
|
||||||
|
}
|
@@ -40,7 +40,6 @@ import { GROUP } from './models/group.resource-type';
|
|||||||
import { DSONameService } from '../breadcrumbs/dso-name.service';
|
import { DSONameService } from '../breadcrumbs/dso-name.service';
|
||||||
import { Community } from '../shared/community.model';
|
import { Community } from '../shared/community.model';
|
||||||
import { Collection } from '../shared/collection.model';
|
import { Collection } from '../shared/collection.model';
|
||||||
import { ComcolRole } from '../../shared/comcol-forms/edit-comcol-page/comcol-role/comcol-role';
|
|
||||||
|
|
||||||
const groupRegistryStateSelector = (state: AppState) => state.groupRegistry;
|
const groupRegistryStateSelector = (state: AppState) => state.groupRegistry;
|
||||||
const editGroupSelector = createSelector(groupRegistryStateSelector, (groupRegistryState: GroupRegistryState) => groupRegistryState.editGroup);
|
const editGroupSelector = createSelector(groupRegistryStateSelector, (groupRegistryState: GroupRegistryState) => groupRegistryState.editGroup);
|
||||||
@@ -324,16 +323,17 @@ export class GroupDataService extends DataService<Group> {
|
|||||||
* Create a group for a given role for a given community or collection.
|
* Create a group for a given role for a given community or collection.
|
||||||
*
|
*
|
||||||
* @param dso The community or collection for which to create a group
|
* @param dso The community or collection for which to create a group
|
||||||
|
* @param role The name of the role for which to create a group
|
||||||
* @param link The REST endpoint to create the group
|
* @param link The REST endpoint to create the group
|
||||||
*/
|
*/
|
||||||
createComcolGroup(dso: Community|Collection, link: string): Observable<RestResponse> {
|
createComcolGroup(dso: Community|Collection, role: string, link: string): Observable<RestResponse> {
|
||||||
|
|
||||||
const requestId = this.requestService.generateRequestId();
|
const requestId = this.requestService.generateRequestId();
|
||||||
const group = Object.assign(new Group(), {
|
const group = Object.assign(new Group(), {
|
||||||
metadata: {
|
metadata: {
|
||||||
'dc.description': [
|
'dc.description': [
|
||||||
{
|
{
|
||||||
value: `${this.nameService.getName(dso)} admin group`,
|
value: `${this.nameService.getName(dso)} ${role} group`,
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@@ -15,8 +15,6 @@ import { RESOURCE_POLICY } from '../resource-policy/models/resource-policy.resou
|
|||||||
import { COMMUNITY } from './community.resource-type';
|
import { COMMUNITY } from './community.resource-type';
|
||||||
import { Community } from './community.model';
|
import { Community } from './community.model';
|
||||||
import { ChildHALResource } from './child-hal-resource.model';
|
import { ChildHALResource } from './child-hal-resource.model';
|
||||||
import { GROUP } from '../eperson/models/group.resource-type';
|
|
||||||
import { Group } from '../eperson/models/group.model';
|
|
||||||
|
|
||||||
@typedObject
|
@typedObject
|
||||||
@inheritSerialization(DSpaceObject)
|
@inheritSerialization(DSpaceObject)
|
||||||
@@ -41,6 +39,11 @@ export class Collection extends DSpaceObject implements ChildHALResource {
|
|||||||
defaultAccessConditions: HALLink;
|
defaultAccessConditions: HALLink;
|
||||||
logo: HALLink;
|
logo: HALLink;
|
||||||
parentCommunity: HALLink;
|
parentCommunity: HALLink;
|
||||||
|
workflowGroups: HALLink[];
|
||||||
|
adminGroup: HALLink;
|
||||||
|
submittersGroup: HALLink;
|
||||||
|
itemReadGroup: HALLink;
|
||||||
|
bitstreamReadGroup: HALLink;
|
||||||
self: HALLink;
|
self: HALLink;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -72,12 +75,6 @@ export class Collection extends DSpaceObject implements ChildHALResource {
|
|||||||
@link(COMMUNITY, false)
|
@link(COMMUNITY, false)
|
||||||
parentCommunity?: Observable<RemoteData<Community>>;
|
parentCommunity?: Observable<RemoteData<Community>>;
|
||||||
|
|
||||||
/**
|
|
||||||
* The administrators group of this community.
|
|
||||||
*/
|
|
||||||
@link(GROUP)
|
|
||||||
adminGroup?: Observable<RemoteData<Group>>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The introductory text of this Collection
|
* The introductory text of this Collection
|
||||||
* Corresponds to the metadata field dc.description
|
* Corresponds to the metadata field dc.description
|
||||||
|
@@ -3,8 +3,6 @@ import { Observable } from 'rxjs';
|
|||||||
import { link, typedObject } from '../cache/builders/build-decorators';
|
import { link, typedObject } from '../cache/builders/build-decorators';
|
||||||
import { PaginatedList } from '../data/paginated-list';
|
import { PaginatedList } from '../data/paginated-list';
|
||||||
import { RemoteData } from '../data/remote-data';
|
import { RemoteData } from '../data/remote-data';
|
||||||
import { Group } from '../eperson/models/group.model';
|
|
||||||
import { GROUP } from '../eperson/models/group.resource-type';
|
|
||||||
import { Bitstream } from './bitstream.model';
|
import { Bitstream } from './bitstream.model';
|
||||||
import { BITSTREAM } from './bitstream.resource-type';
|
import { BITSTREAM } from './bitstream.resource-type';
|
||||||
import { Collection } from './collection.model';
|
import { Collection } from './collection.model';
|
||||||
@@ -66,12 +64,6 @@ export class Community extends DSpaceObject implements ChildHALResource {
|
|||||||
@link(COMMUNITY, false)
|
@link(COMMUNITY, false)
|
||||||
parentCommunity?: Observable<RemoteData<Community>>;
|
parentCommunity?: Observable<RemoteData<Community>>;
|
||||||
|
|
||||||
/**
|
|
||||||
* The administrators group of this community.
|
|
||||||
*/
|
|
||||||
@link(GROUP)
|
|
||||||
adminGroup?: Observable<RemoteData<Group>>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The introductory text of this Community
|
* The introductory text of this Community
|
||||||
* Corresponds to the metadata field dc.description
|
* Corresponds to the metadata field dc.description
|
||||||
|
9
src/app/core/shared/config-property.resource-type.ts
Normal file
9
src/app/core/shared/config-property.resource-type.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { ResourceType } from './resource-type';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The resource type for ConfigurationProperty
|
||||||
|
*
|
||||||
|
* Needs to be in a separate file to prevent circular
|
||||||
|
* dependencies in webpack.
|
||||||
|
*/
|
||||||
|
export const CONFIG_PROPERTY = new ResourceType('property');
|
48
src/app/core/shared/configuration-property.model.ts
Normal file
48
src/app/core/shared/configuration-property.model.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { autoserialize, autoserializeAs, deserialize } from 'cerialize';
|
||||||
|
import { typedObject } from '../cache/builders/build-decorators';
|
||||||
|
import { CacheableObject } from '../cache/object-cache.reducer';
|
||||||
|
import { excludeFromEquals } from '../utilities/equals.decorators';
|
||||||
|
import { HALLink } from './hal-link.model';
|
||||||
|
import { ResourceType } from './resource-type';
|
||||||
|
import { CONFIG_PROPERTY } from './config-property.resource-type';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model class for a Configuration Property
|
||||||
|
*/
|
||||||
|
@typedObject
|
||||||
|
export class ConfigurationProperty implements CacheableObject {
|
||||||
|
static type = CONFIG_PROPERTY;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The object type
|
||||||
|
*/
|
||||||
|
@excludeFromEquals
|
||||||
|
@autoserialize
|
||||||
|
type: ResourceType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The uuid of the configuration property
|
||||||
|
* The name is used as id for configuration properties
|
||||||
|
*/
|
||||||
|
@autoserializeAs(String, 'name')
|
||||||
|
uuid: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the configuration property
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The values of the configuration property
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
values: string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The links of the configuration property
|
||||||
|
*/
|
||||||
|
@deserialize
|
||||||
|
_links: { self: HALLink };
|
||||||
|
|
||||||
|
}
|
@@ -22,6 +22,6 @@ export class HALResource {
|
|||||||
/**
|
/**
|
||||||
* {@link HALLink}s to related {@link HALResource}s
|
* {@link HALLink}s to related {@link HALResource}s
|
||||||
*/
|
*/
|
||||||
[k: string]: HALLink;
|
[k: string]: HALLink | HALLink[];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
20
src/app/curation-form/curation-form.component.html
Normal file
20
src/app/curation-form/curation-form.component.html
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<form [formGroup]="form" (ngSubmit)="submit()">
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-12 col-sm-6">
|
||||||
|
<label class="font-weight-bold" for="task">{{'curation.form.task-select.label' |translate }}</label>
|
||||||
|
<select id="task" formControlName="task" class="form-control">
|
||||||
|
<option *ngFor="let task of tasks" [value]="task">
|
||||||
|
{{ 'curation-task.task.' + task + '.label' | translate }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="!hasHandleValue()" class="col-12 col-sm-6">
|
||||||
|
<label class="font-weight-bold" for="handle">{{'curation.form.handle.label' |translate }}</label>
|
||||||
|
<input id="handle" class="form-control" formControlName="handle">
|
||||||
|
<small class="text-muted">{{'curation.form.handle.hint' |translate }}</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-default btn-primary" type="submit">{{'curation.form.submit' |translate }}</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
165
src/app/curation-form/curation-form.component.spec.ts
Normal file
165
src/app/curation-form/curation-form.component.spec.ts
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||||
|
import { CurationFormComponent } from './curation-form.component';
|
||||||
|
import { ScriptDataService } from '../core/data/processes/script-data.service';
|
||||||
|
import { ProcessDataService } from '../core/data/processes/process-data.service';
|
||||||
|
import { AuthService } from '../core/auth/auth.service';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { RequestEntry } from '../core/data/request.reducer';
|
||||||
|
import { DSOSuccessResponse, RestResponse } from '../core/cache/response.models';
|
||||||
|
import { Process } from '../process-page/processes/process.model';
|
||||||
|
import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
|
||||||
|
import { EPerson } from '../core/eperson/models/eperson.model';
|
||||||
|
import { NotificationsServiceStub } from '../shared/testing/notifications-service.stub';
|
||||||
|
import { RouterStub } from '../shared/testing/router.stub';
|
||||||
|
import { NotificationsService } from '../shared/notifications/notifications.service';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { ConfigurationDataService } from '../core/data/configuration-data.service';
|
||||||
|
import { ConfigurationProperty } from '../core/shared/configuration-property.model';
|
||||||
|
|
||||||
|
describe('CurationFormComponent', () => {
|
||||||
|
let comp: CurationFormComponent;
|
||||||
|
let fixture: ComponentFixture<CurationFormComponent>;
|
||||||
|
|
||||||
|
let scriptDataService: ScriptDataService;
|
||||||
|
let processDataService: ProcessDataService;
|
||||||
|
let configurationDataService: ConfigurationDataService;
|
||||||
|
let authService: AuthService;
|
||||||
|
let notificationsService;
|
||||||
|
let router;
|
||||||
|
|
||||||
|
const requestEntry = Object.assign(new RequestEntry(),
|
||||||
|
{response: new DSOSuccessResponse(['process-link'], 200, 'success')});
|
||||||
|
const failedRequestEntry = Object.assign(new RequestEntry(),
|
||||||
|
{response: new RestResponse(false, 400, 'Bad Request')});
|
||||||
|
|
||||||
|
const process = Object.assign(new Process(), {processId: 'process-id'});
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
|
||||||
|
scriptDataService = jasmine.createSpyObj('scriptDataService', {
|
||||||
|
invoke: observableOf(requestEntry)
|
||||||
|
});
|
||||||
|
|
||||||
|
processDataService = jasmine.createSpyObj('processDataService', {
|
||||||
|
findByHref: createSuccessfulRemoteDataObject$(process)
|
||||||
|
});
|
||||||
|
|
||||||
|
authService = jasmine.createSpyObj('authService', {
|
||||||
|
getAuthenticatedUserFromStore: observableOf(Object.assign(new EPerson(), {email: 'test@mail'}))
|
||||||
|
});
|
||||||
|
|
||||||
|
configurationDataService = jasmine.createSpyObj('configurationDataService', {
|
||||||
|
findByPropertyName: createSuccessfulRemoteDataObject$(Object.assign(new ConfigurationProperty(), {
|
||||||
|
name: 'plugin.named.org.dspace.curate.CurationTask',
|
||||||
|
values: [
|
||||||
|
'org.dspace.ctask.general.ProfileFormats = profileformats',
|
||||||
|
'',
|
||||||
|
'org.dspace.ctask.general.RequiredMetadata = requiredmetadata',
|
||||||
|
'org.dspace.ctask.general.MetadataValueLinkChecker = checklinks',
|
||||||
|
'value-to-be-skipped'
|
||||||
|
]
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
|
||||||
|
notificationsService = new NotificationsServiceStub();
|
||||||
|
router = new RouterStub();
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot(), FormsModule, ReactiveFormsModule],
|
||||||
|
declarations: [CurationFormComponent],
|
||||||
|
providers: [
|
||||||
|
{provide: ScriptDataService, useValue: scriptDataService},
|
||||||
|
{provide: ProcessDataService, useValue: processDataService},
|
||||||
|
{provide: AuthService, useValue: authService},
|
||||||
|
{provide: NotificationsService, useValue: notificationsService},
|
||||||
|
{provide: Router, useValue: router},
|
||||||
|
{provide: ConfigurationDataService, useValue: configurationDataService},
|
||||||
|
],
|
||||||
|
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(CurationFormComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
describe('init', () => {
|
||||||
|
it('should initialise the comp and contain the different tasks', () => {
|
||||||
|
expect(comp).toBeDefined();
|
||||||
|
|
||||||
|
const elements = fixture.debugElement.queryAll(By.css('option'));
|
||||||
|
expect(elements.length).toEqual(3);
|
||||||
|
expect(elements[0].nativeElement.innerHTML).toContain('curation-task.task.profileformats.label');
|
||||||
|
expect(elements[1].nativeElement.innerHTML).toContain('curation-task.task.requiredmetadata.label');
|
||||||
|
expect(elements[2].nativeElement.innerHTML).toContain('curation-task.task.checklinks.label');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('hasHandleValue', () => {
|
||||||
|
it('should return true when a dsoHandle value was provided', () => {
|
||||||
|
comp.dsoHandle = 'some-handle';
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(comp.hasHandleValue()).toBeTrue();
|
||||||
|
});
|
||||||
|
it('should return false when no dsoHandle value was provided', () => {
|
||||||
|
expect(comp.hasHandleValue()).toBeFalse();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('submit', () => {
|
||||||
|
it('should submit the selected process and handle to the scriptservice and navigate to the corresponding process page', () => {
|
||||||
|
comp.dsoHandle = 'test-handle';
|
||||||
|
comp.submit();
|
||||||
|
|
||||||
|
expect(scriptDataService.invoke).toHaveBeenCalledWith('curate', [
|
||||||
|
{name: '-t', value: 'profileformats'},
|
||||||
|
{name: '-i', value: 'test-handle'},
|
||||||
|
{name: '-e', value: 'test@mail'},
|
||||||
|
], []);
|
||||||
|
expect(notificationsService.success).toHaveBeenCalled();
|
||||||
|
expect(processDataService.findByHref).toHaveBeenCalledWith('process-link');
|
||||||
|
expect(router.navigate).toHaveBeenCalledWith(['/processes', 'process-id']);
|
||||||
|
});
|
||||||
|
it('should the selected process and handle to the scriptservice and stay on the page on error', () => {
|
||||||
|
(scriptDataService.invoke as jasmine.Spy).and.returnValue(observableOf(failedRequestEntry));
|
||||||
|
|
||||||
|
comp.dsoHandle = 'test-handle';
|
||||||
|
comp.submit();
|
||||||
|
|
||||||
|
expect(scriptDataService.invoke).toHaveBeenCalledWith('curate', [
|
||||||
|
{name: '-t', value: 'profileformats'},
|
||||||
|
{name: '-i', value: 'test-handle'},
|
||||||
|
{name: '-e', value: 'test@mail'},
|
||||||
|
], []);
|
||||||
|
expect(notificationsService.error).toHaveBeenCalled();
|
||||||
|
expect(processDataService.findByHref).not.toHaveBeenCalled();
|
||||||
|
expect(router.navigate).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should use the handle provided by the form when no dsoHandle is provided', () => {
|
||||||
|
comp.form.get('handle').patchValue('form-handle');
|
||||||
|
|
||||||
|
comp.submit();
|
||||||
|
|
||||||
|
expect(scriptDataService.invoke).toHaveBeenCalledWith('curate', [
|
||||||
|
{name: '-t', value: 'profileformats'},
|
||||||
|
{name: '-i', value: 'form-handle'},
|
||||||
|
{name: '-e', value: 'test@mail'},
|
||||||
|
], []);
|
||||||
|
});
|
||||||
|
it('should use "all" when the handle provided by the form is empty and when no dsoHandle is provided', () => {
|
||||||
|
|
||||||
|
comp.submit();
|
||||||
|
|
||||||
|
expect(scriptDataService.invoke).toHaveBeenCalledWith('curate', [
|
||||||
|
{name: '-t', value: 'profileformats'},
|
||||||
|
{name: '-i', value: 'all'},
|
||||||
|
{name: '-e', value: 'test@mail'},
|
||||||
|
], []);
|
||||||
|
});
|
||||||
|
});
|
118
src/app/curation-form/curation-form.component.ts
Normal file
118
src/app/curation-form/curation-form.component.ts
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
|
import { ScriptDataService } from '../core/data/processes/script-data.service';
|
||||||
|
import { FormControl, FormGroup } from '@angular/forms';
|
||||||
|
import { getResponseFromEntry } from '../core/shared/operators';
|
||||||
|
import { DSOSuccessResponse } from '../core/cache/response.models';
|
||||||
|
import { AuthService } from '../core/auth/auth.service';
|
||||||
|
import { filter, map, switchMap, take } from 'rxjs/operators';
|
||||||
|
import { EPerson } from '../core/eperson/models/eperson.model';
|
||||||
|
import { NotificationsService } from '../shared/notifications/notifications.service';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { hasValue, isEmpty, isNotEmpty } from '../shared/empty.util';
|
||||||
|
import { RemoteData } from '../core/data/remote-data';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { ProcessDataService } from '../core/data/processes/process-data.service';
|
||||||
|
import { Process } from '../process-page/processes/process.model';
|
||||||
|
import { ConfigurationDataService } from '../core/data/configuration-data.service';
|
||||||
|
import { ConfigurationProperty } from '../core/shared/configuration-property.model';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { find } from 'rxjs/internal/operators/find';
|
||||||
|
|
||||||
|
export const CURATION_CFG = 'plugin.named.org.dspace.curate.CurationTask';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component responsible for rendering the Curation Task form
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-curation-form',
|
||||||
|
templateUrl: './curation-form.component.html'
|
||||||
|
})
|
||||||
|
export class CurationFormComponent implements OnInit {
|
||||||
|
|
||||||
|
config: Observable<RemoteData<ConfigurationProperty>>;
|
||||||
|
tasks: string[];
|
||||||
|
form: FormGroup;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
dsoHandle: string;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private scriptDataService: ScriptDataService,
|
||||||
|
private configurationDataService: ConfigurationDataService,
|
||||||
|
private processDataService: ProcessDataService,
|
||||||
|
private authService: AuthService,
|
||||||
|
private notificationsService: NotificationsService,
|
||||||
|
private translateService: TranslateService,
|
||||||
|
private router: Router
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.form = new FormGroup({
|
||||||
|
task: new FormControl(''),
|
||||||
|
handle: new FormControl('')
|
||||||
|
});
|
||||||
|
|
||||||
|
this.config = this.configurationDataService.findByPropertyName(CURATION_CFG);
|
||||||
|
this.config.pipe(
|
||||||
|
find((rd: RemoteData<ConfigurationProperty>) => rd.hasSucceeded),
|
||||||
|
map((rd: RemoteData<ConfigurationProperty>) => rd.payload)
|
||||||
|
).subscribe((configProperties) => {
|
||||||
|
this.tasks = configProperties.values
|
||||||
|
.filter((value) => isNotEmpty(value) && value.includes('='))
|
||||||
|
.map((value) => value.split('=')[1].trim());
|
||||||
|
this.form.get('task').patchValue(this.tasks[0]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether the inputted dsoHandle has a value
|
||||||
|
*/
|
||||||
|
hasHandleValue() {
|
||||||
|
if (hasValue(this.dsoHandle)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit the selected taskName and handle to the script data service to run the corresponding curation script
|
||||||
|
* Navigate to the process page on success
|
||||||
|
*/
|
||||||
|
submit() {
|
||||||
|
const taskName = this.form.get('task').value;
|
||||||
|
let handle;
|
||||||
|
if (this.hasHandleValue()) {
|
||||||
|
handle = this.dsoHandle;
|
||||||
|
} else {
|
||||||
|
handle = this.form.get('handle').value;
|
||||||
|
if (isEmpty(handle)) {
|
||||||
|
handle = 'all';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.authService.getAuthenticatedUserFromStore().pipe(
|
||||||
|
take(1),
|
||||||
|
switchMap((eperson: EPerson) => {
|
||||||
|
return this.scriptDataService.invoke('curate', [
|
||||||
|
{name: '-t', value: taskName},
|
||||||
|
{name: '-i', value: handle},
|
||||||
|
{name: '-e', value: eperson.email},
|
||||||
|
], []).pipe(getResponseFromEntry());
|
||||||
|
})
|
||||||
|
).subscribe((response: DSOSuccessResponse) => {
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
this.notificationsService.success(this.translateService.get('curation.form.submit.success.head'),
|
||||||
|
this.translateService.get('curation.form.submit.success.content'));
|
||||||
|
this.processDataService.findByHref(response.resourceSelfLinks[0]).pipe(
|
||||||
|
filter((processRD: RemoteData<Process>) => hasValue(processRD) && hasValue(processRD.payload)),
|
||||||
|
take(1))
|
||||||
|
.subscribe((processRD: RemoteData<Process>) => {
|
||||||
|
this.router.navigate(['/processes', processRD.payload.processId]);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.notificationsService.error(this.translateService.get('curation.form.submit.error.head'),
|
||||||
|
this.translateService.get('curation.form.submit.error.content'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -4,13 +4,11 @@ import { GroupDataService } from '../../../../core/eperson/group-data.service';
|
|||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
import { SharedModule } from '../../../shared.module';
|
import { SharedModule } from '../../../shared.module';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { ChangeDetectorRef, DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
|
import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { RequestService } from '../../../../core/data/request.service';
|
import { RequestService } from '../../../../core/data/request.service';
|
||||||
import { ComcolRole } from './comcol-role';
|
|
||||||
import { of as observableOf } from 'rxjs/internal/observable/of';
|
import { of as observableOf } from 'rxjs/internal/observable/of';
|
||||||
import { RemoteData } from '../../../../core/data/remote-data';
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
import { Collection } from '../../../../core/shared/collection.model';
|
|
||||||
|
|
||||||
describe('ComcolRoleComponent', () => {
|
describe('ComcolRoleComponent', () => {
|
||||||
|
|
||||||
@@ -65,20 +63,10 @@ describe('ComcolRoleComponent', () => {
|
|||||||
comp = fixture.componentInstance;
|
comp = fixture.componentInstance;
|
||||||
de = fixture.debugElement;
|
de = fixture.debugElement;
|
||||||
|
|
||||||
comp.comcolRole = new ComcolRole(
|
comp.comcolRole = {
|
||||||
'test role name',
|
name: 'test role name',
|
||||||
'test role endpoint',
|
|
||||||
);
|
|
||||||
|
|
||||||
comp.dso = Object.assign(
|
|
||||||
new Collection(), {
|
|
||||||
_links: {
|
|
||||||
'test role endpoint': {
|
|
||||||
href: 'test role link',
|
href: 'test role link',
|
||||||
}
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
@@ -138,7 +126,7 @@ describe('ComcolRoleComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should call the groupService create method', () => {
|
it('should call the groupService create method', () => {
|
||||||
expect(groupService.createComcolGroup).toHaveBeenCalledWith(comp.dso, 'test role link');
|
expect(groupService.createComcolGroup).toHaveBeenCalledWith(comp.dso, 'test role name', 'test role link');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -7,9 +7,9 @@ import { GroupDataService } from '../../../../core/eperson/group-data.service';
|
|||||||
import { Collection } from '../../../../core/shared/collection.model';
|
import { Collection } from '../../../../core/shared/collection.model';
|
||||||
import { filter, map } from 'rxjs/operators';
|
import { filter, map } from 'rxjs/operators';
|
||||||
import { getRemoteDataPayload, getSucceededRemoteData } from '../../../../core/shared/operators';
|
import { getRemoteDataPayload, getSucceededRemoteData } from '../../../../core/shared/operators';
|
||||||
import { ComcolRole } from './comcol-role';
|
|
||||||
import { RequestService } from '../../../../core/data/request.service';
|
import { RequestService } from '../../../../core/data/request.service';
|
||||||
import { RemoteData } from '../../../../core/data/remote-data';
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
|
import { HALLink } from '../../../../core/shared/hal-link.model';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component for managing a community or collection role.
|
* Component for managing a community or collection role.
|
||||||
@@ -31,7 +31,7 @@ export class ComcolRoleComponent implements OnInit {
|
|||||||
* The role to manage
|
* The role to manage
|
||||||
*/
|
*/
|
||||||
@Input()
|
@Input()
|
||||||
comcolRole: ComcolRole;
|
comcolRole: HALLink;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected requestService: RequestService,
|
protected requestService: RequestService,
|
||||||
@@ -43,7 +43,7 @@ export class ComcolRoleComponent implements OnInit {
|
|||||||
* The link to the related group.
|
* The link to the related group.
|
||||||
*/
|
*/
|
||||||
get groupLink(): string {
|
get groupLink(): string {
|
||||||
return this.dso._links[this.comcolRole.linkName].href;
|
return this.comcolRole.href;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -106,7 +106,7 @@ export class ComcolRoleComponent implements OnInit {
|
|||||||
* Create a group for this community or collection role.
|
* Create a group for this community or collection role.
|
||||||
*/
|
*/
|
||||||
create() {
|
create() {
|
||||||
this.groupService.createComcolGroup(this.dso, this.groupLink).subscribe();
|
this.groupService.createComcolGroup(this.dso, this.comcolRole.name, this.groupLink).subscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1,77 +0,0 @@
|
|||||||
import { Community } from '../../../../core/shared/community.model';
|
|
||||||
import { Collection } from '../../../../core/shared/collection.model';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class representing a community or collection role.
|
|
||||||
*/
|
|
||||||
export class ComcolRole {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The community admin role.
|
|
||||||
*/
|
|
||||||
public static COMMUNITY_ADMIN = new ComcolRole(
|
|
||||||
'community-admin',
|
|
||||||
'adminGroup',
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The collection admin role.
|
|
||||||
*/
|
|
||||||
public static COLLECTION_ADMIN = new ComcolRole(
|
|
||||||
'collection-admin',
|
|
||||||
'adminGroup',
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The submitters role.
|
|
||||||
*/
|
|
||||||
public static SUBMITTERS = new ComcolRole(
|
|
||||||
'submitters',
|
|
||||||
'submittersGroup',
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The default item read role.
|
|
||||||
*/
|
|
||||||
public static ITEM_READ = new ComcolRole(
|
|
||||||
'item_read',
|
|
||||||
'itemReadGroup',
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The default bitstream read role.
|
|
||||||
*/
|
|
||||||
public static BITSTREAM_READ = new ComcolRole(
|
|
||||||
'bitstream_read',
|
|
||||||
'bitstreamReadGroup',
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param name The name for this community or collection role.
|
|
||||||
* @param linkName The path linking to this community or collection role.
|
|
||||||
*/
|
|
||||||
constructor(
|
|
||||||
public name,
|
|
||||||
public linkName,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the REST endpoint for managing this role for a given community or collection.
|
|
||||||
* @param dso
|
|
||||||
*/
|
|
||||||
public getEndpoint(dso: Community | Collection) {
|
|
||||||
|
|
||||||
let linkPath;
|
|
||||||
switch (dso.type + '') {
|
|
||||||
case 'community':
|
|
||||||
linkPath = 'communities';
|
|
||||||
break;
|
|
||||||
case 'collection':
|
|
||||||
linkPath = 'collections';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${linkPath}/${dso.uuid}/${this.linkName}`;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -16,9 +16,11 @@
|
|||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
<a *ngIf="!hideReturnButton" [routerLink]="getPageUrl((dsoRD$ | async)?.payload)" class="btn btn-outline-secondary">{{ type + '.edit.return' | translate }}</a>
|
<a *ngIf="!hideReturnButton" [routerLink]="getPageUrl((dsoRD$ | async)?.payload)" class="btn btn-outline-secondary">{{ type + '.edit.return' | translate }}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -318,6 +318,12 @@ export class FormComponent implements OnDestroy, OnInit {
|
|||||||
const model = arrayContext.groups[arrayContext.groups.length - 1].group[0] as any;
|
const model = arrayContext.groups[arrayContext.groups.length - 1].group[0] as any;
|
||||||
if (model.type === DYNAMIC_FORM_CONTROL_TYPE_SCROLLABLE_DROPDOWN) {
|
if (model.type === DYNAMIC_FORM_CONTROL_TYPE_SCROLLABLE_DROPDOWN) {
|
||||||
model.value = Object.values(value)[0];
|
model.value = Object.values(value)[0];
|
||||||
|
const ctrl = formArrayControl.controls[formArrayControl.length - 1];
|
||||||
|
const ctrlValue = ctrl.value;
|
||||||
|
const ctrlValueKey = Object.keys(ctrlValue)[0];
|
||||||
|
ctrl.setValue({
|
||||||
|
[ctrlValueKey]: model.value
|
||||||
|
});
|
||||||
} else if (this.formBuilderService.isQualdropGroup(model)) {
|
} else if (this.formBuilderService.isQualdropGroup(model)) {
|
||||||
const ctrl = formArrayControl.controls[formArrayControl.length - 1];
|
const ctrl = formArrayControl.controls[formArrayControl.length - 1];
|
||||||
const ctrlKey = Object.keys(ctrl.value).find((key: string) => isNotEmpty(key.match(QUALDROP_GROUP_REGEX)));
|
const ctrlKey = Object.keys(ctrl.value).find((key: string) => isNotEmpty(key.match(QUALDROP_GROUP_REGEX)));
|
||||||
|
@@ -208,6 +208,7 @@ import { GroupSearchBoxComponent } from './resource-policies/form/eperson-group-
|
|||||||
import { FileDownloadLinkComponent } from './file-download-link/file-download-link.component';
|
import { FileDownloadLinkComponent } from './file-download-link/file-download-link.component';
|
||||||
import { CollectionDropdownComponent } from './collection-dropdown/collection-dropdown.component';
|
import { CollectionDropdownComponent } from './collection-dropdown/collection-dropdown.component';
|
||||||
import { DsSelectComponent } from './ds-select/ds-select.component';
|
import { DsSelectComponent } from './ds-select/ds-select.component';
|
||||||
|
import { CurationFormComponent } from '../curation-form/curation-form.component';
|
||||||
|
|
||||||
const MODULES = [
|
const MODULES = [
|
||||||
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
|
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
|
||||||
@@ -472,7 +473,8 @@ const ENTRY_COMPONENTS = [
|
|||||||
ClaimedTaskActionsReturnToPoolComponent,
|
ClaimedTaskActionsReturnToPoolComponent,
|
||||||
ClaimedTaskActionsEditMetadataComponent,
|
ClaimedTaskActionsEditMetadataComponent,
|
||||||
CollectionDropdownComponent,
|
CollectionDropdownComponent,
|
||||||
FileDownloadLinkComponent
|
FileDownloadLinkComponent,
|
||||||
|
CurationFormComponent,
|
||||||
];
|
];
|
||||||
|
|
||||||
const SHARED_ITEM_PAGE_COMPONENTS = [
|
const SHARED_ITEM_PAGE_COMPONENTS = [
|
||||||
@@ -530,7 +532,8 @@ const DIRECTIVES = [
|
|||||||
...PIPES,
|
...PIPES,
|
||||||
...COMPONENTS,
|
...COMPONENTS,
|
||||||
...SHARED_ITEM_PAGE_COMPONENTS,
|
...SHARED_ITEM_PAGE_COMPONENTS,
|
||||||
...DIRECTIVES
|
...DIRECTIVES,
|
||||||
|
CurationFormComponent
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
...ENTRY_COMPONENTS
|
...ENTRY_COMPONENTS
|
||||||
|
9
src/app/shared/uploader/uploader-error.model.ts
Normal file
9
src/app/shared/uploader/uploader-error.model.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* An interface that represents the upload error values
|
||||||
|
*/
|
||||||
|
export interface UploaderError {
|
||||||
|
item?: any;
|
||||||
|
response?: any;
|
||||||
|
status?: any;
|
||||||
|
headers?: any;
|
||||||
|
}
|
@@ -17,6 +17,11 @@ export class UploaderOptions {
|
|||||||
*/
|
*/
|
||||||
autoUpload = true;
|
autoUpload = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the max number of files that can be loaded
|
||||||
|
*/
|
||||||
|
maxFileNumber: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The request method to use for the file upload request
|
* The request method to use for the file upload request
|
||||||
*/
|
*/
|
||||||
|
@@ -69,6 +69,11 @@ export class UploaderComponent {
|
|||||||
*/
|
*/
|
||||||
@Output() onUploadError: EventEmitter<any> = new EventEmitter<any>();
|
@Output() onUploadError: EventEmitter<any> = new EventEmitter<any>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The function to call when a file is selected
|
||||||
|
*/
|
||||||
|
@Output() onFileSelected: EventEmitter<any> = new EventEmitter<any>();
|
||||||
|
|
||||||
public uploader: FileUploader;
|
public uploader: FileUploader;
|
||||||
public uploaderId: string;
|
public uploaderId: string;
|
||||||
public isOverBaseDropZone = observableOf(false);
|
public isOverBaseDropZone = observableOf(false);
|
||||||
@@ -102,7 +107,8 @@ export class UploaderComponent {
|
|||||||
itemAlias: this.uploadFilesOptions.itemAlias,
|
itemAlias: this.uploadFilesOptions.itemAlias,
|
||||||
removeAfterUpload: true,
|
removeAfterUpload: true,
|
||||||
autoUpload: this.uploadFilesOptions.autoUpload,
|
autoUpload: this.uploadFilesOptions.autoUpload,
|
||||||
method: this.uploadFilesOptions.method
|
method: this.uploadFilesOptions.method,
|
||||||
|
queueLimit: this.uploadFilesOptions.maxFileNumber
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isUndefined(this.enableDragOverDocument)) {
|
if (isUndefined(this.enableDragOverDocument)) {
|
||||||
@@ -121,6 +127,9 @@ export class UploaderComponent {
|
|||||||
this.uploader.onAfterAddingFile = ((item) => {
|
this.uploader.onAfterAddingFile = ((item) => {
|
||||||
item.withCredentials = false;
|
item.withCredentials = false;
|
||||||
});
|
});
|
||||||
|
this.uploader.onAfterAddingAll = ((items) => {
|
||||||
|
this.onFileSelected.emit(items);
|
||||||
|
});
|
||||||
if (isUndefined(this.onBeforeUpload)) {
|
if (isUndefined(this.onBeforeUpload)) {
|
||||||
this.onBeforeUpload = () => {return};
|
this.onBeforeUpload = () => {return};
|
||||||
}
|
}
|
||||||
@@ -149,7 +158,7 @@ export class UploaderComponent {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.uploader.onErrorItem = (item: any, response: any, status: any, headers: any) => {
|
this.uploader.onErrorItem = (item: any, response: any, status: any, headers: any) => {
|
||||||
this.onUploadError.emit(null);
|
this.onUploadError.emit({ item: item, response: response, status: status, headers: headers });
|
||||||
this.uploader.cancelAll();
|
this.uploader.cancelAll();
|
||||||
};
|
};
|
||||||
this.uploader.onProgressAll = () => this.onProgress();
|
this.uploader.onProgressAll = () => this.onProgress();
|
||||||
|
@@ -28,6 +28,7 @@ import { FormBuilderService } from '../../../shared/form/builder/form-builder.se
|
|||||||
import { FormFieldMetadataValueObject } from '../../../shared/form/builder/models/form-field-metadata-value.model';
|
import { FormFieldMetadataValueObject } from '../../../shared/form/builder/models/form-field-metadata-value.model';
|
||||||
import { DynamicQualdropModel } from '../../../shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-qualdrop.model';
|
import { DynamicQualdropModel } from '../../../shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-qualdrop.model';
|
||||||
import { DynamicRelationGroupModel } from '../../../shared/form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.model';
|
import { DynamicRelationGroupModel } from '../../../shared/form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.model';
|
||||||
|
import { deepClone } from 'fast-json-patch';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The service handling all form section operations
|
* The service handling all form section operations
|
||||||
@@ -310,7 +311,7 @@ export class SectionFormOperationsService {
|
|||||||
event: DynamicFormControlEvent
|
event: DynamicFormControlEvent
|
||||||
): void {
|
): void {
|
||||||
const path = this.getFieldPathSegmentedFromChangeEvent(event);
|
const path = this.getFieldPathSegmentedFromChangeEvent(event);
|
||||||
const value = this.getFieldValueFromChangeEvent(event);
|
const value = deepClone(this.getFieldValueFromChangeEvent(event));
|
||||||
if (isNotEmpty(value)) {
|
if (isNotEmpty(value)) {
|
||||||
value.place = this.getArrayIndexFromEvent(event);
|
value.place = this.getArrayIndexFromEvent(event);
|
||||||
if (hasValue(event.group) && hasValue(event.group.value)) {
|
if (hasValue(event.group) && hasValue(event.group.value)) {
|
||||||
|
@@ -14,7 +14,11 @@
|
|||||||
|
|
||||||
"404.page-not-found": "page not found",
|
"404.page-not-found": "page not found",
|
||||||
|
|
||||||
|
"admin.curation-tasks.breadcrumbs": "System curation tasks",
|
||||||
|
|
||||||
|
"admin.curation-tasks.title": "System curation tasks",
|
||||||
|
|
||||||
|
"admin.curation-tasks.header": "System curation tasks",
|
||||||
|
|
||||||
"admin.registries.bitstream-formats.breadcrumbs": "Format registry",
|
"admin.registries.bitstream-formats.breadcrumbs": "Format registry",
|
||||||
|
|
||||||
@@ -560,6 +564,8 @@
|
|||||||
|
|
||||||
"collection.create.sub-head": "Create a Collection for Community {{ parent }}",
|
"collection.create.sub-head": "Create a Collection for Community {{ parent }}",
|
||||||
|
|
||||||
|
"collection.curate.header": "Curate Collection: {{collection}}",
|
||||||
|
|
||||||
"collection.delete.cancel": "Cancel",
|
"collection.delete.cancel": "Cancel",
|
||||||
|
|
||||||
"collection.delete.confirm": "Confirm",
|
"collection.delete.confirm": "Confirm",
|
||||||
@@ -770,6 +776,8 @@
|
|||||||
|
|
||||||
"community.create.sub-head": "Create a Sub-Community for Community {{ parent }}",
|
"community.create.sub-head": "Create a Sub-Community for Community {{ parent }}",
|
||||||
|
|
||||||
|
"community.curate.header": "Curate Community: {{community}}",
|
||||||
|
|
||||||
"community.delete.cancel": "Cancel",
|
"community.delete.cancel": "Cancel",
|
||||||
|
|
||||||
"community.delete.confirm": "Confirm",
|
"community.delete.confirm": "Confirm",
|
||||||
@@ -909,6 +917,38 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
"curation-task.task.checklinks.label": "Check Links in Metadata",
|
||||||
|
|
||||||
|
"curation-task.task.noop.label": "NOOP",
|
||||||
|
|
||||||
|
"curation-task.task.profileformats.label": "Profile Bitstream Formats",
|
||||||
|
|
||||||
|
"curation-task.task.requiredmetadata.label": "Check for Required Metadata",
|
||||||
|
|
||||||
|
"curation-task.task.translate.label": "Microsoft Translator",
|
||||||
|
|
||||||
|
"curation-task.task.vscan.label": "Virus Scan",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
"curation.form.task-select.label": "Task:",
|
||||||
|
|
||||||
|
"curation.form.submit": "Start",
|
||||||
|
|
||||||
|
"curation.form.submit.success.head": "The curation task has been started successfully",
|
||||||
|
|
||||||
|
"curation.form.submit.success.content": "You will be redirected to the corresponding process page.",
|
||||||
|
|
||||||
|
"curation.form.submit.error.head": "Running the curation task failed",
|
||||||
|
|
||||||
|
"curation.form.submit.error.content": "An error occured when trying to start the curation task.",
|
||||||
|
|
||||||
|
"curation.form.handle.label": "Handle:",
|
||||||
|
|
||||||
|
"curation.form.handle.hint": "Hint: Enter [your-handle-prefix]/0 to run a task across entire site (not all tasks may support this capability)",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"dso-selector.create.collection.head": "New collection",
|
"dso-selector.create.collection.head": "New collection",
|
||||||
|
|
||||||
"dso-selector.create.community.head": "New community",
|
"dso-selector.create.community.head": "New community",
|
||||||
@@ -919,6 +959,8 @@
|
|||||||
|
|
||||||
"dso-selector.create.item.head": "New item",
|
"dso-selector.create.item.head": "New item",
|
||||||
|
|
||||||
|
"dso-selector.create.submission.head": "New submission",
|
||||||
|
|
||||||
"dso-selector.edit.collection.head": "Edit collection",
|
"dso-selector.edit.collection.head": "Edit collection",
|
||||||
|
|
||||||
"dso-selector.edit.community.head": "Edit community",
|
"dso-selector.edit.community.head": "Edit community",
|
||||||
@@ -1941,6 +1983,10 @@
|
|||||||
|
|
||||||
"mydspace.upload.upload-failed": "Error creating new workspace. Please verify the content uploaded before retry.",
|
"mydspace.upload.upload-failed": "Error creating new workspace. Please verify the content uploaded before retry.",
|
||||||
|
|
||||||
|
"mydspace.upload.upload-failed-manyentries": "Unprocessable file. Detected too many entries but allowed only one for file.",
|
||||||
|
|
||||||
|
"mydspace.upload.upload-failed-moreonefile": "Unprocessable request. Only one file is allowed.",
|
||||||
|
|
||||||
"mydspace.upload.upload-multiple-successful": "{{qty}} new workspace items created.",
|
"mydspace.upload.upload-multiple-successful": "{{qty}} new workspace items created.",
|
||||||
|
|
||||||
"mydspace.upload.upload-successful": "New workspace item created. Click {{here}} for edit it.",
|
"mydspace.upload.upload-successful": "New workspace item created. Click {{here}} for edit it.",
|
||||||
|
@@ -215,5 +215,5 @@ export const environment: GlobalConfig = {
|
|||||||
},
|
},
|
||||||
theme: {
|
theme: {
|
||||||
name: 'default',
|
name: 'default',
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
@@ -195,5 +195,5 @@ export const environment: Partial<GlobalConfig> = {
|
|||||||
},
|
},
|
||||||
theme: {
|
theme: {
|
||||||
name: 'default',
|
name: 'default',
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user