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