diff options
146 files changed, 2655 insertions, 2251 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0d70eae0d1e..0e7a67f9cc1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -694,7 +694,10 @@ gitlab:setup-mysql: # Frontend-related jobs gitlab:assets:compile: <<: *dedicated-no-docs-and-no-qa-pull-cache-job + image: dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.4.4-git-2.18-chrome-69.0-node-8.x-yarn-1.2-graphicsmagick-1.3.29-docker-18.06.1 dependencies: [] + services: + - docker:stable-dind variables: NODE_ENV: "production" RAILS_ENV: "production" @@ -703,18 +706,23 @@ gitlab:assets:compile: WEBPACK_REPORT: "true" # we override the max_old_space_size to prevent OOM errors NODE_OPTIONS: --max_old_space_size=3584 + DOCKER_DRIVER: overlay2 + DOCKER_HOST: tcp://docker:2375 script: - date - yarn install --frozen-lockfile --production --cache-folder .yarn-cache - date - free -m - bundle exec rake gitlab:assets:compile + - scripts/build_assets_image artifacts: name: webpack-report expire_in: 31d paths: - webpack-report/ - public/assets/ + tags: + - docker karma: <<: *dedicated-no-docs-pull-cache-job diff --git a/Dockerfile.assets b/Dockerfile.assets new file mode 100644 index 00000000000..403d16cc4ab --- /dev/null +++ b/Dockerfile.assets @@ -0,0 +1,4 @@ +# Simple container to store assets for later use +FROM scratch +ADD public/assets /assets/ +CMD /bin/true diff --git a/app/assets/javascripts/blob_edit/blob_bundle.js b/app/assets/javascripts/blob_edit/blob_bundle.js index 3cc89ff1955..ec27ae8c291 100644 --- a/app/assets/javascripts/blob_edit/blob_bundle.js +++ b/app/assets/javascripts/blob_edit/blob_bundle.js @@ -13,7 +13,7 @@ export default () => { if (editBlobForm.length) { const urlRoot = editBlobForm.data('relativeUrlRoot'); const assetsPath = editBlobForm.data('assetsPrefix'); - const filePath = editBlobForm.data('blobFilename') + const filePath = editBlobForm.data('blobFilename'); const currentAction = $('.js-file-title').data('currentAction'); const projectId = editBlobForm.data('project-id'); diff --git a/app/assets/javascripts/boards/components/board.js b/app/assets/javascripts/boards/components/board.js index 623cda5679a..fb6e5291a61 100644 --- a/app/assets/javascripts/boards/components/board.js +++ b/app/assets/javascripts/boards/components/board.js @@ -42,7 +42,7 @@ export default Vue.extend({ required: true, }, }, - data () { + data() { return { detailIssue: boardsStore.detail, filter: boardsStore.filter, @@ -55,27 +55,26 @@ export default Vue.extend({ }, isNewIssueShown() { return this.list.type === 'backlog' || (!this.disabled && this.list.type !== 'closed'); - } + }, }, watch: { filter: { handler() { this.list.page = 1; - this.list.getIssues(true) - .catch(() => { - // TODO: handle request error - }); + this.list.getIssues(true).catch(() => { + // TODO: handle request error + }); }, deep: true, - } + }, }, - mounted () { + mounted() { this.sortableOptions = getBoardSortableDefaultOptions({ disabled: this.disabled, group: 'boards', draggable: '.is-draggable', handle: '.js-board-handle', - onEnd: (e) => { + onEnd: e => { sortableEnd(); if (e.newIndex !== undefined && e.oldIndex !== e.newIndex) { @@ -86,14 +85,15 @@ export default Vue.extend({ boardsStore.moveList(list, order); }); } - } + }, }); this.sortable = Sortable.create(this.$el.parentNode, this.sortableOptions); }, created() { if (this.list.isExpandable && AccessorUtilities.isLocalStorageAccessSafe()) { - const isCollapsed = localStorage.getItem(`boards.${this.boardId}.${this.list.type}.expanded`) === 'false'; + const isCollapsed = + localStorage.getItem(`boards.${this.boardId}.${this.list.type}.expanded`) === 'false'; this.list.isExpanded = !isCollapsed; } @@ -107,7 +107,10 @@ export default Vue.extend({ this.list.isExpanded = !this.list.isExpanded; if (AccessorUtilities.isLocalStorageAccessSafe()) { - localStorage.setItem(`boards.${this.boardId}.${this.list.type}.expanded`, this.list.isExpanded); + localStorage.setItem( + `boards.${this.boardId}.${this.list.type}.expanded`, + this.list.isExpanded, + ); } } }, diff --git a/app/assets/javascripts/boards/components/board_blank_state.vue b/app/assets/javascripts/boards/components/board_blank_state.vue index 38aaec73d7d..561a4636ef5 100644 --- a/app/assets/javascripts/boards/components/board_blank_state.vue +++ b/app/assets/javascripts/boards/components/board_blank_state.vue @@ -32,18 +32,18 @@ export default { boardsStore.state.lists = _.sortBy(boardsStore.state.lists, 'position'); // Save the labels - gl.boardService.generateDefaultLists() + gl.boardService + .generateDefaultLists() .then(res => res.data) - .then((data) => { - data.forEach((listObj) => { + .then(data => { + data.forEach(listObj => { const list = boardsStore.findList('title', listObj.title); list.id = listObj.id; list.label.id = listObj.label.id; - list.getIssues() - .catch(() => { - // TODO: handle request error - }); + list.getIssues().catch(() => { + // TODO: handle request error + }); }); }) .catch(() => { @@ -57,7 +57,6 @@ export default { clearBlankState: boardsStore.removeBlankState.bind(boardsStore), }, }; - </script> <template> diff --git a/app/assets/javascripts/boards/components/board_card.vue b/app/assets/javascripts/boards/components/board_card.vue index 843498f0d06..2f31316aa76 100644 --- a/app/assets/javascripts/boards/components/board_card.vue +++ b/app/assets/javascripts/boards/components/board_card.vue @@ -1,77 +1,77 @@ <script> - /* eslint-disable vue/require-default-prop */ - import IssueCardInner from './issue_card_inner.vue'; - import eventHub from '../eventhub'; - import boardsStore from '../stores/boards_store'; +/* eslint-disable vue/require-default-prop */ +import IssueCardInner from './issue_card_inner.vue'; +import eventHub from '../eventhub'; +import boardsStore from '../stores/boards_store'; - export default { - name: 'BoardsIssueCard', - components: { - IssueCardInner, +export default { + name: 'BoardsIssueCard', + components: { + IssueCardInner, + }, + props: { + list: { + type: Object, + default: () => ({}), }, - props: { - list: { - type: Object, - default: () => ({}), - }, - issue: { - type: Object, - default: () => ({}), - }, - issueLinkBase: { - type: String, - default: '', - }, - disabled: { - type: Boolean, - default: false, - }, - index: { - type: Number, - default: 0, - }, - rootPath: { - type: String, - default: '', - }, - groupId: { - type: Number, - }, + issue: { + type: Object, + default: () => ({}), }, - data() { - return { - showDetail: false, - detailIssue: boardsStore.detail, - }; + issueLinkBase: { + type: String, + default: '', }, - computed: { - issueDetailVisible() { - return this.detailIssue.issue && this.detailIssue.issue.id === this.issue.id; - }, + disabled: { + type: Boolean, + default: false, }, - methods: { - mouseDown() { - this.showDetail = true; - }, - mouseMove() { - this.showDetail = false; - }, - showIssue(e) { - if (e.target.classList.contains('js-no-trigger')) return; + index: { + type: Number, + default: 0, + }, + rootPath: { + type: String, + default: '', + }, + groupId: { + type: Number, + }, + }, + data() { + return { + showDetail: false, + detailIssue: boardsStore.detail, + }; + }, + computed: { + issueDetailVisible() { + return this.detailIssue.issue && this.detailIssue.issue.id === this.issue.id; + }, + }, + methods: { + mouseDown() { + this.showDetail = true; + }, + mouseMove() { + this.showDetail = false; + }, + showIssue(e) { + if (e.target.classList.contains('js-no-trigger')) return; - if (this.showDetail) { - this.showDetail = false; + if (this.showDetail) { + this.showDetail = false; - if (boardsStore.detail.issue && boardsStore.detail.issue.id === this.issue.id) { - eventHub.$emit('clearDetailIssue'); - } else { - eventHub.$emit('newDetailIssue', this.issue); - boardsStore.detail.list = this.list; - } + if (boardsStore.detail.issue && boardsStore.detail.issue.id === this.issue.id) { + eventHub.$emit('clearDetailIssue'); + } else { + eventHub.$emit('newDetailIssue', this.issue); + boardsStore.detail.list = this.list; } - }, + } }, - }; + }, +}; </script> <template> diff --git a/app/assets/javascripts/boards/components/board_new_issue.vue b/app/assets/javascripts/boards/components/board_new_issue.vue index ae2d1ee3c6e..ee3dc38bca6 100644 --- a/app/assets/javascripts/boards/components/board_new_issue.vue +++ b/app/assets/javascripts/boards/components/board_new_issue.vue @@ -62,7 +62,8 @@ export default { eventHub.$emit(`scroll-board-list-${this.list.id}`); this.cancel(); - return this.list.newIssue(issue) + return this.list + .newIssue(issue) .then(() => { // Need this because our jQuery very kindly disables buttons on ALL form submissions $(this.$refs.submitButton).enable(); diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js index 62666954de0..e637e1f1223 100644 --- a/app/assets/javascripts/boards/components/board_sidebar.js +++ b/app/assets/javascripts/boards/components/board_sidebar.js @@ -38,7 +38,7 @@ export default Vue.extend({ }; }, computed: { - showSidebar () { + showSidebar() { return Object.keys(this.issue).length; }, milestoneTitle() { @@ -51,18 +51,20 @@ export default Vue.extend({ return this.issue.labels && this.issue.labels.length; }, labelDropdownTitle() { - return this.hasLabels ? sprintf(__('%{firstLabel} +%{labelCount} more'), { - firstLabel: this.issue.labels[0].title, - labelCount: this.issue.labels.length - 1 - }) : __('Label'); + return this.hasLabels + ? sprintf(__('%{firstLabel} +%{labelCount} more'), { + firstLabel: this.issue.labels[0].title, + labelCount: this.issue.labels.length - 1, + }) + : __('Label'); }, selectedLabels() { return this.hasLabels ? this.issue.labels.map(l => l.title).join(',') : ''; - } + }, }, watch: { detail: { - handler () { + handler() { if (this.issue.id !== this.detail.issue.id) { $('.block.assignee') .find('input:not(.js-vue)[name="issue[assignee_ids][]"]') @@ -71,17 +73,19 @@ export default Vue.extend({ }); $('.js-issue-board-sidebar', this.$el).each((i, el) => { - $(el).data('glDropdown').clearMenu(); + $(el) + .data('glDropdown') + .clearMenu(); }); } this.issue = this.detail.issue; this.list = this.detail.list; }, - deep: true + deep: true, }, }, - created () { + created() { // Get events from glDropdown eventHub.$on('sidebar.removeAssignee', this.removeAssignee); eventHub.$on('sidebar.addAssignee', this.addAssignee); @@ -94,7 +98,7 @@ export default Vue.extend({ eventHub.$off('sidebar.removeAllAssignees', this.removeAllAssignees); eventHub.$off('sidebar.saveAssignees', this.saveAssignees); }, - mounted () { + mounted() { new IssuableContext(this.currentUser); new MilestoneSelect(); new DueDateSelectors(); @@ -102,29 +106,30 @@ export default Vue.extend({ new Sidebar(); }, methods: { - closeSidebar () { + closeSidebar() { this.detail.issue = {}; }, - assignSelf () { + assignSelf() { // Notify gl dropdown that we are now assigning to current user this.$refs.assigneeBlock.dispatchEvent(new Event('assignYourself')); this.addAssignee(this.currentUser); this.saveAssignees(); }, - removeAssignee (a) { + removeAssignee(a) { boardsStore.detail.issue.removeAssignee(a); }, - addAssignee (a) { + addAssignee(a) { boardsStore.detail.issue.addAssignee(a); }, - removeAllAssignees () { + removeAllAssignees() { boardsStore.detail.issue.removeAllAssignees(); }, - saveAssignees () { + saveAssignees() { this.loadingAssignees = true; - boardsStore.detail.issue.update() + boardsStore.detail.issue + .update() .then(() => { this.loadingAssignees = false; }) diff --git a/app/assets/javascripts/boards/components/issue_card_inner.vue b/app/assets/javascripts/boards/components/issue_card_inner.vue index aa98f35786e..d956777a86b 100644 --- a/app/assets/javascripts/boards/components/issue_card_inner.vue +++ b/app/assets/javascripts/boards/components/issue_card_inner.vue @@ -1,142 +1,142 @@ <script> - import $ from 'jquery'; - import Icon from '~/vue_shared/components/icon.vue'; - import UserAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue'; - import eventHub from '../eventhub'; - import tooltip from '../../vue_shared/directives/tooltip'; - import boardsStore from '../stores/boards_store'; +import $ from 'jquery'; +import Icon from '~/vue_shared/components/icon.vue'; +import UserAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue'; +import eventHub from '../eventhub'; +import tooltip from '../../vue_shared/directives/tooltip'; +import boardsStore from '../stores/boards_store'; - export default { - components: { - UserAvatarLink, - Icon, - }, - directives: { - tooltip, - }, - 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, - }, - groupId: { - type: Number, - required: false, - default: null, - }, - }, - data() { - return { - limitBeforeCounter: 3, - maxRender: 4, - maxCounter: 99, - }; +export default { + components: { + UserAvatarLink, + Icon, + }, + directives: { + tooltip, + }, + props: { + issue: { + type: Object, + required: true, }, - computed: { - numberOverLimit() { - return this.issue.assignees.length - this.limitBeforeCounter; - }, - assigneeCounterTooltip() { - return `${this.assigneeCounterLabel} more`; - }, - assigneeCounterLabel() { - if (this.numberOverLimit > this.maxCounter) { - return `${this.maxCounter}+`; - } - - return `+${this.numberOverLimit}`; - }, - shouldRenderCounter() { - if (this.issue.assignees.length <= this.maxRender) { - return false; - } + issueLinkBase: { + type: String, + required: true, + }, + list: { + type: Object, + required: false, + default: () => ({}), + }, + rootPath: { + type: String, + required: true, + }, + updateFilters: { + type: Boolean, + required: false, + default: false, + }, + groupId: { + type: Number, + required: false, + default: null, + }, + }, + data() { + return { + limitBeforeCounter: 3, + maxRender: 4, + maxCounter: 99, + }; + }, + computed: { + numberOverLimit() { + return this.issue.assignees.length - this.limitBeforeCounter; + }, + assigneeCounterTooltip() { + return `${this.assigneeCounterLabel} more`; + }, + assigneeCounterLabel() { + if (this.numberOverLimit > this.maxCounter) { + return `${this.maxCounter}+`; + } - return this.issue.assignees.length > this.numberOverLimit; - }, - issueId() { - if (this.issue.iid) { - return `#${this.issue.iid}`; - } + return `+${this.numberOverLimit}`; + }, + shouldRenderCounter() { + if (this.issue.assignees.length <= this.maxRender) { return false; - }, - showLabelFooter() { - return this.issue.labels.find(l => this.showLabel(l)) !== undefined; - }, - }, - methods: { - isIndexLessThanlimit(index) { - return index < this.limitBeforeCounter; - }, - shouldRenderAssignee(index) { - // Eg. maxRender is 4, - // Render up to all 4 assignees if there are only 4 assigness - // Otherwise render up to the limitBeforeCounter - if (this.issue.assignees.length <= this.maxRender) { - return index < this.maxRender; - } + } - return index < this.limitBeforeCounter; - }, - assigneeUrl(assignee) { - return `${this.rootPath}${assignee.username}`; - }, - assigneeUrlTitle(assignee) { - return `Assigned to ${assignee.name}`; - }, - avatarUrlTitle(assignee) { - return `Avatar for ${assignee.name}`; - }, - showLabel(label) { - if (!label.id) return false; - return true; - }, - filterByLabel(label, e) { - if (!this.updateFilters) return; + return this.issue.assignees.length > this.numberOverLimit; + }, + issueId() { + if (this.issue.iid) { + return `#${this.issue.iid}`; + } + return false; + }, + showLabelFooter() { + return this.issue.labels.find(l => this.showLabel(l)) !== undefined; + }, + }, + methods: { + isIndexLessThanlimit(index) { + return index < this.limitBeforeCounter; + }, + shouldRenderAssignee(index) { + // Eg. maxRender is 4, + // Render up to all 4 assignees if there are only 4 assigness + // Otherwise render up to the limitBeforeCounter + if (this.issue.assignees.length <= this.maxRender) { + return index < this.maxRender; + } + + return index < this.limitBeforeCounter; + }, + assigneeUrl(assignee) { + return `${this.rootPath}${assignee.username}`; + }, + assigneeUrlTitle(assignee) { + return `Assigned to ${assignee.name}`; + }, + avatarUrlTitle(assignee) { + return `Avatar for ${assignee.name}`; + }, + showLabel(label) { + if (!label.id) return false; + return true; + }, + filterByLabel(label, e) { + if (!this.updateFilters) return; - const filterPath = 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 = 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); + } - boardsStore.filter.path = filterPath.join('&'); + boardsStore.filter.path = filterPath.join('&'); - boardsStore.updateFiltersUrl(); + boardsStore.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, + }; + }, + }, +}; </script> <template> <div> diff --git a/app/assets/javascripts/boards/components/modal/empty_state.vue b/app/assets/javascripts/boards/components/modal/empty_state.vue index dbd69f84526..795ba864545 100644 --- a/app/assets/javascripts/boards/components/modal/empty_state.vue +++ b/app/assets/javascripts/boards/components/modal/empty_state.vue @@ -20,7 +20,7 @@ export default { computed: { contents() { const obj = { - title: 'You haven\'t added any issues to your project yet', + 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. @@ -28,7 +28,7 @@ export default { }; if (this.activeTab === 'selected') { - obj.title = 'You haven\'t selected any issues yet'; + 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. diff --git a/app/assets/javascripts/boards/components/modal/footer.vue b/app/assets/javascripts/boards/components/modal/footer.vue index 268ca6bca13..d51597ed22d 100644 --- a/app/assets/javascripts/boards/components/modal/footer.vue +++ b/app/assets/javascripts/boards/components/modal/footer.vue @@ -42,19 +42,17 @@ export default { const req = this.buildUpdateRequest(list); // Post the data to the backend - gl.boardService - .bulkUpdate(issueIds, req) - .catch(() => { - Flash(__('Failed to update issues, please try again.')); + gl.boardService.bulkUpdate(issueIds, req).catch(() => { + Flash(__('Failed to update issues, please try again.')); - selectedIssues.forEach((issue) => { - list.removeIssue(issue); - list.issuesSize -= 1; - }); + selectedIssues.forEach(issue => { + list.removeIssue(issue); + list.issuesSize -= 1; }); + }); // Add the issues on the frontend - selectedIssues.forEach((issue) => { + selectedIssues.forEach(issue => { list.addIssue(issue); list.issuesSize += 1; }); diff --git a/app/assets/javascripts/boards/components/modal/header.vue b/app/assets/javascripts/boards/components/modal/header.vue index 979fb4d7199..fc6cefa89a9 100644 --- a/app/assets/javascripts/boards/components/modal/header.vue +++ b/app/assets/javascripts/boards/components/modal/header.vue @@ -1,52 +1,52 @@ <script> - import ModalFilters from './filters'; - import ModalTabs from './tabs.vue'; - import ModalStore from '../../stores/modal_store'; - import modalMixin from '../../mixins/modal_mixins'; +import ModalFilters from './filters'; +import ModalTabs from './tabs.vue'; +import ModalStore from '../../stores/modal_store'; +import modalMixin from '../../mixins/modal_mixins'; - export default { - components: { - ModalTabs, - ModalFilters, +export default { + components: { + ModalTabs, + ModalFilters, + }, + mixins: [modalMixin], + props: { + projectId: { + type: Number, + required: true, }, - mixins: [modalMixin], - props: { - projectId: { - type: Number, - required: true, - }, - milestonePath: { - type: String, - required: true, - }, - labelPath: { - type: String, - required: true, - }, + milestonePath: { + type: String, + required: true, }, - data() { - return ModalStore.store; + labelPath: { + type: String, + required: true, }, - computed: { - selectAllText() { - if (ModalStore.selectedCount() !== this.issues.length || this.issues.length === 0) { - return 'Select all'; - } + }, + data() { + return ModalStore.store; + }, + 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; - }, + return 'Deselect all'; }, - methods: { - toggleAll() { - this.$refs.selectAllBtn.blur(); + showSearch() { + return this.activeTab === 'all' && !this.loading && this.issuesCount > 0; + }, + }, + methods: { + toggleAll() { + this.$refs.selectAllBtn.blur(); - ModalStore.toggleAll(); - }, + ModalStore.toggleAll(); }, - }; + }, +}; </script> <template> <div> diff --git a/app/assets/javascripts/boards/components/modal/index.vue b/app/assets/javascripts/boards/components/modal/index.vue index 0c4c709324d..40949cc0656 100644 --- a/app/assets/javascripts/boards/components/modal/index.vue +++ b/app/assets/javascripts/boards/components/modal/index.vue @@ -1,143 +1,144 @@ <script> - /* global ListIssue */ - import { urlParamsToObject } from '~/lib/utils/common_utils'; - import ModalHeader from './header.vue'; - import ModalList from './list.vue'; - import ModalFooter from './footer.vue'; - import EmptyState from './empty_state.vue'; - import ModalStore from '../../stores/modal_store'; +/* global ListIssue */ +import { urlParamsToObject } from '~/lib/utils/common_utils'; +import ModalHeader from './header.vue'; +import ModalList from './list.vue'; +import ModalFooter from './footer.vue'; +import EmptyState from './empty_state.vue'; +import ModalStore from '../../stores/modal_store'; - export default { - components: { - EmptyState, - ModalHeader, - ModalList, - ModalFooter, +export default { + components: { + EmptyState, + ModalHeader, + ModalList, + ModalFooter, + }, + props: { + newIssuePath: { + type: String, + required: true, }, - props: { - newIssuePath: { - type: String, - required: true, - }, - emptyStateSvg: { - 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, - }, + emptyStateSvg: { + type: String, + required: true, }, - data() { - return ModalStore.store; + issueLinkBase: { + type: String, + required: true, }, - computed: { - showList() { - if (this.activeTab === 'selected') { - return this.selectedIssues.length > 0; - } + rootPath: { + type: String, + required: true, + }, + projectId: { + type: Number, + required: true, + }, + milestonePath: { + type: String, + required: true, + }, + labelPath: { + type: String, + required: true, + }, + }, + data() { + return ModalStore.store; + }, + computed: { + showList() { + if (this.activeTab === 'selected') { + return this.selectedIssues.length > 0; + } - return this.issuesCount > 0; - }, - showEmptyState() { - if (!this.loading && this.issuesCount === 0) { - return true; - } + return this.issuesCount > 0; + }, + showEmptyState() { + if (!this.loading && this.issuesCount === 0) { + return true; + } - return this.activeTab === 'selected' && this.selectedIssues.length === 0; - }, + return this.activeTab === 'selected' && this.selectedIssues.length === 0; }, - watch: { - page() { - this.loadIssues(); - }, - showAddIssuesModal() { - if (this.showAddIssuesModal && !this.issues.length) { - this.loading = true; + }, + watch: { + page() { + this.loadIssues(); + }, + showAddIssuesModal() { + if (this.showAddIssuesModal && !this.issues.length) { + this.loading = true; + const loadingDone = () => { + this.loading = false; + }; + + this.loadIssues() + .then(loadingDone) + .catch(loadingDone); + } else if (!this.showAddIssuesModal) { + this.issues = []; + this.selectedIssues = []; + this.issuesCount = false; + } + }, + filter: { + handler() { + if (this.$el.tagName) { + this.page = 1; + this.filterLoading = true; const loadingDone = () => { - this.loading = false; + this.filterLoading = false; }; - this.loadIssues() + this.loadIssues(true) .then(loadingDone) .catch(loadingDone); - } else if (!this.showAddIssuesModal) { - this.issues = []; - this.selectedIssues = []; - this.issuesCount = false; } }, - filter: { - handler() { - if (this.$el.tagName) { - this.page = 1; - this.filterLoading = true; - const loadingDone = () => { - this.filterLoading = false; - }; - - this.loadIssues(true) - .then(loadingDone) - .catch(loadingDone); - } - }, - deep: true, - }, + deep: true, }, - created() { - this.page = 1; - }, - methods: { - loadIssues(clearIssues = false) { - if (!this.showAddIssuesModal) return false; + }, + created() { + this.page = 1; + }, + methods: { + loadIssues(clearIssues = false) { + if (!this.showAddIssuesModal) return false; - return gl.boardService.getBacklog({ + return gl.boardService + .getBacklog({ ...urlParamsToObject(this.filter.path), page: this.page, per: this.perPage, }) - .then(res => res.data) - .then(data => { - if (clearIssues) { - this.issues = []; - } + .then(res => res.data) + .then(data => { + if (clearIssues) { + this.issues = []; + } - data.issues.forEach(issueObj => { - const issue = new ListIssue(issueObj); - const foundSelectedIssue = ModalStore.findSelectedIssue(issue); - issue.selected = !!foundSelectedIssue; + data.issues.forEach(issueObj => { + const issue = new ListIssue(issueObj); + const foundSelectedIssue = ModalStore.findSelectedIssue(issue); + issue.selected = !!foundSelectedIssue; - this.issues.push(issue); - }); + this.issues.push(issue); + }); - this.loadingNewPage = false; + this.loadingNewPage = false; - if (!this.issuesCount) { - this.issuesCount = data.size; - } - }) - .catch(() => { - // TODO: handle request error - }); - }, + if (!this.issuesCount) { + this.issuesCount = data.size; + } + }) + .catch(() => { + // TODO: handle request error + }); }, - }; + }, +}; </script> <template> <div diff --git a/app/assets/javascripts/boards/components/modal/list.vue b/app/assets/javascripts/boards/components/modal/list.vue index c93fd9f415c..e11f398e70d 100644 --- a/app/assets/javascripts/boards/components/modal/list.vue +++ b/app/assets/javascripts/boards/components/modal/list.vue @@ -1,120 +1,120 @@ <script> - import Icon from '~/vue_shared/components/icon.vue'; - import bp from '../../../breakpoints'; - import ModalStore from '../../stores/modal_store'; - import IssueCardInner from '../issue_card_inner.vue'; +import Icon from '~/vue_shared/components/icon.vue'; +import bp from '../../../breakpoints'; +import ModalStore from '../../stores/modal_store'; +import IssueCardInner from '../issue_card_inner.vue'; - export default { - components: { - IssueCardInner, - Icon, +export default { + components: { + IssueCardInner, + Icon, + }, + props: { + issueLinkBase: { + type: String, + required: true, }, - props: { - issueLinkBase: { - type: String, - required: true, - }, - rootPath: { - type: String, - required: true, - }, - emptyStateSvg: { - type: String, - required: true, - }, + rootPath: { + type: String, + required: true, }, - data() { - return ModalStore.store; + emptyStateSvg: { + type: String, + required: true, }, - computed: { - loopIssues() { - if (this.activeTab === 'all') { - return this.issues; - } + }, + data() { + return ModalStore.store; + }, + 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; + return this.selectedIssues; + }, + groupedIssues() { + const groups = []; + this.loopIssues.forEach((issue, i) => { + const index = i % this.columns; - if (!groups[index]) { - groups.push([]); - } + if (!groups[index]) { + groups.push([]); + } - groups[index].push(issue); - }); + groups[index].push(issue); + }); - return groups; - }, + return groups; }, - watch: { - activeTab() { - if (this.activeTab === 'all') { - ModalStore.purgeUnselectedIssues(); - } - }, + }, + watch: { + activeTab() { + if (this.activeTab === 'all') { + ModalStore.purgeUnselectedIssues(); + } }, - mounted() { - this.scrollHandlerWrapper = this.scrollHandler.bind(this); - this.setColumnCountWrapper = this.setColumnCount.bind(this); - this.setColumnCount(); + }, + 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); + 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); + }, + methods: { + scrollHandler() { + const currentPage = Math.floor(this.issues.length / this.perPage); + + if ( + this.scrollTop() > this.scrollHeight() - 100 && + !this.loadingNewPage && + currentPage === this.page + ) { + this.loadingNewPage = true; + this.page += 1; + } }, - beforeDestroy() { - this.$refs.list.removeEventListener('scroll', this.scrollHandlerWrapper); - window.removeEventListener('resize', this.setColumnCountWrapper); + toggleIssue(e, issue) { + if (e.target.tagName !== 'A') { + ModalStore.toggleIssue(issue); + } }, - methods: { - scrollHandler() { - const currentPage = Math.floor(this.issues.length / this.perPage); - - 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; - }, - scrollHeight() { - return this.$refs.list.scrollHeight; - }, - scrollTop() { - return this.$refs.list.scrollTop + this.listHeight(); - }, - showIssue(issue) { - if (this.activeTab === 'all') return true; + 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); + const index = ModalStore.selectedIssueIndex(issue); - return index !== -1; - }, - setColumnCount() { - const breakpoint = bp.getBreakpointSize(); + 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; - } - }, + if (breakpoint === 'lg' || breakpoint === 'md') { + this.columns = 3; + } else if (breakpoint === 'sm') { + this.columns = 2; + } else { + this.columns = 1; + } }, - }; + }, +}; </script> <template> <section diff --git a/app/assets/javascripts/boards/components/modal/tabs.vue b/app/assets/javascripts/boards/components/modal/tabs.vue index d926b080094..5d661590e8e 100644 --- a/app/assets/javascripts/boards/components/modal/tabs.vue +++ b/app/assets/javascripts/boards/components/modal/tabs.vue @@ -1,21 +1,21 @@ <script> - import ModalStore from '../../stores/modal_store'; - import modalMixin from '../../mixins/modal_mixins'; +import ModalStore from '../../stores/modal_store'; +import modalMixin from '../../mixins/modal_mixins'; - export default { - mixins: [modalMixin], - data() { - return ModalStore.store; +export default { + mixins: [modalMixin], + data() { + return ModalStore.store; + }, + computed: { + selectedCount() { + return ModalStore.selectedCount(); }, - computed: { - selectedCount() { - return ModalStore.selectedCount(); - }, - }, - destroyed() { - this.activeTab = 'all'; - }, - }; + }, + destroyed() { + this.activeTab = 'all'; + }, +}; </script> <template> <div class="top-area prepend-top-10 append-bottom-10"> diff --git a/app/assets/javascripts/boards/components/new_list_dropdown.js b/app/assets/javascripts/boards/components/new_list_dropdown.js index 2c2045f8901..f7016561f93 100644 --- a/app/assets/javascripts/boards/components/new_list_dropdown.js +++ b/app/assets/javascripts/boards/components/new_list_dropdown.js @@ -6,36 +6,41 @@ import _ from 'underscore'; import CreateLabelDropdown from '../../create_label'; import boardsStore from '../stores/boards_store'; -$(document).off('created.label').on('created.label', (e, label) => { - boardsStore.new({ - title: label.title, - position: boardsStore.state.lists.length - 2, - list_type: 'label', - label: { - id: label.id, +$(document) + .off('created.label') + .on('created.label', (e, label) => { + boardsStore.new({ title: label.title, - color: label.color, - }, + position: boardsStore.state.lists.length - 2, + list_type: 'label', + label: { + id: label.id, + title: label.title, + color: label.color, + }, + }); }); -}); export default function initNewListDropdown() { - $('.js-new-board-list').each(function () { + $('.js-new-board-list').each(function() { const $this = $(this); - new CreateLabelDropdown($this.closest('.dropdown').find('.dropdown-new-label'), $this.data('namespacePath'), $this.data('projectPath')); + new CreateLabelDropdown( + $this.closest('.dropdown').find('.dropdown-new-label'), + $this.data('namespacePath'), + $this.data('projectPath'), + ); $this.glDropdown({ data(term, callback) { - axios.get($this.attr('data-list-labels-path')) - .then(({ data }) => { - callback(data); - }); + axios.get($this.attr('data-list-labels-path')).then(({ data }) => { + callback(data); + }); }, - renderRow (label) { + renderRow(label) { const active = boardsStore.findList('title', label.title); const $li = $('<li />'); const $a = $('<a />', { - class: (active ? `is-active js-board-list-${active.id}` : ''), + class: active ? `is-active js-board-list-${active.id}` : '', text: label.title, href: '#', }); @@ -53,7 +58,7 @@ export default function initNewListDropdown() { selectable: true, multiSelect: true, containerSelector: '.js-tab-container-labels .dropdown-page-one .dropdown-content', - clicked (options) { + clicked(options) { const { e } = options; const label = options.selectedObj; e.preventDefault(); diff --git a/app/assets/javascripts/boards/components/project_select.vue b/app/assets/javascripts/boards/components/project_select.vue index 427a0868b0c..0f01a2a6c09 100644 --- a/app/assets/javascripts/boards/components/project_select.vue +++ b/app/assets/javascripts/boards/components/project_select.vue @@ -46,7 +46,7 @@ export default { selectable: true, data: (term, callback) => { this.loading = true; - return Api.groupProjects(this.groupId, term, {with_issues_enabled: true}, projects => { + return Api.groupProjects(this.groupId, term, { with_issues_enabled: true }, projects => { this.loading = false; callback(projects); }); @@ -54,7 +54,9 @@ export default { renderRow(project) { return ` <li> - <a href='#' class='dropdown-menu-link' data-project-id="${project.id}" data-project-name="${project.name}"> + <a href='#' class='dropdown-menu-link' data-project-id="${ + project.id + }" data-project-name="${project.name}"> ${_.escape(project.name)} </a> </li> diff --git a/app/assets/javascripts/boards/components/sidebar/remove_issue.vue b/app/assets/javascripts/boards/components/sidebar/remove_issue.vue index b8f2e324d43..d681e6a431c 100644 --- a/app/assets/javascripts/boards/components/sidebar/remove_issue.vue +++ b/app/assets/javascripts/boards/components/sidebar/remove_issue.vue @@ -1,79 +1,77 @@ <script> - import Vue from 'vue'; - import Flash from '../../../flash'; - import { __ } from '../../../locale'; - import boardsStore from '../../stores/boards_store'; +import Vue from 'vue'; +import Flash from '../../../flash'; +import { __ } from '../../../locale'; +import boardsStore from '../../stores/boards_store'; - export default Vue.extend({ - props: { - issue: { - type: Object, - required: true, - }, - list: { - type: Object, - required: true, - }, +export default Vue.extend({ + props: { + issue: { + type: Object, + required: true, }, - computed: { - updateUrl() { - return this.issue.path; - }, + list: { + type: Object, + required: true, }, - methods: { - removeIssue() { - const { issue } = this; - const lists = issue.getLists(); - const req = this.buildPatchRequest(issue, lists); - - const data = { - issue: this.seedPatchRequest(issue, req), - }; + }, + computed: { + updateUrl() { + return this.issue.path; + }, + }, + methods: { + removeIssue() { + const { issue } = this; + const lists = issue.getLists(); + const req = this.buildPatchRequest(issue, lists); - if (data.issue.label_ids.length === 0) { - data.issue.label_ids = ['']; - } + const data = { + issue: this.seedPatchRequest(issue, req), + }; - // Post the remove data - Vue.http.patch(this.updateUrl, data).catch(() => { - Flash(__('Failed to remove issue from board, please try again.')); + if (data.issue.label_ids.length === 0) { + data.issue.label_ids = ['']; + } - lists.forEach(list => { - list.addIssue(issue); - }); - }); + // Post the remove data + Vue.http.patch(this.updateUrl, data).catch(() => { + Flash(__('Failed to remove issue from board, please try again.')); - // Remove from the frontend store lists.forEach(list => { - list.removeIssue(issue); + list.addIssue(issue); }); + }); - boardsStore.detail.issue = {}; - }, - /** - * Build the default patch request. - */ - buildPatchRequest(issue, lists) { - const listLabelIds = lists.map(list => list.label.id); + // Remove from the frontend store + lists.forEach(list => { + list.removeIssue(issue); + }); - const labelIds = issue.labels - .map(label => label.id) - .filter(id => !listLabelIds.includes(id)); + boardsStore.detail.issue = {}; + }, + /** + * Build the default patch request. + */ + buildPatchRequest(issue, lists) { + const listLabelIds = lists.map(list => list.label.id); + + const labelIds = issue.labels.map(label => label.id).filter(id => !listLabelIds.includes(id)); - return { - label_ids: labelIds, - }; - }, - /** - * Seed the given patch request. - * - * (This is overridden in EE) - */ - seedPatchRequest(issue, req) { - return req; - }, + return { + label_ids: labelIds, + }; + }, + /** + * Seed the given patch request. + * + * (This is overridden in EE) + */ + seedPatchRequest(issue, req) { + return req; }, - }); + }, +}); </script> <template> <div diff --git a/app/assets/javascripts/boards/filtered_search_boards.js b/app/assets/javascripts/boards/filtered_search_boards.js index acf41e5689e..c14d69c5d18 100644 --- a/app/assets/javascripts/boards/filtered_search_boards.js +++ b/app/assets/javascripts/boards/filtered_search_boards.js @@ -32,7 +32,7 @@ export default class FilteredSearchBoards extends FilteredSearchManager { const tokens = FilteredSearchContainer.container.querySelectorAll('.js-visual-token'); // Remove all the tokens as they will be replaced by the search manager - [].forEach.call(tokens, (el) => { + [].forEach.call(tokens, el => { el.parentNode.removeChild(el); }); @@ -50,7 +50,10 @@ export default class FilteredSearchBoards extends FilteredSearchManager { canEdit(tokenName, tokenValue) { if (this.cantEdit.includes(tokenName)) return false; - return this.cantEditWithValue.findIndex(token => token.name === tokenName && - token.value === tokenValue) === -1; + return ( + this.cantEditWithValue.findIndex( + token => token.name === tokenName && token.value === tokenValue, + ) === -1 + ); } } diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js index 91861f2f9ee..61a3072ac27 100644 --- a/app/assets/javascripts/boards/index.js +++ b/app/assets/javascripts/boards/index.js @@ -32,9 +32,9 @@ export default () => { const $boardApp = document.getElementById('board-app'); // check for browser back and trigger a hard reload to circumvent browser caching. - window.addEventListener('pageshow', (event) => { - const isNavTypeBackForward = window.performance && - window.performance.navigation.type === NavigationType.TYPE_BACK_FORWARD; + window.addEventListener('pageshow', event => { + const isNavTypeBackForward = + window.performance && window.performance.navigation.type === NavigationType.TYPE_BACK_FORWARD; if (event.persisted || isNavTypeBackForward) { window.location.reload(); diff --git a/app/assets/javascripts/boards/mixins/sortable_default_options.js b/app/assets/javascripts/boards/mixins/sortable_default_options.js index c9cde4effb9..983b28d2e67 100644 --- a/app/assets/javascripts/boards/mixins/sortable_default_options.js +++ b/app/assets/javascripts/boards/mixins/sortable_default_options.js @@ -4,7 +4,8 @@ import $ from 'jquery'; import sortableConfig from '../../sortable/sortable_config'; export function sortableStart() { - $('.has-tooltip').tooltip('hide') + $('.has-tooltip') + .tooltip('hide') .tooltip('disable'); document.body.classList.add('is-dragging'); } @@ -15,7 +16,8 @@ export function sortableEnd() { } export function getBoardSortableDefaultOptions(obj) { - const touchEnabled = ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch; + const touchEnabled = + 'ontouchstart' in window || (window.DocumentTouch && document instanceof DocumentTouch); const defaultSortOptions = Object.assign({}, sortableConfig, { filter: '.board-delete, .btn', @@ -26,6 +28,8 @@ export function getBoardSortableDefaultOptions(obj) { onEnd: sortableEnd, }); - Object.keys(obj).forEach((key) => { defaultSortOptions[key] = obj[key]; }); + Object.keys(obj).forEach(key => { + defaultSortOptions[key] = obj[key]; + }); return defaultSortOptions; } diff --git a/app/assets/javascripts/boards/models/issue.js b/app/assets/javascripts/boards/models/issue.js index 52d04389b88..bb3b2865934 100644 --- a/app/assets/javascripts/boards/models/issue.js +++ b/app/assets/javascripts/boards/models/issue.js @@ -9,7 +9,7 @@ import IssueProject from './project'; import boardsStore from '../stores/boards_store'; class ListIssue { - constructor (obj, defaultAvatar) { + constructor(obj, defaultAvatar) { this.id = obj.id; this.iid = obj.iid; this.title = obj.title; @@ -39,54 +39,54 @@ class ListIssue { this.milestone = new ListMilestone(obj.milestone); } - obj.labels.forEach((label) => { + obj.labels.forEach(label => { this.labels.push(new ListLabel(label)); }); this.assignees = obj.assignees.map(a => new ListAssignee(a, defaultAvatar)); } - addLabel (label) { + addLabel(label) { if (!this.findLabel(label)) { this.labels.push(new ListLabel(label)); } } - findLabel (findLabel) { + findLabel(findLabel) { return this.labels.filter(label => label.title === findLabel.title)[0]; } - removeLabel (removeLabel) { + removeLabel(removeLabel) { if (removeLabel) { this.labels = this.labels.filter(label => removeLabel.title !== label.title); } } - removeLabels (labels) { + removeLabels(labels) { labels.forEach(this.removeLabel.bind(this)); } - addAssignee (assignee) { + addAssignee(assignee) { if (!this.findAssignee(assignee)) { this.assignees.push(new ListAssignee(assignee)); } } - findAssignee (findAssignee) { + findAssignee(findAssignee) { return this.assignees.filter(assignee => assignee.id === findAssignee.id)[0]; } - removeAssignee (removeAssignee) { + removeAssignee(removeAssignee) { if (removeAssignee) { this.assignees = this.assignees.filter(assignee => assignee.id !== removeAssignee.id); } } - removeAllAssignees () { + removeAllAssignees() { this.assignees = []; } - getLists () { + getLists() { return boardsStore.state.lists.filter(list => list.findIssue(this.id)); } @@ -102,14 +102,14 @@ class ListIssue { this.isLoading[key] = value; } - update () { + update() { const data = { issue: { milestone_id: this.milestone ? this.milestone.id : null, due_date: this.dueDate, - assignee_ids: this.assignees.length > 0 ? this.assignees.map((u) => u.id) : [0], - label_ids: this.labels.map((label) => label.id) - } + assignee_ids: this.assignees.length > 0 ? this.assignees.map(u => u.id) : [0], + label_ids: this.labels.map(label => label.id), + }, }; if (!data.issue.label_ids.length) { diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js index 3161f1da8c9..dd3feedbc0e 100644 --- a/app/assets/javascripts/boards/models/list.js +++ b/app/assets/javascripts/boards/models/list.js @@ -234,11 +234,11 @@ class List { }); } - getTypeInfo (type) { + getTypeInfo(type) { return TYPES[type] || {}; } - onNewIssueResponse (issue, data) { + onNewIssueResponse(issue, data) { issue.id = data.id; issue.iid = data.iid; issue.project = data.project; diff --git a/app/assets/javascripts/boards/services/board_service.js b/app/assets/javascripts/boards/services/board_service.js index 029b0971f2c..3de6eb056c2 100644 --- a/app/assets/javascripts/boards/services/board_service.js +++ b/app/assets/javascripts/boards/services/board_service.js @@ -19,7 +19,9 @@ export default class BoardService { } static generateIssuePath(boardId, id) { - return `${gon.relative_url_root}/-/boards/${boardId ? `${boardId}` : ''}/issues${id ? `/${id}` : ''}`; + return `${gon.relative_url_root}/-/boards/${boardId ? `${boardId}` : ''}/issues${ + id ? `/${id}` : '' + }`; } all() { @@ -54,7 +56,9 @@ export default class BoardService { getIssuesForList(id, filter = {}) { const data = { id }; - Object.keys(filter).forEach((key) => { data[key] = filter[key]; }); + Object.keys(filter).forEach(key => { + data[key] = filter[key]; + }); return axios.get(mergeUrlParams(data, this.generateIssuesPath(id))); } @@ -75,7 +79,9 @@ export default class BoardService { } getBacklog(data) { - return axios.get(mergeUrlParams(data, `${gon.relative_url_root}/-/boards/${this.boardId}/issues.json`)); + return axios.get( + mergeUrlParams(data, `${gon.relative_url_root}/-/boards/${this.boardId}/issues.json`), + ); } bulkUpdate(issueIds, extraData = {}) { diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js index 471955747fd..eefe14a1d79 100644 --- a/app/assets/javascripts/boards/stores/boards_store.js +++ b/app/assets/javascripts/boards/stores/boards_store.js @@ -20,20 +20,20 @@ const boardsStore = { issue: {}, list: {}, }, - create () { + create() { this.state.lists = []; this.filter.path = getUrlParamsArray().join('&'); this.detail = { issue: {}, }; }, - addList (listObj, defaultAvatar) { + addList(listObj, defaultAvatar) { const list = new List(listObj, defaultAvatar); this.state.lists.push(list); return list; }, - new (listObj) { + new(listObj) { const list = this.addList(listObj); const backlogList = this.findList('type', 'backlog', 'backlog'); @@ -50,44 +50,44 @@ const boardsStore = { }); this.removeBlankState(); }, - updateNewListDropdown (listId) { + updateNewListDropdown(listId) { $(`.js-board-list-${listId}`).removeClass('is-active'); }, - shouldAddBlankState () { + shouldAddBlankState() { // Decide whether to add the blank state - return !(this.state.lists.filter(list => list.type !== 'backlog' && list.type !== 'closed')[0]); + return !this.state.lists.filter(list => list.type !== 'backlog' && list.type !== 'closed')[0]; }, - addBlankState () { + addBlankState() { if (!this.shouldAddBlankState() || this.welcomeIsHidden() || this.disabled) return; this.addList({ id: 'blank', list_type: 'blank', title: 'Welcome to your Issue Board!', - position: 0 + position: 0, }); this.state.lists = _.sortBy(this.state.lists, 'position'); }, - removeBlankState () { + removeBlankState() { this.removeList('blank'); Cookies.set('issue_board_welcome_hidden', 'true', { expires: 365 * 10, - path: '' + path: '', }); }, - welcomeIsHidden () { + welcomeIsHidden() { return Cookies.get('issue_board_welcome_hidden') === 'true'; }, - removeList (id, type = 'blank') { + removeList(id, type = 'blank') { const list = this.findList('id', id, type); if (!list) return; this.state.lists = this.state.lists.filter(list => list.id !== id); }, - moveList (listFrom, orderLists) { + moveList(listFrom, orderLists) { orderLists.forEach((id, i) => { const list = this.findList('id', parseInt(id, 10)); @@ -95,22 +95,25 @@ const boardsStore = { }); listFrom.update(); }, - moveIssueToList (listFrom, listTo, issue, newIndex) { + moveIssueToList(listFrom, listTo, issue, newIndex) { const issueTo = listTo.findIssue(issue.id); const issueLists = issue.getLists(); const listLabels = issueLists.map(listIssue => listIssue.label); if (!issueTo) { // Check if target list assignee is already present in this issue - if ((listTo.type === 'assignee' && listFrom.type === 'assignee') && - issue.findAssignee(listTo.assignee)) { + if ( + listTo.type === 'assignee' && + listFrom.type === 'assignee' && + issue.findAssignee(listTo.assignee) + ) { const targetIssue = listTo.findIssue(issue.id); targetIssue.removeAssignee(listFrom.assignee); } else if (listTo.type === 'milestone') { const currentMilestone = issue.milestone; const currentLists = this.state.lists - .filter(list => (list.type === 'milestone' && list.id !== listTo.id)) - .filter(list => list.issues.some(listIssue => issue.id === listIssue.id)); + .filter(list => list.type === 'milestone' && list.id !== listTo.id) + .filter(list => list.issues.some(listIssue => issue.id === listIssue.id)); issue.removeMilestone(currentMilestone); issue.addMilestone(listTo.milestone); @@ -126,7 +129,7 @@ const boardsStore = { } if (listTo.type === 'closed' && listFrom.type !== 'backlog') { - issueLists.forEach((list) => { + issueLists.forEach(list => { list.removeIssue(issue); }); issue.removeLabels(listLabels); @@ -144,26 +147,28 @@ const boardsStore = { return ( (listTo.type !== 'label' && listFrom.type === 'assignee') || (listTo.type !== 'assignee' && listFrom.type === 'label') || - (listFrom.type === 'backlog') + listFrom.type === 'backlog' ); }, - moveIssueInList (list, issue, oldIndex, newIndex, idArray) { + 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') { - const filteredList = this.state.lists.filter((list) => { - const byType = type ? (list.type === type) || (list.type === 'assignee') || (list.type === 'milestone') : true; + findList(key, val, type = 'label') { + const filteredList = this.state.lists.filter(list => { + const byType = type + ? list.type === type || list.type === 'assignee' || list.type === 'milestone' + : true; return list[key] === val && byType; }); return filteredList[0]; }, - updateFiltersUrl () { + updateFiltersUrl() { window.history.pushState(null, null, `?${this.filter.path}`); - } + }, }; // hacks added in order to allow milestone_select to function properly diff --git a/app/assets/javascripts/boards/stores/modal_store.js b/app/assets/javascripts/boards/stores/modal_store.js index 0d9ac367a70..b7228bf7bf5 100644 --- a/app/assets/javascripts/boards/stores/modal_store.js +++ b/app/assets/javascripts/boards/stores/modal_store.js @@ -40,7 +40,7 @@ class ModalStore { toggleAll() { const select = this.selectedCount() !== this.store.issues.length; - this.store.issues.forEach((issue) => { + this.store.issues.forEach(issue => { const issueUpdate = issue; if (issueUpdate.selected !== select) { @@ -69,13 +69,14 @@ class ModalStore { removeSelectedIssue(issue, forcePurge = false) { if (this.store.activeTab === 'all' || forcePurge) { - this.store.selectedIssues = this.store.selectedIssues - .filter(fIssue => fIssue.id !== issue.id); + this.store.selectedIssues = this.store.selectedIssues.filter( + fIssue => fIssue.id !== issue.id, + ); } } purgeUnselectedIssues() { - this.store.selectedIssues.forEach((issue) => { + this.store.selectedIssues.forEach(issue => { if (!issue.selected) { this.removeSelectedIssue(issue, true); } @@ -87,8 +88,7 @@ class ModalStore { } findSelectedIssue(issue) { - return this.store.selectedIssues - .filter(filteredIssue => filteredIssue.id === issue.id)[0]; + return this.store.selectedIssues.filter(filteredIssue => filteredIssue.id === issue.id)[0]; } } diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue index 6e7b5eb5526..6d7f45a35d8 100644 --- a/app/assets/javascripts/clusters/components/applications.vue +++ b/app/assets/javascripts/clusters/components/applications.vue @@ -1,6 +1,6 @@ <script> import _ from 'underscore'; -import helmInstallIllustration from '@gitlab-org/gitlab-svgs/dist/illustrations/kubernetes-installation.svg'; +import helmInstallIllustration from '@gitlab/svgs/dist/illustrations/kubernetes-installation.svg'; import elasticsearchLogo from 'images/cluster_app_logos/elasticsearch.png'; import gitlabLogo from 'images/cluster_app_logos/gitlab.png'; import helmLogo from 'images/cluster_app_logos/helm.png'; diff --git a/app/assets/javascripts/commons/gitlab_ui.js b/app/assets/javascripts/commons/gitlab_ui.js index f60665577fe..e93e1f5ea2c 100644 --- a/app/assets/javascripts/commons/gitlab_ui.js +++ b/app/assets/javascripts/commons/gitlab_ui.js @@ -1,17 +1,7 @@ import Vue from 'vue'; -import { - GlPagination, - GlProgressBar, - GlModal, - GlLoadingIcon, - GlModalDirective, - GlTooltipDirective, -} from '@gitlab-org/gitlab-ui'; +import { GlProgressBar, GlLoadingIcon, GlTooltipDirective } from '@gitlab-org/gitlab-ui'; -Vue.component('gl-pagination', GlPagination); Vue.component('gl-progress-bar', GlProgressBar); -Vue.component('gl-ui-modal', GlModal); Vue.component('gl-loading-icon', GlLoadingIcon); -Vue.directive('gl-modal', GlModalDirective); Vue.directive('gl-tooltip', GlTooltipDirective); diff --git a/app/assets/javascripts/diffs/components/tree_list.vue b/app/assets/javascripts/diffs/components/tree_list.vue index 96e7bd63183..91052b303a6 100644 --- a/app/assets/javascripts/diffs/components/tree_list.vue +++ b/app/assets/javascripts/diffs/components/tree_list.vue @@ -18,8 +18,8 @@ export default { }, data() { const treeListStored = localStorage.getItem(treeListStorageKey); - const renderTreeList = treeListStored !== null ? - convertPermissionToBoolean(treeListStored) : true; + const renderTreeList = + treeListStored !== null ? convertPermissionToBoolean(treeListStored) : true; return { search: '', diff --git a/app/assets/javascripts/environments/components/environments_app.vue b/app/assets/javascripts/environments/components/environments_app.vue index e2ecf426e64..557b2062c64 100644 --- a/app/assets/javascripts/environments/components/environments_app.vue +++ b/app/assets/javascripts/environments/components/environments_app.vue @@ -1,94 +1,92 @@ <script> - import Flash from '../../flash'; - import { s__ } from '../../locale'; - import emptyState from './empty_state.vue'; - import eventHub from '../event_hub'; - import environmentsMixin from '../mixins/environments_mixin'; - import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin'; - import StopEnvironmentModal from './stop_environment_modal.vue'; +import Flash from '../../flash'; +import { s__ } from '../../locale'; +import emptyState from './empty_state.vue'; +import eventHub from '../event_hub'; +import environmentsMixin from '../mixins/environments_mixin'; +import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin'; +import StopEnvironmentModal from './stop_environment_modal.vue'; - export default { - components: { - emptyState, - StopEnvironmentModal, - }, +export default { + components: { + emptyState, + StopEnvironmentModal, + }, - mixins: [ - CIPaginationMixin, - environmentsMixin, - ], + mixins: [CIPaginationMixin, environmentsMixin], - props: { - endpoint: { - type: String, - required: true, - }, - canCreateEnvironment: { - type: Boolean, - required: true, - }, - canCreateDeployment: { - type: Boolean, - required: true, - }, - canReadEnvironment: { - type: Boolean, - required: true, - }, - cssContainerClass: { - type: String, - required: true, - }, - newEnvironmentPath: { - type: String, - required: true, - }, - helpPagePath: { - type: String, - required: true, - }, + props: { + endpoint: { + type: String, + required: true, }, - - created() { - eventHub.$on('toggleFolder', this.toggleFolder); + canCreateEnvironment: { + type: Boolean, + required: true, }, - - beforeDestroy() { - eventHub.$off('toggleFolder'); + canCreateDeployment: { + type: Boolean, + required: true, + }, + canReadEnvironment: { + type: Boolean, + required: true, + }, + cssContainerClass: { + type: String, + required: true, + }, + newEnvironmentPath: { + type: String, + required: true, }, + helpPagePath: { + type: String, + required: true, + }, + }, + + created() { + eventHub.$on('toggleFolder', this.toggleFolder); + }, - methods: { - toggleFolder(folder) { - this.store.toggleFolder(folder); + beforeDestroy() { + eventHub.$off('toggleFolder'); + }, - if (!folder.isOpen) { - this.fetchChildEnvironments(folder, true); - } - }, + methods: { + toggleFolder(folder) { + this.store.toggleFolder(folder); - fetchChildEnvironments(folder, showLoader = false) { - this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', showLoader); + if (!folder.isOpen) { + this.fetchChildEnvironments(folder, true); + } + }, - this.service.getFolderContent(folder.folder_path) - .then(response => this.store.setfolderContent(folder, response.data.environments)) - .then(() => this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', false)) - .catch(() => { - Flash(s__('Environments|An error occurred while fetching the environments.')); - this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', false); - }); - }, + fetchChildEnvironments(folder, showLoader = false) { + this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', showLoader); + + this.service + .getFolderContent(folder.folder_path) + .then(response => this.store.setfolderContent(folder, response.data.environments)) + .then(() => this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', false)) + .catch(() => { + Flash(s__('Environments|An error occurred while fetching the environments.')); + this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', false); + }); + }, - successCallback(resp) { - this.saveData(resp); + successCallback(resp) { + this.saveData(resp); - // We need to verify if any folder is open to also update it - const openFolders = this.store.getOpenFolders(); - if (openFolders.length) { - openFolders.forEach(folder => this.fetchChildEnvironments(folder)); - } - }, + // We need to verify if any folder is open to also update it + const openFolders = this.store.getOpenFolders(); + if (openFolders.length) { + openFolders.forEach(folder => this.fetchChildEnvironments(folder)); + } }, - }; + }, +}; </script> <template> <div :class="cssContainerClass"> diff --git a/app/assets/javascripts/environments/stores/environments_store.js b/app/assets/javascripts/environments/stores/environments_store.js index 5ce9225a4bb..5808a2d4afa 100644 --- a/app/assets/javascripts/environments/stores/environments_store.js +++ b/app/assets/javascripts/environments/stores/environments_store.js @@ -34,14 +34,14 @@ export default class EnvironmentsStore { * @returns {Array} */ storeEnvironments(environments = []) { - const filteredEnvironments = environments.map((env) => { - const oldEnvironmentState = this.state.environments - .find((element) => { - if (env.latest) { - return element.id === env.latest.id; - } - return element.id === env.id; - }) || {}; + const filteredEnvironments = environments.map(env => { + const oldEnvironmentState = + this.state.environments.find(element => { + if (env.latest) { + return element.id === env.latest.id; + } + return element.id === env.id; + }) || {}; let filtered = {}; @@ -101,11 +101,11 @@ export default class EnvironmentsStore { } /** - * Toggles folder open property for the given folder. - * - * @param {Object} folder - * @return {Array} - */ + * Toggles folder open property for the given folder. + * + * @param {Object} folder + * @return {Array} + */ toggleFolder(folder) { return this.updateEnvironmentProp(folder, 'isOpen', !folder.isOpen); } @@ -119,7 +119,7 @@ export default class EnvironmentsStore { * @return {Object} */ setfolderContent(folder, environments) { - const updatedEnvironments = environments.map((env) => { + const updatedEnvironments = environments.map(env => { let updated = env; if (env.latest) { @@ -148,7 +148,7 @@ export default class EnvironmentsStore { updateEnvironmentProp(environment, prop, newValue) { const { environments } = this.state; - const updatedEnvironments = environments.map((env) => { + const updatedEnvironments = environments.map(env => { const updateEnv = Object.assign({}, env); if (env.id === environment.id) { updateEnv[prop] = newValue; diff --git a/app/assets/javascripts/filtered_search/dropdown_user.js b/app/assets/javascripts/filtered_search/dropdown_user.js index d36f38a70b5..d5027590bb7 100644 --- a/app/assets/javascripts/filtered_search/dropdown_user.js +++ b/app/assets/javascripts/filtered_search/dropdown_user.js @@ -39,8 +39,9 @@ export default class DropdownUser extends FilteredSearchDropdown { } itemClicked(e) { - super.itemClicked(e, - selected => selected.querySelector('.dropdown-light-content').innerText.trim()); + super.itemClicked(e, selected => + selected.querySelector('.dropdown-light-content').innerText.trim(), + ); } renderContent(forceShowList = false) { @@ -68,7 +69,7 @@ export default class DropdownUser extends FilteredSearchDropdown { // Removes the first character if it is a quotation so that we can search // with multiple words - if (value[0] === '"' || value[0] === '\'') { + if (value[0] === '"' || value[0] === "'") { value = value.slice(1); } diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js index cd3d532c958..57ec6603d80 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js @@ -108,7 +108,7 @@ export default class FilteredSearchDropdownManager { }, }; - supportedTokens.forEach((type) => { + supportedTokens.forEach(type => { if (availableMappings[type]) { allowedMappings[type] = availableMappings[type]; } @@ -142,10 +142,7 @@ export default class FilteredSearchDropdownManager { } static addWordToInput(tokenName, tokenValue = '', clicked = false, options = {}) { - const { - uppercaseTokenName = false, - capitalizeTokenValue = false, - } = options; + const { uppercaseTokenName = false, capitalizeTokenValue = false } = options; const input = FilteredSearchContainer.container.querySelector('.filtered-search'); FilteredSearchVisualTokens.addFilterVisualToken(tokenName, tokenValue, { uppercaseTokenName, @@ -164,13 +161,16 @@ export default class FilteredSearchDropdownManager { updateDropdownOffset(key) { // Always align dropdown with the input field - let offset = this.filteredSearchInput.getBoundingClientRect().left - this.container.querySelector('.scroll-container').getBoundingClientRect().left; + let offset = + this.filteredSearchInput.getBoundingClientRect().left - + this.container.querySelector('.scroll-container').getBoundingClientRect().left; const maxInputWidth = 240; const currentDropdownWidth = this.mapping[key].element.clientWidth || maxInputWidth; // Make sure offset never exceeds the input container - const offsetMaxWidth = this.container.querySelector('.scroll-container').clientWidth - currentDropdownWidth; + const offsetMaxWidth = + this.container.querySelector('.scroll-container').clientWidth - currentDropdownWidth; if (offsetMaxWidth < offset) { offset = offsetMaxWidth; } @@ -196,8 +196,7 @@ export default class FilteredSearchDropdownManager { const glArguments = Object.assign({}, defaultArguments, extraArguments); // Passing glArguments to `new glClass(<arguments>)` - mappingKey.reference = - new (Function.prototype.bind.apply(glClass, [null, glArguments]))(); + mappingKey.reference = new (Function.prototype.bind.apply(glClass, [null, glArguments]))(); } if (firstLoad) { @@ -224,8 +223,8 @@ export default class FilteredSearchDropdownManager { } const match = this.filteredSearchTokenKeys.searchByKey(dropdownName.toLowerCase()); - const shouldOpenFilterDropdown = match && this.currentDropdown !== match.key - && this.mapping[match.key]; + const shouldOpenFilterDropdown = + match && this.currentDropdown !== match.key && this.mapping[match.key]; const shouldOpenHintDropdown = !match && this.currentDropdown !== 'hint'; if (shouldOpenFilterDropdown || shouldOpenHintDropdown) { @@ -236,8 +235,10 @@ export default class FilteredSearchDropdownManager { setDropdown() { const query = DropdownUtils.getSearchQuery(true); - const { lastToken, searchToken } = - this.tokenizer.processTokens(query, this.filteredSearchTokenKeys.getKeys()); + const { lastToken, searchToken } = this.tokenizer.processTokens( + query, + this.filteredSearchTokenKeys.getKeys(), + ); if (this.currentDropdown) { this.updateCurrentDropdownOffset(); diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js index 54533ebb70d..4a2af02b40a 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js @@ -1,8 +1,5 @@ import _ from 'underscore'; -import { - getParameterByName, - getUrlParamsArray, -} from '~/lib/utils/common_utils'; +import { getParameterByName, getUrlParamsArray } from '~/lib/utils/common_utils'; import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys'; import { visitUrl } from '../lib/utils/url_utility'; import Flash from '../flash'; @@ -48,24 +45,28 @@ export default class FilteredSearchManager { isLocalStorageAvailable: RecentSearchesService.isAvailable(), allowedKeys: this.filteredSearchTokenKeys.getKeys(), }); - this.searchHistoryDropdownElement = document.querySelector('.js-filtered-search-history-dropdown'); - const fullPath = this.searchHistoryDropdownElement ? - this.searchHistoryDropdownElement.dataset.fullPath : 'project'; + this.searchHistoryDropdownElement = document.querySelector( + '.js-filtered-search-history-dropdown', + ); + const fullPath = this.searchHistoryDropdownElement + ? this.searchHistoryDropdownElement.dataset.fullPath + : 'project'; const recentSearchesKey = `${fullPath}-${this.recentsStorageKeyNames[this.page]}`; this.recentSearchesService = new RecentSearchesService(recentSearchesKey); } setup() { // Fetch recent searches from localStorage - this.fetchingRecentSearchesPromise = this.recentSearchesService.fetch() - .catch((error) => { + this.fetchingRecentSearchesPromise = this.recentSearchesService + .fetch() + .catch(error => { if (error.name === 'RecentSearchesServiceError') return undefined; // eslint-disable-next-line no-new new Flash('An error occurred while parsing recent searches'); // Gracefully fail to empty array return []; }) - .then((searches) => { + .then(searches => { if (!searches) { return; } @@ -120,7 +121,7 @@ export default class FilteredSearchManager { if (this.stateFilters) { this.searchStateWrapper = this.searchState.bind(this); - this.applyToStateFilters((filterEl) => { + this.applyToStateFilters(filterEl => { filterEl.addEventListener('click', this.searchStateWrapper); }); } @@ -128,14 +129,14 @@ export default class FilteredSearchManager { unbindStateEvents() { if (this.stateFilters) { - this.applyToStateFilters((filterEl) => { + this.applyToStateFilters(filterEl => { filterEl.removeEventListener('click', this.searchStateWrapper); }); } } applyToStateFilters(callback) { - this.stateFilters.querySelectorAll('a[data-state]').forEach((filterEl) => { + this.stateFilters.querySelectorAll('a[data-state]').forEach(filterEl => { if (this.states.indexOf(filterEl.dataset.state) > -1) { callback(filterEl); } @@ -207,7 +208,7 @@ export default class FilteredSearchManager { let backspaceCount = 0; // closure for keeping track of the number of backspace keystrokes - return (e) => { + return e => { // 8 = Backspace Key // 46 = Delete Key if (e.keyCode === 8 || e.keyCode === 46) { @@ -274,8 +275,12 @@ export default class FilteredSearchManager { const isElementInDynamicFilterDropdown = e.target.closest('.filter-dropdown') !== null; const isElementInStaticFilterDropdown = e.target.closest('ul[data-dropdown]') !== null; - if (!isElementInFilteredSearch && !isElementInDynamicFilterDropdown && - !isElementInStaticFilterDropdown && inputContainer) { + if ( + !isElementInFilteredSearch && + !isElementInDynamicFilterDropdown && + !isElementInStaticFilterDropdown && + inputContainer + ) { inputContainer.classList.remove('focus'); } } @@ -368,7 +373,7 @@ export default class FilteredSearchManager { const removeElements = []; - [].forEach.call(this.tokensContainer.children, (t) => { + [].forEach.call(this.tokensContainer.children, t => { let canClearToken = t.classList.contains('js-visual-token'); if (canClearToken) { @@ -381,7 +386,7 @@ export default class FilteredSearchManager { } }); - removeElements.forEach((el) => { + removeElements.forEach(el => { el.parentElement.removeChild(el); }); @@ -397,13 +402,14 @@ export default class FilteredSearchManager { handleInputVisualToken() { const input = this.filteredSearchInput; - const { tokens, searchToken } - = this.tokenizer.processTokens(input.value, this.filteredSearchTokenKeys.getKeys()); - const { isLastVisualTokenValid } - = FilteredSearchVisualTokens.getLastVisualTokenBeforeInput(); + const { tokens, searchToken } = this.tokenizer.processTokens( + input.value, + this.filteredSearchTokenKeys.getKeys(), + ); + const { isLastVisualTokenValid } = FilteredSearchVisualTokens.getLastVisualTokenBeforeInput(); if (isLastVisualTokenValid) { - tokens.forEach((t) => { + tokens.forEach(t => { input.value = input.value.replace(`${t.key}:${t.symbol}${t.value}`, ''); FilteredSearchVisualTokens.addFilterVisualToken(t.key, `${t.symbol}${t.value}`, { uppercaseTokenName: this.filteredSearchTokenKeys.shouldUppercaseTokenName(t.key), @@ -453,15 +459,17 @@ export default class FilteredSearchManager { saveCurrentSearchQuery() { // Don't save before we have fetched the already saved searches - this.fetchingRecentSearchesPromise.then(() => { - const searchQuery = DropdownUtils.getSearchQuery(); - if (searchQuery.length > 0) { - const resultantSearches = this.recentSearchesStore.addRecentSearch(searchQuery); - this.recentSearchesService.save(resultantSearches); - } - }).catch(() => { - // https://gitlab.com/gitlab-org/gitlab-ce/issues/30821 - }); + this.fetchingRecentSearchesPromise + .then(() => { + const searchQuery = DropdownUtils.getSearchQuery(); + if (searchQuery.length > 0) { + const resultantSearches = this.recentSearchesStore.addRecentSearch(searchQuery); + this.recentSearchesService.save(resultantSearches); + } + }) + .catch(() => { + // https://gitlab.com/gitlab-org/gitlab-ce/issues/30821 + }); } // allows for modifying params array when a param can't be included in the URL (e.g. Service Desk) @@ -475,7 +483,7 @@ export default class FilteredSearchManager { const usernameParams = this.getUsernameParams(); let hasFilteredSearch = false; - params.forEach((p) => { + params.forEach(p => { const split = p.split('='); const keyParam = decodeURIComponent(split[0]); const value = split[1]; @@ -486,11 +494,9 @@ export default class FilteredSearchManager { if (condition) { hasFilteredSearch = true; const canEdit = this.canEdit && this.canEdit(condition.tokenKey); - FilteredSearchVisualTokens.addFilterVisualToken( - condition.tokenKey, - condition.value, - { canEdit }, - ); + FilteredSearchVisualTokens.addFilterVisualToken(condition.tokenKey, condition.value, { + canEdit, + }); } else { // Sanitize value since URL converts spaces into + // Replace before decode so that we know what was originally + versus the encoded + @@ -510,7 +516,7 @@ export default class FilteredSearchManager { if (sanitizedValue.indexOf(' ') !== -1) { // Prefer ", but use ' if required - quotationsToUse = sanitizedValue.indexOf('"') === -1 ? '"' : '\''; + quotationsToUse = sanitizedValue.indexOf('"') === -1 ? '"' : "'"; } hasFilteredSearch = true; @@ -531,7 +537,9 @@ export default class FilteredSearchManager { hasFilteredSearch = true; const tokenName = 'assignee'; const canEdit = this.canEdit && this.canEdit(tokenName); - FilteredSearchVisualTokens.addFilterVisualToken(tokenName, `@${usernameParams[id]}`, { canEdit }); + FilteredSearchVisualTokens.addFilterVisualToken(tokenName, `@${usernameParams[id]}`, { + canEdit, + }); } } else if (!match && keyParam === 'author_id') { const id = parseInt(value, 10); @@ -539,7 +547,9 @@ export default class FilteredSearchManager { hasFilteredSearch = true; const tokenName = 'author'; const canEdit = this.canEdit && this.canEdit(tokenName); - FilteredSearchVisualTokens.addFilterVisualToken(tokenName, `@${usernameParams[id]}`, { canEdit }); + FilteredSearchVisualTokens.addFilterVisualToken(tokenName, `@${usernameParams[id]}`, { + canEdit, + }); } } else if (!match && keyParam === 'search') { hasFilteredSearch = true; @@ -580,9 +590,11 @@ export default class FilteredSearchManager { const currentState = state || getParameterByName('state') || 'opened'; paths.push(`state=${currentState}`); - tokens.forEach((token) => { - const condition = this.filteredSearchTokenKeys - .searchByConditionKeyValue(token.key, token.value.toLowerCase()); + tokens.forEach(token => { + const condition = this.filteredSearchTokenKeys.searchByConditionKeyValue( + token.key, + token.value.toLowerCase(), + ); const tokenConfig = this.filteredSearchTokenKeys.searchByKey(token.key) || {}; const { param } = tokenConfig; @@ -601,8 +613,10 @@ export default class FilteredSearchManager { tokenValue = tokenValue.toLowerCase(); } - if ((tokenValue[0] === '\'' && tokenValue[tokenValue.length - 1] === '\'') || - (tokenValue[0] === '"' && tokenValue[tokenValue.length - 1] === '"')) { + if ( + (tokenValue[0] === "'" && tokenValue[tokenValue.length - 1] === "'") || + (tokenValue[0] === '"' && tokenValue[tokenValue.length - 1] === '"') + ) { tokenValue = tokenValue.slice(1, tokenValue.length - 1); } @@ -613,7 +627,10 @@ export default class FilteredSearchManager { }); if (searchToken) { - const sanitized = searchToken.split(' ').map(t => encodeURIComponent(t)).join('+'); + const sanitized = searchToken + .split(' ') + .map(t => encodeURIComponent(t)) + .join('+'); paths.push(`search=${sanitized}`); } @@ -630,7 +647,7 @@ export default class FilteredSearchManager { const usernamesById = {}; try { const attribute = this.filteredSearchInput.getAttribute('data-username-params'); - JSON.parse(attribute).forEach((user) => { + JSON.parse(attribute).forEach(user => { usernamesById[user.id] = user.username; }); } catch (e) { diff --git a/app/assets/javascripts/flash.js b/app/assets/javascripts/flash.js index 749c09f897c..c2397842125 100644 --- a/app/assets/javascripts/flash.js +++ b/app/assets/javascripts/flash.js @@ -40,7 +40,9 @@ const createFlashEl = (message, type, isFixedLayout = false) => ` class="flash-${type}" > <div - class="flash-text ${isFixedLayout ? 'container-fluid container-limited limit-container-width' : ''}" + class="flash-text ${ + isFixedLayout ? 'container-fluid container-limited limit-container-width' : '' + }" > ${_.escape(message)} </div> @@ -78,7 +80,9 @@ const createFlash = function createFlash( if (!flashContainer) return null; - const isFixedLayout = navigation ? navigation.parentNode.classList.contains('container-limited') : true; + const isFixedLayout = navigation + ? navigation.parentNode.classList.contains('container-limited') + : true; flashContainer.innerHTML = createFlashEl(message, type, isFixedLayout); diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js index 7dd0efd622d..00b3d283570 100644 --- a/app/assets/javascripts/gfm_auto_complete.js +++ b/app/assets/javascripts/gfm_auto_complete.js @@ -94,7 +94,7 @@ class GfmAutoComplete { ...this.getDefaultCallbacks(), beforeSave(commands) { if (GfmAutoComplete.isLoading(commands)) return commands; - return $.map(commands, (c) => { + return $.map(commands, c => { let search = c.name; if (c.aliases.length > 0) { search = `${search} ${c.aliases.join(' ')}`; @@ -167,7 +167,7 @@ class GfmAutoComplete { callbacks: { ...this.getDefaultCallbacks(), beforeSave(members) { - return $.map(members, (m) => { + return $.map(members, m => { let title = ''; if (m.username == null) { return m; @@ -178,7 +178,9 @@ class GfmAutoComplete { } const autoCompleteAvatar = m.avatar_url || m.username.charAt(0).toUpperCase(); - const imgAvatar = `<img src="${m.avatar_url}" alt="${m.username}" class="avatar avatar-inline center s26"/>`; + const imgAvatar = `<img src="${m.avatar_url}" alt="${ + m.username + }" class="avatar avatar-inline center s26"/>`; const txtAvatar = `<div class="avatar center avatar-inline s26">${autoCompleteAvatar}</div>`; return { @@ -211,7 +213,7 @@ class GfmAutoComplete { callbacks: { ...this.getDefaultCallbacks(), beforeSave(issues) { - return $.map(issues, (i) => { + return $.map(issues, i => { if (i.title == null) { return i; } @@ -244,7 +246,7 @@ class GfmAutoComplete { callbacks: { ...this.getDefaultCallbacks(), beforeSave(milestones) { - return $.map(milestones, (m) => { + return $.map(milestones, m => { if (m.title == null) { return m; } @@ -277,7 +279,7 @@ class GfmAutoComplete { callbacks: { ...this.getDefaultCallbacks(), beforeSave(merges) { - return $.map(merges, (m) => { + return $.map(merges, m => { if (m.title == null) { return m; } @@ -324,13 +326,20 @@ class GfmAutoComplete { }, matcher(flag, subtext) { const match = GfmAutoComplete.defaultMatcher(flag, subtext, this.app.controllers); - const subtextNodes = subtext.split(/\n+/g).pop().split(GfmAutoComplete.regexSubtext); + const subtextNodes = subtext + .split(/\n+/g) + .pop() + .split(GfmAutoComplete.regexSubtext); // Check if ~ is followed by '/label', '/relabel' or '/unlabel' commands. - command = subtextNodes.find((node) => { - if (node === LABEL_COMMAND.LABEL || - node === LABEL_COMMAND.RELABEL || - node === LABEL_COMMAND.UNLABEL) { return node; } + command = subtextNodes.find(node => { + if ( + node === LABEL_COMMAND.LABEL || + node === LABEL_COMMAND.RELABEL || + node === LABEL_COMMAND.UNLABEL + ) { + return node; + } return null; }); @@ -380,7 +389,7 @@ class GfmAutoComplete { callbacks: { ...this.getDefaultCallbacks(), beforeSave(snippets) { - return $.map(snippets, (m) => { + return $.map(snippets, m => { if (m.title == null) { return m; } @@ -458,13 +467,17 @@ class GfmAutoComplete { this.loadData($input, at, validEmojiNames); GfmAutoComplete.glEmojiTag = glEmojiTag; }) - .catch(() => { this.isLoadingData[at] = false; }); + .catch(() => { + this.isLoadingData[at] = false; + }); } else if (dataSource) { AjaxCache.retrieve(dataSource, true) - .then((data) => { + .then(data => { this.loadData($input, at, data); }) - .catch(() => { this.isLoadingData[at] = false; }); + .catch(() => { + this.isLoadingData[at] = false; + }); } else { this.isLoadingData[at] = false; } @@ -497,15 +510,16 @@ class GfmAutoComplete { } const loadingState = GfmAutoComplete.defaultLoadingData[0]; - return dataToInspect && - (dataToInspect === loadingState || dataToInspect.name === loadingState); + return dataToInspect && (dataToInspect === loadingState || dataToInspect.name === loadingState); } static defaultMatcher(flag, subtext, controllers) { // The below is taken from At.js source // Tweaked to commands to start without a space only if char before is a non-word character // https://github.com/ichord/At.js - const atSymbolsWithBar = Object.keys(controllers).join('|').replace(/[$]/, '\\$&'); + const atSymbolsWithBar = Object.keys(controllers) + .join('|') + .replace(/[$]/, '\\$&'); const atSymbolsWithoutBar = Object.keys(controllers).join(''); const targetSubtext = subtext.split(GfmAutoComplete.regexSubtext).pop(); const resultantFlag = flag.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&'); @@ -513,7 +527,10 @@ class GfmAutoComplete { const accentAChar = decodeURI('%C3%80'); const accentYChar = decodeURI('%C3%BF'); - const regexp = new RegExp(`^(?:\\B|[^a-zA-Z0-9_\`${atSymbolsWithoutBar}]|\\s)${resultantFlag}(?!${atSymbolsWithBar})((?:[A-Za-z${accentAChar}-${accentYChar}0-9_'.+-]|[^\\x00-\\x7a])*)$`, 'gi'); + const regexp = new RegExp( + `^(?:\\B|[^a-zA-Z0-9_\`${atSymbolsWithoutBar}]|\\s)${resultantFlag}(?!${atSymbolsWithBar})((?:[A-Za-z${accentAChar}-${accentYChar}0-9_'.+-]|[^\\x00-\\x7a])*)$`, + 'gi', + ); return regexp.exec(targetSubtext); } @@ -552,8 +569,9 @@ GfmAutoComplete.Members = { template: '<li>${avatarTag} ${username} <small>${title}</small></li>', }; GfmAutoComplete.Labels = { - // eslint-disable-next-line no-template-curly-in-string - template: '<li><span class="dropdown-label-box" style="background: ${color}"></span> ${title}</li>', + template: + // eslint-disable-next-line no-template-curly-in-string + '<li><span class="dropdown-label-box" style="background: ${color}"></span> ${title}</li>', }; // Issues, MergeRequests and Snippets GfmAutoComplete.Issues = { @@ -567,7 +585,8 @@ GfmAutoComplete.Milestones = { template: '<li>${title}</li>', }; GfmAutoComplete.Loading = { - template: '<li style="pointer-events: none;"><i class="fa fa-spinner fa-spin"></i> Loading...</li>', + template: + '<li style="pointer-events: none;"><i class="fa fa-spinner fa-spin"></i> Loading...</li>', }; export default GfmAutoComplete; diff --git a/app/assets/javascripts/ide/components/ide.vue b/app/assets/javascripts/ide/components/ide.vue index ad6151e3bf6..0a368f6558c 100644 --- a/app/assets/javascripts/ide/components/ide.vue +++ b/app/assets/javascripts/ide/components/ide.vue @@ -43,7 +43,7 @@ export default { 'currentProjectId', 'errorMessage', ]), - ...mapGetters(['activeFile', 'hasChanges', 'someUncommitedChanges', 'isCommitModeActive']), + ...mapGetters(['activeFile', 'hasChanges', 'someUncommittedChanges', 'isCommitModeActive']), }, mounted() { window.onbeforeunload = e => this.onBeforeUnload(e); @@ -63,7 +63,7 @@ export default { onBeforeUnload(e = {}) { const returnValue = __('Are you sure you want to lose unsaved changes?'); - if (!this.someUncommitedChanges) return undefined; + if (!this.someUncommittedChanges) return undefined; Object.assign(e, { returnValue, diff --git a/app/assets/javascripts/ide/components/ide_side_bar.vue b/app/assets/javascripts/ide/components/ide_side_bar.vue index d4c430cd2f3..364ab9426e0 100644 --- a/app/assets/javascripts/ide/components/ide_side_bar.vue +++ b/app/assets/javascripts/ide/components/ide_side_bar.vue @@ -25,11 +25,11 @@ export default { }, computed: { ...mapState(['loading', 'currentActivityView', 'changedFiles', 'stagedFiles', 'lastCommitMsg']), - ...mapGetters(['currentProject', 'someUncommitedChanges']), + ...mapGetters(['currentProject', 'someUncommittedChanges']), showSuccessMessage() { return ( this.currentActivityView === activityBarViews.edit && - (this.lastCommitMsg && !this.someUncommitedChanges) + (this.lastCommitMsg && !this.someUncommittedChanges) ); }, }, diff --git a/app/assets/javascripts/ide/components/repo_commit_section.vue b/app/assets/javascripts/ide/components/repo_commit_section.vue index d3b24c5b793..5e86876c1c1 100644 --- a/app/assets/javascripts/ide/components/repo_commit_section.vue +++ b/app/assets/javascripts/ide/components/repo_commit_section.vue @@ -27,10 +27,10 @@ export default { 'unusedSeal', ]), ...mapState('commit', ['commitMessage', 'submitCommitLoading']), - ...mapGetters(['lastOpenedFile', 'hasChanges', 'someUncommitedChanges', 'activeFile']), + ...mapGetters(['lastOpenedFile', 'hasChanges', 'someUncommittedChanges', 'activeFile']), ...mapGetters('commit', ['discardDraftButtonDisabled']), showStageUnstageArea() { - return !!(this.someUncommitedChanges || this.lastCommitMsg || !this.unusedSeal); + return !!(this.someUncommittedChanges || this.lastCommitMsg || !this.unusedSeal); }, activeFileKey() { return this.activeFile ? this.activeFile.key : null; diff --git a/app/assets/javascripts/ide/stores/getters.js b/app/assets/javascripts/ide/stores/getters.js index 709748fb530..8ad85074d6b 100644 --- a/app/assets/javascripts/ide/stores/getters.js +++ b/app/assets/javascripts/ide/stores/getters.js @@ -63,7 +63,7 @@ export const isEditModeActive = state => state.currentActivityView === activityB export const isCommitModeActive = state => state.currentActivityView === activityBarViews.commit; export const isReviewModeActive = state => state.currentActivityView === activityBarViews.review; -export const someUncommitedChanges = state => +export const someUncommittedChanges = state => !!(state.changedFiles.length || state.stagedFiles.length); export const getChangesInFolder = state => path => { diff --git a/app/assets/javascripts/jobs/components/job_app.vue b/app/assets/javascripts/jobs/components/job_app.vue index ac19034f69d..3cabbfc6e27 100644 --- a/app/assets/javascripts/jobs/components/job_app.vue +++ b/app/assets/javascripts/jobs/components/job_app.vue @@ -1,164 +1,164 @@ <script> - import _ from 'underscore'; - import { mapGetters, mapState, mapActions } from 'vuex'; - import { isScrolledToBottom } from '~/lib/utils/scroll_utils'; - import bp from '~/breakpoints'; - import CiHeader from '~/vue_shared/components/header_ci_component.vue'; - import Callout from '~/vue_shared/components/callout.vue'; - import createStore from '../store'; - import EmptyState from './empty_state.vue'; - import EnvironmentsBlock from './environments_block.vue'; - import ErasedBlock from './erased_block.vue'; - import Log from './job_log.vue'; - import LogTopBar from './job_log_controllers.vue'; - import StuckBlock from './stuck_block.vue'; - import Sidebar from './sidebar.vue'; +import _ from 'underscore'; +import { mapGetters, mapState, mapActions } from 'vuex'; +import { isScrolledToBottom } from '~/lib/utils/scroll_utils'; +import bp from '~/breakpoints'; +import CiHeader from '~/vue_shared/components/header_ci_component.vue'; +import Callout from '~/vue_shared/components/callout.vue'; +import createStore from '../store'; +import EmptyState from './empty_state.vue'; +import EnvironmentsBlock from './environments_block.vue'; +import ErasedBlock from './erased_block.vue'; +import Log from './job_log.vue'; +import LogTopBar from './job_log_controllers.vue'; +import StuckBlock from './stuck_block.vue'; +import Sidebar from './sidebar.vue'; - export default { - name: 'JobPageApp', - store: createStore(), - components: { - CiHeader, - Callout, - EmptyState, - EnvironmentsBlock, - ErasedBlock, - Log, - LogTopBar, - StuckBlock, - Sidebar, +export default { + name: 'JobPageApp', + store: createStore(), + components: { + CiHeader, + Callout, + EmptyState, + EnvironmentsBlock, + ErasedBlock, + Log, + LogTopBar, + StuckBlock, + Sidebar, + }, + props: { + runnerSettingsUrl: { + type: String, + required: false, + default: null, }, - props: { - runnerSettingsUrl: { - type: String, - required: false, - default: null, - }, - runnerHelpUrl: { - type: String, - required: false, - default: null, - }, - endpoint: { - type: String, - required: true, - }, - terminalPath: { - type: String, - required: false, - default: null, - }, - pagePath: { - type: String, - required: true, - }, - logState: { - type: String, - required: true, - }, + runnerHelpUrl: { + type: String, + required: false, + default: null, }, - computed: { - ...mapState([ - 'isLoading', - 'job', - 'isSidebarOpen', - 'trace', - 'isTraceComplete', - 'traceSize', - 'isTraceSizeVisible', - 'isScrollBottomDisabled', - 'isScrollTopDisabled', - 'isScrolledToBottomBeforeReceivingTrace', - 'hasError', - ]), - ...mapGetters([ - 'headerActions', - 'headerTime', - 'shouldRenderCalloutMessage', - 'shouldRenderTriggeredLabel', - 'hasEnvironment', - 'hasTrace', - 'emptyStateIllustration', - 'isScrollingDown', - 'emptyStateAction', - 'hasRunnersForProject', - ]), + endpoint: { + type: String, + required: true, + }, + terminalPath: { + type: String, + required: false, + default: null, + }, + pagePath: { + type: String, + required: true, + }, + logState: { + type: String, + required: true, + }, + }, + computed: { + ...mapState([ + 'isLoading', + 'job', + 'isSidebarOpen', + 'trace', + 'isTraceComplete', + 'traceSize', + 'isTraceSizeVisible', + 'isScrollBottomDisabled', + 'isScrollTopDisabled', + 'isScrolledToBottomBeforeReceivingTrace', + 'hasError', + ]), + ...mapGetters([ + 'headerActions', + 'headerTime', + 'shouldRenderCalloutMessage', + 'shouldRenderTriggeredLabel', + 'hasEnvironment', + 'hasTrace', + 'emptyStateIllustration', + 'isScrollingDown', + 'emptyStateAction', + 'hasRunnersForProject', + ]), - shouldRenderContent() { - return !this.isLoading && !this.hasError; - } + shouldRenderContent() { + return !this.isLoading && !this.hasError; }, - watch: { - // Once the job log is loaded, - // fetch the stages for the dropdown on the sidebar - job(newVal, oldVal) { - if (_.isEmpty(oldVal) && !_.isEmpty(newVal.pipeline)) { - this.fetchStages(); - } - }, + }, + watch: { + // Once the job log is loaded, + // fetch the stages for the dropdown on the sidebar + job(newVal, oldVal) { + if (_.isEmpty(oldVal) && !_.isEmpty(newVal.pipeline)) { + this.fetchStages(); + } }, - created() { - this.throttled = _.throttle(this.toggleScrollButtons, 100); + }, + created() { + this.throttled = _.throttle(this.toggleScrollButtons, 100); - this.setJobEndpoint(this.endpoint); - this.setTraceOptions({ - logState: this.logState, - pagePath: this.pagePath, - }); + this.setJobEndpoint(this.endpoint); + this.setTraceOptions({ + logState: this.logState, + pagePath: this.pagePath, + }); - this.fetchJob(); - this.fetchTrace(); + this.fetchJob(); + this.fetchTrace(); - window.addEventListener('resize', this.onResize); - window.addEventListener('scroll', this.updateScroll); - }, + window.addEventListener('resize', this.onResize); + window.addEventListener('scroll', this.updateScroll); + }, - mounted() { + mounted() { + this.updateSidebar(); + }, + + destroyed() { + window.removeEventListener('resize', this.onResize); + window.removeEventListener('scroll', this.updateScroll); + }, + + methods: { + ...mapActions([ + 'setJobEndpoint', + 'setTraceOptions', + 'fetchJob', + 'fetchStages', + 'hideSidebar', + 'showSidebar', + 'toggleSidebar', + 'fetchTrace', + 'scrollBottom', + 'scrollTop', + 'toggleScrollButtons', + 'toggleScrollAnimation', + ]), + onResize() { this.updateSidebar(); + this.updateScroll(); }, - - destroyed() { - window.removeEventListener('resize', this.onResize); - window.removeEventListener('scroll', this.updateScroll); + updateSidebar() { + if (bp.getBreakpointSize() === 'xs') { + this.hideSidebar(); + } else if (!this.isSidebarOpen) { + this.showSidebar(); + } }, + updateScroll() { + if (!isScrolledToBottom()) { + this.toggleScrollAnimation(false); + } else if (this.isScrollingDown) { + this.toggleScrollAnimation(true); + } - methods: { - ...mapActions([ - 'setJobEndpoint', - 'setTraceOptions', - 'fetchJob', - 'fetchStages', - 'hideSidebar', - 'showSidebar', - 'toggleSidebar', - 'fetchTrace', - 'scrollBottom', - 'scrollTop', - 'toggleScrollButtons', - 'toggleScrollAnimation', - ]), - onResize() { - this.updateSidebar(); - this.updateScroll(); - }, - updateSidebar() { - if (bp.getBreakpointSize() === 'xs') { - this.hideSidebar(); - } else if (!this.isSidebarOpen) { - this.showSidebar(); - } - }, - updateScroll() { - if (!isScrolledToBottom()) { - this.toggleScrollAnimation(false); - } else if (this.isScrollingDown) { - this.toggleScrollAnimation(true); - } - - this.throttled(); - }, + this.throttled(); }, - }; + }, +}; </script> <template> <div> diff --git a/app/assets/javascripts/jobs/components/job_log.vue b/app/assets/javascripts/jobs/components/job_log.vue index ffa6ada3e28..92e20e92d66 100644 --- a/app/assets/javascripts/jobs/components/job_log.vue +++ b/app/assets/javascripts/jobs/components/job_log.vue @@ -1,45 +1,45 @@ <script> - import { mapState, mapActions } from 'vuex'; +import { mapState, mapActions } from 'vuex'; - export default { - name: 'JobLog', - props: { - trace: { - type: String, - required: true, - }, - isComplete: { - type: Boolean, - required: true, - }, +export default { + name: 'JobLog', + props: { + trace: { + type: String, + required: true, }, - computed: { - ...mapState(['isScrolledToBottomBeforeReceivingTrace']), + isComplete: { + type: Boolean, + required: true, }, - updated() { - this.$nextTick(() => this.handleScrollDown()); + }, + computed: { + ...mapState(['isScrolledToBottomBeforeReceivingTrace']), + }, + updated() { + this.$nextTick(() => this.handleScrollDown()); + }, + mounted() { + this.$nextTick(() => this.handleScrollDown()); + }, + methods: { + ...mapActions(['scrollBottom']), + /** + * The job log is sent in HTML, which means we need to use `v-html` to render it + * Using the updated hook with $nextTick is not enough to wait for the DOM to be updated + * in this case because it runs before `v-html` has finished running, since there's no + * Vue binding. + * In order to scroll the page down after `v-html` has finished, we need to use setTimeout + */ + handleScrollDown() { + if (this.isScrolledToBottomBeforeReceivingTrace) { + setTimeout(() => { + this.scrollBottom(); + }, 0); + } }, - mounted() { - this.$nextTick(() => this.handleScrollDown()); - }, - methods: { - ...mapActions(['scrollBottom']), - /** - * The job log is sent in HTML, which means we need to use `v-html` to render it - * Using the updated hook with $nextTick is not enough to wait for the DOM to be updated - * in this case because it runs before `v-html` has finished running, since there's no - * Vue binding. - * In order to scroll the page down after `v-html` has finished, we need to use setTimeout - */ - handleScrollDown() { - if (this.isScrolledToBottomBeforeReceivingTrace) { - setTimeout(() => { - this.scrollBottom(); - }, 0); - } - }, - }, - }; + }, +}; </script> <template> <pre class="js-build-trace build-trace qa-build-trace"> diff --git a/app/assets/javascripts/jobs/index.js b/app/assets/javascripts/jobs/index.js index ccd096a1da5..a32e945627c 100644 --- a/app/assets/javascripts/jobs/index.js +++ b/app/assets/javascripts/jobs/index.js @@ -23,4 +23,3 @@ export default () => { }, }); }; - diff --git a/app/assets/javascripts/jobs/store/getters.js b/app/assets/javascripts/jobs/store/getters.js index 4de01f8e532..d440b2c9ef1 100644 --- a/app/assets/javascripts/jobs/store/getters.js +++ b/app/assets/javascripts/jobs/store/getters.js @@ -35,16 +35,19 @@ export const hasEnvironment = state => !_.isEmpty(state.job.deployment_status); * Used to check if it should render the job log or the empty state * @returns {Boolean} */ -export const hasTrace = state => state.job.has_trace || (!_.isEmpty(state.job.status) && state.job.status.group === 'running'); +export const hasTrace = state => + state.job.has_trace || (!_.isEmpty(state.job.status) && state.job.status.group === 'running'); export const emptyStateIllustration = state => (state.job && state.job.status && state.job.status.illustration) || {}; -export const emptyStateAction = state => (state.job && state.job.status && state.job.status.action) || {}; +export const emptyStateAction = state => + (state.job && state.job.status && state.job.status.action) || {}; export const isScrollingDown = state => isScrolledToBottom() && !state.isTraceComplete; -export const hasRunnersForProject = state => state.job.runners.available && !state.job.runners.online; +export const hasRunnersForProject = state => + state.job.runners.available && !state.job.runners.online; // prevent babel-plugin-rewire from generating an invalid default during karma tests export default () => {}; diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index 3c38d998b6c..5457604b3b9 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -25,7 +25,35 @@ export default class LabelsSelect { } $els.each(function(i, dropdown) { - var $block, $colorPreview, $dropdown, $form, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, defaultLabel, enableLabelCreateButton, issueURLSplit, issueUpdateURL, labelUrl, namespacePath, projectPath, saveLabelData, selectedLabel, showAny, showNo, $sidebarLabelTooltip, initialSelected, $toggleText, fieldName, useId, propertyName, showMenuAbove, $container, $dropdownContainer; + var $block, + $colorPreview, + $dropdown, + $form, + $loading, + $selectbox, + $sidebarCollapsedValue, + $value, + abilityName, + defaultLabel, + enableLabelCreateButton, + issueURLSplit, + issueUpdateURL, + labelUrl, + namespacePath, + projectPath, + saveLabelData, + selectedLabel, + showAny, + showNo, + $sidebarLabelTooltip, + initialSelected, + $toggleText, + fieldName, + useId, + propertyName, + showMenuAbove, + $container, + $dropdownContainer; $dropdown = $(dropdown); $dropdownContainer = $dropdown.closest('.labels-filter'); $toggleText = $dropdown.find('.dropdown-toggle-text'); @@ -34,7 +62,7 @@ export default class LabelsSelect { labelUrl = $dropdown.data('labels'); issueUpdateURL = $dropdown.data('issueUpdate'); selectedLabel = $dropdown.data('selected'); - if ((selectedLabel != null) && !$dropdown.hasClass('js-multiselect')) { + if (selectedLabel != null && !$dropdown.hasClass('js-multiselect')) { selectedLabel = selectedLabel.split(','); } showNo = $dropdown.data('showNo'); @@ -50,26 +78,37 @@ export default class LabelsSelect { $value = $block.find('.value'); $loading = $block.find('.block-loading').fadeOut(); fieldName = $dropdown.data('fieldName'); - useId = $dropdown.is('.js-issuable-form-dropdown, .js-filter-bulk-update, .js-label-sidebar-dropdown'); + useId = $dropdown.is( + '.js-issuable-form-dropdown, .js-filter-bulk-update, .js-label-sidebar-dropdown', + ); propertyName = useId ? 'id' : 'title'; initialSelected = $selectbox .find('input[name="' + $dropdown.data('fieldName') + '"]') - .map(function () { + .map(function() { return this.value; - }).get(); + }) + .get(); const { handleClick } = options; $sidebarLabelTooltip.tooltip(); if ($dropdown.closest('.dropdown').find('.dropdown-new-label').length) { - new CreateLabelDropdown($dropdown.closest('.dropdown').find('.dropdown-new-label'), namespacePath, projectPath); + new CreateLabelDropdown( + $dropdown.closest('.dropdown').find('.dropdown-new-label'), + namespacePath, + projectPath, + ); } saveLabelData = function() { var data, selected; - selected = $dropdown.closest('.selectbox').find("input[name='" + fieldName + "']").map(function() { - return this.value; - }).get(); + selected = $dropdown + .closest('.selectbox') + .find("input[name='" + fieldName + "']") + .map(function() { + return this.value; + }) + .get(); if (_.isEqual(initialSelected, selected)) return; initialSelected = selected; @@ -82,7 +121,8 @@ export default class LabelsSelect { } $loading.removeClass('hidden').fadeIn(); $dropdown.trigger('loading.gl.dropdown'); - axios.put(issueUpdateURL, data) + axios + .put(issueUpdateURL, data) .then(({ data }) => { var labelCount, template, labelTooltipTitle, labelTitles, formattedLabels; $loading.fadeOut(); @@ -96,8 +136,7 @@ export default class LabelsSelect { issueUpdateURL, }); labelCount = data.labels.length; - } - else { + } else { template = '<span class="no-value">None</span>'; } $value.removeAttr('style').html(template); @@ -114,17 +153,14 @@ export default class LabelsSelect { } labelTooltipTitle = labelTitles.join(', '); - } - else { + } else { labelTooltipTitle = __('Labels'); } - $sidebarLabelTooltip - .attr('title', labelTooltipTitle) - .tooltip('_fixTitle'); + $sidebarLabelTooltip.attr('title', labelTooltipTitle).tooltip('_fixTitle'); $('.has-tooltip', $value).tooltip({ - container: 'body' + container: 'body', }); }) .catch(() => flash(__('Error saving label update.'))); @@ -132,34 +168,38 @@ export default class LabelsSelect { $dropdown.glDropdown({ showMenuAbove: showMenuAbove, data: function(term, callback) { - axios.get(labelUrl) - .then((res) => { - let data = _.chain(res.data).groupBy(function(label) { - return label.title; - }).map(function(label) { - var color; - color = _.map(label, function(dup) { - return dup.color; - }); - return { - id: label[0].id, - title: label[0].title, - color: color, - duplicate: color.length > 1 - }; - }).value(); + axios + .get(labelUrl) + .then(res => { + let data = _.chain(res.data) + .groupBy(function(label) { + return label.title; + }) + .map(function(label) { + var color; + color = _.map(label, function(dup) { + return dup.color; + }); + return { + id: label[0].id, + title: label[0].title, + color: color, + duplicate: color.length > 1, + }; + }) + .value(); if ($dropdown.hasClass('js-extra-options')) { var extraData = []; if (showNo) { extraData.unshift({ id: 0, - title: 'No Label' + title: 'No Label', }); } if (showAny) { extraData.unshift({ isAny: true, - title: 'Any Label' + title: 'Any Label', }); } if (extraData.length) { @@ -176,11 +216,22 @@ export default class LabelsSelect { .catch(() => flash(__('Error fetching labels.'))); }, renderRow: function(label, instance) { - var $a, $li, color, colorEl, indeterminate, removesAll, selectedClass, spacing, i, marked, dropdownName, dropdownValue; + var $a, + $li, + color, + colorEl, + indeterminate, + removesAll, + selectedClass, + spacing, + i, + marked, + dropdownName, + dropdownValue; $li = $('<li>'); $a = $('<a href="#">'); selectedClass = []; - removesAll = label.id <= 0 || (label.id == null); + removesAll = label.id <= 0 || label.id == null; if ($dropdown.hasClass('js-filter-bulk-update')) { indeterminate = $dropdown.data('indeterminate') || []; marked = $dropdown.data('marked') || []; @@ -200,9 +251,19 @@ export default class LabelsSelect { } else { if (this.id(label)) { dropdownName = $dropdown.data('fieldName'); - dropdownValue = this.id(label).toString().replace(/'/g, '\\\''); - - if ($form.find("input[type='hidden'][name='" + dropdownName + "'][value='" + dropdownValue + "']").length) { + dropdownValue = this.id(label) + .toString() + .replace(/'/g, "\\'"); + + if ( + $form.find( + "input[type='hidden'][name='" + + dropdownName + + "'][value='" + + dropdownValue + + "']", + ).length + ) { selectedClass.push('is-active'); } } @@ -213,16 +274,14 @@ export default class LabelsSelect { } if (label.duplicate) { color = DropdownUtils.duplicateLabelColor(label.color); - } - else { + } else { if (label.color != null) { [color] = label.color; } } if (color) { colorEl = "<span class='dropdown-label-box' style='background: " + color + "'></span>"; - } - else { + } else { colorEl = ''; } // We need to identify which items are actually labels @@ -235,7 +294,7 @@ export default class LabelsSelect { return $li.html($a).prop('outerHTML'); }, search: { - fields: ['title'] + fields: ['title'], }, selectable: true, filterable: true, @@ -255,25 +314,21 @@ export default class LabelsSelect { if (selected && selected.id === 0) { this.selected = []; return 'No Label'; - } - else if (isSelected) { + } else if (isSelected) { this.selected.push(title); - } - else if (!isSelected && title) { + } else if (!isSelected && title) { var index = this.selected.indexOf(title); this.selected.splice(index, 1); } if (selectedLabels.length === 1) { return selectedLabels; - } - else if (selectedLabels.length) { + } else if (selectedLabels.length) { return sprintf(__('%{firstLabel} +%{labelCount} more'), { firstLabel: selectedLabels[0], - labelCount: selectedLabels.length - 1 + labelCount: selectedLabels.length - 1, }); - } - else { + } else { return defaultLabel; } }, @@ -285,10 +340,9 @@ export default class LabelsSelect { return label.id; } - if ($dropdown.hasClass("js-filter-submit") && (label.isAny == null)) { + if ($dropdown.hasClass('js-filter-submit') && label.isAny == null) { return label.title; - } - else { + } else { return label.id; } }, @@ -310,13 +364,13 @@ export default class LabelsSelect { } if ($dropdown.hasClass('js-multiselect')) { if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) { - selectedLabels = $dropdown.closest('form').find("input:hidden[name='" + ($dropdown.data('fieldName')) + "']"); + selectedLabels = $dropdown + .closest('form') + .find("input:hidden[name='" + $dropdown.data('fieldName') + "']"); Issuable.filterResults($dropdown.closest('form')); - } - else if ($dropdown.hasClass('js-filter-submit')) { + } else if ($dropdown.hasClass('js-filter-submit')) { $dropdown.closest('form').submit(); - } - else { + } else { if (!$dropdown.hasClass('js-filter-bulk-update')) { saveLabelData(); } @@ -325,7 +379,7 @@ export default class LabelsSelect { }, multiSelect: $dropdown.hasClass('js-multiselect'), vue: $dropdown.hasClass('js-issue-board-sidebar'), - clicked: function (clickEvent) { + clicked: function(clickEvent) { const { $el, e, isMarking } = clickEvent; const label = clickEvent.selectedObj; @@ -339,7 +393,8 @@ export default class LabelsSelect { isMRIndex = page === 'projects:merge_requests:index'; if ($dropdown.parent().find('.is-active:not(.dropdown-clear-active)').length) { - $dropdown.parent() + $dropdown + .parent() .find('.dropdown-clear-active') .removeClass('is-active'); } @@ -367,28 +422,26 @@ export default class LabelsSelect { e.preventDefault(); return; - } - else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) { + } else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) { if (!$dropdown.hasClass('js-multiselect')) { selectedLabel = label.title; return Issuable.filterResults($dropdown.closest('form')); } - } - else if ($dropdown.hasClass('js-filter-submit')) { + } else if ($dropdown.hasClass('js-filter-submit')) { return $dropdown.closest('form').submit(); - } - else if ($dropdown.hasClass('js-issue-board-sidebar')) { + } else if ($dropdown.hasClass('js-issue-board-sidebar')) { if ($el.hasClass('is-active')) { - boardsStore.detail.issue.labels.push(new ListLabel({ - id: label.id, - title: label.title, - color: label.color[0], - textColor: '#fff' - })); - } - else { + boardsStore.detail.issue.labels.push( + new ListLabel({ + id: label.id, + title: label.title, + color: label.color[0], + textColor: '#fff', + }), + ); + } else { var { labels } = boardsStore.detail.issue; - labels = labels.filter(function (selectedLabel) { + labels = labels.filter(function(selectedLabel) { return selectedLabel.id !== label.id; }); boardsStore.detail.issue.labels = labels; @@ -396,19 +449,16 @@ export default class LabelsSelect { $loading.fadeIn(); - boardsStore.detail.issue.update($dropdown.attr('data-issue-update')) + boardsStore.detail.issue + .update($dropdown.attr('data-issue-update')) .then(fadeOutLoader) .catch(fadeOutLoader); - } - else if (handleClick) { + } else if (handleClick) { e.preventDefault(); handleClick(label); - } - else { + } else { if ($dropdown.hasClass('js-multiselect')) { - - } - else { + } else { return saveLabelData(); } } @@ -436,15 +486,17 @@ export default class LabelsSelect { // so best approach is to use traditional way of // concatenation // see: http://2ality.com/2016/05/template-literal-whitespace.html#joining-arrays - const tpl = _.template([ - '<% _.each(labels, function(label){ %>', - '<a href="<%- issueUpdateURL.slice(0, issueUpdateURL.lastIndexOf("/")) %>?label_name[]=<%- encodeURIComponent(label.title) %>">', - '<span class="badge label has-tooltip color-label" title="<%- label.description %>" style="background-color: <%- label.color %>; color: <%- label.text_color %>;">', - '<%- label.title %>', - '</span>', - '</a>', - '<% }); %>', - ].join('')); + const tpl = _.template( + [ + '<% _.each(labels, function(label){ %>', + '<a href="<%- issueUpdateURL.slice(0, issueUpdateURL.lastIndexOf("/")) %>?label_name[]=<%- encodeURIComponent(label.title) %>">', + '<span class="badge label has-tooltip color-label" title="<%- label.description %>" style="background-color: <%- label.color %>; color: <%- label.text_color %>;">', + '<%- label.title %>', + '</span>', + '</a>', + '<% }); %>', + ].join(''), + ); return tpl(tplData); } diff --git a/app/assets/javascripts/lib/utils/ace_utils.js b/app/assets/javascripts/lib/utils/ace_utils.js index efc4b2a8d94..ee71ae0e61a 100644 --- a/app/assets/javascripts/lib/utils/ace_utils.js +++ b/app/assets/javascripts/lib/utils/ace_utils.js @@ -1,6 +1,6 @@ /* global ace */ export default function getModeByFileExtension(path) { - const modelist = ace.require("ace/ext/modelist"); + const modelist = ace.require('ace/ext/modelist'); return modelist.getModeForPath(path).mode; -}; +} diff --git a/app/assets/javascripts/lib/utils/number_utils.js b/app/assets/javascripts/lib/utils/number_utils.js index afbab59055b..2ccc51c35f7 100644 --- a/app/assets/javascripts/lib/utils/number_utils.js +++ b/app/assets/javascripts/lib/utils/number_utils.js @@ -7,7 +7,7 @@ import { BYTES_IN_KIB } from './constants'; * * * Show 3 digits to the right * * For 2 digits to the left of the decimal point and X digits to the right of it * * * Show 2 digits to the right -*/ + */ export function formatRelevantDigits(number) { let digitsLeft = ''; let relevantDigits = 0; diff --git a/app/assets/javascripts/members.js b/app/assets/javascripts/members.js index 7d0c701fd70..bd263c75a3d 100644 --- a/app/assets/javascripts/members.js +++ b/app/assets/javascripts/members.js @@ -7,8 +7,12 @@ export default class Members { } addListeners() { - $('.js-member-update-control').off('change').on('change', this.formSubmit.bind(this)); - $('.js-edit-member-form').off('ajax:success').on('ajax:success', this.formSuccess.bind(this)); + $('.js-member-update-control') + .off('change') + .on('change', this.formSubmit.bind(this)); + $('.js-edit-member-form') + .off('ajax:success') + .on('ajax:success', this.formSuccess.bind(this)); gl.utils.disableButtonIfEmptyField('#user_ids', 'input[name=commit]', 'change'); } @@ -28,7 +32,7 @@ export default class Members { toggleLabel(selected, $el) { return $el.text(); }, - clicked: (options) => { + clicked: options => { this.formSubmit(null, options.$el); }, }); diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js index 42fb5c7177a..d32f39881dd 100644 --- a/app/assets/javascripts/milestone_select.js +++ b/app/assets/javascripts/milestone_select.js @@ -9,7 +9,10 @@ import '~/gl_dropdown'; import axios from './lib/utils/axios_utils'; import { timeFor } from './lib/utils/datetime_utility'; import ModalStore from './boards/stores/modal_store'; -import boardsStore, { boardStoreIssueSet, boardStoreIssueDelete } from './boards/stores/boards_store'; +import boardsStore, { + boardStoreIssueSet, + boardStoreIssueDelete, +} from './boards/stores/boards_store'; export default class MilestoneSelect { constructor(currentProject, els, options = {}) { diff --git a/app/assets/javascripts/notes/components/notes_app.vue b/app/assets/javascripts/notes/components/notes_app.vue index b0faa443a18..7514ce8a1eb 100644 --- a/app/assets/javascripts/notes/components/notes_app.vue +++ b/app/assets/javascripts/notes/components/notes_app.vue @@ -54,7 +54,13 @@ export default { }; }, computed: { - ...mapGetters(['isNotesFetched', 'discussions', 'getNotesDataByProp', 'discussionCount', 'isLoading']), + ...mapGetters([ + 'isNotesFetched', + 'discussions', + 'getNotesDataByProp', + 'discussionCount', + 'isLoading', + ]), noteableType() { return this.noteableData.noteableType; }, diff --git a/app/assets/javascripts/notes/discussion_filters.js b/app/assets/javascripts/notes/discussion_filters.js index 012ffc4093e..06eadaeea0e 100644 --- a/app/assets/javascripts/notes/discussion_filters.js +++ b/app/assets/javascripts/notes/discussion_filters.js @@ -1,15 +1,17 @@ import Vue from 'vue'; import DiscussionFilter from './components/discussion_filter.vue'; -export default (store) => { +export default store => { const discussionFilterEl = document.getElementById('js-vue-discussion-filter'); if (discussionFilterEl) { const { defaultFilter, notesFilters } = discussionFilterEl.dataset; const defaultValue = defaultFilter ? parseInt(defaultFilter, 10) : null; const filterValues = notesFilters ? JSON.parse(notesFilters) : {}; - const filters = Object.keys(filterValues).map(entry => - ({ title: entry, value: filterValues[entry] })); + const filters = Object.keys(filterValues).map(entry => ({ + title: entry, + value: filterValues[entry], + })); return new Vue({ el: discussionFilterEl, diff --git a/app/assets/javascripts/pages/milestones/shared/components/promote_milestone_modal.vue b/app/assets/javascripts/pages/milestones/shared/components/promote_milestone_modal.vue index 2c683a39f42..9d19e4a095d 100644 --- a/app/assets/javascripts/pages/milestones/shared/components/promote_milestone_modal.vue +++ b/app/assets/javascripts/pages/milestones/shared/components/promote_milestone_modal.vue @@ -1,54 +1,66 @@ <script> - import axios from '~/lib/utils/axios_utils'; - import createFlash from '~/flash'; - import GlModal from '~/vue_shared/components/gl_modal.vue'; - import { s__, sprintf } from '~/locale'; - import { visitUrl } from '~/lib/utils/url_utility'; - import eventHub from '../event_hub'; +import axios from '~/lib/utils/axios_utils'; +import createFlash from '~/flash'; +import GlModal from '~/vue_shared/components/gl_modal.vue'; +import { s__, sprintf } from '~/locale'; +import { visitUrl } from '~/lib/utils/url_utility'; +import eventHub from '../event_hub'; - export default { - components: { - GlModal, +export default { + components: { + GlModal, + }, + props: { + milestoneTitle: { + type: String, + required: true, }, - props: { - milestoneTitle: { - type: String, - required: true, - }, - url: { - type: String, - required: true, - }, - groupName: { - type: String, - required: true, - }, + url: { + type: String, + required: true, }, - computed: { - title() { - return sprintf(s__('Milestones|Promote %{milestoneTitle} to group milestone?'), { milestoneTitle: this.milestoneTitle }); - }, - text() { - return sprintf(s__(`Milestones|Promoting %{milestoneTitle} will make it available for all projects inside %{groupName}. + groupName: { + type: String, + required: true, + }, + }, + computed: { + title() { + return sprintf(s__('Milestones|Promote %{milestoneTitle} to group milestone?'), { + milestoneTitle: this.milestoneTitle, + }); + }, + text() { + return sprintf( + s__(`Milestones|Promoting %{milestoneTitle} will make it available for all projects inside %{groupName}. Existing project milestones with the same title will be merged. - This action cannot be reversed.`), { milestoneTitle: this.milestoneTitle, groupName: this.groupName }); - }, + This action cannot be reversed.`), + { milestoneTitle: this.milestoneTitle, groupName: this.groupName }, + ); }, - methods: { - onSubmit() { - eventHub.$emit('promoteMilestoneModal.requestStarted', this.url); - return axios.post(this.url, { params: { format: 'json' } }) - .then((response) => { - eventHub.$emit('promoteMilestoneModal.requestFinished', { milestoneUrl: this.url, successful: true }); - visitUrl(response.data.url); - }) - .catch((error) => { - eventHub.$emit('promoteMilestoneModal.requestFinished', { milestoneUrl: this.url, successful: false }); - createFlash(error); + }, + methods: { + onSubmit() { + eventHub.$emit('promoteMilestoneModal.requestStarted', this.url); + return axios + .post(this.url, { params: { format: 'json' } }) + .then(response => { + eventHub.$emit('promoteMilestoneModal.requestFinished', { + milestoneUrl: this.url, + successful: true, }); - }, + visitUrl(response.data.url); + }) + .catch(error => { + eventHub.$emit('promoteMilestoneModal.requestFinished', { + milestoneUrl: this.url, + successful: false, + }); + createFlash(error); + }); }, - }; + }, +}; </script> <template> <gl-modal @@ -65,4 +77,3 @@ {{ text }} </gl-modal> </template> - diff --git a/app/assets/javascripts/pages/projects/project.js b/app/assets/javascripts/pages/projects/project.js index 52d66beefc9..a6bee49a6b1 100644 --- a/app/assets/javascripts/pages/projects/project.js +++ b/app/assets/javascripts/pages/projects/project.js @@ -64,7 +64,9 @@ export default class Project { const projectId = $(this).data('project-id'); const cookieKey = `hide_auto_devops_implicitly_enabled_banner_${projectId}`; Cookies.set(cookieKey, 'false'); - $(this).parents('.auto-devops-implicitly-enabled-banner').remove(); + $(this) + .parents('.auto-devops-implicitly-enabled-banner') + .remove(); return e.preventDefault(); }); Project.projectSelectDropdown(); diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue index a16f7e6b77c..c0ec7a5dc94 100644 --- a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue +++ b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue @@ -1,202 +1,200 @@ <script> - import projectFeatureSetting from './project_feature_setting.vue'; - import projectFeatureToggle from '../../../../../vue_shared/components/toggle_button.vue'; - import projectSettingRow from './project_setting_row.vue'; - import { visibilityOptions, visibilityLevelDescriptions } from '../constants'; - import { toggleHiddenClassBySelector } from '../external'; +import projectFeatureSetting from './project_feature_setting.vue'; +import projectFeatureToggle from '../../../../../vue_shared/components/toggle_button.vue'; +import projectSettingRow from './project_setting_row.vue'; +import { visibilityOptions, visibilityLevelDescriptions } from '../constants'; +import { toggleHiddenClassBySelector } from '../external'; - export default { - components: { - projectFeatureSetting, - projectFeatureToggle, - projectSettingRow, - }, +export default { + components: { + projectFeatureSetting, + projectFeatureToggle, + projectSettingRow, + }, - props: { - currentSettings: { - type: Object, - required: true, - }, - canChangeVisibilityLevel: { - type: Boolean, - required: false, - default: false, - }, - allowedVisibilityOptions: { - type: Array, - required: false, - default: () => [0, 10, 20], - }, - lfsAvailable: { - type: Boolean, - required: false, - default: false, - }, - registryAvailable: { - type: Boolean, - required: false, - default: false, - }, - visibilityHelpPath: { - type: String, - required: false, - default: '', - }, - lfsHelpPath: { - type: String, - required: false, - default: '', - }, - registryHelpPath: { - type: String, - required: false, - default: '', - }, - pagesAvailable: { - type: Boolean, - required: false, - default: false, - }, - pagesAccessControlEnabled: { - type: Boolean, - required: false, - default: false, - }, - pagesHelpPath: { - type: String, - required: false, - default: '', - }, + props: { + currentSettings: { + type: Object, + required: true, + }, + canChangeVisibilityLevel: { + type: Boolean, + required: false, + default: false, + }, + allowedVisibilityOptions: { + type: Array, + required: false, + default: () => [0, 10, 20], + }, + lfsAvailable: { + type: Boolean, + required: false, + default: false, + }, + registryAvailable: { + type: Boolean, + required: false, + default: false, + }, + visibilityHelpPath: { + type: String, + required: false, + default: '', }, + lfsHelpPath: { + type: String, + required: false, + default: '', + }, + registryHelpPath: { + type: String, + required: false, + default: '', + }, + pagesAvailable: { + type: Boolean, + required: false, + default: false, + }, + pagesAccessControlEnabled: { + type: Boolean, + required: false, + default: false, + }, + pagesHelpPath: { + type: String, + required: false, + default: '', + }, + }, - data() { - const defaults = { - visibilityOptions, - visibilityLevel: visibilityOptions.PUBLIC, - issuesAccessLevel: 20, - repositoryAccessLevel: 20, - mergeRequestsAccessLevel: 20, - buildsAccessLevel: 20, - wikiAccessLevel: 20, - snippetsAccessLevel: 20, - pagesAccessLevel: 20, - containerRegistryEnabled: true, - lfsEnabled: true, - requestAccessEnabled: true, - highlightChangesClass: false, - }; + data() { + const defaults = { + visibilityOptions, + visibilityLevel: visibilityOptions.PUBLIC, + issuesAccessLevel: 20, + repositoryAccessLevel: 20, + mergeRequestsAccessLevel: 20, + buildsAccessLevel: 20, + wikiAccessLevel: 20, + snippetsAccessLevel: 20, + pagesAccessLevel: 20, + containerRegistryEnabled: true, + lfsEnabled: true, + requestAccessEnabled: true, + highlightChangesClass: false, + }; - return { ...defaults, ...this.currentSettings }; - }, + return { ...defaults, ...this.currentSettings }; + }, - computed: { - featureAccessLevelOptions() { - const options = [ - [10, 'Only Project Members'], - ]; - if (this.visibilityLevel !== visibilityOptions.PRIVATE) { - options.push([20, 'Everyone With Access']); - } - return options; - }, + computed: { + featureAccessLevelOptions() { + const options = [[10, 'Only Project Members']]; + if (this.visibilityLevel !== visibilityOptions.PRIVATE) { + options.push([20, 'Everyone With Access']); + } + return options; + }, - repoFeatureAccessLevelOptions() { - return this.featureAccessLevelOptions.filter( - ([value]) => value <= this.repositoryAccessLevel, - ); - }, + repoFeatureAccessLevelOptions() { + return this.featureAccessLevelOptions.filter( + ([value]) => value <= this.repositoryAccessLevel, + ); + }, - pagesFeatureAccessLevelOptions() { - if (this.visibilityLevel !== visibilityOptions.PUBLIC) { - return this.featureAccessLevelOptions.concat([[30, 'Everyone']]); - } - return this.featureAccessLevelOptions; - }, + pagesFeatureAccessLevelOptions() { + if (this.visibilityLevel !== visibilityOptions.PUBLIC) { + return this.featureAccessLevelOptions.concat([[30, 'Everyone']]); + } + return this.featureAccessLevelOptions; + }, - repositoryEnabled() { - return this.repositoryAccessLevel > 0; - }, + repositoryEnabled() { + return this.repositoryAccessLevel > 0; + }, - visibilityLevelDescription() { - return visibilityLevelDescriptions[this.visibilityLevel]; - }, + visibilityLevelDescription() { + return visibilityLevelDescriptions[this.visibilityLevel]; }, + }, - watch: { - visibilityLevel(value, oldValue) { - if (value === visibilityOptions.PRIVATE) { - // when private, features are restricted to "only team members" - this.issuesAccessLevel = Math.min(10, this.issuesAccessLevel); - this.repositoryAccessLevel = Math.min(10, this.repositoryAccessLevel); - this.mergeRequestsAccessLevel = Math.min(10, this.mergeRequestsAccessLevel); - this.buildsAccessLevel = Math.min(10, this.buildsAccessLevel); - this.wikiAccessLevel = Math.min(10, this.wikiAccessLevel); - this.snippetsAccessLevel = Math.min(10, this.snippetsAccessLevel); - if (this.pagesAccessLevel === 20) { - // When from Internal->Private narrow access for only members - this.pagesAccessLevel = 10; - } - this.highlightChanges(); - } else if (oldValue === visibilityOptions.PRIVATE) { - // if changing away from private, make enabled features more permissive - if (this.issuesAccessLevel > 0) this.issuesAccessLevel = 20; - if (this.repositoryAccessLevel > 0) this.repositoryAccessLevel = 20; - if (this.mergeRequestsAccessLevel > 0) this.mergeRequestsAccessLevel = 20; - if (this.buildsAccessLevel > 0) this.buildsAccessLevel = 20; - if (this.wikiAccessLevel > 0) this.wikiAccessLevel = 20; - if (this.snippetsAccessLevel > 0) this.snippetsAccessLevel = 20; - if (this.pagesAccessLevel === 10) this.pagesAccessLevel = 20; - this.highlightChanges(); + watch: { + visibilityLevel(value, oldValue) { + if (value === visibilityOptions.PRIVATE) { + // when private, features are restricted to "only team members" + this.issuesAccessLevel = Math.min(10, this.issuesAccessLevel); + this.repositoryAccessLevel = Math.min(10, this.repositoryAccessLevel); + this.mergeRequestsAccessLevel = Math.min(10, this.mergeRequestsAccessLevel); + this.buildsAccessLevel = Math.min(10, this.buildsAccessLevel); + this.wikiAccessLevel = Math.min(10, this.wikiAccessLevel); + this.snippetsAccessLevel = Math.min(10, this.snippetsAccessLevel); + if (this.pagesAccessLevel === 20) { + // When from Internal->Private narrow access for only members + this.pagesAccessLevel = 10; } - }, + this.highlightChanges(); + } else if (oldValue === visibilityOptions.PRIVATE) { + // if changing away from private, make enabled features more permissive + if (this.issuesAccessLevel > 0) this.issuesAccessLevel = 20; + if (this.repositoryAccessLevel > 0) this.repositoryAccessLevel = 20; + if (this.mergeRequestsAccessLevel > 0) this.mergeRequestsAccessLevel = 20; + if (this.buildsAccessLevel > 0) this.buildsAccessLevel = 20; + if (this.wikiAccessLevel > 0) this.wikiAccessLevel = 20; + if (this.snippetsAccessLevel > 0) this.snippetsAccessLevel = 20; + if (this.pagesAccessLevel === 10) this.pagesAccessLevel = 20; + this.highlightChanges(); + } + }, - repositoryAccessLevel(value, oldValue) { - if (value < oldValue) { - // sub-features cannot have more premissive access level - this.mergeRequestsAccessLevel = Math.min(this.mergeRequestsAccessLevel, value); - this.buildsAccessLevel = Math.min(this.buildsAccessLevel, value); + repositoryAccessLevel(value, oldValue) { + if (value < oldValue) { + // sub-features cannot have more premissive access level + this.mergeRequestsAccessLevel = Math.min(this.mergeRequestsAccessLevel, value); + this.buildsAccessLevel = Math.min(this.buildsAccessLevel, value); - if (value === 0) { - this.containerRegistryEnabled = false; - this.lfsEnabled = false; - } - } else if (oldValue === 0) { - this.mergeRequestsAccessLevel = value; - this.buildsAccessLevel = value; - this.containerRegistryEnabled = true; - this.lfsEnabled = true; + if (value === 0) { + this.containerRegistryEnabled = false; + this.lfsEnabled = false; } - }, + } else if (oldValue === 0) { + this.mergeRequestsAccessLevel = value; + this.buildsAccessLevel = value; + this.containerRegistryEnabled = true; + this.lfsEnabled = true; + } + }, - issuesAccessLevel(value, oldValue) { - if (value === 0) toggleHiddenClassBySelector('.issues-feature', true); - else if (oldValue === 0) toggleHiddenClassBySelector('.issues-feature', false); - }, + issuesAccessLevel(value, oldValue) { + if (value === 0) toggleHiddenClassBySelector('.issues-feature', true); + else if (oldValue === 0) toggleHiddenClassBySelector('.issues-feature', false); + }, - mergeRequestsAccessLevel(value, oldValue) { - if (value === 0) toggleHiddenClassBySelector('.merge-requests-feature', true); - else if (oldValue === 0) toggleHiddenClassBySelector('.merge-requests-feature', false); - }, + mergeRequestsAccessLevel(value, oldValue) { + if (value === 0) toggleHiddenClassBySelector('.merge-requests-feature', true); + else if (oldValue === 0) toggleHiddenClassBySelector('.merge-requests-feature', false); + }, - buildsAccessLevel(value, oldValue) { - if (value === 0) toggleHiddenClassBySelector('.builds-feature', true); - else if (oldValue === 0) toggleHiddenClassBySelector('.builds-feature', false); - }, + buildsAccessLevel(value, oldValue) { + if (value === 0) toggleHiddenClassBySelector('.builds-feature', true); + else if (oldValue === 0) toggleHiddenClassBySelector('.builds-feature', false); }, + }, - methods: { - highlightChanges() { - this.highlightChangesClass = true; - this.$nextTick(() => { - this.highlightChangesClass = false; - }); - }, + methods: { + highlightChanges() { + this.highlightChangesClass = true; + this.$nextTick(() => { + this.highlightChangesClass = false; + }); + }, - visibilityAllowed(option) { - return this.allowedVisibilityOptions.includes(option); - }, + visibilityAllowed(option) { + return this.allowedVisibilityOptions.includes(option); }, - }; + }, +}; </script> <template> diff --git a/app/assets/javascripts/pages/projects/wikis/components/delete_wiki_modal.vue b/app/assets/javascripts/pages/projects/wikis/components/delete_wiki_modal.vue index 75cb6374ad5..f970a5ebb64 100644 --- a/app/assets/javascripts/pages/projects/wikis/components/delete_wiki_modal.vue +++ b/app/assets/javascripts/pages/projects/wikis/components/delete_wiki_modal.vue @@ -1,8 +1,15 @@ <script> import _ from 'underscore'; import { s__, sprintf } from '~/locale'; +import { GlModal, GlModalDirective } from '@gitlab-org/gitlab-ui'; export default { + components: { + GlModal, + }, + directives: { + 'gl-modal': GlModalDirective, + }, props: { deleteWikiUrl: { type: String, @@ -54,7 +61,7 @@ export default { > {{ __('Delete') }} </button> - <gl-ui-modal + <gl-modal :title="title" :ok-title="s__('WikiPageConfirmDelete|Delete page')" :modal-id="modalId" @@ -81,6 +88,6 @@ export default { name="authenticity_token" /> </form> - </gl-ui-modal> + </gl-modal> </div> </template> diff --git a/app/assets/javascripts/projects/project_new.js b/app/assets/javascripts/projects/project_new.js index ebe18b47e4e..998554d1be5 100644 --- a/app/assets/javascripts/projects/project_new.js +++ b/app/assets/javascripts/projects/project_new.js @@ -4,8 +4,10 @@ import { slugifyWithHyphens } from '../lib/utils/text_utility'; let hasUserDefinedProjectPath = false; -const deriveProjectPathFromUrl = ($projectImportUrl) => { - const $currentProjectPath = $projectImportUrl.parents('.toggle-import-form').find('#project_path'); +const deriveProjectPathFromUrl = $projectImportUrl => { + const $currentProjectPath = $projectImportUrl + .parents('.toggle-import-form') + .find('#project_path'); if (hasUserDefinedProjectPath) { return; } @@ -52,9 +54,11 @@ const bindEvents = () => { return; } - $('.how_to_import_link').on('click', (e) => { + $('.how_to_import_link').on('click', e => { e.preventDefault(); - $(e.currentTarget).next('.modal').show(); + $(e.currentTarget) + .next('.modal') + .show(); }); $('.modal-header .close').on('click', () => { @@ -63,15 +67,21 @@ const bindEvents = () => { $('.btn_import_gitlab_project').on('click', () => { const importHref = $('a.btn_import_gitlab_project').attr('href'); - $('.btn_import_gitlab_project') - .attr('href', `${importHref}?namespace_id=${$('#project_namespace_id').val()}&name=${$projectName.val()}&path=${$projectPath.val()}`); + $('.btn_import_gitlab_project').attr( + 'href', + `${importHref}?namespace_id=${$( + '#project_namespace_id', + ).val()}&name=${$projectName.val()}&path=${$projectPath.val()}`, + ); }); if ($pushNewProjectTipTrigger) { $pushNewProjectTipTrigger .removeAttr('rel') .removeAttr('target') - .on('click', (e) => { e.preventDefault(); }) + .on('click', e => { + e.preventDefault(); + }) .popover({ title: $pushNewProjectTipTrigger.data('title'), placement: 'bottom', @@ -79,13 +89,15 @@ const bindEvents = () => { content: $('.push-new-project-tip-template').html(), }) .on('shown.bs.popover', () => { - $(document).on('click.popover touchstart.popover', (event) => { + $(document).on('click.popover touchstart.popover', event => { if ($(event.target).closest('.popover').length === 0) { $pushNewProjectTipTrigger.trigger('click'); } }); - const target = $(`#${$pushNewProjectTipTrigger.attr('aria-describedby')}`).find('.js-select-on-focus'); + const target = $(`#${$pushNewProjectTipTrigger.attr('aria-describedby')}`).find( + '.js-select-on-focus', + ); addSelectOnFocusBehaviour(target); target.focus(); @@ -117,16 +129,18 @@ const bindEvents = () => { const selectedTemplate = templates[value]; $selectedTemplateText.text(selectedTemplate.text); - $(selectedTemplate.icon).clone().addClass('d-block').appendTo($selectedIcon); + $(selectedTemplate.icon) + .clone() + .addClass('d-block') + .appendTo($selectedIcon); const $activeTabProjectName = $('.tab-pane.active #project_name'); const $activeTabProjectPath = $('.tab-pane.active #project_path'); $activeTabProjectName.focus(); - $activeTabProjectName - .keyup(() => { - onProjectNameChange($activeTabProjectName, $activeTabProjectPath); - hasUserDefinedProjectPath = $activeTabProjectPath.val().trim().length > 0; - }); + $activeTabProjectName.keyup(() => { + onProjectNameChange($activeTabProjectName, $activeTabProjectPath); + hasUserDefinedProjectPath = $activeTabProjectPath.val().trim().length > 0; + }); } $useTemplateBtn.on('change', chooseTemplate); diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js index 6b3753f7966..225e21ad322 100644 --- a/app/assets/javascripts/right_sidebar.js +++ b/app/assets/javascripts/right_sidebar.js @@ -21,7 +21,7 @@ Sidebar.initialize = function(currentUser) { } }; -Sidebar.prototype.removeListeners = function () { +Sidebar.prototype.removeListeners = function() { this.sidebar.off('click', '.sidebar-collapsed-icon'); this.sidebar.off('hidden.gl.dropdown'); $('.dropdown').off('loading.gl.dropdown'); @@ -38,10 +38,12 @@ Sidebar.prototype.addEventListeners = function() { $('.dropdown').on('loaded.gl.dropdown', this.sidebarDropdownLoaded); $document.on('click', '.js-sidebar-toggle', this.sidebarToggleClicked); - return $(document).off('click', '.js-issuable-todo').on('click', '.js-issuable-todo', this.toggleTodo); + return $(document) + .off('click', '.js-issuable-todo') + .on('click', '.js-issuable-todo', this.toggleTodo); }; -Sidebar.prototype.sidebarToggleClicked = function (e, triggered) { +Sidebar.prototype.sidebarToggleClicked = function(e, triggered) { var $allGutterToggleIcons, $this, isExpanded, tooltipLabel; e.preventDefault(); $this = $(this); @@ -51,18 +53,26 @@ Sidebar.prototype.sidebarToggleClicked = function (e, triggered) { if (isExpanded) { $allGutterToggleIcons.removeClass('fa-angle-double-right').addClass('fa-angle-double-left'); - $('aside.right-sidebar').removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed'); - $('.layout-page').removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed'); + $('aside.right-sidebar') + .removeClass('right-sidebar-expanded') + .addClass('right-sidebar-collapsed'); + $('.layout-page') + .removeClass('right-sidebar-expanded') + .addClass('right-sidebar-collapsed'); } else { $allGutterToggleIcons.removeClass('fa-angle-double-left').addClass('fa-angle-double-right'); - $('aside.right-sidebar').removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded'); - $('.layout-page').removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded'); + $('aside.right-sidebar') + .removeClass('right-sidebar-collapsed') + .addClass('right-sidebar-expanded'); + $('.layout-page') + .removeClass('right-sidebar-collapsed') + .addClass('right-sidebar-expanded'); } $this.attr('data-original-title', tooltipLabel); if (!triggered) { - Cookies.set("collapsed_gutter", $('.right-sidebar').hasClass('right-sidebar-collapsed')); + Cookies.set('collapsed_gutter', $('.right-sidebar').hasClass('right-sidebar-collapsed')); } }; @@ -71,21 +81,27 @@ Sidebar.prototype.toggleTodo = function(e) { $this = $(e.currentTarget); ajaxType = $this.attr('data-delete-path') ? 'delete' : 'post'; if ($this.attr('data-delete-path')) { - url = "" + ($this.attr('data-delete-path')); + url = '' + $this.attr('data-delete-path'); } else { - url = "" + ($this.data('url')); + url = '' + $this.data('url'); } $this.tooltip('hide'); - $('.js-issuable-todo').disable().addClass('is-loading'); + $('.js-issuable-todo') + .disable() + .addClass('is-loading'); axios[ajaxType](url, { issuable_id: $this.data('issuableId'), issuable_type: $this.data('issuableType'), - }).then(({ data }) => { - this.todoUpdateDone(data); - }).catch(() => flash(`There was an error ${ajaxType === 'post' ? 'adding a' : 'deleting the'} todo.`)); + }) + .then(({ data }) => { + this.todoUpdateDone(data); + }) + .catch(() => + flash(`There was an error ${ajaxType === 'post' ? 'adding a' : 'deleting the'} todo.`), + ); }; Sidebar.prototype.todoUpdateDone = function(data) { @@ -99,7 +115,8 @@ Sidebar.prototype.todoUpdateDone = function(data) { const $el = $(el); const $elText = $el.find('.js-issuable-todo-inner'); - $el.removeClass('is-loading') + $el + .removeClass('is-loading') .enable() .attr('aria-label', $el.data(`${attrPrefix}Text`)) .attr('data-delete-path', deletePath) @@ -119,7 +136,9 @@ Sidebar.prototype.todoUpdateDone = function(data) { Sidebar.prototype.sidebarDropdownLoading = function(e) { var $loading, $sidebarCollapsedIcon, i, img; - $sidebarCollapsedIcon = $(this).closest('.block').find('.sidebar-collapsed-icon'); + $sidebarCollapsedIcon = $(this) + .closest('.block') + .find('.sidebar-collapsed-icon'); img = $sidebarCollapsedIcon.find('img'); i = $sidebarCollapsedIcon.find('i'); $loading = $('<i class="fa fa-spinner fa-spin"></i>'); @@ -134,7 +153,9 @@ Sidebar.prototype.sidebarDropdownLoading = function(e) { Sidebar.prototype.sidebarDropdownLoaded = function(e) { var $sidebarCollapsedIcon, i, img; - $sidebarCollapsedIcon = $(this).closest('.block').find('.sidebar-collapsed-icon'); + $sidebarCollapsedIcon = $(this) + .closest('.block') + .find('.sidebar-collapsed-icon'); img = $sidebarCollapsedIcon.find('img'); $sidebarCollapsedIcon.find('i.fa-spin').remove(); i = $sidebarCollapsedIcon.find('i'); @@ -220,7 +241,7 @@ Sidebar.prototype.isOpen = function() { }; Sidebar.prototype.getBlock = function(name) { - return this.sidebar.find(".block." + name); + return this.sidebar.find('.block.' + name); }; export default Sidebar; diff --git a/app/assets/javascripts/search_autocomplete.js b/app/assets/javascripts/search_autocomplete.js index ad4f5320ff8..17def77b2d7 100644 --- a/app/assets/javascripts/search_autocomplete.js +++ b/app/assets/javascripts/search_autocomplete.js @@ -234,7 +234,9 @@ export class SearchAutocomplete { icon, text: term, template, - url: `${gon.relative_url_root}/search?search=${term}&project_id=${this.projectInputEl.val()}&group_id=${this.groupInputEl.val()}`, + url: `${ + gon.relative_url_root + }/search?search=${term}&project_id=${this.projectInputEl.val()}&group_id=${this.groupInputEl.val()}`, }); } } diff --git a/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue index 43f0b6651b9..8950ae31627 100644 --- a/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue +++ b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue @@ -5,6 +5,7 @@ import Icon from '~/vue_shared/components/icon.vue'; import GfmAutoComplete from '~/gfm_auto_complete'; import { __, s__ } from '~/locale'; import Api from '~/api'; +import { GlModal } from '@gitlab-org/gitlab-ui'; import eventHub from './event_hub'; import EmojiMenuInModal from './emoji_menu_in_modal'; @@ -13,6 +14,7 @@ const emojiMenuClass = 'js-modal-status-emoji-menu'; export default { components: { Icon, + GlModal, }, props: { currentEmoji: { @@ -152,7 +154,7 @@ export default { </script> <template> - <gl-ui-modal + <gl-modal :title="s__('SetStatusModal|Set a status')" :modal-id="modalId" :ok-title="s__('SetStatusModal|Set status')" @@ -237,5 +239,5 @@ export default { </div> </div> </div> - </gl-ui-modal> + </gl-modal> </template> diff --git a/app/assets/javascripts/sidebar/components/assignees/assignees.vue b/app/assets/javascripts/sidebar/components/assignees/assignees.vue index dd155c133ce..f1ea6aacdb2 100644 --- a/app/assets/javascripts/sidebar/components/assignees/assignees.vue +++ b/app/assets/javascripts/sidebar/components/assignees/assignees.vue @@ -74,8 +74,8 @@ export default { } if (!this.users.length) { - const emptyTooltipLabel = this.issuableType === 'issue' ? - __('Assignee(s)') : __('Assignee'); + const emptyTooltipLabel = + this.issuableType === 'issue' ? __('Assignee(s)') : __('Assignee'); names.push(emptyTooltipLabel); } @@ -248,4 +248,3 @@ export default { </div> </div> </template> - diff --git a/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue b/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue index 448c8fc3602..b6151aa6c64 100644 --- a/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue +++ b/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue @@ -1,74 +1,74 @@ <script> - import { __ } from '~/locale'; - import icon from '~/vue_shared/components/icon.vue'; - import toggleButton from '~/vue_shared/components/toggle_button.vue'; - import tooltip from '~/vue_shared/directives/tooltip'; - import eventHub from '../../event_hub'; +import { __ } from '~/locale'; +import icon from '~/vue_shared/components/icon.vue'; +import toggleButton from '~/vue_shared/components/toggle_button.vue'; +import tooltip from '~/vue_shared/directives/tooltip'; +import eventHub from '../../event_hub'; - const ICON_ON = 'notifications'; - const ICON_OFF = 'notifications-off'; - const LABEL_ON = __('Notifications on'); - const LABEL_OFF = __('Notifications off'); +const ICON_ON = 'notifications'; +const ICON_OFF = 'notifications-off'; +const LABEL_ON = __('Notifications on'); +const LABEL_OFF = __('Notifications off'); - export default { - directives: { - tooltip, +export default { + directives: { + tooltip, + }, + components: { + icon, + toggleButton, + }, + props: { + loading: { + type: Boolean, + required: false, + default: false, }, - components: { - icon, - toggleButton, + subscribed: { + type: Boolean, + required: false, + default: null, }, - props: { - loading: { - type: Boolean, - required: false, - default: false, - }, - subscribed: { - type: Boolean, - required: false, - default: null, - }, - id: { - type: Number, - required: false, - default: null, - }, + id: { + type: Number, + required: false, + default: null, }, - computed: { - showLoadingState() { - return this.subscribed === null; - }, - notificationIcon() { - return this.subscribed ? ICON_ON : ICON_OFF; - }, - notificationTooltip() { - return this.subscribed ? LABEL_ON : LABEL_OFF; - }, + }, + computed: { + showLoadingState() { + return this.subscribed === null; }, - methods: { - /** - * We need to emit this event on both component & eventHub - * for 2 dependencies; - * - * 1. eventHub: This component is used in Issue Boards sidebar - * where component template is part of HAML - * and event listeners are tied to app's eventHub. - * 2. Component: This compone is also used in Epics in EE - * where listeners are tied to component event. - */ - toggleSubscription() { - // App's eventHub event emission. - eventHub.$emit('toggleSubscription', this.id); + notificationIcon() { + return this.subscribed ? ICON_ON : ICON_OFF; + }, + notificationTooltip() { + return this.subscribed ? LABEL_ON : LABEL_OFF; + }, + }, + methods: { + /** + * We need to emit this event on both component & eventHub + * for 2 dependencies; + * + * 1. eventHub: This component is used in Issue Boards sidebar + * where component template is part of HAML + * and event listeners are tied to app's eventHub. + * 2. Component: This compone is also used in Epics in EE + * where listeners are tied to component event. + */ + toggleSubscription() { + // App's eventHub event emission. + eventHub.$emit('toggleSubscription', this.id); - // Component event emission. - this.$emit('toggleSubscription', this.id); - }, - onClickCollapsedIcon() { - this.$emit('toggleSidebar'); - }, + // Component event emission. + this.$emit('toggleSubscription', this.id); + }, + onClickCollapsedIcon() { + this.$emit('toggleSidebar'); }, - }; + }, +}; </script> <template> diff --git a/app/assets/javascripts/sidebar/sidebar_mediator.js b/app/assets/javascripts/sidebar/sidebar_mediator.js index d9ca5e46770..3e040ec8428 100644 --- a/app/assets/javascripts/sidebar/sidebar_mediator.js +++ b/app/assets/javascripts/sidebar/sidebar_mediator.js @@ -39,9 +39,10 @@ export default class SidebarMediator { } fetch() { - return this.service.get() + return this.service + .get() .then(response => response.json()) - .then((data) => { + .then(data => { this.processFetchedData(data); }) .catch(() => new Flash('Error occurred when fetching sidebar data')); @@ -56,30 +57,33 @@ export default class SidebarMediator { toggleSubscription() { this.store.setFetchingState('subscriptions', true); - return this.service.toggleSubscription() + return this.service + .toggleSubscription() .then(() => { this.store.setSubscribedState(!this.store.subscribed); this.store.setFetchingState('subscriptions', false); }) - .catch((err) => { + .catch(err => { this.store.setFetchingState('subscriptions', false); throw err; }); } fetchAutocompleteProjects(searchTerm) { - return this.service.getProjectsAutocomplete(searchTerm) + return this.service + .getProjectsAutocomplete(searchTerm) .then(response => response.json()) - .then((data) => { + .then(data => { this.store.setAutocompleteProjects(data); return this.store.autocompleteProjects; }); } moveIssue() { - return this.service.moveIssue(this.store.moveToProjectId) + return this.service + .moveIssue(this.store.moveToProjectId) .then(response => response.json()) - .then((data) => { + .then(data => { if (window.location.pathname !== data.web_url) { visitUrl(data.web_url); } diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue index fee41b239e8..8bcabc10225 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue @@ -1,5 +1,6 @@ <script> /* eslint-disable vue/require-default-prop */ +import { sprintf, __ } from '~/locale'; import PipelineStage from '~/pipelines/components/stage.vue'; import CiIcon from '~/vue_shared/components/ci_icon.vue'; import Icon from '~/vue_shared/components/icon.vue'; @@ -36,6 +37,10 @@ export default { type: String, required: false, }, + troubleshootingDocsPath: { + type: String, + required: true, + }, }, computed: { hasPipeline() { @@ -57,6 +62,17 @@ export default { hasCommitInfo() { return this.pipeline.commit && Object.keys(this.pipeline.commit).length > 0; }, + errorText() { + return sprintf( + __( + 'Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}', + ), + { + linkStart: `<a href="${this.troubleshootingDocsPath}">`, + linkEnd: '</a>', + }, + ); + }, }, }; </script> @@ -77,8 +93,10 @@ export default { name="status_failed_borderless" /> </div> - <div class="media-body"> - Could not connect to the CI server. Please check your settings and try again + <div + class="media-body" + v-html="errorText" + > </div> </template> <template v-else-if="hasPipeline"> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue index c8ad2aa30a6..e7baecbcde4 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue @@ -71,7 +71,12 @@ export default { return defaultClass; }, iconClass() { - if (this.status === 'failed' || !this.commitMessage.length || !this.mr.isMergeAllowed || this.mr.preventMerge) { + if ( + this.status === 'failed' || + !this.commitMessage.length || + !this.mr.isMergeAllowed || + this.mr.preventMerge + ) { return 'warning'; } return 'success'; @@ -90,10 +95,12 @@ export default { }, isMergeButtonDisabled() { const { commitMessage } = this; - return Boolean(!commitMessage.length - || !this.shouldShowMergeControls() - || this.isMakingRequest - || this.mr.preventMerge); + return Boolean( + !commitMessage.length || + !this.shouldShowMergeControls() || + this.isMakingRequest || + this.mr.preventMerge, + ); }, isRemoveSourceBranchButtonDisabled() { return this.isMergeButtonDisabled; @@ -140,9 +147,10 @@ export default { }; this.isMakingRequest = true; - this.service.merge(options) + this.service + .merge(options) .then(res => res.data) - .then((data) => { + .then(data => { const hasError = data.status === 'failed' || data.status === 'hook_validation_error'; if (data.status === 'merge_when_pipeline_succeeds') { @@ -167,9 +175,10 @@ export default { }); }, handleMergePolling(continuePolling, stopPolling) { - this.service.poll() + this.service + .poll() .then(res => res.data) - .then((data) => { + .then(data => { if (data.state === 'merged') { // If state is merged we should update the widget and stop the polling eventHub.$emit('MRWidgetUpdateRequested'); @@ -205,9 +214,10 @@ export default { }); }, handleRemoveBranchPolling(continuePolling, stopPolling) { - this.service.poll() + this.service + .poll() .then(res => res.data) - .then((data) => { + .then(data => { // If source branch exists then we should continue polling // because removing a source branch is a background task and takes time if (data.source_branch_exists) { diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue index a5c69d2bc7a..063d1e15544 100644 --- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue +++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue @@ -116,7 +116,7 @@ export default { // init polling this.initPostMergeDeploymentsPolling(); } - } + }, }, created() { this.initPolling(); @@ -213,17 +213,21 @@ export default { }) .catch(() => this.throwDeploymentsError()); }, - fetchPostMergeDeployments(){ + fetchPostMergeDeployments() { return this.fetchDeployments('merge_commit') .then(({ data }) => { if (data.length) { this.mr.postMergeDeployments = data; } }) - .catch(() => this.throwDeploymentsError()); + .catch(() => this.throwDeploymentsError()); }, throwDeploymentsError() { - createFlash(__('Something went wrong while fetching the environments for this merge request. Please try again.')); + createFlash( + __( + 'Something went wrong while fetching the environments for this merge request. Please try again.', + ), + ); }, fetchActionsContent() { this.service @@ -301,6 +305,7 @@ export default { :has-ci="mr.hasCI" :source-branch="mr.sourceBranch" :source-branch-link="mr.sourceBranchLink" + :troubleshooting-docs-path="mr.troubleshootingDocsPath" /> <deployment v-for="deployment in mr.deployments" @@ -355,6 +360,7 @@ export default { :has-ci="mr.hasCI" :source-branch="mr.targetBranch" :source-branch-link="mr.targetBranch" + :troubleshooting-docs-path="mr.troubleshootingDocsPath" /> <deployment v-for="postMergeDeployment in mr.postMergeDeployments" diff --git a/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js b/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js index bf5b85b2ae6..0bb70bfd658 100644 --- a/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js +++ b/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js @@ -24,8 +24,8 @@ export default class MRWidgetService { fetchDeployments(targetParam) { return axios.get(this.endpoints.ciEnvironmentsStatusPath, { params: { - environment_target: targetParam - } + environment_target: targetParam, + }, }); } diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js index a0c008e7314..5c9a7133a6e 100644 --- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js +++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js @@ -18,6 +18,7 @@ export default class MergeRequestStore { this.squash = data.squash; this.squashBeforeMergeHelpPath = this.squashBeforeMergeHelpPath || data.squash_before_merge_help_path; + this.troubleshootingDocsPath = this.troubleshootingDocsPath || data.troubleshooting_docs_path; this.enableSquashBeforeMerge = this.enableSquashBeforeMerge || true; this.iid = data.iid; diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_dropdown.vue b/app/assets/javascripts/vue_shared/components/filtered_search_dropdown.vue index 460fa6ad72e..388a2f4ca36 100644 --- a/app/assets/javascripts/vue_shared/components/filtered_search_dropdown.vue +++ b/app/assets/javascripts/vue_shared/components/filtered_search_dropdown.vue @@ -56,12 +56,14 @@ export default { filteredResults() { if (this.filter !== '') { return this.items.filter( - item => item[this.filterKey] && item[this.filterKey].toLowerCase().includes(this.filter.toLowerCase()), + item => + item[this.filterKey] && + item[this.filterKey].toLowerCase().includes(this.filter.toLowerCase()), ); } return this.items.slice(0, this.visibleItems); - } + }, }, mounted() { /** diff --git a/app/assets/javascripts/vue_shared/components/icon.vue b/app/assets/javascripts/vue_shared/components/icon.vue index 26f9d5ddc91..cddebfae115 100644 --- a/app/assets/javascripts/vue_shared/components/icon.vue +++ b/app/assets/javascripts/vue_shared/components/icon.vue @@ -8,7 +8,7 @@ let iconValidator = () => true; */ if (process.env.NODE_ENV !== 'production') { // eslint-disable-next-line global-require - const data = require('@gitlab-org/gitlab-svgs/dist/icons.json'); + const data = require('@gitlab/svgs/dist/icons.json'); const { icons } = data; iconValidator = value => { if (icons.includes(value)) { diff --git a/app/assets/javascripts/vue_shared/components/pagination_links.vue b/app/assets/javascripts/vue_shared/components/pagination_links.vue index 1f2a679c145..89dcf049f6e 100644 --- a/app/assets/javascripts/vue_shared/components/pagination_links.vue +++ b/app/assets/javascripts/vue_shared/components/pagination_links.vue @@ -1,7 +1,11 @@ <script> +import { GlPagination } from '@gitlab-org/gitlab-ui'; import { s__ } from '../../locale'; export default { + components: { + GlPagination, + }, props: { change: { type: Function, diff --git a/app/assets/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon.vue b/app/assets/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon.vue index 11fac3bb12c..5841db52704 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon.vue @@ -1,39 +1,39 @@ <script> - import tooltip from '~/vue_shared/directives/tooltip'; +import tooltip from '~/vue_shared/directives/tooltip'; - export default { - name: 'CollapsedCalendarIcon', - directives: { - tooltip, +export default { + name: 'CollapsedCalendarIcon', + directives: { + tooltip, + }, + props: { + containerClass: { + type: String, + required: false, + default: '', }, - props: { - containerClass: { - type: String, - required: false, - default: '', - }, - text: { - type: String, - required: false, - default: '', - }, - showIcon: { - type: Boolean, - required: false, - default: true, - }, - tooltipText: { - type: String, - required: false, - default: '', - }, + text: { + type: String, + required: false, + default: '', }, - methods: { - click() { - this.$emit('click'); - }, + showIcon: { + type: Boolean, + required: false, + default: true, }, - }; + tooltipText: { + type: String, + required: false, + default: '', + }, + }, + methods: { + click() { + this.$emit('click'); + }, + }, +}; </script> <template> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue b/app/assets/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue index 6e7194ccc9e..174c29809ac 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue @@ -1,84 +1,79 @@ <script> - import { __ } from '~/locale'; - import timeagoMixin from '~/vue_shared/mixins/timeago'; - import { dateInWords, timeFor } from '~/lib/utils/datetime_utility'; - import collapsedCalendarIcon from './collapsed_calendar_icon.vue'; +import { __ } from '~/locale'; +import timeagoMixin from '~/vue_shared/mixins/timeago'; +import { dateInWords, timeFor } from '~/lib/utils/datetime_utility'; +import collapsedCalendarIcon from './collapsed_calendar_icon.vue'; - export default { - name: 'SidebarCollapsedGroupedDatePicker', - components: { - collapsedCalendarIcon, +export default { + name: 'SidebarCollapsedGroupedDatePicker', + components: { + collapsedCalendarIcon, + }, + mixins: [timeagoMixin], + props: { + collapsed: { + type: Boolean, + required: false, + default: true, }, - mixins: [ - timeagoMixin, - ], - props: { - collapsed: { - type: Boolean, - required: false, - default: true, - }, - minDate: { - type: Date, - required: false, - default: null, - }, - maxDate: { - type: Date, - required: false, - default: null, - }, - disableClickableIcons: { - type: Boolean, - required: false, - default: false, - }, + minDate: { + type: Date, + required: false, + default: null, }, - computed: { - hasMinAndMaxDates() { - return this.minDate && this.maxDate; - }, - hasNoMinAndMaxDates() { - return !this.minDate && !this.maxDate; - }, - showMinDateBlock() { - return this.minDate || this.hasNoMinAndMaxDates; - }, - showFromText() { - return !this.maxDate && this.minDate; - }, - iconClass() { - const disabledClass = this.disableClickableIcons ? 'disabled' : ''; - return `sidebar-collapsed-icon calendar-icon ${disabledClass}`; - }, + maxDate: { + type: Date, + required: false, + default: null, }, - methods: { - toggleSidebar() { - this.$emit('toggleCollapse'); - }, - dateText(dateType = 'min') { - const date = this[`${dateType}Date`]; - const dateWords = dateInWords(date, true); - const parsedDateWords = dateWords ? dateWords.replace(',', '') : dateWords; + disableClickableIcons: { + type: Boolean, + required: false, + default: false, + }, + }, + computed: { + hasMinAndMaxDates() { + return this.minDate && this.maxDate; + }, + hasNoMinAndMaxDates() { + return !this.minDate && !this.maxDate; + }, + showMinDateBlock() { + return this.minDate || this.hasNoMinAndMaxDates; + }, + showFromText() { + return !this.maxDate && this.minDate; + }, + iconClass() { + const disabledClass = this.disableClickableIcons ? 'disabled' : ''; + return `sidebar-collapsed-icon calendar-icon ${disabledClass}`; + }, + }, + methods: { + toggleSidebar() { + this.$emit('toggleCollapse'); + }, + dateText(dateType = 'min') { + const date = this[`${dateType}Date`]; + const dateWords = dateInWords(date, true); + const parsedDateWords = dateWords ? dateWords.replace(',', '') : dateWords; - return date ? parsedDateWords : __('None'); - }, - tooltipText(dateType = 'min') { - const defaultText = dateType === 'min' ? __('Start date') : __('Due date'); - const date = this[`${dateType}Date`]; - const timeAgo = dateType === 'min' ? this.timeFormated(date) : timeFor(date); - const dateText = date ? [ - this.dateText(dateType), - `(${timeAgo})`, - ].join(' ') : ''; + return date ? parsedDateWords : __('None'); + }, + tooltipText(dateType = 'min') { + const defaultText = dateType === 'min' ? __('Start date') : __('Due date'); + const date = this[`${dateType}Date`]; + const timeAgo = dateType === 'min' ? this.timeFormated(date) : timeFor(date); + const dateText = date ? [this.dateText(dateType), `(${timeAgo})`].join(' ') : ''; - if (date) { - return [defaultText, dateText].join('<br />'); - } - return __('Start and due date'); - }, + if (date) { + return [defaultText, dateText].join('<br />'); + } + return __('Start and due date'); }, - }; + }, +}; </script> <template> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue index af297f3c408..0d5fc07e6e3 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue @@ -14,7 +14,10 @@ export default { }, computed: { labelsList() { - const labelsString = this.labels.slice(0, 5).map(label => label.title).join(', '); + const labelsString = this.labels + .slice(0, 5) + .map(label => label.title) + .join(', '); if (this.labels.length > 5) { return sprintf(s__('LabelSelect|%{labelsString}, and %{remainingLabelCount} more'), { diff --git a/app/assets/stylesheets/framework/secondary_navigation_elements.scss b/app/assets/stylesheets/framework/secondary_navigation_elements.scss index f47dfe1b563..de9e7c37695 100644 --- a/app/assets/stylesheets/framework/secondary_navigation_elements.scss +++ b/app/assets/stylesheets/framework/secondary_navigation_elements.scss @@ -1,6 +1,6 @@ // For tabbed navigation links, scrolling tabs, etc. For all top/main navigation, // please check nav.scss -.nav-links { +.nav-links:not(.quick-links) { display: flex; padding: 0; margin: 0; @@ -106,7 +106,7 @@ display: inline-block; float: right; text-align: right; - padding: 11px 0; + padding: $gl-padding-8 0; margin-bottom: 0; > .btn, diff --git a/app/finders/autocomplete/users_finder.rb b/app/finders/autocomplete/users_finder.rb index e2283f3266e..45955783be9 100644 --- a/app/finders/autocomplete/users_finder.rb +++ b/app/finders/autocomplete/users_finder.rb @@ -72,7 +72,6 @@ module Autocomplete author_id.present? && current_user end - # rubocop: disable CodeReuse/ActiveRecord def find_users if project project.authorized_users.union_with_user(author_id) @@ -84,6 +83,5 @@ module Autocomplete User.none end end - # rubocop: enable CodeReuse/ActiveRecord end end diff --git a/app/finders/concerns/finder_with_cross_project_access.rb b/app/finders/concerns/finder_with_cross_project_access.rb index e038636f0c4..220f62bcc7f 100644 --- a/app/finders/concerns/finder_with_cross_project_access.rb +++ b/app/finders/concerns/finder_with_cross_project_access.rb @@ -16,7 +16,6 @@ module FinderWithCrossProjectAccess end override :execute - # rubocop: disable CodeReuse/ActiveRecord def execute(*args) check = Gitlab::CrossProjectAccess.find_check(self) original = super @@ -30,7 +29,6 @@ module FinderWithCrossProjectAccess original end end - # rubocop: enable CodeReuse/ActiveRecord # We can skip the cross project check for finding indivitual records. # this would be handled by the `can?(:read_*, result)` call in `FinderMethods` diff --git a/app/finders/group_descendants_finder.rb b/app/finders/group_descendants_finder.rb index 9d57d2d3bc9..c96979619fd 100644 --- a/app/finders/group_descendants_finder.rb +++ b/app/finders/group_descendants_finder.rb @@ -131,7 +131,6 @@ class GroupDescendantsFinder .with_selects_for_list(archived: params[:archived]) end - # rubocop: disable CodeReuse/ActiveRecord def subgroups return Group.none unless Group.supports_nested_groups? @@ -145,7 +144,6 @@ class GroupDescendantsFinder groups.with_selects_for_list(archived: params[:archived]).order_by(sort) end - # rubocop: enable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/Finder def direct_child_projects diff --git a/app/finders/groups_finder.rb b/app/finders/groups_finder.rb index a35a3ed6142..ea954f98220 100644 --- a/app/finders/groups_finder.rb +++ b/app/finders/groups_finder.rb @@ -40,7 +40,6 @@ class GroupsFinder < UnionFinder attr_reader :current_user, :params - # rubocop: disable CodeReuse/ActiveRecord def all_groups return [owned_groups] if params[:owned] return [groups_with_min_access_level] if min_access_level? @@ -52,7 +51,6 @@ class GroupsFinder < UnionFinder groups << Group.none if groups.empty? groups end - # rubocop: enable CodeReuse/ActiveRecord def groups_for_ancestors current_user.authorized_groups @@ -82,11 +80,9 @@ class GroupsFinder < UnionFinder end # rubocop: enable CodeReuse/ActiveRecord - # rubocop: disable CodeReuse/ActiveRecord def owned_groups current_user&.owned_groups || Group.none end - # rubocop: enable CodeReuse/ActiveRecord def include_public_groups? current_user.nil? || all_available? diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index eb3d2498830..92aaa9c6b29 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -196,7 +196,6 @@ class IssuableFinder milestones? && params[:milestone_title] == Milestone::None.title end - # rubocop: disable CodeReuse/ActiveRecord def milestones return @milestones if defined?(@milestones) @@ -217,7 +216,6 @@ class IssuableFinder Milestone.none end end - # rubocop: enable CodeReuse/ActiveRecord def labels? params[:label_name].present? @@ -227,7 +225,6 @@ class IssuableFinder labels? && params[:label_name].include?(Label::None.title) end - # rubocop: disable CodeReuse/ActiveRecord def labels return @labels if defined?(@labels) @@ -238,7 +235,6 @@ class IssuableFinder Label.none end end - # rubocop: enable CodeReuse/ActiveRecord def assignee_id? params[:assignee_id].present? diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb index cee57a83df4..45e494725d7 100644 --- a/app/finders/issues_finder.rb +++ b/app/finders/issues_finder.rb @@ -135,7 +135,6 @@ class IssuesFinder < IssuableFinder current_user.blank? end - # rubocop: disable CodeReuse/ActiveRecord def by_assignee(items) if filter_by_no_assignee? items.unassigned @@ -149,5 +148,4 @@ class IssuesFinder < IssuableFinder items end end - # rubocop: enable CodeReuse/ActiveRecord end diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb index d000af21be3..e523942ea4c 100644 --- a/app/finders/labels_finder.rb +++ b/app/finders/labels_finder.rb @@ -12,7 +12,6 @@ class LabelsFinder < UnionFinder @params = params end - # rubocop: disable CodeReuse/ActiveRecord def execute(skip_authorization: false) @skip_authorization = skip_authorization items = find_union(label_ids, Label) || Label.none @@ -21,7 +20,6 @@ class LabelsFinder < UnionFinder items = by_search(items) sort(items) end - # rubocop: enable CodeReuse/ActiveRecord private diff --git a/app/finders/milestones_finder.rb b/app/finders/milestones_finder.rb index 47231ea80c7..9c477978f60 100644 --- a/app/finders/milestones_finder.rb +++ b/app/finders/milestones_finder.rb @@ -20,7 +20,6 @@ class MilestonesFinder @params = params end - # rubocop: disable CodeReuse/ActiveRecord def execute return Milestone.none if project_ids.empty? && group_ids.empty? @@ -31,7 +30,6 @@ class MilestonesFinder order(items) end - # rubocop: enable CodeReuse/ActiveRecord private diff --git a/app/finders/pipelines_finder.rb b/app/finders/pipelines_finder.rb index 3d0d3219a94..35d0e1acce5 100644 --- a/app/finders/pipelines_finder.rb +++ b/app/finders/pipelines_finder.rb @@ -12,7 +12,6 @@ class PipelinesFinder @params = params end - # rubocop: disable CodeReuse/ActiveRecord def execute unless Ability.allowed?(current_user, :read_pipeline, project) return Ci::Pipeline.none @@ -28,7 +27,6 @@ class PipelinesFinder items = by_yaml_errors(items) sort_items(items) end - # rubocop: enable CodeReuse/ActiveRecord private diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb index 6ececcd4152..93d3c991846 100644 --- a/app/finders/projects_finder.rb +++ b/app/finders/projects_finder.rb @@ -88,7 +88,6 @@ class ProjectsFinder < UnionFinder # rubocop: enable CodeReuse/ActiveRecord # Builds a collection for an anonymous user. - # rubocop: disable CodeReuse/ActiveRecord def collection_without_user if private_only? || owned_projects? || min_access_level? Project.none @@ -96,7 +95,6 @@ class ProjectsFinder < UnionFinder Project.public_to_user end end - # rubocop: enable CodeReuse/ActiveRecord def owned_projects? params[:owned].present? diff --git a/app/finders/snippets_finder.rb b/app/finders/snippets_finder.rb index 3528e4228b2..f90971bb9f6 100644 --- a/app/finders/snippets_finder.rb +++ b/app/finders/snippets_finder.rb @@ -43,7 +43,6 @@ class SnippetsFinder < UnionFinder end end - # rubocop: disable CodeReuse/ActiveRecord def authorized_snippets_from_project if can?(current_user, :read_project_snippet, project) if project.team.member?(current_user) @@ -55,7 +54,6 @@ class SnippetsFinder < UnionFinder Snippet.none end end - # rubocop: enable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord def authorized_snippets diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb index 037004327b9..910c9e9446f 100644 --- a/app/helpers/icons_helper.rb +++ b/app/helpers/icons_helper.rb @@ -153,6 +153,6 @@ module IconsHelper private def known_sprites - @known_sprites ||= JSON.parse(File.read(Rails.root.join('node_modules/@gitlab-org/gitlab-svgs/dist/icons.json')))['icons'] + @known_sprites ||= JSON.parse(File.read(Rails.root.join('node_modules/@gitlab/svgs/dist/icons.json')))['icons'] end end diff --git a/app/models/concerns/noteable.rb b/app/models/concerns/noteable.rb index 098eed137ba..eb315058c3a 100644 --- a/app/models/concerns/noteable.rb +++ b/app/models/concerns/noteable.rb @@ -23,7 +23,7 @@ module Noteable end def supports_discussions? - DiscussionNote::NOTEABLE_TYPES.include?(base_class_name) + DiscussionNote.noteable_types.include?(base_class_name) end def discussions_rendered_on_frontend? diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb index 95694377fe3..5f59e4832db 100644 --- a/app/models/diff_note.rb +++ b/app/models/diff_note.rb @@ -8,12 +8,14 @@ class DiffNote < Note include DiffPositionableNote include Gitlab::Utils::StrongMemoize - NOTEABLE_TYPES = %w(MergeRequest Commit).freeze + def self.noteable_types + %w(MergeRequest Commit) + end validates :original_position, presence: true validates :position, presence: true validates :line_code, presence: true, line_code: true, if: :on_text? - validates :noteable_type, inclusion: { in: NOTEABLE_TYPES } + validates :noteable_type, inclusion: { in: noteable_types } validate :positions_complete validate :verify_supported validate :diff_refs_match_commit, if: :for_commit? diff --git a/app/models/discussion_note.rb b/app/models/discussion_note.rb index 89d86aaed66..142cbdcdfa6 100644 --- a/app/models/discussion_note.rb +++ b/app/models/discussion_note.rb @@ -5,9 +5,11 @@ # A note of this type can be resolvable. class DiscussionNote < Note # Names of all implementers of `Noteable` that support discussions. - NOTEABLE_TYPES = %w(MergeRequest Issue Commit Snippet).freeze + def self.noteable_types + %w(MergeRequest Issue Commit Snippet) + end - validates :noteable_type, inclusion: { in: NOTEABLE_TYPES } + validates :noteable_type, inclusion: { in: noteable_types } def discussion_class(*) Discussion diff --git a/app/presenters/project_presenter.rb b/app/presenters/project_presenter.rb index 79cd3606aec..d61124fa787 100644 --- a/app/presenters/project_presenter.rb +++ b/app/presenters/project_presenter.rb @@ -176,7 +176,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated AnchorData.new(false, _('New file'), project_new_blob_path(project, default_branch || 'master'), - 'new') + 'success') end end diff --git a/app/serializers/job_entity.rb b/app/serializers/job_entity.rb index 0b19cb16955..a0a66511b7b 100644 --- a/app/serializers/job_entity.rb +++ b/app/serializers/job_entity.rb @@ -9,7 +9,7 @@ class JobEntity < Grape::Entity expose :started?, as: :started expose :build_path do |build| - build.target_url || path_to(:namespace_project_job, build) + build_path(build) end expose :retry_path, if: -> (*) { retryable? } do |build| @@ -17,7 +17,11 @@ class JobEntity < Grape::Entity end expose :cancel_path, if: -> (*) { cancelable? } do |build| - path_to(:cancel_namespace_project_job, build) + path_to( + :cancel_namespace_project_job, + build, + { continue: { to: build_path(build) } } + ) end expose :play_path, if: -> (*) { playable? } do |build| @@ -60,8 +64,12 @@ class JobEntity < Grape::Entity build.detailed_status(request.current_user) end - def path_to(route, build) - send("#{route}_path", build.project.namespace, build.project, build) # rubocop:disable GitlabSecurity/PublicSend + def path_to(route, build, params = {}) + send("#{route}_path", build.project.namespace, build.project, build, params) # rubocop:disable GitlabSecurity/PublicSend + end + + def build_path(build) + build.target_url || path_to(:namespace_project_job, build) end def failed? diff --git a/app/services/search/group_service.rb b/app/services/search/group_service.rb index 00372887985..34803d005e3 100644 --- a/app/services/search/group_service.rb +++ b/app/services/search/group_service.rb @@ -11,13 +11,11 @@ module Search @group = group end - # rubocop: disable CodeReuse/ActiveRecord def projects return Project.none unless group return @projects if defined? @projects @projects = super.inside_path(group.full_path) end - # rubocop: enable CodeReuse/ActiveRecord end end diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml index 75f35360e5e..936900a0087 100644 --- a/app/views/projects/empty.html.haml +++ b/app/views/projects/empty.html.haml @@ -36,7 +36,7 @@ .scrolling-tabs-container.inner-page-scroll-tabs.is-smaller .fade-left= icon('angle-left') .fade-right= icon('angle-right') - .nav-links.scrolling-tabs + .nav-links.scrolling-tabs.quick-links = render 'stat_anchor_list', anchors: @project.empty_repo_statistics_anchors = render 'stat_anchor_list', anchors: @project.empty_repo_statistics_buttons diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml index efc2d88172e..5d1bbb077af 100644 --- a/app/views/projects/merge_requests/show.html.haml +++ b/app/views/projects/merge_requests/show.html.haml @@ -21,6 +21,7 @@ window.gl.mrWidgetData = #{serialize_issuable(@merge_request, serializer: 'widget')} window.gl.mrWidgetData.squash_before_merge_help_path = '#{help_page_path("user/project/merge_requests/squash_and_merge")}'; + window.gl.mrWidgetData.troubleshooting_docs_path = '#{help_page_path('user/project/merge_requests', anchor: 'troubleshooting')}'; #js-vue-mr-widget.mr-widget diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 283031b06da..f29ce4f5c06 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -22,7 +22,7 @@ .scrolling-tabs-container.inner-page-scroll-tabs.is-smaller .fade-left= icon('angle-left') .fade-right= icon('angle-right') - .nav-links.scrolling-tabs + .nav-links.scrolling-tabs.quick-links = render 'stat_anchor_list', anchors: @project.statistics_anchors(show_auto_devops_callout: show_auto_devops_callout) = render 'stat_anchor_list', anchors: @project.statistics_buttons(show_auto_devops_callout: show_auto_devops_callout) diff --git a/changelogs/unreleased/41545-gitlab-merge-request-status-could-not-connect-to-the-ci-server-please-check-your-settings-and-try-again.yml b/changelogs/unreleased/41545-gitlab-merge-request-status-could-not-connect-to-the-ci-server-please-check-your-settings-and-try-again.yml new file mode 100644 index 00000000000..103419c1185 --- /dev/null +++ b/changelogs/unreleased/41545-gitlab-merge-request-status-could-not-connect-to-the-ci-server-please-check-your-settings-and-try-again.yml @@ -0,0 +1,5 @@ +--- +title: Reword error message for internal CI unknown pipeline status +merge_request: 22474 +author: +type: changed diff --git a/changelogs/unreleased/gl-ui-modal.yml b/changelogs/unreleased/gl-ui-modal.yml new file mode 100644 index 00000000000..fbdb8260d24 --- /dev/null +++ b/changelogs/unreleased/gl-ui-modal.yml @@ -0,0 +1,5 @@ +--- +title: Remove gitlab-ui's modal from global +merge_request: +author: +type: performance diff --git a/changelogs/unreleased/gl-ui-pagination.yml b/changelogs/unreleased/gl-ui-pagination.yml new file mode 100644 index 00000000000..cf73d6a1f8f --- /dev/null +++ b/changelogs/unreleased/gl-ui-pagination.yml @@ -0,0 +1,5 @@ +--- +title: Remove gitlab-ui's pagination from global +merge_request: +author: +type: performance diff --git a/changelogs/unreleased/gt-fix-ide-typos-in-props.yml b/changelogs/unreleased/gt-fix-ide-typos-in-props.yml new file mode 100644 index 00000000000..a81b227c82f --- /dev/null +++ b/changelogs/unreleased/gt-fix-ide-typos-in-props.yml @@ -0,0 +1,5 @@ +--- +title: Fix IDE typos in props +merge_request: 22685 +author: George Tsiolis +type: other diff --git a/changelogs/unreleased/gt-fix-quick-links-button-styles.yml b/changelogs/unreleased/gt-fix-quick-links-button-styles.yml new file mode 100644 index 00000000000..4c1150631f8 --- /dev/null +++ b/changelogs/unreleased/gt-fix-quick-links-button-styles.yml @@ -0,0 +1,5 @@ +--- +title: Fix quick links button styles +merge_request: 22657 +author: George Tsiolis +type: fixed diff --git a/config/application.rb b/config/application.rb index 9074cf02c46..95b0f74a5a3 100644 --- a/config/application.rb +++ b/config/application.rb @@ -144,7 +144,7 @@ module Gitlab config.assets.precompile << "errors.css" # Import gitlab-svgs directly from vendored directory - config.assets.paths << "#{config.root}/node_modules/@gitlab-org/gitlab-svgs/dist" + config.assets.paths << "#{config.root}/node_modules/@gitlab/svgs/dist" config.assets.precompile << "icons.svg" config.assets.precompile << "icons.json" config.assets.precompile << "illustrations/*.svg" diff --git a/config/dependency_decisions.yml b/config/dependency_decisions.yml index 62760ffee3a..488728e26ab 100644 --- a/config/dependency_decisions.yml +++ b/config/dependency_decisions.yml @@ -461,7 +461,7 @@ :versions: [] :when: 2017-09-13 17:31:16.425819400 Z - - :license - - "@gitlab-org/gitlab-svgs" + - "@gitlab/svgs" - MIT - :who: Tim Zallmann :why: Our own library - GitLab License https://gitlab.com/gitlab-org/gitlab-svgs diff --git a/doc/development/fe_guide/icons.md b/doc/development/fe_guide/icons.md index 3d8da6accc1..533e2001300 100644 --- a/doc/development/fe_guide/icons.md +++ b/doc/development/fe_guide/icons.md @@ -3,7 +3,7 @@ We manage our own Icon and Illustration library in the [gitlab-svgs][gitlab-svgs] repository. This repository is published on [npm][npm] and managed as a dependency via yarn. You can browse all available Icons and Illustrations [here][svg-preview]. -To upgrade to a new version run `yarn upgrade @gitlab-org/gitlab-svgs`. +To upgrade to a new version run `yarn upgrade @gitlab/svgs`. ## Icons @@ -111,6 +111,6 @@ export default { </template> ``` -[npm]: https://www.npmjs.com/package/@gitlab-org/gitlab-svgs +[npm]: https://www.npmjs.com/package/@gitlab/svgs [gitlab-svgs]: https://gitlab.com/gitlab-org/gitlab-svgs [svg-preview]: https://gitlab-org.gitlab.io/gitlab-svgs diff --git a/doc/development/rolling_out_changes_using_feature_flags.md b/doc/development/rolling_out_changes_using_feature_flags.md index dada59ce242..b65fbc9d958 100644 --- a/doc/development/rolling_out_changes_using_feature_flags.md +++ b/doc/development/rolling_out_changes_using_feature_flags.md @@ -152,14 +152,31 @@ in RC1, followed by the feature flag being removed in RC2. This in turn means the feature will be stable by the time we publish a stable package around the 22nd of the month. -## Undefined feature flags default to "on" +## Implicit feature flags -By default, the [`Project#feature_available?`][project-fa], +The [`Project#feature_available?`][project-fa], [`Namespace#feature_available?`][namespace-fa] (EE), and -[`License.feature_available?`][license-fa] (EE) methods will check if the -specified feature is behind a feature flag. Unless the feature is explicitly -disabled or limited to a percentage of users, the feature flag check will -default to `true`. +[`License.feature_available?`][license-fa] (EE) methods all implicitly check for +a feature flag by the same name as the provided argument. + +For example if a feature is license-gated, there's no need to add an additional +explicit feature flag check since the flag will be checked as part of the +`License.feature_available?` call. Similarly, there's no need to "clean up" a +feature flag once the feature has reached general availability. + +You'd still want to use an explicit `Feature.enabled?` check if your new feature +isn't gated by a License or Plan. + +[project-fa]: https://gitlab.com/gitlab-org/gitlab-ee/blob/4cc1c62918aa4c31750cb21dfb1a6c3492d71080/app/models/project_feature.rb#L63-68 +[namespace-fa]: https://gitlab.com/gitlab-org/gitlab-ee/blob/4cc1c62918aa4c31750cb21dfb1a6c3492d71080/ee/app/models/ee/namespace.rb#L71-85 +[license-fa]: https://gitlab.com/gitlab-org/gitlab-ee/blob/4cc1c62918aa4c31750cb21dfb1a6c3492d71080/ee/app/models/license.rb#L293-300 + +### Undefined feature flags default to "on" + +An important side-effect of the [implicit feature +flags][#implicit-feature-flags] mentioned above is that unless the feature is +explicitly disabled or limited to a percentage of users, the feature flag check +will default to `true`. As an example, if you were to ship the backend half of a feature behind a flag, you'd want to explicitly disable that flag until the frontend half is also ready @@ -171,7 +188,3 @@ to be shipped. You can do this via ChatOps: Note that you can do this at any time, even before the merge request using the flag has been merged! - -[project-fa]: https://gitlab.com/gitlab-org/gitlab-ee/blob/4cc1c62918aa4c31750cb21dfb1a6c3492d71080/app/models/project_feature.rb#L63-68 -[namespace-fa]: https://gitlab.com/gitlab-org/gitlab-ee/blob/4cc1c62918aa4c31750cb21dfb1a6c3492d71080/ee/app/models/ee/namespace.rb#L71-85 -[license-fa]: https://gitlab.com/gitlab-org/gitlab-ee/blob/4cc1c62918aa4c31750cb21dfb1a6c3492d71080/ee/app/models/license.rb#L293-300 diff --git a/doc/user/admin_area/settings/usage_statistics.md b/doc/user/admin_area/settings/usage_statistics.md index 35a9d7adb28..bd0155dc712 100644 --- a/doc/user/admin_area/settings/usage_statistics.md +++ b/doc/user/admin_area/settings/usage_statistics.md @@ -42,7 +42,11 @@ not send any project names, usernames, or any other specific data. The information from the usage ping is not anonymous, it is linked to the hostname of the instance. -You can view the exact JSON payload in the administration panel. +You can view the exact JSON payload in the administration panel. To view the payload: + +1. Go to the **Admin area** (spanner symbol on the top bar). +1. Expand **Settings** in the left sidebar and click on **Metrics and profiling**. +1. Expand **Usage statistics** and click on the **Preview payload** button. ### Deactivate the usage ping diff --git a/doc/user/markdown.md b/doc/user/markdown.md index f9bdaea185b..96a509c4b21 100644 --- a/doc/user/markdown.md +++ b/doc/user/markdown.md @@ -1,17 +1,8 @@ # Markdown -## GitLab Flavored Markdown (GFM) - -> **Note:** -> Not all of the GitLab-specific extensions to Markdown that are described in -> this document currently work on our documentation website. -> -> For the best result, we encourage you to check this document out as rendered -> by GitLab: [markdown.md] - -_GitLab uses (as of 11.1) the [CommonMark Ruby Library][commonmarker] for Markdown processing of all new issues, merge requests, comments, and other Markdown content in the GitLab system. As of 11.3, wiki pages and Markdown files (`.md`) in the repositories are also processed with CommonMark. Older content in issues/comments are still processed using the [Redcarpet Ruby library][redcarpet]._ +This markdown guide is valid for GitLab's system markdown entries and files. -_Where there are significant differences, we will try to call them out in this document._ +## GitLab Flavored Markdown (GFM) GitLab uses "GitLab Flavored Markdown" (GFM). It extends the [CommonMark specification][commonmark-spec] (which is based on standard Markdown) in a few significant ways to add some useful functionality. It was inspired by [GitHub Flavored Markdown](https://help.github.com/articles/basic-writing-and-formatting-syntax/). @@ -26,11 +17,28 @@ You can use GFM in the following areas: - markdown documents inside the repository You can also use other rich text files in GitLab. You might have to install a -dependency to do so. Please see the [github-markup gem readme](https://github.com/gitlabhq/markup#markups) for more information. +dependency to do so. Please see the [`github-markup` gem readme](https://github.com/gitlabhq/markup#markups) for more information. + +> **Notes:** +> +> For the best result, we encourage you to check this document out as rendered +> by GitLab itself: [markdown.md] +> +> As of 11.1, GitLab uses the [CommonMark Ruby Library][commonmarker] for Markdown +processing of all new issues, merge requests, comments, and other Markdown content +in the GitLab system. As of 11.3, wiki pages and Markdown files (`.md`) in the +repositories are also processed with CommonMark. Older content in issues/comments +are still processed using the [Redcarpet Ruby library][redcarpet]. +> +> _Where there are significant differences, we will try to call them out in this document._ ### Transitioning to CommonMark -You may have Markdown documents in your repository that were written using some of the nuances of RedCarpet's version of Markdown. Since CommonMark uses a slightly stricter syntax, these documents may now display a little strangely since we've transitioned to CommonMark. Numbered lists with nested lists in particular can be displayed incorrectly. +You may have Markdown documents in your repository that were written using some +of the nuances of RedCarpet's version of Markdown. Since CommonMark uses a +slightly stricter syntax, these documents may now display a little strangely +since we've transitioned to CommonMark. Numbered lists with nested lists in +particular can be displayed incorrectly. It is usually quite easy to fix. In the case of a nested list such as this: @@ -50,11 +58,18 @@ simply add a space to each nested item: In the documentation below, we try to highlight some of the differences. -If you have a need to view a document using RedCarpet, you can add the token `legacy_render=1` to the end of the url, like this: +If you have a need to view a document using RedCarpet, you can add the token +`legacy_render=1` to the end of the url, like this: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md?legacy_render=1 -If you have a large volume of Markdown files, it can be tedious to determine if they will be displayed correctly or not. You can use the [diff_redcarpet_cmark](https://gitlab.com/digitalmoksha/diff_redcarpet_cmark) tool (not an officially supported product) to generate a list of files and differences between how RedCarpet and CommonMark render the files. It can give you a great idea if anything needs to be changed - many times nothing will need to changed. +If you have a large volume of Markdown files, it can be tedious to determine +if they will be displayed correctly or not. You can use the +[diff_redcarpet_cmark](https://gitlab.com/digitalmoksha/diff_redcarpet_cmark) +tool (not an officially supported product) to generate a list of files and +differences between how RedCarpet and CommonMark render the files. It can give +you a great idea if anything needs to be changed - many times nothing will need +to changed. ### Newlines @@ -63,7 +78,8 @@ https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#newline GFM honors the markdown specification in how [paragraphs and line breaks are handled][commonmark-spec]. -A paragraph is simply one or more consecutive lines of text, separated by one or more blank lines. +A paragraph is simply one or more consecutive lines of text, separated by one or +more blank lines. Line-breaks, or soft returns, are rendered if you end a line with two or more spaces: <!-- (Do *NOT* remove the two ending whitespaces in the following line.) --> @@ -85,7 +101,9 @@ Sugar is sweet > If this is not rendered correctly, see https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#multiple-underscores-in-words -It is not reasonable to italicize just _part_ of a word, especially when you're dealing with code and names that often appear with multiple underscores. Therefore, GFM ignores multiple underscores in words: +It is not reasonable to italicize just _part_ of a word, especially when you're +dealing with code and names that often appear with multiple underscores. +Therefore, GFM ignores multiple underscores in words: perform_complicated_task @@ -124,7 +142,7 @@ https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#multili On top of standard Markdown [blockquotes](#blockquotes), which require prepending `>` to quoted lines, GFM supports multiline blockquotes fenced by <code>>>></code>: -```no-highlight +``` >>> If you paste a message from somewhere else @@ -158,7 +176,7 @@ Blocks of code are either fenced by lines with three back-ticks <code>```</code> or are indented with four spaces. Only the fenced code blocks support syntax highlighting: -```no-highlight +``` Inline `code` has `back-ticks around` it. ``` @@ -248,21 +266,23 @@ However the wrapping tags cannot be mixed as such: > If this is not rendered correctly, see https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#emoji - Sometimes you want to :monkey: around a bit and add some :star2: to your :speech_balloon:. Well we have a gift for you: +``` +Sometimes you want to :monkey: around a bit and add some :star2: to your :speech_balloon:. Well we have a gift for you: - :zap: You can use emoji anywhere GFM is supported. :v: +:zap: You can use emoji anywhere GFM is supported. :v: - You can use it to point out a :bug: or warn about :speak_no_evil: patches. And if someone improves your really :snail: code, send them some :birthday:. People will :heart: you for that. +You can use it to point out a :bug: or warn about :speak_no_evil: patches. And if someone improves your really :snail: code, send them some :birthday:. People will :heart: you for that. - If you are new to this, don't be :fearful:. You can easily join the emoji :family:. All you need to do is to look up one of the supported codes. +If you are new to this, don't be :fearful:. You can easily join the emoji :family:. All you need to do is to look up one of the supported codes. - Consult the [Emoji Cheat Sheet](https://www.emojicopy.com) for a list of all supported emoji codes. :thumbsup: +Consult the [Emoji Cheat Sheet](https://www.emojicopy.com) for a list of all supported emoji codes. :thumbsup: - Most emoji are natively supported on macOS, Windows, iOS, Android and will fallback to image-based emoji where there is lack of support. +Most emoji are natively supported on macOS, Windows, iOS, Android and will fallback to image-based emoji where there is lack of support. - On Linux, you can download [Noto Color Emoji](https://www.google.com/get/noto/help/emoji/) to get full native emoji support. +On Linux, you can download [Noto Color Emoji](https://www.google.com/get/noto/help/emoji/) to get full native emoji support. - Ubuntu 18.04 (like many modern Linux distros) has this font installed by default. +Ubuntu 18.04 (like many modern Linux distros) has this font installed by default. +``` Sometimes you want to <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/monkey.png" width="20px" height="20px"> around a bit and add some <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/star2.png" width="20px" height="20px"> to your <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/speech_balloon.png" width="20px" height="20px">. Well we have a gift for you: @@ -281,7 +301,6 @@ On Linux, you can download [Noto Color Emoji](https://www.google.com/get/noto/he Ubuntu 18.04 (like many modern Linux distros) has this font installed by default. - ### Special GitLab References GFM recognizes special references. @@ -343,7 +362,7 @@ https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#task-li You can add task lists to issues, merge requests and comments. To create a task list, add a specially-formatted Markdown list, like so: -```no-highlight +``` - [x] Completed task - [ ] Incomplete task - [ ] Sub-task 1 @@ -355,7 +374,7 @@ You can add task lists to issues, merge requests and comments. To create a task Tasks formatted as ordered lists are supported as well: -```no-highlight +``` 1. [x] Completed task 1. [ ] Incomplete task 1. [ ] Sub-task 1 @@ -412,7 +431,7 @@ This math is inline ![alt text](img/math_inline_sup_render_gfm.png). This is on a separate line -<div align="center"><img src="./img/math_inline_sup_render_gfm.png" ></div> +<img src="./img/math_inline_sup_render_gfm.png" > _Be advised that KaTeX only supports a [subset][katex-subset] of LaTeX._ @@ -440,7 +459,7 @@ Examples: `HSL(540,70%,50%)` `HSLA(540,70%,50%,0.7)` -Become: +Becomes: ![alt color-inline-colorchip-render-gfm](img/color_inline_colorchip_render_gfm.png) @@ -482,7 +501,7 @@ For details see the [Mermaid official page][mermaid]. ### Headers -```no-highlight +``` # H1 ## H2 ### H3 @@ -540,7 +559,7 @@ Note that the Emoji processing happens before the header IDs are generated, so t Examples: -```no-highlight +``` Emphasis, aka italics, with *asterisks* or _underscores_. Strong emphasis, aka bold, with **asterisks** or __underscores__. @@ -550,7 +569,7 @@ Combined emphasis with **asterisks and _underscores_**. Strikethrough uses two tildes. ~~Scratch this.~~ ``` -Become: +Becomes: Emphasis, aka italics, with *asterisks* or _underscores_. @@ -564,7 +583,7 @@ Strikethrough uses two tildes. ~~Scratch this.~~ Examples: -```no-highlight +``` 1. First ordered list item 2. Another item * Unordered sub-list. @@ -577,7 +596,7 @@ Examples: + Or pluses ``` -Become: +Becomes: 1. First ordered list item 2. Another item @@ -595,7 +614,7 @@ each subsequent paragraph should be indented to the same level as the start of t Example: -```no-highlight +``` 1. First ordered list item Second paragraph of first item. @@ -616,7 +635,7 @@ the paragraph will appear outside the list, instead of properly indented under t Example: -```no-highlight +``` 1. First ordered list item Paragraph of first item. @@ -676,7 +695,7 @@ Examples: [logo]: img/markdown_logo.png -Become: +Becomes: Here's our logo: @@ -694,7 +713,7 @@ Reference-style: Examples: -```no-highlight +``` > Blockquotes are very handy in email to emulate reply text. > This line is part of the same quote. @@ -703,7 +722,7 @@ Quote break. > This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can *put* **Markdown** into a blockquote. ``` -Become: +Becomes: > Blockquotes are very handy in email to emulate reply text. > This line is part of the same quote. @@ -720,7 +739,7 @@ See the documentation for HTML::Pipeline's [SanitizationFilter](http://www.rubyd Examples: -```no-highlight +``` <dl> <dt>Definition list</dt> <dd>Is something people use sometimes.</dd> @@ -730,7 +749,7 @@ Examples: </dl> ``` -Become: +Becomes: <dl> <dt>Definition list</dt> @@ -788,7 +807,7 @@ ___ Underscores ``` -Become: +Becomes: Three or more... @@ -826,7 +845,7 @@ This line is *on its own line*, because the previous line ends with two spaces. spaces. ``` -Become: +Becomes: Here's a line for us to start with. diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md index f9ebf277125..0a7f7d37384 100644 --- a/doc/user/project/merge_requests/index.md +++ b/doc/user/project/merge_requests/index.md @@ -236,6 +236,35 @@ all your changes will be available to preview by anyone with the Review Apps lin Find out about [bulk editing merge requests](../../project/bulk_editing.md). +## Troubleshooting + +Sometimes things don't go as expected in a merge request, here are some +troubleshooting steps. + +### Merge request cannot retrieve the pipeline status + +This can occur for one of two reasons: + +* Sidekiq doesn't pick up the changes fast enough +* Because of the bug described in [#41545](https://gitlab.com/gitlab-org/gitlab-ce/issues/41545) + +#### Sidekiq + +Sidekiq didn't process the CI state change fast enough. Please wait a few +seconds and the status will update automatically. + +#### Bug + +Merge Request pipeline statuses can't be retrieved when the following occurs: + +1. A Merge Requst is created +1. The Merge Request is closed +1. Changes are made in the project +1. The Merge Request is reopened + +To enable the pipeline status to be properly retrieved, close and reopen the +Merge Request again. + ## Tips Here are some tips that will help you be more efficient with merge requests in diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 405fc30a2ed..e37083165f5 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -8,6 +8,15 @@ module API helpers ::Gitlab::IssuableMetadata + # EE::API::Issues would override the following helpers + helpers do + params :issues_params_ee do + end + + params :issue_params_ee do + end + end + helpers do # rubocop: disable CodeReuse/ActiveRecord def find_issues(args = {}) @@ -46,9 +55,11 @@ module API desc: 'Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`' optional :my_reaction_emoji, type: String, desc: 'Return issues reacted by the authenticated user by the given emoji' use :pagination + + use :issues_params_ee end - params :issue_params_ce do + params :issue_params do optional :description, type: String, desc: 'The description of an issue' optional :assignee_ids, type: Array[Integer], desc: 'The array of user IDs to assign issue' optional :assignee_id, type: Integer, desc: '[Deprecated] The ID of a user to assign issue' @@ -57,10 +68,8 @@ module API optional :due_date, type: String, desc: 'Date string in the format YEAR-MONTH-DAY' optional :confidential, type: Boolean, desc: 'Boolean parameter if the issue should be confidential' optional :discussion_locked, type: Boolean, desc: " Boolean parameter indicating if the issue's discussion is locked" - end - params :issue_params do - use :issue_params_ce + use :issue_params_ee end end diff --git a/lib/banzai/filter/issuable_state_filter.rb b/lib/banzai/filter/issuable_state_filter.rb index d7fe012883d..8e2358694d4 100644 --- a/lib/banzai/filter/issuable_state_filter.rb +++ b/lib/banzai/filter/issuable_state_filter.rb @@ -18,7 +18,7 @@ module Banzai issuables = extractor.extract([doc]) issuables.each do |node, issuable| - next if !can_read_cross_project? && issuable.project != project + next if !can_read_cross_project? && cross_reference?(issuable) if VISIBLE_STATES.include?(issuable.state) && issuable_reference?(node.inner_html, issuable) node.content += " (#{issuable.state})" @@ -31,7 +31,14 @@ module Banzai private def issuable_reference?(text, issuable) - text == issuable.reference_link_text(project || group) + CGI.unescapeHTML(text) == issuable.reference_link_text(project || group) + end + + def cross_reference?(issuable) + return true if issuable.project != project + return true if issuable.respond_to?(:group) && issuable.group != group + + false end def can_read_cross_project? diff --git a/lib/banzai/issuable_extractor.rb b/lib/banzai/issuable_extractor.rb index 0a05d46db4c..341dbb74fe0 100644 --- a/lib/banzai/issuable_extractor.rb +++ b/lib/banzai/issuable_extractor.rb @@ -9,13 +9,11 @@ module Banzai # so we can avoid N+1 queries problem class IssuableExtractor - QUERY = %q( - descendant-or-self::a[contains(concat(" ", @class, " "), " gfm ")] - [@data-reference-type="issue" or @data-reference-type="merge_request"] - ).freeze - attr_reader :context + ISSUE_REFERENCE_TYPE = '@data-reference-type="issue"'.freeze + MERGE_REQUEST_REFERENCE_TYPE = '@data-reference-type="merge_request"'.freeze + # context - An instance of Banzai::RenderContext. def initialize(context) @context = context @@ -24,21 +22,38 @@ module Banzai # Returns Hash in the form { node => issuable_instance } def extract(documents) nodes = documents.flat_map do |document| - document.xpath(QUERY) + document.xpath(query) end - issue_parser = Banzai::ReferenceParser::IssueParser.new(context) + # The project or group for the issuable might be pending for deletion! + # Filter them out because we don't care about them. + issuables_for_nodes(nodes).select { |node, issuable| issuable.project || issuable.group } + end + + private - merge_request_parser = + def issuables_for_nodes(nodes) + parsers.each_with_object({}) do |parser, result| + result.merge!(parser.records_for_nodes(nodes)) + end + end + + def parsers + [ + Banzai::ReferenceParser::IssueParser.new(context), Banzai::ReferenceParser::MergeRequestParser.new(context) + ] + end - issuables_for_nodes = issue_parser.records_for_nodes(nodes).merge( - merge_request_parser.records_for_nodes(nodes) + def query + %Q( + descendant-or-self::a[contains(concat(" ", @class, " "), " gfm ")] + [#{reference_types.join(' or ')}] ) + end - # The project for the issue/MR might be pending for deletion! - # Filter them out because we don't care about them. - issuables_for_nodes.select { |node, issuable| issuable.project } + def reference_types + [ISSUE_REFERENCE_TYPE, MERGE_REQUEST_REFERENCE_TYPE] end end end diff --git a/lib/gitlab/user_extractor.rb b/lib/gitlab/user_extractor.rb index bd0d24e4369..9abd64c2250 100644 --- a/lib/gitlab/user_extractor.rb +++ b/lib/gitlab/user_extractor.rb @@ -14,13 +14,11 @@ module Gitlab @text = text end - # rubocop: disable CodeReuse/ActiveRecord def users return User.none unless @text.present? @users ||= User.from_union(union_relations) end - # rubocop: enable CodeReuse/ActiveRecord def usernames matches[:usernames] diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 18038d84d45..324e5315821 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -1986,6 +1986,9 @@ msgstr "" msgid "Copy token to clipboard" msgstr "" +msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}" +msgstr "" + msgid "Create" msgstr "" diff --git a/package.json b/package.json index 608d4e58dd3..2d6479fea3f 100644 --- a/package.json +++ b/package.json @@ -24,8 +24,8 @@ "@babel/plugin-syntax-dynamic-import": "^7.0.0", "@babel/plugin-syntax-import-meta": "^7.0.0", "@babel/preset-env": "^7.1.0", - "@gitlab-org/gitlab-svgs": "^1.33.0", "@gitlab-org/gitlab-ui": "^1.10.0", + "@gitlab/svgs": "^1.35.0", "autosize": "^4.0.0", "axios": "^0.17.1", "babel-loader": "^8.0.4", diff --git a/rubocop/cop/code_reuse/active_record.rb b/rubocop/cop/code_reuse/active_record.rb index d25e8548fd0..2be8f7c11aa 100644 --- a/rubocop/cop/code_reuse/active_record.rb +++ b/rubocop/cop/code_reuse/active_record.rb @@ -49,7 +49,6 @@ module RuboCop limit: true, lock: false, many?: false, - none: false, offset: true, order: true, pluck: true, diff --git a/scripts/build_assets_image b/scripts/build_assets_image new file mode 100755 index 00000000000..218606b9a40 --- /dev/null +++ b/scripts/build_assets_image @@ -0,0 +1,21 @@ +#!/bin/bash + +# Generate the image name based on the project this is being run in +ASSETS_IMAGE_NAME=$(echo ${CI_PROJECT_NAME} | + awk '{ + split($1, p, "-"); + interim = sprintf("%s-assets-%s", p[1], p[2]); + sub(/-$/, "", interim); + print interim + }' +) + +ASSETS_IMAGE_PATH=${CI_REGISTRY}/${CI_PROJECT_PATH}/${ASSETS_IMAGE_NAME} + +mkdir -p assets_container.build/public +cp -r public/assets assets_container.build/public/ +cp Dockerfile.assets assets_container.build/ +docker build -t ${ASSETS_IMAGE_PATH}:${CI_COMMIT_REF_NAME} -f assets_container.build/Dockerfile.assets assets_container.build/ +docker login -u gitlab-ci-token -p ${CI_JOB_TOKEN} ${CI_REGISTRY} +docker push ${ASSETS_IMAGE_PATH} + diff --git a/scripts/static-analysis b/scripts/static-analysis index 0e67eabfec1..25ba7ec6c8e 100755 --- a/scripts/static-analysis +++ b/scripts/static-analysis @@ -29,6 +29,7 @@ tasks = [ %w[bin/rake lint:all], %w[bundle exec license_finder], %w[yarn run eslint], + %w[yarn run prettier-all], %w[bundle exec rubocop --parallel], %w[scripts/lint-conflicts.sh], %w[scripts/lint-rugged] diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb index 2023d4b0bd0..8eb01145ed5 100644 --- a/spec/controllers/projects/jobs_controller_spec.rb +++ b/spec/controllers/projects/jobs_controller_spec.rb @@ -152,11 +152,33 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do expect(response).to have_gitlab_http_status(:ok) expect(response).to match_response_schema('job/job_details') expect(json_response['raw_path']).to match(%r{jobs/\d+/raw\z}) - expect(json_response.dig('merge_request', 'path')).to match(%r{merge_requests/\d+\z}) + expect(json_response['merge_request']['path']).to match(%r{merge_requests/\d+\z}) expect(json_response['new_issue_path']).to include('/issues/new') end end + context 'when job is running' do + context 'job is cancelable' do + let(:job) { create(:ci_build, :running, pipeline: pipeline) } + + it 'cancel_path is present with correct redirect' do + expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('job/job_details') + expect(json_response['cancel_path']).to include(CGI.escape(json_response['build_path'])) + end + end + + context 'with web terminal' do + let(:job) { create(:ci_build, :running, :with_runner_session, pipeline: pipeline) } + + it 'exposes the terminal path' do + expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('job/job_details') + expect(json_response['terminal_path']).to match(%r{/terminal}) + end + end + end + context 'when job has artifacts' do context 'with not expiry date' do let(:job) { create(:ci_build, :success, :artifacts, pipeline: pipeline) } @@ -185,16 +207,6 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do end end - context 'when job has terminal' do - let(:job) { create(:ci_build, :running, :with_runner_session, pipeline: pipeline) } - - it 'exposes the terminal path' do - expect(response).to have_gitlab_http_status(:ok) - expect(response).to match_response_schema('job/job_details') - expect(json_response['terminal_path']).to match(%r{/terminal}) - end - end - context 'when job passed with no trace' do let(:job) { create(:ci_build, :success, :artifacts, pipeline: pipeline) } diff --git a/spec/features/merge_request/user_sees_merge_widget_spec.rb b/spec/features/merge_request/user_sees_merge_widget_spec.rb index d2003b61b2a..0c610edd6d1 100644 --- a/spec/features/merge_request/user_sees_merge_widget_spec.rb +++ b/spec/features/merge_request/user_sees_merge_widget_spec.rb @@ -179,7 +179,7 @@ describe 'Merge request > User sees merge widget', :js do # Wait for the `ci_status` and `merge_check` requests wait_for_requests - expect(page).to have_text('Could not connect to the CI server. Please check your settings and try again') + expect(page).to have_text(%r{Could not retrieve the pipeline status\. For troubleshooting steps, read the <a href=\".+\">documentation\.</a>}) end end diff --git a/spec/features/merge_request/user_sees_pipelines_spec.rb b/spec/features/merge_request/user_sees_pipelines_spec.rb index 45cccbee63e..41f447fba95 100644 --- a/spec/features/merge_request/user_sees_pipelines_spec.rb +++ b/spec/features/merge_request/user_sees_pipelines_spec.rb @@ -41,8 +41,8 @@ describe 'Merge request > User sees pipelines', :js do visit project_merge_request_path(project, merge_request) wait_for_requests - expect(page.find('.ci-widget')).to have_content( - 'Could not connect to the CI server. Please check your settings and try again') + expect(page.find('.ci-widget')).to have_text( + %r{Could not retrieve the pipeline status\. For troubleshooting steps, read the <a href=\".+\">documentation\.</a>}) end end diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb index b3bea92e635..5cb3f7c732f 100644 --- a/spec/features/projects/jobs_spec.rb +++ b/spec/features/projects/jobs_spec.rb @@ -198,6 +198,24 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do end end + context 'when job is running', :js do + let(:job) { create(:ci_build, :running, pipeline: pipeline) } + let(:job_url) { project_job_path(project, job) } + + before do + visit job_url + wait_for_requests + end + + context 'job is cancelable' do + it 'shows cancel button' do + click_link 'Cancel' + + expect(page.current_path).to eq(job_url) + end + end + end + context "Job from other project" do before do visit project_job_path(project, job2) diff --git a/spec/javascripts/boards/board_list_spec.js b/spec/javascripts/boards/board_list_spec.js index 037e06cf3b2..2642c8b1bdb 100644 --- a/spec/javascripts/boards/board_list_spec.js +++ b/spec/javascripts/boards/board_list_spec.js @@ -18,7 +18,7 @@ describe('Board list component', () => { let mock; let component; - beforeEach((done) => { + beforeEach(done => { const el = document.createElement('div'); document.body.appendChild(el); @@ -62,122 +62,102 @@ describe('Board list component', () => { }); it('renders component', () => { - expect( - component.$el.classList.contains('board-list-component'), - ).toBe(true); + expect(component.$el.classList.contains('board-list-component')).toBe(true); }); - it('renders loading icon', (done) => { + it('renders loading icon', done => { component.loading = true; Vue.nextTick(() => { - expect( - component.$el.querySelector('.board-list-loading'), - ).not.toBeNull(); + expect(component.$el.querySelector('.board-list-loading')).not.toBeNull(); done(); }); }); it('renders issues', () => { - expect( - component.$el.querySelectorAll('.board-card').length, - ).toBe(1); + expect(component.$el.querySelectorAll('.board-card').length).toBe(1); }); it('sets data attribute with issue id', () => { - expect( - component.$el.querySelector('.board-card').getAttribute('data-issue-id'), - ).toBe('1'); + expect(component.$el.querySelector('.board-card').getAttribute('data-issue-id')).toBe('1'); }); - it('shows new issue form', (done) => { + it('shows new issue form', done => { component.toggleForm(); Vue.nextTick(() => { - expect( - component.$el.querySelector('.board-new-issue-form'), - ).not.toBeNull(); + expect(component.$el.querySelector('.board-new-issue-form')).not.toBeNull(); - expect( - component.$el.querySelector('.is-smaller'), - ).not.toBeNull(); + expect(component.$el.querySelector('.is-smaller')).not.toBeNull(); done(); }); }); - it('shows new issue form after eventhub event', (done) => { + it('shows new issue form after eventhub event', done => { eventHub.$emit(`hide-issue-form-${component.list.id}`); Vue.nextTick(() => { - expect( - component.$el.querySelector('.board-new-issue-form'), - ).not.toBeNull(); + expect(component.$el.querySelector('.board-new-issue-form')).not.toBeNull(); - expect( - component.$el.querySelector('.is-smaller'), - ).not.toBeNull(); + expect(component.$el.querySelector('.is-smaller')).not.toBeNull(); done(); }); }); - it('does not show new issue form for closed list', (done) => { + it('does not show new issue form for closed list', done => { component.list.type = 'closed'; component.toggleForm(); Vue.nextTick(() => { - expect( - component.$el.querySelector('.board-new-issue-form'), - ).toBeNull(); + expect(component.$el.querySelector('.board-new-issue-form')).toBeNull(); done(); }); }); - it('shows count list item', (done) => { + it('shows count list item', done => { component.showCount = true; Vue.nextTick(() => { - expect( - component.$el.querySelector('.board-list-count'), - ).not.toBeNull(); + expect(component.$el.querySelector('.board-list-count')).not.toBeNull(); - expect( - component.$el.querySelector('.board-list-count').textContent.trim(), - ).toBe('Showing all issues'); + expect(component.$el.querySelector('.board-list-count').textContent.trim()).toBe( + 'Showing all issues', + ); done(); }); }); - it('sets data attribute with invalid id', (done) => { + it('sets data attribute with invalid id', done => { component.showCount = true; Vue.nextTick(() => { - expect( - component.$el.querySelector('.board-list-count').getAttribute('data-issue-id'), - ).toBe('-1'); + expect(component.$el.querySelector('.board-list-count').getAttribute('data-issue-id')).toBe( + '-1', + ); done(); }); }); - it('shows how many more issues to load', (done) => { + it('shows how many more issues to load', done => { component.showCount = true; component.list.issuesSize = 20; Vue.nextTick(() => { - expect( - component.$el.querySelector('.board-list-count').textContent.trim(), - ).toBe('Showing 1 of 20 issues'); + expect(component.$el.querySelector('.board-list-count').textContent.trim()).toBe( + 'Showing 1 of 20 issues', + ); done(); }); }); - it('loads more issues after scrolling', (done) => { + it('loads more issues after scrolling', done => { spyOn(component.list, 'nextPage'); component.$refs.list.style.height = '100px'; component.$refs.list.style.overflow = 'scroll'; @@ -200,7 +180,9 @@ describe('Board list component', () => { }); it('does not load issues if already loading', () => { - component.list.nextPage = spyOn(component.list, 'nextPage').and.returnValue(new Promise(() => {})); + component.list.nextPage = spyOn(component.list, 'nextPage').and.returnValue( + new Promise(() => {}), + ); component.onScroll(); component.onScroll(); @@ -208,14 +190,12 @@ describe('Board list component', () => { expect(component.list.nextPage).toHaveBeenCalledTimes(1); }); - it('shows loading more spinner', (done) => { + it('shows loading more spinner', done => { component.showCount = true; component.list.loadingMore = true; Vue.nextTick(() => { - expect( - component.$el.querySelector('.board-list-count .fa-spinner'), - ).not.toBeNull(); + expect(component.$el.querySelector('.board-list-count .fa-spinner')).not.toBeNull(); done(); }); diff --git a/spec/javascripts/boards/components/board_spec.js b/spec/javascripts/boards/components/board_spec.js index d4c53bd5a7d..dee7841c088 100644 --- a/spec/javascripts/boards/components/board_spec.js +++ b/spec/javascripts/boards/components/board_spec.js @@ -8,7 +8,7 @@ describe('Board component', () => { let vm; let el; - beforeEach((done) => { + beforeEach(done => { loadFixtures('boards/show.html.raw'); el = document.createElement('div'); @@ -50,56 +50,46 @@ describe('Board component', () => { }); it('board is expandable when list type is backlog', () => { - expect( - vm.$el.classList.contains('is-expandable'), - ).toBe(true); + expect(vm.$el.classList.contains('is-expandable')).toBe(true); }); - it('board is expandable when list type is closed', (done) => { + it('board is expandable when list type is closed', done => { vm.list.type = 'closed'; Vue.nextTick(() => { - expect( - vm.$el.classList.contains('is-expandable'), - ).toBe(true); + expect(vm.$el.classList.contains('is-expandable')).toBe(true); done(); }); }); - it('board is not expandable when list type is label', (done) => { + it('board is not expandable when list type is label', done => { vm.list.type = 'label'; vm.list.isExpandable = false; Vue.nextTick(() => { - expect( - vm.$el.classList.contains('is-expandable'), - ).toBe(false); + expect(vm.$el.classList.contains('is-expandable')).toBe(false); done(); }); }); - it('collapses when clicking header', (done) => { + it('collapses when clicking header', done => { vm.$el.querySelector('.board-header').click(); Vue.nextTick(() => { - expect( - vm.$el.classList.contains('is-collapsed'), - ).toBe(true); + expect(vm.$el.classList.contains('is-collapsed')).toBe(true); done(); }); }); - it('created sets isExpanded to true from localStorage', (done) => { + it('created sets isExpanded to true from localStorage', done => { vm.$el.querySelector('.board-header').click(); return Vue.nextTick() .then(() => { - expect( - vm.$el.classList.contains('is-collapsed'), - ).toBe(true); + expect(vm.$el.classList.contains('is-collapsed')).toBe(true); // call created manually vm.$options.created[0].call(vm); @@ -107,11 +97,10 @@ describe('Board component', () => { return Vue.nextTick(); }) .then(() => { - expect( - vm.$el.classList.contains('is-collapsed'), - ).toBe(true); + expect(vm.$el.classList.contains('is-collapsed')).toBe(true); done(); - }).catch(done.fail); + }) + .catch(done.fail); }); }); diff --git a/spec/javascripts/environments/emtpy_state_spec.js b/spec/javascripts/environments/emtpy_state_spec.js index d71dfe8197e..1f986d49bc7 100644 --- a/spec/javascripts/environments/emtpy_state_spec.js +++ b/spec/javascripts/environments/emtpy_state_spec.js @@ -1,4 +1,3 @@ - import Vue from 'vue'; import emptyState from '~/environments/components/empty_state.vue'; import mountComponent from 'spec/helpers/vue_mount_component_helper'; @@ -25,13 +24,13 @@ describe('environments empty state', () => { }); it('renders empty state and new environment button', () => { - expect( - vm.$el.querySelector('.js-blank-state-title').textContent.trim(), - ).toEqual('You don\'t have any environments right now'); + expect(vm.$el.querySelector('.js-blank-state-title').textContent.trim()).toEqual( + "You don't have any environments right now", + ); - expect( - vm.$el.querySelector('.js-new-environment-button').getAttribute('href'), - ).toEqual('foo'); + expect(vm.$el.querySelector('.js-new-environment-button').getAttribute('href')).toEqual( + 'foo', + ); }); }); @@ -45,13 +44,11 @@ describe('environments empty state', () => { }); it('renders empty state without new button', () => { - expect( - vm.$el.querySelector('.js-blank-state-title').textContent.trim(), - ).toEqual('You don\'t have any environments right now'); + expect(vm.$el.querySelector('.js-blank-state-title').textContent.trim()).toEqual( + "You don't have any environments right now", + ); - expect( - vm.$el.querySelector('.js-new-environment-button'), - ).toBeNull(); + expect(vm.$el.querySelector('.js-new-environment-button')).toBeNull(); }); }); }); diff --git a/spec/javascripts/environments/environment_item_spec.js b/spec/javascripts/environments/environment_item_spec.js index 0b933dda431..7618c2f50ce 100644 --- a/spec/javascripts/environments/environment_item_spec.js +++ b/spec/javascripts/environments/environment_item_spec.js @@ -38,7 +38,9 @@ describe('Environment item', () => { }); it('Should render the number of children in a badge', () => { - expect(component.$el.querySelector('.folder-name .badge').textContent).toContain(mockItem.size); + expect(component.$el.querySelector('.folder-name .badge').textContent).toContain( + mockItem.size, + ); }); }); @@ -68,7 +70,8 @@ describe('Environment item', () => { username: 'root', id: 1, state: 'active', - avatar_url: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', + avatar_url: + 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', web_url: 'http://localhost:3000/root', }, commit: { @@ -84,7 +87,8 @@ describe('Environment item', () => { username: 'root', id: 1, state: 'active', - avatar_url: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', + avatar_url: + 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', web_url: 'http://localhost:3000/root', }, commit_path: '/root/ci-folders/tree/500aabcb17c97bdcf2d0c410b70cb8556f0362dd', @@ -121,18 +125,18 @@ describe('Environment item', () => { }); it('should render environment name', () => { - expect(component.$el.querySelector('.environment-name').textContent).toContain(environment.name); + expect(component.$el.querySelector('.environment-name').textContent).toContain( + environment.name, + ); }); describe('With deployment', () => { it('should render deployment internal id', () => { - expect( - component.$el.querySelector('.deployment-column span').textContent, - ).toContain(environment.last_deployment.iid); + expect(component.$el.querySelector('.deployment-column span').textContent).toContain( + environment.last_deployment.iid, + ); - expect( - component.$el.querySelector('.deployment-column span').textContent, - ).toContain('#'); + expect(component.$el.querySelector('.deployment-column span').textContent).toContain('#'); }); it('should render last deployment date', () => { @@ -156,56 +160,46 @@ describe('Environment item', () => { describe('With build url', () => { it('Should link to build url provided', () => { - expect( - component.$el.querySelector('.build-link').getAttribute('href'), - ).toEqual(environment.last_deployment.deployable.build_path); + expect(component.$el.querySelector('.build-link').getAttribute('href')).toEqual( + environment.last_deployment.deployable.build_path, + ); }); it('Should render deployable name and id', () => { - expect( - component.$el.querySelector('.build-link').getAttribute('href'), - ).toEqual(environment.last_deployment.deployable.build_path); + expect(component.$el.querySelector('.build-link').getAttribute('href')).toEqual( + environment.last_deployment.deployable.build_path, + ); }); }); describe('With commit information', () => { it('should render commit component', () => { - expect( - component.$el.querySelector('.js-commit-component'), - ).toBeDefined(); + expect(component.$el.querySelector('.js-commit-component')).toBeDefined(); }); }); }); describe('With manual actions', () => { it('Should render actions component', () => { - expect( - component.$el.querySelector('.js-manual-actions-container'), - ).toBeDefined(); + expect(component.$el.querySelector('.js-manual-actions-container')).toBeDefined(); }); }); describe('With external URL', () => { it('should render external url component', () => { - expect( - component.$el.querySelector('.js-external-url-container'), - ).toBeDefined(); + expect(component.$el.querySelector('.js-external-url-container')).toBeDefined(); }); }); describe('With stop action', () => { it('Should render stop action component', () => { - expect( - component.$el.querySelector('.js-stop-component-container'), - ).toBeDefined(); + expect(component.$el.querySelector('.js-stop-component-container')).toBeDefined(); }); }); describe('With retry action', () => { it('Should render rollback component', () => { - expect( - component.$el.querySelector('.js-rollback-component-container'), - ).toBeDefined(); + expect(component.$el.querySelector('.js-rollback-component-container')).toBeDefined(); }); }); }); diff --git a/spec/javascripts/environments/environments_app_spec.js b/spec/javascripts/environments/environments_app_spec.js index 7edc0ccac0b..ffa11e09597 100644 --- a/spec/javascripts/environments/environments_app_spec.js +++ b/spec/javascripts/environments/environments_app_spec.js @@ -33,7 +33,7 @@ describe('Environment', () => { describe('successfull request', () => { describe('without environments', () => { - beforeEach((done) => { + beforeEach(done => { mock.onGet(mockData.endpoint).reply(200, { environments: [] }); component = mountComponent(EnvironmentsComponent, mockData); @@ -44,30 +44,34 @@ describe('Environment', () => { }); it('should render the empty state', () => { - expect( - component.$el.querySelector('.js-new-environment-button').textContent, - ).toContain('New environment'); + expect(component.$el.querySelector('.js-new-environment-button').textContent).toContain( + 'New environment', + ); - expect( - component.$el.querySelector('.js-blank-state-title').textContent, - ).toContain('You don\'t have any environments right now'); + expect(component.$el.querySelector('.js-blank-state-title').textContent).toContain( + "You don't have any environments right now", + ); }); }); describe('with paginated environments', () => { - beforeEach((done) => { - mock.onGet(mockData.endpoint).reply(200, { - environments: [environment], - stopped_count: 1, - available_count: 0, - }, { - 'X-nExt-pAge': '2', - 'x-page': '1', - 'X-Per-Page': '1', - 'X-Prev-Page': '', - 'X-TOTAL': '37', - 'X-Total-Pages': '2', - }); + beforeEach(done => { + mock.onGet(mockData.endpoint).reply( + 200, + { + environments: [environment], + stopped_count: 1, + available_count: 0, + }, + { + 'X-nExt-pAge': '2', + 'x-page': '1', + 'X-Per-Page': '1', + 'X-Prev-Page': '', + 'X-TOTAL': '37', + 'X-Total-Pages': '2', + }, + ); component = mountComponent(EnvironmentsComponent, mockData); @@ -78,19 +82,17 @@ describe('Environment', () => { it('should render a table with environments', () => { expect(component.$el.querySelectorAll('table')).not.toBeNull(); - expect( - component.$el.querySelector('.environment-name').textContent.trim(), - ).toEqual(environment.name); + expect(component.$el.querySelector('.environment-name').textContent.trim()).toEqual( + environment.name, + ); }); describe('pagination', () => { it('should render pagination', () => { - expect( - component.$el.querySelectorAll('.gl-pagination li').length, - ).toEqual(5); + expect(component.$el.querySelectorAll('.gl-pagination li').length).toEqual(5); }); - it('should make an API request when page is clicked', (done) => { + it('should make an API request when page is clicked', done => { spyOn(component, 'updateContent'); setTimeout(() => { component.$el.querySelector('.gl-pagination li:nth-child(5) a').click(); @@ -100,7 +102,7 @@ describe('Environment', () => { }, 0); }); - it('should make an API request when using tabs', (done) => { + it('should make an API request when using tabs', done => { setTimeout(() => { spyOn(component, 'updateContent'); component.$el.querySelector('.js-environments-tab-stopped').click(); @@ -114,7 +116,7 @@ describe('Environment', () => { }); describe('unsuccessfull request', () => { - beforeEach((done) => { + beforeEach(done => { mock.onGet(mockData.endpoint).reply(500, {}); component = mountComponent(EnvironmentsComponent, mockData); @@ -125,15 +127,16 @@ describe('Environment', () => { }); it('should render empty state', () => { - expect( - component.$el.querySelector('.js-blank-state-title').textContent, - ).toContain('You don\'t have any environments right now'); + expect(component.$el.querySelector('.js-blank-state-title').textContent).toContain( + "You don't have any environments right now", + ); }); }); describe('expandable folders', () => { beforeEach(() => { - mock.onGet(mockData.endpoint).reply(200, + mock.onGet(mockData.endpoint).reply( + 200, { environments: [folder], stopped_count: 0, @@ -154,7 +157,7 @@ describe('Environment', () => { component = mountComponent(EnvironmentsComponent, mockData); }); - it('should open a closed folder', (done) => { + it('should open a closed folder', done => { setTimeout(() => { component.$el.querySelector('.folder-name').click(); @@ -165,7 +168,7 @@ describe('Environment', () => { }, 0); }); - it('should close an opened folder', (done) => { + it('should close an opened folder', done => { setTimeout(() => { // open folder component.$el.querySelector('.folder-name').click(); @@ -182,7 +185,7 @@ describe('Environment', () => { }, 0); }); - it('should show children environments and a button to show all environments', (done) => { + it('should show children environments and a button to show all environments', done => { setTimeout(() => { // open folder component.$el.querySelector('.folder-name').click(); @@ -191,7 +194,9 @@ describe('Environment', () => { // wait for next async request setTimeout(() => { expect(component.$el.querySelectorAll('.js-child-row').length).toEqual(1); - expect(component.$el.querySelector('.text-center > a.btn').textContent).toContain('Show all'); + expect(component.$el.querySelector('.text-center > a.btn').textContent).toContain( + 'Show all', + ); done(); }); }); @@ -201,7 +206,8 @@ describe('Environment', () => { describe('methods', () => { beforeEach(() => { - mock.onGet(mockData.endpoint).reply(200, + mock.onGet(mockData.endpoint).reply( + 200, { environments: [], stopped_count: 0, @@ -215,8 +221,9 @@ describe('Environment', () => { }); describe('updateContent', () => { - it('should set given parameters', (done) => { - component.updateContent({ scope: 'stopped', page: '3' }) + it('should set given parameters', done => { + component + .updateContent({ scope: 'stopped', page: '3' }) .then(() => { expect(component.page).toEqual('3'); expect(component.scope).toEqual('stopped'); diff --git a/spec/javascripts/environments/folder/environments_folder_view_spec.js b/spec/javascripts/environments/folder/environments_folder_view_spec.js index 51d4213c38f..862473b5c58 100644 --- a/spec/javascripts/environments/folder/environments_folder_view_spec.js +++ b/spec/javascripts/environments/folder/environments_folder_view_spec.js @@ -32,37 +32,41 @@ describe('Environments Folder View', () => { describe('successfull request', () => { beforeEach(() => { - mock.onGet(mockData.endpoint).reply(200, { - environments: environmentsList, - stopped_count: 1, - available_count: 0, - }, { - 'X-nExt-pAge': '2', - 'x-page': '1', - 'X-Per-Page': '2', - 'X-Prev-Page': '', - 'X-TOTAL': '20', - 'X-Total-Pages': '10', - }); + mock.onGet(mockData.endpoint).reply( + 200, + { + environments: environmentsList, + stopped_count: 1, + available_count: 0, + }, + { + 'X-nExt-pAge': '2', + 'x-page': '1', + 'X-Per-Page': '2', + 'X-Prev-Page': '', + 'X-TOTAL': '20', + 'X-Total-Pages': '10', + }, + ); component = mountComponent(Component, mockData); }); - it('should render a table with environments', (done) => { + it('should render a table with environments', done => { setTimeout(() => { expect(component.$el.querySelectorAll('table')).not.toBeNull(); - expect( - component.$el.querySelector('.environment-name').textContent.trim(), - ).toEqual(environmentsList[0].name); + expect(component.$el.querySelector('.environment-name').textContent.trim()).toEqual( + environmentsList[0].name, + ); done(); }, 0); }); - it('should render available tab with count', (done) => { + it('should render available tab with count', done => { setTimeout(() => { - expect( - component.$el.querySelector('.js-environments-tab-available').textContent, - ).toContain('Available'); + expect(component.$el.querySelector('.js-environments-tab-available').textContent).toContain( + 'Available', + ); expect( component.$el.querySelector('.js-environments-tab-available .badge').textContent, @@ -71,11 +75,11 @@ describe('Environments Folder View', () => { }, 0); }); - it('should render stopped tab with count', (done) => { + it('should render stopped tab with count', done => { setTimeout(() => { - expect( - component.$el.querySelector('.js-environments-tab-stopped').textContent, - ).toContain('Stopped'); + expect(component.$el.querySelector('.js-environments-tab-stopped').textContent).toContain( + 'Stopped', + ); expect( component.$el.querySelector('.js-environments-tab-stopped .badge').textContent, @@ -84,36 +88,37 @@ describe('Environments Folder View', () => { }, 0); }); - it('should render parent folder name', (done) => { + it('should render parent folder name', done => { setTimeout(() => { - expect( - component.$el.querySelector('.js-folder-name').textContent.trim(), - ).toContain('Environments / review'); + expect(component.$el.querySelector('.js-folder-name').textContent.trim()).toContain( + 'Environments / review', + ); done(); }, 0); }); describe('pagination', () => { - it('should render pagination', (done) => { + it('should render pagination', done => { setTimeout(() => { - expect( - component.$el.querySelectorAll('.gl-pagination'), - ).not.toBeNull(); + expect(component.$el.querySelectorAll('.gl-pagination')).not.toBeNull(); done(); }, 0); }); - it('should make an API request when changing page', (done) => { + it('should make an API request when changing page', done => { spyOn(component, 'updateContent'); setTimeout(() => { component.$el.querySelector('.gl-pagination .js-last-button a').click(); - expect(component.updateContent).toHaveBeenCalledWith({ scope: component.scope, page: '10' }); + expect(component.updateContent).toHaveBeenCalledWith({ + scope: component.scope, + page: '10', + }); done(); }, 0); }); - it('should make an API request when using tabs', (done) => { + it('should make an API request when using tabs', done => { setTimeout(() => { spyOn(component, 'updateContent'); component.$el.querySelector('.js-environments-tab-stopped').click(); @@ -134,20 +139,18 @@ describe('Environments Folder View', () => { component = mountComponent(Component, mockData); }); - it('should not render a table', (done) => { + it('should not render a table', done => { setTimeout(() => { - expect( - component.$el.querySelector('table'), - ).toBe(null); + expect(component.$el.querySelector('table')).toBe(null); done(); }, 0); }); - it('should render available tab with count 0', (done) => { + it('should render available tab with count 0', done => { setTimeout(() => { - expect( - component.$el.querySelector('.js-environments-tab-available').textContent, - ).toContain('Available'); + expect(component.$el.querySelector('.js-environments-tab-available').textContent).toContain( + 'Available', + ); expect( component.$el.querySelector('.js-environments-tab-available .badge').textContent, @@ -156,11 +159,11 @@ describe('Environments Folder View', () => { }, 0); }); - it('should render stopped tab with count 0', (done) => { + it('should render stopped tab with count 0', done => { setTimeout(() => { - expect( - component.$el.querySelector('.js-environments-tab-stopped').textContent, - ).toContain('Stopped'); + expect(component.$el.querySelector('.js-environments-tab-stopped').textContent).toContain( + 'Stopped', + ); expect( component.$el.querySelector('.js-environments-tab-stopped .badge').textContent, @@ -181,8 +184,9 @@ describe('Environments Folder View', () => { }); describe('updateContent', () => { - it('should set given parameters', (done) => { - component.updateContent({ scope: 'stopped', page: '4' }) + it('should set given parameters', done => { + component + .updateContent({ scope: 'stopped', page: '4' }) .then(() => { expect(component.page).toEqual('4'); expect(component.scope).toEqual('stopped'); diff --git a/spec/javascripts/issue_show/components/title_spec.js b/spec/javascripts/issue_show/components/title_spec.js index 9e6f236b687..9754c8a6755 100644 --- a/spec/javascripts/issue_show/components/title_spec.js +++ b/spec/javascripts/issue_show/components/title_spec.js @@ -25,25 +25,21 @@ describe('Title component', () => { }); it('renders title HTML', () => { - expect( - vm.$el.querySelector('.title').innerHTML.trim(), - ).toBe('Testing <img>'); + expect(vm.$el.querySelector('.title').innerHTML.trim()).toBe('Testing <img>'); }); - it('updates page title when changing titleHtml', (done) => { + it('updates page title when changing titleHtml', done => { spyOn(vm, 'setPageTitle'); vm.titleHtml = 'test'; Vue.nextTick(() => { - expect( - vm.setPageTitle, - ).toHaveBeenCalled(); + expect(vm.setPageTitle).toHaveBeenCalled(); done(); }); }); - it('animates title changes', (done) => { + it('animates title changes', done => { vm.titleHtml = 'test'; Vue.nextTick(() => { @@ -61,14 +57,12 @@ describe('Title component', () => { }); }); - it('updates page title after changing title', (done) => { + it('updates page title after changing title', done => { vm.titleHtml = 'changed'; vm.titleText = 'changed'; Vue.nextTick(() => { - expect( - document.querySelector('title').textContent.trim(), - ).toContain('changed'); + expect(document.querySelector('title').textContent.trim()).toContain('changed'); done(); }); diff --git a/spec/javascripts/jobs/components/job_app_spec.js b/spec/javascripts/jobs/components/job_app_spec.js index 288c06d6615..49c55202328 100644 --- a/spec/javascripts/jobs/components/job_app_spec.js +++ b/spec/javascripts/jobs/components/job_app_spec.js @@ -234,7 +234,7 @@ describe('Job App ', () => { ); done(); }, 0); - }) + }); }); it('does not renders stuck block when there are no runners', done => { diff --git a/spec/javascripts/jobs/store/actions_spec.js b/spec/javascripts/jobs/store/actions_spec.js index 45130b983e7..77b44995b12 100644 --- a/spec/javascripts/jobs/store/actions_spec.js +++ b/spec/javascripts/jobs/store/actions_spec.js @@ -68,41 +68,20 @@ describe('Job State actions', () => { describe('hideSidebar', () => { it('should commit HIDE_SIDEBAR mutation', done => { - testAction( - hideSidebar, - null, - mockedState, - [{ type: types.HIDE_SIDEBAR }], - [], - done, - ); + testAction(hideSidebar, null, mockedState, [{ type: types.HIDE_SIDEBAR }], [], done); }); }); describe('showSidebar', () => { it('should commit HIDE_SIDEBAR mutation', done => { - testAction( - showSidebar, - null, - mockedState, - [{ type: types.SHOW_SIDEBAR }], - [], - done, - ); + testAction(showSidebar, null, mockedState, [{ type: types.SHOW_SIDEBAR }], [], done); }); }); describe('toggleSidebar', () => { describe('when isSidebarOpen is true', () => { it('should dispatch hideSidebar', done => { - testAction( - toggleSidebar, - null, - mockedState, - [], - [{ type: 'hideSidebar' }], - done, - ); + testAction(toggleSidebar, null, mockedState, [], [{ type: 'hideSidebar' }], done); }); }); @@ -110,14 +89,7 @@ describe('Job State actions', () => { it('should dispatch showSidebar', done => { mockedState.isSidebarOpen = false; - testAction( - toggleSidebar, - null, - mockedState, - [], - [{ type: 'showSidebar' }], - done, - ); + testAction(toggleSidebar, null, mockedState, [], [{ type: 'showSidebar' }], done); }); }); }); diff --git a/spec/javascripts/jobs/store/getters_spec.js b/spec/javascripts/jobs/store/getters_spec.js index 34e9707eadd..4195d9d3680 100644 --- a/spec/javascripts/jobs/store/getters_spec.js +++ b/spec/javascripts/jobs/store/getters_spec.js @@ -180,7 +180,7 @@ describe('Job Store Getters', () => { it('returns true', () => { localState.job.runners = { available: true, - online: false + online: false, }; expect(getters.hasRunnersForProject(localState)).toEqual(true); @@ -191,7 +191,7 @@ describe('Job Store Getters', () => { it('returns false', () => { localState.job.runners = { available: false, - online: false + online: false, }; expect(getters.hasRunnersForProject(localState)).toEqual(false); @@ -202,7 +202,7 @@ describe('Job Store Getters', () => { it('returns false', () => { localState.job.runners = { available: false, - online: true + online: true, }; expect(getters.hasRunnersForProject(localState)).toEqual(false); diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js index 514d6ddeae5..0fb90c3b78c 100644 --- a/spec/javascripts/lib/utils/common_utils_spec.js +++ b/spec/javascripts/lib/utils/common_utils_spec.js @@ -35,9 +35,7 @@ describe('common_utils', () => { }); it('should decode params', () => { - expect( - commonUtils.urlParamsToArray('?label_name%5B%5D=test')[0], - ).toBe('label_name[]=test'); + expect(commonUtils.urlParamsToArray('?label_name%5B%5D=test')[0]).toBe('label_name[]=test'); }); it('should remove the question mark from the search params', () => { @@ -49,25 +47,19 @@ describe('common_utils', () => { describe('urlParamsToObject', () => { it('parses path for label with trailing +', () => { - expect( - commonUtils.urlParamsToObject('label_name[]=label%2B', {}), - ).toEqual({ + expect(commonUtils.urlParamsToObject('label_name[]=label%2B', {})).toEqual({ label_name: ['label+'], }); }); it('parses path for milestone with trailing +', () => { - expect( - commonUtils.urlParamsToObject('milestone_title=A%2B', {}), - ).toEqual({ + expect(commonUtils.urlParamsToObject('milestone_title=A%2B', {})).toEqual({ milestone_title: 'A+', }); }); it('parses path for search terms with spaces', () => { - expect( - commonUtils.urlParamsToObject('search=two+words', {}), - ).toEqual({ + expect(commonUtils.urlParamsToObject('search=two+words', {})).toEqual({ search: 'two words', }); }); @@ -187,7 +179,11 @@ describe('common_utils', () => { describe('parseQueryStringIntoObject', () => { it('should return object with query parameters', () => { - expect(commonUtils.parseQueryStringIntoObject('scope=all&page=2')).toEqual({ scope: 'all', page: '2' }); + expect(commonUtils.parseQueryStringIntoObject('scope=all&page=2')).toEqual({ + scope: 'all', + page: '2', + }); + expect(commonUtils.parseQueryStringIntoObject('scope=all')).toEqual({ scope: 'all' }); expect(commonUtils.parseQueryStringIntoObject()).toEqual({}); }); @@ -211,7 +207,9 @@ describe('common_utils', () => { describe('buildUrlWithCurrentLocation', () => { it('should build an url with current location and given parameters', () => { expect(commonUtils.buildUrlWithCurrentLocation()).toEqual(window.location.pathname); - expect(commonUtils.buildUrlWithCurrentLocation('?page=2')).toEqual(`${window.location.pathname}?page=2`); + expect(commonUtils.buildUrlWithCurrentLocation('?page=2')).toEqual( + `${window.location.pathname}?page=2`, + ); }); }); @@ -266,21 +264,24 @@ describe('common_utils', () => { }); describe('normalizeCRLFHeaders', () => { - beforeEach(function () { - this.CLRFHeaders = 'a-header: a-value\nAnother-Header: ANOTHER-VALUE\nLaSt-HeAdEr: last-VALUE'; + beforeEach(function() { + this.CLRFHeaders = + 'a-header: a-value\nAnother-Header: ANOTHER-VALUE\nLaSt-HeAdEr: last-VALUE'; spyOn(String.prototype, 'split').and.callThrough(); this.normalizeCRLFHeaders = commonUtils.normalizeCRLFHeaders(this.CLRFHeaders); }); - it('should split by newline', function () { + it('should split by newline', function() { expect(String.prototype.split).toHaveBeenCalledWith('\n'); }); - it('should split by colon+space for each header', function () { - expect(String.prototype.split.calls.allArgs().filter(args => args[0] === ': ').length).toBe(3); + it('should split by colon+space for each header', function() { + expect(String.prototype.split.calls.allArgs().filter(args => args[0] === ': ').length).toBe( + 3, + ); }); - it('should return a normalized headers object', function () { + it('should return a normalized headers object', function() { expect(this.normalizeCRLFHeaders).toEqual({ 'A-HEADER': 'a-value', 'ANOTHER-HEADER': 'ANOTHER-VALUE', @@ -359,67 +360,79 @@ describe('common_utils', () => { spyOn(window, 'setTimeout').and.callFake(cb => origSetTimeout(cb, 0)); }); - it('solves the promise from the callback', (done) => { + it('solves the promise from the callback', done => { const expectedResponseValue = 'Success!'; - commonUtils.backOff((next, stop) => ( - new Promise((resolve) => { - resolve(expectedResponseValue); - }).then((resp) => { - stop(resp); + commonUtils + .backOff((next, stop) => + new Promise(resolve => { + resolve(expectedResponseValue); + }) + .then(resp => { + stop(resp); + }) + .catch(done.fail), + ) + .then(respBackoff => { + expect(respBackoff).toBe(expectedResponseValue); + done(); }) - ).catch(done.fail)).then((respBackoff) => { - expect(respBackoff).toBe(expectedResponseValue); - done(); - }).catch(done.fail); + .catch(done.fail); }); - it('catches the rejected promise from the callback ', (done) => { + it('catches the rejected promise from the callback ', done => { const errorMessage = 'Mistakes were made!'; - commonUtils.backOff((next, stop) => { - new Promise((resolve, reject) => { - reject(new Error(errorMessage)); - }).then((resp) => { - stop(resp); - }).catch(err => stop(err)); - }).catch((errBackoffResp) => { - expect(errBackoffResp instanceof Error).toBe(true); - expect(errBackoffResp.message).toBe(errorMessage); - done(); - }); + commonUtils + .backOff((next, stop) => { + new Promise((resolve, reject) => { + reject(new Error(errorMessage)); + }) + .then(resp => { + stop(resp); + }) + .catch(err => stop(err)); + }) + .catch(errBackoffResp => { + expect(errBackoffResp instanceof Error).toBe(true); + expect(errBackoffResp.message).toBe(errorMessage); + done(); + }); }); - it('solves the promise correctly after retrying a third time', (done) => { + it('solves the promise correctly after retrying a third time', done => { let numberOfCalls = 1; const expectedResponseValue = 'Success!'; - commonUtils.backOff((next, stop) => ( - Promise.resolve(expectedResponseValue) - .then((resp) => { - if (numberOfCalls < 3) { - numberOfCalls += 1; - next(); - } else { - stop(resp); - } - }) - ).catch(done.fail)).then((respBackoff) => { - const timeouts = window.setTimeout.calls.allArgs().map(([, timeout]) => timeout); + commonUtils + .backOff((next, stop) => + Promise.resolve(expectedResponseValue) + .then(resp => { + if (numberOfCalls < 3) { + numberOfCalls += 1; + next(); + } else { + stop(resp); + } + }) + .catch(done.fail), + ) + .then(respBackoff => { + const timeouts = window.setTimeout.calls.allArgs().map(([, timeout]) => timeout); - expect(timeouts).toEqual([2000, 4000]); - expect(respBackoff).toBe(expectedResponseValue); - done(); - }).catch(done.fail); + expect(timeouts).toEqual([2000, 4000]); + expect(respBackoff).toBe(expectedResponseValue); + done(); + }) + .catch(done.fail); }); - it('rejects the backOff promise after timing out', (done) => { - commonUtils.backOff(next => next(), 64000) - .catch((errBackoffResp) => { - const timeouts = window.setTimeout.calls.allArgs().map(([, timeout]) => timeout); + it('rejects the backOff promise after timing out', done => { + commonUtils.backOff(next => next(), 64000).catch(errBackoffResp => { + const timeouts = window.setTimeout.calls.allArgs().map(([, timeout]) => timeout); - expect(timeouts).toEqual([2000, 4000, 8000, 16000, 32000, 32000]); - expect(errBackoffResp instanceof Error).toBe(true); - expect(errBackoffResp.message).toBe('BACKOFF_TIMEOUT'); - done(); - }); + expect(timeouts).toEqual([2000, 4000, 8000, 16000, 32000, 32000]); + expect(errBackoffResp instanceof Error).toBe(true); + expect(errBackoffResp.message).toBe('BACKOFF_TIMEOUT'); + done(); + }); }); }); @@ -466,11 +479,14 @@ describe('common_utils', () => { }); describe('createOverlayIcon', () => { - it('should return the favicon with the overlay', (done) => { - commonUtils.createOverlayIcon(faviconDataUrl, overlayDataUrl).then((url) => { - expect(url).toEqual(faviconWithOverlayDataUrl); - done(); - }).catch(done.fail); + it('should return the favicon with the overlay', done => { + commonUtils + .createOverlayIcon(faviconDataUrl, overlayDataUrl) + .then(url => { + expect(url).toEqual(faviconWithOverlayDataUrl); + done(); + }) + .catch(done.fail); }); }); @@ -486,11 +502,16 @@ describe('common_utils', () => { document.body.removeChild(document.getElementById('favicon')); }); - it('should set page favicon to provided favicon overlay', (done) => { - commonUtils.setFaviconOverlay(overlayDataUrl).then(() => { - expect(document.getElementById('favicon').getAttribute('href')).toEqual(faviconWithOverlayDataUrl); - done(); - }).catch(done.fail); + it('should set page favicon to provided favicon overlay', done => { + commonUtils + .setFaviconOverlay(overlayDataUrl) + .then(() => { + expect(document.getElementById('favicon').getAttribute('href')).toEqual( + faviconWithOverlayDataUrl, + ); + done(); + }) + .catch(done.fail); }); }); @@ -512,24 +533,24 @@ describe('common_utils', () => { document.body.removeChild(document.getElementById('favicon')); }); - it('should reset favicon in case of error', (done) => { + it('should reset favicon in case of error', done => { mock.onGet(BUILD_URL).replyOnce(500); - commonUtils.setCiStatusFavicon(BUILD_URL) - .catch(() => { - const favicon = document.getElementById('favicon'); + commonUtils.setCiStatusFavicon(BUILD_URL).catch(() => { + const favicon = document.getElementById('favicon'); - expect(favicon.getAttribute('href')).toEqual(faviconDataUrl); - done(); - }); + expect(favicon.getAttribute('href')).toEqual(faviconDataUrl); + done(); + }); }); - it('should set page favicon to CI status favicon based on provided status', (done) => { + it('should set page favicon to CI status favicon based on provided status', done => { mock.onGet(BUILD_URL).reply(200, { favicon: overlayDataUrl, }); - commonUtils.setCiStatusFavicon(BUILD_URL) + commonUtils + .setCiStatusFavicon(BUILD_URL) .then(() => { const favicon = document.getElementById('favicon'); @@ -554,11 +575,15 @@ describe('common_utils', () => { }); it('should return the svg for a linked icon', () => { - expect(commonUtils.spriteIcon('test')).toEqual('<svg ><use xlink:href="icons.svg#test" /></svg>'); + expect(commonUtils.spriteIcon('test')).toEqual( + '<svg ><use xlink:href="icons.svg#test" /></svg>', + ); }); it('should set svg className when passed', () => { - expect(commonUtils.spriteIcon('test', 'fa fa-test')).toEqual('<svg class="fa fa-test"><use xlink:href="icons.svg#test" /></svg>'); + expect(commonUtils.spriteIcon('test', 'fa fa-test')).toEqual( + '<svg class="fa fa-test"><use xlink:href="icons.svg#test" /></svg>', + ); }); }); @@ -578,7 +603,7 @@ describe('common_utils', () => { const convertedObj = commonUtils.convertObjectPropsToCamelCase(mockObj); - Object.keys(convertedObj).forEach((prop) => { + Object.keys(convertedObj).forEach(prop => { expect(snakeRegEx.test(prop)).toBeFalsy(); expect(convertedObj[prop]).toBe(mockObj[mappings[prop]]); }); @@ -597,9 +622,7 @@ describe('common_utils', () => { }, }; - expect( - commonUtils.convertObjectPropsToCamelCase(obj), - ).toEqual({ + expect(commonUtils.convertObjectPropsToCamelCase(obj)).toEqual({ snakeKey: { child_snake_key: 'value', }, @@ -614,9 +637,7 @@ describe('common_utils', () => { }, }; - expect( - commonUtils.convertObjectPropsToCamelCase(obj, { deep: true }), - ).toEqual({ + expect(commonUtils.convertObjectPropsToCamelCase(obj, { deep: true })).toEqual({ snakeKey: { childSnakeKey: 'value', }, @@ -630,9 +651,7 @@ describe('common_utils', () => { }, ]; - expect( - commonUtils.convertObjectPropsToCamelCase(arr, { deep: true }), - ).toEqual([ + expect(commonUtils.convertObjectPropsToCamelCase(arr, { deep: true })).toEqual([ { childSnakeKey: 'value', }, @@ -648,9 +667,7 @@ describe('common_utils', () => { ], ]; - expect( - commonUtils.convertObjectPropsToCamelCase(arr, { deep: true }), - ).toEqual([ + expect(commonUtils.convertObjectPropsToCamelCase(arr, { deep: true })).toEqual([ [ { childSnakeKey: 'value', diff --git a/spec/javascripts/lib/utils/number_utility_spec.js b/spec/javascripts/lib/utils/number_utility_spec.js index a5099a2a3b8..94c6214c86a 100644 --- a/spec/javascripts/lib/utils/number_utility_spec.js +++ b/spec/javascripts/lib/utils/number_utility_spec.js @@ -1,4 +1,10 @@ -import { formatRelevantDigits, bytesToKiB, bytesToMiB, bytesToGiB, numberToHumanSize } from '~/lib/utils/number_utils'; +import { + formatRelevantDigits, + bytesToKiB, + bytesToMiB, + bytesToGiB, + numberToHumanSize, +} from '~/lib/utils/number_utils'; describe('Number Utils', () => { describe('formatRelevantDigits', () => { diff --git a/spec/javascripts/monitoring/graph/flag_spec.js b/spec/javascripts/monitoring/graph/flag_spec.js index a837b71db0b..038bfffd44f 100644 --- a/spec/javascripts/monitoring/graph/flag_spec.js +++ b/spec/javascripts/monitoring/graph/flag_spec.js @@ -2,7 +2,7 @@ import Vue from 'vue'; import GraphFlag from '~/monitoring/components/graph/flag.vue'; import { deploymentData } from '../mock_data'; -const createComponent = (propsData) => { +const createComponent = propsData => { const Component = Vue.extend(GraphFlag); return new Component({ @@ -51,8 +51,7 @@ describe('GraphFlag', () => { it('has a line at the currentXCoordinate', () => { component = createComponent(defaultValuesComponent); - expect(component.$el.style.left) - .toEqual(`${70 + component.currentXCoordinate}px`); + expect(component.$el.style.left).toEqual(`${70 + component.currentXCoordinate}px`); }); describe('Deployment flag', () => { @@ -62,9 +61,7 @@ describe('GraphFlag', () => { deploymentFlagData, }); - expect( - deploymentFlagComponent.$el.querySelector('.popover-title'), - ).toContainText('Deployed'); + expect(deploymentFlagComponent.$el.querySelector('.popover-title')).toContainText('Deployed'); }); it('contains the ref when a tag is available', () => { @@ -78,13 +75,13 @@ describe('GraphFlag', () => { }, }); - expect( - deploymentFlagComponent.$el.querySelector('.deploy-meta-content'), - ).toContainText('f5bcd1d9'); + expect(deploymentFlagComponent.$el.querySelector('.deploy-meta-content')).toContainText( + 'f5bcd1d9', + ); - expect( - deploymentFlagComponent.$el.querySelector('.deploy-meta-content'), - ).toContainText('1.0'); + expect(deploymentFlagComponent.$el.querySelector('.deploy-meta-content')).toContainText( + '1.0', + ); }); it('does not contain the ref when a tag is unavailable', () => { @@ -98,13 +95,13 @@ describe('GraphFlag', () => { }, }); - expect( - deploymentFlagComponent.$el.querySelector('.deploy-meta-content'), - ).toContainText('f5bcd1d9'); + expect(deploymentFlagComponent.$el.querySelector('.deploy-meta-content')).toContainText( + 'f5bcd1d9', + ); - expect( - deploymentFlagComponent.$el.querySelector('.deploy-meta-content'), - ).not.toContainText('1.0'); + expect(deploymentFlagComponent.$el.querySelector('.deploy-meta-content')).not.toContainText( + '1.0', + ); }); }); diff --git a/spec/javascripts/notes/components/discussion_filter_spec.js b/spec/javascripts/notes/components/discussion_filter_spec.js index 70dd5bb3be5..a81bdf618a3 100644 --- a/spec/javascripts/notes/components/discussion_filter_spec.js +++ b/spec/javascripts/notes/components/discussion_filter_spec.js @@ -11,11 +11,13 @@ describe('DiscussionFilter component', () => { beforeEach(() => { store = createStore(); - const discussions = [{ - ...discussionMock, - id: discussionMock.id, - notes: [{ ...discussionMock.notes[0], resolvable: true, resolved: true }], - }]; + const discussions = [ + { + ...discussionMock, + id: discussionMock.id, + notes: [{ ...discussionMock.notes[0], resolvable: true, resolved: true }], + }, + ]; const Component = Vue.extend(DiscussionFilter); const defaultValue = discussionFiltersMock[0].value; @@ -35,11 +37,15 @@ describe('DiscussionFilter component', () => { }); it('renders the all filters', () => { - expect(vm.$el.querySelectorAll('.dropdown-menu li').length).toEqual(discussionFiltersMock.length); + expect(vm.$el.querySelectorAll('.dropdown-menu li').length).toEqual( + discussionFiltersMock.length, + ); }); it('renders the default selected item', () => { - expect(vm.$el.querySelector('#discussion-filter-dropdown').textContent.trim()).toEqual(discussionFiltersMock[0].title); + expect(vm.$el.querySelector('#discussion-filter-dropdown').textContent.trim()).toEqual( + discussionFiltersMock[0].title, + ); }); it('updates to the selected item', () => { diff --git a/spec/javascripts/notes/components/note_app_spec.js b/spec/javascripts/notes/components/note_app_spec.js index 06b30375306..3e289a6b8e6 100644 --- a/spec/javascripts/notes/components/note_app_spec.js +++ b/spec/javascripts/notes/components/note_app_spec.js @@ -97,7 +97,8 @@ describe('note_app', () => { }); it('should render list of notes', done => { - const note = mockData.INDIVIDUAL_NOTE_RESPONSE_MAP.GET[ + const note = + mockData.INDIVIDUAL_NOTE_RESPONSE_MAP.GET[ '/gitlab-org/gitlab-ce/issues/26/discussions.json' ][0].notes[0]; diff --git a/spec/javascripts/pipelines/graph/graph_component_spec.js b/spec/javascripts/pipelines/graph/graph_component_spec.js index b6fa4272c8b..96a2d5f62fa 100644 --- a/spec/javascripts/pipelines/graph/graph_component_spec.js +++ b/spec/javascripts/pipelines/graph/graph_component_spec.js @@ -40,7 +40,9 @@ describe('graph component', () => { ).toEqual(true); expect( - component.$el.querySelector('.stage-column:nth-child(2) .build:nth-child(1)').classList.contains('left-connector'), + component.$el + .querySelector('.stage-column:nth-child(2) .build:nth-child(1)') + .classList.contains('left-connector'), ).toEqual(true); expect(component.$el.querySelector('loading-icon')).toBe(null); @@ -56,7 +58,9 @@ describe('graph component', () => { pipeline: graphJSON, }); - expect(component.$el.querySelector('.stage-column:nth-child(2) .stage-name').textContent.trim()).toEqual('Deploy <img src=x onerror=alert(document.domain)>'); + expect( + component.$el.querySelector('.stage-column:nth-child(2) .stage-name').textContent.trim(), + ).toEqual('Deploy <img src=x onerror=alert(document.domain)>'); }); }); }); diff --git a/spec/javascripts/sidebar/assignees_spec.js b/spec/javascripts/sidebar/assignees_spec.js index e7f8f4f9936..eced4925489 100644 --- a/spec/javascripts/sidebar/assignees_spec.js +++ b/spec/javascripts/sidebar/assignees_spec.js @@ -78,9 +78,7 @@ describe('Assignee component', () => { component = new AssigneeComponent({ propsData: { rootPath: 'http://localhost:3000', - users: [ - UsersMock.user, - ], + users: [UsersMock.user], editable: false, }, }).$mount(); @@ -90,7 +88,10 @@ describe('Assignee component', () => { expect(collapsed.childElementCount).toEqual(1); expect(assignee.querySelector('.avatar').getAttribute('src')).toEqual(UsersMock.user.avatar); - expect(assignee.querySelector('.avatar').getAttribute('alt')).toEqual(`${UsersMock.user.name}'s avatar`); + expect(assignee.querySelector('.avatar').getAttribute('alt')).toEqual( + `${UsersMock.user.name}'s avatar`, + ); + expect(assignee.querySelector('.author').innerText.trim()).toEqual(UsersMock.user.name); }); @@ -98,34 +99,38 @@ describe('Assignee component', () => { component = new AssigneeComponent({ propsData: { rootPath: 'http://localhost:3000/', - users: [ - UsersMock.user, - ], + users: [UsersMock.user], editable: true, }, }).$mount(); expect(component.$el.querySelector('.author-link')).not.toBeNull(); // The image - expect(component.$el.querySelector('.author-link img').getAttribute('src')).toEqual(UsersMock.user.avatar); + expect(component.$el.querySelector('.author-link img').getAttribute('src')).toEqual( + UsersMock.user.avatar, + ); // Author name - expect(component.$el.querySelector('.author-link .author').innerText.trim()).toEqual(UsersMock.user.name); + expect(component.$el.querySelector('.author-link .author').innerText.trim()).toEqual( + UsersMock.user.name, + ); // Username - expect(component.$el.querySelector('.author-link .username').innerText.trim()).toEqual(`@${UsersMock.user.username}`); + expect(component.$el.querySelector('.author-link .username').innerText.trim()).toEqual( + `@${UsersMock.user.username}`, + ); }); it('has the root url present in the assigneeUrl method', () => { component = new AssigneeComponent({ propsData: { rootPath: 'http://localhost:3000/', - users: [ - UsersMock.user, - ], + users: [UsersMock.user], editable: true, }, }).$mount(); - expect(component.assigneeUrl(UsersMock.user).indexOf('http://localhost:3000/')).not.toEqual(-1); + expect(component.assigneeUrl(UsersMock.user).indexOf('http://localhost:3000/')).not.toEqual( + -1, + ); }); }); @@ -147,13 +152,19 @@ describe('Assignee component', () => { const first = collapsed.children[0]; expect(first.querySelector('.avatar').getAttribute('src')).toEqual(users[0].avatar); - expect(first.querySelector('.avatar').getAttribute('alt')).toEqual(`${users[0].name}'s avatar`); + expect(first.querySelector('.avatar').getAttribute('alt')).toEqual( + `${users[0].name}'s avatar`, + ); + expect(first.querySelector('.author').innerText.trim()).toEqual(users[0].name); const second = collapsed.children[1]; expect(second.querySelector('.avatar').getAttribute('src')).toEqual(users[1].avatar); - expect(second.querySelector('.avatar').getAttribute('alt')).toEqual(`${users[1].name}'s avatar`); + expect(second.querySelector('.avatar').getAttribute('alt')).toEqual( + `${users[1].name}'s avatar`, + ); + expect(second.querySelector('.author').innerText.trim()).toEqual(users[1].name); }); @@ -174,7 +185,10 @@ describe('Assignee component', () => { const first = collapsed.children[0]; expect(first.querySelector('.avatar').getAttribute('src')).toEqual(users[0].avatar); - expect(first.querySelector('.avatar').getAttribute('alt')).toEqual(`${users[0].name}'s avatar`); + expect(first.querySelector('.avatar').getAttribute('alt')).toEqual( + `${users[0].name}'s avatar`, + ); + expect(first.querySelector('.author').innerText.trim()).toEqual(users[0].name); const second = collapsed.children[1]; @@ -196,7 +210,7 @@ describe('Assignee component', () => { expect(component.$el.querySelector('.user-list-more')).toBe(null); }); - it('Shows the "show-less" assignees label', (done) => { + it('Shows the "show-less" assignees label', done => { const users = UsersMockHelper.createNumberRandomUsers(6); component = new AssigneeComponent({ propsData: { @@ -206,21 +220,26 @@ describe('Assignee component', () => { }, }).$mount(); - expect(component.$el.querySelectorAll('.user-item').length).toEqual(component.defaultRenderCount); + expect(component.$el.querySelectorAll('.user-item').length).toEqual( + component.defaultRenderCount, + ); + expect(component.$el.querySelector('.user-list-more')).not.toBe(null); const usersLabelExpectation = users.length - component.defaultRenderCount; - expect(component.$el.querySelector('.user-list-more .btn-link').innerText.trim()) - .not.toBe(`+${usersLabelExpectation} more`); + expect(component.$el.querySelector('.user-list-more .btn-link').innerText.trim()).not.toBe( + `+${usersLabelExpectation} more`, + ); component.toggleShowLess(); Vue.nextTick(() => { - expect(component.$el.querySelector('.user-list-more .btn-link').innerText.trim()) - .toBe('- show less'); + expect(component.$el.querySelector('.user-list-more .btn-link').innerText.trim()).toBe( + '- show less', + ); done(); }); }); - it('Shows the "show-less" when "n+ more " label is clicked', (done) => { + it('Shows the "show-less" when "n+ more " label is clicked', done => { const users = UsersMockHelper.createNumberRandomUsers(6); component = new AssigneeComponent({ propsData: { @@ -232,8 +251,9 @@ describe('Assignee component', () => { component.$el.querySelector('.user-list-more .btn-link').click(); Vue.nextTick(() => { - expect(component.$el.querySelector('.user-list-more .btn-link').innerText.trim()) - .toBe('- show less'); + expect(component.$el.querySelector('.user-list-more .btn-link').innerText.trim()).toBe( + '- show less', + ); done(); }); }); @@ -264,16 +284,18 @@ describe('Assignee component', () => { }); it('shows "+1 more" label', () => { - expect(component.$el.querySelector('.user-list-more .btn-link').innerText.trim()) - .toBe('+ 1 more'); + expect(component.$el.querySelector('.user-list-more .btn-link').innerText.trim()).toBe( + '+ 1 more', + ); }); - it('shows "show less" label', (done) => { + it('shows "show less" label', done => { component.toggleShowLess(); Vue.nextTick(() => { - expect(component.$el.querySelector('.user-list-more .btn-link').innerText.trim()) - .toBe('- show less'); + expect(component.$el.querySelector('.user-list-more .btn-link').innerText.trim()).toBe( + '- show less', + ); done(); }); }); diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js index ea8007d2029..6c7637eed13 100644 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js +++ b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js @@ -22,6 +22,7 @@ describe('MRWidgetPipeline', () => { pipeline: mockData.pipeline, ciStatus: 'success', hasCi: true, + troubleshootingDocsPath: 'help', }); expect(vm.hasPipeline).toEqual(true); @@ -30,6 +31,7 @@ describe('MRWidgetPipeline', () => { it('should return false when there is no pipeline', () => { vm = mountComponent(Component, { pipeline: {}, + troubleshootingDocsPath: 'help', }); expect(vm.hasPipeline).toEqual(false); @@ -42,6 +44,7 @@ describe('MRWidgetPipeline', () => { pipeline: mockData.pipeline, hasCi: true, ciStatus: 'success', + troubleshootingDocsPath: 'help', }); expect(vm.hasCIError).toEqual(false); @@ -52,6 +55,7 @@ describe('MRWidgetPipeline', () => { pipeline: mockData.pipeline, hasCi: true, ciStatus: null, + troubleshootingDocsPath: 'help', }); expect(vm.hasCIError).toEqual(true); @@ -65,11 +69,12 @@ describe('MRWidgetPipeline', () => { pipeline: mockData.pipeline, hasCi: true, ciStatus: null, + troubleshootingDocsPath: 'help', }); - expect( - vm.$el.querySelector('.media-body').textContent.trim(), - ).toEqual('Could not connect to the CI server. Please check your settings and try again'); + expect(vm.$el.querySelector('.media-body').textContent.trim()).toContain( + 'Could not retrieve the pipeline status. For troubleshooting steps, read the <a href="help">documentation.</a>', + ); }); describe('with a pipeline', () => { @@ -78,38 +83,41 @@ describe('MRWidgetPipeline', () => { pipeline: mockData.pipeline, hasCi: true, ciStatus: 'success', + troubleshootingDocsPath: 'help', }); }); it('should render pipeline ID', () => { - expect( - vm.$el.querySelector('.pipeline-id').textContent.trim(), - ).toEqual(`#${mockData.pipeline.id}`); + expect(vm.$el.querySelector('.pipeline-id').textContent.trim()).toEqual( + `#${mockData.pipeline.id}`, + ); }); it('should render pipeline status and commit id', () => { - expect( - vm.$el.querySelector('.media-body').textContent.trim(), - ).toContain(mockData.pipeline.details.status.label); + expect(vm.$el.querySelector('.media-body').textContent.trim()).toContain( + mockData.pipeline.details.status.label, + ); - expect( - vm.$el.querySelector('.js-commit-link').textContent.trim(), - ).toEqual(mockData.pipeline.commit.short_id); + expect(vm.$el.querySelector('.js-commit-link').textContent.trim()).toEqual( + mockData.pipeline.commit.short_id, + ); - expect( - vm.$el.querySelector('.js-commit-link').getAttribute('href'), - ).toEqual(mockData.pipeline.commit.commit_path); + expect(vm.$el.querySelector('.js-commit-link').getAttribute('href')).toEqual( + mockData.pipeline.commit.commit_path, + ); }); it('should render pipeline graph', () => { expect(vm.$el.querySelector('.mr-widget-pipeline-graph')).toBeDefined(); - expect(vm.$el.querySelectorAll('.stage-container').length).toEqual(mockData.pipeline.details.stages.length); + expect(vm.$el.querySelectorAll('.stage-container').length).toEqual( + mockData.pipeline.details.stages.length, + ); }); it('should render coverage information', () => { - expect( - vm.$el.querySelector('.media-body').textContent, - ).toContain(`Coverage ${mockData.pipeline.coverage}`); + expect(vm.$el.querySelector('.media-body').textContent).toContain( + `Coverage ${mockData.pipeline.coverage}`, + ); }); }); @@ -122,34 +130,35 @@ describe('MRWidgetPipeline', () => { pipeline: mockCopy.pipeline, hasCi: true, ciStatus: 'success', + troubleshootingDocsPath: 'help', }); }); it('should render pipeline ID', () => { - expect( - vm.$el.querySelector('.pipeline-id').textContent.trim(), - ).toEqual(`#${mockData.pipeline.id}`); + expect(vm.$el.querySelector('.pipeline-id').textContent.trim()).toEqual( + `#${mockData.pipeline.id}`, + ); }); it('should render pipeline status', () => { - expect( - vm.$el.querySelector('.media-body').textContent.trim(), - ).toContain(mockData.pipeline.details.status.label); + expect(vm.$el.querySelector('.media-body').textContent.trim()).toContain( + mockData.pipeline.details.status.label, + ); - expect( - vm.$el.querySelector('.js-commit-link'), - ).toBeNull(); + expect(vm.$el.querySelector('.js-commit-link')).toBeNull(); }); it('should render pipeline graph', () => { expect(vm.$el.querySelector('.mr-widget-pipeline-graph')).toBeDefined(); - expect(vm.$el.querySelectorAll('.stage-container').length).toEqual(mockData.pipeline.details.stages.length); + expect(vm.$el.querySelectorAll('.stage-container').length).toEqual( + mockData.pipeline.details.stages.length, + ); }); it('should render coverage information', () => { - expect( - vm.$el.querySelector('.media-body').textContent, - ).toContain(`Coverage ${mockData.pipeline.coverage}`); + expect(vm.$el.querySelector('.media-body').textContent).toContain( + `Coverage ${mockData.pipeline.coverage}`, + ); }); }); @@ -162,11 +171,10 @@ describe('MRWidgetPipeline', () => { pipeline: mockCopy.pipeline, hasCi: true, ciStatus: 'success', + troubleshootingDocsPath: 'help', }); - expect( - vm.$el.querySelector('.media-body').textContent, - ).not.toContain('Coverage'); + expect(vm.$el.querySelector('.media-body').textContent).not.toContain('Coverage'); }); }); @@ -179,6 +187,7 @@ describe('MRWidgetPipeline', () => { pipeline: mockCopy.pipeline, hasCi: true, ciStatus: 'success', + troubleshootingDocsPath: 'help', }); expect(vm.$el.querySelector('.js-mini-pipeline-graph')).toEqual(null); diff --git a/spec/javascripts/vue_mr_widget/mock_data.js b/spec/javascripts/vue_mr_widget/mock_data.js index 7fd1a2350f7..17554c4fe42 100644 --- a/spec/javascripts/vue_mr_widget/mock_data.js +++ b/spec/javascripts/vue_mr_widget/mock_data.js @@ -218,5 +218,7 @@ export default { diverged_commits_count: 0, only_allow_merge_if_pipeline_succeeds: false, commit_change_content_path: '/root/acets-app/merge_requests/22/commit_change_content', - merge_commit_path: 'http://localhost:3000/root/acets-app/commit/53027d060246c8f47e4a9310fb332aa52f221775', + merge_commit_path: + 'http://localhost:3000/root/acets-app/commit/53027d060246c8f47e4a9310fb332aa52f221775', + troubleshooting_docs_path: 'help', }; diff --git a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js index 27b6c91e154..09fbe87b27e 100644 --- a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js +++ b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js @@ -454,7 +454,7 @@ describe('mrWidgetOptions', () => { deployed_at: '2017-03-22T22:44:42.258Z', deployed_at_formatted: 'Mar 22, 2017 10:44pm', changes, - status: 'success' + status: 'success', }; beforeEach(done => { @@ -607,33 +607,36 @@ describe('mrWidgetOptions', () => { describe('with post merge deployments', () => { beforeEach(done => { - vm.mr.postMergeDeployments = [{ - id: 15, - name: 'review/diplo', - url: '/root/acets-review-apps/environments/15', - stop_url: '/root/acets-review-apps/environments/15/stop', - metrics_url: '/root/acets-review-apps/environments/15/deployments/1/metrics', - metrics_monitoring_url: '/root/acets-review-apps/environments/15/metrics', - external_url: 'http://diplo.', - external_url_formatted: 'diplo.', - deployed_at: '2017-03-22T22:44:42.258Z', - deployed_at_formatted: 'Mar 22, 2017 10:44pm', - changes: [ - { - path: 'index.html', - external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/index.html', - }, - { - path: 'imgs/gallery.html', - external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/imgs/gallery.html', - }, - { - path: 'about/', - external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/about/', - }, - ], - status: 'success' - }]; + vm.mr.postMergeDeployments = [ + { + id: 15, + name: 'review/diplo', + url: '/root/acets-review-apps/environments/15', + stop_url: '/root/acets-review-apps/environments/15/stop', + metrics_url: '/root/acets-review-apps/environments/15/deployments/1/metrics', + metrics_monitoring_url: '/root/acets-review-apps/environments/15/metrics', + external_url: 'http://diplo.', + external_url_formatted: 'diplo.', + deployed_at: '2017-03-22T22:44:42.258Z', + deployed_at_formatted: 'Mar 22, 2017 10:44pm', + changes: [ + { + path: 'index.html', + external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/index.html', + }, + { + path: 'imgs/gallery.html', + external_url: + 'http://root-master-patch-91341.volatile-watch.surge.sh/imgs/gallery.html', + }, + { + path: 'about/', + external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/about/', + }, + ], + status: 'success', + }, + ]; vm.$nextTick(done); }); diff --git a/spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js b/spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js index 3483b7d387d..c507a97d37e 100644 --- a/spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js +++ b/spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js @@ -12,7 +12,7 @@ describe('collapsedGroupedDatePicker', () => { }); describe('toggleCollapse events', () => { - beforeEach((done) => { + beforeEach(done => { spyOn(vm, 'toggleSidebar'); vm.minDate = new Date('07/17/2016'); Vue.nextTick(done); @@ -26,7 +26,7 @@ describe('collapsedGroupedDatePicker', () => { }); describe('minDate and maxDate', () => { - beforeEach((done) => { + beforeEach(done => { vm.minDate = new Date('07/17/2016'); vm.maxDate = new Date('07/17/2017'); Vue.nextTick(done); @@ -42,7 +42,7 @@ describe('collapsedGroupedDatePicker', () => { }); describe('minDate', () => { - beforeEach((done) => { + beforeEach(done => { vm.minDate = new Date('07/17/2016'); Vue.nextTick(done); }); @@ -56,7 +56,7 @@ describe('collapsedGroupedDatePicker', () => { }); describe('maxDate', () => { - beforeEach((done) => { + beforeEach(done => { vm.maxDate = new Date('07/17/2017'); Vue.nextTick(done); }); diff --git a/spec/javascripts/vue_shared/components/sidebar/date_picker_spec.js b/spec/javascripts/vue_shared/components/sidebar/date_picker_spec.js index 1581f4e3eb1..805ba7b9947 100644 --- a/spec/javascripts/vue_shared/components/sidebar/date_picker_spec.js +++ b/spec/javascripts/vue_shared/components/sidebar/date_picker_spec.js @@ -41,7 +41,7 @@ describe('sidebarDatePicker', () => { expect(vm.$el.querySelector('.value-content span').innerText.trim()).toEqual('None'); }); - it('should render date-picker when editing', (done) => { + it('should render date-picker when editing', done => { vm.editing = true; Vue.nextTick(() => { expect(vm.$el.querySelector('.pika-label')).toBeDefined(); @@ -50,7 +50,7 @@ describe('sidebarDatePicker', () => { }); describe('editable', () => { - beforeEach((done) => { + beforeEach(done => { vm.editable = true; Vue.nextTick(done); }); @@ -59,7 +59,7 @@ describe('sidebarDatePicker', () => { expect(vm.$el.querySelector('.title .btn-blank').innerText.trim()).toEqual('Edit'); }); - it('should enable editing when edit button is clicked', (done) => { + it('should enable editing when edit button is clicked', done => { vm.isLoading = false; Vue.nextTick(() => { vm.$el.querySelector('.title .btn-blank').click(); @@ -70,7 +70,7 @@ describe('sidebarDatePicker', () => { }); }); - it('should render date if selectedDate', (done) => { + it('should render date if selectedDate', done => { vm.selectedDate = new Date('07/07/2017'); Vue.nextTick(() => { expect(vm.$el.querySelector('.value-content strong').innerText.trim()).toEqual('Jul 7, 2017'); @@ -79,7 +79,7 @@ describe('sidebarDatePicker', () => { }); describe('selectedDate and editable', () => { - beforeEach((done) => { + beforeEach(done => { vm.selectedDate = new Date('07/07/2017'); vm.editable = true; Vue.nextTick(done); @@ -100,7 +100,7 @@ describe('sidebarDatePicker', () => { }); describe('showToggleSidebar', () => { - beforeEach((done) => { + beforeEach(done => { vm.showToggleSidebar = true; Vue.nextTick(done); }); diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js index 9a691116cf8..804b33422bd 100644 --- a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js +++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js @@ -49,7 +49,9 @@ describe('DropdownValueCollapsedComponent', () => { const vmMoreLabels = createComponent(mockMoreLabels); - expect(vmMoreLabels.labelsList).toBe('Foo Label, Foo Label, Foo Label, Foo Label, Foo Label, and 2 more'); + expect(vmMoreLabels.labelsList).toBe( + 'Foo Label, Foo Label, Foo Label, Foo Label, Foo Label, and 2 more', + ); vmMoreLabels.$destroy(); }); diff --git a/spec/presenters/project_presenter_spec.rb b/spec/presenters/project_presenter_spec.rb index 3eb2f149311..7b0192fa9c8 100644 --- a/spec/presenters/project_presenter_spec.rb +++ b/spec/presenters/project_presenter_spec.rb @@ -239,7 +239,7 @@ describe ProjectPresenter do expect(presenter.new_file_anchor_data).to have_attributes(enabled: false, label: "New file", link: presenter.project_new_blob_path(project, 'master'), - class_modifier: 'new') + class_modifier: 'success') end it 'returns nil if user cannot push' do diff --git a/vendor/licenses.csv b/vendor/licenses.csv index 9083fe076c3..5a7f7c0ebd1 100644 --- a/vendor/licenses.csv +++ b/vendor/licenses.csv @@ -67,7 +67,7 @@ @babel/template,7.1.2,MIT @babel/traverse,7.1.0,MIT @babel/types,7.1.2,MIT -@gitlab-org/gitlab-svgs,1.31.0,MIT +@gitlab/svgs,1.35.0,MIT @gitlab-org/gitlab-ui,1.8.0,MIT @sindresorhus/is,0.7.0,MIT @types/jquery,2.0.48,MIT diff --git a/yarn.lock b/yarn.lock index 0124ee0572d..38e0f9d6201 100644 --- a/yarn.lock +++ b/yarn.lock @@ -621,11 +621,6 @@ resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.32.0.tgz#a65ab7724fa7d55be8e5cc9b2dbe3f0757432fd3" integrity sha512-L3o8dFUd2nSkVZBwh2hCJWzNzADJ3dTBZxamND8NLosZK9/ohNhccmsQOZGyMCUHaOzm4vifaaXkAXh04UtMKA== -"@gitlab-org/gitlab-svgs@^1.33.0": - version "1.33.0" - resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.33.0.tgz#068566e8ee00795f6f09f58236f08e1716f9f04a" - integrity sha512-8ajtUHk6gQ1xosL/CO5IzHSFM/t18hx5pfzQ3cd0VuQXcyR6QKGuXTLwbYdmJDYOw1Etoo5DqDWxPEClHyZpiA== - "@gitlab-org/gitlab-ui@^1.10.0": version "1.10.0" resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-ui/-/gitlab-ui-1.10.0.tgz#3ac54ecaa25ea558324f0b382c97fcf9e3c4f0a5" @@ -648,6 +643,11 @@ eslint-plugin-promise "^4.0.1" eslint-plugin-vue "^5.0.0-beta.3" +"@gitlab/svgs@^1.35.0": + version "1.35.0" + resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.35.0.tgz#01b6a0948bb3897fbbac9f50ce23c559c514ea0e" + integrity sha512-XKrTniSYKG5U8+8ZqDJqoW8ORahuPBfHrfsC1dHBPvo1xA/QGJxlpUdeqSFw2O19h481ut4yW1dF+OFpIa/mrw== + "@sindresorhus/is@^0.7.0": version "0.7.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd" |