mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
Merged submission module code
This commit is contained in:
@@ -41,6 +41,43 @@ module.exports = {
|
||||
// NOTE: 'fade' | 'fromTop' | 'fromRight' | 'fromBottom' | 'fromLeft' | 'rotate' | 'scale'
|
||||
animate: 'scale'
|
||||
},
|
||||
// Submission settings
|
||||
submission: {
|
||||
autosave: {
|
||||
// NOTE: which metadata trigger an autosave
|
||||
metadata: ['dc.title', 'dc.identifier.doi', 'dc.identifier.pmid', 'dc.identifier.arxiv'],
|
||||
// NOTE: every how many minutes submission is saved automatically
|
||||
timer: 5
|
||||
},
|
||||
metadata: {
|
||||
// NOTE: allow to set icons used to represent metadata belonging to a relation group
|
||||
icons: [
|
||||
/**
|
||||
* NOTE: example of configuration
|
||||
* {
|
||||
* // NOTE: metadata name
|
||||
* name: 'dc.author',
|
||||
* config: {
|
||||
* // NOTE: used when metadata value has an authority
|
||||
* withAuthority: {
|
||||
* // NOTE: fontawesome (v4.x) icon classes and bootstrap color utility classes can be used
|
||||
* style: 'fa-user'
|
||||
* },
|
||||
* // NOTE: used when metadata value has not an authority
|
||||
* withoutAuthority: {
|
||||
* style: 'fa-user text-muted'
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
// default configuration
|
||||
{
|
||||
name: 'default',
|
||||
config: {}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
// Angular Universal settings
|
||||
universal: {
|
||||
preboot: true,
|
||||
|
@@ -79,7 +79,7 @@
|
||||
"@angular/platform-server": "^5.2.5",
|
||||
"@angular/router": "^5.2.5",
|
||||
"@angularclass/bootloader": "1.0.1",
|
||||
"@ng-bootstrap/ng-bootstrap": "^1.0.0",
|
||||
"@ng-bootstrap/ng-bootstrap": "1.1.2",
|
||||
"@ng-dynamic-forms/core": "5.4.7",
|
||||
"@ng-dynamic-forms/ui-ng-bootstrap": "5.4.7",
|
||||
"@ngrx/effects": "^5.1.0",
|
||||
@@ -101,6 +101,7 @@
|
||||
"core-js": "2.5.3",
|
||||
"express": "4.16.2",
|
||||
"express-session": "1.15.6",
|
||||
"file-saver": "^1.3.8",
|
||||
"font-awesome": "4.7.0",
|
||||
"http-server": "0.11.1",
|
||||
"https": "1.0.0",
|
||||
@@ -133,6 +134,7 @@
|
||||
"@types/deep-freeze": "0.1.1",
|
||||
"@types/express": "^4.11.1",
|
||||
"@types/express-serve-static-core": "4.11.1",
|
||||
"@types/file-saver": "^1.3.0",
|
||||
"@types/hammerjs": "2.0.35",
|
||||
"@types/jasmine": "^2.8.6",
|
||||
"@types/js-cookie": "2.1.0",
|
||||
|
@@ -210,6 +210,11 @@
|
||||
"license": {
|
||||
"notgranted": "You must grant this license to complete your submission. If you are unable to grant this license at this time you may save your work and return later or remove the submission."
|
||||
}
|
||||
},
|
||||
"submission": {
|
||||
"sections": {
|
||||
"init-form-error": "An error occurred during section initialize, please check your input-form configuration. Details are below : <br> <br>"
|
||||
}
|
||||
}
|
||||
},
|
||||
"form": {
|
||||
@@ -252,5 +257,95 @@
|
||||
"errors": {
|
||||
"invalid-user": "Invalid email or password."
|
||||
}
|
||||
},
|
||||
"submission": {
|
||||
"general":{
|
||||
"cannot_submit": "You have not the privilege to make a new submission.",
|
||||
"deposit": "Deposit",
|
||||
"discard": {
|
||||
"submit": "Discard",
|
||||
"confirm": {
|
||||
"cancel": "Cancel",
|
||||
"submit": "Yes, I'm sure",
|
||||
"title": "Discard submission",
|
||||
"info": "This operation can't be undone. Are you sure?"
|
||||
}
|
||||
},
|
||||
"save": "Save",
|
||||
"save-later": "Save for later"
|
||||
},
|
||||
"submit": {
|
||||
"title": "Submission"
|
||||
},
|
||||
"edit": {
|
||||
"title": "Edit Submission"
|
||||
},
|
||||
"mydspace": {
|
||||
|
||||
},
|
||||
"sections": {
|
||||
|
||||
"general": {
|
||||
"add-more": "Add more",
|
||||
"sections_not_valid": "There are incomplete sections.",
|
||||
"deposit_success_notice": "Submission deposited successfully.",
|
||||
"deposit_error_notice": "There was an issue when submitting the item, please try again later.",
|
||||
"discard_success_notice": "Submission discarded successfully.",
|
||||
"discard_error_notice": "There was an issue when discarding the item, please try again later.",
|
||||
"save_success_notice": "Submission saved successfully.",
|
||||
"metadata-extracted": "New metadata have been extracted and added to the <strong>{{sectionId}}</strong> section.",
|
||||
"metadata-extracted-new-section": "New <strong>{{sectionId}}</strong> section has been added to submission."
|
||||
},
|
||||
"submit.progressbar.describe.stepone": "Describe",
|
||||
"submit.progressbar.describe.steptwo": "Describe",
|
||||
"submit.progressbar.describe.stepcustom": "Describe",
|
||||
"submit.progressbar.describe.deduplication": "Potential duplicates",
|
||||
"submit.progressbar.describe.recycle": "Recycle",
|
||||
"submit.progressbar.upload": "Upload files",
|
||||
"submit.progressbar.license": "Deposit license",
|
||||
"submit.progressbar.cclicense": "Creative commons license",
|
||||
|
||||
"upload": {
|
||||
"info": "Here you will find all the files currently in the item. You can update the fle metadata and access conditions or <strong>upload additional files just dragging&dropping them everywhere in the page</strong>",
|
||||
"drop-message": "Drop files to attach them to the item",
|
||||
"upload-successful": "Upload successful",
|
||||
"upload-failed": "Upload failed",
|
||||
"header.policy.default.nolist": "Uploaded files in the {{collectionName}} collection will be accessible according to the following group(s):",
|
||||
"header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicity decided for the single file, with the following group(s):",
|
||||
"form": {
|
||||
"access-condition-label": "Access condition type",
|
||||
"from-label": "Access grant from",
|
||||
"from-placeholder": "From",
|
||||
"until-label": "Access grant until",
|
||||
"until-placeholder": "Until",
|
||||
"group-label": "Group"
|
||||
}
|
||||
},
|
||||
"deduplication": {
|
||||
"duplicated": "It's a duplicate",
|
||||
"not_duplicated": "It's not a duplicate",
|
||||
"duplicated_ctrl": "Mark the record to merge",
|
||||
"duplicated_help": "Click here if this is a duplicate of your item",
|
||||
"not_duplicated_help": "Click here if this is not a duplicate of your item",
|
||||
"note_help": "Please enter your reason for the duplication into the box below.",
|
||||
"note_placeholder": "Describe the reason of duplication",
|
||||
"clear_decision": "Undo",
|
||||
"clear_decision_help": "Click for clear the decision about this pontential duplicate",
|
||||
"your_decision": "Your choice:",
|
||||
"submitter_decision": "Submitter choice:",
|
||||
"disclaimer": "The system has identified some potential duplicates. Please carefully review the list and flag each occurency with the appropriate choice or discard this submission.",
|
||||
"disclaimer_ctrl": "The system has identified some potential duplicates. Please carefully review the list and the submitter comments and perform the appropriate action."
|
||||
},
|
||||
"recycle": {
|
||||
"disclaimer": "The following existent information are not valid within the selected collection. Please copy them to the appropriate metadata if applicable. Use the discard button to remove these information when done."
|
||||
}
|
||||
}
|
||||
},
|
||||
"uploader": {
|
||||
"drag-message": "Drag & Drop your files here",
|
||||
"or": ", or",
|
||||
"browse": "browse",
|
||||
"queue-lenght": "Queue length",
|
||||
"processing": "Processing"
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { SharedModule } from './../shared/shared.module';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
|
||||
import { ItemPageComponent } from './simple/item-page.component';
|
||||
import { ItemPageRoutingModule } from './item-page-routing.module';
|
||||
@@ -18,11 +18,13 @@ import { FileSectionComponent } from './simple/field-components/file-section/fil
|
||||
import { CollectionsComponent } from './field-components/collections/collections.component';
|
||||
import { FullItemPageComponent } from './full/full-item-page.component';
|
||||
import { FullFileSectionComponent } from './full/field-components/file-section/full-file-section.component';
|
||||
import { SubmissionModule } from '../submission/submission.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
SubmissionModule,
|
||||
ItemPageRoutingModule
|
||||
],
|
||||
declarations: [
|
||||
|
@@ -6,7 +6,7 @@ import { LoginPageComponent } from './login-page.component';
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild([
|
||||
{ path: '', component: LoginPageComponent, data: { title: 'login.title' } }
|
||||
{ path: '', pathMatch: 'full', component: LoginPageComponent, data: { title: 'login.title' } }
|
||||
])
|
||||
]
|
||||
})
|
||||
|
@@ -1,20 +1,61 @@
|
||||
import { Component, OnDestroy } from '@angular/core';
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
|
||||
import { Store } from '@ngrx/store';
|
||||
|
||||
import { AppState } from '../app.reducer';
|
||||
import { ResetAuthenticationMessagesAction } from '../core/auth/auth.actions';
|
||||
import {
|
||||
AddAuthenticationMessageAction,
|
||||
AuthenticatedAction,
|
||||
AuthenticationSuccessAction,
|
||||
ResetAuthenticationMessagesAction
|
||||
} from '../core/auth/auth.actions';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import { hasValue, isNotEmpty } from '../shared/empty.util';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { AuthTokenInfo } from '../core/auth/models/auth-token-info.model';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { isAuthenticated } from '../core/auth/selectors';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-login-page',
|
||||
styleUrls: ['./login-page.component.scss'],
|
||||
templateUrl: './login-page.component.html'
|
||||
})
|
||||
export class LoginPageComponent implements OnDestroy {
|
||||
export class LoginPageComponent implements OnDestroy, OnInit {
|
||||
sub: Subscription;
|
||||
|
||||
constructor(private store: Store<AppState>) {}
|
||||
constructor(private route: ActivatedRoute,
|
||||
private store: Store<AppState>) {}
|
||||
|
||||
ngOnInit() {
|
||||
const queryParamsObs = this.route.queryParams;
|
||||
const authenticated = this.store.select(isAuthenticated);
|
||||
this.sub = Observable.combineLatest(queryParamsObs, authenticated)
|
||||
.filter(([params, auth]) => isNotEmpty(params.token) || isNotEmpty(params.expired))
|
||||
.take(1)
|
||||
.subscribe(([params, auth]) => {
|
||||
const token = params.token;
|
||||
let authToken: AuthTokenInfo;
|
||||
if (!auth) {
|
||||
if (isNotEmpty(token)) {
|
||||
authToken = new AuthTokenInfo(token);
|
||||
this.store.dispatch(new AuthenticatedAction(authToken));
|
||||
} else if (isNotEmpty(params.expired)) {
|
||||
this.store.dispatch(new AddAuthenticationMessageAction('auth.messages.expired'));
|
||||
}
|
||||
} else {
|
||||
if (isNotEmpty(token)) {
|
||||
authToken = new AuthTokenInfo(token);
|
||||
this.store.dispatch(new AuthenticationSuccessAction(authToken));
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (hasValue(this.sub)) {
|
||||
this.sub.unsubscribe();
|
||||
}
|
||||
// Clear all authentication messages when leaving login page
|
||||
this.store.dispatch(new ResetAuthenticationMessagesAction());
|
||||
}
|
||||
|
20
src/app/+submit-page/submit-page-routing.module.ts
Normal file
20
src/app/+submit-page/submit-page-routing.module.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
|
||||
import { SubmissionSubmitComponent } from '../submission/submit/submission-submit.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild([
|
||||
{
|
||||
canActivate: [AuthenticatedGuard],
|
||||
path: '',
|
||||
pathMatch: 'full',
|
||||
component: SubmissionSubmitComponent,
|
||||
data: { title: 'submission.submit.title' }
|
||||
}
|
||||
])
|
||||
]
|
||||
})
|
||||
export class SubmitPageRoutingModule { }
|
17
src/app/+submit-page/submit-page.module.ts
Normal file
17
src/app/+submit-page/submit-page.module.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { SubmitPageRoutingModule } from './submit-page-routing.module';
|
||||
import { SubmissionModule } from '../submission/submission.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
SubmitPageRoutingModule,
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
SubmissionModule,
|
||||
],
|
||||
})
|
||||
export class SubmitPageModule {
|
||||
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
|
||||
import { SubmissionEditComponent } from '../submission/edit/submission-edit.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild([
|
||||
{ path: '', redirectTo: '/home', pathMatch: 'full' },
|
||||
{
|
||||
canActivate: [AuthenticatedGuard],
|
||||
path: ':id/edit',
|
||||
component: SubmissionEditComponent,
|
||||
data: { title: 'submission.edit.title' }
|
||||
}
|
||||
])
|
||||
]
|
||||
})
|
||||
export class WorkflowitemsEditPageRoutingModule { }
|
@@ -0,0 +1,18 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { WorkflowitemsEditPageRoutingModule } from './workflowitems-edit-page-routing.module';
|
||||
import { SubmissionModule } from '../submission/submission.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
WorkflowitemsEditPageRoutingModule,
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
SubmissionModule,
|
||||
],
|
||||
declarations: []
|
||||
})
|
||||
export class WorkflowitemsEditPageModule {
|
||||
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
|
||||
import { SubmissionEditComponent } from '../submission/edit/submission-edit.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild([
|
||||
{ path: '', redirectTo: '/home', pathMatch: 'full' },
|
||||
{
|
||||
canActivate: [AuthenticatedGuard],
|
||||
path: ':id/edit',
|
||||
component: SubmissionEditComponent,
|
||||
data: { title: 'submission.edit.title' }
|
||||
}
|
||||
])
|
||||
]
|
||||
})
|
||||
export class WorkspaceitemsEditPageRoutingModule { }
|
@@ -0,0 +1,18 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { WorkspaceitemsEditPageRoutingModule } from './workspaceitems-edit-page-routing.module';
|
||||
import { SubmissionModule } from '../submission/submission.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
WorkspaceitemsEditPageRoutingModule,
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
SubmissionModule,
|
||||
],
|
||||
declarations: []
|
||||
})
|
||||
export class WorkspaceitemsEditPageModule {
|
||||
|
||||
}
|
@@ -15,6 +15,9 @@ import { PageNotFoundComponent } from './pagenotfound/pagenotfound.component';
|
||||
{ path: 'admin', loadChildren: './+admin/admin.module#AdminModule' },
|
||||
{ path: 'login', loadChildren: './+login-page/login-page.module#LoginPageModule' },
|
||||
{ path: 'logout', loadChildren: './+logout-page/logout-page.module#LogoutPageModule' },
|
||||
{ path: 'submit', loadChildren: './+submit-page/submit-page.module#SubmitPageModule' },
|
||||
{ path: 'workspaceitems', loadChildren: './+workspaceitems-edit-page/workspaceitems-edit-page.module#WorkspaceitemsEditPageModule' },
|
||||
{ path: 'workflowitems', loadChildren: './+workflowitems-edit-page/workflowitems-edit-page.module#WorkflowitemsEditPageModule' },
|
||||
{ path: '**', pathMatch: 'full', component: PageNotFoundComponent },
|
||||
])
|
||||
],
|
||||
|
@@ -13,5 +13,3 @@
|
||||
<ds-footer></ds-footer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
@@ -27,7 +27,11 @@ body {
|
||||
}
|
||||
|
||||
.main-content {
|
||||
flex: 1 0 auto;
|
||||
flex: 1 1 100%;
|
||||
margin-top: $content-spacing;
|
||||
margin-bottom: $content-spacing;
|
||||
}
|
||||
.alert.hide {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
@@ -31,6 +31,7 @@ import { DSpaceRouterStateSerializer } from './shared/ngrx/dspace-router-state-s
|
||||
import { NotificationsBoardComponent } from './shared/notifications/notifications-board/notifications-board.component';
|
||||
import { NotificationComponent } from './shared/notifications/notification/notification.component';
|
||||
import { SharedModule } from './shared/shared.module';
|
||||
import { ScrollToModule } from '@nicky-lenaers/ngx-scroll-to';
|
||||
|
||||
export function getConfig() {
|
||||
return ENV_CONFIG;
|
||||
@@ -58,6 +59,7 @@ if (!ENV_CONFIG.production) {
|
||||
HttpClientModule,
|
||||
AppRoutingModule,
|
||||
CoreModule.forRoot(),
|
||||
ScrollToModule.forRoot(),
|
||||
NgbModule.forRoot(),
|
||||
TranslateModule.forRoot(),
|
||||
EffectsModule.forRoot(appEffects),
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { autoserialize, inheritSerialization, autoserializeAs } from 'cerialize';
|
||||
import { autoserialize, inheritSerialization } from 'cerialize';
|
||||
|
||||
import { NormalizedDSpaceObject } from './normalized-dspace-object.model';
|
||||
import { Collection } from '../../shared/collection.model';
|
||||
@@ -15,6 +15,20 @@ export class NormalizedCollection extends NormalizedDSpaceObject {
|
||||
@autoserialize
|
||||
handle: string;
|
||||
|
||||
/**
|
||||
* The Bitstream that represents the license of this Collection
|
||||
*/
|
||||
@autoserialize
|
||||
@relationship(ResourceType.License, false)
|
||||
license: string;
|
||||
|
||||
/**
|
||||
* The Bitstream that represents the default Access Conditions of this Collection
|
||||
*/
|
||||
@autoserialize
|
||||
@relationship(ResourceType.ResourcePolicy, false)
|
||||
defaultAccessConditions: string;
|
||||
|
||||
/**
|
||||
* The Bitstream that represents the logo of this Collection
|
||||
*/
|
||||
|
21
src/app/core/cache/models/normalized-license.model.ts
vendored
Normal file
21
src/app/core/cache/models/normalized-license.model.ts
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
import { autoserialize, inheritSerialization } from 'cerialize';
|
||||
import { mapsTo } from '../builders/build-decorators';
|
||||
import { NormalizedDSpaceObject } from './normalized-dspace-object.model';
|
||||
import { License } from '../../shared/license.model';
|
||||
|
||||
@mapsTo(License)
|
||||
@inheritSerialization(NormalizedDSpaceObject)
|
||||
export class NormalizedLicense extends NormalizedDSpaceObject {
|
||||
|
||||
/**
|
||||
* Is the license custom?
|
||||
*/
|
||||
@autoserialize
|
||||
custom: boolean;
|
||||
|
||||
/**
|
||||
* The text of the license
|
||||
*/
|
||||
@autoserialize
|
||||
text: string;
|
||||
}
|
@@ -6,6 +6,12 @@ import { GenericConstructor } from '../../shared/generic-constructor';
|
||||
import { NormalizedCommunity } from './normalized-community.model';
|
||||
import { ResourceType } from '../../shared/resource-type';
|
||||
import { NormalizedObject } from './normalized-object.model';
|
||||
import { NormalizedLicense } from './normalized-license.model';
|
||||
import { NormalizedResourcePolicy } from './normalized-resource-policy.model';
|
||||
import { NormalizedWorkspaceItem } from '../../submission/models/normalized-workspaceitem.model';
|
||||
import { NormalizedEpersonModel } from '../../eperson/models/NormalizedEperson.model';
|
||||
import { NormalizedGroupModel } from '../../eperson/models/NormalizedGroup.model';
|
||||
import { NormalizedWorkflowItem } from '../../submission/models/normalized-workflowitem.model';
|
||||
|
||||
export class NormalizedObjectFactory {
|
||||
public static getConstructor(type: ResourceType): GenericConstructor<NormalizedObject> {
|
||||
@@ -25,6 +31,21 @@ export class NormalizedObjectFactory {
|
||||
case ResourceType.Community: {
|
||||
return NormalizedCommunity
|
||||
}
|
||||
case ResourceType.License: {
|
||||
return NormalizedLicense
|
||||
}
|
||||
case ResourceType.ResourcePolicy: {
|
||||
return NormalizedResourcePolicy
|
||||
}
|
||||
case ResourceType.Workspaceitem: {
|
||||
return NormalizedWorkspaceItem
|
||||
}
|
||||
case ResourceType.Eperson: {
|
||||
return NormalizedEpersonModel
|
||||
}
|
||||
case ResourceType.Group: {
|
||||
return NormalizedGroupModel
|
||||
}
|
||||
default: {
|
||||
return undefined;
|
||||
}
|
||||
|
45
src/app/core/cache/models/normalized-resource-policy.model.ts
vendored
Normal file
45
src/app/core/cache/models/normalized-resource-policy.model.ts
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
import { mapsTo } from '../builders/build-decorators';
|
||||
import { autoserialize, inheritSerialization } from 'cerialize';
|
||||
import { NormalizedDSpaceObject } from './normalized-dspace-object.model';
|
||||
import { ResourcePolicy } from '../../shared/resource-policy.model';
|
||||
|
||||
@mapsTo(ResourcePolicy)
|
||||
@inheritSerialization(NormalizedDSpaceObject)
|
||||
export class NormalizedResourcePolicy extends NormalizedDSpaceObject {
|
||||
|
||||
/**
|
||||
* The action of the resource policy
|
||||
*/
|
||||
@autoserialize
|
||||
action: string;
|
||||
|
||||
/**
|
||||
* The identifier of the resource policy
|
||||
*/
|
||||
@autoserialize
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* The group uuid bound to the resource policy
|
||||
*/
|
||||
@autoserialize
|
||||
groupUUID: string;
|
||||
|
||||
/**
|
||||
* The end date of the resource policy
|
||||
*/
|
||||
@autoserialize
|
||||
endDate: string;
|
||||
|
||||
/**
|
||||
* The start date of the resource policy
|
||||
*/
|
||||
@autoserialize
|
||||
startDate: string;
|
||||
|
||||
/**
|
||||
* The type of the resource policy
|
||||
*/
|
||||
@autoserialize
|
||||
rpType: string
|
||||
}
|
31
src/app/core/cache/response-cache.models.ts
vendored
31
src/app/core/cache/response-cache.models.ts
vendored
@@ -10,6 +10,7 @@ import { MetadataSchema } from '../metadata/metadataschema.model';
|
||||
import { RegistryMetadatafieldsResponse } from '../registry/registry-metadatafields-response.model';
|
||||
import { RegistryBitstreamformatsResponse } from '../registry/registry-bitstreamformats-response.model';
|
||||
import { AuthStatus } from '../auth/models/auth-status.model';
|
||||
import { NormalizedObject } from './models/normalized-object.model';
|
||||
|
||||
/* tslint:disable:max-classes-per-file */
|
||||
export class RestResponse {
|
||||
@@ -176,4 +177,34 @@ export class IntegrationSuccessResponse extends RestResponse {
|
||||
}
|
||||
}
|
||||
|
||||
export class PostPatchSuccessResponse extends RestResponse {
|
||||
constructor(
|
||||
public dataDefinition: any[],
|
||||
public statusCode: string,
|
||||
public pageInfo?: PageInfo
|
||||
) {
|
||||
super(true, statusCode);
|
||||
}
|
||||
}
|
||||
|
||||
export class SubmissionSuccessResponse extends RestResponse {
|
||||
constructor(
|
||||
public dataDefinition: Array<NormalizedObject | ConfigObject | string>,
|
||||
public statusCode: string,
|
||||
public pageInfo?: PageInfo
|
||||
) {
|
||||
super(true, statusCode);
|
||||
}
|
||||
}
|
||||
|
||||
export class EpersonSuccessResponse extends RestResponse {
|
||||
constructor(
|
||||
public epersonDefinition: NormalizedObject[],
|
||||
public statusCode: string,
|
||||
public pageInfo?: PageInfo
|
||||
) {
|
||||
super(true, statusCode);
|
||||
}
|
||||
}
|
||||
|
||||
/* tslint:enable:max-classes-per-file */
|
||||
|
18
src/app/core/config/submission-uploads-config.service.ts
Normal file
18
src/app/core/config/submission-uploads-config.service.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ConfigService } from './config.service';
|
||||
import { ResponseCacheService } from '../cache/response-cache.service';
|
||||
import { RequestService } from '../data/request.service';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
|
||||
@Injectable()
|
||||
export class SubmissionUploadsConfigService extends ConfigService {
|
||||
protected linkPath = 'submissionuploads';
|
||||
protected browseEndpoint = '';
|
||||
|
||||
constructor(
|
||||
protected responseCache: ResponseCacheService,
|
||||
protected requestService: RequestService,
|
||||
protected halService: HALEndpointService) {
|
||||
super();
|
||||
}
|
||||
}
|
@@ -4,11 +4,13 @@ import { ResponseCacheEffects } from './cache/response-cache.effects';
|
||||
import { UUIDIndexEffects } from './index/index.effects';
|
||||
import { RequestEffects } from './data/request.effects';
|
||||
import { AuthEffects } from './auth/auth.effects';
|
||||
import { JsonPatchOperationsEffects } from './json-patch/json-patch-operations.effects';
|
||||
|
||||
export const coreEffects = [
|
||||
ResponseCacheEffects,
|
||||
RequestEffects,
|
||||
ObjectCacheEffects,
|
||||
UUIDIndexEffects,
|
||||
AuthEffects
|
||||
AuthEffects,
|
||||
JsonPatchOperationsEffects,
|
||||
];
|
||||
|
@@ -1,9 +1,4 @@
|
||||
import {
|
||||
NgModule,
|
||||
Optional,
|
||||
SkipSelf,
|
||||
ModuleWithProviders
|
||||
} from '@angular/core';
|
||||
import { ModuleWithProviders, NgModule, Optional, SkipSelf } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { StoreModule } from '@ngrx/store';
|
||||
@@ -24,7 +19,9 @@ import { DSOResponseParsingService } from './data/dso-response-parsing.service';
|
||||
import { SearchResponseParsingService } from './data/search-response-parsing.service';
|
||||
import { DSpaceRESTv2Service } from './dspace-rest-v2/dspace-rest-v2.service';
|
||||
import { FormBuilderService } from '../shared/form/builder/form-builder.service';
|
||||
import { FormOperationsService } from '../submission/sections/form/form-operations.service';
|
||||
import { FormService } from '../shared/form/form.service';
|
||||
import { GroupEpersonService } from './eperson/group-eperson.service';
|
||||
import { HostWindowService } from '../shared/host-window.service';
|
||||
import { ItemDataService } from './data/item-data.service';
|
||||
import { MetadataService } from './metadata/metadata.service';
|
||||
@@ -43,8 +40,12 @@ import { RouteService } from '../shared/services/route.service';
|
||||
import { SubmissionDefinitionsConfigService } from './config/submission-definitions-config.service';
|
||||
import { SubmissionFormsConfigService } from './config/submission-forms-config.service';
|
||||
import { SubmissionSectionsConfigService } from './config/submission-sections-config.service';
|
||||
import { SubmissionResponseParsingService } from './submission/submission-response-parsing.service';
|
||||
import { EpersonResponseParsingService } from './eperson/eperson-response-parsing.service';
|
||||
import { JsonPatchOperationsBuilder } from './json-patch/builder/json-patch-operations-builder';
|
||||
import { AuthorityService } from './integration/authority.service';
|
||||
import { IntegrationResponseParsingService } from './integration/integration-response-parsing.service';
|
||||
import { WorkspaceitemDataService } from './submission/workspaceitem-data.service';
|
||||
import { UUIDService } from './shared/uuid.service';
|
||||
import { AuthenticatedGuard } from './auth/authenticated.guard';
|
||||
import { AuthRequestService } from './auth/auth-request.service';
|
||||
@@ -60,8 +61,12 @@ import { RegistryMetadataschemasResponseParsingService } from './data/registry-m
|
||||
import { MetadataschemaParsingService } from './data/metadataschema-parsing.service';
|
||||
import { RegistryMetadatafieldsResponseParsingService } from './data/registry-metadatafields-response-parsing.service';
|
||||
import { RegistryBitstreamformatsResponseParsingService } from './data/registry-bitstreamformats-response-parsing.service';
|
||||
import { JsonPatchOperationsService } from './json-patch/json-patch-operations.service';
|
||||
import { WorkflowitemDataService } from './submission/workflowitem-data.service';
|
||||
import { NotificationsService } from '../shared/notifications/notifications.service';
|
||||
import { UploaderService } from '../shared/uploader/uploader.service';
|
||||
import { FileService } from './shared/file.service';
|
||||
import { SubmissionRestService } from '../submission/submission-rest.service';
|
||||
|
||||
const IMPORTS = [
|
||||
CommonModule,
|
||||
@@ -90,7 +95,10 @@ const PROVIDERS = [
|
||||
DynamicFormService,
|
||||
DynamicFormValidationService,
|
||||
FormBuilderService,
|
||||
FormOperationsService,
|
||||
FormService,
|
||||
EpersonResponseParsingService,
|
||||
GroupEpersonService,
|
||||
HALEndpointService,
|
||||
HostWindowService,
|
||||
ItemDataService,
|
||||
@@ -119,11 +127,20 @@ const PROVIDERS = [
|
||||
RouteService,
|
||||
SubmissionDefinitionsConfigService,
|
||||
SubmissionFormsConfigService,
|
||||
SubmissionRestService,
|
||||
SubmissionSectionsConfigService,
|
||||
SubmissionResponseParsingService,
|
||||
JsonPatchOperationsBuilder,
|
||||
JsonPatchOperationsService,
|
||||
AuthorityService,
|
||||
IntegrationResponseParsingService,
|
||||
UploaderService,
|
||||
UUIDService,
|
||||
NotificationsService,
|
||||
WorkspaceitemDataService,
|
||||
WorkflowitemDataService,
|
||||
UploaderService,
|
||||
FileService,
|
||||
// register AuthInterceptor as HttpInterceptor
|
||||
{
|
||||
provide: HTTP_INTERCEPTORS,
|
||||
|
@@ -5,6 +5,7 @@ import { objectCacheReducer, ObjectCacheState } from './cache/object-cache.reduc
|
||||
import { indexReducer, IndexState } from './index/index.reducer';
|
||||
import { requestReducer, RequestState } from './data/request.reducer';
|
||||
import { authReducer, AuthState } from './auth/auth.reducer';
|
||||
import { jsonPatchOperationsReducer, JsonPatchOperationsState } from './json-patch/json-patch-operations.reducer';
|
||||
|
||||
export interface CoreState {
|
||||
'data/object': ObjectCacheState,
|
||||
@@ -12,6 +13,7 @@ export interface CoreState {
|
||||
'data/request': RequestState,
|
||||
'index': IndexState,
|
||||
'auth': AuthState,
|
||||
'json/patch': JsonPatchOperationsState
|
||||
}
|
||||
|
||||
export const coreReducers: ActionReducerMap<CoreState> = {
|
||||
@@ -19,7 +21,8 @@ export const coreReducers: ActionReducerMap<CoreState> = {
|
||||
'data/response': responseCacheReducer,
|
||||
'data/request': requestReducer,
|
||||
'index': indexReducer,
|
||||
'auth': authReducer
|
||||
'auth': authReducer,
|
||||
'json/patch': jsonPatchOperationsReducer
|
||||
};
|
||||
|
||||
export const coreSelector = createFeatureSelector<CoreState>('core');
|
||||
|
@@ -17,7 +17,7 @@ function isPaginatedResponse(halObj: any) {
|
||||
|
||||
/* tslint:disable:max-classes-per-file */
|
||||
|
||||
class ProcessRequestDTO<ObjectDomain> {
|
||||
export class ProcessRequestDTO<ObjectDomain> {
|
||||
[key: string]: ObjectDomain[]
|
||||
}
|
||||
|
||||
|
@@ -15,6 +15,7 @@ import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
@Injectable()
|
||||
export class CollectionDataService extends ComColDataService<NormalizedCollection, Collection> {
|
||||
protected linkPath = 'collections';
|
||||
protected forceBypassCache = false;
|
||||
|
||||
constructor(
|
||||
protected responseCache: ResponseCacheService,
|
||||
|
@@ -22,6 +22,7 @@ class NormalizedTestObject extends NormalizedObject {
|
||||
}
|
||||
|
||||
class TestService extends ComColDataService<NormalizedTestObject, any> {
|
||||
protected forceBypassCache = false;
|
||||
|
||||
constructor(
|
||||
protected responseCache: ResponseCacheService,
|
||||
|
@@ -16,6 +16,7 @@ import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
export class CommunityDataService extends ComColDataService<NormalizedCommunity, Community> {
|
||||
protected linkPath = 'communities';
|
||||
protected cds = this;
|
||||
protected forceBypassCache = false;
|
||||
|
||||
constructor(
|
||||
protected responseCache: ResponseCacheService,
|
||||
|
@@ -34,7 +34,7 @@ export class ConfigResponseParsingService extends BaseResponseParsingService imp
|
||||
return new ErrorResponse(
|
||||
Object.assign(
|
||||
new Error('Unexpected response from config endpoint'),
|
||||
{statusText: data.statusCode}
|
||||
{ statusText: data.statusCode }
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@@ -10,6 +10,7 @@ import { PaginatedList } from './paginated-list';
|
||||
import { RemoteData } from './remote-data';
|
||||
import { FindAllOptions, FindAllRequest, FindByIDRequest, GetRequest } from './request.models';
|
||||
import { RequestService } from './request.service';
|
||||
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
|
||||
import { NormalizedObject } from '../cache/models/normalized-object.model';
|
||||
|
||||
export abstract class DataService<TNormalized extends NormalizedObject, TDomain> {
|
||||
@@ -19,6 +20,7 @@ export abstract class DataService<TNormalized extends NormalizedObject, TDomain>
|
||||
protected abstract store: Store<CoreState>;
|
||||
protected abstract linkPath: string;
|
||||
protected abstract halService: HALEndpointService;
|
||||
protected abstract forceBypassCache = false;
|
||||
|
||||
public abstract getScopedEndpoint(scope: string): Observable<string>
|
||||
|
||||
@@ -52,6 +54,36 @@ export abstract class DataService<TNormalized extends NormalizedObject, TDomain>
|
||||
}
|
||||
}
|
||||
|
||||
protected getSearchByHref(endpoint, searchByLink, options: FindAllOptions = {}): Observable<string> {
|
||||
let result: Observable<string>;
|
||||
const args = [];
|
||||
|
||||
if (hasValue(options.scopeID)) {
|
||||
result = Observable.of(`${endpoint}/${searchByLink}?uuid=${options.scopeID}`);
|
||||
} else {
|
||||
result = Observable.of(endpoint);
|
||||
}
|
||||
|
||||
if (hasValue(options.currentPage) && typeof options.currentPage === 'number') {
|
||||
/* TODO: this is a temporary fix for the pagination start index (0 or 1) discrepancy between the rest and the frontend respectively */
|
||||
args.push(`page=${options.currentPage - 1}`);
|
||||
}
|
||||
|
||||
if (hasValue(options.elementsPerPage)) {
|
||||
args.push(`size=${options.elementsPerPage}`);
|
||||
}
|
||||
|
||||
if (hasValue(options.sort)) {
|
||||
args.push(`sort=${options.sort.field},${options.sort.direction}`);
|
||||
}
|
||||
|
||||
if (isNotEmpty(args)) {
|
||||
return result.map((href: string) => new URLCombiner(href, `?${args.join('&')}`).toString());
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
findAll(options: FindAllOptions = {}): Observable<RemoteData<PaginatedList<TDomain>>> {
|
||||
const hrefObs = this.halService.getEndpoint(this.linkPath).filter((href: string) => isNotEmpty(href))
|
||||
.flatMap((endpoint: string) => this.getFindAllHref(endpoint, options));
|
||||
@@ -61,7 +93,7 @@ export abstract class DataService<TNormalized extends NormalizedObject, TDomain>
|
||||
.take(1)
|
||||
.subscribe((href: string) => {
|
||||
const request = new FindAllRequest(this.requestService.generateRequestId(), href, options);
|
||||
this.requestService.configure(request);
|
||||
this.requestService.configure(request, this.forceBypassCache);
|
||||
});
|
||||
|
||||
return this.rdbService.buildList<TNormalized, TDomain>(hrefObs) as Observable<RemoteData<PaginatedList<TDomain>>>;
|
||||
@@ -80,32 +112,69 @@ export abstract class DataService<TNormalized extends NormalizedObject, TDomain>
|
||||
.take(1)
|
||||
.subscribe((href: string) => {
|
||||
const request = new FindByIDRequest(this.requestService.generateRequestId(), href, id);
|
||||
this.requestService.configure(request);
|
||||
this.requestService.configure(request, this.forceBypassCache);
|
||||
});
|
||||
|
||||
return this.rdbService.buildSingle<TNormalized, TDomain>(hrefObs);
|
||||
}
|
||||
|
||||
findByHref(href: string): Observable<RemoteData<TDomain>> {
|
||||
this.requestService.configure(new GetRequest(this.requestService.generateRequestId(), href));
|
||||
findByHref(href: string, options?: HttpOptions): Observable<RemoteData<TDomain>> {
|
||||
this.requestService.configure(new GetRequest(this.requestService.generateRequestId(), href, null, options), this.forceBypassCache);
|
||||
return this.rdbService.buildSingle<TNormalized, TDomain>(href);
|
||||
}
|
||||
|
||||
// TODO implement, after the structure of the REST server's POST response is finalized
|
||||
// create(dso: DSpaceObject): Observable<RemoteData<TDomain>> {
|
||||
// const postHrefObs = this.getEndpoint();
|
||||
//
|
||||
// // TODO ID is unknown at this point
|
||||
// const idHrefObs = postHrefObs.map((href: string) => this.getFindByIDHref(href, dso.id));
|
||||
//
|
||||
// postHrefObs
|
||||
// .filter((href: string) => hasValue(href))
|
||||
// .take(1)
|
||||
// .subscribe((href: string) => {
|
||||
// const request = new RestRequest(this.requestService.generateRequestId(), href, RestRequestMethod.Post, dso);
|
||||
// this.requestService.configure(request);
|
||||
// });
|
||||
//
|
||||
// return this.rdbService.buildSingle<TNormalized, TDomain>(idHrefObs, this.normalizedResourceType);
|
||||
// }
|
||||
// TODO remove when search will be completed
|
||||
public searchBySubmitter(options: FindAllOptions = {}): Observable<RemoteData<PaginatedList<TDomain>>> {
|
||||
return this.searchBy('submitter', options);
|
||||
}
|
||||
|
||||
// TODO remove when search will be completed
|
||||
searchByUser(options: FindAllOptions = {}): Observable<RemoteData<PaginatedList<TDomain>>> {
|
||||
return this.searchBy('user', options);
|
||||
}
|
||||
|
||||
// TODO remove when search will be completed
|
||||
protected searchBy(searchBy: string, options: FindAllOptions = {}): Observable<RemoteData<PaginatedList<TDomain>>> {
|
||||
let url = null;
|
||||
switch (searchBy) {
|
||||
case 'user': {
|
||||
url = 'search/findByUser';
|
||||
break;
|
||||
}
|
||||
case 'submitter': {
|
||||
url = 'search/findBySubmitter';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const hrefObs = this.halService.getEndpoint(this.linkPath).filter((href: string) => isNotEmpty(href))
|
||||
.flatMap((endpoint: string) => this.getSearchByHref(endpoint, url, options));
|
||||
hrefObs
|
||||
.filter((href: string) => hasValue(href))
|
||||
.take(1)
|
||||
.subscribe((href: string) => {
|
||||
const request = new FindAllRequest(this.requestService.generateRequestId(), href, options);
|
||||
this.requestService.configure(request, this.forceBypassCache);
|
||||
});
|
||||
|
||||
return this.rdbService.buildList<TNormalized, TDomain>(hrefObs) as Observable<RemoteData<PaginatedList<TDomain>>>;
|
||||
}
|
||||
|
||||
// TODO implement, after the structure of the REST server's POST response is finalized
|
||||
// create(dso: DSpaceObject): Observable<RemoteData<TDomain>> {
|
||||
// const postHrefObs = this.getEndpoint();
|
||||
//
|
||||
// // TODO ID is unknown at this point
|
||||
// const idHrefObs = postHrefObs.map((href: string) => this.getFindByIDHref(href, dso.id));
|
||||
//
|
||||
// postHrefObs
|
||||
// .filter((href: string) => hasValue(href))
|
||||
// .take(1)
|
||||
// .subscribe((href: string) => {
|
||||
// const request = new RestRequest(this.requestService.generateRequestId(), href, RestRequestMethod.Post, dso);
|
||||
// this.requestService.configure(request);
|
||||
// });
|
||||
//
|
||||
// return this.rdbService.buildSingle<TNormalized, TDomain>(idHrefObs, this.normalizedResourceType);
|
||||
// }
|
||||
}
|
||||
|
@@ -19,6 +19,7 @@ import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
@Injectable()
|
||||
export class ItemDataService extends DataService<NormalizedItem, Item> {
|
||||
protected linkPath = 'items';
|
||||
protected forceBypassCache = false;
|
||||
|
||||
constructor(
|
||||
protected responseCache: ResponseCacheService,
|
||||
|
@@ -11,6 +11,8 @@ import { ConfigResponseParsingService } from './config-response-parsing.service'
|
||||
import { AuthResponseParsingService } from '../auth/auth-response-parsing.service';
|
||||
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
|
||||
import { HttpHeaders } from '@angular/common/http';
|
||||
import { SubmissionResponseParsingService } from '../submission/submission-response-parsing.service';
|
||||
import { EpersonResponseParsingService } from '../eperson/eperson-response-parsing.service';
|
||||
import { IntegrationResponseParsingService } from '../integration/integration-response-parsing.service';
|
||||
|
||||
/* tslint:disable:max-classes-per-file */
|
||||
@@ -184,8 +186,8 @@ export class BrowseEntriesRequest extends GetRequest {
|
||||
}
|
||||
|
||||
export class ConfigRequest extends GetRequest {
|
||||
constructor(uuid: string, href: string) {
|
||||
super(uuid, href);
|
||||
constructor(uuid: string, href: string, public options?: HttpOptions) {
|
||||
super(uuid, href, null, options);
|
||||
}
|
||||
|
||||
getResponseParser(): GenericConstructor<ResponseParsingService> {
|
||||
@@ -222,6 +224,85 @@ export class IntegrationRequest extends GetRequest {
|
||||
return IntegrationResponseParsingService;
|
||||
}
|
||||
}
|
||||
|
||||
export class SubmissionFindAllRequest extends GetRequest {
|
||||
constructor(uuid: string, href: string, public body?: FindAllOptions) {
|
||||
super(uuid, href);
|
||||
}
|
||||
|
||||
getResponseParser(): GenericConstructor<ResponseParsingService> {
|
||||
return SubmissionResponseParsingService;
|
||||
}
|
||||
}
|
||||
|
||||
export class SubmissionFindByIDRequest extends GetRequest {
|
||||
constructor(uuid: string,
|
||||
href: string,
|
||||
public resourceID: string) {
|
||||
super(uuid, href);
|
||||
}
|
||||
|
||||
getResponseParser(): GenericConstructor<ResponseParsingService> {
|
||||
return SubmissionResponseParsingService;
|
||||
}
|
||||
}
|
||||
|
||||
export class SubmissionRequest extends GetRequest {
|
||||
constructor(uuid: string, href: string) {
|
||||
super(uuid, href);
|
||||
}
|
||||
|
||||
getResponseParser(): GenericConstructor<ResponseParsingService> {
|
||||
return SubmissionResponseParsingService;
|
||||
}
|
||||
}
|
||||
|
||||
export class SubmissionDeleteRequest extends DeleteRequest {
|
||||
constructor(public uuid: string,
|
||||
public href: string) {
|
||||
super(uuid, href);
|
||||
}
|
||||
|
||||
getResponseParser(): GenericConstructor<ResponseParsingService> {
|
||||
return SubmissionResponseParsingService;
|
||||
}
|
||||
}
|
||||
|
||||
export class SubmissionPatchRequest extends PatchRequest {
|
||||
constructor(public uuid: string,
|
||||
public href: string,
|
||||
public body?: any) {
|
||||
super(uuid, href, body);
|
||||
}
|
||||
|
||||
getResponseParser(): GenericConstructor<ResponseParsingService> {
|
||||
return SubmissionResponseParsingService;
|
||||
}
|
||||
}
|
||||
|
||||
export class SubmissionPostRequest extends PostRequest {
|
||||
constructor(public uuid: string,
|
||||
public href: string,
|
||||
public body?: any,
|
||||
public options?: HttpOptions) {
|
||||
super(uuid, href, body, options);
|
||||
}
|
||||
|
||||
getResponseParser(): GenericConstructor<ResponseParsingService> {
|
||||
return SubmissionResponseParsingService;
|
||||
}
|
||||
}
|
||||
|
||||
export class EpersonRequest extends GetRequest {
|
||||
constructor(uuid: string, href: string) {
|
||||
super(uuid, href);
|
||||
}
|
||||
|
||||
getResponseParser(): GenericConstructor<ResponseParsingService> {
|
||||
return EpersonResponseParsingService;
|
||||
}
|
||||
}
|
||||
|
||||
export class RequestError extends Error {
|
||||
statusText: string;
|
||||
}
|
||||
|
@@ -1,6 +1,8 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { createSelector, MemoizedSelector, Store } from '@ngrx/store';
|
||||
import { MemoizedSelector, Store } from '@ngrx/store';
|
||||
|
||||
import { remove } from 'lodash';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { hasValue } from '../../shared/empty.util';
|
||||
@@ -67,12 +69,28 @@ export class RequestService {
|
||||
.flatMap((uuid: string) => this.getByUUID(uuid));
|
||||
}
|
||||
|
||||
// TODO to review "overrideRequest" param when https://github.com/DSpace/dspace-angular/issues/217 will be fixed
|
||||
private clearRequestsOnTheirWayToTheStore(href) {
|
||||
this.getByHref(href)
|
||||
.take(1)
|
||||
.subscribe((re: RequestEntry) => {
|
||||
if (!hasValue(re)) {
|
||||
this.responseCache.remove(href);
|
||||
} else if (!re.responsePending) {
|
||||
this.responseCache.remove(href);
|
||||
remove(this.requestsOnTheirWayToTheStore, (item) => item === href);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// TODO to review "forceBypassCache" param when https://github.com/DSpace/dspace-angular/issues/217 will be fixed
|
||||
configure<T extends CacheableObject>(request: RestRequest, forceBypassCache: boolean = false): void {
|
||||
const isGetRequest = request.method === RestRequestMethod.Get;
|
||||
if (!isGetRequest || !this.isCachedOrPending(request) || forceBypassCache) {
|
||||
if (forceBypassCache) {
|
||||
this.clearRequestsOnTheirWayToTheStore(request.href);
|
||||
}
|
||||
if (!isGetRequest || !this.isCachedOrPending(request) || (forceBypassCache && !this.isPending(request))) {
|
||||
this.dispatchRequest(request);
|
||||
if (isGetRequest && !forceBypassCache) {
|
||||
if (isGetRequest) {
|
||||
this.trackRequestsOnTheirWayToTheStore(request);
|
||||
}
|
||||
}
|
||||
@@ -121,7 +139,7 @@ export class RequestService {
|
||||
*/
|
||||
private trackRequestsOnTheirWayToTheStore(request: GetRequest) {
|
||||
this.requestsOnTheirWayToTheStore = [...this.requestsOnTheirWayToTheStore, request.href];
|
||||
this.store.select(this.entryFromUUIDSelector(request.href))
|
||||
this.getByHref(request.href)
|
||||
.filter((re: RequestEntry) => hasValue(re))
|
||||
.take(1)
|
||||
.subscribe((re: RequestEntry) => {
|
||||
|
12
src/app/core/eperson/eperson-data.ts
Normal file
12
src/app/core/eperson/eperson-data.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { PageInfo } from '../shared/page-info.model';
|
||||
import { NormalizedObject } from '../cache/models/normalized-object.model';
|
||||
|
||||
/**
|
||||
* A class to represent the data retrieved by a Eperson service
|
||||
*/
|
||||
export class EpersonData {
|
||||
constructor(
|
||||
public pageInfo: PageInfo,
|
||||
public payload: NormalizedObject[]
|
||||
) { }
|
||||
}
|
21
src/app/core/eperson/eperson-object-factory.ts
Normal file
21
src/app/core/eperson/eperson-object-factory.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { EpersonType } from './eperson-type';
|
||||
import { GenericConstructor } from '../shared/generic-constructor';
|
||||
import { NormalizedEpersonModel } from './models/NormalizedEperson.model';
|
||||
import { NormalizedGroupModel } from './models/NormalizedGroup.model';
|
||||
import { NormalizedObject } from '../cache/models/normalized-object.model';
|
||||
|
||||
export class EpersonObjectFactory {
|
||||
public static getConstructor(type): GenericConstructor<NormalizedObject> {
|
||||
switch (type) {
|
||||
case EpersonType.EpersonsModel: {
|
||||
return NormalizedEpersonModel
|
||||
}
|
||||
case EpersonType.GroupsModel: {
|
||||
return NormalizedGroupModel
|
||||
}
|
||||
default: {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
47
src/app/core/eperson/eperson-response-parsing.service.ts
Normal file
47
src/app/core/eperson/eperson-response-parsing.service.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { Inject, Injectable } from '@angular/core';
|
||||
import { RestRequest } from '../data/request.models';
|
||||
import { ResponseParsingService } from '../data/parsing.service';
|
||||
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
|
||||
import {
|
||||
EpersonSuccessResponse, ErrorResponse,
|
||||
RestResponse
|
||||
} from '../cache/response-cache.models';
|
||||
import { isNotEmpty } from '../../shared/empty.util';
|
||||
import { EpersonObjectFactory } from './eperson-object-factory';
|
||||
import { EpersonType } from './eperson-type';
|
||||
|
||||
import { BaseResponseParsingService } from '../data/base-response-parsing.service';
|
||||
import { GLOBAL_CONFIG } from '../../../config';
|
||||
import { GlobalConfig } from '../../../config/global-config.interface';
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||
import { NormalizedObject } from '../cache/models/normalized-object.model';
|
||||
|
||||
@Injectable()
|
||||
export class EpersonResponseParsingService extends BaseResponseParsingService implements ResponseParsingService {
|
||||
|
||||
protected objectFactory = EpersonObjectFactory;
|
||||
protected toCache = false;
|
||||
|
||||
constructor(
|
||||
@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
|
||||
protected objectCache: ObjectCacheService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
|
||||
if (isNotEmpty(data.payload) && isNotEmpty(data.payload._links)) {
|
||||
const epersonDefinition = this.process<NormalizedObject,EpersonType>(data.payload, request.href);
|
||||
return new EpersonSuccessResponse(epersonDefinition[Object.keys(epersonDefinition)[0]], data.statusCode, this.processPageInfo(data.payload));
|
||||
} else {
|
||||
return new ErrorResponse(
|
||||
Object.assign(
|
||||
new Error('Unexpected response from EPerson endpoint'),
|
||||
{statusText: data.statusCode}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
5
src/app/core/eperson/eperson-type.ts
Normal file
5
src/app/core/eperson/eperson-type.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
|
||||
export enum EpersonType {
|
||||
EpersonsModel = 'eperson',
|
||||
GroupsModel = 'group',
|
||||
}
|
53
src/app/core/eperson/eperson.service.ts
Normal file
53
src/app/core/eperson/eperson.service.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { RequestService } from '../data/request.service';
|
||||
import { ResponseCacheService } from '../cache/response-cache.service';
|
||||
import { EpersonSuccessResponse, ErrorResponse, RestResponse } from '../cache/response-cache.models';
|
||||
import { EpersonRequest, GetRequest } from '../data/request.models';
|
||||
import { ResponseCacheEntry } from '../cache/response-cache.reducer';
|
||||
import { isNotEmpty } from '../../shared/empty.util';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
import { EpersonData } from './eperson-data';
|
||||
|
||||
export abstract class EpersonService {
|
||||
protected request: EpersonRequest;
|
||||
protected abstract responseCache: ResponseCacheService;
|
||||
protected abstract requestService: RequestService;
|
||||
protected abstract linkPath: string;
|
||||
protected abstract browseEndpoint: string;
|
||||
protected abstract halService: HALEndpointService;
|
||||
|
||||
protected getEperson(request: GetRequest): Observable<EpersonData> {
|
||||
const [successResponse, errorResponse] = this.responseCache.get(request.href)
|
||||
.map((entry: ResponseCacheEntry) => entry.response)
|
||||
.partition((response: RestResponse) => response.isSuccessful);
|
||||
return Observable.merge(
|
||||
errorResponse.flatMap((response: ErrorResponse) =>
|
||||
Observable.throw(new Error(`Couldn't retrieve the EPerson`))),
|
||||
successResponse
|
||||
.filter((response: EpersonSuccessResponse) => isNotEmpty(response))
|
||||
.map((response: EpersonSuccessResponse) => new EpersonData(response.pageInfo, response.epersonDefinition))
|
||||
.distinctUntilChanged());
|
||||
}
|
||||
|
||||
public getDataByHref(href: string): Observable<EpersonData> {
|
||||
const request = new EpersonRequest(this.requestService.generateRequestId(), href);
|
||||
this.requestService.configure(request);
|
||||
|
||||
return this.getEperson(request);
|
||||
}
|
||||
|
||||
public getDataByUuid(uuid: string): Observable<EpersonData> {
|
||||
return this.halService.getEndpoint(this.linkPath)
|
||||
.map((endpoint: string) => this.getDataByIDHref(endpoint, uuid))
|
||||
.filter((href: string) => isNotEmpty(href))
|
||||
.distinctUntilChanged()
|
||||
.map((endpointURL: string) => new EpersonRequest(this.requestService.generateRequestId(), endpointURL))
|
||||
.do((request: GetRequest) => this.requestService.configure(request))
|
||||
.flatMap((request: GetRequest) => this.getEperson(request))
|
||||
.distinctUntilChanged();
|
||||
}
|
||||
|
||||
protected getDataByIDHref(endpoint, resourceID): string {
|
||||
return `${endpoint}/${resourceID}`;
|
||||
}
|
||||
}
|
55
src/app/core/eperson/group-eperson.service.ts
Normal file
55
src/app/core/eperson/group-eperson.service.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { EpersonService } from './eperson.service';
|
||||
import { ResponseCacheService } from '../cache/response-cache.service';
|
||||
import { RequestService } from '../data/request.service';
|
||||
import { isNotEmpty } from '../../shared/empty.util';
|
||||
import { EpersonRequest, GetRequest } from '../data/request.models';
|
||||
import { EpersonData } from './eperson-data';
|
||||
import { EpersonSuccessResponse, ErrorResponse, RestResponse } from '../cache/response-cache.models';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { ResponseCacheEntry } from '../cache/response-cache.reducer';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
import { BrowseService } from '../browse/browse.service';
|
||||
|
||||
@Injectable()
|
||||
export class GroupEpersonService extends EpersonService {
|
||||
protected linkPath = 'groups';
|
||||
protected browseEndpoint = '';
|
||||
|
||||
constructor(
|
||||
protected responseCache: ResponseCacheService,
|
||||
protected requestService: RequestService,
|
||||
protected bs: BrowseService,
|
||||
protected halService: HALEndpointService) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected getSearchHref(endpoint, groupName): string {
|
||||
return `${endpoint}/search/isMemberOf?groupName=${groupName}`;
|
||||
}
|
||||
|
||||
isMemberOf(groupName: string) {
|
||||
return this.halService.getEndpoint(this.linkPath)
|
||||
.map((endpoint: string) => this.getSearchHref(endpoint, groupName))
|
||||
.filter((href: string) => isNotEmpty(href))
|
||||
.distinctUntilChanged()
|
||||
.map((endpointURL: string) => new EpersonRequest(this.requestService.generateRequestId(), endpointURL))
|
||||
.do((request: GetRequest) => this.requestService.configure(request))
|
||||
.flatMap((request: GetRequest) => this.getSearch(request))
|
||||
.distinctUntilChanged();
|
||||
}
|
||||
|
||||
protected getSearch(request: GetRequest): Observable<EpersonData> {
|
||||
const [successResponse, errorResponse] = this.responseCache.get(request.href)
|
||||
.map((entry: ResponseCacheEntry) => entry.response)
|
||||
.partition((response: RestResponse) => response.isSuccessful);
|
||||
return Observable.merge(
|
||||
errorResponse.flatMap((response: ErrorResponse) =>
|
||||
Observable.of(new EpersonData(undefined, undefined))),
|
||||
successResponse
|
||||
.filter((response: EpersonSuccessResponse) => isNotEmpty(response))
|
||||
.map((response: EpersonSuccessResponse) => new EpersonData(response.pageInfo, response.epersonDefinition))
|
||||
.distinctUntilChanged());
|
||||
}
|
||||
}
|
@@ -1,10 +1,12 @@
|
||||
import { autoserialize, inheritSerialization } from 'cerialize';
|
||||
import { autoserialize, autoserializeAs, inheritSerialization } from 'cerialize';
|
||||
import { CacheableObject } from '../../cache/object-cache.reducer';
|
||||
import { ListableObject } from '../../../shared/object-collection/shared/listable-object.model';
|
||||
import { NormalizedDSpaceObject } from '../../cache/models/normalized-dspace-object.model';
|
||||
import { Eperson } from './eperson.model';
|
||||
import { mapsTo, relationship } from '../../cache/builders/build-decorators';
|
||||
import { ResourceType } from '../../shared/resource-type';
|
||||
import { Group } from './group.model';
|
||||
import { NormalizedGroupModel } from './NormalizedGroup.model';
|
||||
|
||||
@mapsTo(Eperson)
|
||||
@inheritSerialization(NormalizedDSpaceObject)
|
||||
@@ -13,9 +15,8 @@ export class NormalizedEpersonModel extends NormalizedDSpaceObject implements Ca
|
||||
@autoserialize
|
||||
public handle: string;
|
||||
|
||||
@autoserialize
|
||||
@relationship(ResourceType.Group, true)
|
||||
groups: string[];
|
||||
@autoserializeAs(NormalizedGroupModel)
|
||||
groups: Group[];
|
||||
|
||||
@autoserialize
|
||||
public netid: string;
|
||||
|
@@ -2,6 +2,8 @@ import { DSpaceObject } from '../../shared/dspace-object.model';
|
||||
|
||||
export class Group extends DSpaceObject {
|
||||
|
||||
public groups: Group[];
|
||||
|
||||
public handle: string;
|
||||
|
||||
public permanent: boolean;
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { IntegrationModel } from './integration.model';
|
||||
import { autoserialize } from 'cerialize';
|
||||
import { isNotEmpty } from '../../../shared/empty.util';
|
||||
|
||||
export class AuthorityValueModel extends IntegrationModel {
|
||||
|
||||
@@ -17,4 +18,8 @@ export class AuthorityValueModel extends IntegrationModel {
|
||||
|
||||
@autoserialize
|
||||
language: string;
|
||||
|
||||
hasValue(): boolean {
|
||||
return isNotEmpty(this.value);
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Combines a variable number of strings representing parts
|
||||
* of a relative REST URL in to a single, absolute REST URL
|
||||
*
|
||||
*/
|
||||
import { isNotUndefined } from '../../../shared/empty.util';
|
||||
|
||||
export interface JsonPatchOperationPathObject {
|
||||
rootElement: string;
|
||||
subRootElement: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export class JsonPatchOperationPathCombiner {
|
||||
private _rootElement: string;
|
||||
private _subRootElement: string;
|
||||
|
||||
constructor(rootElement, ...subRootElements: string[]) {
|
||||
this._rootElement = rootElement;
|
||||
this._subRootElement = subRootElements.join('/');
|
||||
}
|
||||
|
||||
get rootElement(): string {
|
||||
return this._rootElement;
|
||||
}
|
||||
|
||||
get subRootElement(): string {
|
||||
return this._subRootElement;
|
||||
}
|
||||
|
||||
public getPath(fragment?: string|string[]): JsonPatchOperationPathObject {
|
||||
if (isNotUndefined(fragment) && Array.isArray(fragment)) {
|
||||
fragment = fragment.join('/');
|
||||
}
|
||||
|
||||
let path;
|
||||
if (isNotUndefined(fragment)) {
|
||||
path = '/' + this._rootElement + '/' + this._subRootElement + '/' + fragment;
|
||||
} else {
|
||||
path = '/' + this._rootElement + '/' + this._subRootElement;
|
||||
}
|
||||
|
||||
return {rootElement: this._rootElement, subRootElement: this._subRootElement, path: path};
|
||||
}
|
||||
}
|
111
src/app/core/json-patch/builder/json-patch-operations-builder.ts
Normal file
111
src/app/core/json-patch/builder/json-patch-operations-builder.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import { Store } from '@ngrx/store';
|
||||
import { CoreState } from '../../core.reducers';
|
||||
import {
|
||||
NewPatchAddOperationAction,
|
||||
NewPatchRemoveOperationAction,
|
||||
NewPatchReplaceOperationAction
|
||||
} from '../json-patch-operations.actions';
|
||||
import { JsonPatchOperationPathObject } from './json-patch-operation-path-combiner';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { isEmpty, isNotEmpty } from '../../../shared/empty.util';
|
||||
import { dateToGMTString } from '../../../shared/date.util';
|
||||
import { AuthorityValueModel } from '../../integration/models/authority-value.model';
|
||||
import { FormFieldMetadataValueObject } from '../../../shared/form/builder/models/form-field-metadata-value.model';
|
||||
import { FormFieldLanguageValueObject } from '../../../shared/form/builder/models/form-field-language-value.model';
|
||||
|
||||
@Injectable()
|
||||
export class JsonPatchOperationsBuilder {
|
||||
|
||||
constructor(private store: Store<CoreState>) {
|
||||
}
|
||||
|
||||
add(path: JsonPatchOperationPathObject, value, first = false, plain = false) {
|
||||
this.store.dispatch(
|
||||
new NewPatchAddOperationAction(
|
||||
path.rootElement,
|
||||
path.subRootElement,
|
||||
path.path, this.prepareValue(value, plain, first)));
|
||||
}
|
||||
|
||||
replace(path: JsonPatchOperationPathObject, value, plain = false) {
|
||||
this.store.dispatch(
|
||||
new NewPatchReplaceOperationAction(
|
||||
path.rootElement,
|
||||
path.subRootElement,
|
||||
path.path,
|
||||
this.prepareValue(value, plain, false)));
|
||||
}
|
||||
|
||||
remove(path: JsonPatchOperationPathObject) {
|
||||
this.store.dispatch(
|
||||
new NewPatchRemoveOperationAction(
|
||||
path.rootElement,
|
||||
path.subRootElement,
|
||||
path.path));
|
||||
}
|
||||
|
||||
protected prepareValue(value: any, plain: boolean, first: boolean) {
|
||||
let operationValue: any = null;
|
||||
if (isNotEmpty(value)) {
|
||||
if (plain) {
|
||||
operationValue = value;
|
||||
} else {
|
||||
if (Array.isArray(value)) {
|
||||
operationValue = [];
|
||||
value.forEach((entry) => {
|
||||
if ((typeof entry === 'object')) {
|
||||
operationValue.push(this.prepareObjectValue(entry));
|
||||
} else {
|
||||
operationValue.push(new FormFieldMetadataValueObject(entry));
|
||||
// operationValue.push({value: entry});
|
||||
// operationValue.push(entry);
|
||||
}
|
||||
});
|
||||
} else if (typeof value === 'object') {
|
||||
operationValue = this.prepareObjectValue(value);
|
||||
} else {
|
||||
operationValue = new FormFieldMetadataValueObject(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return (first && !Array.isArray(operationValue)) ? [operationValue] : operationValue;
|
||||
}
|
||||
|
||||
protected prepareObjectValue(value: any) {
|
||||
let operationValue = Object.create({});
|
||||
if (isEmpty(value) || value instanceof FormFieldMetadataValueObject) {
|
||||
operationValue = value;
|
||||
} else if (value instanceof Date) {
|
||||
operationValue = new FormFieldMetadataValueObject(dateToGMTString(value));
|
||||
} else if (value instanceof AuthorityValueModel) {
|
||||
operationValue = this.prepareAuthorityValue(value);
|
||||
} else if (value instanceof FormFieldLanguageValueObject) {
|
||||
operationValue = new FormFieldMetadataValueObject(value.value, value.language);
|
||||
} else if (value.hasOwnProperty('value')) {
|
||||
operationValue = new FormFieldMetadataValueObject(value.value);
|
||||
// operationValue = value;
|
||||
} else {
|
||||
Object.keys(value)
|
||||
.forEach((key) => {
|
||||
if (typeof value[key] === 'object') {
|
||||
operationValue[key] = this.prepareObjectValue(value[key]);
|
||||
} else {
|
||||
operationValue[key] = value[key];
|
||||
}
|
||||
});
|
||||
// operationValue = {value: value};
|
||||
}
|
||||
return operationValue;
|
||||
}
|
||||
|
||||
protected prepareAuthorityValue(value: any) {
|
||||
let operationValue: any = null;
|
||||
if (isNotEmpty(value.id)) {
|
||||
operationValue = new FormFieldMetadataValueObject(value.value, value.language, value.id);
|
||||
} else {
|
||||
operationValue = new FormFieldMetadataValueObject(value.value, value.language);
|
||||
}
|
||||
return operationValue;
|
||||
}
|
||||
|
||||
}
|
279
src/app/core/json-patch/json-patch-operations.actions.ts
Normal file
279
src/app/core/json-patch/json-patch-operations.actions.ts
Normal file
@@ -0,0 +1,279 @@
|
||||
import { Action } from '@ngrx/store';
|
||||
|
||||
import { type } from '../../shared/ngrx/type';
|
||||
|
||||
/**
|
||||
* For each action type in an action group, make a simple
|
||||
* enum object for all of this group's action types.
|
||||
*
|
||||
* The 'type' utility function coerces strings into string
|
||||
* literal types and runs a simple check to guarantee all
|
||||
* action types in the application are unique.
|
||||
*/
|
||||
export const JsonPatchOperationsActionTypes = {
|
||||
NEW_JSON_PATCH_ADD_OPERATION: type('dspace/core/patch/NEW_JSON_PATCH_ADD_OPERATION'),
|
||||
NEW_JSON_PATCH_COPY_OPERATION: type('dspace/core/patch/NEW_JSON_PATCH_COPY_OPERATION'),
|
||||
NEW_JSON_PATCH_MOVE_OPERATION: type('dspace/core/patch/NEW_JSON_PATCH_MOVE_OPERATION'),
|
||||
NEW_JSON_PATCH_REMOVE_OPERATION: type('dspace/core/patch/NEW_JSON_PATCH_REMOVE_OPERATION'),
|
||||
NEW_JSON_PATCH_REPLACE_OPERATION: type('dspace/core/patch/NEW_JSON_PATCH_REPLACE_OPERATION'),
|
||||
COMMIT_JSON_PATCH_OPERATIONS: type('dspace/core/patch/COMMIT_JSON_PATCH_OPERATIONS'),
|
||||
ROLLBACK_JSON_PATCH_OPERATIONS: type('dspace/core/patch/ROLLBACK_JSON_PATCH_OPERATIONS'),
|
||||
FLUSH_JSON_PATCH_OPERATIONS: type('dspace/core/patch/FLUSH_JSON_PATCH_OPERATIONS'),
|
||||
START_TRANSACTION_JSON_PATCH_OPERATIONS: type('dspace/core/patch/START_TRANSACTION_JSON_PATCH_OPERATIONS'),
|
||||
};
|
||||
|
||||
/* tslint:disable:max-classes-per-file */
|
||||
|
||||
/**
|
||||
* An ngrx action to commit the current transaction
|
||||
*/
|
||||
export class CommitPatchOperationsAction implements Action {
|
||||
type = JsonPatchOperationsActionTypes.COMMIT_JSON_PATCH_OPERATIONS;
|
||||
payload: {
|
||||
resourceType: string;
|
||||
resourceId: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new CommitPatchOperationsAction
|
||||
*
|
||||
* @param resourceType
|
||||
* the resource's type
|
||||
* @param resourceId
|
||||
* the resource's ID
|
||||
*/
|
||||
constructor(resourceType: string, resourceId: string) {
|
||||
this.payload = { resourceType, resourceId };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An ngrx action to rollback the current transaction
|
||||
*/
|
||||
export class RollbacktPatchOperationsAction implements Action {
|
||||
type = JsonPatchOperationsActionTypes.ROLLBACK_JSON_PATCH_OPERATIONS;
|
||||
payload: {
|
||||
resourceType: string;
|
||||
resourceId: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new CommitPatchOperationsAction
|
||||
*
|
||||
* @param resourceType
|
||||
* the resource's type
|
||||
* @param resourceId
|
||||
* the resource's ID
|
||||
*/
|
||||
constructor(resourceType: string, resourceId: string) {
|
||||
this.payload = { resourceType, resourceId };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An ngrx action to initiate a transaction block
|
||||
*/
|
||||
export class StartTransactionPatchOperationsAction implements Action {
|
||||
type = JsonPatchOperationsActionTypes.START_TRANSACTION_JSON_PATCH_OPERATIONS;
|
||||
payload: {
|
||||
resourceType: string;
|
||||
resourceId: string;
|
||||
startTime: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new CommitPatchOperationsAction
|
||||
*
|
||||
* @param resourceType
|
||||
* the resource's type
|
||||
* @param resourceId
|
||||
* the resource's ID
|
||||
* @param startTime
|
||||
* the start timestamp
|
||||
*/
|
||||
constructor(resourceType: string, resourceId: string, startTime: number) {
|
||||
this.payload = { resourceType, resourceId, startTime };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An ngrx action to flush list of the JSON Patch operations
|
||||
*/
|
||||
export class FlushPatchOperationsAction implements Action {
|
||||
type = JsonPatchOperationsActionTypes.FLUSH_JSON_PATCH_OPERATIONS;
|
||||
payload: {
|
||||
resourceType: string;
|
||||
resourceId: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new FlushPatchOperationsAction
|
||||
*
|
||||
* @param resourceType
|
||||
* the resource's type
|
||||
* @param resourceId
|
||||
* the resource's ID
|
||||
*/
|
||||
constructor(resourceType: string, resourceId: string) {
|
||||
this.payload = { resourceType, resourceId };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An ngrx action to Add new HTTP/PATCH ADD operations to state
|
||||
*/
|
||||
export class NewPatchAddOperationAction implements Action {
|
||||
type = JsonPatchOperationsActionTypes.NEW_JSON_PATCH_ADD_OPERATION;
|
||||
payload: {
|
||||
resourceType: string;
|
||||
resourceId: string;
|
||||
path: string;
|
||||
value: any
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new NewPatchAddOperationAction
|
||||
*
|
||||
* @param resourceType
|
||||
* the resource's type where to add operation
|
||||
* @param resourceId
|
||||
* the resource's ID
|
||||
* @param path
|
||||
* the path of the operation
|
||||
* @param value
|
||||
* the operation's payload
|
||||
*/
|
||||
constructor(resourceType: string, resourceId: string, path: string, value: any) {
|
||||
this.payload = { resourceType, resourceId, path, value };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An ngrx action to add new JSON Patch COPY operation to state
|
||||
*/
|
||||
export class NewPatchCopyOperationAction implements Action {
|
||||
type = JsonPatchOperationsActionTypes.NEW_JSON_PATCH_COPY_OPERATION;
|
||||
payload: {
|
||||
resourceType: string;
|
||||
resourceId: string;
|
||||
from: string;
|
||||
path: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new NewPatchCopyOperationAction
|
||||
*
|
||||
* @param resourceType
|
||||
* the resource's type
|
||||
* @param resourceId
|
||||
* the resource's ID
|
||||
* @param from
|
||||
* the path to copy the value from
|
||||
* @param path
|
||||
* the path where to copy the value
|
||||
*/
|
||||
constructor(resourceType: string, resourceId: string, from: string, path: string) {
|
||||
this.payload = { resourceType, resourceId, from, path };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An ngrx action to Add new JSON Patch MOVE operation to state
|
||||
*/
|
||||
export class NewPatchMoveOperationAction implements Action {
|
||||
type = JsonPatchOperationsActionTypes.NEW_JSON_PATCH_MOVE_OPERATION;
|
||||
payload: {
|
||||
resourceType: string;
|
||||
resourceId: string;
|
||||
from: string;
|
||||
path: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new NewPatchMoveOperationAction
|
||||
*
|
||||
* @param resourceType
|
||||
* the resource's type
|
||||
* @param resourceId
|
||||
* the resource's ID
|
||||
* @param from
|
||||
* the path to move the value from
|
||||
* @param path
|
||||
* the path where to move the value
|
||||
*/
|
||||
constructor(resourceType: string, resourceId: string, from: string, path: string) {
|
||||
this.payload = { resourceType, resourceId, from, path };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An ngrx action to Add new JSON Patch REMOVE operation to state
|
||||
*/
|
||||
export class NewPatchRemoveOperationAction implements Action {
|
||||
type = JsonPatchOperationsActionTypes.NEW_JSON_PATCH_REMOVE_OPERATION;
|
||||
payload: {
|
||||
resourceType: string;
|
||||
resourceId: string;
|
||||
path: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new NewPatchRemoveOperationAction
|
||||
*
|
||||
* @param resourceType
|
||||
* the resource's type
|
||||
* @param resourceId
|
||||
* the resource's ID
|
||||
* @param path
|
||||
* the path of the operation
|
||||
*/
|
||||
constructor(resourceType: string, resourceId: string, path: string) {
|
||||
this.payload = { resourceType, resourceId, path };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An ngrx action to add new JSON Patch REPLACE operation to state
|
||||
*/
|
||||
export class NewPatchReplaceOperationAction implements Action {
|
||||
type = JsonPatchOperationsActionTypes.NEW_JSON_PATCH_REPLACE_OPERATION;
|
||||
payload: {
|
||||
resourceType: string;
|
||||
resourceId: string;
|
||||
path: string;
|
||||
value: any
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new NewPatchReplaceOperationAction
|
||||
*
|
||||
* @param resourceType
|
||||
* the resource's type
|
||||
* @param resourceId
|
||||
* the resource's ID
|
||||
* @param path
|
||||
* the path of the operation
|
||||
* @param value
|
||||
* the operation's payload
|
||||
*/
|
||||
constructor(resourceType: string, resourceId: string, path: string, value: any) {
|
||||
this.payload = { resourceType, resourceId, path, value };
|
||||
}
|
||||
}
|
||||
|
||||
/* tslint:enable:max-classes-per-file */
|
||||
|
||||
/**
|
||||
* Export a type alias of all actions in this action group
|
||||
* so that reducers can easily compose action types
|
||||
*/
|
||||
export type PatchOperationsActions
|
||||
= CommitPatchOperationsAction
|
||||
| FlushPatchOperationsAction
|
||||
| NewPatchAddOperationAction
|
||||
| NewPatchCopyOperationAction
|
||||
| NewPatchMoveOperationAction
|
||||
| NewPatchRemoveOperationAction
|
||||
| NewPatchReplaceOperationAction
|
||||
| RollbacktPatchOperationsAction
|
||||
| StartTransactionPatchOperationsAction
|
20
src/app/core/json-patch/json-patch-operations.effects.ts
Normal file
20
src/app/core/json-patch/json-patch-operations.effects.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Effect, Actions } from '@ngrx/effects';
|
||||
|
||||
import {
|
||||
CommitPatchOperationsAction, FlushPatchOperationsAction,
|
||||
JsonPatchOperationsActionTypes
|
||||
} from './json-patch-operations.actions';
|
||||
|
||||
@Injectable()
|
||||
export class JsonPatchOperationsEffects {
|
||||
|
||||
@Effect() commit$ = this.actions$
|
||||
.ofType(JsonPatchOperationsActionTypes.COMMIT_JSON_PATCH_OPERATIONS)
|
||||
.map((action: CommitPatchOperationsAction) => {
|
||||
return new FlushPatchOperationsAction(action.payload.resourceType, action.payload.resourceId);
|
||||
});
|
||||
|
||||
constructor(private actions$: Actions) {}
|
||||
|
||||
}
|
292
src/app/core/json-patch/json-patch-operations.reducer.ts
Normal file
292
src/app/core/json-patch/json-patch-operations.reducer.ts
Normal file
@@ -0,0 +1,292 @@
|
||||
import { hasValue, isNotEmpty, isNotUndefined, isNull } from '../../shared/empty.util';
|
||||
|
||||
import {
|
||||
FlushPatchOperationsAction,
|
||||
PatchOperationsActions,
|
||||
JsonPatchOperationsActionTypes,
|
||||
NewPatchAddOperationAction,
|
||||
NewPatchCopyOperationAction,
|
||||
NewPatchMoveOperationAction,
|
||||
NewPatchRemoveOperationAction,
|
||||
NewPatchReplaceOperationAction,
|
||||
CommitPatchOperationsAction,
|
||||
StartTransactionPatchOperationsAction,
|
||||
RollbacktPatchOperationsAction
|
||||
} from './json-patch-operations.actions';
|
||||
import { JsonPatchOperationModel, JsonPatchOperationType } from './json-patch.model';
|
||||
|
||||
export interface JsonPatchOperationObject {
|
||||
operation: JsonPatchOperationModel;
|
||||
timeAdded: number;
|
||||
}
|
||||
|
||||
export interface JsonPatchOperationsEntry {
|
||||
body: JsonPatchOperationObject[];
|
||||
}
|
||||
|
||||
export interface JsonPatchOperationsResourceEntry {
|
||||
children: { [resourceId: string]: JsonPatchOperationsEntry };
|
||||
transactionStartTime: number;
|
||||
commitPending: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* The JSON patch operations State
|
||||
*
|
||||
* Consists of a map with a namespace as key,
|
||||
* and an array of JsonPatchOperationModel as values
|
||||
*/
|
||||
export interface JsonPatchOperationsState {
|
||||
[resourceType: string]: JsonPatchOperationsResourceEntry;
|
||||
}
|
||||
|
||||
const initialState: JsonPatchOperationsState = Object.create(null);
|
||||
|
||||
export function jsonPatchOperationsReducer(state = initialState, action: PatchOperationsActions): JsonPatchOperationsState {
|
||||
switch (action.type) {
|
||||
|
||||
case JsonPatchOperationsActionTypes.COMMIT_JSON_PATCH_OPERATIONS: {
|
||||
return commitOperations(state, action as CommitPatchOperationsAction);
|
||||
}
|
||||
|
||||
case JsonPatchOperationsActionTypes.FLUSH_JSON_PATCH_OPERATIONS: {
|
||||
return flushOperation(state, action as FlushPatchOperationsAction);
|
||||
}
|
||||
|
||||
case JsonPatchOperationsActionTypes.NEW_JSON_PATCH_ADD_OPERATION: {
|
||||
return newOperation(state, action as NewPatchAddOperationAction);
|
||||
}
|
||||
|
||||
case JsonPatchOperationsActionTypes.NEW_JSON_PATCH_COPY_OPERATION: {
|
||||
return newOperation(state, action as NewPatchCopyOperationAction);
|
||||
}
|
||||
|
||||
case JsonPatchOperationsActionTypes.NEW_JSON_PATCH_MOVE_OPERATION: {
|
||||
return newOperation(state, action as NewPatchMoveOperationAction);
|
||||
}
|
||||
|
||||
case JsonPatchOperationsActionTypes.NEW_JSON_PATCH_REMOVE_OPERATION: {
|
||||
return newOperation(state, action as NewPatchRemoveOperationAction);
|
||||
}
|
||||
|
||||
case JsonPatchOperationsActionTypes.NEW_JSON_PATCH_REPLACE_OPERATION: {
|
||||
return newOperation(state, action as NewPatchReplaceOperationAction);
|
||||
}
|
||||
|
||||
case JsonPatchOperationsActionTypes.ROLLBACK_JSON_PATCH_OPERATIONS: {
|
||||
return rollbackOperations(state, action as RollbacktPatchOperationsAction);
|
||||
}
|
||||
|
||||
case JsonPatchOperationsActionTypes.START_TRANSACTION_JSON_PATCH_OPERATIONS: {
|
||||
return startTransactionPatchOperations(state, action as StartTransactionPatchOperationsAction);
|
||||
}
|
||||
|
||||
default: {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the transaction start time.
|
||||
*
|
||||
* @param state
|
||||
* the current state
|
||||
* @param action
|
||||
* an StartTransactionPatchOperationsAction
|
||||
* @return JsonPatchOperationsState
|
||||
* the new state.
|
||||
*/
|
||||
function startTransactionPatchOperations(state: JsonPatchOperationsState, action: StartTransactionPatchOperationsAction): JsonPatchOperationsState {
|
||||
if (hasValue(state[ action.payload.resourceType ])
|
||||
&& isNull(state[ action.payload.resourceType ].transactionStartTime)) {
|
||||
return Object.assign({}, state, {
|
||||
[action.payload.resourceType]: Object.assign({}, state[ action.payload.resourceType ], {
|
||||
children: state[ action.payload.resourceType ].children,
|
||||
transactionStartTime: action.payload.startTime,
|
||||
commitPending: true
|
||||
})
|
||||
});
|
||||
} else {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set commit pending state.
|
||||
*
|
||||
* @param state
|
||||
* the current state
|
||||
* @param action
|
||||
* an CommitPatchOperationsAction
|
||||
* @return JsonPatchOperationsState
|
||||
* the new state, with the section new validity status.
|
||||
*/
|
||||
function commitOperations(state: JsonPatchOperationsState, action: CommitPatchOperationsAction): JsonPatchOperationsState {
|
||||
if (hasValue(state[ action.payload.resourceType ])
|
||||
&& state[ action.payload.resourceType ].commitPending) {
|
||||
return Object.assign({}, state, {
|
||||
[action.payload.resourceType]: Object.assign({}, state[ action.payload.resourceType ], {
|
||||
children: state[ action.payload.resourceType ].children,
|
||||
transactionStartTime: state[ action.payload.resourceType ].transactionStartTime,
|
||||
commitPending: false
|
||||
})
|
||||
});
|
||||
} else {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set commit pending state.
|
||||
*
|
||||
* @param state
|
||||
* the current state
|
||||
* @param action
|
||||
* an RollbacktPatchOperationsAction
|
||||
* @return JsonPatchOperationsState
|
||||
* the new state.
|
||||
*/
|
||||
function rollbackOperations(state: JsonPatchOperationsState, action: RollbacktPatchOperationsAction): JsonPatchOperationsState {
|
||||
if (hasValue(state[ action.payload.resourceType ])
|
||||
&& state[ action.payload.resourceType ].commitPending) {
|
||||
return Object.assign({}, state, {
|
||||
[action.payload.resourceType]: Object.assign({}, state[ action.payload.resourceType ], {
|
||||
children: state[ action.payload.resourceType ].children,
|
||||
transactionStartTime: null,
|
||||
commitPending: false
|
||||
})
|
||||
});
|
||||
} else {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add new JSON patch operation list.
|
||||
*
|
||||
* @param state
|
||||
* the current state
|
||||
* @param action
|
||||
* an NewPatchAddOperationAction
|
||||
* @return JsonPatchOperationsState
|
||||
* the new state, with the section new validity status.
|
||||
*/
|
||||
function newOperation(state: JsonPatchOperationsState, action): JsonPatchOperationsState {
|
||||
const newState = Object.assign({}, state);
|
||||
const newBody = addOperationToList(
|
||||
(hasValue(newState[ action.payload.resourceType ])
|
||||
&& hasValue(newState[ action.payload.resourceType ].children)
|
||||
&& hasValue(newState[ action.payload.resourceType ].children[ action.payload.resourceId ])
|
||||
&& isNotEmpty(newState[ action.payload.resourceType ].children[ action.payload.resourceId ].body))
|
||||
? newState[ action.payload.resourceType ].children[ action.payload.resourceId ].body : Array.of(),
|
||||
action.type,
|
||||
action.payload.path,
|
||||
hasValue(action.payload.value) ? action.payload.value : null);
|
||||
|
||||
if (hasValue(newState[ action.payload.resourceType ])
|
||||
&& hasValue(newState[ action.payload.resourceType ].children)) {
|
||||
return Object.assign({}, state, {
|
||||
[action.payload.resourceType]: Object.assign({}, state[ action.payload.resourceType ], {
|
||||
children: Object.assign({}, state[ action.payload.resourceType ].children, {
|
||||
[action.payload.resourceId]: {
|
||||
body: newBody,
|
||||
}
|
||||
}),
|
||||
transactionStartTime: state[ action.payload.resourceType ].transactionStartTime,
|
||||
commitPending: isNotUndefined(state[ action.payload.resourceType ].commitPending) ? state[ action.payload.resourceType ].commitPending : false
|
||||
})
|
||||
});
|
||||
} else {
|
||||
return Object.assign({}, state, {
|
||||
[action.payload.resourceType]: Object.assign({}, {
|
||||
children: {
|
||||
[action.payload.resourceId]: {
|
||||
body: newBody,
|
||||
}
|
||||
},
|
||||
transactionStartTime: null,
|
||||
commitPending: false
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the section validity.
|
||||
*
|
||||
* @param state
|
||||
* the current state
|
||||
* @param action
|
||||
* an LoadSubmissionFormAction
|
||||
* @return SubmissionObjectState
|
||||
* the new state, with the section new validity status.
|
||||
*/
|
||||
function flushOperation(state: JsonPatchOperationsState, action: FlushPatchOperationsAction): JsonPatchOperationsState {
|
||||
if (hasValue(state[ action.payload.resourceType ])) {
|
||||
let newChildren;
|
||||
if (isNotUndefined(action.payload.resourceId)) {
|
||||
// flush only specified child's operations
|
||||
if (hasValue(state[ action.payload.resourceType ].children)
|
||||
&& hasValue(state[ action.payload.resourceType ].children[ action.payload.resourceId ])) {
|
||||
newChildren = Object.assign({}, state[ action.payload.resourceType ].children, {
|
||||
[action.payload.resourceId]: {
|
||||
body: state[ action.payload.resourceType ].children[ action.payload.resourceId ].body
|
||||
.filter((entry) => entry.timeAdded > state[ action.payload.resourceType ].transactionStartTime)
|
||||
}
|
||||
});
|
||||
} else {
|
||||
newChildren = state[ action.payload.resourceType ].children;
|
||||
}
|
||||
} else {
|
||||
// flush all children's operations
|
||||
newChildren = state[ action.payload.resourceType ].children;
|
||||
Object.keys(newChildren)
|
||||
.forEach((resourceId) => {
|
||||
newChildren = Object.assign({}, newChildren, {
|
||||
[resourceId]: {
|
||||
body: newChildren[ resourceId ].body
|
||||
.filter((entry) => entry.timeAdded > state[ action.payload.resourceType ].transactionStartTime)
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
return Object.assign({}, state, {
|
||||
[action.payload.resourceType]: Object.assign({}, state[ action.payload.resourceType ], {
|
||||
children: newChildren,
|
||||
transactionStartTime: null,
|
||||
commitPending: state[ action.payload.resourceType ].commitPending
|
||||
})
|
||||
});
|
||||
} else {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
function addOperationToList(body: JsonPatchOperationObject[], actionType, targetPath, value?) {
|
||||
const newBody = Array.from(body);
|
||||
switch (actionType) {
|
||||
case JsonPatchOperationsActionTypes.NEW_JSON_PATCH_ADD_OPERATION:
|
||||
newBody.push(makeOperationEntry({
|
||||
op: JsonPatchOperationType.add,
|
||||
path: targetPath,
|
||||
value: value
|
||||
}));
|
||||
break;
|
||||
case JsonPatchOperationsActionTypes.NEW_JSON_PATCH_REPLACE_OPERATION:
|
||||
newBody.push(makeOperationEntry({
|
||||
op: JsonPatchOperationType.replace,
|
||||
path: targetPath,
|
||||
value: value
|
||||
}));
|
||||
break;
|
||||
case JsonPatchOperationsActionTypes.NEW_JSON_PATCH_REMOVE_OPERATION:
|
||||
newBody.push(makeOperationEntry({ op: JsonPatchOperationType.remove, path: targetPath }));
|
||||
break;
|
||||
}
|
||||
return newBody;
|
||||
}
|
||||
|
||||
function makeOperationEntry(operation) {
|
||||
return { operation: operation, timeAdded: new Date().getTime() };
|
||||
}
|
127
src/app/core/json-patch/json-patch-operations.service.ts
Normal file
127
src/app/core/json-patch/json-patch-operations.service.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { hasValue, isEmpty, isNotEmpty, isNotUndefined, isUndefined } from '../../shared/empty.util';
|
||||
import { ErrorResponse, PostPatchSuccessResponse, RestResponse } from '../cache/response-cache.models';
|
||||
import { ResponseCacheEntry } from '../cache/response-cache.reducer';
|
||||
import { ResponseCacheService } from '../cache/response-cache.service';
|
||||
import { PatchRequest, RestRequest, SubmissionPatchRequest } from '../data/request.models';
|
||||
import { RequestService } from '../data/request.service';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
import { CoreState } from '../core.reducers';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { jsonPatchOperationsByResourceType } from './selectors';
|
||||
import { JsonPatchOperationsResourceEntry } from './json-patch-operations.reducer';
|
||||
import {
|
||||
CommitPatchOperationsAction,
|
||||
RollbacktPatchOperationsAction,
|
||||
StartTransactionPatchOperationsAction
|
||||
} from './json-patch-operations.actions';
|
||||
import { JsonPatchOperationModel } from './json-patch.model';
|
||||
|
||||
@Injectable()
|
||||
export class JsonPatchOperationsService<ResponseDefinitionDomain> {
|
||||
protected linkPath;
|
||||
|
||||
constructor(protected responseCache: ResponseCacheService,
|
||||
protected requestService: RequestService,
|
||||
protected store: Store<CoreState>,
|
||||
protected halService: HALEndpointService) {
|
||||
}
|
||||
|
||||
protected submitData(request: RestRequest): Observable<ResponseDefinitionDomain> {
|
||||
const [successResponse, errorResponse] = this.responseCache.get(request.href)
|
||||
.map((entry: ResponseCacheEntry) => entry.response)
|
||||
.partition((response: RestResponse) => response.isSuccessful);
|
||||
return Observable.merge(
|
||||
errorResponse.flatMap((response: ErrorResponse) =>
|
||||
Observable.throw(new Error(`Couldn't send data to server`))),
|
||||
successResponse
|
||||
.filter((response: PostPatchSuccessResponse) => isNotEmpty(response))
|
||||
.map((response: PostPatchSuccessResponse) => response.dataDefinition)
|
||||
.distinctUntilChanged());
|
||||
}
|
||||
|
||||
protected submitJsonPatchOperations(hrefObs: Observable<string>, resourceType: string, resourceId?: string) {
|
||||
let startTransactionTime = null;
|
||||
const [patchRequestObs, emptyRequestObs] = hrefObs
|
||||
.flatMap((endpointURL: string) => {
|
||||
return this.store.select(jsonPatchOperationsByResourceType(resourceType))
|
||||
.take(1)
|
||||
.filter((operationsList: JsonPatchOperationsResourceEntry) => isUndefined(operationsList) || !(operationsList.commitPending))
|
||||
.do(() => startTransactionTime = new Date().getTime())
|
||||
.map((operationsList: JsonPatchOperationsResourceEntry) => {
|
||||
const body: JsonPatchOperationModel[] = [];
|
||||
if (isNotEmpty(operationsList)) {
|
||||
if (isNotEmpty(resourceId)) {
|
||||
if (isNotUndefined(operationsList.children[resourceId]) && isNotEmpty(operationsList.children[resourceId].body)) {
|
||||
operationsList.children[resourceId].body.forEach((entry) => {
|
||||
body.push(entry.operation);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
Object.keys(operationsList.children)
|
||||
.filter((key) => operationsList.children.hasOwnProperty(key))
|
||||
.filter((key) => hasValue(operationsList.children[key]))
|
||||
.filter((key) => hasValue(operationsList.children[key].body))
|
||||
.forEach((key) => {
|
||||
operationsList.children[key].body.forEach((entry) => {
|
||||
body.push(entry.operation);
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
return new SubmissionPatchRequest(this.requestService.generateRequestId(), endpointURL, body);
|
||||
});
|
||||
})
|
||||
.partition((request: PatchRequest) => isNotEmpty(request.body));
|
||||
|
||||
return Observable.merge(
|
||||
emptyRequestObs
|
||||
.filter((request: PatchRequest) => isEmpty(request.body))
|
||||
.do(() => startTransactionTime = null)
|
||||
.map(() => null),
|
||||
patchRequestObs
|
||||
.filter((request: PatchRequest) => isNotEmpty(request.body))
|
||||
.do(() => this.store.dispatch(new StartTransactionPatchOperationsAction(resourceType, resourceId, startTransactionTime)))
|
||||
.do((request: PatchRequest) => this.requestService.configure(request, true))
|
||||
.flatMap((request: PatchRequest) => {
|
||||
const [successResponse, errorResponse] = this.responseCache.get(request.href)
|
||||
.filter((entry: ResponseCacheEntry) => startTransactionTime < entry.timeAdded)
|
||||
.take(1)
|
||||
.map((entry: ResponseCacheEntry) => entry.response)
|
||||
.partition((response: RestResponse) => response.isSuccessful);
|
||||
return Observable.merge(
|
||||
errorResponse
|
||||
.do(() => this.store.dispatch(new RollbacktPatchOperationsAction(resourceType, resourceId)))
|
||||
.flatMap((response: ErrorResponse) => Observable.of(new Error(`Couldn't patch operations`))),
|
||||
successResponse
|
||||
.filter((response: PostPatchSuccessResponse) => isNotEmpty(response))
|
||||
.do(() => this.store.dispatch(new CommitPatchOperationsAction(resourceType, resourceId)))
|
||||
.map((response: PostPatchSuccessResponse) => response.dataDefinition)
|
||||
.distinctUntilChanged());
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
protected getEndpointByIDHref(endpoint, resourceID): string {
|
||||
return isNotEmpty(resourceID) ? `${endpoint}/${resourceID}` : `${endpoint}`;
|
||||
}
|
||||
|
||||
public jsonPatchByResourceType(linkName: string, scopeId: string, resourceType: string,) {
|
||||
const hrefObs = this.halService.getEndpoint(linkName)
|
||||
.filter((href: string) => isNotEmpty(href))
|
||||
.distinctUntilChanged()
|
||||
.map((endpointURL: string) => this.getEndpointByIDHref(endpointURL, scopeId));
|
||||
|
||||
return this.submitJsonPatchOperations(hrefObs, resourceType);
|
||||
}
|
||||
|
||||
public jsonPatchByResourceID(linkName: string, scopeId: string, resourceType: string, resourceId: string) {
|
||||
const hrefObs = this.halService.getEndpoint(linkName)
|
||||
.filter((href: string) => isNotEmpty(href))
|
||||
.distinctUntilChanged()
|
||||
.map((endpointURL: string) => this.getEndpointByIDHref(endpointURL, scopeId));
|
||||
|
||||
return this.submitJsonPatchOperations(hrefObs, resourceType, resourceId);
|
||||
}
|
||||
}
|
14
src/app/core/json-patch/json-patch.model.ts
Normal file
14
src/app/core/json-patch/json-patch.model.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export enum JsonPatchOperationType {
|
||||
test = 'test',
|
||||
remove = 'remove',
|
||||
add = 'add',
|
||||
replace = 'replace',
|
||||
move = 'move',
|
||||
copy = 'copy',
|
||||
}
|
||||
|
||||
export class JsonPatchOperationModel {
|
||||
op: JsonPatchOperationType;
|
||||
path: string;
|
||||
value: any;
|
||||
}
|
34
src/app/core/json-patch/selectors.ts
Normal file
34
src/app/core/json-patch/selectors.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
// @TODO: Merge with keySelector function present in 'src/app/core/shared/selectors.ts'
|
||||
import { createSelector, MemoizedSelector, Selector } from '@ngrx/store';
|
||||
import { hasValue } from '../../shared/empty.util';
|
||||
import { coreSelector, CoreState } from '../core.reducers';
|
||||
import { JsonPatchOperationsEntry, JsonPatchOperationsResourceEntry } from './json-patch-operations.reducer';
|
||||
|
||||
export function keySelector<T, V>(parentSelector: Selector<any, any>, subState: string, key: string): MemoizedSelector<T, V> {
|
||||
return createSelector(parentSelector, (state: T) => {
|
||||
if (hasValue(state[subState])) {
|
||||
return state[subState][key];
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function subStateSelector<T, V>(parentSelector: Selector<any, any>, subState: string): MemoizedSelector<T, V> {
|
||||
return createSelector(parentSelector, (state: T) => {
|
||||
if (hasValue(state[subState])) {
|
||||
return state[subState];
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function jsonPatchOperationsByResourceType(resourceType: string): MemoizedSelector<CoreState, JsonPatchOperationsResourceEntry> {
|
||||
return keySelector<CoreState, JsonPatchOperationsResourceEntry>(coreSelector,'json/patch', resourceType);
|
||||
}
|
||||
|
||||
export function jsonPatchOperationsByResourcId(resourceType: string, resourceId: string): MemoizedSelector<CoreState, JsonPatchOperationsEntry> {
|
||||
const resourceTypeSelector = jsonPatchOperationsByResourceType(resourceType);
|
||||
return subStateSelector<CoreState, JsonPatchOperationsEntry>(resourceTypeSelector, resourceId);
|
||||
}
|
@@ -3,6 +3,8 @@ import { Bitstream } from './bitstream.model';
|
||||
import { Item } from './item.model';
|
||||
import { RemoteData } from '../data/remote-data';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { License } from './license.model';
|
||||
import { ResourcePolicy } from './resource-policy.model';
|
||||
|
||||
export class Collection extends DSpaceObject {
|
||||
|
||||
@@ -39,7 +41,7 @@ export class Collection extends DSpaceObject {
|
||||
* The license of this Collection
|
||||
* Corresponds to the metadata field dc.rights.license
|
||||
*/
|
||||
get license(): string {
|
||||
get dcLicense(): string {
|
||||
return this.findMetadata('dc.rights.license');
|
||||
}
|
||||
|
||||
@@ -51,11 +53,21 @@ export class Collection extends DSpaceObject {
|
||||
return this.findMetadata('dc.description.tableofcontents');
|
||||
}
|
||||
|
||||
/**
|
||||
* The deposit license of this Collection
|
||||
*/
|
||||
license: Observable<RemoteData<License>>;
|
||||
|
||||
/**
|
||||
* The Bitstream that represents the logo of this Collection
|
||||
*/
|
||||
logo: Observable<RemoteData<Bitstream>>;
|
||||
|
||||
/**
|
||||
* The default access conditions of this Collection
|
||||
*/
|
||||
defaultAccessConditions: Observable<RemoteData<ResourcePolicy[]>>;
|
||||
|
||||
/**
|
||||
* An array of Collections that are direct parents of this Collection
|
||||
*/
|
||||
|
@@ -0,0 +1,8 @@
|
||||
export class AccessConditionOption {
|
||||
name: string;
|
||||
groupUUID: string;
|
||||
hasStartDate: boolean;
|
||||
hasEndDate: boolean;
|
||||
maxStartDate: string;
|
||||
maxEndDate: string;
|
||||
}
|
@@ -1,5 +1,4 @@
|
||||
|
||||
import { GenericConstructor } from '../../shared/generic-constructor';
|
||||
import { GenericConstructor } from '../generic-constructor';
|
||||
|
||||
import { SubmissionSectionModel } from './config-submission-section.model';
|
||||
import { SubmissionFormsModel } from './config-submission-forms.model';
|
||||
@@ -7,6 +6,7 @@ import { SubmissionDefinitionsModel } from './config-submission-definitions.mode
|
||||
import { ConfigType } from './config-type';
|
||||
import { ConfigObject } from './config.model';
|
||||
import { ConfigAuthorityModel } from './config-authority.model';
|
||||
import { SubmissionUploadsModel } from './config-submission-uploads.model';
|
||||
|
||||
export class ConfigObjectFactory {
|
||||
public static getConstructor(type): GenericConstructor<ConfigObject> {
|
||||
@@ -23,6 +23,10 @@ export class ConfigObjectFactory {
|
||||
case ConfigType.SubmissionSections: {
|
||||
return SubmissionSectionModel
|
||||
}
|
||||
case ConfigType.SubmissionUpload:
|
||||
case ConfigType.SubmissionUploads: {
|
||||
return SubmissionUploadsModel
|
||||
}
|
||||
case ConfigType.Authority: {
|
||||
return ConfigAuthorityModel
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { autoserialize, autoserializeAs, inheritSerialization } from 'cerialize';
|
||||
import { autoserialize, inheritSerialization } from 'cerialize';
|
||||
import { ConfigObject } from './config.model';
|
||||
import { SectionsType } from '../../../submission/sections/sections-type';
|
||||
|
||||
@inheritSerialization(ConfigObject)
|
||||
export class SubmissionSectionModel extends ConfigObject {
|
||||
@@ -11,7 +12,7 @@ export class SubmissionSectionModel extends ConfigObject {
|
||||
mandatory: boolean;
|
||||
|
||||
@autoserialize
|
||||
sectionType: string;
|
||||
sectionType: SectionsType;
|
||||
|
||||
@autoserialize
|
||||
visibility: {
|
||||
|
@@ -0,0 +1,21 @@
|
||||
import {autoserialize, autoserializeAs, inheritSerialization} from 'cerialize';
|
||||
import { ConfigObject } from './config.model';
|
||||
import { AccessConditionOption } from './config-access-condition-option.model';
|
||||
import {SubmissionFormsModel} from './config-submission-forms.model';
|
||||
|
||||
@inheritSerialization(ConfigObject)
|
||||
export class SubmissionUploadsModel extends ConfigObject {
|
||||
|
||||
@autoserialize
|
||||
accessConditionOptions: AccessConditionOption[];
|
||||
|
||||
@autoserializeAs(SubmissionFormsModel)
|
||||
metadata: SubmissionFormsModel[];
|
||||
|
||||
@autoserialize
|
||||
required: boolean;
|
||||
|
||||
@autoserialize
|
||||
maxSize: number;
|
||||
|
||||
}
|
@@ -2,7 +2,6 @@
|
||||
* TODO replace with actual string enum after upgrade to TypeScript 2.4:
|
||||
* https://github.com/Microsoft/TypeScript/pull/15486
|
||||
*/
|
||||
import { ResourceType } from '../resource-type';
|
||||
|
||||
export enum ConfigType {
|
||||
SubmissionDefinitions = 'submissiondefinitions',
|
||||
@@ -11,5 +10,6 @@ export enum ConfigType {
|
||||
SubmissionForms = 'submissionforms',
|
||||
SubmissionSections = 'submissionsections',
|
||||
SubmissionSection = 'submissionsection',
|
||||
Authority = 'authority'
|
||||
SubmissionUploads = 'submissionuploads',
|
||||
SubmissionUpload = 'submissionupload',
|
||||
}
|
||||
|
@@ -60,9 +60,9 @@ export class DSpaceObject implements CacheableObject, ListableObject {
|
||||
* @return string
|
||||
*/
|
||||
findMetadata(key: string, language?: string): string {
|
||||
const metadatum = this.metadata.find((m: Metadatum) => {
|
||||
const metadatum = (this.metadata) ? this.metadata.find((m: Metadatum) => {
|
||||
return m.key === key && (isEmpty(language) || m.language === language)
|
||||
});
|
||||
}) : null;
|
||||
if (isNotEmpty(metadatum)) {
|
||||
return metadatum.value;
|
||||
} else {
|
||||
@@ -81,7 +81,7 @@ export class DSpaceObject implements CacheableObject, ListableObject {
|
||||
* @return Array<Metadatum>
|
||||
*/
|
||||
filterMetadata(keys: string[]): Metadatum[] {
|
||||
return this.metadata.filter((metadatum: Metadatum) => {
|
||||
return (this.metadata || []).filter((metadatum: Metadatum) => {
|
||||
return keys.some((key) => key === metadatum.key);
|
||||
});
|
||||
}
|
||||
|
35
src/app/core/shared/file.service.ts
Normal file
35
src/app/core/shared/file.service.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpHeaders } from '@angular/common/http';
|
||||
|
||||
import { DSpaceRESTv2Service, HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
|
||||
import { RestRequestMethod } from '../data/request.models';
|
||||
import { saveAs } from 'file-saver';
|
||||
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
|
||||
|
||||
@Injectable()
|
||||
export class FileService {
|
||||
constructor(
|
||||
private restService: DSpaceRESTv2Service
|
||||
) { }
|
||||
|
||||
downloadFile(url: string) {
|
||||
const headers = new HttpHeaders();
|
||||
const options: HttpOptions = Object.create({headers, responseType: 'blob'});
|
||||
return this.restService.request(RestRequestMethod.Get, url, null, options)
|
||||
.subscribe((data) => {
|
||||
saveAs(data.payload as Blob, this.getFileNameFromResponseContentDisposition(data));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives file name from the http response
|
||||
* by looking inside content-disposition
|
||||
* @param res http DSpaceRESTV2Response
|
||||
*/
|
||||
getFileNameFromResponseContentDisposition(res: DSpaceRESTV2Response) {
|
||||
const contentDisposition = res.headers.get('content-disposition') || '';
|
||||
const matches = /filename="([^;]+)"/ig.exec(contentDisposition) || [];
|
||||
const fileName = (matches[1] || 'untitled').trim().replace(/\.[^/.]+$/, '');
|
||||
return fileName;
|
||||
};
|
||||
}
|
@@ -55,8 +55,7 @@ export class HALEndpointService {
|
||||
return endpointMap[subPath];
|
||||
} else {
|
||||
/*TODO remove if/else block once the rest response contains _links for facets*/
|
||||
currentPath += '/' + subPath;
|
||||
return currentPath;
|
||||
return currentPath + '/' + subPath;
|
||||
}
|
||||
}),
|
||||
])
|
||||
|
@@ -88,8 +88,10 @@ export class Item extends DSpaceObject {
|
||||
*/
|
||||
getBitstreamsByBundleName(bundleName: string): Observable<Bitstream[]> {
|
||||
return this.bitstreams
|
||||
.filter((rd: RemoteData<Bitstream[]>) => rd.hasSucceeded)
|
||||
.map((rd: RemoteData<Bitstream[]>) => rd.payload)
|
||||
.filter((bitstreams: Bitstream[]) => hasValue(bitstreams))
|
||||
.first()
|
||||
.startWith([])
|
||||
.map((bitstreams) => {
|
||||
return bitstreams
|
||||
|
14
src/app/core/shared/license.model.ts
Normal file
14
src/app/core/shared/license.model.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { DSpaceObject } from './dspace-object.model';
|
||||
|
||||
export class License extends DSpaceObject {
|
||||
|
||||
/**
|
||||
* Is the license custom?
|
||||
*/
|
||||
custom: boolean;
|
||||
|
||||
/**
|
||||
* The text of the license
|
||||
*/
|
||||
text: string;
|
||||
}
|
@@ -38,9 +38,9 @@ export const getResourceLinksFromResponse = () =>
|
||||
map((entry: ResponseCacheEntry) => (entry.response as DSOSuccessResponse).resourceSelfLinks),
|
||||
);
|
||||
|
||||
export const configureRequest = (requestService: RequestService) =>
|
||||
export const configureRequest = (requestService: RequestService, forceBypassCache?: boolean) =>
|
||||
(source: Observable<RestRequest>): Observable<RestRequest> =>
|
||||
source.pipe(tap((request: RestRequest) => requestService.configure(request)));
|
||||
source.pipe(tap((request: RestRequest) => requestService.configure(request, forceBypassCache)));
|
||||
|
||||
export const getRemoteDataPayload = () =>
|
||||
<T>(source: Observable<RemoteData<T>>): Observable<T> =>
|
||||
|
14
src/app/core/shared/patch-request.model.ts
Normal file
14
src/app/core/shared/patch-request.model.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export enum PatchOperationType {
|
||||
test = 'test',
|
||||
remove = 'remove',
|
||||
add = 'add',
|
||||
replace = 'replace',
|
||||
move = 'move',
|
||||
copy = 'copy',
|
||||
}
|
||||
|
||||
export class PatchOperationModel {
|
||||
op: PatchOperationType;
|
||||
path: string;
|
||||
value: any;
|
||||
}
|
34
src/app/core/shared/resource-policy.model.ts
Normal file
34
src/app/core/shared/resource-policy.model.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { DSpaceObject } from './dspace-object.model';
|
||||
|
||||
export class ResourcePolicy extends DSpaceObject {
|
||||
|
||||
/**
|
||||
* The action of the resource policy
|
||||
*/
|
||||
action: string;
|
||||
|
||||
/**
|
||||
* The identifier of the resource policy
|
||||
*/
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* The group uuid bound to the resource policy
|
||||
*/
|
||||
groupUUID: string;
|
||||
|
||||
/**
|
||||
* The end date of the resource policy
|
||||
*/
|
||||
endDate: string;
|
||||
|
||||
/**
|
||||
* The start date of the resource policy
|
||||
*/
|
||||
startDate: string;
|
||||
|
||||
/**
|
||||
* The type of the resource policy
|
||||
*/
|
||||
rpType: string
|
||||
}
|
@@ -8,4 +8,8 @@ export enum ResourceType {
|
||||
Community = 'community',
|
||||
Eperson = 'eperson',
|
||||
Group = 'group',
|
||||
ResourcePolicy = 'resourcePolicy',
|
||||
License = 'license',
|
||||
Workflowitem = 'workflowitem',
|
||||
Workspaceitem = 'workspaceitem',
|
||||
}
|
||||
|
11
src/app/core/shared/submit-data-response-definition.model.ts
Normal file
11
src/app/core/shared/submit-data-response-definition.model.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { autoserialize } from 'cerialize';
|
||||
|
||||
export class SubmitDataResponseDefinitionObject {
|
||||
|
||||
@autoserialize
|
||||
public name: string;
|
||||
|
||||
@autoserialize
|
||||
public type: string;
|
||||
|
||||
}
|
4
src/app/core/submission/models/edititem.model.ts
Normal file
4
src/app/core/submission/models/edititem.model.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { Workspaceitem } from './workspaceitem.model';
|
||||
|
||||
export class EditItem extends Workspaceitem {
|
||||
}
|
47
src/app/core/submission/models/normalized-edititem.model.ts
Normal file
47
src/app/core/submission/models/normalized-edititem.model.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { autoserialize, autoserializeAs, inheritSerialization } from 'cerialize';
|
||||
import { mapsTo, relationship } from '../../cache/builders/build-decorators';
|
||||
import { NormalizedWorkspaceItem } from './normalized-workspaceitem.model';
|
||||
import { NormalizedSubmissionObject } from './normalized-submission-object.model';
|
||||
import { ResourceType } from '../../shared/resource-type';
|
||||
import { SubmissionDefinitionsModel } from '../../shared/config/config-submission-definitions.model';
|
||||
import { WorkspaceitemSectionsObject } from './workspaceitem-sections.model';
|
||||
import { SubmissionObjectError } from './submission-object.model';
|
||||
import { EditItem } from './edititem.model';
|
||||
|
||||
@mapsTo(EditItem)
|
||||
@inheritSerialization(NormalizedWorkspaceItem)
|
||||
export class NormalizedEditItem extends NormalizedSubmissionObject {
|
||||
|
||||
/**
|
||||
* The item identifier
|
||||
*/
|
||||
@autoserialize
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* The item last modified date
|
||||
*/
|
||||
@autoserialize
|
||||
lastModified: Date;
|
||||
|
||||
@autoserialize
|
||||
@relationship(ResourceType.Collection, true)
|
||||
collection: string[];
|
||||
|
||||
@autoserialize
|
||||
@relationship(ResourceType.Item, true)
|
||||
item: string[];
|
||||
|
||||
@autoserialize
|
||||
sections: WorkspaceitemSectionsObject;
|
||||
|
||||
@autoserializeAs(SubmissionDefinitionsModel)
|
||||
submissionDefinition: SubmissionDefinitionsModel;
|
||||
|
||||
@autoserialize
|
||||
@relationship(ResourceType.Eperson, true)
|
||||
submitter: string[];
|
||||
|
||||
@autoserialize
|
||||
errors: SubmissionObjectError[]
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
import { NormalizedDSpaceObject } from '../../cache/models/normalized-dspace-object.model';
|
||||
|
||||
/**
|
||||
* An abstract model class for a DSpaceObject.
|
||||
*/
|
||||
export abstract class NormalizedSubmissionObject extends NormalizedDSpaceObject {
|
||||
|
||||
}
|
@@ -0,0 +1,47 @@
|
||||
import { autoserialize, autoserializeAs, inheritSerialization } from 'cerialize';
|
||||
import { mapsTo, relationship } from '../../cache/builders/build-decorators';
|
||||
import { Workflowitem } from './workflowitem.model';
|
||||
import { NormalizedWorkspaceItem } from './normalized-workspaceitem.model';
|
||||
import { NormalizedSubmissionObject } from './normalized-submission-object.model';
|
||||
import { ResourceType } from '../../shared/resource-type';
|
||||
import { SubmissionDefinitionsModel } from '../../shared/config/config-submission-definitions.model';
|
||||
import { WorkspaceitemSectionsObject } from './workspaceitem-sections.model';
|
||||
import { SubmissionObjectError } from './submission-object.model';
|
||||
|
||||
@mapsTo(Workflowitem)
|
||||
@inheritSerialization(NormalizedWorkspaceItem)
|
||||
export class NormalizedWorkflowItem extends NormalizedSubmissionObject {
|
||||
|
||||
/**
|
||||
* The workspaceitem identifier
|
||||
*/
|
||||
@autoserialize
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* The workspaceitem last modified date
|
||||
*/
|
||||
@autoserialize
|
||||
lastModified: Date;
|
||||
|
||||
@autoserialize
|
||||
@relationship(ResourceType.Collection, true)
|
||||
collection: string[];
|
||||
|
||||
@autoserialize
|
||||
@relationship(ResourceType.Item, true)
|
||||
item: string[];
|
||||
|
||||
@autoserialize
|
||||
sections: WorkspaceitemSectionsObject;
|
||||
|
||||
@autoserializeAs(SubmissionDefinitionsModel)
|
||||
submissionDefinition: SubmissionDefinitionsModel;
|
||||
|
||||
@autoserialize
|
||||
@relationship(ResourceType.Eperson, true)
|
||||
submitter: string[];
|
||||
|
||||
@autoserialize
|
||||
errors: SubmissionObjectError[]
|
||||
}
|
@@ -0,0 +1,51 @@
|
||||
import { autoserialize, autoserializeAs, inheritSerialization } from 'cerialize';
|
||||
|
||||
import { Workspaceitem } from './workspaceitem.model';
|
||||
import { WorkspaceitemSectionsObject } from './workspaceitem-sections.model';
|
||||
|
||||
import { NormalizedSubmissionObject } from './normalized-submission-object.model';
|
||||
import { NormalizedDSpaceObject } from '../../cache/models/normalized-dspace-object.model';
|
||||
import { mapsTo, relationship } from '../../cache/builders/build-decorators';
|
||||
import { NormalizedCollection } from '../../cache/models/normalized-collection.model';
|
||||
import { ResourceType } from '../../shared/resource-type';
|
||||
import { SubmissionDefinitionsModel } from '../../shared/config/config-submission-definitions.model';
|
||||
import { Eperson } from '../../eperson/models/eperson.model';
|
||||
import { SubmissionObjectError } from './submission-object.model';
|
||||
|
||||
@mapsTo(Workspaceitem)
|
||||
@inheritSerialization(NormalizedDSpaceObject)
|
||||
export class NormalizedWorkspaceItem extends NormalizedSubmissionObject {
|
||||
|
||||
/**
|
||||
* The workspaceitem identifier
|
||||
*/
|
||||
@autoserialize
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* The workspaceitem last modified date
|
||||
*/
|
||||
@autoserialize
|
||||
lastModified: Date;
|
||||
|
||||
@autoserialize
|
||||
@relationship(ResourceType.Collection, true)
|
||||
collection: string[];
|
||||
|
||||
@autoserialize
|
||||
@relationship(ResourceType.Item, true)
|
||||
item: string[];
|
||||
|
||||
@autoserialize
|
||||
sections: WorkspaceitemSectionsObject;
|
||||
|
||||
@autoserializeAs(SubmissionDefinitionsModel)
|
||||
submissionDefinition: SubmissionDefinitionsModel;
|
||||
|
||||
@autoserialize
|
||||
@relationship(ResourceType.Eperson, true)
|
||||
submitter: string[];
|
||||
|
||||
@autoserialize
|
||||
errors: SubmissionObjectError[]
|
||||
}
|
43
src/app/core/submission/models/submission-object.model.ts
Normal file
43
src/app/core/submission/models/submission-object.model.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { CacheableObject } from '../../cache/object-cache.reducer';
|
||||
import { ListableObject } from '../../../shared/object-collection/shared/listable-object.model';
|
||||
import { DSpaceObject } from '../../shared/dspace-object.model';
|
||||
import { Eperson } from '../../eperson/models/eperson.model';
|
||||
import { RemoteData } from '../../data/remote-data';
|
||||
import { Collection } from '../../shared/collection.model';
|
||||
import { Item } from '../../shared/item.model';
|
||||
import { SubmissionDefinitionsModel } from '../../shared/config/config-submission-definitions.model';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { WorkspaceitemSectionsObject } from './workspaceitem-sections.model';
|
||||
|
||||
export interface SubmissionObjectError {
|
||||
message: string,
|
||||
paths: string[],
|
||||
}
|
||||
|
||||
/**
|
||||
* An abstract model class for a DSpaceObject.
|
||||
*/
|
||||
export abstract class SubmissionObject extends DSpaceObject implements CacheableObject, ListableObject {
|
||||
|
||||
/**
|
||||
* The workspaceitem identifier
|
||||
*/
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* The workspaceitem last modified date
|
||||
*/
|
||||
lastModified: Date;
|
||||
|
||||
collection: Observable<RemoteData<Collection[]>> | Collection[];
|
||||
|
||||
item: Observable<RemoteData<Item[]>> | Item[];
|
||||
|
||||
sections: WorkspaceitemSectionsObject;
|
||||
|
||||
submissionDefinition: SubmissionDefinitionsModel;
|
||||
|
||||
submitter: Observable<RemoteData<Eperson[]>> | Eperson[];
|
||||
|
||||
errors: SubmissionObjectError[];
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
export class SubmissionUploadFileAccessConditionObject {
|
||||
id: string;
|
||||
name: string;
|
||||
groupUUID: string;
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
}
|
4
src/app/core/submission/models/workflowitem.model.ts
Normal file
4
src/app/core/submission/models/workflowitem.model.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { Workspaceitem } from './workspaceitem.model';
|
||||
|
||||
export class Workflowitem extends Workspaceitem {
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
import { FormFieldMetadataValueObject } from '../../../shared/form/builder/models/form-field-metadata-value.model';
|
||||
import { WorkspaceitemSectionUploadFileObject } from './workspaceitem-section-upload-file.model';
|
||||
import { FormFieldChangedObject } from '../../../shared/form/builder/models/form-field-unexpected-object.model';
|
||||
|
||||
import { DSpaceObject } from '../../shared/dspace-object.model';
|
||||
|
||||
export interface WorkspaceitemSectionDeduplicationObject {
|
||||
matches: DeduplicationSchema[];
|
||||
}
|
||||
|
||||
export interface DeduplicationSchema {
|
||||
submitterDecision?: string; // [reject|verify]
|
||||
submitterNote?: string;
|
||||
submitterTime?: string; // (readonly)
|
||||
|
||||
workflowDecision?: string; // [reject|verify]
|
||||
workflowNote?: string;
|
||||
workflowTime?: string; // (readonly)
|
||||
|
||||
matchObject?: DSpaceObject; // item, workspaceItem, workflowItem
|
||||
}
|
@@ -0,0 +1,5 @@
|
||||
import { FormFieldMetadataValueObject } from '../../../shared/form/builder/models/form-field-metadata-value.model';
|
||||
|
||||
export interface WorkspaceitemSectionFormObject {
|
||||
[metadata: string]: FormFieldMetadataValueObject;
|
||||
}
|
@@ -0,0 +1,5 @@
|
||||
export interface WorkspaceitemSectionLicenseObject {
|
||||
url: string;
|
||||
acceptanceDate: string;
|
||||
granted: boolean;
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
import { FormFieldMetadataValueObject } from '../../../shared/form/builder/models/form-field-metadata-value.model';
|
||||
import { WorkspaceitemSectionUploadFileObject } from './workspaceitem-section-upload-file.model';
|
||||
|
||||
export interface WorkspaceitemSectionRecycleObject {
|
||||
unexpected: any;
|
||||
metadata: FormFieldMetadataValueObject[];
|
||||
files: WorkspaceitemSectionUploadFileObject[];
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
import { SubmissionUploadFileAccessConditionObject } from './submission-upload-file-access-condition.model';
|
||||
import { WorkspaceitemSectionFormObject } from './workspaceitem-section-form.model';
|
||||
|
||||
export class WorkspaceitemSectionUploadFileObject {
|
||||
uuid: string;
|
||||
metadata: WorkspaceitemSectionFormObject;
|
||||
sizeBytes: number;
|
||||
checkSum: {
|
||||
checkSumAlgorithm: string;
|
||||
value: string;
|
||||
};
|
||||
url: string;
|
||||
thumbnail: string;
|
||||
accessConditions: SubmissionUploadFileAccessConditionObject[];
|
||||
}
|
@@ -0,0 +1,5 @@
|
||||
import { WorkspaceitemSectionUploadFileObject } from './workspaceitem-section-upload-file.model';
|
||||
|
||||
export interface WorkspaceitemSectionUploadObject {
|
||||
files: WorkspaceitemSectionUploadFileObject[];
|
||||
}
|
@@ -0,0 +1,65 @@
|
||||
import { WorkspaceitemSectionFormObject } from './workspaceitem-section-form.model';
|
||||
import { WorkspaceitemSectionLicenseObject } from './workspaceitem-section-license.model';
|
||||
import { WorkspaceitemSectionUploadObject } from './workspaceitem-section-upload.model';
|
||||
import { isNotEmpty, isNotNull } from '../../../shared/empty.util';
|
||||
import { FormFieldLanguageValueObject } from '../../../shared/form/builder/models/form-field-language-value.model';
|
||||
import { WorkspaceitemSectionRecycleObject } from './workspaceitem-section-recycle.model';
|
||||
import { WorkspaceitemSectionDeduplicationObject } from './workspaceitem-section-deduplication.model';
|
||||
import { FormFieldMetadataValueObject } from '../../../shared/form/builder/models/form-field-metadata-value.model';
|
||||
|
||||
export class WorkspaceitemSectionsObject {
|
||||
[name: string]: WorkspaceitemSectionDataType;
|
||||
|
||||
}
|
||||
|
||||
export function isServerFormValue(obj: any): boolean {
|
||||
return (typeof obj === 'object'
|
||||
&& obj.hasOwnProperty('value')
|
||||
&& obj.hasOwnProperty('language')
|
||||
&& obj.hasOwnProperty('authority')
|
||||
&& obj.hasOwnProperty('confidence')
|
||||
&& obj.hasOwnProperty('place'))
|
||||
}
|
||||
|
||||
export function normalizeSectionData(obj: any) {
|
||||
let result: any = obj;
|
||||
if (isNotNull(obj)) {
|
||||
// If is an Instance of FormFieldMetadataValueObject normalize it
|
||||
if (typeof obj === 'object' && isServerFormValue(obj)) {
|
||||
// If authority property is set normalize as a FormFieldMetadataValueObject object
|
||||
/* NOTE: Data received from server could have authority property equal to null, but into form
|
||||
field's model is required a FormFieldMetadataValueObject object as field value, so double-check in
|
||||
field's parser and eventually instantiate it */
|
||||
// if (isNotEmpty(obj.authority)) {
|
||||
// result = new FormFieldMetadataValueObject(obj.value, obj.language, obj.authority, (obj.display || obj.value), obj.place, obj.confidence);
|
||||
// } else if (isNotEmpty(obj.language)) {
|
||||
// const languageValue = new FormFieldLanguageValueObject(obj.value, obj.language);
|
||||
// result = languageValue;
|
||||
// } else {
|
||||
// // Normalize as a string value
|
||||
// result = obj.value;
|
||||
// }
|
||||
result = new FormFieldMetadataValueObject(obj.value, obj.language, obj.authority, (obj.display || obj.value), obj.place, obj.confidence);
|
||||
} else if (Array.isArray(obj)) {
|
||||
result = [];
|
||||
obj.forEach((item, index) => {
|
||||
result[index] = normalizeSectionData(item);
|
||||
});
|
||||
} else if (typeof obj === 'object') {
|
||||
result = Object.create({});
|
||||
Object.keys(obj)
|
||||
.forEach((key) => {
|
||||
result[key] = normalizeSectionData(obj[key]);
|
||||
});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export type WorkspaceitemSectionDataType
|
||||
= WorkspaceitemSectionUploadObject
|
||||
| WorkspaceitemSectionFormObject
|
||||
| WorkspaceitemSectionLicenseObject
|
||||
| WorkspaceitemSectionRecycleObject
|
||||
| WorkspaceitemSectionDeduplicationObject
|
||||
| string;
|
5
src/app/core/submission/models/workspaceitem.model.ts
Normal file
5
src/app/core/submission/models/workspaceitem.model.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { SubmissionObject } from './submission-object.model';
|
||||
|
||||
export class Workspaceitem extends SubmissionObject {
|
||||
|
||||
}
|
@@ -0,0 +1,69 @@
|
||||
import { SubmissionDefinitionsModel } from '../shared/config/config-submission-definitions.model';
|
||||
import { SubmissionFormsModel } from '../shared/config/config-submission-forms.model';
|
||||
import { SubmissionSectionModel } from '../shared/config/config-submission-section.model';
|
||||
import { GenericConstructor } from '../shared/generic-constructor';
|
||||
import { NormalizedBitstream } from '../cache/models/normalized-bitstream.model';
|
||||
import { NormalizedBundle } from '../cache/models/normalized-bundle.model';
|
||||
import { NormalizedCollection } from '../cache/models/normalized-collection.model';
|
||||
import { NormalizedCommunity } from '../cache/models/normalized-community.model';
|
||||
import { NormalizedItem } from '../cache/models/normalized-item.model';
|
||||
import { NormalizedLicense } from '../cache/models/normalized-license.model';
|
||||
import { NormalizedWorkspaceItem } from './models/normalized-workspaceitem.model';
|
||||
import { NormalizedObject } from '../cache/models/normalized-object.model';
|
||||
import { ConfigObject } from '../shared/config/config.model';
|
||||
import { SubmissionResourceType } from './submission-resource-type';
|
||||
import { NormalizedResourcePolicy } from '../cache/models/normalized-resource-policy.model';
|
||||
import { NormalizedWorkflowItem } from './models/normalized-workflowitem.model';
|
||||
import { NormalizedEditItem } from './models/normalized-edititem.model';
|
||||
|
||||
export class NormalizedSubmissionObjectFactory {
|
||||
public static getConstructor(type: SubmissionResourceType): GenericConstructor<NormalizedObject | ConfigObject> {
|
||||
switch (type) {
|
||||
case SubmissionResourceType.Bitstream: {
|
||||
return NormalizedBitstream
|
||||
}
|
||||
case SubmissionResourceType.Bundle: {
|
||||
return NormalizedBundle
|
||||
}
|
||||
case SubmissionResourceType.Item: {
|
||||
return NormalizedItem
|
||||
}
|
||||
case SubmissionResourceType.Collection: {
|
||||
return NormalizedCollection
|
||||
}
|
||||
case SubmissionResourceType.Community: {
|
||||
return NormalizedCommunity
|
||||
}
|
||||
case SubmissionResourceType.ResourcePolicy: {
|
||||
return NormalizedResourcePolicy
|
||||
}
|
||||
case SubmissionResourceType.License: {
|
||||
return NormalizedLicense
|
||||
}
|
||||
case SubmissionResourceType.WorkspaceItem: {
|
||||
return NormalizedWorkspaceItem
|
||||
}
|
||||
case SubmissionResourceType.WorkflowItem: {
|
||||
return NormalizedWorkflowItem
|
||||
}
|
||||
case SubmissionResourceType.EditItem: {
|
||||
return NormalizedEditItem
|
||||
}
|
||||
case SubmissionResourceType.SubmissionDefinition:
|
||||
case SubmissionResourceType.SubmissionDefinitions: {
|
||||
return SubmissionDefinitionsModel
|
||||
}
|
||||
case SubmissionResourceType.SubmissionForm:
|
||||
case SubmissionResourceType.SubmissionForms: {
|
||||
return SubmissionFormsModel
|
||||
}
|
||||
case SubmissionResourceType.SubmissionSection:
|
||||
case SubmissionResourceType.SubmissionSections: {
|
||||
return SubmissionSectionModel
|
||||
}
|
||||
default: {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
24
src/app/core/submission/submission-resource-type.ts
Normal file
24
src/app/core/submission/submission-resource-type.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* TODO replace with actual string enum after upgrade to TypeScript 2.4:
|
||||
* https://github.com/Microsoft/TypeScript/pull/15486
|
||||
*/
|
||||
export enum SubmissionResourceType {
|
||||
Bundle = 'bundle',
|
||||
Bitstream = 'bitstream',
|
||||
BitstreamFormat = 'bitstreamformat',
|
||||
Item = 'item',
|
||||
Collection = 'collection',
|
||||
Community = 'community',
|
||||
ResourcePolicy = 'resourcePolicies',
|
||||
License = 'license',
|
||||
WorkspaceItem = 'workspaceitem',
|
||||
WorkflowItem = 'workflowitem',
|
||||
EditItem = 'edititem',
|
||||
SubmissionDefinitions = 'submissiondefinitions',
|
||||
SubmissionDefinition = 'submissiondefinition',
|
||||
SubmissionForm = 'submissionform',
|
||||
SubmissionForms = 'submissionforms',
|
||||
SubmissionSections = 'submissionsections',
|
||||
SubmissionSection = 'submissionsection',
|
||||
Authority = 'authority'
|
||||
}
|
@@ -0,0 +1,99 @@
|
||||
import { Inject, Injectable } from '@angular/core';
|
||||
|
||||
import { ResponseParsingService } from '../data/parsing.service';
|
||||
import { RestRequest } from '../data/request.models';
|
||||
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
|
||||
import { ErrorResponse, RestResponse, SubmissionSuccessResponse } from '../cache/response-cache.models';
|
||||
import { isEmpty, isNotEmpty, isNotNull } from '../../shared/empty.util';
|
||||
|
||||
import { ConfigObject } from '../shared/config/config.model';
|
||||
import { BaseResponseParsingService, ProcessRequestDTO } from '../data/base-response-parsing.service';
|
||||
import { GLOBAL_CONFIG } from '../../../config';
|
||||
import { GlobalConfig } from '../../../config/global-config.interface';
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { NormalizedSubmissionObjectFactory } from './normalized-submission-object-factory';
|
||||
import { NormalizedObject } from '../cache/models/normalized-object.model';
|
||||
import { SubmissionResourceType } from './submission-resource-type';
|
||||
import { NormalizedWorkspaceItem } from './models/normalized-workspaceitem.model';
|
||||
import { normalizeSectionData } from './models/workspaceitem-sections.model';
|
||||
import { NormalizedWorkflowItem } from './models/normalized-workflowitem.model';
|
||||
import { NormalizedEditItem } from './models/normalized-edititem.model';
|
||||
|
||||
@Injectable()
|
||||
export class SubmissionResponseParsingService extends BaseResponseParsingService implements ResponseParsingService {
|
||||
|
||||
protected objectFactory = NormalizedSubmissionObjectFactory;
|
||||
protected toCache = false;
|
||||
|
||||
constructor(@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
|
||||
protected objectCache: ObjectCacheService,) {
|
||||
super();
|
||||
}
|
||||
|
||||
parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
|
||||
if (isNotEmpty(data.payload)
|
||||
&& isNotEmpty(data.payload._links)
|
||||
&& (data.statusCode === '201' || data.statusCode === '200')) {
|
||||
const dataDefinition = this.processResponse<NormalizedObject | ConfigObject, SubmissionResourceType>(data.payload, request.href);
|
||||
return new SubmissionSuccessResponse(dataDefinition[Object.keys(dataDefinition)[0]], data.statusCode, this.processPageInfo(data.payload));
|
||||
} else if (isEmpty(data.payload) && data.statusCode === '204') {
|
||||
// Response from a DELETE request
|
||||
return new SubmissionSuccessResponse(null, data.statusCode);
|
||||
} else {
|
||||
return new ErrorResponse(
|
||||
Object.assign(
|
||||
new Error('Unexpected response from server'),
|
||||
{statusText: data.statusCode}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected processResponse<ObjectDomain, ObjectType>(data: any, requestHref: string): ProcessRequestDTO<ObjectDomain> {
|
||||
const dataDefinition = this.process<NormalizedObject | ConfigObject, SubmissionResourceType>(data, requestHref);
|
||||
const normalizedDefinition = Object.create({});
|
||||
normalizedDefinition[Object.keys(dataDefinition)[0]] = [];
|
||||
dataDefinition[Object.keys(dataDefinition)[0]].forEach((item, index) => {
|
||||
let normalizedItem = Object.assign({}, item);
|
||||
// In case data is an Instance of NormalizedWorkspaceItem normalize field value of all the section of type form
|
||||
if (item instanceof NormalizedWorkspaceItem
|
||||
|| item instanceof NormalizedWorkflowItem
|
||||
|| item instanceof NormalizedEditItem) {
|
||||
if (item.sections) {
|
||||
const precessedSection = Object.create({});
|
||||
// Iterate over all workspaceitem's sections
|
||||
Object.keys(item.sections)
|
||||
.forEach((sectionId) => {
|
||||
if (typeof item.sections[sectionId] === 'object' && isNotEmpty(item.sections[sectionId])) {
|
||||
const normalizedSectionData = Object.create({});
|
||||
// Iterate over all sections property
|
||||
Object.keys(item.sections[sectionId])
|
||||
.forEach((metdadataId) => {
|
||||
const entry = item.sections[sectionId][metdadataId];
|
||||
// If entry is not an array, for sure is not a section of type form
|
||||
if (isNotNull(entry) && Array.isArray(entry)) {
|
||||
normalizedSectionData[metdadataId] = [];
|
||||
entry.forEach((valueItem) => {
|
||||
// Parse value and normalize it
|
||||
const normValue = normalizeSectionData(valueItem);
|
||||
if (isNotEmpty(normValue)) {
|
||||
normalizedSectionData[metdadataId].push(normValue);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
normalizedSectionData[metdadataId] = entry;
|
||||
}
|
||||
});
|
||||
precessedSection[sectionId] = normalizedSectionData;
|
||||
}
|
||||
});
|
||||
normalizedItem = Object.assign({}, item, {sections: precessedSection});
|
||||
}
|
||||
}
|
||||
normalizedDefinition[Object.keys(dataDefinition)[0]][index] = normalizedItem;
|
||||
});
|
||||
|
||||
return normalizedDefinition as ProcessRequestDTO<ObjectDomain>;
|
||||
}
|
||||
|
||||
}
|
5
src/app/core/submission/submission-scope-type.ts
Normal file
5
src/app/core/submission/submission-scope-type.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export enum SubmissionScopeType {
|
||||
WorkspaceItem = 'WORKSPACE',
|
||||
WorkflowItem = 'WORKFLOW',
|
||||
EditItem = 'ITEM',
|
||||
}
|
35
src/app/core/submission/workflowitem-data.service.ts
Normal file
35
src/app/core/submission/workflowitem-data.service.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { BrowseService } from '../browse/browse.service';
|
||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||
import { ResponseCacheService } from '../cache/response-cache.service';
|
||||
import { CoreState } from '../core.reducers';
|
||||
|
||||
import { DataService } from '../data/data.service';
|
||||
import { RequestService } from '../data/request.service';
|
||||
import { NormalizedWorkflowItem } from './models/normalized-workflowitem.model';
|
||||
import { Workflowitem } from './models/workflowitem.model';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
|
||||
@Injectable()
|
||||
export class WorkflowitemDataService extends DataService<NormalizedWorkflowItem, Workflowitem> {
|
||||
protected linkPath = 'workflowitems';
|
||||
protected forceBypassCache = true;
|
||||
|
||||
constructor(
|
||||
protected responseCache: ResponseCacheService,
|
||||
protected requestService: RequestService,
|
||||
protected rdbService: RemoteDataBuildService,
|
||||
protected store: Store<CoreState>,
|
||||
protected bs: BrowseService,
|
||||
protected halService: HALEndpointService) {
|
||||
super();
|
||||
}
|
||||
|
||||
public getScopedEndpoint(scopeID: string): Observable<string> {
|
||||
return this.halService.getEndpoint(this.linkPath);
|
||||
}
|
||||
|
||||
}
|
35
src/app/core/submission/workspaceitem-data.service.ts
Normal file
35
src/app/core/submission/workspaceitem-data.service.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { BrowseService } from '../browse/browse.service';
|
||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||
import { ResponseCacheService } from '../cache/response-cache.service';
|
||||
import { CoreState } from '../core.reducers';
|
||||
|
||||
import { DataService } from '../data/data.service';
|
||||
import { RequestService } from '../data/request.service';
|
||||
import { Workspaceitem } from './models/workspaceitem.model';
|
||||
import { NormalizedWorkspaceItem } from './models/normalized-workspaceitem.model';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceitemDataService extends DataService<NormalizedWorkspaceItem, Workspaceitem> {
|
||||
protected linkPath = 'workspaceitems';
|
||||
protected forceBypassCache = true;
|
||||
|
||||
constructor(
|
||||
protected responseCache: ResponseCacheService,
|
||||
protected requestService: RequestService,
|
||||
protected rdbService: RemoteDataBuildService,
|
||||
protected store: Store<CoreState>,
|
||||
protected bs: BrowseService,
|
||||
protected halService: HALEndpointService) {
|
||||
super();
|
||||
}
|
||||
|
||||
public getScopedEndpoint(scopeID: string): Observable<string> {
|
||||
return this.halService.getEndpoint(this.linkPath);
|
||||
}
|
||||
|
||||
}
|
9
src/app/shared/alerts/alerts.component.html
Normal file
9
src/app/shared/alerts/alerts.component.html
Normal file
@@ -0,0 +1,9 @@
|
||||
<div *ngIf="!dismissed" class="alert {{type}} alert-dismissible fade show" role="alert" [@enterLeave]="animate">
|
||||
<span *ngIf="content" [innerHTML]="content | translate"></span>
|
||||
|
||||
<ng-content></ng-content>
|
||||
|
||||
<button *ngIf="dismissible" type="button" class="close" data-dismiss="alert" aria-label="Close" (click)="dismiss()">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
3
src/app/shared/alerts/alerts.component.scss
Normal file
3
src/app/shared/alerts/alerts.component.scss
Normal file
@@ -0,0 +1,3 @@
|
||||
.close:focus {
|
||||
outline: none !important;
|
||||
}
|
44
src/app/shared/alerts/alerts.component.ts
Normal file
44
src/app/shared/alerts/alerts.component.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { ChangeDetectorRef, Component, EventEmitter, Input, Output, ViewEncapsulation } from '@angular/core';
|
||||
import { trigger } from '@angular/animations';
|
||||
|
||||
import { AlertType } from './aletrs-type';
|
||||
import { fadeOutLeave, fadeOutState } from '../animations/fade';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-alert',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
animations: [
|
||||
trigger('enterLeave', [
|
||||
fadeOutLeave, fadeOutState,
|
||||
])
|
||||
],
|
||||
templateUrl: './alerts.component.html',
|
||||
styleUrls: ['./alerts.component.scss']
|
||||
})
|
||||
|
||||
export class AlertsComponent {
|
||||
|
||||
@Input() content: string;
|
||||
@Input() dismissible = false;
|
||||
@Input() type: AlertType;
|
||||
@Output() close: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
public animate = 'fadeIn';
|
||||
public dismissed = false;
|
||||
|
||||
constructor(private cdr: ChangeDetectorRef) {
|
||||
}
|
||||
|
||||
dismiss() {
|
||||
if (this.dismissible) {
|
||||
this.animate = 'fadeOut';
|
||||
this.cdr.detectChanges();
|
||||
setTimeout(() => {
|
||||
this.dismissed = true;
|
||||
this.close.emit();
|
||||
this.cdr.detectChanges();
|
||||
}, 300);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
6
src/app/shared/alerts/aletrs-type.ts
Normal file
6
src/app/shared/alerts/aletrs-type.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export enum AlertType {
|
||||
Success = 'alert-success',
|
||||
Error = 'alert-danger',
|
||||
Info = 'alert-info',
|
||||
Warning = 'alert-warning'
|
||||
}
|
@@ -93,6 +93,51 @@ export const hasValueOperator = () =>
|
||||
<T>(source: Observable<T>): Observable<T> =>
|
||||
source.pipe(filter((obj: T) => hasValue(obj)));
|
||||
|
||||
/**
|
||||
* Returns true if the passed value is null or undefined.
|
||||
* hasUndefinedValue(); // false
|
||||
* hasUndefinedValue(null); // false
|
||||
* hasUndefinedValue(undefined); // false
|
||||
* hasUndefinedValue(''); // true
|
||||
* hasUndefinedValue({undefined, obj}); // true
|
||||
* hasUndefinedValue([undefined, val]); // true
|
||||
*/
|
||||
export function hasUndefinedValue(obj?: any): boolean {
|
||||
let result = false;
|
||||
|
||||
if (isUndefined(obj) || isNull(obj)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const objectType = typeof obj;
|
||||
|
||||
if (objectType === 'object') {
|
||||
if (Object.keys(obj).length === 0) {
|
||||
return false;
|
||||
}
|
||||
Object.entries(obj).forEach(([key, value]) => {
|
||||
if (isUndefined(value)) {
|
||||
result = true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the passed value is null or undefined.
|
||||
* hasUndefinedValue(); // true
|
||||
* hasUndefinedValue(null); // true
|
||||
* hasUndefinedValue(undefined); // true
|
||||
* hasUndefinedValue(''); // false
|
||||
* hasUndefinedValue({undefined, obj}); // false
|
||||
* hasUndefinedValue([undefined, val]); // false
|
||||
*/
|
||||
export function hasNoUndefinedValue(obj?: any): boolean {
|
||||
return !hasUndefinedValue(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that a value is `null` or an empty string, empty array,
|
||||
* or empty function.
|
||||
|
@@ -89,7 +89,8 @@ export class DsDynamicGroupComponent implements OnDestroy, OnInit {
|
||||
this.chips = new Chips(
|
||||
initChipsValue,
|
||||
'value',
|
||||
this.model.mandatoryField);
|
||||
this.model.mandatoryField,
|
||||
this.EnvConfig.submission.metadata.icons);
|
||||
this.subs.push(
|
||||
this.chips.chipsItems
|
||||
.subscribe((subItems: any[]) => {
|
||||
|
@@ -10,6 +10,7 @@ import { FormFieldModel } from '../models/form-field.model';
|
||||
import { ParserType } from './parser-type';
|
||||
import { ParserOptions } from './parser-options';
|
||||
import { ParserFactory } from './parser-factory';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
export const ROW_ID_PREFIX = 'df-row-group-config-';
|
||||
|
||||
@@ -49,7 +50,7 @@ export class RowParser {
|
||||
if (parserCo) {
|
||||
fieldModel = new parserCo(fieldData, this.initFormValues, parserOptions).parse();
|
||||
} else {
|
||||
throw new Error(`unknown form control model type defined with label "${fieldData.label}"`);
|
||||
throw new Error(`unknown form control model type "${fieldData.input.type}" defined for Input field with label "${fieldData.label}".`, );
|
||||
}
|
||||
|
||||
if (fieldModel) {
|
||||
|
@@ -55,8 +55,6 @@ import { DsDynamicFormComponent } from './form/builder/ds-dynamic-form-ui/ds-dyn
|
||||
import { DynamicFormsCoreModule } from '@ng-dynamic-forms/core';
|
||||
import { DynamicFormsNGBootstrapUIModule } from '@ng-dynamic-forms/ui-ng-bootstrap';
|
||||
import { TextMaskModule } from 'angular2-text-mask';
|
||||
import { NotificationComponent } from './notifications/notification/notification.component';
|
||||
import { NotificationsBoardComponent } from './notifications/notifications-board/notifications-board.component';
|
||||
import { DragClickDirective } from './utils/drag-click.directive';
|
||||
import { TruncatePipe } from './utils/truncate.pipe';
|
||||
import { TruncatableComponent } from './truncatable/truncatable.component';
|
||||
@@ -72,6 +70,7 @@ import { NumberPickerComponent } from './number-picker/number-picker.component';
|
||||
import { DsDatePickerComponent } from './form/builder/ds-dynamic-form-ui/models/date-picker/date-picker.component';
|
||||
import { DsDynamicLookupComponent } from './form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component';
|
||||
import { MockAdminGuard } from './mocks/mock-admin-guard.service';
|
||||
import { AlertsComponent } from './alerts/alerts.component';
|
||||
|
||||
const MODULES = [
|
||||
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
|
||||
@@ -104,6 +103,7 @@ const PIPES = [
|
||||
|
||||
const COMPONENTS = [
|
||||
// put shared components here
|
||||
AlertsComponent,
|
||||
AuthNavMenuComponent,
|
||||
ChipsComponent,
|
||||
ComcolPageContentComponent,
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
|
||||
import { TruncatableService } from '../truncatable.service';
|
||||
import { hasValue } from '../../empty.util';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-truncatable-part',
|
||||
@@ -34,6 +35,8 @@ export class TruncatablePartComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.sub.unsubscribe();
|
||||
if (hasValue(this.sub)) {
|
||||
this.sub.unsubscribe();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -10,7 +10,7 @@
|
||||
}
|
||||
|
||||
.ds-base-drop-zone p {
|
||||
height: 42px;
|
||||
min-height: 42px;
|
||||
}
|
||||
|
||||
.ds-document-drop-zone {
|
||||
|
7
src/app/submission/edit/submission-edit.component.html
Normal file
7
src/app/submission/edit/submission-edit.component.html
Normal file
@@ -0,0 +1,7 @@
|
||||
<div class="submission-submit-container" >
|
||||
<ds-submission-submit-form [collectionId]="collectionId"
|
||||
[sections]="sections"
|
||||
[selfUrl]="selfUrl"
|
||||
[submissionDefinition]="submissionDefinition"
|
||||
[submissionId]="submissionId"></ds-submission-submit-form>
|
||||
</div>
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user