diff options
108 files changed, 922 insertions, 897 deletions
diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 70cce05d2b5..00000000000 --- a/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -*.js.es6 gitlab-language=javascript diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9aecdde266d..ea273334d4c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -58,7 +58,7 @@ stages: <<: *dedicated-runner <<: *use-db script: - - JOB_NAME=( $CI_BUILD_NAME ) + - JOB_NAME=( $CI_JOB_NAME ) - export CI_NODE_INDEX=${JOB_NAME[1]} - export CI_NODE_TOTAL=${JOB_NAME[2]} - export KNAPSACK_REPORT_PATH=knapsack/rspec_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json @@ -78,7 +78,7 @@ stages: <<: *dedicated-runner <<: *use-db script: - - JOB_NAME=( $CI_BUILD_NAME ) + - JOB_NAME=( $CI_JOB_NAME ) - export CI_NODE_INDEX=${JOB_NAME[1]} - export CI_NODE_TOTAL=${JOB_NAME[2]} - export KNAPSACK_REPORT_PATH=knapsack/spinach_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json @@ -180,7 +180,7 @@ spinach 9 10: *spinach-knapsack <<: *dedicated-runner stage: test script: - - bundle exec $CI_BUILD_NAME + - bundle exec $CI_JOB_NAME rubocop: <<: *ruby-static-analysis @@ -211,7 +211,7 @@ rake ee_compat_check: - ee_compat_check/repo/ - vendor/ruby artifacts: - name: "${CI_BUILD_NAME}_${CI_BUILD_REF_NAME}_${CI_BUILD_REF}" + name: "${CI_JOB_NAME}_${CI_COMIT_REF_NAME}_${CI_COMMIT_REF}" when: on_failure expire_in: 10d paths: @@ -330,7 +330,7 @@ migration paths: - sed -i 's/localhost/redis/g' config/resque.yml - bundle install --without postgres production --jobs $(nproc) $FLAGS --retry=3 - bundle exec rake db:drop db:create db:schema:load db:seed_fu - - git checkout $CI_BUILD_REF + - git checkout $CI_COMMIT_REF - source scripts/prepare_build.sh - bundle exec rake db:migrate @@ -368,7 +368,7 @@ lint:javascript:report: stage: post-test before_script: [] script: - - find app/ spec/ -name '*.js' -or -name '*.js.es6' -exec sed --in-place 's|/\* eslint-disable .*\*/||' {} \; # run report over all files + - find app/ spec/ -name '*.js' -exec sed --in-place 's|/\* eslint-disable .*\*/||' {} \; # run report over all files - yarn run eslint-report || true # ignore exit code artifacts: name: eslint-report @@ -402,7 +402,7 @@ notify:slack: SETUP_DB: "false" USE_BUNDLE_INSTALL: "false" script: - - ./scripts/notify_slack.sh "#development" "Build on \`$CI_BUILD_REF_NAME\` failed! Commit \`$(git log -1 --oneline)\` See <https://gitlab.com/gitlab-org/$(basename "$PWD")/commit/"$CI_BUILD_REF"/pipelines>" + - ./scripts/notify_slack.sh "#development" "Build on \`$CI_COMMIT_REF_NAME\` failed! Commit \`$(git log -1 --oneline)\` See <https://gitlab.com/gitlab-org/$(basename "$PWD")/commit/"$CI_COMMIT_REF"/pipelines>" when: on_failure only: - master@gitlab-org/gitlab-ce diff --git a/Gemfile.lock b/Gemfile.lock index e38f45b8e98..c60c045a4c2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -304,7 +304,7 @@ GEM multi_json (~> 1.10) retriable (~> 1.4) signet (~> 0.6) - google-protobuf (3.2.0.1) + google-protobuf (3.2.0) googleauth (0.5.1) faraday (~> 0.9) jwt (~> 1.4) diff --git a/app/assets/javascripts/boards/components/modal/filters.js b/app/assets/javascripts/boards/components/modal/filters.js index 6de06811d94..bd394a2318c 100644 --- a/app/assets/javascripts/boards/components/modal/filters.js +++ b/app/assets/javascripts/boards/components/modal/filters.js @@ -1,49 +1,24 @@ -/* global Vue */ -const userFilter = require('./filters/user'); -const milestoneFilter = require('./filters/milestone'); -const labelFilter = require('./filters/label'); +import FilteredSearchBoards from '../../filtered_search_boards'; +import FilteredSearchContainer from '../../../filtered_search/container'; -module.exports = Vue.extend({ +export default { name: 'modal-filters', props: { - projectId: { - type: Number, - required: true, - }, - milestonePath: { - type: String, - required: true, - }, - labelPath: { - type: String, + store: { + type: Object, required: true, }, }, - destroyed() { - gl.issueBoards.ModalStore.setDefaultFilter(); + mounted() { + FilteredSearchContainer.container = this.$el; + + this.filteredSearch = new FilteredSearchBoards(this.store); + this.filteredSearch.removeTokens(); }, - components: { - userFilter, - milestoneFilter, - labelFilter, + beforeDestroy() { + this.filteredSearch.cleanup(); + FilteredSearchContainer.container = document; + this.store.path = ''; }, - template: ` - <div class="modal-filters"> - <user-filter - dropdown-class-name="dropdown-menu-author" - toggle-class-name="js-user-search js-author-search" - toggle-label="Author" - field-name="author_id" - :project-id="projectId"></user-filter> - <user-filter - dropdown-class-name="dropdown-menu-author" - toggle-class-name="js-assignee-search" - toggle-label="Assignee" - field-name="assignee_id" - :null-user="true" - :project-id="projectId"></user-filter> - <milestone-filter :milestone-path="milestonePath"></milestone-filter> - <label-filter :label-path="labelPath"></label-filter> - </div> - `, -}); + template: '#js-board-modal-filter', +}; diff --git a/app/assets/javascripts/boards/components/modal/filters/label.js b/app/assets/javascripts/boards/components/modal/filters/label.js deleted file mode 100644 index 4fc8f72a145..00000000000 --- a/app/assets/javascripts/boards/components/modal/filters/label.js +++ /dev/null @@ -1,54 +0,0 @@ -/* eslint-disable no-new */ -/* global Vue */ -/* global LabelsSelect */ -module.exports = Vue.extend({ - name: 'filter-label', - props: { - labelPath: { - type: String, - required: true, - }, - }, - mounted() { - new LabelsSelect(this.$refs.dropdown); - }, - template: ` - <div class="dropdown"> - <button - class="dropdown-menu-toggle js-label-select js-multiselect js-extra-options" - type="button" - data-toggle="dropdown" - data-show-any="true" - data-show-no="true" - :data-labels="labelPath" - ref="dropdown"> - <span class="dropdown-toggle-text"> - Label - </span> - <i class="fa fa-chevron-down"></i> - </button> - <div class="dropdown-menu dropdown-select dropdown-menu-paging dropdown-menu-labels dropdown-menu-selectable"> - <div class="dropdown-title"> - Filter by label - <button - class="dropdown-title-button dropdown-menu-close" - aria-label="Close" - type="button"> - <i class="fa fa-times dropdown-menu-close-icon"></i> - </button> - </div> - <div class="dropdown-input"> - <input - type="search" - class="dropdown-input-field" - placeholder="Search" - autocomplete="off" /> - <i class="fa fa-search dropdown-input-search"></i> - <i role="button" class="fa fa-times dropdown-input-clear js-dropdown-input-clear"></i> - </div> - <div class="dropdown-content"></div> - <div class="dropdown-loading"><i class="fa fa-spinner fa-spin"></i></div> - </div> - </div> - `, -}); diff --git a/app/assets/javascripts/boards/components/modal/filters/milestone.js b/app/assets/javascripts/boards/components/modal/filters/milestone.js deleted file mode 100644 index d555599d300..00000000000 --- a/app/assets/javascripts/boards/components/modal/filters/milestone.js +++ /dev/null @@ -1,55 +0,0 @@ -/* eslint-disable no-new */ -/* global Vue */ -/* global MilestoneSelect */ -module.exports = Vue.extend({ - name: 'filter-milestone', - props: { - milestonePath: { - type: String, - required: true, - }, - }, - mounted() { - new MilestoneSelect(null, this.$refs.dropdown); - }, - template: ` - <div class="dropdown"> - <button - class="dropdown-menu-toggle js-milestone-select" - type="button" - data-toggle="dropdown" - data-show-any="true" - data-show-upcoming="true" - data-field-name="milestone_title" - :data-milestones="milestonePath" - ref="dropdown"> - <span class="dropdown-toggle-text"> - Milestone - </span> - <i class="fa fa-chevron-down"></i> - </button> - <div class="dropdown-menu dropdown-select dropdown-menu-selectable dropdown-menu-milestone"> - <div class="dropdown-title"> - <span>Filter by milestone</span> - <button - class="dropdown-title-button dropdown-menu-close" - aria-label="Close" - type="button"> - <i class="fa fa-times dropdown-menu-close-icon"></i> - </button> - </div> - <div class="dropdown-input"> - <input - type="search" - class="dropdown-input-field" - placeholder="Search milestones" - autocomplete="off" /> - <i class="fa fa-search dropdown-input-search"></i> - <i role="button" class="fa fa-times dropdown-input-clear js-dropdown-input-clear"></i> - </div> - <div class="dropdown-content"></div> - <div class="dropdown-loading"><i class="fa fa-spinner fa-spin"></i></div> - </div> - </div> - `, -}); diff --git a/app/assets/javascripts/boards/components/modal/filters/user.js b/app/assets/javascripts/boards/components/modal/filters/user.js deleted file mode 100644 index 8523028c29c..00000000000 --- a/app/assets/javascripts/boards/components/modal/filters/user.js +++ /dev/null @@ -1,96 +0,0 @@ -/* eslint-disable no-new */ -/* global Vue */ -/* global UsersSelect */ -module.exports = Vue.extend({ - name: 'filter-user', - props: { - toggleClassName: { - type: String, - required: true, - }, - dropdownClassName: { - type: String, - required: false, - default: '', - }, - toggleLabel: { - type: String, - required: true, - }, - fieldName: { - type: String, - required: true, - }, - nullUser: { - type: Boolean, - required: false, - default: false, - }, - projectId: { - type: Number, - required: true, - }, - }, - mounted() { - new UsersSelect(null, this.$refs.dropdown); - }, - computed: { - currentUsername() { - return gon.current_username; - }, - dropdownTitle() { - return `Filter by ${this.toggleLabel.toLowerCase()}`; - }, - inputPlaceholder() { - return `Search ${this.toggleLabel.toLowerCase()}`; - }, - }, - template: ` - <div class="dropdown"> - <button - class="dropdown-menu-toggle js-user-search" - :class="toggleClassName" - type="button" - data-toggle="dropdown" - data-current-user="true" - :data-any-user="'Any ' + toggleLabel" - :data-null-user="nullUser" - :data-field-name="fieldName" - :data-project-id="projectId" - :data-first-user="currentUsername" - ref="dropdown"> - <span class="dropdown-toggle-text"> - {{ toggleLabel }} - </span> - <i class="fa fa-chevron-down"></i> - </button> - <div - class="dropdown-menu dropdown-select dropdown-menu-user dropdown-menu-selectable" - :class="dropdownClassName"> - <div class="dropdown-title"> - {{ dropdownTitle }} - <button - class="dropdown-title-button dropdown-menu-close" - aria-label="Close" - type="button"> - <i class="fa fa-times dropdown-menu-close-icon"></i> - </button> - </div> - <div class="dropdown-input"> - <input - type="search" - class="dropdown-input-field" - autocomplete="off" - :placeholder="inputPlaceholder" /> - <i class="fa fa-search dropdown-input-search"></i> - <i - role="button" - class="fa fa-times dropdown-input-clear js-dropdown-input-clear"> - </i> - </div> - <div class="dropdown-content"></div> - <div class="dropdown-loading"><i class="fa fa-spinner fa-spin"></i></div> - </div> - </div> - `, -}); diff --git a/app/assets/javascripts/boards/components/modal/header.js b/app/assets/javascripts/boards/components/modal/header.js index 70c088f9054..116e29cd177 100644 --- a/app/assets/javascripts/boards/components/modal/header.js +++ b/app/assets/javascripts/boards/components/modal/header.js @@ -1,6 +1,7 @@ -/* global Vue */ +import Vue from 'vue'; +import modalFilters from './filters'; + require('./tabs'); -const modalFilters = require('./filters'); (() => { const ModalStore = gl.issueBoards.ModalStore; @@ -66,16 +67,7 @@ const modalFilters = require('./filters'); <div class="add-issues-search append-bottom-10" v-if="showSearch"> - <modal-filters - :project-id="projectId" - :milestone-path="milestonePath" - :label-path="labelPath"> - </modal-filters> - <input - placeholder="Search issues..." - class="form-control" - type="search" - v-model="searchTerm" /> + <modal-filters :store="filter" /> <button type="button" class="btn btn-success btn-inverted prepend-left-10" diff --git a/app/assets/javascripts/boards/components/modal/index.js b/app/assets/javascripts/boards/components/modal/index.js index f290cd13763..1b66c8b922d 100644 --- a/app/assets/javascripts/boards/components/modal/index.js +++ b/app/assets/javascripts/boards/components/modal/index.js @@ -1,5 +1,6 @@ /* global Vue */ /* global ListIssue */ +import queryData from '../../utils/query_data'; require('./header'); require('./list'); @@ -47,9 +48,6 @@ require('./empty_state'); page() { this.loadIssues(); }, - searchTerm() { - this.searchOperation(); - }, showAddIssuesModal() { if (this.showAddIssuesModal && !this.issues.length) { this.loading = true; @@ -72,19 +70,13 @@ require('./empty_state'); }, }, methods: { - searchOperation: _.debounce(function searchOperationDebounce() { - this.loadIssues(true); - }, 500), loadIssues(clearIssues = false) { if (!this.showAddIssuesModal) return false; - const queryData = Object.assign({}, this.filter, { - search: this.searchTerm, + return gl.boardService.getBacklog(queryData(this.filter.path, { page: this.page, per: this.perPage, - }); - - return gl.boardService.getBacklog(queryData).then((res) => { + })).then((res) => { const data = res.json(); if (clearIssues) { diff --git a/app/assets/javascripts/boards/filtered_search_boards.js b/app/assets/javascripts/boards/filtered_search_boards.js index 47448b02bdd..101732309ea 100644 --- a/app/assets/javascripts/boards/filtered_search_boards.js +++ b/app/assets/javascripts/boards/filtered_search_boards.js @@ -1,3 +1,6 @@ +/* eslint-disable class-methods-use-this */ +import FilteredSearchContainer from '../filtered_search/container'; + export default class FilteredSearchBoards extends gl.FilteredSearchManager { constructor(store, updateUrl = false) { super('boards'); @@ -18,13 +21,17 @@ export default class FilteredSearchBoards extends gl.FilteredSearchManager { } } - updateTokens() { - const tokens = document.querySelectorAll('.js-visual-token'); + removeTokens() { + 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) => { el.parentNode.removeChild(el); }); + } + + updateTokens() { + this.removeTokens(); this.loadSearchParamsFromURL(); diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js index 3251ca76b26..f18ad2a0fac 100644 --- a/app/assets/javascripts/boards/models/list.js +++ b/app/assets/javascripts/boards/models/list.js @@ -1,6 +1,7 @@ /* eslint-disable space-before-function-paren, no-underscore-dangle, class-methods-use-this, consistent-return, no-shadow, no-param-reassign, max-len, no-unused-vars */ /* global ListIssue */ /* global ListLabel */ +import queryData from '../utils/query_data'; class List { constructor (obj) { @@ -64,25 +65,7 @@ class List { } getIssues (emptyIssues = true) { - const data = gl.issueBoards.BoardsStore.filter.path.split('&').reduce((data, filterParam) => { - if (filterParam === '') return data; - const paramSplit = filterParam.split('='); - const paramKeyNormalized = paramSplit[0].replace('[]', ''); - const isArray = paramSplit[0].indexOf('[]'); - const value = decodeURIComponent(paramSplit[1]).replace(/\+/g, ' '); - - if (isArray !== -1) { - if (!data[paramKeyNormalized]) { - data[paramKeyNormalized] = []; - } - - data[paramKeyNormalized].push(value); - } else { - data[paramKeyNormalized] = value; - } - - return data; - }, { page: this.page }); + const data = queryData(gl.issueBoards.BoardsStore.filter.path, { page: this.page }); if (this.label && data.label_name) { data.label_name = data.label_name.filter(label => label !== this.label.title); diff --git a/app/assets/javascripts/boards/stores/modal_store.js b/app/assets/javascripts/boards/stores/modal_store.js index 15fc6c79e8d..7ee266a831f 100644 --- a/app/assets/javascripts/boards/stores/modal_store.js +++ b/app/assets/javascripts/boards/stores/modal_store.js @@ -17,17 +17,9 @@ loadingNewPage: false, page: 1, perPage: 50, - }; - - this.setDefaultFilter(); - } - - setDefaultFilter() { - this.store.filter = { - author_id: '', - assignee_id: '', - milestone_title: '', - label_name: [], + filter: { + path: '', + }, }; } diff --git a/app/assets/javascripts/boards/utils/query_data.js b/app/assets/javascripts/boards/utils/query_data.js new file mode 100644 index 00000000000..2cd3c146f11 --- /dev/null +++ b/app/assets/javascripts/boards/utils/query_data.js @@ -0,0 +1,21 @@ +export default (path, extraData) => path.split('&').reduce((dataParam, filterParam) => { + if (filterParam === '') return dataParam; + + const data = dataParam; + const paramSplit = filterParam.split('='); + const paramKeyNormalized = paramSplit[0].replace('[]', ''); + const isArray = paramSplit[0].indexOf('[]'); + const value = decodeURIComponent(paramSplit[1]).replace(/\+/g, ' '); + + if (isArray !== -1) { + if (!data[paramKeyNormalized]) { + data[paramKeyNormalized] = []; + } + + data[paramKeyNormalized].push(value); + } else { + data[paramKeyNormalized] = value; + } + + return data; +}, extraData); diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index f1c86dd14f1..db1a2848d8d 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -201,10 +201,13 @@ const UserCallout = require('./user_callout'); new gl.Diff(); new ZenMode(); shortcut_handler = new ShortcutsNavigation(); + new MiniPipelineGraph({ + container: '.js-commit-pipeline-graph', + }).bindEvents(); break; case 'projects:commit:pipelines': new MiniPipelineGraph({ - container: '.js-pipeline-table', + container: '.js-commit-pipeline-graph', }).bindEvents(); break; case 'projects:commits:show': diff --git a/app/assets/javascripts/filtered_search/container.js b/app/assets/javascripts/filtered_search/container.js new file mode 100644 index 00000000000..2243c4dd2c5 --- /dev/null +++ b/app/assets/javascripts/filtered_search/container.js @@ -0,0 +1,14 @@ +/* eslint-disable class-methods-use-this */ +let container = document; + +class FilteredSearchContainerClass { + set container(containerParam) { + container = containerParam; + } + + get container() { + return container; + } +} + +export default new FilteredSearchContainerClass(); diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js b/app/assets/javascripts/filtered_search/dropdown_hint.js index 38ff3fb7158..98dcb697af9 100644 --- a/app/assets/javascripts/filtered_search/dropdown_hint.js +++ b/app/assets/javascripts/filtered_search/dropdown_hint.js @@ -45,7 +45,7 @@ require('./filtered_search_dropdown'); gl.FilteredSearchVisualTokens.addSearchVisualToken(searchTerms.join(' ')); } - gl.FilteredSearchDropdownManager.addWordToInput(token.replace(':', '')); + gl.FilteredSearchDropdownManager.addWordToInput(token.replace(':', ''), '', false, this.container); } this.dismissDropdown(); this.dispatchInputEvent(); @@ -57,13 +57,15 @@ require('./filtered_search_dropdown'); const dropdownData = []; [].forEach.call(this.input.closest('.filtered-search-input-container').querySelectorAll('.dropdown-menu'), (dropdownMenu) => { - const { icon, hint, tag } = dropdownMenu.dataset; + const { icon, hint, tag, type } = dropdownMenu.dataset; if (icon && hint && tag) { - dropdownData.push({ - icon: `fa-${icon}`, - hint, - tag: `<${tag}>`, - }); + dropdownData.push( + Object.assign({ + icon: `fa-${icon}`, + hint, + tag: `<${tag}>`, + }, type && { type }), + ); } }); diff --git a/app/assets/javascripts/filtered_search/dropdown_utils.js b/app/assets/javascripts/filtered_search/dropdown_utils.js index a5a6b56a0d3..59998c2108c 100644 --- a/app/assets/javascripts/filtered_search/dropdown_utils.js +++ b/app/assets/javascripts/filtered_search/dropdown_utils.js @@ -1,3 +1,5 @@ +import FilteredSearchContainer from './container'; + (() => { class DropdownUtils { static getEscapedText(text) { @@ -51,14 +53,18 @@ static filterHint(input, item) { const updatedItem = item; - const searchInput = gl.DropdownUtils.getSearchInput(input); - let { lastToken } = gl.FilteredSearchTokenizer.processTokens(searchInput); - lastToken = lastToken.key || lastToken || ''; - - if (!lastToken || searchInput.split('').last() === ' ') { + const searchInput = gl.DropdownUtils.getSearchQuery(input); + const { lastToken, tokens } = gl.FilteredSearchTokenizer.processTokens(searchInput); + const lastKey = lastToken.key || lastToken || ''; + const allowMultiple = item.type === 'array'; + const itemInExistingTokens = tokens.some(t => t.key === item.hint); + + if (!allowMultiple && itemInExistingTokens) { + updatedItem.droplab_hidden = true; + } else if (!lastKey || searchInput.split('').last() === ' ') { updatedItem.droplab_hidden = false; - } else if (lastToken) { - const split = lastToken.split(':'); + } else if (lastKey) { + const split = lastKey.split(':'); const tokenName = split[0].split(' ').last(); const match = updatedItem.hint.indexOf(tokenName.toLowerCase()) === -1; @@ -81,7 +87,8 @@ // Determines the full search query (visual tokens + input) static getSearchQuery(untilInput = false) { - const tokens = [].slice.call(document.querySelectorAll('.tokens-container li')); + const container = FilteredSearchContainer.container; + const tokens = [].slice.call(container.querySelectorAll('.tokens-container li')); const values = []; if (untilInput) { 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 d37c812c1f7..5fbe0450bb8 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js @@ -1,12 +1,14 @@ /* global DropLab */ +import FilteredSearchContainer from './container'; (() => { class FilteredSearchDropdownManager { constructor(baseEndpoint = '', page) { + this.container = FilteredSearchContainer.container; this.baseEndpoint = baseEndpoint.replace(/\/$/, ''); this.tokenizer = gl.FilteredSearchTokenizer; this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeys; - this.filteredSearchInput = document.querySelector('.filtered-search'); + this.filteredSearchInput = this.container.querySelector('.filtered-search'); this.page = page; this.setupMapping(); @@ -31,35 +33,35 @@ author: { reference: null, gl: 'DropdownUser', - element: document.querySelector('#js-dropdown-author'), + element: this.container.querySelector('#js-dropdown-author'), }, assignee: { reference: null, gl: 'DropdownUser', - element: document.querySelector('#js-dropdown-assignee'), + element: this.container.querySelector('#js-dropdown-assignee'), }, milestone: { reference: null, gl: 'DropdownNonUser', extraArguments: [`${this.baseEndpoint}/milestones.json`, '%'], - element: document.querySelector('#js-dropdown-milestone'), + element: this.container.querySelector('#js-dropdown-milestone'), }, label: { reference: null, gl: 'DropdownNonUser', extraArguments: [`${this.baseEndpoint}/labels.json`, '~'], - element: document.querySelector('#js-dropdown-label'), + element: this.container.querySelector('#js-dropdown-label'), }, hint: { reference: null, gl: 'DropdownHint', - element: document.querySelector('#js-dropdown-hint'), + element: this.container.querySelector('#js-dropdown-hint'), }, }; } static addWordToInput(tokenName, tokenValue = '', clicked = false) { - const input = document.querySelector('.filtered-search'); + const input = FilteredSearchContainer.container.querySelector('.filtered-search'); gl.FilteredSearchVisualTokens.addFilterVisualToken(tokenName, tokenValue); input.value = ''; @@ -75,13 +77,13 @@ updateDropdownOffset(key) { // Always align dropdown with the input field - let offset = this.filteredSearchInput.getBoundingClientRect().left - document.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 = document.querySelector('.scroll-container').clientWidth - currentDropdownWidth; + const offsetMaxWidth = this.container.querySelector('.scroll-container').clientWidth - currentDropdownWidth; if (offsetMaxWidth < offset) { offset = offsetMaxWidth; } diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js index f885932bd91..7ace51748aa 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js @@ -1,9 +1,12 @@ +import FilteredSearchContainer from './container'; + (() => { class FilteredSearchManager { constructor(page) { - this.filteredSearchInput = document.querySelector('.filtered-search'); - this.clearSearchButton = document.querySelector('.clear-search'); - this.tokensContainer = document.querySelector('.tokens-container'); + this.container = FilteredSearchContainer.container; + this.filteredSearchInput = this.container.querySelector('.filtered-search'); + this.clearSearchButton = this.container.querySelector('.clear-search'); + this.tokensContainer = this.container.querySelector('.tokens-container'); this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeys; if (this.filteredSearchInput) { @@ -132,7 +135,7 @@ } unselectEditTokens(e) { - const inputContainer = document.querySelector('.filtered-search-input-container'); + const inputContainer = this.container.querySelector('.filtered-search-input-container'); const isElementInFilteredSearch = inputContainer && inputContainer.contains(e.target); const isElementInFilterDropdown = e.target.closest('.filter-dropdown') !== null; const isElementTokensContainer = e.target.classList.contains('tokens-container'); diff --git a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js index e6b53cd4b55..6d5df86f2a5 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js +++ b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js @@ -43,6 +43,10 @@ tokenKey: 'milestone', value: 'upcoming', }, { + url: 'milestone_title=%23started', + tokenKey: 'milestone', + value: 'started', + }, { url: 'label_name[]=No+Label', tokenKey: 'label', value: 'none', diff --git a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js index 320afa26130..e48d7196c7b 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js +++ b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js @@ -1,6 +1,8 @@ +import FilteredSearchContainer from './container'; + class FilteredSearchVisualTokens { static getLastVisualTokenBeforeInput() { - const inputLi = document.querySelector('.input-token'); + const inputLi = FilteredSearchContainer.container.querySelector('.input-token'); const lastVisualToken = inputLi && inputLi.previousElementSibling; return { @@ -10,7 +12,7 @@ class FilteredSearchVisualTokens { } static unselectTokens() { - const otherTokens = document.querySelectorAll('.js-visual-token .selectable.selected'); + const otherTokens = FilteredSearchContainer.container.querySelectorAll('.js-visual-token .selectable.selected'); [].forEach.call(otherTokens, t => t.classList.remove('selected')); } @@ -24,7 +26,7 @@ class FilteredSearchVisualTokens { } static removeSelectedToken() { - const selected = document.querySelector('.js-visual-token .selected'); + const selected = FilteredSearchContainer.container.querySelector('.js-visual-token .selected'); if (selected) { const li = selected.closest('.js-visual-token'); @@ -54,8 +56,8 @@ class FilteredSearchVisualTokens { } li.querySelector('.name').innerText = name; - const tokensContainer = document.querySelector('.tokens-container'); - const input = document.querySelector('.filtered-search'); + const tokensContainer = FilteredSearchContainer.container.querySelector('.tokens-container'); + const input = FilteredSearchContainer.container.querySelector('.filtered-search'); tokensContainer.insertBefore(li, input.parentElement); } @@ -77,14 +79,14 @@ class FilteredSearchVisualTokens { const addVisualTokenElement = FilteredSearchVisualTokens.addVisualTokenElement; if (isLastVisualTokenValid) { - addVisualTokenElement(tokenName, tokenValue); + addVisualTokenElement(tokenName, tokenValue, false); } else { const previousTokenName = lastVisualToken.querySelector('.name').innerText; - const tokensContainer = document.querySelector('.tokens-container'); + const tokensContainer = FilteredSearchContainer.container.querySelector('.tokens-container'); tokensContainer.removeChild(lastVisualToken); const value = tokenValue || tokenName; - addVisualTokenElement(previousTokenName, value); + addVisualTokenElement(previousTokenName, value, false); } } @@ -129,7 +131,7 @@ class FilteredSearchVisualTokens { } static tokenizeInput() { - const input = document.querySelector('.filtered-search'); + const input = FilteredSearchContainer.container.querySelector('.filtered-search'); const { isLastVisualTokenValid } = gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput(); @@ -145,7 +147,7 @@ class FilteredSearchVisualTokens { } static editToken(token) { - const input = document.querySelector('.filtered-search'); + const input = FilteredSearchContainer.container.querySelector('.filtered-search'); FilteredSearchVisualTokens.tokenizeInput(); @@ -174,9 +176,9 @@ class FilteredSearchVisualTokens { } static moveInputToTheRight() { - const input = document.querySelector('.filtered-search'); + const input = FilteredSearchContainer.container.querySelector('.filtered-search'); const inputLi = input.parentElement; - const tokenContainer = document.querySelector('.tokens-container'); + const tokenContainer = FilteredSearchContainer.container.querySelector('.tokens-container'); FilteredSearchVisualTokens.tokenizeInput(); diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js index 4c4f94cb9f3..02ff6f5682c 100644 --- a/app/assets/javascripts/milestone_select.js +++ b/app/assets/javascripts/milestone_select.js @@ -19,7 +19,7 @@ } $els.each(function(i, dropdown) { - var $block, $dropdown, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, collapsedSidebarLabelTemplate, defaultLabel, issuableId, issueUpdateURL, milestoneLinkNoneTemplate, milestoneLinkTemplate, milestonesUrl, projectId, selectedMilestone, showAny, showNo, showUpcoming, useId, showMenuAbove; + var $block, $dropdown, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, collapsedSidebarLabelTemplate, defaultLabel, issuableId, issueUpdateURL, milestoneLinkNoneTemplate, milestoneLinkTemplate, milestonesUrl, projectId, selectedMilestone, showAny, showNo, showUpcoming, showStarted, useId, showMenuAbove; $dropdown = $(dropdown); projectId = $dropdown.data('project-id'); milestonesUrl = $dropdown.data('milestones'); @@ -29,6 +29,7 @@ showAny = $dropdown.data('show-any'); showMenuAbove = $dropdown.data('showMenuAbove'); showUpcoming = $dropdown.data('show-upcoming'); + showStarted = $dropdown.data('show-started'); useId = $dropdown.data('use-id'); defaultLabel = $dropdown.data('default-label'); issuableId = $dropdown.data('issuable-id'); @@ -71,6 +72,13 @@ title: 'Upcoming' }); } + if (showStarted) { + extraOptions.push({ + id: -3, + name: '#started', + title: 'Started' + }); + } if (extraOptions.length) { extraOptions.push('divider'); } diff --git a/app/assets/javascripts/profile/profile.js b/app/assets/javascripts/profile/profile.js index 4ccea0624ee..c38bc762675 100644 --- a/app/assets/javascripts/profile/profile.js +++ b/app/assets/javascripts/profile/profile.js @@ -25,7 +25,6 @@ bindEvents() { $('.js-preferences-form').on('change.preference', 'input[type=radio]', this.submitForm); $('#user_notification_email').on('change', this.submitForm); - $('#user_notified_of_own_activity').on('change', this.submitForm); $('.update-username').on('ajax:before', this.beforeUpdateUsername); $('.update-username').on('ajax:complete', this.afterUpdateUsername); $('.update-notifications').on('ajax:success', this.onUpdateNotifs); diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js index 891f1f17fb3..583d6915a85 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js +++ b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js @@ -47,6 +47,7 @@ const playIconSvg = require('icons/_icon_play.svg'); data-toggle="dropdown" title="Manual job" data-placement="top" + data-container="body" aria-label="Manual job"> <span v-html="playIconSvg" aria-hidden="true"></span> <i class="fa fa-caret-down" aria-hidden="true"></i> @@ -69,6 +70,7 @@ const playIconSvg = require('icons/_icon_play.svg'); class="dropdown-toggle btn btn-default build-artifacts has-tooltip js-pipeline-dropdown-download" title="Artifacts" data-placement="top" + data-container="body" data-toggle="dropdown" aria-label="Artifacts"> <i class="fa fa-download" aria-hidden="true"></i> @@ -92,6 +94,7 @@ const playIconSvg = require('icons/_icon_play.svg'); rel="nofollow" data-method="post" data-placement="top" + data-container="body" data-toggle="dropdown" :href='pipeline.retry_path' aria-label="Retry"> @@ -105,6 +108,7 @@ const playIconSvg = require('icons/_icon_play.svg'); rel="nofollow" data-method="post" data-placement="top" + data-container="body" data-toggle="dropdown" :href='pipeline.cancel_path' aria-label="Cancel"> diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index 9a36d76136b..f9ee33019cd 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -420,12 +420,9 @@ display: -webkit-flex; display: flex; - .form-control { - margin-left: auto; - - @media (min-width: $screen-sm-min) { - max-width: 200px; - } + .issues-filters { + -webkit-flex: 1; + flex: 1; } } diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index 2029b6893ef..da8410eca66 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -38,6 +38,38 @@ } } +.pipeline-info { + .status-icon-container { + display: inline-block; + vertical-align: middle; + margin-right: 3px; + + svg { + display: block; + width: 22px; + height: 22px; + } + } + + .mr-widget-pipeline-graph { + display: inline-block; + vertical-align: middle; + margin: 0 -6px 0 0; + + .dropdown-menu { + margin-top: 11px; + } + } +} + +.branch-info .commit-icon { + margin-right: 3px; + + svg { + top: 3px; + } +} + /* * Commit message textarea for web editor and * custom merge request message diff --git a/app/controllers/profiles/notifications_controller.rb b/app/controllers/profiles/notifications_controller.rb index a271e2dfc4b..b8b71d295f6 100644 --- a/app/controllers/profiles/notifications_controller.rb +++ b/app/controllers/profiles/notifications_controller.rb @@ -17,6 +17,6 @@ class Profiles::NotificationsController < Profiles::ApplicationController end def user_params - params.require(:user).permit(:notification_email, :notified_of_own_activity) + params.require(:user).permit(:notification_email) end end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index e4fd53eed29..395a8bffe92 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -267,8 +267,9 @@ class ProjectsController < Projects::ApplicationController @project_wiki = @project.wiki @wiki_home = @project_wiki.find_page('home', params[:version_id]) elsif @project.feature_available?(:issues, current_user) - @issues = issues_collection - @issues = @issues.page(params[:page]) + @issues = issues_collection.page(params[:page]) + @collection_type = 'Issue' + @issuable_meta_data = issuable_meta_data(@issues, @collection_type) end render :show diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 2fca012252e..f7ebb1807d7 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -310,6 +310,10 @@ class IssuableFinder params[:milestone_title] == Milestone::Upcoming.name end + def filter_by_started_milestone? + params[:milestone_title] == Milestone::Started.name + end + def by_milestone(items) if milestones? if filter_by_no_milestone? @@ -317,6 +321,8 @@ class IssuableFinder elsif filter_by_upcoming_milestone? upcoming_ids = Milestone.upcoming_ids_by_projects(projects(items)) items = items.left_joins_milestones.where(milestone_id: upcoming_ids) + elsif filter_by_started_milestone? + items = items.left_joins_milestones.where('milestones.start_date <= NOW()') else items = items.with_milestone(params[:milestone_title]) items_projects = projects(items) diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index aad83731b87..a777db2826b 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -90,11 +90,14 @@ module IssuablesHelper end def milestone_dropdown_label(milestone_title, default_label = "Milestone") - if milestone_title == Milestone::Upcoming.name - milestone_title = Milestone::Upcoming.title - end + title = + case milestone_title + when Milestone::Upcoming.name then Milestone::Upcoming.title + when Milestone::Started.name then Milestone::Started.title + else milestone_title.presence + end - h(milestone_title.presence || default_label) + h(title || default_label) end def to_url_reference(issuable) diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb index 18734f1411f..959ee310867 100644 --- a/app/helpers/sorting_helper.rb +++ b/app/helpers/sorting_helper.rb @@ -16,7 +16,8 @@ module SortingHelper sort_value_oldest_signin => sort_title_oldest_signin, sort_value_downvotes => sort_title_downvotes, sort_value_upvotes => sort_title_upvotes, - sort_value_priority => sort_title_priority + sort_value_priority => sort_title_priority, + sort_value_label_priority => sort_title_label_priority } end @@ -50,6 +51,10 @@ module SortingHelper end def sort_title_priority + 'Priority' + end + + def sort_title_label_priority 'Label priority' end @@ -161,6 +166,10 @@ module SortingHelper 'priority' end + def sort_value_label_priority + 'label_priority' + end + def sort_value_oldest_updated 'updated_asc' end diff --git a/app/models/commit.rb b/app/models/commit.rb index 0a18986ef26..6ea5b1ae51f 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -231,6 +231,10 @@ class Commit project.pipelines.where(sha: sha) end + def latest_pipeline + pipelines.last + end + def status(ref = nil) @statuses ||= {} diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 3cf4c67d7e7..3b2c6a178e7 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -144,7 +144,8 @@ module Issuable when 'milestone_due_desc' then order_milestone_due_desc when 'downvotes_desc' then order_downvotes_desc when 'upvotes_desc' then order_upvotes_desc - when 'priority' then order_labels_priority(excluded_labels: excluded_labels) + when 'label_priority' then order_labels_priority(excluded_labels: excluded_labels) + when 'priority' then order_due_date_and_labels_priority(excluded_labels: excluded_labels) when 'position_asc' then order_position_asc else order_by(method) @@ -154,7 +155,28 @@ module Issuable sorted.order(id: :desc) end - def order_labels_priority(excluded_labels: []) + def order_due_date_and_labels_priority(excluded_labels: []) + # The order_ methods also modify the query in other ways: + # + # - For milestones, we add a JOIN. + # - For label priority, we change the SELECT, and add a GROUP BY.# + # + # After doing those, we need to reorder to the order we want. The existing + # ORDER BYs won't work because: + # + # 1. We need milestone due date first. + # 2. We can't ORDER BY a column that isn't in the GROUP BY and doesn't + # have an aggregate function applied, so we do a useless MIN() instead. + # + milestones_due_date = 'MIN(milestones.due_date)' + + order_milestone_due_asc. + order_labels_priority(excluded_labels: excluded_labels, extra_select_columns: [milestones_due_date]). + reorder(Gitlab::Database.nulls_last_order(milestones_due_date, 'ASC'), + Gitlab::Database.nulls_last_order('highest_priority', 'ASC')) + end + + def order_labels_priority(excluded_labels: [], extra_select_columns: []) params = { target_type: name, target_column: "#{table_name}.id", @@ -164,7 +186,12 @@ module Issuable highest_priority = highest_label_priority(params).to_sql - select("#{table_name}.*, (#{highest_priority}) AS highest_priority"). + select_columns = [ + "#{table_name}.*", + "(#{highest_priority}) AS highest_priority" + ] + extra_select_columns + + select(select_columns.join(', ')). group(arel_table[:id]). reorder(Gitlab::Database.nulls_last_order('highest_priority', 'ASC')) end diff --git a/app/models/milestone.rb b/app/models/milestone.rb index 7331000a9f2..c0deb59ec4c 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -5,6 +5,7 @@ class Milestone < ActiveRecord::Base None = MilestoneStruct.new('No Milestone', 'No Milestone', 0) Any = MilestoneStruct.new('Any Milestone', '', -1) Upcoming = MilestoneStruct.new('Upcoming', '#upcoming', -2) + Started = MilestoneStruct.new('Started', '#started', -3) include CacheMarkdownField include InternalId diff --git a/app/models/todo.rb b/app/models/todo.rb index 47789a21133..da3fa7277c2 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -48,8 +48,14 @@ class Todo < ActiveRecord::Base after_save :keep_around_commit class << self + # Priority sorting isn't displayed in the dropdown, because we don't show + # milestones, but still show something if the user has a URL with that + # selected. def sort(method) - method == "priority" ? order_by_labels_priority : order_by(method) + case method.to_s + when 'priority', 'label_priority' then order_by_labels_priority + else order_by(method) + end end # Order by priority depending on which issue/merge request the Todo belongs to diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index d12692ecc90..fdaba9b95fb 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -217,7 +217,7 @@ class NotificationService recipients = reject_unsubscribed_users(recipients, note.noteable) recipients = reject_users_without_access(recipients, note.noteable) - recipients.delete(note.author) unless note.author.notified_of_own_activity? + recipients.delete(note.author) recipients = recipients.uniq notify_method = "note_#{note.to_ability_name}_email".to_sym @@ -327,9 +327,8 @@ class NotificationService recipients ||= build_recipients( pipeline, pipeline.project, - pipeline.user, - action: pipeline.status, - skip_current_user: false).map(&:notification_email) + nil, # The acting user, who won't be added to recipients + action: pipeline.status).map(&:notification_email) if recipients.any? mailer.public_send(email_template, pipeline, recipients).deliver_later @@ -628,7 +627,7 @@ class NotificationService recipients = reject_unsubscribed_users(recipients, target) recipients = reject_users_without_access(recipients, target) - recipients.delete(current_user) if skip_current_user && !current_user.notified_of_own_activity? + recipients.delete(current_user) if skip_current_user recipients.uniq end @@ -637,7 +636,7 @@ class NotificationService recipients = add_labels_subscribers([], project, target, labels: labels) recipients = reject_unsubscribed_users(recipients, target) recipients = reject_users_without_access(recipients, target) - recipients.delete(current_user) unless current_user.notified_of_own_activity? + recipients.delete(current_user) recipients.uniq end diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml index d7e0a8e4b2c..3ed67d9258c 100644 --- a/app/views/dashboard/todos/index.html.haml +++ b/app/views/dashboard/todos/index.html.haml @@ -57,8 +57,8 @@ = icon('chevron-down') %ul.dropdown-menu.dropdown-menu-sort %li - = link_to todos_filter_path(sort: sort_value_priority) do - = sort_title_priority + = link_to todos_filter_path(sort: sort_value_label_priority) do + = sort_title_label_priority = link_to todos_filter_path(sort: sort_value_recently_created) do = sort_title_recently_created = link_to todos_filter_path(sort: sort_value_oldest_created) do diff --git a/app/views/groups/_settings_head.html.haml b/app/views/groups/_settings_head.html.haml new file mode 100644 index 00000000000..2454e7355a7 --- /dev/null +++ b/app/views/groups/_settings_head.html.haml @@ -0,0 +1,14 @@ += content_for :sub_nav do + .scrolling-tabs-container.sub-nav-scroll + = render 'shared/nav_scroll' + .nav-links.sub-nav.scrolling-tabs + %ul{ class: container_class } + = nav_link(path: 'groups#edit') do + = link_to edit_group_path(@group), title: 'General' do + %span + General + + = nav_link(path: 'groups#projects') do + = link_to projects_group_path(@group), title: 'Projects' do + %span + Projects diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index 2706e8692d1..80a77dab97f 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -1,3 +1,4 @@ += render "groups/settings_head" .panel.panel-default.prepend-top-default .panel-heading Group settings diff --git a/app/views/groups/projects.html.haml b/app/views/groups/projects.html.haml index 2e7e5e5c309..83bdd654f27 100644 --- a/app/views/groups/projects.html.haml +++ b/app/views/groups/projects.html.haml @@ -1,4 +1,4 @@ -- page_title "Projects" += render "groups/settings_head" .panel.panel-default.prepend-top-default .panel-heading diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml index a6e96942021..8605380848d 100644 --- a/app/views/layouts/nav/_group.html.haml +++ b/app/views/layouts/nav/_group.html.haml @@ -1,4 +1,3 @@ -= render 'layouts/nav/group_settings' .scrolling-tabs-container{ class: nav_control_class } .fade-left = icon('angle-left') @@ -25,3 +24,8 @@ = link_to group_group_members_path(@group), title: 'Members' do %span Members + - if current_user && can?(current_user, :admin_group, @group) + = nav_link(path: %w[groups#projects groups#edit]) do + = link_to edit_group_path(@group), title: 'Settings' do + %span + Settings diff --git a/app/views/layouts/nav/_group_settings.html.haml b/app/views/layouts/nav/_group_settings.html.haml deleted file mode 100644 index 30feb6813b4..00000000000 --- a/app/views/layouts/nav/_group_settings.html.haml +++ /dev/null @@ -1,18 +0,0 @@ -- if current_user - - can_admin_group = can?(current_user, :admin_group, @group) - - can_edit = can?(current_user, :admin_group, @group) - - - if can_admin_group || can_edit - .controls - .dropdown.group-settings-dropdown - %a.dropdown-new.btn.btn-default#group-settings-button{ href: '#', 'data-toggle' => 'dropdown' } - = icon('cog') - = icon('caret-down') - %ul.dropdown-menu.dropdown-menu-align-right - - if can_admin_group - = nav_link(path: 'groups#projects') do - = link_to 'Projects', projects_group_path(@group), title: 'Projects' - - if can_edit && can_admin_group - %li.divider - %li - = link_to 'Edit Group', edit_group_path(@group) diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml index 51c4e8e5a73..5c5e5940365 100644 --- a/app/views/profiles/notifications/show.html.haml +++ b/app/views/profiles/notifications/show.html.haml @@ -34,11 +34,6 @@ .clearfix - = form_for @user, url: profile_notifications_path, method: :put do |f| - %label{ for: 'user_notified_of_own_activity' } - = f.check_box :notified_of_own_activity - %span Receive notifications about your own activity - %hr %h5 Groups (#{@group_notifications.count}) diff --git a/app/views/projects/boards/_show.html.haml b/app/views/projects/boards/_show.html.haml index fa463edd526..added3f669b 100644 --- a/app/views/projects/boards/_show.html.haml +++ b/app/views/projects/boards/_show.html.haml @@ -10,6 +10,7 @@ %script#js-board-template{ type: "text/x-template" }= render "projects/boards/components/board" %script#js-board-list-template{ type: "text/x-template" }= render "projects/boards/components/board_list" + %script#js-board-modal-filter{ type: "text/x-template" }= render "shared/issuable/search_bar", type: :boards_modal = render "projects/issues/head" diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index d001e01609a..a0a292d0508 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -63,15 +63,15 @@ - if @commit.status .well-segment.pipeline-info - %div{ class: "icon-container ci-status-icon-#{@commit.status}" } - = link_to namespace_project_pipeline_path(@project.namespace, @project, @commit.pipelines.last.id) do + .status-icon-container{ class: "ci-status-icon-#{@commit.status}" } + = link_to namespace_project_pipeline_path(@project.namespace, @project, @commit.latest_pipeline.id) do = ci_icon_for_status(@commit.status) Pipeline - = link_to "##{@commit.pipelines.last.id}", namespace_project_pipeline_path(@project.namespace, @project, @commit.pipelines.last.id), class: "monospace" - for - = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace" - %span.ci-status-label - = ci_label_for_status(@commit.status) + = link_to "##{@commit.latest_pipeline.id}", namespace_project_pipeline_path(@project.namespace, @project, @commit.latest_pipeline.id), class: "monospace" + = ci_label_for_status(@commit.status) + - if @commit.latest_pipeline.stages.any? + .mr-widget-pipeline-graph + = render 'shared/mini_pipeline_graph', pipeline: @commit.latest_pipeline, klass: 'js-commit-pipeline-graph' in = time_interval_in_words @commit.pipelines.total_duration diff --git a/app/views/shared/_sort_dropdown.html.haml b/app/views/shared/_sort_dropdown.html.haml index 0ce0d759e86..367aa550a78 100644 --- a/app/views/shared/_sort_dropdown.html.haml +++ b/app/views/shared/_sort_dropdown.html.haml @@ -10,6 +10,8 @@ %li = link_to page_filter_path(sort: sort_value_priority, label: true) do = sort_title_priority + = link_to page_filter_path(sort: sort_value_label_priority, label: true) do + = sort_title_label_priority = link_to page_filter_path(sort: sort_value_recently_created, label: true) do = sort_title_recently_created = link_to page_filter_path(sort: sort_value_oldest_created, label: true) do diff --git a/app/views/shared/empty_states/_labels.html.haml b/app/views/shared/empty_states/_labels.html.haml index ba5c2dae09d..00fb77bdb3b 100644 --- a/app/views/shared/empty_states/_labels.html.haml +++ b/app/views/shared/empty_states/_labels.html.haml @@ -5,7 +5,7 @@ .col-xs-12.col-sm-6 .text-content %h4 Labels can be applied to issues and merge requests to categorize them. - %p You can also star label to make it a priority label. + %p You can also star a label to make it a priority label. - if can?(current_user, :admin_label, @project) = link_to 'New label', new_namespace_project_label_path(@project.namespace, @project), class: 'btn btn-new', title: 'New label', id: 'new_label_link' = link_to 'Generate a default set of labels', generate_namespace_project_labels_path(@project.namespace, @project), method: :post, class: 'btn btn-success btn-inverted', title: 'Generate a default set of labels', id: 'generate_labels_link' diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index f0bad69a989..847a86e2e68 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -24,7 +24,7 @@ placeholder: "Search assignee", data: { any_user: "Any Assignee", first_user: current_user.try(:username), null_user: true, current_user: true, project_id: @project.try(:id), selected: params[:assignee_id], field_name: "assignee_id", default_label: "Assignee" } }) .filter-item.inline.milestone-filter - = render "shared/issuable/milestone_dropdown", selected: finder.milestones.try(:first), name: :milestone_title, show_any: true, show_upcoming: true + = render "shared/issuable/milestone_dropdown", selected: finder.milestones.try(:first), name: :milestone_title, show_any: true, show_upcoming: true, show_started: true .filter-item.inline.labels-filter = render "shared/issuable/label_dropdown", selected: finder.labels.select(:title).uniq, use_id: false, selected_toggle: params[:label_name], data_options: { field_name: "label_name[]" } diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml index 415361f8fbf..f0d50828e2a 100644 --- a/app/views/shared/issuable/_milestone_dropdown.html.haml +++ b/app/views/shared/issuable/_milestone_dropdown.html.haml @@ -6,7 +6,7 @@ - if selected.present? || params[:milestone_title].present? = hidden_field_tag(name, name == :milestone_title ? selected_text : selected.id) = dropdown_tag(milestone_dropdown_label(selected_text), options: { title: dropdown_title, toggle_class: "js-milestone-select js-filter-submit #{extra_class}", filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone", - placeholder: "Search milestones", footer_content: project.present?, data: { show_no: true, show_menu_above: show_menu_above, show_any: show_any, show_upcoming: show_upcoming, field_name: name, selected: selected.try(:title), project_id: project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do + placeholder: "Search milestones", footer_content: project.present?, data: { show_no: true, show_menu_above: show_menu_above, show_any: show_any, show_upcoming: show_upcoming, show_started: show_started, field_name: name, selected: selected.try(:title), project_id: project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do - if project %ul.dropdown-footer-list - if can? current_user, :admin_milestone, project diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index f1730b1791c..b58640c3ef0 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -1,7 +1,8 @@ - type = local_assigns.fetch(:type) +- block_css_class = type != :boards_modal ? 'row-content-block second-block' : '' .issues-filters - .issues-details-filters.row-content-block.second-block.filtered-search-block + .issues-details-filters.filtered-search-block{ class: block_css_class, "v-pre" => type == :boards_modal } = form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :search]), method: :get, class: 'filter-form js-filter-form' do - if params[:search].present? = hidden_field_tag :search, params[:search] @@ -14,7 +15,7 @@ .scroll-container %ul.tokens-container.list-unstyled %li.input-token - %input.form-control.filtered-search{ placeholder: 'Search or filter results...', data: { id: 'filtered-search', 'project-id' => @project.id, 'username-params' => @users.to_json(only: [:id, :username]), 'base-endpoint' => namespace_project_path(@project.namespace, @project) } } + %input.form-control.filtered-search{ placeholder: 'Search or filter results...', data: { id: "filtered-search-#{type.to_s}", 'project-id' => @project.id, 'username-params' => @users.to_json(only: [:id, :username]), 'base-endpoint' => namespace_project_path(@project.namespace, @project) } } = icon('filter') %button.clear-search.hidden{ type: 'button' } = icon('times') @@ -68,12 +69,15 @@ %li.filter-dropdown-item{ data: { value: 'upcoming' } } %button.btn.btn-link Upcoming + %li.filter-dropdown-item{ 'data-value' => 'started' } + %button.btn.btn-link + Started %li.divider %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } } %li.filter-dropdown-item %button.btn.btn-link.js-data-value {{title}} - #js-dropdown-label.dropdown-menu{ data: { icon: 'tag', hint: 'label', tag: '~label' } } + #js-dropdown-label.dropdown-menu{ data: { icon: 'tag', hint: 'label', tag: '~label', type: 'array' } } %ul{ data: { dropdown: true } } %li.filter-dropdown-item{ data: { value: 'none' } } %button.btn.btn-link @@ -97,7 +101,7 @@ = render partial: "shared/issuable/label_page_create" = dropdown_loading #js-add-issues-btn.prepend-left-10 - - else + - elsif type != :boards_modal = render 'shared/sort_dropdown' - if @bulk_edit @@ -130,19 +134,20 @@ .filter-item.inline.update-issues-btn = button_tag "Update #{type.to_s.humanize(capitalize: false)}", class: "btn update_selected_issues btn-save" -:javascript - new UsersSelect(); - new LabelsSelect(); - new MilestoneSelect(); - new IssueStatusSelect(); - new SubscriptionSelect(); +- unless type === :boards_modal + :javascript + new UsersSelect(); + new LabelsSelect(); + new MilestoneSelect(); + new IssueStatusSelect(); + new SubscriptionSelect(); - $(document).off('page:restore').on('page:restore', function (event) { - if (gl.FilteredSearchManager) { - new gl.FilteredSearchManager(); - } - Issuable.init(); - new gl.IssuableBulkActions({ - prefixId: 'issue_', + $(document).off('page:restore').on('page:restore', function (event) { + if (gl.FilteredSearchManager) { + new gl.FilteredSearchManager(); + } + Issuable.init(); + new gl.IssuableBulkActions({ + prefixId: 'issue_', + }); }); - }); diff --git a/app/views/shared/issuable/form/_metadata.html.haml b/app/views/shared/issuable/form/_metadata.html.haml index 7a21f19ded4..9dbfedb84f1 100644 --- a/app/views/shared/issuable/form/_metadata.html.haml +++ b/app/views/shared/issuable/form/_metadata.html.haml @@ -21,7 +21,7 @@ = form.label :milestone_id, "Milestone", class: "control-label #{"col-lg-4" if has_due_date}" .col-sm-10{ class: ("col-lg-8" if has_due_date) } .issuable-form-select-holder - = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_upcoming: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: "Select milestone" + = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_upcoming: false, show_started: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: "Select milestone" .form-group - has_labels = @labels && @labels.any? = form.label :label_ids, "Labels", class: "control-label #{"col-lg-4" if has_due_date}" diff --git a/app/views/shared/snippets/_form.html.haml b/app/views/shared/snippets/_form.html.haml index e7f7db73223..0296597b294 100644 --- a/app/views/shared/snippets/_form.html.haml +++ b/app/views/shared/snippets/_form.html.haml @@ -3,7 +3,7 @@ = page_specific_javascript_bundle_tag('snippet') .snippet-form-holder - = form_for @snippet, url: url, html: { class: "form-horizontal snippet-form js-requires-input" } do |f| + = form_for @snippet, url: url, html: { class: "form-horizontal snippet-form js-requires-input js-quick-submit" } do |f| = form_errors(@snippet) .form-group diff --git a/changelogs/unreleased/27174-filter-filters.yml b/changelogs/unreleased/27174-filter-filters.yml new file mode 100644 index 00000000000..0da1e4d5d3b --- /dev/null +++ b/changelogs/unreleased/27174-filter-filters.yml @@ -0,0 +1,4 @@ +--- +title: Prevent filtering issues by multiple Milestones or Authors +merge_request: +author: diff --git a/changelogs/unreleased/28494-mini-pipeline-graph-commit-view.yml b/changelogs/unreleased/28494-mini-pipeline-graph-commit-view.yml new file mode 100644 index 00000000000..67dbc30e760 --- /dev/null +++ b/changelogs/unreleased/28494-mini-pipeline-graph-commit-view.yml @@ -0,0 +1,4 @@ +--- +title: Adds pipeline mini-graph to system information box in Commit View +merge_request: +author: diff --git a/changelogs/unreleased/add_quick_submit_for_snippets_form.yml b/changelogs/unreleased/add_quick_submit_for_snippets_form.yml new file mode 100644 index 00000000000..088f1335796 --- /dev/null +++ b/changelogs/unreleased/add_quick_submit_for_snippets_form.yml @@ -0,0 +1,4 @@ +--- +title: Add quick submit for snippet forms +merge_request: 9911 +author: blackst0ne diff --git a/changelogs/unreleased/better-priority-sorting-2.yml b/changelogs/unreleased/better-priority-sorting-2.yml new file mode 100644 index 00000000000..ca0d14718dc --- /dev/null +++ b/changelogs/unreleased/better-priority-sorting-2.yml @@ -0,0 +1,4 @@ +--- +title: Allow filtering by all started milestones +merge_request: +author: diff --git a/changelogs/unreleased/better-priority-sorting.yml b/changelogs/unreleased/better-priority-sorting.yml new file mode 100644 index 00000000000..a44cd090ceb --- /dev/null +++ b/changelogs/unreleased/better-priority-sorting.yml @@ -0,0 +1,4 @@ +--- +title: Allow sorting by due date and priority +merge_request: +author: diff --git a/changelogs/unreleased/group-gear-setting-dropdown-to-tab.yml b/changelogs/unreleased/group-gear-setting-dropdown-to-tab.yml new file mode 100644 index 00000000000..aff1bdd957c --- /dev/null +++ b/changelogs/unreleased/group-gear-setting-dropdown-to-tab.yml @@ -0,0 +1,4 @@ +--- +title: Moved the gear settings dropdown to a tab in the groups view +merge_request: +author: diff --git a/changelogs/unreleased/option-to-be-notified-of-own-activity.yml b/changelogs/unreleased/option-to-be-notified-of-own-activity.yml deleted file mode 100644 index c2e0410cc33..00000000000 --- a/changelogs/unreleased/option-to-be-notified-of-own-activity.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Add option to receive email notifications about your own activity -merge_request: 8836 -author: Richard Macklin diff --git a/changelogs/unreleased/pipeline-tooltips-overflow.yml b/changelogs/unreleased/pipeline-tooltips-overflow.yml new file mode 100644 index 00000000000..184da8049f3 --- /dev/null +++ b/changelogs/unreleased/pipeline-tooltips-overflow.yml @@ -0,0 +1,4 @@ +--- +title: Fixed pipeline actions tooltips overflowing +merge_request: +author: diff --git a/config/initializers/rspec_profiling.rb b/config/initializers/rspec_profiling.rb index ac353d14499..70177995356 100644 --- a/config/initializers/rspec_profiling.rb +++ b/config/initializers/rspec_profiling.rb @@ -7,7 +7,7 @@ module RspecProfilingExt module Git def branch - ENV['CI_BUILD_REF_NAME'] || super + ENV['CI_COMMIT_REF_NAME'] || super end end diff --git a/config/karma.config.js b/config/karma.config.js index a23e62f5022..c1d3751d88f 100644 --- a/config/karma.config.js +++ b/config/karma.config.js @@ -36,7 +36,7 @@ module.exports = function(config) { { pattern: 'spec/javascripts/fixtures/**/*@(.json|.html|.html.raw)', included: false }, ], preprocessors: { - 'spec/javascripts/**/*.js?(.es6)': ['webpack', 'sourcemap'], + 'spec/javascripts/**/*.js': ['webpack', 'sourcemap'], }, reporters: [progressReporter, 'coverage-istanbul'], coverageIstanbulReporter: { diff --git a/config/webpack.config.js b/config/webpack.config.js index 8e2b11a4145..cbcc9ac5aea 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -56,7 +56,7 @@ var config = { module: { rules: [ { - test: /\.(js|es6)$/, + test: /\.js$/, exclude: /(node_modules|vendor\/assets)/, loader: 'babel-loader', options: { @@ -130,7 +130,7 @@ var config = { ], resolve: { - extensions: ['.js', '.es6', '.js.es6'], + extensions: ['.js'], alias: { '~': path.join(ROOT_PATH, 'app/assets/javascripts'), 'emojis': path.join(ROOT_PATH, 'fixtures/emojis'), diff --git a/db/migrate/20170123061730_add_notified_of_own_activity_to_users.rb b/db/migrate/20170123061730_add_notified_of_own_activity_to_users.rb deleted file mode 100644 index f90637e1e35..00000000000 --- a/db/migrate/20170123061730_add_notified_of_own_activity_to_users.rb +++ /dev/null @@ -1,14 +0,0 @@ -class AddNotifiedOfOwnActivityToUsers < ActiveRecord::Migration - include Gitlab::Database::MigrationHelpers - disable_ddl_transaction! - - DOWNTIME = false - - def up - add_column_with_default :users, :notified_of_own_activity, :boolean, default: false - end - - def down - remove_column :users, :notified_of_own_activity - end -end diff --git a/db/migrate/20170315174634_revert_add_notified_of_own_activity_to_users.rb b/db/migrate/20170315174634_revert_add_notified_of_own_activity_to_users.rb new file mode 100644 index 00000000000..b39c0a3be0f --- /dev/null +++ b/db/migrate/20170315174634_revert_add_notified_of_own_activity_to_users.rb @@ -0,0 +1,24 @@ +class RevertAddNotifiedOfOwnActivityToUsers < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + disable_ddl_transaction! + + DOWNTIME = false + + def up + if our_column_exists? + remove_column :users, :notified_of_own_activity + end + end + + def down + unless our_column_exists? + add_column_with_default :users, :notified_of_own_activity, :boolean, default: false + end + end + + private + + def our_column_exists? + column_exists?(:users, :notified_of_own_activity) + end +end diff --git a/db/schema.rb b/db/schema.rb index ca88198079f..3bef910c1d6 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20170313133418) do +ActiveRecord::Schema.define(version: 20170315174634) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -1318,7 +1318,6 @@ ActiveRecord::Schema.define(version: 20170313133418) do t.string "incoming_email_token" t.string "organization" t.boolean "authorized_projects_populated" - t.boolean "notified_of_own_activity", default: false, null: false t.boolean "ghost" end diff --git a/doc/administration/pages/source.md b/doc/administration/pages/source.md index f6f50e2c571..a45c3306457 100644 --- a/doc/administration/pages/source.md +++ b/doc/administration/pages/source.md @@ -1,5 +1,9 @@ # GitLab Pages administration for source installations +>**Note:** +Before attempting to enable GitLab Pages, first make sure you have +[installed GitLab](../../install/installation.md) successfully. + This is the documentation for configuring a GitLab Pages when you have installed GitLab from source and not using the Omnibus packages. @@ -13,7 +17,33 @@ Pages to the latest supported version. ## Overview -[Read the Omnibus overview section.](index.md#overview) +GitLab Pages makes use of the [GitLab Pages daemon], a simple HTTP server +written in Go that can listen on an external IP address and provide support for +custom domains and custom certificates. It supports dynamic certificates through +SNI and exposes pages using HTTP2 by default. +You are encouraged to read its [README][pages-readme] to fully understand how +it works. + +--- + +In the case of [custom domains](#custom-domains) (but not +[wildcard domains](#wildcard-domains)), the Pages daemon needs to listen on +ports `80` and/or `443`. For that reason, there is some flexibility in the way +which you can set it up: + +1. Run the Pages daemon in the same server as GitLab, listening on a secondary IP. +1. Run the Pages daemon in a separate server. In that case, the + [Pages path](#change-storage-path) must also be present in the server that + the Pages daemon is installed, so you will have to share it via network. +1. Run the Pages daemon in the same server as GitLab, listening on the same IP + but on different ports. In that case, you will have to proxy the traffic with + a loadbalancer. If you choose that route note that you should use TCP load + balancing for HTTPS. If you use TLS-termination (HTTPS-load balancing) the + pages will not be able to be served with user provided certificates. For + HTTP it's OK to use HTTP or TCP load balancing. + +In this document, we will proceed assuming the first option. If you are not +supporting custom domains a secondary IP is not needed. ## Prerequisites @@ -75,7 +105,7 @@ The Pages daemon doesn't listen to the outside world. cd /home/git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git cd gitlab-pages - sudo -u git -H git checkout v0.2.4 + sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_PAGES_VERSION) sudo -u git -H make ``` @@ -100,14 +130,21 @@ The Pages daemon doesn't listen to the outside world. https: false ``` -1. Copy the `gitlab-pages-ssl` Nginx configuration file: +1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in + order to enable the pages daemon. In `gitlab_pages_options` the + `-pages-domain` must match the `host` setting that you set above. - ```bash - sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf - sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf ``` + gitlab_pages_enabled=true + gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 + ``` + +1. Copy the `gitlab-pages` Nginx configuration file: - Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. + ```bash + sudo cp lib/support/nginx/gitlab-pages /etc/nginx/sites-available/gitlab-pages.conf + sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages.conf + ``` 1. Restart NGINX 1. [Restart GitLab][restart] @@ -131,7 +168,7 @@ outside world. cd /home/git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git cd gitlab-pages - sudo -u git -H git checkout v0.2.4 + sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_PAGES_VERSION) sudo -u git -H make ``` @@ -149,6 +186,17 @@ outside world. https: true ``` +1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in + order to enable the pages daemon. In `gitlab_pages_options` the + `-pages-domain` must match the `host` setting that you set above. + The `-root-cert` and `-root-key` settings are the wildcard TLS certificates + of the `example.io` domain: + + ``` + gitlab_pages_enabled=true + gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -root-cert /path/to/example.io.crt -root-key /path/to/example.io.key + ``` + 1. Copy the `gitlab-pages-ssl` Nginx configuration file: ```bash @@ -156,12 +204,9 @@ outside world. sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf ``` - Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. - 1. Restart NGINX 1. [Restart GitLab][restart] - ## Advanced configuration In addition to the wildcard domains, you can also have the option to configure @@ -189,7 +234,7 @@ world. Custom domains are supported, but no TLS. cd /home/git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git cd gitlab-pages - sudo -u git -H git checkout v0.2.4 + sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_PAGES_VERSION) sudo -u git -H make ``` @@ -224,12 +269,10 @@ world. Custom domains are supported, but no TLS. 1. Copy the `gitlab-pages-ssl` Nginx configuration file: ```bash - sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf - sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf + sudo cp lib/support/nginx/gitlab-pages /etc/nginx/sites-available/gitlab-pages.conf + sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages.conf ``` - Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. - 1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace `0.0.0.0` with `1.1.1.1`, where `1.1.1.1` the primary IP where GitLab listens to. @@ -257,7 +300,7 @@ world. Custom domains and TLS are supported. cd /home/git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git cd gitlab-pages - sudo -u git -H git checkout v0.2.4 + sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_PAGES_VERSION) sudo -u git -H make ``` @@ -300,8 +343,6 @@ world. Custom domains and TLS are supported. sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf ``` - Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. - 1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace `0.0.0.0` with `1.1.1.1`, where `1.1.1.1` the primary IP where GitLab listens to. @@ -392,5 +433,6 @@ than GitLab to prevent XSS attacks. [pages-userguide]: ../../user/project/pages/index.md [reconfigure]: ../restart_gitlab.md#omnibus-gitlab-reconfigure [restart]: ../restart_gitlab.md#installations-from-source -[gitlab-pages]: https://gitlab.com/gitlab-org/gitlab-pages/tree/v0.2.4 +[gitlab-pages]: https://gitlab.com/gitlab-org/gitlab-pages/tree/v0.4.0 +[gl-example]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/support/init.d/gitlab.default.example [shared runners]: ../../ci/runners/README.md diff --git a/doc/ci/docker/using_docker_build.md b/doc/ci/docker/using_docker_build.md index 8620984d40d..b3c9fe275c4 100644 --- a/doc/ci/docker/using_docker_build.md +++ b/doc/ci/docker/using_docker_build.md @@ -298,14 +298,14 @@ could look like: - docker:dind stage: build script: - - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN registry.example.com + - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN registry.example.com - docker build -t registry.example.com/group/project:latest . - docker push registry.example.com/group/project:latest ``` You have to use the special `gitlab-ci-token` user created for you in order to push to the Registry connected to your project. Its password is provided in the -`$CI_BUILD_TOKEN` variable. This allows you to automate building and deployment +`$CI_JOB_TOKEN` variable. This allows you to automate building and deployment of your Docker images. You can also make use of [other variables](../variables/README.md) to avoid hardcoding: @@ -315,10 +315,10 @@ services: - docker:dind variables: - IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_BUILD_REF_NAME + IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME before_script: - - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN $CI_REGISTRY + - docker login -u gitlab-ci-token -p $CI_COMMIT_TOKEN $CI_REGISTRY build: stage: build @@ -328,7 +328,7 @@ build: ``` Here, `$CI_REGISTRY_IMAGE` would be resolved to the address of the registry tied -to this project, and `$CI_BUILD_REF_NAME` would be resolved to the branch or +to this project, and `$CI_COMMIT_REF_NAME` would be resolved to the branch or tag name for this particular job. We also declare our own variable, `$IMAGE_TAG`, combining the two to save us some typing in the `script` section. @@ -350,11 +350,11 @@ stages: - deploy variables: - CONTAINER_TEST_IMAGE: registry.example.com/my-group/my-project:$CI_BUILD_REF_NAME + CONTAINER_TEST_IMAGE: registry.example.com/my-group/my-project:$CI_COMMIT_REF_NAME CONTAINER_RELEASE_IMAGE: registry.example.com/my-group/my-project:latest before_script: - - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN registry.example.com + - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN registry.example.com build: stage: build diff --git a/doc/ci/environments.md b/doc/ci/environments.md index 3c31ba45d3d..b28f3e13eae 100644 --- a/doc/ci/environments.md +++ b/doc/ci/environments.md @@ -263,7 +263,7 @@ This works just like any other terminal - you'll be in the container created by your deployment, so you can run shell commands and get responses in real time, check the logs, try out configuration or code tweaks, etc. You can open multiple terminals to the same environment - they each get their own shell -session - and even a multiplexer like `screen` or `tmux`! +session - and even a multiplexer like `screen` or `tmux`! >**Note:** Container-based deployments often lack basic tools (like an editor), and may @@ -295,7 +295,7 @@ deploy_review: script: - echo "Deploy a review app" environment: - name: review/$CI_BUILD_REF_NAME + name: review/$CI_COMMIT_REF_NAME url: https://$CI_ENVIRONMENT_SLUG.example.com only: - branches @@ -306,22 +306,22 @@ deploy_review: Let's break it down in pieces. The job's name is `deploy_review` and it runs on the `deploy` stage. The `script` at this point is fictional, you'd have to use your own based on your deployment. Then, we set the `environment` with the -`environment:name` being `review/$CI_BUILD_REF_NAME`. Now that's an interesting +`environment:name` being `review/$CI_COMMIT_REF_NAME`. Now that's an interesting one. Since the [environment name][env-name] can contain slashes (`/`), we can use this pattern to distinguish between dynamic environments and the regular ones. -So, the first part is `review`, followed by a `/` and then `$CI_BUILD_REF_NAME` -which takes the value of the branch name. Since `$CI_BUILD_REF_NAME` itself may +So, the first part is `review`, followed by a `/` and then `$CI_COMMIT_REF_NAME` +which takes the value of the branch name. Since `$CI_COMMIT_REF_NAME` itself may also contain `/`, or other characters that would be invalid in a domain name or URL, we use `$CI_ENVIRONMENT_SLUG` in the `environment:url` so that the environment can get a specific and distinct URL for each branch. In this case, -given a `$CI_BUILD_REF_NAME` of `100-Do-The-Thing`, the URL will be something +given a `$CI_COMMIT_REF_NAME` of `100-Do-The-Thing`, the URL will be something like `https://100-do-the-4f99a2.example.com`. Again, the way you set up the web server to serve these requests is based on your setup. -You could also use `$CI_BUILD_REF_SLUG` in `environment:url`, e.g.: -`https://$CI_BUILD_REF_SLUG.example.com`. We use `$CI_ENVIRONMENT_SLUG` +You could also use `$CI_COMMIT_REF_SLUG` in `environment:url`, e.g.: +`https://$CI_COMMIT_REF_SLUG.example.com`. We use `$CI_ENVIRONMENT_SLUG` here because it is guaranteed to be unique, but if you're using a workflow like [GitLab Flow][gitlab-flow], collisions are very unlikely, and you may prefer environment names to be more closely based on the branch name - the example @@ -356,7 +356,7 @@ deploy_review: script: - echo "Deploy a review app" environment: - name: review/$CI_BUILD_REF_NAME + name: review/$CI_COMMIT_REF_NAME url: https://$CI_ENVIRONMENT_SLUG.example.com only: - branches @@ -387,16 +387,16 @@ deploy_prod: A more realistic example would include copying files to a location where a webserver (NGINX) could then read and serve. The example below will copy the -`public` directory to `/srv/nginx/$CI_BUILD_REF_SLUG/public`: +`public` directory to `/srv/nginx/$CI_COMMIT_REF_SLUG/public`: ```yaml review_app: stage: deploy script: - - rsync -av --delete public /srv/nginx/$CI_BUILD_REF_SLUG + - rsync -av --delete public /srv/nginx/$CI_COMMIT_REF_SLUG environment: - name: review/$CI_BUILD_REF_NAME - url: https://$CI_BUILD_REF_SLUG.example.com + name: review/$CI_COMMIT_REF_NAME + url: https://$CI_COMMIT_REF_SLUG.example.com ``` It is assumed that the user has already setup NGINX and GitLab Runner in the @@ -526,7 +526,7 @@ deploy_review: script: - echo "Deploy a review app" environment: - name: review/$CI_BUILD_REF_NAME + name: review/$CI_COMMIT_REF_NAME url: https://$CI_ENVIRONMENT_SLUG.example.com on_stop: stop_review only: @@ -542,7 +542,7 @@ stop_review: - echo "Remove review app" when: manual environment: - name: review/$CI_BUILD_REF_NAME + name: review/$CI_COMMIT_REF_NAME action: stop ``` @@ -568,13 +568,13 @@ You can read more in the [`.gitlab-ci.yml` reference][onstop]. As we've seen in the [dynamic environments](#dynamic-environments), you can prepend their name with a word, then followed by a `/` and finally the branch -name which is automatically defined by the `CI_BUILD_REF_NAME` variable. +name which is automatically defined by the `CI_COMMIT_REF_NAME` variable. In short, environments that are named like `type/foo` are presented under a group named `type`. -In our minimal example, we name the environments `review/$CI_BUILD_REF_NAME` -where `$CI_BUILD_REF_NAME` is the branch name: +In our minimal example, we name the environments `review/$CI_COMMIT_REF_NAME` +where `$CI_COMMIT_REF_NAME` is the branch name: ```yaml deploy_review: @@ -582,7 +582,7 @@ deploy_review: script: - echo "Deploy a review app" environment: - name: review/$CI_BUILD_REF_NAME + name: review/$CI_COMMIT_REF_NAME ``` In that case, if you visit the Environments page, and provided the branches diff --git a/doc/ci/review_apps/index.md b/doc/ci/review_apps/index.md index c679ea4e298..28c484ddbe6 100644 --- a/doc/ci/review_apps/index.md +++ b/doc/ci/review_apps/index.md @@ -80,7 +80,7 @@ The process of adding Review Apps in your workflow would look like: 1. [Install][install-runner] and [configure][conf-runner] a Runner that does the deployment. 1. Set up a job in `.gitlab-ci.yml` that uses the predefined - [predefined CI environment variable][variables] `${CI_BUILD_REF_NAME}` to + [predefined CI environment variable][variables] `${CI_COMMIT_REF_NAME}` to create dynamic environments and restrict it to run only on branches. 1. Optionally set a job that [manually stops][manual-env] the Review Apps. diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index 03e6b5303c5..5baff9cc6ba 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -236,18 +236,18 @@ Running on runner-8a2f473d-project-1796893-concurrent-0 via runner-8a2f473d-mach ++ CI=true ++ export CI_DEBUG_TRACE=false ++ CI_DEBUG_TRACE=false -++ export CI_BUILD_REF=dd648b2e48ce6518303b0bb580b2ee32fadaf045 -++ CI_BUILD_REF=dd648b2e48ce6518303b0bb580b2ee32fadaf045 -++ export CI_BUILD_BEFORE_SHA=dd648b2e48ce6518303b0bb580b2ee32fadaf045 -++ CI_BUILD_BEFORE_SHA=dd648b2e48ce6518303b0bb580b2ee32fadaf045 -++ export CI_BUILD_REF_NAME=master -++ CI_BUILD_REF_NAME=master -++ export CI_BUILD_ID=7046507 -++ CI_BUILD_ID=7046507 -++ export CI_BUILD_REPO=https://gitlab-ci-token:xxxxxxxxxxxxxxxxxxxx@example.com/gitlab-examples/ci-debug-trace.git -++ CI_BUILD_REPO=https://gitlab-ci-token:xxxxxxxxxxxxxxxxxxxx@example.com/gitlab-examples/ci-debug-trace.git -++ export CI_BUILD_TOKEN=xxxxxxxxxxxxxxxxxxxx -++ CI_BUILD_TOKEN=xxxxxxxxxxxxxxxxxxxx +++ export CI_COMMIT_REF=dd648b2e48ce6518303b0bb580b2ee32fadaf045 +++ CI_COMMIT_REF=dd648b2e48ce6518303b0bb580b2ee32fadaf045 +++ export CI_COMMIT_BEFORE_SHA=dd648b2e48ce6518303b0bb580b2ee32fadaf045 +++ CI_COMMIT_BEFORE_SHA=dd648b2e48ce6518303b0bb580b2ee32fadaf045 +++ export CI_COMMIT_REF_NAME=master +++ CI_COMMIT_REF_NAME=master +++ export CI_JOB_ID=7046507 +++ CI_JOB_ID=7046507 +++ export CI_REPOSITORY_URL=https://gitlab-ci-token:xxxxxxxxxxxxxxxxxxxx@example.com/gitlab-examples/ci-debug-trace.git +++ CI_REPOSITORY_URL=https://gitlab-ci-token:xxxxxxxxxxxxxxxxxxxx@example.com/gitlab-examples/ci-debug-trace.git +++ export CI_JOB_TOKEN=xxxxxxxxxxxxxxxxxxxx +++ CI_JOB_TOKEN=xxxxxxxxxxxxxxxxxxxx ++ export CI_PROJECT_ID=1796893 ++ CI_PROJECT_ID=1796893 ++ export CI_PROJECT_DIR=/builds/gitlab-examples/ci-debug-trace @@ -266,20 +266,20 @@ Running on runner-8a2f473d-project-1796893-concurrent-0 via runner-8a2f473d-mach ++ CI=true ++ export GITLAB_CI=true ++ GITLAB_CI=true -++ export CI_BUILD_ID=7046507 -++ CI_BUILD_ID=7046507 -++ export CI_BUILD_TOKEN=xxxxxxxxxxxxxxxxxxxx -++ CI_BUILD_TOKEN=xxxxxxxxxxxxxxxxxxxx -++ export CI_BUILD_REF=dd648b2e48ce6518303b0bb580b2ee32fadaf045 -++ CI_BUILD_REF=dd648b2e48ce6518303b0bb580b2ee32fadaf045 -++ export CI_BUILD_BEFORE_SHA=dd648b2e48ce6518303b0bb580b2ee32fadaf045 -++ CI_BUILD_BEFORE_SHA=dd648b2e48ce6518303b0bb580b2ee32fadaf045 -++ export CI_BUILD_REF_NAME=master -++ CI_BUILD_REF_NAME=master -++ export CI_BUILD_NAME=debug_trace -++ CI_BUILD_NAME=debug_trace -++ export CI_BUILD_STAGE=test -++ CI_BUILD_STAGE=test +++ export CI_JOB_ID=7046507 +++ CI_JOB_ID=7046507 +++ export CI_JOB_TOKEN=xxxxxxxxxxxxxxxxxxxx +++ CI_JOB_TOKEN=xxxxxxxxxxxxxxxxxxxx +++ export CI_COMMIT_REF=dd648b2e48ce6518303b0bb580b2ee32fadaf045 +++ CI_COMMIT_REF=dd648b2e48ce6518303b0bb580b2ee32fadaf045 +++ export CI_COMMIT_BEFORE_SHA=dd648b2e48ce6518303b0bb580b2ee32fadaf045 +++ CI_COMMIT_BEFORE_SHA=dd648b2e48ce6518303b0bb580b2ee32fadaf045 +++ export CI_COMMIT_REF_NAME=master +++ CI_COMMIT_REF_NAME=master +++ export CI_COMMIT_NAME=debug_trace +++ CI_JOB_NAME=debug_trace +++ export CI_JOB_STAGE=test +++ CI_JOB_STAGE=test ++ export CI_SERVER_NAME=GitLab ++ CI_SERVER_NAME=GitLab ++ export CI_SERVER_VERSION=8.14.3-ee diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 49fa8761e5e..ad3ebd144df 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -153,7 +153,7 @@ thus allowing to fine tune them. Variables can be also defined on a [job level](#job-variables). Except for the user defined variables, there are also the ones set up by the -Runner itself. One example would be `CI_BUILD_REF_NAME` which has the value of +Runner itself. One example would be `CI_COMMIT_REF_NAME` which has the value of the branch or tag name for which project is built. Apart from the variables you can set in `.gitlab-ci.yml`, there are also the so called secret variables which can be set in GitLab's UI. @@ -252,7 +252,7 @@ To enable per-job caching: ```yaml cache: - key: "$CI_BUILD_NAME" + key: "$CI_JOB_NAME" untracked: true ``` @@ -260,7 +260,7 @@ To enable per-branch caching: ```yaml cache: - key: "$CI_BUILD_REF_NAME" + key: "$CI_COMMIT_REF_NAME" untracked: true ``` @@ -268,7 +268,7 @@ To enable per-job and per-branch caching: ```yaml cache: - key: "$CI_BUILD_NAME/$CI_BUILD_REF_NAME" + key: "$CI_JOB_NAME/$CI_COMMIT_REF_NAME" untracked: true ``` @@ -276,7 +276,7 @@ To enable per-branch and per-stage caching: ```yaml cache: - key: "$CI_BUILD_STAGE/$CI_BUILD_REF_NAME" + key: "$CI_JOB_STAGE/$CI_COMMIT_REF_NAME" untracked: true ``` @@ -285,7 +285,7 @@ If you use **Windows Batch** to run your shell scripts you need to replace ```yaml cache: - key: "%CI_BUILD_STAGE%/%CI_BUILD_REF_NAME%" + key: "%CI_JOB_STAGE%/%CI_COMMIT_REF_NAME%" untracked: true ``` @@ -739,12 +739,12 @@ deploy as review app: stage: deploy script: make deploy environment: - name: review/$CI_BUILD_REF_NAME + name: review/$CI_COMMIT_REF_NAME url: https://$CI_ENVIRONMENT_SLUG.example.com/ ``` The `deploy as review app` job will be marked as deployment to dynamically -create the `review/$CI_BUILD_REF_NAME` environment, where `$CI_BUILD_REF_NAME` +create the `review/$CI_COMMIT_REF_NAME` environment, where `$CI_COMMIT_REF_NAME` is an [environment variable][variables] set by the Runner. The `$CI_ENVIRONMENT_SLUG` variable is based on the environment name, but suitable for inclusion in URLs. In this case, if the `deploy as review app` job was run @@ -850,7 +850,7 @@ To create an archive with a name of the current job: ```yaml job: artifacts: - name: "$CI_BUILD_NAME" + name: "$CI_JOB_NAME" ``` To create an archive with a name of the current branch or tag including only @@ -859,7 +859,7 @@ the files that are untracked by Git: ```yaml job: artifacts: - name: "$CI_BUILD_REF_NAME" + name: "$CI_COMMIT_REF_NAME" untracked: true ``` @@ -869,7 +869,7 @@ tag including only the files that are untracked by Git: ```yaml job: artifacts: - name: "${CI_BUILD_NAME}_${CI_BUILD_REF_NAME}" + name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}" untracked: true ``` @@ -878,7 +878,7 @@ To create an archive with a name of the current [stage](#stages) and branch name ```yaml job: artifacts: - name: "${CI_BUILD_STAGE}_${CI_BUILD_REF_NAME}" + name: "${CI_JOB_STAGE}_${CI_COMMIT_REF_NAME}" untracked: true ``` @@ -890,7 +890,7 @@ If you use **Windows Batch** to run your shell scripts you need to replace ```yaml job: artifacts: - name: "%CI_BUILD_STAGE%_%CI_BUILD_REF_NAME%" + name: "%CI_JOB_STAGE%_%CI_COMMIT_REF_NAME%" untracked: true ``` diff --git a/doc/development/testing.md b/doc/development/testing.md index 9b545d7f0f1..5ac7b8dadeb 100644 --- a/doc/development/testing.md +++ b/doc/development/testing.md @@ -35,8 +35,8 @@ GitLab uses [Karma] to run its [Jasmine] JavaScript specs. They can be run on the command line via `bundle exec karma`. - JavaScript tests live in `spec/javascripts/`, matching the folder structure of - `app/assets/javascripts/`: `app/assets/javascripts/behaviors/autosize.js.es6` has a corresponding - `spec/javascripts/behaviors/autosize_spec.js.es6` file. + `app/assets/javascripts/`: `app/assets/javascripts/behaviors/autosize.js` has a corresponding + `spec/javascripts/behaviors/autosize_spec.js` file. - Haml fixtures required for JavaScript tests live in `spec/javascripts/fixtures`. They should contain the bare minimum amount of markup necessary for the test. diff --git a/doc/user/project/labels.md b/doc/user/project/labels.md index cf1d9cbe69c..8ec7adad172 100644 --- a/doc/user/project/labels.md +++ b/doc/user/project/labels.md @@ -65,7 +65,7 @@ issues and merge requests assigned to each label. > https://gitlab.com/gitlab-org/gitlab-ce/issues/18554. Prioritized labels are like any other label, but sorted by priority. This allows -you to sort issues and merge requests by priority. +you to sort issues and merge requests by label priority. To prioritize labels, navigate to your project's **Issues > Labels** and click on the star icon next to them to put them in the priority list. Click on the @@ -77,9 +77,13 @@ having their priority set to null. ![Prioritize labels](img/labels_prioritize.png) -Now that you have labels prioritized, you can use the 'Priority' filter in the -issues or merge requests tracker. Those with the highest priority label, will -appear on top. +Now that you have labels prioritized, you can use the 'Priority' and 'Label +priority' filters in the issues or merge requests tracker. + +The 'Label priority' filter puts issues with the highest priority label on top. + +The 'Priority' filter sorts issues by their soonest milestone due date, then by +label priority. ![Filter labels by priority](img/labels_filter_by_priority.png) @@ -156,4 +160,3 @@ mouse over the label in the issue tracker or wherever else the label is rendered. ![Label tooltips](img/labels_description_tooltip.png) - diff --git a/doc/user/project/new_ci_build_permissions_model.md b/doc/user/project/new_ci_build_permissions_model.md index 5f631f63050..b559d132590 100644 --- a/doc/user/project/new_ci_build_permissions_model.md +++ b/doc/user/project/new_ci_build_permissions_model.md @@ -119,7 +119,7 @@ And then the users could also use it in their CI jobs all Docker related commands to interact with GitLab Container Registry. For example: ``` -docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN registry.gitlab.com +docker login -u gitlab-ci-token -p $CI_JOB_TOKEN registry.gitlab.com ``` Using single token had multiple security implications: @@ -208,7 +208,7 @@ This is how an example usage can look like: ``` test: script: - - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN $CI_REGISTRY + - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY - docker pull $CI_REGISTRY/group/other-project:latest - docker run $CI_REGISTRY/group/other-project:latest ``` diff --git a/doc/workflow/milestones.md b/doc/workflow/milestones.md index dff36899aec..37afe553e55 100644 --- a/doc/workflow/milestones.md +++ b/doc/workflow/milestones.md @@ -1,13 +1,28 @@ # Milestones -Milestones allow you to organize issues and merge requests into a cohesive group, optionally setting a due date. +Milestones allow you to organize issues and merge requests into a cohesive group, optionally setting a due date. A common use is keeping track of an upcoming software version. Milestones are created per-project. ![milestone form](milestones/form.png) ## Groups and milestones -You can create a milestone for several projects in the same group simultaneously. +You can create a milestone for several projects in the same group simultaneously. On the group's milestones page, you will be able to see the status of that milestone across all of the selected projects. ![group milestone form](milestones/group_form.png) + +## Special milestone filters + +In addition to the milestones that exist in the project or group, there are some +special options available when filtering by milestone: + +* **No Milestone** - only show issues or merge requests without a milestone. +* **Upcoming** - show issues or merge request that belong to the next open + milestone with a due date, by project. (For example: if project A has + milestone v1 due in three days, and project B has milestone v2 due in a week, + then this will show issues or merge requests from milestone v1 in project A + and milestone v2 in project B.) +* **Started** - show issues or merge requests from any milestone with a start + date less than today. Note that this can return results from several + milestones in the same project. diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb index 18e267294e4..cf75fac8ac6 100644 --- a/features/steps/project/commits/commits.rb +++ b/features/steps/project/commits/commits.rb @@ -163,7 +163,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps end step 'I see commit ci info' do - expect(page).to have_content "Pipeline #1 for 570e7b2a pending" + expect(page).to have_content "Pipeline #1 pending" end step 'I search "submodules" commits' do diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb index b25d6f18d59..fd4a6a107c2 100644 --- a/lib/banzai/pipeline/gfm_pipeline.rb +++ b/lib/banzai/pipeline/gfm_pipeline.rb @@ -2,10 +2,10 @@ module Banzai module Pipeline class GfmPipeline < BasePipeline # These filters convert GitLab Flavored Markdown (GFM) to HTML. - # The handlers defined in app/assets/javascripts/copy_as_gfm.js.es6 + # The handlers defined in app/assets/javascripts/copy_as_gfm.js # consequently convert that same HTML to GFM to be copied to the clipboard. # Every filter that generates HTML from GFM should have a handler in - # app/assets/javascripts/copy_as_gfm.js.es6, in reverse order. + # app/assets/javascripts/copy_as_gfm.js, in reverse order. # The GFM-to-HTML-to-GFM cycle is tested in spec/features/copy_as_gfm_spec.rb. def self.filters @filters ||= FilterArray[ diff --git a/lib/gitlab/redis.rb b/lib/gitlab/redis.rb index 62dbd429156..bc5370de32a 100644 --- a/lib/gitlab/redis.rb +++ b/lib/gitlab/redis.rb @@ -1,6 +1,7 @@ # This file should not have any direct dependency on Rails environment # please require all dependencies below: require 'active_support/core_ext/hash/keys' +require 'active_support/core_ext/module/delegation' module Gitlab class Redis @@ -9,7 +10,6 @@ module Gitlab SIDEKIQ_NAMESPACE = 'resque:gitlab'.freeze MAILROOM_NAMESPACE = 'mail_room:gitlab'.freeze DEFAULT_REDIS_URL = 'redis://localhost:6379'.freeze - CONFIG_FILE = File.expand_path('../../config/resque.yml', __dir__) class << self delegate :params, :url, to: :new @@ -33,13 +33,17 @@ module Gitlab return @_raw_config if defined?(@_raw_config) begin - @_raw_config = ERB.new(File.read(CONFIG_FILE)).result.freeze + @_raw_config = ERB.new(File.read(config_file)).result.freeze rescue Errno::ENOENT @_raw_config = false end @_raw_config end + + def config_file + ENV['GITLAB_REDIS_CONFIG_FILE'] || File.expand_path('../../config/resque.yml', __dir__) + end end def initialize(rails_env = nil) diff --git a/lib/tasks/gitlab/dev.rake b/lib/tasks/gitlab/dev.rake index 7db0779def8..7ccda04a35f 100644 --- a/lib/tasks/gitlab/dev.rake +++ b/lib/tasks/gitlab/dev.rake @@ -4,7 +4,7 @@ namespace :gitlab do task :ee_compat_check, [:branch] => :environment do |_, args| opts = if ENV['CI'] - { branch: ENV['CI_BUILD_REF_NAME'] } + { branch: ENV['CI_COMMIT_REF_NAME'] } else unless args[:branch] puts "Must specify a branch as an argument".color(:red) diff --git a/package.json b/package.json index 9652dd8f972..1048e29d0ac 100644 --- a/package.json +++ b/package.json @@ -2,9 +2,9 @@ "private": true, "scripts": { "dev-server": "webpack-dev-server --config config/webpack.config.js", - "eslint": "eslint --max-warnings 0 --ext .js,.js.es6 .", - "eslint-fix": "eslint --max-warnings 0 --ext .js,.js.es6 --fix .", - "eslint-report": "eslint --max-warnings 0 --ext .js,.js.es6 --format html --output-file ./eslint-report.html .", + "eslint": "eslint --max-warnings 0 --ext .js .", + "eslint-fix": "eslint --max-warnings 0 --ext .js --fix .", + "eslint-report": "eslint --max-warnings 0 --ext .js --format html --output-file ./eslint-report.html .", "karma": "karma start config/karma.config.js --single-run", "karma-start": "karma start config/karma.config.js", "webpack": "webpack --config config/webpack.config.js", @@ -62,7 +62,6 @@ "exclude": [ "spec/javascripts/test_bundle.js", "spec/javascripts/**/*_spec.js", - "spec/javascripts/**/*_spec.js.es6", "app/assets/javascripts/droplab/**/*" ] } diff --git a/spec/config/mail_room_spec.rb b/spec/config/mail_room_spec.rb index 0b8ff006d22..092048a6259 100644 --- a/spec/config/mail_room_spec.rb +++ b/spec/config/mail_room_spec.rb @@ -1,20 +1,36 @@ require 'spec_helper' describe 'mail_room.yml' do - let(:config_path) { 'config/mail_room.yml' } - let(:configuration) { YAML.load(ERB.new(File.read(config_path)).result) } - before(:each) { clear_raw_config } - after(:each) { clear_raw_config } + include StubENV - context 'when incoming email is disabled' do - before do - ENV['MAIL_ROOM_GITLAB_CONFIG_FILE'] = Rails.root.join('spec/fixtures/config/mail_room_disabled.yml').to_s - Gitlab::MailRoom.reset_config! - end + let(:mailroom_config_path) { 'config/mail_room.yml' } + let(:gitlab_config_path) { 'config/mail_room.yml' } + let(:redis_config_path) { 'config/resque.yml' } - after do - ENV['MAIL_ROOM_GITLAB_CONFIG_FILE'] = nil - end + let(:configuration) do + vars = { + 'MAIL_ROOM_GITLAB_CONFIG_FILE' => absolute_path(gitlab_config_path), + 'GITLAB_REDIS_CONFIG_FILE' => absolute_path(redis_config_path) + } + cmd = "puts ERB.new(File.read(#{absolute_path(mailroom_config_path).inspect})).result" + + output, status = Gitlab::Popen.popen(%W(ruby -rerb -e #{cmd}), absolute_path('config'), vars) + raise "Error interpreting #{mailroom_config_path}: #{output}" unless status.zero? + + YAML.load(output) + end + + before(:each) do + stub_env('GITLAB_REDIS_CONFIG_FILE', absolute_path(redis_config_path)) + clear_redis_raw_config + end + + after(:each) do + clear_redis_raw_config + end + + context 'when incoming email is disabled' do + let(:gitlab_config_path) { 'spec/fixtures/config/mail_room_disabled.yml' } it 'contains no configuration' do expect(configuration[:mailboxes]).to be_nil @@ -22,21 +38,12 @@ describe 'mail_room.yml' do end context 'when incoming email is enabled' do - let(:redis_config) { Rails.root.join('spec/fixtures/config/redis_new_format_host.yml') } - let(:gitlab_redis) { Gitlab::Redis.new(Rails.env) } - - before do - ENV['MAIL_ROOM_GITLAB_CONFIG_FILE'] = Rails.root.join('spec/fixtures/config/mail_room_enabled.yml').to_s - Gitlab::MailRoom.reset_config! - end + let(:gitlab_config_path) { 'spec/fixtures/config/mail_room_enabled.yml' } + let(:redis_config_path) { 'spec/fixtures/config/redis_new_format_host.yml' } - after do - ENV['MAIL_ROOM_GITLAB_CONFIG_FILE'] = nil - end + let(:gitlab_redis) { Gitlab::Redis.new(Rails.env) } it 'contains the intended configuration' do - stub_const('Gitlab::Redis::CONFIG_FILE', redis_config) - expect(configuration[:mailboxes].length).to eq(1) mailbox = configuration[:mailboxes].first @@ -66,9 +73,13 @@ describe 'mail_room.yml' do end end - def clear_raw_config + def clear_redis_raw_config Gitlab::Redis.remove_instance_variable(:@_raw_config) rescue NameError # raised if @_raw_config was not set; ignore end + + def absolute_path(path) + Rails.root.join(path).to_s + end end diff --git a/spec/controllers/profiles/notifications_controller_spec.rb b/spec/controllers/profiles/notifications_controller_spec.rb deleted file mode 100644 index 58caf7999cf..00000000000 --- a/spec/controllers/profiles/notifications_controller_spec.rb +++ /dev/null @@ -1,45 +0,0 @@ -require 'spec_helper' - -describe Profiles::NotificationsController do - let(:user) do - create(:user) do |user| - user.emails.create(email: 'original@example.com') - user.emails.create(email: 'new@example.com') - user.update(notification_email: 'original@example.com') - user.save! - end - end - - describe 'GET show' do - it 'renders' do - sign_in(user) - - get :show - - expect(response).to render_template :show - end - end - - describe 'POST update' do - it 'updates only permitted attributes' do - sign_in(user) - - put :update, user: { notification_email: 'new@example.com', notified_of_own_activity: true, admin: true } - - user.reload - expect(user.notification_email).to eq('new@example.com') - expect(user.notified_of_own_activity).to eq(true) - expect(user.admin).to eq(false) - expect(controller).to set_flash[:notice].to('Notification settings saved') - end - - it 'shows an error message if the params are invalid' do - sign_in(user) - - put :update, user: { notification_email: '' } - - expect(user.reload.notification_email).to eq('original@example.com') - expect(controller).to set_flash[:alert].to('Failed to save new settings') - end - end -end diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index a1ec41322ad..a88ffc1ea6a 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -78,10 +78,12 @@ describe ProjectsController do it 'shows issues list page if wiki is disabled' do project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::DISABLED) + create(:issue, project: project) get :show, namespace_id: project.namespace, id: project expect(response).to render_template('projects/issues/_issues') + expect(assigns(:issuable_meta_data)).not_to be_nil end it 'shows customize workflow page if wiki and issues are disabled' do diff --git a/spec/features/boards/add_issues_modal_spec.rb b/spec/features/boards/add_issues_modal_spec.rb index f7f2d883d2f..c87c0632cb5 100644 --- a/spec/features/boards/add_issues_modal_spec.rb +++ b/spec/features/boards/add_issues_modal_spec.rb @@ -107,6 +107,7 @@ describe 'Issue Boards add issue modal', :feature, :js do it 'returns issues' do page.within('.add-issues-modal') do find('.form-control').native.send_keys(issue.title) + find('.form-control').native.send_keys(:enter) expect(page).to have_selector('.card', count: 1) end @@ -115,6 +116,7 @@ describe 'Issue Boards add issue modal', :feature, :js do it 'returns no issues' do page.within('.add-issues-modal') do find('.form-control').native.send_keys('testing search') + find('.form-control').native.send_keys(:enter) expect(page).not_to have_selector('.card') expect(page).not_to have_content("You haven't added any issues to your project yet") diff --git a/spec/features/boards/modal_filter_spec.rb b/spec/features/boards/modal_filter_spec.rb index 1cf0d11d448..e2281a7da55 100644 --- a/spec/features/boards/modal_filter_spec.rb +++ b/spec/features/boards/modal_filter_spec.rb @@ -1,7 +1,6 @@ require 'rails_helper' describe 'Issue Boards add issue modal filtering', :feature, :js do - include WaitForAjax include WaitForVueResource let(:project) { create(:empty_project, :public) } @@ -23,6 +22,7 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do page.within('.add-issues-modal') do find('.form-control').native.send_keys('testing empty state') + find('.form-control').native.send_keys(:enter) wait_for_vue_resource @@ -33,13 +33,11 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do it 'restores filters when closing' do visit_board - page.within('.add-issues-modal') do - click_button 'Milestone' - - wait_for_ajax - - click_link 'Upcoming' + set_filter('milestone') + click_filter_link('Upcoming') + submit_filter + page.within('.add-issues-modal') do wait_for_vue_resource expect(page).to have_selector('.card', count: 0) @@ -56,39 +54,44 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do end end - context 'author' do - let!(:issue) { create(:issue, project: project, author: user2) } - - before do - project.team << [user2, :developer] + it 'resotres filters after clicking clear button' do + visit_board - visit_board - end + set_filter('milestone') + click_filter_link('Upcoming') + submit_filter - it 'filters by any author' do - page.within('.add-issues-modal') do - click_button 'Author' + page.within('.add-issues-modal') do + wait_for_vue_resource - wait_for_ajax + expect(page).to have_selector('.card', count: 0) - click_link 'Any Author' + find('.clear-search').click - wait_for_vue_resource + wait_for_vue_resource - expect(page).to have_selector('.card', count: 2) - end + expect(page).to have_selector('.card', count: 1) end + end - it 'filters by selected user' do - page.within('.add-issues-modal') do - click_button 'Author' + context 'author' do + let!(:issue) { create(:issue, project: project, author: user2) } + + before do + project.team << [user2, :developer] - wait_for_ajax + visit_board + end - click_link user2.name + it 'filters by selected user' do + set_filter('author') + click_filter_link(user2.name) + submit_filter + page.within('.add-issues-modal') do wait_for_vue_resource + expect(page).to have_selector('.js-visual-token', text: user2.username) expect(page).to have_selector('.card', count: 1) end end @@ -103,46 +106,28 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do visit_board end - it 'filters by any assignee' do - page.within('.add-issues-modal') do - click_button 'Assignee' - - wait_for_ajax - - click_link 'Any Assignee' - - wait_for_vue_resource - - expect(page).to have_selector('.card', count: 2) - end - end - it 'filters by unassigned' do - page.within('.add-issues-modal') do - click_button 'Assignee' - - wait_for_ajax - - click_link 'Unassigned' + set_filter('assignee') + click_filter_link('No Assignee') + submit_filter + page.within('.add-issues-modal') do wait_for_vue_resource + expect(page).to have_selector('.js-visual-token', text: 'none') expect(page).to have_selector('.card', count: 1) end end it 'filters by selected user' do - page.within('.add-issues-modal') do - click_button 'Assignee' - - wait_for_ajax - - page.within '.dropdown-menu-user' do - click_link user2.name - end + set_filter('assignee') + click_filter_link(user2.name) + submit_filter + page.within('.add-issues-modal') do wait_for_vue_resource + expect(page).to have_selector('.js-visual-token', text: user2.username) expect(page).to have_selector('.card', count: 1) end end @@ -156,44 +141,28 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do visit_board end - it 'filters by any milestone' do - page.within('.add-issues-modal') do - click_button 'Milestone' - - wait_for_ajax - - click_link 'Any Milestone' - - wait_for_vue_resource - - expect(page).to have_selector('.card', count: 2) - end - end - it 'filters by upcoming milestone' do - page.within('.add-issues-modal') do - click_button 'Milestone' - - wait_for_ajax - - click_link 'Upcoming' + set_filter('milestone') + click_filter_link('Upcoming') + submit_filter + page.within('.add-issues-modal') do wait_for_vue_resource + expect(page).to have_selector('.js-visual-token', text: 'upcoming') expect(page).to have_selector('.card', count: 0) end end it 'filters by selected milestone' do - page.within('.add-issues-modal') do - click_button 'Milestone' - - wait_for_ajax - - click_link milestone.name + set_filter('milestone') + click_filter_link(milestone.name) + submit_filter + page.within('.add-issues-modal') do wait_for_vue_resource + expect(page).to have_selector('.js-visual-token', text: milestone.name) expect(page).to have_selector('.card', count: 1) end end @@ -207,44 +176,28 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do visit_board end - it 'filters by any label' do - page.within('.add-issues-modal') do - click_button 'Label' - - wait_for_ajax - - click_link 'Any Label' - - wait_for_vue_resource - - expect(page).to have_selector('.card', count: 2) - end - end - it 'filters by no label' do - page.within('.add-issues-modal') do - click_button 'Label' - - wait_for_ajax - - click_link 'No Label' + set_filter('label') + click_filter_link('No Label') + submit_filter + page.within('.add-issues-modal') do wait_for_vue_resource + expect(page).to have_selector('.js-visual-token', text: 'none') expect(page).to have_selector('.card', count: 1) end end it 'filters by label' do - page.within('.add-issues-modal') do - click_button 'Label' - - wait_for_ajax - - click_link label.title + set_filter('label') + click_filter_link(label.title) + submit_filter + page.within('.add-issues-modal') do wait_for_vue_resource + expect(page).to have_selector('.js-visual-token', text: label.title) expect(page).to have_selector('.card', count: 1) end end @@ -256,4 +209,20 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do click_button('Add issues') end + + def set_filter(type, text = '') + find('.add-issues-modal .filtered-search').native.send_keys("#{type}:#{text}") + end + + def submit_filter + find('.add-issues-modal .filtered-search').native.send_keys(:enter) + end + + def click_filter_link(link_text) + page.within('.add-issues-modal .filtered-search-input-container') do + expect(page).to have_button(link_text) + + click_button(link_text) + end + end end diff --git a/spec/features/copy_as_gfm_spec.rb b/spec/features/copy_as_gfm_spec.rb index ce34f48aaaf..55df7e45f79 100644 --- a/spec/features/copy_as_gfm_spec.rb +++ b/spec/features/copy_as_gfm_spec.rb @@ -20,7 +20,7 @@ describe 'Copy as GFM', feature: true, js: true do end # The filters referenced in lib/banzai/pipeline/gfm_pipeline.rb convert GitLab Flavored Markdown (GFM) to HTML. - # The handlers defined in app/assets/javascripts/copy_as_gfm.js.es6 consequently convert that same HTML to GFM. + # The handlers defined in app/assets/javascripts/copy_as_gfm.js consequently convert that same HTML to GFM. # To make sure these filters and handlers are properly aligned, this spec tests the GFM-to-HTML-to-GFM cycle # by verifying (`html_to_gfm(gfm_to_html(gfm)) == gfm`) for a number of examples of GFM for every filter, using the `verify` helper. diff --git a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb index 85ffffe4b6d..ce96a420699 100644 --- a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb @@ -202,6 +202,14 @@ describe 'Dropdown milestone', :feature, :js do expect_tokens([{ name: 'milestone', value: 'upcoming' }]) expect_filtered_search_input_empty end + + it 'selects `started milestones`' do + click_static_milestone('Started') + + expect(page).to have_css(js_dropdown_milestone, visible: false) + expect_tokens([{ name: 'milestone', value: 'started' }]) + expect_filtered_search_input_empty + end end describe 'input has existing content' do diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb index f079a9627e4..f463312bf57 100644 --- a/spec/features/issues/filtered_search/filter_issues_spec.rb +++ b/spec/features/issues/filtered_search/filter_issues_spec.rb @@ -8,13 +8,12 @@ describe 'Filter issues', js: true, feature: true do let!(:project) { create(:project, group: group) } let!(:user) { create(:user) } let!(:user2) { create(:user) } - let!(:milestone) { create(:milestone, project: project) } let!(:label) { create(:label, project: project) } let!(:wontfix) { create(:label, project: project, title: "Won't fix") } let!(:bug_label) { create(:label, project: project, title: 'bug') } let!(:caps_sensitive_label) { create(:label, project: project, title: 'CAPS_sensitive') } - let!(:milestone) { create(:milestone, title: "8", project: project) } + let!(:milestone) { create(:milestone, title: "8", project: project, start_date: 2.days.ago) } let!(:multiple_words_label) { create(:label, project: project, title: "Two words") } let!(:closed_issue) { create(:issue, title: 'bug that is closed', project: project, state: :closed) } @@ -505,6 +504,14 @@ describe 'Filter issues', js: true, feature: true do expect_filtered_search_input_empty end + it 'filters issues by started milestones' do + input_filtered_search("milestone:started") + + expect_tokens([{ name: 'milestone', value: 'started' }]) + expect_issues_list_count(5) + expect_filtered_search_input_empty + end + it 'filters issues by invalid milestones' do skip('to be tested, issue #26546') end diff --git a/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb b/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb deleted file mode 100644 index e05fbb3715c..00000000000 --- a/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb +++ /dev/null @@ -1,32 +0,0 @@ -require 'spec_helper' - -feature 'Profile > Notifications > User changes notified_of_own_activity setting', feature: true, js: true do - let(:user) { create(:user) } - - before do - login_as(user) - end - - scenario 'User opts into receiving notifications about their own activity' do - visit profile_notifications_path - - expect(page).not_to have_checked_field('user[notified_of_own_activity]') - - check 'user[notified_of_own_activity]' - - expect(page).to have_content('Notification settings saved') - expect(page).to have_checked_field('user[notified_of_own_activity]') - end - - scenario 'User opts out of receiving notifications about their own activity' do - user.update!(notified_of_own_activity: true) - visit profile_notifications_path - - expect(page).to have_checked_field('user[notified_of_own_activity]') - - uncheck 'user[notified_of_own_activity]' - - expect(page).to have_content('Notification settings saved') - expect(page).not_to have_checked_field('user[notified_of_own_activity]') - end -end diff --git a/spec/features/projects/commit/mini_pipeline_graph_spec.rb b/spec/features/projects/commit/mini_pipeline_graph_spec.rb new file mode 100644 index 00000000000..30a2b2bcf8c --- /dev/null +++ b/spec/features/projects/commit/mini_pipeline_graph_spec.rb @@ -0,0 +1,55 @@ +require 'rails_helper' + +feature 'Mini Pipeline Graph in Commit View', :js, :feature do + include WaitForAjax + + let(:user) { create(:user) } + let(:project) { create(:project, :public) } + + before do + login_as(user) + end + + context 'when commit has pipelines' do + let(:pipeline) do + create(:ci_empty_pipeline, + project: project, + ref: project.default_branch, + sha: project.commit.sha) + end + + let(:build) do + create(:ci_build, pipeline: pipeline) + end + + before do + build.run + visit namespace_project_commit_path(project.namespace, project, project.commit.id) + end + + it 'should display a mini pipeline graph' do + expect(page).to have_selector('.mr-widget-pipeline-graph') + end + + it 'should show the builds list when stage is clicked' do + first('.mini-pipeline-graph-dropdown-toggle').click + + wait_for_ajax + + page.within '.js-builds-dropdown-list' do + expect(page).to have_selector('.ci-status-icon-running') + expect(page).to have_content(build.stage) + end + end + end + + context 'when commit does not have pipelines' do + before do + visit namespace_project_commit_path(project.namespace, project, project.commit.id) + end + + it 'should not display a mini pipeline graph' do + expect(page).not_to have_selector('.mr-widget-pipeline-graph') + end + end +end diff --git a/spec/features/projects/labels/issues_sorted_by_priority_spec.rb b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb index de3c6eceb82..e2911a37e40 100644 --- a/spec/features/projects/labels/issues_sorted_by_priority_spec.rb +++ b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb @@ -29,7 +29,7 @@ feature 'Issue prioritization', feature: true do issue_1.labels << label_5 login_as user - visit namespace_project_issues_path(project.namespace, project, sort: 'priority') + visit namespace_project_issues_path(project.namespace, project, sort: 'label_priority') # Ensure we are indicating that issues are sorted by priority expect(page).to have_selector('.dropdown-toggle', text: 'Label priority') @@ -68,7 +68,7 @@ feature 'Issue prioritization', feature: true do issue_6.labels << label_5 # 8 - No priority login_as user - visit namespace_project_issues_path(project.namespace, project, sort: 'priority') + visit namespace_project_issues_path(project.namespace, project, sort: 'label_priority') expect(page).to have_selector('.dropdown-toggle', text: 'Label priority') diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb index 2a008427478..ee52dc65175 100644 --- a/spec/finders/issues_finder_spec.rb +++ b/spec/finders/issues_finder_spec.rb @@ -101,6 +101,41 @@ describe IssuesFinder do end end + context 'filtering by started milestone' do + let(:params) { { milestone_title: Milestone::Started.name } } + + let(:project_no_started_milestones) { create(:empty_project, :public) } + let(:project_started_1_and_2) { create(:empty_project, :public) } + let(:project_started_8) { create(:empty_project, :public) } + + let(:yesterday) { Date.today - 1.day } + let(:tomorrow) { Date.today + 1.day } + let(:two_days_ago) { Date.today - 2.days } + + let(:milestones) do + [ + create(:milestone, project: project_no_started_milestones, start_date: tomorrow), + create(:milestone, project: project_started_1_and_2, title: '1.0', start_date: two_days_ago), + create(:milestone, project: project_started_1_and_2, title: '2.0', start_date: yesterday), + create(:milestone, project: project_started_1_and_2, title: '3.0', start_date: tomorrow), + create(:milestone, project: project_started_8, title: '7.0'), + create(:milestone, project: project_started_8, title: '8.0', start_date: yesterday), + create(:milestone, project: project_started_8, title: '9.0', start_date: tomorrow) + ] + end + + before do + milestones.each do |milestone| + create(:issue, project: milestone.project, milestone: milestone, author: user, assignee: user) + end + end + + it 'returns issues in the started milestones for each project' do + expect(issues.map { |issue| issue.milestone.title }).to contain_exactly('1.0', '2.0', '8.0') + expect(issues.map { |issue| issue.milestone.start_date }).to contain_exactly(two_days_ago, yesterday, yesterday) + end + end + context 'filtering by label' do let(:params) { { label_name: label.title } } diff --git a/spec/javascripts/filtered_search/dropdown_utils_spec.js b/spec/javascripts/filtered_search/dropdown_utils_spec.js index 5c65903701b..e6538020896 100644 --- a/spec/javascripts/filtered_search/dropdown_utils_spec.js +++ b/spec/javascripts/filtered_search/dropdown_utils_spec.js @@ -126,7 +126,11 @@ require('~/filtered_search/filtered_search_dropdown_manager'); beforeEach(() => { setFixtures(` - <input type="text" id="test" /> + <ul class="tokens-container"> + <li class="input-token"> + <input class="filtered-search" type="text" id="test" /> + </li> + </ul> `); input = document.getElementById('test'); @@ -142,7 +146,7 @@ require('~/filtered_search/filtered_search_dropdown_manager'); input.value = 'o'; updatedItem = gl.DropdownUtils.filterHint(input, { hint: 'label', - }, 'o'); + }); expect(updatedItem.droplab_hidden).toBe(true); }); @@ -150,6 +154,29 @@ require('~/filtered_search/filtered_search_dropdown_manager'); const updatedItem = gl.DropdownUtils.filterHint(input, {}, ''); expect(updatedItem.droplab_hidden).toBe(false); }); + + it('should allow multiple if item.type is array', () => { + input.value = 'label:~first la'; + const updatedItem = gl.DropdownUtils.filterHint(input, { + hint: 'label', + type: 'array', + }); + expect(updatedItem.droplab_hidden).toBe(false); + }); + + it('should prevent multiple if item.type is not array', () => { + input.value = 'milestone:~first mile'; + let updatedItem = gl.DropdownUtils.filterHint(input, { + hint: 'milestone', + }); + expect(updatedItem.droplab_hidden).toBe(true); + + updatedItem = gl.DropdownUtils.filterHint(input, { + hint: 'milestone', + type: 'string', + }); + expect(updatedItem.droplab_hidden).toBe(true); + }); }); describe('setDataValueIfSelected', () => { diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js index fae462561e9..c12b44cea89 100644 --- a/spec/javascripts/test_bundle.js +++ b/spec/javascripts/test_bundle.js @@ -48,10 +48,10 @@ describe('Uncovered files', function () { './network/branch_graph.js', ]; - const sourceFiles = require.context('~', true, /^\.\/(?!application\.js).*\.(js|es6)$/); + const sourceFiles = require.context('~', true, /^\.\/(?!application\.js).*\.js$/); sourceFiles.keys().forEach(function (path) { // ignore if there is a matching spec file - if (testsContext.keys().indexOf(`${path.replace(/\.js(\.es6)?$/, '')}_spec`) > -1) { + if (testsContext.keys().indexOf(`${path.replace(/\.js$/, '')}_spec`) > -1) { return; } diff --git a/spec/lib/expand_variables_spec.rb b/spec/lib/expand_variables_spec.rb index 730ca1f7c0a..90628917943 100644 --- a/spec/lib/expand_variables_spec.rb +++ b/spec/lib/expand_variables_spec.rb @@ -45,10 +45,10 @@ describe ExpandVariables do { key: 'variable', value: 'value' }, { key: 'variable2', value: 'result' } ] }, - { value: 'review/$CI_BUILD_REF_NAME', + { value: 'review/$CI_COMMIT_REF_NAME', result: 'review/feature/add-review-apps', variables: [ - { key: 'CI_BUILD_REF_NAME', value: 'feature/add-review-apps' } + { key: 'CI_COMMIT_REF_NAME', value: 'feature/add-review-apps' } ] }, ] diff --git a/spec/lib/gitlab/ci/config/entry/environment_spec.rb b/spec/lib/gitlab/ci/config/entry/environment_spec.rb index 2adbed2154f..c330e609337 100644 --- a/spec/lib/gitlab/ci/config/entry/environment_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/environment_spec.rb @@ -151,8 +151,8 @@ describe Gitlab::Ci::Config::Entry::Environment do context 'when variables are used for environment' do let(:config) do - { name: 'review/$CI_BUILD_REF_NAME', - url: 'https://$CI_BUILD_REF_NAME.review.gitlab.com' } + { name: 'review/$CI_COMMIT_REF_NAME', + url: 'https://$CI_COMMIT_REF_NAME.review.gitlab.com' } end describe '#valid?' do diff --git a/spec/lib/gitlab/redis_spec.rb b/spec/lib/gitlab/redis_spec.rb index 917c5c46db1..8b77c925705 100644 --- a/spec/lib/gitlab/redis_spec.rb +++ b/spec/lib/gitlab/redis_spec.rb @@ -3,8 +3,16 @@ require 'spec_helper' describe Gitlab::Redis do include StubENV - before(:each) { clear_raw_config } - after(:each) { clear_raw_config } + let(:config) { 'config/resque.yml' } + + before(:each) do + stub_env('GITLAB_REDIS_CONFIG_FILE', Rails.root.join(config).to_s) + clear_raw_config + end + + after(:each) do + clear_raw_config + end describe '.params' do subject { described_class.params } @@ -18,22 +26,22 @@ describe Gitlab::Redis do end context 'when url contains unix socket reference' do - let(:config_old) { Rails.root.join('spec/fixtures/config/redis_old_format_socket.yml').to_s } - let(:config_new) { Rails.root.join('spec/fixtures/config/redis_new_format_socket.yml').to_s } + let(:config_old) { 'spec/fixtures/config/redis_old_format_socket.yml' } + let(:config_new) { 'spec/fixtures/config/redis_new_format_socket.yml' } context 'with old format' do - it 'returns path key instead' do - stub_const("#{described_class}::CONFIG_FILE", config_old) + let(:config) { config_old } + it 'returns path key instead' do is_expected.to include(path: '/path/to/old/redis.sock') is_expected.not_to have_key(:url) end end context 'with new format' do - it 'returns path key instead' do - stub_const("#{described_class}::CONFIG_FILE", config_new) + let(:config) { config_new } + it 'returns path key instead' do is_expected.to include(path: '/path/to/redis.sock') is_expected.not_to have_key(:url) end @@ -41,22 +49,22 @@ describe Gitlab::Redis do end context 'when url is host based' do - let(:config_old) { Rails.root.join('spec/fixtures/config/redis_old_format_host.yml') } - let(:config_new) { Rails.root.join('spec/fixtures/config/redis_new_format_host.yml') } + let(:config_old) { 'spec/fixtures/config/redis_old_format_host.yml' } + let(:config_new) { 'spec/fixtures/config/redis_new_format_host.yml' } context 'with old format' do - it 'returns hash with host, port, db, and password' do - stub_const("#{described_class}::CONFIG_FILE", config_old) + let(:config) { config_old } + it 'returns hash with host, port, db, and password' do is_expected.to include(host: 'localhost', password: 'mypassword', port: 6379, db: 99) is_expected.not_to have_key(:url) end end context 'with new format' do - it 'returns hash with host, port, db, and password' do - stub_const("#{described_class}::CONFIG_FILE", config_new) + let(:config) { config_new } + it 'returns hash with host, port, db, and password' do is_expected.to include(host: 'localhost', password: 'mynewpassword', port: 6379, db: 99) is_expected.not_to have_key(:url) end @@ -74,15 +82,13 @@ describe Gitlab::Redis do end context 'when yml file with env variable' do - let(:redis_config) { Rails.root.join('spec/fixtures/config/redis_config_with_env.yml') } + let(:config) { 'spec/fixtures/config/redis_config_with_env.yml' } before do stub_env('TEST_GITLAB_REDIS_URL', 'redis://redishost:6379') end it 'reads redis url from env variable' do - stub_const("#{described_class}::CONFIG_FILE", redis_config) - expect(described_class.url).to eq 'redis://redishost:6379' end end @@ -90,14 +96,13 @@ describe Gitlab::Redis do describe '._raw_config' do subject { described_class._raw_config } + let(:config) { '/var/empty/doesnotexist' } it 'should be frozen' do expect(subject).to be_frozen end it 'returns false when the file does not exist' do - stub_const("#{described_class}::CONFIG_FILE", '/var/empty/doesnotexist') - expect(subject).to eq(false) end end @@ -134,22 +139,18 @@ describe Gitlab::Redis do subject { described_class.new(Rails.env).sentinels } context 'when sentinels are defined' do - let(:config) { Rails.root.join('spec/fixtures/config/redis_new_format_host.yml') } + let(:config) { 'spec/fixtures/config/redis_new_format_host.yml' } it 'returns an array of hashes with host and port keys' do - stub_const("#{described_class}::CONFIG_FILE", config) - is_expected.to include(host: 'localhost', port: 26380) is_expected.to include(host: 'slave2', port: 26381) end end context 'when sentinels are not defined' do - let(:config) { Rails.root.join('spec/fixtures/config/redis_old_format_host.yml') } + let(:config) { 'spec/fixtures/config/redis_old_format_host.yml' } it 'returns nil' do - stub_const("#{described_class}::CONFIG_FILE", config) - is_expected.to be_nil end end @@ -159,21 +160,17 @@ describe Gitlab::Redis do subject { described_class.new(Rails.env).sentinels? } context 'when sentinels are defined' do - let(:config) { Rails.root.join('spec/fixtures/config/redis_new_format_host.yml') } + let(:config) { 'spec/fixtures/config/redis_new_format_host.yml' } it 'returns true' do - stub_const("#{described_class}::CONFIG_FILE", config) - is_expected.to be_truthy end end context 'when sentinels are not defined' do - let(:config) { Rails.root.join('spec/fixtures/config/redis_old_format_host.yml') } + let(:config) { 'spec/fixtures/config/redis_old_format_host.yml' } it 'returns false' do - stub_const("#{described_class}::CONFIG_FILE", config) - is_expected.to be_falsey end end diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index 32f9366a14c..4b449546a30 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -212,6 +212,25 @@ eos end end + describe '#latest_pipeline' do + let!(:first_pipeline) do + create(:ci_empty_pipeline, + project: project, + sha: commit.sha, + status: 'success') + end + let!(:second_pipeline) do + create(:ci_empty_pipeline, + project: project, + sha: commit.sha, + status: 'success') + end + + it 'returns latest pipeline' do + expect(commit.latest_pipeline).to eq second_pipeline + end + end + describe '#status' do context 'without ref argument' do before do diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index 545a11912e3..31ae0dce140 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -344,6 +344,46 @@ describe Issue, "Issuable" do end end + describe '.order_due_date_and_labels_priority' do + let(:project) { create(:empty_project) } + + def create_issue(milestone, labels) + create(:labeled_issue, milestone: milestone, labels: labels, project: project) + end + + it 'sorts issues in order of milestone due date, then label priority' do + first_priority = create(:label, project: project, priority: 1) + second_priority = create(:label, project: project, priority: 2) + no_priority = create(:label, project: project) + + first_milestone = create(:milestone, project: project, due_date: Time.now) + second_milestone = create(:milestone, project: project, due_date: Time.now + 1.month) + third_milestone = create(:milestone, project: project) + + # The issues here are ordered by label priority, to ensure that we don't + # accidentally just sort by creation date. + second_milestone_first_priority = create_issue(second_milestone, [first_priority, second_priority, no_priority]) + third_milestone_first_priority = create_issue(third_milestone, [first_priority, second_priority, no_priority]) + first_milestone_second_priority = create_issue(first_milestone, [second_priority, no_priority]) + second_milestone_second_priority = create_issue(second_milestone, [second_priority, no_priority]) + no_milestone_second_priority = create_issue(nil, [second_priority, no_priority]) + first_milestone_no_priority = create_issue(first_milestone, [no_priority]) + second_milestone_no_labels = create_issue(second_milestone, []) + third_milestone_no_priority = create_issue(third_milestone, [no_priority]) + + result = Issue.order_due_date_and_labels_priority + + expect(result).to eq([first_milestone_second_priority, + first_milestone_no_priority, + second_milestone_first_priority, + second_milestone_second_priority, + second_milestone_no_labels, + third_milestone_first_priority, + no_milestone_second_priority, + third_milestone_no_priority]) + end + end + describe '.order_labels_priority' do let(:label_1) { create(:label, title: 'label_1', project: issue.project, priority: 1) } let(:label_2) { create(:label, title: 'label_2', project: issue.project, priority: 2) } diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index b4305e92812..9f0e7fbbe26 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -239,7 +239,7 @@ describe Environment, models: true do describe '#actions_for' do let(:deployment) { create(:deployment, environment: environment) } let(:pipeline) { deployment.deployable.pipeline } - let!(:review_action) { create(:ci_build, :manual, name: 'review-apps', pipeline: pipeline, environment: 'review/$CI_BUILD_REF_NAME' )} + let!(:review_action) { create(:ci_build, :manual, name: 'review-apps', pipeline: pipeline, environment: 'review/$CI_COMMIT_REF_NAME' )} let!(:production_action) { create(:ci_build, :manual, name: 'production', pipeline: pipeline, environment: 'production' )} it 'returns a list of actions with matching environment' do diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index d50fe80b36a..442b2df1952 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -312,8 +312,8 @@ describe API::Runner do end let(:expected_variables) do - [{ 'key' => 'CI_BUILD_NAME', 'value' => 'spinach', 'public' => true }, - { 'key' => 'CI_BUILD_STAGE', 'value' => 'test', 'public' => true }, + [{ 'key' => 'CI_JOB_NAME', 'value' => 'spinach', 'public' => true }, + { 'key' => 'CI_JOB_STAGE', 'value' => 'test', 'public' => true }, { 'key' => 'DB_NAME', 'value' => 'postgres', 'public' => true }] end @@ -437,9 +437,9 @@ describe API::Runner do context 'when triggered job is available' do let(:expected_variables) do - [{ 'key' => 'CI_BUILD_NAME', 'value' => 'spinach', 'public' => true }, - { 'key' => 'CI_BUILD_STAGE', 'value' => 'test', 'public' => true }, - { 'key' => 'CI_BUILD_TRIGGERED', 'value' => 'true', 'public' => true }, + [{ 'key' => 'CI_JOB_NAME', 'value' => 'spinach', 'public' => true }, + { 'key' => 'CI_JOB_STAGE', 'value' => 'test', 'public' => true }, + { 'key' => 'CI_PIPELINE_TRIGGERED', 'value' => 'true', 'public' => true }, { 'key' => 'DB_NAME', 'value' => 'postgres', 'public' => true }, { 'key' => 'SECRET_KEY', 'value' => 'secret_value', 'public' => false }, { 'key' => 'TRIGGER_KEY_1', 'value' => 'TRIGGER_VALUE_1', 'public' => false }] diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index 9948d1a9ea0..c879f37f50d 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -81,8 +81,8 @@ describe Ci::API::Builds do expect(runner.reload.platform).to eq("darwin") expect(json_response["options"]).to eq({ "image" => "ruby:2.1", "services" => ["postgres"] }) expect(json_response["variables"]).to include( - { "key" => "CI_BUILD_NAME", "value" => "spinach", "public" => true }, - { "key" => "CI_BUILD_STAGE", "value" => "test", "public" => true }, + { "key" => "CI_JOB_NAME", "value" => "spinach", "public" => true }, + { "key" => "CI_JOB_STAGE", "value" => "test", "public" => true }, { "key" => "DB_NAME", "value" => "postgres", "public" => true } ) end @@ -182,9 +182,9 @@ describe Ci::API::Builds do expect(response).to have_http_status(201) expect(json_response["variables"]).to include( - { "key" => "CI_BUILD_NAME", "value" => "spinach", "public" => true }, - { "key" => "CI_BUILD_STAGE", "value" => "test", "public" => true }, - { "key" => "CI_BUILD_TRIGGERED", "value" => "true", "public" => true }, + { "key" => "CI_JOB_NAME", "value" => "spinach", "public" => true }, + { "key" => "CI_JOB_STAGE", "value" => "test", "public" => true }, + { "key" => "CI_PIPELINE_TRIGGERED", "value" => "true", "public" => true }, { "key" => "DB_NAME", "value" => "postgres", "public" => true }, { "key" => "SECRET_KEY", "value" => "secret_value", "public" => false }, { "key" => "TRIGGER_KEY_1", "value" => "TRIGGER_VALUE_1", "public" => false }, diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb index 8459a3d8cfb..a969829a63e 100644 --- a/spec/services/ci/create_pipeline_service_spec.rb +++ b/spec/services/ci/create_pipeline_service_spec.rb @@ -200,7 +200,7 @@ describe Ci::CreatePipelineService, services: true do context 'with environment' do before do - config = YAML.dump(deploy: { environment: { name: "review/$CI_BUILD_REF_NAME" }, script: 'ls' }) + config = YAML.dump(deploy: { environment: { name: "review/$CI_COMMIT_REF_NAME" }, script: 'ls' }) stub_ci_pipeline_yaml_file(config) end diff --git a/spec/services/create_deployment_service_spec.rb b/spec/services/create_deployment_service_spec.rb index 18b964e2453..a883705bd45 100644 --- a/spec/services/create_deployment_service_spec.rb +++ b/spec/services/create_deployment_service_spec.rb @@ -104,16 +104,16 @@ describe CreateDeploymentService, services: true do context 'when variables are used' do let(:params) do { - environment: 'review-apps/$CI_BUILD_REF_NAME', + environment: 'review-apps/$CI_COMMIT_REF_NAME', ref: 'master', tag: false, sha: '97de212e80737a608d939f648d959671fb0a0142', options: { - name: 'review-apps/$CI_BUILD_REF_NAME', - url: 'http://$CI_BUILD_REF_NAME.review-apps.gitlab.com' + name: 'review-apps/$CI_COMMIT_REF_NAME', + url: 'http://$CI_COMMIT_REF_NAME.review-apps.gitlab.com' }, variables: [ - { key: 'CI_BUILD_REF_NAME', value: 'feature-review-apps' } + { key: 'CI_COMMIT_REF_NAME', value: 'feature-review-apps' } ] } end diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index ebbaea4e59a..82a4ec3f581 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -146,16 +146,6 @@ describe NotificationService, services: true do should_not_email(@u_lazy_participant) end - it "emails the note author if they've opted into notifications about their activity" do - add_users_with_subscription(note.project, issue) - note.author.notified_of_own_activity = true - reset_delivered_emails! - - notification.new_note(note) - - should_email(note.author) - end - it 'filters out "mentioned in" notes' do mentioned_note = SystemNoteService.cross_reference(mentioned_issue, issue, issue.author) @@ -486,20 +476,6 @@ describe NotificationService, services: true do should_not_email(issue.assignee) end - it "emails the author if they've opted into notifications about their activity" do - issue.author.notified_of_own_activity = true - - notification.new_issue(issue, issue.author) - - should_email(issue.author) - end - - it "doesn't email the author if they haven't opted into notifications about their activity" do - notification.new_issue(issue, issue.author) - - should_not_email(issue.author) - end - it "emails subscribers of the issue's labels" do user_1 = create(:user) user_2 = create(:user) @@ -689,19 +665,6 @@ describe NotificationService, services: true do should_email(subscriber_to_label_2) end - it "emails the current user if they've opted into notifications about their activity" do - subscriber_to_label_2.notified_of_own_activity = true - notification.relabeled_issue(issue, [group_label_2, label_2], subscriber_to_label_2) - - should_email(subscriber_to_label_2) - end - - it "doesn't email the current user if they haven't opted into notifications about their activity" do - notification.relabeled_issue(issue, [group_label_2, label_2], subscriber_to_label_2) - - should_not_email(subscriber_to_label_2) - end - it "doesn't send email to anyone but subscribers of the given labels" do notification.relabeled_issue(issue, [group_label_2, label_2], @u_disabled) @@ -855,20 +818,6 @@ describe NotificationService, services: true do should_not_email(@u_lazy_participant) end - it "emails the author if they've opted into notifications about their activity" do - merge_request.author.notified_of_own_activity = true - - notification.new_merge_request(merge_request, merge_request.author) - - should_email(merge_request.author) - end - - it "doesn't email the author if they haven't opted into notifications about their activity" do - notification.new_merge_request(merge_request, merge_request.author) - - should_not_email(merge_request.author) - end - it "emails subscribers of the merge request's labels" do user_1 = create(:user) user_2 = create(:user) @@ -1064,14 +1013,6 @@ describe NotificationService, services: true do should_not_email(@u_watcher) end - it "notifies the merger when the pipeline succeeds is false but they've opted into notifications about their activity" do - merge_request.merge_when_pipeline_succeeds = false - @u_watcher.notified_of_own_activity = true - notification.merge_mr(merge_request, @u_watcher) - - should_email(@u_watcher) - end - it_behaves_like 'participating notifications' do let(:participant) { create(:user, username: 'user-participant') } let(:issuable) { merge_request } diff --git a/spec/simplecov_env.rb b/spec/simplecov_env.rb index b507d38f472..ac2c89b3ff9 100644 --- a/spec/simplecov_env.rb +++ b/spec/simplecov_env.rb @@ -15,9 +15,9 @@ module SimpleCovEnv def configure_job SimpleCov.configure do - if ENV['CI_BUILD_NAME'] - coverage_dir "coverage/#{ENV['CI_BUILD_NAME']}" - command_name ENV['CI_BUILD_NAME'] + if ENV['CI_JOB_NAME'] + coverage_dir "coverage/#{ENV['CI_JOB_NAME']}" + command_name ENV['CI_JOB_NAME'] end if ENV['CI'] diff --git a/spec/views/projects/commit/_commit_box.html.haml_spec.rb b/spec/views/projects/commit/_commit_box.html.haml_spec.rb index f2919f20e85..8bc344bfbf6 100644 --- a/spec/views/projects/commit/_commit_box.html.haml_spec.rb +++ b/spec/views/projects/commit/_commit_box.html.haml_spec.rb @@ -25,7 +25,7 @@ describe 'projects/commit/_commit_box.html.haml' do render - expect(rendered).to have_text("Pipeline ##{third_pipeline.id} for #{Commit.truncate_sha(project.commit.sha)} failed") + expect(rendered).to have_text("Pipeline ##{third_pipeline.id} failed") end context 'viewing a commit' do |