Skip to content

Commit

Permalink
fix: class instance adding and preview. (#1317)
Browse files Browse the repository at this point in the history
  • Loading branch information
irmastnt committed Dec 18, 2023
1 parent 4e5c19a commit 3c1e684
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 99 deletions.
5 changes: 4 additions & 1 deletion apps/dsp-app/src/app/app-routing.module.ts
Expand Up @@ -26,8 +26,9 @@ import { UserComponent } from './user/user.component';
import { ResourceComponent } from './workspace/resource/resource.component';
import { ResultsComponent } from './workspace/results/results.component';
import { AdvancedSearchContainerComponent } from './workspace/search/advanced-search/advanced-search-container.component';
import { ProjectFormComponent } from "@dsp-app/src/app/project/project-form/project-form.component";
import { RouteConstants } from '@dasch-swiss/vre/shared/app-config';
import { OntologyClassInstanceGuard } from './main/guard/ontology-class-instance.guard';
import { ProjectFormComponent } from './project/project-form/project-form.component';

const routes: Routes = [
{
Expand Down Expand Up @@ -75,10 +76,12 @@ const routes: Routes = [
canActivate: [AuthGuard],
},
{
canActivate: [OntologyClassInstanceGuard],
path: RouteConstants.OntologyClassRelative,
component: OntologyClassInstanceComponent,
},
{
canActivate: [OntologyClassInstanceGuard],
path: RouteConstants.OntologyClassInstanceRelative,
component: OntologyClassInstanceComponent,
},
Expand Down
49 changes: 49 additions & 0 deletions apps/dsp-app/src/app/main/guard/ontology-class-instance.guard.ts
@@ -0,0 +1,49 @@
import { ProjectService } from '@dasch-swiss/vre/shared/app-helper-services';
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router';
import { Observable, combineLatest } from 'rxjs';
import { map } from 'rxjs/operators';
import { AuthService } from '@dasch-swiss/vre/shared/app-session';
import { RouteConstants } from '@dasch-swiss/vre/shared/app-config';
import { Select } from '@ngxs/store';
import { UserSelectors } from '@dasch-swiss/vre/shared/app-state';
import { StoredProject } from '@dasch-swiss/dsp-js';


@Injectable({
providedIn: 'root'
})
export class OntologyClassInstanceGuard implements CanActivate {
isLoggedIn$: Observable<boolean> = this.authService.isLoggedIn$;

@Select(UserSelectors.isSysAdmin) isSysAdmin$: Observable<boolean>;
@Select(UserSelectors.userProjects) userProjects$: Observable<StoredProject[]>;

constructor(
private authService: AuthService,
private projectService: ProjectService,
private router: Router,
) {
}

canActivate(activatedRoute: ActivatedRouteSnapshot): Observable<boolean> {
const instanceId = activatedRoute.params[RouteConstants.instanceParameter];
return combineLatest([this.isLoggedIn$, this.isSysAdmin$, this.userProjects$])
.pipe(
map(([isLoggedIn, isSysAdmin, userProjects]) => {
const projectUuid = activatedRoute.parent.params[RouteConstants.uuidParameter];
if (!isLoggedIn && instanceId === RouteConstants.addClassInstance) {
this.router.navigateByUrl(`/${RouteConstants.project}/${projectUuid}`);
return false;
}

return (instanceId === RouteConstants.addClassInstance
&& (userProjects?.some((p) => p.id === this.projectService.uuidToIri(projectUuid)) // project member
|| isSysAdmin // system admin
)
);
})
)
}
}

@@ -1,9 +1,9 @@
<!-- display all resource instances if instance id does not exist -->
<!-- If results are present -->
<div class="multiple-instances" *ngIf="searchParams">
<div class="multiple-instances" *ngIf="searchParams$ | async">
<as-split direction="horizontal" (dragEnd)="splitSizeChanged = $event">
<as-split-area [size]="40">
<app-list-view [search]="searchParams" [withMultipleSelection]="true"
<app-list-view [search]="searchParams$ | async" [withMultipleSelection]="true"
(selectedResources)="openSelectedResources($event)">
</app-list-view>
</as-split-area>
Expand All @@ -26,15 +26,15 @@


<!-- add new resource instance if instance id is called "add" -->
<div class="single-instance-form" *ngIf="instanceId && instanceId === 'add'">
<h3>Create new resource of type: {{resClass?.label}}</h3>
<div class="single-instance-form" *ngIf="(instanceId$ | async) && (instanceId$ | async) === routeConstants.addClassInstance">
<h3>Create new resource of type: {{(resClass$ | async)?.label}}</h3>
<app-resource-instance-form
[resourceClassIri]="classId"
[resourceClassIri]="classId$ | async"
[projectIri]="projectIri">
</app-resource-instance-form>
</div>

<!-- display single resource instance if instance id exists and is not called "add" -->
<div class="single-instance" *ngIf="instanceId && instanceId !== 'add'">
<app-resource [resourceIri]="resourceIri"></app-resource>
<div class="single-instance" *ngIf="(instanceId$ | async) && (instanceId$ | async) !== routeConstants.addClassInstance">
<app-resource [resourceIri]="resourceIri$ | async"></app-resource>
</div>
@@ -1,37 +1,115 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnChanges, OnInit } from '@angular/core';
import { ActivatedRoute, Params, Router } from '@angular/router';
import { ResourceClassDefinition } from '@dasch-swiss/dsp-js';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ResourceClassDefinition, StoredProject } from '@dasch-swiss/dsp-js';
import { AppConfigService, getAllEntityDefinitionsAsArray, RouteConstants } from '@dasch-swiss/vre/shared/app-config';
import { OntologyService, ProjectService } from '@dasch-swiss/vre/shared/app-helper-services';
import { FilteredResources, SearchParams } from '@dsp-app/src/app/workspace/results/list-view/list-view.component';
import { SplitSize } from '@dsp-app/src/app/workspace/results/results.component';
import { ProjectBase } from '../../project-base';
import { Actions, Store } from '@ngxs/store';
import { Actions, Select, Store } from '@ngxs/store';
import { Title } from '@angular/platform-browser';
import { OntologiesSelectors, UserSelectors } from '@dasch-swiss/vre/shared/app-state';
import { AuthService } from '@dasch-swiss/vre/shared/app-session';
import { filter } from 'rxjs/operators';
import { IProjectOntologiesKeyValuePairs, OntologiesSelectors, UserSelectors } from '@dasch-swiss/vre/shared/app-state';
import { map, takeWhile } from 'rxjs/operators';
import { takeUntil } from 'rxjs/operators';
import { Observable, Subject, combineLatest } from 'rxjs';

@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'app-ontology-class-instance',
templateUrl: './ontology-class-instance.component.html',
styleUrls: ['./ontology-class-instance.component.scss'],
})
export class OntologyClassInstanceComponent extends ProjectBase implements OnInit, OnChanges {
ontoId: string;
export class OntologyClassInstanceComponent extends ProjectBase implements OnInit, OnDestroy {
private ngUnsubscribe: Subject<void> = new Subject<void>();

routeConstants = RouteConstants;

get ontoId$(): Observable<string> {
return combineLatest([this.project$, this._route.params])
.pipe(
takeWhile(([project]) => project !== undefined),
takeUntil(this.ngUnsubscribe),
map(([project, params]) => {
const iriBase = this._ontologyService.getIriBaseUrl();
const ontologyName = params[RouteConstants.ontoParameter];
// get the resource ids from the route. Do not use the RouteConstants ontology route constant here,
// because the ontology and class ids are not defined within the apps domain. They are defined by
// the api and can not be changed generically via route constants.
return `${iriBase}/ontology/${project.shortcode}/${ontologyName}/v2`;
})
)
}

// uuid of resource instance
get instanceId$(): Observable<string> {
return this._route.params.pipe(
takeUntil(this.ngUnsubscribe),
map(params => {
return params[RouteConstants.instanceParameter]
})
)
}

// id (iri) of resource class
classId: string;
get classId$(): Observable<string> {
return combineLatest([this.ontoId$, this._route.params])
.pipe(
takeUntil(this.ngUnsubscribe),
map(([ontoId, params]) => {
const className = params[RouteConstants.classParameter];
return `${ontoId}#${className}`;
})
)
}

resClass: ResourceClassDefinition;
get resClass$(): Observable<ResourceClassDefinition> {
return combineLatest([this.projectOntologies$, this.classId$, this.ontoId$, this.instanceId$, this.userProjects$, this.isSysAdmin$])
.pipe(
takeUntil(this.ngUnsubscribe),
map(([projectOntologies, classId, ontoId, instanceId, userProjects, isSysAdmin]) => {
if (
(instanceId !== RouteConstants.addClassInstance
|| (instanceId === RouteConstants.addClassInstance && !(userProjects?.some((p) => p.id === this.projectIri) || isSysAdmin)))
|| !projectOntologies[this.projectIri]
) {
return;
}

const ontology = projectOntologies[this.projectIri].readOntologies.find((onto) => onto.id === ontoId);
if (ontology) {
// find ontology of current resource class to get the class label
const classes = getAllEntityDefinitionsAsArray(ontology.classes);
return <ResourceClassDefinition>(classes[classes.findIndex((res) => res.id === classId)]);
}
})
)
}

// uuid of resource instance
instanceId: string;
// id (iri) or resource instance
resourceIri: string;
get resourceIri$(): Observable<string> {
return this.instanceId$.pipe(
takeUntil(this.ngUnsubscribe),
map(instanceId => instanceId !== RouteConstants.addClassInstance
? `${this._acs.dspAppConfig.iriBase}/${this.projectUuid}/${instanceId}`
: ''
)
)
}

searchParams: SearchParams;
get searchParams$(): Observable<SearchParams> {
return combineLatest([this.classId$, this.instanceId$, this.project$])
.pipe(
takeWhile(([project]) => project !== undefined),
takeUntil(this.ngUnsubscribe),
map(([classId, instanceId]) => !instanceId
? <SearchParams>{
query: this._setGravsearch(classId),
mode: 'gravsearch',
}
: null
)
)
}

// which resources are selected?
selectedResources: FilteredResources;
Expand All @@ -41,9 +119,12 @@ export class OntologyClassInstanceComponent extends ProjectBase implements OnIni

splitSizeChanged: SplitSize;

@Select(OntologiesSelectors.projectOntologies) projectOntologies$: Observable<IProjectOntologiesKeyValuePairs>;
@Select(UserSelectors.isSysAdmin) isSysAdmin$: Observable<boolean>;
@Select(UserSelectors.userProjects) userProjects$: Observable<StoredProject[]>;

constructor(
private _acs: AppConfigService,
private _authService: AuthService,
protected _route: ActivatedRoute,
private _ontologyService: OntologyService,
protected _projectService: ProjectService,
Expand All @@ -57,26 +138,13 @@ export class OntologyClassInstanceComponent extends ProjectBase implements OnIni
}

ngOnInit() {
// this._route.params.subscribe((params) => {
// this.initProject(params);
// });

this.project$.pipe(
filter(project => project !== undefined),
).subscribe((project) => {
this.project = project;
this.initProject(this._route.snapshot.params);
});
// waits for current project data to be loaded to the state if not already loaded
this.project$.pipe(takeUntil(this.ngUnsubscribe)).subscribe(() => this._cdr.markForCheck());
}

ngOnChanges() {
const projectOntologies = this._store.selectSnapshot(OntologiesSelectors.projectOntologies)[this.projectIri];
// find ontology of current resource class to get the class label
const ontology = projectOntologies.readOntologies.find((onto) => onto.id === this.ontoId);
const classes = getAllEntityDefinitionsAsArray(ontology.classes);
this.resClass = <ResourceClassDefinition>(
classes[classes.findIndex((res) => res.id === this.classId)]
);
ngOnDestroy() {
this.ngUnsubscribe.next();
this.ngUnsubscribe.complete();
}

openSelectedResources(res: FilteredResources) {
Expand All @@ -93,55 +161,6 @@ export class OntologyClassInstanceComponent extends ProjectBase implements OnIni
this._cdr.detectChanges();
}

private initProject(params: Params): void {
const iriBase = this._ontologyService.getIriBaseUrl();
const ontologyName = params[RouteConstants.ontoParameter];
const className = params[RouteConstants.classParameter];

// get the resource ids from the route. Do not use the RouteConstants ontology route constant here,
// because the ontology and class ids are not defined within the apps domain. They are defined by
// the api and can not be changed generically via route constants.
this.ontoId = `${iriBase}/ontology/${this.project.shortcode}/${ontologyName}/v2`;
this.classId = `${this.ontoId}#${className}`;

this.instanceId = params[RouteConstants.instanceParameter];
if (this.instanceId) {
// single instance view

if (this.instanceId === RouteConstants.addClassInstance) {
if (!this._authService.isLoggedIn) {
// user isn't signed in, redirect to project description
this._router.navigateByUrl(`/${RouteConstants.project}/${this.projectUuid}`);
} else {
const isSysAdmin = this._store.selectSnapshot(UserSelectors.isSysAdmin);
const usersProjects = this._store.selectSnapshot(UserSelectors.userProjects);
if (
usersProjects?.some((p) => p.id === this.projectIri) || // project member
isSysAdmin // system admin
) {
// user has permission, display create resource instance form
this.ngOnChanges();
} else {
// user is not a member of the project or a systemAdmin, redirect to project description
this._router.navigateByUrl(
`/${RouteConstants.project}/${this.projectUuid}`,
);
}
}
} else {
// get the single resource instance
this.resourceIri = `${this._acs.dspAppConfig.iriBase}/${this.projectUuid}/${this.instanceId}`;
}
} else {
// display all resource instances of this resource class
this.searchParams = {
query: this._setGravsearch(this.classId),
mode: 'gravsearch',
};
this._cdr.markForCheck();
}
}

private _setGravsearch(iri: string): string {
return `
PREFIX knora-api: <http://api.knora.org/ontology/knora-api/v2#>
Expand Down
Expand Up @@ -134,12 +134,11 @@ export class ResourceClassPropertyInfoComponent
}

ngAfterContentInit() {
if (this.propDef.isLinkProperty) {
// get current ontology to get linked res class information
const ontology = this._store.selectSnapshot(OntologiesSelectors.currentOntology);
const currentProjectOntologies = this._store.selectSnapshot(OntologiesSelectors.currentProjectOntologies);
if (ontology && currentProjectOntologies && this.propDef.isLinkProperty) {
// this property is a link property to another resource class
// get current ontology to get linked res class information

const ontology = this._store.selectSnapshot(OntologiesSelectors.currentOntology);
const currentProjectOntologies = this._store.selectSnapshot(OntologiesSelectors.currentProjectOntologies);
// get the base ontology of object type
const baseOnto = this.propDef.objectType.split('#')[0];
if (baseOnto !== ontology.id) {
Expand All @@ -162,10 +161,10 @@ export class ResourceClassPropertyInfoComponent
}
}

if (this.propDef.objectType === Constants.ListValue) {
// get current ontology lists to get linked list information
const currentOntologyLists = this._store.selectSnapshot(ListsSelectors.listsInProject);
if (currentOntologyLists && this.propDef.objectType === Constants.ListValue) {
// this property is a list property
// get current ontology lists to get linked list information
const currentOntologyLists = this._store.selectSnapshot(ListsSelectors.listsInProject);
const re = /\<([^)]+)\>/;
const listIri = this.propDef.guiAttributes[0].match(re)[1];
const listUrl = `/project/${
Expand Down
Expand Up @@ -102,6 +102,10 @@ export class ResourceInstanceFormComponent implements OnInit, OnChanges {
) {}

ngOnInit(): void {
if (!this.resourceClassIri) {
return;
}

// get ontology iri from res class iri
const splitIri = this.resourceClassIri.split('#');
this.ontologyIri = splitIri[0];
Expand Down

0 comments on commit 3c1e684

Please sign in to comment.