Commit fd288571 authored by François Agneray's avatar François Agneray
Browse files

Merge branch '51-gestion-des-groups-datasets' into 'develop'

Resolve "Gestion des groups/datasets"

Closes #51

See merge request !35
parents ff7561ef abd8b9ba
Pipeline #3626 passed with stages
in 7 minutes and 19 seconds
<div class="row mb-3"> <div class="btn-toolbar mb-3" role="toolbar" aria-label="Toolbar with button groups">
<div class="col-12"> <div class="btn-group mr-2" role="group" aria-label="First group">
<button (click)="openModalNew(templateForNew); $event.stopPropagation()" title="Add new dataset family" class="btn btn-outline-success float-right"> <button (click)="openModalNew(templateForNew); $event.stopPropagation()" title="Add new dataset family" class="btn btn-outline-success">
<span class="fas fa-plus"></span> New dataset family <span class="fas fa-plus"></span> New dataset family
</button> </button>
</div> </div>
<div class="btn-group mr-2" role="group" aria-label="Second group">
<a routerLink="group" title="Handle groups" class="btn btn-outline-primary">
<span class="fas fa-users"></span> Handle groups
</a>
</div>
</div> </div>
<div *ngFor="let datasetFamily of datasetFamilyList" class="card mb-3"> <div *ngFor="let datasetFamily of datasetFamilyList" class="card mb-3">
......
<form name="form" (ngSubmit)="f.form.valid && emit(f.form.value)" #f="ngForm" novalidate>
<div class="form-group">
<label for="label">Label</label>
<input type="text" class="form-control" name="label" [ngModel]="model.label" #label="ngModel" required>
</div>
<div class="form-group">
<div class="form-row h-100">
<div class="col-4">
<label for="datasets">Available datasets</label>
<select multiple class="form-control" name="availableDatasets" #selectAvailableDatasets>
<option *ngFor="let dataset of getAvailableDatasets()" [value]="dataset.name">{{dataset.name}}</option>
</select>
</div>
<div class="col-2 text-center my-auto">
<button (click)="addDatasets(selectAvailableDatasets)" type="button" class="btn btn-dark mb-1">>>></button><br>
<button (click)="removeDatasets(selectGroupDatasets)" type="button" class="btn btn-dark"><<<</button>
</div>
<div class="col-4">
<label for="datasets">Group's datasets</label>
<select multiple class="form-control" name="datasets" #selectGroupDatasets>
<option *ngFor="let dataset of groupDatasets" [value]="dataset">{{dataset}}</option>
</select>
</div>
</div>
</div>
<div class="form-group">
<ng-content></ng-content>
</div>
</form>
\ No newline at end of file
import { Component, Input, Output, EventEmitter, ViewChild, SimpleChanges, OnChanges } from '@angular/core';
import { NgForm } from '@angular/forms';
import { Group, Dataset } from '../../store/model';
@Component({
selector: 'app-form-group',
templateUrl: 'form-group.component.html'
})
export class FormGroupComponent implements OnChanges {
@ViewChild(NgForm, { static: true }) ngForm: NgForm;
@Input() model: Group = new Group();
@Input() datasetList: Dataset[];
@Output() submitted: EventEmitter<Group> = new EventEmitter();
public availableDatasets: string[];
public groupDatasets: string[] = [];
ngOnChanges(changes: SimpleChanges) {
if (changes.model && changes.model.currentValue) {
this.groupDatasets = [...this.model.datasets];
}
}
emit(group: Group) {
this.submitted.emit({
id: this.model.id,
label: group.label,
instance_name: this.model.instance_name,
datasets: this.groupDatasets
});
}
getAvailableDatasets() {
return this.datasetList.filter(d => !this.groupDatasets.includes(d.name));
}
addDatasets(selectElement) {
let availableDatasetsSelected = [];
for (var i = 0; i < selectElement.options.length; i++) {
const optionElement = selectElement.options[i];
if (optionElement.selected == true) {
availableDatasetsSelected.push(optionElement.value);
}
}
this.groupDatasets.push(...availableDatasetsSelected);
this.ngForm.control.markAsDirty();
}
removeDatasets(selectElement) {
let groupDatasetsSelected = [];
for (var i = 0; i < selectElement.options.length; i++) {
const optionElement = selectElement.options[i];
if (optionElement.selected == true) {
groupDatasetsSelected.push(optionElement.value);
}
}
this.groupDatasets = [...this.groupDatasets.filter(d => !groupDatasetsSelected.includes(d))]
this.ngForm.control.markAsDirty();
}
}
<div class="row mb-3">
<div class="col-12">
<button routerLink="../new-group" title="Add new dataset family" class="btn btn-outline-success">
<span class="fas fa-plus"></span> New group
</button>
</div>
</div>
<div class="card">
<div class="card-header">
Groups
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th scope="col">ID</th>
<th scope="col">Label</th>
<th scope="col">Datasets</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let group of groupList">
<td class="align-middle">{{ group.id }}</td>
<td class="align-middle">{{ group.label }}</td>
<td class="align-middle">
<span *ngIf="getDatasetsLenght(group) < 1" class="badge badge-pill badge-warning">
Empty
</span>
<span *ngFor="let d of group.datasets" class="badge badge-pill badge-info mr-1">
{{d}}
</span>
</td>
<td class="align-middle">
<a title="Edit this group" routerLink="../edit-group/{{group.id}}" class="btn btn-outline-primary">
<span class="fas fa-edit"></span>
</a>
&nbsp;
<button title="Delete this group" (click)="openModal(template, group); $event.stopPropagation()" class="btn btn-outline-danger">
<span class="fas fa-trash-alt"></span>
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<ng-template #template>
<div class="modal-header">
<h4 class="modal-title pull-left">Confirm</h4>
</div>
<div class="modal-body">
<p>Are you sure you want to delete this group : <strong>{{ groupForDel.label }}</strong> ?</p>
<p>
<button (click)="modalRef.hide()" class="btn btn-default">No</button>
&nbsp;
<button (click)="confirmDel()" class="btn btn-danger">Yes</button>
</p>
</div>
</ng-template>
import { Component, Input, Output, ChangeDetectionStrategy, EventEmitter, TemplateRef } from '@angular/core';
import { BsModalService } from 'ngx-bootstrap/modal';
import { BsModalRef } from 'ngx-bootstrap/modal/bs-modal-ref.service';
import { Group } from '../../store/model';
@Component({
selector: 'app-group-list',
templateUrl: 'group-list.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class GroupListComponent {
@Input() groupList: Group[];
@Output() deleteGroup: EventEmitter<Group> = new EventEmitter();
modalRef: BsModalRef;
groupForDel: Group;
constructor(private modalService: BsModalService) { }
getDatasetsLenght(group: Group) {
return group.datasets.length;
}
openModal(template: TemplateRef<any>, group: Group) {
this.groupForDel = group;
this.modalRef = this.modalService.show(template);
}
confirmDel() {
this.deleteGroup.emit(this.groupForDel);
this.modalRef.hide();
}
}
...@@ -9,6 +9,8 @@ import { FormDatasetFamilyComponent } from './dataset-family/form-dataset-famil ...@@ -9,6 +9,8 @@ import { FormDatasetFamilyComponent } from './dataset-family/form-dataset-famil
import { DatasetListComponent } from './dataset/dataset-list.component'; import { DatasetListComponent } from './dataset/dataset-list.component';
import { FormDatasetComponent } from './dataset/form-dataset.component'; import { FormDatasetComponent } from './dataset/form-dataset.component';
import { attributeDummiesComponents } from './attribute'; import { attributeDummiesComponents } from './attribute';
import { GroupListComponent } from './group/group-list.component';
import { FormGroupComponent } from './group/form-group.component';
export const dummiesComponents = [ export const dummiesComponents = [
ProjectListComponent, ProjectListComponent,
...@@ -21,5 +23,7 @@ export const dummiesComponents = [ ...@@ -21,5 +23,7 @@ export const dummiesComponents = [
FormDatasetFamilyComponent, FormDatasetFamilyComponent,
DatasetListComponent, DatasetListComponent,
FormDatasetComponent, FormDatasetComponent,
attributeDummiesComponents attributeDummiesComponents,
GroupListComponent,
FormGroupComponent
]; ];
<div class="container-fluid">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item">
<a routerLink="/instance-list">
Instances
</a>
</li>
<li class="breadcrumb-item">
<a routerLink="/configure-instance/{{ instanceSelected | async }}">
Configure instance {{ instanceSelected | async }}
</a>
</li>
<li class="breadcrumb-item" aria-current="page">
<a routerLink="/configure-instance/{{ instanceSelected | async }}/group">
Groups
</a>
</li>
<li *ngIf="groupListIsLoaded | async" class="breadcrumb-item active" aria-current="page">Edit group {{ (group | async).label }}</li>
</ol>
</nav>
</div>
<div class="container">
<div *ngIf="(groupListIsLoading | async) || (datasetListIsLoading | async)" class="row justify-content-center mt-5">
<span class="fas fa-circle-notch fa-spin fa-3x"></span>
<span class="sr-only">Loading...</span>
</div>
<div *ngIf="(groupListIsLoaded | async) && (datasetListIsLoaded | async)">
<div class="row">
<div class="col-12">
<app-form-group [model]="group | async" [datasetList]="datasetList | async" (submitted)="editGroup($event)" #formGroup>
<button [disabled]="!formGroup.ngForm.form.valid || formGroup.ngForm.form.pristine"
type="submit" class="btn btn-primary">
<i class="fa fa-database"></i> Update group information
</button>
&nbsp;
<a routerLink="/configure-instance/{{ instanceSelected | async }}/group" class="btn btn-danger">Cancel</a>
</app-form-group>
</div>
</div>
</div>
</div>
\ No newline at end of file
import { Component, OnInit} from '@angular/core';
import { Observable } from 'rxjs';
import { Store } from '@ngrx/store';
import { Group, Dataset } from '../../store/model';
import * as groupActions from '../../store/action/group.action';
import * as groupReducer from '../../store/reducer/group.reducer';
import * as groupSelector from '../../store/selector/group.selector';
import * as instanceSelector from '../../store/selector/instance.selector';
import * as datasetActions from '../../store/action/dataset.action';
import * as datasetSelector from '../../store/selector/dataset.selector';
@Component({
selector: 'app-edit-group',
templateUrl: 'edit-group.component.html'
})
export class EditGroupComponent implements OnInit {
public instanceSelected: Observable<string>;
public groupListIsLoading: Observable<boolean>;
public groupListIsLoaded: Observable<boolean>;
public group: Observable<Group>;
public datasetListIsLoading: Observable<boolean>;
public datasetListIsLoaded: Observable<boolean>;
public datasetList: Observable<Dataset[]>;
constructor(private store: Store<groupReducer.State>) {
this.instanceSelected = store.select(instanceSelector.getInstanceSelected);
this.groupListIsLoading = store.select(groupSelector.getGroupListIsLoading);
this.groupListIsLoaded = store.select(groupSelector.getGroupListIsLoaded);
this.group = store.select(groupSelector.getGroupByRouteId);
this.datasetListIsLoading = store.select(datasetSelector.getDatasetListIsLoading);
this.datasetListIsLoaded = store.select(datasetSelector.getDatasetListIsLoaded);
this.datasetList = store.select(datasetSelector.getDatasetList);
}
ngOnInit() {
this.store.dispatch(new groupActions.LoadGroupListAction());
this.store.dispatch(new datasetActions.LoadDatasetListAction());
}
editGroup(group: Group) {
this.store.dispatch(new groupActions.EditGroupAction(group));
}
}
<div class="container-fluid">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item">
<a routerLink="/instance-list">
Instances
</a>
</li>
<li class="breadcrumb-item">
<a routerLink="/configure-instance/{{ instanceSelected | async }}">
Configure instance {{ instanceSelected | async }}
</a>
</li>
<li class="breadcrumb-item active" aria-current="page">Groups</li>
</ol>
</nav>
<div *ngIf="groupListIsLoading | async" class="row justify-content-center mt-5">
<span class="fas fa-circle-notch fa-spin fa-3x"></span>
<span class="sr-only">Loading...</span>
</div>
<div *ngIf="groupListIsLoaded | async">
<app-group-list [groupList]="groupList | async" (deleteGroup)="deleteGroup($event)">
</app-group-list>
</div>
</div>
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { Store } from '@ngrx/store';
import { Group } from '../../store/model';
import * as groupActions from '../../store/action/group.action';
import * as groupReducer from '../../store/reducer/group.reducer';
import * as groupSelector from '../../store/selector/group.selector';
import * as instanceSelector from '../../store/selector/instance.selector';
@Component({
selector: 'app-group',
templateUrl: 'group.component.html'
})
export class GroupComponent implements OnInit {
public instanceSelected: Observable<string>;
public groupListIsLoading: Observable<boolean>;
public groupListIsLoaded: Observable<boolean>;
public groupList: Observable<Group[]>;
constructor(private store: Store<groupReducer.State>) {
this.instanceSelected = store.select(instanceSelector.getInstanceSelected);
this.groupListIsLoading = store.select(groupSelector.getGroupListIsLoading);
this.groupListIsLoaded = store.select(groupSelector.getGroupListIsLoaded);
this.groupList = store.select(groupSelector.getGroupList);
}
ngOnInit() {
this.store.dispatch(new groupActions.LoadGroupListAction());
}
deleteGroup(group: Group) {
this.store.dispatch(new groupActions.DeleteGroupAction(group));
}
}
<div class="container-fluid">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item">
<a routerLink="/instance-list">
Instances
</a>
</li>
<li class="breadcrumb-item">
<a routerLink="/configure-instance/{{ instanceSelected | async }}">
Configure instance {{ instanceSelected | async }}
</a>
</li>
<li class="breadcrumb-item" aria-current="page">
<a routerLink="/configure-instance/{{ instanceSelected | async }}/group">
Groups
</a>
</li>
<li class="breadcrumb-item active" aria-current="page">Add a new group</li>
</ol>
</nav>
</div>
<div class="container">
<div *ngIf="datasetListIsLoading | async" class="row justify-content-center mt-5">
<span class="fas fa-circle-notch fa-spin fa-3x"></span>
<span class="sr-only">Loading...</span>
</div>
<div *ngIf="datasetListIsLoaded | async">
<div class="row">
<div class="col-12">
<app-form-group [datasetList]="datasetList | async" (submitted)="addNewGroup($event)" #formGroup>
<button [disabled]="!formGroup.ngForm.form.valid || formGroup.ngForm.form.pristine" type="submit"
class="btn btn-primary">
<i class="fa fa-database"></i> Add the new group
</button>
&nbsp;
<a routerLink="/configure-instance/{{ instanceSelected | async }}/group" class="btn btn-danger">Cancel</a>
</app-form-group>
</div>
</div>
</div>
</div>
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { Store } from '@ngrx/store';
import { Group, Dataset } from '../../store/model';
import * as groupActions from '../../store/action/group.action';
import * as groupReducer from '../../store/reducer/group.reducer';
import * as instanceSelector from '../../store/selector/instance.selector';
import * as datasetActions from '../../store/action/dataset.action';
import * as datasetSelector from '../../store/selector/dataset.selector';
@Component({
selector: 'app-new-group',
templateUrl: 'new-group.component.html'
})
export class NewGroupComponent implements OnInit {
public instanceSelected: Observable<string>;
public datasetListIsLoading: Observable<boolean>;
public datasetListIsLoaded: Observable<boolean>;
public datasetList: Observable<Dataset[]>;
constructor(private store: Store<groupReducer.State>) {
this.instanceSelected = store.select(instanceSelector.getInstanceSelected);
this.datasetListIsLoading = store.select(datasetSelector.getDatasetListIsLoading);
this.datasetListIsLoaded = store.select(datasetSelector.getDatasetListIsLoaded);
this.datasetList = store.select(datasetSelector.getDatasetList);
}
ngOnInit() {
this.store.dispatch(new datasetActions.LoadDatasetListAction());
}
addNewGroup(group: Group) {
this.store.dispatch(new groupActions.AddNewGroupAction(group));
}
}
...@@ -15,6 +15,9 @@ import { ConfigureInstanceComponent } from './containers/instance/configure-inst ...@@ -15,6 +15,9 @@ import { ConfigureInstanceComponent } from './containers/instance/configure-inst
import { NewDatasetComponent } from './containers/dataset/new-dataset.component'; import { NewDatasetComponent } from './containers/dataset/new-dataset.component';
import { EditDatasetComponent } from './containers/dataset/edit-dataset.component'; import { EditDatasetComponent } from './containers/dataset/edit-dataset.component';
import { AttributeComponent } from './containers/attribute/attribute.component'; import { AttributeComponent } from './containers/attribute/attribute.component';
import { GroupComponent } from './containers/group/group.component';
import { NewGroupComponent } from './containers/group/new-group.component';
import { EditGroupComponent } from './containers/group/edit-group.component';
import { AuthGuard } from '../core/auth.guard'; import { AuthGuard } from '../core/auth.guard';
const routes: Routes = [ const routes: Routes = [
...@@ -22,6 +25,9 @@ const routes: Routes = [ ...@@ -22,6 +25,9 @@ const routes: Routes = [
{ path: 'new-instance', canActivate: [AuthGuard], component: NewInstanceComponent }, { path: 'new-instance', canActivate: [AuthGuard], component: NewInstanceComponent },
{ path: 'edit-instance/:iname', canActivate: [AuthGuard], component: EditInstanceComponent }, { path: 'edit-instance/:iname', canActivate: [AuthGuard], component: EditInstanceComponent },
{ path: 'configure-instance/:iname', canActivate: [AuthGuard], component: ConfigureInstanceComponent }, { path: 'configure-instance/:iname', canActivate: [AuthGuard], component: ConfigureInstanceComponent },
{ path: 'configure-instance/:iname/group', canActivate: [AuthGuard], component: GroupComponent },
{ path: 'configure-instance/:iname/new-group', canActivate: [AuthGuard], component: NewGroupComponent },
{ path: 'configure-instance/:iname/edit-group/:id', canActivate: [AuthGuard], component: EditGroupComponent },
{ path: 'configure-instance/:iname/new-dataset', canActivate: [AuthGuard], component: NewDatasetComponent }, { path: 'configure-instance/:iname/new-dataset', canActivate: [AuthGuard], component: NewDatasetComponent },
{ path: 'configure-instance/:iname/edit-dataset/:dname', canActivate: [AuthGuard], component: EditDatasetComponent }, { path: 'configure-instance/:iname/edit-dataset/:dname', canActivate: [AuthGuard], component: EditDatasetComponent },
{ path: 'configure-instance/:iname/configure-dataset/:dname', canActivate: [AuthGuard], component: AttributeComponent }, { path: 'configure-instance/:iname/configure-dataset/:dname', canActivate: [AuthGuard], component: AttributeComponent },
...@@ -58,5 +64,8 @@ export const routedComponents = [ ...@@ -58,5 +64,8 @@ export const routedComponents = [
ConfigureInstanceComponent, ConfigureInstanceComponent,
NewDatasetComponent, NewDatasetComponent,
EditDatasetComponent, EditDatasetComponent,
AttributeComponent AttributeComponent,
GroupComponent,
NewGroupComponent,
EditGroupComponent
]; ];
import { Action } from '@ngrx/store';
import { Group } from '../model';
export const LOAD_GROUP_LIST = '[Group] Load Group List';
export const LOAD_GROUP_LIST_SUCCESS = '[Group] Load Group List Success';
export const LOAD_GROUP_LIST_FAIL = '[Group] Load Group List Fail';
export const ADD_NEW_GROUP = '[Group] Add New Group';
export const ADD_NEW_GROUP_SUCCESS = '[Group] Add New Group Success';
export const ADD_NEW_GROUP_FAIL = '[Group] Add New Group Fail';
export const EDIT_GROUP = '[Group] Edit Group';
export const EDIT_GROUP_SUCCESS = '[Group] Edit Group Success';
export const EDIT_GROUP_FAIL = '[Group] Edit Group Fail';
export const DELETE_GROUP = '[Group] Delete Group';
export const DELETE_GROUP_SUCCESS = '[Group] Delete Group Success';
export const DELETE_GROUP_FAIL = '[Group] Delete Group Fail';
export class LoadGroupListAction implements Action {
type = LOAD_GROUP_LIST;
constructor(public payload: {} = null) { }
}
export class LoadGroupListSuccessAction implements Action {
type = LOAD_GROUP_LIST_SUCCESS;
constructor(public payload: Group[]) { }
}
export class LoadGroupListFailAction implements Action {
type = LOAD_GROUP_LIST_FAIL;
constructor(public payload: {} = null) { }
}
export class AddNewGroupAction implements Action {
type = ADD_NEW_GROUP;
constructor(public payload: Group) { }
}
export class AddNewGroupSuccessAction implements Action {
type = ADD_NEW_GROUP_SUCCESS;
constructor(public payload: Group) { }
}
export class AddNewGroupFailAction implements Action {
type = ADD_NEW_GROUP_FAIL;
constructor(public payload: {} = null) { }
}
export class EditGroupAction implements Action {
type = EDIT_GROUP;