diff --git a/README.md b/README.md
index 78d7816f65..5d8a323461 100644
--- a/README.md
+++ b/README.md
@@ -104,7 +104,7 @@ DSPACE_REST_SSL # Whether the angular REST uses SSL [true/false]
The same settings can also be overwritten by setting system environment variables instead, E.g.:
```bash
-export DSPACE_HOST=https://dspace7.4science.cloud/server
+export DSPACE_HOST=dspace7.4science.cloud
```
The priority works as follows: **environment variable** overrides **variable in `.env` file** overrides **`environment.(prod, dev or test).ts`** overrides **`environment.common.ts`**
diff --git a/package.json b/package.json
index 8d3c936212..21a89400bf 100644
--- a/package.json
+++ b/package.json
@@ -101,7 +101,7 @@
"moment": "^2.22.1",
"morgan": "^1.9.1",
"ng-mocks": "^8.1.0",
- "ng2-file-upload": "1.2.1",
+ "ng2-file-upload": "1.4.0",
"ng2-nouislider": "^1.8.2",
"ngx-bootstrap": "^5.3.2",
"ngx-infinite-scroll": "6.0.1",
diff --git a/src/app/+admin/admin-sidebar/admin-sidebar.component.spec.ts b/src/app/+admin/admin-sidebar/admin-sidebar.component.spec.ts
index 581d3341da..e3e8d08753 100644
--- a/src/app/+admin/admin-sidebar/admin-sidebar.component.spec.ts
+++ b/src/app/+admin/admin-sidebar/admin-sidebar.component.spec.ts
@@ -13,6 +13,8 @@ import { AuthService } from '../../core/auth/auth.service';
import { of as observableOf } from 'rxjs';
import { By } from '@angular/platform-browser';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
+import { RouterTestingModule } from '@angular/router/testing';
+import { ActivatedRoute } from '@angular/router';
describe('AdminSidebarComponent', () => {
let comp: AdminSidebarComponent;
@@ -21,13 +23,14 @@ describe('AdminSidebarComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
- imports: [TranslateModule.forRoot(), NoopAnimationsModule],
+ imports: [TranslateModule.forRoot(), NoopAnimationsModule, RouterTestingModule],
declarations: [AdminSidebarComponent],
providers: [
{ provide: Injector, useValue: {} },
{ provide: MenuService, useValue: menuService },
{ provide: CSSVariableService, useClass: CSSVariableServiceStub },
{ provide: AuthService, useClass: AuthServiceStub },
+ { provide: ActivatedRoute, useValue: {} },
{
provide: NgbModal, useValue: {
open: () => {/*comment*/}
diff --git a/src/app/+admin/admin-sidebar/admin-sidebar.component.ts b/src/app/+admin/admin-sidebar/admin-sidebar.component.ts
index 5d885b918b..eedebae80b 100644
--- a/src/app/+admin/admin-sidebar/admin-sidebar.component.ts
+++ b/src/app/+admin/admin-sidebar/admin-sidebar.component.ts
@@ -93,7 +93,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
/**
* Initialize all menu sections and items for this menu
*/
- private createMenu() {
+ createMenu() {
const menuList = [
/* News */
{
@@ -145,11 +145,17 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
this.modalService.open(CreateItemParentSelectorComponent);
}
} as OnClickMenuItemModel,
- // model: {
- // type: MenuItemType.LINK,
- // text: 'menu.section.new_item',
- // link: '/submit'
- // } as LinkMenuItemModel,
+ },
+ {
+ id: 'new_process',
+ parentID: 'new',
+ active: false,
+ visible: true,
+ model: {
+ type: MenuItemType.LINK,
+ text: 'menu.section.new_process',
+ link: '/processes/new'
+ } as LinkMenuItemModel,
},
{
id: 'new_item_version',
@@ -439,6 +445,19 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
icon: 'cogs',
index: 9
},
+ /* Processes */
+ {
+ id: 'processes',
+ active: false,
+ visible: true,
+ model: {
+ type: MenuItemType.LINK,
+ text: 'menu.section.processes',
+ link: '/processes'
+ } as LinkMenuItemModel,
+ icon: 'terminal',
+ index: 10
+ },
/* Workflow */
{
id: 'workflow',
@@ -453,7 +472,9 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
index: 10
},
];
- menuList.forEach((menuSection) => this.menuService.addSection(this.menuID, menuSection));
+ menuList.forEach((menuSection) => this.menuService.addSection(this.menuID, Object.assign(menuSection, {
+ shouldPersistOnRouteChange: true
+ })));
}
diff --git a/src/app/+item-page/full/field-components/file-section/full-file-section.component.html b/src/app/+item-page/full/field-components/file-section/full-file-section.component.html
index b8ab9bdb41..7c1719eb82 100644
--- a/src/app/+item-page/full/field-components/file-section/full-file-section.component.html
+++ b/src/app/+item-page/full/field-components/file-section/full-file-section.component.html
@@ -21,9 +21,9 @@
diff --git a/src/app/+item-page/simple/field-components/file-section/file-section.component.html b/src/app/+item-page/simple/field-components/file-section/file-section.component.html
index 6533322e03..17e4a795e7 100644
--- a/src/app/+item-page/simple/field-components/file-section/file-section.component.html
+++ b/src/app/+item-page/simple/field-components/file-section/file-section.component.html
@@ -1,11 +1,11 @@
0" [label]="label | translate">
diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts
index aace169f6c..3bd2f64961 100644
--- a/src/app/app-routing.module.ts
+++ b/src/app/app-routing.module.ts
@@ -114,6 +114,7 @@ export function getDSOPath(dso: DSpaceObject): string {
path: PROFILE_MODULE_PATH,
loadChildren: './profile-page/profile-page.module#ProfilePageModule', canActivate: [AuthenticatedGuard]
},
+ { path: 'processes', loadChildren: './process-page/process-page.module#ProcessPageModule', canActivate: [AuthenticatedGuard] },
{ path: '**', pathMatch: 'full', component: PageNotFoundComponent },
],
{
diff --git a/src/app/community-list-page/community-list-service.spec.ts b/src/app/community-list-page/community-list-service.spec.ts
index accd0f23a5..6868b8d546 100644
--- a/src/app/community-list-page/community-list-service.spec.ts
+++ b/src/app/community-list-page/community-list-service.spec.ts
@@ -462,7 +462,7 @@ describe('CommunityListService', () => {
});
let flatNodeList;
describe('should return list containing only flatnode corresponding to that community', () => {
- beforeAll((done) => {
+ beforeEach((done) => {
service.transformCommunity(communityWithSubcoms, 0, null, null)
.pipe(take(1))
.subscribe((value) => {
diff --git a/src/app/core/auth/auth-request.service.ts b/src/app/core/auth/auth-request.service.ts
index 465fb69dd2..93f55389f9 100644
--- a/src/app/core/auth/auth-request.service.ts
+++ b/src/app/core/auth/auth-request.service.ts
@@ -1,12 +1,18 @@
import { Observable, of as observableOf, throwError as observableThrowError } from 'rxjs';
-import { distinctUntilChanged, filter, map, mergeMap, tap } from 'rxjs/operators';
-import { Inject, Injectable } from '@angular/core';
+import { distinctUntilChanged, filter, map, mergeMap, switchMap, tap } from 'rxjs/operators';
+import { Injectable } from '@angular/core';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { RequestService } from '../data/request.service';
-import { GlobalConfig } from '../../../config/global-config.interface';
import { isNotEmpty } from '../../shared/empty.util';
-import { AuthGetRequest, AuthPostRequest, GetRequest, PostRequest, RestRequest } from '../data/request.models';
-import { AuthStatusResponse, ErrorResponse } from '../cache/response.models';
+import {
+ AuthGetRequest,
+ AuthPostRequest,
+ GetRequest,
+ PostRequest,
+ RestRequest,
+ TokenPostRequest
+} from '../data/request.models';
+import { AuthStatusResponse, ErrorResponse, TokenResponse } from '../cache/response.models';
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
import { getResponseFromEntry } from '../shared/operators';
import { HttpClient } from '@angular/common/http';
@@ -15,6 +21,7 @@ import { HttpClient } from '@angular/common/http';
export class AuthRequestService {
protected linkName = 'authn';
protected browseEndpoint = '';
+ protected shortlivedtokensEndpoint = 'shortlivedtokens';
constructor(protected halService: HALEndpointService,
protected requestService: RequestService,
@@ -67,4 +74,19 @@ export class AuthRequestService {
mergeMap((request: GetRequest) => this.fetchRequest(request)),
distinctUntilChanged());
}
+
+ /**
+ * Send a POST request to retrieve a short-lived token which provides download access of restricted files
+ */
+ public getShortlivedToken(): Observable {
+ return this.halService.getEndpoint(`${this.linkName}/${this.shortlivedtokensEndpoint}`).pipe(
+ filter((href: string) => isNotEmpty(href)),
+ distinctUntilChanged(),
+ map((endpointURL: string) => new TokenPostRequest(this.requestService.generateRequestId(), endpointURL)),
+ tap((request: PostRequest) => this.requestService.configure(request)),
+ switchMap((request: PostRequest) => this.requestService.getByUUID(request.uuid)),
+ getResponseFromEntry(),
+ map((response: TokenResponse) => response.token)
+ );
+ }
}
diff --git a/src/app/core/auth/auth.service.spec.ts b/src/app/core/auth/auth.service.spec.ts
index 3b6fae4dd1..a15d604cc4 100644
--- a/src/app/core/auth/auth.service.spec.ts
+++ b/src/app/core/auth/auth.service.spec.ts
@@ -1,17 +1,14 @@
import { async, inject, TestBed } from '@angular/core/testing';
import { CommonModule } from '@angular/common';
import { ActivatedRoute, Router } from '@angular/router';
-
import { Store, StoreModule } from '@ngrx/store';
import { REQUEST } from '@nguniversal/express-engine/tokens';
import { of as observableOf } from 'rxjs';
-
import { authReducer, AuthState } from './auth.reducer';
import { NativeWindowRef, NativeWindowService } from '../services/window.service';
import { AuthService, IMPERSONATING_COOKIE } from './auth.service';
import { RouterStub } from '../../shared/testing/router.stub';
import { ActivatedRouteStub } from '../../shared/testing/active-router.stub';
-
import { CookieService } from '../services/cookie.service';
import { AuthRequestServiceStub } from '../../shared/testing/auth-request-service.stub';
import { AuthRequestService } from './auth-request.service';
@@ -49,6 +46,7 @@ describe('AuthService test', () => {
let storage: CookieService;
let token: AuthTokenInfo;
let authenticatedState;
+ let unAuthenticatedState;
let linkService;
function init() {
@@ -67,6 +65,13 @@ describe('AuthService test', () => {
authToken: token,
user: EPersonMock
};
+ unAuthenticatedState = {
+ authenticated: false,
+ loaded: true,
+ loading: false,
+ authToken: undefined,
+ user: undefined
+ };
authRequest = new AuthRequestServiceStub();
routeStub = new ActivatedRouteStub();
linkService = {
@@ -214,6 +219,12 @@ describe('AuthService test', () => {
});
});
+ it('should return the shortlived token when user is logged in', () => {
+ authService.getShortlivedToken().subscribe((shortlivedToken: string) => {
+ expect(shortlivedToken).toEqual(authRequest.mockShortLivedToken);
+ });
+ });
+
it('should return token object when it is valid', () => {
authService.hasValidAuthenticationToken().subscribe((tokenState: AuthTokenInfo) => {
expect(tokenState).toBe(token);
@@ -448,4 +459,44 @@ describe('AuthService test', () => {
});
});
});
+
+ describe('when user is not logged in', () => {
+ beforeEach(async(() => {
+ init();
+ TestBed.configureTestingModule({
+ imports: [
+ StoreModule.forRoot({ authReducer }, {
+ runtimeChecks: {
+ strictStateImmutability: false,
+ strictActionImmutability: false
+ }
+ })
+ ],
+ providers: [
+ { provide: AuthRequestService, useValue: authRequest },
+ { provide: REQUEST, useValue: {} },
+ { provide: Router, useValue: routerStub },
+ { provide: RouteService, useValue: routeServiceStub },
+ { provide: RemoteDataBuildService, useValue: linkService },
+ CookieService,
+ AuthService
+ ]
+ }).compileComponents();
+ }));
+
+ beforeEach(inject([CookieService, AuthRequestService, Store, Router, RouteService], (cookieService: CookieService, authReqService: AuthRequestService, store: Store, router: Router, routeService: RouteService) => {
+ store
+ .subscribe((state) => {
+ (state as any).core = Object.create({});
+ (state as any).core.auth = unAuthenticatedState;
+ });
+ authService = new AuthService({}, window, undefined, authReqService, mockEpersonDataService, router, routeService, cookieService, store);
+ }));
+
+ it('should return null for the shortlived token', () => {
+ authService.getShortlivedToken().subscribe((shortlivedToken: string) => {
+ expect(shortlivedToken).toBeNull();
+ });
+ });
+ });
});
diff --git a/src/app/core/auth/auth.service.ts b/src/app/core/auth/auth.service.ts
index f9c1fc2cb9..fe9828bc73 100644
--- a/src/app/core/auth/auth.service.ts
+++ b/src/app/core/auth/auth.service.ts
@@ -534,4 +534,14 @@ export class AuthService {
return this.getImpersonateID() === epersonId;
}
+ /**
+ * Get a short-lived token for appending to download urls of restricted files
+ * Returns null if the user isn't authenticated
+ */
+ getShortlivedToken(): Observable {
+ return this.isAuthenticated().pipe(
+ switchMap((authenticated) => authenticated ? this.authRequestService.getShortlivedToken() : observableOf(null))
+ );
+ }
+
}
diff --git a/src/app/core/auth/token-response-parsing.service.spec.ts b/src/app/core/auth/token-response-parsing.service.spec.ts
new file mode 100644
index 0000000000..35927708f6
--- /dev/null
+++ b/src/app/core/auth/token-response-parsing.service.spec.ts
@@ -0,0 +1,45 @@
+import { TokenResponseParsingService } from './token-response-parsing.service';
+import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
+import { TokenResponse } from '../cache/response.models';
+
+describe('TokenResponseParsingService', () => {
+ let service: TokenResponseParsingService;
+
+ beforeEach(() => {
+ service = new TokenResponseParsingService();
+ });
+
+ describe('parse', () => {
+ it('should return a TokenResponse containing the token', () => {
+ const data = {
+ payload: {
+ token: 'valid-token'
+ },
+ statusCode: 200,
+ statusText: 'OK'
+ } as DSpaceRESTV2Response;
+ const expected = new TokenResponse(data.payload.token, true, 200, 'OK');
+ expect(service.parse(undefined, data)).toEqual(expected);
+ });
+
+ it('should return an empty TokenResponse when payload doesn\'t contain a token', () => {
+ const data = {
+ payload: {},
+ statusCode: 200,
+ statusText: 'OK'
+ } as DSpaceRESTV2Response;
+ const expected = new TokenResponse(null, false, 200, 'OK');
+ expect(service.parse(undefined, data)).toEqual(expected);
+ });
+
+ it('should return an error TokenResponse when the response failed', () => {
+ const data = {
+ payload: {},
+ statusCode: 400,
+ statusText: 'BAD REQUEST'
+ } as DSpaceRESTV2Response;
+ const expected = new TokenResponse(null, false, 400, 'BAD REQUEST');
+ expect(service.parse(undefined, data)).toEqual(expected);
+ });
+ });
+});
diff --git a/src/app/core/auth/token-response-parsing.service.ts b/src/app/core/auth/token-response-parsing.service.ts
new file mode 100644
index 0000000000..a1b1e23aa4
--- /dev/null
+++ b/src/app/core/auth/token-response-parsing.service.ts
@@ -0,0 +1,23 @@
+import { ResponseParsingService } from '../data/parsing.service';
+import { RestRequest } from '../data/request.models';
+import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
+import { RestResponse, TokenResponse } from '../cache/response.models';
+import { isNotEmpty } from '../../shared/empty.util';
+import { Injectable } from '@angular/core';
+
+@Injectable()
+/**
+ * A ResponseParsingService used to parse DSpaceRESTV2Response coming from the REST API to a token string
+ * wrapped in a TokenResponse
+ */
+export class TokenResponseParsingService implements ResponseParsingService {
+
+ parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
+ if (isNotEmpty(data.payload) && isNotEmpty(data.payload.token) && (data.statusCode === 200)) {
+ return new TokenResponse(data.payload.token, true, data.statusCode, data.statusText);
+ } else {
+ return new TokenResponse(null, false, data.statusCode, data.statusText)
+ }
+ }
+
+}
diff --git a/src/app/core/breadcrumbs/i18n-breadcrumb.resolver.spec.ts b/src/app/core/breadcrumbs/i18n-breadcrumb.resolver.spec.ts
index a06abdc816..6b640a9db0 100644
--- a/src/app/core/breadcrumbs/i18n-breadcrumb.resolver.spec.ts
+++ b/src/app/core/breadcrumbs/i18n-breadcrumb.resolver.spec.ts
@@ -1,21 +1,38 @@
import { I18nBreadcrumbResolver } from './i18n-breadcrumb.resolver';
+import { URLCombiner } from '../url-combiner/url-combiner';
describe('I18nBreadcrumbResolver', () => {
describe('resolve', () => {
let resolver: I18nBreadcrumbResolver;
let i18nBreadcrumbService: any;
let i18nKey: string;
- let path: string;
+ let route: any;
+ let parentSegment;
+ let segment;
+ let expectedPath;
beforeEach(() => {
i18nKey = 'example.key';
- path = 'rest.com/path/to/breadcrumb';
+ parentSegment = 'path';
+ segment = 'breadcrumb';
+ route = {
+ data: { breadcrumbKey: i18nKey },
+ routeConfig: {
+ path: segment
+ },
+ parent: {
+ routeConfig: {
+ path: parentSegment
+ }
+ } as any
+ };
+ expectedPath = new URLCombiner(parentSegment, segment).toString();
i18nBreadcrumbService = {};
resolver = new I18nBreadcrumbResolver(i18nBreadcrumbService);
});
it('should resolve the breadcrumb config', () => {
- const resolvedConfig = resolver.resolve({ data: { breadcrumbKey: i18nKey }, url: [path], pathFromRoot: [{ url: [path] }] } as any, {} as any);
- const expectedConfig = { provider: i18nBreadcrumbService, key: i18nKey, url: path };
+ const resolvedConfig = resolver.resolve(route, {} as any);
+ const expectedConfig = { provider: i18nBreadcrumbService, key: i18nKey, url: expectedPath };
expect(resolvedConfig).toEqual(expectedConfig);
});
diff --git a/src/app/core/breadcrumbs/i18n-breadcrumb.resolver.ts b/src/app/core/breadcrumbs/i18n-breadcrumb.resolver.ts
index a6298628c7..cce36f590a 100644
--- a/src/app/core/breadcrumbs/i18n-breadcrumb.resolver.ts
+++ b/src/app/core/breadcrumbs/i18n-breadcrumb.resolver.ts
@@ -3,6 +3,7 @@ import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { I18nBreadcrumbsService } from './i18n-breadcrumbs.service';
import { hasNoValue } from '../../shared/empty.util';
+import { currentPathFromSnapshot } from '../../shared/utils/route.utils';
/**
* The class that resolves a BreadcrumbConfig object with an i18n key string for a route
@@ -25,17 +26,7 @@ export class I18nBreadcrumbResolver implements Resolve>
if (hasNoValue(key)) {
throw new Error('You provided an i18nBreadcrumbResolver for url \"' + route.url + '\" but no breadcrumbKey in the route\'s data')
}
- const fullPath = this.getResolvedUrl(route);
+ const fullPath = currentPathFromSnapshot(route);
return { provider: this.breadcrumbService, key: key, url: fullPath };
}
-
- /**
- * Resolve the full URL of an ActivatedRouteSnapshot
- * @param route
- */
- getResolvedUrl(route: ActivatedRouteSnapshot): string {
- return route.pathFromRoot
- .map((v) => v.url.map((segment) => segment.toString()).join('/'))
- .join('/');
- }
}
diff --git a/src/app/core/cache/response.models.ts b/src/app/core/cache/response.models.ts
index dc7af65c8d..5f19185d1c 100644
--- a/src/app/core/cache/response.models.ts
+++ b/src/app/core/cache/response.models.ts
@@ -167,6 +167,20 @@ export class AuthStatusResponse extends RestResponse {
}
}
+/**
+ * A REST Response containing a token
+ */
+export class TokenResponse extends RestResponse {
+ constructor(
+ public token: string,
+ public isSuccessful: boolean,
+ public statusCode: number,
+ public statusText: string
+ ) {
+ super(isSuccessful, statusCode, statusText);
+ }
+}
+
export class IntegrationSuccessResponse extends RestResponse {
constructor(
public dataDefinition: PaginatedList,
diff --git a/src/app/core/core.effects.ts b/src/app/core/core.effects.ts
index 9f4242cc9a..cd14a527e8 100644
--- a/src/app/core/core.effects.ts
+++ b/src/app/core/core.effects.ts
@@ -7,6 +7,7 @@ import { ServerSyncBufferEffects } from './cache/server-sync-buffer.effects';
import { ObjectUpdatesEffects } from './data/object-updates/object-updates.effects';
import { RouteEffects } from './services/route.effects';
import { RouterEffects } from './router/router.effects';
+import { MenuEffects } from '../shared/menu/menu.effects';
export const coreEffects = [
RequestEffects,
@@ -17,5 +18,6 @@ export const coreEffects = [
ServerSyncBufferEffects,
ObjectUpdatesEffects,
RouteEffects,
- RouterEffects
+ RouterEffects,
+ MenuEffects
];
diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts
index 300d33fda8..07ff1f9572 100644
--- a/src/app/core/core.module.ts
+++ b/src/app/core/core.module.ts
@@ -1,7 +1,6 @@
import { CommonModule } from '@angular/common';
import { HTTP_INTERCEPTORS, HttpClient } from '@angular/common/http';
import { ModuleWithProviders, NgModule, Optional, SkipSelf } from '@angular/core';
-
import { DynamicFormLayoutService, DynamicFormService, DynamicFormValidationService } from '@ng-dynamic-forms/core';
import { EffectsModule } from '@ngrx/effects';
@@ -138,11 +137,21 @@ import { VersionDataService } from './data/version-data.service';
import { VersionHistoryDataService } from './data/version-history-data.service';
import { Version } from './shared/version.model';
import { VersionHistory } from './shared/version-history.model';
+import { Script } from '../process-page/scripts/script.model';
+import { Process } from '../process-page/processes/process.model';
+import { ProcessDataService } from './data/processes/process-data.service';
+import { ScriptDataService } from './data/processes/script-data.service';
+import { ProcessFilesResponseParsingService } from './data/process-files-response-parsing.service';
import { WorkflowActionDataService } from './data/workflow-action-data.service';
import { WorkflowAction } from './tasks/models/workflow-action-object.model';
import { Registration } from './shared/registration.model';
import { MetadataSchemaDataService } from './data/metadata-schema-data.service';
import { MetadataFieldDataService } from './data/metadata-field-data.service';
+import { TokenResponseParsingService } from './auth/token-response-parsing.service';
+import { SubmissionCcLicenseDataService } from './submission/submission-cc-license-data.service';
+import { SubmissionCcLicence } from './submission/models/submission-cc-license.model';
+import { SubmissionCcLicenceUrl } from './submission/models/submission-cc-license-url.model';
+import { SubmissionCcLicenseUrlDataService } from './submission/submission-cc-license-url-data.service';
/**
* When not in production, endpoint responses can be mocked for testing purposes
@@ -209,6 +218,8 @@ const PROVIDERS = [
BrowseItemsResponseParsingService,
BrowseService,
ConfigResponseParsingService,
+ SubmissionCcLicenseDataService,
+ SubmissionCcLicenseUrlDataService,
SubmissionDefinitionsConfigService,
SubmissionFormsConfigService,
SubmissionRestService,
@@ -257,8 +268,12 @@ const PROVIDERS = [
LicenseDataService,
ItemTypeDataService,
WorkflowActionDataService,
+ ProcessDataService,
+ ScriptDataService,
+ ProcessFilesResponseParsingService,
MetadataSchemaDataService,
MetadataFieldDataService,
+ TokenResponseParsingService,
// register AuthInterceptor as HttpInterceptor
{
provide: HTTP_INTERCEPTORS,
@@ -291,6 +306,8 @@ export const models =
License,
WorkflowItem,
WorkspaceItem,
+ SubmissionCcLicence,
+ SubmissionCcLicenceUrl,
SubmissionDefinitionsModel,
SubmissionFormsModel,
SubmissionSectionModel,
@@ -307,6 +324,8 @@ export const models =
ItemType,
ExternalSource,
ExternalSourceEntry,
+ Script,
+ Process,
Version,
VersionHistory,
WorkflowAction,
diff --git a/src/app/core/data/process-files-response-parsing.service.ts b/src/app/core/data/process-files-response-parsing.service.ts
new file mode 100644
index 0000000000..0fa7c66869
--- /dev/null
+++ b/src/app/core/data/process-files-response-parsing.service.ts
@@ -0,0 +1,41 @@
+import { ResponseParsingService } from './parsing.service';
+import { RestRequest } from './request.models';
+import { GenericSuccessResponse, RestResponse } from '../cache/response.models';
+import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
+import { isEmpty, isNotEmpty } from '../../shared/empty.util';
+import { PaginatedList } from './paginated-list';
+import { PageInfo } from '../shared/page-info.model';
+import { Injectable } from '@angular/core';
+import { DSpaceSerializer } from '../dspace-rest-v2/dspace.serializer';
+import { Bitstream } from '../shared/bitstream.model';
+
+@Injectable()
+/**
+ * A ResponseParsingService used to parse DSpaceRESTV2Response coming from the REST API to a GenericSuccessResponse
+ * containing a PaginatedList of a process's output files
+ */
+export class ProcessFilesResponseParsingService implements ResponseParsingService {
+ parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
+ const payload = data.payload;
+
+ let page;
+ if (isNotEmpty(payload._embedded) && isNotEmpty(Object.keys(payload._embedded))) {
+ const bitstreams = new DSpaceSerializer(Bitstream).deserializeArray(payload._embedded[Object.keys(payload._embedded)[0]]);
+
+ if (isNotEmpty(bitstreams)) {
+ page = new PaginatedList(Object.assign(new PageInfo(), {
+ elementsPerPage: bitstreams.length,
+ totalElements: bitstreams.length,
+ totalPages: 1,
+ currentPage: 1
+ }), bitstreams);
+ }
+ }
+
+ if (isEmpty(page)) {
+ page = new PaginatedList(new PageInfo(), []);
+ }
+
+ return new GenericSuccessResponse(page, data.statusCode, data.statusText);
+ }
+}
diff --git a/src/app/core/data/processes/process-data.service.ts b/src/app/core/data/processes/process-data.service.ts
new file mode 100644
index 0000000000..48c1d502cc
--- /dev/null
+++ b/src/app/core/data/processes/process-data.service.ts
@@ -0,0 +1,70 @@
+import { Injectable } from '@angular/core';
+import { DataService } from '../data.service';
+import { RequestService } from '../request.service';
+import { RemoteDataBuildService } from '../../cache/builders/remote-data-build.service';
+import { Store } from '@ngrx/store';
+import { CoreState } from '../../core.reducers';
+import { ObjectCacheService } from '../../cache/object-cache.service';
+import { HALEndpointService } from '../../shared/hal-endpoint.service';
+import { NotificationsService } from '../../../shared/notifications/notifications.service';
+import { HttpClient } from '@angular/common/http';
+import { DefaultChangeAnalyzer } from '../default-change-analyzer.service';
+import { Process } from '../../../process-page/processes/process.model';
+import { dataService } from '../../cache/builders/build-decorators';
+import { PROCESS } from '../../../process-page/processes/process.resource-type';
+import { Observable } from 'rxjs/internal/Observable';
+import { map, switchMap } from 'rxjs/operators';
+import { ProcessFilesRequest, RestRequest } from '../request.models';
+import { configureRequest, filterSuccessfulResponses } from '../../shared/operators';
+import { GenericSuccessResponse } from '../../cache/response.models';
+import { PaginatedList } from '../paginated-list';
+import { Bitstream } from '../../shared/bitstream.model';
+import { RemoteData } from '../remote-data';
+
+@Injectable()
+@dataService(PROCESS)
+export class ProcessDataService extends DataService {
+ protected linkPath = 'processes';
+
+ constructor(
+ protected requestService: RequestService,
+ protected rdbService: RemoteDataBuildService,
+ protected store: Store,
+ protected objectCache: ObjectCacheService,
+ protected halService: HALEndpointService,
+ protected notificationsService: NotificationsService,
+ protected http: HttpClient,
+ protected comparator: DefaultChangeAnalyzer) {
+ super();
+ }
+
+ /**
+ * Get the endpoint for a process his files
+ * @param processId The ID of the process
+ */
+ getFilesEndpoint(processId: string): Observable {
+ return this.getBrowseEndpoint().pipe(
+ switchMap((href) => this.halService.getEndpoint('files', `${href}/${processId}`))
+ );
+ }
+
+ /**
+ * Get a process his output files
+ * @param processId The ID of the process
+ */
+ getFiles(processId: string): Observable>> {
+ const request$ = this.getFilesEndpoint(processId).pipe(
+ map((href) => new ProcessFilesRequest(this.requestService.generateRequestId(), href)),
+ configureRequest(this.requestService)
+ );
+ const requestEntry$ = request$.pipe(
+ switchMap((request: RestRequest) => this.requestService.getByHref(request.href))
+ );
+ const payload$ = requestEntry$.pipe(
+ filterSuccessfulResponses(),
+ map((response: GenericSuccessResponse>) => response.payload)
+ );
+
+ return this.rdbService.toRemoteDataObservable(requestEntry$, payload$);
+ }
+}
diff --git a/src/app/core/data/processes/script-data.service.ts b/src/app/core/data/processes/script-data.service.ts
new file mode 100644
index 0000000000..6600444ea0
--- /dev/null
+++ b/src/app/core/data/processes/script-data.service.ts
@@ -0,0 +1,61 @@
+import { Injectable } from '@angular/core';
+import { DataService } from '../data.service';
+import { RemoteDataBuildService } from '../../cache/builders/remote-data-build.service';
+import { Store } from '@ngrx/store';
+import { CoreState } from '../../core.reducers';
+import { ObjectCacheService } from '../../cache/object-cache.service';
+import { HALEndpointService } from '../../shared/hal-endpoint.service';
+import { NotificationsService } from '../../../shared/notifications/notifications.service';
+import { HttpClient } from '@angular/common/http';
+import { DefaultChangeAnalyzer } from '../default-change-analyzer.service';
+import { Script } from '../../../process-page/scripts/script.model';
+import { ProcessParameter } from '../../../process-page/processes/process-parameter.model';
+import { find, map, switchMap } from 'rxjs/operators';
+import { URLCombiner } from '../../url-combiner/url-combiner';
+import { MultipartPostRequest, RestRequest } from '../request.models';
+import { RequestService } from '../request.service';
+import { Observable } from 'rxjs';
+import { RequestEntry } from '../request.reducer';
+import { dataService } from '../../cache/builders/build-decorators';
+import { SCRIPT } from '../../../process-page/scripts/script.resource-type';
+
+@Injectable()
+@dataService(SCRIPT)
+export class ScriptDataService extends DataService