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({
selector: 'ds-metadata-schema-form',
templateUrl: './metadata-schema-form.component.html',
// styleUrls: ['./metadata-schema-form.component.css']
templateUrl: './metadata-schema-form.component.html'
})
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 { Observable } from 'rxjs/internal/Observable';
import { MetadataField } from '../../../../core/metadata/metadatafield.model';
import { take } from 'rxjs/operators';
@Component({
selector: 'ds-metadata-field-form',
templateUrl: './metadata-field-form.component.html',
// styleUrls: ['./metadata-field-form.component.css']
templateUrl: './metadata-field-form.component.html'
})
export class MetadataFieldFormComponent implements OnInit {
@@ -60,7 +60,6 @@ export class MetadataFieldFormComponent implements OnInit {
}
ngOnInit() {
this.formGroup = this.formBuilderService.createFormGroup(this.formModel);
this.registryService.getActiveMetadataField().subscribe((field) => {
this.formGroup.patchValue({
@@ -74,36 +73,33 @@ export class MetadataFieldFormComponent implements OnInit {
});
}
getMetadataField(): Observable<MetadataField> {
return this.registryService.getActiveMetadataField();
}
onCancel() {
this.registryService.cancelEditMetadataField();
}
onSubmit() {
this.registryService.getActiveMetadataField().subscribe(
this.registryService.getActiveMetadataField().pipe(take(1)).subscribe(
(field) => {
const values = {
schema: this.metadataSchema,
element: this.element.value,
qualifier: this.qualifier.value,
scopeNote: this.scopeNote.value
};
if (field == null) {
console.log('metadata field to create:');
console.log('element: ' + this.element.value);
if (this.qualifier.value) {
console.log('qualifier: ' + this.qualifier.value);
}
if (this.scopeNote.value) {
console.log('scopeNote: ' + this.scopeNote.value);
}
this.registryService.createOrUpdateMetadataField(Object.assign(new MetadataField(), values)).subscribe((newField) => {
this.submitForm.emit(newField);
});
} else {
console.log('metadata field to update:');
console.log('element: ' + this.element.value);
if (this.qualifier.value) {
console.log('qualifier: ' + this.qualifier.value);
}
if (this.scopeNote.value) {
console.log('scopeNote: ' + this.scopeNote.value);
}
this.registryService.createOrUpdateMetadataField(Object.assign(new MetadataField(), {
id: field.id,
schema: this.metadataSchema,
element: (values.element ? values.element : field.element),
qualifier: (values.qualifier ? values.qualifier : field.qualifier),
scopeNote: (values.scopeNote ? values.scopeNote : field.scopeNote)
})).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>
<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>
@@ -28,7 +30,8 @@
</tr>
</thead>
<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>
<label>
<input type="checkbox"
@@ -36,8 +39,8 @@
(change)="selectMetadataField(field, $event)">
</label>
</td>
<td>{{(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)">{{(metadataSchema | async)?.payload?.prefix}}.{{field.element}}<label *ngIf="field.qualifier">.</label>{{field.qualifier}}</td>
<td class="selectable-row" (click)="editField(field)">{{field.scopeNote}}</td>
</tr>
</tbody>
</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 { RegistryService } from '../../../core/registry/registry.service';
import { ActivatedRoute } from '@angular/router';
import { ActivatedRoute, Router } from '@angular/router';
import { Observable } from 'rxjs';
import { RemoteData } from '../../../core/data/remote-data';
import { PaginatedList } from '../../../core/data/paginated-list';
import { MetadataField } from '../../../core/metadata/metadatafield.model';
import { MetadataSchema } from '../../../core/metadata/metadataschema.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({
selector: 'ds-metadata-schema',
templateUrl: './metadata-schema.component.html'
templateUrl: './metadata-schema.component.html',
styleUrls: ['./metadata-schema.component.scss']
})
export class MetadataSchemaComponent implements OnInit {
@@ -25,7 +30,10 @@ export class MetadataSchemaComponent implements OnInit {
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) {
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> {
@@ -80,12 +99,27 @@ export class MetadataSchemaComponent implements OnInit {
}
deleteFields() {
this.registryService.getSelectedMetadataFields().subscribe(
this.registryService.getSelectedMetadataFields().pipe(take(1)).subscribe(
(fields) => {
console.log('metadata fields to delete: ');
const tasks$ = [];
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 { AuthStatus } from '../auth/models/auth-status.model';
import { MetadataSchema } from '../metadata/metadataschema.model';
import { MetadataField } from '../metadata/metadatafield.model';
/* tslint:disable:max-classes-per-file */
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 {
constructor(
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 { Injectable } from '@angular/core';
import { RegistryMetadatafieldsResponse } from '../registry/registry-metadatafields-response.model';
import { hasValue } from '../../shared/empty.util';
@Injectable()
export class RegistryMetadatafieldsResponseParsingService implements ResponseParsingService {
@@ -18,10 +19,14 @@ export class RegistryMetadatafieldsResponseParsingService implements ResponsePar
parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
const payload = data.payload;
const metadatafields = payload._embedded.metadatafields;
metadatafields.forEach((field) => {
field.schema = field._embedded.schema;
});
let metadatafields = [];
if (hasValue(payload._embedded)) {
metadatafields = payload._embedded.metadatafields;
metadatafields.forEach((field) => {
field.schema = field._embedded.schema;
});
}
payload.metadatafields = metadatafields;

View File

@@ -13,6 +13,7 @@ import { RestRequestMethod } from './rest-request-method';
import { BrowseItemsResponseParsingService } from './browse-items-response-parsing-service';
import { RegistryMetadataschemasResponseParsingService } from './registry-metadataschemas-response-parsing.service';
import { MetadataschemaParsingService } from './metadataschema-parsing.service';
import { MetadatafieldParsingService } from './metadatafield-parsing.service';
/* 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 {
statusText: string;
}

View File

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

View File

@@ -20,7 +20,7 @@ import { RemoteDataBuildService } from '../cache/builders/remote-data-build.serv
import { RequestService } from '../data/request.service';
import { RegistryMetadataschemasResponse } from './registry-metadataschemas-response.model';
import {
ErrorResponse, MetadataschemaSuccessResponse,
ErrorResponse, MetadatafieldSuccessResponse, MetadataschemaSuccessResponse,
RegistryBitstreamformatsSuccessResponse,
RegistryMetadatafieldsSuccessResponse,
RegistryMetadataschemasSuccessResponse, RestResponse
@@ -304,7 +304,7 @@ export class RegistryService {
this.store.dispatch(new MetadataRegistryDeselectFieldAction(field))
}
public deselectAllMetadataField(field: MetadataField) {
public deselectAllMetadataField() {
this.store.dispatch(new MetadataRegistryDeselectAllFieldAction())
}
@@ -366,8 +366,79 @@ export class RegistryService {
}
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 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(),
map((endpoint: string) => `${endpoint}/${id}`),
distinctUntilChanged()
@@ -387,10 +458,4 @@ export class RegistryService {
getResponseFromEntry()
);
}
public clearMetadataSchemaRequests(): Observable<string> {
return this.halService.getEndpoint(this.metadataSchemasPath).pipe(
tap((href: string) => this.requestService.removeByHrefSubstring(href))
)
}
}