diff --git a/Gemfile b/Gemfile
index e98c127d2f57b84029af7676366e5489b57abaa6..6c1f63460715440111130ac27ad0345e905edc81 100644
--- a/Gemfile
+++ b/Gemfile
@@ -73,6 +73,9 @@ gem 'grape', '~> 0.19.0'
 gem 'grape-entity', '~> 0.6.0'
 gem 'rack-cors', '~> 0.4.0', require: 'rack/cors'
 
+# Disable strong_params so that Mash does not respond to :permitted?
+gem 'hashie-forbidden_attributes'
+
 # Pagination
 gem 'kaminari', '~> 0.17.0'
 
diff --git a/Gemfile.lock b/Gemfile.lock
index eeec4b67764fba74d8cb393ed7b3497551b34e2d..ca4084e18a232e01931624d5859f8e9376a9bb51 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -336,7 +336,7 @@ GEM
     grape-entity (0.6.0)
       activesupport
       multi_json (>= 1.3.2)
-    grpc (1.2.2)
+    grpc (1.1.2)
       google-protobuf (~> 3.1)
       googleauth (~> 0.5.1)
     haml (4.0.7)
@@ -352,6 +352,8 @@ GEM
       tilt
     hashdiff (0.3.2)
     hashie (3.5.5)
+    hashie-forbidden_attributes (0.1.1)
+      hashie (>= 3.0)
     health_check (2.6.0)
       rails (>= 4.0)
     hipchat (1.5.2)
@@ -925,6 +927,7 @@ DEPENDENCIES
   grape-entity (~> 0.6.0)
   haml_lint (~> 0.21.0)
   hamlit (~> 2.6.1)
+  hashie-forbidden_attributes
   health_check (~> 2.6.0)
   hipchat (~> 1.5.0)
   html-pipeline (~> 1.11.0)
diff --git a/app/assets/javascripts/boards/components/board.js b/app/assets/javascripts/boards/components/board.js
index 93b8960da2edc10826d42a75aeeb2946d5fe1809..239eeacf2d74842e94c3de7a5958ef26fccbf6db 100644
--- a/app/assets/javascripts/boards/components/board.js
+++ b/app/assets/javascripts/boards/components/board.js
@@ -7,100 +7,98 @@ import boardBlankState from './board_blank_state';
 require('./board_delete');
 require('./board_list');
 
-(() => {
-  const Store = gl.issueBoards.BoardsStore;
+const Store = gl.issueBoards.BoardsStore;
 
-  window.gl = window.gl || {};
-  window.gl.issueBoards = window.gl.issueBoards || {};
+window.gl = window.gl || {};
+window.gl.issueBoards = window.gl.issueBoards || {};
 
-  gl.issueBoards.Board = Vue.extend({
-    template: '#js-board-template',
-    components: {
-      boardList,
-      'board-delete': gl.issueBoards.BoardDelete,
-      boardBlankState,
-    },
-    props: {
-      list: Object,
-      disabled: Boolean,
-      issueLinkBase: String,
-      rootPath: String,
-    },
-    data () {
-      return {
-        detailIssue: Store.detail,
-        filter: Store.filter,
-      };
-    },
-    watch: {
-      filter: {
-        handler() {
-          this.list.page = 1;
-          this.list.getIssues(true);
-        },
-        deep: true,
+gl.issueBoards.Board = Vue.extend({
+  template: '#js-board-template',
+  components: {
+    boardList,
+    'board-delete': gl.issueBoards.BoardDelete,
+    boardBlankState,
+  },
+  props: {
+    list: Object,
+    disabled: Boolean,
+    issueLinkBase: String,
+    rootPath: String,
+  },
+  data () {
+    return {
+      detailIssue: Store.detail,
+      filter: Store.filter,
+    };
+  },
+  watch: {
+    filter: {
+      handler() {
+        this.list.page = 1;
+        this.list.getIssues(true);
       },
-      detailIssue: {
-        handler () {
-          if (!Object.keys(this.detailIssue.issue).length) return;
+      deep: true,
+    },
+    detailIssue: {
+      handler () {
+        if (!Object.keys(this.detailIssue.issue).length) return;
 
-          const issue = this.list.findIssue(this.detailIssue.issue.id);
+        const issue = this.list.findIssue(this.detailIssue.issue.id);
 
-          if (issue) {
-            const offsetLeft = this.$el.offsetLeft;
-            const boardsList = document.querySelectorAll('.boards-list')[0];
-            const left = boardsList.scrollLeft - offsetLeft;
-            let right = (offsetLeft + this.$el.offsetWidth);
+        if (issue) {
+          const offsetLeft = this.$el.offsetLeft;
+          const boardsList = document.querySelectorAll('.boards-list')[0];
+          const left = boardsList.scrollLeft - offsetLeft;
+          let right = (offsetLeft + this.$el.offsetWidth);
 
-            if (window.innerWidth > 768 && boardsList.classList.contains('is-compact')) {
-              // -290 here because width of boardsList is animating so therefore
-              // getting the width here is incorrect
-              // 290 is the width of the sidebar
-              right -= (boardsList.offsetWidth - 290);
-            } else {
-              right -= boardsList.offsetWidth;
-            }
+          if (window.innerWidth > 768 && boardsList.classList.contains('is-compact')) {
+            // -290 here because width of boardsList is animating so therefore
+            // getting the width here is incorrect
+            // 290 is the width of the sidebar
+            right -= (boardsList.offsetWidth - 290);
+          } else {
+            right -= boardsList.offsetWidth;
+          }
 
-            if (right - boardsList.scrollLeft > 0) {
-              $(boardsList).animate({
-                scrollLeft: right
-              }, this.sortableOptions.animation);
-            } else if (left > 0) {
-              $(boardsList).animate({
-                scrollLeft: offsetLeft
-              }, this.sortableOptions.animation);
-            }
+          if (right - boardsList.scrollLeft > 0) {
+            $(boardsList).animate({
+              scrollLeft: right
+            }, this.sortableOptions.animation);
+          } else if (left > 0) {
+            $(boardsList).animate({
+              scrollLeft: offsetLeft
+            }, this.sortableOptions.animation);
           }
-        },
-        deep: true
-      }
-    },
-    methods: {
-      showNewIssueForm() {
-        this.$refs['board-list'].showIssueForm = !this.$refs['board-list'].showIssueForm;
-      }
-    },
-    mounted () {
-      this.sortableOptions = gl.issueBoards.getBoardSortableDefaultOptions({
-        disabled: this.disabled,
-        group: 'boards',
-        draggable: '.is-draggable',
-        handle: '.js-board-handle',
-        onEnd: (e) => {
-          gl.issueBoards.onEnd();
+        }
+      },
+      deep: true
+    }
+  },
+  methods: {
+    showNewIssueForm() {
+      this.$refs['board-list'].showIssueForm = !this.$refs['board-list'].showIssueForm;
+    }
+  },
+  mounted () {
+    this.sortableOptions = gl.issueBoards.getBoardSortableDefaultOptions({
+      disabled: this.disabled,
+      group: 'boards',
+      draggable: '.is-draggable',
+      handle: '.js-board-handle',
+      onEnd: (e) => {
+        gl.issueBoards.onEnd();
 
-          if (e.newIndex !== undefined && e.oldIndex !== e.newIndex) {
-            const order = this.sortable.toArray();
-            const list = Store.findList('id', parseInt(e.item.dataset.id, 10));
+        if (e.newIndex !== undefined && e.oldIndex !== e.newIndex) {
+          const order = this.sortable.toArray();
+          const list = Store.findList('id', parseInt(e.item.dataset.id, 10));
 
-            this.$nextTick(() => {
-              Store.moveList(list, order);
-            });
-          }
+          this.$nextTick(() => {
+            Store.moveList(list, order);
+          });
         }
-      });
+      }
+    });
 
-      this.sortable = Sortable.create(this.$el.parentNode, this.sortableOptions);
-    },
-  });
-})();
+    this.sortable = Sortable.create(this.$el.parentNode, this.sortableOptions);
+  },
+});
diff --git a/app/assets/javascripts/boards/components/board_delete.js b/app/assets/javascripts/boards/components/board_delete.js
index af621cfd57fda2270f0b69365c3e32fc2d0be1ac..8a1b177bba8c31110e40b004e6b6a452ce4b410e 100644
--- a/app/assets/javascripts/boards/components/board_delete.js
+++ b/app/assets/javascripts/boards/components/board_delete.js
@@ -2,22 +2,20 @@
 
 import Vue from 'vue';
 
-(() => {
-  window.gl = window.gl || {};
-  window.gl.issueBoards = window.gl.issueBoards || {};
+window.gl = window.gl || {};
+window.gl.issueBoards = window.gl.issueBoards || {};
 
-  gl.issueBoards.BoardDelete = Vue.extend({
-    props: {
-      list: Object
-    },
-    methods: {
-      deleteBoard () {
-        $(this.$el).tooltip('hide');
+gl.issueBoards.BoardDelete = Vue.extend({
+  props: {
+    list: Object
+  },
+  methods: {
+    deleteBoard () {
+      $(this.$el).tooltip('hide');
 
-        if (confirm('Are you sure you want to delete this list?')) {
-          this.list.destroy();
-        }
+      if (confirm('Are you sure you want to delete this list?')) {
+        this.list.destroy();
       }
     }
-  });
-})();
+  }
+});
diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js
index 3c080008244f360e046bb7c393e7a3c29e0e5f93..004bac09f598bcacf345a74d6eb948def8acc227 100644
--- a/app/assets/javascripts/boards/components/board_sidebar.js
+++ b/app/assets/javascripts/boards/components/board_sidebar.js
@@ -8,66 +8,64 @@ import Vue from 'vue';
 
 require('./sidebar/remove_issue');
 
-(() => {
-  const Store = gl.issueBoards.BoardsStore;
+const Store = gl.issueBoards.BoardsStore;
 
-  window.gl = window.gl || {};
-  window.gl.issueBoards = window.gl.issueBoards || {};
+window.gl = window.gl || {};
+window.gl.issueBoards = window.gl.issueBoards || {};
 
-  gl.issueBoards.BoardSidebar = Vue.extend({
-    props: {
-      currentUser: Object
-    },
-    data() {
-      return {
-        detail: Store.detail,
-        issue: {},
-        list: {},
-      };
-    },
-    computed: {
-      showSidebar () {
-        return Object.keys(this.issue).length;
-      }
-    },
-    watch: {
-      detail: {
-        handler () {
-          if (this.issue.id !== this.detail.issue.id) {
-            $('.js-issue-board-sidebar', this.$el).each((i, el) => {
-              $(el).data('glDropdown').clearMenu();
-            });
-          }
-
-          this.issue = this.detail.issue;
-          this.list = this.detail.list;
-        },
-        deep: true
-      },
-      issue () {
-        if (this.showSidebar) {
-          this.$nextTick(() => {
-            $('.right-sidebar').getNiceScroll(0).doScrollTop(0, 0);
-            $('.right-sidebar').getNiceScroll().resize();
+gl.issueBoards.BoardSidebar = Vue.extend({
+  props: {
+    currentUser: Object
+  },
+  data() {
+    return {
+      detail: Store.detail,
+      issue: {},
+      list: {},
+    };
+  },
+  computed: {
+    showSidebar () {
+      return Object.keys(this.issue).length;
+    }
+  },
+  watch: {
+    detail: {
+      handler () {
+        if (this.issue.id !== this.detail.issue.id) {
+          $('.js-issue-board-sidebar', this.$el).each((i, el) => {
+            $(el).data('glDropdown').clearMenu();
           });
         }
-      }
+
+        this.issue = this.detail.issue;
+        this.list = this.detail.list;
+      },
+      deep: true
     },
-    methods: {
-      closeSidebar () {
-        this.detail.issue = {};
+    issue () {
+      if (this.showSidebar) {
+        this.$nextTick(() => {
+          $('.right-sidebar').getNiceScroll(0).doScrollTop(0, 0);
+          $('.right-sidebar').getNiceScroll().resize();
+        });
       }
-    },
-    mounted () {
-      new IssuableContext(this.currentUser);
-      new MilestoneSelect();
-      new gl.DueDateSelectors();
-      new LabelsSelect();
-      new Sidebar();
-      gl.Subscription.bindAll('.subscription');
-    },
-    components: {
-      removeBtn: gl.issueBoards.RemoveIssueBtn,
-    },
-  });
-})();
+    }
+  },
+  methods: {
+    closeSidebar () {
+      this.detail.issue = {};
+    }
+  },
+  mounted () {
+    new IssuableContext(this.currentUser);
+    new MilestoneSelect();
+    new gl.DueDateSelectors();
+    new LabelsSelect();
+    new Sidebar();
+    gl.Subscription.bindAll('.subscription');
+  },
+  components: {
+    removeBtn: gl.issueBoards.RemoveIssueBtn,
+  },
+});
diff --git a/app/assets/javascripts/boards/components/issue_card_inner.js b/app/assets/javascripts/boards/components/issue_card_inner.js
index e48d3344a2b5d99960176441a8128051cc090181..fc154ee7b8b031f6d79d1454c0c0ad133d8b9001 100644
--- a/app/assets/javascripts/boards/components/issue_card_inner.js
+++ b/app/assets/javascripts/boards/components/issue_card_inner.js
@@ -1,141 +1,139 @@
 import Vue from 'vue';
 import eventHub from '../eventhub';
 
-(() => {
-  const Store = gl.issueBoards.BoardsStore;
+const Store = gl.issueBoards.BoardsStore;
 
-  window.gl = window.gl || {};
-  window.gl.issueBoards = window.gl.issueBoards || {};
+window.gl = window.gl || {};
+window.gl.issueBoards = window.gl.issueBoards || {};
 
-  gl.issueBoards.IssueCardInner = Vue.extend({
-    props: {
-      issue: {
-        type: Object,
-        required: true,
-      },
-      issueLinkBase: {
-        type: String,
-        required: true,
-      },
-      list: {
-        type: Object,
-        required: false,
-        default: () => ({}),
-      },
-      rootPath: {
-        type: String,
-        required: true,
-      },
-      updateFilters: {
-        type: Boolean,
-        required: false,
-        default: false,
-      },
+gl.issueBoards.IssueCardInner = Vue.extend({
+  props: {
+    issue: {
+      type: Object,
+      required: true,
     },
-    computed: {
-      cardUrl() {
-        return `${this.issueLinkBase}/${this.issue.id}`;
-      },
-      assigneeUrl() {
-        return `${this.rootPath}${this.issue.assignee.username}`;
-      },
-      assigneeUrlTitle() {
-        return `Assigned to ${this.issue.assignee.name}`;
-      },
-      avatarUrlTitle() {
-        return `Avatar for ${this.issue.assignee.name}`;
-      },
-      issueId() {
-        return `#${this.issue.id}`;
-      },
-      showLabelFooter() {
-        return this.issue.labels.find(l => this.showLabel(l)) !== undefined;
-      },
+    issueLinkBase: {
+      type: String,
+      required: true,
     },
-    methods: {
-      showLabel(label) {
-        if (!this.list) return true;
+    list: {
+      type: Object,
+      required: false,
+      default: () => ({}),
+    },
+    rootPath: {
+      type: String,
+      required: true,
+    },
+    updateFilters: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
+  },
+  computed: {
+    cardUrl() {
+      return `${this.issueLinkBase}/${this.issue.id}`;
+    },
+    assigneeUrl() {
+      return `${this.rootPath}${this.issue.assignee.username}`;
+    },
+    assigneeUrlTitle() {
+      return `Assigned to ${this.issue.assignee.name}`;
+    },
+    avatarUrlTitle() {
+      return `Avatar for ${this.issue.assignee.name}`;
+    },
+    issueId() {
+      return `#${this.issue.id}`;
+    },
+    showLabelFooter() {
+      return this.issue.labels.find(l => this.showLabel(l)) !== undefined;
+    },
+  },
+  methods: {
+    showLabel(label) {
+      if (!this.list) return true;
 
-        return !this.list.label || label.id !== this.list.label.id;
-      },
-      filterByLabel(label, e) {
-        if (!this.updateFilters) return;
+      return !this.list.label || label.id !== this.list.label.id;
+    },
+    filterByLabel(label, e) {
+      if (!this.updateFilters) return;
 
-        const filterPath = gl.issueBoards.BoardsStore.filter.path.split('&');
-        const labelTitle = encodeURIComponent(label.title);
-        const param = `label_name[]=${labelTitle}`;
-        const labelIndex = filterPath.indexOf(param);
-        $(e.currentTarget).tooltip('hide');
+      const filterPath = gl.issueBoards.BoardsStore.filter.path.split('&');
+      const labelTitle = encodeURIComponent(label.title);
+      const param = `label_name[]=${labelTitle}`;
+      const labelIndex = filterPath.indexOf(param);
+      $(e.currentTarget).tooltip('hide');
 
-        if (labelIndex === -1) {
-          filterPath.push(param);
-        } else {
-          filterPath.splice(labelIndex, 1);
-        }
+      if (labelIndex === -1) {
+        filterPath.push(param);
+      } else {
+        filterPath.splice(labelIndex, 1);
+      }
 
-        gl.issueBoards.BoardsStore.filter.path = filterPath.join('&');
+      gl.issueBoards.BoardsStore.filter.path = filterPath.join('&');
 
-        Store.updateFiltersUrl();
+      Store.updateFiltersUrl();
 
-        eventHub.$emit('updateTokens');
-      },
-      labelStyle(label) {
-        return {
-          backgroundColor: label.color,
-          color: label.textColor,
-        };
-      },
+      eventHub.$emit('updateTokens');
+    },
+    labelStyle(label) {
+      return {
+        backgroundColor: label.color,
+        color: label.textColor,
+      };
     },
-    template: `
-      <div>
-        <div class="card-header">
-          <h4 class="card-title">
-            <i
-              class="fa fa-eye-slash confidential-icon"
-              v-if="issue.confidential"
-              aria-hidden="true"
-            />
-            <a
-              class="js-no-trigger"
-              :href="cardUrl"
-              :title="issue.title">{{ issue.title }}</a>
-            <span
-              class="card-number"
-              v-if="issue.id"
-            >
-              {{ issueId }}
-            </span>
-          </h4>
+  },
+  template: `
+    <div>
+      <div class="card-header">
+        <h4 class="card-title">
+          <i
+            class="fa fa-eye-slash confidential-icon"
+            v-if="issue.confidential"
+            aria-hidden="true"
+          />
           <a
-            class="card-assignee has-tooltip js-no-trigger"
-            :href="assigneeUrl"
-            :title="assigneeUrlTitle"
-            v-if="issue.assignee"
-            data-container="body"
+            class="js-no-trigger"
+            :href="cardUrl"
+            :title="issue.title">{{ issue.title }}</a>
+          <span
+            class="card-number"
+            v-if="issue.id"
           >
-            <img
-              class="avatar avatar-inline s20 js-no-trigger"
-              :src="issue.assignee.avatar"
-              width="20"
-              height="20"
-              :alt="avatarUrlTitle"
-            />
-          </a>
-        </div>
-        <div class="card-footer" v-if="showLabelFooter">
-          <button
-            class="label color-label has-tooltip js-no-trigger"
-            v-for="label in issue.labels"
-            type="button"
-            v-if="showLabel(label)"
-            @click="filterByLabel(label, $event)"
-            :style="labelStyle(label)"
-            :title="label.description"
-            data-container="body">
-            {{ label.title }}
-          </button>
-        </div>
+            {{ issueId }}
+          </span>
+        </h4>
+        <a
+          class="card-assignee has-tooltip js-no-trigger"
+          :href="assigneeUrl"
+          :title="assigneeUrlTitle"
+          v-if="issue.assignee"
+          data-container="body"
+        >
+          <img
+            class="avatar avatar-inline s20 js-no-trigger"
+            :src="issue.assignee.avatar"
+            width="20"
+            height="20"
+            :alt="avatarUrlTitle"
+          />
+        </a>
+      </div>
+      <div class="card-footer" v-if="showLabelFooter">
+        <button
+          class="label color-label has-tooltip js-no-trigger"
+          v-for="label in issue.labels"
+          type="button"
+          v-if="showLabel(label)"
+          @click="filterByLabel(label, $event)"
+          :style="labelStyle(label)"
+          :title="label.description"
+          data-container="body">
+          {{ label.title }}
+        </button>
       </div>
-    `,
-  });
-})();
+    </div>
+  `,
+});
diff --git a/app/assets/javascripts/boards/components/modal/empty_state.js b/app/assets/javascripts/boards/components/modal/empty_state.js
index 823319df6e7aa87ee995a69905d0aee3f02839a3..13569df0c2059068cae5728b064e96e09a36ecca 100644
--- a/app/assets/javascripts/boards/components/modal/empty_state.js
+++ b/app/assets/javascripts/boards/components/modal/empty_state.js
@@ -1,71 +1,69 @@
 import Vue from 'vue';
 
-(() => {
-  const ModalStore = gl.issueBoards.ModalStore;
+const ModalStore = gl.issueBoards.ModalStore;
 
-  gl.issueBoards.ModalEmptyState = Vue.extend({
-    mixins: [gl.issueBoards.ModalMixins],
-    data() {
-      return ModalStore.store;
+gl.issueBoards.ModalEmptyState = Vue.extend({
+  mixins: [gl.issueBoards.ModalMixins],
+  data() {
+    return ModalStore.store;
+  },
+  props: {
+    image: {
+      type: String,
+      required: true,
     },
-    props: {
-      image: {
-        type: String,
-        required: true,
-      },
-      newIssuePath: {
-        type: String,
-        required: true,
-      },
+    newIssuePath: {
+      type: String,
+      required: true,
     },
-    computed: {
-      contents() {
-        const obj = {
-          title: 'You haven\'t added any issues to your project yet',
-          content: `
-            An issue can be a bug, a todo or a feature request that needs to be
-            discussed in a project. Besides, issues are searchable and filterable.
-          `,
-        };
+  },
+  computed: {
+    contents() {
+      const obj = {
+        title: 'You haven\'t added any issues to your project yet',
+        content: `
+          An issue can be a bug, a todo or a feature request that needs to be
+          discussed in a project. Besides, issues are searchable and filterable.
+        `,
+      };
 
-        if (this.activeTab === 'selected') {
-          obj.title = 'You haven\'t selected any issues yet';
-          obj.content = `
-            Go back to <strong>Open issues</strong> and select some issues
-            to add to your board.
-          `;
-        }
+      if (this.activeTab === 'selected') {
+        obj.title = 'You haven\'t selected any issues yet';
+        obj.content = `
+          Go back to <strong>Open issues</strong> and select some issues
+          to add to your board.
+        `;
+      }
 
-        return obj;
-      },
+      return obj;
     },
-    template: `
-      <section class="empty-state">
-        <div class="row">
-          <div class="col-xs-12 col-sm-6 col-sm-push-6">
-            <aside class="svg-content" v-html="image"></aside>
-          </div>
-          <div class="col-xs-12 col-sm-6 col-sm-pull-6">
-            <div class="text-content">
-              <h4>{{ contents.title }}</h4>
-              <p v-html="contents.content"></p>
-              <a
-                :href="newIssuePath"
-                class="btn btn-success btn-inverted"
-                v-if="activeTab === 'all'">
-                New issue
-              </a>
-              <button
-                type="button"
-                class="btn btn-default"
-                @click="changeTab('all')"
-                v-if="activeTab === 'selected'">
-                Open issues
-              </button>
-            </div>
+  },
+  template: `
+    <section class="empty-state">
+      <div class="row">
+        <div class="col-xs-12 col-sm-6 col-sm-push-6">
+          <aside class="svg-content" v-html="image"></aside>
+        </div>
+        <div class="col-xs-12 col-sm-6 col-sm-pull-6">
+          <div class="text-content">
+            <h4>{{ contents.title }}</h4>
+            <p v-html="contents.content"></p>
+            <a
+              :href="newIssuePath"
+              class="btn btn-success btn-inverted"
+              v-if="activeTab === 'all'">
+              New issue
+            </a>
+            <button
+              type="button"
+              class="btn btn-default"
+              @click="changeTab('all')"
+              v-if="activeTab === 'selected'">
+              Open issues
+            </button>
           </div>
         </div>
-      </section>
-    `,
-  });
-})();
+      </div>
+    </section>
+  `,
+});
diff --git a/app/assets/javascripts/boards/components/modal/footer.js b/app/assets/javascripts/boards/components/modal/footer.js
index 887ce37309615dc01bd357c1e55a7eb9810cf834..ccd270b27dab42e6962567ae03423c0c0116a4d4 100644
--- a/app/assets/javascripts/boards/components/modal/footer.js
+++ b/app/assets/javascripts/boards/components/modal/footer.js
@@ -5,80 +5,78 @@ import Vue from 'vue';
 
 require('./lists_dropdown');
 
-(() => {
-  const ModalStore = gl.issueBoards.ModalStore;
+const ModalStore = gl.issueBoards.ModalStore;
 
-  gl.issueBoards.ModalFooter = Vue.extend({
-    mixins: [gl.issueBoards.ModalMixins],
-    data() {
-      return {
-        modal: ModalStore.store,
-        state: gl.issueBoards.BoardsStore.state,
-      };
+gl.issueBoards.ModalFooter = Vue.extend({
+  mixins: [gl.issueBoards.ModalMixins],
+  data() {
+    return {
+      modal: ModalStore.store,
+      state: gl.issueBoards.BoardsStore.state,
+    };
+  },
+  computed: {
+    submitDisabled() {
+      return !ModalStore.selectedCount();
     },
-    computed: {
-      submitDisabled() {
-        return !ModalStore.selectedCount();
-      },
-      submitText() {
-        const count = ModalStore.selectedCount();
+    submitText() {
+      const count = ModalStore.selectedCount();
 
-        return `Add ${count > 0 ? count : ''} ${gl.text.pluralize('issue', count)}`;
-      },
+      return `Add ${count > 0 ? count : ''} ${gl.text.pluralize('issue', count)}`;
     },
-    methods: {
-      addIssues() {
-        const list = this.modal.selectedList || this.state.lists[0];
-        const selectedIssues = ModalStore.getSelectedIssues();
-        const issueIds = selectedIssues.map(issue => issue.globalId);
+  },
+  methods: {
+    addIssues() {
+      const list = this.modal.selectedList || this.state.lists[0];
+      const selectedIssues = ModalStore.getSelectedIssues();
+      const issueIds = selectedIssues.map(issue => issue.globalId);
 
-        // Post the data to the backend
-        gl.boardService.bulkUpdate(issueIds, {
-          add_label_ids: [list.label.id],
-        }).catch(() => {
-          new Flash('Failed to update issues, please try again.', 'alert');
+      // Post the data to the backend
+      gl.boardService.bulkUpdate(issueIds, {
+        add_label_ids: [list.label.id],
+      }).catch(() => {
+        new Flash('Failed to update issues, please try again.', 'alert');
 
-          selectedIssues.forEach((issue) => {
-            list.removeIssue(issue);
-            list.issuesSize -= 1;
-          });
-        });
-
-        // Add the issues on the frontend
         selectedIssues.forEach((issue) => {
-          list.addIssue(issue);
-          list.issuesSize += 1;
+          list.removeIssue(issue);
+          list.issuesSize -= 1;
         });
+      });
 
-        this.toggleModal(false);
-      },
-    },
-    components: {
-      'lists-dropdown': gl.issueBoards.ModalFooterListsDropdown,
+      // Add the issues on the frontend
+      selectedIssues.forEach((issue) => {
+        list.addIssue(issue);
+        list.issuesSize += 1;
+      });
+
+      this.toggleModal(false);
     },
-    template: `
-      <footer
-        class="form-actions add-issues-footer">
-        <div class="pull-left">
-          <button
-            class="btn btn-success"
-            type="button"
-            :disabled="submitDisabled"
-            @click="addIssues">
-            {{ submitText }}
-          </button>
-          <span class="inline add-issues-footer-to-list">
-            to list
-          </span>
-          <lists-dropdown></lists-dropdown>
-        </div>
+  },
+  components: {
+    'lists-dropdown': gl.issueBoards.ModalFooterListsDropdown,
+  },
+  template: `
+    <footer
+      class="form-actions add-issues-footer">
+      <div class="pull-left">
         <button
-          class="btn btn-default pull-right"
+          class="btn btn-success"
           type="button"
-          @click="toggleModal(false)">
-          Cancel
+          :disabled="submitDisabled"
+          @click="addIssues">
+          {{ submitText }}
         </button>
-      </footer>
-    `,
-  });
-})();
+        <span class="inline add-issues-footer-to-list">
+          to list
+        </span>
+        <lists-dropdown></lists-dropdown>
+      </div>
+      <button
+        class="btn btn-default pull-right"
+        type="button"
+        @click="toggleModal(false)">
+        Cancel
+      </button>
+    </footer>
+  `,
+});
diff --git a/app/assets/javascripts/boards/components/modal/header.js b/app/assets/javascripts/boards/components/modal/header.js
index 116e29cd1778882203e133da9b990197f9316a35..e2b3f9ae7e2234fa50e267fc6cc1689b3a9ac5a4 100644
--- a/app/assets/javascripts/boards/components/modal/header.js
+++ b/app/assets/javascripts/boards/components/modal/header.js
@@ -3,80 +3,78 @@ import modalFilters from './filters';
 
 require('./tabs');
 
-(() => {
-  const ModalStore = gl.issueBoards.ModalStore;
+const ModalStore = gl.issueBoards.ModalStore;
 
-  gl.issueBoards.ModalHeader = Vue.extend({
-    mixins: [gl.issueBoards.ModalMixins],
-    props: {
-      projectId: {
-        type: Number,
-        required: true,
-      },
-      milestonePath: {
-        type: String,
-        required: true,
-      },
-      labelPath: {
-        type: String,
-        required: true,
-      },
+gl.issueBoards.ModalHeader = Vue.extend({
+  mixins: [gl.issueBoards.ModalMixins],
+  props: {
+    projectId: {
+      type: Number,
+      required: true,
     },
-    data() {
-      return ModalStore.store;
+    milestonePath: {
+      type: String,
+      required: true,
     },
-    computed: {
-      selectAllText() {
-        if (ModalStore.selectedCount() !== this.issues.length || this.issues.length === 0) {
-          return 'Select all';
-        }
-
-        return 'Deselect all';
-      },
-      showSearch() {
-        return this.activeTab === 'all' && !this.loading && this.issuesCount > 0;
-      },
+    labelPath: {
+      type: String,
+      required: true,
     },
-    methods: {
-      toggleAll() {
-        this.$refs.selectAllBtn.blur();
+  },
+  data() {
+    return ModalStore.store;
+  },
+  computed: {
+    selectAllText() {
+      if (ModalStore.selectedCount() !== this.issues.length || this.issues.length === 0) {
+        return 'Select all';
+      }
 
-        ModalStore.toggleAll();
-      },
+      return 'Deselect all';
+    },
+    showSearch() {
+      return this.activeTab === 'all' && !this.loading && this.issuesCount > 0;
     },
-    components: {
-      'modal-tabs': gl.issueBoards.ModalTabs,
-      modalFilters,
+  },
+  methods: {
+    toggleAll() {
+      this.$refs.selectAllBtn.blur();
+
+      ModalStore.toggleAll();
     },
-    template: `
-      <div>
-        <header class="add-issues-header form-actions">
-          <h2>
-            Add issues
-            <button
-              type="button"
-              class="close"
-              data-dismiss="modal"
-              aria-label="Close"
-              @click="toggleModal(false)">
-              <span aria-hidden="true">×</span>
-            </button>
-          </h2>
-        </header>
-        <modal-tabs v-if="!loading && issuesCount > 0"></modal-tabs>
-        <div
-          class="add-issues-search append-bottom-10"
-          v-if="showSearch">
-          <modal-filters :store="filter" />
+  },
+  components: {
+    'modal-tabs': gl.issueBoards.ModalTabs,
+    modalFilters,
+  },
+  template: `
+    <div>
+      <header class="add-issues-header form-actions">
+        <h2>
+          Add issues
           <button
             type="button"
-            class="btn btn-success btn-inverted prepend-left-10"
-            ref="selectAllBtn"
-            @click="toggleAll">
-            {{ selectAllText }}
+            class="close"
+            data-dismiss="modal"
+            aria-label="Close"
+            @click="toggleModal(false)">
+            <span aria-hidden="true">×</span>
           </button>
-        </div>
+        </h2>
+      </header>
+      <modal-tabs v-if="!loading && issuesCount > 0"></modal-tabs>
+      <div
+        class="add-issues-search append-bottom-10"
+        v-if="showSearch">
+        <modal-filters :store="filter" />
+        <button
+          type="button"
+          class="btn btn-success btn-inverted prepend-left-10"
+          ref="selectAllBtn"
+          @click="toggleAll">
+          {{ selectAllText }}
+        </button>
       </div>
-    `,
-  });
-})();
+    </div>
+  `,
+});
diff --git a/app/assets/javascripts/boards/components/modal/index.js b/app/assets/javascripts/boards/components/modal/index.js
index 91c08cde13a92909b53c2d11350b508775724b81..fb0aac3c0e4944519c27acd80ffec20936ad3405 100644
--- a/app/assets/javascripts/boards/components/modal/index.js
+++ b/app/assets/javascripts/boards/components/modal/index.js
@@ -8,160 +8,158 @@ require('./list');
 require('./footer');
 require('./empty_state');
 
-(() => {
-  const ModalStore = gl.issueBoards.ModalStore;
+const ModalStore = gl.issueBoards.ModalStore;
 
-  gl.issueBoards.IssuesModal = Vue.extend({
-    props: {
-      blankStateImage: {
-        type: String,
-        required: true,
-      },
-      newIssuePath: {
-        type: String,
-        required: true,
-      },
-      issueLinkBase: {
-        type: String,
-        required: true,
-      },
-      rootPath: {
-        type: String,
-        required: true,
-      },
-      projectId: {
-        type: Number,
-        required: true,
-      },
-      milestonePath: {
-        type: String,
-        required: true,
-      },
-      labelPath: {
-        type: String,
-        required: true,
-      },
+gl.issueBoards.IssuesModal = Vue.extend({
+  props: {
+    blankStateImage: {
+      type: String,
+      required: true,
     },
-    data() {
-      return ModalStore.store;
+    newIssuePath: {
+      type: String,
+      required: true,
     },
-    watch: {
-      page() {
-        this.loadIssues();
-      },
-      showAddIssuesModal() {
-        if (this.showAddIssuesModal && !this.issues.length) {
-          this.loading = true;
+    issueLinkBase: {
+      type: String,
+      required: true,
+    },
+    rootPath: {
+      type: String,
+      required: true,
+    },
+    projectId: {
+      type: Number,
+      required: true,
+    },
+    milestonePath: {
+      type: String,
+      required: true,
+    },
+    labelPath: {
+      type: String,
+      required: true,
+    },
+  },
+  data() {
+    return ModalStore.store;
+  },
+  watch: {
+    page() {
+      this.loadIssues();
+    },
+    showAddIssuesModal() {
+      if (this.showAddIssuesModal && !this.issues.length) {
+        this.loading = true;
+
+        this.loadIssues()
+          .then(() => {
+            this.loading = false;
+          });
+      } else if (!this.showAddIssuesModal) {
+        this.issues = [];
+        this.selectedIssues = [];
+        this.issuesCount = false;
+      }
+    },
+    filter: {
+      handler() {
+        if (this.$el.tagName) {
+          this.page = 1;
+          this.filterLoading = true;
 
-          this.loadIssues()
+          this.loadIssues(true)
             .then(() => {
-              this.loading = false;
+              this.filterLoading = false;
             });
-        } else if (!this.showAddIssuesModal) {
-          this.issues = [];
-          this.selectedIssues = [];
-          this.issuesCount = false;
         }
       },
-      filter: {
-        handler() {
-          if (this.$el.tagName) {
-            this.page = 1;
-            this.filterLoading = true;
-
-            this.loadIssues(true)
-              .then(() => {
-                this.filterLoading = false;
-              });
-          }
-        },
-        deep: true,
-      },
+      deep: true,
     },
-    methods: {
-      loadIssues(clearIssues = false) {
-        if (!this.showAddIssuesModal) return false;
-
-        return gl.boardService.getBacklog(queryData(this.filter.path, {
-          page: this.page,
-          per: this.perPage,
-        })).then((res) => {
-          const data = res.json();
-
-          if (clearIssues) {
-            this.issues = [];
-          }
+  },
+  methods: {
+    loadIssues(clearIssues = false) {
+      if (!this.showAddIssuesModal) return false;
 
-          data.issues.forEach((issueObj) => {
-            const issue = new ListIssue(issueObj);
-            const foundSelectedIssue = ModalStore.findSelectedIssue(issue);
-            issue.selected = !!foundSelectedIssue;
+      return gl.boardService.getBacklog(queryData(this.filter.path, {
+        page: this.page,
+        per: this.perPage,
+      })).then((res) => {
+        const data = res.json();
 
-            this.issues.push(issue);
-          });
+        if (clearIssues) {
+          this.issues = [];
+        }
 
-          this.loadingNewPage = false;
+        data.issues.forEach((issueObj) => {
+          const issue = new ListIssue(issueObj);
+          const foundSelectedIssue = ModalStore.findSelectedIssue(issue);
+          issue.selected = !!foundSelectedIssue;
 
-          if (!this.issuesCount) {
-            this.issuesCount = data.size;
-          }
+          this.issues.push(issue);
         });
-      },
-    },
-    computed: {
-      showList() {
-        if (this.activeTab === 'selected') {
-          return this.selectedIssues.length > 0;
-        }
 
-        return this.issuesCount > 0;
-      },
-      showEmptyState() {
-        if (!this.loading && this.issuesCount === 0) {
-          return true;
-        }
+        this.loadingNewPage = false;
 
-        return this.activeTab === 'selected' && this.selectedIssues.length === 0;
-      },
+        if (!this.issuesCount) {
+          this.issuesCount = data.size;
+        }
+      });
     },
-    created() {
-      this.page = 1;
+  },
+  computed: {
+    showList() {
+      if (this.activeTab === 'selected') {
+        return this.selectedIssues.length > 0;
+      }
+
+      return this.issuesCount > 0;
     },
-    components: {
-      'modal-header': gl.issueBoards.ModalHeader,
-      'modal-list': gl.issueBoards.ModalList,
-      'modal-footer': gl.issueBoards.ModalFooter,
-      'empty-state': gl.issueBoards.ModalEmptyState,
+    showEmptyState() {
+      if (!this.loading && this.issuesCount === 0) {
+        return true;
+      }
+
+      return this.activeTab === 'selected' && this.selectedIssues.length === 0;
     },
-    template: `
-      <div
-        class="add-issues-modal"
-        v-if="showAddIssuesModal">
-        <div class="add-issues-container">
-          <modal-header
-            :project-id="projectId"
-            :milestone-path="milestonePath"
-            :label-path="labelPath">
-          </modal-header>
-          <modal-list
-            :image="blankStateImage"
-            :issue-link-base="issueLinkBase"
-            :root-path="rootPath"
-            v-if="!loading && showList && !filterLoading"></modal-list>
-          <empty-state
-            v-if="showEmptyState"
-            :image="blankStateImage"
-            :new-issue-path="newIssuePath"></empty-state>
-          <section
-            class="add-issues-list text-center"
-            v-if="loading || filterLoading">
-            <div class="add-issues-list-loading">
-              <i class="fa fa-spinner fa-spin"></i>
-            </div>
-          </section>
-          <modal-footer></modal-footer>
-        </div>
+  },
+  created() {
+    this.page = 1;
+  },
+  components: {
+    'modal-header': gl.issueBoards.ModalHeader,
+    'modal-list': gl.issueBoards.ModalList,
+    'modal-footer': gl.issueBoards.ModalFooter,
+    'empty-state': gl.issueBoards.ModalEmptyState,
+  },
+  template: `
+    <div
+      class="add-issues-modal"
+      v-if="showAddIssuesModal">
+      <div class="add-issues-container">
+        <modal-header
+          :project-id="projectId"
+          :milestone-path="milestonePath"
+          :label-path="labelPath">
+        </modal-header>
+        <modal-list
+          :image="blankStateImage"
+          :issue-link-base="issueLinkBase"
+          :root-path="rootPath"
+          v-if="!loading && showList && !filterLoading"></modal-list>
+        <empty-state
+          v-if="showEmptyState"
+          :image="blankStateImage"
+          :new-issue-path="newIssuePath"></empty-state>
+        <section
+          class="add-issues-list text-center"
+          v-if="loading || filterLoading">
+          <div class="add-issues-list-loading">
+            <i class="fa fa-spinner fa-spin"></i>
+          </div>
+        </section>
+        <modal-footer></modal-footer>
       </div>
-    `,
-  });
-})();
+    </div>
+  `,
+});
diff --git a/app/assets/javascripts/boards/components/modal/list.js b/app/assets/javascripts/boards/components/modal/list.js
index aba56d4aa3127136621da960969810497ed77201..363269c0d5d1dcb617d4a9415c9154409d4aaf92 100644
--- a/app/assets/javascripts/boards/components/modal/list.js
+++ b/app/assets/javascripts/boards/components/modal/list.js
@@ -3,159 +3,157 @@
 
 import Vue from 'vue';
 
-(() => {
-  const ModalStore = gl.issueBoards.ModalStore;
+const ModalStore = gl.issueBoards.ModalStore;
 
-  gl.issueBoards.ModalList = Vue.extend({
-    props: {
-      issueLinkBase: {
-        type: String,
-        required: true,
-      },
-      rootPath: {
-        type: String,
-        required: true,
-      },
-      image: {
-        type: String,
-        required: true,
-      },
+gl.issueBoards.ModalList = Vue.extend({
+  props: {
+    issueLinkBase: {
+      type: String,
+      required: true,
     },
-    data() {
-      return ModalStore.store;
+    rootPath: {
+      type: String,
+      required: true,
     },
-    watch: {
-      activeTab() {
-        if (this.activeTab === 'all') {
-          ModalStore.purgeUnselectedIssues();
-        }
-      },
+    image: {
+      type: String,
+      required: true,
     },
-    computed: {
-      loopIssues() {
-        if (this.activeTab === 'all') {
-          return this.issues;
-        }
-
-        return this.selectedIssues;
-      },
-      groupedIssues() {
-        const groups = [];
-        this.loopIssues.forEach((issue, i) => {
-          const index = i % this.columns;
-
-          if (!groups[index]) {
-            groups.push([]);
-          }
-
-          groups[index].push(issue);
-        });
+  },
+  data() {
+    return ModalStore.store;
+  },
+  watch: {
+    activeTab() {
+      if (this.activeTab === 'all') {
+        ModalStore.purgeUnselectedIssues();
+      }
+    },
+  },
+  computed: {
+    loopIssues() {
+      if (this.activeTab === 'all') {
+        return this.issues;
+      }
 
-        return groups;
-      },
+      return this.selectedIssues;
     },
-    methods: {
-      scrollHandler() {
-        const currentPage = Math.floor(this.issues.length / this.perPage);
+    groupedIssues() {
+      const groups = [];
+      this.loopIssues.forEach((issue, i) => {
+        const index = i % this.columns;
 
-        if ((this.scrollTop() > this.scrollHeight() - 100) && !this.loadingNewPage
-          && currentPage === this.page) {
-          this.loadingNewPage = true;
-          this.page += 1;
+        if (!groups[index]) {
+          groups.push([]);
         }
-      },
-      toggleIssue(e, issue) {
-        if (e.target.tagName !== 'A') {
-          ModalStore.toggleIssue(issue);
-        }
-      },
-      listHeight() {
-        return this.$refs.list.getBoundingClientRect().height;
-      },
-      scrollHeight() {
-        return this.$refs.list.scrollHeight;
-      },
-      scrollTop() {
-        return this.$refs.list.scrollTop + this.listHeight();
-      },
-      showIssue(issue) {
-        if (this.activeTab === 'all') return true;
-
-        const index = ModalStore.selectedIssueIndex(issue);
 
-        return index !== -1;
-      },
-      setColumnCount() {
-        const breakpoint = bp.getBreakpointSize();
+        groups[index].push(issue);
+      });
 
-        if (breakpoint === 'lg' || breakpoint === 'md') {
-          this.columns = 3;
-        } else if (breakpoint === 'sm') {
-          this.columns = 2;
-        } else {
-          this.columns = 1;
-        }
-      },
+      return groups;
     },
-    mounted() {
-      this.scrollHandlerWrapper = this.scrollHandler.bind(this);
-      this.setColumnCountWrapper = this.setColumnCount.bind(this);
-      this.setColumnCount();
+  },
+  methods: {
+    scrollHandler() {
+      const currentPage = Math.floor(this.issues.length / this.perPage);
 
-      this.$refs.list.addEventListener('scroll', this.scrollHandlerWrapper);
-      window.addEventListener('resize', this.setColumnCountWrapper);
+      if ((this.scrollTop() > this.scrollHeight() - 100) && !this.loadingNewPage
+        && currentPage === this.page) {
+        this.loadingNewPage = true;
+        this.page += 1;
+      }
+    },
+    toggleIssue(e, issue) {
+      if (e.target.tagName !== 'A') {
+        ModalStore.toggleIssue(issue);
+      }
+    },
+    listHeight() {
+      return this.$refs.list.getBoundingClientRect().height;
     },
-    beforeDestroy() {
-      this.$refs.list.removeEventListener('scroll', this.scrollHandlerWrapper);
-      window.removeEventListener('resize', this.setColumnCountWrapper);
+    scrollHeight() {
+      return this.$refs.list.scrollHeight;
     },
-    components: {
-      'issue-card-inner': gl.issueBoards.IssueCardInner,
+    scrollTop() {
+      return this.$refs.list.scrollTop + this.listHeight();
     },
-    template: `
-      <section
-        class="add-issues-list add-issues-list-columns"
-        ref="list">
+    showIssue(issue) {
+      if (this.activeTab === 'all') return true;
+
+      const index = ModalStore.selectedIssueIndex(issue);
+
+      return index !== -1;
+    },
+    setColumnCount() {
+      const breakpoint = bp.getBreakpointSize();
+
+      if (breakpoint === 'lg' || breakpoint === 'md') {
+        this.columns = 3;
+      } else if (breakpoint === 'sm') {
+        this.columns = 2;
+      } else {
+        this.columns = 1;
+      }
+    },
+  },
+  mounted() {
+    this.scrollHandlerWrapper = this.scrollHandler.bind(this);
+    this.setColumnCountWrapper = this.setColumnCount.bind(this);
+    this.setColumnCount();
+
+    this.$refs.list.addEventListener('scroll', this.scrollHandlerWrapper);
+    window.addEventListener('resize', this.setColumnCountWrapper);
+  },
+  beforeDestroy() {
+    this.$refs.list.removeEventListener('scroll', this.scrollHandlerWrapper);
+    window.removeEventListener('resize', this.setColumnCountWrapper);
+  },
+  components: {
+    'issue-card-inner': gl.issueBoards.IssueCardInner,
+  },
+  template: `
+    <section
+      class="add-issues-list add-issues-list-columns"
+      ref="list">
+      <div
+        class="empty-state add-issues-empty-state-filter text-center"
+        v-if="issuesCount > 0 && issues.length === 0">
         <div
-          class="empty-state add-issues-empty-state-filter text-center"
-          v-if="issuesCount > 0 && issues.length === 0">
-          <div
-            class="svg-content"
-            v-html="image">
-          </div>
-          <div class="text-content">
-            <h4>
-              There are no issues to show.
-            </h4>
-          </div>
+          class="svg-content"
+          v-html="image">
+        </div>
+        <div class="text-content">
+          <h4>
+            There are no issues to show.
+          </h4>
         </div>
+      </div>
+      <div
+        v-for="group in groupedIssues"
+        class="add-issues-list-column">
         <div
-          v-for="group in groupedIssues"
-          class="add-issues-list-column">
+          v-for="issue in group"
+          v-if="showIssue(issue)"
+          class="card-parent">
           <div
-            v-for="issue in group"
-            v-if="showIssue(issue)"
-            class="card-parent">
-            <div
-              class="card"
-              :class="{ 'is-active': issue.selected }"
-              @click="toggleIssue($event, issue)">
-              <issue-card-inner
-                :issue="issue"
-                :issue-link-base="issueLinkBase"
-                :root-path="rootPath">
-              </issue-card-inner>
-              <span
-                :aria-label="'Issue #' + issue.id + ' selected'"
-                aria-checked="true"
-                v-if="issue.selected"
-                class="issue-card-selected text-center">
-                <i class="fa fa-check"></i>
-              </span>
-            </div>
+            class="card"
+            :class="{ 'is-active': issue.selected }"
+            @click="toggleIssue($event, issue)">
+            <issue-card-inner
+              :issue="issue"
+              :issue-link-base="issueLinkBase"
+              :root-path="rootPath">
+            </issue-card-inner>
+            <span
+              :aria-label="'Issue #' + issue.id + ' selected'"
+              aria-checked="true"
+              v-if="issue.selected"
+              class="issue-card-selected text-center">
+              <i class="fa fa-check"></i>
+            </span>
           </div>
         </div>
-      </section>
-    `,
-  });
-})();
+      </div>
+    </section>
+  `,
+});
diff --git a/app/assets/javascripts/boards/components/modal/lists_dropdown.js b/app/assets/javascripts/boards/components/modal/lists_dropdown.js
index 9e9ed46ab8de6fec1598f1a517733a807f90f80f..8cd15df90fa09b64af1a1bb9953cf80161dac5d6 100644
--- a/app/assets/javascripts/boards/components/modal/lists_dropdown.js
+++ b/app/assets/javascripts/boards/components/modal/lists_dropdown.js
@@ -1,57 +1,55 @@
 import Vue from 'vue';
 
-(() => {
-  const ModalStore = gl.issueBoards.ModalStore;
+const ModalStore = gl.issueBoards.ModalStore;
 
-  gl.issueBoards.ModalFooterListsDropdown = Vue.extend({
-    data() {
-      return {
-        modal: ModalStore.store,
-        state: gl.issueBoards.BoardsStore.state,
-      };
+gl.issueBoards.ModalFooterListsDropdown = Vue.extend({
+  data() {
+    return {
+      modal: ModalStore.store,
+      state: gl.issueBoards.BoardsStore.state,
+    };
+  },
+  computed: {
+    selected() {
+      return this.modal.selectedList || this.state.lists[0];
     },
-    computed: {
-      selected() {
-        return this.modal.selectedList || this.state.lists[0];
-      },
-    },
-    destroyed() {
-      this.modal.selectedList = null;
-    },
-    template: `
-      <div class="dropdown inline">
-        <button
-          class="dropdown-menu-toggle"
-          type="button"
-          data-toggle="dropdown"
-          aria-expanded="false">
-          <span
-            class="dropdown-label-box"
-            :style="{ backgroundColor: selected.label.color }">
-          </span>
-          {{ selected.title }}
-          <i class="fa fa-chevron-down"></i>
-        </button>
-        <div class="dropdown-menu dropdown-menu-selectable dropdown-menu-drop-up">
-          <ul>
-            <li
-              v-for="list in state.lists"
-              v-if="list.type == 'label'">
-              <a
-                href="#"
-                role="button"
-                :class="{ 'is-active': list.id == selected.id }"
-                @click.prevent="modal.selectedList = list">
-                <span
-                  class="dropdown-label-box"
-                  :style="{ backgroundColor: list.label.color }">
-                </span>
-                {{ list.title }}
-              </a>
-            </li>
-          </ul>
-        </div>
+  },
+  destroyed() {
+    this.modal.selectedList = null;
+  },
+  template: `
+    <div class="dropdown inline">
+      <button
+        class="dropdown-menu-toggle"
+        type="button"
+        data-toggle="dropdown"
+        aria-expanded="false">
+        <span
+          class="dropdown-label-box"
+          :style="{ backgroundColor: selected.label.color }">
+        </span>
+        {{ selected.title }}
+        <i class="fa fa-chevron-down"></i>
+      </button>
+      <div class="dropdown-menu dropdown-menu-selectable dropdown-menu-drop-up">
+        <ul>
+          <li
+            v-for="list in state.lists"
+            v-if="list.type == 'label'">
+            <a
+              href="#"
+              role="button"
+              :class="{ 'is-active': list.id == selected.id }"
+              @click.prevent="modal.selectedList = list">
+              <span
+                class="dropdown-label-box"
+                :style="{ backgroundColor: list.label.color }">
+              </span>
+              {{ list.title }}
+            </a>
+          </li>
+        </ul>
       </div>
-    `,
-  });
-})();
+    </div>
+  `,
+});
diff --git a/app/assets/javascripts/boards/components/modal/tabs.js b/app/assets/javascripts/boards/components/modal/tabs.js
index 23cb1b13d111489b24b5eb54ae2d19ceb0888d88..3e5d08e3d755a6a3201dfc987135ead43367217c 100644
--- a/app/assets/javascripts/boards/components/modal/tabs.js
+++ b/app/assets/javascripts/boards/components/modal/tabs.js
@@ -1,48 +1,46 @@
 import Vue from 'vue';
 
-(() => {
-  const ModalStore = gl.issueBoards.ModalStore;
+const ModalStore = gl.issueBoards.ModalStore;
 
-  gl.issueBoards.ModalTabs = Vue.extend({
-    mixins: [gl.issueBoards.ModalMixins],
-    data() {
-      return ModalStore.store;
+gl.issueBoards.ModalTabs = Vue.extend({
+  mixins: [gl.issueBoards.ModalMixins],
+  data() {
+    return ModalStore.store;
+  },
+  computed: {
+    selectedCount() {
+      return ModalStore.selectedCount();
     },
-    computed: {
-      selectedCount() {
-        return ModalStore.selectedCount();
-      },
-    },
-    destroyed() {
-      this.activeTab = 'all';
-    },
-    template: `
-      <div class="top-area prepend-top-10 append-bottom-10">
-        <ul class="nav-links issues-state-filters">
-          <li :class="{ 'active': activeTab == 'all' }">
-            <a
-              href="#"
-              role="button"
-              @click.prevent="changeTab('all')">
-              Open issues
-              <span class="badge">
-                {{ issuesCount }}
-              </span>
-            </a>
-          </li>
-          <li :class="{ 'active': activeTab == 'selected' }">
-            <a
-              href="#"
-              role="button"
-              @click.prevent="changeTab('selected')">
-              Selected issues
-              <span class="badge">
-                {{ selectedCount }}
-              </span>
-            </a>
-          </li>
-        </ul>
-      </div>
-    `,
-  });
-})();
+  },
+  destroyed() {
+    this.activeTab = 'all';
+  },
+  template: `
+    <div class="top-area prepend-top-10 append-bottom-10">
+      <ul class="nav-links issues-state-filters">
+        <li :class="{ 'active': activeTab == 'all' }">
+          <a
+            href="#"
+            role="button"
+            @click.prevent="changeTab('all')">
+            Open issues
+            <span class="badge">
+              {{ issuesCount }}
+            </span>
+          </a>
+        </li>
+        <li :class="{ 'active': activeTab == 'selected' }">
+          <a
+            href="#"
+            role="button"
+            @click.prevent="changeTab('selected')">
+            Selected issues
+            <span class="badge">
+              {{ selectedCount }}
+            </span>
+          </a>
+        </li>
+      </ul>
+    </div>
+  `,
+});
diff --git a/app/assets/javascripts/boards/components/new_list_dropdown.js b/app/assets/javascripts/boards/components/new_list_dropdown.js
index 556826a91487b2f40f1005f60570ee99fb266df5..22f20305624f02ecbc9ed97cd0b830cfc530be22 100644
--- a/app/assets/javascripts/boards/components/new_list_dropdown.js
+++ b/app/assets/javascripts/boards/components/new_list_dropdown.js
@@ -1,76 +1,74 @@
 /* eslint-disable comma-dangle, func-names, no-new, space-before-function-paren, one-var */
 
-(() => {
-  window.gl = window.gl || {};
-  window.gl.issueBoards = window.gl.issueBoards || {};
+window.gl = window.gl || {};
+window.gl.issueBoards = window.gl.issueBoards || {};
 
-  const Store = gl.issueBoards.BoardsStore;
+const Store = gl.issueBoards.BoardsStore;
 
-  $(document).off('created.label').on('created.label', (e, label) => {
-    Store.new({
+$(document).off('created.label').on('created.label', (e, label) => {
+  Store.new({
+    title: label.title,
+    position: Store.state.lists.length - 2,
+    list_type: 'label',
+    label: {
+      id: label.id,
       title: label.title,
-      position: Store.state.lists.length - 2,
-      list_type: 'label',
-      label: {
-        id: label.id,
-        title: label.title,
-        color: label.color
-      }
-    });
+      color: label.color
+    }
   });
+});
 
-  gl.issueBoards.newListDropdownInit = () => {
-    $('.js-new-board-list').each(function () {
-      const $this = $(this);
-      new gl.CreateLabelDropdown($this.closest('.dropdown').find('.dropdown-new-label'), $this.data('namespace-path'), $this.data('project-path'));
+gl.issueBoards.newListDropdownInit = () => {
+  $('.js-new-board-list').each(function () {
+    const $this = $(this);
+    new gl.CreateLabelDropdown($this.closest('.dropdown').find('.dropdown-new-label'), $this.data('namespace-path'), $this.data('project-path'));
 
-      $this.glDropdown({
-        data(term, callback) {
-          $.get($this.attr('data-labels'))
-            .then((resp) => {
-              callback(resp);
-            });
-        },
-        renderRow (label) {
-          const active = Store.findList('title', label.title);
-          const $li = $('<li />');
-          const $a = $('<a />', {
-            class: (active ? `is-active js-board-list-${active.id}` : ''),
-            text: label.title,
-            href: '#'
-          });
-          const $labelColor = $('<span />', {
-            class: 'dropdown-label-box',
-            style: `background-color: ${label.color}`
+    $this.glDropdown({
+      data(term, callback) {
+        $.get($this.attr('data-labels'))
+          .then((resp) => {
+            callback(resp);
           });
+      },
+      renderRow (label) {
+        const active = Store.findList('title', label.title);
+        const $li = $('<li />');
+        const $a = $('<a />', {
+          class: (active ? `is-active js-board-list-${active.id}` : ''),
+          text: label.title,
+          href: '#'
+        });
+        const $labelColor = $('<span />', {
+          class: 'dropdown-label-box',
+          style: `background-color: ${label.color}`
+        });
 
-          return $li.append($a.prepend($labelColor));
-        },
-        search: {
-          fields: ['title']
-        },
-        filterable: true,
-        selectable: true,
-        multiSelect: true,
-        clicked (label, $el, e) {
-          e.preventDefault();
+        return $li.append($a.prepend($labelColor));
+      },
+      search: {
+        fields: ['title']
+      },
+      filterable: true,
+      selectable: true,
+      multiSelect: true,
+      clicked (label, $el, e) {
+        e.preventDefault();
 
-          if (!Store.findList('title', label.title)) {
-            Store.new({
+        if (!Store.findList('title', label.title)) {
+          Store.new({
+            title: label.title,
+            position: Store.state.lists.length - 2,
+            list_type: 'label',
+            label: {
+              id: label.id,
               title: label.title,
-              position: Store.state.lists.length - 2,
-              list_type: 'label',
-              label: {
-                id: label.id,
-                title: label.title,
-                color: label.color
-              }
-            });
+              color: label.color
+            }
+          });
 
-            Store.state.lists = _.sortBy(Store.state.lists, 'position');
-          }
+          Store.state.lists = _.sortBy(Store.state.lists, 'position');
         }
-      });
+      }
     });
-  };
-})();
+  });
+};
diff --git a/app/assets/javascripts/boards/components/sidebar/remove_issue.js b/app/assets/javascripts/boards/components/sidebar/remove_issue.js
index 772ea4c55651b19c86ebaaa85ed7448dc940e7a3..5597f128b80db4aa078042afd11cf60820d8d4e9 100644
--- a/app/assets/javascripts/boards/components/sidebar/remove_issue.js
+++ b/app/assets/javascripts/boards/components/sidebar/remove_issue.js
@@ -3,59 +3,57 @@
 
 import Vue from 'vue';
 
-(() => {
-  const Store = gl.issueBoards.BoardsStore;
-
-  window.gl = window.gl || {};
-  window.gl.issueBoards = window.gl.issueBoards || {};
-
-  gl.issueBoards.RemoveIssueBtn = Vue.extend({
-    props: {
-      issue: {
-        type: Object,
-        required: true,
-      },
-      list: {
-        type: Object,
-        required: true,
-      },
+const Store = gl.issueBoards.BoardsStore;
+
+window.gl = window.gl || {};
+window.gl.issueBoards = window.gl.issueBoards || {};
+
+gl.issueBoards.RemoveIssueBtn = Vue.extend({
+  props: {
+    issue: {
+      type: Object,
+      required: true,
     },
-    methods: {
-      removeIssue() {
-        const issue = this.issue;
-        const lists = issue.getLists();
-        const labelIds = lists.map(list => list.label.id);
-
-        // Post the remove data
-        gl.boardService.bulkUpdate([issue.globalId], {
-          remove_label_ids: labelIds,
-        }).catch(() => {
-          new Flash('Failed to remove issue from board, please try again.', 'alert');
-
-          lists.forEach((list) => {
-            list.addIssue(issue);
-          });
-        });
+    list: {
+      type: Object,
+      required: true,
+    },
+  },
+  methods: {
+    removeIssue() {
+      const issue = this.issue;
+      const lists = issue.getLists();
+      const labelIds = lists.map(list => list.label.id);
+
+      // Post the remove data
+      gl.boardService.bulkUpdate([issue.globalId], {
+        remove_label_ids: labelIds,
+      }).catch(() => {
+        new Flash('Failed to remove issue from board, please try again.', 'alert');
 
-        // Remove from the frontend store
         lists.forEach((list) => {
-          list.removeIssue(issue);
+          list.addIssue(issue);
         });
+      });
+
+      // Remove from the frontend store
+      lists.forEach((list) => {
+        list.removeIssue(issue);
+      });
 
-        Store.detail.issue = {};
-      },
+      Store.detail.issue = {};
     },
-    template: `
-      <div
-        class="block list"
-        v-if="list.type !== 'closed'">
-        <button
-          class="btn btn-default btn-block"
-          type="button"
-          @click="removeIssue">
-          Remove from board
-        </button>
-      </div>
-    `,
-  });
-})();
+  },
+  template: `
+    <div
+      class="block list"
+      v-if="list.type !== 'closed'">
+      <button
+        class="btn btn-default btn-block"
+        type="button"
+        @click="removeIssue">
+        Remove from board
+      </button>
+    </div>
+  `,
+});
diff --git a/app/assets/javascripts/boards/mixins/modal_mixins.js b/app/assets/javascripts/boards/mixins/modal_mixins.js
index d378b7d4baf0be3265482c8e482587e8a2c36586..2b0a1aaa89ffb31c6a7165e079091b78700ce666 100644
--- a/app/assets/javascripts/boards/mixins/modal_mixins.js
+++ b/app/assets/javascripts/boards/mixins/modal_mixins.js
@@ -1,14 +1,12 @@
-(() => {
-  const ModalStore = gl.issueBoards.ModalStore;
+const ModalStore = gl.issueBoards.ModalStore;
 
-  gl.issueBoards.ModalMixins = {
-    methods: {
-      toggleModal(toggle) {
-        ModalStore.store.showAddIssuesModal = toggle;
-      },
-      changeTab(tab) {
-        ModalStore.store.activeTab = tab;
-      },
+gl.issueBoards.ModalMixins = {
+  methods: {
+    toggleModal(toggle) {
+      ModalStore.store.showAddIssuesModal = toggle;
     },
-  };
-})();
+    changeTab(tab) {
+      ModalStore.store.activeTab = tab;
+    },
+  },
+};
diff --git a/app/assets/javascripts/boards/mixins/sortable_default_options.js b/app/assets/javascripts/boards/mixins/sortable_default_options.js
index b6c6d17274f1d351e77018ee03b04525fa3c1536..38a0eb12f920a40d9cda4ef773ac7dbc2ac80b13 100644
--- a/app/assets/javascripts/boards/mixins/sortable_default_options.js
+++ b/app/assets/javascripts/boards/mixins/sortable_default_options.js
@@ -1,39 +1,37 @@
 /* eslint-disable no-unused-vars, no-mixed-operators, comma-dangle */
 /* global DocumentTouch */
 
-((w) => {
-  window.gl = window.gl || {};
-  window.gl.issueBoards = window.gl.issueBoards || {};
+window.gl = window.gl || {};
+window.gl.issueBoards = window.gl.issueBoards || {};
 
-  gl.issueBoards.onStart = () => {
-    $('.has-tooltip').tooltip('hide')
-      .tooltip('disable');
-    document.body.classList.add('is-dragging');
-  };
-
-  gl.issueBoards.onEnd = () => {
-    $('.has-tooltip').tooltip('enable');
-    document.body.classList.remove('is-dragging');
-  };
+gl.issueBoards.onStart = () => {
+  $('.has-tooltip').tooltip('hide')
+    .tooltip('disable');
+  document.body.classList.add('is-dragging');
+};
 
-  gl.issueBoards.touchEnabled = ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch;
+gl.issueBoards.onEnd = () => {
+  $('.has-tooltip').tooltip('enable');
+  document.body.classList.remove('is-dragging');
+};
 
-  gl.issueBoards.getBoardSortableDefaultOptions = (obj) => {
-    const defaultSortOptions = {
-      animation: 200,
-      forceFallback: true,
-      fallbackClass: 'is-dragging',
-      fallbackOnBody: true,
-      ghostClass: 'is-ghost',
-      filter: '.board-delete, .btn',
-      delay: gl.issueBoards.touchEnabled ? 100 : 0,
-      scrollSensitivity: gl.issueBoards.touchEnabled ? 60 : 100,
-      scrollSpeed: 20,
-      onStart: gl.issueBoards.onStart,
-      onEnd: gl.issueBoards.onEnd
-    };
+gl.issueBoards.touchEnabled = ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch;
 
-    Object.keys(obj).forEach((key) => { defaultSortOptions[key] = obj[key]; });
-    return defaultSortOptions;
+gl.issueBoards.getBoardSortableDefaultOptions = (obj) => {
+  const defaultSortOptions = {
+    animation: 200,
+    forceFallback: true,
+    fallbackClass: 'is-dragging',
+    fallbackOnBody: true,
+    ghostClass: 'is-ghost',
+    filter: '.board-delete, .btn',
+    delay: gl.issueBoards.touchEnabled ? 100 : 0,
+    scrollSensitivity: gl.issueBoards.touchEnabled ? 60 : 100,
+    scrollSpeed: 20,
+    onStart: gl.issueBoards.onStart,
+    onEnd: gl.issueBoards.onEnd
   };
-})(window);
+
+  Object.keys(obj).forEach((key) => { defaultSortOptions[key] = obj[key]; });
+  return defaultSortOptions;
+};
diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js
index bcda70d063846b1396800bc1f20f4a51592339a8..66384d9c03861d74878538c32589489f128864e6 100644
--- a/app/assets/javascripts/boards/stores/boards_store.js
+++ b/app/assets/javascripts/boards/stores/boards_store.js
@@ -3,125 +3,123 @@
 
 import Cookies from 'js-cookie';
 
-(() => {
-  window.gl = window.gl || {};
-  window.gl.issueBoards = window.gl.issueBoards || {};
+window.gl = window.gl || {};
+window.gl.issueBoards = window.gl.issueBoards || {};
 
-  gl.issueBoards.BoardsStore = {
-    disabled: false,
-    filter: {
-      path: '',
-    },
-    state: {},
-    detail: {
-      issue: {}
-    },
-    moving: {
-      issue: {},
-      list: {}
-    },
-    create () {
-      this.state.lists = [];
-      this.filter.path = gl.utils.getUrlParamsArray().join('&');
-    },
-    addList (listObj) {
-      const list = new List(listObj);
-      this.state.lists.push(list);
+gl.issueBoards.BoardsStore = {
+  disabled: false,
+  filter: {
+    path: '',
+  },
+  state: {},
+  detail: {
+    issue: {}
+  },
+  moving: {
+    issue: {},
+    list: {}
+  },
+  create () {
+    this.state.lists = [];
+    this.filter.path = gl.utils.getUrlParamsArray().join('&');
+  },
+  addList (listObj) {
+    const list = new List(listObj);
+    this.state.lists.push(list);
 
-      return list;
-    },
-    new (listObj) {
-      const list = this.addList(listObj);
+    return list;
+  },
+  new (listObj) {
+    const list = this.addList(listObj);
 
-      list
-        .save()
-        .then(() => {
-          this.state.lists = _.sortBy(this.state.lists, 'position');
-        });
-      this.removeBlankState();
-    },
-    updateNewListDropdown (listId) {
-      $(`.js-board-list-${listId}`).removeClass('is-active');
-    },
-    shouldAddBlankState () {
-      // Decide whether to add the blank state
-      return !(this.state.lists.filter(list => list.type !== 'closed')[0]);
-    },
-    addBlankState () {
-      if (!this.shouldAddBlankState() || this.welcomeIsHidden() || this.disabled) return;
-
-      this.addList({
-        id: 'blank',
-        list_type: 'blank',
-        title: 'Welcome to your Issue Board!',
-        position: 0
+    list
+      .save()
+      .then(() => {
+        this.state.lists = _.sortBy(this.state.lists, 'position');
       });
+    this.removeBlankState();
+  },
+  updateNewListDropdown (listId) {
+    $(`.js-board-list-${listId}`).removeClass('is-active');
+  },
+  shouldAddBlankState () {
+    // Decide whether to add the blank state
+    return !(this.state.lists.filter(list => list.type !== 'closed')[0]);
+  },
+  addBlankState () {
+    if (!this.shouldAddBlankState() || this.welcomeIsHidden() || this.disabled) return;
 
-      this.state.lists = _.sortBy(this.state.lists, 'position');
-    },
-    removeBlankState () {
-      this.removeList('blank');
-
-      Cookies.set('issue_board_welcome_hidden', 'true', {
-        expires: 365 * 10,
-        path: ''
-      });
-    },
-    welcomeIsHidden () {
-      return Cookies.get('issue_board_welcome_hidden') === 'true';
-    },
-    removeList (id, type = 'blank') {
-      const list = this.findList('id', id, type);
+    this.addList({
+      id: 'blank',
+      list_type: 'blank',
+      title: 'Welcome to your Issue Board!',
+      position: 0
+    });
 
-      if (!list) return;
+    this.state.lists = _.sortBy(this.state.lists, 'position');
+  },
+  removeBlankState () {
+    this.removeList('blank');
 
-      this.state.lists = this.state.lists.filter(list => list.id !== id);
-    },
-    moveList (listFrom, orderLists) {
-      orderLists.forEach((id, i) => {
-        const list = this.findList('id', parseInt(id, 10));
+    Cookies.set('issue_board_welcome_hidden', 'true', {
+      expires: 365 * 10,
+      path: ''
+    });
+  },
+  welcomeIsHidden () {
+    return Cookies.get('issue_board_welcome_hidden') === 'true';
+  },
+  removeList (id, type = 'blank') {
+    const list = this.findList('id', id, type);
 
-        list.position = i;
-      });
-      listFrom.update();
-    },
-    moveIssueToList (listFrom, listTo, issue, newIndex) {
-      const issueTo = listTo.findIssue(issue.id);
-      const issueLists = issue.getLists();
-      const listLabels = issueLists.map(listIssue => listIssue.label);
+    if (!list) return;
 
-      if (!issueTo) {
-        // Add to new lists issues if it doesn't already exist
-        listTo.addIssue(issue, listFrom, newIndex);
-      } else {
-        listTo.updateIssueLabel(issue, listFrom);
-        issueTo.removeLabel(listFrom.label);
-      }
+    this.state.lists = this.state.lists.filter(list => list.id !== id);
+  },
+  moveList (listFrom, orderLists) {
+    orderLists.forEach((id, i) => {
+      const list = this.findList('id', parseInt(id, 10));
 
-      if (listTo.type === 'closed') {
-        issueLists.forEach((list) => {
-          list.removeIssue(issue);
-        });
-        issue.removeLabels(listLabels);
-      } else {
-        listFrom.removeIssue(issue);
-      }
-    },
-    moveIssueInList (list, issue, oldIndex, newIndex, idArray) {
-      const beforeId = parseInt(idArray[newIndex - 1], 10) || null;
-      const afterId = parseInt(idArray[newIndex + 1], 10) || null;
+      list.position = i;
+    });
+    listFrom.update();
+  },
+  moveIssueToList (listFrom, listTo, issue, newIndex) {
+    const issueTo = listTo.findIssue(issue.id);
+    const issueLists = issue.getLists();
+    const listLabels = issueLists.map(listIssue => listIssue.label);
 
-      list.moveIssue(issue, oldIndex, newIndex, beforeId, afterId);
-    },
-    findList (key, val, type = 'label') {
-      return this.state.lists.filter((list) => {
-        const byType = type ? list['type'] === type : true;
+    if (!issueTo) {
+      // Add to new lists issues if it doesn't already exist
+      listTo.addIssue(issue, listFrom, newIndex);
+    } else {
+      listTo.updateIssueLabel(issue, listFrom);
+      issueTo.removeLabel(listFrom.label);
+    }
 
-        return list[key] === val && byType;
-      })[0];
-    },
-    updateFiltersUrl () {
-      history.pushState(null, null, `?${this.filter.path}`);
+    if (listTo.type === 'closed') {
+      issueLists.forEach((list) => {
+        list.removeIssue(issue);
+      });
+      issue.removeLabels(listLabels);
+    } else {
+      listFrom.removeIssue(issue);
     }
-  };
-})();
+  },
+  moveIssueInList (list, issue, oldIndex, newIndex, idArray) {
+    const beforeId = parseInt(idArray[newIndex - 1], 10) || null;
+    const afterId = parseInt(idArray[newIndex + 1], 10) || null;
+
+    list.moveIssue(issue, oldIndex, newIndex, beforeId, afterId);
+  },
+  findList (key, val, type = 'label') {
+    return this.state.lists.filter((list) => {
+      const byType = type ? list['type'] === type : true;
+
+      return list[key] === val && byType;
+    })[0];
+  },
+  updateFiltersUrl () {
+    history.pushState(null, null, `?${this.filter.path}`);
+  }
+};
diff --git a/app/assets/javascripts/boards/stores/modal_store.js b/app/assets/javascripts/boards/stores/modal_store.js
index 9b009483a3c6c255dfac47c957b88a5d2c38b54a..4fdc925c825de136764e9e4e7b78ae972333f971 100644
--- a/app/assets/javascripts/boards/stores/modal_store.js
+++ b/app/assets/javascripts/boards/stores/modal_store.js
@@ -1,100 +1,98 @@
-(() => {
-  window.gl = window.gl || {};
-  window.gl.issueBoards = window.gl.issueBoards || {};
-
-  class ModalStore {
-    constructor() {
-      this.store = {
-        columns: 3,
-        issues: [],
-        issuesCount: false,
-        selectedIssues: [],
-        showAddIssuesModal: false,
-        activeTab: 'all',
-        selectedList: null,
-        searchTerm: '',
-        loading: false,
-        loadingNewPage: false,
-        filterLoading: false,
-        page: 1,
-        perPage: 50,
-        filter: {
-          path: '',
-        },
-      };
-    }
+window.gl = window.gl || {};
+window.gl.issueBoards = window.gl.issueBoards || {};
+
+class ModalStore {
+  constructor() {
+    this.store = {
+      columns: 3,
+      issues: [],
+      issuesCount: false,
+      selectedIssues: [],
+      showAddIssuesModal: false,
+      activeTab: 'all',
+      selectedList: null,
+      searchTerm: '',
+      loading: false,
+      loadingNewPage: false,
+      filterLoading: false,
+      page: 1,
+      perPage: 50,
+      filter: {
+        path: '',
+      },
+    };
+  }
 
-    selectedCount() {
-      return this.getSelectedIssues().length;
-    }
+  selectedCount() {
+    return this.getSelectedIssues().length;
+  }
 
-    toggleIssue(issueObj) {
-      const issue = issueObj;
-      const selected = issue.selected;
+  toggleIssue(issueObj) {
+    const issue = issueObj;
+    const selected = issue.selected;
 
-      issue.selected = !selected;
+    issue.selected = !selected;
 
-      if (!selected) {
-        this.addSelectedIssue(issue);
-      } else {
-        this.removeSelectedIssue(issue);
-      }
+    if (!selected) {
+      this.addSelectedIssue(issue);
+    } else {
+      this.removeSelectedIssue(issue);
     }
+  }
 
-    toggleAll() {
-      const select = this.selectedCount() !== this.store.issues.length;
+  toggleAll() {
+    const select = this.selectedCount() !== this.store.issues.length;
 
-      this.store.issues.forEach((issue) => {
-        const issueUpdate = issue;
+    this.store.issues.forEach((issue) => {
+      const issueUpdate = issue;
 
-        if (issueUpdate.selected !== select) {
-          issueUpdate.selected = select;
+      if (issueUpdate.selected !== select) {
+        issueUpdate.selected = select;
 
-          if (select) {
-            this.addSelectedIssue(issue);
-          } else {
-            this.removeSelectedIssue(issue);
-          }
+        if (select) {
+          this.addSelectedIssue(issue);
+        } else {
+          this.removeSelectedIssue(issue);
         }
-      });
-    }
+      }
+    });
+  }
 
-    getSelectedIssues() {
-      return this.store.selectedIssues.filter(issue => issue.selected);
-    }
+  getSelectedIssues() {
+    return this.store.selectedIssues.filter(issue => issue.selected);
+  }
 
-    addSelectedIssue(issue) {
-      const index = this.selectedIssueIndex(issue);
+  addSelectedIssue(issue) {
+    const index = this.selectedIssueIndex(issue);
 
-      if (index === -1) {
-        this.store.selectedIssues.push(issue);
-      }
+    if (index === -1) {
+      this.store.selectedIssues.push(issue);
     }
+  }
 
-    removeSelectedIssue(issue, forcePurge = false) {
-      if (this.store.activeTab === 'all' || forcePurge) {
-        this.store.selectedIssues = this.store.selectedIssues
-          .filter(fIssue => fIssue.id !== issue.id);
-      }
+  removeSelectedIssue(issue, forcePurge = false) {
+    if (this.store.activeTab === 'all' || forcePurge) {
+      this.store.selectedIssues = this.store.selectedIssues
+        .filter(fIssue => fIssue.id !== issue.id);
     }
+  }
 
-    purgeUnselectedIssues() {
-      this.store.selectedIssues.forEach((issue) => {
-        if (!issue.selected) {
-          this.removeSelectedIssue(issue, true);
-        }
-      });
-    }
+  purgeUnselectedIssues() {
+    this.store.selectedIssues.forEach((issue) => {
+      if (!issue.selected) {
+        this.removeSelectedIssue(issue, true);
+      }
+    });
+  }
 
-    selectedIssueIndex(issue) {
-      return this.store.selectedIssues.indexOf(issue);
-    }
+  selectedIssueIndex(issue) {
+    return this.store.selectedIssues.indexOf(issue);
+  }
 
-    findSelectedIssue(issue) {
-      return this.store.selectedIssues
-        .filter(filteredIssue => filteredIssue.id === issue.id)[0];
-    }
+  findSelectedIssue(issue) {
+    return this.store.selectedIssues
+      .filter(filteredIssue => filteredIssue.id === issue.id)[0];
   }
+}
 
-  gl.issueBoards.ModalStore = new ModalStore();
-})();
+gl.issueBoards.ModalStore = new ModalStore();
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_code_component.js b/app/assets/javascripts/cycle_analytics/components/stage_code_component.js
index 3f419a96ff96a490bb3c8a1bd05cefb874cb7391..80bd2df6f42eaf1fb91c6f72d4ed4129002a8b99 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_code_component.js
+++ b/app/assets/javascripts/cycle_analytics/components/stage_code_component.js
@@ -2,46 +2,45 @@
 
 import Vue from 'vue';
 
-((global) => {
-  global.cycleAnalytics = global.cycleAnalytics || {};
+const global = window.gl || (window.gl = {});
+global.cycleAnalytics = global.cycleAnalytics || {};
 
-  global.cycleAnalytics.StageCodeComponent = Vue.extend({
-    props: {
-      items: Array,
-      stage: Object,
-    },
-    template: `
-      <div>
-        <div class="events-description">
-          {{ stage.description }}
-          <limit-warning :count="items.length" />
-        </div>
-        <ul class="stage-event-list">
-          <li v-for="mergeRequest in items" class="stage-event-item">
-            <div class="item-details">
-              <img class="avatar" :src="mergeRequest.author.avatarUrl">
-              <h5 class="item-title merge-merquest-title">
-                <a :href="mergeRequest.url">
-                  {{ mergeRequest.title }}
-                </a>
-              </h5>
-              <a :href="mergeRequest.url" class="issue-link">!{{ mergeRequest.iid }}</a>
-              &middot;
-              <span>
-                Opened
-                <a :href="mergeRequest.url" class="issue-date">{{ mergeRequest.createdAt }}</a>
-              </span>
-              <span>
-                by
-                <a :href="mergeRequest.author.webUrl" class="issue-author-link">{{ mergeRequest.author.name }}</a>
-              </span>
-            </div>
-            <div class="item-time">
-              <total-time :time="mergeRequest.totalTime"></total-time>
-            </div>
-          </li>
-        </ul>
+global.cycleAnalytics.StageCodeComponent = Vue.extend({
+  props: {
+    items: Array,
+    stage: Object,
+  },
+  template: `
+    <div>
+      <div class="events-description">
+        {{ stage.description }}
+        <limit-warning :count="items.length" />
       </div>
-    `,
-  });
-})(window.gl || (window.gl = {}));
+      <ul class="stage-event-list">
+        <li v-for="mergeRequest in items" class="stage-event-item">
+          <div class="item-details">
+            <img class="avatar" :src="mergeRequest.author.avatarUrl">
+            <h5 class="item-title merge-merquest-title">
+              <a :href="mergeRequest.url">
+                {{ mergeRequest.title }}
+              </a>
+            </h5>
+            <a :href="mergeRequest.url" class="issue-link">!{{ mergeRequest.iid }}</a>
+            &middot;
+            <span>
+              Opened
+              <a :href="mergeRequest.url" class="issue-date">{{ mergeRequest.createdAt }}</a>
+            </span>
+            <span>
+              by
+              <a :href="mergeRequest.author.webUrl" class="issue-author-link">{{ mergeRequest.author.name }}</a>
+            </span>
+          </div>
+          <div class="item-time">
+            <total-time :time="mergeRequest.totalTime"></total-time>
+          </div>
+        </li>
+      </ul>
+    </div>
+  `,
+});
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_issue_component.js b/app/assets/javascripts/cycle_analytics/components/stage_issue_component.js
index 7ffa38edd9ed2abd68012fa252352aef6c5731fd..20a43798fbedc752ca528f5523cba5dd74006867 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_issue_component.js
+++ b/app/assets/javascripts/cycle_analytics/components/stage_issue_component.js
@@ -2,48 +2,47 @@
 
 import Vue from 'vue';
 
-((global) => {
-  global.cycleAnalytics = global.cycleAnalytics || {};
+const global = window.gl || (window.gl = {});
+global.cycleAnalytics = global.cycleAnalytics || {};
 
-  global.cycleAnalytics.StageIssueComponent = Vue.extend({
-    props: {
-      items: Array,
-      stage: Object,
-    },
-    template: `
-      <div>
-        <div class="events-description">
-          {{ stage.description }}
-          <limit-warning :count="items.length" />
-        </div>
-        <ul class="stage-event-list">
-          <li v-for="issue in items" class="stage-event-item">
-            <div class="item-details">
-              <img class="avatar" :src="issue.author.avatarUrl">
-              <h5 class="item-title issue-title">
-                <a class="issue-title" :href="issue.url">
-                  {{ issue.title }}
-                </a>
-              </h5>
-              <a :href="issue.url" class="issue-link">#{{ issue.iid }}</a>
-              &middot;
-              <span>
-                Opened
-                <a :href="issue.url" class="issue-date">{{ issue.createdAt }}</a>
-              </span>
-              <span>
-                by
-                <a :href="issue.author.webUrl" class="issue-author-link">
-                  {{ issue.author.name }}
-                </a>
-              </span>
-            </div>
-            <div class="item-time">
-              <total-time :time="issue.totalTime"></total-time>
-            </div>
-          </li>
-        </ul>
+global.cycleAnalytics.StageIssueComponent = Vue.extend({
+  props: {
+    items: Array,
+    stage: Object,
+  },
+  template: `
+    <div>
+      <div class="events-description">
+        {{ stage.description }}
+        <limit-warning :count="items.length" />
       </div>
-    `,
-  });
-})(window.gl || (window.gl = {}));
+      <ul class="stage-event-list">
+        <li v-for="issue in items" class="stage-event-item">
+          <div class="item-details">
+            <img class="avatar" :src="issue.author.avatarUrl">
+            <h5 class="item-title issue-title">
+              <a class="issue-title" :href="issue.url">
+                {{ issue.title }}
+              </a>
+            </h5>
+            <a :href="issue.url" class="issue-link">#{{ issue.iid }}</a>
+            &middot;
+            <span>
+              Opened
+              <a :href="issue.url" class="issue-date">{{ issue.createdAt }}</a>
+            </span>
+            <span>
+              by
+              <a :href="issue.author.webUrl" class="issue-author-link">
+                {{ issue.author.name }}
+              </a>
+            </span>
+          </div>
+          <div class="item-time">
+            <total-time :time="issue.totalTime"></total-time>
+          </div>
+        </li>
+      </ul>
+    </div>
+  `,
+});
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_plan_component.js b/app/assets/javascripts/cycle_analytics/components/stage_plan_component.js
index d736c8b0c2897445ad49832b53d175aafb526f73..f33cac3da8248131689a98764d158f1f47c19c46 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_plan_component.js
+++ b/app/assets/javascripts/cycle_analytics/components/stage_plan_component.js
@@ -2,50 +2,49 @@
 import Vue from 'vue';
 import iconCommit from '../svg/icon_commit.svg';
 
-((global) => {
-  global.cycleAnalytics = global.cycleAnalytics || {};
+const global = window.gl || (window.gl = {});
+global.cycleAnalytics = global.cycleAnalytics || {};
 
-  global.cycleAnalytics.StagePlanComponent = Vue.extend({
-    props: {
-      items: Array,
-      stage: Object,
-    },
+global.cycleAnalytics.StagePlanComponent = Vue.extend({
+  props: {
+    items: Array,
+    stage: Object,
+  },
 
-    data() {
-      return { iconCommit };
-    },
+  data() {
+    return { iconCommit };
+  },
 
-    template: `
-      <div>
-        <div class="events-description">
-          {{ stage.description }}
-          <limit-warning :count="items.length" />
-        </div>
-        <ul class="stage-event-list">
-          <li v-for="commit in items" class="stage-event-item">
-            <div class="item-details item-conmmit-component">
-              <img class="avatar" :src="commit.author.avatarUrl">
-              <h5 class="item-title commit-title">
-                <a :href="commit.commitUrl">
-                  {{ commit.title }}
-                </a>
-              </h5>
-              <span>
-                First
-                <span class="commit-icon">${iconCommit}</span>
-                <a :href="commit.commitUrl" class="commit-hash-link monospace">{{ commit.shortSha }}</a>
-                pushed by
-                <a :href="commit.author.webUrl" class="commit-author-link">
-                  {{ commit.author.name }}
-                </a>
-              </span>
-            </div>
-            <div class="item-time">
-              <total-time :time="commit.totalTime"></total-time>
-            </div>
-          </li>
-        </ul>
+  template: `
+    <div>
+      <div class="events-description">
+        {{ stage.description }}
+        <limit-warning :count="items.length" />
       </div>
-    `,
-  });
-})(window.gl || (window.gl = {}));
+      <ul class="stage-event-list">
+        <li v-for="commit in items" class="stage-event-item">
+          <div class="item-details item-conmmit-component">
+            <img class="avatar" :src="commit.author.avatarUrl">
+            <h5 class="item-title commit-title">
+              <a :href="commit.commitUrl">
+                {{ commit.title }}
+              </a>
+            </h5>
+            <span>
+              First
+              <span class="commit-icon">${iconCommit}</span>
+              <a :href="commit.commitUrl" class="commit-hash-link monospace">{{ commit.shortSha }}</a>
+              pushed by
+              <a :href="commit.author.webUrl" class="commit-author-link">
+                {{ commit.author.name }}
+              </a>
+            </span>
+          </div>
+          <div class="item-time">
+            <total-time :time="commit.totalTime"></total-time>
+          </div>
+        </li>
+      </ul>
+    </div>
+  `,
+});
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_production_component.js b/app/assets/javascripts/cycle_analytics/components/stage_production_component.js
index 698a79ca68c4bde999537ff16481919619daefcc..657f538537433799d46416188dcbade6875f3e85 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_production_component.js
+++ b/app/assets/javascripts/cycle_analytics/components/stage_production_component.js
@@ -2,48 +2,47 @@
 
 import Vue from 'vue';
 
-((global) => {
-  global.cycleAnalytics = global.cycleAnalytics || {};
+const global = window.gl || (window.gl = {});
+global.cycleAnalytics = global.cycleAnalytics || {};
 
-  global.cycleAnalytics.StageProductionComponent = Vue.extend({
-    props: {
-      items: Array,
-      stage: Object,
-    },
-    template: `
-      <div>
-        <div class="events-description">
-          {{ stage.description }}
-          <limit-warning :count="items.length" />
-        </div>
-        <ul class="stage-event-list">
-          <li v-for="issue in items" class="stage-event-item">
-            <div class="item-details">
-              <img class="avatar" :src="issue.author.avatarUrl">
-              <h5 class="item-title issue-title">
-                <a class="issue-title" :href="issue.url">
-                  {{ issue.title }}
-                </a>
-              </h5>
-              <a :href="issue.url" class="issue-link">#{{ issue.iid }}</a>
-              &middot;
-              <span>
-                Opened
-                <a :href="issue.url" class="issue-date">{{ issue.createdAt }}</a>
-              </span>
-              <span>
-              by
-              <a :href="issue.author.webUrl" class="issue-author-link">
-                {{ issue.author.name }}
-              </a>
-              </span>
-            </div>
-            <div class="item-time">
-              <total-time :time="issue.totalTime"></total-time>
-            </div>
-          </li>
-        </ul>
+global.cycleAnalytics.StageProductionComponent = Vue.extend({
+  props: {
+    items: Array,
+    stage: Object,
+  },
+  template: `
+    <div>
+      <div class="events-description">
+        {{ stage.description }}
+        <limit-warning :count="items.length" />
       </div>
-    `,
-  });
-})(window.gl || (window.gl = {}));
+      <ul class="stage-event-list">
+        <li v-for="issue in items" class="stage-event-item">
+          <div class="item-details">
+            <img class="avatar" :src="issue.author.avatarUrl">
+            <h5 class="item-title issue-title">
+              <a class="issue-title" :href="issue.url">
+                {{ issue.title }}
+              </a>
+            </h5>
+            <a :href="issue.url" class="issue-link">#{{ issue.iid }}</a>
+            &middot;
+            <span>
+              Opened
+              <a :href="issue.url" class="issue-date">{{ issue.createdAt }}</a>
+            </span>
+            <span>
+            by
+            <a :href="issue.author.webUrl" class="issue-author-link">
+              {{ issue.author.name }}
+            </a>
+            </span>
+          </div>
+          <div class="item-time">
+            <total-time :time="issue.totalTime"></total-time>
+          </div>
+        </li>
+      </ul>
+    </div>
+  `,
+});
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_review_component.js b/app/assets/javascripts/cycle_analytics/components/stage_review_component.js
index e63c41f2a5730410240c870d7c93008c78744194..8a801300647aac189d99c940f971bb771890b44b 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_review_component.js
+++ b/app/assets/javascripts/cycle_analytics/components/stage_review_component.js
@@ -2,58 +2,57 @@
 
 import Vue from 'vue';
 
-((global) => {
-  global.cycleAnalytics = global.cycleAnalytics || {};
+const global = window.gl || (window.gl = {});
+global.cycleAnalytics = global.cycleAnalytics || {};
 
-  global.cycleAnalytics.StageReviewComponent = Vue.extend({
-    props: {
-      items: Array,
-      stage: Object,
-    },
-    template: `
-      <div>
-        <div class="events-description">
-          {{ stage.description }}
-          <limit-warning :count="items.length" />
-        </div>
-        <ul class="stage-event-list">
-          <li v-for="mergeRequest in items" class="stage-event-item">
-            <div class="item-details">
-              <img class="avatar" :src="mergeRequest.author.avatarUrl">
-              <h5 class="item-title merge-merquest-title">
-                <a :href="mergeRequest.url">
-                  {{ mergeRequest.title }}
-                </a>
-              </h5>
-              <a :href="mergeRequest.url" class="issue-link">!{{ mergeRequest.iid }}</a>
-              &middot;
-              <span>
-                Opened
-                <a :href="mergeRequest.url" class="issue-date">{{ mergeRequest.createdAt }}</a>
+global.cycleAnalytics.StageReviewComponent = Vue.extend({
+  props: {
+    items: Array,
+    stage: Object,
+  },
+  template: `
+    <div>
+      <div class="events-description">
+        {{ stage.description }}
+        <limit-warning :count="items.length" />
+      </div>
+      <ul class="stage-event-list">
+        <li v-for="mergeRequest in items" class="stage-event-item">
+          <div class="item-details">
+            <img class="avatar" :src="mergeRequest.author.avatarUrl">
+            <h5 class="item-title merge-merquest-title">
+              <a :href="mergeRequest.url">
+                {{ mergeRequest.title }}
+              </a>
+            </h5>
+            <a :href="mergeRequest.url" class="issue-link">!{{ mergeRequest.iid }}</a>
+            &middot;
+            <span>
+              Opened
+              <a :href="mergeRequest.url" class="issue-date">{{ mergeRequest.createdAt }}</a>
+            </span>
+            <span>
+              by
+              <a :href="mergeRequest.author.webUrl" class="issue-author-link">{{ mergeRequest.author.name }}</a>
+            </span>
+            <template v-if="mergeRequest.state === 'closed'">
+              <span class="merge-request-state">
+                <i class="fa fa-ban"></i>
+                {{ mergeRequest.state.toUpperCase() }}
               </span>
-              <span>
-                by
-                <a :href="mergeRequest.author.webUrl" class="issue-author-link">{{ mergeRequest.author.name }}</a>
+            </template>
+            <template v-else>
+              <span class="merge-request-branch" v-if="mergeRequest.branch">
+                <i class= "fa fa-code-fork"></i>
+                <a :href="mergeRequest.branch.url">{{ mergeRequest.branch.name }}</a>
               </span>
-              <template v-if="mergeRequest.state === 'closed'">
-                <span class="merge-request-state">
-                  <i class="fa fa-ban"></i>
-                  {{ mergeRequest.state.toUpperCase() }}
-                </span>
-              </template>
-              <template v-else>
-                <span class="merge-request-branch" v-if="mergeRequest.branch">
-                  <i class= "fa fa-code-fork"></i>
-                  <a :href="mergeRequest.branch.url">{{ mergeRequest.branch.name }}</a>
-                </span>
-              </template>
-            </div>
-            <div class="item-time">
-              <total-time :time="mergeRequest.totalTime"></total-time>
-            </div>
-          </li>
-        </ul>
-      </div>
-    `,
-  });
-})(window.gl || (window.gl = {}));
+            </template>
+          </div>
+          <div class="item-time">
+            <total-time :time="mergeRequest.totalTime"></total-time>
+          </div>
+        </li>
+      </ul>
+    </div>
+  `,
+});
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_staging_component.js b/app/assets/javascripts/cycle_analytics/components/stage_staging_component.js
index d51f7134e251b722a0fdc10daa66ab8a5cc3f242..4a28637958848ce2fff20c3c63139226b90e489a 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_staging_component.js
+++ b/app/assets/javascripts/cycle_analytics/components/stage_staging_component.js
@@ -2,48 +2,47 @@
 import Vue from 'vue';
 import iconBranch from '../svg/icon_branch.svg';
 
-((global) => {
-  global.cycleAnalytics = global.cycleAnalytics || {};
+const global = window.gl || (window.gl = {});
+global.cycleAnalytics = global.cycleAnalytics || {};
 
-  global.cycleAnalytics.StageStagingComponent = Vue.extend({
-    props: {
-      items: Array,
-      stage: Object,
-    },
-    data() {
-      return { iconBranch };
-    },
-    template: `
-      <div>
-        <div class="events-description">
-          {{ stage.description }}
-          <limit-warning :count="items.length" />
-        </div>
-        <ul class="stage-event-list">
-          <li v-for="build in items" class="stage-event-item item-build-component">
-            <div class="item-details">
-              <img class="avatar" :src="build.author.avatarUrl">
-              <h5 class="item-title">
-                <a :href="build.url" class="pipeline-id">#{{ build.id }}</a>
-                <i class="fa fa-code-fork"></i>
-                <a :href="build.branch.url" class="branch-name monospace">{{ build.branch.name }}</a>
-                <span class="icon-branch">${iconBranch}</span>
-                <a :href="build.commitUrl" class="short-sha monospace">{{ build.shortSha }}</a>
-              </h5>
-              <span>
-                <a :href="build.url" class="build-date">{{ build.date }}</a>
-                by
-                <a :href="build.author.webUrl" class="issue-author-link">
-                  {{ build.author.name }}
-                </a>
-              </span>
-            </div>
-            <div class="item-time">
-              <total-time :time="build.totalTime"></total-time>
-            </div>
-          </li>
-        </ul>
+global.cycleAnalytics.StageStagingComponent = Vue.extend({
+  props: {
+    items: Array,
+    stage: Object,
+  },
+  data() {
+    return { iconBranch };
+  },
+  template: `
+    <div>
+      <div class="events-description">
+        {{ stage.description }}
+        <limit-warning :count="items.length" />
       </div>
-    `,
-  });
-})(window.gl || (window.gl = {}));
+      <ul class="stage-event-list">
+        <li v-for="build in items" class="stage-event-item item-build-component">
+          <div class="item-details">
+            <img class="avatar" :src="build.author.avatarUrl">
+            <h5 class="item-title">
+              <a :href="build.url" class="pipeline-id">#{{ build.id }}</a>
+              <i class="fa fa-code-fork"></i>
+              <a :href="build.branch.url" class="branch-name monospace">{{ build.branch.name }}</a>
+              <span class="icon-branch">${iconBranch}</span>
+              <a :href="build.commitUrl" class="short-sha monospace">{{ build.shortSha }}</a>
+            </h5>
+            <span>
+              <a :href="build.url" class="build-date">{{ build.date }}</a>
+              by
+              <a :href="build.author.webUrl" class="issue-author-link">
+                {{ build.author.name }}
+              </a>
+            </span>
+          </div>
+          <div class="item-time">
+            <total-time :time="build.totalTime"></total-time>
+          </div>
+        </li>
+      </ul>
+    </div>
+  `,
+});
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_test_component.js b/app/assets/javascripts/cycle_analytics/components/stage_test_component.js
index 17ae3a9ddc1516172e7999d79b9b09f5fa204bed..e306026429e004cffcb52e5409b38b42c9794f50 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_test_component.js
+++ b/app/assets/javascripts/cycle_analytics/components/stage_test_component.js
@@ -3,48 +3,47 @@ import Vue from 'vue';
 import iconBuildStatus from '../svg/icon_build_status.svg';
 import iconBranch from '../svg/icon_branch.svg';
 
-((global) => {
-  global.cycleAnalytics = global.cycleAnalytics || {};
+const global = window.gl || (window.gl = {});
+global.cycleAnalytics = global.cycleAnalytics || {};
 
-  global.cycleAnalytics.StageTestComponent = Vue.extend({
-    props: {
-      items: Array,
-      stage: Object,
-    },
-    data() {
-      return { iconBuildStatus, iconBranch };
-    },
-    template: `
-      <div>
-        <div class="events-description">
-          {{ stage.description }}
-          <limit-warning :count="items.length" />
-        </div>
-        <ul class="stage-event-list">
-          <li v-for="build in items" class="stage-event-item item-build-component">
-            <div class="item-details">
-              <h5 class="item-title">
-                <span class="icon-build-status">${iconBuildStatus}</span>
-                <a :href="build.url" class="item-build-name">{{ build.name }}</a>
-                &middot;
-                <a :href="build.url" class="pipeline-id">#{{ build.id }}</a>
-                <i class="fa fa-code-fork"></i>
-                <a :href="build.branch.url" class="branch-name monospace">{{ build.branch.name }}</a>
-                <span class="icon-branch">${iconBranch}</span>
-                <a :href="build.commitUrl" class="short-sha monospace">{{ build.shortSha }}</a>
-              </h5>
-              <span>
-                <a :href="build.url" class="issue-date">
-                  {{ build.date }}
-                </a>
-              </span>
-            </div>
-            <div class="item-time">
-              <total-time :time="build.totalTime"></total-time>
-            </div>
-          </li>
-        </ul>
+global.cycleAnalytics.StageTestComponent = Vue.extend({
+  props: {
+    items: Array,
+    stage: Object,
+  },
+  data() {
+    return { iconBuildStatus, iconBranch };
+  },
+  template: `
+    <div>
+      <div class="events-description">
+        {{ stage.description }}
+        <limit-warning :count="items.length" />
       </div>
-    `,
-  });
-})(window.gl || (window.gl = {}));
+      <ul class="stage-event-list">
+        <li v-for="build in items" class="stage-event-item item-build-component">
+          <div class="item-details">
+            <h5 class="item-title">
+              <span class="icon-build-status">${iconBuildStatus}</span>
+              <a :href="build.url" class="item-build-name">{{ build.name }}</a>
+              &middot;
+              <a :href="build.url" class="pipeline-id">#{{ build.id }}</a>
+              <i class="fa fa-code-fork"></i>
+              <a :href="build.branch.url" class="branch-name monospace">{{ build.branch.name }}</a>
+              <span class="icon-branch">${iconBranch}</span>
+              <a :href="build.commitUrl" class="short-sha monospace">{{ build.shortSha }}</a>
+            </h5>
+            <span>
+              <a :href="build.url" class="issue-date">
+                {{ build.date }}
+              </a>
+            </span>
+          </div>
+          <div class="item-time">
+            <total-time :time="build.totalTime"></total-time>
+          </div>
+        </li>
+      </ul>
+    </div>
+  `,
+});
diff --git a/app/assets/javascripts/cycle_analytics/components/total_time_component.js b/app/assets/javascripts/cycle_analytics/components/total_time_component.js
index b4442ea5566d5d33d46b8738b73f47ffdc84b0ea..77edcb7627323eaa7f22b02b6fe969b095a10490 100644
--- a/app/assets/javascripts/cycle_analytics/components/total_time_component.js
+++ b/app/assets/javascripts/cycle_analytics/components/total_time_component.js
@@ -2,25 +2,24 @@
 
 import Vue from 'vue';
 
-((global) => {
-  global.cycleAnalytics = global.cycleAnalytics || {};
+const global = window.gl || (window.gl = {});
+global.cycleAnalytics = global.cycleAnalytics || {};
 
-  global.cycleAnalytics.TotalTimeComponent = Vue.extend({
-    props: {
-      time: Object,
-    },
-    template: `
-      <span class="total-time">
-        <template v-if="Object.keys(time).length">
-          <template v-if="time.days">{{ time.days }} <span>{{ time.days === 1 ? 'day' : 'days' }}</span></template>
-          <template v-if="time.hours">{{ time.hours }} <span>hr</span></template>
-          <template v-if="time.mins && !time.days">{{ time.mins }} <span>mins</span></template>
-          <template v-if="time.seconds && Object.keys(time).length === 1 || time.seconds === 0">{{ time.seconds }} <span>s</span></template>
-        </template>
-        <template v-else>
-          --
-        </template>
-      </span>
-    `,
-  });
-})(window.gl || (window.gl = {}));
+global.cycleAnalytics.TotalTimeComponent = Vue.extend({
+  props: {
+    time: Object,
+  },
+  template: `
+    <span class="total-time">
+      <template v-if="Object.keys(time).length">
+        <template v-if="time.days">{{ time.days }} <span>{{ time.days === 1 ? 'day' : 'days' }}</span></template>
+        <template v-if="time.hours">{{ time.hours }} <span>hr</span></template>
+        <template v-if="time.mins && !time.days">{{ time.mins }} <span>mins</span></template>
+        <template v-if="time.seconds && Object.keys(time).length === 1 || time.seconds === 0">{{ time.seconds }} <span>s</span></template>
+      </template>
+      <template v-else>
+        --
+      </template>
+    </span>
+  `,
+});
diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_service.js b/app/assets/javascripts/cycle_analytics/cycle_analytics_service.js
index 9f74b14c4b970f896bdbe1a8a871874fc290605a..681d6eef565770152c1c17ad065bdf5b60cc9cef 100644
--- a/app/assets/javascripts/cycle_analytics/cycle_analytics_service.js
+++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_service.js
@@ -1,41 +1,41 @@
 /* eslint-disable no-param-reassign */
-((global) => {
-  global.cycleAnalytics = global.cycleAnalytics || {};
 
-  class CycleAnalyticsService {
-    constructor(options) {
-      this.requestPath = options.requestPath;
-    }
+const global = window.gl || (window.gl = {});
+global.cycleAnalytics = global.cycleAnalytics || {};
 
-    fetchCycleAnalyticsData(options) {
-      options = options || { startDate: 30 };
-
-      return $.ajax({
-        url: this.requestPath,
-        method: 'GET',
-        dataType: 'json',
-        contentType: 'application/json',
-        data: {
-          cycle_analytics: {
-            start_date: options.startDate,
-          },
-        },
-      });
-    }
+class CycleAnalyticsService {
+  constructor(options) {
+    this.requestPath = options.requestPath;
+  }
 
-    fetchStageData(options) {
-      const {
-        stage,
-        startDate,
-      } = options;
+  fetchCycleAnalyticsData(options) {
+    options = options || { startDate: 30 };
 
-      return $.get(`${this.requestPath}/events/${stage.title.toLowerCase()}.json`, {
+    return $.ajax({
+      url: this.requestPath,
+      method: 'GET',
+      dataType: 'json',
+      contentType: 'application/json',
+      data: {
         cycle_analytics: {
-          start_date: startDate,
+          start_date: options.startDate,
         },
-      });
-    }
+      },
+    });
+  }
+
+  fetchStageData(options) {
+    const {
+      stage,
+      startDate,
+    } = options;
+
+    return $.get(`${this.requestPath}/events/${stage.title.toLowerCase()}.json`, {
+      cycle_analytics: {
+        start_date: startDate,
+      },
+    });
   }
+}
 
-  global.cycleAnalytics.CycleAnalyticsService = CycleAnalyticsService;
-})(window.gl || (window.gl = {}));
+global.cycleAnalytics.CycleAnalyticsService = CycleAnalyticsService;
diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js b/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js
index 7ae9de7297c680b201ebc25269a81674d93c9a07..6536a8fd7fa286a657afeebd765e8e05fde1f835 100644
--- a/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js
+++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js
@@ -3,102 +3,101 @@
 require('../lib/utils/text_utility');
 const DEFAULT_EVENT_OBJECTS = require('./default_event_objects');
 
-((global) => {
-  global.cycleAnalytics = global.cycleAnalytics || {};
+const global = window.gl || (window.gl = {});
+global.cycleAnalytics = global.cycleAnalytics || {};
 
-  const EMPTY_STAGE_TEXTS = {
-    issue: 'The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.',
-    plan: 'The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.',
-    code: 'The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.',
-    test: 'The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.',
-    review: 'The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.',
-    staging: 'The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.',
-    production: 'The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.',
-  };
+const EMPTY_STAGE_TEXTS = {
+  issue: 'The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.',
+  plan: 'The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.',
+  code: 'The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.',
+  test: 'The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.',
+  review: 'The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.',
+  staging: 'The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.',
+  production: 'The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.',
+};
 
-  global.cycleAnalytics.CycleAnalyticsStore = {
-    state: {
-      summary: '',
-      stats: '',
-      analytics: '',
-      events: [],
-      stages: [],
-    },
-    setCycleAnalyticsData(data) {
-      this.state = Object.assign(this.state, this.decorateData(data));
-    },
-    decorateData(data) {
-      const newData = {};
+global.cycleAnalytics.CycleAnalyticsStore = {
+  state: {
+    summary: '',
+    stats: '',
+    analytics: '',
+    events: [],
+    stages: [],
+  },
+  setCycleAnalyticsData(data) {
+    this.state = Object.assign(this.state, this.decorateData(data));
+  },
+  decorateData(data) {
+    const newData = {};
 
-      newData.stages = data.stats || [];
-      newData.summary = data.summary || [];
+    newData.stages = data.stats || [];
+    newData.summary = data.summary || [];
 
-      newData.summary.forEach((item) => {
-        item.value = item.value || '-';
-      });
+    newData.summary.forEach((item) => {
+      item.value = item.value || '-';
+    });
 
-      newData.stages.forEach((item) => {
-        const stageSlug = gl.text.dasherize(item.title.toLowerCase());
-        item.active = false;
-        item.isUserAllowed = data.permissions[stageSlug];
-        item.emptyStageText = EMPTY_STAGE_TEXTS[stageSlug];
-        item.component = `stage-${stageSlug}-component`;
-        item.slug = stageSlug;
-      });
-      newData.analytics = data;
-      return newData;
-    },
-    setLoadingState(state) {
-      this.state.isLoading = state;
-    },
-    setErrorState(state) {
-      this.state.hasError = state;
-    },
-    deactivateAllStages() {
-      this.state.stages.forEach((stage) => {
-        stage.active = false;
-      });
-    },
-    setActiveStage(stage) {
-      this.deactivateAllStages();
-      stage.active = true;
-    },
-    setStageEvents(events, stage) {
-      this.state.events = this.decorateEvents(events, stage);
-    },
-    decorateEvents(events, stage) {
-      const newEvents = [];
+    newData.stages.forEach((item) => {
+      const stageSlug = gl.text.dasherize(item.title.toLowerCase());
+      item.active = false;
+      item.isUserAllowed = data.permissions[stageSlug];
+      item.emptyStageText = EMPTY_STAGE_TEXTS[stageSlug];
+      item.component = `stage-${stageSlug}-component`;
+      item.slug = stageSlug;
+    });
+    newData.analytics = data;
+    return newData;
+  },
+  setLoadingState(state) {
+    this.state.isLoading = state;
+  },
+  setErrorState(state) {
+    this.state.hasError = state;
+  },
+  deactivateAllStages() {
+    this.state.stages.forEach((stage) => {
+      stage.active = false;
+    });
+  },
+  setActiveStage(stage) {
+    this.deactivateAllStages();
+    stage.active = true;
+  },
+  setStageEvents(events, stage) {
+    this.state.events = this.decorateEvents(events, stage);
+  },
+  decorateEvents(events, stage) {
+    const newEvents = [];
 
-      events.forEach((item) => {
-        if (!item) return;
+    events.forEach((item) => {
+      if (!item) return;
 
-        const eventItem = Object.assign({}, DEFAULT_EVENT_OBJECTS[stage.slug], item);
+      const eventItem = Object.assign({}, DEFAULT_EVENT_OBJECTS[stage.slug], item);
 
-        eventItem.totalTime = eventItem.total_time;
+      eventItem.totalTime = eventItem.total_time;
 
-        if (eventItem.author) {
-          eventItem.author.webUrl = eventItem.author.web_url;
-          eventItem.author.avatarUrl = eventItem.author.avatar_url;
-        }
+      if (eventItem.author) {
+        eventItem.author.webUrl = eventItem.author.web_url;
+        eventItem.author.avatarUrl = eventItem.author.avatar_url;
+      }
 
-        if (eventItem.created_at) eventItem.createdAt = eventItem.created_at;
-        if (eventItem.short_sha) eventItem.shortSha = eventItem.short_sha;
-        if (eventItem.commit_url) eventItem.commitUrl = eventItem.commit_url;
+      if (eventItem.created_at) eventItem.createdAt = eventItem.created_at;
+      if (eventItem.short_sha) eventItem.shortSha = eventItem.short_sha;
+      if (eventItem.commit_url) eventItem.commitUrl = eventItem.commit_url;
 
-        delete eventItem.author.web_url;
-        delete eventItem.author.avatar_url;
-        delete eventItem.total_time;
-        delete eventItem.created_at;
-        delete eventItem.short_sha;
-        delete eventItem.commit_url;
+      delete eventItem.author.web_url;
+      delete eventItem.author.avatar_url;
+      delete eventItem.total_time;
+      delete eventItem.created_at;
+      delete eventItem.short_sha;
+      delete eventItem.commit_url;
 
-        newEvents.push(eventItem);
-      });
+      newEvents.push(eventItem);
+    });
 
-      return newEvents;
-    },
-    currentActiveStage() {
-      return this.state.stages.find(stage => stage.active);
-    },
-  };
-})(window.gl || (window.gl = {}));
+    return newEvents;
+  },
+  currentActiveStage() {
+    return this.state.stages.find(stage => stage.active);
+  },
+};
diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js
index 2e5f8a09fc1ba8642957d75feead5ee499379c09..fecd531328de5c8809cd9fa25e9204e2d40fd3e5 100644
--- a/app/assets/javascripts/lib/utils/text_utility.js
+++ b/app/assets/javascripts/lib/utils/text_utility.js
@@ -1,192 +1,188 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, quotes, one-var, one-var-declaration-per-line, operator-assignment, no-else-return, prefer-template, prefer-arrow-callback, no-empty, max-len, consistent-return, no-unused-vars, no-return-assign, max-len */
-
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, quotes, one-var, one-var-declaration-per-line, operator-assignment, no-else-return, prefer-template, prefer-arrow-callback, no-empty, max-len, consistent-return, no-unused-vars, no-return-assign, max-len, vars-on-top */
 require('vendor/latinise');
 
-(function() {
-  (function(w) {
-    var base;
-    if (w.gl == null) {
-      w.gl = {};
-    }
-    if ((base = w.gl).text == null) {
-      base.text = {};
+var base;
+var w = window;
+if (w.gl == null) {
+  w.gl = {};
+}
+if ((base = w.gl).text == null) {
+  base.text = {};
+}
+gl.text.addDelimiter = function(text) {
+  return text ? text.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") : text;
+};
+gl.text.highCountTrim = function(count) {
+  return count > 99 ? '99+' : count;
+};
+gl.text.randomString = function() {
+  return Math.random().toString(36).substring(7);
+};
+gl.text.replaceRange = function(s, start, end, substitute) {
+  return s.substring(0, start) + substitute + s.substring(end);
+};
+gl.text.getTextWidth = function(text, font) {
+  /**
+  * Uses canvas.measureText to compute and return the width of the given text of given font in pixels.
+  *
+  * @param {String} text The text to be rendered.
+  * @param {String} font The css font descriptor that text is to be rendered with (e.g. "bold 14px verdana").
+  *
+  * @see http://stackoverflow.com/questions/118241/calculate-text-width-with-javascript/21015393#21015393
+  */
+  // re-use canvas object for better performance
+  var canvas = gl.text.getTextWidth.canvas || (gl.text.getTextWidth.canvas = document.createElement('canvas'));
+  var context = canvas.getContext('2d');
+  context.font = font;
+  return context.measureText(text).width;
+};
+gl.text.selectedText = function(text, textarea) {
+  return text.substring(textarea.selectionStart, textarea.selectionEnd);
+};
+gl.text.lineBefore = function(text, textarea) {
+  var split;
+  split = text.substring(0, textarea.selectionStart).trim().split('\n');
+  return split[split.length - 1];
+};
+gl.text.lineAfter = function(text, textarea) {
+  return text.substring(textarea.selectionEnd).trim().split('\n')[0];
+};
+gl.text.blockTagText = function(text, textArea, blockTag, selected) {
+  var lineAfter, lineBefore;
+  lineBefore = this.lineBefore(text, textArea);
+  lineAfter = this.lineAfter(text, textArea);
+  if (lineBefore === blockTag && lineAfter === blockTag) {
+    // To remove the block tag we have to select the line before & after
+    if (blockTag != null) {
+      textArea.selectionStart = textArea.selectionStart - (blockTag.length + 1);
+      textArea.selectionEnd = textArea.selectionEnd + (blockTag.length + 1);
     }
-    gl.text.addDelimiter = function(text) {
-      return text ? text.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") : text;
-    };
-    gl.text.highCountTrim = function(count) {
-      return count > 99 ? '99+' : count;
-    };
-    gl.text.randomString = function() {
-      return Math.random().toString(36).substring(7);
-    };
-    gl.text.replaceRange = function(s, start, end, substitute) {
-      return s.substring(0, start) + substitute + s.substring(end);
-    };
-    gl.text.getTextWidth = function(text, font) {
-      /**
-      * Uses canvas.measureText to compute and return the width of the given text of given font in pixels.
-      *
-      * @param {String} text The text to be rendered.
-      * @param {String} font The css font descriptor that text is to be rendered with (e.g. "bold 14px verdana").
-      *
-      * @see http://stackoverflow.com/questions/118241/calculate-text-width-with-javascript/21015393#21015393
-      */
-      // re-use canvas object for better performance
-      var canvas = gl.text.getTextWidth.canvas || (gl.text.getTextWidth.canvas = document.createElement('canvas'));
-      var context = canvas.getContext('2d');
-      context.font = font;
-      return context.measureText(text).width;
-    };
-    gl.text.selectedText = function(text, textarea) {
-      return text.substring(textarea.selectionStart, textarea.selectionEnd);
-    };
-    gl.text.lineBefore = function(text, textarea) {
-      var split;
-      split = text.substring(0, textarea.selectionStart).trim().split('\n');
-      return split[split.length - 1];
-    };
-    gl.text.lineAfter = function(text, textarea) {
-      return text.substring(textarea.selectionEnd).trim().split('\n')[0];
-    };
-    gl.text.blockTagText = function(text, textArea, blockTag, selected) {
-      var lineAfter, lineBefore;
-      lineBefore = this.lineBefore(text, textArea);
-      lineAfter = this.lineAfter(text, textArea);
-      if (lineBefore === blockTag && lineAfter === blockTag) {
-        // To remove the block tag we have to select the line before & after
-        if (blockTag != null) {
-          textArea.selectionStart = textArea.selectionStart - (blockTag.length + 1);
-          textArea.selectionEnd = textArea.selectionEnd + (blockTag.length + 1);
-        }
-        return selected;
-      } else {
-        return blockTag + "\n" + selected + "\n" + blockTag;
-      }
-    };
-    gl.text.insertText = function(textArea, text, tag, blockTag, selected, wrap) {
-      var insertText, inserted, selectedSplit, startChar, removedLastNewLine, removedFirstNewLine, currentLineEmpty, lastNewLine;
-      removedLastNewLine = false;
-      removedFirstNewLine = false;
-      currentLineEmpty = false;
+    return selected;
+  } else {
+    return blockTag + "\n" + selected + "\n" + blockTag;
+  }
+};
+gl.text.insertText = function(textArea, text, tag, blockTag, selected, wrap) {
+  var insertText, inserted, selectedSplit, startChar, removedLastNewLine, removedFirstNewLine, currentLineEmpty, lastNewLine;
+  removedLastNewLine = false;
+  removedFirstNewLine = false;
+  currentLineEmpty = false;
 
-      // Remove the first newline
-      if (selected.indexOf('\n') === 0) {
-        removedFirstNewLine = true;
-        selected = selected.replace(/\n+/, '');
-      }
+  // Remove the first newline
+  if (selected.indexOf('\n') === 0) {
+    removedFirstNewLine = true;
+    selected = selected.replace(/\n+/, '');
+  }
 
-      // Remove the last newline
-      if (textArea.selectionEnd - textArea.selectionStart > selected.replace(/\n$/, '').length) {
-        removedLastNewLine = true;
-        selected = selected.replace(/\n$/, '');
-      }
+  // Remove the last newline
+  if (textArea.selectionEnd - textArea.selectionStart > selected.replace(/\n$/, '').length) {
+    removedLastNewLine = true;
+    selected = selected.replace(/\n$/, '');
+  }
 
-      selectedSplit = selected.split('\n');
+  selectedSplit = selected.split('\n');
 
-      if (!wrap) {
-        lastNewLine = textArea.value.substr(0, textArea.selectionStart).lastIndexOf('\n');
+  if (!wrap) {
+    lastNewLine = textArea.value.substr(0, textArea.selectionStart).lastIndexOf('\n');
 
-        // Check whether the current line is empty or consists only of spaces(=handle as empty)
-        if (/^\s*$/.test(textArea.value.substring(lastNewLine, textArea.selectionStart))) {
-          currentLineEmpty = true;
-        }
-      }
+    // Check whether the current line is empty or consists only of spaces(=handle as empty)
+    if (/^\s*$/.test(textArea.value.substring(lastNewLine, textArea.selectionStart))) {
+      currentLineEmpty = true;
+    }
+  }
 
-      startChar = !wrap && !currentLineEmpty && textArea.selectionStart > 0 ? '\n' : '';
+  startChar = !wrap && !currentLineEmpty && textArea.selectionStart > 0 ? '\n' : '';
 
-      if (selectedSplit.length > 1 && (!wrap || (blockTag != null))) {
-        if (blockTag != null) {
-          insertText = this.blockTagText(text, textArea, blockTag, selected);
+  if (selectedSplit.length > 1 && (!wrap || (blockTag != null))) {
+    if (blockTag != null) {
+      insertText = this.blockTagText(text, textArea, blockTag, selected);
+    } else {
+      insertText = selectedSplit.map(function(val) {
+        if (val.indexOf(tag) === 0) {
+          return "" + (val.replace(tag, ''));
         } else {
-          insertText = selectedSplit.map(function(val) {
-            if (val.indexOf(tag) === 0) {
-              return "" + (val.replace(tag, ''));
-            } else {
-              return "" + tag + val;
-            }
-          }).join('\n');
+          return "" + tag + val;
         }
-      } else {
-        insertText = "" + startChar + tag + selected + (wrap ? tag : ' ');
-      }
+      }).join('\n');
+    }
+  } else {
+    insertText = "" + startChar + tag + selected + (wrap ? tag : ' ');
+  }
 
-      if (removedFirstNewLine) {
-        insertText = '\n' + insertText;
-      }
+  if (removedFirstNewLine) {
+    insertText = '\n' + insertText;
+  }
 
-      if (removedLastNewLine) {
-        insertText += '\n';
-      }
+  if (removedLastNewLine) {
+    insertText += '\n';
+  }
 
-      if (document.queryCommandSupported('insertText')) {
-        inserted = document.execCommand('insertText', false, insertText);
-      }
-      if (!inserted) {
-        try {
-          document.execCommand("ms-beginUndoUnit");
-        } catch (error) {}
-        textArea.value = this.replaceRange(text, textArea.selectionStart, textArea.selectionEnd, insertText);
-        try {
-          document.execCommand("ms-endUndoUnit");
-        } catch (error) {}
-      }
-      return this.moveCursor(textArea, tag, wrap, removedLastNewLine);
-    };
-    gl.text.moveCursor = function(textArea, tag, wrapped, removedLastNewLine) {
-      var pos;
-      if (!textArea.setSelectionRange) {
-        return;
-      }
-      if (textArea.selectionStart === textArea.selectionEnd) {
-        if (wrapped) {
-          pos = textArea.selectionStart - tag.length;
-        } else {
-          pos = textArea.selectionStart;
-        }
+  if (document.queryCommandSupported('insertText')) {
+    inserted = document.execCommand('insertText', false, insertText);
+  }
+  if (!inserted) {
+    try {
+      document.execCommand("ms-beginUndoUnit");
+    } catch (error) {}
+    textArea.value = this.replaceRange(text, textArea.selectionStart, textArea.selectionEnd, insertText);
+    try {
+      document.execCommand("ms-endUndoUnit");
+    } catch (error) {}
+  }
+  return this.moveCursor(textArea, tag, wrap, removedLastNewLine);
+};
+gl.text.moveCursor = function(textArea, tag, wrapped, removedLastNewLine) {
+  var pos;
+  if (!textArea.setSelectionRange) {
+    return;
+  }
+  if (textArea.selectionStart === textArea.selectionEnd) {
+    if (wrapped) {
+      pos = textArea.selectionStart - tag.length;
+    } else {
+      pos = textArea.selectionStart;
+    }
 
-        if (removedLastNewLine) {
-          pos -= 1;
-        }
+    if (removedLastNewLine) {
+      pos -= 1;
+    }
 
-        return textArea.setSelectionRange(pos, pos);
-      }
-    };
-    gl.text.updateText = function(textArea, tag, blockTag, wrap) {
-      var $textArea, selected, text;
-      $textArea = $(textArea);
-      textArea = $textArea.get(0);
-      text = $textArea.val();
-      selected = this.selectedText(text, textArea);
-      $textArea.focus();
-      return this.insertText(textArea, text, tag, blockTag, selected, wrap);
-    };
-    gl.text.init = function(form) {
-      var self;
-      self = this;
-      return $('.js-md', form).off('click').on('click', function() {
-        var $this;
-        $this = $(this);
-        return self.updateText($this.closest('.md-area').find('textarea'), $this.data('md-tag'), $this.data('md-block'), !$this.data('md-prepend'));
-      });
-    };
-    gl.text.removeListeners = function(form) {
-      return $('.js-md', form).off();
-    };
-    gl.text.humanize = function(string) {
-      return string.charAt(0).toUpperCase() + string.replace(/_/g, ' ').slice(1);
-    };
-    gl.text.pluralize = function(str, count) {
-      return str + (count > 1 || count === 0 ? 's' : '');
-    };
-    gl.text.truncate = function(string, maxLength) {
-      return string.substr(0, (maxLength - 3)) + '...';
-    };
-    gl.text.dasherize = function(str) {
-      return str.replace(/[_\s]+/g, '-');
-    };
-    gl.text.slugify = function(str) {
-      return str.trim().toLowerCase().latinise();
-    };
-  })(window);
-}).call(window);
+    return textArea.setSelectionRange(pos, pos);
+  }
+};
+gl.text.updateText = function(textArea, tag, blockTag, wrap) {
+  var $textArea, selected, text;
+  $textArea = $(textArea);
+  textArea = $textArea.get(0);
+  text = $textArea.val();
+  selected = this.selectedText(text, textArea);
+  $textArea.focus();
+  return this.insertText(textArea, text, tag, blockTag, selected, wrap);
+};
+gl.text.init = function(form) {
+  var self;
+  self = this;
+  return $('.js-md', form).off('click').on('click', function() {
+    var $this;
+    $this = $(this);
+    return self.updateText($this.closest('.md-area').find('textarea'), $this.data('md-tag'), $this.data('md-block'), !$this.data('md-prepend'));
+  });
+};
+gl.text.removeListeners = function(form) {
+  return $('.js-md', form).off();
+};
+gl.text.humanize = function(string) {
+  return string.charAt(0).toUpperCase() + string.replace(/_/g, ' ').slice(1);
+};
+gl.text.pluralize = function(str, count) {
+  return str + (count > 1 || count === 0 ? 's' : '');
+};
+gl.text.truncate = function(string, maxLength) {
+  return string.substr(0, (maxLength - 3)) + '...';
+};
+gl.text.dasherize = function(str) {
+  return str.replace(/[_\s]+/g, '-');
+};
+gl.text.slugify = function(str) {
+  return str.trim().toLowerCase().latinise();
+};
diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js
index 09c4261b3187d25d5486220db2a7035844749b7f..b9d2fc25c39dca4fc0e5e446c1bfc274e6defdd5 100644
--- a/app/assets/javascripts/lib/utils/url_utility.js
+++ b/app/assets/javascripts/lib/utils/url_utility.js
@@ -1,93 +1,90 @@
 /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, one-var, one-var-declaration-per-line, no-void, guard-for-in, no-restricted-syntax, prefer-template, quotes, max-len */
-(function() {
-  (function(w) {
-    var base;
-    if (w.gl == null) {
-      w.gl = {};
+var base;
+var w = window;
+if (w.gl == null) {
+  w.gl = {};
+}
+if ((base = w.gl).utils == null) {
+  base.utils = {};
+}
+// Returns an array containing the value(s) of the
+// of the key passed as an argument
+w.gl.utils.getParameterValues = function(sParam) {
+  var i, sPageURL, sParameterName, sURLVariables, values;
+  sPageURL = decodeURIComponent(window.location.search.substring(1));
+  sURLVariables = sPageURL.split('&');
+  sParameterName = void 0;
+  values = [];
+  i = 0;
+  while (i < sURLVariables.length) {
+    sParameterName = sURLVariables[i].split('=');
+    if (sParameterName[0] === sParam) {
+      values.push(sParameterName[1].replace(/\+/g, ' '));
     }
-    if ((base = w.gl).utils == null) {
-      base.utils = {};
+    i += 1;
+  }
+  return values;
+};
+// @param {Object} params - url keys and value to merge
+// @param {String} url
+w.gl.utils.mergeUrlParams = function(params, url) {
+  var lastChar, newUrl, paramName, paramValue, pattern;
+  newUrl = decodeURIComponent(url);
+  for (paramName in params) {
+    paramValue = params[paramName];
+    pattern = new RegExp("\\b(" + paramName + "=).*?(&|$)");
+    if (paramValue == null) {
+      newUrl = newUrl.replace(pattern, '');
+    } else if (url.search(pattern) !== -1) {
+      newUrl = newUrl.replace(pattern, "$1" + paramValue + "$2");
+    } else {
+      newUrl = "" + newUrl + (newUrl.indexOf('?') > 0 ? '&' : '?') + paramName + "=" + paramValue;
     }
-    // Returns an array containing the value(s) of the
-    // of the key passed as an argument
-    w.gl.utils.getParameterValues = function(sParam) {
-      var i, sPageURL, sParameterName, sURLVariables, values;
-      sPageURL = decodeURIComponent(window.location.search.substring(1));
-      sURLVariables = sPageURL.split('&');
-      sParameterName = void 0;
-      values = [];
-      i = 0;
-      while (i < sURLVariables.length) {
-        sParameterName = sURLVariables[i].split('=');
-        if (sParameterName[0] === sParam) {
-          values.push(sParameterName[1].replace(/\+/g, ' '));
-        }
-        i += 1;
+  }
+  // Remove a trailing ampersand
+  lastChar = newUrl[newUrl.length - 1];
+  if (lastChar === '&') {
+    newUrl = newUrl.slice(0, -1);
+  }
+  return newUrl;
+};
+// removes parameter query string from url. returns the modified url
+w.gl.utils.removeParamQueryString = function(url, param) {
+  var urlVariables, variables;
+  url = decodeURIComponent(url);
+  urlVariables = url.split('&');
+  return ((function() {
+    var j, len, results;
+    results = [];
+    for (j = 0, len = urlVariables.length; j < len; j += 1) {
+      variables = urlVariables[j];
+      if (variables.indexOf(param) === -1) {
+        results.push(variables);
       }
-      return values;
-    };
-    // @param {Object} params - url keys and value to merge
-    // @param {String} url
-    w.gl.utils.mergeUrlParams = function(params, url) {
-      var lastChar, newUrl, paramName, paramValue, pattern;
-      newUrl = decodeURIComponent(url);
-      for (paramName in params) {
-        paramValue = params[paramName];
-        pattern = new RegExp("\\b(" + paramName + "=).*?(&|$)");
-        if (paramValue == null) {
-          newUrl = newUrl.replace(pattern, '');
-        } else if (url.search(pattern) !== -1) {
-          newUrl = newUrl.replace(pattern, "$1" + paramValue + "$2");
-        } else {
-          newUrl = "" + newUrl + (newUrl.indexOf('?') > 0 ? '&' : '?') + paramName + "=" + paramValue;
-        }
-      }
-      // Remove a trailing ampersand
-      lastChar = newUrl[newUrl.length - 1];
-      if (lastChar === '&') {
-        newUrl = newUrl.slice(0, -1);
-      }
-      return newUrl;
-    };
-    // removes parameter query string from url. returns the modified url
-    w.gl.utils.removeParamQueryString = function(url, param) {
-      var urlVariables, variables;
-      url = decodeURIComponent(url);
-      urlVariables = url.split('&');
-      return ((function() {
-        var j, len, results;
-        results = [];
-        for (j = 0, len = urlVariables.length; j < len; j += 1) {
-          variables = urlVariables[j];
-          if (variables.indexOf(param) === -1) {
-            results.push(variables);
-          }
-        }
-        return results;
-      })()).join('&');
-    };
-    w.gl.utils.removeParams = (params) => {
-      const url = new URL(window.location.href);
-      params.forEach((param) => {
-        url.search = w.gl.utils.removeParamQueryString(url.search, param);
-      });
-      return url.href;
-    };
-    w.gl.utils.getLocationHash = function(url) {
-      var hashIndex;
-      if (typeof url === 'undefined') {
-        // Note: We can't use window.location.hash here because it's
-        // not consistent across browsers - Firefox will pre-decode it
-        url = window.location.href;
-      }
-      hashIndex = url.indexOf('#');
-      return hashIndex === -1 ? null : url.substring(hashIndex + 1);
-    };
+    }
+    return results;
+  })()).join('&');
+};
+w.gl.utils.removeParams = (params) => {
+  const url = new URL(window.location.href);
+  params.forEach((param) => {
+    url.search = w.gl.utils.removeParamQueryString(url.search, param);
+  });
+  return url.href;
+};
+w.gl.utils.getLocationHash = function(url) {
+  var hashIndex;
+  if (typeof url === 'undefined') {
+    // Note: We can't use window.location.hash here because it's
+    // not consistent across browsers - Firefox will pre-decode it
+    url = window.location.href;
+  }
+  hashIndex = url.indexOf('#');
+  return hashIndex === -1 ? null : url.substring(hashIndex + 1);
+};
 
-    w.gl.utils.refreshCurrentPage = () => gl.utils.visitUrl(document.location.href);
+w.gl.utils.refreshCurrentPage = () => gl.utils.visitUrl(document.location.href);
 
-    w.gl.utils.visitUrl = (url) => {
-      document.location.href = url;
-    };
-  })(window);
-}).call(window);
+w.gl.utils.visitUrl = (url) => {
+  document.location.href = url;
+};
diff --git a/app/assets/javascripts/merged_buttons.js b/app/assets/javascripts/merged_buttons.js
index 9548a98f499ec87f0777a3e1961e168102540d9b..7b0997c6520d0ec6c0f1b0a84230c13a7c3b4c3b 100644
--- a/app/assets/javascripts/merged_buttons.js
+++ b/app/assets/javascripts/merged_buttons.js
@@ -1,11 +1,13 @@
 /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, max-len */
 
-(function() {
-  var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
+import '~/lib/utils/url_utility';
 
+(function() {
   this.MergedButtons = (function() {
     function MergedButtons() {
-      this.removeSourceBranch = bind(this.removeSourceBranch, this);
+      this.removeSourceBranch = this.removeSourceBranch.bind(this);
+      this.removeBranchSuccess = this.removeBranchSuccess.bind(this);
+      this.removeBranchError = this.removeBranchError.bind(this);
       this.$removeBranchWidget = $('.remove_source_branch_widget');
       this.$removeBranchProgress = $('.remove_source_branch_in_progress');
       this.$removeBranchFailed = $('.remove_source_branch_widget.failed');
@@ -22,7 +24,7 @@
     MergedButtons.prototype.initEventListeners = function() {
       $(document).on('click', '.remove_source_branch', this.removeSourceBranch);
       $(document).on('ajax:success', '.remove_source_branch', this.removeBranchSuccess);
-      return $(document).on('ajax:error', '.remove_source_branch', this.removeBranchError);
+      $(document).on('ajax:error', '.remove_source_branch', this.removeBranchError);
     };
 
     MergedButtons.prototype.removeSourceBranch = function() {
@@ -31,7 +33,7 @@
     };
 
     MergedButtons.prototype.removeBranchSuccess = function() {
-      return location.reload();
+      gl.utils.refreshCurrentPage();
     };
 
     MergedButtons.prototype.removeBranchError = function() {
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index 279a50eaa33cf548966d8319d691146f05841848..11d44df48671469c89c7e29db55c4604e4396e32 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -246,17 +246,17 @@
   }
 }
 
-.filtered-search-history-dropdown-toggle-button {
+.filtered-search-history-dropdown-wrapper {
+  position: static;
   display: flex;
-  align-items: center;
+  flex-direction: column;
+}
+
+.filtered-search-history-dropdown-toggle-button {
+  flex: 1;
   width: auto;
-  height: 100%;
-  padding-top: 0;
-  padding-left: 0.75em;
-  padding-bottom: 0;
-  padding-right: 0.5em;
+  padding-right: 10px;
 
-  background-color: transparent;
   border-radius: 0;
   border-top: 0;
   border-left: 0;
@@ -264,6 +264,7 @@
   border-right: 1px solid $border-color;
 
   color: $gl-text-color-secondary;
+  line-height: 1;
 
   transition: color 0.1s linear;
 
@@ -275,24 +276,21 @@
   }
 
   .dropdown-toggle-text {
+    display: inline-block;
     color: inherit;
 
     .fa {
+      vertical-align: middle;
       color: inherit;
     }
   }
 
   .fa {
-    position: initial;
+    position: static;
   }
 
 }
 
-.filtered-search-history-dropdown-wrapper {
-  position: initial;
-  flex-shrink: 0;
-}
-
 .filtered-search-history-dropdown {
   width: 40%;
 
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index c241816788b4f9ee361fde5c5a5f9a4601dfbcbc..664539e93e1be8c2255eb2d8dfe24e53b2c2384a 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -158,6 +158,7 @@
     li.task-list-item {
       list-style-type: none;
       position: relative;
+      min-height: 22px;
       padding-left: 28px;
       margin-left: 0 !important;
 
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 712eb7caf33f898b0b3ae1478d44603952dfaba8..20ef9a774e4ff37548d752568a4357e99751a407 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -26,6 +26,7 @@ $gray-dark: darken($gray-light, $darken-dark-factor);
 $gray-darker: #eee;
 $gray-darkest: #c4c4c4;
 
+$green-25: #f6fcf8;
 $green-50: #e4f5eb;
 $green-100: #bae6cc;
 $green-200: #8dd5aa;
@@ -37,6 +38,7 @@ $green-700: #12753a;
 $green-800: #0e5a2d;
 $green-900: #0a4020;
 
+$blue-25: #f6fafd;
 $blue-50: #e4eff9;
 $blue-100: #bcd7f1;
 $blue-200: #8fbce8;
@@ -48,6 +50,7 @@ $blue-700: #17599c;
 $blue-800: #134a81;
 $blue-900: #0f3b66;
 
+$orange-25: #fffcf8;
 $orange-50: #fff2e1;
 $orange-100: #fedfb3;
 $orange-200: #feca81;
@@ -59,6 +62,7 @@ $orange-700: #c26700;
 $orange-800: #a35100;
 $orange-900: #853b00;
 
+$red-25: #fef7f6;
 $red-50: #fbe7e4;
 $red-100: #f4c4bc;
 $red-200: #ed9d90;
@@ -147,7 +151,7 @@ $gl-sidebar-padding: 22px;
 /*
  * Misc
  */
-$row-hover: lighten($blue-50, 2%);
+$row-hover: $blue-25;
 $row-hover-border: $blue-100;
 $progress-color: #c0392b;
 $header-height: 50px;
@@ -223,18 +227,18 @@ $gl-btn-active-gradient: inset 0 2px 3px $gl-btn-active-background;
 /*
  * Commit Diff Colors
  */
-$added: $green-300;
-$deleted: $red-300;
-$line-added: $green-50;
-$line-added-dark: $green-100;
-$line-removed: $red-50;
-$line-removed-dark: $red-100;
-$line-number-old: lighten($red-100, 5%);
-$line-number-new: lighten($green-100, 5%);
-$line-number-select: lighten($orange-100, 5%);
-$line-target-blue: $blue-50;
-$line-select-yellow: $orange-50;
-$line-select-yellow-dark: $orange-100;
+$added: #63c363;
+$deleted: #f77;
+$line-added: #ecfdf0;
+$line-added-dark: #c7f0d2;
+$line-removed: #fbe9eb;
+$line-removed-dark: #fac5cd;
+$line-number-old: #f9d7dc;
+$line-number-new: #ddfbe6;
+$line-number-select: #fbf2da;
+$line-target-blue: #f6faff;
+$line-select-yellow: #fcf8e7;
+$line-select-yellow-dark: #f0e2bd;
 $dark-diff-match-bg: rgba(255, 255, 255, 0.3);
 $dark-diff-match-color: rgba(255, 255, 255, 0.1);
 $file-mode-changed: #777;
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index ad0f2f6efbb6ddcb657a67d585cd32886123c3a2..c78fb8ede79d595860e721f1893612e012d009f4 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -627,7 +627,6 @@ ul.notes {
   }
 
   &:not(.is-disabled):hover,
-  &:not(.is-disabled):focus,
   &.is-active {
     color: $gl-text-green;
 
@@ -641,6 +640,11 @@ ul.notes {
     height: 15px;
     width: 15px;
   }
+
+  .loading {
+    margin: 0;
+    height: auto;
+  }
 }
 
 .discussion-next-btn {
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index 8109427a45f34cfeb6b1d0f78f98b22062e18c25..3ca14dee33c8e7b5acd57fd62930a499c3bb240a 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -60,7 +60,7 @@ class RegistrationsController < Devise::RegistrationsController
   end
 
   def resource
-    @resource ||= Users::CreateService.new(current_user, sign_up_params).build
+    @resource ||= Users::BuildService.new(current_user, sign_up_params).execute
   end
 
   def devise_mapping
diff --git a/app/models/container_repository.rb b/app/models/container_repository.rb
index 82f4182d59a91c7a943b672cfeb6e372a7db1dbb..d0c94d3b6944c82124b628a8cd37063bba1b7cb6 100644
--- a/app/models/container_repository.rb
+++ b/app/models/container_repository.rb
@@ -20,7 +20,8 @@ class ContainerRepository < ActiveRecord::Base
   end
 
   def path
-    @path ||= [project.full_path, name].select(&:present?).join('/')
+    @path ||= [project.full_path, name]
+      .select(&:present?).join('/').downcase
   end
 
   def location
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index fbdaa45565104fbcf1d81f141428e6d79703ceec..7828c5806b06c9090daa8261137a7a055bf65100 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -58,6 +58,9 @@ module Projects
         fail(error: @project.errors.full_messages.join(', '))
       end
       @project
+    rescue ActiveRecord::RecordInvalid => e
+      message = "Unable to save #{e.record.type}: #{e.record.errors.full_messages.join(", ")} "
+      fail(error: message)
     rescue => e
       fail(error: e.message)
     end
diff --git a/app/services/users/build_service.rb b/app/services/users/build_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9a0a5a12f91817b8442a357461844ff482a582be
--- /dev/null
+++ b/app/services/users/build_service.rb
@@ -0,0 +1,100 @@
+module Users
+  # Service for building a new user.
+  class BuildService < BaseService
+    def initialize(current_user, params = {})
+      @current_user = current_user
+      @params = params.dup
+    end
+
+    def execute
+      raise Gitlab::Access::AccessDeniedError unless can_create_user?
+
+      user = User.new(build_user_params)
+
+      if current_user&.admin?
+        if params[:reset_password]
+          user.generate_reset_token
+          params[:force_random_password] = true
+        end
+
+        if params[:force_random_password]
+          random_password = Devise.friendly_token.first(Devise.password_length.min)
+          user.password = user.password_confirmation = random_password
+        end
+      end
+
+      identity_attrs = params.slice(:extern_uid, :provider)
+
+      if identity_attrs.any?
+        user.identities.build(identity_attrs)
+      end
+
+      user
+    end
+
+    private
+
+    def can_create_user?
+      (current_user.nil? && current_application_settings.signup_enabled?) || current_user&.admin?
+    end
+
+    # Allowed params for creating a user (admins only)
+    def admin_create_params
+      [
+        :access_level,
+        :admin,
+        :avatar,
+        :bio,
+        :can_create_group,
+        :color_scheme_id,
+        :email,
+        :external,
+        :force_random_password,
+        :hide_no_password,
+        :hide_no_ssh_key,
+        :key_id,
+        :linkedin,
+        :name,
+        :password,
+        :password_automatically_set,
+        :password_expires_at,
+        :projects_limit,
+        :remember_me,
+        :skip_confirmation,
+        :skype,
+        :theme_id,
+        :twitter,
+        :username,
+        :website_url
+      ]
+    end
+
+    # Allowed params for user signup
+    def signup_params
+      [
+        :email,
+        :email_confirmation,
+        :password_automatically_set,
+        :name,
+        :password,
+        :username
+      ]
+    end
+
+    def build_user_params
+      if current_user&.admin?
+        user_params = params.slice(*admin_create_params)
+        user_params[:created_by_id] = current_user&.id
+
+        if params[:reset_password]
+          user_params.merge!(force_random_password: true, password_expires_at: nil)
+        end
+      else
+        user_params = params.slice(*signup_params)
+        user_params[:skip_confirmation] = !current_application_settings.send_user_confirmation_email
+      end
+
+      user_params
+    end
+  end
+end
diff --git a/app/services/users/create_service.rb b/app/services/users/create_service.rb
index 93ca7b1141a18ddccfa238f21621f164aa57c8b8..a2105d31f71f3f2281f1f5028d5e95cc47e8f48d 100644
--- a/app/services/users/create_service.rb
+++ b/app/services/users/create_service.rb
@@ -6,34 +6,10 @@ module Users
       @params = params.dup
     end
 
-    def build
-      raise Gitlab::Access::AccessDeniedError unless can_create_user?
-
-      user = User.new(build_user_params)
-
-      if current_user&.admin?
-        if params[:reset_password]
-          @reset_token = user.generate_reset_token
-          params[:force_random_password] = true
-        end
-
-        if params[:force_random_password]
-          random_password = Devise.friendly_token.first(Devise.password_length.min)
-          user.password = user.password_confirmation = random_password
-        end
-      end
-
-      identity_attrs = params.slice(:extern_uid, :provider)
-
-      if identity_attrs.any?
-        user.identities.build(identity_attrs)
-      end
-
-      user
-    end
-
     def execute
-      user = build
+      user = Users::BuildService.new(current_user, params).execute
+
+      @reset_token = user.generate_reset_token if user.recently_sent_password_reset?
 
       if user.save
         log_info("User \"#{user.name}\" (#{user.email}) was created")
@@ -43,70 +19,5 @@ module Users
 
       user
     end
-
-    private
-
-    def can_create_user?
-      (current_user.nil? && current_application_settings.signup_enabled?) || current_user&.admin?
-    end
-
-    # Allowed params for creating a user (admins only)
-    def admin_create_params
-      [
-        :access_level,
-        :admin,
-        :avatar,
-        :bio,
-        :can_create_group,
-        :color_scheme_id,
-        :email,
-        :external,
-        :force_random_password,
-        :password_automatically_set,
-        :hide_no_password,
-        :hide_no_ssh_key,
-        :key_id,
-        :linkedin,
-        :name,
-        :password,
-        :password_expires_at,
-        :projects_limit,
-        :remember_me,
-        :skip_confirmation,
-        :skype,
-        :theme_id,
-        :twitter,
-        :username,
-        :website_url
-      ]
-    end
-
-    # Allowed params for user signup
-    def signup_params
-      [
-        :email,
-        :email_confirmation,
-        :password_automatically_set,
-        :name,
-        :password,
-        :username
-      ]
-    end
-
-    def build_user_params
-      if current_user&.admin?
-        user_params = params.slice(*admin_create_params)
-        user_params[:created_by_id] = current_user&.id
-
-        if params[:reset_password]
-          user_params.merge!(force_random_password: true, password_expires_at: nil)
-        end
-      else
-        user_params = params.slice(*signup_params)
-        user_params[:skip_confirmation] = !current_application_settings.send_user_confirmation_email
-      end
-
-      user_params
-    end
   end
 end
diff --git a/app/views/admin/services/index.html.haml b/app/views/admin/services/index.html.haml
index 6a5986f496a9c4b49e5e76e7bf6669dc0cbfe16c..50132572096df2a02890a3a1c3061ce8468fac4b 100644
--- a/app/views/admin/services/index.html.haml
+++ b/app/views/admin/services/index.html.haml
@@ -13,7 +13,7 @@
     - @services.sort_by(&:title).each do |service|
       %tr
         %td
-          = icon("copy", class: 'clgray')
+          = boolean_to_icon service.activated?
         %td
           = link_to edit_admin_application_settings_service_path(service.id) do
             %strong= service.title
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index 299dace34069072aded8fecbf98ab09d21f8aeee..e34cddeb3e26c00d75ea999850fbc15f036c0416 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -23,7 +23,7 @@
             Registry
 
     - if project_nav_tab? :issues
-      = nav_link(controller: [:issues, :labels, :milestones, :boards]) do
+      = nav_link(controller: @project.default_issues_tracker? ? [:issues, :labels, :milestones, :boards] : :issues) do
         = link_to namespace_project_issues_path(@project.namespace, @project), title: 'Issues', class: 'shortcuts-issues' do
           %span
             Issues
@@ -31,7 +31,7 @@
               %span.badge.count.issue_counter= number_with_delimiter(IssuesFinder.new(current_user, project_id: @project.id).execute.opened.count)
 
     - if project_nav_tab? :merge_requests
-      = nav_link(controller: :merge_requests) do
+      = nav_link(controller: @project.default_issues_tracker? ? :merge_requests : [:merge_requests, :labels, :milestones]) do
         = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do
           %span
             Merge Requests
diff --git a/app/views/projects/labels/edit.html.haml b/app/views/projects/labels/edit.html.haml
index a80a07b52e61d0dcec5c7af5632ecfa4c64d780e..7f0059cdcda05e51623dc869782835957e5dd03c 100644
--- a/app/views/projects/labels/edit.html.haml
+++ b/app/views/projects/labels/edit.html.haml
@@ -1,6 +1,6 @@
 - @no_container = true
 - page_title "Edit", @label.name, "Labels"
-= render "projects/issues/head"
+= render "shared/mr_head"
 
 %div{ class: container_class }
   %h3.page-title
diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index 29f861c09c614955e4060c9335fc8a026c4ffcef..fc72c4fb635d4d7cfe0242b9ad9a6416d2eb7eeb 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -1,7 +1,7 @@
 - @no_container = true
 - page_title "Labels"
 - hide_class = ''
-= render "projects/issues/head"
+= render "shared/mr_head"
 
 - if @labels.exists? || @prioritized_labels.exists?
   %div{ class: container_class }
diff --git a/app/views/projects/labels/new.html.haml b/app/views/projects/labels/new.html.haml
index f0d9be744d1438952059a0d00198dbe604f335eb..8f6c085a361dfb303388569028f6df663995e241 100644
--- a/app/views/projects/labels/new.html.haml
+++ b/app/views/projects/labels/new.html.haml
@@ -1,6 +1,6 @@
 - @no_container = true
 - page_title "New Label"
-= render "projects/issues/head"
+= render "shared/mr_head"
 
 %div{ class: container_class }
   %h3.page-title
diff --git a/app/views/projects/merge_requests/_head.html.haml b/app/views/projects/merge_requests/_head.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..b7f73fe5339dd6efba89ca9f663c93ea6ef086e6
--- /dev/null
+++ b/app/views/projects/merge_requests/_head.html.haml
@@ -0,0 +1,21 @@
+= content_for :sub_nav do
+  .scrolling-tabs-container.sub-nav-scroll
+    = render 'shared/nav_scroll'
+    .nav-links.sub-nav.scrolling-tabs
+      %ul{ class: (container_class) }
+        = nav_link(controller: :merge_requests) do
+          = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests' do
+            %span
+              List
+
+        - if project_nav_tab? :labels
+          = nav_link(controller: :labels) do
+            = link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do
+              %span
+                Labels
+
+        - if project_nav_tab? :milestones
+          = nav_link(controller: :milestones) do
+            = link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do
+              %span
+                Milestones
diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml
index 64f17ab34b1d262dbf6c4ab89d38ad63c67899e9..6bf0035e051e65c1b898e14839ae484e0fcb77c5 100644
--- a/app/views/projects/merge_requests/index.html.haml
+++ b/app/views/projects/merge_requests/index.html.haml
@@ -2,6 +2,9 @@
 - @bulk_edit = can?(current_user, :admin_merge_request, @project)
 
 - page_title "Merge Requests"
+- unless @project.default_issues_tracker?
+  = content_for :sub_nav do
+    = render "projects/merge_requests/head"
 = render 'projects/last_push'
 
 - content_for :page_specific_javascripts do
diff --git a/app/views/projects/milestones/edit.html.haml b/app/views/projects/milestones/edit.html.haml
index 55b0b837c6d438ace95aac72c8f74eb8dc5fe3a1..e57a76dbfd2c0897258204a2c831f6205ed84a88 100644
--- a/app/views/projects/milestones/edit.html.haml
+++ b/app/views/projects/milestones/edit.html.haml
@@ -1,6 +1,6 @@
 - @no_container = true
 - page_title "Edit", @milestone.title, "Milestones"
-= render "projects/issues/head"
+= render "shared/mr_head"
 
 %div{ class: container_class }
 
diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml
index 8e85b2e8a209866957819d990584f288f9da3435..e1096bd1d67f5e6af2b74fa8f6e8dbe5ca454c1c 100644
--- a/app/views/projects/milestones/index.html.haml
+++ b/app/views/projects/milestones/index.html.haml
@@ -1,6 +1,6 @@
 - @no_container = true
 - page_title 'Milestones'
-= render 'projects/issues/head'
+= render "shared/mr_head"
 
 %div{ class: container_class }
   .top-area
diff --git a/app/views/projects/milestones/new.html.haml b/app/views/projects/milestones/new.html.haml
index cda093ade819502f923f7bd2b8cd7e1994b95ba3..586eb909afa09f6721a65597d8be7d689df5ddd9 100644
--- a/app/views/projects/milestones/new.html.haml
+++ b/app/views/projects/milestones/new.html.haml
@@ -1,6 +1,6 @@
 - @no_container = true
 - page_title "New Milestone"
-= render "projects/issues/head"
+= render "shared/mr_head"
 
 %div{ class: container_class }
   %h3.page-title
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index 8b62b156853e7c64892959bd74436b41321ec099..a173117984d435f17b9834effd1d743a7979a181 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -1,7 +1,7 @@
 - @no_container = true
 - page_title       @milestone.title, "Milestones"
 - page_description @milestone.description
-= render "projects/issues/head"
+= render "shared/mr_head"
 
 %div{ class: container_class }
   .detail-page-header.milestone-page-header
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index c12c05eeb73dc85079f89edf11fc7b175b137efc..1f021ad77e58c239c9076d25de74f2e4b0a4d914 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -52,11 +52,10 @@
                     ":aria-label" => "buttonText",
                     "@click" => "resolve",
                     ":title" => "buttonText",
-                    "v-show" => "!loading",
                     ":ref" => "'button'" }
-                  = icon("spin spinner", "v-show" => "loading")
 
-                  = render "shared/icons/icon_status_success.svg"
+                  = icon("spin spinner", "v-show" => "loading", class: 'loading')
+                  %div{ 'v-show' => '!loading' }= render "shared/icons/icon_status_success.svg"
 
             - if current_user
               - if note.emoji_awardable?
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index de1229d58aaefcdcf8bc84ec6d20716a9b054e5f..fd7bd21677cbeafba43a091450b8be8fd1e85eb8 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -12,7 +12,7 @@
 = render "projects/last_push"
 = render "home_panel"
 
-- if current_user && can?(current_user, :download_code, @project)
+- if can?(current_user, :download_code, @project)
   %nav.project-stats{ class: container_class }
     %ul.nav
       %li
diff --git a/app/views/shared/_import_form.html.haml b/app/views/shared/_import_form.html.haml
index 54b5ae2402eca58dff15422a322b524252efd5e9..1c7c73be933a3ab99725dda8717e3e4a75e59edc 100644
--- a/app/views/shared/_import_form.html.haml
+++ b/app/views/shared/_import_form.html.haml
@@ -2,7 +2,7 @@
   = f.label :import_url, class: 'control-label' do
     %span Git repository URL
   .col-sm-10
-    = f.text_field :import_url, autocomplete: 'off', class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git', disabled: true
+    = f.text_field :import_url, autocomplete: 'off', class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git'
 
     .well.prepend-top-20
       %ul
diff --git a/app/views/shared/_mr_head.html.haml b/app/views/shared/_mr_head.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..4211ec6351dfeb9f30825fdd8be5213c4063e233
--- /dev/null
+++ b/app/views/shared/_mr_head.html.haml
@@ -0,0 +1,4 @@
+- if @project.default_issues_tracker?
+  = render "projects/issues/head"
+- else
+  = render "projects/merge_requests/head"
diff --git a/app/views/shared/empty_states/_issues.html.haml b/app/views/shared/empty_states/_issues.html.haml
index 7a7e3d467969b115fbd39a27b8ba37b35b117446..c229d18903f4f793825643f99c4fef3564040c2f 100644
--- a/app/views/shared/empty_states/_issues.html.haml
+++ b/app/views/shared/empty_states/_issues.html.haml
@@ -16,6 +16,8 @@
           Also, issues are searchable and filterable.
         - if project_select_button
           = render 'shared/new_project_item_select', path: 'issues/new', label: 'New issue'
+        = link_to 'New issue', button_path, class: 'btn btn-new', title: 'New issue', id: 'new_issue_link'
       - else
-        %h4 There are no issues to show.
-      = link_to 'New issue', button_path, class: 'btn btn-new', title: 'New issue', id: 'new_issue_link'
+        .text-center
+          %h4 There are no issues to show.
+          = link_to 'New issue', button_path, class: 'btn btn-new', title: 'New issue', id: 'new_issue_link'
diff --git a/changelogs/unreleased/30349-create-users-build-service.yml b/changelogs/unreleased/30349-create-users-build-service.yml
new file mode 100644
index 0000000000000000000000000000000000000000..49b571f5646a5b945af0d8a8f93b68a1b4af0c4f
--- /dev/null
+++ b/changelogs/unreleased/30349-create-users-build-service.yml
@@ -0,0 +1,4 @@
+---
+title: Implement Users::BuildService
+merge_request: 30349
+author: George Andrinopoulos
diff --git a/changelogs/unreleased/30779-show-mr-subnav-issue-tracker.yml b/changelogs/unreleased/30779-show-mr-subnav-issue-tracker.yml
new file mode 100644
index 0000000000000000000000000000000000000000..59f8942911c5b7a2c70b8d0e29489400d77bba90
--- /dev/null
+++ b/changelogs/unreleased/30779-show-mr-subnav-issue-tracker.yml
@@ -0,0 +1,4 @@
+---
+title: Show sub-nav under Merge Requests when issue tracker is non-default.
+merge_request: 10658
+author:
diff --git a/changelogs/unreleased/empty-task-list-alignment.yml b/changelogs/unreleased/empty-task-list-alignment.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ca04e1cab5a0c99b1d12944dff8d363c3eaab0de
--- /dev/null
+++ b/changelogs/unreleased/empty-task-list-alignment.yml
@@ -0,0 +1,4 @@
+---
+title: Fixed alignment of empty task list items
+merge_request:
+author:
diff --git a/changelogs/unreleased/fix-trace-seeking.yml b/changelogs/unreleased/fix-trace-seeking.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b753df4bb430fa2b597f1702a57302a38a7d6d59
--- /dev/null
+++ b/changelogs/unreleased/fix-trace-seeking.yml
@@ -0,0 +1,4 @@
+---
+title: Fix invalid encoding when showing some traces
+merge_request: 10681
+author:
diff --git a/changelogs/unreleased/issues-empty-state-not-centered.yml b/changelogs/unreleased/issues-empty-state-not-centered.yml
new file mode 100644
index 0000000000000000000000000000000000000000..883125e28b1e4f916736cf4647565cd06b0efa04
--- /dev/null
+++ b/changelogs/unreleased/issues-empty-state-not-centered.yml
@@ -0,0 +1,4 @@
+---
+title: Centered issues empty state
+merge_request:
+author:
diff --git a/changelogs/unreleased/pms-lighter-colors.yml b/changelogs/unreleased/pms-lighter-colors.yml
new file mode 100644
index 0000000000000000000000000000000000000000..958d4bc0ac0c94cb21daa27a4f77bf30ba7ad0bf
--- /dev/null
+++ b/changelogs/unreleased/pms-lighter-colors.yml
@@ -0,0 +1,4 @@
+---
+title: Add lighter colors and fix existing light colors
+merge_request: 10690
+author:
diff --git a/changelogs/unreleased/use-hashie-forbidden_attributes.yml b/changelogs/unreleased/use-hashie-forbidden_attributes.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4f429b03a0d019bb3edc869fe8387d4fc1bf5a09
--- /dev/null
+++ b/changelogs/unreleased/use-hashie-forbidden_attributes.yml
@@ -0,0 +1,4 @@
+---
+title: Add hashie-forbidden_attributes gem
+merge_request: 10579
+author: Andy Brown
diff --git a/doc/update/README.md b/doc/update/README.md
index 7921d03d6118539905b2b96413fcd504ed097890..d024a809f24b26101159a08652f3799556e028d2 100644
--- a/doc/update/README.md
+++ b/doc/update/README.md
@@ -50,20 +50,17 @@ update them are in [a separate document][omnidocker].
 
 ## Upgrading without downtime
 
-Starting with GitLab 9.1.0 it's possible to upgrade to a newer version of GitLab
+Starting with GitLab 9.1.0 it's possible to upgrade to a newer major, minor, or patch version of GitLab
 without having to take your GitLab instance offline. However, for this to work
 there are the following requirements:
 
-1. You can only upgrade 1 release at a time. For example, if 9.1.15 is the last
-   release of 9.1 then you can safely upgrade from that version to 9.2.0.
+1. You can only upgrade 1 minor release at a time. So from 9.1 to 9.2, not to 9.3.
+2. You have to be on the most recent patch release. For example, if 9.1.15 is the last
+   release of 9.1 then you can safely upgrade from that version to any 9.2.x version.
    However, if you are running 9.1.14 you first need to upgrade to 9.1.15.
 2. You have to use [post-deployment
    migrations](../development/post_deployment_migrations.md).
-3. You are using PostgreSQL. If you are using MySQL you will still need downtime
-   when upgrading.
-
-This applies to major, minor, and patch releases unless stated otherwise in a
-release post.
+3. You are using PostgreSQL. If you are using MySQL please look at the release post to see if downtime is required.
 
 ## Upgrading between editions
 
diff --git a/lib/banzai/filter/issuable_state_filter.rb b/lib/banzai/filter/issuable_state_filter.rb
index 6b78aa795b444da2525a8535c07503d945707f3e..0b2b8bd7f4db909f46e2baa091d5a58f0d0a7fc1 100644
--- a/lib/banzai/filter/issuable_state_filter.rb
+++ b/lib/banzai/filter/issuable_state_filter.rb
@@ -13,8 +13,8 @@ module Banzai
         issuables = extractor.extract([doc])
 
         issuables.each do |node, issuable|
-          if VISIBLE_STATES.include?(issuable.state)
-            node.children.last.content += " [#{issuable.state}]"
+          if VISIBLE_STATES.include?(issuable.state) && node.children.present?
+            node.add_child(Nokogiri::XML::Text.new(" [#{issuable.state}]", doc))
           end
         end
 
diff --git a/lib/container_registry/path.rb b/lib/container_registry/path.rb
index a4b5f2aba6cf14372e49f623af76172d016914f7..4a585996aa59757a1cd6d099cdce7443d1a9eddf 100644
--- a/lib/container_registry/path.rb
+++ b/lib/container_registry/path.rb
@@ -15,7 +15,7 @@ module ContainerRegistry
     LEVELS_SUPPORTED = 3
 
     def initialize(path)
-      @path = path
+      @path = path.to_s.downcase
     end
 
     def valid?
@@ -25,7 +25,7 @@ module ContainerRegistry
     end
 
     def components
-      @components ||= @path.to_s.split('/')
+      @components ||= @path.split('/')
     end
 
     def nodes
diff --git a/lib/gitlab/ci/trace/stream.rb b/lib/gitlab/ci/trace/stream.rb
index 41dcf846fed1d0a82d8c1c68f78d7419cd901447..3b335cdfd01feb5947a4aa475a6b30f565dd8cdf 100644
--- a/lib/gitlab/ci/trace/stream.rb
+++ b/lib/gitlab/ci/trace/stream.rb
@@ -25,11 +25,10 @@ module Gitlab
         end
 
         def limit(last_bytes = LIMIT_SIZE)
-          stream_size = size
-          if stream_size < last_bytes
-            last_bytes = stream_size
+          if last_bytes < size
+            stream.seek(-last_bytes, IO::SEEK_END)
+            stream.readline
           end
-          stream.seek(-last_bytes, IO::SEEK_END)
         end
 
         def append(data, offset)
diff --git a/lib/gitlab/o_auth/user.rb b/lib/gitlab/o_auth/user.rb
index f98481c6d3a9d0f20a7919e5162e1dceea63c7bd..6e42d8941fbb73a320ff2454d327e1ad87b0a964 100644
--- a/lib/gitlab/o_auth/user.rb
+++ b/lib/gitlab/o_auth/user.rb
@@ -148,7 +148,7 @@ module Gitlab
 
       def build_new_user
         user_params = user_attributes.merge(extern_uid: auth_hash.uid, provider: auth_hash.provider, skip_confirmation: true)
-        Users::CreateService.new(nil, user_params).build
+        Users::BuildService.new(nil, user_params).execute
       end
 
       def user_attributes
diff --git a/lib/tasks/gitlab/gitaly.rake b/lib/tasks/gitlab/gitaly.rake
index 9f6cfe3957c96572aedddbcac59c305e76a77775..8079c6e416cdd89dc2ed86151dca8f447a5bb02a 100644
--- a/lib/tasks/gitlab/gitaly.rake
+++ b/lib/tasks/gitlab/gitaly.rake
@@ -7,10 +7,10 @@ namespace :gitlab do
         abort %(Please specify the directory where you want to install gitaly:\n  rake "gitlab:gitaly:install[/home/git/gitaly]")
       end
 
-      tag = "v#{Gitlab::GitalyClient.expected_server_version}"
+      version = Gitlab::GitalyClient.expected_server_version
       repo = 'https://gitlab.com/gitlab-org/gitaly.git'
 
-      checkout_or_clone_tag(tag: tag, repo: repo, target_dir: args.dir)
+      checkout_or_clone_version(version: version, repo: repo, target_dir: args.dir)
 
       _, status = Gitlab::Popen.popen(%w[which gmake])
       command = status.zero? ? 'gmake' : 'make'
diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake
index dd2fda54e622922c61d96b0a05b8d9e3954278fa..956870668190c0a685d4e78ee32fe39fbc2cd78b 100644
--- a/lib/tasks/gitlab/shell.rake
+++ b/lib/tasks/gitlab/shell.rake
@@ -1,19 +1,18 @@
 namespace :gitlab do
   namespace :shell do
     desc "GitLab | Install or upgrade gitlab-shell"
-    task :install, [:tag, :repo] => :environment do |t, args|
+    task :install, [:repo] => :environment do |t, args|
       warn_user_is_not_gitlab
 
       default_version = Gitlab::Shell.version_required
-      default_version_tag = "v#{default_version}"
-      args.with_defaults(tag: default_version_tag, repo: 'https://gitlab.com/gitlab-org/gitlab-shell.git')
+      args.with_defaults(repo: 'https://gitlab.com/gitlab-org/gitlab-shell.git')
 
       gitlab_url = Gitlab.config.gitlab.url
       # gitlab-shell requires a / at the end of the url
       gitlab_url += '/' unless gitlab_url.end_with?('/')
       target_dir = Gitlab.config.gitlab_shell.path
 
-      checkout_or_clone_tag(tag: default_version_tag, repo: args.repo, target_dir: target_dir)
+      checkout_or_clone_version(version: default_version, repo: args.repo, target_dir: target_dir)
 
       # Make sure we're on the right tag
       Dir.chdir(target_dir) do
diff --git a/lib/tasks/gitlab/task_helpers.rb b/lib/tasks/gitlab/task_helpers.rb
index cdba2262bc27aa642410101dc6be89424f1e1cd1..e3c9d3b491c8e0460491bb7bad89eaf32d350d1a 100644
--- a/lib/tasks/gitlab/task_helpers.rb
+++ b/lib/tasks/gitlab/task_helpers.rb
@@ -147,41 +147,30 @@ module Gitlab
       Rails.env.test? ? Rails.root.join('tmp/tests') : Gitlab.config.gitlab.user_home
     end
 
-    def checkout_or_clone_tag(tag:, repo:, target_dir:)
-      if Dir.exist?(target_dir)
-        checkout_tag(tag, target_dir)
-      else
-        clone_repo(repo, target_dir)
-      end
+    def checkout_or_clone_version(version:, repo:, target_dir:)
+      version =
+        if version.starts_with?("=")
+          version.sub(/\A=/, '') # tag or branch
+        else
+          "v#{version}" # tag
+        end
 
-      reset_to_tag(tag, target_dir)
+      clone_repo(repo, target_dir) unless Dir.exist?(target_dir)
+      checkout_version(version, target_dir)
+      reset_to_version(version, target_dir)
     end
 
     def clone_repo(repo, target_dir)
       run_command!(%W[#{Gitlab.config.git.bin_path} clone -- #{repo} #{target_dir}])
     end
 
-    def checkout_tag(tag, target_dir)
-      run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} fetch --tags --quiet])
-      run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} checkout --quiet #{tag}])
+    def checkout_version(version, target_dir)
+      run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} fetch --quiet])
+      run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} checkout --quiet #{version}])
     end
 
-    def reset_to_tag(tag_wanted, target_dir)
-      tag =
-        begin
-          # First try to checkout without fetching
-          # to avoid stalling tests if the Internet is down.
-          run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} describe -- #{tag_wanted}])
-        rescue Gitlab::TaskFailedError
-          run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} fetch origin])
-          run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} describe -- origin/#{tag_wanted}])
-        end
-
-      if tag
-        run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} reset --hard #{tag.strip}])
-      else
-        raise Gitlab::TaskFailedError
-      end
+    def reset_to_version(version, target_dir)
+      run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} reset --hard #{version}])
     end
   end
 end
diff --git a/lib/tasks/gitlab/workhorse.rake b/lib/tasks/gitlab/workhorse.rake
index baea94bf8ca0456fb9e26b31cf9eed11ffb41858..a00b02188cfa26e2bd12c2b43b46b4f5d6f9d585 100644
--- a/lib/tasks/gitlab/workhorse.rake
+++ b/lib/tasks/gitlab/workhorse.rake
@@ -7,10 +7,10 @@ namespace :gitlab do
         abort %(Please specify the directory where you want to install gitlab-workhorse:\n  rake "gitlab:workhorse:install[/home/git/gitlab-workhorse]")
       end
 
-      tag = "v#{Gitlab::Workhorse.version}"
+      version = Gitlab::Workhorse.version
       repo = 'https://gitlab.com/gitlab-org/gitlab-workhorse.git'
 
-      checkout_or_clone_tag(tag: tag, repo: repo, target_dir: args.dir)
+      checkout_or_clone_version(version: version, repo: repo, target_dir: args.dir)
 
       _, status = Gitlab::Popen.popen(%w[which gmake])
       command = status.zero? ? 'gmake' : 'make'
diff --git a/spec/features/merge_requests/diff_notes_resolve_spec.rb b/spec/features/merge_requests/diff_notes_resolve_spec.rb
index 88d28b649a4ba0e61be205b490d34321ea8a8ddd..0e23c3a8849d938824e2c350fa99413621027212 100644
--- a/spec/features/merge_requests/diff_notes_resolve_spec.rb
+++ b/spec/features/merge_requests/diff_notes_resolve_spec.rb
@@ -198,6 +198,8 @@ feature 'Diff notes resolve', feature: true, js: true do
       it 'does not mark discussion as resolved when resolving single note' do
         page.first '.diff-content .note' do
           first('.line-resolve-btn').click
+
+          expect(page).to have_selector('.note-action-button .loading')
           expect(first('.line-resolve-btn')['data-original-title']).to eq("Resolved by #{user.name}")
         end
 
diff --git a/spec/fixtures/trace/ansi-sequence-and-unicode b/spec/fixtures/trace/ansi-sequence-and-unicode
new file mode 100644
index 0000000000000000000000000000000000000000..5d2466f0d0f4ed17b6cc1834728458e89b66f4c5
--- /dev/null
+++ b/spec/fixtures/trace/ansi-sequence-and-unicode
@@ -0,0 +1,5 @@
+.
+..
+😺
+ヾ(´༎ຶД༎ຶ`)ノ
+許功蓋
diff --git a/spec/javascripts/fixtures/merge_requests.rb b/spec/javascripts/fixtures/merge_requests.rb
index fddeaaf504d6dd074f66cad4b0e041352d61024a..47d904b865bf43550bc83a87df524fa93f3b3e34 100644
--- a/spec/javascripts/fixtures/merge_requests.rb
+++ b/spec/javascripts/fixtures/merge_requests.rb
@@ -7,6 +7,7 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont
   let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
   let(:project) { create(:project, namespace: namespace, path: 'merge-requests-project') }
   let(:merge_request) { create(:merge_request, :with_diffs, source_project: project, target_project: project, description: '- [ ] Task List Item') }
+  let(:merged_merge_request) { create(:merge_request, :merged, source_project: project, target_project: project) }
   let(:pipeline) do
     create(
       :ci_pipeline,
@@ -32,6 +33,12 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont
     render_merge_request(example.description, merge_request)
   end
 
+  it 'merge_requests/merged_merge_request.html.raw' do |example|
+    allow_any_instance_of(MergeRequest).to receive(:source_branch_exists?).and_return(true)
+    allow_any_instance_of(MergeRequest).to receive(:can_remove_source_branch?).and_return(true)
+    render_merge_request(example.description, merged_merge_request)
+  end
+
   private
 
   def render_merge_request(fixture_file_name, merge_request)
diff --git a/spec/javascripts/lib/utils/text_utility_spec.js b/spec/javascripts/lib/utils/text_utility_spec.js
index 4200e9431219cef3dcc030e73a41852b5e092fcb..daef9b93fa57084a8a04bc9ab3a9f3ba20fe5872 100644
--- a/spec/javascripts/lib/utils/text_utility_spec.js
+++ b/spec/javascripts/lib/utils/text_utility_spec.js
@@ -1,110 +1,108 @@
 require('~/lib/utils/text_utility');
 
-(() => {
-  describe('text_utility', () => {
-    describe('gl.text.getTextWidth', () => {
-      it('returns zero width when no text is passed', () => {
-        expect(gl.text.getTextWidth('')).toBe(0);
-      });
+describe('text_utility', () => {
+  describe('gl.text.getTextWidth', () => {
+    it('returns zero width when no text is passed', () => {
+      expect(gl.text.getTextWidth('')).toBe(0);
+    });
 
-      it('returns zero width when no text is passed and font is passed', () => {
-        expect(gl.text.getTextWidth('', '100px sans-serif')).toBe(0);
-      });
+    it('returns zero width when no text is passed and font is passed', () => {
+      expect(gl.text.getTextWidth('', '100px sans-serif')).toBe(0);
+    });
 
-      it('returns width when text is passed', () => {
-        expect(gl.text.getTextWidth('foo') > 0).toBe(true);
-      });
+    it('returns width when text is passed', () => {
+      expect(gl.text.getTextWidth('foo') > 0).toBe(true);
+    });
 
-      it('returns bigger width when font is larger', () => {
-        const largeFont = gl.text.getTextWidth('foo', '100px sans-serif');
-        const regular = gl.text.getTextWidth('foo', '10px sans-serif');
-        expect(largeFont > regular).toBe(true);
-      });
+    it('returns bigger width when font is larger', () => {
+      const largeFont = gl.text.getTextWidth('foo', '100px sans-serif');
+      const regular = gl.text.getTextWidth('foo', '10px sans-serif');
+      expect(largeFont > regular).toBe(true);
     });
+  });
 
-    describe('gl.text.pluralize', () => {
-      it('returns pluralized', () => {
-        expect(gl.text.pluralize('test', 2)).toBe('tests');
-      });
+  describe('gl.text.pluralize', () => {
+    it('returns pluralized', () => {
+      expect(gl.text.pluralize('test', 2)).toBe('tests');
+    });
 
-      it('returns pluralized when count is 0', () => {
-        expect(gl.text.pluralize('test', 0)).toBe('tests');
-      });
+    it('returns pluralized when count is 0', () => {
+      expect(gl.text.pluralize('test', 0)).toBe('tests');
+    });
 
-      it('does not return pluralized', () => {
-        expect(gl.text.pluralize('test', 1)).toBe('test');
-      });
+    it('does not return pluralized', () => {
+      expect(gl.text.pluralize('test', 1)).toBe('test');
     });
+  });
 
-    describe('gl.text.highCountTrim', () => {
-      it('returns 99+ for count >= 100', () => {
-        expect(gl.text.highCountTrim(105)).toBe('99+');
-        expect(gl.text.highCountTrim(100)).toBe('99+');
-      });
+  describe('gl.text.highCountTrim', () => {
+    it('returns 99+ for count >= 100', () => {
+      expect(gl.text.highCountTrim(105)).toBe('99+');
+      expect(gl.text.highCountTrim(100)).toBe('99+');
+    });
 
-      it('returns exact number for count < 100', () => {
-        expect(gl.text.highCountTrim(45)).toBe(45);
-      });
+    it('returns exact number for count < 100', () => {
+      expect(gl.text.highCountTrim(45)).toBe(45);
     });
+  });
 
-    describe('gl.text.insertText', () => {
-      let textArea;
+  describe('gl.text.insertText', () => {
+    let textArea;
 
-      beforeAll(() => {
-        textArea = document.createElement('textarea');
-        document.querySelector('body').appendChild(textArea);
-      });
+    beforeAll(() => {
+      textArea = document.createElement('textarea');
+      document.querySelector('body').appendChild(textArea);
+    });
 
-      afterAll(() => {
-        textArea.parentNode.removeChild(textArea);
-      });
+    afterAll(() => {
+      textArea.parentNode.removeChild(textArea);
+    });
 
-      describe('without selection', () => {
-        it('inserts the tag on an empty line', () => {
-          const initialValue = '';
+    describe('without selection', () => {
+      it('inserts the tag on an empty line', () => {
+        const initialValue = '';
 
-          textArea.value = initialValue;
-          textArea.selectionStart = 0;
-          textArea.selectionEnd = 0;
+        textArea.value = initialValue;
+        textArea.selectionStart = 0;
+        textArea.selectionEnd = 0;
 
-          gl.text.insertText(textArea, textArea.value, '*', null, '', false);
+        gl.text.insertText(textArea, textArea.value, '*', null, '', false);
 
-          expect(textArea.value).toEqual(`${initialValue}* `);
-        });
+        expect(textArea.value).toEqual(`${initialValue}* `);
+      });
 
-        it('inserts the tag on a new line if the current one is not empty', () => {
-          const initialValue = 'some text';
+      it('inserts the tag on a new line if the current one is not empty', () => {
+        const initialValue = 'some text';
 
-          textArea.value = initialValue;
-          textArea.setSelectionRange(initialValue.length, initialValue.length);
+        textArea.value = initialValue;
+        textArea.setSelectionRange(initialValue.length, initialValue.length);
 
-          gl.text.insertText(textArea, textArea.value, '*', null, '', false);
+        gl.text.insertText(textArea, textArea.value, '*', null, '', false);
 
-          expect(textArea.value).toEqual(`${initialValue}\n* `);
-        });
+        expect(textArea.value).toEqual(`${initialValue}\n* `);
+      });
 
-        it('inserts the tag on the same line if the current line only contains spaces', () => {
-          const initialValue = '  ';
+      it('inserts the tag on the same line if the current line only contains spaces', () => {
+        const initialValue = '  ';
 
-          textArea.value = initialValue;
-          textArea.setSelectionRange(initialValue.length, initialValue.length);
+        textArea.value = initialValue;
+        textArea.setSelectionRange(initialValue.length, initialValue.length);
 
-          gl.text.insertText(textArea, textArea.value, '*', null, '', false);
+        gl.text.insertText(textArea, textArea.value, '*', null, '', false);
 
-          expect(textArea.value).toEqual(`${initialValue}* `);
-        });
+        expect(textArea.value).toEqual(`${initialValue}* `);
+      });
 
-        it('inserts the tag on the same line if the current line only contains tabs', () => {
-          const initialValue = '\t\t\t';
+      it('inserts the tag on the same line if the current line only contains tabs', () => {
+        const initialValue = '\t\t\t';
 
-          textArea.value = initialValue;
-          textArea.setSelectionRange(initialValue.length, initialValue.length);
+        textArea.value = initialValue;
+        textArea.setSelectionRange(initialValue.length, initialValue.length);
 
-          gl.text.insertText(textArea, textArea.value, '*', null, '', false);
+        gl.text.insertText(textArea, textArea.value, '*', null, '', false);
 
-          expect(textArea.value).toEqual(`${initialValue}* `);
-        });
+        expect(textArea.value).toEqual(`${initialValue}* `);
       });
     });
   });
-})();
+});
diff --git a/spec/javascripts/merged_buttons_spec.js b/spec/javascripts/merged_buttons_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..b5c5e60dd971a358a9e531f0d9eb6364055d28a0
--- /dev/null
+++ b/spec/javascripts/merged_buttons_spec.js
@@ -0,0 +1,44 @@
+/* global MergedButtons */
+
+import '~/merged_buttons';
+
+describe('MergedButtons', () => {
+  const fixturesPath = 'merge_requests/merged_merge_request.html.raw';
+  preloadFixtures(fixturesPath);
+
+  beforeEach(() => {
+    loadFixtures(fixturesPath);
+    this.mergedButtons = new MergedButtons();
+    this.$removeBranchWidget = $('.remove_source_branch_widget:not(.failed)');
+    this.$removeBranchProgress = $('.remove_source_branch_in_progress');
+    this.$removeBranchFailed = $('.remove_source_branch_widget.failed');
+    this.$removeBranchButton = $('.remove_source_branch');
+  });
+
+  describe('removeSourceBranch', () => {
+    it('shows loader', () => {
+      $('.remove_source_branch').trigger('click');
+      expect(this.$removeBranchProgress).toBeVisible();
+      expect(this.$removeBranchWidget).not.toBeVisible();
+    });
+  });
+
+  describe('removeBranchSuccess', () => {
+    it('refreshes page when branch removed', () => {
+      spyOn(gl.utils, 'refreshCurrentPage').and.stub();
+      const response = { status: 200 };
+      this.$removeBranchButton.trigger('ajax:success', response, 'xhr');
+      expect(gl.utils.refreshCurrentPage).toHaveBeenCalled();
+    });
+  });
+
+  describe('removeBranchError', () => {
+    it('shows error message', () => {
+      const response = { status: 500 };
+      this.$removeBranchButton.trigger('ajax:error', response, 'xhr');
+      expect(this.$removeBranchFailed).toBeVisible();
+      expect(this.$removeBranchProgress).not.toBeVisible();
+      expect(this.$removeBranchWidget).not.toBeVisible();
+    });
+  });
+});
diff --git a/spec/lib/banzai/filter/issuable_state_filter_spec.rb b/spec/lib/banzai/filter/issuable_state_filter_spec.rb
index 603b79a323c5591dbc301b723c79ecf47a165a4f..5cb98163746ddcd842e5b72d282c10f0dc1c4597 100644
--- a/spec/lib/banzai/filter/issuable_state_filter_spec.rb
+++ b/spec/lib/banzai/filter/issuable_state_filter_spec.rb
@@ -6,8 +6,8 @@ describe Banzai::Filter::IssuableStateFilter, lib: true do
 
   let(:user) { create(:user) }
 
-  def create_link(data)
-    link_to('text', '', class: 'gfm has-tooltip', data: data)
+  def create_link(text, data)
+    link_to(text, '', class: 'gfm has-tooltip', data: data)
   end
 
   it 'ignores non-GFM links' do
@@ -19,16 +19,37 @@ describe Banzai::Filter::IssuableStateFilter, lib: true do
 
   it 'ignores non-issuable links' do
     project = create(:empty_project, :public)
-    link = create_link(project: project, reference_type: 'issue')
+    link = create_link('text', project: project, reference_type: 'issue')
     doc = filter(link, current_user: user)
 
     expect(doc.css('a').last.text).to eq('text')
   end
 
+  it 'ignores issuable links with empty content' do
+    issue = create(:issue, :closed)
+    link = create_link('', issue: issue.id, reference_type: 'issue')
+    doc = filter(link, current_user: user)
+
+    expect(doc.css('a').last.text).to eq('')
+  end
+
+  it 'adds text with standard formatting' do
+    issue = create(:issue, :closed)
+    link = create_link(
+      'something <strong>else</strong>'.html_safe,
+      issue: issue.id,
+      reference_type: 'issue'
+    )
+    doc = filter(link, current_user: user)
+
+    expect(doc.css('a').last.inner_html).
+      to eq('something <strong>else</strong> [closed]')
+  end
+
   context 'for issue references' do
     it 'ignores open issue references' do
       issue = create(:issue)
-      link = create_link(issue: issue.id, reference_type: 'issue')
+      link = create_link('text', issue: issue.id, reference_type: 'issue')
       doc = filter(link, current_user: user)
 
       expect(doc.css('a').last.text).to eq('text')
@@ -36,7 +57,7 @@ describe Banzai::Filter::IssuableStateFilter, lib: true do
 
     it 'ignores reopened issue references' do
       reopened_issue = create(:issue, :reopened)
-      link = create_link(issue: reopened_issue.id, reference_type: 'issue')
+      link = create_link('text', issue: reopened_issue.id, reference_type: 'issue')
       doc = filter(link, current_user: user)
 
       expect(doc.css('a').last.text).to eq('text')
@@ -44,7 +65,7 @@ describe Banzai::Filter::IssuableStateFilter, lib: true do
 
     it 'appends [closed] to closed issue references' do
       closed_issue = create(:issue, :closed)
-      link = create_link(issue: closed_issue.id, reference_type: 'issue')
+      link = create_link('text', issue: closed_issue.id, reference_type: 'issue')
       doc = filter(link, current_user: user)
 
       expect(doc.css('a').last.text).to eq('text [closed]')
@@ -54,7 +75,7 @@ describe Banzai::Filter::IssuableStateFilter, lib: true do
   context 'for merge request references' do
     it 'ignores open merge request references' do
       mr = create(:merge_request)
-      link = create_link(merge_request: mr.id, reference_type: 'merge_request')
+      link = create_link('text', merge_request: mr.id, reference_type: 'merge_request')
       doc = filter(link, current_user: user)
 
       expect(doc.css('a').last.text).to eq('text')
@@ -62,7 +83,7 @@ describe Banzai::Filter::IssuableStateFilter, lib: true do
 
     it 'ignores reopened merge request references' do
       mr = create(:merge_request, :reopened)
-      link = create_link(merge_request: mr.id, reference_type: 'merge_request')
+      link = create_link('text', merge_request: mr.id, reference_type: 'merge_request')
       doc = filter(link, current_user: user)
 
       expect(doc.css('a').last.text).to eq('text')
@@ -70,7 +91,7 @@ describe Banzai::Filter::IssuableStateFilter, lib: true do
 
     it 'ignores locked merge request references' do
       mr = create(:merge_request, :locked)
-      link = create_link(merge_request: mr.id, reference_type: 'merge_request')
+      link = create_link('text', merge_request: mr.id, reference_type: 'merge_request')
       doc = filter(link, current_user: user)
 
       expect(doc.css('a').last.text).to eq('text')
@@ -78,7 +99,7 @@ describe Banzai::Filter::IssuableStateFilter, lib: true do
 
     it 'appends [closed] to closed merge request references' do
       mr = create(:merge_request, :closed)
-      link = create_link(merge_request: mr.id, reference_type: 'merge_request')
+      link = create_link('text', merge_request: mr.id, reference_type: 'merge_request')
       doc = filter(link, current_user: user)
 
       expect(doc.css('a').last.text).to eq('text [closed]')
@@ -86,7 +107,7 @@ describe Banzai::Filter::IssuableStateFilter, lib: true do
 
     it 'appends [merged] to merged merge request references' do
       mr = create(:merge_request, :merged)
-      link = create_link(merge_request: mr.id, reference_type: 'merge_request')
+      link = create_link('text', merge_request: mr.id, reference_type: 'merge_request')
       doc = filter(link, current_user: user)
 
       expect(doc.css('a').last.text).to eq('text [merged]')
diff --git a/spec/lib/container_registry/path_spec.rb b/spec/lib/container_registry/path_spec.rb
index b9c4572c26926f0f63c815a21de5d7bc86494182..f3b3a9a715f1816ae8dd538237ee4347f43360ee 100644
--- a/spec/lib/container_registry/path_spec.rb
+++ b/spec/lib/container_registry/path_spec.rb
@@ -33,10 +33,20 @@ describe ContainerRegistry::Path do
   end
 
   describe '#to_s' do
-    let(:path) { 'some/image' }
+    context 'when path does not have uppercase characters' do
+      let(:path) { 'some/image' }
 
-    it 'return a string with a repository path' do
-      expect(subject.to_s).to eq path
+      it 'return a string with a repository path' do
+        expect(subject.to_s).to eq 'some/image'
+      end
+    end
+
+    context 'when path has uppercase characters' do
+      let(:path) { 'SoMe/ImAgE' }
+
+      it 'return a string with a repository path' do
+        expect(subject.to_s).to eq 'some/image'
+      end
     end
   end
 
@@ -70,6 +80,12 @@ describe ContainerRegistry::Path do
 
       it { is_expected.to be_valid }
     end
+
+    context 'when path contains uppercase letters' do
+      let(:path) { 'Some/Registry' }
+
+      it { is_expected.to be_valid }
+    end
   end
 
   describe '#has_repository?' do
diff --git a/spec/lib/gitlab/ci/trace/stream_spec.rb b/spec/lib/gitlab/ci/trace/stream_spec.rb
index 2e57ccef18282c5fd5fe6a957315915c5399cdf5..9e3bd6d662fdd9fcca3e702e1f156eadc6d77337 100644
--- a/spec/lib/gitlab/ci/trace/stream_spec.rb
+++ b/spec/lib/gitlab/ci/trace/stream_spec.rb
@@ -17,12 +17,12 @@ describe Gitlab::Ci::Trace::Stream do
   describe '#limit' do
     let(:stream) do
       described_class.new do
-        StringIO.new("12345678")
+        StringIO.new((1..8).to_a.join("\n"))
       end
     end
 
-    it 'if size is larger we start from beggining' do
-      stream.limit(10)
+    it 'if size is larger we start from beginning' do
+      stream.limit(20)
 
       expect(stream.tell).to eq(0)
     end
@@ -30,7 +30,27 @@ describe Gitlab::Ci::Trace::Stream do
     it 'if size is smaller we start from the end' do
       stream.limit(2)
 
-      expect(stream.tell).to eq(6)
+      expect(stream.raw).to eq("8")
+    end
+
+    context 'when the trace contains ANSI sequence and Unicode' do
+      let(:stream) do
+        described_class.new do
+          File.open(expand_fixture_path('trace/ansi-sequence-and-unicode'))
+        end
+      end
+
+      it 'forwards to the next linefeed, case 1' do
+        stream.limit(7)
+
+        expect(stream.raw).to eq('')
+      end
+
+      it 'forwards to the next linefeed, case 2' do
+        stream.limit(29)
+
+        expect(stream.raw).to eq("\e[01;32m許功蓋\e[0m\n")
+      end
     end
   end
 
diff --git a/spec/models/container_repository_spec.rb b/spec/models/container_repository_spec.rb
index 6d6c9f2adfcdd1ae6654e568c361eb5fb81c5ba6..eff41d85972d4d7775abb3507e0efa3469a8b92c 100644
--- a/spec/models/container_repository_spec.rb
+++ b/spec/models/container_repository_spec.rb
@@ -34,8 +34,18 @@ describe ContainerRepository do
   end
 
   describe '#path' do
-    it 'returns a full path to the repository' do
-      expect(repository.path).to eq('group/test/my_image')
+    context 'when project path does not contain uppercase letters' do
+      it 'returns a full path to the repository' do
+        expect(repository.path).to eq('group/test/my_image')
+      end
+    end
+
+    context 'when path contains uppercase letters' do
+      let(:project) { create(:project, path: 'MY_PROJECT', group: group) }
+
+      it 'returns a full path without capital letters' do
+        expect(repository.path).to eq('group/my_project/my_image')
+      end
     end
   end
 
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index 62f21049b0bf7936ad982e9585e90112f6855341..7a07ea618c01944d6ea35b4c1e03b0914ae38f92 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -144,6 +144,20 @@ describe Projects::CreateService, '#execute', services: true do
     end
   end
 
+  context 'when a bad service template is created' do
+    before do
+      create(:service, type: 'DroneCiService', project: nil, template: true, active: true)
+    end
+
+    it 'reports an error in the imported project' do
+      opts[:import_url] = 'http://www.gitlab.com/gitlab-org/gitlab-ce'
+      project = create_project(user, opts)
+
+      expect(project.errors.full_messages_for(:base).first).to match /Unable to save project. Error: Unable to save DroneCiService/
+      expect(project.services.count).to eq 0
+    end
+  end
+
   def create_project(user, opts)
     Projects::CreateService.new(user, opts).execute
   end
diff --git a/spec/services/users/build_service_spec.rb b/spec/services/users/build_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2a6bfc1b3a085a93d684d8273f51a1dcfe274143
--- /dev/null
+++ b/spec/services/users/build_service_spec.rb
@@ -0,0 +1,55 @@
+require 'spec_helper'
+
+describe Users::BuildService, services: true do
+  describe '#execute' do
+    let(:params) do
+      { name: 'John Doe', username: 'jduser', email: 'jd@example.com', password: 'mydummypass' }
+    end
+
+    context 'with an admin user' do
+      let(:admin_user) { create(:admin) }
+      let(:service) { described_class.new(admin_user, params) }
+
+      it 'returns a valid user' do
+        expect(service.execute).to be_valid
+      end
+    end
+
+    context 'with non admin user' do
+      let(:user) { create(:user) }
+      let(:service) { described_class.new(user, params) }
+
+      it 'raises AccessDeniedError exception' do
+        expect { service.execute }.to raise_error Gitlab::Access::AccessDeniedError
+      end
+    end
+
+    context 'with nil user' do
+      let(:service) { described_class.new(nil, params) }
+
+      it 'returns a valid user' do
+        expect(service.execute).to be_valid
+      end
+
+      context 'when "send_user_confirmation_email" application setting is true' do
+        before do
+          stub_application_setting(send_user_confirmation_email: true, signup_enabled?: true)
+        end
+
+        it 'does not confirm the user' do
+          expect(service.execute).not_to be_confirmed
+        end
+      end
+
+      context 'when "send_user_confirmation_email" application setting is false' do
+        before do
+          stub_application_setting(send_user_confirmation_email: false, signup_enabled?: true)
+        end
+
+        it 'confirms the user' do
+          expect(service.execute).to be_confirmed
+        end
+      end
+    end
+  end
+end
diff --git a/spec/services/users/create_service_spec.rb b/spec/services/users/create_service_spec.rb
index a111aec2f899ecea894d6403ede9612c1d9fbc01..7574627857311ce1fbc3218a3bf0f03566e034f8 100644
--- a/spec/services/users/create_service_spec.rb
+++ b/spec/services/users/create_service_spec.rb
@@ -1,38 +1,6 @@
 require 'spec_helper'
 
 describe Users::CreateService, services: true do
-  describe '#build' do
-    let(:params) do
-      { name: 'John Doe', username: 'jduser', email: 'jd@example.com', password: 'mydummypass' }
-    end
-
-    context 'with an admin user' do
-      let(:admin_user) { create(:admin) }
-      let(:service) { described_class.new(admin_user, params) }
-
-      it 'returns a valid user' do
-        expect(service.build).to be_valid
-      end
-    end
-
-    context 'with non admin user' do
-      let(:user) { create(:user) }
-      let(:service) { described_class.new(user, params) }
-
-      it 'raises AccessDeniedError exception' do
-        expect { service.build }.to raise_error Gitlab::Access::AccessDeniedError
-      end
-    end
-
-    context 'with nil user' do
-      let(:service) { described_class.new(nil, params) }
-
-      it 'returns a valid user' do
-        expect(service.build).to be_valid
-      end
-    end
-  end
-
   describe '#execute' do
     let(:admin_user) { create(:admin) }
 
@@ -185,40 +153,18 @@ describe Users::CreateService, services: true do
       end
       let(:service) { described_class.new(nil, params) }
 
-      context 'when "send_user_confirmation_email" application setting is true' do
-        before do
-          current_application_settings = double(:current_application_settings, send_user_confirmation_email: true, signup_enabled?: true)
-          allow(service).to receive(:current_application_settings).and_return(current_application_settings)
-        end
-
-        it 'does not confirm the user' do
-          expect(service.execute).not_to be_confirmed
-        end
-      end
-
-      context 'when "send_user_confirmation_email" application setting is false' do
-        before do
-          current_application_settings = double(:current_application_settings, send_user_confirmation_email: false, signup_enabled?: true)
-          allow(service).to receive(:current_application_settings).and_return(current_application_settings)
-        end
-
-        it 'confirms the user' do
-          expect(service.execute).to be_confirmed
-        end
-
-        it 'persists the given attributes' do
-          user = service.execute
-          user.reload
-
-          expect(user).to have_attributes(
-            name: params[:name],
-            username: params[:username],
-            email: params[:email],
-            password: params[:password],
-            created_by_id: nil,
-            admin: false
-          )
-        end
+      it 'persists the given attributes' do
+        user = service.execute
+        user.reload
+
+        expect(user).to have_attributes(
+          name: params[:name],
+          username: params[:username],
+          email: params[:email],
+          password: params[:password],
+          created_by_id: nil,
+          admin: false
+        )
       end
     end
   end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index a366579545222528348aeb88f0057a10bb1c5e37..e67ad8f345593e9bd433cd475db6b9324d58884a 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -9,8 +9,14 @@ require 'rspec/rails'
 require 'shoulda/matchers'
 require 'rspec/retry'
 
-if (ENV['RSPEC_PROFILING_POSTGRES_URL'] || ENV['RSPEC_PROFILING']) &&
-    (!ENV.has_key?('CI') || ENV['CI_COMMIT_REF_NAME'] == 'master')
+rspec_profiling_is_configured =
+  ENV['RSPEC_PROFILING_POSTGRES_URL'] ||
+  ENV['RSPEC_PROFILING']
+branch_can_be_profiled =
+  ENV['CI_COMMIT_REF_NAME'] == 'master' ||
+  ENV['CI_COMMIT_REF_NAME'] =~ /rspec-profile/
+
+if rspec_profiling_is_configured && (!ENV.key?('CI') || branch_can_be_profiled)
   require 'rspec_profiling/rspec'
 end
 
diff --git a/spec/support/fixture_helpers.rb b/spec/support/fixture_helpers.rb
index a05c9d18002e4a19e0bf9efa709daa04d44ee57a..5515c355cea9b2b5bc4f2efa4b8b65eb8a3f60a3 100644
--- a/spec/support/fixture_helpers.rb
+++ b/spec/support/fixture_helpers.rb
@@ -1,8 +1,11 @@
 module FixtureHelpers
   def fixture_file(filename)
     return '' if filename.blank?
-    file_path = File.expand_path(Rails.root.join('spec/fixtures/', filename))
-    File.read(file_path)
+    File.read(expand_fixture_path(filename))
+  end
+
+  def expand_fixture_path(filename)
+    File.expand_path(Rails.root.join('spec/fixtures/', filename))
   end
 end
 
diff --git a/spec/tasks/gitlab/gitaly_rake_spec.rb b/spec/tasks/gitlab/gitaly_rake_spec.rb
index b369dcbb305cfb45beea6d7b7029897c0ef63a11..aaf998a546f6f4ca7e0940f7ccd2be81b19ecfea 100644
--- a/spec/tasks/gitlab/gitaly_rake_spec.rb
+++ b/spec/tasks/gitlab/gitaly_rake_spec.rb
@@ -8,7 +8,7 @@ describe 'gitlab:gitaly namespace rake task' do
   describe 'install' do
     let(:repo) { 'https://gitlab.com/gitlab-org/gitaly.git' }
     let(:clone_path) { Rails.root.join('tmp/tests/gitaly').to_s }
-    let(:tag) { "v#{File.read(Rails.root.join(Gitlab::GitalyClient::SERVER_VERSION_FILE)).chomp}" }
+    let(:version) { File.read(Rails.root.join(Gitlab::GitalyClient::SERVER_VERSION_FILE)).chomp }
 
     context 'no dir given' do
       it 'aborts and display a help message' do
@@ -21,7 +21,7 @@ describe 'gitlab:gitaly namespace rake task' do
     context 'when an underlying Git command fail' do
       it 'aborts and display a help message' do
         expect_any_instance_of(Object).
-          to receive(:checkout_or_clone_tag).and_raise 'Git error'
+          to receive(:checkout_or_clone_version).and_raise 'Git error'
 
         expect { run_rake_task('gitlab:gitaly:install', clone_path) }.to raise_error 'Git error'
       end
@@ -32,9 +32,9 @@ describe 'gitlab:gitaly namespace rake task' do
         expect(Dir).to receive(:chdir).with(clone_path)
       end
 
-      it 'calls checkout_or_clone_tag with the right arguments' do
+      it 'calls checkout_or_clone_version with the right arguments' do
         expect_any_instance_of(Object).
-          to receive(:checkout_or_clone_tag).with(tag: tag, repo: repo, target_dir: clone_path)
+          to receive(:checkout_or_clone_version).with(version: version, repo: repo, target_dir: clone_path)
 
         run_rake_task('gitlab:gitaly:install', clone_path)
       end
@@ -48,7 +48,7 @@ describe 'gitlab:gitaly namespace rake task' do
 
       context 'gmake is available' do
         before do
-          expect_any_instance_of(Object).to receive(:checkout_or_clone_tag)
+          expect_any_instance_of(Object).to receive(:checkout_or_clone_version)
           allow_any_instance_of(Object).to receive(:run_command!).with(['gmake']).and_return(true)
         end
 
@@ -62,7 +62,7 @@ describe 'gitlab:gitaly namespace rake task' do
 
       context 'gmake is not available' do
         before do
-          expect_any_instance_of(Object).to receive(:checkout_or_clone_tag)
+          expect_any_instance_of(Object).to receive(:checkout_or_clone_version)
           allow_any_instance_of(Object).to receive(:run_command!).with(['make']).and_return(true)
         end
 
diff --git a/spec/tasks/gitlab/task_helpers_spec.rb b/spec/tasks/gitlab/task_helpers_spec.rb
index 86e42d845cea48de85c8c290d8be9e0b6335c420..3d9ba7cdc6f4a74e32d3741c4d86c04f54485c83 100644
--- a/spec/tasks/gitlab/task_helpers_spec.rb
+++ b/spec/tasks/gitlab/task_helpers_spec.rb
@@ -10,19 +10,38 @@ describe Gitlab::TaskHelpers do
 
   let(:repo) { 'https://gitlab.com/gitlab-org/gitlab-test.git' }
   let(:clone_path) { Rails.root.join('tmp/tests/task_helpers_tests').to_s }
+  let(:version) { '1.1.0' }
   let(:tag) { 'v1.1.0' }
 
-  describe '#checkout_or_clone_tag' do
+  describe '#checkout_or_clone_version' do
     before do
       allow(subject).to receive(:run_command!)
-      expect(subject).to receive(:reset_to_tag).with(tag, clone_path)
     end
 
-    context 'target_dir does not exist' do
-      it 'clones the repo, retrieve the tag from origin, and checkout the tag' do
+    it 'checkout the version and reset to it' do
+      expect(subject).to receive(:checkout_version).with(tag, clone_path)
+      expect(subject).to receive(:reset_to_version).with(tag, clone_path)
+
+      subject.checkout_or_clone_version(version: version, repo: repo, target_dir: clone_path)
+    end
+
+    context 'with a branch version' do
+      let(:version) { '=branch_name' }
+      let(:branch) { 'branch_name' }
+
+      it 'checkout the version and reset to it with a branch name' do
+        expect(subject).to receive(:checkout_version).with(branch, clone_path)
+        expect(subject).to receive(:reset_to_version).with(branch, clone_path)
+
+        subject.checkout_or_clone_version(version: version, repo: repo, target_dir: clone_path)
+      end
+    end
+
+    context "target_dir doesn't exist" do
+      it 'clones the repo' do
         expect(subject).to receive(:clone_repo).with(repo, clone_path)
 
-        subject.checkout_or_clone_tag(tag: tag, repo: repo, target_dir: clone_path)
+        subject.checkout_or_clone_version(version: version, repo: repo, target_dir: clone_path)
       end
     end
 
@@ -31,10 +50,10 @@ describe Gitlab::TaskHelpers do
         expect(Dir).to receive(:exist?).and_return(true)
       end
 
-      it 'fetch and checkout the tag' do
-        expect(subject).to receive(:checkout_tag).with(tag, clone_path)
+      it "doesn't clone the repository" do
+        expect(subject).not_to receive(:clone_repo)
 
-        subject.checkout_or_clone_tag(tag: tag, repo: repo, target_dir: clone_path)
+        subject.checkout_or_clone_version(version: version, repo: repo, target_dir: clone_path)
       end
     end
   end
@@ -48,49 +67,23 @@ describe Gitlab::TaskHelpers do
     end
   end
 
-  describe '#checkout_tag' do
+  describe '#checkout_version' do
     it 'clones the repo in the target dir' do
       expect(subject).
-        to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} fetch --tags --quiet])
+        to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} fetch --quiet])
       expect(subject).
         to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} checkout --quiet #{tag}])
 
-      subject.checkout_tag(tag, clone_path)
+      subject.checkout_version(tag, clone_path)
     end
   end
 
-  describe '#reset_to_tag' do
-    let(:tag) { 'v1.1.0' }
-    before do
+  describe '#reset_to_version' do
+    it 'resets --hard to the given version' do
       expect(subject).
         to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} reset --hard #{tag}])
-    end
 
-    context 'when the tag is not checked out locally' do
-      before do
-        expect(subject).
-          to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} describe -- #{tag}]).and_raise(Gitlab::TaskFailedError)
-      end
-
-      it 'fetch origin, ensure the tag exists, and resets --hard to the given tag' do
-        expect(subject).
-          to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} fetch origin])
-        expect(subject).
-          to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} describe -- origin/#{tag}]).and_return(tag)
-
-        subject.reset_to_tag(tag, clone_path)
-      end
-    end
-
-    context 'when the tag is checked out locally' do
-      before do
-        expect(subject).
-          to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} describe -- #{tag}]).and_return(tag)
-      end
-
-      it 'resets --hard to the given tag' do
-        subject.reset_to_tag(tag, clone_path)
-      end
+      subject.reset_to_version(tag, clone_path)
     end
   end
 end
diff --git a/spec/tasks/gitlab/workhorse_rake_spec.rb b/spec/tasks/gitlab/workhorse_rake_spec.rb
index 8a66a4aa047f67a6874a7de544bd68535ac92d2a..63d1cf2bbe59732bb985a71dfb1386b2eccd0229 100644
--- a/spec/tasks/gitlab/workhorse_rake_spec.rb
+++ b/spec/tasks/gitlab/workhorse_rake_spec.rb
@@ -8,7 +8,7 @@ describe 'gitlab:workhorse namespace rake task' do
   describe 'install' do
     let(:repo) { 'https://gitlab.com/gitlab-org/gitlab-workhorse.git' }
     let(:clone_path) { Rails.root.join('tmp/tests/gitlab-workhorse').to_s }
-    let(:tag) { "v#{File.read(Rails.root.join(Gitlab::Workhorse::VERSION_FILE)).chomp}" }
+    let(:version) { File.read(Rails.root.join(Gitlab::Workhorse::VERSION_FILE)).chomp }
 
     context 'no dir given' do
       it 'aborts and display a help message' do
@@ -21,7 +21,7 @@ describe 'gitlab:workhorse namespace rake task' do
     context 'when an underlying Git command fail' do
       it 'aborts and display a help message' do
         expect_any_instance_of(Object).
-          to receive(:checkout_or_clone_tag).and_raise 'Git error'
+          to receive(:checkout_or_clone_version).and_raise 'Git error'
 
         expect { run_rake_task('gitlab:workhorse:install', clone_path) }.to raise_error 'Git error'
       end
@@ -32,9 +32,9 @@ describe 'gitlab:workhorse namespace rake task' do
         expect(Dir).to receive(:chdir).with(clone_path)
       end
 
-      it 'calls checkout_or_clone_tag with the right arguments' do
+      it 'calls checkout_or_clone_version with the right arguments' do
         expect_any_instance_of(Object).
-          to receive(:checkout_or_clone_tag).with(tag: tag, repo: repo, target_dir: clone_path)
+          to receive(:checkout_or_clone_version).with(version: version, repo: repo, target_dir: clone_path)
 
         run_rake_task('gitlab:workhorse:install', clone_path)
       end
@@ -48,7 +48,7 @@ describe 'gitlab:workhorse namespace rake task' do
 
       context 'gmake is available' do
         before do
-          expect_any_instance_of(Object).to receive(:checkout_or_clone_tag)
+          expect_any_instance_of(Object).to receive(:checkout_or_clone_version)
           allow_any_instance_of(Object).to receive(:run_command!).with(['gmake']).and_return(true)
         end
 
@@ -62,7 +62,7 @@ describe 'gitlab:workhorse namespace rake task' do
 
       context 'gmake is not available' do
         before do
-          expect_any_instance_of(Object).to receive(:checkout_or_clone_tag)
+          expect_any_instance_of(Object).to receive(:checkout_or_clone_version)
           allow_any_instance_of(Object).to receive(:run_command!).with(['make']).and_return(true)
         end
 
diff --git a/vendor/assets/javascripts/notebooklab.js b/vendor/assets/javascripts/notebooklab.js
index 601a645b655cd26c77830740e38b75af40b854fe..b8cfdc53b4835d0c8d4a535a13a86affea4c2a13 100644
--- a/vendor/assets/javascripts/notebooklab.js
+++ b/vendor/assets/javascripts/notebooklab.js
@@ -699,6 +699,48 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
 //
 //
 
+var renderer = new _marked2.default.Renderer();
+
+/*
+  Regex to match KaTex blocks.
+
+  Supports the following:
+
+  \begin{equation}<math>\end{equation}
+  $$<math>$$
+  inline $<math>$
+
+  The matched text then goes through the KaTex renderer & then outputs the HTML
+*/
+var katexRegexString = '(\n  ^\\\\begin{[a-zA-Z]+}\\s\n  |\n  ^\\$\\$\n  |\n  \\s\\$(?!\\$)\n)\n  (.+?)\n(\n  \\s\\\\end{[a-zA-Z]+}$\n  |\n  \\$\\$$\n  |\n  \\$\n)\n'.replace(/\s/g, '').trim();
+
+renderer.paragraph = function (t) {
+  var text = t;
+  var inline = false;
+
+  if (typeof katex !== 'undefined') {
+    var katexString = text.replace(/\\/g, '\\');
+    var matches = new RegExp(katexRegexString, 'gi').exec(katexString);
+
+    if (matches && matches.length > 0) {
+      if (matches[1].trim() === '$' && matches[3].trim() === '$') {
+        inline = true;
+
+        text = katexString.replace(matches[0], '') + ' ' + katex.renderToString(matches[2]);
+      } else {
+        text = katex.renderToString(matches[2]);
+      }
+    }
+  }
+
+  return '<p class="' + (inline ? 'inline-katex' : '') + '">' + text + '</p>';
+};
+
+_marked2.default.setOptions({
+  sanitize: true,
+  renderer: renderer
+});
+
 exports.default = {
   components: {
     prompt: _prompt2.default
@@ -711,20 +753,7 @@ exports.default = {
   },
   computed: {
     markdown: function markdown() {
-      var regex = new RegExp('^\\$\\$(.*)\\$\\$$', 'g');
-
-      var source = this.cell.source.map(function (line) {
-        var matches = regex.exec(line.trim());
-
-        // Only render use the Katex library if it is actually loaded
-        if (matches && matches.length > 0 && typeof katex !== 'undefined') {
-          return katex.renderToString(matches[1]);
-        }
-
-        return line;
-      });
-
-      return (0, _marked2.default)(source.join(''));
+      return (0, _marked2.default)(this.cell.source.join(''));
     }
   }
 };
@@ -3047,7 +3076,7 @@ exports = module.exports = __webpack_require__(1)(undefined);
 
 
 // module
-exports.push([module.i, ".markdown .katex{display:block;text-align:center}", ""]);
+exports.push([module.i, ".markdown .katex{display:block;text-align:center}.markdown .inline-katex .katex{display:inline;text-align:initial}", ""]);
 
 // exports