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

[Do not merge] Shanoir issue#2388 planned execution #2468

Draft
wants to merge 13 commits into
base: develop
Choose a base branch
from
Draft
Prev Previous commit
Next Next commit
#2388-planned-execution
- Basic component
- Front logic
jcomedouteau committed Oct 24, 2024
commit 89a2a0c434fd98243a9f01178dfda813ed7fe8f1
Original file line number Diff line number Diff line change
@@ -38,7 +38,7 @@ public interface PlannedExecutionApi {
@ApiResponse(responseCode = "403", description = "forbidden"),
@ApiResponse(responseCode = "500", description = "unexpected error"),
@ApiResponse(responseCode = "503", description = "Internal error")})
@PostMapping(value = "/create", consumes = "application/json", produces = "application/json")
@PostMapping(value = "", consumes = "application/json", produces = "application/json")
ResponseEntity<PlannedExecution> createPlannedExecution(@RequestBody PlannedExecution plannedExecution) throws IOException, RestServiceException, SecurityException;

@Operation(summary = "Delete a PlannedExecution entity", description = "Deletes the planned execution by its ID", tags={ })
@@ -68,7 +68,7 @@ public interface PlannedExecutionApi {
@ApiResponse(responseCode = "404", description = "not found"),
@ApiResponse(responseCode = "500", description = "unexpected error"),
@ApiResponse(responseCode = "503", description = "Internal error")})
@PostMapping(value = "/update/{executionId}", consumes = "application/json", produces = "application/json")
@PostMapping(value = "/{executionId}", consumes = "application/json", produces = "application/json")
ResponseEntity<PlannedExecution> updatePlannedExecution(@RequestBody PlannedExecution plannedExecution) throws IOException, RestServiceException, EntityNotFoundException, SecurityException;

}
15 changes: 8 additions & 7 deletions shanoir-ng-front/src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
@@ -86,8 +86,8 @@ import { AccessRequestComponent } from './users/access-request/access-request.co
import { PipelinesComponent } from './vip/pipelines/pipelines.component';
import { ExecutionComponent } from './vip/execution/execution.component';
import { ExecutionMonitoringsComponent } from './vip/execution-monitorings/execution-monitorings.component';
import { ExecutionPlanningList } from "./vip/execution-planning/execution-planning-list.component"
import { ExecutionPlanning } from "./vip/execution-planning/execution-planning.component";
import { PlannedExecutionListComponent } from "./vip/planned-execution/planned-execution-list.component"
import { PlannedExecutionComponent } from "./vip/planned-execution/planned-execution.component";
import { MetadataComponent } from './datasets/dataset/metadata/metadata.component';
import { ApplyStudyCardOnComponent } from './study-cards/apply-study-card-on/apply-study-card-on.component';
import { PreClinicalContextComponent } from './import/pre-clinical-context/pre-clinical-context.component';
@@ -130,11 +130,11 @@ let routes: Routes = [
path: 'execution-monitoring',
component: ExecutionMonitoringsComponent
}, {
path: 'execution-planning',
component: ExecutionPlanning
path: 'planned-execution',
component: PlannedExecutionComponent
}, {
path: 'execution-monitoring-list',
component: ExecutionPlanningList
path: 'planned-execution-list',
component: PlannedExecutionListComponent
}, {
path: 'pipelines',
component: PipelinesComponent
@@ -174,7 +174,8 @@ let routes: Routes = [
component: ImportProcessedDatasetComponent,
data: {importMode: 'Processed Dataset'}
}, {
path: 'series',
path: 'ser' +
'ies',
component: SelectSeriesComponent
}, {
path: 'eegseries',
10 changes: 6 additions & 4 deletions shanoir-ng-front/src/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -273,8 +273,9 @@ import {StudyHistoryComponent} from "./studies/study-history/study-history.compo
import { StudyTreeComponent } from './studies/study/study-tree.component';
import { TreeService } from './studies/study/tree.service';
import { CoilNodeComponent } from './coils/coil/tree/coil-node.component';
import {ExecutionPlanningList} from "./vip/execution-planning/execution-planning-list.component";
import {ExecutionPlanning} from "./vip/execution-planning/execution-planning.component";
import { PlannedExecutionListComponent } from "./vip/planned-execution/planned-execution-list.component";
import { PlannedExecutionComponent } from "./vip/planned-execution/planned-execution.component";
import { PlannedExecutionService } from "./vip/planned-execution/planned-execution.service";

@NgModule({
imports: [
@@ -466,8 +467,8 @@ import {ExecutionPlanning} from "./vip/execution-planning/execution-planning.com
TestQualityCardOptionsComponent,
StudyTreeComponent,
CoilNodeComponent,
ExecutionPlanningList,
ExecutionPlanning
PlannedExecutionListComponent,
PlannedExecutionComponent
],
providers: [
AcquisitionEquipmentService,
@@ -555,6 +556,7 @@ import {ExecutionPlanning} from "./vip/execution-planning/execution-planning.com
SessionService,
ShanoirEventService,
TreeService,
PlannedExecutionService,
{ provide: HTTP_INTERCEPTORS, useClass: ShanoirHttpInterceptor, multi: true }
],
bootstrap: [AppComponent]
Original file line number Diff line number Diff line change
@@ -426,7 +426,7 @@ <h2 class="header command-zone" i18n="Create study|Title@@studyDetailCreateTitle
</fieldset>
<fieldset [class.hidden-tab]="activeTab != 'executions'" tab="executions">
<legend>Execution planning</legend>
<execution-planning-list [studyId]="study.id" [mode]="mode"/>
<planned-execution-list [studyId]="study.id" />
</fieldset>
</form>
</div>
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!--
<!--PlannedexecutionApi
Shanoir NG - Import, manage and share neuroimaging data
Copyright (C) 2009-2019 Inria - https://www.inria.fr/
Contact us on https://project.inria.fr/shanoir/
@@ -22,31 +22,31 @@
(cancel)="goToView()"
(back)="goBack()">
<button *ngIf="mode != 'view'"
type="button"
class="Button right-icon show-errors"
type="button"
class="Button right-icon show-errors"
(click)="onShowErrors()"
[disabled]="footerState.valid || showRulesErrors || qualityCard.rules?.length == 0">
Show all errors<i class="fas fa-bug"></i>
</button>
<button
<button
*ngIf="mode == 'view' && isStudyAdmin"
type="button"
class="right-icon apply test"
(click)="test()"
class="right-icon apply test"
(click)="test()"
[disabled]="applying || testing">
Test
<i *ngIf="!testing" class="fa-solid fa-play"></i>
<i *ngIf="testing" class="fa fa-cog fa-spin"></i>
<i *ngIf="testing" class="fa fa-cog fa-spin"></i>
</button>
<button
<button
*ngIf="mode == 'view' && isStudyAdmin"
type="button"
class="alt right-icon apply"
(click)="apply()"
class="alt right-icon apply"
(click)="apply()"
[disabled]="applying || testing">
Apply now
<i *ngIf="!applying" class="fa-solid fa-play"></i>
<i *ngIf="applying" class="fa fa-cog fa-spin"></i>
<i *ngIf="applying" class="fa fa-cog fa-spin"></i>
</button>
<button *ngIf="report" type="button" class="apply right-icon" (click)="downloadReport()">
Download error report
@@ -118,8 +118,8 @@ <h2 class="header command-zone">Create quality card</h2>
<study-card-rules class="rules" [formGroup]="form"
cardType="qualitycard"
formControlName="rules"
[(ngModel)]="qualityCard.rules"
[mode]="selectMode ? 'select' : mode"
[(ngModel)]="qualityCard.rules"
[mode]="selectMode ? 'select' : mode"
[allCoils]="allCoils"
[studyId]="qualityCard.study?.id"
[showErrors]="showRulesErrors"
@@ -133,7 +133,7 @@ <h2 class="header command-zone">Create quality card</h2>
</fieldset>
<fieldset class="report" *ngIf="report">
<legend>
Application Report
Application Report
<span *ngIf="reportIsTest">(test)</span>
</legend>
<shanoir-table #table class="report"
Original file line number Diff line number Diff line change
@@ -2,12 +2,12 @@
* Shanoir NG - Import, manage and share neuroimaging data
* Copyright (C) 2009-2019 Inria - https://www.inria.fr/
* Contact us on https://project.inria.fr/shanoir/
*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see https://www.gnu.org/licenses/gpl-3.0.html
*/
@@ -80,7 +80,7 @@ export class QualityCardComponent extends EntityComponent<QualityCard> {

constructor(
private route: ActivatedRoute,
private qualityCardService: QualityCardService,
private qualityCardService: QualityCardService,
private studyService: StudyService,
private examinationService: ExaminationService,
private studyRightsService: StudyRightsService,
@@ -114,13 +114,13 @@ export class QualityCardComponent extends EntityComponent<QualityCard> {

initView(): Promise<void> {
this.hasAdministrateRightPromise = this.hasAdminRightsOnStudy().then(res => this.isStudyAdmin = res);
return Promise.resolve();
return Promise.resolve();
}

initEdit(): Promise<void> {
this.hasAdministrateRightPromise = this.hasAdminRightsOnStudy().then(res => this.isStudyAdmin = res);
this.fetchStudies();
return Promise.resolve();
return Promise.resolve();
}

initCreate(): Promise<void> {
@@ -168,7 +168,7 @@ export class QualityCardComponent extends EntityComponent<QualityCard> {
});
}
}

private fetchStudies(): Promise<void | IdName[]> {
return this.studyService.findStudyIdNamesIcanAdmin()
.then(studies => this.studies = studies);
@@ -201,7 +201,7 @@ export class QualityCardComponent extends EntityComponent<QualityCard> {

apply() {
this.confirmService.confirm(
'Apply Quality Card',
'Apply Quality Card',
`Do you want to apply the quality card named "${this.qualityCard.name}" all over the study "${this.qualityCard.study.name}" ? This would permanentely overwrite previous quality tags for the study's subjects.`
).then(accept => {
if (accept) {
@@ -236,7 +236,7 @@ export class QualityCardComponent extends EntityComponent<QualityCard> {
this.performTest();
}
}
});
});
}

openSetTestInterval(nbExaminations: number): Promise<Interval | 'cancel'> {
@@ -248,7 +248,7 @@ export class QualityCardComponent extends EntityComponent<QualityCard> {
private waitForEnd(modalRef: ComponentRef<any>): Promise<Interval | 'cancel'> {
let resPromise: SuperPromise<any | 'cancel'> = new SuperPromise();
let result: Observable<any> = Observable.race([
modalRef.instance.test,
modalRef.instance.test,
modalRef.instance.close.map(() => 'cancel')
]);
result.pipe(take(1)).subscribe(ret => {
@@ -288,4 +288,4 @@ export class QualityCardComponent extends EntityComponent<QualityCard> {
protected downloadReport() {
QualityCardComponent.downloadReport(this.report, this.qualityCard?.name);
}
}
}
1 change: 1 addition & 0 deletions shanoir-ng-front/src/app/utils/app.utils.ts
Original file line number Diff line number Diff line change
@@ -154,6 +154,7 @@ export const BACKEND_API_PRECLINICAL_MS_URL: string = BACKEND_API_URL + '/precli
export const BACKEND_API_VIP_URL: string = BACKEND_API_DATASET_MS_URL + '/vip';
export const BACKEND_API_VIP_EXEC_URL : string = BACKEND_API_VIP_URL + "/execution";
export const BACKEND_API_VIP_PIPE_URL : string = BACKEND_API_VIP_URL + "/pipeline";
export const BACKEND_API_VIP_PLANNED_EXEC_URL : string = BACKEND_API_VIP_EXEC_URL + "/planned";

export const BACKEND_API_VIP_EXEC_MONITORING_URL: string = BACKEND_API_DATASET_MS_URL + '/execution-monitoring';

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

6 changes: 3 additions & 3 deletions shanoir-ng-front/src/app/vip/execution/execution.service.ts
Original file line number Diff line number Diff line change
@@ -20,7 +20,7 @@ import {Execution} from "../models/execution";
import {ExecutionCandidateDto} from "../models/execution-candidate.dto";
import {IdName} from "../../shared/models/id-name.model";
import {Id} from "../../shared/models/id.model";
import {AutomaticExecution} from "../models/automatic-execution";
import {PlannedExecution} from "../models/planned-execution";

@Injectable()
export class ExecutionService {
@@ -47,8 +47,8 @@ export class ExecutionService {
* Get all automatic executions linked to a study
* @param study_id the study id we want the automatic executions from
*/
public getAutomaticExecutions(study_id: number): Promise<AutomaticExecution[]> {
return this.httpClient.get<AutomaticExecution[]>(`${this.executionUrl}/automatic/` + study_id).toPromise();
public getAutomaticExecutions(study_id: number): Promise<PlannedExecution[]> {
return this.httpClient.get<PlannedExecution[]>(`${this.executionUrl}/automatic/` + study_id).toPromise();
}


Original file line number Diff line number Diff line change
@@ -11,8 +11,9 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see https://www.gnu.org/licenses/gpl-3.0.html
*/
import {DatasetProcessingType} from "../../enum/dataset-processing-type.enum";
import {Entity} from "../../shared/components/entity/entity.abstract";

export class AutomaticExecution {
export class PlannedExecution extends Entity {
id: number
name: string
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<label i18n="Execution planning">Executions's planification</label>

<h2 i18n="Manage examinations|Title@@examinationListTitle">Manage examinations</h2>

<shanoir-table #table
[getPage]="getPage.bind(this)"
[columnDefs]="columnDefs"
[customActionDefs]="customActionDefs"
[rowRoute]="getRowRoute.bind(this)">
</shanoir-table>
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import {Component, Input, ViewChild} from '@angular/core';
import {PlannedExecution} from "../models/planned-execution";
import {TableComponent} from "../../shared/components/table/table.component";
import { EntityService } from 'src/app/shared/components/entity/entity.abstract.service';
import { ColumnDefinition } from 'src/app/shared/components/table/column.definition.type';
import { PlannedExecutionService } from "./planned-execution.service";
import {BrowserPaginEntityListComponent} from "../../shared/components/entity/entity-list.browser.component.abstract";
import {StudyUserRight} from "../../studies/shared/study-user-right.enum";
import {StudyRightsService} from "../../studies/shared/study-rights.service";

@Component({
selector: 'planned-execution-list',
templateUrl: './planned-execution-list.component.html',
styleUrls: ['./planned-execution-list.component.css']
})
export class PlannedExecutionListComponent extends BrowserPaginEntityListComponent<PlannedExecution> {

@Input() studyId: number

@ViewChild('table', { static: false }) table: TableComponent;

constructor(protected plannedExecutionService: PlannedExecutionService,
protected studyRightsService: StudyRightsService) {
super('plannedExecution')
}

getService(): EntityService<PlannedExecution> {
return this.plannedExecutionService
}

getEntities(): Promise<PlannedExecution[]> {
return this.plannedExecutionService.getPlannedExecutionsByStudy(this.studyId)
}

getColumnDefs(): ColumnDefinition[] {
return [
{headerName: "Id", field: "id", type: "number", width: "60px", defaultSortCol: true, defaultAsc: false},
{headerName: "Name", field: "name", type: "string", width: "60px", defaultSortCol: false, defaultAsc: false},
];
}

getCustomActionsDefs(): any[] {
return [];
}

getOptions() {
return {
new: this.hasAdminRightsOnStudy(),
view: true,
edit: this.hasAdminRightsOnStudy(),
delete: this.hasAdminRightsOnStudy()
};
}

private hasAdminRightsOnStudy(): Promise<boolean> {
if (this.keycloakService.isUserAdmin()) {
return Promise.resolve(true);
} else {
return this.studyRightsService.getMyRightsForStudy(this.studyId).then(rights => {
return rights.includes(StudyUserRight.CAN_ADMINISTRATE);
});
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<div #formContainer class="content-component detail">
<form *ngIf="form" [formGroup]="form" class="max-content" novalidate [class.disabled]="footerState.loading">
<form-footer
[state]="footerState"
(save)="save()"
(edit)="goToEdit()"
(delete) = "delete()"
(cancel)="goToView()"
(back)="goBack()"
>
</form-footer>

<span [ngSwitch]="mode">
<ng-template [ngSwitchCase]="'view'">
<h2 class="header command-zone"i18n="View planned execution center|Title@@plannedexecutionDetailViewTitle">Details on planned execution</h2>
</ng-template>
<ng-template [ngSwitchCase]="'edit'">
<h2 class="header command-zone"i18n="Edit planned execution|Title@@plannedexecutionDetailEditTitle">Edit planned execution</h2>
</ng-template>
<ng-template [ngSwitchCase]="'create'">
<h2 class="header command-zone"i18n="Create planned execution|Title@@plannedexecutionDetailCreateTitle">Create planned execution</h2>
</ng-template>
</span>

<fieldset>
<ol>
<li>
<label i18n="Center detail|Name label@@centerDetailName">Name</label>
<span class="right-col" [ngSwitch]="mode">
<ng-template [ngSwitchCase]="'view'">
{{plannedExecution.name}}
</ng-template>
<ng-template ngSwitchDefault>
<input type="text" id="name" formControlName="name" [(ngModel)]="plannedExecution.name" />
<label *ngIf="hasError('name', ['required'])" class="form-validation-alert" i18n="Edit center|Name required error@@centerDetailNameRequiredError">Name is required!</label>
<label *ngIf="hasError('name', ['minlength', 'maxlength'])" class="form-validation-alert" i18n="Edit center|Name length error@@centerDetailNameLengthError">Name length must be between 2 and 200!</label>
</ng-template>
</span>
</li>
</ol>
</fieldset>
</form>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import {Component, Input} from '@angular/core';
import {PlannedExecution} from "../models/planned-execution";
import {ModesAware} from "../../preclinical/shared/mode/mode.decorator";
import {EntityComponent, Mode} from "../../shared/components/entity/entity.component.abstract";
import {ActivatedRoute} from "@angular/router";
import {QualityCardService} from "../../study-cards/shared/quality-card.service";
import {StudyService} from "../../studies/shared/study.service";
import {ExaminationService} from "../../examinations/shared/examination.service";
import {StudyRightsService} from "../../studies/shared/study-rights.service";
import {KeycloakService} from "../../shared/keycloak/keycloak.service";
import {CoilService} from "../../coils/shared/coil.service";
import {ConfirmDialogService} from "../../shared/components/confirm-dialog/confirm-dialog.service";
import {PlannedExecutionService} from "./planned-execution.service";
import {FormArray, FormGroup, Validators} from "@angular/forms";
import {StudyCardRulesComponent} from "../../study-cards/study-card-rules/study-card-rules.component";
import {QualityCard} from "../../study-cards/shared/quality-card.model";
import {EntityService} from "../../shared/components/entity/entity.abstract.service";


@Component({
selector: 'planned-execution',
templateUrl: './planned-execution.component.html',
styleUrls: ['./planned-execution.component.css']
})
@ModesAware
export class PlannedExecutionComponent extends EntityComponent<PlannedExecution> {

constructor(
private route: ActivatedRoute,
private studyRightsService: StudyRightsService,
keycloakService: KeycloakService,
protected plannedExecutionService: PlannedExecutionService) {
super(route, 'planned-execution');
}

get plannedExecution(): PlannedExecution { return this.entity; }
set plannedExecution(pe: PlannedExecution) { this.entity= pe; }


buildForm(): FormGroup {
return this.formBuilder.group({
'name': [this.plannedExecution.name, [Validators.required, Validators.minLength(2), this.registerOnSubmitValidator('unique', 'name')]],
});
}

getService(): EntityService<PlannedExecution> {
return this.plannedExecutionService;
}

initCreate(): Promise<void> {
return Promise.resolve();
}

initEdit(): Promise<void> {
return Promise.resolve();
}

initView(): Promise<void> {
return Promise.resolve();
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* Shanoir NG - Import, manage and share neuroimaging data
* Copyright (C) 2009-2022 Inria - https://www.inria.fr/
* Contact us on https://project.inria.fr/shanoir/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see https://www.gnu.org/licenses/gpl-3.0.html
*/

import {Injectable} from "@angular/core";
import {HttpClient} from "@angular/common/http";
import * as AppUtils from "../../utils/app.utils";
import {PlannedExecution} from "../models/planned-execution";
import {EntityService} from "../../shared/components/entity/entity.abstract.service";

@Injectable()
export class PlannedExecutionService extends EntityService<PlannedExecution> {

API_URL: string = AppUtils.BACKEND_API_VIP_PLANNED_EXEC_URL;

constructor(protected httpClient: HttpClient) {super(httpClient);}

getEntityInstance(entity: PlannedExecution | undefined): PlannedExecution {return new PlannedExecution();}

/**
* Get all planned executions linked to a study
* @param study_id the study id we want the automatic executions from
*/
public getPlannedExecutionsByStudy(study_id: number): Promise<PlannedExecution[]> {
return this.httpClient.get<PlannedExecution[]>(this.API_URL + study_id).toPromise();
}
}