Rule.vue 13.8 KB
Newer Older
1
<template>
David Foucher's avatar
David Foucher committed
2
  <div id="Rule">
3 4 5 6 7 8 9
    <div v-if="!isLoading">
      <div class="mb-5">
        <div class="row mb-3">
          <div  class="col-md-6 col-sm-12 col-xs-12">
            <h5>
              <span style="vertical-align:-30%" >{{ displayedName }}</span>
            </h5>
10
            <div v-if="this.modifiedHashFlag">
Lucas Détré's avatar
Lucas Détré committed
11
              <a :href="'#'+name" v-if="this.modification_count" @click="displayList()" id="modification_link">Voir la règle</a>
12 13 14 15
            </div>
            <div v-else>
              <a :href="'#'+name+'#modified'" v-if="this.modification_count" @click="displayModification()" id="modification_link">({{ this.modification_count }} modification<span v-if="this.modification_count > 1">s</span> en cours)</a>
            </div>
16 17 18
          </div>
          <div class="col-md-6 col-sm-12 col-xs-12">
            <h4 v-if="isEditMode" class="pull-right"><em>Modification de la règle</em></h4>
19
            <input v-else v-b-modal.auth-modal type="button" class="main-button-primary btn pull-right" value="Soumettre une modification"/>
20 21
            <!-- TODO: show gitlab link of modification if exists -->
          </div>
22
        </div>
23 24 25 26
        <div v-show="!isEditMode" class="row">
          <div class="col-md-12">
            <span v-html="printRulePath"></span>
          </div>
27 28
        </div>
      </div>
29 30 31 32
      <div v-if="viewModification">
        <ul v-for="modification in modification_list" :key="modification.id">
          <Modification :modification="modification"/>
        </ul>
33
      </div>
34 35 36 37
      <div v-show="!isEditMode && !viewModification">
        <ul>
          <TreeItem class="item" :item="this.ruleTree" :rootElement="true" :rulePath="this.rulePath"></TreeItem>
        </ul>
38
      </div>
39 40
      <div v-show="isEditMode">
        <div class="container">
41 42 43 44 45 46 47 48
          <div class="row mb-3">
            <div class="col-md-6 pl-0">
              <input @click="closeEdit" type="button" class="btn btn-outline-danger pull-left" value="Annuler"/>
            </div>
            <div class="col-md-6 pr-0">
              <button v-b-modal.mail-modal class="btn main-button-primary pull-right">Enregistrer</button>
            </div>
          </div>
49 50 51 52 53 54 55 56 57 58
          <div class="row mb-3">
            <label for="content"><u>Contenu de la règle</u></label>
            <textarea-autosize id="content" v-model="content" class="rule-modification-text" :class="{editErrorClass: error_flags.notModified }"></textarea-autosize>
            <span v-if="error_flags.notModified" class="text-danger font-weight-light">Aucune modification n'a été renseignée</span>
          </div>
          <div class="row mb-3">
            <div class="col-md-6 pl-0">
              <input @click="closeEdit" type="button" class="btn btn-outline-danger pull-left" value="Annuler"/>
            </div>
            <div class="col-md-6 pr-0">
59
              <button v-b-modal.mail-modal class="btn main-button-primary pull-right">Enregistrer</button>
60 61 62
            </div>
          </div>
        </div>
63
        <b-modal id="auth-modal" title="Authentifiez-vous pour soumettre une modification">
64
          <label for="contributor_email" class="pb-2"><u>Votre email</u> *</label><br>
65 66
          <input id="contributor_email" v-model="auth.email" type="text" :class="{editErrorClass: (error_flags.badUser || error_flags.noUser)}" class="form-control" style="border: 1px solid #bfbfbf; border-radius: 2px;"/><br>
          <span v-if="error_flags.noUser" class="mt-0 text-danger font-weight-light">Ce champ est obligatoire</span><br>
67
          <label for="contributor_passwd" class="pb-2"><u>Votre mot de passe</u> *</label><br>
68 69 70 71
          <input id="contributor_passwd" v-model="auth.password" type="password" :class="{editErrorClass: (error_flags.badUser || error_flags.noPass)}" class="form-control" style="border: 1px solid #bfbfbf; border-radius: 2px;"/><br>
          <span v-if="error_flags.badUser" class="text-danger font-weight-light">Cet utilisateur n'est pas autorisé à soumettre des modifications<br></span>
          <span v-if="error_flags.noPass" class="text-danger font-weight-light">Ce champ est obligatoire<br></span>
          <span v-if="!error_flags.noUser && !error_flags.noPass" class="font-weight-light">* Champs obligatoires<br></span>
72
          <template v-slot:modal-footer>       
73
            <input @click="auth_to_edit" type="button" class="btn main-button-primary pull-right" value="Suivant"/>
74 75 76 77 78
          </template>
        </b-modal>

        <b-modal id="mail-modal" title="Soumettre votre modification">
          <label for="comment" class="mb-2"><u>Résumé de la modification</u> * </label>
79 80 81
          <textarea id="comment" v-model="comment" :class="{editErrorClass: error_flags.noResume}" rows="3"></textarea>
          <span v-if="error_flags.noResume" class="text-danger font-weight-light">Ce champ est obligatoire</span>
          <span v-if="!error_flags.noUser && !error_flags.noResume" class="font-weight-light">* Champ obligatoire</span>
82
          <template v-slot:modal-footer>       
83
            <input @click="save" type="button" class="btn main-button-primary pull-right" value="Enregistrer"/>
84 85 86
          </template>
        </b-modal>

87 88
      </div>
    </div>
89 90 91
    <div v-else class="text-center loading-gif">
      <img src="./../assets/images/loading.gif" alt="loading...">
    </div>    
92
  </div>
93 94
</template>
<script>
95
  import TreeItem from './TreeItem.vue';
96
  import Modification from './Modification.vue';
97 98 99 100 101 102 103

  function Node(name) {
    this.name = name;
    this.parent = null;
    this.children = [];
  }

David Foucher's avatar
David Foucher committed
104
  export default {
105
    name: 'Rule',
106 107
    components: {
      TreeItem,
108
      Modification
109
    },
110
    props: ['name', 'data', 'path', 'printRulePath', 'rulePath'],
111 112
    data: function(){
      return {
113 114
        windowLocationHash: '',
        modifiedHashFlag: decodeURI(window.location.hash).split('#').pop() == "modified",
115
        isLoading: true,
116
        ruleData: this.data,
117
        modification_list: [],
118 119
        content: '',
        comment: '',
120
        isEditMode: '',
121
        viewModification: false,
122 123 124 125 126 127 128 129 130 131 132 133
        auth: {
          email: '',
          password: '',
          file: this.rulePath
        },
        error_flags: {
          badUser: false,
          notModified: false,
          noUser: false,
          noPass: false,
          noResume: false,
        },
134 135
      }
    },
136
    created: function() {
137
      this.loadInProgressModification();
138
      this.updateLayout();
139 140

      window.addEventListener('popstate', () => {
141
        this.updateLayout();             
142
      })
143
    },
144
    computed: {
145 146 147 148
      displayedName: function () {
        return this.name.split('.')[0];
      },
      modification_count: function () {
149
        return Object.keys(this.modification_list).length;
150
      },
151 152 153 154 155 156 157
      ruleTree: function() {
        return this.toTree(this.ruleData.split('\n'));
      },
      ruleToEdit: function() {
        return this.ruleData;
      },
    },
158
    methods: {
159 160 161 162 163 164 165 166 167 168 169
      updateLayout: function() {
        this.windowLocationHash = decodeURI(window.location.hash);
        this.modifiedHashFlag = decodeURI(window.location.hash).split('#').pop() == "modified";
        if (this.modifiedHashFlag) {
          this.currentRuleName = decodeURI(window.location.hash).split('#')[1];
          this.displayModification();
        } else {
          this.currentRuleName = decodeURI(window.location.hash).split('#').pop();
          this.viewModification = false;
        }  
      },
170 171
      loadInProgressModification: function () {
        this.$http
172
          .get('/source/modified?branch='+encodeURIComponent(this.displayedName))
173
          .then(response => {
174
            this.modification_list = response.body;
175
            this.isLoading = false;
176 177 178 179
            return true;
          }, response => {
            if(response.status == 500) this.modification_list = {};
            return false;
180 181
          })
      },
182
      auth_to_edit: function () {
183 184
        this.error_flags.noUser = false;
        this.error_flags.noPass = false;
185

186 187
        if (this.auth.email== '' ) {
          this.error_flags.noUser = true;
188
        }
189 190
        if (this.auth.password== '' ) {
          this.error_flags.noPass = true;
191 192
          return false;
        }
193 194
        this.$http
          .post('/authentification', this.auth)
195
          .then(() => {
196 197
            this.viewModification = false;
            this.$parent.collapsed = true;
198 199 200 201 202
            this.content = this.ruleToEdit;
            this.isEditMode=!this.isEditMode;
            this.$bvModal.hide("auth-modal");
          }, error => { 
            if (error.status == 401) {
203 204 205
              this.error_flags.badUser = true;
              this.error_flags.noUser = false;
              this.error_flags.noResume = false;
206 207 208
            }
            else if (error.status == 422) {
              if (error.body.args == "`user` est vide") {
209 210
                this.error_flags.noUser = true;
                this.error_flags.badUser = false;
211
              } else if (error.body.args == "`pass` est vide") {
212 213
                this.error_flags.noResume = true;
                this.error_flags.noUser = false;
214
              }
215
              this.error_flags.badUser = false;
216
            }
217
          });
218 219 220 221
      },
      closeEdit: function () {
        this.content = this.ruleToEdit;
        this.comment = "";
222
        this.auth = "";
223
        this.isEditMode=!this.isEditMode;
224 225 226 227 228 229 230
        this.error_flags = {
          badUser: false,
          notModified: false,
          noUser: false,
          noPass: false,
          noResume: false,
        };
231 232
      },
      save: function() {
233 234
        this.$parent.isLoading = true;
        this.$parent.modificationInProgress = true;
235
        this.ruleData = this.content
236
        if (this.comment == '') {
237
          this.error_flags.noResume = true;
238 239
          return false;
        }
240
        const postData = {
241 242
          author_email:this.auth.email,
          author_name:this.auth.email.split("@")[0],
243
          title: this.displayedName,
244 245
          comment: this.comment,
          content: this.content,
246
          filename: 'trefle/config/rules/' + this.path
247 248 249 250 251
        }

        this.$http
          .post('/source/save', postData)
          .then(response => {
252 253 254 255 256
            var commit = {}
            commit.url = 'https://beta.pole-emploi.fr/open-source/trefle/commit/' + response.id
            commit.title = response.title
            this.$parent.rules[this.name]['data'] = this.data
            this.$parent.rules[this.name]['gitlab'] = {'commit': commit}
257 258
            this.$bvModal.hide("mail-modal");
            return this.isEditMode=!this.isEditMode            
259 260
          }, error => {
              if(error.status == 304){
261 262 263 264
                this.error_flags.notModified = true;
                this.error_flags.noUser = false;
                this.error_flags.noResume = false;
                this.error_flags.badUser = false;
265
                this.$bvModal.hide("mail-modal");
266
              } 
267
              else if (error.status == 401) {
268 269 270 271
                this.error_flags.badUser = true;
                this.error_flags.noUser = false;
                this.error_flags.notModified = false;
                this.error_flags.noResume = false;
272 273 274
              }
              else if (error.status == 422) {
                if (error.body.args == "`author_email` est vide") {
275 276
                  this.error_flags.noUser = true;
                  this.error_flags.badUser = false;
277
                } else if (error.body.args == "`comment` est vide") {
278 279
                  this.error_flags.noResume = true;
                  this.error_flags.noUser = false;
280
                }
281 282
                this.error_flags.badUser = false;
                this.error_flags.notModified = false;
283 284
              }
              return false;
285 286 287 288
          }).then(()=> {
            this.$parent.isLoading = false;
            this.$parent.modificationInProgress = false;
            location.reload();
289 290 291
          });
      },
      toTree: function (lines) { // eslint-disable-line no-unused-vars
292
        var root= new Node(this.name.split('.')[0]);
293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327
        var currentIndent=-1;
        var currentNode=root;
        // we scan the rules line by line
        for (var i=0;i<lines.length;i++) {
          var line=lines[i];
          if (line.match(/^\s*$/)) continue; // Empty line, skip
          var indent=line.search(/\S|$/); // number of indenting spaces
          var newNode= new Node(line.trim());
          if (indent>currentIndent) { // New child
            // Set the new node parent
            newNode.parent=currentNode;
            // attach the new node to its parent
            currentNode.children.push(newNode);
            // Set the new current node
            currentNode=newNode;
            currentIndent=indent;
          } else if (indent<currentIndent){
            // Move up in the tree
            var level=currentIndent-indent;
            for (var j=0;j<level/4;j++) {
              // up one level
              currentNode=currentNode.parent;
              currentIndent=currentIndent-4;
            }
            newNode.parent=currentNode.parent;
            currentNode.parent.children.push(newNode); // Add a sibbling
            currentNode=newNode;
          } else {
            // Add as sibbling
            newNode.parent=currentNode.parent;
            currentNode.parent.children.push(newNode);
            currentNode=newNode;
          }
        }
        return root;
328 329
      },
      displayModification: function () {
330 331 332 333 334 335
        this.viewModification = true;
        this.$parent.collapsed = true;
        this.isEditMode = false;
      },
      displayList: function () {
        this.viewModification = false;
336
        this.$parent.collapsed = true;
337
        this.isEditMode = false;
338
      }
339
    },
David Foucher's avatar
David Foucher committed
340 341
  }
</script>
342
<style scoped>
343 344 345
.editErrorClass {
  color: #dc3545;
  border: 1px solid #dc3545;
346 347
  border-radius:3px;
}
348 349 350
.rule-modification-text {
  font-family: 'Courier New', Courier, monospace;
}
351 352 353 354 355

textarea {
  border: 1px solid #bfbfbf;
  border-radius: 2px;
}
356

357 358 359 360 361 362 363 364 365
#modification_link {
  cursor:pointer;
  color:blue;
}

#modification_link:hover {
  text-decoration: underline;
}

366
</style>