Skip to content

Commit

Permalink
Merge 3a73de9 into b535d16
Browse files Browse the repository at this point in the history
  • Loading branch information
artlowel committed Mar 31, 2020
2 parents b535d16 + 3a73de9 commit a101ed4
Show file tree
Hide file tree
Showing 84 changed files with 2,892 additions and 36 deletions.
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -125,7 +125,7 @@
"moment-range": "^4.0.2",
"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",
Expand Down
85 changes: 85 additions & 0 deletions resources/i18n/en.json5
Expand Up @@ -1316,6 +1316,8 @@

"menu.section.icon.pin": "Pin sidebar",

"menu.section.icon.processes": "Processes menu section",

"menu.section.icon.registries": "Registries menu section",

"menu.section.icon.statistics_task": "Statistics Task menu section",
Expand All @@ -1342,6 +1344,8 @@

"menu.section.new_item_version": "Item Version",

"menu.section.new_process": "Process",



"menu.section.pin": "Pin sidebar",
Expand All @@ -1350,6 +1354,10 @@



"menu.section.processes": "Processes",



"menu.section.registries": "Registries",

"menu.section.registries_format": "Format",
Expand Down Expand Up @@ -1534,6 +1542,83 @@



"process.new.select-parameters": "Parameters",

"process.new.cancel": "Cancel",

"process.new.submit": "Submit",

"process.new.select-script": "Script",

"process.new.select-script.placeholder": "Choose a script...",

"process.new.select-script.required": "Script is required",

"process.new.parameter.file.upload-button": "Select file...",

"process.new.parameter.file.required": "Please select a file",

"process.new.parameter.string.required": "Parameter value is required",

"process.new.parameter.type.value": "value",

"process.new.parameter.type.file": "file",

"process.new.parameter.required.missing": "The following parameters are required but still missing:",

"process.new.notification.success.title": "Success",

"process.new.notification.success.content": "The process was successfully created",

"process.new.notification.error.title": "Error",

"process.new.notification.error.content": "An error occurred while creating this process",

"process.new.header": "Create a new process",

"process.new.title": "Create a new process",

"process.new.breadcrumbs": "Create a new process",



"process.detail.arguments" : "Arguments",

"process.detail.arguments.empty" : "This process doesn't contain any arguments",

"process.detail.back" : "Back",

"process.detail.output" : "Process Output",

"process.detail.output.alert" : "Work in progress - Process output is not available yet",

"process.detail.output-files" : "Output Files",

"process.detail.output-files.empty" : "This process doesn't contain any output files",

"process.detail.script" : "Script",

"process.detail.title" : "Process: {{ id }} - {{ name }}",



"process.overview.table.finish" : "Finish time",

"process.overview.table.id" : "Process ID",

"process.overview.table.name" : "Name",

"process.overview.table.start" : "Start time",

"process.overview.table.status" : "Status",

"process.overview.table.user" : "User",

"process.overview.title": "Processes Overview",

"process.overview.breadcrumbs": "Processes Overview",


"profile.breadcrumbs": "Update Profile",

"profile.card.identify": "Identify",
Expand Down
28 changes: 23 additions & 5 deletions src/app/+admin/admin-sidebar/admin-sidebar.component.ts
Expand Up @@ -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',
Expand Down Expand Up @@ -439,6 +445,18 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
icon: 'cogs',
index: 9
},
{
id: 'processes',
active: false,
visible: true,
model: {
type: MenuItemType.LINK,
text: 'menu.section.processes',
link: '/processes'
} as LinkMenuItemModel,
icon: 'terminal',
index: 10
},
];
menuList.forEach((menuSection) => this.menuService.addSection(this.menuID, menuSection));

Expand Down
1 change: 1 addition & 0 deletions src/app/app-routing.module.ts
Expand Up @@ -86,6 +86,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 },
],
{
Expand Down
25 changes: 21 additions & 4 deletions 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] } 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);
});

Expand Down
3 changes: 2 additions & 1 deletion src/app/core/breadcrumbs/i18n-breadcrumb.resolver.ts
Expand Up @@ -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
Expand All @@ -23,7 +24,7 @@ export class I18nBreadcrumbResolver implements Resolve<BreadcrumbConfig<string>>
if (hasNoValue(key)) {
throw new Error('You provided an i18nBreadcrumbResolver for url \"' + route.url + '\" but no breadcrumbKey in the route\'s data')
}
const fullPath = route.url.join('');
const fullPath = currentPathFromSnapshot(route);
return { provider: this.breadcrumbService, key: key, url: fullPath };
}
}
17 changes: 11 additions & 6 deletions 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';
import { StoreModule } from '@ngrx/store';
Expand All @@ -14,11 +13,7 @@ import { FormService } from '../shared/form/form.service';
import { HostWindowService } from '../shared/host-window.service';
import { MenuService } from '../shared/menu/menu.service';
import { EndpointMockingRestService } from '../shared/mocks/dspace-rest-v2/endpoint-mocking-rest.service';
import {
MOCK_RESPONSE_MAP,
MockResponseMap,
mockResponseMap
} from '../shared/mocks/dspace-rest-v2/mocks/mock-response-map';
import { MOCK_RESPONSE_MAP, MockResponseMap, mockResponseMap } from '../shared/mocks/dspace-rest-v2/mocks/mock-response-map';
import { NotificationsService } from '../shared/notifications/notifications.service';
import { SelectableListService } from '../shared/object-list/selectable-list/selectable-list.service';
import { ObjectSelectService } from '../shared/object-select/object-select.service';
Expand Down Expand Up @@ -139,6 +134,11 @@ 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';

/**
* When not in production, endpoint responses can be mocked for testing purposes
Expand Down Expand Up @@ -259,6 +259,9 @@ const PROVIDERS = [
VersionHistoryDataService,
LicenseDataService,
ItemTypeDataService,
ProcessDataService,
ScriptDataService,
ProcessFilesResponseParsingService,
// register AuthInterceptor as HttpInterceptor
{
provide: HTTP_INTERCEPTORS,
Expand Down Expand Up @@ -307,6 +310,8 @@ export const models =
ItemType,
ExternalSource,
ExternalSourceEntry,
Script,
Process,
Version,
VersionHistory
];
Expand Down
41 changes: 41 additions & 0 deletions 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);
}
}
70 changes: 70 additions & 0 deletions 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<Process> {
protected linkPath = 'processes';

constructor(
protected requestService: RequestService,
protected rdbService: RemoteDataBuildService,
protected store: Store<CoreState>,
protected objectCache: ObjectCacheService,
protected halService: HALEndpointService,
protected notificationsService: NotificationsService,
protected http: HttpClient,
protected comparator: DefaultChangeAnalyzer<Process>) {
super();
}

/**
* Get the endpoint for a process his files
* @param processId The ID of the process
*/
getFilesEndpoint(processId: string): Observable<string> {
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<RemoteData<PaginatedList<Bitstream>>> {
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<PaginatedList<Bitstream>>) => response.payload)
);

return this.rdbService.toRemoteDataObservable(requestEntry$, payload$);
}
}

0 comments on commit a101ed4

Please sign in to comment.