Rule.vue 13.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 11 12 13 14 15
            <div v-if="this.modifiedHashFlag">
              <a :href="'#'+name" v-if="this.modification_count" @click="displayList()" id="modification_link">Retour à la liste</a>
            </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 233
      },
      save: function() {
        this.ruleData = this.content
234
        if (this.comment == '') {
235
          this.error_flags.noResume = true;
236 237
          return false;
        }
238
        const postData = {
239 240
          author_email:this.auth.email,
          author_name:this.auth.email.split("@")[0],
241
          title: this.displayedName,
242 243
          comment: this.comment,
          content: this.content,
244
          filename: 'trefle/config/rules/' + this.path
245 246 247 248 249
        }

        this.$http
          .post('/source/save', postData)
          .then(response => {
250 251 252 253 254
            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}
255 256
            this.$bvModal.hide("mail-modal");
            return this.isEditMode=!this.isEditMode            
257 258
          }, error => {
              if(error.status == 304){
259 260 261 262
                this.error_flags.notModified = true;
                this.error_flags.noUser = false;
                this.error_flags.noResume = false;
                this.error_flags.badUser = false;
263
                this.$bvModal.hide("mail-modal");
264
              } 
265
              else if (error.status == 401) {
266 267 268 269
                this.error_flags.badUser = true;
                this.error_flags.noUser = false;
                this.error_flags.notModified = false;
                this.error_flags.noResume = false;
270 271 272
              }
              else if (error.status == 422) {
                if (error.body.args == "`author_email` est vide") {
273 274
                  this.error_flags.noUser = true;
                  this.error_flags.badUser = false;
275
                } else if (error.body.args == "`comment` est vide") {
276 277
                  this.error_flags.noResume = true;
                  this.error_flags.noUser = false;
278
                }
279 280
                this.error_flags.badUser = false;
                this.error_flags.notModified = false;
281 282
              }
              return false;
283 284 285
          });
      },
      toTree: function (lines) { // eslint-disable-line no-unused-vars
286
        var root= new Node(this.name.split('.')[0]);
287 288 289 290 291 292 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
        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;
322 323
      },
      displayModification: function () {
324 325 326 327 328 329
        this.viewModification = true;
        this.$parent.collapsed = true;
        this.isEditMode = false;
      },
      displayList: function () {
        this.viewModification = false;
330
        this.$parent.collapsed = true;
331
        this.isEditMode = false;
332
      }
333
    },
David Foucher's avatar
David Foucher committed
334 335
  }
</script>
336
<style scoped>
337 338 339
.editErrorClass {
  color: #dc3545;
  border: 1px solid #dc3545;
340 341
  border-radius:3px;
}
342 343 344
.rule-modification-text {
  font-family: 'Courier New', Courier, monospace;
}
345 346 347 348 349

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

351 352 353 354 355 356 357 358 359
#modification_link {
  cursor:pointer;
  color:blue;
}

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

360
</style>