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(ontology): delete property from resource class (DSP-1854 / DEV-28) #499

Merged
merged 10 commits into from Oct 19, 2021
21 changes: 10 additions & 11 deletions src/app/project/ontology/property-info/property-info.component.html
@@ -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>
Expand All @@ -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>
Expand Down Expand Up @@ -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>
Expand Down
Expand Up @@ -24,6 +24,10 @@
.mat-line.info {
font-size: small;

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

.mat-icon {
width: 12px;
height: 12px;
Expand Down
Expand Up @@ -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();
Expand All @@ -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();
Expand Down
126 changes: 82 additions & 44 deletions src/app/project/ontology/property-info/property-info.component.ts
Expand Up @@ -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';
Expand Down Expand Up @@ -85,6 +87,8 @@ export class PropertyInfoComponent implements OnChanges, AfterContentInit {

@Input() propCard?: IHasProperty;

@Input() resourceIri?: string;

@Input() projectCode: string;

@Input() projectStatus: boolean;
Expand Down Expand Up @@ -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 {

Expand Down Expand Up @@ -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) {
Expand All @@ -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;
}

Expand Down
Expand Up @@ -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>
Expand Down Expand Up @@ -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"
Expand All @@ -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>
Expand Down
Expand Up @@ -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 = [];
Expand All @@ -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) {
Expand Down Expand Up @@ -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);
Expand All @@ -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);
}
);

Expand Down