Skip to content

Commit f762c3c

Browse files
authored
feat(add-link-resource-button): Add Link Resource Button (DEV-404) (#645)
* 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
1 parent 8554764 commit f762c3c

10 files changed

Lines changed: 550 additions & 1 deletion

‎src/app/app.module.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ import { SearchSelectOntologyComponent } from './workspace/search/advanced-searc
155155
import { ExpertSearchComponent } from './workspace/search/expert-search/expert-search.component';
156156
import { FulltextSearchComponent } from './workspace/search/fulltext-search/fulltext-search.component';
157157
import { SearchPanelComponent } from './workspace/search/search-panel/search-panel.component';
158+
import { CreateLinkResourceComponent } from './workspace/resource/operations/create-link-resource/create-link-resource.component';
158159

159160
// translate: AoT requires an exported function for factories
160161
export function httpLoaderFactory(httpClient: HttpClient) {
@@ -298,6 +299,7 @@ export function httpLoaderFactory(httpClient: HttpClient) {
298299
UsersComponent,
299300
UsersListComponent,
300301
YetAnotherDateValueComponent,
302+
CreateLinkResourceComponent,
301303
],
302304
imports: [
303305
AngularSplitModule.forRoot(),

‎src/app/main/dialog/dialog.component.html‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,13 @@
399399
</mat-dialog-content>
400400
</div>
401401

402+
<div *ngSwitchCase="'createLinkResource'">
403+
<app-dialog-header [title]="data.title" [subtitle]="'Create a new linked resource'"></app-dialog-header>
404+
<mat-dialog-content>
405+
<app-create-link-resource [parentResource]="data.parentResource" [propDef]="data.id" [resourceClassDef]="data.resourceClassDefinition" (closeDialog)="dialogRef.close($event)"></app-create-link-resource>
406+
</mat-dialog-content>
407+
</div>
408+
402409
<!-- general error message -->
403410
<div *ngSwitchCase="'error'">
404411
<app-error [status]="data.id"></app-error>

‎src/app/main/dialog/dialog.component.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Component, Inject, OnInit, ViewChild } from '@angular/core';
22
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
3+
import { ReadResource } from '@dasch-swiss/dsp-js';
34
import { PropertyInfoObject } from 'src/app/project/ontology/default-data/default-properties';
45
import { FilteredResources } from 'src/app/workspace/results/list-view/list-view.component';
56

@@ -16,8 +17,10 @@ export interface DialogData {
1617
canBeUpdated?: boolean;
1718
position?: number;
1819
parentIri?: string;
20+
parentResource?: ReadResource;
1921
projectCode?: string;
2022
selectedResources?: FilteredResources;
23+
resourceClassDefinition?: string;
2124
}
2225

2326
export interface ConfirmationWithComment {
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<form [formGroup]="propertiesForm" (ngSubmit)="onSubmit()">
2+
<!-- upload file -->
3+
<app-upload *ngIf="hasFileValue"
4+
[parentForm]="propertiesForm"
5+
[representation]="hasFileValue"
6+
(fileInfo)="setFileValue($event)">
7+
</app-upload>
8+
9+
<app-select-properties #selectProps
10+
[ontologyInfo]="ontologyInfo"
11+
[resourceClass]="resourceClass"
12+
[properties]="properties"
13+
[parentForm]="propertiesForm"
14+
class="select-properties">
15+
</app-select-properties>
16+
<div class="form-panel form-action">
17+
<span>
18+
<button mat-button type="button" (click)="closeDialog.emit()">
19+
{{ 'appLabels.form.action.cancel' | translate }}
20+
</button>
21+
</span>
22+
<span class="fill-remaining-space"></span>
23+
<span>
24+
<button mat-raised-button type="submit" color="primary" class="form-submit" [disabled]="!propertiesForm.valid">
25+
{{ 'appLabels.form.action.submit' | translate}}
26+
</button>
27+
</span>
28+
</div>
29+
</form>

‎src/app/workspace/resource/operations/create-link-resource/create-link-resource.component.scss‎

Whitespace-only changes.
Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
import { Component, DebugElement, Inject, Input, OnInit, QueryList, ViewChild, ViewChildren } from '@angular/core';
2+
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
3+
import { FormBuilder, FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
4+
import { MatButtonModule } from '@angular/material/button';
5+
import { MatDialogModule } from '@angular/material/dialog';
6+
import { MatFormFieldModule } from '@angular/material/form-field';
7+
import { MatSnackBarModule } from '@angular/material/snack-bar';
8+
import { By } from '@angular/platform-browser';
9+
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
10+
import {
11+
CreateIntValue,
12+
CreateResource,
13+
CreateTextValueAsString,
14+
CreateValue,
15+
MockOntology,
16+
MockResource,
17+
ReadResource,
18+
ResourceClassAndPropertyDefinitions,
19+
ResourceClassDefinition,
20+
ResourcePropertyDefinition,
21+
ResourcesEndpointV2
22+
} from '@dasch-swiss/dsp-js';
23+
import { OntologyCache } from '@dasch-swiss/dsp-js/src/cache/ontology-cache/OntologyCache';
24+
import { TranslateModule } from '@ngx-translate/core';
25+
import { of } from 'rxjs';
26+
import { DspApiConnectionToken } from 'src/app/main/declarations/dsp-api-tokens';
27+
import { BaseValueDirective } from 'src/app/main/directive/base-value.directive';
28+
import { SwitchPropertiesComponent } from '../../resource-instance-form/select-properties/switch-properties/switch-properties.component';
29+
import { ValueService } from '../../services/value.service';
30+
import { IntValueComponent } from '../../values/int-value/int-value.component';
31+
import { TextValueAsStringComponent } from '../../values/text-value/text-value-as-string/text-value-as-string.component';
32+
33+
import { CreateLinkResourceComponent } from './create-link-resource.component';
34+
35+
/**
36+
* test host component to simulate parent component.
37+
*/
38+
@Component({
39+
template: `
40+
<app-create-link-resource [parentResource]="parentResource" [propDef]="propDef" [resourceClassDef]="resourceClassDef" #createLinkResourceComp></app-create-link-resource>`
41+
})
42+
class TestHostComponent implements OnInit {
43+
44+
@ViewChild('createLinkResourceComp') createLinkResourceComponent: CreateLinkResourceComponent;
45+
46+
parentResource: ReadResource;
47+
propDef: string;
48+
resourceClassDef: string;
49+
50+
ngOnInit() {
51+
MockResource.getTestThing().subscribe(res => {
52+
this.parentResource = res;
53+
this.propDef = 'http://0.0.0.0:3333/ontology/0001/anything/v2#hasOtherThingValue';
54+
this.resourceClassDef = 'http://0.0.0.0:3333/ontology/0001/anything/v2#Thing';
55+
});
56+
}
57+
58+
}
59+
60+
/**
61+
* mock select-properties component to use in tests.
62+
*/
63+
@Component({
64+
selector: 'app-select-properties',
65+
template: `
66+
<app-text-value-as-string #createVal
67+
[mode]="'create'"
68+
[commentDisabled]="true"
69+
[valueRequiredValidator]="true"
70+
[parentForm]="parentForm"
71+
[formName]="'label'">
72+
</app-text-value-as-string>
73+
`
74+
})
75+
class MockSelectPropertiesComponent {
76+
@ViewChildren('switchProp') switchPropertiesComponent: QueryList<SwitchPropertiesComponent>;
77+
78+
// input for resource's label
79+
@ViewChild('createVal') createValueComponent: BaseValueDirective;
80+
81+
@Input() properties: ResourcePropertyDefinition[];
82+
83+
@Input() ontologyInfo: ResourceClassAndPropertyDefinitions;
84+
85+
@Input() resourceClass: ResourceClassDefinition;
86+
87+
@Input() parentForm: FormGroup;
88+
89+
parentResource = new ReadResource();
90+
91+
constructor(private _valueService: ValueService) { }
92+
}
93+
94+
/**
95+
* mock switch-properties component to use in tests.
96+
*/
97+
@Component({
98+
selector: 'app-switch-properties'
99+
})
100+
class MockSwitchPropertiesComponent {
101+
@ViewChild('createVal') createValueComponent: BaseValueDirective;
102+
103+
@Input() property: ResourcePropertyDefinition;
104+
105+
@Input() parentResource: ReadResource;
106+
107+
@Input() parentForm: FormGroup;
108+
109+
@Input() formName: string;
110+
}
111+
112+
/**
113+
* mock value component to use in tests.
114+
*/
115+
@Component({
116+
selector: 'dsp-int-value'
117+
})
118+
class MockCreateIntValueComponent implements OnInit {
119+
120+
@ViewChild('createVal') createValueComponent: IntValueComponent;
121+
122+
@Input() parentForm: FormGroup;
123+
124+
@Input() formName: string;
125+
126+
@Input() mode;
127+
128+
@Input() displayValue;
129+
130+
form: FormGroup;
131+
132+
valueFormControl: FormControl;
133+
134+
constructor(@Inject(FormBuilder) private _fb: FormBuilder) { }
135+
136+
ngOnInit(): void {
137+
this.valueFormControl = new FormControl(null, [Validators.required]);
138+
139+
this.form = this._fb.group({
140+
test: this.valueFormControl
141+
});
142+
}
143+
144+
getNewValue(): CreateValue {
145+
const createIntVal = new CreateIntValue();
146+
147+
createIntVal.int = 123;
148+
149+
return createIntVal;
150+
}
151+
152+
updateCommentVisibility(): void { }
153+
}
154+
155+
/**
156+
* mock value component to use in tests.
157+
*/
158+
@Component({
159+
selector: 'app-text-value-as-string'
160+
})
161+
class MockCreateTextValueComponent implements OnInit {
162+
163+
@ViewChild('createVal') createValueComponent: TextValueAsStringComponent;
164+
165+
@Input() parentForm: FormGroup;
166+
167+
@Input() formName: string;
168+
169+
@Input() mode;
170+
171+
@Input() displayValue;
172+
173+
@Input() commentDisabled?: boolean;
174+
175+
@Input() valueRequiredValidator: boolean;
176+
177+
form: FormGroup;
178+
179+
valueFormControl: FormControl;
180+
constructor(@Inject(FormBuilder) private _fb: FormBuilder) { }
181+
ngOnInit(): void {
182+
this.valueFormControl = new FormControl(null, [Validators.required]);
183+
this.form = this._fb.group({
184+
label: this.valueFormControl
185+
});
186+
}
187+
getNewValue(): CreateValue {
188+
const createTextVal = new CreateTextValueAsString();
189+
createTextVal.text = 'My Label';
190+
return createTextVal;
191+
}
192+
updateCommentVisibility(): void { }
193+
}
194+
195+
describe('CreateLinkResourceComponent', () => {
196+
let testHostComponent: TestHostComponent;
197+
let testHostFixture: ComponentFixture<TestHostComponent>;
198+
let createLinkResourceComponentDe: DebugElement;
199+
200+
beforeEach(waitForAsync(() => {
201+
202+
const dspConnSpy = {
203+
v2: {
204+
ontologyCache: jasmine.createSpyObj('ontologyCache', ['getOntology', 'getResourceClassDefinition']),
205+
res: jasmine.createSpyObj('res', ['createResource'])
206+
}
207+
};
208+
209+
TestBed.configureTestingModule({
210+
declarations: [
211+
CreateLinkResourceComponent,
212+
TestHostComponent,
213+
MockSelectPropertiesComponent,
214+
MockCreateTextValueComponent,
215+
MockSwitchPropertiesComponent,
216+
MockCreateIntValueComponent
217+
],
218+
imports: [
219+
BrowserAnimationsModule,
220+
MatButtonModule,
221+
MatDialogModule,
222+
MatFormFieldModule,
223+
MatSnackBarModule,
224+
ReactiveFormsModule,
225+
TranslateModule.forRoot()
226+
],
227+
providers: [
228+
{
229+
provide: DspApiConnectionToken,
230+
useValue: dspConnSpy
231+
},
232+
ValueService
233+
]
234+
})
235+
.compileComponents();
236+
}));
237+
238+
beforeEach(() => {
239+
const dspConnSpy = TestBed.inject(DspApiConnectionToken);
240+
241+
(dspConnSpy.v2.ontologyCache as jasmine.SpyObj<OntologyCache>).getResourceClassDefinition.and.callFake(
242+
(resClassIri: string) => of(MockOntology.mockIResourceClassAndPropertyDefinitions('http://0.0.0.0:3333/ontology/0001/anything/v2#Thing'))
243+
);
244+
245+
testHostFixture = TestBed.createComponent(TestHostComponent);
246+
testHostComponent = testHostFixture.componentInstance;
247+
testHostFixture.detectChanges();
248+
expect(testHostComponent).toBeTruthy();
249+
250+
const hostCompDe = testHostFixture.debugElement;
251+
252+
createLinkResourceComponentDe = hostCompDe.query(By.directive(CreateLinkResourceComponent));
253+
254+
});
255+
256+
it('should initialize the properties array', async () => {
257+
expect(testHostComponent.createLinkResourceComponent.properties.length).toEqual(18);
258+
});
259+
260+
it('should submit the form', () => {
261+
const dspConnSpy = TestBed.inject(DspApiConnectionToken);
262+
263+
(dspConnSpy.v2.res as jasmine.SpyObj<ResourcesEndpointV2>).createResource.and.callFake(
264+
() => {
265+
let resource = new ReadResource();
266+
267+
MockResource.getTestThing().subscribe((res) => {
268+
resource = res;
269+
});
270+
271+
return of(resource);
272+
}
273+
);
274+
275+
const anythingOnto = MockOntology.mockReadOntology('http://0.0.0.0:3333/ontology/0001/anything/v2');
276+
277+
// get resource class definitions
278+
const resourceClasses = anythingOnto.getClassDefinitionsByType(ResourceClassDefinition);
279+
280+
testHostComponent.createLinkResourceComponent.properties = new Array<ResourcePropertyDefinition>();
281+
282+
MockResource.getTestThing().subscribe(res => {
283+
const resourcePropDef = (res.entityInfo as ResourceClassAndPropertyDefinitions).getAllPropertyDefinitions()[9];
284+
testHostComponent.createLinkResourceComponent.properties.push(resourcePropDef as ResourcePropertyDefinition);
285+
});
286+
287+
testHostFixture.detectChanges();
288+
289+
const selectPropertiesComp = createLinkResourceComponentDe.query(By.directive(MockSelectPropertiesComponent));
290+
291+
expect(selectPropertiesComp).toBeTruthy();
292+
293+
const label = new CreateTextValueAsString();
294+
label.text = 'My Label';
295+
296+
const props = {};
297+
const createVal = new CreateIntValue();
298+
createVal.int = 123;
299+
props['http://0.0.0.0:3333/ontology/0001/anything/v2#hasInteger'] = [createVal];
300+
301+
const expectedCreateResource = new CreateResource();
302+
expectedCreateResource.label = 'My Label';
303+
expectedCreateResource.type = 'http://0.0.0.0:3333/ontology/0001/anything/v2#Thing';
304+
expectedCreateResource.properties = props;
305+
306+
testHostComponent.createLinkResourceComponent.onSubmit();
307+
308+
expect(dspConnSpy.v2.res.createResource).toHaveBeenCalledTimes(1);
309+
});
310+
});

0 commit comments

Comments
 (0)