summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/boards/components/board.js168
-rw-r--r--app/assets/javascripts/boards/components/board_delete.js28
-rw-r--r--app/assets/javascripts/boards/components/board_sidebar.js112
-rw-r--r--app/assets/javascripts/boards/components/issue_card_inner.js246
-rw-r--r--app/assets/javascripts/boards/components/modal/empty_state.js120
-rw-r--r--app/assets/javascripts/boards/components/modal/footer.js126
-rw-r--r--app/assets/javascripts/boards/components/modal/header.js130
-rw-r--r--app/assets/javascripts/boards/components/modal/index.js272
-rw-r--r--app/assets/javascripts/boards/components/modal/list.js268
-rw-r--r--app/assets/javascripts/boards/components/modal/lists_dropdown.js102
-rw-r--r--app/assets/javascripts/boards/components/modal/tabs.js86
-rw-r--r--app/assets/javascripts/boards/components/new_list_dropdown.js120
-rw-r--r--app/assets/javascripts/boards/components/sidebar/remove_issue.js98
-rw-r--r--app/assets/javascripts/boards/mixins/modal_mixins.js22
-rw-r--r--app/assets/javascripts/boards/mixins/sortable_default_options.js60
-rw-r--r--app/assets/javascripts/boards/stores/boards_store.js216
-rw-r--r--app/assets/javascripts/boards/stores/modal_store.js156
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_code_component.js81
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_issue_component.js85
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_plan_component.js85
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_production_component.js85
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_review_component.js103
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_staging_component.js85
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_test_component.js85
-rw-r--r--app/assets/javascripts/cycle_analytics/components/total_time_component.js41
-rw-r--r--app/assets/javascripts/cycle_analytics/cycle_analytics_service.js64
-rw-r--r--app/assets/javascripts/cycle_analytics/cycle_analytics_store.js171
-rw-r--r--app/assets/javascripts/lib/utils/text_utility.js346
-rw-r--r--app/assets/javascripts/lib/utils/url_utility.js171
-rw-r--r--app/assets/javascripts/merged_buttons.js12
-rw-r--r--app/assets/stylesheets/pages/notes.scss6
-rw-r--r--app/views/layouts/nav/_project.html.haml4
-rw-r--r--app/views/projects/labels/edit.html.haml2
-rw-r--r--app/views/projects/labels/index.html.haml2
-rw-r--r--app/views/projects/labels/new.html.haml2
-rw-r--r--app/views/projects/merge_requests/_head.html.haml21
-rw-r--r--app/views/projects/merge_requests/index.html.haml3
-rw-r--r--app/views/projects/milestones/edit.html.haml2
-rw-r--r--app/views/projects/milestones/index.html.haml2
-rw-r--r--app/views/projects/milestones/new.html.haml2
-rw-r--r--app/views/projects/milestones/show.html.haml2
-rw-r--r--app/views/projects/notes/_note.html.haml5
-rw-r--r--app/views/shared/_mr_head.html.haml4
-rw-r--r--changelogs/unreleased/30779-show-mr-subnav-issue-tracker.yml4
-rw-r--r--spec/features/merge_requests/diff_notes_resolve_spec.rb2
-rw-r--r--spec/javascripts/fixtures/merge_requests.rb7
-rw-r--r--spec/javascripts/lib/utils/text_utility_spec.js148
-rw-r--r--spec/javascripts/merged_buttons_spec.js44
48 files changed, 2022 insertions, 1984 deletions
diff --git a/app/assets/javascripts/boards/components/board.js b/app/assets/javascripts/boards/components/board.js
index 93b8960da2e..239eeacf2d7 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 af621cfd57f..8a1b177bba8 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 3c080008244..004bac09f59 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 e48d3344a2b..fc154ee7b8b 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 823319df6e7..13569df0c20 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 887ce373096..ccd270b27da 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 116e29cd177..e2b3f9ae7e2 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 91c08cde13a..fb0aac3c0e4 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 aba56d4aa31..363269c0d5d 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 9e9ed46ab8d..8cd15df90fa 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 23cb1b13d11..3e5d08e3d75 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 556826a9148..22f20305624 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 772ea4c5565..5597f128b80 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 d378b7d4baf..2b0a1aaa89f 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 b6c6d17274f..38a0eb12f92 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 bcda70d0638..66384d9c038 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 9b009483a3c..4fdc925c825 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 3f419a96ff9..80bd2df6f42 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 7ffa38edd9e..20a43798fbe 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 d736c8b0c28..f33cac3da82 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 698a79ca68c..657f5385374 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 e63c41f2a57..8a801300647 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 d51f7134e25..4a286379588 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 17ae3a9ddc1..e306026429e 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 b4442ea5566..77edcb76273 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 9f74b14c4b9..681d6eef565 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 7ae9de7297c..6536a8fd7fa 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 2e5f8a09fc1..fecd531328d 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 09c4261b318..b9d2fc25c39 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 9548a98f499..7b0997c6520 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/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index ad0f2f6efbb..c78fb8ede79 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/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index 299dace3406..e34cddeb3e2 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 a80a07b52e6..7f0059cdcda 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 29f861c09c6..fc72c4fb635 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 f0d9be744d1..8f6c085a361 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 00000000000..b7f73fe5339
--- /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 64f17ab34b1..6bf0035e051 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 55b0b837c6d..e57a76dbfd2 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 8e85b2e8a20..e1096bd1d67 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 cda093ade81..586eb909afa 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 8b62b156853..a173117984d 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 c12c05eeb73..1f021ad77e5 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/shared/_mr_head.html.haml b/app/views/shared/_mr_head.html.haml
new file mode 100644
index 00000000000..4211ec6351d
--- /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/changelogs/unreleased/30779-show-mr-subnav-issue-tracker.yml b/changelogs/unreleased/30779-show-mr-subnav-issue-tracker.yml
new file mode 100644
index 00000000000..59f8942911c
--- /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/spec/features/merge_requests/diff_notes_resolve_spec.rb b/spec/features/merge_requests/diff_notes_resolve_spec.rb
index 88d28b649a4..0e23c3a8849 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/javascripts/fixtures/merge_requests.rb b/spec/javascripts/fixtures/merge_requests.rb
index fddeaaf504d..47d904b865b 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 4200e943121..daef9b93fa5 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 00000000000..b5c5e60dd97
--- /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();
+ });
+ });
+});