Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(advanced search): specify linked resource (DSP-1587) #293

Merged
merged 31 commits into from May 25, 2021
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
048fd34
feat(advanced search): make component to search for a resource
May 6, 2021
c992fd3
feat(advanced search): get class of linked resource
May 6, 2021
062875c
feat(advanced search): distinguish between specifying and searching f…
May 6, 2021
2694171
feat(advanced search): distinguish between specifying and searching f…
May 6, 2021
15d68e5
feat(gravsearch generation): prepare adaption
May 6, 2021
1990e3b
Merge branch 'main' into wip/dsp-1587-search-linked-res-2
tobiasschweizer May 17, 2021
31d775d
refactor(gravsearch service): make method for props handling
May 17, 2021
25f36ea
refactor(gravsearch service): make method for props handling
May 17, 2021
6f859fa
refactor(gravsearch service): generate method for props handling
May 17, 2021
f5ce3a3
refactor(gravsearch service): avoid redundant statements
May 17, 2021
c312374
test(gravsearch service): adapt expectation
May 17, 2021
cb78757
refactor(gravsearch service): make var names unique
May 17, 2021
12bf0c3
refactor(gravsearch service): make var names unique
May 18, 2021
e8d2fc2
refactor(gravsearch service): improve type checking
May 18, 2021
90f9ef2
refactor(gravsearch service): improve comments
May 18, 2021
6ea481c
feature(advanced search): introduce top level flag
May 18, 2021
acf8e89
test(advanced search): add test for comp op
May 18, 2021
316fa54
test(advanced search): add new Input in mocked components
May 18, 2021
cbc95b5
test(advanced search): add test for SearchResourceComponent
May 18, 2021
ee11f2f
test(gravsearch service): add test for Gravsearch service
May 18, 2021
dce6a5b
feat(gravsearch service): add variable for rec. call count
May 19, 2021
0e9a8fc
feat(gravsearch service): add optional res. class restriction on link…
May 19, 2021
456e68e
feat(advanced search): only show sort criterion for main resource on …
May 19, 2021
e7efbfc
docs(advanced search): adapt design docs (ongoing)
May 19, 2021
a4e9156
refactor(advanced search): move select res. class comp.
May 19, 2021
dd19e12
refactor(advanced search): move select prop. comp.
May 19, 2021
bb65e7f
feat(advanced search): add fallback for object class constraint
May 19, 2021
fecdbf2
feat(advanced search): determine ontology from resource class
May 19, 2021
5681916
docs(advanced search): adapt design docs
May 19, 2021
7f0b17f
docs(gravsearch service): add comment
May 20, 2021
bcc1655
Merge branch 'main' into wip/dsp-1587-search-linked-res-2
tobiasschweizer May 21, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
38 changes: 26 additions & 12 deletions docs/how-to-contribute/design-documentation.md
Expand Up @@ -208,18 +208,20 @@ The advanced search consists of the following components:

- `AdvancedSearchComponent`: Main form: Reset and submit buttons, buttons to add and remove properties.
- `SelectOntologyComponent`: Select an ontology from a list.
- `SelectResourceClassComponent`: Select a resource class from a list.
- `SelectPropertyComponent`: Select a property from a list.
- `SpecifyPropertyValueComponent`: Specify a comparison operator and a value for a chosen property.
- `SearchBooleanValueComponent`: Specify a Boolean value.
- `SearchDateValueComponent`: Specify a date value.
- `SearchDecimalValueComponent`: Specify a decimal value.
- `SearchIntegerValueComponent`: Specify an integer value.
- `SearchLinkValueComponent`: Specify the target of a link property.
- `SearchListValueComponent`: Specify a list value.
- `SearchDisplayListComponent`: Displays the children of a list node recursively.
- `TextValueComponent`: Specify a text value.
- `UriValueComponent`: Specify a URI value.
- `ResourceAndPropertySelectionComponent`: Parent component that contains `SelectResourceClassComponent` and `SelectPropertyComponent` for recursive reuse.
- `SelectResourceClassComponent`: Select a resource class from a list.
- `SelectPropertyComponent`: Select a property from a list.
- `SpecifyPropertyValueComponent`: Specify a comparison operator and a value for a chosen property.
- `SearchBooleanValueComponent`: Specify a Boolean value.
- `SearchDateValueComponent`: Specify a date value.
- `SearchDecimalValueComponent`: Specify a decimal value.
- `SearchIntegerValueComponent`: Specify an integer value.
- `SearchLinkValueComponent`: Specify the target of a link property.
- `SearchResourceComponent`: Specify the class and/or properties of a linked resource (uses `ResourceAndPropertySelectionComponent`, see below).
- `SearchListValueComponent`: Specify a list value.
- `SearchDisplayListComponent`: Displays the children of a list node recursively.
- `TextValueComponent`: Specify a text value.
- `UriValueComponent`: Specify a URI value.

#### Component Interaction

Expand All @@ -235,6 +237,18 @@ When a property is chosen, a comparison operator can be specified.
Once a comparison operator is specified other than "EXISTS", a value can be specified using `SpecifyPropertyValueComponent`.
Depending on the value type of the property, `SpecifyPropertyValueComponent` chooses the apt component to let the user enter a value.

##### Recursive Use of ResourceAndPropertySelectionComponent

`ResourceAndPropertySelectionComponent` is used in the main form and in `SearchResourceComponent`'s template (if the user chooses the operator `Match` for a linking property)
to allow for searching linked resources by specifying their class and/or properties.

Only one level of recursion is allowed, i.e. linking properties on the linked resource **cannot** use the `Match` operator.

Sort criteria can only be chosen on the level of the main resource.
The boolean `@Input` `toplevel` distinguishes the top level from the level below.



#### Form Validation

`AdvancedSearchComponent` creates the main form that is then passed down to the child components.
Expand Down
Expand Up @@ -5,7 +5,7 @@
(ontologySelected)="setActiveOntology($event)"></dsp-select-ontology>
</div>

<dsp-resource-and-property-selection *ngIf="activeOntology !== undefined" #resAndPropSel [formGroup]="form" [activeOntology]="activeOntology">
<dsp-resource-and-property-selection *ngIf="activeOntology !== undefined" #resAndPropSel [formGroup]="form" [activeOntology]="activeOntology" [topLevel]="true">
</dsp-resource-and-property-selection>

<div class="dsp-form-action">
Expand Down
Expand Up @@ -48,6 +48,8 @@ class TestSelectResourceClassAndPropertyComponent {

@Input() resClassRestriction?: string;

@Input() topLevel: boolean;

}

/**
Expand Down
Expand Up @@ -17,7 +17,7 @@ import { DspApiConnectionToken } from '../../core/core.module';
import { SearchParams } from '../../viewer/views/list-view/list-view.component';
import { GravsearchGenerationService } from '../services/gravsearch-generation.service';
import { ResourceAndPropertySelectionComponent } from './resource-and-property-selection/resource-and-property-selection.component';
import { PropertyWithValue } from './select-property/specify-property-value/operator';
import { PropertyWithValue } from './resource-and-property-selection/select-property/specify-property-value/operator';

@Component({
selector: 'dsp-advanced-search',
Expand Down
Expand Up @@ -13,7 +13,7 @@
<div *ngFor="let prop of activeProperties; let i = index">

<dsp-select-property #property [activeResourceClass]="activeResourceClass" [formGroup]="form" [index]="i"
[properties]="properties"></dsp-select-property>
[properties]="properties" [topLevel]="topLevel"></dsp-select-property>

</div>
</div>
Expand Down
Expand Up @@ -50,6 +50,8 @@ class TestSelectPropertyComponent {

@Input() activeResourceClass: ResourceClassDefinition;

@Input() topLevel: boolean;

}

/**
Expand Down
Expand Up @@ -16,8 +16,8 @@ import {
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 { SelectResourceClassComponent } from './select-resource-class/select-resource-class.component';
import { SelectPropertyComponent } from './select-property/select-property.component';
import { DspApiConnectionToken } from '../../../core/core.module';


Expand All @@ -34,6 +34,8 @@ export class ResourceAndPropertySelectionComponent implements OnInit, OnChanges

@Input() resourceClassRestriction?: string;

@Input() topLevel;

form: FormGroup;

activeResourceClass: ResourceClassDefinition;
Expand Down
Expand Up @@ -6,7 +6,7 @@
</mat-form-field>

<dsp-specify-property-value #specifyPropertyValue [formGroup]="form" *ngIf="propertySelected !== undefined"
[property]="propertySelected"></dsp-specify-property-value>
[property]="propertySelected" [topLevel]="topLevel"></dsp-specify-property-value>

<mat-checkbox *ngIf="propertySelected !== undefined && sortCriterion()" [formControlName]="'isSortCriterion'" matTooltip="Sort criterion"></mat-checkbox>
</span>
Expand Up @@ -26,7 +26,7 @@ import { ComparisonOperatorAndValue, Equals, ValueLiteral } from './specify-prop
*/
@Component({
template: `
<dsp-select-property #selectProp [formGroup]="form" [index]="0" [activeResourceClass]="activeResourceClass" [properties]="propertyDefs"></dsp-select-property>`
<dsp-select-property #selectProp [formGroup]="form" [index]="0" [activeResourceClass]="activeResourceClass" [properties]="propertyDefs" [topLevel]="topLevel"></dsp-select-property>`
})
class TestHostComponent implements OnInit {

Expand All @@ -38,6 +38,8 @@ class TestHostComponent implements OnInit {

activeResourceClass: ResourceClassDefinition;

topLevel: boolean;

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

Expand All @@ -47,6 +49,8 @@ class TestHostComponent implements OnInit {
const resProps = MockOntology.mockReadOntology('http://0.0.0.0:3333/ontology/0001/anything/v2').getPropertyDefinitionsByType(ResourcePropertyDefinition);

this.propertyDefs = resProps;

this.topLevel = true;
}

}
Expand All @@ -64,12 +68,13 @@ class TestSpecifyPropertyValueComponent implements OnInit {

@Input() property: ResourcePropertyDefinition;

@Input() topLevel: boolean;

getComparisonOperatorAndValueLiteralForProperty(): ComparisonOperatorAndValue {
return new ComparisonOperatorAndValue(new Equals(), new ValueLiteral('1', 'http://www.w3.org/2001/XMLSchema#integer'));
}

ngOnInit() {

}

}
Expand Down Expand Up @@ -174,7 +179,7 @@ describe('SelectPropertyComponent', () => {

});

it('should show the sort checkbox when a property with cardinality 1 is selected', async () => {
it('should show the sort checkbox when a property with cardinality 1 is selected for the top level resource', async () => {

const select = await loader.getHarness(MatSelectHarness);
await select.open();
Expand All @@ -201,6 +206,35 @@ describe('SelectPropertyComponent', () => {

});

it('should not show the sort checkbox when a property with cardinality 1 is selected for a linked resource', async () => {

testHostComponent.topLevel = false;

const select = await loader.getHarness(MatSelectHarness);
await select.open();

const options = await select.getOptions();
expect(await options[4].getText()).toEqual('Date');

await options[4].click();

const resClass = new ResourceClassDefinition();
resClass.propertiesList = [{
propertyIndex: 'http://0.0.0.0:3333/ontology/0001/anything/v2#hasDate',
cardinality: Cardinality._1,
isInherited: true
}];

testHostComponent.activeResourceClass = resClass;

testHostFixture.detectChanges();

const checkbox = await loader.getAllHarnesses(MatCheckboxHarness);

expect(checkbox.length).toEqual(0);

});

it('should get the specified value for the selected property', async () => {

const select = await loader.getHarness(MatSelectHarness);
Expand Down
Expand Up @@ -2,7 +2,7 @@ import { Component, Inject, Input, OnDestroy, OnInit, ViewChild } from '@angular
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Cardinality, IHasProperty, ResourceClassDefinition, ResourcePropertyDefinition } from '@dasch-swiss/dsp-js';
import { Subscription } from 'rxjs';
import { SortingService } from '../../../action/services/sorting.service';
import { SortingService } from '../../../../action/services/sorting.service';
import { ComparisonOperatorAndValue, PropertyWithValue } from './specify-property-value/operator';
import { SpecifyPropertyValueComponent } from './specify-property-value/specify-property-value.component';

Expand All @@ -22,6 +22,8 @@ export class SelectPropertyComponent implements OnInit, OnDestroy {
// index of the given property (unique)
@Input() index: number;

@Input() topLevel: boolean;

// properties that can be selected from
private _properties: ResourcePropertyDefinition[];

Expand Down Expand Up @@ -107,12 +109,13 @@ export class SelectPropertyComponent implements OnInit, OnDestroy {
*/
sortCriterion(): boolean {

// TODO: this method is called from teh template. It is called on each change detection cycle.
// TODO: this method is called from the template. It is called on each change detection cycle.
// TODO: this is acceptable because this method has no side-effects
// TODO: find a better way: evaluate once and store the result in a class member

// check if a resource class is selected and if the property's cardinality is 1 for the selected resource class
if (this._activeResourceClass !== undefined && this.propertySelected !== undefined && !this.propertySelected.isLinkProperty) {
// sort criterion is only available for main resource on top level
if (this.topLevel && this._activeResourceClass !== undefined && this.propertySelected !== undefined && !this.propertySelected.isLinkProperty) {

const cardinalities: IHasProperty[] = this._activeResourceClass.propertiesList.filter(
(card: IHasProperty) => {
Expand Down
Expand Up @@ -213,6 +213,26 @@ export class IRI implements Value {

}

/**
* Represents a linked resource.
*/
export class LinkedResource implements Value {

/**
* Constructs a [LinkedResource].
*
* @param properties the properties of the linked resource.
* @param resourceClass the class of the linked resource, if any.
*/
constructor(public properties: PropertyWithValue[], public resourceClass?: string) {
}

public toSparql(): string {
throw Error('invalid call of toSparql');
}

}

/**
* An abstract interface that represents a value.
* This interface has to be implemented for all value types (value component classes).
Expand Down
Expand Up @@ -7,7 +7,7 @@ import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatInputModule } from '@angular/material/input';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { CalendarDate, CalendarPeriod, GregorianCalendarDate } from 'jdnconvertiblecalendar';
import { DspViewerModule } from '../../../../../viewer/viewer.module';
import { DspViewerModule } from '../../../../../../viewer/viewer.module';
import { ValueLiteral } from '../operator';
import { SearchDateValueComponent } from './search-date-value.component';

Expand Down
Expand Up @@ -2,7 +2,7 @@ import { Component, Inject, Input, OnDestroy, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Constants } from '@dasch-swiss/dsp-js';
import { JDNConvertibleCalendar } from 'jdnconvertiblecalendar';
import { CalendarHeaderComponent } from '../../../../../viewer/values/date-value/calendar-header/calendar-header.component';
import { CalendarHeaderComponent } from '../../../../../../viewer/values/date-value/calendar-header/calendar-header.component';
import { PropertyValue, Value, ValueLiteral } from '../operator';

// https://stackoverflow.com/questions/45661010/dynamic-nested-reactive-form-expressionchangedafterithasbeencheckederror
Expand Down
Expand Up @@ -14,7 +14,7 @@ import {
SearchEndpointV2
} from '@dasch-swiss/dsp-js';
import { of } from 'rxjs';
import { DspApiConnectionToken } from '../../../../../core/core.module';
import { DspApiConnectionToken } from '../../../../../../core/core.module';
import { IRI } from '../operator';
import { SearchLinkValueComponent } from './search-link-value.component';

Expand Down
@@ -1,7 +1,7 @@
import { Component, Inject, Input, OnDestroy, OnInit } from '@angular/core';
import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
import { Constants, KnoraApiConnection, ReadResource, ReadResourceSequence } from '@dasch-swiss/dsp-js';
import { DspApiConnectionToken } from '../../../../../core/core.module';
import { DspApiConnectionToken } from '../../../../../../core/core.module';
import { IRI, PropertyValue, Value } from '../operator';

// https://stackoverflow.com/questions/45661010/dynamic-nested-reactive-form-expressionchangedafterithasbeencheckederror
Expand Down
Expand Up @@ -11,7 +11,7 @@ import { By } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ListNodeV2, ListsEndpointV2, MockList, MockOntology, ResourcePropertyDefinition } from '@dasch-swiss/dsp-js';
import { of } from 'rxjs';
import { DspApiConnectionToken } from '../../../../../core/core.module';
import { DspApiConnectionToken } from '../../../../../../core/core.module';
import { IRI } from '../operator';
import { SearchListValueComponent } from './search-list-value.component';

Expand Down
Expand Up @@ -8,8 +8,8 @@ import {
ListNodeV2,
ResourcePropertyDefinition
} from '@dasch-swiss/dsp-js';
import { NotificationService } from '../../../../../action/services/notification.service';
import { DspApiConnectionToken } from '../../../../../core/core.module';
import { NotificationService } from '../../../../../../action/services/notification.service';
import { DspApiConnectionToken } from '../../../../../../core/core.module';
import { IRI, PropertyValue, Value } from '../operator';

// https://stackoverflow.com/questions/45661010/dynamic-nested-reactive-form-expressionchangedafterithasbeencheckederror
Expand Down
@@ -0,0 +1,2 @@
<dsp-resource-and-property-selection #resAndPropSel [formGroup]="form" [activeOntology]="ontology"
[resourceClassRestriction]="restrictResourceClass" [topLevel]="false"></dsp-resource-and-property-selection>