Rule.vue 15.6 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
              <TextAreaAutosize id="content" :value="content" class="rule-modification-text" :class="{editErrorClass: error_flags.notModified }" v-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
    <div v-else class="text-center loading-gif">
      <img src="./../assets/images/loading.gif" alt="loading...">
96
    </div>
97
  </div>
98 99
</template>
<script>
100
  import TreeItem from './TreeItem.vue';
101
  import Modification from './Modification.vue';
102
  import TextAreaAutosize from "./TextAreaAutosize.vue";
103 104 105 106 107 108 109

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

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

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

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

298 299 300 301

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

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

416 417 418 419 420 421 422 423 424
#modification_link {
  cursor:pointer;
  color:blue;
}

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

425 426 427 428
#content {
  overflow-y: scroll;
  scroll-behavior: smooth;
}
429
</style>