Rule.vue 13.5 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 83 84 85 86
          <template v-slot:modal-footer>       
            <input @click="save" type="button" class="btn btn-outline-success pull-right" value="Enregistrer"/>
          </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
    beforeMount: function() {
137
      this.loadInProgressModification();
138 139 140 141 142 143 144 145 146 147 148 149

      window.addEventListener('popstate', () => {
        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;
        }      
      })
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 164
      ruleTree: function() {
        return this.toTree(this.ruleData.split('\n'));
      },
      ruleToEdit: function() {
        return this.ruleData;
      },
    },
165
    methods: {
166 167
      loadInProgressModification: function () {
        this.$http
168
          .get('/source/modified?branch='+encodeURIComponent(this.displayedName))
169
          .then(response => {
170
            this.modification_list = response.body;
171
            this.isLoading = false;
172 173 174 175
            return true;
          }, response => {
            if(response.status == 500) this.modification_list = {};
            return false;
176 177
          })
      },
178
      auth_to_edit: function () {
179 180
        this.error_flags.noUser = false;
        this.error_flags.noPass = false;
181

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

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

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

347 348 349 350 351 352 353 354 355
#modification_link {
  cursor:pointer;
  color:blue;
}

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

356
</style>