Rule.vue 15.4 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
      <div v-show="isEditMode">
40
        <div v-if="content!=null">
41 42 43 44 45 46 47 48
          <div class="container">
            <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>
49
            </div>
50 51
            <div class="row mb-3">
              <label for="content"><u>Contenu de la règle</u></label>
52
              <textarea-autosize id="content" v-model="content" class="rule-modification-text" :class="{editErrorClass: error_flags.notModified }" v-focus v-on:click="this.el.focus();"/>
53
              <span v-if="error_flags.notModified" class="text-danger font-weight-light">Aucune modification n'a été renseignée</span>
54
            </div>
55 56 57 58 59 60 61
            <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>
62 63 64
            </div>
          </div>
        </div>
65 66 67
        <div v-else class="text-center loading-gif">
          <img src="./../assets/images/loading.gif" alt="loading...">
        </div>
68
        <b-modal id="auth-modal" title="Authentifiez-vous pour soumettre une modification">
69
          <label for="contributor_email" class="pb-2"><u>Votre email</u> *</label><br>
70 71
          <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>
72
          <label for="contributor_passwd" class="pb-2"><u>Votre mot de passe</u> *</label><br>
73 74 75 76
          <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>
77
          <template v-slot:modal-footer>       
78
            <input @click="auth_to_edit" type="button" class="btn main-button-primary pull-right" value="Suivant"/>
79 80 81 82 83
          </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>
84 85 86
          <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>
87
          <template v-slot:modal-footer>       
88
            <input @click="save" type="button" class="btn main-button-primary pull-right" value="Enregistrer"/>
89 90 91
          </template>
        </b-modal>

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

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

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

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

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

288 289 290 291

        this.$http
          .post('/source/save', postData)
          .then(response => {
292 293 294 295 296
            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}
297 298
            this.$bvModal.hide("mail-modal");
            return this.isEditMode=!this.isEditMode            
299 300
          }, error => {
              if(error.status == 304){
301 302 303 304
                this.error_flags.notModified = true;
                this.error_flags.noUser = false;
                this.error_flags.noResume = false;
                this.error_flags.badUser = false;
305
                this.$bvModal.hide("mail-modal");
306
              } 
307
              else if (error.status == 401) {
308 309 310 311
                this.error_flags.badUser = true;
                this.error_flags.noUser = false;
                this.error_flags.notModified = false;
                this.error_flags.noResume = false;
312 313 314
              }
              else if (error.status == 422) {
                if (error.body.args == "`author_email` est vide") {
315 316
                  this.error_flags.noUser = true;
                  this.error_flags.badUser = false;
317
                } else if (error.body.args == "`comment` est vide") {
318 319
                  this.error_flags.noResume = true;
                  this.error_flags.noUser = false;
320
                }
321 322
                this.error_flags.badUser = false;
                this.error_flags.notModified = false;
323 324
              }
              return false;
325 326 327 328
          }).then(()=> {
            this.$parent.isLoading = false;
            this.$parent.modificationInProgress = false;
            location.reload();
329 330 331
          });
      },
      toTree: function (lines) { // eslint-disable-line no-unused-vars
332
        var root= new Node(this.name.split('.')[0]);
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 358 359 360 361 362 363 364 365 366 367
        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;
368 369
      },
      displayModification: function () {
370 371 372 373 374
        this.viewModification = true;
        this.$parent.collapsed = true;
        this.isEditMode = false;
      },
      displayList: function () {
375
        this.closeEdit();
376
      }
377
    },
378 379 380 381 382 383 384 385 386 387
    directives: {
      focus: {
        inserted: function (el) {
          el.focus();
        },
        componentUpdated: function (el) {
          el.focus();
        },
      }
    }
David Foucher's avatar
David Foucher committed
388 389
  }
</script>
390
<style scoped>
391 392 393
.editErrorClass {
  color: #dc3545;
  border: 1px solid #dc3545;
394 395
  border-radius:3px;
}
396 397 398
.rule-modification-text {
  font-family: 'Courier New', Courier, monospace;
}
399 400 401 402 403

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

405 406 407 408 409 410 411 412 413
#modification_link {
  cursor:pointer;
  color:blue;
}

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

414
</style>