Skip to content
Permalink
Browse files
feat(resource): optimize resource instance form (DSP-1256) (#518)
* feat(resource): optimize resource instance form

* refactor(resource): clean up code and fix tests

* fix(resource): replace dsp comps by app comps

* fix(properties): adds canDelete input

Co-authored-by: mdelez <60604010+mdelez@users.noreply.github.com>
  • Loading branch information
kilchenmann and mdelez committed Sep 6, 2021
1 parent e7a2c4f commit 51516773a81d97960d6340889b5455094d771cc7
Showing with 232 additions and 216 deletions.
  1. +5 −0 src/app/main/directive/base-value.directive.ts
  2. +5 −4 src/app/workspace/resource/properties/properties.component.html
  3. +1 −1 src/app/workspace/resource/properties/properties.component.spec.ts
  4. +25 −13 src/app/workspace/resource/properties/properties.component.ts
  5. +17 −21 src/app/workspace/resource/resource-instance-form/resource-instance-form.component.html
  6. +10 −3 src/app/workspace/resource/resource-instance-form/resource-instance-form.component.scss
  7. +64 −7 src/app/workspace/resource/resource-instance-form/resource-instance-form.component.spec.ts
  8. +42 −10 src/app/workspace/resource/resource-instance-form/resource-instance-form.component.ts
  9. +25 −6 src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.html
  10. +25 −77 src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.scss
  11. +5 −3 src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.ts
  12. +0 −10 ...kspace/resource/resource-instance-form/select-resource-class/select-resource-class.component.html
  13. +2 −36 ...ace/resource/resource-instance-form/select-resource-class/select-resource-class.component.spec.ts
  14. +2 −23 ...orkspace/resource/resource-instance-form/select-resource-class/select-resource-class.component.ts
  15. +3 −1 src/app/workspace/resource/services/value-operation-event.service.ts
  16. +1 −1 ...app/workspace/resource/values/text-value/text-value-as-string/text-value-as-string.component.html
@@ -31,6 +31,11 @@ export abstract class BaseValueDirective {
*/
@Input() valueRequiredValidator = true;

/**
* disable the comment field
*/
@Input() commentDisabled? = false;

shouldShowComment = false;

/**
@@ -117,23 +117,24 @@ <h3 class="label mat-title">
<div class="property-value">
<!-- the value(s) of the property -->
<div *ngFor="let val of prop.values">
<dsp-display-edit *ngIf="val"
<app-display-edit *ngIf="val"
#displayEdit
[parentResource]="resource.res"
[displayValue]="val"
[propArray]="resource.resProps"
[canDelete]="deleteValueIsAllowed(prop)"
(referredResourceClicked)="openResource($event)"
(referredResourceHovered)="previewResource($event)">
</dsp-display-edit>
</app-display-edit>
</div>
<!-- Add value form -->
<div *ngIf="addValueFormIsVisible && propID === prop.propDef.id">
<dsp-add-value #addValue
<app-add-value #addValue
class="add-value"
[parentResource]="resource.res"
[resourcePropertyDefinition]="$any(resource.res.entityInfo.properties[prop.propDef.id])"
(operationCancelled)="hideAddValueForm()">
</dsp-add-value>
</app-add-value>
</div>
<!-- Add button -->
<div *ngIf="addValueIsAllowed(prop)">
@@ -28,14 +28,14 @@ import {
EmitEvent,
Events,
PropertyInfoValues,
UserService,
ValueOperationEventService
} from '@dasch-swiss/dsp-ui';
import { of, Subscription } from 'rxjs';
import { DspResource } from '../dsp-resource';
import { PropertiesComponent } from './properties.component';
import { IncomingService } from '../incoming.service';
import { DspApiConnectionToken } from 'src/app/main/declarations/dsp-api-tokens';
import { UserService } from '../services/user.service';

/**
* test host component to simulate parent component.
@@ -1,5 +1,6 @@
import { Component, EventEmitter, Inject, Input, OnChanges, OnDestroy, OnInit, Output } from '@angular/core';
import { MatDialog, MatDialogConfig } from '@angular/material/dialog';
import { PageEvent } from '@angular/material/paginator';
import {
ApiResponseData,
ApiResponseError,
@@ -25,24 +26,23 @@ import {
UpdateResourceMetadataResponse,
UserResponse
} from '@dasch-swiss/dsp-js';
import {
AddedEventValue,
DeletedEventValue,
Events,
UpdatedEventValues,
UserService,
ValueOperationEventService,
ValueService
} from '@dasch-swiss/dsp-ui';
import { Subscription } from 'rxjs';
import { DspApiConnectionToken } from 'src/app/main/declarations/dsp-api-tokens';
import { ConfirmationWithComment, DialogComponent } from 'src/app/main/dialog/dialog.component';
import { ErrorHandlerService } from 'src/app/main/error/error-handler.service';
import { NotificationService } from 'src/app/main/services/notification.service';
import { DspResource } from '../dsp-resource';
import { RepresentationConstants } from '../representation/file-representation';
import { IncomingService } from '../incoming.service';
import { PageEvent } from '@angular/material/paginator';
import { DspApiConnectionToken } from 'src/app/main/declarations/dsp-api-tokens';
import { NotificationService } from 'src/app/main/services/notification.service';
import { RepresentationConstants } from '../representation/file-representation';
import { UserService } from '../services/user.service';
import {
AddedEventValue,
DeletedEventValue,
Events,
UpdatedEventValues,
ValueOperationEventService
} from '../services/value-operation-event.service';
import { ValueService } from '../services/value.service';

// object of property information from ontology class, properties and property values
export interface PropertyInfoValues {
@@ -434,6 +434,18 @@ export class PropertiesComponent implements OnInit, OnChanges, OnDestroy {
}
}

/**
* given a resource property, check if its cardinality allows a value to be deleted
*
* @param prop the resource property
*/
deleteValueIsAllowed(prop: PropertyInfoValues): boolean {
const isAllowed = CardinalityUtil.deleteValueForPropertyAllowed(
prop.propDef.id, prop.values.length, this.resource.res.entityInfo.classes[this.resource.res.type]);

return isAllowed;
}

/**
* updates the UI in the event of an existing value being deleted
*
@@ -1,5 +1,6 @@
<!-- Step1: form to select one project, one ontology, one resource with label -->
<form *ngIf="selectResourceForm && showNextStepForm" [formGroup]="selectResourceForm" class="resource-instance-form stepOne form-content" (ngSubmit)="nextStep()">
<!-- Step1: form to select one project, one ontology, one resource -->
<form *ngIf="selectResourceForm && showNextStepForm" [formGroup]="selectResourceForm"
class="resource-instance-form stepOne form-content" (ngSubmit)="nextStep()">

<!-- select one project -->
<div *ngIf="usersProjects?.length > 0">
@@ -14,8 +15,7 @@
<!-- select one ontology -->
<span>
<div *ngIf="ontologiesMetadata?.ontologies.length > 0">
<app-select-ontology
#selectOntology
<app-select-ontology #selectOntology
[formGroup]="selectResourceForm"
[ontologiesMetadata]="ontologiesMetadata"
[selectedOntology]="selectedOntology"
@@ -26,14 +26,11 @@

<!-- select one resource -->
<span *ngIf="resourceClasses?.length > 0">
<app-select-resource-class
#selectResourceClass
<app-select-resource-class #selectResourceClass
[formGroup]="selectResourceForm"
[resourceClassDefinitions]="resourceClasses"
[selectedResourceClass]="selectedResourceClass"
[chosenResourceLabel]="resourceLabel"
(resourceClassSelected)="selectProperties($event)"
(resourceLabel)="getResourceLabel($event)">
(resourceClassSelected)="selectProperties($event)">
</app-select-resource-class>
</span>

@@ -42,20 +39,16 @@
</span>

<!-- action buttons: cancel and next -->
<div class="form-panel large-field">
<div class="form-panel form-action">
<span>
<button mat-button type="button" (click)="closeDialog.emit()">
{{ 'appLabels.form.action.cancel' | translate }}
</button>
</span>
<span class="fill-remaining-space"></span>
<span>
<button
mat-raised-button
type="button"
color="primary"
[disabled]="!selectResourceForm.valid || this.errorMessage"
(click)="nextStep()" class="form-next">
<button mat-raised-button type="button" color="primary"
[disabled]="!selectResourceForm.valid || this.errorMessage" (click)="nextStep()" class="form-next">
Next
</button>
</span>
@@ -67,11 +60,14 @@
<form *ngIf="propertiesParentForm && !showNextStepForm" [formGroup]="propertiesParentForm" class="resource-instance-form stepTwo form-content" (ngSubmit)="submitData()" appInvalidControlScroll>

<!-- upload file -->
<app-upload *ngIf="hasFileValue" [parentForm]="propertiesParentForm" [representation]="hasFileValue" (fileInfo)="setFileValue($event)"></app-upload>
<app-upload *ngIf="hasFileValue"
[parentForm]="propertiesParentForm"
[representation]="hasFileValue"
(fileInfo)="setFileValue($event)">
</app-upload>

<!-- create property values -->
<app-select-properties
#selectProps
<app-select-properties #selectProps
[ontologyInfo]="ontologyInfo"
[resourceClass]="selectedResourceClass"
[properties]="properties"
@@ -80,10 +76,10 @@
</app-select-properties>

<!-- action buttons: previous, cancel and save -->
<div class="form-panel large-field btn-field">
<div class="form-panel form-action">
<span>
<button mat-button type="button" (click)="prevStep($event)">
{{ 'appLabels.form.action.back' | translate }}
&larr;&nbsp;{{ 'appLabels.form.action.back' | translate }}
</button>
</span>
<button mat-button type="button" (click)="closeDialog.emit()">
@@ -1,10 +1,17 @@
.stepTwo {
margin: 24px 56px !important;
width: calc(100% - 144px);

.btn-field {
margin-top: 48px;
margin-bottom: 0;
}
}

.btn-field {
width: 600px !important;
margin-left: 48px;
.form-action {
width: 100%;
margin-top: 24px;
// margin-left: 48px;
}

.errorIfNoElement {
@@ -17,6 +17,7 @@ import {
ApiResponseData,
CreateIntValue,
CreateResource,
CreateTextValueAsString,
CreateValue,
MockOntology,
MockProjects,
@@ -35,7 +36,7 @@ import {
UsersEndpointAdmin
} from '@dasch-swiss/dsp-js';
import { OntologyCache } from '@dasch-swiss/dsp-js/src/cache/ontology-cache/OntologyCache';
import { DspActionModule, IntValueComponent, ValueService } from '@dasch-swiss/dsp-ui';
import { DspActionModule, IntValueComponent, TextValueAsStringComponent, ValueService } from '@dasch-swiss/dsp-ui';
import { TranslateModule } from '@ngx-translate/core';
import { of } from 'rxjs';
import { AjaxResponse } from 'rxjs/ajax';
@@ -75,6 +76,7 @@ class TestHostComponent implements OnInit {
class MockSelectProjectComponent implements OnInit {
@Input() formGroup: FormGroup;
@Input() usersProjects: StoredProject[];
@Input() selectedProject?: string;
@Output() projectSelected = new EventEmitter<string>();

form: FormGroup;
@@ -102,6 +104,7 @@ class MockSelectProjectComponent implements OnInit {
class MockSelectOntologyComponent implements OnInit {
@Input() formGroup: FormGroup;
@Input() ontologiesMetadata: OntologiesMetadata;
@Input() selectedOntology?: string;
@Output() ontologySelected = new EventEmitter<string>();

form: FormGroup;
@@ -129,16 +132,15 @@ class MockSelectOntologyComponent implements OnInit {
class MockSelectResourceClassComponent implements OnInit {
@Input() formGroup: FormGroup;
@Input() resourceClassDefinitions: ResourceClassDefinition[];
@Input() selectedResourceClass?: string;

label: string;
form: FormGroup;

constructor(@Inject(FormBuilder) private _fb: FormBuilder) { }

ngOnInit() {
this.form = this._fb.group({
resources: [null, Validators.required],
label: [null, Validators.required]
resources: [null, Validators.required]
});

resolvedPromise.then(() => {
@@ -152,11 +154,23 @@ class MockSelectResourceClassComponent implements OnInit {
* mock select-properties component to use in tests.
*/
@Component({
selector: 'app-select-properties'
selector: 'app-select-properties',
template: `
<app-text-value-as-string #createVal
[mode]="'create'"
[commentDisabled]="true"
[valueRequiredValidator]="true"
[parentForm]="parentForm"
[formName]="'label'">
</app-text-value-as-string>
`
})
class MockSelectPropertiesComponent {
@ViewChildren('switchProp') switchPropertiesComponent: QueryList<SwitchPropertiesComponent>;

// input for resource's label
@ViewChild('createVal') createValueComponent: BaseValueDirective;

@Input() properties: ResourcePropertyDefinition[];

@Input() ontologyInfo: ResourceClassAndPropertyDefinitions;
@@ -231,6 +245,46 @@ class MockCreateIntValueComponent implements OnInit {
updateCommentVisibility(): void { }
}

/**
* mock value component to use in tests.
*/
@Component({
selector: 'app-text-value-as-string'
})
class MockCreateTextValueComponent implements OnInit {

@ViewChild('createVal') createValueComponent: TextValueAsStringComponent;

@Input() parentForm: FormGroup;

@Input() formName: string;

@Input() mode;

@Input() displayValue;

@Input() commentDisabled?: boolean;

@Input() valueRequiredValidator: boolean;

form: FormGroup;

valueFormControl: FormControl;
constructor(@Inject(FormBuilder) private _fb: FormBuilder) { }
ngOnInit(): void {
this.valueFormControl = new FormControl(null, [Validators.required]);
this.form = this._fb.group({
label: this.valueFormControl
});
}
getNewValue(): CreateValue {
const createTextVal = new CreateTextValueAsString();
createTextVal.text = 'My Label';
return createTextVal;
}
updateCommentVisibility(): void { }
}

describe('ResourceInstanceFormComponent', () => {
let testHostComponent: TestHostComponent;
let testHostFixture: ComponentFixture<TestHostComponent>;
@@ -264,7 +318,8 @@ describe('ResourceInstanceFormComponent', () => {
MockSelectResourceClassComponent,
MockSelectPropertiesComponent,
MockSwitchPropertiesComponent,
MockCreateIntValueComponent
MockCreateIntValueComponent,
MockCreateTextValueComponent
],
imports: [
BrowserAnimationsModule,
@@ -462,7 +517,6 @@ describe('ResourceInstanceFormComponent', () => {
expect(selectResourceClassComp).toBeTruthy();

(selectResourceClassComp.componentInstance as MockSelectResourceClassComponent).form.controls.resources.setValue('http://0.0.0.0:3333/ontology/0001/anything/v2#Thing');
(selectResourceClassComp.componentInstance as MockSelectResourceClassComponent).form.controls.label.setValue('My Label');

testHostComponent.resourceInstanceFormComponent.selectedResourceClass = (selectResourceClassComp.componentInstance as MockSelectResourceClassComponent).resourceClassDefinitions[1];

@@ -521,6 +575,9 @@ describe('ResourceInstanceFormComponent', () => {

expect(selectPropertiesComp).toBeTruthy();

const label = new CreateTextValueAsString();
label.text = 'My Label';

const props = {};
const createVal = new CreateIntValue();
createVal.int = 123;
Loading

0 comments on commit 5151677

Please sign in to comment.