Skip to content

Commit

Permalink
feat(add-link-resource-button): Add Link Resource Button (DEV-404) (#645
Browse files Browse the repository at this point in the history
)

* feat(add-link-resource-button): adds new component to use in popup when clicking on add new link value button while creating a resource

* feat(add-link-resource-button): correctly generates form for the appropriate link resource class

* feat(add-link-resource-button): adds submit button to submit the form and create the resource

* feat(add-link-resource-button): adds support for uploading files in the form

* refactor(add-link-resource-button): refactors code to be more clear and fixes link value tests

* tests(add-link-resource-button): fixes tests

* tests(add-link-resource-button): adds tests and cleans up all the console logs

* feat(add-link-resource-button): moves the create new resource button to the first list item

* feat(add-link-resource-button): adds resource class label to input placeholder, create new resource button and the title of the create new resource form

* chore: removes a comment
  • Loading branch information
mdelez committed Feb 14, 2022
1 parent 8554764 commit f762c3c
Show file tree
Hide file tree
Showing 10 changed files with 550 additions and 1 deletion.
2 changes: 2 additions & 0 deletions src/app/app.module.ts
Expand Up @@ -155,6 +155,7 @@ import { SearchSelectOntologyComponent } from './workspace/search/advanced-searc
import { ExpertSearchComponent } from './workspace/search/expert-search/expert-search.component';
import { FulltextSearchComponent } from './workspace/search/fulltext-search/fulltext-search.component';
import { SearchPanelComponent } from './workspace/search/search-panel/search-panel.component';
import { CreateLinkResourceComponent } from './workspace/resource/operations/create-link-resource/create-link-resource.component';

// translate: AoT requires an exported function for factories
export function httpLoaderFactory(httpClient: HttpClient) {
Expand Down Expand Up @@ -298,6 +299,7 @@ export function httpLoaderFactory(httpClient: HttpClient) {
UsersComponent,
UsersListComponent,
YetAnotherDateValueComponent,
CreateLinkResourceComponent,
],
imports: [
AngularSplitModule.forRoot(),
Expand Down
7 changes: 7 additions & 0 deletions src/app/main/dialog/dialog.component.html
Expand Up @@ -399,6 +399,13 @@
</mat-dialog-content>
</div>

<div *ngSwitchCase="'createLinkResource'">
<app-dialog-header [title]="data.title" [subtitle]="'Create a new linked resource'"></app-dialog-header>
<mat-dialog-content>
<app-create-link-resource [parentResource]="data.parentResource" [propDef]="data.id" [resourceClassDef]="data.resourceClassDefinition" (closeDialog)="dialogRef.close($event)"></app-create-link-resource>
</mat-dialog-content>
</div>

<!-- general error message -->
<div *ngSwitchCase="'error'">
<app-error [status]="data.id"></app-error>
Expand Down
3 changes: 3 additions & 0 deletions src/app/main/dialog/dialog.component.ts
@@ -1,5 +1,6 @@
import { Component, Inject, OnInit, ViewChild } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { ReadResource } from '@dasch-swiss/dsp-js';
import { PropertyInfoObject } from 'src/app/project/ontology/default-data/default-properties';
import { FilteredResources } from 'src/app/workspace/results/list-view/list-view.component';

Expand All @@ -16,8 +17,10 @@ export interface DialogData {
canBeUpdated?: boolean;
position?: number;
parentIri?: string;
parentResource?: ReadResource;
projectCode?: string;
selectedResources?: FilteredResources;
resourceClassDefinition?: string;
}

export interface ConfirmationWithComment {
Expand Down
@@ -0,0 +1,29 @@
<form [formGroup]="propertiesForm" (ngSubmit)="onSubmit()">
<!-- upload file -->
<app-upload *ngIf="hasFileValue"
[parentForm]="propertiesForm"
[representation]="hasFileValue"
(fileInfo)="setFileValue($event)">
</app-upload>

<app-select-properties #selectProps
[ontologyInfo]="ontologyInfo"
[resourceClass]="resourceClass"
[properties]="properties"
[parentForm]="propertiesForm"
class="select-properties">
</app-select-properties>
<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="submit" color="primary" class="form-submit" [disabled]="!propertiesForm.valid">
{{ 'appLabels.form.action.submit' | translate}}
</button>
</span>
</div>
</form>
@@ -0,0 +1,310 @@
import { Component, DebugElement, Inject, Input, OnInit, QueryList, ViewChild, ViewChildren } from '@angular/core';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { FormBuilder, FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatDialogModule } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatSnackBarModule } from '@angular/material/snack-bar';
import { By } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import {
CreateIntValue,
CreateResource,
CreateTextValueAsString,
CreateValue,
MockOntology,
MockResource,
ReadResource,
ResourceClassAndPropertyDefinitions,
ResourceClassDefinition,
ResourcePropertyDefinition,
ResourcesEndpointV2
} from '@dasch-swiss/dsp-js';
import { OntologyCache } from '@dasch-swiss/dsp-js/src/cache/ontology-cache/OntologyCache';
import { TranslateModule } from '@ngx-translate/core';
import { of } from 'rxjs';
import { DspApiConnectionToken } from 'src/app/main/declarations/dsp-api-tokens';
import { BaseValueDirective } from 'src/app/main/directive/base-value.directive';
import { SwitchPropertiesComponent } from '../../resource-instance-form/select-properties/switch-properties/switch-properties.component';
import { ValueService } from '../../services/value.service';
import { IntValueComponent } from '../../values/int-value/int-value.component';
import { TextValueAsStringComponent } from '../../values/text-value/text-value-as-string/text-value-as-string.component';

import { CreateLinkResourceComponent } from './create-link-resource.component';

/**
* test host component to simulate parent component.
*/
@Component({
template: `
<app-create-link-resource [parentResource]="parentResource" [propDef]="propDef" [resourceClassDef]="resourceClassDef" #createLinkResourceComp></app-create-link-resource>`
})
class TestHostComponent implements OnInit {

@ViewChild('createLinkResourceComp') createLinkResourceComponent: CreateLinkResourceComponent;

parentResource: ReadResource;
propDef: string;
resourceClassDef: string;

ngOnInit() {
MockResource.getTestThing().subscribe(res => {
this.parentResource = res;
this.propDef = 'http://0.0.0.0:3333/ontology/0001/anything/v2#hasOtherThingValue';
this.resourceClassDef = 'http://0.0.0.0:3333/ontology/0001/anything/v2#Thing';
});
}

}

/**
* mock select-properties component to use in tests.
*/
@Component({
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;

@Input() resourceClass: ResourceClassDefinition;

@Input() parentForm: FormGroup;

parentResource = new ReadResource();

constructor(private _valueService: ValueService) { }
}

/**
* mock switch-properties component to use in tests.
*/
@Component({
selector: 'app-switch-properties'
})
class MockSwitchPropertiesComponent {
@ViewChild('createVal') createValueComponent: BaseValueDirective;

@Input() property: ResourcePropertyDefinition;

@Input() parentResource: ReadResource;

@Input() parentForm: FormGroup;

@Input() formName: string;
}

/**
* mock value component to use in tests.
*/
@Component({
selector: 'dsp-int-value'
})
class MockCreateIntValueComponent implements OnInit {

@ViewChild('createVal') createValueComponent: IntValueComponent;

@Input() parentForm: FormGroup;

@Input() formName: string;

@Input() mode;

@Input() displayValue;

form: FormGroup;

valueFormControl: FormControl;

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

ngOnInit(): void {
this.valueFormControl = new FormControl(null, [Validators.required]);

this.form = this._fb.group({
test: this.valueFormControl
});
}

getNewValue(): CreateValue {
const createIntVal = new CreateIntValue();

createIntVal.int = 123;

return createIntVal;
}

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('CreateLinkResourceComponent', () => {
let testHostComponent: TestHostComponent;
let testHostFixture: ComponentFixture<TestHostComponent>;
let createLinkResourceComponentDe: DebugElement;

beforeEach(waitForAsync(() => {

const dspConnSpy = {
v2: {
ontologyCache: jasmine.createSpyObj('ontologyCache', ['getOntology', 'getResourceClassDefinition']),
res: jasmine.createSpyObj('res', ['createResource'])
}
};

TestBed.configureTestingModule({
declarations: [
CreateLinkResourceComponent,
TestHostComponent,
MockSelectPropertiesComponent,
MockCreateTextValueComponent,
MockSwitchPropertiesComponent,
MockCreateIntValueComponent
],
imports: [
BrowserAnimationsModule,
MatButtonModule,
MatDialogModule,
MatFormFieldModule,
MatSnackBarModule,
ReactiveFormsModule,
TranslateModule.forRoot()
],
providers: [
{
provide: DspApiConnectionToken,
useValue: dspConnSpy
},
ValueService
]
})
.compileComponents();
}));

beforeEach(() => {
const dspConnSpy = TestBed.inject(DspApiConnectionToken);

(dspConnSpy.v2.ontologyCache as jasmine.SpyObj<OntologyCache>).getResourceClassDefinition.and.callFake(
(resClassIri: string) => of(MockOntology.mockIResourceClassAndPropertyDefinitions('http://0.0.0.0:3333/ontology/0001/anything/v2#Thing'))
);

testHostFixture = TestBed.createComponent(TestHostComponent);
testHostComponent = testHostFixture.componentInstance;
testHostFixture.detectChanges();
expect(testHostComponent).toBeTruthy();

const hostCompDe = testHostFixture.debugElement;

createLinkResourceComponentDe = hostCompDe.query(By.directive(CreateLinkResourceComponent));

});

it('should initialize the properties array', async () => {
expect(testHostComponent.createLinkResourceComponent.properties.length).toEqual(18);
});

it('should submit the form', () => {
const dspConnSpy = TestBed.inject(DspApiConnectionToken);

(dspConnSpy.v2.res as jasmine.SpyObj<ResourcesEndpointV2>).createResource.and.callFake(
() => {
let resource = new ReadResource();

MockResource.getTestThing().subscribe((res) => {
resource = res;
});

return of(resource);
}
);

const anythingOnto = MockOntology.mockReadOntology('http://0.0.0.0:3333/ontology/0001/anything/v2');

// get resource class definitions
const resourceClasses = anythingOnto.getClassDefinitionsByType(ResourceClassDefinition);

testHostComponent.createLinkResourceComponent.properties = new Array<ResourcePropertyDefinition>();

MockResource.getTestThing().subscribe(res => {
const resourcePropDef = (res.entityInfo as ResourceClassAndPropertyDefinitions).getAllPropertyDefinitions()[9];
testHostComponent.createLinkResourceComponent.properties.push(resourcePropDef as ResourcePropertyDefinition);
});

testHostFixture.detectChanges();

const selectPropertiesComp = createLinkResourceComponentDe.query(By.directive(MockSelectPropertiesComponent));

expect(selectPropertiesComp).toBeTruthy();

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

const props = {};
const createVal = new CreateIntValue();
createVal.int = 123;
props['http://0.0.0.0:3333/ontology/0001/anything/v2#hasInteger'] = [createVal];

const expectedCreateResource = new CreateResource();
expectedCreateResource.label = 'My Label';
expectedCreateResource.type = 'http://0.0.0.0:3333/ontology/0001/anything/v2#Thing';
expectedCreateResource.properties = props;

testHostComponent.createLinkResourceComponent.onSubmit();

expect(dspConnSpy.v2.res.createResource).toHaveBeenCalledTimes(1);
});
});

0 comments on commit f762c3c

Please sign in to comment.