Skip to content
Permalink
Browse files
feat(ontology): delete property from resource class (DSP-1854 / DEV-28)…
… (#499)

* feat(ontology): delete property from resource class

* feat(ontology): add can delete and delete prop handler

* test(ontology): fix tests

* style(ontology): fix style issue in res class info
  • Loading branch information
kilchenmann committed Oct 19, 2021
1 parent f661e28 commit 436c2704717d66fbb817589dda4c12ac50f6fece
@@ -1,7 +1,7 @@
<div class="property-info" (mouseenter)="mouseEnter()" (mouseleave)="mouseLeave()">

<div mat-line class="title">
<span class="icon" *ngIf="propType" [matTooltip]="propType?.group + ': ' + propType?.label"
<span class="icon" *ngIf="propType" [matTooltip]="propType?.group + ': ' + propType?.label + ' (' + propDef.id.split('#')[1] + ')'"
matTooltipPosition="above">
<mat-icon class="type">{{propType?.icon}}</mat-icon>
</span>
@@ -20,7 +20,7 @@
</div>

<div mat-line class="info additional-info" [class.flex]="propCard">
<span [matTooltip]="'id: ' + propDef.id" matTooltipPosition="above">
<span [matTooltip]="'id: ' + propDef.id" matTooltipPosition="above" class="mat-caption">
{{propDef.id | split: '#':1}}
</span>
<span class="fill-remaining-space center">&nbsp;&middot;&nbsp;</span>
@@ -48,30 +48,29 @@
</span>
</div>

<div class="action-bubble" *ngIf="lastModificationDate && showActionBubble && projectStatus" [@simpleFadeAnimation]="'in'">
<div class="action-bubble" *ngIf="lastModificationDate && projectStatus && showActionBubble" [@simpleFadeAnimation]="'in'">
<!-- property action in case of resource class' view has different buttons and actions -->
<div class="button-container" *ngIf="propCard">
<!-- TODO: edit cardinality is disabled for the moment. Will be activated as soon as we use DSP-API 13.9.1 -->
<button mat-button [disabled]="true" [matTooltip]="'Edit property and cardinality'">
<button mat-button [disabled]="true" [matTooltip]="'Edit property and cardinality'" matTooltipPosition="above">
<mat-icon>tune</mat-icon>
</button>
<span
[matTooltip]="((resClasses.length > 0) ? 'The property can\'t be removed because it\'s in use' : 'Remove property from resource class')">
<!-- TODO: add "disabled" param as soon as we use DSP-API 13.9.1 and DSP-JS 2.4.0; use same value in matTooltip above -->
<button mat-button class="delete"
<span matTooltipPosition="above"
[matTooltip]="((!propCanBeDeleted) ? 'The property can\'t be removed because it is in use' : 'Remove property from resource class')">
<button mat-button [disabled]="!propCanBeDeleted" class="delete"
(click)="removePropertyFromClass.emit({iri: propDef.id, label: propDef.label})">
<mat-icon>backspace</mat-icon>
</button>
</span>
</div>
<!-- property action in case of properties' list view -->
<div class="button-container" *ngIf="!propCard">
<button mat-button [disabled]="!propDef.isEditable" class="edit" title="edit" [matTooltip]="'Edit property'"
<button mat-button [disabled]="!propDef.isEditable" class="edit" title="edit" [matTooltip]="'Edit property'" matTooltipPosition="above"
(click)="editResourceProperty.emit({propDef: propDef, propType: propType})">
<mat-icon>edit</mat-icon>
</button>
<span
[matTooltip]="((resClasses.length > 0 || !propCanBeDeleted)? 'The property can\'t be deleted because it\'s used in a class' : 'Delete property')">
<span matTooltipPosition="above"
[matTooltip]="((resClasses.length > 0 || !propCanBeDeleted) ? 'The property can\'t be deleted because it is used in a class' : 'Delete property')">
<button mat-button [disabled]="resClasses.length > 0" class="delete"
(click)="deleteResourceProperty.emit({iri: propDef.id, label: propDef.label})">
<mat-icon>delete</mat-icon>
@@ -24,6 +24,10 @@
.mat-line.info {
font-size: small;

.mat-caption {
color: rgba($dark, .7);
}

.mat-icon {
width: 12px;
height: 12px;
@@ -242,6 +242,16 @@ describe('PropertyInfoComponent', () => {
});

beforeEach(() => {
// mock cache service for currentOntology
const cacheSpy = TestBed.inject(CacheService);

(cacheSpy as jasmine.SpyObj<CacheService>).get.and.callFake(
() => {
const response: ReadOntology = MockOntology.mockReadOntology('http://0.0.0.0:3333/ontology/0001/anything/v2');
return of(response);
}
);

simpleTextHostFixture = TestBed.createComponent(SimpleTextHostComponent);
simpleTextHostComponent = simpleTextHostFixture.componentInstance;
simpleTextHostFixture.detectChanges();
@@ -254,19 +264,6 @@ describe('PropertyInfoComponent', () => {
overlayContainer = TestBed.inject(OverlayContainer);
rootLoader = TestbedHarnessEnvironment.documentRootLoader(simpleTextHostFixture);

});

beforeEach(() => {
// mock cache service for currentOntology
const cacheSpy = TestBed.inject(CacheService);

(cacheSpy as jasmine.SpyObj<CacheService>).get.and.callFake(
() => {
const response: ReadOntology = MockOntology.mockReadOntology('http://0.0.0.0:3333/ontology/0001/anything/v2');
return of(response);
}
);

linkHostFixture = TestBed.createComponent(LinkHostComponent);
linkHostComponent = linkHostFixture.componentInstance;
linkHostFixture.detectChanges();
@@ -10,7 +10,9 @@ import {
ReadOntology,
ReadProject,
ResourceClassDefinitionWithAllLanguages,
ResourcePropertyDefinitionWithAllLanguages
ResourcePropertyDefinitionWithAllLanguages,
UpdateOntology,
UpdateResourceClassCardinality
} from '@dasch-swiss/dsp-js';
import { CacheService } from 'src/app/main/cache/cache.service';
import { DspApiConnectionToken } from 'src/app/main/declarations/dsp-api-tokens';
@@ -85,6 +87,8 @@ export class PropertyInfoComponent implements OnChanges, AfterContentInit {

@Input() propCard?: IHasProperty;

@Input() resourceIri?: string;

@Input() projectCode: string;

@Input() projectStatus: boolean;
@@ -126,7 +130,15 @@ export class PropertyInfoComponent implements OnChanges, AfterContentInit {
@Inject(DspApiConnectionToken) private _dspApiConnection: KnoraApiConnection,
private _cache: CacheService,
private _errorHandler: ErrorHandlerService
) { }
) {

this._cache.get('currentOntology').subscribe(
(response: ReadOntology) => {
this.ontology = response;

}
);
}

ngOnChanges(): void {

@@ -179,31 +191,26 @@ export class PropertyInfoComponent implements OnChanges, AfterContentInit {
if (this.propDef.isLinkProperty) {
// this property is a link property to another resource class
// get current ontology to get linked res class information
this._cache.get('currentOntology').subscribe(
(response: ReadOntology) => {
this.ontology = response;
// get the base ontology of object type
const baseOnto = this.propDef.objectType.split('#')[0];
if (baseOnto !== response.id) {
// get class info from another ontology
this._cache.get('currentProjectOntologies').subscribe(
(ontologies: ReadOntology[]) => {
const onto = ontologies.find(i => i.id === baseOnto);
if (!onto && this.propDef.objectType === Constants.Region) {
this.propAttribute = 'Region';
} else {
this.propAttribute = onto.classes[this.propDef.objectType].label;
this.propAttributeComment = onto.classes[this.propDef.objectType].comment;
}
}
);
} else {
this.propAttribute = response.classes[this.propDef.objectType].label;
this.propAttributeComment = response.classes[this.propDef.objectType].comment;
}

}
);
// get the base ontology of object type
const baseOnto = this.propDef.objectType.split('#')[0];
if (baseOnto !== this.ontology.id) {
// get class info from another ontology
this._cache.get('currentProjectOntologies').subscribe(
(ontologies: ReadOntology[]) => {
const onto = ontologies.find(i => i.id === baseOnto);
if (!onto && this.propDef.objectType === Constants.Region) {
this.propAttribute = 'Region';
} else {
this.propAttribute = onto.classes[this.propDef.objectType].label;
this.propAttributeComment = onto.classes[this.propDef.objectType].comment;
}
}
);
} else {
this.propAttribute = this.ontology.classes[this.propDef.objectType].label;
this.propAttributeComment = this.ontology.classes[this.propDef.objectType].comment;
}
}

if (this.propDef.objectType === Constants.ListValue) {
@@ -224,40 +231,71 @@ export class PropertyInfoComponent implements OnChanges, AfterContentInit {
// get all classes where the property is used
if (!this.propCard) {

this._cache.get('currentOntology').subscribe(
(response: ReadOntology) => {
this.ontology = response;
const classes = response.getAllClassDefinitions();
for (const c of classes) {
if (c.propertiesList.find(i => i.propertyIndex === this.propDef.id)) {
this.resClasses.push(c as ResourceClassDefinitionWithAllLanguages);
}
// const splittedSubClass = ontology.classes[c].subClassOf[0].split('#');

// if (splittedSubClass[0] !== Constants.StandoffOntology && splittedSubClass[1] !== 'StandoffTag' && splittedSubClass[1] !== 'StandoffLinkTag') {
// this.ontoClasses.push(this.ontology.classes[c]);
// }
}
const classes = this.ontology.getAllClassDefinitions();
for (const c of classes) {
if (c.propertiesList.find(i => i.propertyIndex === this.propDef.id)) {
this.resClasses.push(c as ResourceClassDefinitionWithAllLanguages);
}
);
// const splittedSubClass = ontology.classes[c].subClassOf[0].split('#');

// if (splittedSubClass[0] !== Constants.StandoffOntology && splittedSubClass[1] !== 'StandoffTag' && splittedSubClass[1] !== 'StandoffLinkTag') {
// this.ontoClasses.push(this.ontology.classes[c]);
// }
}

}
}

/**
* determines whether property can be deleted
* resp. removed from res class if we have the cardinality info
*/
canBeDeleted() {
if (!this.propCard) {
// check if the property can be deleted
this._dspApiConnection.v2.onto.canDeleteResourceProperty(this.propDef.id).subscribe(
(response: CanDoResponse) => {
this.propCanBeDeleted = response.canDo;
(canDoRes: CanDoResponse) => {
this.propCanBeDeleted = canDoRes.canDo;
},
(error: ApiResponseError) => {
this._errorHandler.showMessage(error);
}
);
}
} else {
// check if the property can be removed from res class
if (this.lastModificationDate) {
const onto = new UpdateOntology<UpdateResourceClassCardinality>();

onto.lastModificationDate = this.lastModificationDate;

onto.id = this.ontology.id;

const delCard = new UpdateResourceClassCardinality();

delCard.id = this.resourceIri;

delCard.cardinalities = [];

delCard.cardinalities = [this.propCard];
onto.entity = delCard;

this._dspApiConnection.v2.onto.canDeleteCardinalityFromResourceClass(onto).subscribe(
(canDoRes: CanDoResponse) => {
this.propCanBeDeleted = canDoRes.canDo;
},
(error: ApiResponseError) => {
this._errorHandler.showMessage(error);
}
);
}
}
}

/**
* show action bubble with various CRUD buttons when hovered over.
*/
mouseEnter() {
this.canBeDeleted();
this.showActionBubble = true;
}

@@ -3,7 +3,7 @@
<mat-card-header class="resource-class-header" cdkDragHandle>
<!-- TODO: the res class icon is missing in ClassDefinition from DSP-JS-Lib; DSP-JS has to be updated first (s. DSP-1366) -->
<!-- <mat-icon mat-card-avatar>{{resourceClass.icon}}</mat-icon> -->
<mat-card-title [matTooltip]="resourceClass.comment" matTooltipPosition="above">
<mat-card-title [matTooltip]="resourceClass.comment + ' (' + resourceClass.id.split('#')[1] + ')'" matTooltipPosition="above">
{{resourceClass.label | appTruncate: 24}}
</mat-card-title>
<mat-card-subtitle>
@@ -47,9 +47,9 @@
<mat-list cdkDropList class="resource-class-properties" (cdkDropListDropped)="drop($event)"
*ngIf="propsToDisplay.length; else noProperties">
<div cdkDrag [cdkDragDisabled]="!ontology.lastModificationDate"
*ngFor="let prop of propsToDisplay; let i = index; let odd = odd">
*ngFor="let prop of propsToDisplay; let i = index;">
<div class="drag-n-drop-placeholder" *cdkDragPlaceholder></div>
<mat-list-item class="property" [class.odd]="odd">
<mat-list-item class="property">
<span cdkDragHandle mat-list-icon class="list-icon gui-order">
<span [class.hide-on-hover]="cardinalityUpdateEnabled && lastModificationDate">{{i + 1}})</span>
<span *ngIf="lastModificationDate && cardinalityUpdateEnabled"
@@ -60,7 +60,7 @@
<!-- display only properties if they exist in list of properties;
objectType is not a linkValue (otherwise we have the property twice) -->
<app-property-info class="property-info" [propDef]="ontology?.properties[prop.propertyIndex]"
[propCard]="propsToDisplay[i]" [projectCode]="projectCode"
[propCard]="propsToDisplay[i]" [projectCode]="projectCode" [projectStatus]="projectStatus" [resourceIri]="resourceClass.id"
[(lastModificationDate)]="lastModificationDate"
(removePropertyFromClass)="removeProperty($event)">
</app-property-info>
@@ -170,7 +170,7 @@ export class ResourceClassInfoComponent implements OnInit {
*/
preparePropsToDisplay(classProps: IHasProperty[]) {

const ontoProps = this.ontology.getAllPropertyDefinitions();
const ontoProps = <ResourcePropertyDefinitionWithAllLanguages[]>this.ontology.getAllPropertyDefinitions();

// reset properties to display
this.propsToDisplay = [];
@@ -181,9 +181,8 @@ export class ResourceClassInfoComponent implements OnInit {

const propToDisplay = ontoProps.find(obj =>
obj.id === hasProp.propertyIndex &&
(obj.objectType !== 'http://api.knora.org/ontology/knora-api/v2#LinkValue' ||
(obj.subjectType && !obj.subjectType.includes('Standoff'))
)
((!obj.isLinkValueProperty) || (obj.subjectType && !obj.subjectType.includes('Standoff')))

);

if (propToDisplay) {
@@ -294,19 +293,20 @@ export class ResourceClassInfoComponent implements OnInit {

onto.id = this.ontology.id;

const addCard = new UpdateResourceClassCardinality();
const delCard = new UpdateResourceClassCardinality();

addCard.id = this.resourceClass.id;
delCard.id = this.resourceClass.id;

addCard.cardinalities = [];
delCard.cardinalities = [];

this.propsToDisplay = this.propsToDisplay.filter(prop => !(prop.propertyIndex === property.iri));
this.propsToDisplay = this.propsToDisplay.filter(prop => (prop.propertyIndex === property.iri));

addCard.cardinalities = this.propsToDisplay;
onto.entity = addCard;
delCard.cardinalities = this.propsToDisplay;
onto.entity = delCard;

this._dspApiConnection.v2.onto.replaceCardinalityOfResourceClass(onto).subscribe(
this._dspApiConnection.v2.onto.deleteCardinalityFromResourceClass(onto).subscribe(
(res: ResourceClassDefinitionWithAllLanguages) => {

this.lastModificationDate = res.lastModificationDate;
this.lastModificationDateChange.emit(this.lastModificationDate);
this.preparePropsToDisplay(this.propsToDisplay);
@@ -316,7 +316,7 @@ export class ResourceClassInfoComponent implements OnInit {
this._notification.openSnackBar(`You have successfully removed "${property.label}" from "${this.resourceClass.label}".`);
},
(error: ApiResponseError) => {
this._errorHandler.showMessage(error);
this._errorHandler.showMessage(<ApiResponseError>error);
}
);

0 comments on commit 436c270

Please sign in to comment.