From fe9e21968549da39b07f5f7e616a5e31ad2b8f78 Mon Sep 17 00:00:00 2001
From: Aurore Lecointe <lecointe@codelutin.com>
Date: Mon, 28 Jun 2021 16:18:43 +0200
Subject: [PATCH 01/26] =?UTF-8?q?Apr=C3=A8s=20cr=C3=A9ation=20application,?=
 =?UTF-8?q?=20redirige=20vers=20la=20liste?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 ui2/src/views/application/ApplicationCreationView.vue | 1 +
 1 file changed, 1 insertion(+)

diff --git a/ui2/src/views/application/ApplicationCreationView.vue b/ui2/src/views/application/ApplicationCreationView.vue
index 56f125e51..3b9174bfa 100644
--- a/ui2/src/views/application/ApplicationCreationView.vue
+++ b/ui2/src/views/application/ApplicationCreationView.vue
@@ -108,6 +108,7 @@ export default class ApplicationCreationView extends Vue {
     try {
       await this.applicationService.createApplication(this.applicationConfig);
       this.alertService.toastSuccess(this.$t("alert.application-creation-success"));
+      this.$router.push("/applications");
     } catch (error) {
       this.checkMessageErrors(error);
     }
-- 
GitLab


From 15f0c19d38f547b7ed65e3fb25f0d181e84f6bcc Mon Sep 17 00:00:00 2001
From: Aurore Lecointe <lecointe@codelutin.com>
Date: Mon, 28 Jun 2021 16:24:20 +0200
Subject: [PATCH 02/26] =?UTF-8?q?Connexion=20apr=C3=A8s=20appui=20sur=20"E?=
 =?UTF-8?q?ntr=C3=A9e"?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 ui2/src/components/login/Signin.vue | 1 +
 1 file changed, 1 insertion(+)

diff --git a/ui2/src/components/login/Signin.vue b/ui2/src/components/login/Signin.vue
index 87a70f249..35bc30ad6 100644
--- a/ui2/src/components/login/Signin.vue
+++ b/ui2/src/components/login/Signin.vue
@@ -45,6 +45,7 @@
             v-model="password"
             :placeholder="$t('login.pwd-placeholder')"
             :password-reveal="true"
+            @keyup.native.enter="handleSubmit(signIn)"
           >
           </b-input>
         </b-field>
-- 
GitLab


From 594e66e450b35760f8b365c7f41a0f5cefec2c81 Mon Sep 17 00:00:00 2001
From: Aurore Lecointe <lecointe@codelutin.com>
Date: Mon, 28 Jun 2021 16:26:56 +0200
Subject: [PATCH 03/26] =?UTF-8?q?D=C3=A9place=20les=20logos=20sur=20la=20g?=
 =?UTF-8?q?auche=20du=20Menu?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 ui2/src/views/common/MenuView.vue | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/ui2/src/views/common/MenuView.vue b/ui2/src/views/common/MenuView.vue
index b98c505e9..8489fd763 100644
--- a/ui2/src/views/common/MenuView.vue
+++ b/ui2/src/views/common/MenuView.vue
@@ -2,6 +2,11 @@
   <div class="menu-view-container">
     <b-navbar class="menu-view" v-if="open">
       <template #start>
+        <b-navbar-item href="https://www.inrae.fr/">
+          <img class="logo_blanc" src="@/assets/logo-inrae_blanc.svg" />
+          <img class="logo_vert" src="@/assets/Logo-INRAE.svg" />
+        </b-navbar-item>
+        <img class="logo_rep" src="@/assets/Rep-FR-logo.svg" />
         <b-navbar-item tag="router-link" :to="{ path: '/applications' }">
           {{ $t("menu.applications") }}
         </b-navbar-item>
@@ -28,11 +33,6 @@
             </b-select>
           </b-field>
         </b-navbar-item>
-        <b-navbar-item href="https://www.inrae.fr/">
-          <img class="logo_blanc" src="@/assets/logo-inrae_blanc.svg" />
-          <img class="logo_vert" src="@/assets/Logo-INRAE.svg" />
-        </b-navbar-item>
-        <img class="logo_rep" src="@/assets/Rep-FR-logo.svg" />
       </template>
     </b-navbar>
     <FontAwesomeIcon
-- 
GitLab


From cf4b6c0cf1ab31c2ba17bf1998afde0d51e5db66 Mon Sep 17 00:00:00 2001
From: Aurore Lecointe <lecointe@codelutin.com>
Date: Mon, 28 Jun 2021 16:31:31 +0200
Subject: [PATCH 04/26] :lipstick: change la couleur de la pagination

---
 ui2/src/style/_common.scss | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/ui2/src/style/_common.scss b/ui2/src/style/_common.scss
index 04baf8edb..8d7d94115 100644
--- a/ui2/src/style/_common.scss
+++ b/ui2/src/style/_common.scss
@@ -70,3 +70,8 @@ a {
 .message .media {
   align-items: center;
 }
+
+.pagination-link.is-current {
+  background-color: $dark;
+  border-color: $dark;
+}
-- 
GitLab


From 60cd5b9a9a45e3705362558d7a5356fb9bf1fd0a Mon Sep 17 00:00:00 2001
From: Aurore Lecointe <lecointe@codelutin.com>
Date: Mon, 28 Jun 2021 16:45:59 +0200
Subject: [PATCH 05/26] =?UTF-8?q?Place=20toutes=20les=20ic=C3=B4nes=20?=
 =?UTF-8?q?=C3=A0=20gauche?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 ui2/src/components/login/Signin.vue                   | 2 +-
 ui2/src/main.js                                       | 4 +++-
 ui2/src/views/application/ApplicationCreationView.vue | 4 ++--
 ui2/src/views/application/ApplicationsView.vue        | 5 +++--
 ui2/src/views/common/MenuView.vue                     | 2 +-
 5 files changed, 10 insertions(+), 7 deletions(-)

diff --git a/ui2/src/components/login/Signin.vue b/ui2/src/components/login/Signin.vue
index 35bc30ad6..93f284d20 100644
--- a/ui2/src/components/login/Signin.vue
+++ b/ui2/src/components/login/Signin.vue
@@ -53,7 +53,7 @@
     </section>
 
     <div class="buttons">
-      <b-button type="is-primary" @click="handleSubmit(signIn)" icon-right="plus">
+      <b-button type="is-primary" @click="handleSubmit(signIn)" icon-left="sign-in-alt">
         {{ $t("login.signin") }}
       </b-button>
       <router-link :to="{ path: '/' }">
diff --git a/ui2/src/main.js b/ui2/src/main.js
index 7936b7271..b3cf151a1 100644
--- a/ui2/src/main.js
+++ b/ui2/src/main.js
@@ -28,6 +28,7 @@ import {
   faVial,
   faCaretRight,
   faArrowLeft,
+  faSignInAlt,
 } from "@fortawesome/free-solid-svg-icons";
 import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
 library.add(
@@ -53,7 +54,8 @@ library.add(
   faDownload,
   faVial,
   faCaretRight,
-  faArrowLeft
+  faArrowLeft,
+  faSignInAlt
 );
 Vue.component("vue-fontawesome", FontAwesomeIcon);
 
diff --git a/ui2/src/views/application/ApplicationCreationView.vue b/ui2/src/views/application/ApplicationCreationView.vue
index 3b9174bfa..de85979b8 100644
--- a/ui2/src/views/application/ApplicationCreationView.vue
+++ b/ui2/src/views/application/ApplicationCreationView.vue
@@ -56,10 +56,10 @@
             </b-field>
           </ValidationProvider>
           <div class="buttons">
-            <b-button type="is-light" @click="handleSubmit(testApplication)" icon-right="vial">
+            <b-button type="is-light" @click="handleSubmit(testApplication)" icon-left="vial">
               {{ $t("applications.test") }}
             </b-button>
-            <b-button type="is-primary" @click="handleSubmit(createApplication)" icon-right="plus">
+            <b-button type="is-primary" @click="handleSubmit(createApplication)" icon-left="plus">
               {{ $t("applications.create") }}
             </b-button>
           </div>
diff --git a/ui2/src/views/application/ApplicationsView.vue b/ui2/src/views/application/ApplicationsView.vue
index 6abee7308..287a24379 100644
--- a/ui2/src/views/application/ApplicationsView.vue
+++ b/ui2/src/views/application/ApplicationsView.vue
@@ -2,7 +2,7 @@
   <PageView>
     <h1 class="title main-title">{{ $t("titles.applications-page") }}</h1>
     <div class="buttons" v-if="canCreateApplication">
-      <b-button type="is-primary" @click="createApplication" icon-right="plus">
+      <b-button type="is-primary" @click="createApplication" icon-left="plus">
         {{ $t("applications.create") }}
       </b-button>
     </div>
@@ -52,7 +52,8 @@ export default class ApplicationsView extends Vue {
   applicationService = ApplicationService.INSTANCE;
 
   applications = [];
-  canCreateApplication = LoginService.INSTANCE.getAuthenticatedUser().authorizedForApplicationCreation;
+  canCreateApplication = LoginService.INSTANCE.getAuthenticatedUser()
+    .authorizedForApplicationCreation;
 
   created() {
     this.init();
diff --git a/ui2/src/views/common/MenuView.vue b/ui2/src/views/common/MenuView.vue
index 8489fd763..a3879bc35 100644
--- a/ui2/src/views/common/MenuView.vue
+++ b/ui2/src/views/common/MenuView.vue
@@ -15,7 +15,7 @@
       <template #end>
         <b-navbar-item tag="div">
           <div class="buttons">
-            <b-button type="is-info" @click="logout" icon-right="sign-out-alt">{{
+            <b-button type="is-info" @click="logout" icon-left="sign-out-alt">{{
               $t("menu.logout")
             }}</b-button>
           </div>
-- 
GitLab


From 16804dfdc85245f1888619a23a665bdcde18f917 Mon Sep 17 00:00:00 2001
From: Aurore Lecointe <lecointe@codelutin.com>
Date: Mon, 28 Jun 2021 17:15:21 +0200
Subject: [PATCH 06/26] =?UTF-8?q?Ajoute=20la=20possibilit=C3=A9=20de=20se?=
 =?UTF-8?q?=20cr=C3=A9er=20un=20compte?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 ui2/src/components/login/Register.vue | 127 ++++++++++++++++++++++++++
 ui2/src/locales/en.json               |  11 ++-
 ui2/src/locales/fr.json               |  13 ++-
 ui2/src/main.js                       |  11 ++-
 ui2/src/services/rest/LoginService.js |   7 ++
 ui2/src/views/LoginView.vue           |  18 +++-
 6 files changed, 172 insertions(+), 15 deletions(-)
 create mode 100644 ui2/src/components/login/Register.vue

diff --git a/ui2/src/components/login/Register.vue b/ui2/src/components/login/Register.vue
new file mode 100644
index 000000000..a5cbb0495
--- /dev/null
+++ b/ui2/src/components/login/Register.vue
@@ -0,0 +1,127 @@
+<template>
+  <ValidationObserver ref="observer" v-slot="{ handleSubmit }">
+    <section>
+      <ValidationProvider rules="required" name="login" v-slot="{ errors, valid }" vid="login">
+        <b-field
+          class="input-field"
+          :type="{
+            'is-danger': errors && errors.length > 0,
+            'is-success': valid,
+          }"
+          :message="errors[0]"
+        >
+          <template slot="label">
+            {{ $t("login.login") }}
+            <span class="mandatory">
+              {{ $t("validation.obligatoire") }}
+            </span>
+          </template>
+          <b-input v-model="login" :placeholder="$t('login.login-placeholder')"> </b-input>
+        </b-field>
+      </ValidationProvider>
+
+      <ValidationProvider
+        rules="required"
+        name="password"
+        v-slot="{ errors, valid }"
+        vid="password"
+      >
+        <b-field
+          class="input-field"
+          :type="{
+            'is-danger': errors && errors.length > 0,
+            'is-success': valid,
+          }"
+          :message="errors[0]"
+        >
+          <template slot="label">
+            {{ $t("login.pwd") }}
+            <span class="mandatory">
+              {{ $t("validation.obligatoire") }}
+            </span>
+          </template>
+          <b-input
+            type="password"
+            v-model="password"
+            :placeholder="$t('login.pwd-placeholder')"
+            :password-reveal="true"
+          >
+          </b-input>
+        </b-field>
+      </ValidationProvider>
+
+      <ValidationProvider
+        rules="required|confirmed:password"
+        name="confirm_password"
+        v-slot="{ errors, valid }"
+        vid="confirm_password"
+      >
+        <b-field
+          class="input-field"
+          :type="{
+            'is-danger': errors && errors.length > 0,
+            'is-success': valid,
+          }"
+          :message="errors[0]"
+        >
+          <template slot="label">
+            {{ $t("login.confirm-pwd") }}
+            <span class="mandatory">
+              {{ $t("validation.obligatoire") }}
+            </span>
+          </template>
+          <b-input
+            type="password"
+            v-model="confirmedPwd"
+            :placeholder="$t('login.pwd-placeholder')"
+            :password-reveal="true"
+            @keyup.native.enter="handleSubmit(register)"
+          >
+          </b-input>
+        </b-field>
+      </ValidationProvider>
+    </section>
+
+    <div class="buttons">
+      <b-button type="is-primary" @click="handleSubmit(register)" icon-left="user-plus">
+        {{ $t("login.register") }}
+      </b-button>
+    </div>
+  </ValidationObserver>
+</template>
+
+<script>
+import { Component, Vue } from "vue-property-decorator";
+import { ValidationObserver, ValidationProvider } from "vee-validate";
+import { LoginService } from "@/services/rest/LoginService";
+import { AlertService } from "@/services/AlertService";
+
+@Component({
+  components: { ValidationObserver, ValidationProvider },
+})
+export default class Register extends Vue {
+  loginService = LoginService.INSTANCE;
+  alertService = AlertService.INSTANCE;
+
+  login = "";
+  password = "";
+  confirmedPwd = "";
+
+  async register() {
+    try {
+      await this.loginService.register(this.login, this.password);
+      this.alertService.toastSuccess(this.$t("alert.registered-user"));
+      this.resetVariables();
+      this.$emit("userRegistered");
+    } catch (error) {
+      this.alertService.toastServerError(error);
+    }
+  }
+
+  resetVariables() {
+    this.login = "";
+    this.password = "";
+    this.confirmedPwd = "";
+  }
+}
+</script>
diff --git a/ui2/src/locales/en.json b/ui2/src/locales/en.json
index a13556411..0456acf0e 100644
--- a/ui2/src/locales/en.json
+++ b/ui2/src/locales/en.json
@@ -9,19 +9,21 @@
   },
   "login": {
     "signin": "Sign in",
-    "signup": "Sign up",
     "login": "Login",
     "login-placeholder": "Ex: michel",
     "pwd": "Password",
     "pwd-placeholder": "Ex: xxxx",
-    "pwd-forgotten": "Forgotten password ? "
+    "pwd-forgotten": "Forgotten password ? ",
+    "register": "Register",
+    "confirm-pwd": "Confirmer le mot de passe"
   },
   "validation": {
     "obligatoire": "Mandatory",
     "facultatif": "Optional",
     "invalid-required": "Please fill the field",
     "invalid-application-name": "The name must only includes lowercase letters.",
-    "invalid-application-name-length": "The name's length should be between 4 and 20 characters."
+    "invalid-application-name-length": "The name's length should be between 4 and 20 characters.",
+    "invalid-confirmed": "Fields don't match."
   },
   "alert": {
     "cancel": "Cancel",
@@ -34,7 +36,8 @@
     "delete": "Delete",
     "reference-csv-upload-error": "An error occured while uploading the csv file",
     "reference-updated": "Reference updated",
-    "data-updated": "Data type updated"
+    "data-updated": "Data type updated",
+    "registered-user": "User registered"
   },
   "message": {
     "app-config-error": "Error in yaml file",
diff --git a/ui2/src/locales/fr.json b/ui2/src/locales/fr.json
index 4d090ad0b..2b0e642e8 100644
--- a/ui2/src/locales/fr.json
+++ b/ui2/src/locales/fr.json
@@ -9,19 +9,21 @@
   },
   "login": {
     "signin": "Se connecter",
-    "signup": "Créer un compte",
     "login": "Identifiant",
     "login-placeholder": "Ex: michel",
     "pwd": "Mot de passe",
     "pwd-placeholder": "Ex: xxxx",
-    "pwd-forgotten": "Mot de passe oublié ?"
+    "pwd-forgotten": "Mot de passe oublié ?",
+    "register": "Créer un compte",
+    "confirm-pwd": "Confirmer le mot de passe"
   },
   "validation": {
     "obligatoire": "Obligatoire",
     "facultatif": "Facultatif",
     "invalid-required": "Merci de remplir le champ",
-    "invalid-application-name": "Le nom ne doit comporter que des lettresminuscules .",
-    "invalid-application-name-length": "Le nom doit être compris en 4 et 20 caractères."
+    "invalid-application-name": "Le nom ne doit comporter que des lettres minuscules .",
+    "invalid-application-name-length": "Le nom doit être compris en 4 et 20 caractères.",
+    "invalid-confirmed": "Les deux champs ne correspondent pas."
   },
   "alert": {
     "cancel": "Annuler",
@@ -34,7 +36,8 @@
     "delete": "Supprimer",
     "reference-csv-upload-error": "Une erreur s'est produite au téléversement du fichier csv",
     "reference-updated": "Référentiel mis à jour",
-    "data-updated": "Type de donnée mis à jour"
+    "data-updated": "Type de donnée mis à jour",
+    "registered-user": "Compte utilisateur créé"
   },
   "message": {
     "app-config-error": "Erreur dans le fichier yaml",
diff --git a/ui2/src/main.js b/ui2/src/main.js
index b3cf151a1..458397510 100644
--- a/ui2/src/main.js
+++ b/ui2/src/main.js
@@ -29,6 +29,7 @@ import {
   faCaretRight,
   faArrowLeft,
   faSignInAlt,
+  faUserPlus,
 } from "@fortawesome/free-solid-svg-icons";
 import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
 library.add(
@@ -55,7 +56,8 @@ library.add(
   faVial,
   faCaretRight,
   faArrowLeft,
-  faSignInAlt
+  faSignInAlt,
+  faUserPlus
 );
 Vue.component("vue-fontawesome", FontAwesomeIcon);
 
@@ -79,7 +81,7 @@ export const i18n = new VueI18n({
 
 // Validation
 import "vee-validate";
-import { required } from "vee-validate/dist/rules";
+import { confirmed, required } from "vee-validate/dist/rules";
 import { extend } from "vee-validate";
 // Ici on surcharge les messages d'erreur de vee-validate.
 // Pour plus de règles :  https://logaretm.github.io/vee-validate/guide/rules.html
@@ -89,6 +91,11 @@ extend("required", {
   message: i18n.t("validation.invalid-required"),
 });
 
+extend("confirmed", {
+  ...confirmed,
+  message: i18n.t("validation.invalid-confirmed").toString(),
+});
+
 extend("validApplicationName", {
   message: i18n.t("validation.invalid-application-name"),
   validate: (value) => {
diff --git a/ui2/src/services/rest/LoginService.js b/ui2/src/services/rest/LoginService.js
index 6ab7d2575..045efddf7 100644
--- a/ui2/src/services/rest/LoginService.js
+++ b/ui2/src/services/rest/LoginService.js
@@ -30,6 +30,13 @@ export class LoginService extends Fetcher {
     return Promise.resolve(response);
   }
 
+  async register(login, pwd) {
+    return this.post("users", {
+      login: login,
+      password: pwd,
+    });
+  }
+
   async logout() {
     await this.delete("logout");
     this.notifyCrendentialsLost();
diff --git a/ui2/src/views/LoginView.vue b/ui2/src/views/LoginView.vue
index 9530262eb..d1cadcf51 100644
--- a/ui2/src/views/LoginView.vue
+++ b/ui2/src/views/LoginView.vue
@@ -2,24 +2,34 @@
   <PageView class="LoginView" :hasMenu="false">
     <h1 class="title main-title">{{ $t("titles.login-page") }}</h1>
     <div class="card LoginView-card">
-      <b-tabs type="is-boxed">
-        <b-tab-item :label="$t('login.signin')">
+      <b-tabs v-model="selectedTab" type="is-boxed" :animated="false">
+        <b-tab-item :label="$t('login.signin')" icon="sign-in-alt">
           <SignIn />
         </b-tab-item>
+        <b-tab-item :label="$t('login.register')" icon="user-plus">
+          <Register @userRegistered="changeTabToSignIn" />
+        </b-tab-item>
       </b-tabs>
     </div>
   </PageView>
 </template>
 
 <script>
+import Register from "@/components/login/Register.vue";
 import SignIn from "@/components/login/Signin.vue";
 import { Component, Vue } from "vue-property-decorator";
 import PageView from "./common/PageView.vue";
 
 @Component({
-  components: { PageView, SignIn },
+  components: { PageView, SignIn, Register },
 })
-export default class LoginView extends Vue {}
+export default class LoginView extends Vue {
+  selectedTab = 0;
+
+  changeTabToSignIn() {
+    this.selectedTab = 0;
+  }
+}
 </script>
 
 <style lang="scss">
-- 
GitLab


From 6445a15c0c7139f1f2c7881b7ac9e137a9042f5f Mon Sep 17 00:00:00 2001
From: Aurore Lecointe <lecointe@codelutin.com>
Date: Mon, 28 Jun 2021 17:54:01 +0200
Subject: [PATCH 07/26] Ajoute le nom de l'utilisateur dans le menu

---
 ui2/src/main.js                   |  4 +++-
 ui2/src/style/_common.scss        |  5 +++++
 ui2/src/views/common/MenuView.vue | 36 +++++++++++++++++++++++++------
 3 files changed, 37 insertions(+), 8 deletions(-)

diff --git a/ui2/src/main.js b/ui2/src/main.js
index 458397510..14b986862 100644
--- a/ui2/src/main.js
+++ b/ui2/src/main.js
@@ -30,6 +30,7 @@ import {
   faArrowLeft,
   faSignInAlt,
   faUserPlus,
+  faUserAstronaut,
 } from "@fortawesome/free-solid-svg-icons";
 import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
 library.add(
@@ -57,7 +58,8 @@ library.add(
   faCaretRight,
   faArrowLeft,
   faSignInAlt,
-  faUserPlus
+  faUserPlus,
+  faUserAstronaut
 );
 Vue.component("vue-fontawesome", FontAwesomeIcon);
 
diff --git a/ui2/src/style/_common.scss b/ui2/src/style/_common.scss
index 8d7d94115..8bd9cb0be 100644
--- a/ui2/src/style/_common.scss
+++ b/ui2/src/style/_common.scss
@@ -75,3 +75,8 @@ a {
   background-color: $dark;
   border-color: $dark;
 }
+
+a.dropdown-item {
+  display: flex;
+  align-items: center;
+}
diff --git a/ui2/src/views/common/MenuView.vue b/ui2/src/views/common/MenuView.vue
index a3879bc35..b5d862810 100644
--- a/ui2/src/views/common/MenuView.vue
+++ b/ui2/src/views/common/MenuView.vue
@@ -13,13 +13,6 @@
       </template>
 
       <template #end>
-        <b-navbar-item tag="div">
-          <div class="buttons">
-            <b-button type="is-info" @click="logout" icon-left="sign-out-alt">{{
-              $t("menu.logout")
-            }}</b-button>
-          </div>
-        </b-navbar-item>
         <b-navbar-item tag="div">
           <b-field>
             <b-select
@@ -33,8 +26,26 @@
             </b-select>
           </b-field>
         </b-navbar-item>
+
+        <b-navbar-item tag="div" class="MenuView-user">
+          <b-dropdown position="is-bottom-left" append-to-body aria-role="menu">
+            <template #trigger>
+              <a class="navbar-item" role="button">
+                <b-icon icon="user-astronaut" class="mr-1" />
+                <span>{{ currentUser.login }}</span>
+                <b-icon icon="caret-down" class="ml-2" />
+              </a>
+            </template>
+
+            <b-dropdown-item @click="logout()" aria-role="menuitem">
+              <b-icon icon="sign-out-alt" />
+              {{ $t("menu.logout") }}
+            </b-dropdown-item>
+          </b-dropdown>
+        </b-navbar-item>
       </template>
     </b-navbar>
+
     <FontAwesomeIcon
       @click="open = !open"
       :icon="open ? 'caret-up' : 'caret-down'"
@@ -62,9 +73,11 @@ export default class MenuView extends Vue {
   locales = Locales;
   chosenLocale = "";
   open = false;
+  currentUser = null;
 
   created() {
     this.chosenLocale = this.userPreferencesService.getUserPrefLocale();
+    this.currentUser = this.loginService.getAuthenticatedUser();
   }
 
   logout() {
@@ -127,6 +140,15 @@ export default class MenuView extends Vue {
       margin: 0;
     }
   }
+
+  .MenuView-user.navbar-item {
+    .navbar-item {
+      color: white;
+      &:hover {
+        color: $primary;
+      }
+    }
+  }
 }
 
 .menu-view-container {
-- 
GitLab


From e986b28f0f3c235a85d55d1ea40c120df733bdc2 Mon Sep 17 00:00:00 2001
From: Aurore Lecointe <lecointe@codelutin.com>
Date: Mon, 28 Jun 2021 18:24:29 +0200
Subject: [PATCH 08/26] :construction: Ajoute 1page pour les autorisations

---
 .../datatype/DataTypeDetailsPanel.vue         | 44 +++++++++++++++++++
 ui2/src/locales/en.json                       |  3 +-
 ui2/src/locales/fr.json                       |  3 +-
 ui2/src/main.js                               |  4 +-
 ui2/src/router/index.js                       |  6 +++
 .../DataTypeAuthorizationsView.vue            | 20 +++++++++
 .../datatype/DataTypesManagementView.vue      | 16 +++++--
 7 files changed, 90 insertions(+), 6 deletions(-)
 create mode 100644 ui2/src/components/datatype/DataTypeDetailsPanel.vue
 create mode 100644 ui2/src/views/authorizations/DataTypeAuthorizationsView.vue

diff --git a/ui2/src/components/datatype/DataTypeDetailsPanel.vue b/ui2/src/components/datatype/DataTypeDetailsPanel.vue
new file mode 100644
index 000000000..57849b532
--- /dev/null
+++ b/ui2/src/components/datatype/DataTypeDetailsPanel.vue
@@ -0,0 +1,44 @@
+<template>
+  <SidePanel
+    :open="open"
+    :leftAlign="leftAlign"
+    :title="dataType && dataType.label"
+    :closeCb="closeCb"
+  >
+    <div class="Panel-buttons">
+      <b-button type="is-primary" icon-left="key" @click="consultAuthorization">{{
+        $t("dataTypesManagement.consult-authorization")
+      }}</b-button>
+    </div>
+  </SidePanel>
+</template>
+
+<script>
+import { Component, Prop, Vue } from "vue-property-decorator";
+import SidePanel from "../common/SidePanel.vue";
+
+@Component({
+  components: { SidePanel },
+})
+export default class DataTypeDetailsPanel extends Vue {
+  @Prop({ default: false }) leftAlign;
+  @Prop({ default: false }) open;
+  @Prop() dataType;
+  @Prop() closeCb;
+
+  consultAuthorization() {
+    this.$router.push(`/applications/acbb/dataTypes/${this.dataType.id}/authorizations`);
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.Panel-buttons {
+  display: flex;
+  flex-direction: column;
+
+  .button {
+    margin-bottom: 0.5rem;
+  }
+}
+</style>
diff --git a/ui2/src/locales/en.json b/ui2/src/locales/en.json
index 0456acf0e..db089948f 100644
--- a/ui2/src/locales/en.json
+++ b/ui2/src/locales/en.json
@@ -106,6 +106,7 @@
     "data": "Data"
   },
   "dataTypesManagement": {
-    "data-types": "Data types"
+    "data-types": "Data types",
+    "consult-authorization": "Consult authorizations"
   }
 }
diff --git a/ui2/src/locales/fr.json b/ui2/src/locales/fr.json
index 2b0e642e8..1f2f55c10 100644
--- a/ui2/src/locales/fr.json
+++ b/ui2/src/locales/fr.json
@@ -106,6 +106,7 @@
     "data": "Données"
   },
   "dataTypesManagement": {
-    "data-types": "Type de données"
+    "data-types": "Type de données",
+    "consult-authorization": "Consulter les autorisations"
   }
 }
diff --git a/ui2/src/main.js b/ui2/src/main.js
index 14b986862..f07d7f716 100644
--- a/ui2/src/main.js
+++ b/ui2/src/main.js
@@ -31,6 +31,7 @@ import {
   faSignInAlt,
   faUserPlus,
   faUserAstronaut,
+  faKey,
 } from "@fortawesome/free-solid-svg-icons";
 import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
 library.add(
@@ -59,7 +60,8 @@ library.add(
   faArrowLeft,
   faSignInAlt,
   faUserPlus,
-  faUserAstronaut
+  faUserAstronaut,
+  faKey
 );
 Vue.component("vue-fontawesome", FontAwesomeIcon);
 
diff --git a/ui2/src/router/index.js b/ui2/src/router/index.js
index 14cca1145..76447268d 100644
--- a/ui2/src/router/index.js
+++ b/ui2/src/router/index.js
@@ -7,6 +7,7 @@ import ReferencesManagementView from "@/views/references/ReferencesManagementVie
 import ReferenceTable from "@/views/references/ReferenceTableView.vue";
 import DataTypeTableView from "@/views/datatype/DataTypeTableView.vue";
 import DataTypesManagementView from "@/views/datatype/DataTypesManagementView.vue";
+import DataTypeAuthorizationsView from "@/views/authorizations/DataTypeAuthorizationsView.vue";
 
 Vue.use(VueRouter);
 
@@ -51,6 +52,11 @@ const routes = [
     component: DataTypeTableView,
     props: true,
   },
+  {
+    path: "/applications/acbb/dataTypes/:dataTypeId/authorizations",
+    component: DataTypeAuthorizationsView,
+    props: true,
+  },
 ];
 
 const router = new VueRouter({
diff --git a/ui2/src/views/authorizations/DataTypeAuthorizationsView.vue b/ui2/src/views/authorizations/DataTypeAuthorizationsView.vue
new file mode 100644
index 000000000..0662b2917
--- /dev/null
+++ b/ui2/src/views/authorizations/DataTypeAuthorizationsView.vue
@@ -0,0 +1,20 @@
+<template>
+  <PageView class="with-submenu">
+    <h1 class="title main-title">
+      {{ dataTypeId }}
+    </h1>
+  </PageView>
+</template>
+
+<script>
+import { Component, Prop, Vue } from "vue-property-decorator";
+import PageView from "../common/PageView.vue";
+
+@Component({
+  components: { PageView },
+})
+export default class DataTypeAuthorizationsView extends Vue {
+  @Prop() dataTypeId;
+  @Prop() applicationName;
+}
+</script>
diff --git a/ui2/src/views/datatype/DataTypesManagementView.vue b/ui2/src/views/datatype/DataTypesManagementView.vue
index 3001b559b..831082dc8 100644
--- a/ui2/src/views/datatype/DataTypesManagementView.vue
+++ b/ui2/src/views/datatype/DataTypesManagementView.vue
@@ -14,6 +14,12 @@
         :onUploadCb="(label, file) => uploadDataTypeCsv(label, file)"
         :buttons="buttons"
       />
+      <DataTypeDetailsPanel
+        :leftAlign="false"
+        :open="openPanel"
+        :dataType="chosenDataType"
+        :closeCb="(newVal) => (openPanel = newVal)"
+      />
     </div>
     <div v-if="errorsMessages.length">
       <div v-for="msg in errorsMessages" v-bind:key="msg">
@@ -43,9 +49,10 @@ import { AlertService } from "@/services/AlertService";
 import { DataService } from "@/services/rest/DataService";
 import { HttpStatusCodes } from "@/utils/HttpUtils";
 import { ErrorsService } from "@/services/ErrorsService";
+import DataTypeDetailsPanel from "@/components/datatype/DataTypeDetailsPanel.vue";
 
 @Component({
-  components: { CollapsibleTree, PageView, SubMenu },
+  components: { CollapsibleTree, PageView, SubMenu, DataTypeDetailsPanel },
 })
 export default class DataTypesManagementView extends Vue {
   @Prop() applicationName;
@@ -70,6 +77,8 @@ export default class DataTypesManagementView extends Vue {
   ];
   dataTypes = [];
   errorsMessages = [];
+  openPanel = false;
+  chosenDataType = null;
 
   created() {
     this.subMenuPaths = [
@@ -104,8 +113,9 @@ export default class DataTypesManagementView extends Vue {
 
   openDataTypeCb(event, label) {
     event.stopPropagation();
-
-    console.log("OPEN", label);
+    this.openPanel =
+      this.chosenDataType && this.chosenDataType.label === label ? !this.openPanel : true;
+    this.chosenDataType = this.dataTypes.find((dt) => dt.label === label);
   }
 
   async uploadDataTypeCsv(label, file) {
-- 
GitLab


From 2fcbc8a1150085def5f36a3f8461b76b61d17dad Mon Sep 17 00:00:00 2001
From: Aurore Lecointe <lecointe@codelutin.com>
Date: Tue, 29 Jun 2021 16:48:37 +0200
Subject: [PATCH 09/26] =?UTF-8?q?Corrige=20le=20nom=20de=20l'application?=
 =?UTF-8?q?=20qui=20=C3=A9tait=20rest=C3=A9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../datatype/DataTypeDetailsPanel.vue           |  5 ++++-
 ui2/src/router/index.js                         |  2 +-
 ui2/src/services/rest/AuthorizationService.js   | 13 +++++++++++++
 .../DataTypeAuthorizationsView.vue              | 17 +++++++++++++++++
 .../views/datatype/DataTypesManagementView.vue  |  2 ++
 5 files changed, 37 insertions(+), 2 deletions(-)
 create mode 100644 ui2/src/services/rest/AuthorizationService.js

diff --git a/ui2/src/components/datatype/DataTypeDetailsPanel.vue b/ui2/src/components/datatype/DataTypeDetailsPanel.vue
index 57849b532..bad14a483 100644
--- a/ui2/src/components/datatype/DataTypeDetailsPanel.vue
+++ b/ui2/src/components/datatype/DataTypeDetailsPanel.vue
@@ -25,9 +25,12 @@ export default class DataTypeDetailsPanel extends Vue {
   @Prop({ default: false }) open;
   @Prop() dataType;
   @Prop() closeCb;
+  @Prop() applicationName;
 
   consultAuthorization() {
-    this.$router.push(`/applications/acbb/dataTypes/${this.dataType.id}/authorizations`);
+    this.$router.push(
+      `/applications/${this.applicationName}/dataTypes/${this.dataType.id}/authorizations`
+    );
   }
 }
 </script>
diff --git a/ui2/src/router/index.js b/ui2/src/router/index.js
index 76447268d..f3956435e 100644
--- a/ui2/src/router/index.js
+++ b/ui2/src/router/index.js
@@ -53,7 +53,7 @@ const routes = [
     props: true,
   },
   {
-    path: "/applications/acbb/dataTypes/:dataTypeId/authorizations",
+    path: "/applications/:applicationName/dataTypes/:dataTypeId/authorizations",
     component: DataTypeAuthorizationsView,
     props: true,
   },
diff --git a/ui2/src/services/rest/AuthorizationService.js b/ui2/src/services/rest/AuthorizationService.js
new file mode 100644
index 000000000..47c63be4b
--- /dev/null
+++ b/ui2/src/services/rest/AuthorizationService.js
@@ -0,0 +1,13 @@
+import { Fetcher } from "../Fetcher";
+
+export class AuthorizationService extends Fetcher {
+  static INSTANCE = new AuthorizationService();
+
+  constructor() {
+    super();
+  }
+
+  async getDataAuthorizations(applicationName, dataTypeId) {
+    return this.get(`/applications/${applicationName}/dataType/${dataTypeId}/authorization`);
+  }
+}
diff --git a/ui2/src/views/authorizations/DataTypeAuthorizationsView.vue b/ui2/src/views/authorizations/DataTypeAuthorizationsView.vue
index 0662b2917..0680b2d65 100644
--- a/ui2/src/views/authorizations/DataTypeAuthorizationsView.vue
+++ b/ui2/src/views/authorizations/DataTypeAuthorizationsView.vue
@@ -7,6 +7,7 @@
 </template>
 
 <script>
+import { AuthorizationService } from "@/services/rest/AuthorizationService";
 import { Component, Prop, Vue } from "vue-property-decorator";
 import PageView from "../common/PageView.vue";
 
@@ -16,5 +17,21 @@ import PageView from "../common/PageView.vue";
 export default class DataTypeAuthorizationsView extends Vue {
   @Prop() dataTypeId;
   @Prop() applicationName;
+
+  authorizationService = AuthorizationService.INSTANCE;
+
+  authorizations = [];
+
+  created() {
+    this.init();
+  }
+
+  async init() {
+    this.authorizations = await this.authorizationService.getDataAuthorizations(
+      this.applicationName,
+      this.dataTypeId
+    );
+    console.log(this.authorizations);
+  }
 }
 </script>
diff --git a/ui2/src/views/datatype/DataTypesManagementView.vue b/ui2/src/views/datatype/DataTypesManagementView.vue
index 831082dc8..d3fdcfd42 100644
--- a/ui2/src/views/datatype/DataTypesManagementView.vue
+++ b/ui2/src/views/datatype/DataTypesManagementView.vue
@@ -19,6 +19,7 @@
         :open="openPanel"
         :dataType="chosenDataType"
         :closeCb="(newVal) => (openPanel = newVal)"
+        :applicationName="applicationName"
       />
     </div>
     <div v-if="errorsMessages.length">
@@ -116,6 +117,7 @@ export default class DataTypesManagementView extends Vue {
     this.openPanel =
       this.chosenDataType && this.chosenDataType.label === label ? !this.openPanel : true;
     this.chosenDataType = this.dataTypes.find((dt) => dt.label === label);
+    console.log(this.chosenDataType);
   }
 
   async uploadDataTypeCsv(label, file) {
-- 
GitLab


From cf74b244b9271f732ecb34288819c9045cbb6bf1 Mon Sep 17 00:00:00 2001
From: Brendan Le Ny <bleny@codelutin.com>
Date: Wed, 30 Jun 2021 12:13:58 +0200
Subject: [PATCH 10/26] =?UTF-8?q?Ajoute=20une=20API=20permettant=20d'avoir?=
 =?UTF-8?q?=20les=20informations=20n=C3=A9cessaires=20=C3=A0=20la=20cr?=
 =?UTF-8?q?=C3=A9ation=20d'une=20autorisation?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../oresing/rest/AuthorizationResources.java  |  6 ++
 .../oresing/rest/AuthorizationService.java    | 72 +++++++++++++++++++
 .../inra/oresing/rest/GetGrantableResult.java | 40 +++++++++++
 .../rest/HierarchicalReferenceAsTree.java     | 18 +++++
 .../fr/inra/oresing/rest/OreSiService.java    | 37 ++++++++++
 .../rest/AuthorizationResourcesTest.java      |  8 +++
 6 files changed, 181 insertions(+)
 create mode 100644 src/main/java/fr/inra/oresing/rest/GetGrantableResult.java
 create mode 100644 src/main/java/fr/inra/oresing/rest/HierarchicalReferenceAsTree.java

diff --git a/src/main/java/fr/inra/oresing/rest/AuthorizationResources.java b/src/main/java/fr/inra/oresing/rest/AuthorizationResources.java
index 2ed18d77f..61aa69081 100644
--- a/src/main/java/fr/inra/oresing/rest/AuthorizationResources.java
+++ b/src/main/java/fr/inra/oresing/rest/AuthorizationResources.java
@@ -44,6 +44,12 @@ public class AuthorizationResources {
         return ResponseEntity.ok(getAuthorizationResults);
     }
 
+    @GetMapping(value = "/applications/{nameOrId}/dataType/{dataType}/grantable", produces = MediaType.APPLICATION_JSON_VALUE)
+    public ResponseEntity<GetGrantableResult> getGrantable(@PathVariable("nameOrId") String applicationNameOrId, @PathVariable("dataType") String dataType) {
+        GetGrantableResult getGrantableResult = authorizationService.getGrantable(applicationNameOrId, dataType);
+        return ResponseEntity.ok(getGrantableResult);
+    }
+
     @DeleteMapping(value = "/applications/{nameOrId}/dataType/{dataType}/authorization/{authorizationId}", produces = MediaType.APPLICATION_JSON_VALUE)
     public ResponseEntity<?> revokeAuthorization(@PathVariable("nameOrId") String applicationNameOrId, @PathVariable("authorizationId") UUID authorizationId) {
         authorizationService.revoke(new AuthorizationRequest(applicationNameOrId, authorizationId));
diff --git a/src/main/java/fr/inra/oresing/rest/AuthorizationService.java b/src/main/java/fr/inra/oresing/rest/AuthorizationService.java
index 8cf658a56..3f32206a8 100644
--- a/src/main/java/fr/inra/oresing/rest/AuthorizationService.java
+++ b/src/main/java/fr/inra/oresing/rest/AuthorizationService.java
@@ -1,16 +1,24 @@
 package fr.inra.oresing.rest;
 
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSortedSet;
 import com.google.common.collect.Range;
+import fr.inra.oresing.checker.CheckerFactory;
+import fr.inra.oresing.checker.ReferenceLineChecker;
 import fr.inra.oresing.model.Application;
 import fr.inra.oresing.model.Configuration;
 import fr.inra.oresing.model.OreSiAuthorization;
+import fr.inra.oresing.model.OreSiUser;
+import fr.inra.oresing.model.ReferenceValue;
+import fr.inra.oresing.model.VariableComponentKey;
 import fr.inra.oresing.persistence.AuthenticationService;
 import fr.inra.oresing.persistence.AuthorizationRepository;
 import fr.inra.oresing.persistence.OreSiRepository;
 import fr.inra.oresing.persistence.SqlPolicy;
 import fr.inra.oresing.persistence.SqlSchema;
 import fr.inra.oresing.persistence.SqlService;
+import fr.inra.oresing.persistence.UserRepository;
 import fr.inra.oresing.persistence.roles.OreSiRightOnApplicationRole;
 import fr.inra.oresing.persistence.roles.OreSiUserRole;
 import lombok.extern.slf4j.Slf4j;
@@ -21,7 +29,9 @@ import org.testcontainers.shaded.com.google.common.base.Preconditions;
 
 import java.time.LocalDate;
 import java.time.LocalDateTime;
+import java.util.Comparator;
 import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.Set;
 import java.util.UUID;
 import java.util.stream.Collectors;
@@ -40,6 +50,15 @@ public class AuthorizationService {
     @Autowired
     private OreSiRepository repository;
 
+    @Autowired
+    private UserRepository userRepository;
+
+    @Autowired
+    private OreSiService oreSiService;
+
+    @Autowired
+    private CheckerFactory checkerFactory;
+
     public UUID addAuthorization(CreateAuthorizationRequest authorization) {
         OreSiUserRole userRole = authenticationService.getUserRole(authorization.getUserId());
 
@@ -167,4 +186,57 @@ public class AuthorizationService {
             toDay
         );
     }
+
+    public GetGrantableResult getGrantable(String applicationNameOrId, String dataType) {
+        Application application = repository.application().findApplication(applicationNameOrId);
+        Configuration configuration = application.getConfiguration();
+        Preconditions.checkArgument(configuration.getDataTypes().containsKey(dataType));
+        ImmutableSortedSet<GetGrantableResult.User> users = getGrantableUsers();
+        ImmutableSortedSet<GetGrantableResult.DataGroup> dataGroups = getDataGroups(application, dataType);
+        ImmutableSortedSet<GetGrantableResult.AuthorizationScope> authorizationScopes = getAuthorizationScopes(application, dataType);
+        return new GetGrantableResult(users, dataGroups, authorizationScopes);
+    }
+
+    private ImmutableSortedSet<GetGrantableResult.DataGroup> getDataGroups(Application application, String dataType) {
+        ImmutableSortedSet<GetGrantableResult.DataGroup> dataGroups = application.getConfiguration().getDataTypes().get(dataType).getAuthorization().getDataGroups().entrySet().stream()
+                .map(dataGroupEntry -> new GetGrantableResult.DataGroup(dataGroupEntry.getKey(), dataGroupEntry.getValue().getLabel()))
+                .collect(ImmutableSortedSet.toImmutableSortedSet(Comparator.comparing(GetGrantableResult.DataGroup::getId)));
+        return dataGroups;
+    }
+
+    private ImmutableSortedSet<GetGrantableResult.User> getGrantableUsers() {
+        List<OreSiUser> allUsers = userRepository.findAll();
+        ImmutableSortedSet<GetGrantableResult.User> users = allUsers.stream()
+                .map(oreSiUserEntity -> new GetGrantableResult.User(oreSiUserEntity.getId(), oreSiUserEntity.getLogin()))
+                .collect(ImmutableSortedSet.toImmutableSortedSet(Comparator.comparing(GetGrantableResult.User::getId)));
+        return users;
+    }
+
+    private ImmutableSortedSet<GetGrantableResult.AuthorizationScope> getAuthorizationScopes(Application application, String dataType) {
+        ImmutableMap<VariableComponentKey, ReferenceLineChecker> referenceLineCheckers = checkerFactory.getReferenceLineCheckers(application, dataType);
+        Configuration.AuthorizationDescription authorizationDescription = application.getConfiguration().getDataTypes().get(dataType).getAuthorization();
+        ImmutableSortedSet<GetGrantableResult.AuthorizationScope> authorizationScopes = authorizationDescription.getAuthorizationScopes().entrySet().stream()
+                .map(authorizationScopeEntry -> {
+                    String variable = authorizationScopeEntry.getValue().getVariable();
+                    String component = authorizationScopeEntry.getValue().getComponent();
+                    VariableComponentKey variableComponentKey = new VariableComponentKey(variable, component);
+                    ReferenceLineChecker referenceLineChecker = referenceLineCheckers.get(variableComponentKey);
+                    String lowestLevelReference = referenceLineChecker.getRefType();
+                    HierarchicalReferenceAsTree hierarchicalReferenceAsTree = oreSiService.getHierarchicalReferenceAsTree(application, lowestLevelReference);
+                    ImmutableSortedSet<GetGrantableResult.AuthorizationScope.Option> rootOptions = hierarchicalReferenceAsTree.getRoots().stream()
+                            .map(rootReferenceValue -> toOption(hierarchicalReferenceAsTree, rootReferenceValue))
+                            .collect(ImmutableSortedSet.toImmutableSortedSet(Comparator.comparing(GetGrantableResult.AuthorizationScope.Option::getId)));
+                    String authorizationScopeId = authorizationScopeEntry.getKey();
+                    return new GetGrantableResult.AuthorizationScope(authorizationScopeId, authorizationScopeId, rootOptions);
+                })
+                .collect(ImmutableSortedSet.toImmutableSortedSet(Comparator.comparing(GetGrantableResult.AuthorizationScope::getId)));
+        return authorizationScopes;
+    }
+
+    private GetGrantableResult.AuthorizationScope.Option toOption(HierarchicalReferenceAsTree tree, ReferenceValue referenceValue) {
+        ImmutableSortedSet<GetGrantableResult.AuthorizationScope.Option> options = tree.getChildren(referenceValue).stream()
+                .map(child -> toOption(tree, child))
+                .collect(ImmutableSortedSet.toImmutableSortedSet(Comparator.comparing(GetGrantableResult.AuthorizationScope.Option::getId)));
+        return new GetGrantableResult.AuthorizationScope.Option(referenceValue.getHierarchicalKey(), referenceValue.getHierarchicalKey(), options);
+    }
 }
diff --git a/src/main/java/fr/inra/oresing/rest/GetGrantableResult.java b/src/main/java/fr/inra/oresing/rest/GetGrantableResult.java
new file mode 100644
index 000000000..70eae042d
--- /dev/null
+++ b/src/main/java/fr/inra/oresing/rest/GetGrantableResult.java
@@ -0,0 +1,40 @@
+package fr.inra.oresing.rest;
+
+import lombok.Value;
+
+import java.util.Set;
+import java.util.UUID;
+
+@Value
+public class GetGrantableResult {
+
+    Set<User> users;
+    Set<DataGroup> dataGroups;
+    Set<AuthorizationScope> authorizationScopes;
+
+    @Value
+    public static class User {
+        UUID id;
+        String label;
+    }
+
+    @Value
+    public static class DataGroup {
+        String id;
+        String label;
+    }
+
+    @Value
+    public static class AuthorizationScope {
+        String id;
+        String label;
+        Set<Option> options;
+
+        @Value
+        public static class Option {
+            String id;
+            String label;
+            Set<Option> children;
+        }
+    }
+}
diff --git a/src/main/java/fr/inra/oresing/rest/HierarchicalReferenceAsTree.java b/src/main/java/fr/inra/oresing/rest/HierarchicalReferenceAsTree.java
new file mode 100644
index 000000000..14d88fdf3
--- /dev/null
+++ b/src/main/java/fr/inra/oresing/rest/HierarchicalReferenceAsTree.java
@@ -0,0 +1,18 @@
+package fr.inra.oresing.rest;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSetMultimap;
+import fr.inra.oresing.model.ReferenceValue;
+import lombok.Value;
+
+@Value
+public class HierarchicalReferenceAsTree {
+
+    ImmutableSetMultimap<ReferenceValue, ReferenceValue> tree;
+
+    ImmutableSet<ReferenceValue> roots;
+
+    public ImmutableSet<ReferenceValue> getChildren(ReferenceValue referenceValue) {
+        return getTree().get(referenceValue);
+    }
+}
diff --git a/src/main/java/fr/inra/oresing/rest/OreSiService.java b/src/main/java/fr/inra/oresing/rest/OreSiService.java
index 7f06aa49d..3f3b13693 100644
--- a/src/main/java/fr/inra/oresing/rest/OreSiService.java
+++ b/src/main/java/fr/inra/oresing/rest/OreSiService.java
@@ -4,15 +4,22 @@ import com.google.common.base.Charsets;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Predicate;
 import com.google.common.base.Splitter;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+import com.google.common.collect.HashMultimap;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableMultiset;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSetMultimap;
+import com.google.common.collect.ImmutableSortedSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Iterators;
 import com.google.common.collect.Maps;
 import com.google.common.collect.MoreCollectors;
+import com.google.common.collect.Ordering;
+import com.google.common.collect.SetMultimap;
+import com.google.common.collect.Sets;
 import com.google.common.primitives.Ints;
 import fr.inra.oresing.OreSiTechnicalException;
 import fr.inra.oresing.checker.CheckerFactory;
@@ -385,6 +392,36 @@ public class OreSiService {
         Splitter.on(LTREE_SEPARATOR).split(compositeKey).forEach(this::checkNaturalKeySyntax);
     }
 
+    HierarchicalReferenceAsTree getHierarchicalReferenceAsTree(Application application, String lowestLevelReference) {
+        ReferenceValueRepository referenceValueRepository = repo.getRepository(application).referenceValue();
+        Configuration.CompositeReferenceDescription compositeReferenceDescription = application.getConfiguration().getCompositeReferencesUsing(lowestLevelReference).orElseThrow();
+        BiMap<String, ReferenceValue> indexedByHierarchicalKeyReferenceValues = HashBiMap.create();
+        Map<ReferenceValue, String> parentHierarchicalKeys = new LinkedHashMap<>();
+        ImmutableList<String> referenceTypes = compositeReferenceDescription.getComponents().stream()
+                .map(Configuration.CompositeReferenceComponentDescription::getReference)
+                .collect(ImmutableList.toImmutableList());
+        ImmutableSortedSet<String> sortedReferenceTypes = ImmutableSortedSet.copyOf(Ordering.explicit(referenceTypes), referenceTypes);
+        ImmutableSortedSet<String> includedReferences = sortedReferenceTypes.headSet(lowestLevelReference, true);
+        compositeReferenceDescription.getComponents().stream()
+                .filter(compositeReferenceComponentDescription -> includedReferences.contains(compositeReferenceComponentDescription.getReference()))
+                .forEach(compositeReferenceComponentDescription -> {
+            String reference = compositeReferenceComponentDescription.getReference();
+            String parentKeyColumn = compositeReferenceComponentDescription.getParentKeyColumn();
+            referenceValueRepository.findAllByReferenceType(reference).forEach(referenceValue -> {
+                indexedByHierarchicalKeyReferenceValues.put(referenceValue.getHierarchicalKey(), referenceValue);
+                if (parentKeyColumn != null) {
+                    String parentHierarchicalKey = referenceValue.getRefValues().get(parentKeyColumn);
+                    parentHierarchicalKeys.put(referenceValue, parentHierarchicalKey);
+                }
+            });
+        });
+        Map<ReferenceValue, ReferenceValue> childToParents = Maps.transformValues(parentHierarchicalKeys, indexedByHierarchicalKeyReferenceValues::get);
+        SetMultimap<ReferenceValue, ReferenceValue> tree = HashMultimap.create();
+        childToParents.forEach((child, parent) -> tree.put(parent, child));
+        ImmutableSet<ReferenceValue> roots = Sets.difference(indexedByHierarchicalKeyReferenceValues.values(), parentHierarchicalKeys.keySet()).immutableCopy();
+        return new HierarchicalReferenceAsTree(ImmutableSetMultimap.copyOf(tree), roots);
+    }
+
     @Value
     private static class RowWithData {
         int lineNumber;
diff --git a/src/test/java/fr/inra/oresing/rest/AuthorizationResourcesTest.java b/src/test/java/fr/inra/oresing/rest/AuthorizationResourcesTest.java
index 8f32ccaa1..e26a48b0b 100644
--- a/src/test/java/fr/inra/oresing/rest/AuthorizationResourcesTest.java
+++ b/src/test/java/fr/inra/oresing/rest/AuthorizationResourcesTest.java
@@ -78,6 +78,14 @@ public class AuthorizationResourcesTest {
                     .andExpect(status().is4xxClientError());
         }
 
+        {
+            String response = mockMvc.perform(get("/api/v1/applications/acbb/dataType/biomasse_production_teneur/grantable")
+                    .cookie(authCookie)
+            ).andReturn().getResponse().getContentAsString();
+            Assert.assertTrue(response.contains("lusignan"));
+            Assert.assertTrue(response.contains("laqueuille.laqueuille__1"));
+        }
+
         {
             String json = "{\"userId\":\"" + readerUserId + "\",\"applicationNameOrId\":\"acbb\",\"dataType\":\"biomasse_production_teneur\",\"dataGroup\":\"all\",\"authorizedScopes\":{\"localization\":\"theix.theix__22\"},\"fromDay\":[2010,1,1],\"toDay\":[2010,6,1]}";
 
-- 
GitLab


From 02b5d1a337e47560ef4845b9608ee43157c1f27f Mon Sep 17 00:00:00 2001
From: Aurore Lecointe <lecointe@codelutin.com>
Date: Thu, 1 Jul 2021 10:49:12 +0200
Subject: [PATCH 11/26] =?UTF-8?q?Ajoute=20une=20page=20de=20cr=C3=A9ation?=
 =?UTF-8?q?=20d'autorisations?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 ui2/src/locales/en.json                       |  9 ++-
 ui2/src/locales/fr.json                       |  9 ++-
 ui2/src/router/index.js                       |  6 ++
 ui2/src/services/rest/AuthorizationService.js |  2 +-
 .../DataTypeAuthorizationInfoView.vue         | 75 +++++++++++++++++++
 .../DataTypeAuthorizationsView.vue            | 52 +++++++++++--
 .../datatype/DataTypesManagementView.vue      |  1 -
 7 files changed, 144 insertions(+), 10 deletions(-)
 create mode 100644 ui2/src/views/authorizations/DataTypeAuthorizationInfoView.vue

diff --git a/ui2/src/locales/en.json b/ui2/src/locales/en.json
index db089948f..697443424 100644
--- a/ui2/src/locales/en.json
+++ b/ui2/src/locales/en.json
@@ -5,7 +5,9 @@
     "references-page": "{applicationName} references",
     "references-data": "{refName} data",
     "application-creation": "Application creation",
-    "data-types-page": "{applicationName} data types"
+    "data-types-page": "{applicationName} data types",
+    "data-type-authorizations": "{dataType} authorizations",
+    "data-type-new-authorization": "New authorization for {dataType}"
   },
   "login": {
     "signin": "Sign in",
@@ -108,5 +110,10 @@
   "dataTypesManagement": {
     "data-types": "Data types",
     "consult-authorization": "Consult authorizations"
+  },
+  "dataTypeAuthorizations": {
+    "add-auhtorization": "Add an authorization",
+    "sub-menu-data-type-authorizations": "{dataType}_authorizations",
+    "sub-menu-new-authorization": "new authorization"
   }
 }
diff --git a/ui2/src/locales/fr.json b/ui2/src/locales/fr.json
index 1f2f55c10..a6153a24e 100644
--- a/ui2/src/locales/fr.json
+++ b/ui2/src/locales/fr.json
@@ -5,7 +5,9 @@
     "references-page": "Référentiels de {applicationName}",
     "references-data": "Données de {refName}",
     "application-creation": "Créer une application",
-    "data-types-page": "Type de données de {applicationName}"
+    "data-types-page": "Type de données de {applicationName}",
+    "data-type-authorizations": "Autorisations de {dataType}",
+    "data-type-new-authorization": "Nouvelle autorisation pour {dataType}"
   },
   "login": {
     "signin": "Se connecter",
@@ -108,5 +110,10 @@
   "dataTypesManagement": {
     "data-types": "Type de données",
     "consult-authorization": "Consulter les autorisations"
+  },
+  "dataTypeAuthorizations": {
+    "add-auhtorization": "Ajouter une autorisation",
+    "sub-menu-data-type-authorizations": "autorisations_{dataType}",
+    "sub-menu-new-authorization": "nouvelle autorisation"
   }
 }
diff --git a/ui2/src/router/index.js b/ui2/src/router/index.js
index f3956435e..b3ea63b1b 100644
--- a/ui2/src/router/index.js
+++ b/ui2/src/router/index.js
@@ -8,6 +8,7 @@ import ReferenceTable from "@/views/references/ReferenceTableView.vue";
 import DataTypeTableView from "@/views/datatype/DataTypeTableView.vue";
 import DataTypesManagementView from "@/views/datatype/DataTypesManagementView.vue";
 import DataTypeAuthorizationsView from "@/views/authorizations/DataTypeAuthorizationsView.vue";
+import DataTypeAuthorizationInfoView from "@/views/authorizations/DataTypeAuthorizationInfoView.vue";
 
 Vue.use(VueRouter);
 
@@ -57,6 +58,11 @@ const routes = [
     component: DataTypeAuthorizationsView,
     props: true,
   },
+  {
+    path: "/applications/:applicationName/dataTypes/:dataTypeId/authorizations/:authorizationId",
+    component: DataTypeAuthorizationInfoView,
+    props: true,
+  },
 ];
 
 const router = new VueRouter({
diff --git a/ui2/src/services/rest/AuthorizationService.js b/ui2/src/services/rest/AuthorizationService.js
index 47c63be4b..ddb67edb3 100644
--- a/ui2/src/services/rest/AuthorizationService.js
+++ b/ui2/src/services/rest/AuthorizationService.js
@@ -8,6 +8,6 @@ export class AuthorizationService extends Fetcher {
   }
 
   async getDataAuthorizations(applicationName, dataTypeId) {
-    return this.get(`/applications/${applicationName}/dataType/${dataTypeId}/authorization`);
+    return this.get(`applications/${applicationName}/dataType/${dataTypeId}/authorization`);
   }
 }
diff --git a/ui2/src/views/authorizations/DataTypeAuthorizationInfoView.vue b/ui2/src/views/authorizations/DataTypeAuthorizationInfoView.vue
new file mode 100644
index 000000000..35795fa70
--- /dev/null
+++ b/ui2/src/views/authorizations/DataTypeAuthorizationInfoView.vue
@@ -0,0 +1,75 @@
+<template>
+  <PageView class="with-submenu">
+    <SubMenu :root="application.title" :paths="subMenuPaths" />
+
+    <h1 class="title main-title">
+      <span v-if="authorizationId === 'new'">{{
+        $t("titles.data-type-new-authorization", { dataType: dataTypeId })
+      }}</span>
+    </h1>
+  </PageView>
+</template>
+
+<script>
+import SubMenu, { SubMenuPath } from "@/components/common/SubMenu.vue";
+import { AlertService } from "@/services/AlertService";
+import { ApplicationService } from "@/services/rest/ApplicationService";
+import { AuthorizationService } from "@/services/rest/AuthorizationService";
+import { Component, Prop, Vue } from "vue-property-decorator";
+import PageView from "../common/PageView.vue";
+
+@Component({
+  components: { PageView, SubMenu },
+})
+export default class DataTypeAuthorizationInfoView extends Vue {
+  @Prop() dataTypeId;
+  @Prop() applicationName;
+  @Prop() authorizationId;
+
+  authorizationService = AuthorizationService.INSTANCE;
+  alertService = AlertService.INSTANCE;
+  applicationService = ApplicationService.INSTANCE;
+
+  authorizations = [];
+  application = {};
+
+  created() {
+    this.init();
+    this.subMenuPaths = [
+      new SubMenuPath(
+        this.$t("dataTypesManagement.data-types").toLowerCase(),
+        () => this.$router.push(`/applications/${this.applicationName}/dataTypes`),
+        () => this.$router.push("/applications")
+      ),
+      new SubMenuPath(
+        this.$t(`dataTypeAuthorizations.sub-menu-data-type-authorizations`, {
+          dataType: this.dataTypeId,
+        }),
+        () => {
+          this.$router.push(
+            `/applications/${this.applicationName}/dataTypes/${this.dataTypeId}/authorizations`
+          );
+        },
+        () => this.$router.push(`/applications/${this.applicationName}/dataTypes`)
+      ),
+      new SubMenuPath(
+        this.$t(`dataTypeAuthorizations.sub-menu-new-authorization`),
+        () => {},
+        () => {
+          this.$router.push(
+            `/applications/${this.applicationName}/dataTypes/${this.dataTypeId}/authorizations`
+          );
+        }
+      ),
+    ];
+  }
+
+  async init() {
+    try {
+      this.application = await this.applicationService.getApplication(this.applicationName);
+    } catch (error) {
+      this.alertService.toastServerError(error);
+    }
+  }
+}
+</script>
diff --git a/ui2/src/views/authorizations/DataTypeAuthorizationsView.vue b/ui2/src/views/authorizations/DataTypeAuthorizationsView.vue
index 0680b2d65..c17419316 100644
--- a/ui2/src/views/authorizations/DataTypeAuthorizationsView.vue
+++ b/ui2/src/views/authorizations/DataTypeAuthorizationsView.vue
@@ -1,37 +1,77 @@
 <template>
   <PageView class="with-submenu">
+    <SubMenu :root="application.title" :paths="subMenuPaths" />
     <h1 class="title main-title">
-      {{ dataTypeId }}
+      {{ $t("titles.data-type-authorizations", { dataType: dataTypeId }) }}
     </h1>
+    <div class="buttons">
+      <b-button type="is-primary" @click="addAuthorization" icon-left="plus">
+        {{ $t("dataTypeAuthorizations.add-auhtorization") }}
+      </b-button>
+    </div>
   </PageView>
 </template>
 
 <script>
+import SubMenu, { SubMenuPath } from "@/components/common/SubMenu.vue";
+import { AlertService } from "@/services/AlertService";
+import { ApplicationService } from "@/services/rest/ApplicationService";
 import { AuthorizationService } from "@/services/rest/AuthorizationService";
 import { Component, Prop, Vue } from "vue-property-decorator";
 import PageView from "../common/PageView.vue";
 
 @Component({
-  components: { PageView },
+  components: { PageView, SubMenu },
 })
 export default class DataTypeAuthorizationsView extends Vue {
   @Prop() dataTypeId;
   @Prop() applicationName;
 
   authorizationService = AuthorizationService.INSTANCE;
+  alertService = AlertService.INSTANCE;
+  applicationService = ApplicationService.INSTANCE;
 
   authorizations = [];
+  application = {};
 
   created() {
     this.init();
+    this.subMenuPaths = [
+      new SubMenuPath(
+        this.$t("dataTypesManagement.data-types").toLowerCase(),
+        () => this.$router.push(`/applications/${this.applicationName}/dataTypes`),
+        () => this.$router.push("/applications")
+      ),
+      new SubMenuPath(
+        this.$t(`dataTypeAuthorizations.sub-menu-data-type-authorizations`, {
+          dataType: this.dataTypeId,
+        }),
+        () => {
+          this.$router.push(
+            `/applications/${this.applicationName}/dataTypes/${this.dataTypeId}/authorizations`
+          );
+        },
+        () => this.$router.push(`/applications/${this.applicationName}/dataTypes`)
+      ),
+    ];
   }
 
   async init() {
-    this.authorizations = await this.authorizationService.getDataAuthorizations(
-      this.applicationName,
-      this.dataTypeId
+    try {
+      this.application = await this.applicationService.getApplication(this.applicationName);
+      this.authorizations = await this.authorizationService.getDataAuthorizations(
+        this.applicationName,
+        this.dataTypeId
+      );
+    } catch (error) {
+      this.alertService.toastServerError(error);
+    }
+  }
+
+  addAuthorization() {
+    this.$router.push(
+      `/applications/${this.applicationName}/dataTypes/${this.dataTypeId}/authorizations/new`
     );
-    console.log(this.authorizations);
   }
 }
 </script>
diff --git a/ui2/src/views/datatype/DataTypesManagementView.vue b/ui2/src/views/datatype/DataTypesManagementView.vue
index d3fdcfd42..f7bbb2609 100644
--- a/ui2/src/views/datatype/DataTypesManagementView.vue
+++ b/ui2/src/views/datatype/DataTypesManagementView.vue
@@ -117,7 +117,6 @@ export default class DataTypesManagementView extends Vue {
     this.openPanel =
       this.chosenDataType && this.chosenDataType.label === label ? !this.openPanel : true;
     this.chosenDataType = this.dataTypes.find((dt) => dt.label === label);
-    console.log(this.chosenDataType);
   }
 
   async uploadDataTypeCsv(label, file) {
-- 
GitLab


From 9e6d312bae542479da389eb3e9d0a8051ade7411 Mon Sep 17 00:00:00 2001
From: Aurore Lecointe <lecointe@codelutin.com>
Date: Thu, 1 Jul 2021 10:57:56 +0200
Subject: [PATCH 12/26] Ajoute l'api indiquant les infos pour les droits

---
 ui2/src/services/rest/AuthorizationService.js               | 4 ++++
 .../views/authorizations/DataTypeAuthorizationInfoView.vue  | 6 ++++++
 2 files changed, 10 insertions(+)

diff --git a/ui2/src/services/rest/AuthorizationService.js b/ui2/src/services/rest/AuthorizationService.js
index ddb67edb3..7e8325be7 100644
--- a/ui2/src/services/rest/AuthorizationService.js
+++ b/ui2/src/services/rest/AuthorizationService.js
@@ -10,4 +10,8 @@ export class AuthorizationService extends Fetcher {
   async getDataAuthorizations(applicationName, dataTypeId) {
     return this.get(`applications/${applicationName}/dataType/${dataTypeId}/authorization`);
   }
+
+  async getAuthorizationGrantableInfos(applicationName, dataTypeId) {
+    return this.get(`applications/${applicationName}/dataType/${dataTypeId}/grantable`);
+  }
 }
diff --git a/ui2/src/views/authorizations/DataTypeAuthorizationInfoView.vue b/ui2/src/views/authorizations/DataTypeAuthorizationInfoView.vue
index 35795fa70..a05af341e 100644
--- a/ui2/src/views/authorizations/DataTypeAuthorizationInfoView.vue
+++ b/ui2/src/views/authorizations/DataTypeAuthorizationInfoView.vue
@@ -32,6 +32,7 @@ export default class DataTypeAuthorizationInfoView extends Vue {
 
   authorizations = [];
   application = {};
+  grantableInfos = {};
 
   created() {
     this.init();
@@ -67,6 +68,11 @@ export default class DataTypeAuthorizationInfoView extends Vue {
   async init() {
     try {
       this.application = await this.applicationService.getApplication(this.applicationName);
+      this.grantableInfos = await this.authorizationService.getAuthorizationGrantableInfos(
+        this.applicationName,
+        this.dataTypeId
+      );
+      console.log(this.grantableInfos);
     } catch (error) {
       this.alertService.toastServerError(error);
     }
-- 
GitLab


From 7f092389c8f4aa6f4dfa5dc4da99000c708bea53 Mon Sep 17 00:00:00 2001
From: Aurore Lecointe <lecointe@codelutin.com>
Date: Thu, 1 Jul 2021 15:25:18 +0200
Subject: [PATCH 13/26] Ajoute des checkboxes dans le CollapsibleTree

---
 ui2/src/components/common/CollapsibleTree.vue | 38 +++++----
 ui2/src/locales/en.json                       |  9 ++-
 ui2/src/locales/fr.json                       |  9 ++-
 ui2/src/main.js                               |  6 +-
 .../DataTypeAuthorizationInfoView.vue         | 78 ++++++++++++++++++-
 .../datatype/DataTypesManagementView.vue      |  2 +-
 .../references/ReferencesManagementView.vue   |  3 +-
 7 files changed, 118 insertions(+), 27 deletions(-)

diff --git a/ui2/src/components/common/CollapsibleTree.vue b/ui2/src/components/common/CollapsibleTree.vue
index c372b3648..1b9b3277c 100644
--- a/ui2/src/components/common/CollapsibleTree.vue
+++ b/ui2/src/components/common/CollapsibleTree.vue
@@ -1,24 +1,32 @@
 <template>
   <div>
     <div
-      :class="`CollapsibleTree-header ${children && children.length !== 0 ? 'clickable' : ''} ${
-        children && children.length !== 0 && displayChildren ? '' : 'mb-1'
-      }`"
+      :class="`CollapsibleTree-header ${
+        option.children && option.children.length !== 0 ? 'clickable' : ''
+      } ${option.children && option.children.length !== 0 && displayChildren ? '' : 'mb-1'}`"
       :style="`background-color:rgba(240, 245, 245, ${1 - level / 2})`"
       @click="displayChildren = !displayChildren"
     >
       <div class="CollapsibleTree-header-infos">
         <FontAwesomeIcon
-          v-if="children && children.length !== 0"
+          v-if="option.children && option.children.length !== 0"
           :icon="displayChildren ? 'caret-down' : 'caret-right'"
           class="clickable mr-3"
         />
+        <b-checkbox
+          v-if="withCheckBoxes"
+          :native-value="option.id"
+          :style="`transform:translate(${level * 50}px);`"
+        >
+          {{ option.label }}
+        </b-checkbox>
         <div
-          class="link"
+          v-else
+          :class="onClickLabelCb ? 'link' : ''"
           :style="`transform:translate(${level * 50}px);`"
-          @click="(event) => onClickLabelCb(event, label)"
+          @click="(event) => onClickLabelCb && onClickLabelCb(event, option.label)"
         >
-          {{ label }}
+          {{ option.label }}
         </div>
       </div>
       <div class="CollapsibleTree-buttons">
@@ -27,7 +35,7 @@
             v-model="refFile"
             class="file-label"
             accept=".csv"
-            @input="() => onUploadCb(label, refFile)"
+            @input="() => onUploadCb(option.label, refFile)"
           >
             <span class="file-name" v-if="refFile">
               {{ refFile.name }}
@@ -41,7 +49,7 @@
           <b-button
             :icon-left="button.iconName"
             size="is-small"
-            @click="button.clickCb(label)"
+            @click="button.clickCb(option.label)"
             class="ml-1"
             :type="button.type"
           >
@@ -52,14 +60,14 @@
     </div>
     <div v-if="displayChildren">
       <CollapsibleTree
-        v-for="child in children"
+        v-for="child in option.children"
         :key="child.id"
-        :label="child.label"
-        :children="child.children"
+        :option="child"
         :level="level + 1"
         :onClickLabelCb="onClickLabelCb"
         :onUploadCb="onUploadCb"
         :buttons="buttons"
+        :withCheckBoxes="withCheckBoxes"
       />
     </div>
   </div>
@@ -73,12 +81,12 @@ import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
   components: { FontAwesomeIcon },
 })
 export default class CollapsibleTree extends Vue {
-  @Prop() label;
-  @Prop() children;
-  @Prop() level;
+  @Prop() option;
+  @Prop({ default: 0 }) level;
   @Prop() onClickLabelCb;
   @Prop() onUploadCb;
   @Prop() buttons;
+  @Prop({ default: false }) withCheckBoxes;
 
   displayChildren = false;
   refFile = null;
diff --git a/ui2/src/locales/en.json b/ui2/src/locales/en.json
index 697443424..636ec4974 100644
--- a/ui2/src/locales/en.json
+++ b/ui2/src/locales/en.json
@@ -113,7 +113,12 @@
   },
   "dataTypeAuthorizations": {
     "add-auhtorization": "Add an authorization",
-    "sub-menu-data-type-authorizations": "{dataType}_authorizations",
-    "sub-menu-new-authorization": "new authorization"
+    "sub-menu-data-type-authorizations": "{dataType} authorizations",
+    "sub-menu-new-authorization": "new authorization",
+    "users": "Users",
+    "users-placeholder": "Chose users to authorize",
+    "data-groups": "Data groups",
+    "data-groups-placeholder": "Chose data groups to authorize",
+    "authorization-scopes": "Authorization scopes"
   }
 }
diff --git a/ui2/src/locales/fr.json b/ui2/src/locales/fr.json
index a6153a24e..6ab0541de 100644
--- a/ui2/src/locales/fr.json
+++ b/ui2/src/locales/fr.json
@@ -113,7 +113,12 @@
   },
   "dataTypeAuthorizations": {
     "add-auhtorization": "Ajouter une autorisation",
-    "sub-menu-data-type-authorizations": "autorisations_{dataType}",
-    "sub-menu-new-authorization": "nouvelle autorisation"
+    "sub-menu-data-type-authorizations": "autorisations de {dataType}",
+    "sub-menu-new-authorization": "nouvelle autorisation",
+    "users": "Utilisateurs",
+    "users-placeholder": "Choisir les utilisateurs à autoriser",
+    "data-groups": "Groupes de données",
+    "data-groups-placeholder": "Choisir les données à autoriser",
+    "authorization-scopes": "Périmètres d'autorisation"
   }
 }
diff --git a/ui2/src/main.js b/ui2/src/main.js
index f07d7f716..e642f2486 100644
--- a/ui2/src/main.js
+++ b/ui2/src/main.js
@@ -32,6 +32,8 @@ import {
   faUserPlus,
   faUserAstronaut,
   faKey,
+  faChevronUp,
+  faChevronDown,
 } from "@fortawesome/free-solid-svg-icons";
 import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
 library.add(
@@ -61,7 +63,9 @@ library.add(
   faSignInAlt,
   faUserPlus,
   faUserAstronaut,
-  faKey
+  faKey,
+  faChevronUp,
+  faChevronDown
 );
 Vue.component("vue-fontawesome", FontAwesomeIcon);
 
diff --git a/ui2/src/views/authorizations/DataTypeAuthorizationInfoView.vue b/ui2/src/views/authorizations/DataTypeAuthorizationInfoView.vue
index a05af341e..73e922df9 100644
--- a/ui2/src/views/authorizations/DataTypeAuthorizationInfoView.vue
+++ b/ui2/src/views/authorizations/DataTypeAuthorizationInfoView.vue
@@ -7,10 +7,70 @@
         $t("titles.data-type-new-authorization", { dataType: dataTypeId })
       }}</span>
     </h1>
+
+    <b-field :label="$t('dataTypeAuthorizations.users')" class="mb-4">
+      <b-select
+        :placeholder="$t('dataTypeAuthorizations.users-placeholder')"
+        multiple
+        v-model="usersToAuthorize"
+        :native-size="Math.min(users.length, 5)"
+        expanded
+      >
+        <option v-for="user in users" :value="user.id" :key="user.id">
+          {{ user.label }}
+        </option>
+      </b-select>
+    </b-field>
+
+    <b-field :label="$t('dataTypeAuthorizations.data-groups')" class="mb-4">
+      <b-select
+        :placeholder="$t('dataTypeAuthorizations.data-groups-placeholder')"
+        v-model="dataGroupToAuthorize"
+        :native-size="Math.min(dataGroups.length, 5)"
+        expanded
+      >
+        <option v-for="dataGroup in dataGroups" :value="dataGroup.id" :key="dataGroup.id">
+          {{ dataGroup.label }}
+        </option>
+      </b-select>
+    </b-field>
+
+    <b-field :label="$t('dataTypeAuthorizations.authorization-scopes')">
+      <b-collapse
+        class="card"
+        animation="slide"
+        v-for="(scope, index) of authorizationScopes"
+        :key="scope.id"
+        :open="openCollapse == index"
+        @open="openCollapse = index"
+      >
+        <template #trigger="props">
+          <div class="card-header" role="button">
+            <p class="card-header-title">
+              {{ scope.label }}
+            </p>
+            <a class="card-header-icon">
+              <b-icon :icon="props.open ? 'chevron-down' : 'chevron-up'"> </b-icon>
+            </a>
+          </div>
+        </template>
+        <div class="card-content">
+          <div class="content">
+            <CollapsibleTree
+              v-for="option in scope.options"
+              :key="option.id"
+              :option="option"
+              :withCheckBoxes="true"
+            />
+          </div>
+        </div>
+      </b-collapse>
+    </b-field>
   </PageView>
 </template>
 
 <script>
+import CollapsibleTree from "@/components/common/CollapsibleTree.vue";
 import SubMenu, { SubMenuPath } from "@/components/common/SubMenu.vue";
 import { AlertService } from "@/services/AlertService";
 import { ApplicationService } from "@/services/rest/ApplicationService";
@@ -19,7 +79,7 @@ import { Component, Prop, Vue } from "vue-property-decorator";
 import PageView from "../common/PageView.vue";
 
 @Component({
-  components: { PageView, SubMenu },
+  components: { PageView, SubMenu, CollapsibleTree },
 })
 export default class DataTypeAuthorizationInfoView extends Vue {
   @Prop() dataTypeId;
@@ -32,7 +92,12 @@ export default class DataTypeAuthorizationInfoView extends Vue {
 
   authorizations = [];
   application = {};
-  grantableInfos = {};
+  users = [];
+  dataGroups = [];
+  authorizationScopes = [];
+  usersToAuthorize = [];
+  dataGroupToAuthorize = {};
+  openCollapse = null;
 
   created() {
     this.init();
@@ -68,11 +133,16 @@ export default class DataTypeAuthorizationInfoView extends Vue {
   async init() {
     try {
       this.application = await this.applicationService.getApplication(this.applicationName);
-      this.grantableInfos = await this.authorizationService.getAuthorizationGrantableInfos(
+      const grantableInfos = await this.authorizationService.getAuthorizationGrantableInfos(
         this.applicationName,
         this.dataTypeId
       );
-      console.log(this.grantableInfos);
+      ({
+        authorizationScopes: this.authorizationScopes,
+        dataGroups: this.dataGroups,
+        users: this.users,
+      } = grantableInfos);
+      console.log(this.authorizationScopes, this.dataGroups, this.users);
     } catch (error) {
       this.alertService.toastServerError(error);
     }
diff --git a/ui2/src/views/datatype/DataTypesManagementView.vue b/ui2/src/views/datatype/DataTypesManagementView.vue
index f7bbb2609..c0a8ede31 100644
--- a/ui2/src/views/datatype/DataTypesManagementView.vue
+++ b/ui2/src/views/datatype/DataTypesManagementView.vue
@@ -8,7 +8,7 @@
       <CollapsibleTree
         v-for="data in dataTypes"
         :key="data.id"
-        :label="data.label"
+        :option="data"
         :level="0"
         :onClickLabelCb="(event, label) => openDataTypeCb(event, label)"
         :onUploadCb="(label, file) => uploadDataTypeCsv(label, file)"
diff --git a/ui2/src/views/references/ReferencesManagementView.vue b/ui2/src/views/references/ReferencesManagementView.vue
index 5ca712013..09000adb7 100644
--- a/ui2/src/views/references/ReferencesManagementView.vue
+++ b/ui2/src/views/references/ReferencesManagementView.vue
@@ -8,8 +8,7 @@
       <CollapsibleTree
         v-for="ref in references"
         :key="ref.id"
-        :label="ref.label"
-        :children="ref.children"
+        :option="ref"
         :level="0"
         :onClickLabelCb="(event, label) => openRefDetails(event, label)"
         :onUploadCb="(label, refFile) => uploadReferenceCsv(label, refFile)"
-- 
GitLab


From eab77ed1f0d81c469ff0845d79a14ddd7d351ad8 Mon Sep 17 00:00:00 2001
From: Aurore Lecointe <lecointe@codelutin.com>
Date: Thu, 1 Jul 2021 16:44:23 +0200
Subject: [PATCH 14/26] :construction: Tentative d'arbre avec checkboxes

---
 ui2/src/components/common/CollapsibleTree.vue | 57 ++++++++++++++-----
 ui2/src/style/_common.scss                    |  4 ++
 .../DataTypeAuthorizationInfoView.vue         | 12 ++++
 3 files changed, 60 insertions(+), 13 deletions(-)

diff --git a/ui2/src/components/common/CollapsibleTree.vue b/ui2/src/components/common/CollapsibleTree.vue
index 1b9b3277c..c2ca28313 100644
--- a/ui2/src/components/common/CollapsibleTree.vue
+++ b/ui2/src/components/common/CollapsibleTree.vue
@@ -15,8 +15,11 @@
         />
         <b-checkbox
           v-if="withCheckBoxes"
+          v-model="innerChecked"
           :native-value="option.id"
           :style="`transform:translate(${level * 50}px);`"
+          @click.native="stopPropagation"
+          :disabled="hasParentChecked"
         >
           {{ option.label }}
         </b-checkbox>
@@ -58,23 +61,24 @@
         </div>
       </div>
     </div>
-    <div v-if="displayChildren">
-      <CollapsibleTree
-        v-for="child in option.children"
-        :key="child.id"
-        :option="child"
-        :level="level + 1"
-        :onClickLabelCb="onClickLabelCb"
-        :onUploadCb="onUploadCb"
-        :buttons="buttons"
-        :withCheckBoxes="withCheckBoxes"
-      />
-    </div>
+    <CollapsibleTree
+      v-for="child in option.children"
+      :key="child.id"
+      :option="child"
+      :level="level + 1"
+      :onClickLabelCb="onClickLabelCb"
+      :onUploadCb="onUploadCb"
+      :buttons="buttons"
+      :withCheckBoxes="withCheckBoxes"
+      :hasParentChecked="innerChecked"
+      :class="displayChildren ? '' : 'hide'"
+      @childCheck="updateChildrenChecked"
+    />
   </div>
 </template>
 
 <script>
-import { Component, Prop, Vue } from "vue-property-decorator";
+import { Component, Prop, Vue, Watch } from "vue-property-decorator";
 import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
 
 @Component({
@@ -87,9 +91,36 @@ export default class CollapsibleTree extends Vue {
   @Prop() onUploadCb;
   @Prop() buttons;
   @Prop({ default: false }) withCheckBoxes;
+  @Prop({ default: false }) hasParentChecked;
 
   displayChildren = false;
   refFile = null;
+  innerChecked = false;
+  innerChildrenChecked = [];
+
+  @Watch("hasParentChecked")
+  onParentChecked(newVal) {
+    this.innerChecked = newVal;
+  }
+
+  @Watch("innerChecked")
+  onInnerChecked() {
+    this.$emit("childCheck", { id: this.option.id, checked: this.innerChecked });
+  }
+
+  updateChildrenChecked({ id, checked }) {
+    if (checked) {
+      this.innerChildrenChecked.push(id);
+    } else {
+      const removalIndex = this.innerChildrenChecked.indexOf(id);
+      this.innerChildrenChecked.splice(removalIndex, 1);
+    }
+    this.$emit("updateChildrenChecked", this.innerChildrenChecked);
+  }
+
+  stopPropagation(event) {
+    event.stopPropagation();
+  }
 }
 </script>
 
diff --git a/ui2/src/style/_common.scss b/ui2/src/style/_common.scss
index 8bd9cb0be..f4caa1b3c 100644
--- a/ui2/src/style/_common.scss
+++ b/ui2/src/style/_common.scss
@@ -31,6 +31,10 @@ a {
   }
 }
 
+.hide {
+  display: none;
+}
+
 // Input style
 
 .input-field {
diff --git a/ui2/src/views/authorizations/DataTypeAuthorizationInfoView.vue b/ui2/src/views/authorizations/DataTypeAuthorizationInfoView.vue
index 73e922df9..846b43e99 100644
--- a/ui2/src/views/authorizations/DataTypeAuthorizationInfoView.vue
+++ b/ui2/src/views/authorizations/DataTypeAuthorizationInfoView.vue
@@ -61,6 +61,7 @@
               :key="option.id"
               :option="option"
               :withCheckBoxes="true"
+              @updateChildrenChecked="updateScopesToAuthorize"
             />
           </div>
         </div>
@@ -98,6 +99,7 @@ export default class DataTypeAuthorizationInfoView extends Vue {
   usersToAuthorize = [];
   dataGroupToAuthorize = {};
   openCollapse = null;
+  scopesToAuthorize = [];
 
   created() {
     this.init();
@@ -143,9 +145,19 @@ export default class DataTypeAuthorizationInfoView extends Vue {
         users: this.users,
       } = grantableInfos);
       console.log(this.authorizationScopes, this.dataGroups, this.users);
+      this.authorizationScopes[0].options[0].children[0].children.push({
+        children: [],
+        id: "toto",
+        label: "toto",
+      });
     } catch (error) {
+      console.log(error);
       this.alertService.toastServerError(error);
     }
   }
+
+  updateScopesToAuthorize(scopesChecked) {
+    console.log(scopesChecked);
+  }
 }
 </script>
-- 
GitLab


From 308bb3be0e3aa4ace9daae1654ee67a5f306c636 Mon Sep 17 00:00:00 2001
From: Aurore Lecointe <lecointe@codelutin.com>
Date: Thu, 1 Jul 2021 17:19:37 +0200
Subject: [PATCH 15/26] Remplace les checkBoxes par des boutons radio

---
 ui2/src/components/common/CollapsibleTree.vue | 79 ++++++++-----------
 .../DataTypeAuthorizationInfoView.vue         | 25 +++---
 2 files changed, 45 insertions(+), 59 deletions(-)

diff --git a/ui2/src/components/common/CollapsibleTree.vue b/ui2/src/components/common/CollapsibleTree.vue
index c2ca28313..7f60add94 100644
--- a/ui2/src/components/common/CollapsibleTree.vue
+++ b/ui2/src/components/common/CollapsibleTree.vue
@@ -8,28 +8,29 @@
       @click="displayChildren = !displayChildren"
     >
       <div class="CollapsibleTree-header-infos">
-        <FontAwesomeIcon
-          v-if="option.children && option.children.length !== 0"
-          :icon="displayChildren ? 'caret-down' : 'caret-right'"
-          class="clickable mr-3"
-        />
-        <b-checkbox
-          v-if="withCheckBoxes"
-          v-model="innerChecked"
-          :native-value="option.id"
-          :style="`transform:translate(${level * 50}px);`"
-          @click.native="stopPropagation"
-          :disabled="hasParentChecked"
-        >
-          {{ option.label }}
-        </b-checkbox>
-        <div
-          v-else
-          :class="onClickLabelCb ? 'link' : ''"
-          :style="`transform:translate(${level * 50}px);`"
-          @click="(event) => onClickLabelCb && onClickLabelCb(event, option.label)"
-        >
-          {{ option.label }}
+        <div class="CollapsibleTree-header-infos" :style="`transform:translate(${level * 50}px);`">
+          <FontAwesomeIcon
+            v-if="option.children && option.children.length !== 0"
+            :icon="displayChildren ? 'caret-down' : 'caret-right'"
+            class="clickable mr-3"
+          />
+
+          <b-radio
+            v-if="withRadios"
+            v-model="innerOptionChecked"
+            :name="radioName"
+            @click.native="stopPropagation"
+            :native-value="option.id"
+          >
+            {{ option.label }}
+          </b-radio>
+          <div
+            v-else
+            :class="onClickLabelCb ? 'link' : ''"
+            @click="(event) => onClickLabelCb && onClickLabelCb(event, option.label)"
+          >
+            {{ option.label }}
+          </div>
         </div>
       </div>
       <div class="CollapsibleTree-buttons">
@@ -69,10 +70,10 @@
       :onClickLabelCb="onClickLabelCb"
       :onUploadCb="onUploadCb"
       :buttons="buttons"
-      :withCheckBoxes="withCheckBoxes"
-      :hasParentChecked="innerChecked"
       :class="displayChildren ? '' : 'hide'"
-      @childCheck="updateChildrenChecked"
+      :withRadios="withRadios"
+      :radioName="radioName"
+      @optionChecked="onInnerOptionChecked"
     />
   </div>
 </template>
@@ -90,32 +91,16 @@ export default class CollapsibleTree extends Vue {
   @Prop() onClickLabelCb;
   @Prop() onUploadCb;
   @Prop() buttons;
-  @Prop({ default: false }) withCheckBoxes;
-  @Prop({ default: false }) hasParentChecked;
+  @Prop({ default: false }) withRadios;
+  @Prop() radioName;
 
   displayChildren = false;
   refFile = null;
-  innerChecked = false;
-  innerChildrenChecked = [];
-
-  @Watch("hasParentChecked")
-  onParentChecked(newVal) {
-    this.innerChecked = newVal;
-  }
+  innerOptionChecked = null;
 
-  @Watch("innerChecked")
-  onInnerChecked() {
-    this.$emit("childCheck", { id: this.option.id, checked: this.innerChecked });
-  }
-
-  updateChildrenChecked({ id, checked }) {
-    if (checked) {
-      this.innerChildrenChecked.push(id);
-    } else {
-      const removalIndex = this.innerChildrenChecked.indexOf(id);
-      this.innerChildrenChecked.splice(removalIndex, 1);
-    }
-    this.$emit("updateChildrenChecked", this.innerChildrenChecked);
+  @Watch("innerOptionChecked")
+  onInnerOptionChecked(value) {
+    this.$emit("optionChecked", value);
   }
 
   stopPropagation(event) {
diff --git a/ui2/src/views/authorizations/DataTypeAuthorizationInfoView.vue b/ui2/src/views/authorizations/DataTypeAuthorizationInfoView.vue
index 846b43e99..4963e36ec 100644
--- a/ui2/src/views/authorizations/DataTypeAuthorizationInfoView.vue
+++ b/ui2/src/views/authorizations/DataTypeAuthorizationInfoView.vue
@@ -60,8 +60,9 @@
               v-for="option in scope.options"
               :key="option.id"
               :option="option"
-              :withCheckBoxes="true"
-              @updateChildrenChecked="updateScopesToAuthorize"
+              :withRadios="true"
+              :radioName="`dataTypeAuthorizations_${applicationName}_${dataTypeId}`"
+              @optionChecked="(value) => (scopeToAuthorize = value)"
             />
           </div>
         </div>
@@ -76,7 +77,7 @@ import SubMenu, { SubMenuPath } from "@/components/common/SubMenu.vue";
 import { AlertService } from "@/services/AlertService";
 import { ApplicationService } from "@/services/rest/ApplicationService";
 import { AuthorizationService } from "@/services/rest/AuthorizationService";
-import { Component, Prop, Vue } from "vue-property-decorator";
+import { Component, Prop, Vue, Watch } from "vue-property-decorator";
 import PageView from "../common/PageView.vue";
 
 @Component({
@@ -99,7 +100,7 @@ export default class DataTypeAuthorizationInfoView extends Vue {
   usersToAuthorize = [];
   dataGroupToAuthorize = {};
   openCollapse = null;
-  scopesToAuthorize = [];
+  scopeToAuthorize = null;
 
   created() {
     this.init();
@@ -145,19 +146,19 @@ export default class DataTypeAuthorizationInfoView extends Vue {
         users: this.users,
       } = grantableInfos);
       console.log(this.authorizationScopes, this.dataGroups, this.users);
-      this.authorizationScopes[0].options[0].children[0].children.push({
-        children: [],
-        id: "toto",
-        label: "toto",
-      });
+      // this.authorizationScopes[0].options[0].children[0].children.push({
+      //   children: [],
+      //   id: "toto",
+      //   label: "toto",
+      // });
     } catch (error) {
-      console.log(error);
       this.alertService.toastServerError(error);
     }
   }
 
-  updateScopesToAuthorize(scopesChecked) {
-    console.log(scopesChecked);
+  @Watch("scopeToAuthorize")
+  onScopeToAuthorizeChanged() {
+    console.log(this.scopeToAuthorize);
   }
 }
 </script>
-- 
GitLab


From 125a66b931537e8f0bb99660e4b3d89c4aa895e1 Mon Sep 17 00:00:00 2001
From: Aurore Lecointe <lecointe@codelutin.com>
Date: Thu, 1 Jul 2021 18:09:58 +0200
Subject: [PATCH 16/26] =?UTF-8?q?Ajoute=20le=20choix=20de=20la=20p=C3=A9ri?=
 =?UTF-8?q?ode.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 ui2/src/locales/en.json                       |  9 +-
 ui2/src/locales/fr.json                       |  9 +-
 ui2/src/main.js                               |  4 +-
 .../DataTypeAuthorizationInfoView.vue         | 91 ++++++++++++++++++-
 4 files changed, 105 insertions(+), 8 deletions(-)

diff --git a/ui2/src/locales/en.json b/ui2/src/locales/en.json
index 636ec4974..e124fcea2 100644
--- a/ui2/src/locales/en.json
+++ b/ui2/src/locales/en.json
@@ -119,6 +119,13 @@
     "users-placeholder": "Chose users to authorize",
     "data-groups": "Data groups",
     "data-groups-placeholder": "Chose data groups to authorize",
-    "authorization-scopes": "Authorization scopes"
+    "authorization-scopes": "Authorization scopes",
+    "start-date": "Start date",
+    "end-date": "End date",
+    "period": "Authorization period",
+    "from-date": "From date : ",
+    "to-date": "To date : ",
+    "from-date-to-date": "From date to date : ",
+    "always": "Always"
   }
 }
diff --git a/ui2/src/locales/fr.json b/ui2/src/locales/fr.json
index 6ab0541de..d9d86522f 100644
--- a/ui2/src/locales/fr.json
+++ b/ui2/src/locales/fr.json
@@ -119,6 +119,13 @@
     "users-placeholder": "Choisir les utilisateurs à autoriser",
     "data-groups": "Groupes de données",
     "data-groups-placeholder": "Choisir les données à autoriser",
-    "authorization-scopes": "Périmètres d'autorisation"
+    "authorization-scopes": "Périmètres d'autorisation",
+    "start-date": "Date de début",
+    "end-date": "Date de fin",
+    "period": "Période d'autorisation",
+    "from-date": "À partir de",
+    "to-date": "Jusqu'au",
+    "from-date-to-date": "De date à date",
+    "always": "Toujours"
   }
 }
diff --git a/ui2/src/main.js b/ui2/src/main.js
index e642f2486..38ab2103b 100644
--- a/ui2/src/main.js
+++ b/ui2/src/main.js
@@ -34,6 +34,7 @@ import {
   faKey,
   faChevronUp,
   faChevronDown,
+  faCalendarDay,
 } from "@fortawesome/free-solid-svg-icons";
 import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
 library.add(
@@ -65,7 +66,8 @@ library.add(
   faUserAstronaut,
   faKey,
   faChevronUp,
-  faChevronDown
+  faChevronDown,
+  faCalendarDay
 );
 Vue.component("vue-fontawesome", FontAwesomeIcon);
 
diff --git a/ui2/src/views/authorizations/DataTypeAuthorizationInfoView.vue b/ui2/src/views/authorizations/DataTypeAuthorizationInfoView.vue
index 4963e36ec..74cea602c 100644
--- a/ui2/src/views/authorizations/DataTypeAuthorizationInfoView.vue
+++ b/ui2/src/views/authorizations/DataTypeAuthorizationInfoView.vue
@@ -8,12 +8,65 @@
       }}</span>
     </h1>
 
+    <b-field
+      :label="$t('dataTypeAuthorizations.period')"
+      class="DataTypeAuthorizationInfoView-periods-container mb-4"
+    >
+      <b-radio
+        name="dataTypeAuthorization-period"
+        v-model="period"
+        :native-value="periods.FROM_DATE"
+        class="DataTypeAuthorizationInfoView-radio-field"
+      >
+        {{ periods.FROM_DATE }}
+        <b-field :label="$t('dataTypeAuthorizations.start-date')" class="mb-4">
+          <b-datepicker show-week-number :locale="chosenLocale" icon="calendar-day" trap-focus>
+          </b-datepicker>
+        </b-field>
+      </b-radio>
+
+      <b-radio
+        name="dataTypeAuthorization-period"
+        v-model="period"
+        :native-value="periods.TO_DATE"
+        class="DataTypeAuthorizationInfoView-radio-field"
+      >
+        {{ periods.TO_DATE }}
+        <b-field :label="$t('dataTypeAuthorizations.end-date')" class="mb-4">
+          <b-datepicker show-week-number :locale="chosenLocale" icon="calendar-day" trap-focus>
+          </b-datepicker>
+        </b-field>
+      </b-radio>
+
+      <b-radio
+        name="dataTypeAuthorization-period"
+        v-model="period"
+        :native-value="periods.FROM_DATE_TO_DATE"
+        class="DataTypeAuthorizationInfoView-radio-field"
+      >
+        {{ periods.FROM_DATE_TO_DATE }}
+        <b-field :label="$t('dataTypeAuthorizations.start-date')" class="mb-4">
+          <b-datepicker show-week-number :locale="chosenLocale" icon="calendar-day" trap-focus>
+          </b-datepicker>
+        </b-field>
+        <b-field :label="$t('dataTypeAuthorizations.end-date')" class="mb-4">
+          <b-datepicker show-week-number :locale="chosenLocale" icon="calendar-day" trap-focus>
+          </b-datepicker>
+        </b-field>
+      </b-radio>
+
+      <b-radio
+        name="dataTypeAuthorization-period"
+        v-model="period"
+        :native-value="periods.ALWAYS"
+        >{{ periods.ALWAYS }}</b-radio
+      >
+    </b-field>
+
     <b-field :label="$t('dataTypeAuthorizations.users')" class="mb-4">
       <b-select
         :placeholder="$t('dataTypeAuthorizations.users-placeholder')"
-        multiple
-        v-model="usersToAuthorize"
-        :native-size="Math.min(users.length, 5)"
+        v-model="userToAuthorize"
         expanded
       >
         <option v-for="user in users" :value="user.id" :key="user.id">
@@ -26,7 +79,6 @@
       <b-select
         :placeholder="$t('dataTypeAuthorizations.data-groups-placeholder')"
         v-model="dataGroupToAuthorize"
-        :native-size="Math.min(dataGroups.length, 5)"
         expanded
       >
         <option v-for="dataGroup in dataGroups" :value="dataGroup.id" :key="dataGroup.id">
@@ -77,6 +129,7 @@ import SubMenu, { SubMenuPath } from "@/components/common/SubMenu.vue";
 import { AlertService } from "@/services/AlertService";
 import { ApplicationService } from "@/services/rest/ApplicationService";
 import { AuthorizationService } from "@/services/rest/AuthorizationService";
+import { UserPreferencesService } from "@/services/UserPreferencesService";
 import { Component, Prop, Vue, Watch } from "vue-property-decorator";
 import PageView from "../common/PageView.vue";
 
@@ -91,19 +144,29 @@ export default class DataTypeAuthorizationInfoView extends Vue {
   authorizationService = AuthorizationService.INSTANCE;
   alertService = AlertService.INSTANCE;
   applicationService = ApplicationService.INSTANCE;
+  userPreferencesService = UserPreferencesService.INSTANCE;
+
+  periods = {
+    FROM_DATE: this.$t("dataTypeAuthorizations.from-date"),
+    TO_DATE: this.$t("dataTypeAuthorizations.to-date"),
+    FROM_DATE_TO_DATE: this.$t("dataTypeAuthorizations.from-date-to-date"),
+    ALWAYS: this.$t("dataTypeAuthorizations.always"),
+  };
 
   authorizations = [];
   application = {};
   users = [];
   dataGroups = [];
   authorizationScopes = [];
-  usersToAuthorize = [];
+  userToAuthorize = [];
   dataGroupToAuthorize = {};
   openCollapse = null;
   scopeToAuthorize = null;
+  period = this.periods.FROM_DATE;
 
   created() {
     this.init();
+    this.chosenLocale = this.userPreferencesService.getUserPrefLocale();
     this.subMenuPaths = [
       new SubMenuPath(
         this.$t("dataTypesManagement.data-types").toLowerCase(),
@@ -162,3 +225,21 @@ export default class DataTypeAuthorizationInfoView extends Vue {
   }
 }
 </script>
+
+<style lang="scss">
+.DataTypeAuthorizationInfoView-periods-container {
+  .field-body .field.has-addons {
+    display: flex;
+    flex-direction: column;
+  }
+}
+
+.DataTypeAuthorizationInfoView-radio-field {
+  &.b-radio {
+    .control-label {
+      display: flex;
+      align-items: center;
+    }
+  }
+}
+</style>
-- 
GitLab


From 512d0fe96f882293327f1d3020d022c26afae112 Mon Sep 17 00:00:00 2001
From: Aurore Lecointe <lecointe@codelutin.com>
Date: Fri, 2 Jul 2021 10:40:48 +0200
Subject: [PATCH 17/26] =?UTF-8?q?Am=C3=A9liore=20le=20style=20de=20la=20p?=
 =?UTF-8?q?=C3=A9riode=20d'autorisation?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 ui2/src/locales/en.json                       |  5 +--
 ui2/src/locales/fr.json                       |  7 ++--
 .../DataTypeAuthorizationInfoView.vue         | 39 +++++++++++++------
 3 files changed, 33 insertions(+), 18 deletions(-)

diff --git a/ui2/src/locales/en.json b/ui2/src/locales/en.json
index e124fcea2..04c372fd7 100644
--- a/ui2/src/locales/en.json
+++ b/ui2/src/locales/en.json
@@ -120,12 +120,11 @@
     "data-groups": "Data groups",
     "data-groups-placeholder": "Chose data groups to authorize",
     "authorization-scopes": "Authorization scopes",
-    "start-date": "Start date",
-    "end-date": "End date",
     "period": "Authorization period",
     "from-date": "From date : ",
     "to-date": "To date : ",
     "from-date-to-date": "From date to date : ",
-    "always": "Always"
+    "always": "Always",
+    "to": "to"
   }
 }
diff --git a/ui2/src/locales/fr.json b/ui2/src/locales/fr.json
index d9d86522f..8e207548f 100644
--- a/ui2/src/locales/fr.json
+++ b/ui2/src/locales/fr.json
@@ -120,12 +120,11 @@
     "data-groups": "Groupes de données",
     "data-groups-placeholder": "Choisir les données à autoriser",
     "authorization-scopes": "Périmètres d'autorisation",
-    "start-date": "Date de début",
-    "end-date": "Date de fin",
     "period": "Période d'autorisation",
-    "from-date": "À partir de",
+    "from-date": "À partir du",
     "to-date": "Jusqu'au",
     "from-date-to-date": "De date à date",
-    "always": "Toujours"
+    "always": "Toujours",
+    "to": "à"
   }
 }
diff --git a/ui2/src/views/authorizations/DataTypeAuthorizationInfoView.vue b/ui2/src/views/authorizations/DataTypeAuthorizationInfoView.vue
index 74cea602c..8bb0a8749 100644
--- a/ui2/src/views/authorizations/DataTypeAuthorizationInfoView.vue
+++ b/ui2/src/views/authorizations/DataTypeAuthorizationInfoView.vue
@@ -16,10 +16,12 @@
         name="dataTypeAuthorization-period"
         v-model="period"
         :native-value="periods.FROM_DATE"
-        class="DataTypeAuthorizationInfoView-radio-field"
+        class="DataTypeAuthorizationInfoView-radio-field mb-2"
       >
-        {{ periods.FROM_DATE }}
-        <b-field :label="$t('dataTypeAuthorizations.start-date')" class="mb-4">
+        <span class="DataTypeAuthorizationInfoView-radio-label">
+          {{ periods.FROM_DATE }}
+        </span>
+        <b-field>
           <b-datepicker show-week-number :locale="chosenLocale" icon="calendar-day" trap-focus>
           </b-datepicker>
         </b-field>
@@ -29,10 +31,12 @@
         name="dataTypeAuthorization-period"
         v-model="period"
         :native-value="periods.TO_DATE"
-        class="DataTypeAuthorizationInfoView-radio-field"
+        class="DataTypeAuthorizationInfoView-radio-field mb-2"
       >
-        {{ periods.TO_DATE }}
-        <b-field :label="$t('dataTypeAuthorizations.end-date')" class="mb-4">
+        <span class="DataTypeAuthorizationInfoView-radio-label">
+          {{ periods.TO_DATE }}
+        </span>
+        <b-field>
           <b-datepicker show-week-number :locale="chosenLocale" icon="calendar-day" trap-focus>
           </b-datepicker>
         </b-field>
@@ -42,24 +46,31 @@
         name="dataTypeAuthorization-period"
         v-model="period"
         :native-value="periods.FROM_DATE_TO_DATE"
-        class="DataTypeAuthorizationInfoView-radio-field"
+        class="DataTypeAuthorizationInfoView-radio-field mb-2"
       >
-        {{ periods.FROM_DATE_TO_DATE }}
-        <b-field :label="$t('dataTypeAuthorizations.start-date')" class="mb-4">
+        <span class="DataTypeAuthorizationInfoView-radio-label">
+          {{ periods.FROM_DATE_TO_DATE }}
+        </span>
+        <b-field class="mr-4">
           <b-datepicker show-week-number :locale="chosenLocale" icon="calendar-day" trap-focus>
           </b-datepicker>
         </b-field>
-        <b-field :label="$t('dataTypeAuthorizations.end-date')" class="mb-4">
+        <span class="mr-4">{{ $t("dataTypeAuthorizations.to") }}</span>
+        <b-field>
           <b-datepicker show-week-number :locale="chosenLocale" icon="calendar-day" trap-focus>
           </b-datepicker>
         </b-field>
       </b-radio>
 
       <b-radio
+        class="DataTypeAuthorizationInfoView-radio-field"
         name="dataTypeAuthorization-period"
         v-model="period"
         :native-value="periods.ALWAYS"
-        >{{ periods.ALWAYS }}</b-radio
+      >
+        <span class="DataTypeAuthorizationInfoView-radio-label">
+          {{ periods.ALWAYS }}</span
+        ></b-radio
       >
     </b-field>
 
@@ -235,11 +246,17 @@ export default class DataTypeAuthorizationInfoView extends Vue {
 }
 
 .DataTypeAuthorizationInfoView-radio-field {
+  height: 40px;
   &.b-radio {
     .control-label {
       display: flex;
       align-items: center;
+      width: 100%;
     }
   }
 }
+
+.DataTypeAuthorizationInfoView-radio-label {
+  width: 200px;
+}
 </style>
-- 
GitLab


From 4417f22cba8a73f7a0a6a0c7221b0c69fa9a4bc6 Mon Sep 17 00:00:00 2001
From: Aurore Lecointe <lecointe@codelutin.com>
Date: Fri, 2 Jul 2021 11:12:24 +0200
Subject: [PATCH 18/26] =?UTF-8?q?Termine=20le=20formulaire=20de=20cr=C3=A9?=
 =?UTF-8?q?ation=20d'autorisation?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 ui2/src/locales/en.json                       |   3 +-
 ui2/src/locales/fr.json                       |   3 +-
 ui2/src/main.js                               |   4 +-
 .../DataTypeAuthorizationInfoView.vue         | 379 ++++++++++++------
 4 files changed, 264 insertions(+), 125 deletions(-)

diff --git a/ui2/src/locales/en.json b/ui2/src/locales/en.json
index 04c372fd7..7fa232053 100644
--- a/ui2/src/locales/en.json
+++ b/ui2/src/locales/en.json
@@ -125,6 +125,7 @@
     "to-date": "To date : ",
     "from-date-to-date": "From date to date : ",
     "always": "Always",
-    "to": "to"
+    "to": "to",
+    "create": "Create authorization"
   }
 }
diff --git a/ui2/src/locales/fr.json b/ui2/src/locales/fr.json
index 8e207548f..2463ed647 100644
--- a/ui2/src/locales/fr.json
+++ b/ui2/src/locales/fr.json
@@ -125,6 +125,7 @@
     "to-date": "Jusqu'au",
     "from-date-to-date": "De date à date",
     "always": "Toujours",
-    "to": "à"
+    "to": "à",
+    "create": "Créer l'autorisation"
   }
 }
diff --git a/ui2/src/main.js b/ui2/src/main.js
index 38ab2103b..a8087dc40 100644
--- a/ui2/src/main.js
+++ b/ui2/src/main.js
@@ -35,6 +35,7 @@ import {
   faChevronUp,
   faChevronDown,
   faCalendarDay,
+  faPaperPlane,
 } from "@fortawesome/free-solid-svg-icons";
 import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
 library.add(
@@ -67,7 +68,8 @@ library.add(
   faKey,
   faChevronUp,
   faChevronDown,
-  faCalendarDay
+  faCalendarDay,
+  faPaperPlane
 );
 Vue.component("vue-fontawesome", FontAwesomeIcon);
 
diff --git a/ui2/src/views/authorizations/DataTypeAuthorizationInfoView.vue b/ui2/src/views/authorizations/DataTypeAuthorizationInfoView.vue
index 8bb0a8749..b56cd0b5d 100644
--- a/ui2/src/views/authorizations/DataTypeAuthorizationInfoView.vue
+++ b/ui2/src/views/authorizations/DataTypeAuthorizationInfoView.vue
@@ -8,129 +8,253 @@
       }}</span>
     </h1>
 
-    <b-field
-      :label="$t('dataTypeAuthorizations.period')"
-      class="DataTypeAuthorizationInfoView-periods-container mb-4"
-    >
-      <b-radio
-        name="dataTypeAuthorization-period"
-        v-model="period"
-        :native-value="periods.FROM_DATE"
-        class="DataTypeAuthorizationInfoView-radio-field mb-2"
+    <ValidationObserver ref="observer" v-slot="{ handleSubmit }">
+      <b-field
+        :label="$t('dataTypeAuthorizations.period')"
+        class="DataTypeAuthorizationInfoView-periods-container mb-4"
       >
-        <span class="DataTypeAuthorizationInfoView-radio-label">
-          {{ periods.FROM_DATE }}
-        </span>
-        <b-field>
-          <b-datepicker show-week-number :locale="chosenLocale" icon="calendar-day" trap-focus>
-          </b-datepicker>
-        </b-field>
-      </b-radio>
+        <b-radio
+          name="dataTypeAuthorization-period"
+          v-model="period"
+          :native-value="periods.FROM_DATE"
+          class="DataTypeAuthorizationInfoView-radio-field mb-2"
+        >
+          <span class="DataTypeAuthorizationInfoView-radio-label">
+            {{ periods.FROM_DATE }}
+          </span>
+          <ValidationProvider
+            :rules="period === periods.FROM_DATE ? 'required' : ''"
+            name="period_fromDate"
+            v-slot="{ errors, valid }"
+            vid="period_fromDate"
+          >
+            <b-field
+              :type="{
+                'is-danger': errors && errors.length > 0,
+                'is-success': valid && period === periods.FROM_DATE,
+              }"
+              :message="errors[0]"
+            >
+              <b-datepicker
+                v-model="startDate"
+                show-week-number
+                :locale="chosenLocale"
+                icon="calendar-day"
+                trap-focus
+                :disabled="period !== periods.FROM_DATE"
+              >
+              </b-datepicker>
+            </b-field>
+          </ValidationProvider>
+        </b-radio>
 
-      <b-radio
-        name="dataTypeAuthorization-period"
-        v-model="period"
-        :native-value="periods.TO_DATE"
-        class="DataTypeAuthorizationInfoView-radio-field mb-2"
-      >
-        <span class="DataTypeAuthorizationInfoView-radio-label">
-          {{ periods.TO_DATE }}
-        </span>
-        <b-field>
-          <b-datepicker show-week-number :locale="chosenLocale" icon="calendar-day" trap-focus>
-          </b-datepicker>
-        </b-field>
-      </b-radio>
+        <b-radio
+          name="dataTypeAuthorization-period"
+          v-model="period"
+          :native-value="periods.TO_DATE"
+          class="DataTypeAuthorizationInfoView-radio-field mb-2"
+        >
+          <span class="DataTypeAuthorizationInfoView-radio-label">
+            {{ periods.TO_DATE }}
+          </span>
+          <ValidationProvider
+            :rules="period === periods.TO_DATE ? 'required' : ''"
+            name="period_toDate"
+            v-slot="{ errors, valid }"
+            vid="period_toDate"
+          >
+            <b-field
+              :type="{
+                'is-danger': errors && errors.length > 0,
+                'is-success': valid && period === periods.TO_DATE,
+              }"
+              :message="errors[0]"
+            >
+              <b-datepicker
+                v-model="endDate"
+                show-week-number
+                :locale="chosenLocale"
+                icon="calendar-day"
+                trap-focus
+                :disabled="period !== periods.TO_DATE"
+              >
+              </b-datepicker>
+            </b-field>
+          </ValidationProvider>
+        </b-radio>
 
-      <b-radio
-        name="dataTypeAuthorization-period"
-        v-model="period"
-        :native-value="periods.FROM_DATE_TO_DATE"
-        class="DataTypeAuthorizationInfoView-radio-field mb-2"
-      >
-        <span class="DataTypeAuthorizationInfoView-radio-label">
-          {{ periods.FROM_DATE_TO_DATE }}
-        </span>
-        <b-field class="mr-4">
-          <b-datepicker show-week-number :locale="chosenLocale" icon="calendar-day" trap-focus>
-          </b-datepicker>
-        </b-field>
-        <span class="mr-4">{{ $t("dataTypeAuthorizations.to") }}</span>
-        <b-field>
-          <b-datepicker show-week-number :locale="chosenLocale" icon="calendar-day" trap-focus>
-          </b-datepicker>
-        </b-field>
-      </b-radio>
+        <b-radio
+          name="dataTypeAuthorization-period"
+          v-model="period"
+          :native-value="periods.FROM_DATE_TO_DATE"
+          class="DataTypeAuthorizationInfoView-radio-field mb-2"
+        >
+          <span class="DataTypeAuthorizationInfoView-radio-label">
+            {{ periods.FROM_DATE_TO_DATE }}
+          </span>
+          <ValidationProvider
+            :rules="period === periods.FROM_DATE_TO_DATE ? 'required' : ''"
+            name="period_fromDateToDate_1"
+            v-slot="{ errors, valid }"
+            vid="period_fromDateToDate_1"
+          >
+            <b-field
+              class="mr-4"
+              :type="{
+                'is-danger': errors && errors.length > 0,
+                'is-success': valid && period === periods.FROM_DATE_TO_DATE,
+              }"
+              :message="errors[0]"
+            >
+              <b-datepicker
+                v-model="startDate"
+                show-week-number
+                :locale="chosenLocale"
+                icon="calendar-day"
+                trap-focus
+                :disabled="period !== periods.FROM_DATE_TO_DATE"
+              >
+              </b-datepicker>
+            </b-field>
+          </ValidationProvider>
+          <span class="mr-4">{{ $t("dataTypeAuthorizations.to") }}</span>
+          <ValidationProvider
+            :rules="period === periods.FROM_DATE_TO_DATE ? 'required' : ''"
+            name="period_fromDateToDate_2"
+            v-slot="{ errors, valid }"
+            vid="period_fromDateToDate_2"
+          >
+            <b-field
+              :type="{
+                'is-danger': errors && errors.length > 0,
+                'is-success': valid && period === periods.FROM_DATE_TO_DATE,
+              }"
+              :message="errors[0]"
+            >
+              <b-datepicker
+                v-model="endDate"
+                show-week-number
+                :locale="chosenLocale"
+                icon="calendar-day"
+                trap-focus
+                :disabled="period !== periods.FROM_DATE_TO_DATE"
+              >
+              </b-datepicker>
+            </b-field>
+          </ValidationProvider>
+        </b-radio>
 
-      <b-radio
-        class="DataTypeAuthorizationInfoView-radio-field"
-        name="dataTypeAuthorization-period"
-        v-model="period"
-        :native-value="periods.ALWAYS"
-      >
-        <span class="DataTypeAuthorizationInfoView-radio-label">
-          {{ periods.ALWAYS }}</span
-        ></b-radio
-      >
-    </b-field>
+        <b-radio
+          class="DataTypeAuthorizationInfoView-radio-field"
+          name="dataTypeAuthorization-period"
+          v-model="period"
+          :native-value="periods.ALWAYS"
+        >
+          <span class="DataTypeAuthorizationInfoView-radio-label">
+            {{ periods.ALWAYS }}</span
+          ></b-radio
+        >
+      </b-field>
 
-    <b-field :label="$t('dataTypeAuthorizations.users')" class="mb-4">
-      <b-select
-        :placeholder="$t('dataTypeAuthorizations.users-placeholder')"
-        v-model="userToAuthorize"
-        expanded
-      >
-        <option v-for="user in users" :value="user.id" :key="user.id">
-          {{ user.label }}
-        </option>
-      </b-select>
-    </b-field>
+      <ValidationProvider rules="required" name="users" v-slot="{ errors, valid }" vid="users">
+        <b-field
+          :label="$t('dataTypeAuthorizations.users')"
+          class="mb-4"
+          :type="{
+            'is-danger': errors && errors.length > 0,
+            'is-success': valid,
+          }"
+          :message="errors[0]"
+        >
+          <b-select
+            :placeholder="$t('dataTypeAuthorizations.users-placeholder')"
+            v-model="userToAuthorize"
+            expanded
+          >
+            <option v-for="user in users" :value="user.id" :key="user.id">
+              {{ user.label }}
+            </option>
+          </b-select>
+        </b-field>
+      </ValidationProvider>
 
-    <b-field :label="$t('dataTypeAuthorizations.data-groups')" class="mb-4">
-      <b-select
-        :placeholder="$t('dataTypeAuthorizations.data-groups-placeholder')"
-        v-model="dataGroupToAuthorize"
-        expanded
+      <ValidationProvider
+        rules="required"
+        name="dataGroups"
+        v-slot="{ errors, valid }"
+        vid="dataGroups"
       >
-        <option v-for="dataGroup in dataGroups" :value="dataGroup.id" :key="dataGroup.id">
-          {{ dataGroup.label }}
-        </option>
-      </b-select>
-    </b-field>
+        <b-field
+          :label="$t('dataTypeAuthorizations.data-groups')"
+          class="mb-4"
+          :type="{
+            'is-danger': errors && errors.length > 0,
+            'is-success': valid,
+          }"
+          :message="errors[0]"
+        >
+          <b-select
+            :placeholder="$t('dataTypeAuthorizations.data-groups-placeholder')"
+            v-model="dataGroupToAuthorize"
+            expanded
+          >
+            <option v-for="dataGroup in dataGroups" :value="dataGroup.id" :key="dataGroup.id">
+              {{ dataGroup.label }}
+            </option>
+          </b-select>
+        </b-field>
+      </ValidationProvider>
 
-    <b-field :label="$t('dataTypeAuthorizations.authorization-scopes')">
-      <b-collapse
-        class="card"
-        animation="slide"
-        v-for="(scope, index) of authorizationScopes"
-        :key="scope.id"
-        :open="openCollapse == index"
-        @open="openCollapse = index"
-      >
-        <template #trigger="props">
-          <div class="card-header" role="button">
-            <p class="card-header-title">
-              {{ scope.label }}
-            </p>
-            <a class="card-header-icon">
-              <b-icon :icon="props.open ? 'chevron-down' : 'chevron-up'"> </b-icon>
-            </a>
-          </div>
-        </template>
-        <div class="card-content">
-          <div class="content">
-            <CollapsibleTree
-              v-for="option in scope.options"
-              :key="option.id"
-              :option="option"
-              :withRadios="true"
-              :radioName="`dataTypeAuthorizations_${applicationName}_${dataTypeId}`"
-              @optionChecked="(value) => (scopeToAuthorize = value)"
-            />
-          </div>
-        </div>
-      </b-collapse>
-    </b-field>
+      <ValidationProvider rules="required" name="scopes" v-slot="{ errors, valid }" vid="scopes">
+        <b-field
+          :label="$t('dataTypeAuthorizations.authorization-scopes')"
+          class="mb-4"
+          :type="{
+            'is-danger': errors && errors.length > 0,
+            'is-success': valid,
+          }"
+          :message="errors[0]"
+        >
+          <b-collapse
+            class="card"
+            animation="slide"
+            v-for="(scope, index) of authorizationScopes"
+            :key="scope.id"
+            :open="openCollapse == index"
+            @open="openCollapse = index"
+          >
+            <template #trigger="props">
+              <div class="card-header" role="button">
+                <p class="card-header-title">
+                  {{ scope.label }}
+                </p>
+                <a class="card-header-icon">
+                  <b-icon :icon="props.open ? 'chevron-down' : 'chevron-up'"> </b-icon>
+                </a>
+              </div>
+            </template>
+            <div class="card-content">
+              <div class="content">
+                <CollapsibleTree
+                  v-for="option in scope.options"
+                  :key="option.id"
+                  :option="option"
+                  :withRadios="true"
+                  :radioName="`dataTypeAuthorizations_${applicationName}_${dataTypeId}`"
+                  @optionChecked="(value) => (scopeToAuthorize = value)"
+                />
+              </div>
+            </div>
+          </b-collapse>
+        </b-field>
+      </ValidationProvider>
+
+      <div class="buttons">
+        <b-button type="is-primary" @click="handleSubmit(createAuthorization)" icon-left="plus">
+          {{ $t("dataTypeAuthorizations.create") }}
+        </b-button>
+      </div>
+    </ValidationObserver>
   </PageView>
 </template>
 
@@ -141,11 +265,12 @@ import { AlertService } from "@/services/AlertService";
 import { ApplicationService } from "@/services/rest/ApplicationService";
 import { AuthorizationService } from "@/services/rest/AuthorizationService";
 import { UserPreferencesService } from "@/services/UserPreferencesService";
+import { ValidationObserver, ValidationProvider } from "vee-validate";
 import { Component, Prop, Vue, Watch } from "vue-property-decorator";
 import PageView from "../common/PageView.vue";
 
 @Component({
-  components: { PageView, SubMenu, CollapsibleTree },
+  components: { PageView, SubMenu, CollapsibleTree, ValidationObserver, ValidationProvider },
 })
 export default class DataTypeAuthorizationInfoView extends Vue {
   @Prop() dataTypeId;
@@ -169,11 +294,13 @@ export default class DataTypeAuthorizationInfoView extends Vue {
   users = [];
   dataGroups = [];
   authorizationScopes = [];
-  userToAuthorize = [];
-  dataGroupToAuthorize = {};
+  userToAuthorize = null;
+  dataGroupToAuthorize = null;
   openCollapse = null;
   scopeToAuthorize = null;
   period = this.periods.FROM_DATE;
+  startDate = null;
+  endDate = null;
 
   created() {
     this.init();
@@ -219,7 +346,6 @@ export default class DataTypeAuthorizationInfoView extends Vue {
         dataGroups: this.dataGroups,
         users: this.users,
       } = grantableInfos);
-      console.log(this.authorizationScopes, this.dataGroups, this.users);
       // this.authorizationScopes[0].options[0].children[0].children.push({
       //   children: [],
       //   id: "toto",
@@ -230,9 +356,17 @@ export default class DataTypeAuthorizationInfoView extends Vue {
     }
   }
 
-  @Watch("scopeToAuthorize")
-  onScopeToAuthorizeChanged() {
-    console.log(this.scopeToAuthorize);
+  @Watch("period")
+  onPeriodChanged() {
+    this.endDate = null;
+    this.startDate = null;
+  }
+
+  createAuthorization() {
+    console.log("CREATE AUTHORIZATION");
+    console.log(this.userToAuthorize);
+    console.log(this.startDate, this.endDate);
+    console.log(this.scopeToAuthorize, this.dataGroupToAuthorize);
   }
 }
 </script>
@@ -247,6 +381,7 @@ export default class DataTypeAuthorizationInfoView extends Vue {
 
 .DataTypeAuthorizationInfoView-radio-field {
   height: 40px;
+
   &.b-radio {
     .control-label {
       display: flex;
-- 
GitLab


From af398f288b94363c92fb6235825f515c4d26af14 Mon Sep 17 00:00:00 2001
From: Aurore Lecointe <lecointe@codelutin.com>
Date: Fri, 2 Jul 2021 16:01:58 +0200
Subject: [PATCH 19/26] =?UTF-8?q?Branche=20la=20cr=C3=A9ation=20d'autorisa?=
 =?UTF-8?q?tion?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 ui2/src/main.js                               |  8 +++++
 ui2/src/model/DataTypeAuthorization.js        |  9 +++++
 ui2/src/services/Fetcher.js                   | 10 ++++--
 ui2/src/services/rest/AuthorizationService.js |  8 +++++
 .../DataTypeAuthorizationInfoView.vue         | 34 +++++++++++++++----
 5 files changed, 60 insertions(+), 9 deletions(-)
 create mode 100644 ui2/src/model/DataTypeAuthorization.js

diff --git a/ui2/src/main.js b/ui2/src/main.js
index a8087dc40..d5ac2dbba 100644
--- a/ui2/src/main.js
+++ b/ui2/src/main.js
@@ -122,6 +122,14 @@ extend("validApplicationNameLength", {
   },
 });
 
+// extend("dateIsAfter", {
+//   message: i18n.t("validation.date-not-after").toString(),
+//   validate: (value, { min }: Record<string, any>) => {
+//     return isAfter(value, new Date(min))
+//   },
+//   params: ["min"],
+// })
+
 // Buefy
 Vue.use(Buefy, {
   defaultIconComponent: "vue-fontawesome",
diff --git a/ui2/src/model/DataTypeAuthorization.js b/ui2/src/model/DataTypeAuthorization.js
new file mode 100644
index 000000000..7b2f08f11
--- /dev/null
+++ b/ui2/src/model/DataTypeAuthorization.js
@@ -0,0 +1,9 @@
+export class DataTypeAuthorization {
+  userId;
+  applicationNameOrId;
+  dataType;
+  dataGroup;
+  authorizedScopes;
+  fromDay;
+  toDay;
+}
diff --git a/ui2/src/services/Fetcher.js b/ui2/src/services/Fetcher.js
index a5f89e63f..8047cece9 100644
--- a/ui2/src/services/Fetcher.js
+++ b/ui2/src/services/Fetcher.js
@@ -7,16 +7,20 @@ export const LOCAL_STORAGE_LANG = "lang";
 export const LOCAL_STORAGE_AUTHENTICATED_USER = "authenticatedUser";
 
 export class Fetcher {
-  async post(url, data) {
-    const formData = this.convertToFormData(data);
+  async post(url, data, withFormData = true) {
+    let body = JSON.stringify(data);
+    if (withFormData) {
+      body = this.convertToFormData(data);
+    }
 
     const response = await fetch(`${config.API_URL}${url}`, {
       method: "POST",
       mode: "cors",
       credentials: "include",
-      body: formData,
+      body: body,
       headers: {
         "Accept-Language": this.getUserPrefLocale(),
+        "Content-Type": "application/json;charset=UTF-8",
       },
     });
 
diff --git a/ui2/src/services/rest/AuthorizationService.js b/ui2/src/services/rest/AuthorizationService.js
index 7e8325be7..2942407fa 100644
--- a/ui2/src/services/rest/AuthorizationService.js
+++ b/ui2/src/services/rest/AuthorizationService.js
@@ -14,4 +14,12 @@ export class AuthorizationService extends Fetcher {
   async getAuthorizationGrantableInfos(applicationName, dataTypeId) {
     return this.get(`applications/${applicationName}/dataType/${dataTypeId}/grantable`);
   }
+
+  async createAuthorization(applicationName, dataTypeId, authorizationModel) {
+    return this.post(
+      `applications/${applicationName}/dataType/${dataTypeId}/authorization`,
+      authorizationModel,
+      false
+    );
+  }
 }
diff --git a/ui2/src/views/authorizations/DataTypeAuthorizationInfoView.vue b/ui2/src/views/authorizations/DataTypeAuthorizationInfoView.vue
index b56cd0b5d..cda0a64dc 100644
--- a/ui2/src/views/authorizations/DataTypeAuthorizationInfoView.vue
+++ b/ui2/src/views/authorizations/DataTypeAuthorizationInfoView.vue
@@ -241,7 +241,7 @@
                   :option="option"
                   :withRadios="true"
                   :radioName="`dataTypeAuthorizations_${applicationName}_${dataTypeId}`"
-                  @optionChecked="(value) => (scopeToAuthorize = value)"
+                  @optionChecked="(value) => (scopesToAuthorize[scope.id] = value)"
                 />
               </div>
             </div>
@@ -261,6 +261,7 @@
 <script>
 import CollapsibleTree from "@/components/common/CollapsibleTree.vue";
 import SubMenu, { SubMenuPath } from "@/components/common/SubMenu.vue";
+import { DataTypeAuthorization } from "@/model/DataTypeAuthorization";
 import { AlertService } from "@/services/AlertService";
 import { ApplicationService } from "@/services/rest/ApplicationService";
 import { AuthorizationService } from "@/services/rest/AuthorizationService";
@@ -297,7 +298,7 @@ export default class DataTypeAuthorizationInfoView extends Vue {
   userToAuthorize = null;
   dataGroupToAuthorize = null;
   openCollapse = null;
-  scopeToAuthorize = null;
+  scopesToAuthorize = {};
   period = this.periods.FROM_DATE;
   startDate = null;
   endDate = null;
@@ -363,10 +364,31 @@ export default class DataTypeAuthorizationInfoView extends Vue {
   }
 
   createAuthorization() {
-    console.log("CREATE AUTHORIZATION");
-    console.log(this.userToAuthorize);
-    console.log(this.startDate, this.endDate);
-    console.log(this.scopeToAuthorize, this.dataGroupToAuthorize);
+    const dataTypeAuthorization = new DataTypeAuthorization();
+    dataTypeAuthorization.userId = this.userToAuthorize;
+    dataTypeAuthorization.applicationNameOrId = this.applicationName;
+    dataTypeAuthorization.dataType = this.dataTypeId;
+    dataTypeAuthorization.dataGroup = this.dataGroupToAuthorize;
+    dataTypeAuthorization.authorizedScopes = this.scopesToAuthorize;
+    let fromDay = null;
+    if (this.startDate) {
+      fromDay = [
+        this.startDate.getFullYear(),
+        this.startDate.getMonth() + 1,
+        this.startDate.getDate(),
+      ];
+    }
+    dataTypeAuthorization.fromDay = fromDay;
+    let toDay = null;
+    if (this.endDate) {
+      toDay = [this.endDate.getFullYear(), this.endDate.getMonth() + 1, this.endDate.getDate()];
+    }
+    dataTypeAuthorization.toDay = toDay;
+    this.authorizationService.createAuthorization(
+      this.applicationName,
+      this.dataTypeId,
+      dataTypeAuthorization
+    );
   }
 }
 </script>
-- 
GitLab


From 29caa054fbd115c1310b0d4186af8de53c31f8f0 Mon Sep 17 00:00:00 2001
From: Aurore Lecointe <lecointe@codelutin.com>
Date: Fri, 2 Jul 2021 16:10:25 +0200
Subject: [PATCH 20/26] corrige le content-type des headers

---
 ui2/src/services/Fetcher.js | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

diff --git a/ui2/src/services/Fetcher.js b/ui2/src/services/Fetcher.js
index 8047cece9..35934f54f 100644
--- a/ui2/src/services/Fetcher.js
+++ b/ui2/src/services/Fetcher.js
@@ -12,16 +12,19 @@ export class Fetcher {
     if (withFormData) {
       body = this.convertToFormData(data);
     }
+    const headers = withFormData
+      ? { "Accept-Language": this.getUserPrefLocale() }
+      : {
+          "Accept-Language": this.getUserPrefLocale(),
+          "Content-Type": "application/json;charset=UTF-8;multipart/form-data",
+        };
 
     const response = await fetch(`${config.API_URL}${url}`, {
       method: "POST",
       mode: "cors",
       credentials: "include",
       body: body,
-      headers: {
-        "Accept-Language": this.getUserPrefLocale(),
-        "Content-Type": "application/json;charset=UTF-8",
-      },
+      headers: headers,
     });
 
     return this._handleResponse(response);
-- 
GitLab


From e9db9ddd0689213cc1db807664aab962123cb408 Mon Sep 17 00:00:00 2001
From: Aurore Lecointe <lecointe@codelutin.com>
Date: Fri, 2 Jul 2021 16:26:21 +0200
Subject: [PATCH 21/26] Affiche les autorisations

---
 ui2/src/locales/en.json                       |  5 +-
 ui2/src/locales/fr.json                       |  5 +-
 .../DataTypeAuthorizationsView.vue            | 53 +++++++++++++++++++
 3 files changed, 61 insertions(+), 2 deletions(-)

diff --git a/ui2/src/locales/en.json b/ui2/src/locales/en.json
index 7fa232053..6eefab499 100644
--- a/ui2/src/locales/en.json
+++ b/ui2/src/locales/en.json
@@ -126,6 +126,9 @@
     "from-date-to-date": "From date to date : ",
     "always": "Always",
     "to": "to",
-    "create": "Create authorization"
+    "create": "Create authorization",
+    "user": "User",
+    "data-group": "Data group",
+    "data-type": "Data type"
   }
 }
diff --git a/ui2/src/locales/fr.json b/ui2/src/locales/fr.json
index 2463ed647..52e56f1fd 100644
--- a/ui2/src/locales/fr.json
+++ b/ui2/src/locales/fr.json
@@ -126,6 +126,9 @@
     "from-date-to-date": "De date à date",
     "always": "Toujours",
     "to": "à",
-    "create": "Créer l'autorisation"
+    "create": "Créer l'autorisation",
+    "user": "Utilisateur",
+    "data-group": "Groupe de données",
+    "data-type": "Type de donnée"
   }
 }
diff --git a/ui2/src/views/authorizations/DataTypeAuthorizationsView.vue b/ui2/src/views/authorizations/DataTypeAuthorizationsView.vue
index c17419316..2c0d7457d 100644
--- a/ui2/src/views/authorizations/DataTypeAuthorizationsView.vue
+++ b/ui2/src/views/authorizations/DataTypeAuthorizationsView.vue
@@ -9,6 +9,55 @@
         {{ $t("dataTypeAuthorizations.add-auhtorization") }}
       </b-button>
     </div>
+
+    <b-table
+      :data="authorizations"
+      :striped="true"
+      :isFocusable="true"
+      :isHoverable="true"
+      :sticky-header="true"
+      :paginated="true"
+      :per-page="15"
+      height="100%"
+    >
+      <b-table-column
+        b-table-column
+        field="user"
+        :label="$t('dataTypeAuthorizations.user')"
+        sortable
+        v-slot="props"
+      >
+        {{ props.row.id }}
+      </b-table-column>
+      <b-table-column
+        b-table-column
+        field="dataType"
+        :label="$t('dataTypeAuthorizations.data-type')"
+        sortable
+        v-slot="props"
+      >
+        {{ props.row.dataType }}
+      </b-table-column>
+      <b-table-column
+        b-table-column
+        field="dataGroup"
+        :label="$t('dataTypeAuthorizations.data-group')"
+        sortable
+        v-slot="props"
+      >
+        {{ props.row.dataGroup }}
+      </b-table-column>
+      <b-table-column
+        v-for="scope in scopes"
+        :key="scope"
+        b-table-column
+        :label="scope"
+        sortable
+        v-slot="props"
+      >
+        {{ props.row.authorizedScopes[scope] }}
+      </b-table-column>
+    </b-table>
   </PageView>
 </template>
 
@@ -33,6 +82,7 @@ export default class DataTypeAuthorizationsView extends Vue {
 
   authorizations = [];
   application = {};
+  scopes = [];
 
   created() {
     this.init();
@@ -63,6 +113,9 @@ export default class DataTypeAuthorizationsView extends Vue {
         this.applicationName,
         this.dataTypeId
       );
+      if (this.authorizations && this.authorizations.length !== 0) {
+        this.scopes = Object.keys(this.authorizations[0].authorizedScopes);
+      }
     } catch (error) {
       this.alertService.toastServerError(error);
     }
-- 
GitLab


From 93d757ed10698f47307a46e374b92371096a3c6f Mon Sep 17 00:00:00 2001
From: Aurore Lecointe <lecointe@codelutin.com>
Date: Fri, 2 Jul 2021 16:38:19 +0200
Subject: [PATCH 22/26] =?UTF-8?q?Branche=20la=20r=C3=A9vocation?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 ui2/src/locales/en.json                          |  4 +++-
 ui2/src/locales/fr.json                          |  4 +++-
 ui2/src/services/rest/AuthorizationService.js    |  6 ++++++
 .../DataTypeAuthorizationsView.vue               | 16 +++++++++++++++-
 4 files changed, 27 insertions(+), 3 deletions(-)

diff --git a/ui2/src/locales/en.json b/ui2/src/locales/en.json
index 6eefab499..51969014a 100644
--- a/ui2/src/locales/en.json
+++ b/ui2/src/locales/en.json
@@ -129,6 +129,8 @@
     "create": "Create authorization",
     "user": "User",
     "data-group": "Data group",
-    "data-type": "Data type"
+    "data-type": "Data type",
+    "actions": "Actions",
+    "revoke": "Revoke"
   }
 }
diff --git a/ui2/src/locales/fr.json b/ui2/src/locales/fr.json
index 52e56f1fd..a9b6aa08a 100644
--- a/ui2/src/locales/fr.json
+++ b/ui2/src/locales/fr.json
@@ -129,6 +129,8 @@
     "create": "Créer l'autorisation",
     "user": "Utilisateur",
     "data-group": "Groupe de données",
-    "data-type": "Type de donnée"
+    "data-type": "Type de donnée",
+    "actions": "Actions",
+    "revoke": "Révoquer"
   }
 }
diff --git a/ui2/src/services/rest/AuthorizationService.js b/ui2/src/services/rest/AuthorizationService.js
index 2942407fa..c369b7110 100644
--- a/ui2/src/services/rest/AuthorizationService.js
+++ b/ui2/src/services/rest/AuthorizationService.js
@@ -22,4 +22,10 @@ export class AuthorizationService extends Fetcher {
       false
     );
   }
+
+  async revokeAuthorization(applicationName, dataTypeId, authorizationId) {
+    return this.delete(
+      `applications/${applicationName}/dataType/${dataTypeId}/authorization/${authorizationId}`
+    );
+  }
 }
diff --git a/ui2/src/views/authorizations/DataTypeAuthorizationsView.vue b/ui2/src/views/authorizations/DataTypeAuthorizationsView.vue
index 2c0d7457d..59dd8898e 100644
--- a/ui2/src/views/authorizations/DataTypeAuthorizationsView.vue
+++ b/ui2/src/views/authorizations/DataTypeAuthorizationsView.vue
@@ -27,7 +27,7 @@
         sortable
         v-slot="props"
       >
-        {{ props.row.id }}
+        {{ props.row.user }}
       </b-table-column>
       <b-table-column
         b-table-column
@@ -57,6 +57,16 @@
       >
         {{ props.row.authorizedScopes[scope] }}
       </b-table-column>
+      <b-table-column b-table-column :label="$t('dataTypeAuthorizations.actions')" v-slot="props">
+        <b-button
+          type="is-danger"
+          size="is-small"
+          @click="revoke(props.row.id)"
+          icon-left="trash-alt"
+        >
+          {{ $t("dataTypeAuthorizations.revoke") }}
+        </b-button>
+      </b-table-column>
     </b-table>
   </PageView>
 </template>
@@ -126,5 +136,9 @@ export default class DataTypeAuthorizationsView extends Vue {
       `/applications/${this.applicationName}/dataTypes/${this.dataTypeId}/authorizations/new`
     );
   }
+
+  revoke(id) {
+    this.authorizationService.revokeAuthorization(this.applicationName, this.dataTypeId, id);
+  }
 }
 </script>
-- 
GitLab


From d76089860034376d0593f75e88beb6dbd6c113f0 Mon Sep 17 00:00:00 2001
From: Aurore Lecointe <lecointe@codelutin.com>
Date: Fri, 2 Jul 2021 16:48:14 +0200
Subject: [PATCH 23/26] Ajoute des messages

---
 ui2/src/locales/en.json                       |  4 ++-
 ui2/src/locales/fr.json                       |  4 ++-
 .../DataTypeAuthorizationInfoView.vue         | 21 ++++++++++-----
 .../DataTypeAuthorizationsView.vue            | 27 +++++++++++--------
 4 files changed, 37 insertions(+), 19 deletions(-)

diff --git a/ui2/src/locales/en.json b/ui2/src/locales/en.json
index 51969014a..ad1f74668 100644
--- a/ui2/src/locales/en.json
+++ b/ui2/src/locales/en.json
@@ -39,7 +39,9 @@
     "reference-csv-upload-error": "An error occured while uploading the csv file",
     "reference-updated": "Reference updated",
     "data-updated": "Data type updated",
-    "registered-user": "User registered"
+    "registered-user": "User registered",
+    "revoke-authorization": "Authorization revoked",
+    "create-authorization": "Authorization created"
   },
   "message": {
     "app-config-error": "Error in yaml file",
diff --git a/ui2/src/locales/fr.json b/ui2/src/locales/fr.json
index a9b6aa08a..6d3bd7013 100644
--- a/ui2/src/locales/fr.json
+++ b/ui2/src/locales/fr.json
@@ -39,7 +39,9 @@
     "reference-csv-upload-error": "Une erreur s'est produite au téléversement du fichier csv",
     "reference-updated": "Référentiel mis à jour",
     "data-updated": "Type de donnée mis à jour",
-    "registered-user": "Compte utilisateur créé"
+    "registered-user": "Compte utilisateur créé",
+    "revoke-authorization": "Autorisation révoquée",
+    "create-authorization": "Autorisation créée"
   },
   "message": {
     "app-config-error": "Erreur dans le fichier yaml",
diff --git a/ui2/src/views/authorizations/DataTypeAuthorizationInfoView.vue b/ui2/src/views/authorizations/DataTypeAuthorizationInfoView.vue
index cda0a64dc..b7af2cfd9 100644
--- a/ui2/src/views/authorizations/DataTypeAuthorizationInfoView.vue
+++ b/ui2/src/views/authorizations/DataTypeAuthorizationInfoView.vue
@@ -363,7 +363,7 @@ export default class DataTypeAuthorizationInfoView extends Vue {
     this.startDate = null;
   }
 
-  createAuthorization() {
+  async createAuthorization() {
     const dataTypeAuthorization = new DataTypeAuthorization();
     dataTypeAuthorization.userId = this.userToAuthorize;
     dataTypeAuthorization.applicationNameOrId = this.applicationName;
@@ -384,11 +384,20 @@ export default class DataTypeAuthorizationInfoView extends Vue {
       toDay = [this.endDate.getFullYear(), this.endDate.getMonth() + 1, this.endDate.getDate()];
     }
     dataTypeAuthorization.toDay = toDay;
-    this.authorizationService.createAuthorization(
-      this.applicationName,
-      this.dataTypeId,
-      dataTypeAuthorization
-    );
+
+    try {
+      await this.authorizationService.createAuthorization(
+        this.applicationName,
+        this.dataTypeId,
+        dataTypeAuthorization
+      );
+      this.alertService.toastSuccess(this.$t("alert.create-authorization"));
+      this.$router.push(
+        `/applications/${this.applicationName}/dataTypes/${this.dataTypeId}/authorizations`
+      );
+    } catch (error) {
+      this.alertService.toastServerError(error);
+    }
   }
 }
 </script>
diff --git a/ui2/src/views/authorizations/DataTypeAuthorizationsView.vue b/ui2/src/views/authorizations/DataTypeAuthorizationsView.vue
index 59dd8898e..df8a1f11d 100644
--- a/ui2/src/views/authorizations/DataTypeAuthorizationsView.vue
+++ b/ui2/src/views/authorizations/DataTypeAuthorizationsView.vue
@@ -29,15 +29,7 @@
       >
         {{ props.row.user }}
       </b-table-column>
-      <b-table-column
-        b-table-column
-        field="dataType"
-        :label="$t('dataTypeAuthorizations.data-type')"
-        sortable
-        v-slot="props"
-      >
-        {{ props.row.dataType }}
-      </b-table-column>
+
       <b-table-column
         b-table-column
         field="dataGroup"
@@ -137,8 +129,21 @@ export default class DataTypeAuthorizationsView extends Vue {
     );
   }
 
-  revoke(id) {
-    this.authorizationService.revokeAuthorization(this.applicationName, this.dataTypeId, id);
+  async revoke(id) {
+    try {
+      await this.authorizationService.revokeAuthorization(
+        this.applicationName,
+        this.dataTypeId,
+        id
+      );
+      this.alertService.toastSuccess(this.$t("alert.revoke-authorization"));
+      this.authorizations.splice(
+        this.authorizations.findIndex((a) => a.id === id),
+        1
+      );
+    } catch (error) {
+      this.alertService.toastServerError(error);
+    }
   }
 }
 </script>
-- 
GitLab


From 4f759049890dd1d877bacfd7d2e976bda07c7f1a Mon Sep 17 00:00:00 2001
From: Aurore Lecointe <lecointe@codelutin.com>
Date: Wed, 7 Jul 2021 12:32:03 +0200
Subject: [PATCH 24/26] =?UTF-8?q?Ajoute=20la=20colonne=20de=20p=C3=A9riode?=
 =?UTF-8?q?=20pr=20les=20autorisations?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../DataTypeAuthorizationsView.vue            | 34 +++++++++++++++++++
 1 file changed, 34 insertions(+)

diff --git a/ui2/src/views/authorizations/DataTypeAuthorizationsView.vue b/ui2/src/views/authorizations/DataTypeAuthorizationsView.vue
index df8a1f11d..9eaf738f5 100644
--- a/ui2/src/views/authorizations/DataTypeAuthorizationsView.vue
+++ b/ui2/src/views/authorizations/DataTypeAuthorizationsView.vue
@@ -39,6 +39,15 @@
       >
         {{ props.row.dataGroup }}
       </b-table-column>
+      <b-table-column
+        b-table-column
+        field="dataGroup"
+        :label="$t('dataTypeAuthorizations.period')"
+        sortable
+        v-slot="props"
+      >
+        {{ getPeriod(props.row) }}
+      </b-table-column>
       <b-table-column
         v-for="scope in scopes"
         :key="scope"
@@ -85,6 +94,12 @@ export default class DataTypeAuthorizationsView extends Vue {
   authorizations = [];
   application = {};
   scopes = [];
+  periods = {
+    FROM_DATE: this.$t("dataTypeAuthorizations.from-date"),
+    TO_DATE: this.$t("dataTypeAuthorizations.to-date"),
+    FROM_DATE_TO_DATE: this.$t("dataTypeAuthorizations.from-date-to-date"),
+    ALWAYS: this.$t("dataTypeAuthorizations.always"),
+  };
 
   created() {
     this.init();
@@ -115,6 +130,7 @@ export default class DataTypeAuthorizationsView extends Vue {
         this.applicationName,
         this.dataTypeId
       );
+      console.log(this.authorizations);
       if (this.authorizations && this.authorizations.length !== 0) {
         this.scopes = Object.keys(this.authorizations[0].authorizedScopes);
       }
@@ -145,5 +161,23 @@ export default class DataTypeAuthorizationsView extends Vue {
       this.alertService.toastServerError(error);
     }
   }
+
+  getPeriod(authorization) {
+    if (!authorization.fromDay && !authorization.toDay) {
+      return this.periods.ALWAYS;
+    } else if (authorization.fromDay && !authorization.toDay) {
+      return (
+        this.periods.FROM_DATE +
+        ` ${authorization.fromDay[2]}/${authorization.fromDay[1]}/${authorization.fromDay[0]}`
+      );
+    } else if (!authorization.fromDay && authorization.toDay) {
+      return (
+        this.periods.TO_DATE +
+        ` ${authorization.toDay[2]}/${authorization.toDay[1]}/${authorization.toDay[0]}`
+      );
+    } else {
+      return `${authorization.fromDay[2]}/${authorization.fromDay[1]}/${authorization.fromDay[0]} - ${authorization.toDay[2]}/${authorization.toDay[1]}/${authorization.toDay[0]}`;
+    }
+  }
 }
 </script>
-- 
GitLab


From a221e7e6e8eb4463a96e4a8fb09dc9fac3013d6f Mon Sep 17 00:00:00 2001
From: Aurore Lecointe <lecointe@codelutin.com>
Date: Thu, 8 Jul 2021 10:13:57 +0200
Subject: [PATCH 25/26] Corrige autorisation quand il y a plusieurs scopes

---
 ui2/src/views/authorizations/DataTypeAuthorizationInfoView.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/ui2/src/views/authorizations/DataTypeAuthorizationInfoView.vue b/ui2/src/views/authorizations/DataTypeAuthorizationInfoView.vue
index b7af2cfd9..569c48be3 100644
--- a/ui2/src/views/authorizations/DataTypeAuthorizationInfoView.vue
+++ b/ui2/src/views/authorizations/DataTypeAuthorizationInfoView.vue
@@ -240,7 +240,7 @@
                   :key="option.id"
                   :option="option"
                   :withRadios="true"
-                  :radioName="`dataTypeAuthorizations_${applicationName}_${dataTypeId}`"
+                  :radioName="`dataTypeAuthorizations_${applicationName}_${dataTypeId}_${option.id}`"
                   @optionChecked="(value) => (scopesToAuthorize[scope.id] = value)"
                 />
               </div>
-- 
GitLab


From 858cef5ddd89e5c6ea1a26240a2a36b144ac11ab Mon Sep 17 00:00:00 2001
From: Aurore Lecointe <lecointe@codelutin.com>
Date: Mon, 2 Aug 2021 13:04:05 +0200
Subject: [PATCH 26/26] Stringify les erreurs pour l'affichage.

---
 ui2/src/locales/en.json           | 3 ++-
 ui2/src/locales/fr.json           | 5 +++--
 ui2/src/services/ErrorsService.js | 9 ++++++++-
 3 files changed, 13 insertions(+), 4 deletions(-)

diff --git a/ui2/src/locales/en.json b/ui2/src/locales/en.json
index ad1f74668..780299ed3 100644
--- a/ui2/src/locales/en.json
+++ b/ui2/src/locales/en.json
@@ -99,7 +99,8 @@
     "invalidDate": "For the component : <code>{variableComponentKey}</code> the date <code>{value}</code> doesn't match expected pattern : <code>{pattern}</code>. ",
     "invalidInteger": "For the component : <code>{variableComponentKey}</code> the value <code>{value}</code> must be an integer.",
     "invalidFloat": "For the component : <code>{variableComponentKey}</code> the value <code>{value}</code> must be a float.",
-    "checkerExpressionReturnedFalse": "The following checker expression isn't fulfilled : <code>{expression}</code>"
+    "checkerExpressionReturnedFalse": "The following checker expression isn't fulfilled : <code>{expression}</code>",
+    "invalidReference": "Référence non valide. La référence <code>{value}</code> à la ligne <code>{lineNumber}</code>."
   },
   "referencesManagement": {
     "actions": "Actions",
diff --git a/ui2/src/locales/fr.json b/ui2/src/locales/fr.json
index 6d3bd7013..763f71b82 100644
--- a/ui2/src/locales/fr.json
+++ b/ui2/src/locales/fr.json
@@ -96,10 +96,11 @@
     "invalidHeaders": "En-têtes de colonne invalides. Les colonnes attendues sont : <code>{expectedColumns}</code><br/>Les colonnes actuelles sont : <code>{actualColumns}</code><br/> Il manque les colonnes : <code>{missingColumns}</code><br/>Les colonnes suivantes sont inconnues : <code>{unknownColumns}</code>",
     "duplicatedHeaders": "Les en-têtes suivants sont dupliqués : <code>{duplicatedHeaders}</code>",
     "patternNotMatched": "Pour le composant identifié : <code>{variableComponentKey}</code> la valeur <code>{value}</code> ne respecte pas le format attendu : <code>{pattern}</code>.",
-    "invalidDate": "Pour le composant identifié : <code>{variableComponentKey}</code> la date <code>{value}</code> ne respecte pas le format attendu : <code>{pattern}</code>. ",
+    "invalidDate": "Pour le composant identifié : <code>{variableComponentKey}</code>. La date <code>{value}</code> à la ligne <code>{lineNumber}</code> ne respecte pas le format attendu : <code>{pattern}</code>. ",
     "invalidInteger": "Pour le composant identifié : <code>{variableComponentKey}</code> la valeur <code>{value}</code> doit être un entier.",
     "invalidFloat": "Pour le composant identifié : <code>{variableComponentKey}</code> la valeur <code>{value}</code> doit être un nombre décimal.",
-    "checkerExpressionReturnedFalse": "La contrainte suivante n'est pas respectée : <code>{expression}</code>"
+    "checkerExpressionReturnedFalse": "La contrainte suivante n'est pas respectée : <code>{expression}</code>",
+    "invalidReference": "Référence non valide. La référence <code>{value}</code> à la ligne <code>{lineNumber}</code> n'est pas valide."
   },
   "referencesManagement": {
     "actions": "Actions",
diff --git a/ui2/src/services/ErrorsService.js b/ui2/src/services/ErrorsService.js
index e514a88c2..41b54bf6e 100644
--- a/ui2/src/services/ErrorsService.js
+++ b/ui2/src/services/ErrorsService.js
@@ -34,6 +34,7 @@ const ERRORS = {
   invalidInteger : (params) => i18n.t("errors.invalidInteger", params),
   invalidFloat : (params) => i18n.t("errors.invalidFloat", params),
   checkerExpressionReturnedFalse : (params) => i18n.t("errors.checkerExpressionReturnedFalse", params),
+  invalidReference: (params) => i18n.t("errors.invalidReference", params)
 };
 
 export class ErrorsService {
@@ -58,9 +59,15 @@ export class ErrorsService {
             csvError.validationCheckResult.message
         );
       }
+      const messageParams = csvError.validationCheckResult.messageParams;
+
+      Object.entries(messageParams).forEach(([key, value]) => {
+        messageParams[key] = JSON.stringify(value);
+      });
+
       const params = {
         lineNumber: csvError.lineNumber,
-        ...csvError.validationCheckResult.messageParams,
+        ...messageParams,
       };
       return func(params);
     });
-- 
GitLab