From 523af24459e284e55bd13e463237339bfb02ea43 Mon Sep 17 00:00:00 2001 From: Tobias Schweizer Date: Thu, 6 May 2021 11:24:07 +0200 Subject: [PATCH] feat(advanced search): try to reuse resource and property selection (DSP-1587) (#290) * feat(advanced search): try to reuse resource and property selection * feat(advanced search): restrict resource classes by linking property's object constraint * feat(advanced search): restrict resource classes by linking property's object constraint * feat(advanced search): make new parent component for resource and property selection * feat(advanced search): move new parent component into lib * feat(advanced search): use new component in advanced search form * test(advanced search): adapt tests (ongoing) * test(advanced search): adapt tests (ongoing) * test(advanced search): adapt tests (ongoing) * fix(dev build): remove exports from module * test(advanced search): adapt tests (ongoing) * test(advanced search): adapt tests (ongoing) * test(advanced search): adapt tests (ongoing) * test(advanced search): adapt tests (ongoing) * refactor(advanced search): start from validation on AfterViewChecked * test(advanced search): fix e2e tests (ongoing) * test(advanced search): fix e2e tests (ongoing) * test(advanced search): do not install specific version of webdriver * refactor(advanced search): move CSS * test(advanced search): simplify spec * test(advanced search): simplify spec * test(advanced search): simplify spec --- .github/workflows/main.yml | 1 - e2e/src/app.e2e-spec.ts | 34 +- package.json | 3 +- .../advanced-search.component.html | 35 +- .../advanced-search.component.scss | 9 - .../advanced-search.component.spec.ts | 252 +------------- .../advanced-search.component.ts | 142 ++------ ...urce-and-property-selection.component.html | 33 ++ ...urce-and-property-selection.component.scss | 7 + ...e-and-property-selection.component.spec.ts | 329 ++++++++++++++++++ ...source-and-property-selection.component.ts | 149 ++++++++ .../select-resource-class.component.scss | 3 + .../dsp-ui/src/lib/search/search.module.ts | 4 +- 13 files changed, 577 insertions(+), 424 deletions(-) create mode 100644 projects/dsp-ui/src/lib/search/advanced-search/resource-and-property-selection/resource-and-property-selection.component.html create mode 100644 projects/dsp-ui/src/lib/search/advanced-search/resource-and-property-selection/resource-and-property-selection.component.scss create mode 100644 projects/dsp-ui/src/lib/search/advanced-search/resource-and-property-selection/resource-and-property-selection.component.spec.ts create mode 100644 projects/dsp-ui/src/lib/search/advanced-search/resource-and-property-selection/resource-and-property-selection.component.ts diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b16b04a5a..9b4062cb1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -62,7 +62,6 @@ jobs: run: npm run build-lib - name: Run knora stack run: make knora-stack - - run: npm run webdriver-update - name: Run e2e tests run: npm run e2e env: diff --git a/e2e/src/app.e2e-spec.ts b/e2e/src/app.e2e-spec.ts index b84510cf4..94927658f 100644 --- a/e2e/src/app.e2e-spec.ts +++ b/e2e/src/app.e2e-spec.ts @@ -88,9 +88,7 @@ describe('Test App', () => { const loader = ProtractorHarnessEnvironment.loader(); - const submitButton = await page.getAdvancedSearchSubmitButton(loader); - - expect(await submitButton.isDisabled()).toBe(true); + expect(await (await page.getAdvancedSearchSubmitButton(loader)).isDisabled()).toBe(true); const selectOntos = await page.getAdvancedSearchOntologySelection(loader, timeout); @@ -121,7 +119,7 @@ describe('Test App', () => { await resClasses.clickOptions({ text: 'Thing'}); - expect(await submitButton.isDisabled()).toBe(false); + expect(await (await page.getAdvancedSearchSubmitButton(loader)).isDisabled()).toBe(false); // browser.sleep(200000); @@ -134,9 +132,7 @@ describe('Test App', () => { const loader = ProtractorHarnessEnvironment.loader(); - const submitButton = await page.getAdvancedSearchSubmitButton(loader); - - expect(await submitButton.isDisabled()).toBe(true); + expect(await (await page.getAdvancedSearchSubmitButton(loader)).isDisabled()).toBe(true); const selectOntos = await page.getAdvancedSearchOntologySelection(loader, timeout); @@ -146,13 +142,13 @@ describe('Test App', () => { await browser.wait(EC.presenceOf(element(by.css('.select-resource-class'))), timeout, 'Wait for resource class options to be visible.'); - expect(await submitButton.isDisabled()).toBe(true); + expect(await (await page.getAdvancedSearchSubmitButton(loader)).isDisabled()).toBe(true); const addPropButton = await page.getAdvancedSearchPropertyAddButton(loader); await addPropButton.click(); - expect(await submitButton.isDisabled()).toBe(true); + expect(await (await page.getAdvancedSearchSubmitButton(loader)).isDisabled()).toBe(true); const selectProps = await page.getAdvancedSearchPropertySelection(loader); @@ -160,19 +156,19 @@ describe('Test App', () => { await selectProps.clickOptions({text: 'Integer'}); - expect(await submitButton.isDisabled()).toBe(true); + expect(await (await page.getAdvancedSearchSubmitButton(loader)).isDisabled()).toBe(true); const selectCompOps = await page.getAdvancedSearchComparisonOperatorSelection(loader); await selectCompOps.clickOptions({ text: 'is equal to'}); - expect(await submitButton.isDisabled()).toBe(true); + expect(await (await page.getAdvancedSearchSubmitButton(loader)).isDisabled()).toBe(true); const input = await loader.getHarness(MatInputHarness); await input.setValue('1'); - expect(await submitButton.isDisabled()).toBe(false); + expect(await (await page.getAdvancedSearchSubmitButton(loader)).isDisabled()).toBe(false); // browser.sleep(200000); }); @@ -184,9 +180,7 @@ describe('Test App', () => { const loader = ProtractorHarnessEnvironment.loader(); - const submitButton = await page.getAdvancedSearchSubmitButton(loader); - - expect(await submitButton.isDisabled()).toBe(true); + expect(await (await page.getAdvancedSearchSubmitButton(loader)).isDisabled()).toBe(true); const selectOntos = await page.getAdvancedSearchOntologySelection(loader, timeout); @@ -196,13 +190,13 @@ describe('Test App', () => { await browser.wait(EC.presenceOf(element(by.css('.select-resource-class'))), timeout, 'Wait for resource class options to be visible.'); - expect(await submitButton.isDisabled()).toBe(true); + expect(await (await page.getAdvancedSearchSubmitButton(loader)).isDisabled()).toBe(true); const addPropButton = await page.getAdvancedSearchPropertyAddButton(loader); await addPropButton.click(); - expect(await submitButton.isDisabled()).toBe(true); + expect(await (await page.getAdvancedSearchSubmitButton(loader)).isDisabled()).toBe(true); const selectProps = await page.getAdvancedSearchPropertySelection(loader); @@ -210,13 +204,13 @@ describe('Test App', () => { await selectProps.clickOptions({text: 'Another thing'}); - expect(await submitButton.isDisabled()).toBe(true); + expect(await (await page.getAdvancedSearchSubmitButton(loader)).isDisabled()).toBe(true); const selectCompOps = await page.getAdvancedSearchComparisonOperatorSelection(loader); await selectCompOps.clickOptions({ text: 'is equal to'}); - expect(await submitButton.isDisabled()).toBe(true); + expect(await (await page.getAdvancedSearchSubmitButton(loader)).isDisabled()).toBe(true); const input = await loader.getHarness(MatInputHarness); @@ -236,7 +230,7 @@ describe('Test App', () => { await options[0].click(); - expect(await submitButton.isDisabled()).toBe(false); + expect(await (await page.getAdvancedSearchSubmitButton(loader)).isDisabled()).toBe(false); // browser.sleep(200000); }); diff --git a/package.json b/package.json index 0ba9c7f45..ac8cd72db 100644 --- a/package.json +++ b/package.json @@ -30,8 +30,7 @@ "yalc-publish-lib": "npm run build-lib && yalc publish dist/@dasch-swiss/dsp-ui", "build-app": "ng b --prod", "lint": "ng lint", - "e2e": "ng e2e --prod=true --protractor-config=./e2e/protractor.conf.js --webdriver-update=false", - "webdriver-update": "webdriver-manager update --standalone false --gecko false --versions.chrome 2.37" + "e2e": "ng e2e --prod=true --protractor-config=./e2e/protractor.conf.js --webdriver-update" }, "private": true, "dependencies": { diff --git a/projects/dsp-ui/src/lib/search/advanced-search/advanced-search.component.html b/projects/dsp-ui/src/lib/search/advanced-search/advanced-search.component.html index 45065fa48..884ab26b4 100644 --- a/projects/dsp-ui/src/lib/search/advanced-search/advanced-search.component.html +++ b/projects/dsp-ui/src/lib/search/advanced-search/advanced-search.component.html @@ -2,41 +2,14 @@
+ (ontologySelected)="setActiveOntology($event)">
-
- - -
- -
-
- - - -
-
- -
- - - -
+ +
- diff --git a/projects/dsp-ui/src/lib/search/advanced-search/advanced-search.component.scss b/projects/dsp-ui/src/lib/search/advanced-search/advanced-search.component.scss index 1bd605717..8b1378917 100644 --- a/projects/dsp-ui/src/lib/search/advanced-search/advanced-search.component.scss +++ b/projects/dsp-ui/src/lib/search/advanced-search/advanced-search.component.scss @@ -1,10 +1 @@ -.select-resource-class { - margin-left: 8px; -} -.select-property { - margin-left: 16px; - .property-button { - margin: 0 12px 64px 0; - } -} diff --git a/projects/dsp-ui/src/lib/search/advanced-search/advanced-search.component.spec.ts b/projects/dsp-ui/src/lib/search/advanced-search/advanced-search.component.spec.ts index 51a4c4148..1b26aaa40 100644 --- a/projects/dsp-ui/src/lib/search/advanced-search/advanced-search.component.spec.ts +++ b/projects/dsp-ui/src/lib/search/advanced-search/advanced-search.component.spec.ts @@ -1,23 +1,13 @@ import { HarnessLoader } from '@angular/cdk/testing'; import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'; -import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { FormGroup, ReactiveFormsModule } from '@angular/forms'; -import { MatButtonHarness } from '@angular/material/button/testing'; import { MatIconModule } from '@angular/material/icon'; import { MatSnackBarModule } from '@angular/material/snack-bar'; import { By } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { - ClassDefinition, - MockOntology, - OntologiesEndpointV2, - OntologiesMetadata, - OntologyMetadata, PropertyDefinition, - ReadOntology, - ResourceClassDefinition, ResourcePropertyDefinition -} from '@dasch-swiss/dsp-js'; -import { OntologyCache } from '@dasch-swiss/dsp-js/src/cache/ontology-cache/OntologyCache'; +import { OntologiesEndpointV2, OntologiesMetadata, OntologyMetadata } from '@dasch-swiss/dsp-js'; import { of } from 'rxjs'; import { DspApiConnectionToken } from '../../core/core.module'; import { AdvancedSearchComponent } from './advanced-search.component'; @@ -44,46 +34,19 @@ class TestSelectOntologyComponent implements OnInit { } /** - * Test component to simulate select ontology component. - */ -@Component({ - selector: 'dsp-select-resource-class', - template: `` -}) -class TestSelectResourceClassComponent implements OnInit { - - @Input() formGroup: FormGroup; - - @Input() resourceClassDefinitions: ResourceClassDefinition[]; - - @Output() resourceClassSelected = new EventEmitter(); - - ngOnInit() { - - } - -} - -/** - * Test component to simulate select ontology component. + * Test component to simulate select resource class and property component. */ @Component({ - selector: 'dsp-select-property', + selector: 'dsp-resource-and-property-selection', template: `` }) -class TestSelectPropertyComponent implements OnInit { +class TestSelectResourceClassAndPropertyComponent { @Input() formGroup: FormGroup; - @Input() properties: ResourcePropertyDefinition[]; - - @Input() index: number; + @Input() activeOntology: string; - @Input() activeResourceClass: ResourceClassDefinition; - - ngOnInit() { - - } + @Input() resClassRestriction?: string; } @@ -113,8 +76,7 @@ describe('AdvancedSearchComponent', () => { const dspConnSpy = { v2: { - onto: jasmine.createSpyObj('onto', ['getOntologiesMetadata']), - ontologyCache: jasmine.createSpyObj('ontologyCache', ['getOntology', 'getResourceClassDefinition']) + onto: jasmine.createSpyObj('onto', ['getOntologiesMetadata']) } }; @@ -123,8 +85,7 @@ describe('AdvancedSearchComponent', () => { AdvancedSearchComponent, TestHostComponent, TestSelectOntologyComponent, - TestSelectResourceClassComponent, - TestSelectPropertyComponent + TestSelectResourceClassAndPropertyComponent ], imports: [ ReactiveFormsModule, @@ -196,39 +157,7 @@ describe('AdvancedSearchComponent', () => { }); - it('should disable add property button on init', async () => { - - const addPropButton = await loader.getHarness(MatButtonHarness.with({selector: '.add-property-button'})); - - expect(await addPropButton.isDisabled()).toBe(true); - }); - - it('should disable remove property button on init', async () => { - - const rmPropButton = await loader.getHarness(MatButtonHarness.with({selector: '.remove-property-button'})); - - expect(await rmPropButton.isDisabled()).toBe(true); - - }); - - it('should react when an ontology is selected', async () => { - - const dspConnSpy = TestBed.inject(DspApiConnectionToken); - - (dspConnSpy.v2.ontologyCache as jasmine.SpyObj).getOntology.and.callFake( - (ontoIri: string) => { - - const anythingOnto = MockOntology.mockReadOntology('http://0.0.0.0:3333/ontology/0001/anything/v2'); - const knoraApiOnto = MockOntology.mockReadOntology('http://api.knora.org/ontology/knora-api/v2'); - - const ontoMap: Map = new Map(); - - ontoMap.set('http://api.knora.org/ontology/knora-api/v2', knoraApiOnto); - ontoMap.set('http://0.0.0.0:3333/ontology/0001/anything/v2', anythingOnto); - - return of(ontoMap); - } - ); + it('should set the active ontology when an ontology is selected', () => { const hostCompDe = testHostFixture.debugElement; const selectOntoComp = hostCompDe.query(By.directive(TestSelectOntologyComponent)); @@ -238,170 +167,11 @@ describe('AdvancedSearchComponent', () => { testHostFixture.detectChanges(); expect(testHostComponent.advancedSearch.activeOntology).toEqual('http://0.0.0.0:3333/ontology/0001/anything/v2'); - expect(testHostComponent.advancedSearch.activeResourceClass).toEqual(undefined); - expect(testHostComponent.advancedSearch.resourceClasses.length).toEqual(8); - expect(Object.keys(testHostComponent.advancedSearch.properties).length).toEqual(28); - - const selectResClassComp = hostCompDe.query(By.directive(TestSelectResourceClassComponent)); - expect((selectResClassComp.componentInstance as TestSelectResourceClassComponent).resourceClassDefinitions.length).toEqual(8); - expect(dspConnSpy.v2.ontologyCache.getOntology).toHaveBeenCalledTimes(1); - expect(dspConnSpy.v2.ontologyCache.getOntology).toHaveBeenCalledWith('http://0.0.0.0:3333/ontology/0001/anything/v2'); + expect(testHostComponent.advancedSearch.resourceAndPropertySelection.activeOntology).toEqual('http://0.0.0.0:3333/ontology/0001/anything/v2'); }); - it('should display a property selection when the add property button has been clicked', async () => { - - // simulate state after anything onto selection - testHostComponent.advancedSearch.activeOntology = 'http://0.0.0.0:3333/ontology/0001/anything/v2'; - - const anythingOnto = MockOntology.mockReadOntology('http://0.0.0.0:3333/ontology/0001/anything/v2'); - - // get resource class defs - testHostComponent.advancedSearch.resourceClasses = anythingOnto.getClassDefinitionsByType(ResourceClassDefinition); - - const resProps = anythingOnto.getPropertyDefinitionsByType(ResourcePropertyDefinition); - - testHostComponent.advancedSearch.properties = resProps; - - testHostFixture.detectChanges(); - - expect(testHostComponent.advancedSearch.activeProperties.length).toEqual(0); - - const addPropButton = await loader.getHarness(MatButtonHarness.with({selector: '.add-property-button'})); - - expect(await addPropButton.isDisabled()).toBe(false); - - await addPropButton.click(); - - expect(testHostComponent.advancedSearch.activeProperties.length).toEqual(1); - - const hostCompDe = testHostFixture.debugElement; - const selectPropComp = hostCompDe.query(By.directive(TestSelectPropertyComponent)); - - expect((selectPropComp.componentInstance as TestSelectPropertyComponent).activeResourceClass).toEqual(undefined); - expect((selectPropComp.componentInstance as TestSelectPropertyComponent).index).toEqual(0); - expect((selectPropComp.componentInstance as TestSelectPropertyComponent).properties).toEqual(resProps); - - const rmPropButton = await loader.getHarness(MatButtonHarness.with({selector: '.remove-property-button'})); - - expect(await rmPropButton.isDisabled()).toBe(false ); - }); - - it('should add to and remove from active properties array when property buttons are clicked', async () => { - - // simulate state after anything onto selection - testHostComponent.advancedSearch.activeOntology = 'http://0.0.0.0:3333/ontology/0001/anything/v2'; - - const anythingOnto = MockOntology.mockReadOntology('http://0.0.0.0:3333/ontology/0001/anything/v2'); - - // get resource class defs - testHostComponent.advancedSearch.resourceClasses = anythingOnto.getClassDefinitionsByType(ResourceClassDefinition); - - const resProps = anythingOnto.getPropertyDefinitionsByType(ResourcePropertyDefinition); - - testHostComponent.advancedSearch.properties = resProps; - - testHostFixture.detectChanges(); - - const addPropButton = await loader.getHarness(MatButtonHarness.with({selector: '.add-property-button'})); - - const rmPropButton = await loader.getHarness(MatButtonHarness.with({selector: '.remove-property-button'})); - - expect(testHostComponent.advancedSearch.activeProperties.length).toEqual(0); - - await addPropButton.click(); - - expect(testHostComponent.advancedSearch.activeProperties.length).toEqual(1); - - await addPropButton.click(); - - expect(testHostComponent.advancedSearch.activeProperties.length).toEqual(2); - - await rmPropButton.click(); - - expect(testHostComponent.advancedSearch.activeProperties.length).toEqual(1); - - await rmPropButton.click(); - - expect(testHostComponent.advancedSearch.activeProperties.length).toEqual(0); - }); - - it('should add at max four property selections', async () => { - - // simulate state after anything onto selection - testHostComponent.advancedSearch.activeOntology = 'http://0.0.0.0:3333/ontology/0001/anything/v2'; - - const anythingOnto = MockOntology.mockReadOntology('http://0.0.0.0:3333/ontology/0001/anything/v2'); - - // get resource class defs - testHostComponent.advancedSearch.resourceClasses = anythingOnto.getClassDefinitionsByType(ResourceClassDefinition); - - const resProps = anythingOnto.getPropertyDefinitionsByType(ResourcePropertyDefinition); - - testHostComponent.advancedSearch.properties = resProps; - - testHostComponent.advancedSearch.activeProperties = [true, true, true, true]; - - testHostFixture.detectChanges(); - - const addPropButton = await loader.getHarness(MatButtonHarness.with({selector: '.add-property-button'})); - - expect(await addPropButton.isDisabled()).toEqual(true); - - }); - - it('should react when a resource class is selected', async () => { - - const dspConnSpy = TestBed.inject(DspApiConnectionToken); - - (dspConnSpy.v2.ontologyCache as jasmine.SpyObj).getResourceClassDefinition.and.callFake( - (resClassIri: string) => { - return of(MockOntology.mockIResourceClassAndPropertyDefinitions('http://0.0.0.0:3333/ontology/0001/anything/v2#Thing')); - } - ); - - // simulate state after anything onto selection - testHostComponent.advancedSearch.activeOntology = 'http://0.0.0.0:3333/ontology/0001/anything/v2'; - - const anythingOnto = MockOntology.mockReadOntology('http://0.0.0.0:3333/ontology/0001/anything/v2'); - - // get resource class defs - testHostComponent.advancedSearch.resourceClasses = anythingOnto.getClassDefinitionsByType(ResourceClassDefinition); - - const resProps = anythingOnto.getPropertyDefinitionsByType(ResourcePropertyDefinition); - - testHostComponent.advancedSearch.properties = resProps; - - testHostFixture.detectChanges(); - - const hostCompDe = testHostFixture.debugElement; - const selectResClassComp = hostCompDe.query(By.directive(TestSelectResourceClassComponent)); - - (selectResClassComp.componentInstance as TestSelectResourceClassComponent).resourceClassSelected.emit('http://0.0.0.0:3333/ontology/0001/anything/v2#Thing'); - - testHostFixture.detectChanges(); - - expect(testHostComponent.advancedSearch.activeResourceClass) - .toEqual(MockOntology.mockIResourceClassAndPropertyDefinitions('http://0.0.0.0:3333/ontology/0001/anything/v2#Thing').classes['http://0.0.0.0:3333/ontology/0001/anything/v2#Thing']); - expect(Object.keys(testHostComponent.advancedSearch.properties).length).toEqual(25); - - expect(dspConnSpy.v2.ontologyCache.getResourceClassDefinition).toHaveBeenCalledTimes(1); - expect(dspConnSpy.v2.ontologyCache.getResourceClassDefinition).toHaveBeenCalledWith('http://0.0.0.0:3333/ontology/0001/anything/v2#Thing'); - - const addPropButton = await loader.getHarness(MatButtonHarness.with({selector: '.add-property-button'})); - - await addPropButton.click(); - - const selectPropComp = hostCompDe.query(By.directive(TestSelectPropertyComponent)); - - expect((selectPropComp.componentInstance as TestSelectPropertyComponent).activeResourceClass) - .toEqual(MockOntology.mockIResourceClassAndPropertyDefinitions('http://0.0.0.0:3333/ontology/0001/anything/v2#Thing') - .classes['http://0.0.0.0:3333/ontology/0001/anything/v2#Thing']); - expect((selectPropComp.componentInstance as TestSelectPropertyComponent).index).toEqual(0); - expect(Object.keys((selectPropComp.componentInstance as TestSelectPropertyComponent).properties).length).toEqual(25); - - }); }); }); diff --git a/projects/dsp-ui/src/lib/search/advanced-search/advanced-search.component.ts b/projects/dsp-ui/src/lib/search/advanced-search/advanced-search.component.ts index 837f34cac..4a8268221 100644 --- a/projects/dsp-ui/src/lib/search/advanced-search/advanced-search.component.ts +++ b/projects/dsp-ui/src/lib/search/advanced-search/advanced-search.component.ts @@ -1,4 +1,5 @@ import { + AfterViewChecked, Component, EventEmitter, Inject, @@ -6,36 +7,24 @@ import { OnDestroy, OnInit, Output, - QueryList, - ViewChild, - ViewChildren + ViewChild } from '@angular/core'; import { FormBuilder, FormGroup } from '@angular/forms'; -import { - ApiResponseError, - Constants, - KnoraApiConnection, - OntologiesMetadata, - ReadOntology, - ResourceClassAndPropertyDefinitions, - ResourceClassDefinition, - ResourcePropertyDefinition -} from '@dasch-swiss/dsp-js'; +import { ApiResponseError, Constants, KnoraApiConnection, OntologiesMetadata } from '@dasch-swiss/dsp-js'; import { Subscription } from 'rxjs'; import { NotificationService } from '../../action/services/notification.service'; import { DspApiConnectionToken } from '../../core/core.module'; import { SearchParams } from '../../viewer/views/list-view/list-view.component'; import { GravsearchGenerationService } from '../services/gravsearch-generation.service'; -import { SelectPropertyComponent } from './select-property/select-property.component'; +import { ResourceAndPropertySelectionComponent } from './resource-and-property-selection/resource-and-property-selection.component'; import { PropertyWithValue } from './select-property/specify-property-value/operator'; -import { SelectResourceClassComponent } from './select-resource-class/select-resource-class.component'; @Component({ selector: 'dsp-advanced-search', templateUrl: './advanced-search.component.html', styleUrls: ['./advanced-search.component.scss'] }) -export class AdvancedSearchComponent implements OnInit, OnDestroy { +export class AdvancedSearchComponent implements OnInit, OnDestroy, AfterViewChecked { /** * Filter ontologies by specified project IRI @@ -61,23 +50,12 @@ export class AdvancedSearchComponent implements OnInit, OnDestroy { activeOntology: string; - activeResourceClass: ResourceClassDefinition; - - resourceClasses: ResourceClassDefinition[]; - - activeProperties: boolean[] = []; - - properties: ResourcePropertyDefinition[]; - formChangesSubscription: Subscription; errorMessage: ApiResponseError; // reference to the component that controls the resource class selection - @ViewChild('resourceClass') resourceClassComponent: SelectResourceClassComponent; - - // reference to the component controlling the property selection - @ViewChildren('property') propertyComponents: QueryList; + @ViewChild('resAndPropSel') resourceAndPropertySelection: ResourceAndPropertySelectionComponent; constructor( @Inject(FormBuilder) private _fb: FormBuilder, @@ -91,12 +69,15 @@ export class AdvancedSearchComponent implements OnInit, OnDestroy { // parent form is empty, it gets passed to the child components this.form = this._fb.group({}); + // initialize ontologies to be used for the ontologies selection in the search form + this.initializeOntologies(); + } + + ngAfterViewChecked() { // if form status changes, re-run validation this.formChangesSubscription = this.form.statusChanges.subscribe((data) => { this.formValid = this._validateForm(); }); - // initialize ontologies to be used for the ontologies selection in the search form - this.initializeOntologies(); } ngOnDestroy() { @@ -105,24 +86,6 @@ export class AdvancedSearchComponent implements OnInit, OnDestroy { } } - /** - * @ignore - * Add a property to the search form. - * @returns void - */ - addProperty(): void { - this.activeProperties.push(true); - } - - /** - * @ignore - * Remove the last property from the search form. - * @returns void - */ - removeProperty(): void { - this.activeProperties.splice(-1, 1); - } - /** * @ignore * Gets all available ontologies for the search form. @@ -155,66 +118,10 @@ export class AdvancedSearchComponent implements OnInit, OnDestroy { this.errorMessage = error; }); } - - } - /** - * Initialises resources classes and properties, - * when an ontology is selected - * - * @param ontologyIri the Iri of the selected ontology. - */ - getResourceClassesAndPropertiesForOntology(ontologyIri: string) { - - // reset active resource class definition - this.activeResourceClass = undefined; - - // reset specified properties - this.activeProperties = []; - + setActiveOntology(ontologyIri: string) { this.activeOntology = ontologyIri; - - this._dspApiConnection.v2.ontologyCache.getOntology(ontologyIri).subscribe( - (onto: Map) => { - - this.resourceClasses = onto.get(ontologyIri).getClassDefinitionsByType(ResourceClassDefinition); - - this.properties = onto.get(ontologyIri).getPropertyDefinitionsByType(ResourcePropertyDefinition); - }, - error => { - this._notification.openSnackBar(error); - } - ); - } - - /** - * @ignore - * Once a resource class has been selected, gets its properties. - * The properties will be made available to the user for selection. - * - * @param resourceClassIri the IRI of the selected resource class, if any. - */ - getPropertiesForResourceClass(resourceClassIri: string | null) { - - // reset specified properties - this.activeProperties = []; - - // if the client undoes the selection of a resource class, use the active ontology as a fallback - if (resourceClassIri === null) { - this.getResourceClassesAndPropertiesForOntology(this.activeOntology); - } else { - - this._dspApiConnection.v2.ontologyCache.getResourceClassDefinition(resourceClassIri).subscribe( - (onto: ResourceClassAndPropertyDefinitions) => { - - this.activeResourceClass = onto.classes[resourceClassIri]; - - this.properties = onto.getPropertyDefinitionsByType(ResourcePropertyDefinition); - - } - ); - } } /** @@ -223,20 +130,17 @@ export class AdvancedSearchComponent implements OnInit, OnDestroy { */ private _validateForm(): boolean { + if (this.resourceAndPropertySelection === undefined + || this.resourceAndPropertySelection.resourceClassComponent === undefined + || this.resourceAndPropertySelection.propertyComponents === undefined) { + return false; + } + // check that either a resource class is selected or at least one property is specified return this.form.valid && - (this.propertyComponents.length > 0 || (this.resourceClassComponent !== undefined && this.resourceClassComponent.selectedResourceClassIri !== false)); - - } - - /** - * @ignore - * Resets the form (selected resource class and specified properties) preserving the active ontology. - */ - resetForm() { - if (this.activeOntology !== undefined) { - this.getResourceClassesAndPropertiesForOntology(this.activeOntology); - } + (this.resourceAndPropertySelection.propertyComponents.length > 0 + || this.resourceAndPropertySelection.resourceClassComponent.selectedResourceClassIri !== false + ); } submit() { @@ -245,7 +149,7 @@ export class AdvancedSearchComponent implements OnInit, OnDestroy { return; // check that form is valid } - const resClassOption = this.resourceClassComponent.selectedResourceClassIri; + const resClassOption = this.resourceAndPropertySelection.resourceClassComponent.selectedResourceClassIri; let resClass; @@ -253,7 +157,7 @@ export class AdvancedSearchComponent implements OnInit, OnDestroy { resClass = resClassOption; } - const properties: PropertyWithValue[] = this.propertyComponents.map( + const properties: PropertyWithValue[] = this.resourceAndPropertySelection.propertyComponents.map( (propComp) => { return propComp.getPropertySelectedWithValue(); } diff --git a/projects/dsp-ui/src/lib/search/advanced-search/resource-and-property-selection/resource-and-property-selection.component.html b/projects/dsp-ui/src/lib/search/advanced-search/resource-and-property-selection/resource-and-property-selection.component.html new file mode 100644 index 000000000..7592823b2 --- /dev/null +++ b/projects/dsp-ui/src/lib/search/advanced-search/resource-and-property-selection/resource-and-property-selection.component.html @@ -0,0 +1,33 @@ +
+ +
+ + +
+ +
+
+ + + +
+
+ +
+ + + +
+ +
diff --git a/projects/dsp-ui/src/lib/search/advanced-search/resource-and-property-selection/resource-and-property-selection.component.scss b/projects/dsp-ui/src/lib/search/advanced-search/resource-and-property-selection/resource-and-property-selection.component.scss new file mode 100644 index 000000000..d7bdb4c4d --- /dev/null +++ b/projects/dsp-ui/src/lib/search/advanced-search/resource-and-property-selection/resource-and-property-selection.component.scss @@ -0,0 +1,7 @@ +.select-property { + margin-left: 16px; + + .property-button { + margin: 0 12px 64px 0; + } +} diff --git a/projects/dsp-ui/src/lib/search/advanced-search/resource-and-property-selection/resource-and-property-selection.component.spec.ts b/projects/dsp-ui/src/lib/search/advanced-search/resource-and-property-selection/resource-and-property-selection.component.spec.ts new file mode 100644 index 000000000..84282e2c4 --- /dev/null +++ b/projects/dsp-ui/src/lib/search/advanced-search/resource-and-property-selection/resource-and-property-selection.component.spec.ts @@ -0,0 +1,329 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ResourceAndPropertySelectionComponent } from './resource-and-property-selection.component'; +import { HarnessLoader } from '@angular/cdk/testing'; +import { Component, EventEmitter, Inject, Input, OnInit, Output, ViewChild } from '@angular/core'; +import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms'; +import { DspApiConnectionToken } from '../../../core'; +import { OntologyCache } from '@dasch-swiss/dsp-js/src/cache/ontology-cache/OntologyCache'; +import { MockOntology, ReadOntology, ResourceClassDefinition, ResourcePropertyDefinition } from '@dasch-swiss/dsp-js'; +import { of } from 'rxjs'; +import { MatIconModule } from '@angular/material/icon'; +import { By } from '@angular/platform-browser'; +import { MatButtonHarness } from '@angular/material/button/testing'; +import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; + +/** + * Test host component to simulate select resource class component. + */ +@Component({ + selector: '', + template: `` +}) +class TestSelectResourceClassComponent { + + @Input() formGroup: FormGroup; + + @Input() resourceClassDefinitions: ResourceClassDefinition[]; + + @Output() resourceClassSelected = new EventEmitter(); + +} + +/** + * Test host component to simulate select property component. + */ +@Component({ + selector: '', + template: `` +}) +class TestSelectPropertyComponent { + + // parent FormGroup + @Input() formGroup: FormGroup; + + // index of the given property (unique) + @Input() index: number; + + // properties that can be selected from + @Input() properties: ResourcePropertyDefinition[]; + + @Input() activeResourceClass: ResourceClassDefinition; + +} + +/** + * Test host component to simulate parent component. + */ +@Component({ + template: ` + ` +}) +class TestHostComponent implements OnInit { + + @ViewChild('resClassAndProp') resourceClassAndPropertySelection: ResourceAndPropertySelectionComponent; + + form: FormGroup; + + activeOntology = 'http://0.0.0.0:3333/ontology/0001/anything/v2'; + + restrictByResourceClass?: string; + + constructor(@Inject(FormBuilder) private _fb: FormBuilder) { + } + + ngOnInit() { + this.form = this._fb.group({}); + } + +} + +describe('ResourceAndPropertySelectionComponent', () => { + let testHostComponent: TestHostComponent; + let testHostFixture: ComponentFixture; + + let loader: HarnessLoader; + + beforeEach(async () => { + + const dspConnSpy = { + v2: { + ontologyCache: jasmine.createSpyObj('ontologyCache', ['getOntology', 'getResourceClassDefinition']) + } + }; + + await TestBed.configureTestingModule({ + imports: [ + ReactiveFormsModule, + MatIconModule + ], + declarations: [ + ResourceAndPropertySelectionComponent, + TestHostComponent, + TestSelectResourceClassComponent, + TestSelectPropertyComponent + ], + providers: [ + { + provide: DspApiConnectionToken, + useValue: dspConnSpy + } + ] + }) + .compileComponents(); + }); + + beforeEach(() => { + + const dspConnSpy = TestBed.inject(DspApiConnectionToken); + + (dspConnSpy.v2.ontologyCache as jasmine.SpyObj).getOntology.and.callFake( + (ontoIri: string) => { + + const anythingOnto = MockOntology.mockReadOntology('http://0.0.0.0:3333/ontology/0001/anything/v2'); + const knoraApiOnto = MockOntology.mockReadOntology('http://api.knora.org/ontology/knora-api/v2'); + + const ontoMap: Map = new Map(); + + ontoMap.set('http://api.knora.org/ontology/knora-api/v2', knoraApiOnto); + ontoMap.set('http://0.0.0.0:3333/ontology/0001/anything/v2', anythingOnto); + + return of(ontoMap); + } + ); + + testHostFixture = TestBed.createComponent(TestHostComponent); + testHostComponent = testHostFixture.componentInstance; + + loader = TestbedHarnessEnvironment.loader(testHostFixture); + + testHostFixture.detectChanges(); + }); + + it('should create', () => { + expect(testHostComponent).toBeTruthy(); + }); + + it('should request the active ontology', () => { + + const dspConnSpy = TestBed.inject(DspApiConnectionToken); + + const hostCompDe = testHostFixture.debugElement; + + expect(testHostComponent.resourceClassAndPropertySelection.activeOntology).toEqual('http://0.0.0.0:3333/ontology/0001/anything/v2'); + + expect(testHostComponent.resourceClassAndPropertySelection.activeResourceClass).toEqual(undefined); + expect(testHostComponent.resourceClassAndPropertySelection.resourceClasses.length).toEqual(8); + expect(Object.keys(testHostComponent.resourceClassAndPropertySelection.properties).length).toEqual(28); + + const selectResClassComp = hostCompDe.query(By.directive(TestSelectResourceClassComponent)); + expect((selectResClassComp.componentInstance as TestSelectResourceClassComponent).resourceClassDefinitions.length).toEqual(8); + + expect(dspConnSpy.v2.ontologyCache.getOntology).toHaveBeenCalledTimes(1); + expect(dspConnSpy.v2.ontologyCache.getOntology).toHaveBeenCalledWith('http://0.0.0.0:3333/ontology/0001/anything/v2'); + + }); + + it('should react when a resource class is selected', async () => { + + const dspConnSpy = TestBed.inject(DspApiConnectionToken); + + (dspConnSpy.v2.ontologyCache as jasmine.SpyObj).getResourceClassDefinition.and.callFake( + (resClassIri: string) => { + return of(MockOntology.mockIResourceClassAndPropertyDefinitions('http://0.0.0.0:3333/ontology/0001/anything/v2#Thing')); + } + ); + + const anythingOnto = MockOntology.mockReadOntology('http://0.0.0.0:3333/ontology/0001/anything/v2'); + + // get resource class defs + testHostComponent.resourceClassAndPropertySelection.resourceClasses = anythingOnto.getClassDefinitionsByType(ResourceClassDefinition); + + const resProps = anythingOnto.getPropertyDefinitionsByType(ResourcePropertyDefinition); + + testHostComponent.resourceClassAndPropertySelection.properties = resProps; + + testHostFixture.detectChanges(); + + const hostCompDe = testHostFixture.debugElement; + const selectResClassComp = hostCompDe.query(By.directive(TestSelectResourceClassComponent)); + + (selectResClassComp.componentInstance as TestSelectResourceClassComponent).resourceClassSelected.emit('http://0.0.0.0:3333/ontology/0001/anything/v2#Thing'); + + testHostFixture.detectChanges(); + + expect(testHostComponent.resourceClassAndPropertySelection.activeResourceClass) + .toEqual(MockOntology.mockIResourceClassAndPropertyDefinitions('http://0.0.0.0:3333/ontology/0001/anything/v2#Thing').classes['http://0.0.0.0:3333/ontology/0001/anything/v2#Thing']); + expect(Object.keys(testHostComponent.resourceClassAndPropertySelection.properties).length).toEqual(25); + + expect(dspConnSpy.v2.ontologyCache.getResourceClassDefinition).toHaveBeenCalledTimes(1); + expect(dspConnSpy.v2.ontologyCache.getResourceClassDefinition).toHaveBeenCalledWith('http://0.0.0.0:3333/ontology/0001/anything/v2#Thing'); + + const addPropButton = await loader.getHarness(MatButtonHarness.with({selector: '.add-property-button'})); + + await addPropButton.click(); + + const selectPropComp = hostCompDe.query(By.directive(TestSelectPropertyComponent)); + + expect((selectPropComp.componentInstance as TestSelectPropertyComponent).activeResourceClass) + .toEqual(MockOntology.mockIResourceClassAndPropertyDefinitions('http://0.0.0.0:3333/ontology/0001/anything/v2#Thing') + .classes['http://0.0.0.0:3333/ontology/0001/anything/v2#Thing']); + + expect((selectPropComp.componentInstance as TestSelectPropertyComponent).index).toEqual(0); + expect(Object.keys((selectPropComp.componentInstance as TestSelectPropertyComponent).properties).length).toEqual(25); + + }); + + it('should disable add property button on init', async () => { + + const addPropButton = await loader.getHarness(MatButtonHarness.with({selector: '.add-property-button'})); + + expect(await addPropButton.isDisabled()).toBe(false); + }); + + it('should disable remove property button on init', async () => { + + const rmPropButton = await loader.getHarness(MatButtonHarness.with({selector: '.remove-property-button'})); + + expect(await rmPropButton.isDisabled()).toBe(true); + + }); + + it('should display a property selection when the add property button has been clicked', async () => { + + const anythingOnto = MockOntology.mockReadOntology('http://0.0.0.0:3333/ontology/0001/anything/v2'); + + // get resource class defs + testHostComponent.resourceClassAndPropertySelection.resourceClasses = anythingOnto.getClassDefinitionsByType(ResourceClassDefinition); + + const resProps = anythingOnto.getPropertyDefinitionsByType(ResourcePropertyDefinition); + + testHostComponent.resourceClassAndPropertySelection.properties = resProps; + + testHostFixture.detectChanges(); + + expect(testHostComponent.resourceClassAndPropertySelection.activeProperties.length).toEqual(0); + + const addPropButton = await loader.getHarness(MatButtonHarness.with({selector: '.add-property-button'})); + + expect(await addPropButton.isDisabled()).toBe(false); + + await addPropButton.click(); + + expect(testHostComponent.resourceClassAndPropertySelection.activeProperties.length).toEqual(1); + + const hostCompDe = testHostFixture.debugElement; + const selectPropComp = hostCompDe.query(By.directive(TestSelectPropertyComponent)); + + expect((selectPropComp.componentInstance as TestSelectPropertyComponent).activeResourceClass).toEqual(undefined); + expect((selectPropComp.componentInstance as TestSelectPropertyComponent).index).toEqual(0); + expect((selectPropComp.componentInstance as TestSelectPropertyComponent).properties).toEqual(resProps); + + const rmPropButton = await loader.getHarness(MatButtonHarness.with({selector: '.remove-property-button'})); + + expect(await rmPropButton.isDisabled()).toBe(false); + }); + + it('should add to and remove from active properties array when property buttons are clicked', async () => { + + const anythingOnto = MockOntology.mockReadOntology('http://0.0.0.0:3333/ontology/0001/anything/v2'); + + // get resource class defs + testHostComponent.resourceClassAndPropertySelection.resourceClasses = anythingOnto.getClassDefinitionsByType(ResourceClassDefinition); + + const resProps = anythingOnto.getPropertyDefinitionsByType(ResourcePropertyDefinition); + + testHostComponent.resourceClassAndPropertySelection.properties = resProps; + + testHostFixture.detectChanges(); + + const addPropButton = await loader.getHarness(MatButtonHarness.with({selector: '.add-property-button'})); + + const rmPropButton = await loader.getHarness(MatButtonHarness.with({selector: '.remove-property-button'})); + + expect(testHostComponent.resourceClassAndPropertySelection.activeProperties.length).toEqual(0); + + await addPropButton.click(); + + expect(testHostComponent.resourceClassAndPropertySelection.activeProperties.length).toEqual(1); + + await addPropButton.click(); + + expect(testHostComponent.resourceClassAndPropertySelection.activeProperties.length).toEqual(2); + + await rmPropButton.click(); + + expect(testHostComponent.resourceClassAndPropertySelection.activeProperties.length).toEqual(1); + + await rmPropButton.click(); + + expect(testHostComponent.resourceClassAndPropertySelection.activeProperties.length).toEqual(0); + }); + + + it('should add at max four property selections', async () => { + + // simulate state after anything onto selection + testHostComponent.resourceClassAndPropertySelection.activeOntology = 'http://0.0.0.0:3333/ontology/0001/anything/v2'; + + const anythingOnto = MockOntology.mockReadOntology('http://0.0.0.0:3333/ontology/0001/anything/v2'); + + // get resource class defs + testHostComponent.resourceClassAndPropertySelection.resourceClasses = anythingOnto.getClassDefinitionsByType(ResourceClassDefinition); + + const resProps = anythingOnto.getPropertyDefinitionsByType(ResourcePropertyDefinition); + + testHostComponent.resourceClassAndPropertySelection.properties = resProps; + + testHostComponent.resourceClassAndPropertySelection.activeProperties = [true, true, true, true]; + + testHostFixture.detectChanges(); + + const addPropButton = await loader.getHarness(MatButtonHarness.with({selector: '.add-property-button'})); + + expect(await addPropButton.isDisabled()).toEqual(true); + + }); + +}); diff --git a/projects/dsp-ui/src/lib/search/advanced-search/resource-and-property-selection/resource-and-property-selection.component.ts b/projects/dsp-ui/src/lib/search/advanced-search/resource-and-property-selection/resource-and-property-selection.component.ts new file mode 100644 index 000000000..73504c7b8 --- /dev/null +++ b/projects/dsp-ui/src/lib/search/advanced-search/resource-and-property-selection/resource-and-property-selection.component.ts @@ -0,0 +1,149 @@ +import { + Component, + Inject, + Input, + OnChanges, + OnInit, + QueryList, + SimpleChanges, + ViewChild, + ViewChildren +} from '@angular/core'; +import { FormBuilder, FormGroup } from '@angular/forms'; +import { + KnoraApiConnection, + ReadOntology, ResourceClassAndPropertyDefinitions, + ResourceClassDefinition, + ResourcePropertyDefinition +} from '@dasch-swiss/dsp-js'; +import { SelectResourceClassComponent } from '../select-resource-class/select-resource-class.component'; +import { SelectPropertyComponent } from '../select-property/select-property.component'; +import { DspApiConnectionToken } from '../../../core/core.module'; + + +@Component({ + selector: 'dsp-resource-and-property-selection', + templateUrl: './resource-and-property-selection.component.html', + styleUrls: ['./resource-and-property-selection.component.scss'] +}) +export class ResourceAndPropertySelectionComponent implements OnInit, OnChanges { + + @Input() formGroup: FormGroup; + + @Input() activeOntology: string; + + @Input() resourceClassRestriction?: string; + + form: FormGroup; + + activeResourceClass: ResourceClassDefinition; + + activeProperties: boolean[] = []; + + resourceClasses: ResourceClassDefinition[]; + + properties: ResourcePropertyDefinition[]; + + // reference to the component that controls the resource class selection + @ViewChild('resourceClass') resourceClassComponent: SelectResourceClassComponent; + + // reference to the component controlling the property selection + @ViewChildren('property') propertyComponents: QueryList; + + constructor( + @Inject(FormBuilder) private _fb: FormBuilder, + @Inject(DspApiConnectionToken) private _dspApiConnection: KnoraApiConnection) { + } + + ngOnInit(): void { + + this.form = this._fb.group({}); + + // add form to the parent form group + this.formGroup.addControl('resourceAndPropertySelection', this.form); + } + + ngOnChanges(changes: SimpleChanges) { + this.getResourceClassesAndPropertiesForOntology(this.activeOntology); + } + + getResourceClassesAndPropertiesForOntology(ontologyIri: string) { + + // reset active resource class definition + this.activeResourceClass = undefined; + + // reset specified properties + this.activeProperties = []; + + this._dspApiConnection.v2.ontologyCache.getOntology(ontologyIri).subscribe( + (onto: Map) => { + + const resClasses = onto.get(ontologyIri).getClassDefinitionsByType(ResourceClassDefinition); + + if (this.resourceClassRestriction !== undefined) { + this.resourceClasses = resClasses.filter( + (resClassDef: ResourceClassDefinition) => resClassDef.id === this.resourceClassRestriction + ); + } else { + this.resourceClasses = resClasses; + } + + this.properties = onto.get(ontologyIri).getPropertyDefinitionsByType(ResourcePropertyDefinition); + }, + error => { + console.error(error); + } + ); + } + + getPropertiesForResourceClass(resourceClassIri: string | null) { + + // reset specified properties + this.activeProperties = []; + + // if the client undoes the selection of a resource class, use the active ontology as a fallback + if (resourceClassIri === null) { + this.getResourceClassesAndPropertiesForOntology(this.activeOntology); + } else { + + this._dspApiConnection.v2.ontologyCache.getResourceClassDefinition(resourceClassIri).subscribe( + (onto: ResourceClassAndPropertyDefinitions) => { + + this.activeResourceClass = onto.classes[resourceClassIri]; + + this.properties = onto.getPropertyDefinitionsByType(ResourcePropertyDefinition); + + } + ); + } + } + + /** + * @ignore + * Add a property to the search form. + * @returns void + */ + addProperty(): void { + this.activeProperties.push(true); + } + + /** + * @ignore + * Remove the last property from the search form. + * @returns void + */ + removeProperty(): void { + this.activeProperties.splice(-1, 1); + } + + /** + * @ignore + * Resets the form (selected resource class and specified properties) preserving the active ontology. + */ + resetForm() { + if (this.activeOntology !== undefined) { + this.getResourceClassesAndPropertiesForOntology(this.activeOntology); + } + } + +} diff --git a/projects/dsp-ui/src/lib/search/advanced-search/select-resource-class/select-resource-class.component.scss b/projects/dsp-ui/src/lib/search/advanced-search/select-resource-class/select-resource-class.component.scss index e69de29bb..2a8509a7d 100644 --- a/projects/dsp-ui/src/lib/search/advanced-search/select-resource-class/select-resource-class.component.scss +++ b/projects/dsp-ui/src/lib/search/advanced-search/select-resource-class/select-resource-class.component.scss @@ -0,0 +1,3 @@ +.select-resource-class { + margin-left: 8px; +} diff --git a/projects/dsp-ui/src/lib/search/search.module.ts b/projects/dsp-ui/src/lib/search/search.module.ts index 1ae2a24ff..ee96699bc 100644 --- a/projects/dsp-ui/src/lib/search/search.module.ts +++ b/projects/dsp-ui/src/lib/search/search.module.ts @@ -35,6 +35,7 @@ import { SearchDisplayListComponent } from './advanced-search/select-property/sp import { SearchTextValueComponent } from './advanced-search/select-property/specify-property-value/search-text-value/search-text-value.component'; import { SearchUriValueComponent } from './advanced-search/select-property/specify-property-value/search-uri-value/search-uri-value.component'; import { DspActionModule } from '../action'; +import { ResourceAndPropertySelectionComponent } from './advanced-search/resource-and-property-selection/resource-and-property-selection.component'; @@ -56,7 +57,8 @@ import { DspActionModule } from '../action'; SearchListValueComponent, SearchDisplayListComponent, SearchTextValueComponent, - SearchUriValueComponent + SearchUriValueComponent, + ResourceAndPropertySelectionComponent ], imports: [ CommonModule,