Merge pull request #455 from atmire/Bitstream-format-registries

Bitstream format registries
This commit is contained in:
Art Lowel
2019-08-30 09:55:01 +02:00
committed by GitHub
34 changed files with 1998 additions and 256 deletions

View File

@@ -2,16 +2,47 @@
"404.help": "We can't find the page you're looking for. The page may have been moved or deleted. You can use the button below to get back to the home page. ", "404.help": "We can't find the page you're looking for. The page may have been moved or deleted. You can use the button below to get back to the home page. ",
"404.link.home-page": "Take me to the home page", "404.link.home-page": "Take me to the home page",
"404.page-not-found": "page not found", "404.page-not-found": "page not found",
"admin.registries.bitstream-formats.create.failure.content": "An error occurred while creating the new bitstream format.",
"admin.registries.bitstream-formats.create.failure.head": "Failure",
"admin.registries.bitstream-formats.create.head": "Create Bitstream format",
"admin.registries.bitstream-formats.create.new": "Add a new bitstream format",
"admin.registries.bitstream-formats.create.success.content": "The new bitstream format was successfully created.",
"admin.registries.bitstream-formats.create.success.head": "Success",
"admin.registries.bitstream-formats.delete.failure.amount": "Failed to remove {{ amount }} format(s)",
"admin.registries.bitstream-formats.delete.failure.head": "Failure",
"admin.registries.bitstream-formats.delete.success.amount": "Successfully removed {{ amount }} format(s)",
"admin.registries.bitstream-formats.delete.success.head": "Success",
"admin.registries.bitstream-formats.description": "This list of bitstream formats provides information about known formats and their support level.", "admin.registries.bitstream-formats.description": "This list of bitstream formats provides information about known formats and their support level.",
"admin.registries.bitstream-formats.formats.no-items": "No bitstream formats to show.", "admin.registries.bitstream-formats.edit.description.hint": "",
"admin.registries.bitstream-formats.formats.table.internal": "internal", "admin.registries.bitstream-formats.edit.description.label": "Description",
"admin.registries.bitstream-formats.formats.table.mimetype": "MIME Type", "admin.registries.bitstream-formats.edit.extensions.hint": "Extensions are file extensions that are used to automatically identify the format of uploaded files. You can enter several extensions for each format.",
"admin.registries.bitstream-formats.formats.table.name": "Name", "admin.registries.bitstream-formats.edit.extensions.label": "File extensions",
"admin.registries.bitstream-formats.formats.table.supportLevel.0": "Unknown", "admin.registries.bitstream-formats.edit.extensions.placeholder": "Enter a file extenstion without the dot",
"admin.registries.bitstream-formats.formats.table.supportLevel.1": "Known", "admin.registries.bitstream-formats.edit.failure.content": "An error occurred while editing the bitstream format.",
"admin.registries.bitstream-formats.formats.table.supportLevel.2": "Support", "admin.registries.bitstream-formats.edit.failure.head": "Failure",
"admin.registries.bitstream-formats.formats.table.supportLevel.head": "Support Level", "admin.registries.bitstream-formats.edit.head": "Bitstream format: {{ format }}",
"admin.registries.bitstream-formats.edit.internal.hint": "Formats marked as internal are are hidden from the user, and used for administrative purposes.",
"admin.registries.bitstream-formats.edit.internal.label": "Internal",
"admin.registries.bitstream-formats.edit.mimetype.hint": "The MIME type associated with this format, does not have to be unique.",
"admin.registries.bitstream-formats.edit.mimetype.label": "MIME Type",
"admin.registries.bitstream-formats.edit.shortDescription.hint": "A unique name for this format, (e.g. Microsoft Word XP or Microsoft Word 2000)",
"admin.registries.bitstream-formats.edit.shortDescription.label": "Name",
"admin.registries.bitstream-formats.edit.success.content": "The bitstream format was successfully edited.",
"admin.registries.bitstream-formats.edit.success.head": "Success",
"admin.registries.bitstream-formats.edit.supportLevel.hint": "The level of support your institution pledges for this format.",
"admin.registries.bitstream-formats.edit.supportLevel.label": "Support level",
"admin.registries.bitstream-formats.head": "Bitstream Format Registry", "admin.registries.bitstream-formats.head": "Bitstream Format Registry",
"admin.registries.bitstream-formats.no-items": "No bitstream formats to show.",
"admin.registries.bitstream-formats.table.delete": "Delete selected",
"admin.registries.bitstream-formats.table.deselect-all": "Deselect all",
"admin.registries.bitstream-formats.table.internal": "internal",
"admin.registries.bitstream-formats.table.mimetype": "MIME Type",
"admin.registries.bitstream-formats.table.name": "Name",
"admin.registries.bitstream-formats.table.return": "Return",
"admin.registries.bitstream-formats.table.supportLevel.KNOWN": "Known",
"admin.registries.bitstream-formats.table.supportLevel.SUPPORTED": "Supported",
"admin.registries.bitstream-formats.table.supportLevel.UNKNOWN": "Unknown",
"admin.registries.bitstream-formats.table.supportLevel.head": "Support Level",
"admin.registries.bitstream-formats.title": "DSpace Angular :: Bitstream Format Registry", "admin.registries.bitstream-formats.title": "DSpace Angular :: Bitstream Format Registry",
"admin.registries.metadata.description": "The metadata registry maintains a list of all metadata fields available in the repository. These fields may be divided amongst multiple schemas. However, DSpace requires the qualified Dublin Core schema.", "admin.registries.metadata.description": "The metadata registry maintains a list of all metadata fields available in the repository. These fields may be divided amongst multiple schemas. However, DSpace requires the qualified Dublin Core schema.",
"admin.registries.metadata.form.create": "Create metadata schema", "admin.registries.metadata.form.create": "Create metadata schema",

View File

@@ -2,14 +2,29 @@ import { MetadataRegistryComponent } from './metadata-registry/metadata-registry
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { MetadataSchemaComponent } from './metadata-schema/metadata-schema.component'; import { MetadataSchemaComponent } from './metadata-schema/metadata-schema.component';
import { BitstreamFormatsComponent } from './bitstream-formats/bitstream-formats.component'; import { URLCombiner } from '../../core/url-combiner/url-combiner';
import { getRegistriesModulePath } from '../admin-routing.module';
const BITSTREAMFORMATS_MODULE_PATH = 'bitstream-formats';
export function getBitstreamFormatsModulePath() {
return new URLCombiner(getRegistriesModulePath(), BITSTREAMFORMATS_MODULE_PATH).toString();
}
@NgModule({ @NgModule({
imports: [ imports: [
RouterModule.forChild([ RouterModule.forChild([
{path: 'metadata', component: MetadataRegistryComponent, data: {title: 'admin.registries.metadata.title'}}, {path: 'metadata', component: MetadataRegistryComponent, data: {title: 'admin.registries.metadata.title'}},
{ path: 'metadata/:schemaName', component: MetadataSchemaComponent, data: { title: 'admin.registries.schema.title' } }, {
{ path: 'bitstream-formats', component: BitstreamFormatsComponent, data: { title: 'admin.registries.bitstream-formats.title' } }, path: 'metadata/:schemaName',
component: MetadataSchemaComponent,
data: {title: 'admin.registries.schema.title'}
},
{
path: BITSTREAMFORMATS_MODULE_PATH,
loadChildren: './bitstream-formats/bitstream-formats.module#BitstreamFormatsModule',
data: {title: 'admin.registries.bitstream-formats.title'}
},
]) ])
] ]
}) })

View File

@@ -5,10 +5,10 @@ import { CommonModule } from '@angular/common';
import { MetadataSchemaComponent } from './metadata-schema/metadata-schema.component'; import { MetadataSchemaComponent } from './metadata-schema/metadata-schema.component';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { BitstreamFormatsComponent } from './bitstream-formats/bitstream-formats.component';
import { SharedModule } from '../../shared/shared.module'; import { SharedModule } from '../../shared/shared.module';
import { MetadataSchemaFormComponent } from './metadata-registry/metadata-schema-form/metadata-schema-form.component'; import { MetadataSchemaFormComponent } from './metadata-registry/metadata-schema-form/metadata-schema-form.component';
import { MetadataFieldFormComponent } from './metadata-schema/metadata-field-form/metadata-field-form.component'; import { MetadataFieldFormComponent } from './metadata-schema/metadata-field-form/metadata-field-form.component';
import { BitstreamFormatsModule } from './bitstream-formats/bitstream-formats.module';
@NgModule({ @NgModule({
imports: [ imports: [
@@ -16,12 +16,12 @@ import {MetadataFieldFormComponent} from './metadata-schema/metadata-field-form/
SharedModule, SharedModule,
RouterModule, RouterModule,
TranslateModule, TranslateModule,
BitstreamFormatsModule,
AdminRegistriesRoutingModule AdminRegistriesRoutingModule
], ],
declarations: [ declarations: [
MetadataRegistryComponent, MetadataRegistryComponent,
MetadataSchemaComponent, MetadataSchemaComponent,
BitstreamFormatsComponent,
MetadataSchemaFormComponent, MetadataSchemaFormComponent,
MetadataFieldFormComponent MetadataFieldFormComponent
], ],

View File

@@ -0,0 +1,11 @@
<div class="container">
<div class="row">
<div class="col-12 mb-4">
<h2 id="sub-header"
class="border-bottom mb-2">{{ 'admin.registries.bitstream-formats.create.new' | translate }}</h2>
<ds-bitstream-format-form (updatedFormat)="createBitstreamFormat($event)"></ds-bitstream-format-form>
</div>
</div>
</div>

View File

@@ -0,0 +1,106 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CommonModule } from '@angular/common';
import { RouterTestingModule } from '@angular/router/testing';
import { TranslateModule } from '@ngx-translate/core';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { Router } from '@angular/router';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { RouterStub } from '../../../../shared/testing/router-stub';
import { of as observableOf } from 'rxjs';
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service-stub';
import { BitstreamFormatDataService } from '../../../../core/data/bitstream-format-data.service';
import { RestResponse } from '../../../../core/cache/response.models';
import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model';
import { BitstreamFormatSupportLevel } from '../../../../core/shared/bitstream-format-support-level';
import { ResourceType } from '../../../../core/shared/resource-type';
import { AddBitstreamFormatComponent } from './add-bitstream-format.component';
describe('AddBitstreamFormatComponent', () => {
let comp: AddBitstreamFormatComponent;
let fixture: ComponentFixture<AddBitstreamFormatComponent>;
const bitstreamFormat = new BitstreamFormat();
bitstreamFormat.uuid = 'test-uuid-1';
bitstreamFormat.id = 'test-uuid-1';
bitstreamFormat.shortDescription = 'Unknown';
bitstreamFormat.description = 'Unknown data format';
bitstreamFormat.mimetype = 'application/octet-stream';
bitstreamFormat.supportLevel = BitstreamFormatSupportLevel.Unknown;
bitstreamFormat.internal = false;
bitstreamFormat.extensions = null;
let router;
let notificationService: NotificationsServiceStub;
let bitstreamFormatDataService: BitstreamFormatDataService;
const initAsync = () => {
router = new RouterStub();
notificationService = new NotificationsServiceStub();
bitstreamFormatDataService = jasmine.createSpyObj('bitstreamFormatDataService', {
createBitstreamFormat: observableOf(new RestResponse(true, 200, 'Success')),
clearBitStreamFormatRequests: observableOf(null)
});
TestBed.configureTestingModule({
imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
declarations: [AddBitstreamFormatComponent],
providers: [
{provide: Router, useValue: router},
{provide: NotificationsService, useValue: notificationService},
{provide: BitstreamFormatDataService, useValue: bitstreamFormatDataService},
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
}).compileComponents();
};
const initBeforeEach = () => {
fixture = TestBed.createComponent(AddBitstreamFormatComponent);
comp = fixture.componentInstance;
fixture.detectChanges();
};
describe('createBitstreamFormat success', () => {
beforeEach(async(initAsync));
beforeEach(initBeforeEach);
it('should send the updated form to the service, show a notification and navigate to ', () => {
comp.createBitstreamFormat(bitstreamFormat);
expect(bitstreamFormatDataService.createBitstreamFormat).toHaveBeenCalledWith(bitstreamFormat);
expect(notificationService.success).toHaveBeenCalled();
expect(router.navigate).toHaveBeenCalledWith(['/admin/registries/bitstream-formats']);
});
});
describe('createBitstreamFormat error', () => {
beforeEach(async(() => {
router = new RouterStub();
notificationService = new NotificationsServiceStub();
bitstreamFormatDataService = jasmine.createSpyObj('bitstreamFormatDataService', {
createBitstreamFormat: observableOf(new RestResponse(false, 400, 'Bad Request')),
clearBitStreamFormatRequests: observableOf(null)
});
TestBed.configureTestingModule({
imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
declarations: [AddBitstreamFormatComponent],
providers: [
{provide: Router, useValue: router},
{provide: NotificationsService, useValue: notificationService},
{provide: BitstreamFormatDataService, useValue: bitstreamFormatDataService},
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
}).compileComponents();
}));
beforeEach(initBeforeEach);
it('should send the updated form to the service, show a notification and navigate to ', () => {
comp.createBitstreamFormat(bitstreamFormat);
expect(bitstreamFormatDataService.createBitstreamFormat).toHaveBeenCalledWith(bitstreamFormat);
expect(notificationService.error).toHaveBeenCalled();
expect(router.navigate).not.toHaveBeenCalled();
});
});
});

View File

@@ -0,0 +1,49 @@
import { take } from 'rxjs/operators';
import { Router } from '@angular/router';
import { Component } from '@angular/core';
import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model';
import { BitstreamFormatDataService } from '../../../../core/data/bitstream-format-data.service';
import { RestResponse } from '../../../../core/cache/response.models';
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
import { getBitstreamFormatsModulePath } from '../../admin-registries-routing.module';
import { TranslateService } from '@ngx-translate/core';
/**
* This component renders the page to create a new bitstream format.
*/
@Component({
selector: 'ds-add-bitstream-format',
templateUrl: './add-bitstream-format.component.html',
})
export class AddBitstreamFormatComponent {
constructor(
private router: Router,
private notificationService: NotificationsService,
private translateService: TranslateService,
private bitstreamFormatDataService: BitstreamFormatDataService,
) {
}
/**
* Creates a new bitstream format based on the provided bitstream format emitted by the form.
* When successful, a success notification will be shown and the user will be navigated back to the overview page.
* When failed, an error notification will be shown.
* @param bitstreamFormat
*/
createBitstreamFormat(bitstreamFormat: BitstreamFormat) {
this.bitstreamFormatDataService.createBitstreamFormat(bitstreamFormat).pipe(take(1)
).subscribe((response: RestResponse) => {
if (response.isSuccessful) {
this.notificationService.success(this.translateService.get('admin.registries.bitstream-formats.create.success.head'),
this.translateService.get('admin.registries.bitstream-formats.create.success.content'));
this.router.navigate([getBitstreamFormatsModulePath()]);
this.bitstreamFormatDataService.clearBitStreamFormatRequests().subscribe();
} else {
this.notificationService.error(this.translateService.get('admin.registries.bitstream-formats.create.failure.head'),
this.translateService.get('admin.registries.bitstream-formats.create.failure.content'));
}
}
);
}
}

View File

@@ -0,0 +1,64 @@
import { Action } from '@ngrx/store';
import { type } from '../../../shared/ngrx/type';
import { BitstreamFormat } from '../../../core/shared/bitstream-format.model';
/**
* 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 BitstreamFormatsRegistryActionTypes = {
SELECT_FORMAT: type('dspace/bitstream-formats-registry/SELECT_FORMAT'),
DESELECT_FORMAT: type('dspace/bitstream-formats-registry/DESELECT_FORMAT'),
DESELECT_ALL_FORMAT: type('dspace/bitstream-formats-registry/DESELECT_ALL_FORMAT')
};
/* tslint:disable:max-classes-per-file */
/**
* Used to select a single bitstream format in the bitstream format registry
*/
export class BitstreamFormatsRegistrySelectAction implements Action {
type = BitstreamFormatsRegistryActionTypes.SELECT_FORMAT;
bitstreamFormat: BitstreamFormat;
constructor(bitstreamFormat: BitstreamFormat) {
this.bitstreamFormat = bitstreamFormat;
}
}
/**
* Used to deselect a single bitstream format in the bitstream format registry
*/
export class BitstreamFormatsRegistryDeselectAction implements Action {
type = BitstreamFormatsRegistryActionTypes.DESELECT_FORMAT;
bitstreamFormat: BitstreamFormat;
constructor(bitstreamFormat: BitstreamFormat) {
this.bitstreamFormat = bitstreamFormat;
}
}
/**
* Used to deselect all bitstream formats in the bitstream format registry
*/
export class BitstreamFormatsRegistryDeselectAllAction implements Action {
type = BitstreamFormatsRegistryActionTypes.DESELECT_ALL_FORMAT;
}
/* 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
* These are all the actions to perform on the bitstream format registry state
*/
export type BitstreamFormatsRegistryAction
= BitstreamFormatsRegistrySelectAction
| BitstreamFormatsRegistryDeselectAction
| BitstreamFormatsRegistryDeselectAllAction

View File

@@ -0,0 +1,83 @@
import { Action } from '@ngrx/store';
import { BitstreamFormat } from '../../../core/shared/bitstream-format.model';
import { bitstreamFormatReducer, BitstreamFormatRegistryState } from './bitstream-format.reducers';
import {
BitstreamFormatsRegistryDeselectAction,
BitstreamFormatsRegistryDeselectAllAction,
BitstreamFormatsRegistrySelectAction
} from './bitstream-format.actions';
const bitstreamFormat1: BitstreamFormat = new BitstreamFormat();
bitstreamFormat1.id = 'test-uuid-1';
bitstreamFormat1.shortDescription = 'test-short-1';
const bitstreamFormat2: BitstreamFormat = new BitstreamFormat();
bitstreamFormat2.id = 'test-uuid-2';
bitstreamFormat2.shortDescription = 'test-short-2';
const initialState: BitstreamFormatRegistryState = {
selectedBitstreamFormats: []
};
const bitstream1SelectedState: BitstreamFormatRegistryState = {
selectedBitstreamFormats: [bitstreamFormat1]
};
const bitstream1and2SelectedState: BitstreamFormatRegistryState = {
selectedBitstreamFormats: [bitstreamFormat1, bitstreamFormat2]
};
describe('BitstreamFormatReducer', () => {
describe('BitstreamFormatsRegistryActionTypes.SELECT_FORMAT', () => {
it('should add the format to the list of selected formats when initial list is empty', () => {
const state = initialState;
const action = new BitstreamFormatsRegistrySelectAction(bitstreamFormat1);
const newState = bitstreamFormatReducer(state, action);
expect(newState).toEqual(bitstream1SelectedState);
});
it('should add the format to the list of selected formats when formats are already present', () => {
const state = bitstream1SelectedState;
const action = new BitstreamFormatsRegistrySelectAction(bitstreamFormat2);
const newState = bitstreamFormatReducer(state, action);
expect(newState).toEqual(bitstream1and2SelectedState);
});
});
describe('BitstreamFormatsRegistryActionTypes.DESELECT_FORMAT', () => {
it('should deselect a format', () => {
const state = bitstream1and2SelectedState;
const action = new BitstreamFormatsRegistryDeselectAction(bitstreamFormat2);
const newState = bitstreamFormatReducer(state, action);
expect(newState).toEqual(bitstream1SelectedState);
});
});
describe('BitstreamFormatsRegistryActionTypes.DESELECT_ALL_FORMAT', () => {
it('should deselect all formats', () => {
const state = bitstream1and2SelectedState;
const action = new BitstreamFormatsRegistryDeselectAllAction();
const newState = bitstreamFormatReducer(state, action);
expect(newState).toEqual(initialState);
});
});
describe('Invalid action', () => {
it('should return the current state', () => {
const state = initialState;
const action = new NullAction();
const newState = bitstreamFormatReducer(state, action);
expect(newState).toEqual(state);
});
});
});
class NullAction implements Action {
type = null;
constructor() {
// empty constructor
}
}

View File

@@ -0,0 +1,55 @@
import { BitstreamFormat } from '../../../core/shared/bitstream-format.model';
import {
BitstreamFormatsRegistryAction,
BitstreamFormatsRegistryActionTypes,
BitstreamFormatsRegistryDeselectAction,
BitstreamFormatsRegistrySelectAction
} from './bitstream-format.actions';
/**
* The bitstream format registry state.
* @interface BitstreamFormatRegistryState
*/
export interface BitstreamFormatRegistryState {
selectedBitstreamFormats: BitstreamFormat[];
}
/**
* The initial state.
*/
const initialState: BitstreamFormatRegistryState = {
selectedBitstreamFormats: [],
};
/**
* Reducer that handles BitstreamFormatsRegistryActions to modify the bitstream format registry state
* @param state The current BitstreamFormatRegistryState
* @param action The BitstreamFormatsRegistryAction to perform on the state
*/
export function bitstreamFormatReducer(state = initialState, action: BitstreamFormatsRegistryAction): BitstreamFormatRegistryState {
switch (action.type) {
case BitstreamFormatsRegistryActionTypes.SELECT_FORMAT: {
return Object.assign({}, state, {
selectedBitstreamFormats: [...state.selectedBitstreamFormats, (action as BitstreamFormatsRegistrySelectAction).bitstreamFormat]
});
}
case BitstreamFormatsRegistryActionTypes.DESELECT_FORMAT: {
return Object.assign({}, state, {
selectedBitstreamFormats: state.selectedBitstreamFormats.filter(
(selectedBitstreamFormats) => selectedBitstreamFormats !== (action as BitstreamFormatsRegistryDeselectAction).bitstreamFormat
)
});
}
case BitstreamFormatsRegistryActionTypes.DESELECT_ALL_FORMAT: {
return Object.assign({}, state, {
selectedBitstreamFormats: []
});
}
default:
return state;
}
}

View File

@@ -0,0 +1,37 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { BitstreamFormatsResolver } from './bitstream-formats.resolver';
import { EditBitstreamFormatComponent } from './edit-bitstream-format/edit-bitstream-format.component';
import { BitstreamFormatsComponent } from './bitstream-formats.component';
import { AddBitstreamFormatComponent } from './add-bitstream-format/add-bitstream-format.component';
const BITSTREAMFORMAT_EDIT_PATH = ':id/edit';
const BITSTREAMFORMAT_ADD_PATH = 'add';
@NgModule({
imports: [
RouterModule.forChild([
{
path: '',
component: BitstreamFormatsComponent
},
{
path: BITSTREAMFORMAT_ADD_PATH,
component: AddBitstreamFormatComponent,
},
{
path: BITSTREAMFORMAT_EDIT_PATH,
component: EditBitstreamFormatComponent,
resolve: {
bitstreamFormat: BitstreamFormatsResolver
}
},
])
],
providers: [
BitstreamFormatsResolver,
]
})
export class BitstreamFormatsRoutingModule {
}

View File

@@ -4,11 +4,13 @@
<h2 id="header" class="border-bottom pb-2 ">{{'admin.registries.bitstream-formats.head' | translate}}</h2> <h2 id="header" class="border-bottom pb-2 ">{{'admin.registries.bitstream-formats.head' | translate}}</h2>
<p id="description" class="pb-2">{{'admin.registries.bitstream-formats.description' | translate}}</p> <p id="description">{{'admin.registries.bitstream-formats.description' | translate}}</p>
<p id="create-new" class="mb-2"><a [routerLink]="'add'" class="btn btn-success">{{'admin.registries.bitstream-formats.create.new' | translate}}</a></p>
<ds-pagination <ds-pagination
*ngIf="(bitstreamFormats | async)?.payload?.totalElements > 0" *ngIf="(bitstreamFormats | async)?.payload?.totalElements > 0"
[paginationOptions]="config" [paginationOptions]="pageConfig"
[pageInfoState]="(bitstreamFormats | async)?.payload" [pageInfoState]="(bitstreamFormats | async)?.payload"
[collectionSize]="(bitstreamFormats | async)?.payload?.totalElements" [collectionSize]="(bitstreamFormats | async)?.payload?.totalElements"
[hideGear]="true" [hideGear]="true"
@@ -18,25 +20,38 @@
<table id="formats" class="table table-striped table-hover"> <table id="formats" class="table table-striped table-hover">
<thead> <thead>
<tr> <tr>
<th scope="col">{{'admin.registries.bitstream-formats.formats.table.name' | translate}}</th> <th scope="col"></th>
<th scope="col">{{'admin.registries.bitstream-formats.formats.table.mimetype' | translate}}</th> <th scope="col">{{'admin.registries.bitstream-formats.table.name' | translate}}</th>
<th scope="col">{{'admin.registries.bitstream-formats.formats.table.supportLevel.head' | translate}}</th> <th scope="col">{{'admin.registries.bitstream-formats.table.mimetype' | translate}}</th>
<th scope="col">{{'admin.registries.bitstream-formats.table.supportLevel.head' | translate}}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr *ngFor="let bitstreamFormat of (bitstreamFormats | async)?.payload?.page"> <tr *ngFor="let bitstreamFormat of (bitstreamFormats | async)?.payload?.page">
<td>{{bitstreamFormat.shortDescription}}</td> <td>
<td>{{bitstreamFormat.mimetype}} <span *ngIf="bitstreamFormat.internal">({{'admin.registries.bitstream-formats.formats.table.internal' | translate}})</span></td> <label>
<td>{{'admin.registries.bitstream-formats.formats.table.supportLevel.'+bitstreamFormat.supportLevel | translate}}</td> <input type="checkbox"
[checked]="isSelected(bitstreamFormat) | async"
(change)="selectBitStreamFormat(bitstreamFormat, $event)"
>
</label>
</td>
<td><a [routerLink]="['/admin/registries/bitstream-formats', bitstreamFormat.id, 'edit']">{{bitstreamFormat.shortDescription}}</a></td>
<td><a [routerLink]="['/admin/registries/bitstream-formats', bitstreamFormat.id, 'edit']">{{bitstreamFormat.mimetype}} <span *ngIf="bitstreamFormat.internal">({{'admin.registries.bitstream-formats.table.internal' | translate}})</span></a></td>
<td><a [routerLink]="['/admin/registries/bitstream-formats', bitstreamFormat.id, 'edit']">{{'admin.registries.bitstream-formats.table.supportLevel.'+bitstreamFormat.supportLevel | translate}}</a></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div> </div>
</ds-pagination> </ds-pagination>
<div *ngIf="(bitstreamFormats | async)?.payload?.totalElements == 0" class="alert alert-info" role="alert"> <div *ngIf="(bitstreamFormats | async)?.payload?.totalElements == 0" class="alert alert-info" role="alert">
{{'admin.registries.bitstream-formats.formats.no-items' | translate}} {{'admin.registries.bitstream-formats.no-items' | translate}}
</div> </div>
<div>
<button *ngIf="(bitstreamFormats | async)?.payload?.page?.length > 0" class="btn btn-primary deselect" (click)="deselectAll()">{{'admin.registries.bitstream-formats.table.deselect-all' | translate}}</button>
<button *ngIf="(bitstreamFormats | async)?.payload?.page?.length > 0" type="submit" class="btn btn-danger float-right" (click)="deleteFormats()">{{'admin.registries.bitstream-formats.table.delete' | translate}}</button>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,6 +1,5 @@
import { BitstreamFormatsComponent } from './bitstream-formats.component'; import { BitstreamFormatsComponent } from './bitstream-formats.component';
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { RegistryService } from '../../../core/registry/registry.service';
import { of as observableOf } from 'rxjs'; import { of as observableOf } from 'rxjs';
import { RemoteData } from '../../../core/data/remote-data'; import { RemoteData } from '../../../core/data/remote-data';
import { PaginatedList } from '../../../core/data/paginated-list'; import { PaginatedList } from '../../../core/data/paginated-list';
@@ -13,68 +12,105 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { EnumKeysPipe } from '../../../shared/utils/enum-keys-pipe'; import { EnumKeysPipe } from '../../../shared/utils/enum-keys-pipe';
import { HostWindowService } from '../../../shared/host-window.service'; import { HostWindowService } from '../../../shared/host-window.service';
import { HostWindowServiceStub } from '../../../shared/testing/host-window-service-stub'; import { HostWindowServiceStub } from '../../../shared/testing/host-window-service-stub';
import { createSuccessfulRemoteDataObject$ } from '../../../shared/testing/utils'; import { BitstreamFormatDataService } from '../../../core/data/bitstream-format-data.service';
import { NotificationsService } from '../../../shared/notifications/notifications.service';
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service-stub';
import { BitstreamFormat } from '../../../core/shared/bitstream-format.model';
import { BitstreamFormatSupportLevel } from '../../../core/shared/bitstream-format-support-level';
import { cold, getTestScheduler, hot } from 'jasmine-marbles';
import { TestScheduler } from 'rxjs/testing';
describe('BitstreamFormatsComponent', () => { describe('BitstreamFormatsComponent', () => {
let comp: BitstreamFormatsComponent; let comp: BitstreamFormatsComponent;
let fixture: ComponentFixture<BitstreamFormatsComponent>; let fixture: ComponentFixture<BitstreamFormatsComponent>;
let registryService: RegistryService; let bitstreamFormatService;
const mockFormatsList = [ let scheduler: TestScheduler;
{ let notificationsServiceStub;
shortDescription: 'Unknown',
description: 'Unknown data format', const bitstreamFormat1 = new BitstreamFormat();
mimetype: 'application/octet-stream', bitstreamFormat1.uuid = 'test-uuid-1';
supportLevel: 0, bitstreamFormat1.id = 'test-uuid-1';
internal: false, bitstreamFormat1.shortDescription = 'Unknown';
extensions: null bitstreamFormat1.description = 'Unknown data format';
}, bitstreamFormat1.mimetype = 'application/octet-stream';
{ bitstreamFormat1.supportLevel = BitstreamFormatSupportLevel.Unknown;
shortDescription: 'License', bitstreamFormat1.internal = false;
description: 'Item-specific license agreed upon to submission', bitstreamFormat1.extensions = null;
mimetype: 'text/plain; charset=utf-8',
supportLevel: 1, const bitstreamFormat2 = new BitstreamFormat();
internal: true, bitstreamFormat2.uuid = 'test-uuid-2';
extensions: null bitstreamFormat2.id = 'test-uuid-2';
}, bitstreamFormat2.shortDescription = 'License';
{ bitstreamFormat2.description = 'Item-specific license agreed upon to submission';
shortDescription: 'CC License', bitstreamFormat2.mimetype = 'text/plain; charset=utf-8';
description: 'Item-specific Creative Commons license agreed upon to submission', bitstreamFormat2.supportLevel = BitstreamFormatSupportLevel.Known;
mimetype: 'text/html; charset=utf-8', bitstreamFormat2.internal = true;
supportLevel: 2, bitstreamFormat2.extensions = null;
internal: true,
extensions: null const bitstreamFormat3 = new BitstreamFormat();
}, bitstreamFormat3.uuid = 'test-uuid-3';
{ bitstreamFormat3.id = 'test-uuid-3';
shortDescription: 'Adobe PDF', bitstreamFormat3.shortDescription = 'CC License';
description: 'Adobe Portable Document Format', bitstreamFormat3.description = 'Item-specific Creative Commons license agreed upon to submission';
mimetype: 'application/pdf', bitstreamFormat3.mimetype = 'text/html; charset=utf-8';
supportLevel: 0, bitstreamFormat3.supportLevel = BitstreamFormatSupportLevel.Supported;
internal: false, bitstreamFormat3.internal = true;
extensions: null bitstreamFormat3.extensions = null;
}
]; const bitstreamFormat4 = new BitstreamFormat();
const mockFormats = createSuccessfulRemoteDataObject$(new PaginatedList(null, mockFormatsList)); bitstreamFormat4.uuid = 'test-uuid-4';
const registryServiceStub = { bitstreamFormat4.id = 'test-uuid-4';
getBitstreamFormats: () => mockFormats bitstreamFormat4.shortDescription = 'Adobe PDF';
}; bitstreamFormat4.description = 'Adobe Portable Document Format';
bitstreamFormat4.mimetype = 'application/pdf';
bitstreamFormat4.supportLevel = BitstreamFormatSupportLevel.Unknown;
bitstreamFormat4.internal = false;
bitstreamFormat4.extensions = null;
const mockFormatsList: BitstreamFormat[] = [
bitstreamFormat1,
bitstreamFormat2,
bitstreamFormat3,
bitstreamFormat4
];
const mockFormatsRD = new RemoteData(false, false, true, undefined, new PaginatedList(null, mockFormatsList));
const initAsync = () => {
notificationsServiceStub = new NotificationsServiceStub();
scheduler = getTestScheduler();
bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', {
findAll: observableOf(mockFormatsRD),
find: observableOf(new RemoteData(false, false, true, undefined, mockFormatsList[0])),
getSelectedBitstreamFormats: hot('a', {a: mockFormatsList}),
selectBitstreamFormat: {},
deselectBitstreamFormat: {},
deselectAllBitstreamFormats: {},
delete: observableOf(true),
clearBitStreamFormatRequests: observableOf('cleared')
});
beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()], imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
declarations: [BitstreamFormatsComponent, PaginationComponent, EnumKeysPipe], declarations: [BitstreamFormatsComponent, PaginationComponent, EnumKeysPipe],
providers: [ providers: [
{ provide: RegistryService, useValue: registryServiceStub }, {provide: BitstreamFormatDataService, useValue: bitstreamFormatService},
{ provide: HostWindowService, useValue: new HostWindowServiceStub(0) } {provide: HostWindowService, useValue: new HostWindowServiceStub(0)},
{provide: NotificationsService, useValue: notificationsServiceStub}
] ]
}).compileComponents(); }).compileComponents();
})); };
beforeEach(() => { const initBeforeEach = () => {
fixture = TestBed.createComponent(BitstreamFormatsComponent); fixture = TestBed.createComponent(BitstreamFormatsComponent);
comp = fixture.componentInstance; comp = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
registryService = (comp as any).service; };
});
describe('Bitstream format page content', () => {
beforeEach(async(initAsync));
beforeEach(initBeforeEach);
it('should contain four formats', () => { it('should contain four formats', () => {
const tbody: HTMLElement = fixture.debugElement.query(By.css('#formats>tbody')).nativeElement; const tbody: HTMLElement = fixture.debugElement.query(By.css('#formats>tbody')).nativeElement;
@@ -82,17 +118,172 @@ describe('BitstreamFormatsComponent', () => {
}); });
it('should contain the correct formats', () => { it('should contain the correct formats', () => {
const unknownName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(1) td:nth-child(1)')).nativeElement; const unknownName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(1) td:nth-child(2)')).nativeElement;
expect(unknownName.textContent).toBe('Unknown'); expect(unknownName.textContent).toBe('Unknown');
const licenseName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(2) td:nth-child(1)')).nativeElement; const licenseName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(2) td:nth-child(2)')).nativeElement;
expect(licenseName.textContent).toBe('License'); expect(licenseName.textContent).toBe('License');
const ccLicenseName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(3) td:nth-child(1)')).nativeElement; const ccLicenseName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(3) td:nth-child(2)')).nativeElement;
expect(ccLicenseName.textContent).toBe('CC License'); expect(ccLicenseName.textContent).toBe('CC License');
const adobeName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(4) td:nth-child(1)')).nativeElement; const adobeName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(4) td:nth-child(2)')).nativeElement;
expect(adobeName.textContent).toBe('Adobe PDF'); expect(adobeName.textContent).toBe('Adobe PDF');
}); });
});
describe('selectBitStreamFormat', () => {
beforeEach(async(initAsync));
beforeEach(initBeforeEach);
it('should select a bitstreamFormat if it was selected in the event', () => {
const event = {target: {checked: true}};
comp.selectBitStreamFormat(bitstreamFormat1, event);
expect(bitstreamFormatService.selectBitstreamFormat).toHaveBeenCalledWith(bitstreamFormat1);
});
it('should deselect a bitstreamFormat if it is deselected in the event', () => {
const event = {target: {checked: false}};
comp.selectBitStreamFormat(bitstreamFormat1, event);
expect(bitstreamFormatService.deselectBitstreamFormat).toHaveBeenCalledWith(bitstreamFormat1);
});
it('should be called when a user clicks a checkbox', () => {
spyOn(comp, 'selectBitStreamFormat');
const unknownFormat = fixture.debugElement.query(By.css('#formats tr:nth-child(1) input'));
const event = {target: {checked: true}};
unknownFormat.triggerEventHandler('change', event);
expect(comp.selectBitStreamFormat).toHaveBeenCalledWith(bitstreamFormat1, event);
});
});
describe('isSelected', () => {
beforeEach(async(initAsync));
beforeEach(initBeforeEach);
it('should return an observable of true if the provided bistream is in the list returned by the service', () => {
const result = comp.isSelected(bitstreamFormat1);
expect(result).toBeObservable(cold('b', {b: true}));
});
it('should return an observable of false if the provided bistream is not in the list returned by the service', () => {
const format = new BitstreamFormat();
format.uuid = 'new';
const result = comp.isSelected(format);
expect(result).toBeObservable(cold('b', {b: false}));
});
});
describe('deselectAll', () => {
beforeEach(async(initAsync));
beforeEach(initBeforeEach);
it('should deselect all bitstreamFormats', () => {
comp.deselectAll();
expect(bitstreamFormatService.deselectAllBitstreamFormats).toHaveBeenCalled();
});
it('should be called when the deselect all button is clicked', () => {
spyOn(comp, 'deselectAll');
const deselectAllButton = fixture.debugElement.query(By.css('button.deselect'));
deselectAllButton.triggerEventHandler('click', null);
expect(comp.deselectAll).toHaveBeenCalled();
}); });
});
describe('deleteFormats success', () => {
beforeEach(async(() => {
notificationsServiceStub = new NotificationsServiceStub();
scheduler = getTestScheduler();
bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', {
findAll: observableOf(mockFormatsRD),
find: observableOf(new RemoteData(false, false, true, undefined, mockFormatsList[0])),
getSelectedBitstreamFormats: observableOf(mockFormatsList),
selectBitstreamFormat: {},
deselectBitstreamFormat: {},
deselectAllBitstreamFormats: {},
delete: observableOf(true),
clearBitStreamFormatRequests: observableOf('cleared')
});
TestBed.configureTestingModule({
imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
declarations: [BitstreamFormatsComponent, PaginationComponent, EnumKeysPipe],
providers: [
{provide: BitstreamFormatDataService, useValue: bitstreamFormatService},
{provide: HostWindowService, useValue: new HostWindowServiceStub(0)},
{provide: NotificationsService, useValue: notificationsServiceStub}
]
}).compileComponents();
}
));
beforeEach(initBeforeEach);
it('should clear bitstream formats ', () => {
comp.deleteFormats();
expect(bitstreamFormatService.clearBitStreamFormatRequests).toHaveBeenCalled();
expect(bitstreamFormatService.delete).toHaveBeenCalledWith(bitstreamFormat1);
expect(bitstreamFormatService.delete).toHaveBeenCalledWith(bitstreamFormat2);
expect(bitstreamFormatService.delete).toHaveBeenCalledWith(bitstreamFormat3);
expect(bitstreamFormatService.delete).toHaveBeenCalledWith(bitstreamFormat4);
expect(notificationsServiceStub.success).toHaveBeenCalledWith('admin.registries.bitstream-formats.delete.success.head',
'admin.registries.bitstream-formats.delete.success.amount');
expect(notificationsServiceStub.error).not.toHaveBeenCalled();
});
});
describe('deleteFormats error', () => {
beforeEach(async(() => {
notificationsServiceStub = new NotificationsServiceStub();
scheduler = getTestScheduler();
bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', {
findAll: observableOf(mockFormatsRD),
find: observableOf(new RemoteData(false, false, true, undefined, mockFormatsList[0])),
getSelectedBitstreamFormats: observableOf(mockFormatsList),
selectBitstreamFormat: {},
deselectBitstreamFormat: {},
deselectAllBitstreamFormats: {},
delete: observableOf(false),
clearBitStreamFormatRequests: observableOf('cleared')
});
TestBed.configureTestingModule({
imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
declarations: [BitstreamFormatsComponent, PaginationComponent, EnumKeysPipe],
providers: [
{provide: BitstreamFormatDataService, useValue: bitstreamFormatService},
{provide: HostWindowService, useValue: new HostWindowServiceStub(0)},
{provide: NotificationsService, useValue: notificationsServiceStub}
]
}).compileComponents();
}
));
beforeEach(initBeforeEach);
it('should clear bitstream formats ', () => {
comp.deleteFormats();
expect(bitstreamFormatService.clearBitStreamFormatRequests).toHaveBeenCalled();
expect(bitstreamFormatService.delete).toHaveBeenCalledWith(bitstreamFormat1);
expect(bitstreamFormatService.delete).toHaveBeenCalledWith(bitstreamFormat2);
expect(bitstreamFormatService.delete).toHaveBeenCalledWith(bitstreamFormat3);
expect(bitstreamFormatService.delete).toHaveBeenCalledWith(bitstreamFormat4);
expect(notificationsServiceStub.error).toHaveBeenCalledWith('admin.registries.bitstream-formats.delete.failure.head',
'admin.registries.bitstream-formats.delete.failure.amount');
expect(notificationsServiceStub.success).not.toHaveBeenCalled();
});
});
});

View File

@@ -1,10 +1,16 @@
import { Component } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { RegistryService } from '../../../core/registry/registry.service'; import { BehaviorSubject, combineLatest as observableCombineLatest, Observable, zip } from 'rxjs';
import { Observable } from 'rxjs';
import { RemoteData } from '../../../core/data/remote-data'; import { RemoteData } from '../../../core/data/remote-data';
import { PaginatedList } from '../../../core/data/paginated-list'; import { PaginatedList } from '../../../core/data/paginated-list';
import { BitstreamFormat } from '../../../core/registry/mock-bitstream-format.model';
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
import { BitstreamFormat } from '../../../core/shared/bitstream-format.model';
import { BitstreamFormatDataService } from '../../../core/data/bitstream-format-data.service';
import { FindAllOptions } from '../../../core/data/request.models';
import { map, switchMap, take } from 'rxjs/operators';
import { hasValue } from '../../../shared/empty.util';
import { NotificationsService } from '../../../shared/notifications/notifications.service';
import { Router } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
/** /**
* This component renders a list of bitstream formats * This component renders a list of bitstream formats
@@ -13,24 +19,125 @@ import { PaginationComponentOptions } from '../../../shared/pagination/paginatio
selector: 'ds-bitstream-formats', selector: 'ds-bitstream-formats',
templateUrl: './bitstream-formats.component.html' templateUrl: './bitstream-formats.component.html'
}) })
export class BitstreamFormatsComponent { export class BitstreamFormatsComponent implements OnInit {
/** /**
* A paginated list of bitstream formats to be shown on the page * A paginated list of bitstream formats to be shown on the page
*/ */
bitstreamFormats: Observable<RemoteData<PaginatedList<BitstreamFormat>>>; bitstreamFormats: Observable<RemoteData<PaginatedList<BitstreamFormat>>>;
/**
* A BehaviourSubject that keeps track of the pageState used to update the currently displayed bitstreamFormats
*/
pageState: BehaviorSubject<string>;
/**
* The current pagination configuration for the page used by the FindAll method
* Currently simply renders all bitstream formats
*/
config: FindAllOptions = Object.assign(new FindAllOptions(), {
elementsPerPage: 20
});
/** /**
* The current pagination configuration for the page * The current pagination configuration for the page
* Currently simply renders all bitstream formats * Currently simply renders all bitstream formats
*/ */
config: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), { pageConfig: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), {
id: 'registry-bitstreamformats-pagination', id: 'registry-bitstreamformats-pagination',
pageSize: 10000 pageSize: 20
}); });
constructor(private registryService: RegistryService) { constructor(private notificationsService: NotificationsService,
this.updateFormats(); private router: Router,
private translateService: TranslateService,
private bitstreamFormatService: BitstreamFormatDataService) {
}
/**
* Deletes the currently selected formats from the registry and updates the presented list
*/
deleteFormats() {
this.bitstreamFormatService.clearBitStreamFormatRequests().subscribe();
this.bitstreamFormatService.getSelectedBitstreamFormats().pipe(take(1)).subscribe(
(formats) => {
const tasks$ = [];
for (const format of formats) {
if (hasValue(format.id)) {
tasks$.push(this.bitstreamFormatService.delete(format));
}
}
zip(...tasks$).subscribe((results: boolean[]) => {
const successResponses = results.filter((result: boolean) => result);
const failedResponses = results.filter((result: boolean) => !result);
if (successResponses.length > 0) {
this.showNotification(true, successResponses.length);
}
if (failedResponses.length > 0) {
this.showNotification(false, failedResponses.length);
}
this.deselectAll();
this.router.navigate([], {
queryParams: Object.assign({}, { page: 1 }),
queryParamsHandling: 'merge'
}); });
}
);
}
/**
* Deselects all selecetd bitstream formats
*/
deselectAll() {
this.bitstreamFormatService.deselectAllBitstreamFormats();
}
/**
* Checks whether a given bitstream format is selected in the list (checkbox)
* @param bitstreamFormat
*/
isSelected(bitstreamFormat: BitstreamFormat): Observable<boolean> {
return this.bitstreamFormatService.getSelectedBitstreamFormats().pipe(
map((bitstreamFormats: BitstreamFormat[]) => {
return bitstreamFormats.find((selectedFormat) => selectedFormat.id === bitstreamFormat.id) != null;
})
);
}
/**
* Selects or deselects a bitstream format based on the checkbox state
* @param bitstreamFormat
* @param event
*/
selectBitStreamFormat(bitstreamFormat: BitstreamFormat, event) {
event.target.checked ?
this.bitstreamFormatService.selectBitstreamFormat(bitstreamFormat) :
this.bitstreamFormatService.deselectBitstreamFormat(bitstreamFormat);
}
/**
* Show notifications for an amount of deleted bitstream formats
* @param success Whether or not the notification should be a success message (error message when false)
* @param amount The amount of deleted bitstream formats
*/
private showNotification(success: boolean, amount: number) {
const prefix = 'admin.registries.bitstream-formats.delete';
const suffix = success ? 'success' : 'failure';
const messages = observableCombineLatest(
this.translateService.get(`${prefix}.${suffix}.head`),
this.translateService.get(`${prefix}.${suffix}.amount`, {amount: amount})
);
messages.subscribe(([head, content]) => {
if (success) {
this.notificationsService.success(head, content);
} else {
this.notificationsService.error(head, content);
}
});
} }
/** /**
@@ -38,14 +145,26 @@ export class BitstreamFormatsComponent {
* @param event The page change event * @param event The page change event
*/ */
onPageChange(event) { onPageChange(event) {
this.config.currentPage = event; this.config = Object.assign(new FindAllOptions(), this.config, {
this.updateFormats(); currentPage: event,
});
this.pageConfig.currentPage = event;
this.pageState.next('pageChange');
}
ngOnInit(): void {
this.pageState = new BehaviorSubject('init');
this.bitstreamFormats = this.pageState.pipe(
switchMap(() => {
return this.updateFormats()
;
}));
} }
/** /**
* Method to update the bitstream formats that are shown * Finds all formats based on the current config
*/ */
private updateFormats() { private updateFormats() {
this.bitstreamFormats = this.registryService.getBitstreamFormats(this.config); return this.bitstreamFormatService.findAll(this.config);
} }
} }

View File

@@ -0,0 +1,30 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core';
import { BitstreamFormatsComponent } from './bitstream-formats.component';
import { SharedModule } from '../../../shared/shared.module';
import { FormatFormComponent } from './format-form/format-form.component';
import { EditBitstreamFormatComponent } from './edit-bitstream-format/edit-bitstream-format.component';
import { BitstreamFormatsRoutingModule } from './bitstream-formats-routing.module';
import { AddBitstreamFormatComponent } from './add-bitstream-format/add-bitstream-format.component';
@NgModule({
imports: [
CommonModule,
SharedModule,
RouterModule,
TranslateModule,
BitstreamFormatsRoutingModule
],
declarations: [
BitstreamFormatsComponent,
EditBitstreamFormatComponent,
AddBitstreamFormatComponent,
FormatFormComponent
],
entryComponents: []
})
export class BitstreamFormatsModule {
}

View File

@@ -0,0 +1,31 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { find } from 'rxjs/operators';
import { RemoteData } from '../../../core/data/remote-data';
import { BitstreamFormat } from '../../../core/shared/bitstream-format.model';
import { BitstreamFormatDataService } from '../../../core/data/bitstream-format-data.service';
import { hasValue } from '../../../shared/empty.util';
/**
* This class represents a resolver that requests a specific bitstreamFormat before the route is activated
*/
@Injectable()
export class BitstreamFormatsResolver implements Resolve<RemoteData<BitstreamFormat>> {
constructor(private bitstreamFormatDataService: BitstreamFormatDataService) {
}
/**
* Method for resolving an bitstreamFormat based on the parameters in the current route
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
* @returns Observable<<RemoteData<BitstreamFormat>> Emits the found bitstreamFormat based on the parameters in the current route,
* or an error if something went wrong
*/
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<BitstreamFormat>> {
return this.bitstreamFormatDataService.findById(route.params.id)
.pipe(
find((RD) => hasValue(RD.error) || RD.hasSucceeded),
);
}
}

View File

@@ -0,0 +1,11 @@
<div class="container">
<div class="row">
<div class="col-12 mb-4">
<h2 id="sub-header"
class="border-bottom mb-2">{{'admin.registries.bitstream-formats.edit.head' | translate:{format: (bitstreamFormatRD$ | async)?.payload.shortDescription} }}</h2>
<ds-bitstream-format-form [bitstreamFormat]="(bitstreamFormatRD$ | async)?.payload" (updatedFormat)="updateFormat($event)"></ds-bitstream-format-form>
</div>
</div>
</div>

View File

@@ -0,0 +1,123 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CommonModule } from '@angular/common';
import { RouterTestingModule } from '@angular/router/testing';
import { TranslateModule } from '@ngx-translate/core';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { ActivatedRoute, Router } from '@angular/router';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { RouterStub } from '../../../../shared/testing/router-stub';
import { of as observableOf } from 'rxjs';
import { RemoteData } from '../../../../core/data/remote-data';
import { EditBitstreamFormatComponent } from './edit-bitstream-format.component';
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service-stub';
import { BitstreamFormatDataService } from '../../../../core/data/bitstream-format-data.service';
import { RestResponse } from '../../../../core/cache/response.models';
import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model';
import { BitstreamFormatSupportLevel } from '../../../../core/shared/bitstream-format-support-level';
import { ResourceType } from '../../../../core/shared/resource-type';
describe('EditBitstreamFormatComponent', () => {
let comp: EditBitstreamFormatComponent;
let fixture: ComponentFixture<EditBitstreamFormatComponent>;
const bitstreamFormat = new BitstreamFormat();
bitstreamFormat.uuid = 'test-uuid-1';
bitstreamFormat.id = 'test-uuid-1';
bitstreamFormat.shortDescription = 'Unknown';
bitstreamFormat.description = 'Unknown data format';
bitstreamFormat.mimetype = 'application/octet-stream';
bitstreamFormat.supportLevel = BitstreamFormatSupportLevel.Unknown;
bitstreamFormat.internal = false;
bitstreamFormat.extensions = null;
const routeStub = {
data: observableOf({
bitstreamFormat: new RemoteData(false, false, true, null, bitstreamFormat)
})
};
let router;
let notificationService: NotificationsServiceStub;
let bitstreamFormatDataService: BitstreamFormatDataService;
const initAsync = () => {
router = new RouterStub();
notificationService = new NotificationsServiceStub();
bitstreamFormatDataService = jasmine.createSpyObj('bitstreamFormatDataService', {
updateBitstreamFormat: observableOf(new RestResponse(true, 200, 'Success'))
});
TestBed.configureTestingModule({
imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
declarations: [EditBitstreamFormatComponent],
providers: [
{provide: ActivatedRoute, useValue: routeStub},
{provide: Router, useValue: router},
{provide: NotificationsService, useValue: notificationService},
{provide: BitstreamFormatDataService, useValue: bitstreamFormatDataService},
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
}).compileComponents();
};
const initBeforeEach = () => {
fixture = TestBed.createComponent(EditBitstreamFormatComponent);
comp = fixture.componentInstance;
fixture.detectChanges();
};
describe('init', () => {
beforeEach(async(initAsync));
beforeEach(initBeforeEach);
it('should initialise the bitstreamFormat based on the route', () => {
comp.bitstreamFormatRD$.subscribe((format: RemoteData<BitstreamFormat>) => {
expect(format).toEqual(new RemoteData(false, false, true, null, bitstreamFormat));
});
});
});
describe('updateFormat success', () => {
beforeEach(async(initAsync));
beforeEach(initBeforeEach);
it('should send the updated form to the service, show a notification and navigate to ', () => {
comp.updateFormat(bitstreamFormat);
expect(bitstreamFormatDataService.updateBitstreamFormat).toHaveBeenCalledWith(bitstreamFormat);
expect(notificationService.success).toHaveBeenCalled();
expect(router.navigate).toHaveBeenCalledWith(['/admin/registries/bitstream-formats']);
});
});
describe('updateFormat error', () => {
beforeEach(async( () => {
router = new RouterStub();
notificationService = new NotificationsServiceStub();
bitstreamFormatDataService = jasmine.createSpyObj('bitstreamFormatDataService', {
updateBitstreamFormat: observableOf(new RestResponse(false, 400, 'Bad Request'))
});
TestBed.configureTestingModule({
imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
declarations: [EditBitstreamFormatComponent],
providers: [
{provide: ActivatedRoute, useValue: routeStub},
{provide: Router, useValue: router},
{provide: NotificationsService, useValue: notificationService},
{provide: BitstreamFormatDataService, useValue: bitstreamFormatDataService},
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
}).compileComponents();
}));
beforeEach(initBeforeEach);
it('should send the updated form to the service, show a notification and navigate to ', () => {
comp.updateFormat(bitstreamFormat);
expect(bitstreamFormatDataService.updateBitstreamFormat).toHaveBeenCalledWith(bitstreamFormat);
expect(notificationService.error).toHaveBeenCalled();
expect(router.navigate).not.toHaveBeenCalled();
});
});
});

View File

@@ -0,0 +1,62 @@
import { map, take } from 'rxjs/operators';
import { ActivatedRoute, Router } from '@angular/router';
import { Observable } from 'rxjs';
import { Component, OnInit } from '@angular/core';
import { RemoteData } from '../../../../core/data/remote-data';
import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model';
import { BitstreamFormatDataService } from '../../../../core/data/bitstream-format-data.service';
import { RestResponse } from '../../../../core/cache/response.models';
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
import { getBitstreamFormatsModulePath } from '../../admin-registries-routing.module';
import { TranslateService } from '@ngx-translate/core';
/**
* This component renders the edit page of a bitstream format.
* The route parameter 'id' is used to request the bitstream format.
*/
@Component({
selector: 'ds-edit-bitstream-format',
templateUrl: './edit-bitstream-format.component.html',
})
export class EditBitstreamFormatComponent implements OnInit {
/**
* The bitstream format wrapped in a remote-data object
*/
bitstreamFormatRD$: Observable<RemoteData<BitstreamFormat>>;
constructor(
private route: ActivatedRoute,
private router: Router,
private notificationService: NotificationsService,
private translateService: TranslateService,
private bitstreamFormatDataService: BitstreamFormatDataService,
) {
}
ngOnInit(): void {
this.bitstreamFormatRD$ = this.route.data.pipe(
map((data) => data.bitstreamFormat as RemoteData<BitstreamFormat>)
);
}
/**
* Updates the bitstream format based on the provided bitstream format emitted by the form.
* When successful, a success notification will be shown and the user will be navigated back to the overview page.
* When failed, an error notification will be shown.
*/
updateFormat(bitstreamFormat: BitstreamFormat) {
this.bitstreamFormatDataService.updateBitstreamFormat(bitstreamFormat).pipe(take(1)
).subscribe((response: RestResponse) => {
if (response.isSuccessful) {
this.notificationService.success(this.translateService.get('admin.registries.bitstream-formats.edit.success.head'),
this.translateService.get('admin.registries.bitstream-formats.edit.success.content'));
this.router.navigate([getBitstreamFormatsModulePath()]);
} else {
this.notificationService.error('admin.registries.bitstream-formats.edit.failure.head',
'admin.registries.bitstream-formats.create.edit.content');
}
}
);
}
}

View File

@@ -0,0 +1,3 @@
<ds-form *ngIf="formModel"
[formId]="'comcol-form-id'"
[formModel]="formModel" (submitForm)="onSubmit()" (cancel)="onCancel()"></ds-form>

View File

@@ -0,0 +1,104 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CommonModule } from '@angular/common';
import { RouterTestingModule } from '@angular/router/testing';
import { TranslateModule } from '@ngx-translate/core';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { RouterStub } from '../../../../shared/testing/router-stub';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { Router } from '@angular/router';
import { FormatFormComponent } from './format-form.component';
import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model';
import { BitstreamFormatSupportLevel } from '../../../../core/shared/bitstream-format-support-level';
import { DynamicCheckboxModel, DynamicFormArrayModel, DynamicInputModel } from '@ng-dynamic-forms/core';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { isEmpty } from '../../../../shared/empty.util';
describe('FormatFormComponent', () => {
let comp: FormatFormComponent;
let fixture: ComponentFixture<FormatFormComponent>;
const router = new RouterStub();
const bitstreamFormat = new BitstreamFormat();
bitstreamFormat.uuid = 'test-uuid-1';
bitstreamFormat.id = 'test-uuid-1';
bitstreamFormat.shortDescription = 'Unknown';
bitstreamFormat.description = 'Unknown data format';
bitstreamFormat.mimetype = 'application/octet-stream';
bitstreamFormat.supportLevel = BitstreamFormatSupportLevel.Unknown;
bitstreamFormat.internal = false;
bitstreamFormat.extensions = [];
const submittedBitstreamFormat = new BitstreamFormat();
submittedBitstreamFormat.id = bitstreamFormat.id;
submittedBitstreamFormat.shortDescription = bitstreamFormat.shortDescription;
submittedBitstreamFormat.mimetype = bitstreamFormat.mimetype;
submittedBitstreamFormat.description = bitstreamFormat.description;
submittedBitstreamFormat.supportLevel = bitstreamFormat.supportLevel;
submittedBitstreamFormat.internal = bitstreamFormat.internal;
submittedBitstreamFormat.extensions = bitstreamFormat.extensions;
const initAsync = () => {
TestBed.configureTestingModule({
imports: [CommonModule, RouterTestingModule.withRoutes([]), ReactiveFormsModule, FormsModule, TranslateModule.forRoot(), NgbModule.forRoot()],
declarations: [FormatFormComponent],
providers: [
{provide: Router, useValue: router},
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
}).compileComponents();
};
const initBeforeEach = () => {
fixture = TestBed.createComponent(FormatFormComponent);
comp = fixture.componentInstance;
comp.bitstreamFormat = bitstreamFormat;
fixture.detectChanges();
};
describe('initialise', () => {
beforeEach(async(initAsync));
beforeEach(initBeforeEach);
it('should initialises the values in the form', () => {
expect((comp.formModel[0] as DynamicInputModel).value).toBe(bitstreamFormat.shortDescription);
expect((comp.formModel[1] as DynamicInputModel).value).toBe(bitstreamFormat.mimetype);
expect((comp.formModel[2] as DynamicInputModel).value).toBe(bitstreamFormat.description);
expect((comp.formModel[3] as DynamicInputModel).value).toBe(bitstreamFormat.supportLevel);
expect((comp.formModel[4] as DynamicCheckboxModel).value).toBe(bitstreamFormat.internal);
const formArray = (comp.formModel[5] as DynamicFormArrayModel);
const extensions = [];
for (let i = 0; i < formArray.groups.length; i++) {
const value = (formArray.get(i).get(0) as DynamicInputModel).value;
if (!isEmpty(value)) {
extensions.push((formArray.get(i).get(0) as DynamicInputModel).value);
}
}
expect(extensions).toEqual(bitstreamFormat.extensions);
});
});
describe('onSubmit', () => {
beforeEach(async(initAsync));
beforeEach(initBeforeEach);
it('should emit the bitstreamFormat currently present in the form', () => {
spyOn(comp.updatedFormat, 'emit');
comp.onSubmit();
expect(comp.updatedFormat.emit).toHaveBeenCalledWith(submittedBitstreamFormat);
});
});
describe('onCancel', () => {
beforeEach(async(initAsync));
beforeEach(initBeforeEach);
it('should navigate back to the bitstream overview', () => {
comp.onCancel();
expect(router.navigate).toHaveBeenCalledWith(['/admin/registries/bitstream-formats']);
});
});
});

View File

@@ -0,0 +1,194 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model';
import { BitstreamFormatSupportLevel } from '../../../../core/shared/bitstream-format-support-level';
import {
DynamicCheckboxModel,
DynamicFormArrayModel,
DynamicFormControlLayout, DynamicFormControlLayoutConfig,
DynamicFormControlModel,
DynamicFormService,
DynamicInputModel,
DynamicSelectModel,
DynamicTextAreaModel
} from '@ng-dynamic-forms/core';
import { Router } from '@angular/router';
import { getBitstreamFormatsModulePath } from '../../admin-registries-routing.module';
import { hasValue, isEmpty } from '../../../../shared/empty.util';
import { TranslateService } from '@ngx-translate/core';
/**
* The component responsible for rendering the form to create/edit a bitstream format
*/
@Component({
selector: 'ds-bitstream-format-form',
templateUrl: './format-form.component.html'
})
export class FormatFormComponent implements OnInit {
/**
* The current bitstream format
* This can either be and existing one or a new one
*/
@Input() bitstreamFormat: BitstreamFormat = new BitstreamFormat();
/**
* EventEmitter that will emit the updated bitstream format
*/
@Output() updatedFormat: EventEmitter<BitstreamFormat> = new EventEmitter<BitstreamFormat>();
/**
* The different supported support level of the bitstream format
*/
supportLevelOptions = [{label: BitstreamFormatSupportLevel.Known, value: BitstreamFormatSupportLevel.Known},
{label: BitstreamFormatSupportLevel.Unknown, value: BitstreamFormatSupportLevel.Unknown},
{label: BitstreamFormatSupportLevel.Supported, value: BitstreamFormatSupportLevel.Supported}];
/**
* Styling element for repeatable field
*/
arrayElementLayout: DynamicFormControlLayout = {
grid: {
group: 'form-row',
},
};
/**
* Styling element for element of repeatable field
*/
arrayInputElementLayout: DynamicFormControlLayout = {
grid: {
host: 'col'
}
};
/**
* The form model representing the bitstream format
*/
formModel: DynamicFormControlModel[] = [
new DynamicInputModel({
id: 'shortDescription',
name: 'shortDescription',
label: 'admin.registries.bitstream-formats.edit.shortDescription.label',
hint: 'admin.registries.bitstream-formats.edit.shortDescription.hint',
required: true,
validators: {
required: null
},
errorMessages: {
required: 'Please enter a name for this bitstream format'
},
}),
new DynamicInputModel({
id: 'mimetype',
name: 'mimetype',
label: 'admin.registries.bitstream-formats.edit.mimetype.label',
hint: 'admin.registries.bitstream-formats.edit.mimetype.hint',
}),
new DynamicTextAreaModel({
id: 'description',
name: 'description',
label: 'admin.registries.bitstream-formats.edit.description.label',
hint: 'admin.registries.bitstream-formats.edit.description.hint',
}),
new DynamicSelectModel({
id: 'supportLevel',
name: 'supportLevel',
options: this.supportLevelOptions,
label: 'admin.registries.bitstream-formats.edit.supportLevel.label',
hint: 'admin.registries.bitstream-formats.edit.supportLevel.hint',
value: this.supportLevelOptions[0].value
}),
new DynamicCheckboxModel({
id: 'internal',
name: 'internal',
label: 'Internal',
hint: 'admin.registries.bitstream-formats.edit.internal.hint',
}),
new DynamicFormArrayModel({
id: 'extensions',
name: 'extensions',
label: 'admin.registries.bitstream-formats.edit.extensions.label',
groupFactory: () => [
new DynamicInputModel({
id: 'extension',
placeholder: 'admin.registries.bitstream-formats.edit.extensions.placeholder',
}, this.arrayInputElementLayout)
]
}, this.arrayElementLayout),
];
constructor(private dynamicFormService: DynamicFormService,
private translateService: TranslateService,
private router: Router) {
}
ngOnInit(): void {
this.initValues();
}
/**
* Initializes the form based on the provided bitstream format
*/
initValues() {
this.formModel.forEach(
(fieldModel: DynamicFormControlModel) => {
if (fieldModel.name === 'extensions') {
if (hasValue(this.bitstreamFormat.extensions)) {
const extenstions = this.bitstreamFormat.extensions;
const formArray = (fieldModel as DynamicFormArrayModel);
for (let i = 0; i < extenstions.length; i++) {
formArray.insertGroup(i).group[0] = new DynamicInputModel({
id: `extension-${i}`,
value: extenstions[i]
}, this.arrayInputElementLayout);
}
}
} else {
if (hasValue(this.bitstreamFormat[fieldModel.name])) {
(fieldModel as DynamicInputModel).value = this.bitstreamFormat[fieldModel.name];
}
}
});
}
/**
* Creates an updated bistream format based on the current values in the form
* Emits the updated bitstream format trouhg the updatedFormat emitter
*/
onSubmit() {
const updatedBitstreamFormat = Object.assign(new BitstreamFormat(),
{
id: this.bitstreamFormat.id
});
this.formModel.forEach(
(fieldModel: DynamicFormControlModel) => {
if (fieldModel.name === 'extensions') {
const formArray = (fieldModel as DynamicFormArrayModel);
const extensions = [];
for (let i = 0; i < formArray.groups.length; i++) {
const value = (formArray.get(i).get(0) as DynamicInputModel).value;
if (!isEmpty(value)) {
extensions.push((formArray.get(i).get(0) as DynamicInputModel).value);
}
}
updatedBitstreamFormat.extensions = extensions;
} else {
updatedBitstreamFormat[fieldModel.name] = (fieldModel as DynamicInputModel).value;
}
});
this.updatedFormat.emit(updatedBitstreamFormat);
}
/**
* Cancels the edit/create action of the bitstream format and navigates back to the bitstream format registry
*/
onCancel() {
this.router.navigate([getBitstreamFormatsModulePath()]);
}
}

View File

@@ -1,11 +1,19 @@
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { URLCombiner } from '../core/url-combiner/url-combiner';
import { getAdminModulePath } from '../app-routing.module';
const REGISTRIES_MODULE_PATH = 'registries';
export function getRegistriesModulePath() {
return new URLCombiner(getAdminModulePath(), REGISTRIES_MODULE_PATH).toString();
}
@NgModule({ @NgModule({
imports: [ imports: [
RouterModule.forChild([ RouterModule.forChild([
{ {
path: 'registries', path: REGISTRIES_MODULE_PATH,
loadChildren: './admin-registries/admin-registries.module#AdminRegistriesModule' loadChildren: './admin-registries/admin-registries.module#AdminRegistriesModule'
} }
]) ])

View File

@@ -16,6 +16,12 @@ const COMMUNITY_MODULE_PATH = 'communities';
export function getCommunityModulePath() { export function getCommunityModulePath() {
return `/${COMMUNITY_MODULE_PATH}`; return `/${COMMUNITY_MODULE_PATH}`;
} }
const ADMIN_MODULE_PATH = 'admin';
export function getAdminModulePath() {
return `/${ADMIN_MODULE_PATH}`;
}
@NgModule({ @NgModule({
imports: [ imports: [
RouterModule.forRoot([ RouterModule.forRoot([
@@ -27,7 +33,7 @@ export function getCommunityModulePath() {
{ path: 'mydspace', loadChildren: './+my-dspace-page/my-dspace-page.module#MyDSpacePageModule', canActivate: [AuthenticatedGuard] }, { path: 'mydspace', loadChildren: './+my-dspace-page/my-dspace-page.module#MyDSpacePageModule', canActivate: [AuthenticatedGuard] },
{ path: 'search', loadChildren: './+search-page/search-page.module#SearchPageModule' }, { path: 'search', loadChildren: './+search-page/search-page.module#SearchPageModule' },
{ path: 'browse', loadChildren: './+browse-by/browse-by.module#BrowseByModule' }, { path: 'browse', loadChildren: './+browse-by/browse-by.module#BrowseByModule' },
{ path: 'admin', loadChildren: './+admin/admin.module#AdminModule', canActivate: [AuthenticatedGuard] }, { path: ADMIN_MODULE_PATH, loadChildren: './+admin/admin.module#AdminModule', canActivate: [AuthenticatedGuard] },
{ path: 'login', loadChildren: './+login-page/login-page.module#LoginPageModule' }, { path: 'login', loadChildren: './+login-page/login-page.module#LoginPageModule' },
{ path: 'logout', loadChildren: './+logout-page/logout-page.module#LogoutPageModule' }, { path: 'logout', loadChildren: './+logout-page/logout-page.module#LogoutPageModule' },
{ path: 'submit', loadChildren: './+submit-page/submit-page.module#SubmitPageModule' }, { path: 'submit', loadChildren: './+submit-page/submit-page.module#SubmitPageModule' },

View File

@@ -23,6 +23,10 @@ import { hasValue } from './shared/empty.util';
import { cssVariablesReducer, CSSVariablesState } from './shared/sass-helper/sass-helper.reducer'; import { cssVariablesReducer, CSSVariablesState } from './shared/sass-helper/sass-helper.reducer';
import { menusReducer, MenusState } from './shared/menu/menu.reducer'; import { menusReducer, MenusState } from './shared/menu/menu.reducer';
import { historyReducer, HistoryState } from './shared/history/history.reducer'; import { historyReducer, HistoryState } from './shared/history/history.reducer';
import {
bitstreamFormatReducer,
BitstreamFormatRegistryState
} from './+admin/admin-registries/bitstream-formats/bitstream-format.reducers';
export interface AppState { export interface AppState {
router: fromRouter.RouterReducerState; router: fromRouter.RouterReducerState;
@@ -30,6 +34,7 @@ export interface AppState {
hostWindow: HostWindowState; hostWindow: HostWindowState;
forms: FormState; forms: FormState;
metadataRegistry: MetadataRegistryState; metadataRegistry: MetadataRegistryState;
bitstreamFormats: BitstreamFormatRegistryState;
notifications: NotificationsState; notifications: NotificationsState;
searchSidebar: SearchSidebarState; searchSidebar: SearchSidebarState;
searchFilter: SearchFiltersState; searchFilter: SearchFiltersState;
@@ -44,6 +49,7 @@ export const appReducers: ActionReducerMap<AppState> = {
hostWindow: hostWindowReducer, hostWindow: hostWindowReducer,
forms: formReducer, forms: formReducer,
metadataRegistry: metadataRegistryReducer, metadataRegistry: metadataRegistryReducer,
bitstreamFormats: bitstreamFormatReducer,
notifications: notificationsReducer, notifications: notificationsReducer,
searchSidebar: sidebarReducer, searchSidebar: sidebarReducer,
searchFilter: filterReducer, searchFilter: filterReducer,

View File

@@ -4,7 +4,7 @@ import { BitstreamFormat } from '../../shared/bitstream-format.model';
import { mapsTo } from '../builders/build-decorators'; import { mapsTo } from '../builders/build-decorators';
import { IDToUUIDSerializer } from '../id-to-uuid-serializer'; import { IDToUUIDSerializer } from '../id-to-uuid-serializer';
import { NormalizedObject } from './normalized-object.model'; import { NormalizedObject } from './normalized-object.model';
import { SupportLevel } from './support-level.model'; import { BitstreamFormatSupportLevel } from '../../shared/bitstream-format-support-level';
/** /**
* Normalized model class for a Bitstream Format * Normalized model class for a Bitstream Format
@@ -34,7 +34,7 @@ export class NormalizedBitstreamFormat extends NormalizedObject<BitstreamFormat>
* The level of support the system offers for this Bitstream Format * The level of support the system offers for this Bitstream Format
*/ */
@autoserialize @autoserialize
supportLevel: SupportLevel; supportLevel: BitstreamFormatSupportLevel;
/** /**
* True if the Bitstream Format is used to store system information, rather than the content of items in the system * True if the Bitstream Format is used to store system information, rather than the content of items in the system
@@ -46,7 +46,7 @@ export class NormalizedBitstreamFormat extends NormalizedObject<BitstreamFormat>
* String representing this Bitstream Format's file extension * String representing this Bitstream Format's file extension
*/ */
@autoserialize @autoserialize
extensions: string; extensions: string[];
/** /**
* Identifier for this Bitstream Format * Identifier for this Bitstream Format

View File

@@ -107,6 +107,7 @@ import { MyDSpaceResponseParsingService } from './data/mydspace-response-parsing
import { ClaimedTaskDataService } from './tasks/claimed-task-data.service'; import { ClaimedTaskDataService } from './tasks/claimed-task-data.service';
import { PoolTaskDataService } from './tasks/pool-task-data.service'; import { PoolTaskDataService } from './tasks/pool-task-data.service';
import { TaskResponseParsingService } from './tasks/task-response-parsing.service'; import { TaskResponseParsingService } from './tasks/task-response-parsing.service';
import { BitstreamFormatDataService } from './data/bitstream-format-data.service';
import { NormalizedClaimedTask } from './tasks/models/normalized-claimed-task-object.model'; import { NormalizedClaimedTask } from './tasks/models/normalized-claimed-task-object.model';
import { NormalizedTaskObject } from './tasks/models/normalized-task-object.model'; import { NormalizedTaskObject } from './tasks/models/normalized-task-object.model';
import { NormalizedPoolTask } from './tasks/models/normalized-pool-task-object.model'; import { NormalizedPoolTask } from './tasks/models/normalized-pool-task-object.model';
@@ -153,6 +154,7 @@ const PROVIDERS = [
PaginationComponentOptions, PaginationComponentOptions,
ResourcePolicyService, ResourcePolicyService,
RegistryService, RegistryService,
BitstreamFormatDataService,
NormalizedObjectBuildService, NormalizedObjectBuildService,
RemoteDataBuildService, RemoteDataBuildService,
RequestService, RequestService,

View File

@@ -0,0 +1,293 @@
import { BitstreamFormatDataService } from './bitstream-format-data.service';
import { RequestEntry } from './request.reducer';
import { RestResponse } from '../cache/response.models';
import { Observable, of as observableOf } from 'rxjs';
import { Action, Store } from '@ngrx/store';
import { CoreState } from '../core.reducers';
import { ObjectCacheService } from '../cache/object-cache.service';
import { cold, getTestScheduler, hot } from 'jasmine-marbles';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { HttpClient } from '@angular/common/http';
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { BitstreamFormat } from '../shared/bitstream-format.model';
import { async } from '@angular/core/testing';
import {
BitstreamFormatsRegistryDeselectAction,
BitstreamFormatsRegistryDeselectAllAction,
BitstreamFormatsRegistrySelectAction
} from '../../+admin/admin-registries/bitstream-formats/bitstream-format.actions';
import { TestScheduler } from 'rxjs/testing';
describe('BitstreamFormatDataService', () => {
let service: BitstreamFormatDataService;
let requestService;
let scheduler: TestScheduler;
const bitstreamFormatsEndpoint = 'https://rest.api/core/bitstream-formats';
const bitstreamFormatsIdEndpoint = 'https://rest.api/core/bitstream-formats/format-id';
const responseCacheEntry = new RequestEntry();
responseCacheEntry.response = new RestResponse(true, 200, 'Success');
responseCacheEntry.completed = true;
const store = {
dispatch(action: Action) {
// Do Nothing
}
} as Store<CoreState>;
const objectCache = {} as ObjectCacheService;
const halEndpointService = {
getEndpoint(linkPath: string): Observable<string> {
return cold('a', {a: bitstreamFormatsEndpoint});
}
} as HALEndpointService;
const notificationsService = {} as NotificationsService;
const http = {} as HttpClient;
const comparator = {} as any;
const dataBuildService = {} as NormalizedObjectBuildService;
const rdbService = {} as RemoteDataBuildService;
function initTestService(halService) {
return new BitstreamFormatDataService(
requestService,
rdbService,
dataBuildService,
store,
objectCache,
halService,
notificationsService,
http,
comparator
);
}
describe('getBrowseEndpoint', () => {
beforeEach(async(() => {
scheduler = getTestScheduler();
requestService = jasmine.createSpyObj('requestService', {
configure: {},
getByHref: observableOf(responseCacheEntry),
getByUUID: cold('a', {a: responseCacheEntry}),
generateRequestId: 'request-id',
removeByHrefSubstring: {}
});
service = initTestService(halEndpointService);
}));
it('should get the browse endpoint', () => {
const result = service.getBrowseEndpoint();
const expected = cold('b', {b: bitstreamFormatsEndpoint});
expect(result).toBeObservable(expected);
});
});
describe('getUpdateEndpoint', () => {
beforeEach(async(() => {
scheduler = getTestScheduler();
requestService = jasmine.createSpyObj('requestService', {
configure: {},
getByHref: observableOf(responseCacheEntry),
getByUUID: cold('a', {a: responseCacheEntry}),
generateRequestId: 'request-id',
removeByHrefSubstring: {}
});
service = initTestService(halEndpointService);
}));
it('should get the update endpoint', () => {
const formatId = 'format-id';
const result = service.getUpdateEndpoint(formatId);
const expected = cold('b', {b: bitstreamFormatsIdEndpoint});
expect(result).toBeObservable(expected);
});
});
describe('getCreateEndpoint', () => {
beforeEach(async(() => {
scheduler = getTestScheduler();
requestService = jasmine.createSpyObj('requestService', {
configure: {},
getByHref: observableOf(responseCacheEntry),
getByUUID: cold('a', {a: responseCacheEntry}),
generateRequestId: 'request-id',
removeByHrefSubstring: {}
});
service = initTestService(halEndpointService);
}));
it('should get the create endpoint ', () => {
const result = service.getCreateEndpoint();
const expected = cold('b', {b: bitstreamFormatsEndpoint});
expect(result).toBeObservable(expected);
});
});
describe('updateBitstreamFormat', () => {
beforeEach(async(() => {
scheduler = getTestScheduler();
requestService = jasmine.createSpyObj('requestService', {
configure: {},
getByHref: observableOf(responseCacheEntry),
getByUUID: cold('a', {a: responseCacheEntry}),
generateRequestId: 'request-id',
removeByHrefSubstring: {}
});
service = initTestService(halEndpointService);
}));
it('should update the bitstream format', () => {
const updatedBistreamFormat = new BitstreamFormat();
updatedBistreamFormat.uuid = 'updated-uuid';
const expected = cold('(b)', {b: new RestResponse(true, 200, 'Success')});
const result = service.updateBitstreamFormat(updatedBistreamFormat);
expect(result).toBeObservable(expected);
});
});
describe('createBitstreamFormat', () => {
beforeEach(async(() => {
scheduler = getTestScheduler();
requestService = jasmine.createSpyObj('requestService', {
configure: {},
getByHref: observableOf(responseCacheEntry),
getByUUID: cold('a', {a: responseCacheEntry}),
generateRequestId: 'request-id',
removeByHrefSubstring: {}
});
service = initTestService(halEndpointService);
}));
it('should create a new bitstream format', () => {
const newFormat = new BitstreamFormat();
newFormat.uuid = 'new-uuid';
const expected = cold('(b)', {b: new RestResponse(true, 200, 'Success')});
const result = service.createBitstreamFormat(newFormat);
expect(result).toBeObservable(expected);
});
});
describe('clearBitStreamFormatRequests', () => {
beforeEach(async(() => {
scheduler = getTestScheduler();
requestService = jasmine.createSpyObj('requestService', {
configure: {},
getByHref: observableOf(responseCacheEntry),
getByUUID: cold('a', {a: responseCacheEntry}),
generateRequestId: 'request-id',
removeByHrefSubstring: {}
});
const halService = {
getEndpoint(linkPath: string): Observable<string> {
return observableOf(bitstreamFormatsEndpoint);
}
} as HALEndpointService;
service = initTestService(halService);
service.clearBitStreamFormatRequests().subscribe();
}));
it('should remove the bitstream format hrefs in the request service', () => {
expect(requestService.removeByHrefSubstring).toHaveBeenCalledWith(bitstreamFormatsEndpoint);
});
});
describe('selectBitstreamFormat', () => {
beforeEach(async(() => {
scheduler = getTestScheduler();
requestService = jasmine.createSpyObj('requestService', {
configure: {},
getByHref: observableOf(responseCacheEntry),
getByUUID: cold('a', {a: responseCacheEntry}),
generateRequestId: 'request-id',
removeByHrefSubstring: {}
});
service = initTestService(halEndpointService);
spyOn(store, 'dispatch');
}));
it('should add a selected bitstream to the store', () => {
const format = new BitstreamFormat();
format.uuid = 'uuid';
service.selectBitstreamFormat(format);
expect(store.dispatch).toHaveBeenCalledWith(new BitstreamFormatsRegistrySelectAction(format));
});
});
describe('deselectBitstreamFormat', () => {
beforeEach(async(() => {
scheduler = getTestScheduler();
requestService = jasmine.createSpyObj('requestService', {
configure: {},
getByHref: observableOf(responseCacheEntry),
getByUUID: cold('a', {a: responseCacheEntry}),
generateRequestId: 'request-id',
removeByHrefSubstring: {}
});
service = initTestService(halEndpointService);
spyOn(store, 'dispatch');
}));
it('should remove a bitstream from the store', () => {
const format = new BitstreamFormat();
format.uuid = 'uuid';
service.deselectBitstreamFormat(format);
expect(store.dispatch).toHaveBeenCalledWith(new BitstreamFormatsRegistryDeselectAction(format));
});
});
describe('deselectAllBitstreamFormats', () => {
beforeEach(async(() => {
scheduler = getTestScheduler();
requestService = jasmine.createSpyObj('requestService', {
configure: {},
getByHref: observableOf(responseCacheEntry),
getByUUID: cold('a', {a: responseCacheEntry}),
generateRequestId: 'request-id',
removeByHrefSubstring: {}
});
service = initTestService(halEndpointService);
spyOn(store, 'dispatch');
}));
it('should remove all bitstreamFormats from the store', () => {
service.deselectAllBitstreamFormats();
expect(store.dispatch).toHaveBeenCalledWith(new BitstreamFormatsRegistryDeselectAllAction());
});
});
describe('delete', () => {
beforeEach(async(() => {
scheduler = getTestScheduler();
requestService = jasmine.createSpyObj('requestService', {
configure: {},
getByHref: observableOf(responseCacheEntry),
getByUUID: hot('a', {a: responseCacheEntry}),
generateRequestId: 'request-id',
removeByHrefSubstring: {}
});
const halService = {
getEndpoint(linkPath: string): Observable<string> {
return observableOf(bitstreamFormatsEndpoint);
}
} as HALEndpointService;
service = initTestService(halService);
}));
it('should delete a bitstream format', () => {
const format = new BitstreamFormat();
format.uuid = 'format-uuid';
format.id = 'format-id';
const expected = cold('(b|)', {b: true});
const result = service.delete(format);
expect(result).toBeObservable(expected);
});
});
});

View File

@@ -0,0 +1,183 @@
import { Injectable } from '@angular/core';
import { DataService } from './data.service';
import { BitstreamFormat } from '../shared/bitstream-format.model';
import { RequestService } from './request.service';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
import { createSelector, select, 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 { DeleteByIDRequest, FindAllOptions, PostRequest, PutRequest } from './request.models';
import { Observable } from 'rxjs';
import { find, map, tap } from 'rxjs/operators';
import { configureRequest, getResponseFromEntry } from '../shared/operators';
import { distinctUntilChanged } from 'rxjs/internal/operators/distinctUntilChanged';
import { RestResponse } from '../cache/response.models';
import { AppState } from '../../app.reducer';
import { BitstreamFormatRegistryState } from '../../+admin/admin-registries/bitstream-formats/bitstream-format.reducers';
import {
BitstreamFormatsRegistryDeselectAction,
BitstreamFormatsRegistryDeselectAllAction,
BitstreamFormatsRegistrySelectAction
} from '../../+admin/admin-registries/bitstream-formats/bitstream-format.actions';
import { hasValue } from '../../shared/empty.util';
import { RequestEntry } from './request.reducer';
const bitstreamFormatsStateSelector = (state: AppState) => state.bitstreamFormats;
const selectedBitstreamFormatSelector = createSelector(bitstreamFormatsStateSelector,
(bitstreamFormatRegistryState: BitstreamFormatRegistryState) => bitstreamFormatRegistryState.selectedBitstreamFormats);
/**
* A service responsible for fetching/sending data from/to the REST API on the bitstreamformats endpoint
*/
@Injectable()
export class BitstreamFormatDataService extends DataService<BitstreamFormat> {
protected linkPath = 'bitstreamformats';
protected forceBypassCache = false;
constructor(
protected requestService: RequestService,
protected rdbService: RemoteDataBuildService,
protected dataBuildService: NormalizedObjectBuildService,
protected store: Store<CoreState>,
protected objectCache: ObjectCacheService,
protected halService: HALEndpointService,
protected notificationsService: NotificationsService,
protected http: HttpClient,
protected comparator: DefaultChangeAnalyzer<BitstreamFormat>) {
super();
}
/**
* Get the endpoint for browsing bitstream formats
* @param {FindAllOptions} options
* @returns {Observable<string>}
*/
getBrowseEndpoint(options: FindAllOptions = {}, linkPath?: string): Observable<string> {
return this.halService.getEndpoint(this.linkPath);
}
/**
* Get the endpoint to update an existing bitstream format
* @param formatId
*/
public getUpdateEndpoint(formatId: string): Observable<string> {
return this.getBrowseEndpoint().pipe(
map((endpoint: string) => this.getIDHref(endpoint, formatId))
);
}
/**
* Get the endpoint to create a new bitstream format
*/
public getCreateEndpoint(): Observable<string> {
return this.getBrowseEndpoint();
}
/**
* Update an existing bitstreamFormat
* @param bitstreamFormat
*/
updateBitstreamFormat(bitstreamFormat: BitstreamFormat): Observable<RestResponse> {
const requestId = this.requestService.generateRequestId();
this.getUpdateEndpoint(bitstreamFormat.id).pipe(
distinctUntilChanged(),
map((endpointURL: string) =>
new PutRequest(requestId, endpointURL, bitstreamFormat)),
configureRequest(this.requestService)).subscribe();
return this.requestService.getByUUID(requestId).pipe(
getResponseFromEntry()
);
}
/**
* Create a new BitstreamFormat
* @param BitstreamFormat
*/
public createBitstreamFormat(bitstreamFormat: BitstreamFormat): Observable<RestResponse> {
const requestId = this.requestService.generateRequestId();
this.getCreateEndpoint().pipe(
map((endpointURL: string) => {
return new PostRequest(requestId, endpointURL, bitstreamFormat);
}),
configureRequest(this.requestService)
).subscribe();
return this.requestService.getByUUID(requestId).pipe(
getResponseFromEntry()
);
}
/**
* Clears the cache of the list of BitstreamFormats
*/
public clearBitStreamFormatRequests(): Observable<string> {
return this.getBrowseEndpoint().pipe(
tap((href: string) => this.requestService.removeByHrefSubstring(href))
);
}
/**
* Gets all the selected BitstreamFormats from the store
*/
public getSelectedBitstreamFormats(): Observable<BitstreamFormat[]> {
return this.store.pipe(select(selectedBitstreamFormatSelector));
}
/**
* Adds a BistreamFormat to the selected BitstreamFormats in the store
* @param bitstreamFormat
*/
public selectBitstreamFormat(bitstreamFormat: BitstreamFormat) {
this.store.dispatch(new BitstreamFormatsRegistrySelectAction(bitstreamFormat));
}
/**
* Removes a BistreamFormat from the list of selected BitstreamFormats in the store
* @param bitstreamFormat
*/
public deselectBitstreamFormat(bitstreamFormat: BitstreamFormat) {
this.store.dispatch(new BitstreamFormatsRegistryDeselectAction(bitstreamFormat));
}
/**
* Removes all BitstreamFormats from the list of selected BitstreamFormats in the store
*/
public deselectAllBitstreamFormats() {
this.store.dispatch(new BitstreamFormatsRegistryDeselectAllAction());
}
/**
* Delete an existing DSpace Object on the server
* @param format The DSpace Object to be removed
* Return an observable that emits true when the deletion was successful, false when it failed
*/
delete(format: BitstreamFormat): Observable<boolean> {
const requestId = this.requestService.generateRequestId();
const hrefObs = this.halService.getEndpoint(this.linkPath).pipe(
map((endpoint: string) => this.getIDHref(endpoint, format.id)));
hrefObs.pipe(
find((href: string) => hasValue(href)),
map((href: string) => {
const request = new DeleteByIDRequest(requestId, href, format.id);
this.requestService.configure(request);
})
).subscribe();
return this.requestService.getByUUID(requestId).pipe(
find((request: RequestEntry) => request.completed),
map((request: RequestEntry) => request.response.isSuccessful)
);
}
}

View File

@@ -1,8 +0,0 @@
export class BitstreamFormat {
shortDescription: string;
description: string;
mimetype: string;
supportLevel: number;
internal: boolean;
extensions: string;
}

View File

@@ -12,7 +12,6 @@ import { PageInfo } from '../shared/page-info.model';
import { getMockRequestService } from '../../shared/mocks/mock-request.service'; import { getMockRequestService } from '../../shared/mocks/mock-request.service';
import { import {
RegistryBitstreamformatsSuccessResponse,
RegistryMetadatafieldsSuccessResponse, RegistryMetadatafieldsSuccessResponse,
RegistryMetadataschemasSuccessResponse, RegistryMetadataschemasSuccessResponse,
RestResponse RestResponse
@@ -20,7 +19,6 @@ import {
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { RegistryMetadataschemasResponse } from './registry-metadataschemas-response.model'; import { RegistryMetadataschemasResponse } from './registry-metadataschemas-response.model';
import { RegistryMetadatafieldsResponse } from './registry-metadatafields-response.model'; import { RegistryMetadatafieldsResponse } from './registry-metadatafields-response.model';
import { RegistryBitstreamformatsResponse } from './registry-bitstreamformats-response.model';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { Store, StoreModule } from '@ngrx/store'; import { Store, StoreModule } from '@ngrx/store';
import { MockStore } from '../../shared/testing/mock-store'; import { MockStore } from '../../shared/testing/mock-store';
@@ -243,35 +241,6 @@ describe('RegistryService', () => {
}); });
}); });
describe('when requesting bitstreamformats', () => {
const queryResponse = Object.assign(new RegistryBitstreamformatsResponse(), {
bitstreamformats: mockFieldsList,
page: pageInfo
});
const response = new RegistryBitstreamformatsSuccessResponse(queryResponse, 200, 'OK', pageInfo);
const responseEntry = Object.assign(new RequestEntry(), { response: response });
beforeEach(() => {
(registryService as any).requestService.getByHref.and.returnValue(observableOf(responseEntry));
/* tslint:disable:no-empty */
registryService.getBitstreamFormats(pagination).subscribe((value) => {
});
/* tslint:enable:no-empty */
});
it('should call getEndpoint on the halService', () => {
expect((registryService as any).halService.getEndpoint).toHaveBeenCalled();
});
it('should send out the request on the request service', () => {
expect((registryService as any).requestService.configure).toHaveBeenCalled();
});
it('should call getByHref on the request service with the correct request url', () => {
expect((registryService as any).requestService.getByHref).toHaveBeenCalledWith(endpointWithParams);
});
});
describe('when dispatching to the store', () => { describe('when dispatching to the store', () => {
beforeEach(() => { beforeEach(() => {
spyOn(mockStore, 'dispatch'); spyOn(mockStore, 'dispatch');
@@ -284,7 +253,7 @@ describe('RegistryService', () => {
it('should dispatch a MetadataRegistryEditSchemaAction with the correct schema', () => { it('should dispatch a MetadataRegistryEditSchemaAction with the correct schema', () => {
expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryEditSchemaAction(mockSchemasList[0])); expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryEditSchemaAction(mockSchemasList[0]));
}) });
}); });
describe('when calling cancelEditMetadataSchema', () => { describe('when calling cancelEditMetadataSchema', () => {
@@ -294,7 +263,7 @@ describe('RegistryService', () => {
it('should dispatch a MetadataRegistryCancelSchemaAction', () => { it('should dispatch a MetadataRegistryCancelSchemaAction', () => {
expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryCancelSchemaAction()); expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryCancelSchemaAction());
}) });
}); });
describe('when calling selectMetadataSchema', () => { describe('when calling selectMetadataSchema', () => {
@@ -304,7 +273,7 @@ describe('RegistryService', () => {
it('should dispatch a MetadataRegistrySelectSchemaAction with the correct schema', () => { it('should dispatch a MetadataRegistrySelectSchemaAction with the correct schema', () => {
expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistrySelectSchemaAction(mockSchemasList[0])); expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistrySelectSchemaAction(mockSchemasList[0]));
}) });
}); });
describe('when calling deselectMetadataSchema', () => { describe('when calling deselectMetadataSchema', () => {
@@ -314,7 +283,7 @@ describe('RegistryService', () => {
it('should dispatch a MetadataRegistryDeselectSchemaAction with the correct schema', () => { it('should dispatch a MetadataRegistryDeselectSchemaAction with the correct schema', () => {
expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryDeselectSchemaAction(mockSchemasList[0])); expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryDeselectSchemaAction(mockSchemasList[0]));
}) });
}); });
describe('when calling deselectAllMetadataSchema', () => { describe('when calling deselectAllMetadataSchema', () => {
@@ -324,7 +293,7 @@ describe('RegistryService', () => {
it('should dispatch a MetadataRegistryDeselectAllSchemaAction', () => { it('should dispatch a MetadataRegistryDeselectAllSchemaAction', () => {
expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryDeselectAllSchemaAction()); expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryDeselectAllSchemaAction());
}) });
}); });
describe('when calling editMetadataField', () => { describe('when calling editMetadataField', () => {
@@ -334,7 +303,7 @@ describe('RegistryService', () => {
it('should dispatch a MetadataRegistryEditFieldAction with the correct Field', () => { it('should dispatch a MetadataRegistryEditFieldAction with the correct Field', () => {
expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryEditFieldAction(mockFieldsList[0])); expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryEditFieldAction(mockFieldsList[0]));
}) });
}); });
describe('when calling cancelEditMetadataField', () => { describe('when calling cancelEditMetadataField', () => {
@@ -344,7 +313,7 @@ describe('RegistryService', () => {
it('should dispatch a MetadataRegistryCancelFieldAction', () => { it('should dispatch a MetadataRegistryCancelFieldAction', () => {
expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryCancelFieldAction()); expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryCancelFieldAction());
}) });
}); });
describe('when calling selectMetadataField', () => { describe('when calling selectMetadataField', () => {
@@ -354,7 +323,7 @@ describe('RegistryService', () => {
it('should dispatch a MetadataRegistrySelectFieldAction with the correct Field', () => { it('should dispatch a MetadataRegistrySelectFieldAction with the correct Field', () => {
expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistrySelectFieldAction(mockFieldsList[0])); expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistrySelectFieldAction(mockFieldsList[0]));
}) });
}); });
describe('when calling deselectMetadataField', () => { describe('when calling deselectMetadataField', () => {
@@ -364,7 +333,7 @@ describe('RegistryService', () => {
it('should dispatch a MetadataRegistryDeselectFieldAction with the correct Field', () => { it('should dispatch a MetadataRegistryDeselectFieldAction with the correct Field', () => {
expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryDeselectFieldAction(mockFieldsList[0])); expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryDeselectFieldAction(mockFieldsList[0]));
}) });
}); });
describe('when calling deselectAllMetadataField', () => { describe('when calling deselectAllMetadataField', () => {
@@ -374,7 +343,7 @@ describe('RegistryService', () => {
it('should dispatch a MetadataRegistryDeselectAllFieldAction', () => { it('should dispatch a MetadataRegistryDeselectAllFieldAction', () => {
expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryDeselectAllFieldAction()); expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryDeselectAllFieldAction());
}) });
}); });
}); });
@@ -417,7 +386,7 @@ describe('RegistryService', () => {
result.subscribe((response: RestResponse) => { result.subscribe((response: RestResponse) => {
expect(response.isSuccessful).toBe(true); expect(response.isSuccessful).toBe(true);
}); });
}) });
}); });
describe('when deleteMetadataField is called', () => { describe('when deleteMetadataField is called', () => {
@@ -431,7 +400,7 @@ describe('RegistryService', () => {
result.subscribe((response: RestResponse) => { result.subscribe((response: RestResponse) => {
expect(response.isSuccessful).toBe(true); expect(response.isSuccessful).toBe(true);
}); });
}) });
}); });
describe('when clearMetadataSchemaRequests is called', () => { describe('when clearMetadataSchemaRequests is called', () => {

View File

@@ -3,13 +3,13 @@ import { Injectable } from '@angular/core';
import { RemoteData } from '../data/remote-data'; import { RemoteData } from '../data/remote-data';
import { PaginatedList } from '../data/paginated-list'; import { PaginatedList } from '../data/paginated-list';
import { PageInfo } from '../shared/page-info.model'; import { PageInfo } from '../shared/page-info.model';
import { BitstreamFormat } from './mock-bitstream-format.model';
import { import {
CreateMetadataFieldRequest, CreateMetadataFieldRequest,
CreateMetadataSchemaRequest, CreateMetadataSchemaRequest,
DeleteRequest, DeleteRequest,
GetRequest, GetRequest,
RestRequest, UpdateMetadataFieldRequest, RestRequest,
UpdateMetadataFieldRequest,
UpdateMetadataSchemaRequest UpdateMetadataSchemaRequest
} from '../data/request.models'; } from '../data/request.models';
import { GenericConstructor } from '../shared/generic-constructor'; import { GenericConstructor } from '../shared/generic-constructor';
@@ -19,24 +19,19 @@ import { RemoteDataBuildService } from '../cache/builders/remote-data-build.serv
import { RequestService } from '../data/request.service'; import { RequestService } from '../data/request.service';
import { RegistryMetadataschemasResponse } from './registry-metadataschemas-response.model'; import { RegistryMetadataschemasResponse } from './registry-metadataschemas-response.model';
import { import {
ErrorResponse, MetadatafieldSuccessResponse, MetadataschemaSuccessResponse, MetadatafieldSuccessResponse,
RegistryBitstreamformatsSuccessResponse, MetadataschemaSuccessResponse,
RegistryMetadatafieldsSuccessResponse, RegistryMetadatafieldsSuccessResponse,
RegistryMetadataschemasSuccessResponse, RestResponse RegistryMetadataschemasSuccessResponse,
RestResponse
} from '../cache/response.models'; } from '../cache/response.models';
import { HALEndpointService } from '../shared/hal-endpoint.service'; import { HALEndpointService } from '../shared/hal-endpoint.service';
import { RegistryMetadatafieldsResponseParsingService } from '../data/registry-metadatafields-response-parsing.service'; import { RegistryMetadatafieldsResponseParsingService } from '../data/registry-metadatafields-response-parsing.service';
import { RegistryMetadatafieldsResponse } from './registry-metadatafields-response.model'; import { RegistryMetadatafieldsResponse } from './registry-metadatafields-response.model';
import { hasValue, hasNoValue, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util'; import { hasNoValue, hasValue, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util';
import { URLCombiner } from '../url-combiner/url-combiner'; import { URLCombiner } from '../url-combiner/url-combiner';
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
import { RegistryBitstreamformatsResponseParsingService } from '../data/registry-bitstreamformats-response-parsing.service'; import { configureRequest, getResponseFromEntry } from '../shared/operators';
import { RegistryBitstreamformatsResponse } from './registry-bitstreamformats-response.model';
import {
configureRequest,
getResponseFromEntry,
getSucceededRemoteData
} from '../shared/operators';
import { createSelector, select, Store } from '@ngrx/store'; import { createSelector, select, Store } from '@ngrx/store';
import { AppState } from '../../app.reducer'; import { AppState } from '../../app.reducer';
import { MetadataRegistryState } from '../../+admin/admin-registries/metadata-registry/metadata-registry.reducers'; import { MetadataRegistryState } from '../../+admin/admin-registries/metadata-registry/metadata-registry.reducers';
@@ -52,9 +47,8 @@ import {
MetadataRegistrySelectFieldAction, MetadataRegistrySelectFieldAction,
MetadataRegistrySelectSchemaAction MetadataRegistrySelectSchemaAction
} from '../../+admin/admin-registries/metadata-registry/metadata-registry.actions'; } from '../../+admin/admin-registries/metadata-registry/metadata-registry.actions';
import { distinctUntilChanged, flatMap, map, switchMap, take, tap } from 'rxjs/operators'; import { distinctUntilChanged, flatMap, map, take, tap } from 'rxjs/operators';
import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer'; import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer';
import { ResourceType } from '../shared/resource-type';
import { NormalizedMetadataSchema } from '../metadata/normalized-metadata-schema.model'; import { NormalizedMetadataSchema } from '../metadata/normalized-metadata-schema.model';
import { NotificationsService } from '../../shared/notifications/notifications.service'; import { NotificationsService } from '../../shared/notifications/notifications.service';
import { NotificationOptions } from '../../shared/notifications/models/notification-options.model'; import { NotificationOptions } from '../../shared/notifications/models/notification-options.model';
@@ -79,7 +73,8 @@ export class RegistryService {
private metadataSchemasPath = 'metadataschemas'; private metadataSchemasPath = 'metadataschemas';
private metadataFieldsPath = 'metadatafields'; private metadataFieldsPath = 'metadatafields';
private bitstreamFormatsPath = 'bitstreamformats';
// private bitstreamFormatsPath = 'bitstreamformats';
constructor(protected requestService: RequestService, constructor(protected requestService: RequestService,
private rdb: RemoteDataBuildService, private rdb: RemoteDataBuildService,
@@ -231,41 +226,7 @@ export class RegistryService {
return this.rdb.toRemoteDataObservable(requestEntryObs, payloadObs); return this.rdb.toRemoteDataObservable(requestEntryObs, payloadObs);
} }
/** public getMetadataSchemasRequestObs(pagination: PaginationComponentOptions): Observable<RestRequest> {
* Retrieves all bitstream formats
* @param pagination The pagination info used to retrieve the bitstream formats
*/
public getBitstreamFormats(pagination: PaginationComponentOptions): Observable<RemoteData<PaginatedList<BitstreamFormat>>> {
const requestObs = this.getBitstreamFormatsRequestObs(pagination);
const requestEntryObs = requestObs.pipe(
flatMap((request: RestRequest) => this.requestService.getByHref(request.href))
);
const rbrObs: Observable<RegistryBitstreamformatsResponse> = requestEntryObs.pipe(
getResponseFromEntry(),
map((response: RegistryBitstreamformatsSuccessResponse) => response.bitstreamformatsResponse)
);
const bitstreamformatsObs: Observable<BitstreamFormat[]> = rbrObs.pipe(
map((rbr: RegistryBitstreamformatsResponse) => rbr.bitstreamformats)
);
const pageInfoObs: Observable<PageInfo> = requestEntryObs.pipe(
getResponseFromEntry(),
map((response: RegistryBitstreamformatsSuccessResponse) => response.pageInfo)
);
const payloadObs = observableCombineLatest(bitstreamformatsObs, pageInfoObs).pipe(
map(([bitstreamformats, pageInfo]) => {
return new PaginatedList(pageInfo, bitstreamformats);
})
);
return this.rdb.toRemoteDataObservable(requestEntryObs, payloadObs);
}
private getMetadataSchemasRequestObs(pagination: PaginationComponentOptions): Observable<RestRequest> {
return this.halService.getEndpoint(this.metadataSchemasPath).pipe( return this.halService.getEndpoint(this.metadataSchemasPath).pipe(
map((url: string) => { map((url: string) => {
const args: string[] = []; const args: string[] = [];
@@ -327,30 +288,6 @@ export class RegistryService {
); );
} }
private getBitstreamFormatsRequestObs(pagination: PaginationComponentOptions): Observable<RestRequest> {
return this.halService.getEndpoint(this.bitstreamFormatsPath).pipe(
map((url: string) => {
const args: string[] = [];
args.push(`size=${pagination.pageSize}`);
args.push(`page=${pagination.currentPage - 1}`);
if (isNotEmpty(args)) {
url = new URLCombiner(url, `?${args.join('&')}`).toString();
}
const request = new GetRequest(this.requestService.generateRequestId(), url);
return Object.assign(request, {
getResponseParser(): GenericConstructor<ResponseParsingService> {
return RegistryBitstreamformatsResponseParsingService;
}
});
}),
tap((request: RestRequest) => this.requestService.configure(request)),
);
}
/**
* Method to start editing a metadata schema, dispatches an edit schema action
* @param schema The schema that's being edited
*/
public editMetadataSchema(schema: MetadataSchema) { public editMetadataSchema(schema: MetadataSchema) {
this.store.dispatch(new MetadataRegistryEditSchemaAction(schema)); this.store.dispatch(new MetadataRegistryEditSchemaAction(schema));
} }
@@ -374,7 +311,7 @@ export class RegistryService {
* @param schema The schema that's being selected * @param schema The schema that's being selected
*/ */
public selectMetadataSchema(schema: MetadataSchema) { public selectMetadataSchema(schema: MetadataSchema) {
this.store.dispatch(new MetadataRegistrySelectSchemaAction(schema)) this.store.dispatch(new MetadataRegistrySelectSchemaAction(schema));
} }
/** /**
@@ -382,14 +319,14 @@ export class RegistryService {
* @param schema The schema that's it being deselected * @param schema The schema that's it being deselected
*/ */
public deselectMetadataSchema(schema: MetadataSchema) { public deselectMetadataSchema(schema: MetadataSchema) {
this.store.dispatch(new MetadataRegistryDeselectSchemaAction(schema)) this.store.dispatch(new MetadataRegistryDeselectSchemaAction(schema));
} }
/** /**
* Method to deselect all currently selected metadata schema, dispatches a deselect all schema action * Method to deselect all currently selected metadata schema, dispatches a deselect all schema action
*/ */
public deselectAllMetadataSchema() { public deselectAllMetadataSchema() {
this.store.dispatch(new MetadataRegistryDeselectAllSchemaAction()) this.store.dispatch(new MetadataRegistryDeselectAllSchemaAction());
} }
/** /**
@@ -423,20 +360,20 @@ export class RegistryService {
* @param field The field that's being selected * @param field The field that's being selected
*/ */
public selectMetadataField(field: MetadataField) { public selectMetadataField(field: MetadataField) {
this.store.dispatch(new MetadataRegistrySelectFieldAction(field)) this.store.dispatch(new MetadataRegistrySelectFieldAction(field));
} }
/** /**
* Method to deselect a metadata field, dispatches a deselect field action * Method to deselect a metadata field, dispatches a deselect field action
* @param field The field that's it being deselected * @param field The field that's it being deselected
*/ */
public deselectMetadataField(field: MetadataField) { public deselectMetadataField(field: MetadataField) {
this.store.dispatch(new MetadataRegistryDeselectFieldAction(field)) this.store.dispatch(new MetadataRegistryDeselectFieldAction(field));
} }
/** /**
* Method to deselect all currently selected metadata fields, dispatches a deselect all field action * Method to deselect all currently selected metadata fields, dispatches a deselect all field action
*/ */
public deselectAllMetadataField() { public deselectAllMetadataField() {
this.store.dispatch(new MetadataRegistryDeselectAllFieldAction()) this.store.dispatch(new MetadataRegistryDeselectAllFieldAction());
} }
/** /**
@@ -521,7 +458,7 @@ export class RegistryService {
public clearMetadataSchemaRequests(): Observable<string> { public clearMetadataSchemaRequests(): Observable<string> {
return this.halService.getEndpoint(this.metadataSchemasPath).pipe( return this.halService.getEndpoint(this.metadataSchemasPath).pipe(
tap((href: string) => this.requestService.removeByHrefSubstring(href)) tap((href: string) => this.requestService.removeByHrefSubstring(href))
) );
} }
/** /**
@@ -597,7 +534,7 @@ export class RegistryService {
public clearMetadataFieldRequests(): Observable<string> { public clearMetadataFieldRequests(): Observable<string> {
return this.halService.getEndpoint(this.metadataFieldsPath).pipe( return this.halService.getEndpoint(this.metadataFieldsPath).pipe(
tap((href: string) => this.requestService.removeByHrefSubstring(href)) tap((href: string) => this.requestService.removeByHrefSubstring(href))
) );
} }
private delete(path: string, id: number): Observable<RestResponse> { private delete(path: string, id: number): Observable<RestResponse> {
@@ -633,9 +570,9 @@ export class RegistryService {
); );
messages.subscribe(([head, content]) => { messages.subscribe(([head, content]) => {
if (success) { if (success) {
this.notificationsService.success(head, content) this.notificationsService.success(head, content);
} else { } else {
this.notificationsService.error(head, content) this.notificationsService.error(head, content);
} }
}); });
} }

View File

@@ -0,0 +1,5 @@
export enum BitstreamFormatSupportLevel {
Known = 'KNOWN',
Unknown = 'UNKNOWN',
Supported = 'SUPPORTED'
}

View File

@@ -1,6 +1,6 @@
import { CacheableObject, TypedObject } from '../cache/object-cache.reducer'; import { CacheableObject, TypedObject } from '../cache/object-cache.reducer';
import { ResourceType } from './resource-type'; import { ResourceType } from './resource-type';
import { BitstreamFormatSupportLevel } from './bitstream-format-support-level';
/** /**
* Model class for a Bitstream Format * Model class for a Bitstream Format
@@ -27,7 +27,7 @@ export class BitstreamFormat implements CacheableObject {
/** /**
* The level of support the system offers for this Bitstream Format * The level of support the system offers for this Bitstream Format
*/ */
supportLevel: number; supportLevel: BitstreamFormatSupportLevel;
/** /**
* True if the Bitstream Format is used to store system information, rather than the content of items in the system * True if the Bitstream Format is used to store system information, rather than the content of items in the system
@@ -37,7 +37,7 @@ export class BitstreamFormat implements CacheableObject {
/** /**
* String representing this Bitstream Format's file extension * String representing this Bitstream Format's file extension
*/ */
extensions: string; extensions: string[];
/** /**
* The link to the rest endpoint where this Bitstream Format can be found * The link to the rest endpoint where this Bitstream Format can be found
@@ -49,4 +49,11 @@ export class BitstreamFormat implements CacheableObject {
*/ */
uuid: string; uuid: string;
/**
* Identifier for this Bitstream Format
* Note that this ID is unique for bitstream formats,
* but might not be unique across different object types
*/
id: string;
} }

View File

@@ -14,7 +14,7 @@
<ng-container #componentViewContainer></ng-container> <ng-container #componentViewContainer></ng-container>
<small *ngIf="hasHint" class="text-muted" [innerHTML]="model.hint" [ngClass]="getClass('element', 'hint')"></small> <small *ngIf="hasHint" class="text-muted" [innerHTML]="model.hint | translate" [ngClass]="getClass('element', 'hint')"></small>
<div *ngIf="showErrorMessages" [ngClass]="[getClass('element', 'errors'), getClass('grid', 'errors')]"> <div *ngIf="showErrorMessages" [ngClass]="[getClass('element', 'errors'), getClass('grid', 'errors')]">
<small *ngFor="let message of errorMessages" class="invalid-feedback d-block">{{ message | translate:model.validators }}</small> <small *ngFor="let message of errorMessages" class="invalid-feedback d-block">{{ message | translate:model.validators }}</small>