58789: Metadata field create/edit/delete feature

This commit is contained in:
Kristof De Langhe
2019-01-22 17:15:14 +01:00
parent 09eb01c545
commit fe44146291
11 changed files with 212 additions and 52 deletions

View File

@@ -13,8 +13,7 @@ import { MetadataSchema } from '../../../../core/metadata/metadataschema.model';
@Component({ @Component({
selector: 'ds-metadata-schema-form', selector: 'ds-metadata-schema-form',
templateUrl: './metadata-schema-form.component.html', templateUrl: './metadata-schema-form.component.html'
// styleUrls: ['./metadata-schema-form.component.css']
}) })
export class MetadataSchemaFormComponent implements OnInit { export class MetadataSchemaFormComponent implements OnInit {

View File

@@ -6,11 +6,11 @@ import { RegistryService } from '../../../../core/registry/registry.service';
import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service'; import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service';
import { Observable } from 'rxjs/internal/Observable'; import { Observable } from 'rxjs/internal/Observable';
import { MetadataField } from '../../../../core/metadata/metadatafield.model'; import { MetadataField } from '../../../../core/metadata/metadatafield.model';
import { take } from 'rxjs/operators';
@Component({ @Component({
selector: 'ds-metadata-field-form', selector: 'ds-metadata-field-form',
templateUrl: './metadata-field-form.component.html', templateUrl: './metadata-field-form.component.html'
// styleUrls: ['./metadata-field-form.component.css']
}) })
export class MetadataFieldFormComponent implements OnInit { export class MetadataFieldFormComponent implements OnInit {
@@ -60,7 +60,6 @@ export class MetadataFieldFormComponent implements OnInit {
} }
ngOnInit() { ngOnInit() {
this.formGroup = this.formBuilderService.createFormGroup(this.formModel); this.formGroup = this.formBuilderService.createFormGroup(this.formModel);
this.registryService.getActiveMetadataField().subscribe((field) => { this.registryService.getActiveMetadataField().subscribe((field) => {
this.formGroup.patchValue({ this.formGroup.patchValue({
@@ -74,36 +73,33 @@ export class MetadataFieldFormComponent implements OnInit {
}); });
} }
getMetadataField(): Observable<MetadataField> {
return this.registryService.getActiveMetadataField();
}
onCancel() { onCancel() {
this.registryService.cancelEditMetadataField(); this.registryService.cancelEditMetadataField();
} }
onSubmit() { onSubmit() {
this.registryService.getActiveMetadataField().subscribe( this.registryService.getActiveMetadataField().pipe(take(1)).subscribe(
(field) => { (field) => {
const values = {
schema: this.metadataSchema,
element: this.element.value,
qualifier: this.qualifier.value,
scopeNote: this.scopeNote.value
};
if (field == null) { if (field == null) {
console.log('metadata field to create:'); this.registryService.createOrUpdateMetadataField(Object.assign(new MetadataField(), values)).subscribe((newField) => {
console.log('element: ' + this.element.value); this.submitForm.emit(newField);
if (this.qualifier.value) { });
console.log('qualifier: ' + this.qualifier.value);
}
if (this.scopeNote.value) {
console.log('scopeNote: ' + this.scopeNote.value);
}
} else { } else {
console.log('metadata field to update:'); this.registryService.createOrUpdateMetadataField(Object.assign(new MetadataField(), {
console.log('element: ' + this.element.value); id: field.id,
if (this.qualifier.value) { schema: this.metadataSchema,
console.log('qualifier: ' + this.qualifier.value); element: (values.element ? values.element : field.element),
} qualifier: (values.qualifier ? values.qualifier : field.qualifier),
if (this.scopeNote.value) { scopeNote: (values.scopeNote ? values.scopeNote : field.scopeNote)
console.log('scopeNote: ' + this.scopeNote.value); })).subscribe((updatedField) => {
} this.submitForm.emit(updatedField);
});
} }
} }
); );

View File

@@ -6,7 +6,9 @@
<p id="description" class="pb-2">{{'admin.registries.schema.description' | translate:namespace }}</p> <p id="description" class="pb-2">{{'admin.registries.schema.description' | translate:namespace }}</p>
<ds-metadata-field-form [metadataSchema]="metadataSchema | async"></ds-metadata-field-form> <ds-metadata-field-form
[metadataSchema]="(metadataSchema | async)?.payload"
(submitForm)="forceUpdateFields()"></ds-metadata-field-form>
<h3>{{'admin.registries.schema.fields.head' | translate}}</h3> <h3>{{'admin.registries.schema.fields.head' | translate}}</h3>
@@ -28,7 +30,8 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr *ngFor="let field of (metadataFields | async)?.payload?.page" (click)="editField(field)"> <tr *ngFor="let field of (metadataFields | async)?.payload?.page"
[ngClass]="{'table-primary' : isActive(field) | async}">
<td> <td>
<label> <label>
<input type="checkbox" <input type="checkbox"
@@ -36,8 +39,8 @@
(change)="selectMetadataField(field, $event)"> (change)="selectMetadataField(field, $event)">
</label> </label>
</td> </td>
<td>{{(metadataSchema | async)?.payload?.prefix}}.{{field.element}}<label *ngIf="field.qualifier">.</label>{{field.qualifier}}</td> <td class="selectable-row" (click)="editField(field)">{{(metadataSchema | async)?.payload?.prefix}}.{{field.element}}<label *ngIf="field.qualifier">.</label>{{field.qualifier}}</td>
<td>{{field.scopeNote}}</td> <td class="selectable-row" (click)="editField(field)">{{field.scopeNote}}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View File

@@ -0,0 +1,5 @@
@import '../../../../styles/variables.scss';
.selectable-row:hover {
cursor: pointer;
}

View File

@@ -1,17 +1,22 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { RegistryService } from '../../../core/registry/registry.service'; import { RegistryService } from '../../../core/registry/registry.service';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { Observable } 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 { MetadataField } from '../../../core/metadata/metadatafield.model'; import { MetadataField } from '../../../core/metadata/metadatafield.model';
import { MetadataSchema } from '../../../core/metadata/metadataschema.model'; import { MetadataSchema } from '../../../core/metadata/metadataschema.model';
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
import { map } from 'rxjs/operators'; import { map, take } from 'rxjs/operators';
import { hasValue } from '../../../shared/empty.util';
import { RestResponse } from '../../../core/cache/response.models';
import { zip } from 'rxjs/internal/observable/zip';
import { NotificationsService } from '../../../shared/notifications/notifications.service';
@Component({ @Component({
selector: 'ds-metadata-schema', selector: 'ds-metadata-schema',
templateUrl: './metadata-schema.component.html' templateUrl: './metadata-schema.component.html',
styleUrls: ['./metadata-schema.component.scss']
}) })
export class MetadataSchemaComponent implements OnInit { export class MetadataSchemaComponent implements OnInit {
@@ -25,7 +30,10 @@ export class MetadataSchemaComponent implements OnInit {
pageSizeOptions: [25, 50, 100, 200] pageSizeOptions: [25, 50, 100, 200]
}); });
constructor(private registryService: RegistryService, private route: ActivatedRoute) { constructor(private registryService: RegistryService,
private route: ActivatedRoute,
private notificationsService: NotificationsService,
private router: Router) {
} }
@@ -53,8 +61,19 @@ export class MetadataSchemaComponent implements OnInit {
}); });
} }
private forceUpdateFields() {
this.registryService.clearMetadataFieldRequests().subscribe();
this.updateFields();
}
editField(field: MetadataField) { editField(field: MetadataField) {
this.registryService.editMetadataField(field); this.getActiveField().pipe(take(1)).subscribe((activeField) => {
if (field === activeField) {
this.registryService.cancelEditMetadataField();
} else {
this.registryService.editMetadataField(field);
}
});
} }
isActive(field: MetadataField): Observable<boolean> { isActive(field: MetadataField): Observable<boolean> {
@@ -80,12 +99,27 @@ export class MetadataSchemaComponent implements OnInit {
} }
deleteFields() { deleteFields() {
this.registryService.getSelectedMetadataFields().subscribe( this.registryService.getSelectedMetadataFields().pipe(take(1)).subscribe(
(fields) => { (fields) => {
console.log('metadata fields to delete: '); const tasks$ = [];
for (const field of fields) { for (const field of fields) {
console.log(field); if (hasValue(field.id)) {
tasks$.push(this.registryService.deleteMetadataSchema(field.id));
}
} }
zip(...tasks$).subscribe((responses: RestResponse[]) => {
const successResponses = responses.filter((response: RestResponse) => response.isSuccessful);
const failedResponses = responses.filter((response: RestResponse) => !response.isSuccessful);
if (successResponses.length > 0) {
this.notificationsService.success('Success', `Successfully deleted ${successResponses.length} metadata fields`);
}
if (failedResponses.length > 0) {
this.notificationsService.error('Error', `Failed to delete ${failedResponses.length} metadata fields`);
}
this.registryService.deselectAllMetadataField();
this.router.navigate([], { queryParams: { page: 1 }, queryParamsHandling: 'merge'});
this.forceUpdateFields();
});
} }
) )
} }

View File

@@ -10,6 +10,7 @@ import { RegistryMetadatafieldsResponse } from '../registry/registry-metadatafie
import { RegistryBitstreamformatsResponse } from '../registry/registry-bitstreamformats-response.model'; import { RegistryBitstreamformatsResponse } from '../registry/registry-bitstreamformats-response.model';
import { AuthStatus } from '../auth/models/auth-status.model'; import { AuthStatus } from '../auth/models/auth-status.model';
import { MetadataSchema } from '../metadata/metadataschema.model'; import { MetadataSchema } from '../metadata/metadataschema.model';
import { MetadataField } from '../metadata/metadatafield.model';
/* tslint:disable:max-classes-per-file */ /* tslint:disable:max-classes-per-file */
export class RestResponse { export class RestResponse {
@@ -71,6 +72,15 @@ export class MetadataschemaSuccessResponse extends RestResponse {
} }
} }
export class MetadatafieldSuccessResponse extends RestResponse {
constructor(
public metadatafield: MetadataField,
public statusCode: string
) {
super(true, statusCode);
}
}
export class SearchSuccessResponse extends RestResponse { export class SearchSuccessResponse extends RestResponse {
constructor( constructor(
public results: SearchQueryResponse, public results: SearchQueryResponse,

View File

@@ -0,0 +1,19 @@
import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer';
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
import { RestRequest } from './request.models';
import { ResponseParsingService } from './parsing.service';
import { Injectable } from '@angular/core';
import { MetadatafieldSuccessResponse, MetadataschemaSuccessResponse, RestResponse } from '../cache/response.models';
import { MetadataField } from '../metadata/metadatafield.model';
@Injectable()
export class MetadatafieldParsingService implements ResponseParsingService {
parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
const payload = data.payload;
const deserialized = new DSpaceRESTv2Serializer(MetadataField).deserialize(payload);
return new MetadatafieldSuccessResponse(deserialized, data.statusCode);
}
}

View File

@@ -9,6 +9,7 @@ import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.seriali
import { DSOResponseParsingService } from './dso-response-parsing.service'; import { DSOResponseParsingService } from './dso-response-parsing.service';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { RegistryMetadatafieldsResponse } from '../registry/registry-metadatafields-response.model'; import { RegistryMetadatafieldsResponse } from '../registry/registry-metadatafields-response.model';
import { hasValue } from '../../shared/empty.util';
@Injectable() @Injectable()
export class RegistryMetadatafieldsResponseParsingService implements ResponseParsingService { export class RegistryMetadatafieldsResponseParsingService implements ResponseParsingService {
@@ -18,10 +19,14 @@ export class RegistryMetadatafieldsResponseParsingService implements ResponsePar
parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse { parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
const payload = data.payload; const payload = data.payload;
const metadatafields = payload._embedded.metadatafields; let metadatafields = [];
metadatafields.forEach((field) => {
field.schema = field._embedded.schema; if (hasValue(payload._embedded)) {
}); metadatafields = payload._embedded.metadatafields;
metadatafields.forEach((field) => {
field.schema = field._embedded.schema;
});
}
payload.metadatafields = metadatafields; payload.metadatafields = metadatafields;

View File

@@ -13,6 +13,7 @@ import { RestRequestMethod } from './rest-request-method';
import { BrowseItemsResponseParsingService } from './browse-items-response-parsing-service'; import { BrowseItemsResponseParsingService } from './browse-items-response-parsing-service';
import { RegistryMetadataschemasResponseParsingService } from './registry-metadataschemas-response-parsing.service'; import { RegistryMetadataschemasResponseParsingService } from './registry-metadataschemas-response-parsing.service';
import { MetadataschemaParsingService } from './metadataschema-parsing.service'; import { MetadataschemaParsingService } from './metadataschema-parsing.service';
import { MetadatafieldParsingService } from './metadatafield-parsing.service';
/* tslint:disable:max-classes-per-file */ /* tslint:disable:max-classes-per-file */
@@ -239,6 +240,26 @@ export class UpdateMetadataSchemaRequest extends PutRequest {
} }
} }
export class CreateMetadataFieldRequest extends PostRequest {
constructor(uuid: string, href: string, public body?: any, public options?: HttpOptions) {
super(uuid, href, body, options);
}
getResponseParser(): GenericConstructor<ResponseParsingService> {
return MetadatafieldParsingService;
}
}
export class UpdateMetadataFieldRequest extends PutRequest {
constructor(uuid: string, href: string, public body?: any, public options?: HttpOptions) {
super(uuid, href, body, options);
}
getResponseParser(): GenericConstructor<ResponseParsingService> {
return MetadatafieldParsingService;
}
}
export class RequestError extends Error { export class RequestError extends Error {
statusText: string; statusText: string;
} }

View File

@@ -3,6 +3,9 @@ import { autoserialize } from 'cerialize';
import { ListableObject } from '../../shared/object-collection/shared/listable-object.model'; import { ListableObject } from '../../shared/object-collection/shared/listable-object.model';
export class MetadataField implements ListableObject { export class MetadataField implements ListableObject {
@autoserialize
id: number;
@autoserialize @autoserialize
self: string; self: string;

View File

@@ -20,7 +20,7 @@ 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, MetadataschemaSuccessResponse, ErrorResponse, MetadatafieldSuccessResponse, MetadataschemaSuccessResponse,
RegistryBitstreamformatsSuccessResponse, RegistryBitstreamformatsSuccessResponse,
RegistryMetadatafieldsSuccessResponse, RegistryMetadatafieldsSuccessResponse,
RegistryMetadataschemasSuccessResponse, RestResponse RegistryMetadataschemasSuccessResponse, RestResponse
@@ -304,7 +304,7 @@ export class RegistryService {
this.store.dispatch(new MetadataRegistryDeselectFieldAction(field)) this.store.dispatch(new MetadataRegistryDeselectFieldAction(field))
} }
public deselectAllMetadataField(field: MetadataField) { public deselectAllMetadataField() {
this.store.dispatch(new MetadataRegistryDeselectAllFieldAction()) this.store.dispatch(new MetadataRegistryDeselectAllFieldAction())
} }
@@ -366,8 +366,79 @@ export class RegistryService {
} }
public deleteMetadataSchema(id: number): Observable<RestResponse> { public deleteMetadataSchema(id: number): Observable<RestResponse> {
return this.delete(this.metadataSchemasPath, id);
}
public clearMetadataSchemaRequests(): Observable<string> {
return this.halService.getEndpoint(this.metadataSchemasPath).pipe(
tap((href: string) => this.requestService.removeByHrefSubstring(href))
)
}
public createOrUpdateMetadataField(field: MetadataField): Observable<MetadataField> {
const isUpdate = hasValue(field.id);
const requestId = this.requestService.generateRequestId(); const requestId = this.requestService.generateRequestId();
const endpoint$ = this.halService.getEndpoint(this.metadataSchemasPath).pipe( const endpoint$ = this.halService.getEndpoint(this.metadataFieldsPath).pipe(
isNotEmptyOperator(),
map((endpoint: string) => (isUpdate ? `${endpoint}/${field.id}` : `${endpoint}?schemaId=${field.schema.id}`)),
distinctUntilChanged()
);
const request$ = endpoint$.pipe(
take(1),
map((endpoint: string) => {
if (isUpdate) {
const options: HttpOptions = Object.create({});
let headers = new HttpHeaders();
headers = headers.append('Content-Type', 'application/json');
options.headers = headers;
return new UpdateMetadataSchemaRequest(requestId, endpoint, JSON.stringify(field), options);
} else {
return new CreateMetadataSchemaRequest(requestId, endpoint, JSON.stringify(field));
}
})
);
// Execute the post/put request
request$.pipe(
configureRequest(this.requestService)
).subscribe();
// Return created/updated field
return this.requestService.getByUUID(requestId).pipe(
getResponseFromEntry(),
map((response: RestResponse) => {
if (!response.isSuccessful) {
if (hasValue((response as any).errorMessage)) {
this.notificationsService.error('Server Error:', (response as any).errorMessage, new NotificationOptions(-1));
}
} else {
this.notificationsService.success('Success', `Successfully ${isUpdate ? 'updated' : 'created'} metadata field "${field.schema.prefix}.${field.element}.${field.qualifier}"`);
return response;
}
}),
isNotEmptyOperator(),
map((response: MetadatafieldSuccessResponse) => {
if (isNotEmpty(response.metadatafield)) {
return response.metadatafield;
}
})
);
}
public deleteMetadataField(id: number): Observable<RestResponse> {
return this.delete(this.metadataFieldsPath, id);
}
public clearMetadataFieldRequests(): Observable<string> {
return this.halService.getEndpoint(this.metadataFieldsPath).pipe(
tap((href: string) => this.requestService.removeByHrefSubstring(href))
)
}
private delete(path: string, id: number): Observable<RestResponse> {
const requestId = this.requestService.generateRequestId();
const endpoint$ = this.halService.getEndpoint(path).pipe(
isNotEmptyOperator(), isNotEmptyOperator(),
map((endpoint: string) => `${endpoint}/${id}`), map((endpoint: string) => `${endpoint}/${id}`),
distinctUntilChanged() distinctUntilChanged()
@@ -387,10 +458,4 @@ export class RegistryService {
getResponseFromEntry() getResponseFromEntry()
); );
} }
public clearMetadataSchemaRequests(): Observable<string> {
return this.halService.getEndpoint(this.metadataSchemasPath).pipe(
tap((href: string) => this.requestService.removeByHrefSubstring(href))
)
}
} }