Rule.vue 14.9 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-if="this.modifiedHashFlag | this.newModification" 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
          <div v-if="content" class="row mb-3">
50 51 52 53 54 55 56 57 58
            <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 118
        modification_list: {},
        commit_id: '',
119 120
        content: '',
        comment: '',
121
        filename: 'trefle/config/rules/' + this.path,
122
        isEditMode: '',
123
        viewModification: false,
124 125 126
        auth: {
          email: '',
          password: '',
127
          file: this.path
128 129 130 131 132 133 134 135
        },
        error_flags: {
          badUser: false,
          notModified: false,
          noUser: false,
          noPass: false,
          noResume: false,
        },
136 137
      }
    },
138
    created: function() {
139
      this.loadInProgressModification();
140
      this.updateLayout();
141 142

      window.addEventListener('popstate', () => {
143
        this.updateLayout();
144
      })
145
    },
146
    computed: {
147 148 149 150
      displayedName: function () {
        return this.name.split('.')[0];
      },
      modification_count: function () {
151
        return Object.keys(this.modification_list).length;
152
      },
153 154 155 156 157 158
      ruleTree: function() {
        return this.toTree(this.ruleData.split('\n'));
      },
      ruleToEdit: function() {
        return this.ruleData;
      },
159 160 161
      newModification: function() {
        return Boolean(!this.modification_count)
      }
162
    },
163
    methods: {
164 165 166 167 168 169 170 171 172 173 174
      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;
        }  
      },
175 176
      loadInProgressModification: function () {
        this.$http
177
          .get('/source/modified?branch='+encodeURIComponent(this.displayedName))
178
          .then(response => {
179
            this.modification_list = response.body;
180 181 182
            if(Object.keys(this.modification_list).length) {
              this.commit_id = this.modification_list[Object.keys(this.modification_list)[0]].id;
            }
183
            this.isLoading = false;
184 185 186 187
            return true;
          }, response => {
            if(response.status == 500) this.modification_list = {};
            return false;
188 189
          })
      },
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
      getContentRule: function(){
        if(this.commit_id) {
          let data_param = {
            'file': this.filename,
            'commit_id': this.commit_id,
          }
          this.$http
            .get('/source/file', {params: data_param})
            .then(response => {
              this.content = response.body;
              return true;
            }, response => {
              if(response.status == 500) this.content = '';
              return false;
            })
         } else {
           this.content = this.ruleData
         }
      },
209
      auth_to_edit: function () {
210 211
        this.error_flags.noUser = false;
        this.error_flags.noPass = false;
212

213 214
        if (this.auth.email== '' ) {
          this.error_flags.noUser = true;
215
        }
216 217
        if (this.auth.password== '' ) {
          this.error_flags.noPass = true;
218 219
          return false;
        }
220 221
        this.$http
          .post('/authentification', this.auth)
222
          .then(() => {
223 224
            this.viewModification = false;
            this.$parent.collapsed = true;
225
            this.getContentRule();
226 227 228 229
            this.isEditMode=!this.isEditMode;
            this.$bvModal.hide("auth-modal");
          }, error => { 
            if (error.status == 401) {
230 231 232
              this.error_flags.badUser = true;
              this.error_flags.noUser = false;
              this.error_flags.noResume = false;
233 234 235
            }
            else if (error.status == 422) {
              if (error.body.args == "`user` est vide") {
236 237
                this.error_flags.noUser = true;
                this.error_flags.badUser = false;
238
              } else if (error.body.args == "`pass` est vide") {
239 240
                this.error_flags.noResume = true;
                this.error_flags.noUser = false;
241
              }
242
              this.error_flags.badUser = false;
243
            }
244
          });
245 246 247 248
      },
      closeEdit: function () {
        this.content = this.ruleToEdit;
        this.comment = "";
249
        this.auth = "";
250
        this.isEditMode=!this.isEditMode;
251 252 253 254 255 256 257
        this.error_flags = {
          badUser: false,
          notModified: false,
          noUser: false,
          noPass: false,
          noResume: false,
        };
258 259
      },
      save: function() {
260 261
        this.$parent.isLoading = true;
        this.$parent.modificationInProgress = true;
262
        this.ruleData = this.content
263
        if (this.comment == '') {
264
          this.error_flags.noResume = true;
265 266
          return false;
        }
267 268
        let commitId = (this.commit_id != '') ? {"commit_id" : this.commit_id} : {}
        const prePostData = {
269 270
          author_email:this.auth.email,
          author_name:this.auth.email.split("@")[0],
271
          title: this.displayedName,
272 273
          comment: this.comment,
          content: this.content,
274
          filename: this.filename
275
        }
276 277
        let postData = {...prePostData, ...commitId}

278 279 280 281

        this.$http
          .post('/source/save', postData)
          .then(response => {
282 283 284 285 286
            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}
287 288
            this.$bvModal.hide("mail-modal");
            return this.isEditMode=!this.isEditMode            
289 290
          }, error => {
              if(error.status == 304){
291 292 293 294
                this.error_flags.notModified = true;
                this.error_flags.noUser = false;
                this.error_flags.noResume = false;
                this.error_flags.badUser = false;
295
                this.$bvModal.hide("mail-modal");
296
              } 
297
              else if (error.status == 401) {
298 299 300 301
                this.error_flags.badUser = true;
                this.error_flags.noUser = false;
                this.error_flags.notModified = false;
                this.error_flags.noResume = false;
302 303 304
              }
              else if (error.status == 422) {
                if (error.body.args == "`author_email` est vide") {
305 306
                  this.error_flags.noUser = true;
                  this.error_flags.badUser = false;
307
                } else if (error.body.args == "`comment` est vide") {
308 309
                  this.error_flags.noResume = true;
                  this.error_flags.noUser = false;
310
                }
311 312
                this.error_flags.badUser = false;
                this.error_flags.notModified = false;
313 314
              }
              return false;
315 316 317 318
          }).then(()=> {
            this.$parent.isLoading = false;
            this.$parent.modificationInProgress = false;
            location.reload();
319 320 321
          });
      },
      toTree: function (lines) { // eslint-disable-line no-unused-vars
322
        var root= new Node(this.name.split('.')[0]);
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357
        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;
358 359
      },
      displayModification: function () {
360 361 362 363 364 365
        this.viewModification = true;
        this.$parent.collapsed = true;
        this.isEditMode = false;
      },
      displayList: function () {
        this.viewModification = false;
366
        this.$parent.collapsed = true;
367
        this.isEditMode = false;
368
      }
369
    },
David Foucher's avatar
David Foucher committed
370 371
  }
</script>
372
<style scoped>
373 374 375
.editErrorClass {
  color: #dc3545;
  border: 1px solid #dc3545;
376 377
  border-radius:3px;
}
378 379 380
.rule-modification-text {
  font-family: 'Courier New', Courier, monospace;
}
381 382 383 384 385

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

387 388 389 390 391 392 393 394 395
#modification_link {
  cursor:pointer;
  color:blue;
}

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

396
</style>