diff options
240 files changed, 1995 insertions, 743 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f271ab4c4c8..ff4da3a8884 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -14,13 +14,15 @@ variables: GIT_DEPTH: "20" PHANTOMJS_VERSION: "2.1.1" GET_SOURCES_ATTEMPTS: "3" + KNAPSACK_RSPEC_SUITE_REPORT_PATH: knapsack/${CI_PROJECT_NAME}/rspec_report-master.json + KNAPSACK_SPINACH_SUITE_REPORT_PATH: knapsack/${CI_PROJECT_NAME}/spinach_report-master.json before_script: - source ./scripts/prepare_build.sh - cp config/gitlab.yml.example config/gitlab.yml - bundle --version - '[ "$USE_BUNDLE_INSTALL" != "true" ] || retry bundle install --without postgres production --jobs $(nproc) --clean $FLAGS' - - retry gem install knapsack + - retry gem install knapsack fog-aws mime-types - '[ "$SETUP_DB" != "true" ] || bundle exec rake db:drop db:create db:schema:load db:migrate add_limits_mysql' stages: @@ -39,14 +41,15 @@ stages: variables: SETUP_DB: "false" USE_BUNDLE_INSTALL: "false" + KNAPSACK_S3_BUCKET: "gitlab-ce-cache" cache: key: "knapsack" paths: - - knapsack/ + - knapsack/ artifacts: expire_in: 31d paths: - - knapsack/ + - knapsack/ .use-db: &use-db services: @@ -61,17 +64,17 @@ stages: - 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 + - export KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/${JOB_NAME[0]}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json - export KNAPSACK_GENERATE_REPORT=true - - cp knapsack/rspec_report.json ${KNAPSACK_REPORT_PATH} + - cp ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} ${KNAPSACK_REPORT_PATH} - knapsack rspec "--color --format documentation" artifacts: expire_in: 31d when: always paths: - - coverage/ - - knapsack/ - - tmp/capybara/ + - coverage/ + - knapsack/ + - tmp/capybara/ .spinach-knapsack: &spinach-knapsack stage: test @@ -81,28 +84,44 @@ stages: - 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 + - export KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/${JOB_NAME[0]}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json - export KNAPSACK_GENERATE_REPORT=true - - cp knapsack/spinach_report.json ${KNAPSACK_REPORT_PATH} + - cp ${KNAPSACK_SPINACH_SUITE_REPORT_PATH} ${KNAPSACK_REPORT_PATH} - knapsack spinach "-r rerun" || retry '[[ -e tmp/spinach-rerun.txt ]] && bundle exec spinach -r rerun $(cat tmp/spinach-rerun.txt)' artifacts: expire_in: 31d when: always paths: - - coverage/ - - knapsack/ - - tmp/capybara/ + - coverage/ + - knapsack/ + - tmp/capybara/ # Prepare and merge knapsack tests - knapsack: <<: *knapsack-state <<: *dedicated-runner stage: prepare script: - - mkdir -p knapsack/ - - '[[ -f knapsack/rspec_report.json ]] || echo "{}" > knapsack/rspec_report.json' - - '[[ -f knapsack/spinach_report.json ]] || echo "{}" > knapsack/spinach_report.json' + - mkdir -p knapsack/${CI_PROJECT_NAME}/ + - wget -O $KNAPSACK_RSPEC_SUITE_REPORT_PATH http://${KNAPSACK_S3_BUCKET}.s3.amazonaws.com/$KNAPSACK_RSPEC_SUITE_REPORT_PATH || rm $KNAPSACK_RSPEC_SUITE_REPORT_PATH + - wget -O $KNAPSACK_SPINACH_SUITE_REPORT_PATH http://${KNAPSACK_S3_BUCKET}.s3.amazonaws.com/$KNAPSACK_SPINACH_SUITE_REPORT_PATH || rm $KNAPSACK_SPINACH_SUITE_REPORT_PATH + - '[[ -f $KNAPSACK_RSPEC_SUITE_REPORT_PATH ]] || echo "{}" > ${KNAPSACK_RSPEC_SUITE_REPORT_PATH}' + - '[[ -f $KNAPSACK_SPINACH_SUITE_REPORT_PATH ]] || echo "{}" > ${KNAPSACK_SPINACH_SUITE_REPORT_PATH}' + +update-knapsack: + <<: *knapsack-state + <<: *dedicated-runner + stage: post-test + script: + - scripts/merge-reports ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/rspec_node_*.json + - scripts/merge-reports ${KNAPSACK_SPINACH_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/spinach_node_*.json + - '[[ -z ${KNAPSACK_S3_BUCKET} ]] || scripts/sync-reports put $KNAPSACK_S3_BUCKET $KNAPSACK_RSPEC_SUITE_REPORT_PATH $KNAPSACK_SPINACH_SUITE_REPORT_PATH' + - rm -f knapsack/${CI_PROJECT_NAME}/*_node_*.json + only: + - master@gitlab-org/gitlab-ce + - master@gitlab-org/gitlab-ee + - master@gitlab/gitlabhq + - master@gitlab/gitlab-ee setup-test-env: <<: *use-db @@ -122,20 +141,6 @@ setup-test-env: - public/assets - tmp/tests -update-knapsack: - <<: *knapsack-state - <<: *dedicated-runner - stage: post-test - script: - - scripts/merge-reports knapsack/rspec_report.json knapsack/rspec_node_*.json - - scripts/merge-reports knapsack/spinach_report.json knapsack/spinach_node_*.json - - rm -f knapsack/*_node_*.json - only: - - master@gitlab-org/gitlab-ce - - master@gitlab-org/gitlab-ee - - master@gitlab/gitlabhq - - master@gitlab/gitlab-ee - rspec 0 20: *rspec-knapsack rspec 1 20: *rspec-knapsack rspec 2 20: *rspec-knapsack diff --git a/CHANGELOG.md b/CHANGELOG.md index 4291eca8dc7..eeb756d5963 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,19 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 9.0.1 (2017-03-28) + +- Resolve "404 when requesting build trace". !9759 (dosuken123) +- Simplify search queries for projects and merge requests. !10053 (mhasbini) +- Fix after_script processing for Runners APIv4. !10185 +- Fix escaped html appearing in milestone page. !10224 +- Fix bug that caused jobs that already had been retried to be retried again when retrying a pipeline. !10249 +- Allow filtering by all started milestones. +- Allow sorting by due date and priority. +- Fixed branches pagination not displaying. +- Fixed filtered search not working in IE. +- Optimize labels finder query when searching for a project with a group. (mhasbini) + ## 9.0.0 (2017-03-22) - Fix inconsistent naming for services that delete things. !5803 (dixpac) @@ -244,7 +244,7 @@ gem 'net-ssh', '~> 3.0.1' gem 'base32', '~> 0.3.0' # Sentry integration -gem 'sentry-raven', '~> 2.0.0' +gem 'sentry-raven', '~> 2.4.0' gem 'premailer-rails', '~> 1.9.0' @@ -257,15 +257,14 @@ end group :development do gem 'foreman', '~> 0.78.0' - gem 'brakeman', '~> 3.4.0', require: false + gem 'brakeman', '~> 3.6.0', require: false gem 'letter_opener_web', '~> 1.3.0' - gem 'bullet', '~> 5.2.0', require: false + gem 'bullet', '~> 5.5.0', require: false gem 'rblineprof', '~> 0.3.6', platform: :mri, require: false - gem 'web-console', '~> 2.0' # Better errors handler - gem 'better_errors', '~> 1.0.1' + gem 'better_errors', '~> 2.1.0' gem 'binding_of_caller', '~> 0.7.2' # thin instead webrick @@ -297,7 +296,7 @@ group :development, :test do gem 'capybara-screenshot', '~> 1.0.0' gem 'poltergeist', '~> 1.9.0' - gem 'spring', '~> 1.7.0' + gem 'spring', '~> 2.0.0' gem 'spring-commands-rspec', '~> 1.0.4' gem 'spring-commands-spinach', '~> 1.1.0' @@ -305,8 +304,8 @@ group :development, :test do gem 'rubocop-rspec', '~> 1.12.0', require: false gem 'scss_lint', '~> 0.47.0', require: false gem 'haml_lint', '~> 0.21.0', require: false - gem 'simplecov', '0.12.0', require: false - gem 'flay', '~> 2.6.1', require: false + gem 'simplecov', '~> 0.14.0', require: false + gem 'flay', '~> 2.8.0', require: false gem 'bundler-audit', '~> 0.5.0', require: false gem 'benchmark-ips', '~> 2.3.0', require: false @@ -323,7 +322,7 @@ group :test do gem 'shoulda-matchers', '~> 2.8.0', require: false gem 'email_spec', '~> 1.6.0' gem 'json-schema', '~> 2.6.2' - gem 'webmock', '~> 1.21.0' + gem 'webmock', '~> 1.24.0' gem 'test_after_commit', '~> 1.1' gem 'sham_rack', '~> 1.3.6' gem 'timecop', '~> 0.8.0' diff --git a/Gemfile.lock b/Gemfile.lock index 07be5d7aded..2cb0e88962a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -75,19 +75,20 @@ GEM base32 (0.3.2) bcrypt (3.1.11) benchmark-ips (2.3.0) - better_errors (1.0.1) + better_errors (2.1.1) coderay (>= 1.0.0) erubis (>= 2.6.6) + rack (>= 0.9.0) bindata (2.3.5) binding_of_caller (0.7.2) debug_inspector (>= 0.0.1) bootstrap-sass (3.3.6) autoprefixer-rails (>= 5.2.1) sass (>= 3.3.4) - brakeman (3.4.1) + brakeman (3.6.1) browser (2.2.0) builder (3.2.3) - bullet (5.2.0) + bullet (5.5.1) activesupport (>= 3.0.0) uniform_notifier (~> 1.10.0) bundler-audit (0.5.0) @@ -101,7 +102,7 @@ GEM rack (>= 1.0.0) rack-test (>= 0.5.4) xpath (~> 2.0) - capybara-screenshot (1.0.11) + capybara-screenshot (1.0.14) capybara (>= 1.0, < 3) launchy carrierwave (0.11.2) @@ -117,7 +118,7 @@ GEM numerizer (~> 0.1.1) chunky_png (1.3.5) cliver (0.3.2) - coderay (1.1.0) + coderay (1.1.1) coercible (1.0.0) descendants_tracker (~> 0.0.1) coffee-rails (4.1.1) @@ -200,7 +201,9 @@ GEM multi_json ffaker (2.4.0) ffi (1.9.10) - flay (2.6.1) + flay (2.8.1) + erubis (~> 2.7.0) + path_expander (~> 1.0) ruby_parser (~> 3.0) sexp_processor (~> 4.0) flowdock (0.7.1) @@ -340,6 +343,7 @@ GEM temple (~> 0.7.6) thor tilt + hashdiff (0.3.2) hashie (3.5.5) health_check (2.6.0) rails (>= 4.0) @@ -518,6 +522,7 @@ GEM activerecord (>= 4.0, < 5.1) parser (2.4.0.0) ast (~> 2.2) + path_expander (1.0.1) pg (0.18.4) poltergeist (1.9.0) capybara (~> 2.1) @@ -532,14 +537,14 @@ GEM premailer-rails (1.9.2) actionmailer (>= 3, < 6) premailer (~> 1.7, >= 1.7.9) - pry (0.10.3) + pry (0.10.4) coderay (~> 1.1.0) method_source (~> 0.8.1) slop (~> 3.4) - pry-byebug (3.4.1) + pry-byebug (3.4.2) byebug (~> 9.0) pry (~> 0.10) - pry-rails (0.3.4) + pry-rails (0.3.5) pry (>= 0.9.10) pyu-ruby-sasl (0.0.3.3) rack (1.6.5) @@ -671,7 +676,7 @@ GEM ruby-progressbar (1.8.1) ruby-saml (1.4.1) nokogiri (>= 1.5.10) - ruby_parser (3.8.2) + ruby_parser (3.8.4) sexp_processor (~> 4.1) rubyntlm (0.5.2) rubypants (0.2.0) @@ -700,10 +705,10 @@ GEM activesupport (>= 3.1) select2-rails (3.5.9.3) thor (~> 0.14) - sentry-raven (2.0.2) - faraday (>= 0.7.6, < 0.10.x) + sentry-raven (2.4.0) + faraday (>= 0.7.6, < 1.0) settingslogic (2.0.9) - sexp_processor (4.7.0) + sexp_processor (4.8.0) sham_rack (1.3.6) rack shoulda-matchers (2.8.0) @@ -724,7 +729,7 @@ GEM faraday (~> 0.9) jwt (~> 1.5) multi_json (~> 1.10) - simplecov (0.12.0) + simplecov (0.14.1) docile (~> 1.1.0) json (>= 1.8, < 3) simplecov-html (~> 0.10.0) @@ -741,7 +746,8 @@ GEM spinach (>= 0.4) spinach-rerun-reporter (0.0.2) spinach (~> 0.8) - spring (1.7.2) + spring (2.0.1) + activesupport (>= 4.2) spring-commands-rspec (1.0.4) spring (>= 0.9.1) spring-commands-spinach (1.1.0) @@ -813,14 +819,10 @@ GEM vmstat (2.3.0) warden (1.2.6) rack (>= 1.0) - web-console (2.3.0) - activemodel (>= 4.0) - binding_of_caller (>= 0.7.2) - railties (>= 4.0) - sprockets-rails (>= 2.0, < 4.0) - webmock (1.21.0) + webmock (1.24.6) addressable (>= 2.3.6) crack (>= 0.3.2) + hashdiff webpack-rails (0.9.9) rails (>= 3.2.0) websocket-driver (0.6.3) @@ -854,12 +856,12 @@ DEPENDENCIES babosa (~> 1.0.2) base32 (~> 0.3.0) benchmark-ips (~> 2.3.0) - better_errors (~> 1.0.1) + better_errors (~> 2.1.0) binding_of_caller (~> 0.7.2) bootstrap-sass (~> 3.3.0) - brakeman (~> 3.4.0) + brakeman (~> 3.6.0) browser (~> 2.2) - bullet (~> 5.2.0) + bullet (~> 5.5.0) bundler-audit (~> 0.5.0) capybara (~> 2.6.2) capybara-screenshot (~> 1.0.0) @@ -885,7 +887,7 @@ DEPENDENCIES email_spec (~> 1.6.0) factory_girl_rails (~> 4.7.0) ffaker (~> 2.4) - flay (~> 2.6.1) + flay (~> 2.8.0) fog-aws (~> 0.9) fog-core (~> 1.40) fog-google (~> 0.5) @@ -991,18 +993,18 @@ DEPENDENCIES scss_lint (~> 0.47.0) seed-fu (~> 2.3.5) select2-rails (~> 3.5.9) - sentry-raven (~> 2.0.0) + sentry-raven (~> 2.4.0) settingslogic (~> 2.0.9) sham_rack (~> 1.3.6) shoulda-matchers (~> 2.8.0) sidekiq (~> 4.2.7) sidekiq-cron (~> 0.4.4) sidekiq-limit_fetch (~> 3.4) - simplecov (= 0.12.0) + simplecov (~> 0.14.0) slack-notifier (~> 1.5.1) spinach-rails (~> 0.2.1) spinach-rerun-reporter (~> 0.0.2) - spring (~> 1.7.0) + spring (~> 2.0.0) spring-commands-rspec (~> 1.0.4) spring-commands-spinach (~> 1.1.0) sprockets (~> 3.7.0) @@ -1023,8 +1025,7 @@ DEPENDENCIES version_sorter (~> 2.1.0) virtus (~> 1.0.1) vmstat (~> 2.3.0) - web-console (~> 2.0) - webmock (~> 1.21.0) + webmock (~> 1.24.0) webpack-rails (~> 0.9.9) wikicloth (= 0.8.1) @@ -1 +1 @@ -8.18.0-pre +9.1.0-pre diff --git a/app/assets/javascripts/behaviors/gl_emoji/is_emoji_unicode_supported.js b/app/assets/javascripts/behaviors/gl_emoji/is_emoji_unicode_supported.js index 5e3c45f7e92..20ab2d7e827 100644 --- a/app/assets/javascripts/behaviors/gl_emoji/is_emoji_unicode_supported.js +++ b/app/assets/javascripts/behaviors/gl_emoji/is_emoji_unicode_supported.js @@ -1,5 +1,3 @@ -import spreadString from './spread_string'; - // On Windows, flags render as two-letter country codes, see http://emojipedia.org/flags/ const flagACodePoint = 127462; // parseInt('1F1E6', 16) const flagZCodePoint = 127487; // parseInt('1F1FF', 16) @@ -20,7 +18,7 @@ function isKeycapEmoji(emojiUnicode) { const tone1 = 127995;// parseInt('1F3FB', 16) const tone5 = 127999;// parseInt('1F3FF', 16) function isSkinToneComboEmoji(emojiUnicode) { - return emojiUnicode.length > 2 && spreadString(emojiUnicode).some((char) => { + return emojiUnicode.length > 2 && Array.from(emojiUnicode).some((char) => { const cp = char.codePointAt(0); return cp >= tone1 && cp <= tone5; }); @@ -30,7 +28,7 @@ function isSkinToneComboEmoji(emojiUnicode) { // doesn't support the skin tone versions of horse racing const horseRacingCodePoint = 127943;// parseInt('1F3C7', 16) function isHorceRacingSkinToneComboEmoji(emojiUnicode) { - return spreadString(emojiUnicode)[0].codePointAt(0) === horseRacingCodePoint && + return Array.from(emojiUnicode)[0].codePointAt(0) === horseRacingCodePoint && isSkinToneComboEmoji(emojiUnicode); } @@ -42,7 +40,7 @@ const personEndCodePoint = 128105; // parseInt('1F469', 16) function isPersonZwjEmoji(emojiUnicode) { let hasPersonEmoji = false; let hasZwj = false; - spreadString(emojiUnicode).forEach((character) => { + Array.from(emojiUnicode).forEach((character) => { const cp = character.codePointAt(0); if (cp === zwj) { hasZwj = true; diff --git a/app/assets/javascripts/behaviors/gl_emoji/spread_string.js b/app/assets/javascripts/behaviors/gl_emoji/spread_string.js deleted file mode 100644 index 327764ec6e9..00000000000 --- a/app/assets/javascripts/behaviors/gl_emoji/spread_string.js +++ /dev/null @@ -1,50 +0,0 @@ -// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/charCodeAt#Fixing_charCodeAt()_to_handle_non-Basic-Multilingual-Plane_characters_if_their_presence_earlier_in_the_string_is_known -function knownCharCodeAt(givenString, index) { - const str = `${givenString}`; - const end = str.length; - - const surrogatePairs = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g; - let idx = index; - while ((surrogatePairs.exec(str)) != null) { - const li = surrogatePairs.lastIndex; - if (li - 2 < idx) { - idx += 1; - } else { - break; - } - } - - if (idx >= end || idx < 0) { - return NaN; - } - - const code = str.charCodeAt(idx); - - let high; - let low; - if (code >= 0xD800 && code <= 0xDBFF) { - high = code; - low = str.charCodeAt(idx + 1); - // Go one further, since one of the "characters" is part of a surrogate pair - return ((high - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000; - } - return code; -} - -// See http://stackoverflow.com/a/38901550/796832 -// ES5/PhantomJS compatible version of spreading a string -// -// [...'foo'] -> ['f', 'o', 'o'] -// [...'🖐🏿'] -> ['🖐', '🏿'] -function spreadString(str) { - const arr = []; - let i = 0; - while (!isNaN(knownCharCodeAt(str, i))) { - const codePoint = knownCharCodeAt(str, i); - arr.push(String.fromCodePoint(codePoint)); - i += 1; - } - return arr; -} - -export default spreadString; diff --git a/app/assets/javascripts/boards/boards_bundle.js b/app/assets/javascripts/boards/boards_bundle.js index 149bfbc8e8b..e057ac8df02 100644 --- a/app/assets/javascripts/boards/boards_bundle.js +++ b/app/assets/javascripts/boards/boards_bundle.js @@ -79,7 +79,7 @@ $(() => { resp.json().forEach((board) => { const list = Store.addList(board); - if (list.type === 'done') { + if (list.type === 'closed') { list.position = Infinity; } }); diff --git a/app/assets/javascripts/boards/components/board_card.js b/app/assets/javascripts/boards/components/board_card.js index 9320848bcca..f591134c548 100644 --- a/app/assets/javascripts/boards/components/board_card.js +++ b/app/assets/javascripts/boards/components/board_card.js @@ -50,9 +50,7 @@ export default { this.showDetail = false; }, showIssue(e) { - const targetTagName = e.target.tagName.toLowerCase(); - - if (targetTagName === 'a' || targetTagName === 'button') return; + if (e.target.classList.contains('js-no-trigger')) return; if (this.showDetail) { this.showDetail = false; diff --git a/app/assets/javascripts/boards/components/issue_card_inner.js b/app/assets/javascripts/boards/components/issue_card_inner.js index ba44dc5ed94..a4629b092bf 100644 --- a/app/assets/javascripts/boards/components/issue_card_inner.js +++ b/app/assets/javascripts/boards/components/issue_card_inner.js @@ -84,20 +84,20 @@ import eventHub from '../eventhub'; #{{ issue.id }} </span> <a - class="card-assignee has-tooltip" + class="card-assignee has-tooltip js-no-trigger" :href="rootPath + issue.assignee.username" :title="'Assigned to ' + issue.assignee.name" v-if="issue.assignee" data-container="body"> <img - class="avatar avatar-inline s20" + class="avatar avatar-inline s20 js-no-trigger" :src="issue.assignee.avatar" width="20" height="20" :alt="'Avatar for ' + issue.assignee.name" /> </a> <button - class="label color-label has-tooltip" + class="label color-label has-tooltip js-no-trigger" v-for="label in issue.labels" type="button" v-if="showLabel(label)" diff --git a/app/assets/javascripts/boards/components/sidebar/remove_issue.js b/app/assets/javascripts/boards/components/sidebar/remove_issue.js index d8322b34d44..772ea4c5565 100644 --- a/app/assets/javascripts/boards/components/sidebar/remove_issue.js +++ b/app/assets/javascripts/boards/components/sidebar/remove_issue.js @@ -48,7 +48,7 @@ import Vue from 'vue'; template: ` <div class="block list" - v-if="list.type !== 'done'"> + v-if="list.type !== 'closed'"> <button class="btn btn-default btn-block" type="button" diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js index f18ad2a0fac..91e5fb2a666 100644 --- a/app/assets/javascripts/boards/models/list.js +++ b/app/assets/javascripts/boards/models/list.js @@ -10,7 +10,7 @@ class List { this.position = obj.position; this.title = obj.title; this.type = obj.list_type; - this.preset = ['done', 'blank'].indexOf(this.type) > -1; + this.preset = ['closed', 'blank'].indexOf(this.type) > -1; this.page = 1; this.loading = true; this.loadingMore = false; diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js index 8912f234aa6..bcda70d0638 100644 --- a/app/assets/javascripts/boards/stores/boards_store.js +++ b/app/assets/javascripts/boards/stores/boards_store.js @@ -45,7 +45,7 @@ import Cookies from 'js-cookie'; }, shouldAddBlankState () { // Decide whether to add the blank state - return !(this.state.lists.filter(list => list.type !== 'done')[0]); + return !(this.state.lists.filter(list => list.type !== 'closed')[0]); }, addBlankState () { if (!this.shouldAddBlankState() || this.welcomeIsHidden() || this.disabled) return; @@ -98,7 +98,7 @@ import Cookies from 'js-cookie'; issueTo.removeLabel(listFrom.label); } - if (listTo.type === 'done') { + if (listTo.type === 'closed') { issueLists.forEach((list) => { list.removeIssue(issue); }); diff --git a/app/assets/javascripts/cycle_analytics/components/limit_warning_component.js b/app/assets/javascripts/cycle_analytics/components/limit_warning_component.js new file mode 100644 index 00000000000..abe48572347 --- /dev/null +++ b/app/assets/javascripts/cycle_analytics/components/limit_warning_component.js @@ -0,0 +1,17 @@ +export default { + props: { + count: { + type: Number, + required: true, + }, + }, + template: ` + <span v-if="count === 50" class="events-info pull-right"> + <i class="fa fa-warning has-tooltip" + aria-hidden="true" + title="Limited to showing 50 events at most" + data-placement="top"></i> + Showing 50 events + </span> + `, +}; diff --git a/app/assets/javascripts/cycle_analytics/components/stage_code_component.js b/app/assets/javascripts/cycle_analytics/components/stage_code_component.js index 9947f355aca..3f419a96ff9 100644 --- a/app/assets/javascripts/cycle_analytics/components/stage_code_component.js +++ b/app/assets/javascripts/cycle_analytics/components/stage_code_component.js @@ -14,6 +14,7 @@ import Vue from 'vue'; <div> <div class="events-description"> {{ stage.description }} + <limit-warning :count="items.length" /> </div> <ul class="stage-event-list"> <li v-for="mergeRequest in items" class="stage-event-item"> diff --git a/app/assets/javascripts/cycle_analytics/components/stage_issue_component.js b/app/assets/javascripts/cycle_analytics/components/stage_issue_component.js index 6ad4805e8c5..7ffa38edd9e 100644 --- a/app/assets/javascripts/cycle_analytics/components/stage_issue_component.js +++ b/app/assets/javascripts/cycle_analytics/components/stage_issue_component.js @@ -14,6 +14,7 @@ import Vue from 'vue'; <div> <div class="events-description"> {{ stage.description }} + <limit-warning :count="items.length" /> </div> <ul class="stage-event-list"> <li v-for="issue in items" class="stage-event-item"> diff --git a/app/assets/javascripts/cycle_analytics/components/stage_plan_component.js b/app/assets/javascripts/cycle_analytics/components/stage_plan_component.js index 42e1bbce744..d736c8b0c28 100644 --- a/app/assets/javascripts/cycle_analytics/components/stage_plan_component.js +++ b/app/assets/javascripts/cycle_analytics/components/stage_plan_component.js @@ -19,12 +19,7 @@ import iconCommit from '../svg/icon_commit.svg'; <div> <div class="events-description"> {{ stage.description }} - <span v-if="items.length === 50" class="events-info pull-right"> - <i class="fa fa-warning has-tooltip" - title="Limited to showing 50 events at most" - data-placement="top"></i> - Showing 50 events - </span> + <limit-warning :count="items.length" /> </div> <ul class="stage-event-list"> <li v-for="commit in items" class="stage-event-item"> diff --git a/app/assets/javascripts/cycle_analytics/components/stage_production_component.js b/app/assets/javascripts/cycle_analytics/components/stage_production_component.js index da80450a32c..698a79ca68c 100644 --- a/app/assets/javascripts/cycle_analytics/components/stage_production_component.js +++ b/app/assets/javascripts/cycle_analytics/components/stage_production_component.js @@ -14,6 +14,7 @@ import Vue from 'vue'; <div> <div class="events-description"> {{ stage.description }} + <limit-warning :count="items.length" /> </div> <ul class="stage-event-list"> <li v-for="issue in items" class="stage-event-item"> diff --git a/app/assets/javascripts/cycle_analytics/components/stage_review_component.js b/app/assets/javascripts/cycle_analytics/components/stage_review_component.js index 2200f43914f..e63c41f2a57 100644 --- a/app/assets/javascripts/cycle_analytics/components/stage_review_component.js +++ b/app/assets/javascripts/cycle_analytics/components/stage_review_component.js @@ -14,6 +14,7 @@ import Vue from 'vue'; <div> <div class="events-description"> {{ stage.description }} + <limit-warning :count="items.length" /> </div> <ul class="stage-event-list"> <li v-for="mergeRequest in items" class="stage-event-item"> diff --git a/app/assets/javascripts/cycle_analytics/components/stage_staging_component.js b/app/assets/javascripts/cycle_analytics/components/stage_staging_component.js index 8fa63734cf1..d51f7134e25 100644 --- a/app/assets/javascripts/cycle_analytics/components/stage_staging_component.js +++ b/app/assets/javascripts/cycle_analytics/components/stage_staging_component.js @@ -17,6 +17,7 @@ import iconBranch from '../svg/icon_branch.svg'; <div> <div class="events-description"> {{ stage.description }} + <limit-warning :count="items.length" /> </div> <ul class="stage-event-list"> <li v-for="build in items" class="stage-event-item item-build-component"> diff --git a/app/assets/javascripts/cycle_analytics/components/stage_test_component.js b/app/assets/javascripts/cycle_analytics/components/stage_test_component.js index 0015249cfaa..17ae3a9ddc1 100644 --- a/app/assets/javascripts/cycle_analytics/components/stage_test_component.js +++ b/app/assets/javascripts/cycle_analytics/components/stage_test_component.js @@ -18,6 +18,7 @@ import iconBranch from '../svg/icon_branch.svg'; <div> <div class="events-description"> {{ stage.description }} + <limit-warning :count="items.length" /> </div> <ul class="stage-event-list"> <li v-for="build in items" class="stage-event-item item-build-component"> diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js index ae17d05e679..b099b39e58f 100644 --- a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js +++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js @@ -2,6 +2,7 @@ import Vue from 'vue'; import Cookies from 'js-cookie'; +import LimitWarningComponent from './components/limit_warning_component'; require('./components/stage_code_component'); require('./components/stage_issue_component'); @@ -130,5 +131,6 @@ $(() => { }); // Register global components + Vue.component('limit-warning', LimitWarningComponent); Vue.component('total-time', gl.cycleAnalytics.TotalTimeComponent); }); diff --git a/app/assets/javascripts/group_name.js b/app/assets/javascripts/group_name.js index 6a028f299b1..62675d7e67e 100644 --- a/app/assets/javascripts/group_name.js +++ b/app/assets/javascripts/group_name.js @@ -1,40 +1,64 @@ -const GROUP_LIMIT = 2; + +import _ from 'underscore'; export default class GroupName { constructor() { - this.titleContainer = document.querySelector('.title'); - this.groups = document.querySelectorAll('.group-path'); + this.titleContainer = document.querySelector('.title-container'); + this.title = document.querySelector('.title'); + this.titleWidth = this.title.offsetWidth; this.groupTitle = document.querySelector('.group-title'); + this.groups = document.querySelectorAll('.group-path'); this.toggle = null; this.isHidden = false; this.init(); } init() { - if (this.groups.length > GROUP_LIMIT) { + if (this.groups.length > 0) { this.groups[this.groups.length - 1].classList.remove('hidable'); - this.addToggle(); + this.toggleHandler(); + window.addEventListener('resize', _.debounce(this.toggleHandler.bind(this), 100)); } this.render(); } - addToggle() { - const header = document.querySelector('.header-content'); + toggleHandler() { + if (this.titleWidth > this.titleContainer.offsetWidth) { + if (!this.toggle) this.createToggle(); + this.showToggle(); + } else if (this.toggle) { + this.hideToggle(); + } + } + + createToggle() { this.toggle = document.createElement('button'); this.toggle.className = 'text-expander group-name-toggle'; this.toggle.setAttribute('aria-label', 'Toggle full path'); this.toggle.innerHTML = '...'; this.toggle.addEventListener('click', this.toggleGroups.bind(this)); - header.insertBefore(this.toggle, this.titleContainer); + this.titleContainer.insertBefore(this.toggle, this.title); this.toggleGroups(); } + showToggle() { + this.title.classList.add('wrap'); + this.toggle.classList.remove('hidden'); + if (this.isHidden) this.groupTitle.classList.add('is-hidden'); + } + + hideToggle() { + this.title.classList.remove('wrap'); + this.toggle.classList.add('hidden'); + if (this.isHidden) this.groupTitle.classList.remove('is-hidden'); + } + toggleGroups() { this.isHidden = !this.isHidden; this.groupTitle.classList.toggle('is-hidden'); } render() { - this.titleContainer.classList.remove('initializing'); + this.title.classList.remove('initializing'); } } diff --git a/app/assets/javascripts/lib/utils/poll.js b/app/assets/javascripts/lib/utils/poll.js index c30a1fcb5da..5c22aea51cd 100644 --- a/app/assets/javascripts/lib/utils/poll.js +++ b/app/assets/javascripts/lib/utils/poll.js @@ -5,23 +5,37 @@ import httpStatusCodes from './http_status'; * Service for vue resouce and method need to be provided as props * * @example - * new poll({ + * new Poll({ * resource: resource, * method: 'name', - * data: {page: 1, scope: 'all'}, + * data: {page: 1, scope: 'all'}, // optional * successCallback: () => {}, * errorCallback: () => {}, + * notificationCallback: () => {}, // optional * }).makeRequest(); * - * this.service = new BoardsService(endpoint); - * new poll({ - * resource: this.service, - * method: 'get', - * data: {page: 1, scope: 'all'}, - * successCallback: () => {}, - * errorCallback: () => {}, - * }).makeRequest(); + * Usage in pipelines table with visibility lib: * + * const poll = new Poll({ + * resource: this.service, + * method: 'getPipelines', + * data: { page: pageNumber, scope }, + * successCallback: this.successCallback, + * errorCallback: this.errorCallback, + * notificationCallback: this.updateLoading, + * }); + * + * if (!Visibility.hidden()) { + * poll.makeRequest(); + * } + * + * Visibility.change(() => { + * if (!Visibility.hidden()) { + * poll.restart(); + * } else { + * poll.stop(); + * } +* }); * * 1. Checks for response and headers before start polling * 2. Interval is provided by `Poll-Interval` header. @@ -34,6 +48,8 @@ export default class Poll { constructor(options = {}) { this.options = options; this.options.data = options.data || {}; + this.options.notificationCallback = options.notificationCallback || + function notificationCallback() {}; this.intervalHeader = 'POLL-INTERVAL'; this.timeoutID = null; @@ -42,7 +58,7 @@ export default class Poll { checkConditions(response) { const headers = gl.utils.normalizeHeaders(response.headers); - const pollInterval = headers[this.intervalHeader]; + const pollInterval = parseInt(headers[this.intervalHeader], 10); if (pollInterval > 0 && response.status === httpStatusCodes.OK && this.canPoll) { this.timeoutID = setTimeout(() => { @@ -54,11 +70,14 @@ export default class Poll { } makeRequest() { - const { resource, method, data, errorCallback } = this.options; + const { resource, method, data, errorCallback, notificationCallback } = this.options; + + // It's called everytime a new request is made. Useful to update the status. + notificationCallback(true); return resource[method](data) - .then(response => this.checkConditions(response)) - .catch(error => errorCallback(error)); + .then(response => this.checkConditions(response)) + .catch(error => errorCallback(error)); } /** @@ -70,4 +89,12 @@ export default class Poll { this.canPoll = false; clearTimeout(this.timeoutID); } + + /** + * Restarts polling after it has been stoped + */ + restart() { + this.canPoll = true; + this.makeRequest(); + } } diff --git a/app/assets/javascripts/profile/profile.js b/app/assets/javascripts/profile/profile.js index c38bc762675..4ccea0624ee 100644 --- a/app/assets/javascripts/profile/profile.js +++ b/app/assets/javascripts/profile/profile.js @@ -25,6 +25,7 @@ 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/user_callout.js b/app/assets/javascripts/user_callout.js index b27d252a3ef..fa078b48bf8 100644 --- a/app/assets/javascripts/user_callout.js +++ b/app/assets/javascripts/user_callout.js @@ -1,57 +1,26 @@ import Cookies from 'js-cookie'; -const userCalloutElementName = '.user-callout'; -const closeButton = '.close-user-callout'; -const userCalloutBtn = '.user-callout-btn'; -const userCalloutSvgAttrName = 'callout-svg'; - const USER_CALLOUT_COOKIE = 'user_callout_dismissed'; -const USER_CALLOUT_TEMPLATE = ` - <div class="bordered-box landing content-block"> - <button class="btn btn-default close close-user-callout" type="button"> - <i class="fa fa-times dismiss-icon"></i> - </button> - <div class="row"> - <div class="col-sm-3 col-xs-12 svg-container"> - </div> - <div class="col-sm-8 col-xs-12 inner-content"> - <h4> - Customize your experience - </h4> - <p> - Change syntax themes, default project pages, and more in preferences. - </p> - <a class="btn user-callout-btn" href="/profile/preferences">Check it out</a> - </div> - </div> -</div>`; - export default class UserCallout { constructor() { this.isCalloutDismissed = Cookies.get(USER_CALLOUT_COOKIE); - this.userCalloutBody = $(userCalloutElementName); - this.userCalloutSvg = $(userCalloutElementName).attr(userCalloutSvgAttrName); - $(userCalloutElementName).removeAttr(userCalloutSvgAttrName); + this.userCalloutBody = $('.user-callout'); this.init(); } init() { - const $template = $(USER_CALLOUT_TEMPLATE); if (!this.isCalloutDismissed || this.isCalloutDismissed === 'false') { - $template.find('.svg-container').append(this.userCalloutSvg); - this.userCalloutBody.append($template); - $template.find(closeButton).on('click', e => this.dismissCallout(e)); - $template.find(userCalloutBtn).on('click', e => this.dismissCallout(e)); - } else { - this.userCalloutBody.remove(); + $('.js-close-callout').on('click', e => this.dismissCallout(e)); } } dismissCallout(e) { - Cookies.set(USER_CALLOUT_COOKIE, 'true'); const $currentTarget = $(e.currentTarget); - if ($currentTarget.hasClass('close-user-callout')) { + + Cookies.set(USER_CALLOUT_COOKIE, 'true'); + + if ($currentTarget.hasClass('close')) { this.userCalloutBody.remove(); } } diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index fa02598760f..65bbbda41c8 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -26,7 +26,7 @@ header { padding: 0 16px; z-index: 100; margin-bottom: 0; - height: $header-height; + min-height: $header-height; background-color: $gray-light; border: none; border-bottom: 1px solid $border-color; @@ -85,7 +85,7 @@ header { .navbar-toggle { color: $nav-toggle-gray; - margin: 6px 0; + margin: 7px 0; border-radius: 0; position: absolute; right: -10px; @@ -135,12 +135,14 @@ header { } .header-content { + display: flex; + justify-content: space-between; position: relative; - height: $header-height; + min-height: $header-height; padding-left: 30px; - @media (min-width: $screen-sm-min) { - padding-right: 0; + @media (max-width: $screen-sm-max) { + padding-right: 20px; } .dropdown-menu { @@ -165,8 +167,7 @@ header { } .group-name-toggle { - margin: 0 5px; - vertical-align: sub; + margin: 3px 5px; } .group-title { @@ -177,39 +178,32 @@ header { } } + .title-container { + display: flex; + align-items: flex-start; + flex: 1 1 auto; + padding-top: (($header-height - 19) / 2); + overflow: hidden; + } + .title { position: relative; padding-right: 20px; margin: 0; font-size: 18px; - max-width: 385px; + line-height: 22px; display: inline-block; - line-height: $header-height; font-weight: normal; color: $gl-text-color; - overflow: hidden; - text-overflow: ellipsis; vertical-align: top; white-space: nowrap; - &.initializing { - display: none; - } - - @media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) { - max-width: 300px; + &.wrap { + white-space: normal; } - @media (max-width: $screen-xs-max) { - max-width: 190px; - } - - @media (min-width: $screen-sm-min) and (max-width: $screen-md-max) { - max-width: 428px; - } - - @media (min-width: $screen-lg-min) { - max-width: 685px; + &.initializing { + opacity: 0; } a { @@ -226,10 +220,10 @@ header { border: transparent; background: transparent; position: absolute; + top: 2px; right: 3px; width: 12px; line-height: 19px; - margin-top: (($header-height - 19) / 2); padding: 0; font-size: 10px; text-align: center; @@ -247,7 +241,7 @@ header { } .navbar-collapse { - float: right; + flex: 0 0 auto; border-top: none; @media (min-width: $screen-md-min) { @@ -255,7 +249,7 @@ header { } @media (max-width: $screen-xs-max) { - float: none; + flex: 1 1 auto; } } } @@ -269,14 +263,6 @@ header { } } -.page-sidebar-pinned.right-sidebar-expanded { - @media (max-width: $screen-md-max) { - .header-content .title { - width: 300px; - } - } -} - @media (max-width: $screen-xs-max) { header .container-fluid { font-size: 18px; diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index 575d32b1a23..b6168a293e0 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -240,8 +240,13 @@ font-size: (14px / $issue-boards-font-size) * 1em; } + .card-assignee { + margin-right: 5px; + } + .avatar { margin-left: 0; + margin-right: 0; } } @@ -296,7 +301,7 @@ } } -.issue-boards-sidebar { +.page-with-layout-nav.page-with-sub-nav .issue-boards-sidebar { &.right-sidebar { top: 0; bottom: 0; diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index da8410eca66..0dad91ba128 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -142,7 +142,9 @@ border: 1px solid $border-gray-dark; border-radius: $border-radius-default; margin-left: 5px; - line-height: 1; + font-size: $gl-font-size; + line-height: $gl-font-size; + outline: none; &:hover { background-color: darken($gray-light, 10%); diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss index 84d21e48463..cf45f0af2aa 100644 --- a/app/assets/stylesheets/pages/groups.scss +++ b/app/assets/stylesheets/pages/groups.scss @@ -9,6 +9,13 @@ } } +.group-root-path { + max-width: 40vw; + overflow: hidden; + text-overflow: ellipsis; + word-wrap: nowrap; +} + .group-row { .stats { float: right; diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index a2129722633..6e63bb75e88 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -243,22 +243,6 @@ ul.notes { } } -.page-sidebar-pinned.right-sidebar-expanded { - @media (max-width: $screen-md-max) { - .note-header { - .note-headline-light { - display: block; - } - - .note-actions { - position: absolute; - right: 0; - top: 0; - } - } - } -} - // Diff code in discussion view .discussion-body .diff-file { .file-title { diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 949d52cffa2..eed58e618e8 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -477,20 +477,6 @@ a.deploy-project-label { } } -.page-sidebar-pinned { - .project-stats .nav > li.right { - @media (min-width: $screen-lg-min) { - float: none; - } - } - - .download-button { - @media (min-width: $screen-lg-min) { - margin-left: 0; - } - } -} - .project-stats { font-size: 0; text-align: center; diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 24504685e48..563bcc65bd6 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -95,18 +95,14 @@ class Admin::UsersController < Admin::ApplicationController def create opts = { - force_random_password: true, - password_expires_at: nil + reset_password: true, + skip_confirmation: true } - @user = User.new(user_params.merge(opts)) - @user.created_by_id = current_user.id - @user.generate_password - @user.generate_reset_token - @user.skip_confirmation! + @user = Users::CreateService.new(current_user, user_params.merge(opts)).execute respond_to do |format| - if @user.save + if @user.persisted? format.html { redirect_to [:admin, @user], notice: 'User was successfully created.' } format.json { render json: @user, status: :created, location: @user } else diff --git a/app/controllers/profiles/notifications_controller.rb b/app/controllers/profiles/notifications_controller.rb index b8b71d295f6..a271e2dfc4b 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) + params.require(:user).permit(:notification_email, :notified_of_own_activity) end end diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb index f1e4246e7fb..3f3c90a49ab 100644 --- a/app/controllers/projects/builds_controller.rb +++ b/app/controllers/projects/builds_controller.rb @@ -74,7 +74,9 @@ class Projects::BuildsController < Projects::ApplicationController end def status - render json: @build.to_json(only: [:status, :id, :sha, :coverage], methods: :sha) + render json: BuildSerializer + .new(project: @project, user: @current_user) + .represent_status(@build) end def erase diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 0d6d9f492c1..d984e6d3918 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -260,4 +260,13 @@ class Projects::IssuesController < Projects::ApplicationController :milestone_id, :due_date, :state_event, :task_num, :lock_version, label_ids: [] ) end + + def authenticate_user! + return if current_user + + notice = "Please sign in to create the new issue." + + store_location_for :user, request.fullpath + redirect_to new_user_session_path, notice: notice + end end diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 2fadf7c8c81..9621b30b251 100755 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -10,7 +10,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController before_action :module_enabled before_action :merge_request, only: [ :edit, :update, :show, :diffs, :commits, :conflicts, :conflict_for_path, :pipelines, :merge, :merge_check, - :ci_status, :ci_environments_status, :toggle_subscription, :cancel_merge_when_pipeline_succeeds, :remove_wip, :resolve_conflicts, :assign_related_issues + :ci_status, :pipeline_status, :ci_environments_status, :toggle_subscription, :cancel_merge_when_pipeline_succeeds, :remove_wip, :resolve_conflicts, :assign_related_issues ] before_action :validates_merge_request, only: [:show, :diffs, :commits, :pipelines] before_action :define_show_vars, only: [:show, :diffs, :commits, :conflicts, :conflict_for_path, :builds, :pipelines] @@ -97,31 +97,31 @@ class Projects::MergeRequestsController < Projects::ApplicationController def diffs apply_diff_view_cookie! - @merge_request_diff = - if params[:diff_id] - @merge_request.merge_request_diffs.viewable.find(params[:diff_id]) - else - @merge_request.merge_request_diff - end - - @merge_request_diffs = @merge_request.merge_request_diffs.viewable.select_without_diff - @comparable_diffs = @merge_request_diffs.select { |diff| diff.id < @merge_request_diff.id } - - if params[:start_sha].present? - @start_sha = params[:start_sha] - @start_version = @comparable_diffs.find { |diff| diff.head_commit_sha == @start_sha } - - unless @start_version - @start_sha = @merge_request_diff.head_commit_sha - @start_version = @merge_request_diff - end - end - - @environment = @merge_request.environments_for(current_user).last - respond_to do |format| format.html { define_discussion_vars } format.json do + @merge_request_diff = + if params[:diff_id] + @merge_request.merge_request_diffs.viewable.find(params[:diff_id]) + else + @merge_request.merge_request_diff + end + + @merge_request_diffs = @merge_request.merge_request_diffs.viewable.select_without_diff + @comparable_diffs = @merge_request_diffs.select { |diff| diff.id < @merge_request_diff.id } + + if params[:start_sha].present? + @start_sha = params[:start_sha] + @start_version = @comparable_diffs.find { |diff| diff.head_commit_sha == @start_sha } + + unless @start_version + @start_sha = @merge_request_diff.head_commit_sha + @start_version = @merge_request_diff + end + end + + @environment = @merge_request.environments_for(current_user).last + if @start_sha compared_diff_version else @@ -473,6 +473,12 @@ class Projects::MergeRequestsController < Projects::ApplicationController render json: response end + def pipeline_status + render json: PipelineSerializer + .new(project: @project, user: @current_user) + .represent_status(@merge_request.head_pipeline) + end + def ci_environments_status environments = begin diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index 718d9e86bea..43a1abaa662 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -72,6 +72,12 @@ class Projects::PipelinesController < Projects::ApplicationController end end + def status + render json: PipelineSerializer + .new(project: @project, user: @current_user) + .represent_status(@pipeline) + end + def stage @stage = pipeline.stage(params[:stage]) return not_found unless @stage diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb index f210f7e61d2..c5e24b9e365 100644 --- a/app/controllers/projects/wikis_controller.rb +++ b/app/controllers/projects/wikis_controller.rb @@ -124,6 +124,6 @@ class Projects::WikisController < Projects::ApplicationController end def wiki_params - params[:wiki].slice(:title, :content, :format, :message) + params.require(:wiki).permit(:title, :content, :format, :message) end end diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index b44f38d4a0c..a49a1f50a81 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -1,5 +1,4 @@ class RegistrationsController < Devise::RegistrationsController - before_action :signup_enabled? include Recaptcha::Verify def new @@ -21,6 +20,8 @@ class RegistrationsController < Devise::RegistrationsController flash.delete :recaptcha_error render action: 'new' end + rescue Gitlab::Access::AccessDeniedError + redirect_to(new_user_session_path) end def destroy @@ -50,12 +51,6 @@ class RegistrationsController < Devise::RegistrationsController private - def signup_enabled? - unless current_application_settings.signup_enabled? - redirect_to(new_user_session_path) - end - end - def sign_up_params params.require(:user).permit(:username, :email, :email_confirmation, :name, :password) end @@ -65,7 +60,7 @@ class RegistrationsController < Devise::RegistrationsController end def resource - @resource ||= User.new(sign_up_params) + @resource ||= Users::CreateService.new(current_user, sign_up_params).build end def devise_mapping diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb index fa0e2a5e3d8..e52083f86e4 100644 --- a/app/finders/labels_finder.rb +++ b/app/finders/labels_finder.rb @@ -20,8 +20,17 @@ class LabelsFinder < UnionFinder if project? if project - label_ids << project.group.labels if project.group.present? - label_ids << project.labels + if project.group.present? + labels_table = Label.arel_table + + label_ids << Label.where( + labels_table[:type].eq('GroupLabel').and(labels_table[:group_id].eq(project.group.id)).or( + labels_table[:type].eq('ProjectLabel').and(labels_table[:project_id].eq(project.id)) + ) + ) + else + label_ids << project.labels + end end else label_ids << Label.where(group_id: projects.group_ids) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index a3213581498..e5b811f3300 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -306,4 +306,8 @@ module ApplicationHelper def active_when(condition) 'active' if condition end + + def show_user_callout? + cookies[:user_callout_dismissed] == 'true' + end end diff --git a/app/models/board.rb b/app/models/board.rb index 2780acc67c0..cf8317891b5 100644 --- a/app/models/board.rb +++ b/app/models/board.rb @@ -5,7 +5,7 @@ class Board < ActiveRecord::Base validates :project, presence: true - def done_list - lists.merge(List.done).take + def closed_list + lists.merge(List.closed).take end end diff --git a/app/models/list.rb b/app/models/list.rb index 1e5da7f4dd4..fbd19acd1f5 100644 --- a/app/models/list.rb +++ b/app/models/list.rb @@ -2,7 +2,7 @@ class List < ActiveRecord::Base belongs_to :board belongs_to :label - enum list_type: { label: 1, done: 2 } + enum list_type: { label: 1, closed: 2 } validates :board, :list_type, presence: true validates :label, :position, presence: true, if: :label? diff --git a/app/models/project_services/prometheus_service.rb b/app/models/project_services/prometheus_service.rb index 5cff9a42484..6854d2243d7 100644 --- a/app/models/project_services/prometheus_service.rb +++ b/app/models/project_services/prometheus_service.rb @@ -31,7 +31,7 @@ class PrometheusService < MonitoringService def help <<-MD.strip_heredoc - Retrieves the Kubernetes node metrics `container_cpu_usage_seconds_total` + Retrieves the Kubernetes node metrics `container_cpu_usage_seconds_total` and `container_memory_usage_bytes` from the configured Prometheus server. If you are not using [Auto-Deploy](https://docs.gitlab.com/ee/ci/autodeploy/index.html) @@ -74,8 +74,8 @@ class PrometheusService < MonitoringService def calculate_reactive_cache(environment_slug) return unless active? && project && !project.pending_delete? - memory_query = %{(sum(container_memory_usage_bytes{container_name="app",environment="#{environment_slug}"}) / count(container_memory_usage_bytes{container_name="app",environment="#{environment_slug}"})) /1024/1024} - cpu_query = %{sum(rate(container_cpu_usage_seconds_total{container_name="app",environment="#{environment_slug}"}[2m])) / count(container_cpu_usage_seconds_total{container_name="app",environment="#{environment_slug}"}) * 100} + memory_query = %{(sum(container_memory_usage_bytes{container_name!="POD",environment="#{environment_slug}"}) / count(container_memory_usage_bytes{container_name!="POD",environment="#{environment_slug}"})) /1024/1024} + cpu_query = %{sum(rate(container_cpu_usage_seconds_total{container_name!="POD",environment="#{environment_slug}"}[2m])) / count(container_cpu_usage_seconds_total{container_name!="POD",environment="#{environment_slug}"}) * 100} { success: true, diff --git a/app/models/user.rb b/app/models/user.rb index 1c2821bb91a..cbd741f96ed 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -22,6 +22,7 @@ class User < ActiveRecord::Base default_value_for :hide_no_ssh_key, false default_value_for :hide_no_password, false default_value_for :project_view, :files + default_value_for :notified_of_own_activity, false attr_encrypted :otp_secret, key: Gitlab::Application.secrets.otp_key_base, @@ -128,10 +129,9 @@ class User < ActiveRecord::Base validate :unique_email, if: ->(user) { user.email_changed? } validate :owns_notification_email, if: ->(user) { user.notification_email_changed? } validate :owns_public_email, if: ->(user) { user.public_email_changed? } + validate :signup_domain_valid?, on: :create, if: ->(user) { !user.created_by_id } validates :avatar, file_size: { maximum: 200.kilobytes.to_i } - before_validation :generate_password, on: :create - before_validation :signup_domain_valid?, on: :create, if: ->(user) { !user.created_by_id } before_validation :sanitize_attrs before_validation :set_notification_email, if: ->(user) { user.email_changed? } before_validation :set_public_email, if: ->(user) { user.public_email_changed? } @@ -141,8 +141,6 @@ class User < ActiveRecord::Base before_save :ensure_external_user_rights after_save :ensure_namespace_correct after_initialize :set_projects_limit - before_create :check_confirmation_email - after_create :post_create_hook after_destroy :post_destroy_hook # User's Layout preference @@ -386,10 +384,8 @@ class User < ActiveRecord::Base "#{self.class.reference_prefix}#{username}" end - def generate_password - if force_random_password - self.password = self.password_confirmation = Devise.friendly_token.first(Devise.password_length.min) - end + def skip_confirmation=(bool) + skip_confirmation! if bool end def generate_reset_token @@ -401,10 +397,6 @@ class User < ActiveRecord::Base @reset_token end - def check_confirmation_email - skip_confirmation! unless current_application_settings.send_user_confirmation_email - end - def recently_sent_password_reset? reset_password_sent_at.present? && reset_password_sent_at >= 1.minute.ago end @@ -799,12 +791,6 @@ class User < ActiveRecord::Base end end - def post_create_hook - log_info("User \"#{name}\" (#{email}) was created") - notification_service.new_user(self, @reset_token) if created_by_id - system_hook_service.execute_hooks_for(self, :create) - end - def post_destroy_hook log_info("User \"#{name}\" (#{email}) was removed") system_hook_service.execute_hooks_for(self, :destroy) diff --git a/app/serializers/build_entity.rb b/app/serializers/build_entity.rb index 5bcbe285052..fadd6c5c597 100644 --- a/app/serializers/build_entity.rb +++ b/app/serializers/build_entity.rb @@ -18,10 +18,17 @@ class BuildEntity < Grape::Entity expose :created_at expose :updated_at + expose :detailed_status, as: :status, with: StatusEntity private + alias_method :build, :object + def path_to(route, build) send("#{route}_path", build.project.namespace, build.project, build) end + + def detailed_status + build.detailed_status(request.user) + end end diff --git a/app/serializers/build_serializer.rb b/app/serializers/build_serializer.rb new file mode 100644 index 00000000000..79b67001199 --- /dev/null +++ b/app/serializers/build_serializer.rb @@ -0,0 +1,8 @@ +class BuildSerializer < BaseSerializer + entity BuildEntity + + def represent_status(resource) + data = represent(resource, { only: [:status] }) + data.fetch(:status, {}) + end +end diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb index 61f0f11d7d2..3f16dd66d54 100644 --- a/app/serializers/pipeline_entity.rb +++ b/app/serializers/pipeline_entity.rb @@ -12,12 +12,7 @@ class PipelineEntity < Grape::Entity end expose :details do - expose :status do |pipeline, options| - StatusEntity.represent( - pipeline.detailed_status(request.user), - options) - end - + expose :detailed_status, as: :status, with: StatusEntity expose :duration expose :finished_at expose :stages, using: StageEntity @@ -82,4 +77,8 @@ class PipelineEntity < Grape::Entity pipeline.cancelable? && can?(request.user, :update_pipeline, pipeline) end + + def detailed_status + pipeline.detailed_status(request.user) + end end diff --git a/app/serializers/pipeline_serializer.rb b/app/serializers/pipeline_serializer.rb index ab2d3d5a3ec..7829df9fada 100644 --- a/app/serializers/pipeline_serializer.rb +++ b/app/serializers/pipeline_serializer.rb @@ -22,4 +22,11 @@ class PipelineSerializer < BaseSerializer super(resource, opts) end end + + def represent_status(resource) + return {} unless resource.present? + + data = represent(resource, { only: [{ details: [:status] }] }) + data.dig(:details, :status) || {} + end end diff --git a/app/serializers/status_entity.rb b/app/serializers/status_entity.rb index 47066bebfb1..dfd9d1584a1 100644 --- a/app/serializers/status_entity.rb +++ b/app/serializers/status_entity.rb @@ -1,7 +1,7 @@ class StatusEntity < Grape::Entity include RequestAwareEntity - expose :icon, :text, :label, :group + expose :icon, :favicon, :text, :label, :group expose :has_details?, as: :has_details expose :details_path diff --git a/app/services/boards/create_service.rb b/app/services/boards/create_service.rb index f6275a63109..fd9ff115eab 100644 --- a/app/services/boards/create_service.rb +++ b/app/services/boards/create_service.rb @@ -12,7 +12,7 @@ module Boards def create_board! board = project.boards.create - board.lists.create(list_type: :done) + board.lists.create(list_type: :closed) board end diff --git a/app/services/boards/issues/list_service.rb b/app/services/boards/issues/list_service.rb index cb6d30396ec..533e6787855 100644 --- a/app/services/boards/issues/list_service.rb +++ b/app/services/boards/issues/list_service.rb @@ -41,7 +41,7 @@ module Boards end def set_state - params[:state] = list && list.done? ? 'closed' : 'opened' + params[:state] = list && list.closed? ? 'closed' : 'opened' end def board_label_ids diff --git a/app/services/boards/issues/move_service.rb b/app/services/boards/issues/move_service.rb index 2a9981ab884..d5735f13c1e 100644 --- a/app/services/boards/issues/move_service.rb +++ b/app/services/boards/issues/move_service.rb @@ -48,8 +48,8 @@ module Boards end def issue_state - return 'reopen' if moving_from_list.done? - return 'close' if moving_to_list.done? + return 'reopen' if moving_from_list.closed? + return 'close' if moving_to_list.closed? end def add_label_ids diff --git a/app/services/ci/retry_pipeline_service.rb b/app/services/ci/retry_pipeline_service.rb index 574561adc4c..f72ddbf690c 100644 --- a/app/services/ci/retry_pipeline_service.rb +++ b/app/services/ci/retry_pipeline_service.rb @@ -7,14 +7,14 @@ module Ci raise Gitlab::Access::AccessDeniedError end - pipeline.builds.failed_or_canceled.find_each do |build| + pipeline.builds.latest.failed_or_canceled.find_each do |build| next unless build.retryable? Ci::RetryBuildService.new(project, current_user) .reprocess(build) end - pipeline.builds.skipped.find_each do |skipped| + pipeline.builds.latest.skipped.find_each do |skipped| retry_optimistic_lock(skipped) { |build| build.process } end diff --git a/app/services/notification_recipient_service.rb b/app/services/notification_recipient_service.rb index 44ae23fad18..940e850600f 100644 --- a/app/services/notification_recipient_service.rb +++ b/app/services/notification_recipient_service.rb @@ -38,7 +38,7 @@ class NotificationRecipientService recipients = reject_unsubscribed_users(recipients, target) recipients = reject_users_without_access(recipients, target) - recipients.delete(current_user) if skip_current_user + recipients.delete(current_user) if skip_current_user && !current_user.notified_of_own_activity? recipients.uniq end @@ -47,7 +47,7 @@ class NotificationRecipientService recipients = add_labels_subscribers([], target, labels: labels) recipients = reject_unsubscribed_users(recipients, target) recipients = reject_users_without_access(recipients, target) - recipients.delete(current_user) + recipients.delete(current_user) unless current_user.notified_of_own_activity? recipients.uniq end @@ -88,7 +88,7 @@ class NotificationRecipientService recipients = reject_unsubscribed_users(recipients, note.noteable) recipients = reject_users_without_access(recipients, note.noteable) - recipients.delete(note.author) + recipients.delete(note.author) unless note.author.notified_of_own_activity? recipients.uniq end diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index f9aa2346759..2c6f849259e 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -280,8 +280,9 @@ class NotificationService recipients ||= NotificationRecipientService.new(pipeline.project).build_recipients( pipeline, - nil, # The acting user, who won't be added to recipients - action: pipeline.status).map(&:notification_email) + pipeline.user, + action: pipeline.status, + skip_current_user: false).map(&:notification_email) if recipients.any? mailer.public_send(email_template, pipeline, recipients).deliver_later diff --git a/app/services/users/create_service.rb b/app/services/users/create_service.rb new file mode 100644 index 00000000000..f4f0b80f30a --- /dev/null +++ b/app/services/users/create_service.rb @@ -0,0 +1,110 @@ +module Users + # Service for creating a new user. + class CreateService < BaseService + def initialize(current_user, params = {}) + @current_user = current_user + @params = params.dup + end + + def build + raise Gitlab::Access::AccessDeniedError unless can_create_user? + + user = User.new(build_user_params) + + if current_user&.is_admin? + if params[:reset_password] + @reset_token = user.generate_reset_token + params[:force_random_password] = true + end + + if params[:force_random_password] + random_password = Devise.friendly_token.first(Devise.password_length.min) + user.password = user.password_confirmation = random_password + end + end + + identity_attrs = params.slice(:extern_uid, :provider) + + if identity_attrs.any? + user.identities.build(identity_attrs) + end + + user + end + + def execute + user = build + + if user.save + log_info("User \"#{user.name}\" (#{user.email}) was created") + notification_service.new_user(user, @reset_token) if @reset_token + system_hook_service.execute_hooks_for(user, :create) + end + + user + end + + private + + def can_create_user? + (current_user.nil? && current_application_settings.signup_enabled?) || current_user&.is_admin? + end + + # Allowed params for creating a user (admins only) + def admin_create_params + [ + :access_level, + :admin, + :avatar, + :bio, + :can_create_group, + :color_scheme_id, + :email, + :external, + :force_random_password, + :hide_no_password, + :hide_no_ssh_key, + :key_id, + :linkedin, + :name, + :password, + :password_expires_at, + :projects_limit, + :remember_me, + :skip_confirmation, + :skype, + :theme_id, + :twitter, + :username, + :website_url + ] + end + + # Allowed params for user signup + def signup_params + [ + :email, + :email_confirmation, + :name, + :password, + :username + ] + end + + def build_user_params + if current_user&.is_admin? + user_params = params.slice(*admin_create_params) + user_params[:created_by_id] = current_user.id + + if params[:reset_password] + user_params.merge!(force_random_password: true, password_expires_at: nil) + end + else + user_params = params.slice(*signup_params) + user_params[:skip_confirmation] = !current_application_settings.send_user_confirmation_email + end + + user_params + end + end +end diff --git a/app/views/dashboard/projects/index.html.haml b/app/views/dashboard/projects/index.html.haml index eef794dbd51..596499230f9 100644 --- a/app/views/dashboard/projects/index.html.haml +++ b/app/views/dashboard/projects/index.html.haml @@ -4,7 +4,9 @@ - page_title "Projects" - header_title "Projects", dashboard_projects_path -.user-callout{ 'callout-svg' => custom_icon('icon_customization') } +- unless show_user_callout? + = render 'shared/user_callout' + - if @projects.any? || params[:name] = render 'dashboard/projects_head' diff --git a/app/views/explore/projects/_filter.html.haml b/app/views/explore/projects/_filter.html.haml index 56f463572bb..f630f1effdc 100644 --- a/app/views/explore/projects/_filter.html.haml +++ b/app/views/explore/projects/_filter.html.haml @@ -17,24 +17,3 @@ = link_to filter_projects_path(visibility_level: level) do = visibility_level_icon(level) = visibility_level_label(level) - -- if @tags.present? - .dropdown - %button.dropdown-toggle{ href: '#', "data-toggle" => "dropdown" } - = icon('tags') - %span.light Tags: - - if params[:tag].present? - = params[:tag] - - else - Any - = icon('chevron-down') - %ul.dropdown-menu.dropdown-menu-align-right - %li - = link_to filter_projects_path(tag: nil) do - Any - - - @tags.each do |tag| - %li{ class: active_when(tag.name == params[:tag]) || 'light' } - = link_to filter_projects_path(tag: tag.name) do - = icon('tag') - = tag.name diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 7ddee0e5244..7bf4bc70f7c 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -15,6 +15,13 @@ %span.sr-only Toggle navigation = icon('ellipsis-v') + .header-logo + = link_to root_path, class: 'home', title: 'Dashboard', id: 'logo' do + = brand_header_logo + + .title-container + %h1.title{ class: ('initializing' if @has_group_title) }= title + .navbar-collapse.collapse %ul.nav.navbar-nav %li.hidden-sm.hidden-xs @@ -63,12 +70,6 @@ %div = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-success' - .header-logo - = link_to root_path, class: 'home', title: 'Dashboard', id: 'logo' do - = brand_header_logo - - %h1.title{ class: ('initializing' if @has_group_title) }= title - = yield :header_content = render 'shared/outdated_browser' diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml index 5c5e5940365..51c4e8e5a73 100644 --- a/app/views/profiles/notifications/show.html.haml +++ b/app/views/profiles/notifications/show.html.haml @@ -34,6 +34,11 @@ .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/components/_board.html.haml b/app/views/projects/boards/components/_board.html.haml index 0bca6a786cb..5a4eaf92b16 100644 --- a/app/views/projects/boards/components/_board.html.haml +++ b/app/views/projects/boards/components/_board.html.haml @@ -7,12 +7,12 @@ data: { container: "body", placement: "bottom" } } {{ list.title }} .board-issue-count-holder.pull-right.clearfix{ "v-if" => 'list.type !== "blank"' } - %span.board-issue-count.pull-left{ ":class" => '{ "has-btn": list.type !== "done" && !disabled }' } + %span.board-issue-count.pull-left{ ":class" => '{ "has-btn": list.type !== "closed" && !disabled }' } {{ list.issuesSize }} - if can?(current_user, :admin_issue, @project) %button.btn.btn-small.btn-default.pull-right.has-tooltip{ type: "button", "@click" => "showNewIssueForm", - "v-if" => 'list.type !== "done"', + "v-if" => 'list.type !== "closed"', "aria-label" => "Add an issue", "title" => "Add an issue", data: { placement: "top", container: "body" } } diff --git a/app/views/projects/boards/components/_board_list.html.haml b/app/views/projects/boards/components/_board_list.html.haml index 4a4dd84d5d2..4a0b2110601 100644 --- a/app/views/projects/boards/components/_board_list.html.haml +++ b/app/views/projects/boards/components/_board_list.html.haml @@ -3,7 +3,7 @@ = icon("spinner spin") - if can? current_user, :create_issue, @project %board-new-issue{ ":list" => "list", - "v-if" => 'list.type !== "done" && showIssueForm' } + "v-if" => 'list.type !== "closed" && showIssueForm' } %ul.board-list{ "ref" => "list", "v-show" => "!loading", ":data-board" => "list.id", diff --git a/app/views/projects/pipelines/_head.html.haml b/app/views/projects/pipelines/_head.html.haml index b02fef638ff..bc57f7f1c46 100644 --- a/app/views/projects/pipelines/_head.html.haml +++ b/app/views/projects/pipelines/_head.html.haml @@ -10,13 +10,13 @@ Pipelines - if project_nav_tab? :builds - = nav_link(path: 'builds#index', controller: :builds) do + = nav_link(controller: :builds) do = link_to project_builds_path(@project), title: 'Jobs', class: 'shortcuts-builds' do %span Jobs - if project_nav_tab? :environments - = nav_link(path: 'environments#index', controller: :environments) do + = nav_link(controller: :environments) do = link_to project_environments_path(@project), title: 'Environments', class: 'shortcuts-environments' do %span Environments diff --git a/app/views/shared/_group_form.html.haml b/app/views/shared/_group_form.html.haml index c2d9ac87b20..7974eb67f0f 100644 --- a/app/views/shared/_group_form.html.haml +++ b/app/views/shared/_group_form.html.haml @@ -1,4 +1,6 @@ - parent = Group.find_by(id: params[:parent_id] || @group.parent_id) +- group_path = root_url +- group_path << parent.full_path + '/' if parent - if @group.persisted? .form-group = f.label :name, class: 'control-label' do @@ -11,7 +13,7 @@ Group path .col-sm-10 .input-group.gl-field-error-anchor - .input-group-addon + .group-root-path.input-group-addon.has-tooltip{ title: group_path, :'data-placement' => 'bottom' } %span>= root_url - if parent %strong= parent.full_path + '/' diff --git a/app/views/shared/_user_callout.html.haml b/app/views/shared/_user_callout.html.haml new file mode 100644 index 00000000000..8f1293adcb1 --- /dev/null +++ b/app/views/shared/_user_callout.html.haml @@ -0,0 +1,14 @@ +.user-callout + .bordered-box.landing.content-block + %button.btn.btn-default.close.js-close-callout{ type: 'button', + 'aria-label' => 'Dismiss customize experience box' } + = icon('times', class: 'dismiss-icon', 'aria-hidden' => 'true') + .row + .col-sm-3.col-xs-12.svg-container + = custom_icon('icon_customization') + .col-sm-8.col-xs-12.inner-content + %h4 + Customize your experience + %p + Change syntax themes, default project pages, and more in preferences. + = link_to 'Check it out', profile_preferences_path, class: 'btn btn-default js-close-callout' diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 884bd3ca9ca..f19f362f514 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -121,7 +121,7 @@ - selected_labels = issuable.labels .block.labels .sidebar-collapsed-icon.js-sidebar-labels-tooltip{ title: issuable_labels_tooltip(issuable.labels_array), data: { placement: "left", container: "body" } } - = icon('tags', class: 'hidden', 'aria-hidden': 'true') + = icon('tags', 'aria-hidden': 'true') %span = selected_labels.size .title.hide-collapsed diff --git a/app/views/users/calendar_activities.html.haml b/app/views/users/calendar_activities.html.haml index 4afd31f788b..d1e88274878 100644 --- a/app/views/users/calendar_activities.html.haml +++ b/app/views/users/calendar_activities.html.haml @@ -18,9 +18,9 @@ = event_action_name(event) %strong - if event.note? - = link_to event.note_target.to_reference, event_note_target_path(event) + = link_to event.note_target.to_reference, event_note_target_path(event), class: 'has-tooltip', title: event.target_title - elsif event.target - = link_to event.target.to_reference, [event.project.namespace.becomes(Namespace), event.project, event.target] + = link_to event.target.to_reference, [event.project.namespace.becomes(Namespace), event.project, event.target], class: 'has-tooltip', title: event.target_title at %strong diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 601187455b3..1e5b0d2ece2 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -97,8 +97,8 @@ Snippets %div{ class: container_class } - - if @user == current_user - .user-callout{ 'callout-svg' => custom_icon('icon_customization') } + - if @user == current_user && !show_user_callout? + = render 'shared/user_callout' .tab-content #activity.tab-pane .row-content-block.calender-block.white.second-block.hidden-xs diff --git a/changelogs/unreleased/12818-expose-simple-cicd-status-endpoints-with-status-serializer-gitlab-ci-status-for-pipeline-job-and-merge-request.yml b/changelogs/unreleased/12818-expose-simple-cicd-status-endpoints-with-status-serializer-gitlab-ci-status-for-pipeline-job-and-merge-request.yml new file mode 100644 index 00000000000..953009213df --- /dev/null +++ b/changelogs/unreleased/12818-expose-simple-cicd-status-endpoints-with-status-serializer-gitlab-ci-status-for-pipeline-job-and-merge-request.yml @@ -0,0 +1,5 @@ +--- +title: Expose CI/CD status API endpoints with Gitlab::Ci::Status facility on pipeline, + job and merge request for favicon +merge_request: 9561 +author: dosuken123 diff --git a/changelogs/unreleased/22850-404-when-requesting-build-trace.yml b/changelogs/unreleased/22850-404-when-requesting-build-trace.yml deleted file mode 100644 index 6b442130d9b..00000000000 --- a/changelogs/unreleased/22850-404-when-requesting-build-trace.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Resolve "404 when requesting build trace" -merge_request: 9759 -author: dosuken123 diff --git a/changelogs/unreleased/23363-use-strong-params-in-wikis-controller.yml b/changelogs/unreleased/23363-use-strong-params-in-wikis-controller.yml new file mode 100644 index 00000000000..dd342d38fef --- /dev/null +++ b/changelogs/unreleased/23363-use-strong-params-in-wikis-controller.yml @@ -0,0 +1,4 @@ +--- +title: Update wikis_controller.rb to use strong params +merge_request: +author: diff --git a/changelogs/unreleased/27878-new-service-for-creating-user.yml b/changelogs/unreleased/27878-new-service-for-creating-user.yml new file mode 100644 index 00000000000..c07f0cef8db --- /dev/null +++ b/changelogs/unreleased/27878-new-service-for-creating-user.yml @@ -0,0 +1,4 @@ +--- +title: Implement user create service +merge_request: 9220 +author: George Andrinopoulos diff --git a/changelogs/unreleased/30035-milestone-with-due-date-shows-escaped-html.yml b/changelogs/unreleased/30035-milestone-with-due-date-shows-escaped-html.yml deleted file mode 100644 index 651c299ac66..00000000000 --- a/changelogs/unreleased/30035-milestone-with-due-date-shows-escaped-html.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix escaped html appearing in milestone page -merge_request: 10224 -author: diff --git a/changelogs/unreleased/30098-banzai-filter-mergerequestreferencefilter-has-an-n-1-query-problem.yml b/changelogs/unreleased/30098-banzai-filter-mergerequestreferencefilter-has-an-n-1-query-problem.yml new file mode 100644 index 00000000000..f3f4e065aef --- /dev/null +++ b/changelogs/unreleased/30098-banzai-filter-mergerequestreferencefilter-has-an-n-1-query-problem.yml @@ -0,0 +1,4 @@ +--- +title: Improve Markdown rendering when a lot of merge requests are referenced +merge_request: 10252 +author: diff --git a/changelogs/unreleased/30112-fix-pipelines-sub-nav-highlight.yml b/changelogs/unreleased/30112-fix-pipelines-sub-nav-highlight.yml new file mode 100644 index 00000000000..deca629be83 --- /dev/null +++ b/changelogs/unreleased/30112-fix-pipelines-sub-nav-highlight.yml @@ -0,0 +1,4 @@ +--- +title: Fix sub-nav highlighting for `Environments` and `Jobs` pages +merge_request: 10254 +author: diff --git a/changelogs/unreleased/better-priority-sorting-2.yml b/changelogs/unreleased/better-priority-sorting-2.yml deleted file mode 100644 index ca0d14718dc..00000000000 --- a/changelogs/unreleased/better-priority-sorting-2.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -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 deleted file mode 100644 index a44cd090ceb..00000000000 --- a/changelogs/unreleased/better-priority-sorting.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Allow sorting by due date and priority -merge_request: -author: diff --git a/changelogs/unreleased/calendar-tooltips.yml b/changelogs/unreleased/calendar-tooltips.yml new file mode 100644 index 00000000000..d1517bbab58 --- /dev/null +++ b/changelogs/unreleased/calendar-tooltips.yml @@ -0,0 +1,4 @@ +--- +title: Add tooltip to user's calendar activities +merge_request: 10123 +author: Alex Argunov diff --git a/changelogs/unreleased/filter-bar-fix-ie.yml b/changelogs/unreleased/filter-bar-fix-ie.yml deleted file mode 100644 index f1fa7d9b177..00000000000 --- a/changelogs/unreleased/filter-bar-fix-ie.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fixed filtered search not working in IE -merge_request: -author: diff --git a/changelogs/unreleased/fix-ci-api-regression-for-after-script.yml b/changelogs/unreleased/fix-ci-api-regression-for-after-script.yml deleted file mode 100644 index cdd7d1e6945..00000000000 --- a/changelogs/unreleased/fix-ci-api-regression-for-after-script.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix after_script processing for Runners APIv4 -merge_request: 10185 -author: diff --git a/changelogs/unreleased/fix-gb-environments-folders-route.yml b/changelogs/unreleased/fix-gb-environments-folders-route.yml new file mode 100644 index 00000000000..fd9d9e6f168 --- /dev/null +++ b/changelogs/unreleased/fix-gb-environments-folders-route.yml @@ -0,0 +1,4 @@ +--- +title: Fix environment folder route when special chars present in environment name +merge_request: 10250 +author: diff --git a/changelogs/unreleased/mr-diffs-speed-up.yml b/changelogs/unreleased/mr-diffs-speed-up.yml new file mode 100644 index 00000000000..ccc7a99d05e --- /dev/null +++ b/changelogs/unreleased/mr-diffs-speed-up.yml @@ -0,0 +1,4 @@ +--- +title: Speed up initial rendering of MR diffs page +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 new file mode 100644 index 00000000000..542287a09be --- /dev/null +++ b/changelogs/unreleased/option-to-be-notified-of-own-activity.yml @@ -0,0 +1,4 @@ +--- +title: Add option to receive email notifications about your own activity +merge_request: 10032 +author: Richard Macklin diff --git a/changelogs/unreleased/rename_done_to_closed.yml b/changelogs/unreleased/rename_done_to_closed.yml new file mode 100644 index 00000000000..6de112c4b0d --- /dev/null +++ b/changelogs/unreleased/rename_done_to_closed.yml @@ -0,0 +1,4 @@ +--- +title: Change Done column to Closed in issue boards +merge_request: 10198 +author: blackst0ne diff --git a/changelogs/unreleased/sh-remove-tags-from-explore.yml b/changelogs/unreleased/sh-remove-tags-from-explore.yml new file mode 100644 index 00000000000..b76ec89a006 --- /dev/null +++ b/changelogs/unreleased/sh-remove-tags-from-explore.yml @@ -0,0 +1,4 @@ +--- +title: Remove Tags filter from Projects Explore dropdown +merge_request: +author: diff --git a/changelogs/unreleased/slow-search-changelog.yml b/changelogs/unreleased/slow-search-changelog.yml deleted file mode 100644 index d50cf1f94cd..00000000000 --- a/changelogs/unreleased/slow-search-changelog.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Simplify search queries for projects and merge requests -merge_request: 10053 -author: mhasbini diff --git a/changelogs/unreleased/update-test-bundle-ignored-files.yml b/changelogs/unreleased/update-test-bundle-ignored-files.yml new file mode 100644 index 00000000000..1235d4ced6c --- /dev/null +++ b/changelogs/unreleased/update-test-bundle-ignored-files.yml @@ -0,0 +1,4 @@ +--- +title: update test_bundle.js ignored files +merge_request: +author: diff --git a/config/initializers/rspec_profiling.rb b/config/initializers/rspec_profiling.rb index 70177995356..764c067c6f0 100644 --- a/config/initializers/rspec_profiling.rb +++ b/config/initializers/rspec_profiling.rb @@ -7,7 +7,11 @@ module RspecProfilingExt module Git def branch - ENV['CI_COMMIT_REF_NAME'] || super + if ENV['CI_COMMIT_REF_NAME'] + "#{defined?(Gitlab::License) ? 'ee' : 'ce'}:#{ENV['CI_COMMIT_REF_NAME']}" + else + super + end end end diff --git a/config/routes/project.rb b/config/routes/project.rb index 44b8ae7aedd..7244f851869 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -102,6 +102,7 @@ constraints(ProjectUrlConstrainer.new) do get :merge_widget_refresh post :cancel_merge_when_pipeline_succeeds get :ci_status + get :pipeline_status get :ci_environments_status post :toggle_subscription post :remove_wip @@ -152,6 +153,7 @@ constraints(ProjectUrlConstrainer.new) do post :cancel post :retry get :builds + get :status end end @@ -164,7 +166,7 @@ constraints(ProjectUrlConstrainer.new) do end collection do - get :folder, path: 'folders/:id' + get :folder, path: 'folders/*id', constraints: { format: /(html|json)/ } end end diff --git a/config/webpack.config.js b/config/webpack.config.js index 0859c8416c8..30e9e9c09b4 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -115,7 +115,11 @@ var config = { // create cacheable common library bundle for all d3 chunks new webpack.optimize.CommonsChunkPlugin({ name: 'common_d3', - chunks: ['graphs', 'users', 'monitoring'], + chunks: [ + 'graphs', + 'users', + 'monitoring', + ], }), // create cacheable common library bundles diff --git a/db/migrate/20170301205639_remove_unused_ci_tables_and_columns.rb b/db/migrate/20170301205639_remove_unused_ci_tables_and_columns.rb index 1e2abea5254..69dd15b8b4e 100644 --- a/db/migrate/20170301205639_remove_unused_ci_tables_and_columns.rb +++ b/db/migrate/20170301205639_remove_unused_ci_tables_and_columns.rb @@ -17,7 +17,7 @@ class RemoveUnusedCiTablesAndColumns < ActiveRecord::Migration end remove_column :ci_pipelines, :push_data, :text - remove_column :ci_builds, :job_id, :integer + remove_column :ci_builds, :job_id, :integer if column_exists?(:ci_builds, :job_id) remove_column :ci_builds, :deploy, :boolean end diff --git a/db/migrate/20170316061730_readd_notified_of_own_activity_to_users.rb b/db/migrate/20170316061730_readd_notified_of_own_activity_to_users.rb new file mode 100644 index 00000000000..524eb2557ce --- /dev/null +++ b/db/migrate/20170316061730_readd_notified_of_own_activity_to_users.rb @@ -0,0 +1,10 @@ +class ReaddNotifiedOfOwnActivityToUsers < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + disable_ddl_transaction! + + DOWNTIME = false + + def change + add_column :users, :notified_of_own_activity, :boolean + end +end diff --git a/db/schema.rb b/db/schema.rb index 904fef4a381..f476637ceb2 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -1236,6 +1236,7 @@ ActiveRecord::Schema.define(version: 20170317203554) do t.string "organization" t.boolean "authorized_projects_populated" t.boolean "ghost" + t.boolean "notified_of_own_activity" end add_index "users", ["admin"], name: "index_users_on_admin", using: :btree diff --git a/doc/api/boards.md b/doc/api/boards.md index a74e82335eb..b2106463639 100644 --- a/doc/api/boards.md +++ b/doc/api/boards.md @@ -63,7 +63,7 @@ Example response: ## List board lists Get a list of the board's lists. -Does not include `backlog` and `done` lists +Does not include `backlog` and `closed` lists ``` GET /projects/:id/boards/:board_id/lists diff --git a/doc/api/v3_to_v4.md b/doc/api/v3_to_v4.md index 7f4426ee85d..8e002fe0022 100644 --- a/doc/api/v3_to_v4.md +++ b/doc/api/v3_to_v4.md @@ -80,3 +80,4 @@ Below are the changes made between V3 and V4. - `GET /projects/:id/repository/blobs/:sha` now returns JSON attributes for the blob identified by `:sha`, instead of finding the commit identified by `:sha` and returning the raw content of the blob in that commit identified by the required `?filepath=:filepath` - Moved `GET /projects/:id/repository/commits/:sha/blob?file_path=:file_path` and `GET /projects/:id/repository/blobs/:sha?file_path=:file_path` to `GET /projects/:id/repository/files/:file_path/raw?ref=:sha` - `GET /projects/:id/repository/tree` parameter `ref_name` has been renamed to `ref` for consistency +- `confirm` parameter for `POST /users` has been deprecated in favor of `skip_confirmation` parameter diff --git a/doc/ci/docker/using_docker_build.md b/doc/ci/docker/using_docker_build.md index b3c9fe275c4..edb315d5b84 100644 --- a/doc/ci/docker/using_docker_build.md +++ b/doc/ci/docker/using_docker_build.md @@ -318,7 +318,7 @@ variables: IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME before_script: - - docker login -u gitlab-ci-token -p $CI_COMMIT_TOKEN $CI_REGISTRY + - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY build: stage: build diff --git a/doc/ci/triggers/README.md b/doc/ci/triggers/README.md index ccaee33dc92..e380282f910 100644 --- a/doc/ci/triggers/README.md +++ b/doc/ci/triggers/README.md @@ -4,6 +4,7 @@ - [Introduced][ci-229] in GitLab CE 7.14. - GitLab 8.12 has a completely redesigned job permissions system. Read all about the [new model and its implications](../../user/project/new_ci_build_permissions_model.md#job-triggers). +- GitLab 9.0 introduced a trigger ownership to solve permission problems. Triggers can be used to force a rebuild of a specific `ref` (branch or tag) with an API call. @@ -21,13 +22,30 @@ overview of the time the triggers were last used.  +## Take ownership + +Each created trigger when run will impersonate their associated user including +their access to projects and their project permissions. + +You can take ownership of existing triggers by clicking *Take ownership*. +From now on the trigger will be run as you. + +## Legacy triggers + +Old triggers, created before 9.0 will be marked as Legacy. Triggers with +the legacy label do not have an associated user and only have access +to the current project. + +Legacy trigger are considered deprecated and will be removed +with one of the future versions of GitLab. + ## Revoke a trigger You can revoke a trigger any time by going at your project's **Settings > Triggers** and hitting the **Revoke** button. The action is irreversible. -## Trigger a job +## Trigger a pipeline > **Note**: Valid refs are only the branches and tags. If you pass a commit SHA as a ref, @@ -63,7 +81,7 @@ below. See the [Examples](#examples) section for more details on how to actually trigger a rebuild. -## Trigger a job from webhook +## Trigger a pipeline from webhook > Introduced in GitLab 8.14. @@ -117,7 +135,7 @@ curl --request POST \ "https://gitlab.example.com/api/v4/projects/9/trigger/pipeline?token=TOKEN&ref=master" ``` -### Triggering a job within `.gitlab-ci.yml` +### Triggering a pipeline within `.gitlab-ci.yml` You can also benefit by using triggers in your `.gitlab-ci.yml`. Let's say that you have two projects, A and B, and you want to trigger a rebuild on the `master` diff --git a/doc/ci/triggers/img/triggers_page.png b/doc/ci/triggers/img/triggers_page.png Binary files differindex 8ebf68d0384..eafd8519a23 100644 --- a/doc/ci/triggers/img/triggers_page.png +++ b/doc/ci/triggers/img/triggers_page.png diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index 4e9094cb0f1..b35caf672a8 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -352,7 +352,7 @@ Example values: export CI_JOB_ID="50" export CI_COMMIT_SHA="1ecfd275763eff1d6b4844ea3168962458c9f27a" export CI_COMMIT_REF_NAME="master" -export CI_REPOSITORY="https://gitab-ci-token:abcde-1234ABCD5678ef@example.com/gitlab-org/gitlab-ce.git" +export CI_REPOSITORY_URL="https://gitab-ci-token:abcde-1234ABCD5678ef@example.com/gitlab-org/gitlab-ce.git" export CI_COMMIT_TAG="1.0.0" export CI_JOB_NAME="spec:other" export CI_JOB_STAGE="test" diff --git a/doc/development/fe_guide/style_guide_js.md b/doc/development/fe_guide/style_guide_js.md index 034cfe73d33..abd241c0bc8 100644 --- a/doc/development/fe_guide/style_guide_js.md +++ b/doc/development/fe_guide/style_guide_js.md @@ -200,7 +200,6 @@ See [our current .eslintrc][eslintrc] for specific rules and patterns. #### Naming - **Extensions**: Use `.vue` extension for Vue components. - **Reference Naming**: Use PascalCase for Vue components and camelCase for their instances: - ```javascript // bad import cardBoard from 'cardBoard'; @@ -218,15 +217,23 @@ See [our current .eslintrc][eslintrc] for specific rules and patterns. cardBoard: CardBoard }; ``` -- **Props Naming**: Avoid using DOM component prop names. +- **Props Naming:** +- Avoid using DOM component prop names. +- Use kebab-case instead of camelCase to provide props in templates. ```javascript // bad <component class="btn"> // good - <component cssClass="btn"> - ``` + <component css-class="btn"> + + // bad + <component myProp="prop" /> + + // good + <component my-prop="prop" /> +``` #### Alignment - Follow these alignment styles for the template method: diff --git a/doc/development/ux_guide/components.md b/doc/development/ux_guide/components.md index 18d0647c798..ac7c1b6207d 100644 --- a/doc/development/ux_guide/components.md +++ b/doc/development/ux_guide/components.md @@ -19,14 +19,24 @@ --- ## Tooltips +Tooltips identify elements or provide additional, useful information about the referring elements. Tooltips are different from ALT-attributes, which are intended primarily for static images. Tooltips are summoned by: + +* Hovering over an element with a cursor +* Focusing on an element with a keyboard (usually the tab key) +* Upon touch ### Usage -A tooltip should only be added if additional information is required. +A tooltip should be used: +* When there isn’t enough space to show the information +* When it isn’t critical for the user to see the information +* For icons that don’t have a label + +Tooltips shouldn’t repeat information that is shown near the referring element. However, they can show the same data in a different format (e.g. date or timestamps).  ### Placement -By default, tooltips should be placed below the element that they refer to. However, if there is not enough space in the viewpoint, the tooltip should be moved to the side as needed. +By default, tooltips should be placed below the referring element. However, if there isn’t enough space in the viewport, the tooltip should be moved to the side as needed.  diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md index 676a21e85c4..12d7700176c 100644 --- a/doc/user/project/integrations/prometheus.md +++ b/doc/user/project/integrations/prometheus.md @@ -153,8 +153,8 @@ The queries utilized by GitLab are shown in the following table. | Metric | Query | | ------ | ----- | -| Average Memory (MB) | `(sum(container_memory_usage_bytes{container_name="app",environment="$CI_ENVIRONMENT_SLUG"}) / count(container_memory_usage_bytes{container_name="app",environment="$CI_ENVIRONMENT_SLUG"})) /1024/1024` | -| Average CPU Utilization (%) | `sum(rate(container_cpu_usage_seconds_total{container_name="app",environment="$CI_ENVIRONMENT_SLUG"}[2m])) / count(container_cpu_usage_seconds_total{container_name="app",environment="$CI_ENVIRONMENT_SLUG"}) * 100` | +| Average Memory (MB) | `(sum(container_memory_usage_bytes{container_name!="POD",environment="$CI_ENVIRONMENT_SLUG"}) / count(container_memory_usage_bytes{container_name!="POD",environment="$CI_ENVIRONMENT_SLUG"})) /1024/1024` | +| Average CPU Utilization (%) | `sum(rate(container_cpu_usage_seconds_total{container_name!="POD",environment="$CI_ENVIRONMENT_SLUG"}[2m])) / count(container_cpu_usage_seconds_total{container_name!="POD",environment="$CI_ENVIRONMENT_SLUG"}) * 100` | ## Monitoring CI/CD Environments diff --git a/doc/user/project/new_ci_build_permissions_model.md b/doc/user/project/new_ci_build_permissions_model.md index b559d132590..55610a7b014 100644 --- a/doc/user/project/new_ci_build_permissions_model.md +++ b/doc/user/project/new_ci_build_permissions_model.md @@ -87,12 +87,12 @@ your Runners in the most possible secure way, by avoiding the following: By using an insecure GitLab Runner configuration, you allow the rogue developers to steal the tokens of other jobs. -## job triggers +## Pipeline triggers -[job triggers][triggers] do not support the new permission model. -They continue to use the old authentication mechanism where the CI job -can access only its own sources. We plan to remove that limitation in one of -the upcoming releases. +Since 9.0 [pipelnie triggers][triggers] do support the new permission model. +The new triggers do impersonate their associated user including their access +to projects and their project permissions. To migrate trigger to use new permisison +model use **Take ownership**. ## Before GitLab 8.12 diff --git a/lib/api/users.rb b/lib/api/users.rb index 2d4d5a25221..a4201fe6fed 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -27,7 +27,7 @@ module API optional :location, type: String, desc: 'The location of the user' optional :admin, type: Boolean, desc: 'Flag indicating the user is an administrator' optional :can_create_group, type: Boolean, desc: 'Flag indicating the user can create groups' - optional :confirm, type: Boolean, desc: 'Flag indicating the account needs to be confirmed' + optional :skip_confirmation, type: Boolean, default: false, desc: 'Flag indicating the account is confirmed' optional :external, type: Boolean, desc: 'Flag indicating the user is an external user' all_or_none_of :extern_uid, :provider end @@ -97,29 +97,10 @@ module API post do authenticated_as_admin! - # Filter out params which are used later - user_params = declared_params(include_missing: false) - identity_attrs = user_params.slice(:provider, :extern_uid) - confirm = user_params.delete(:confirm) - user = User.new(user_params.except(:extern_uid, :provider, :reset_password)) - - if user_params.delete(:reset_password) - user.attributes = { - force_random_password: true, - password_expires_at: nil, - created_by_id: current_user.id - } - user.generate_password - user.generate_reset_token - end - - user.skip_confirmation! unless confirm - - if identity_attrs.any? - user.identities.build(identity_attrs) - end + params = declared_params(include_missing: false) + user = ::Users::CreateService.new(current_user, params).execute - if user.save + if user.persisted? present user, with: Entities::UserPublic else conflict!('Email has already been taken') if User. diff --git a/lib/api/v3/users.rb b/lib/api/v3/users.rb index 14f54731730..5e18cecc431 100644 --- a/lib/api/v3/users.rb +++ b/lib/api/v3/users.rb @@ -9,6 +9,59 @@ module API end resource :users, requirements: { uid: /[0-9]*/, id: /[0-9]*/ } do + helpers do + params :optional_attributes do + optional :skype, type: String, desc: 'The Skype username' + optional :linkedin, type: String, desc: 'The LinkedIn username' + optional :twitter, type: String, desc: 'The Twitter username' + optional :website_url, type: String, desc: 'The website of the user' + optional :organization, type: String, desc: 'The organization of the user' + optional :projects_limit, type: Integer, desc: 'The number of projects a user can create' + optional :extern_uid, type: String, desc: 'The external authentication provider UID' + optional :provider, type: String, desc: 'The external provider' + optional :bio, type: String, desc: 'The biography of the user' + optional :location, type: String, desc: 'The location of the user' + optional :admin, type: Boolean, desc: 'Flag indicating the user is an administrator' + optional :can_create_group, type: Boolean, desc: 'Flag indicating the user can create groups' + optional :confirm, type: Boolean, default: true, desc: 'Flag indicating the account needs to be confirmed' + optional :external, type: Boolean, desc: 'Flag indicating the user is an external user' + all_or_none_of :extern_uid, :provider + end + end + + desc 'Create a user. Available only for admins.' do + success ::API::Entities::UserPublic + end + params do + requires :email, type: String, desc: 'The email of the user' + optional :password, type: String, desc: 'The password of the new user' + optional :reset_password, type: Boolean, desc: 'Flag indicating the user will be sent a password reset token' + at_least_one_of :password, :reset_password + requires :name, type: String, desc: 'The name of the user' + requires :username, type: String, desc: 'The username of the user' + use :optional_attributes + end + post do + authenticated_as_admin! + + params = declared_params(include_missing: false) + user = ::Users::CreateService.new(current_user, params.merge!(skip_confirmation: !params[:confirm])).execute + + if user.persisted? + present user, with: ::API::Entities::UserPublic + else + conflict!('Email has already been taken') if User. + where(email: user.email). + count > 0 + + conflict!('Username has already been taken') if User. + where(username: user.username). + count > 0 + + render_validation_error!(user) + end + end + desc 'Get the SSH keys of a specified user. Available only for admins.' do success ::API::Entities::SSHKey end diff --git a/lib/banzai/filter/merge_request_reference_filter.rb b/lib/banzai/filter/merge_request_reference_filter.rb index ac5216d9cfb..3888acf935e 100644 --- a/lib/banzai/filter/merge_request_reference_filter.rb +++ b/lib/banzai/filter/merge_request_reference_filter.rb @@ -11,8 +11,8 @@ module Banzai MergeRequest end - def find_object(project, id) - project.merge_requests.find_by(iid: id) + def find_object(project, iid) + merge_requests_per_project[project][iid] end def url_for_object(mr, project) @@ -21,6 +21,31 @@ module Banzai only_path: context[:only_path]) end + def project_from_ref(ref) + projects_per_reference[ref || current_project_path] + end + + # Returns a Hash containing the merge_requests per Project instance. + def merge_requests_per_project + @merge_requests_per_project ||= begin + hash = Hash.new { |h, k| h[k] = {} } + + projects_per_reference.each do |path, project| + merge_request_ids = references_per_project[path] + + merge_requests = project.merge_requests + .where(iid: merge_request_ids.to_a) + .includes(target_project: :namespace) + + merge_requests.each do |merge_request| + hash[project][merge_request.iid.to_i] = merge_request + end + end + + hash + end + end + def object_link_text_extras(object, matches) extras = super diff --git a/lib/gitlab/ci/status/canceled.rb b/lib/gitlab/ci/status/canceled.rb index dd6d99e9075..97c121ce7b9 100644 --- a/lib/gitlab/ci/status/canceled.rb +++ b/lib/gitlab/ci/status/canceled.rb @@ -13,6 +13,10 @@ module Gitlab def icon 'icon_status_canceled' end + + def favicon + 'favicon_status_canceled' + end end end end diff --git a/lib/gitlab/ci/status/core.rb b/lib/gitlab/ci/status/core.rb index 3dd2b9e01f6..d4fd83b93f8 100644 --- a/lib/gitlab/ci/status/core.rb +++ b/lib/gitlab/ci/status/core.rb @@ -18,6 +18,10 @@ module Gitlab raise NotImplementedError end + def favicon + raise NotImplementedError + end + def label raise NotImplementedError end diff --git a/lib/gitlab/ci/status/created.rb b/lib/gitlab/ci/status/created.rb index 6596d7e01ca..0721bf6ec7c 100644 --- a/lib/gitlab/ci/status/created.rb +++ b/lib/gitlab/ci/status/created.rb @@ -13,6 +13,10 @@ module Gitlab def icon 'icon_status_created' end + + def favicon + 'favicon_status_created' + end end end end diff --git a/lib/gitlab/ci/status/failed.rb b/lib/gitlab/ci/status/failed.rb index c5b5e3203ad..cb75e9383a8 100644 --- a/lib/gitlab/ci/status/failed.rb +++ b/lib/gitlab/ci/status/failed.rb @@ -13,6 +13,10 @@ module Gitlab def icon 'icon_status_failed' end + + def favicon + 'favicon_status_failed' + end end end end diff --git a/lib/gitlab/ci/status/manual.rb b/lib/gitlab/ci/status/manual.rb index 5f28521901d..f8f6c2903ba 100644 --- a/lib/gitlab/ci/status/manual.rb +++ b/lib/gitlab/ci/status/manual.rb @@ -13,6 +13,10 @@ module Gitlab def icon 'icon_status_manual' end + + def favicon + 'favicon_status_manual' + end end end end diff --git a/lib/gitlab/ci/status/pending.rb b/lib/gitlab/ci/status/pending.rb index d30f35a59a2..f40cc1314dc 100644 --- a/lib/gitlab/ci/status/pending.rb +++ b/lib/gitlab/ci/status/pending.rb @@ -13,6 +13,10 @@ module Gitlab def icon 'icon_status_pending' end + + def favicon + 'favicon_status_pending' + end end end end diff --git a/lib/gitlab/ci/status/running.rb b/lib/gitlab/ci/status/running.rb index 2aba3c373c7..1237cd47dc8 100644 --- a/lib/gitlab/ci/status/running.rb +++ b/lib/gitlab/ci/status/running.rb @@ -13,6 +13,10 @@ module Gitlab def icon 'icon_status_running' end + + def favicon + 'favicon_status_running' + end end end end diff --git a/lib/gitlab/ci/status/skipped.rb b/lib/gitlab/ci/status/skipped.rb index 16282aefd03..28005d91503 100644 --- a/lib/gitlab/ci/status/skipped.rb +++ b/lib/gitlab/ci/status/skipped.rb @@ -13,6 +13,10 @@ module Gitlab def icon 'icon_status_skipped' end + + def favicon + 'favicon_status_skipped' + end end end end diff --git a/lib/gitlab/ci/status/success.rb b/lib/gitlab/ci/status/success.rb index c09c5f006e3..88f7758a270 100644 --- a/lib/gitlab/ci/status/success.rb +++ b/lib/gitlab/ci/status/success.rb @@ -13,6 +13,10 @@ module Gitlab def icon 'icon_status_success' end + + def favicon + 'favicon_status_success' + end end end end diff --git a/lib/gitlab/ee_compat_check.rb b/lib/gitlab/ee_compat_check.rb index 0829c1c318e..496ee0bdcb0 100644 --- a/lib/gitlab/ee_compat_check.rb +++ b/lib/gitlab/ee_compat_check.rb @@ -125,7 +125,7 @@ module Gitlab end puts - puts applies_cleanly_msg(ee_branch) + puts applies_cleanly_msg(ee_branch_found) end def check_patch(patch_path) @@ -215,7 +215,7 @@ module Gitlab end def ee_patch_name - @ee_patch_name ||= patch_name_from_branch(ee_branch) + @ee_patch_name ||= patch_name_from_branch(ee_branch_found) end def ee_patch_full_path diff --git a/lib/gitlab/o_auth/user.rb b/lib/gitlab/o_auth/user.rb index fcf51b7fc5b..f98481c6d3a 100644 --- a/lib/gitlab/o_auth/user.rb +++ b/lib/gitlab/o_auth/user.rb @@ -147,10 +147,8 @@ module Gitlab end def build_new_user - user = ::User.new(user_attributes) - user.skip_confirmation! - user.identities.new(extern_uid: auth_hash.uid, provider: auth_hash.provider) - user + user_params = user_attributes.merge(extern_uid: auth_hash.uid, provider: auth_hash.provider, skip_confirmation: true) + Users::CreateService.new(nil, user_params).build end def user_attributes diff --git a/scripts/merge-reports b/scripts/merge-reports index f7b574001ac..aad76bcc327 100755 --- a/scripts/merge-reports +++ b/scripts/merge-reports @@ -1,7 +1,6 @@ #!/usr/bin/env ruby require 'json' -require 'yaml' main_report_file = ARGV.shift unless main_report_file diff --git a/scripts/sync-reports b/scripts/sync-reports new file mode 100755 index 00000000000..5ed65e78005 --- /dev/null +++ b/scripts/sync-reports @@ -0,0 +1,95 @@ +#!/usr/bin/env ruby + +require 'rubygems' +require 'fog/aws' + +class SyncReports + ACTIONS = %w[get put].freeze + + attr_reader :options + + def initialize(options) + @options = options + + perform_sync! + end + + private + + def perform_sync! + case options[:action] + when 'get' + get_reports! + when 'put' + put_reports! + end + end + + def get_reports! + options[:report_paths].each { |report_path| get_report!(report_path) } + end + + def put_reports! + options[:report_paths].each { |report_path| put_report!(report_path) } + end + + def get_report!(report_path) + file = bucket.files.get(report_path) + + if file.respond_to?(:body) + File.write(report_path, file.body) + puts "#{report_path} was retrieved from S3." + else + puts "#{report_path} does not seem to exist on S3." + end + end + + def put_report!(report_path) + bucket.files.create( + key: report_path, + body: File.open(report_path), + public: true + ) + puts "#{report_path} was uploaded to S3." + end + + def bucket + @bucket ||= storage.directories.get(options[:bucket]) + end + + def storage + @storage ||= + Fog::Storage.new( + provider: 'AWS', + aws_access_key_id: ENV['AWS_ACCESS_KEY_ID'], + aws_secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'] + ) + end +end + +def usage!(error: 'action') + print "\n[ERROR]: " + case error + when 'action' + puts "Please specify an action as first argument: #{SyncReports::ACTIONS.join(', ')}\n\n" + when 'bucket' + puts "Please specify a bucket as second argument!\n\n" + when 'files' + puts "Please specify one or more file paths as third argument!\n\n" + end + puts "Usage: #{__FILE__} [get|put] bucket report_path ...\n\n" + puts "Note: the AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment "\ + "variables need to be set\n\n" + exit 1 +end + +if $0 == __FILE__ + action = ARGV.shift + usage!(error: 'action') unless SyncReports::ACTIONS.include?(action) + + bucket = ARGV.shift + usage!(error: 'bucket') unless bucket + usage!(error: 'files') unless ARGV.any? + + SyncReports.new(action: action, bucket: bucket, report_paths: ARGV) +end diff --git a/spec/controllers/profiles/notifications_controller_spec.rb b/spec/controllers/profiles/notifications_controller_spec.rb new file mode 100644 index 00000000000..b97cdd4d489 --- /dev/null +++ b/spec/controllers/profiles/notifications_controller_spec.rb @@ -0,0 +1,45 @@ +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.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/builds_controller_spec.rb b/spec/controllers/projects/builds_controller_spec.rb new file mode 100644 index 00000000000..683667129e5 --- /dev/null +++ b/spec/controllers/projects/builds_controller_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' + +describe Projects::BuildsController do + include ApiHelpers + + let(:user) { create(:user) } + let(:project) { create(:empty_project, :public) } + + before do + sign_in(user) + end + + describe 'GET status.json' do + let(:pipeline) { create(:ci_pipeline, project: project) } + let(:build) { create(:ci_build, pipeline: pipeline) } + let(:status) { build.detailed_status(double('user')) } + + before do + get :status, namespace_id: project.namespace, + project_id: project, + id: build.id, + format: :json + end + + it 'return a detailed build status in json' do + expect(response).to have_http_status(:ok) + expect(json_response['text']).to eq status.text + expect(json_response['label']).to eq status.label + expect(json_response['icon']).to eq status.icon + expect(json_response['favicon']).to eq status.favicon + end + end +end diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb index 83d80b376fb..5525fbd8130 100644 --- a/spec/controllers/projects/environments_controller_spec.rb +++ b/spec/controllers/projects/environments_controller_spec.rb @@ -81,6 +81,39 @@ describe Projects::EnvironmentsController do end end + describe 'GET folder' do + before do + create(:environment, project: project, + name: 'staging-1.0/review', + state: :available) + end + + context 'when using default format' do + it 'responds with HTML' do + get :folder, namespace_id: project.namespace, + project_id: project, + id: 'staging-1.0' + + expect(response).to be_ok + expect(response).to render_template 'folder' + end + end + + context 'when using JSON format' do + it 'responds with JSON' do + get :folder, namespace_id: project.namespace, + project_id: project, + id: 'staging-1.0', + format: :json + + expect(response).to be_ok + expect(response).not_to render_template 'folder' + expect(json_response['environments'][0]) + .to include('name' => 'staging-1.0/review') + end + end + end + describe 'GET show' do context 'with valid id' do it 'responds with a status code 200' do diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index c467ab9fb8a..734966d50b2 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -90,6 +90,7 @@ describe Projects::IssuesController do it 'redirects to signin if not logged in' do get :new, namespace_id: project.namespace, project_id: project + expect(flash[:notice]).to eq 'Please sign in to create the new issue.' expect(response).to redirect_to(new_user_session_path) end diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index c310d830e81..72f41f7209a 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -1178,4 +1178,42 @@ describe Projects::MergeRequestsController do end end end + + describe 'GET pipeline_status.json' do + context 'when head_pipeline exists' do + let!(:pipeline) do + create(:ci_pipeline, project: merge_request.source_project, + ref: merge_request.source_branch, + sha: merge_request.diff_head_sha) + end + + let(:status) { pipeline.detailed_status(double('user')) } + + before { get_pipeline_status } + + it 'return a detailed head_pipeline status in json' do + expect(response).to have_http_status(:ok) + expect(json_response['text']).to eq status.text + expect(json_response['label']).to eq status.label + expect(json_response['icon']).to eq status.icon + expect(json_response['favicon']).to eq status.favicon + end + end + + context 'when head_pipeline does not exist' do + before { get_pipeline_status } + + it 'return empty' do + expect(response).to have_http_status(:ok) + expect(json_response).to be_empty + end + end + + def get_pipeline_status + get :pipeline_status, namespace_id: project.namespace, + project_id: project, + id: merge_request.iid, + format: :json + end + end end diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb index 04bb5cbbd59..d8f9bfd0d37 100644 --- a/spec/controllers/projects/pipelines_controller_spec.rb +++ b/spec/controllers/projects/pipelines_controller_spec.rb @@ -69,4 +69,24 @@ describe Projects::PipelinesController do format: :json end end + + describe 'GET status.json' do + let(:pipeline) { create(:ci_pipeline, project: project) } + let(:status) { pipeline.detailed_status(double('user')) } + + before do + get :status, namespace_id: project.namespace, + project_id: project, + id: pipeline.id, + format: :json + end + + it 'return a detailed pipeline status in json' do + expect(response).to have_http_status(:ok) + expect(json_response['text']).to eq status.text + expect(json_response['label']).to eq status.label + expect(json_response['icon']).to eq status.icon + expect(json_response['favicon']).to eq status.favicon + end + end end diff --git a/spec/controllers/registrations_controller_spec.rb b/spec/controllers/registrations_controller_spec.rb index 8cc216445eb..902911071c4 100644 --- a/spec/controllers/registrations_controller_spec.rb +++ b/spec/controllers/registrations_controller_spec.rb @@ -30,6 +30,15 @@ describe RegistrationsController do expect(subject.current_user).to be_nil end end + + context 'when signup_enabled? is false' do + it 'redirects to sign_in' do + allow_any_instance_of(ApplicationSetting).to receive(:signup_enabled?).and_return(false) + + expect { post(:create, user_params) }.not_to change(User, :count) + expect(response).to redirect_to(new_user_session_path) + end + end end context 'when reCAPTCHA is enabled' do diff --git a/spec/factories/boards.rb b/spec/factories/boards.rb index a581725245a..4df9aef2846 100644 --- a/spec/factories/boards.rb +++ b/spec/factories/boards.rb @@ -3,7 +3,7 @@ FactoryGirl.define do project factory: :empty_project after(:create) do |board| - board.lists.create(list_type: :done) + board.lists.create(list_type: :closed) end end end diff --git a/spec/factories/lists.rb b/spec/factories/lists.rb index 2a2f3cca91c..f6a78811cbe 100644 --- a/spec/factories/lists.rb +++ b/spec/factories/lists.rb @@ -6,8 +6,8 @@ FactoryGirl.define do sequence(:position) end - factory :done_list, parent: :list do - list_type :done + factory :closed_list, parent: :list do + list_type :closed label nil position nil end diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index f7e8b78b54d..e168585534d 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -42,7 +42,7 @@ describe 'Issue Boards', feature: true, js: true do end it 'creates default lists' do - lists = ['To Do', 'Doing', 'Done'] + lists = ['To Do', 'Doing', 'Closed'] page.within(find('.board-blank-state')) do click_button('Add default lists') @@ -65,7 +65,7 @@ describe 'Issue Boards', feature: true, js: true do let(:testing) { create(:label, project: project, name: 'Testing') } let(:bug) { create(:label, project: project, name: 'Bug') } let!(:backlog) { create(:label, project: project, name: 'Backlog') } - let!(:done) { create(:label, project: project, name: 'Done') } + let!(:closed) { create(:label, project: project, name: 'Closed') } let!(:accepting) { create(:label, project: project, name: 'Accepting Merge Requests') } let!(:list1) { create(:list, board: board, label: planning, position: 0) } @@ -114,7 +114,7 @@ describe 'Issue Boards', feature: true, js: true do end end - it 'search done list' do + it 'search closed list' do find('.filtered-search').set(issue8.title) find('.filtered-search').native.send_keys(:enter) @@ -186,13 +186,13 @@ describe 'Issue Boards', feature: true, js: true do end end - context 'done' do - it 'shows list of done issues' do + context 'closed' do + it 'shows list of closed issues' do wait_for_board_cards(3, 1) wait_for_ajax end - it 'moves issue to done' do + it 'moves issue to closed' do drag(list_from_index: 0, list_to_index: 2) wait_for_board_cards(1, 7) @@ -205,7 +205,7 @@ describe 'Issue Boards', feature: true, js: true do expect(find('.board:nth-child(3)')).not_to have_content(planning.title) end - it 'removes all of the same issue to done' do + it 'removes all of the same issue to closed' do drag(list_from_index: 0, list_to_index: 2) wait_for_board_cards(1, 7) @@ -252,7 +252,7 @@ describe 'Issue Boards', feature: true, js: true do expect(find('.board:nth-child(1)').all('.card').first).not_to have_content(planning.title) end - it 'issue moves from done' do + it 'issue moves from closed' do drag(list_from_index: 2, list_to_index: 1) expect(find('.board:nth-child(2)')).to have_content(issue8.title) @@ -308,12 +308,12 @@ describe 'Issue Boards', feature: true, js: true do expect(page).to have_selector('.board', count: 4) end - it 'creates new list for Done label' do + it 'creates new list for Closed label' do click_button 'Add list' wait_for_ajax page.within('.dropdown-menu-issues-board-new') do - click_link done.title + click_link closed.title end wait_for_vue_resource @@ -326,7 +326,7 @@ describe 'Issue Boards', feature: true, js: true do wait_for_ajax page.within('.dropdown-menu-issues-board-new') do - click_link done.title + click_link closed.title end wait_for_vue_resource diff --git a/spec/features/boards/new_issue_spec.rb b/spec/features/boards/new_issue_spec.rb index 6d14a8cf483..e6d7cf106d4 100644 --- a/spec/features/boards/new_issue_spec.rb +++ b/spec/features/boards/new_issue_spec.rb @@ -25,7 +25,7 @@ describe 'Issue Boards new issue', feature: true, js: true do expect(page).to have_selector('.board-issue-count-holder .btn', count: 1) end - it 'does not display new issue button in done list' do + it 'does not display new issue button in closed list' do page.within('.board:nth-child(2)') do expect(page).not_to have_selector('.board-issue-count-holder .btn') end diff --git a/spec/features/groups/group_name_toggle_spec.rb b/spec/features/groups/group_name_toggle_spec.rb index 8528718a2f7..8a1d415c4f1 100644 --- a/spec/features/groups/group_name_toggle_spec.rb +++ b/spec/features/groups/group_name_toggle_spec.rb @@ -6,39 +6,46 @@ feature 'Group name toggle', feature: true, js: true do let(:nested_group_2) { create(:group, parent: nested_group_1) } let(:nested_group_3) { create(:group, parent: nested_group_2) } + SMALL_SCREEN = 300 + before do login_as :user end - it 'is not present for less than 3 groups' do - visit group_path(group) - expect(page).not_to have_css('.group-name-toggle') + it 'is not present if enough horizontal space' do + visit group_path(nested_group_3) - visit group_path(nested_group_1) + container_width = page.evaluate_script("$('.title-container')[0].offsetWidth") + title_width = page.evaluate_script("$('.title')[0].offsetWidth") + + expect(container_width).to be > title_width expect(page).not_to have_css('.group-name-toggle') end - it 'is present for nested group of 3 or more in the namespace' do - visit group_path(nested_group_2) - expect(page).to have_css('.group-name-toggle') - + it 'is present if the title is longer than the container' do visit group_path(nested_group_3) - expect(page).to have_css('.group-name-toggle') + title_width = page.evaluate_script("$('.title')[0].offsetWidth") + + page_height = page.current_window.size[1] + page.current_window.resize_to(SMALL_SCREEN, page_height) + + find('.group-name-toggle') + container_width = page.evaluate_script("$('.title-container')[0].offsetWidth") + + expect(title_width).to be > container_width end - context 'for group with at least 3 groups' do - before do - visit group_path(nested_group_2) - end + it 'should show the full group namespace when toggled' do + page_height = page.current_window.size[1] + page.current_window.resize_to(SMALL_SCREEN, page_height) + visit group_path(nested_group_3) - it 'should show the full group namespace when toggled' do - expect(page).not_to have_content(group.name) - expect(page).to have_css('.group-path.hidable', visible: false) + expect(page).not_to have_content(group.name) + expect(page).to have_css('.group-path.hidable', visible: false) - click_button '...' + click_button '...' - expect(page).to have_content(group.name) - expect(page).to have_css('.group-path.hidable', visible: true) - end + expect(page).to have_content(group.name) + expect(page).to have_css('.group-path.hidable', visible: true) end end diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb index d243f9478bb..144d069b632 100644 --- a/spec/features/groups_spec.rb +++ b/spec/features/groups_spec.rb @@ -46,7 +46,7 @@ feature 'Group', feature: true do describe 'Mattermost team creation' do before do - allow(Settings.mattermost).to receive_messages(enabled: mattermost_enabled) + stub_mattermost_setting(enabled: mattermost_enabled) visit new_group_path end diff --git a/spec/features/merge_requests/toggler_behavior_spec.rb b/spec/features/merge_requests/toggler_behavior_spec.rb index a2cf9b18bf2..3acd3f6a8b3 100644 --- a/spec/features/merge_requests/toggler_behavior_spec.rb +++ b/spec/features/merge_requests/toggler_behavior_spec.rb @@ -18,7 +18,7 @@ feature 'toggler_behavior', js: true, feature: true do it 'should be scrolled down to fragment' do page_height = page.current_window.size[1] page_scroll_y = page.evaluate_script("window.scrollY") - fragment_position_top = page.evaluate_script("$('#{fragment_id}').offset().top") + fragment_position_top = page.evaluate_script("Math.round($('#{fragment_id}').offset().top)") expect(find('.js-toggle-content').visible?).to eq true expect(find(fragment_id).visible?).to eq true expect(fragment_position_top).to be >= page_scroll_y 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 new file mode 100644 index 00000000000..e05fbb3715c --- /dev/null +++ b/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb @@ -0,0 +1,32 @@ +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/environments/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb index e2d16e0830a..acc3efe04e6 100644 --- a/spec/features/projects/environments/environment_spec.rb +++ b/spec/features/projects/environments/environment_spec.rb @@ -166,6 +166,25 @@ feature 'Environment', :feature do end end + feature 'environment folders', :js do + context 'when folder name contains special charaters' do + before do + create(:environment, project: project, + name: 'staging-1.0/review', + state: :available) + + visit folder_namespace_project_environments_path(project.namespace, + project, + id: 'staging-1.0') + end + + it 'renders a correct environment folder' do + expect(page).to have_http_status(:ok) + expect(page).to have_content('Environments / staging-1.0') + end + end + end + feature 'auto-close environment when branch is deleted' do given(:project) { create(:project) } diff --git a/spec/features/projects/services/mattermost_slash_command_spec.rb b/spec/features/projects/services/mattermost_slash_command_spec.rb index 24d22a092d4..dc3854262e7 100644 --- a/spec/features/projects/services/mattermost_slash_command_spec.rb +++ b/spec/features/projects/services/mattermost_slash_command_spec.rb @@ -7,7 +7,7 @@ feature 'Setup Mattermost slash commands', :feature, :js do let(:mattermost_enabled) { true } before do - Settings.mattermost['enabled'] = mattermost_enabled + stub_mattermost_setting(enabled: mattermost_enabled) project.team << [user, :master] login_as(user) visit edit_namespace_project_service_path(project.namespace, project, service) diff --git a/spec/features/user_callout_spec.rb b/spec/features/user_callout_spec.rb index 659cd7c7af7..848af5e3a4d 100644 --- a/spec/features/user_callout_spec.rb +++ b/spec/features/user_callout_spec.rb @@ -7,15 +7,27 @@ describe 'User Callouts', js: true do before do login_as(user) - project.team << [user, :master] + project.team << [user, :master] end - it 'takes you to the profile preferences when the link is clicked' do + it 'takes you to the profile preferences when the link is clicked' do visit dashboard_projects_path click_link 'Check it out' expect(current_path).to eq profile_preferences_path end + it 'does not show when cookie is set' do + visit dashboard_projects_path + + within('.user-callout') do + find('.close').click + end + + visit dashboard_projects_path + + expect(page).not_to have_selector('.user-callout') + end + describe 'user callout should appear in two routes' do it 'shows up on the user profile' do visit user_path(user) @@ -31,7 +43,7 @@ describe 'User Callouts', js: true do it 'hides the user callout when click on the dismiss icon' do visit user_path(user) within('.user-callout') do - find('.close-user-callout').click + find('.close').click end expect(page).not_to have_selector('.user-callout') end diff --git a/spec/fixtures/api/schemas/list.json b/spec/fixtures/api/schemas/list.json index 819287bf919..11a4caf6628 100644 --- a/spec/fixtures/api/schemas/list.json +++ b/spec/fixtures/api/schemas/list.json @@ -10,7 +10,7 @@ "id": { "type": "integer" }, "list_type": { "type": "string", - "enum": ["label", "done"] + "enum": ["label", "closed"] }, "label": { "type": ["object", "null"], diff --git a/spec/javascripts/boards/board_card_spec.js b/spec/javascripts/boards/board_card_spec.js index 73d18458366..de072e7e470 100644 --- a/spec/javascripts/boards/board_card_spec.js +++ b/spec/javascripts/boards/board_card_spec.js @@ -1,10 +1,12 @@ /* global List */ +/* global ListUser */ /* global ListLabel */ /* global listObj */ /* global boardsMockInterceptor */ /* global BoardService */ import Vue from 'vue'; +import '~/boards/models/user'; require('~/boards/models/list'); require('~/boards/models/label'); @@ -130,6 +132,23 @@ describe('Issue card', () => { expect(gl.issueBoards.BoardsStore.detail.issue).toEqual({}); }); + it('does not set detail issue if img is clicked', (done) => { + vm.issue.assignee = new ListUser({ + id: 1, + name: 'testing 123', + username: 'test', + avatar: 'test_image', + }); + + Vue.nextTick(() => { + triggerEvent('mouseup', vm.$el.querySelector('img')); + + expect(gl.issueBoards.BoardsStore.detail.issue).toEqual({}); + + done(); + }); + }); + it('does not set detail issue if showDetail is false after mouseup', () => { triggerEvent('mouseup'); diff --git a/spec/javascripts/boards/boards_store_spec.js b/spec/javascripts/boards/boards_store_spec.js index e21f4ca2bc0..b55ff2f473a 100644 --- a/spec/javascripts/boards/boards_store_spec.js +++ b/spec/javascripts/boards/boards_store_spec.js @@ -50,9 +50,9 @@ describe('Store', () => { it('finds list by ID', () => { gl.issueBoards.BoardsStore.addList(listObj); - const list = gl.issueBoards.BoardsStore.findList('id', 1); + const list = gl.issueBoards.BoardsStore.findList('id', listObj.id); - expect(list.id).toBe(1); + expect(list.id).toBe(listObj.id); }); it('finds list by type', () => { @@ -64,7 +64,7 @@ describe('Store', () => { it('gets issue when new list added', (done) => { gl.issueBoards.BoardsStore.addList(listObj); - const list = gl.issueBoards.BoardsStore.findList('id', 1); + const list = gl.issueBoards.BoardsStore.findList('id', listObj.id); expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(1); @@ -89,9 +89,9 @@ describe('Store', () => { expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(1); setTimeout(() => { - const list = gl.issueBoards.BoardsStore.findList('id', 1); + const list = gl.issueBoards.BoardsStore.findList('id', listObj.id); expect(list).toBeDefined(); - expect(list.id).toBe(1); + expect(list.id).toBe(listObj.id); expect(list.position).toBe(0); done(); }, 0); @@ -106,9 +106,9 @@ describe('Store', () => { expect(gl.issueBoards.BoardsStore.shouldAddBlankState()).toBe(false); }); - it('check for blank state adding when done list exist', () => { + it('check for blank state adding when closed list exist', () => { gl.issueBoards.BoardsStore.addList({ - list_type: 'done' + list_type: 'closed' }); expect(gl.issueBoards.BoardsStore.shouldAddBlankState()).toBe(true); @@ -126,7 +126,7 @@ describe('Store', () => { expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(1); - gl.issueBoards.BoardsStore.removeList(1, 'label'); + gl.issueBoards.BoardsStore.removeList(listObj.id, 'label'); expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(0); }); @@ -137,7 +137,7 @@ describe('Store', () => { expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(2); - gl.issueBoards.BoardsStore.moveList(listOne, ['2', '1']); + gl.issueBoards.BoardsStore.moveList(listOne, [listObjDuplicate.id, listObj.id]); expect(listOne.position).toBe(1); }); diff --git a/spec/javascripts/boards/list_spec.js b/spec/javascripts/boards/list_spec.js index 66fc01fa1e5..a9d4c6ef76f 100644 --- a/spec/javascripts/boards/list_spec.js +++ b/spec/javascripts/boards/list_spec.js @@ -43,7 +43,7 @@ describe('List model', () => { list = new List({ title: 'test', label: { - id: 1, + id: _.random(10000), title: 'test', color: 'red' } @@ -51,7 +51,7 @@ describe('List model', () => { list.save(); setTimeout(() => { - expect(list.id).toBe(1); + expect(list.id).toBe(listObj.id); expect(list.type).toBe('label'); expect(list.position).toBe(0); done(); @@ -60,7 +60,7 @@ describe('List model', () => { it('destroys the list', (done) => { gl.issueBoards.BoardsStore.addList(listObj); - list = gl.issueBoards.BoardsStore.findList('id', 1); + list = gl.issueBoards.BoardsStore.findList('id', listObj.id); expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(1); list.destroy(); @@ -92,7 +92,7 @@ describe('List model', () => { const listDup = new List(listObjDuplicate); const issue = new ListIssue({ title: 'Testing', - iid: 1, + iid: _.random(10000), confidential: false, labels: [list.label, listDup.label] }); @@ -102,7 +102,7 @@ describe('List model', () => { spyOn(gl.boardService, 'moveIssue').and.callThrough(); - listDup.updateIssueLabel(list, issue); + listDup.updateIssueLabel(issue, list); expect(gl.boardService.moveIssue) .toHaveBeenCalledWith(issue.id, list.id, listDup.id, undefined, undefined); diff --git a/spec/javascripts/boards/mock_data.js b/spec/javascripts/boards/mock_data.js index 7a399b307ad..a4fa694eebe 100644 --- a/spec/javascripts/boards/mock_data.js +++ b/spec/javascripts/boards/mock_data.js @@ -1,12 +1,12 @@ /* eslint-disable comma-dangle, no-unused-vars, quote-props */ const listObj = { - id: 1, + id: _.random(10000), position: 0, title: 'Test', list_type: 'label', label: { - id: 1, + id: _.random(10000), title: 'Testing', color: 'red', description: 'testing;' @@ -14,12 +14,12 @@ const listObj = { }; const listObjDuplicate = { - id: 2, + id: listObj.id, position: 1, title: 'Test', list_type: 'label', label: { - id: 2, + id: listObj.label.id, title: 'Testing', color: 'red', description: 'testing;' diff --git a/spec/javascripts/cycle_analytics/limit_warning_component_spec.js b/spec/javascripts/cycle_analytics/limit_warning_component_spec.js new file mode 100644 index 00000000000..50000c5a5f5 --- /dev/null +++ b/spec/javascripts/cycle_analytics/limit_warning_component_spec.js @@ -0,0 +1,39 @@ +import Vue from 'vue'; +import limitWarningComp from '~/cycle_analytics/components/limit_warning_component'; + +describe('Limit warning component', () => { + let component; + let LimitWarningComponent; + + beforeEach(() => { + LimitWarningComponent = Vue.extend(limitWarningComp); + }); + + it('should not render if count is not exactly than 50', () => { + component = new LimitWarningComponent({ + propsData: { + count: 5, + }, + }).$mount(); + + expect(component.$el.textContent.trim()).toBe(''); + + component = new LimitWarningComponent({ + propsData: { + count: 55, + }, + }).$mount(); + + expect(component.$el.textContent.trim()).toBe(''); + }); + + it('should render if count is exactly 50', () => { + component = new LimitWarningComponent({ + propsData: { + count: 50, + }, + }).$mount(); + + expect(component.$el.textContent.trim()).toBe('Showing 50 events'); + }); +}); diff --git a/spec/javascripts/fixtures/dashboard.rb b/spec/javascripts/fixtures/dashboard.rb new file mode 100644 index 00000000000..e83db8daaf2 --- /dev/null +++ b/spec/javascripts/fixtures/dashboard.rb @@ -0,0 +1,31 @@ +require 'spec_helper' + +describe Dashboard::ProjectsController, '(JavaScript fixtures)', type: :controller do + include JavaScriptFixturesHelpers + + let(:admin) { create(:admin) } + let(:namespace) { create(:namespace, name: 'frontend-fixtures' )} + let(:project) { create(:project, namespace: namespace, path: 'builds-project') } + + render_views + + before(:all) do + clean_frontend_fixtures('dashboard/') + end + + before(:each) do + sign_in(admin) + end + + it 'dashboard/user-callout.html.raw' do |example| + rendered = render_template('shared/_user_callout') + store_frontend_fixture(rendered, example.description) + end + + private + + def render_template(template_file_name) + controller.prepend_view_path(JavaScriptFixturesHelpers::FIXTURE_PATH) + controller.render_to_string(template_file_name, layout: false) + end +end diff --git a/spec/javascripts/fixtures/user_callout.html.haml b/spec/javascripts/fixtures/user_callout.html.haml deleted file mode 100644 index 275359bde0a..00000000000 --- a/spec/javascripts/fixtures/user_callout.html.haml +++ /dev/null @@ -1,2 +0,0 @@ -.user-callout{ 'callout-svg' => custom_icon('icon_customization') } - diff --git a/spec/javascripts/lib/utils/poll_spec.js b/spec/javascripts/lib/utils/poll_spec.js index c794a632417..e3429c2a1cb 100644 --- a/spec/javascripts/lib/utils/poll_spec.js +++ b/spec/javascripts/lib/utils/poll_spec.js @@ -160,4 +160,44 @@ describe('Poll', () => { Vue.http.interceptors = _.without(Vue.http.interceptors, pollInterceptor); }); }); + + describe('restart', () => { + it('should restart polling when its called', (done) => { + const pollInterceptor = (request, next) => { + next(request.respondWith(JSON.stringify([]), { status: 200, headers: { 'poll-interval': 2 } })); + }; + + Vue.http.interceptors.push(pollInterceptor); + + const service = new ServiceMock('endpoint'); + + spyOn(service, 'fetch').and.callThrough(); + + const Polling = new Poll({ + resource: service, + method: 'fetch', + data: { page: 1 }, + successCallback: () => { + Polling.stop(); + setTimeout(() => { + Polling.restart(); + }, 0); + }, + errorCallback: callbacks.error, + }); + + spyOn(Polling, 'stop').and.callThrough(); + + Polling.makeRequest(); + + setTimeout(() => { + expect(service.fetch.calls.count()).toEqual(2); + expect(service.fetch).toHaveBeenCalledWith({ page: 1 }); + expect(Polling.stop).toHaveBeenCalled(); + done(); + }, 10); + + Vue.http.interceptors = _.without(Vue.http.interceptors, pollInterceptor); + }); + }); }); diff --git a/spec/javascripts/right_sidebar_spec.js b/spec/javascripts/right_sidebar_spec.js index 285b7940174..464b54c62de 100644 --- a/spec/javascripts/right_sidebar_spec.js +++ b/spec/javascripts/right_sidebar_spec.js @@ -78,5 +78,11 @@ import '~/right_sidebar'; expect(todoToggleSpy.calls.count()).toEqual(1); }); + + it('should not hide collapsed icons', () => { + [].forEach.call(document.querySelectorAll('.sidebar-collapsed-icon'), (el) => { + expect(el.querySelector('.fa, svg').classList.contains('hidden')).toBeFalsy(); + }); + }); }); }).call(window); diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js index d658f680f97..b30c5da8822 100644 --- a/spec/javascripts/test_bundle.js +++ b/spec/javascripts/test_bundle.js @@ -37,14 +37,33 @@ if (process.env.BABEL_ENV === 'coverage') { const troubleMakers = [ './blob_edit/blob_bundle.js', './boards/boards_bundle.js', + './cycle_analytics/cycle_analytics_bundle.js', './cycle_analytics/components/stage_plan_component.js', './cycle_analytics/components/stage_staging_component.js', './cycle_analytics/components/stage_test_component.js', + './commit/pipelines/pipelines_bundle.js', + './diff_notes/diff_notes_bundle.js', './diff_notes/components/jump_to_discussion.js', './diff_notes/components/resolve_count.js', + './dispatcher.js', + './environments/environments_bundle.js', + './filtered_search/filtered_search_bundle.js', + './graphs/graphs_bundle.js', + './issuable/issuable_bundle.js', + './issuable/time_tracking/time_tracking_bundle.js', + './main.js', + './merge_conflicts/merge_conflicts_bundle.js', './merge_conflicts/components/inline_conflict_lines.js', './merge_conflicts/components/parallel_conflict_lines.js', + './merge_request_widget/ci_bundle.js', + './monitoring/monitoring_bundle.js', + './network/network_bundle.js', './network/branch_graph.js', + './profile/profile_bundle.js', + './protected_branches/protected_branches_bundle.js', + './snippet/snippet_bundle.js', + './terminal/terminal_bundle.js', + './users/users_bundle.js', ]; describe('Uncovered files', function () { diff --git a/spec/javascripts/user_callout_spec.js b/spec/javascripts/user_callout_spec.js index 2398149d3ad..c0375ebc61c 100644 --- a/spec/javascripts/user_callout_spec.js +++ b/spec/javascripts/user_callout_spec.js @@ -4,7 +4,7 @@ import UserCallout from '~/user_callout'; const USER_CALLOUT_COOKIE = 'user_callout_dismissed'; describe('UserCallout', function () { - const fixtureName = 'static/user_callout.html.raw'; + const fixtureName = 'dashboard/user-callout.html.raw'; preloadFixtures(fixtureName); beforeEach(() => { @@ -12,26 +12,22 @@ describe('UserCallout', function () { Cookies.remove(USER_CALLOUT_COOKIE); this.userCallout = new UserCallout(); - this.closeButton = $('.close-user-callout'); - this.userCalloutBtn = $('.user-callout-btn'); + this.closeButton = $('.js-close-callout.close'); + this.userCalloutBtn = $('.js-close-callout:not(.close)'); this.userCalloutContainer = $('.user-callout'); }); - it('does not show when cookie is set not defined', () => { - expect(Cookies.get(USER_CALLOUT_COOKIE)).toBeUndefined(); - expect(this.userCalloutContainer.is(':visible')).toBe(true); - }); - - it('shows when cookie is set to false', () => { - Cookies.set(USER_CALLOUT_COOKIE, 'false'); - - expect(Cookies.get(USER_CALLOUT_COOKIE)).toBeDefined(); - expect(this.userCalloutContainer.is(':visible')).toBe(true); - }); - - it('hides when user clicks on the dismiss-icon', () => { + it('hides when user clicks on the dismiss-icon', (done) => { this.closeButton.click(); expect(Cookies.get(USER_CALLOUT_COOKIE)).toBe('true'); + + setTimeout(() => { + expect( + document.querySelector('.user-callout'), + ).toBeNull(); + + done(); + }); }); it('hides when user clicks on the "check it out" button', () => { @@ -39,19 +35,3 @@ describe('UserCallout', function () { expect(Cookies.get(USER_CALLOUT_COOKIE)).toBe('true'); }); }); - -describe('UserCallout when cookie is present', function () { - const fixtureName = 'static/user_callout.html.raw'; - preloadFixtures(fixtureName); - - beforeEach(() => { - loadFixtures(fixtureName); - Cookies.set(USER_CALLOUT_COOKIE, 'true'); - this.userCallout = new UserCallout(); - this.userCalloutContainer = $('.user-callout'); - }); - - it('removes the DOM element', () => { - expect(this.userCalloutContainer.length).toBe(0); - }); -}); diff --git a/spec/lib/banzai/filter/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/issue_reference_filter_spec.rb index 11607d4fb26..f1082495fcc 100644 --- a/spec/lib/banzai/filter/issue_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/issue_reference_filter_spec.rb @@ -21,6 +21,19 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do end end + describe 'performance' do + let(:another_issue) { create(:issue, project: project) } + + it 'does not have a N+1 query problem' do + single_reference = "Issue #{issue.to_reference}" + multiple_references = "Issues #{issue.to_reference} and #{another_issue.to_reference}" + + control_count = ActiveRecord::QueryRecorder.new { reference_filter(single_reference).to_html }.count + + expect { reference_filter(multiple_references).to_html }.not_to exceed_query_limit(control_count) + end + end + context 'internal reference' do it_behaves_like 'a reference containing an element node' diff --git a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb index 3d3d36061f4..40232f6e426 100644 --- a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb @@ -17,6 +17,19 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do end end + describe 'performance' do + let(:another_merge) { create(:merge_request, source_project: project, source_branch: 'fix') } + + it 'does not have a N+1 query problem' do + single_reference = "Merge request #{merge.to_reference}" + multiple_references = "Merge requests #{merge.to_reference} and #{another_merge.to_reference}" + + control_count = ActiveRecord::QueryRecorder.new { reference_filter(single_reference).to_html }.count + + expect { reference_filter(multiple_references).to_html }.not_to exceed_query_limit(control_count) + end + end + context 'internal reference' do let(:reference) { merge.to_reference } diff --git a/spec/lib/gitlab/ci/status/build/factory_spec.rb b/spec/lib/gitlab/ci/status/build/factory_spec.rb index 8b3bd08cf13..e648a3ac3a2 100644 --- a/spec/lib/gitlab/ci/status/build/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/build/factory_spec.rb @@ -27,6 +27,7 @@ describe Gitlab::Ci::Status::Build::Factory do it 'fabricates status with correct details' do expect(status.text).to eq 'passed' expect(status.icon).to eq 'icon_status_success' + expect(status.favicon).to eq 'favicon_status_success' expect(status.label).to eq 'passed' expect(status).to have_details expect(status).to have_action @@ -53,6 +54,7 @@ describe Gitlab::Ci::Status::Build::Factory do it 'fabricates status with correct details' do expect(status.text).to eq 'failed' expect(status.icon).to eq 'icon_status_failed' + expect(status.favicon).to eq 'favicon_status_failed' expect(status.label).to eq 'failed' expect(status).to have_details expect(status).to have_action @@ -79,6 +81,7 @@ describe Gitlab::Ci::Status::Build::Factory do it 'fabricates status with correct details' do expect(status.text).to eq 'failed' expect(status.icon).to eq 'icon_status_warning' + expect(status.favicon).to eq 'favicon_status_failed' expect(status.label).to eq 'failed (allowed to fail)' expect(status).to have_details expect(status).to have_action @@ -107,6 +110,7 @@ describe Gitlab::Ci::Status::Build::Factory do it 'fabricates status with correct details' do expect(status.text).to eq 'canceled' expect(status.icon).to eq 'icon_status_canceled' + expect(status.favicon).to eq 'favicon_status_canceled' expect(status.label).to eq 'canceled' expect(status).to have_details expect(status).to have_action @@ -132,6 +136,7 @@ describe Gitlab::Ci::Status::Build::Factory do it 'fabricates status with correct details' do expect(status.text).to eq 'running' expect(status.icon).to eq 'icon_status_running' + expect(status.favicon).to eq 'favicon_status_running' expect(status.label).to eq 'running' expect(status).to have_details expect(status).to have_action @@ -157,6 +162,7 @@ describe Gitlab::Ci::Status::Build::Factory do it 'fabricates status with correct details' do expect(status.text).to eq 'pending' expect(status.icon).to eq 'icon_status_pending' + expect(status.favicon).to eq 'favicon_status_pending' expect(status.label).to eq 'pending' expect(status).to have_details expect(status).to have_action @@ -181,6 +187,7 @@ describe Gitlab::Ci::Status::Build::Factory do it 'fabricates status with correct details' do expect(status.text).to eq 'skipped' expect(status.icon).to eq 'icon_status_skipped' + expect(status.favicon).to eq 'favicon_status_skipped' expect(status.label).to eq 'skipped' expect(status).to have_details expect(status).not_to have_action @@ -208,6 +215,7 @@ describe Gitlab::Ci::Status::Build::Factory do expect(status.text).to eq 'manual' expect(status.group).to eq 'manual' expect(status.icon).to eq 'icon_status_manual' + expect(status.favicon).to eq 'favicon_status_manual' expect(status.label).to eq 'manual play action' expect(status).to have_details expect(status).to have_action @@ -235,6 +243,7 @@ describe Gitlab::Ci::Status::Build::Factory do expect(status.text).to eq 'manual' expect(status.group).to eq 'manual' expect(status.icon).to eq 'icon_status_manual' + expect(status.favicon).to eq 'favicon_status_manual' expect(status.label).to eq 'manual stop action' expect(status).to have_details expect(status).to have_action diff --git a/spec/lib/gitlab/ci/status/canceled_spec.rb b/spec/lib/gitlab/ci/status/canceled_spec.rb index 768f8926f1d..530639a5897 100644 --- a/spec/lib/gitlab/ci/status/canceled_spec.rb +++ b/spec/lib/gitlab/ci/status/canceled_spec.rb @@ -17,6 +17,10 @@ describe Gitlab::Ci::Status::Canceled do it { expect(subject.icon).to eq 'icon_status_canceled' } end + describe '#favicon' do + it { expect(subject.favicon).to eq 'favicon_status_canceled' } + end + describe '#group' do it { expect(subject.group).to eq 'canceled' } end diff --git a/spec/lib/gitlab/ci/status/created_spec.rb b/spec/lib/gitlab/ci/status/created_spec.rb index e96c13aede3..aef982e17f1 100644 --- a/spec/lib/gitlab/ci/status/created_spec.rb +++ b/spec/lib/gitlab/ci/status/created_spec.rb @@ -17,6 +17,10 @@ describe Gitlab::Ci::Status::Created do it { expect(subject.icon).to eq 'icon_status_created' } end + describe '#favicon' do + it { expect(subject.favicon).to eq 'favicon_status_created' } + end + describe '#group' do it { expect(subject.group).to eq 'created' } end diff --git a/spec/lib/gitlab/ci/status/failed_spec.rb b/spec/lib/gitlab/ci/status/failed_spec.rb index e5da0a91159..9a25743885c 100644 --- a/spec/lib/gitlab/ci/status/failed_spec.rb +++ b/spec/lib/gitlab/ci/status/failed_spec.rb @@ -17,6 +17,10 @@ describe Gitlab::Ci::Status::Failed do it { expect(subject.icon).to eq 'icon_status_failed' } end + describe '#favicon' do + it { expect(subject.favicon).to eq 'favicon_status_failed' } + end + describe '#group' do it { expect(subject.group).to eq 'failed' } end diff --git a/spec/lib/gitlab/ci/status/manual_spec.rb b/spec/lib/gitlab/ci/status/manual_spec.rb index 3fd3727b92d..6fdc3801d71 100644 --- a/spec/lib/gitlab/ci/status/manual_spec.rb +++ b/spec/lib/gitlab/ci/status/manual_spec.rb @@ -17,6 +17,10 @@ describe Gitlab::Ci::Status::Manual do it { expect(subject.icon).to eq 'icon_status_manual' } end + describe '#favicon' do + it { expect(subject.favicon).to eq 'favicon_status_manual' } + end + describe '#group' do it { expect(subject.group).to eq 'manual' } end diff --git a/spec/lib/gitlab/ci/status/pending_spec.rb b/spec/lib/gitlab/ci/status/pending_spec.rb index 8d09cf2a05a..ffc53f0506b 100644 --- a/spec/lib/gitlab/ci/status/pending_spec.rb +++ b/spec/lib/gitlab/ci/status/pending_spec.rb @@ -17,6 +17,10 @@ describe Gitlab::Ci::Status::Pending do it { expect(subject.icon).to eq 'icon_status_pending' } end + describe '#favicon' do + it { expect(subject.favicon).to eq 'favicon_status_pending' } + end + describe '#group' do it { expect(subject.group).to eq 'pending' } end diff --git a/spec/lib/gitlab/ci/status/running_spec.rb b/spec/lib/gitlab/ci/status/running_spec.rb index 10d3bf749c1..0babf1fb54e 100644 --- a/spec/lib/gitlab/ci/status/running_spec.rb +++ b/spec/lib/gitlab/ci/status/running_spec.rb @@ -17,6 +17,10 @@ describe Gitlab::Ci::Status::Running do it { expect(subject.icon).to eq 'icon_status_running' } end + describe '#favicon' do + it { expect(subject.favicon).to eq 'favicon_status_running' } + end + describe '#group' do it { expect(subject.group).to eq 'running' } end diff --git a/spec/lib/gitlab/ci/status/skipped_spec.rb b/spec/lib/gitlab/ci/status/skipped_spec.rb index 10db93d3802..670747c9f0b 100644 --- a/spec/lib/gitlab/ci/status/skipped_spec.rb +++ b/spec/lib/gitlab/ci/status/skipped_spec.rb @@ -17,6 +17,10 @@ describe Gitlab::Ci::Status::Skipped do it { expect(subject.icon).to eq 'icon_status_skipped' } end + describe '#favicon' do + it { expect(subject.favicon).to eq 'favicon_status_skipped' } + end + describe '#group' do it { expect(subject.group).to eq 'skipped' } end diff --git a/spec/lib/gitlab/ci/status/success_spec.rb b/spec/lib/gitlab/ci/status/success_spec.rb index 230f24b94a4..ff65b074808 100644 --- a/spec/lib/gitlab/ci/status/success_spec.rb +++ b/spec/lib/gitlab/ci/status/success_spec.rb @@ -17,6 +17,10 @@ describe Gitlab::Ci::Status::Success do it { expect(subject.icon).to eq 'icon_status_success' } end + describe '#favicon' do + it { expect(subject.favicon).to eq 'favicon_status_success' } + end + describe '#group' do it { expect(subject.group).to eq 'success' } end diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb index e8caad00c44..8acec805584 100644 --- a/spec/models/hooks/system_hook_spec.rb +++ b/spec/models/hooks/system_hook_spec.rb @@ -6,6 +6,9 @@ describe SystemHook, models: true do let(:user) { create(:user) } let(:project) { create(:empty_project, namespace: user.namespace) } let(:group) { create(:group) } + let(:params) do + { name: 'John Doe', username: 'jduser', email: 'jg@example.com', password: 'mydummypass' } + end before do WebMock.stub_request(:post, system_hook.url) @@ -29,7 +32,7 @@ describe SystemHook, models: true do end it "user_create hook" do - create(:user) + Users::CreateService.new(nil, params).execute expect(WebMock).to have_requested(:post, system_hook.url).with( body: /user_create/, diff --git a/spec/models/list_spec.rb b/spec/models/list_spec.rb index e6ca4853873..db2c2619968 100644 --- a/spec/models/list_spec.rb +++ b/spec/models/list_spec.rb @@ -19,8 +19,8 @@ describe List do expect(subject).to validate_uniqueness_of(:label_id).scoped_to(:board_id) end - context 'when list_type is set to done' do - subject { described_class.new(list_type: :done) } + context 'when list_type is set to closed' do + subject { described_class.new(list_type: :closed) } it { is_expected.not_to validate_presence_of(:label) } it { is_expected.not_to validate_presence_of(:position) } @@ -34,8 +34,8 @@ describe List do expect(subject.destroy).to be_truthy end - it 'can not be destroyed when when list_type is set to done' do - subject = create(:done_list) + it 'can not be destroyed when when list_type is set to closed' do + subject = create(:closed_list) expect(subject.destroy).to be_falsey end @@ -48,8 +48,8 @@ describe List do expect(subject).to be_destroyable end - it 'returns false when list_type is set to done' do - subject.list_type = :done + it 'returns false when list_type is set to closed' do + subject.list_type = :closed expect(subject).not_to be_destroyable end @@ -62,8 +62,8 @@ describe List do expect(subject).to be_movable end - it 'returns false when list_type is set to done' do - subject.list_type = :done + it 'returns false when list_type is set to closed' do + subject.list_type = :closed expect(subject).not_to be_movable end @@ -77,10 +77,10 @@ describe List do expect(subject.title).to eq 'Development' end - it 'returns Done when list_type is set to done' do - subject.list_type = :done + it 'returns Closed when list_type is set to closed' do + subject.list_type = :closed - expect(subject.title).to eq 'Done' + expect(subject.title).to eq 'Closed' end end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 570abd44dc6..a9e37be1157 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -361,22 +361,10 @@ describe User, models: true do end describe '#generate_password' do - it "executes callback when force_random_password specified" do - user = build(:user, force_random_password: true) - expect(user).to receive(:generate_password) - user.save - end - it "does not generate password by default" do user = create(:user, password: 'abcdefghe') expect(user.password).to eq('abcdefghe') end - - it "generates password when forcing random password" do - allow(Devise).to receive(:friendly_token).and_return('123456789') - user = create(:user, password: 'abcdefg', force_random_password: true) - expect(user.password).to eq('12345678') - end end describe 'authentication token' do diff --git a/spec/requests/api/v3/users_spec.rb b/spec/requests/api/v3/users_spec.rb index 17bbb0b53c1..b38cbe74b85 100644 --- a/spec/requests/api/v3/users_spec.rb +++ b/spec/requests/api/v3/users_spec.rb @@ -263,4 +263,18 @@ describe API::V3::Users, api: true do expect(json_response['message']).to eq('404 User Not Found') end end + + describe 'POST /users' do + it 'creates confirmed user when confirm parameter is false' do + optional_attributes = { confirm: false } + attributes = attributes_for(:user).merge(optional_attributes) + + post v3_api('/users', admin), attributes + + user_id = json_response['id'] + new_user = User.find(user_id) + + expect(new_user).to be_confirmed + end + end end diff --git a/spec/routing/environments_spec.rb b/spec/routing/environments_spec.rb new file mode 100644 index 00000000000..ba124de70bb --- /dev/null +++ b/spec/routing/environments_spec.rb @@ -0,0 +1,49 @@ +require 'spec_helper' + +describe Projects::EnvironmentsController, :routing do + let(:project) { create(:empty_project) } + + let(:environment) do + create(:environment, project: project, + name: 'staging-1.0/review') + end + + let(:environments_route) do + "#{project.namespace.name}/#{project.name}/environments/" + end + + describe 'routing environment folders' do + context 'when using JSON format' do + it 'correctly matches environment name and JSON format' do + expect(get_folder('staging-1.0.json')) + .to route_to(*folder_action(id: 'staging-1.0', format: 'json')) + end + end + + context 'when using HTML format' do + it 'correctly matches environment name and HTML format' do + expect(get_folder('staging-1.0.html')) + .to route_to(*folder_action(id: 'staging-1.0', format: 'html')) + end + end + + context 'when using implicit format' do + it 'correctly matches environment name' do + expect(get_folder('staging-1.0')) + .to route_to(*folder_action(id: 'staging-1.0')) + end + end + end + + def get_folder(folder) + get("#{project.namespace.name}/#{project.name}/" \ + "environments/folders/#{folder}") + end + + def folder_action(**opts) + options = { namespace_id: project.namespace.name, + project_id: project.name } + + ['projects/environments#folder', options.merge(opts)] + end +end diff --git a/spec/serializers/build_entity_spec.rb b/spec/serializers/build_entity_spec.rb index 60c9642ee2c..7dcdf54fd93 100644 --- a/spec/serializers/build_entity_spec.rb +++ b/spec/serializers/build_entity_spec.rb @@ -1,10 +1,16 @@ require 'spec_helper' describe BuildEntity do + let(:user) { create(:user) } let(:build) { create(:ci_build) } + let(:request) { double('request') } + + before do + allow(request).to receive(:user).and_return(user) + end let(:entity) do - described_class.new(build, request: double) + described_class.new(build, request: request) end subject { entity.as_json } @@ -22,6 +28,11 @@ describe BuildEntity do expect(subject).to include(:created_at, :updated_at) end + it 'contains details' do + expect(subject).to include :status + expect(subject[:status]).to include :icon, :favicon, :text, :label + end + context 'when build is a regular job' do it 'does not contain path to play action' do expect(subject).not_to include(:play_path) diff --git a/spec/serializers/build_serializer_spec.rb b/spec/serializers/build_serializer_spec.rb new file mode 100644 index 00000000000..3cc791bca50 --- /dev/null +++ b/spec/serializers/build_serializer_spec.rb @@ -0,0 +1,45 @@ +require 'spec_helper' + +describe BuildSerializer do + let(:user) { create(:user) } + + let(:serializer) do + described_class.new(user: user) + end + + subject { serializer.represent(resource) } + + describe '#represent' do + context 'when a single object is being serialized' do + let(:resource) { create(:ci_build) } + + it 'serializers the pipeline object' do + expect(subject[:id]).to eq resource.id + end + end + + context 'when multiple objects are being serialized' do + let(:resource) { create_list(:ci_build, 2) } + + it 'serializers the array of pipelines' do + expect(subject).not_to be_empty + end + end + end + + describe '#represent_status' do + context 'when represents only status' do + let(:resource) { create(:ci_build) } + let(:status) { resource.detailed_status(double('user')) } + + subject { serializer.represent_status(resource) } + + it 'serializes only status' do + expect(subject[:text]).to eq(status.text) + expect(subject[:label]).to eq(status.label) + expect(subject[:icon]).to eq(status.icon) + expect(subject[:favicon]).to eq(status.favicon) + end + end + end +end diff --git a/spec/serializers/deployment_entity_spec.rb b/spec/serializers/deployment_entity_spec.rb index ea87771e2a2..95eca5463eb 100644 --- a/spec/serializers/deployment_entity_spec.rb +++ b/spec/serializers/deployment_entity_spec.rb @@ -1,8 +1,15 @@ require 'spec_helper' describe DeploymentEntity do + let(:user) { create(:user) } + let(:request) { double('request') } + + before do + allow(request).to receive(:user).and_return(user) + end + let(:entity) do - described_class.new(deployment, request: double) + described_class.new(deployment, request: request) end let(:deployment) { create(:deployment) } diff --git a/spec/serializers/pipeline_entity_spec.rb b/spec/serializers/pipeline_entity_spec.rb index ccb72973f9c..93d5a21419d 100644 --- a/spec/serializers/pipeline_entity_spec.rb +++ b/spec/serializers/pipeline_entity_spec.rb @@ -30,7 +30,7 @@ describe PipelineEntity do .to include :duration, :finished_at expect(subject[:details]) .to include :stages, :artifacts, :manual_actions - expect(subject[:details][:status]).to include :icon, :text, :label + expect(subject[:details][:status]).to include :icon, :favicon, :text, :label end it 'contains flags' do diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb index 2aaef03cb93..8642b803844 100644 --- a/spec/serializers/pipeline_serializer_spec.rb +++ b/spec/serializers/pipeline_serializer_spec.rb @@ -94,4 +94,20 @@ describe PipelineSerializer do end end end + + describe '#represent_status' do + context 'when represents only status' do + let(:resource) { create(:ci_pipeline) } + let(:status) { resource.detailed_status(double('user')) } + + subject { serializer.represent_status(resource) } + + it 'serializes only status' do + expect(subject[:text]).to eq(status.text) + expect(subject[:label]).to eq(status.label) + expect(subject[:icon]).to eq(status.icon) + expect(subject[:favicon]).to eq(status.favicon) + end + end + end end diff --git a/spec/serializers/status_entity_spec.rb b/spec/serializers/status_entity_spec.rb index 89428b4216e..c94902dbab8 100644 --- a/spec/serializers/status_entity_spec.rb +++ b/spec/serializers/status_entity_spec.rb @@ -16,7 +16,7 @@ describe StatusEntity do subject { entity.as_json } it 'contains status details' do - expect(subject).to include :text, :icon, :label, :group + expect(subject).to include :text, :icon, :favicon, :label, :group expect(subject).to include :has_details, :details_path end end diff --git a/spec/services/after_branch_delete_service_spec.rb b/spec/services/after_branch_delete_service_spec.rb index d29e0addb53..77ca17bc82c 100644 --- a/spec/services/after_branch_delete_service_spec.rb +++ b/spec/services/after_branch_delete_service_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe AfterBranchDeleteService, services: true do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } let(:service) { described_class.new(project, user) } diff --git a/spec/services/boards/create_service_spec.rb b/spec/services/boards/create_service_spec.rb index 7b29b043296..a8555f5b4a0 100644 --- a/spec/services/boards/create_service_spec.rb +++ b/spec/services/boards/create_service_spec.rb @@ -15,7 +15,7 @@ describe Boards::CreateService, services: true do board = service.execute expect(board.lists.size).to eq 1 - expect(board.lists.first).to be_done + expect(board.lists.first).to be_closed end end diff --git a/spec/services/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb index d841bdaa292..c982031c791 100644 --- a/spec/services/boards/issues/list_service_spec.rb +++ b/spec/services/boards/issues/list_service_spec.rb @@ -15,7 +15,7 @@ describe Boards::Issues::ListService, services: true do let!(:list1) { create(:list, board: board, label: development, position: 0) } let!(:list2) { create(:list, board: board, label: testing, position: 1) } - let!(:done) { create(:done_list, board: board) } + let!(:closed) { create(:closed_list, board: board) } let!(:opened_issue1) { create(:labeled_issue, project: project, labels: [bug]) } let!(:opened_issue2) { create(:labeled_issue, project: project, labels: [p2]) } @@ -53,8 +53,8 @@ describe Boards::Issues::ListService, services: true do expect(issues).to eq [opened_issue2, reopened_issue1, opened_issue1] end - it 'returns closed issues when listing issues from Done' do - params = { board_id: board.id, id: done.id } + it 'returns closed issues when listing issues from Closed' do + params = { board_id: board.id, id: closed.id } issues = described_class.new(project, user, params).execute diff --git a/spec/services/boards/issues/move_service_spec.rb b/spec/services/boards/issues/move_service_spec.rb index 727ea04ea5c..4ff7ac6bb2f 100644 --- a/spec/services/boards/issues/move_service_spec.rb +++ b/spec/services/boards/issues/move_service_spec.rb @@ -12,7 +12,7 @@ describe Boards::Issues::MoveService, services: true do let!(:list1) { create(:list, board: board1, label: development, position: 0) } let!(:list2) { create(:list, board: board1, label: testing, position: 1) } - let!(:done) { create(:done_list, board: board1) } + let!(:closed) { create(:closed_list, board: board1) } before do project.team << [user, :developer] @@ -35,13 +35,13 @@ describe Boards::Issues::MoveService, services: true do end end - context 'when moving to done' do + context 'when moving to closed' do let(:board2) { create(:board, project: project) } let(:regression) { create(:label, project: project, name: 'Regression') } let!(:list3) { create(:list, board: board2, label: regression, position: 1) } let(:issue) { create(:labeled_issue, project: project, labels: [bug, development, testing, regression]) } - let(:params) { { board_id: board1.id, from_list_id: list2.id, to_list_id: done.id } } + let(:params) { { board_id: board1.id, from_list_id: list2.id, to_list_id: closed.id } } it 'delegates the close proceedings to Issues::CloseService' do expect_any_instance_of(Issues::CloseService).to receive(:execute).with(issue).once @@ -58,9 +58,9 @@ describe Boards::Issues::MoveService, services: true do end end - context 'when moving from done' do + context 'when moving from closed' do let(:issue) { create(:labeled_issue, :closed, project: project, labels: [bug]) } - let(:params) { { board_id: board1.id, from_list_id: done.id, to_list_id: list2.id } } + let(:params) { { board_id: board1.id, from_list_id: closed.id, to_list_id: list2.id } } it 'delegates the re-open proceedings to Issues::ReopenService' do expect_any_instance_of(Issues::ReopenService).to receive(:execute).with(issue).once diff --git a/spec/services/boards/lists/destroy_service_spec.rb b/spec/services/boards/lists/destroy_service_spec.rb index a30860f828a..af2d7c784bb 100644 --- a/spec/services/boards/lists/destroy_service_spec.rb +++ b/spec/services/boards/lists/destroy_service_spec.rb @@ -18,18 +18,18 @@ describe Boards::Lists::DestroyService, services: true do development = create(:list, board: board, position: 0) review = create(:list, board: board, position: 1) staging = create(:list, board: board, position: 2) - done = board.done_list + closed = board.closed_list described_class.new(project, user).execute(development) expect(review.reload.position).to eq 0 expect(staging.reload.position).to eq 1 - expect(done.reload.position).to be_nil + expect(closed.reload.position).to be_nil end end - it 'does not remove list from board when list type is done' do - list = board.done_list + it 'does not remove list from board when list type is closed' do + list = board.closed_list service = described_class.new(project, user) expect { service.execute(list) }.not_to change(board.lists, :count) diff --git a/spec/services/boards/lists/list_service_spec.rb b/spec/services/boards/lists/list_service_spec.rb index 2dffc62b215..ab9fb1bc914 100644 --- a/spec/services/boards/lists/list_service_spec.rb +++ b/spec/services/boards/lists/list_service_spec.rb @@ -10,7 +10,7 @@ describe Boards::Lists::ListService, services: true do service = described_class.new(project, double) - expect(service.execute(board)).to eq [list, board.done_list] + expect(service.execute(board)).to eq [list, board.closed_list] end end end diff --git a/spec/services/boards/lists/move_service_spec.rb b/spec/services/boards/lists/move_service_spec.rb index 3786dc82bf0..4b3bdd133f2 100644 --- a/spec/services/boards/lists/move_service_spec.rb +++ b/spec/services/boards/lists/move_service_spec.rb @@ -10,7 +10,7 @@ describe Boards::Lists::MoveService, services: true do let!(:development) { create(:list, board: board, position: 1) } let!(:review) { create(:list, board: board, position: 2) } let!(:staging) { create(:list, board: board, position: 3) } - let!(:done) { create(:done_list, board: board) } + let!(:closed) { create(:closed_list, board: board) } context 'when list type is set to label' do it 'keeps position of lists when new position is nil' do @@ -86,10 +86,10 @@ describe Boards::Lists::MoveService, services: true do end end - it 'keeps position of lists when list type is done' do + it 'keeps position of lists when list type is closed' do service = described_class.new(project, user, position: 2) - service.execute(done) + service.execute(closed) expect(current_list_positions).to eq [0, 1, 2, 3] end diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb index a969829a63e..d2f0337c260 100644 --- a/spec/services/ci/create_pipeline_service_spec.rb +++ b/spec/services/ci/create_pipeline_service_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Ci::CreatePipelineService, services: true do - let(:project) { FactoryGirl.create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:admin) } before do diff --git a/spec/services/ci/create_trigger_request_service_spec.rb b/spec/services/ci/create_trigger_request_service_spec.rb index 5e68343784d..5a20102872a 100644 --- a/spec/services/ci/create_trigger_request_service_spec.rb +++ b/spec/services/ci/create_trigger_request_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Ci::CreateTriggerRequestService, services: true do let(:service) { described_class.new } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:trigger) { create(:ci_trigger, project: project) } before do diff --git a/spec/services/ci/retry_pipeline_service_spec.rb b/spec/services/ci/retry_pipeline_service_spec.rb index 5445b65f4e8..f1b2d3a4798 100644 --- a/spec/services/ci/retry_pipeline_service_spec.rb +++ b/spec/services/ci/retry_pipeline_service_spec.rb @@ -9,6 +9,19 @@ describe Ci::RetryPipelineService, '#execute', :services do context 'when user has ability to modify pipeline' do let(:user) { create(:admin) } + context 'when there are already retried jobs present' do + before do + create_build('rspec', :canceled, 0) + create_build('rspec', :failed, 0) + end + + it 'does not retry jobs that has already been retried' do + expect(statuses.first).to be_retried + expect { service.execute(pipeline) } + .to change { CommitStatus.count }.by(1) + end + end + context 'when there are failed builds in the last stage' do before do create_build('rspec 1', :success, 0) diff --git a/spec/services/ci/stop_environments_service_spec.rb b/spec/services/ci/stop_environments_service_spec.rb index 560f83d94f7..32c72a9cf5e 100644 --- a/spec/services/ci/stop_environments_service_spec.rb +++ b/spec/services/ci/stop_environments_service_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Ci::StopEnvironmentsService, services: true do - let(:project) { create(:project, :private) } + let(:project) { create(:project, :private, :repository) } let(:user) { create(:user) } let(:service) { described_class.new(project, user) } diff --git a/spec/services/ci/update_build_queue_service_spec.rb b/spec/services/ci/update_build_queue_service_spec.rb index f01a388b895..c44e6b2a48b 100644 --- a/spec/services/ci/update_build_queue_service_spec.rb +++ b/spec/services/ci/update_build_queue_service_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Ci::UpdateBuildQueueService, :services do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:build) { create(:ci_build, pipeline: pipeline) } let(:pipeline) { create(:ci_pipeline, project: project) } diff --git a/spec/services/compare_service_spec.rb b/spec/services/compare_service_spec.rb index 0a7fc58523f..bea7c965233 100644 --- a/spec/services/compare_service_spec.rb +++ b/spec/services/compare_service_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe CompareService, services: true do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } let(:service) { described_class.new(project, 'feature') } diff --git a/spec/services/create_release_service_spec.rb b/spec/services/create_release_service_spec.rb index 61e5ae72f51..271ccfe7968 100644 --- a/spec/services/create_release_service_spec.rb +++ b/spec/services/create_release_service_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe CreateReleaseService, services: true do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } let(:tag_name) { project.repository.tag_names.first } let(:description) { 'Awesome release!' } diff --git a/spec/services/delete_branch_service_spec.rb b/spec/services/delete_branch_service_spec.rb index 336f5dafb5b..c4685c9aa31 100644 --- a/spec/services/delete_branch_service_spec.rb +++ b/spec/services/delete_branch_service_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe DeleteBranchService, services: true do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:repository) { project.repository } let(:user) { create(:user) } let(:service) { described_class.new(project, user) } diff --git a/spec/services/delete_merged_branches_service_spec.rb b/spec/services/delete_merged_branches_service_spec.rb index 181488e89c7..a41a421fa6e 100644 --- a/spec/services/delete_merged_branches_service_spec.rb +++ b/spec/services/delete_merged_branches_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe DeleteMergedBranchesService, services: true do subject(:service) { described_class.new(project, project.owner) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } context '#execute' do context 'unprotected branches' do diff --git a/spec/services/files/update_service_spec.rb b/spec/services/files/update_service_spec.rb index 35e6e139238..26aa5b432d4 100644 --- a/spec/services/files/update_service_spec.rb +++ b/spec/services/files/update_service_spec.rb @@ -3,7 +3,7 @@ require "spec_helper" describe Files::UpdateService do subject { described_class.new(project, user, commit_params) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } let(:file_path) { 'files/ruby/popen.rb' } let(:new_contents) { 'New Content' } diff --git a/spec/services/git_hooks_service_spec.rb b/spec/services/git_hooks_service_spec.rb index 3318dfb22b6..ac7ccfbaab0 100644 --- a/spec/services/git_hooks_service_spec.rb +++ b/spec/services/git_hooks_service_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' describe GitHooksService, services: true do include RepoHelpers - let(:user) { create :user } - let(:project) { create :project } + let(:user) { create(:user) } + let(:project) { create(:project, :repository) } let(:service) { GitHooksService.new } before do diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index bd71618e6f4..0477cac6677 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' describe GitPushService, services: true do include RepoHelpers - let(:user) { create :user } - let(:project) { create :project } + let(:user) { create(:user) } + let(:project) { create(:project, :repository) } before do project.team << [user, :master] diff --git a/spec/services/git_tag_push_service_spec.rb b/spec/services/git_tag_push_service_spec.rb index bd074b9bd71..b73beb3f6fc 100644 --- a/spec/services/git_tag_push_service_spec.rb +++ b/spec/services/git_tag_push_service_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' describe GitTagPushService, services: true do include RepoHelpers - let(:user) { create :user } - let(:project) { create :project } + let(:user) { create(:user) } + let(:project) { create(:project, :repository) } let(:service) { GitTagPushService.new(project, user, oldrev: oldrev, newrev: newrev, ref: ref) } let(:oldrev) { Gitlab::Git::BLANK_SHA } diff --git a/spec/services/groups/create_service_spec.rb b/spec/services/groups/create_service_spec.rb index ec89b540e6a..bcb62429275 100644 --- a/spec/services/groups/create_service_spec.rb +++ b/spec/services/groups/create_service_spec.rb @@ -44,7 +44,7 @@ describe Groups::CreateService, '#execute', services: true do let!(:service) { described_class.new(user, params) } before do - Settings.mattermost['enabled'] = true + stub_mattermost_setting(enabled: true) end it 'create the chat team with the group' do diff --git a/spec/services/groups/destroy_service_spec.rb b/spec/services/groups/destroy_service_spec.rb index 98c560ffb26..2ee11fc8b4c 100644 --- a/spec/services/groups/destroy_service_spec.rb +++ b/spec/services/groups/destroy_service_spec.rb @@ -6,7 +6,7 @@ describe Groups::DestroyService, services: true do let!(:user) { create(:user) } let!(:group) { create(:group) } let!(:nested_group) { create(:group, parent: group) } - let!(:project) { create(:project, namespace: group) } + let!(:project) { create(:empty_project, namespace: group) } let!(:gitlab_shell) { Gitlab::Shell.new } let!(:remove_path) { group.path + "+#{group.id}+deleted" } diff --git a/spec/services/groups/update_service_spec.rb b/spec/services/groups/update_service_spec.rb index 7c0fccb9d41..91ec224d1c4 100644 --- a/spec/services/groups/update_service_spec.rb +++ b/spec/services/groups/update_service_spec.rb @@ -13,7 +13,7 @@ describe Groups::UpdateService, services: true do before do public_group.add_user(user, Gitlab::Access::MASTER) - create(:project, :public, group: public_group) + create(:empty_project, :public, group: public_group) end it "does not change permission level" do @@ -27,7 +27,7 @@ describe Groups::UpdateService, services: true do before do internal_group.add_user(user, Gitlab::Access::MASTER) - create(:project, :internal, group: internal_group) + create(:empty_project, :internal, group: internal_group) end it "does not change permission level" do @@ -55,7 +55,7 @@ describe Groups::UpdateService, services: true do before do internal_group.add_user(user, Gitlab::Access::MASTER) - create(:project, :internal, group: internal_group) + create(:empty_project, :internal, group: internal_group) end it 'returns true' do diff --git a/spec/services/issues/build_service_spec.rb b/spec/services/issues/build_service_spec.rb index 1dd53236fbd..17990f41b3b 100644 --- a/spec/services/issues/build_service_spec.rb +++ b/spec/services/issues/build_service_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper.rb' describe Issues::BuildService, services: true do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } before do diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb index db196ed5751..9f8346d52bb 100644 --- a/spec/services/issues/move_service_spec.rb +++ b/spec/services/issues/move_service_spec.rb @@ -5,8 +5,8 @@ describe Issues::MoveService, services: true do let(:author) { create(:user) } let(:title) { 'Some issue' } let(:description) { 'Some issue description' } - let(:old_project) { create(:project) } - let(:new_project) { create(:project) } + let(:old_project) { create(:empty_project) } + let(:new_project) { create(:empty_project) } let(:milestone1) { create(:milestone, project_id: old_project.id, title: 'v9.0') } let(:old_issue) do diff --git a/spec/services/issues/resolve_discussions_spec.rb b/spec/services/issues/resolve_discussions_spec.rb index 6cc738aec08..3a72f92383c 100644 --- a/spec/services/issues/resolve_discussions_spec.rb +++ b/spec/services/issues/resolve_discussions_spec.rb @@ -10,7 +10,7 @@ class DummyService < Issues::BaseService end describe DummyService, services: true do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } before do diff --git a/spec/services/labels/find_or_create_service_spec.rb b/spec/services/labels/find_or_create_service_spec.rb index 7a9b34f9f96..de8f1827cce 100644 --- a/spec/services/labels/find_or_create_service_spec.rb +++ b/spec/services/labels/find_or_create_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Labels::FindOrCreateService, services: true do describe '#execute' do let(:group) { create(:group) } - let(:project) { create(:project, namespace: group) } + let(:project) { create(:empty_project, namespace: group) } let(:params) do { diff --git a/spec/services/labels/transfer_service_spec.rb b/spec/services/labels/transfer_service_spec.rb index 13654a0881c..11d5f1cad5e 100644 --- a/spec/services/labels/transfer_service_spec.rb +++ b/spec/services/labels/transfer_service_spec.rb @@ -6,8 +6,8 @@ describe Labels::TransferService, services: true do let(:group_1) { create(:group) } let(:group_2) { create(:group) } let(:group_3) { create(:group) } - let(:project_1) { create(:project, namespace: group_2) } - let(:project_2) { create(:project, namespace: group_3) } + let(:project_1) { create(:empty_project, namespace: group_2) } + let(:project_2) { create(:empty_project, namespace: group_3) } let(:group_label_1) { create(:group_label, group: group_1, name: 'Group Label 1') } let(:group_label_2) { create(:group_label, group: group_1, name: 'Group Label 2') } diff --git a/spec/services/members/destroy_service_spec.rb b/spec/services/members/destroy_service_spec.rb index 574df6e0f42..41450c67d7e 100644 --- a/spec/services/members/destroy_service_spec.rb +++ b/spec/services/members/destroy_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Members::DestroyService, services: true do let(:user) { create(:user) } let(:member_user) { create(:user) } - let(:project) { create(:project, :public) } + let(:project) { create(:empty_project, :public) } let(:group) { create(:group, :public) } shared_examples 'a service raising ActiveRecord::RecordNotFound' do diff --git a/spec/services/members/request_access_service_spec.rb b/spec/services/members/request_access_service_spec.rb index 853c125dadb..814c17b9ad0 100644 --- a/spec/services/members/request_access_service_spec.rb +++ b/spec/services/members/request_access_service_spec.rb @@ -29,7 +29,7 @@ describe Members::RequestAccessService, services: true do end context 'when current user cannot request access to the project' do - %i[project group].each do |source_type| + %i[empty_project group].each do |source_type| it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do let(:source) { create(source_type, :private) } end @@ -37,7 +37,7 @@ describe Members::RequestAccessService, services: true do end context 'when access requests are disabled' do - %i[project group].each do |source_type| + %i[empty_project group].each do |source_type| it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do let(:source) { create(source_type, :public) } end @@ -45,7 +45,7 @@ describe Members::RequestAccessService, services: true do end context 'when current user can request access to the project' do - %i[project group].each do |source_type| + %i[empty_project group].each do |source_type| it_behaves_like 'a service creating a access request' do let(:source) { create(source_type, :public, :access_requestable) } end diff --git a/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb b/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb index d80fb8a1af1..af0a214c00f 100644 --- a/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb +++ b/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe MergeRequests::AddTodoWhenBuildFailsService do let(:user) { create(:user) } let(:merge_request) { create(:merge_request) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:sha) { '1234567890abcdef1234567890abcdef12345678' } let(:ref) { merge_request.source_branch } diff --git a/spec/services/merge_requests/assign_issues_service_spec.rb b/spec/services/merge_requests/assign_issues_service_spec.rb index 5034b6ef33f..fe75757dd29 100644 --- a/spec/services/merge_requests/assign_issues_service_spec.rb +++ b/spec/services/merge_requests/assign_issues_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe MergeRequests::AssignIssuesService, services: true do let(:user) { create(:user) } - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:issue) { create(:issue, project: project) } let(:merge_request) { create(:merge_request, :simple, source_project: project, author: user, description: "fixes #{issue.to_reference}") } let(:service) { described_class.new(project, user, merge_request: merge_request) } diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb index adfa75a524f..c8bd4d4601a 100644 --- a/spec/services/merge_requests/build_service_spec.rb +++ b/spec/services/merge_requests/build_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe MergeRequests::BuildService, services: true do include RepoHelpers - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } let(:issue_confidential) { false } let(:issue) { create(:issue, project: project, title: 'A bug', confidential: issue_confidential) } diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb index 673c0bd6c9c..0e16c7cc94b 100644 --- a/spec/services/merge_requests/create_service_spec.rb +++ b/spec/services/merge_requests/create_service_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe MergeRequests::CreateService, services: true do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } let(:assignee) { create(:user) } diff --git a/spec/services/merge_requests/get_urls_service_spec.rb b/spec/services/merge_requests/get_urls_service_spec.rb index b7a05907208..290e00ea1ba 100644 --- a/spec/services/merge_requests/get_urls_service_spec.rb +++ b/spec/services/merge_requests/get_urls_service_spec.rb @@ -1,7 +1,7 @@ require "spec_helper" describe MergeRequests::GetUrlsService do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:service) { MergeRequests::GetUrlsService.new(project) } let(:source_branch) { "my_branch" } let(:new_merge_request_url) { "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/new?merge_request%5Bsource_branch%5D=#{source_branch}" } diff --git a/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb index c2f205c389d..769b3193275 100644 --- a/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb +++ b/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe MergeRequests::MergeWhenPipelineSucceedsService do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:mr_merge_if_green_enabled) do create(:merge_request, merge_when_pipeline_succeeds: true, merge_user: user, diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index 92729f68e5f..c22d145ca5d 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe MergeRequests::RefreshService, services: true do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } let(:service) { MergeRequests::RefreshService } @@ -11,7 +11,7 @@ describe MergeRequests::RefreshService, services: true do group = create(:group) group.add_owner(@user) - @project = create(:project, namespace: group) + @project = create(:project, :repository, namespace: group) @fork_project = Projects::ForkService.new(@project, @user).execute @merge_request = create(:merge_request, source_project: @project, @@ -252,7 +252,7 @@ describe MergeRequests::RefreshService, services: true do context 'when the merge request is sourced from a different project' do it 'creates a `MergeRequestsClosingIssues` record for each issue closed by a commit' do - forked_project = create(:project) + forked_project = create(:project, :repository) create(:forked_project_link, forked_to_project: forked_project, forked_from_project: @project) merge_request = create(:merge_request, diff --git a/spec/services/merge_requests/resolve_service_spec.rb b/spec/services/merge_requests/resolve_service_spec.rb index d33535d22af..eaf7785e549 100644 --- a/spec/services/merge_requests/resolve_service_spec.rb +++ b/spec/services/merge_requests/resolve_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe MergeRequests::ResolveService do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:fork_project) do create(:forked_project_with_submodules) do |fork_project| diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index 7d73c0ea5d0..ad3d767f193 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe MergeRequests::UpdateService, services: true do include EmailHelpers - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } let(:user2) { create(:user) } let(:user3) { create(:user) } diff --git a/spec/services/milestones/close_service_spec.rb b/spec/services/milestones/close_service_spec.rb index fe6a19e97ea..d581b94ff43 100644 --- a/spec/services/milestones/close_service_spec.rb +++ b/spec/services/milestones/close_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Milestones::CloseService, services: true do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:milestone) { create(:milestone, title: "Milestone v1.2", project: project) } before do diff --git a/spec/services/notes/diff_position_update_service_spec.rb b/spec/services/notes/diff_position_update_service_spec.rb index 110efb54fa0..d73ae51fbc3 100644 --- a/spec/services/notes/diff_position_update_service_spec.rb +++ b/spec/services/notes/diff_position_update_service_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Notes::DiffPositionUpdateService, services: true do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:create_commit) { project.commit("913c66a37b4a45b9769037c55c2d238bd0942d2e") } let(:modify_commit) { project.commit("874797c3a73b60d2187ed6e2fcabd289ff75171e") } let(:edit_commit) { project.commit("570e7b2abdd848b95f2f578043fc23bd6f6fd24d") } diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index f7240969588..5c841843b40 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -146,6 +146,16 @@ 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) @@ -362,7 +372,7 @@ describe NotificationService, services: true do end context 'commit note' do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:note) { create(:note_on_commit, project: project) } before do @@ -411,7 +421,7 @@ describe NotificationService, services: true do end context "merge request diff note" do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } let(:merge_request) { create(:merge_request, source_project: project, assignee: user) } let(:note) { create(:diff_note_on_merge_request, project: project, noteable: merge_request) } @@ -476,6 +486,20 @@ 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) @@ -665,6 +689,19 @@ 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) @@ -812,7 +849,7 @@ describe NotificationService, services: true do describe 'Merge Requests' do let(:group) { create(:group) } - let(:project) { create(:project, :public, namespace: group) } + let(:project) { create(:project, :public, :repository, namespace: group) } let(:another_project) { create(:empty_project, :public, namespace: group) } let(:merge_request) { create :merge_request, source_project: project, assignee: create(:user), description: 'cc @participant' } @@ -845,6 +882,20 @@ 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) @@ -1040,6 +1091,14 @@ 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 } @@ -1102,7 +1161,7 @@ describe NotificationService, services: true do end describe 'Projects' do - let(:project) { create :project } + let(:project) { create(:empty_project) } before do build_team(project) @@ -1147,7 +1206,7 @@ describe NotificationService, services: true do describe 'ProjectMember' do describe '#decline_group_invite' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:member) { create(:user) } before(:each) do @@ -1221,7 +1280,7 @@ describe NotificationService, services: true do describe 'Pipelines' do describe '#pipeline_finished' do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:current_user) { create(:user) } let(:u_member) { create(:user) } let(:u_other) { create(:user) } diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb index 74bfba44dfd..b1e10f4562e 100644 --- a/spec/services/projects/destroy_service_spec.rb +++ b/spec/services/projects/destroy_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Projects::DestroyService, services: true do let!(:user) { create(:user) } - let!(:project) { create(:project, namespace: user.namespace) } + let!(:project) { create(:project, :repository, namespace: user.namespace) } let!(:path) { project.repository.path_to_repo } let!(:remove_path) { path.sub(/\.git\Z/, "+#{project.id}+deleted.git") } let!(:async) { false } # execute or async_execute diff --git a/spec/services/projects/download_service_spec.rb b/spec/services/projects/download_service_spec.rb index 122a7cea2a1..33b267c069c 100644 --- a/spec/services/projects/download_service_spec.rb +++ b/spec/services/projects/download_service_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' describe Projects::DownloadService, services: true do describe 'File service' do before do - @user = create :user - @project = create :project, creator_id: @user.id, namespace: @user.namespace + @user = create(:user) + @project = create(:empty_project, creator_id: @user.id, namespace: @user.namespace) end context 'for a URL that is not on whitelist' do diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb index e3be1989c93..f8eb34f2ef4 100644 --- a/spec/services/projects/fork_service_spec.rb +++ b/spec/services/projects/fork_service_spec.rb @@ -7,6 +7,7 @@ describe Projects::ForkService, services: true do @from_user = create(:user, namespace: @from_namespace ) avatar = fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png") @from_project = create(:project, + :repository, creator_id: @from_user.id, namespace: @from_namespace, star_count: 107, @@ -54,7 +55,7 @@ describe Projects::ForkService, services: true do context 'project already exists' do it "fails due to validation, not transaction failure" do - @existing_project = create(:project, creator_id: @to_user.id, name: @from_project.name, namespace: @to_namespace) + @existing_project = create(:project, :repository, creator_id: @to_user.id, name: @from_project.name, namespace: @to_namespace) @to_project = fork_project(@from_project, @to_user) expect(@existing_project).to be_persisted @@ -104,9 +105,10 @@ describe Projects::ForkService, services: true do before do @group_owner = create(:user) @developer = create(:user) - @project = create(:project, creator_id: @group_owner.id, - star_count: 777, - description: 'Wow, such a cool project!') + @project = create(:project, :repository, + creator_id: @group_owner.id, + star_count: 777, + description: 'Wow, such a cool project!') @group = create(:group) @group.add_user(@group_owner, GroupMember::OWNER) @group.add_user(@developer, GroupMember::DEVELOPER) @@ -139,8 +141,9 @@ describe Projects::ForkService, services: true do context 'project already exists in group' do it 'fails due to validation, not transaction failure' do - existing_project = create(:project, name: @project.name, - namespace: @group) + existing_project = create(:project, :repository, + name: @project.name, + namespace: @group) to_project = fork_project(@project, @group_owner, @opts) expect(existing_project.persisted?).to be_truthy expect(to_project.errors[:name]).to eq(['has already been taken']) diff --git a/spec/services/projects/housekeeping_service_spec.rb b/spec/services/projects/housekeeping_service_spec.rb index 57a5aa5cedc..eaf63457b32 100644 --- a/spec/services/projects/housekeeping_service_spec.rb +++ b/spec/services/projects/housekeeping_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Projects::HousekeepingService do subject { Projects::HousekeepingService.new(project) } - let(:project) { create :project } + let(:project) { create(:project, :repository) } before do project.reset_pushes_since_gc diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb index 5c6fbea8d0e..f8187fefc14 100644 --- a/spec/services/projects/transfer_service_spec.rb +++ b/spec/services/projects/transfer_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Projects::TransferService, services: true do let(:user) { create(:user) } let(:group) { create(:group) } - let(:project) { create(:project, namespace: user.namespace) } + let(:project) { create(:project, :repository, namespace: user.namespace) } context 'namespace -> namespace' do before do @@ -58,7 +58,7 @@ describe Projects::TransferService, services: true do before { internal_group.add_owner(user) } context 'when namespace visibility level < project visibility level' do - let(:public_project) { create(:project, :public, namespace: user.namespace) } + let(:public_project) { create(:project, :public, :repository, namespace: user.namespace) } before { transfer_project(public_project, user, internal_group) } @@ -66,7 +66,7 @@ describe Projects::TransferService, services: true do end context 'when namespace visibility level > project visibility level' do - let(:private_project) { create(:project, :private, namespace: user.namespace) } + let(:private_project) { create(:project, :private, :repository, namespace: user.namespace) } before { transfer_project(private_project, user, internal_group) } diff --git a/spec/services/projects/update_pages_service_spec.rb b/spec/services/projects/update_pages_service_spec.rb index f75fdd9e03f..fc0a17296f3 100644 --- a/spec/services/projects/update_pages_service_spec.rb +++ b/spec/services/projects/update_pages_service_spec.rb @@ -1,9 +1,9 @@ require "spec_helper" describe Projects::UpdatePagesService do - let(:project) { create :project } - let(:pipeline) { create :ci_pipeline, project: project, sha: project.commit('HEAD').sha } - let(:build) { create :ci_build, pipeline: pipeline, ref: 'HEAD' } + let(:project) { create(:project, :repository) } + let(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit('HEAD').sha) } + let(:build) { create(:ci_build, pipeline: pipeline, ref: 'HEAD') } let(:invalid_file) { fixture_file_upload(Rails.root + 'spec/fixtures/dk.png') } subject { described_class.new(project, build) } diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb index caa23962519..05b18fef061 100644 --- a/spec/services/projects/update_service_spec.rb +++ b/spec/services/projects/update_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Projects::UpdateService, services: true do let(:user) { create(:user) } let(:admin) { create(:admin) } - let(:project) { create(:project, creator_id: user.id, namespace: user.namespace) } + let(:project) { create(:empty_project, creator_id: user.id, namespace: user.namespace) } describe 'update_by_user' do context 'when visibility_level is INTERNAL' do @@ -56,7 +56,7 @@ describe Projects::UpdateService, services: true do end describe 'visibility_level' do - let(:project) { create(:project, :internal) } + let(:project) { create(:empty_project, :internal) } let(:forked_project) { create(:forked_project_with_submodules, :internal) } before do diff --git a/spec/services/projects/upload_service_spec.rb b/spec/services/projects/upload_service_spec.rb index 150c8ccaef7..d2cefa46bfa 100644 --- a/spec/services/projects/upload_service_spec.rb +++ b/spec/services/projects/upload_service_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' describe Projects::UploadService, services: true do describe 'File service' do before do - @user = create :user - @project = create :project, creator_id: @user.id, namespace: @user.namespace + @user = create(:user) + @project = create(:empty_project, creator_id: @user.id, namespace: @user.namespace) end context 'for valid gif file' do diff --git a/spec/services/search_service_spec.rb b/spec/services/search_service_spec.rb index bed1031e40a..6ef5fa008aa 100644 --- a/spec/services/search_service_spec.rb +++ b/spec/services/search_service_spec.rb @@ -44,7 +44,7 @@ describe 'Search::GlobalService', services: true do context 'nested group' do let!(:nested_group) { create(:group, :nested) } - let!(:project) { create(:project, namespace: nested_group) } + let!(:project) { create(:empty_project, namespace: nested_group) } before { project.add_master(user) } diff --git a/spec/services/slash_commands/interpret_service_spec.rb b/spec/services/slash_commands/interpret_service_spec.rb index 52e8678cb9d..a63281f0eab 100644 --- a/spec/services/slash_commands/interpret_service_spec.rb +++ b/spec/services/slash_commands/interpret_service_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe SlashCommands::InterpretService, services: true do - let(:project) { create(:project, :public) } + let(:project) { create(:empty_project, :public) } let(:developer) { create(:user) } let(:issue) { create(:issue, project: project) } let(:milestone) { create(:milestone, project: project, title: '9.10') } @@ -260,6 +260,8 @@ describe SlashCommands::InterpretService, services: true do end shared_examples 'merge command' do + let(:project) { create(:project, :repository) } + it 'runs merge command if content contains /merge' do _, updates = service.execute(content, issuable) @@ -322,6 +324,7 @@ describe SlashCommands::InterpretService, services: true do end context 'when sha is missing' do + let(:project) { create(:project, :repository) } let(:service) { described_class.new(project, developer, {}) } it 'precheck passes and returns merge command' do @@ -694,7 +697,7 @@ describe SlashCommands::InterpretService, services: true do end context '/target_branch command' do - let(:non_empty_project) { create(:project) } + let(:non_empty_project) { create(:project, :repository) } let(:another_merge_request) { create(:merge_request, author: developer, source_project: non_empty_project) } let(:service) { described_class.new(non_empty_project, developer)} diff --git a/spec/services/spam_service_spec.rb b/spec/services/spam_service_spec.rb index e09c05ccf32..74cba8c014b 100644 --- a/spec/services/spam_service_spec.rb +++ b/spec/services/spam_service_spec.rb @@ -15,7 +15,7 @@ describe SpamService, services: true do end context 'when recaptcha was not verified' do - let(:project) { create(:project, :public) } + let(:project) { create(:empty_project, :public) } let(:issue) { create(:issue, project: project) } let(:request) { double(:request, env: {}) } diff --git a/spec/services/system_hooks_service_spec.rb b/spec/services/system_hooks_service_spec.rb index 11037a4917b..667059f230c 100644 --- a/spec/services/system_hooks_service_spec.rb +++ b/spec/services/system_hooks_service_spec.rb @@ -1,13 +1,13 @@ require 'spec_helper' describe SystemHooksService, services: true do - let(:user) { create :user } - let(:project) { create :project } - let(:project_member) { create :project_member } - let(:key) { create(:key, user: user) } - let(:deploy_key) { create(:key) } - let(:group) { create(:group) } - let(:group_member) { create(:group_member) } + let(:user) { create(:user) } + let(:project) { create(:empty_project) } + let(:project_member) { create(:project_member) } + let(:key) { create(:key, user: user) } + let(:deploy_key) { create(:key) } + let(:group) { create(:group) } + let(:group_member) { create(:group_member) } context 'event data' do it { expect(event_data(user, :create)).to include(:event_name, :name, :created_at, :updated_at, :email, :user_id, :username) } diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 36a17a3bf2e..d7bf4c39cc0 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe SystemNoteService, services: true do include Gitlab::Routing.url_helpers - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:author) { create(:user) } let(:noteable) { create(:issue, project: project) } @@ -32,6 +32,7 @@ describe SystemNoteService, services: true do describe '.add_commits' do subject { described_class.add_commits(noteable, project, author, new_commits, old_commits, oldrev) } + let(:project) { create(:project, :repository) } let(:noteable) { create(:merge_request, source_project: project) } let(:new_commits) { noteable.commits } let(:old_commits) { [] } @@ -216,6 +217,7 @@ describe SystemNoteService, services: true do end describe '.merge_when_pipeline_succeeds' do + let(:project) { create(:project, :repository) } let(:pipeline) { build(:ci_pipeline_without_jobs )} let(:noteable) do create(:merge_request, source_project: project, target_project: project) @@ -226,11 +228,12 @@ describe SystemNoteService, services: true do it_behaves_like 'a system note' it "posts the 'merge when pipeline succeeds' system note" do - expect(subject.note).to match /enabled an automatic merge when the pipeline for (\w+\/\w+@)?\h{40} succeeds/ + expect(subject.note).to match(/enabled an automatic merge when the pipeline for (\w+\/\w+@)?\h{40} succeeds/) end end describe '.cancel_merge_when_pipeline_succeeds' do + let(:project) { create(:project, :repository) } let(:noteable) do create(:merge_request, source_project: project, target_project: project) end @@ -273,6 +276,8 @@ describe SystemNoteService, services: true do describe '.change_branch' do subject { described_class.change_branch(noteable, project, author, 'target', old_branch, new_branch) } + + let(:project) { create(:project, :repository) } let(:old_branch) { 'old_branch'} let(:new_branch) { 'new_branch'} @@ -288,6 +293,8 @@ describe SystemNoteService, services: true do describe '.change_branch_presence' do subject { described_class.change_branch_presence(noteable, project, author, :source, 'feature', :delete) } + let(:project) { create(:project, :repository) } + it_behaves_like 'a system note' context 'when source branch deleted' do @@ -300,11 +307,13 @@ describe SystemNoteService, services: true do describe '.new_issue_branch' do subject { described_class.new_issue_branch(noteable, project, author, "1-mepmep") } + let(:project) { create(:project, :repository) } + it_behaves_like 'a system note' context 'when a branch is created from the new branch button' do it 'sets the note text' do - expect(subject.note).to match /\Acreated branch [`1-mepmep`]/ + expect(subject.note).to start_with("created branch [`1-mepmep`]") end end end @@ -333,7 +342,7 @@ describe SystemNoteService, services: true do describe 'note_body' do context 'cross-project' do - let(:project2) { create(:project) } + let(:project2) { create(:project, :repository) } let(:mentioner) { create(:issue, project: project2) } context 'from Commit' do @@ -353,6 +362,7 @@ describe SystemNoteService, services: true do context 'within the same project' do context 'from Commit' do + let(:project) { create(:project, :repository) } let(:mentioner) { project.repository.commit } it 'references the mentioning commit' do @@ -394,6 +404,7 @@ describe SystemNoteService, services: true do end context 'when mentioner is a MergeRequest' do + let(:project) { create(:project, :repository) } let(:mentioner) { create(:merge_request, :simple, source_project: project) } let(:noteable) { project.commit } @@ -421,6 +432,7 @@ describe SystemNoteService, services: true do end describe '.cross_reference_exists?' do + let(:project) { create(:project, :repository) } let(:commit0) { project.commit } let(:commit1) { project.commit('HEAD~2') } @@ -513,7 +525,7 @@ describe SystemNoteService, services: true do end describe '.noteable_moved' do - let(:new_project) { create(:project) } + let(:new_project) { create(:empty_project) } let(:new_noteable) { create(:issue, project: new_project) } subject do @@ -542,7 +554,7 @@ describe SystemNoteService, services: true do it_behaves_like 'cross project mentionable' it 'notifies about noteable being moved to' do - expect(subject.note).to match /moved to/ + expect(subject.note).to match('moved to') end end @@ -552,7 +564,7 @@ describe SystemNoteService, services: true do it_behaves_like 'cross project mentionable' it 'notifies about noteable being moved from' do - expect(subject.note).to match /moved from/ + expect(subject.note).to match('moved from') end end @@ -574,13 +586,13 @@ describe SystemNoteService, services: true do end end - include JiraServiceHelper - describe 'JIRA integration' do + include JiraServiceHelper + let(:project) { create(:jira_project) } let(:author) { create(:user) } let(:issue) { create(:issue, project: project) } - let(:merge_request) { create(:merge_request, :simple, target_project: project, source_project: project) } + let(:merge_request) { create(:merge_request, :simple, target_project: project, source_project: project) } let(:jira_issue) { ExternalIssue.new("JIRA-1", project)} let(:jira_tracker) { project.jira_service } let(:commit) { project.commit } @@ -809,6 +821,7 @@ describe SystemNoteService, services: true do end describe '.add_merge_request_wip_from_commit' do + let(:project) { create(:project, :repository) } let(:noteable) do create(:merge_request, source_project: project, target_project: project) end diff --git a/spec/services/tags/create_service_spec.rb b/spec/services/tags/create_service_spec.rb index 5478b8c9ec0..b9121b1de49 100644 --- a/spec/services/tags/create_service_spec.rb +++ b/spec/services/tags/create_service_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Tags::CreateService, services: true do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:repository) { project.repository } let(:user) { create(:user) } let(:service) { described_class.new(project, user) } diff --git a/spec/services/tags/destroy_service_spec.rb b/spec/services/tags/destroy_service_spec.rb index a388c93379a..28396fc3658 100644 --- a/spec/services/tags/destroy_service_spec.rb +++ b/spec/services/tags/destroy_service_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Tags::DestroyService, services: true do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:repository) { project.repository } let(:user) { create(:user) } let(:service) { described_class.new(project, user) } diff --git a/spec/services/test_hook_service_spec.rb b/spec/services/test_hook_service_spec.rb index 4f6dd8c6d3f..f99fd8434c2 100644 --- a/spec/services/test_hook_service_spec.rb +++ b/spec/services/test_hook_service_spec.rb @@ -1,9 +1,9 @@ require 'spec_helper' describe TestHookService, services: true do - let(:user) { create :user } - let(:project) { create :project } - let(:hook) { create :project_hook, project: project } + let(:user) { create(:user) } + let(:project) { create(:project, :repository) } + let(:hook) { create(:project_hook, project: project) } describe '#execute' do it "executes successfully" do diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb index 3645b73b039..f9e432bb216 100644 --- a/spec/services/todo_service_spec.rb +++ b/spec/services/todo_service_spec.rb @@ -8,7 +8,7 @@ describe TodoService, services: true do let(:guest) { create(:user) } let(:admin) { create(:admin) } let(:john_doe) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:mentions) { 'FYI: ' + [author, assignee, john_doe, member, guest, non_member, admin].map(&:to_reference).join(' ') } let(:directly_addressed) { [author, assignee, john_doe, member, guest, non_member, admin].map(&:to_reference).join(' ') } let(:directly_addressed_and_mentioned) { member.to_reference + ", what do you think? cc: " + [guest, admin].map(&:to_reference).join(' ') } @@ -99,9 +99,9 @@ describe TodoService, services: true do end context 'when a private group is mentioned' do - let(:group) { create :group, :private } - let(:project) { create :project, :private, group: group } - let(:issue) { create :issue, author: author, project: project, description: group.to_reference } + let(:group) { create(:group, :private) } + let(:project) { create(:empty_project, :private, group: group) } + let(:issue) { create(:issue, author: author, project: project, description: group.to_reference) } before do group.add_owner(author) @@ -422,22 +422,26 @@ describe TodoService, services: true do should_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_confidential_issue) end - it 'creates a todo for each valid mentioned user when leaving a note on commit' do - service.new_note(note_on_commit, john_doe) + context 'on commit' do + let(:project) { create(:project, :repository) } - should_create_todo(user: member, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit) - should_create_todo(user: author, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit) - should_create_todo(user: john_doe, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit) - should_not_create_todo(user: non_member, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit) - end + it 'creates a todo for each valid mentioned user when leaving a note on commit' do + service.new_note(note_on_commit, john_doe) + + should_create_todo(user: member, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit) + should_create_todo(user: author, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit) + should_create_todo(user: john_doe, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit) + should_not_create_todo(user: non_member, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit) + end - it 'creates a directly addressed todo for each valid mentioned user when leaving a note on commit' do - service.new_note(addressed_note_on_commit, john_doe) + it 'creates a directly addressed todo for each valid mentioned user when leaving a note on commit' do + service.new_note(addressed_note_on_commit, john_doe) - should_create_todo(user: member, target_id: nil, target_type: 'Commit', commit_id: addressed_note_on_commit.commit_id, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_commit) - should_create_todo(user: author, target_id: nil, target_type: 'Commit', commit_id: addressed_note_on_commit.commit_id, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_commit) - should_create_todo(user: john_doe, target_id: nil, target_type: 'Commit', commit_id: addressed_note_on_commit.commit_id, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_commit) - should_not_create_todo(user: non_member, target_id: nil, target_type: 'Commit', commit_id: addressed_note_on_commit.commit_id, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_commit) + should_create_todo(user: member, target_id: nil, target_type: 'Commit', commit_id: addressed_note_on_commit.commit_id, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_commit) + should_create_todo(user: author, target_id: nil, target_type: 'Commit', commit_id: addressed_note_on_commit.commit_id, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_commit) + should_create_todo(user: john_doe, target_id: nil, target_type: 'Commit', commit_id: addressed_note_on_commit.commit_id, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_commit) + should_not_create_todo(user: non_member, target_id: nil, target_type: 'Commit', commit_id: addressed_note_on_commit.commit_id, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_commit) + end end it 'does not create todo when leaving a note on snippet' do @@ -720,6 +724,7 @@ describe TodoService, services: true do end describe '#new_note' do + let(:project) { create(:project, :repository) } let(:mention) { john_doe.to_reference } let(:diff_note_on_merge_request) { create(:diff_note_on_merge_request, project: project, noteable: mr_unassigned, author: author, note: "Hey #{mention}") } let(:addressed_diff_note_on_merge_request) { create(:diff_note_on_merge_request, project: project, noteable: mr_unassigned, author: author, note: "#{mention}, hey!") } diff --git a/spec/services/update_release_service_spec.rb b/spec/services/update_release_service_spec.rb index bba211089a8..69ed8de9c31 100644 --- a/spec/services/update_release_service_spec.rb +++ b/spec/services/update_release_service_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe UpdateReleaseService, services: true do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } let(:tag_name) { project.repository.tag_names.first } let(:description) { 'Awesome release!' } diff --git a/spec/services/users/create_service_spec.rb b/spec/services/users/create_service_spec.rb new file mode 100644 index 00000000000..5f79203701a --- /dev/null +++ b/spec/services/users/create_service_spec.rb @@ -0,0 +1,182 @@ +require 'spec_helper' + +describe Users::CreateService, services: true do + describe '#build' do + let(:params) do + { name: 'John Doe', username: 'jduser', email: 'jd@example.com', password: 'mydummypass' } + end + + context 'with an admin user' do + let(:admin_user) { create(:admin) } + let(:service) { described_class.new(admin_user, params) } + + it 'returns a valid user' do + expect(service.build).to be_valid + end + end + + context 'with non admin user' do + let(:user) { create(:user) } + let(:service) { described_class.new(user, params) } + + it 'raises AccessDeniedError exception' do + expect { service.build }.to raise_error Gitlab::Access::AccessDeniedError + end + end + + context 'with nil user' do + let(:service) { described_class.new(nil, params) } + + it 'returns a valid user' do + expect(service.build).to be_valid + end + end + end + + describe '#execute' do + let(:admin_user) { create(:admin) } + + context 'with an admin user' do + let(:service) { described_class.new(admin_user, params) } + + context 'when required parameters are provided' do + let(:params) do + { name: 'John Doe', username: 'jduser', email: 'jd@example.com', password: 'mydummypass' } + end + + it 'returns a persisted user' do + expect(service.execute).to be_persisted + end + + it 'persists the given attributes' do + user = service.execute + user.reload + + expect(user).to have_attributes( + name: params[:name], + username: params[:username], + email: params[:email], + password: params[:password], + created_by_id: admin_user.id + ) + end + + it 'user is not confirmed if skip_confirmation param is not present' do + expect(service.execute).not_to be_confirmed + end + + it 'logs the user creation' do + expect(service).to receive(:log_info).with("User \"John Doe\" (jd@example.com) was created") + + service.execute + end + + it 'executes system hooks ' do + system_hook_service = spy(:system_hook_service) + + expect(service).to receive(:system_hook_service).and_return(system_hook_service) + + user = service.execute + + expect(system_hook_service).to have_received(:execute_hooks_for).with(user, :create) + end + + it 'does not send a notification email' do + notification_service = spy(:notification_service) + + expect(service).not_to receive(:notification_service) + + service.execute + + expect(notification_service).not_to have_received(:new_user) + end + end + + context 'when force_random_password parameter is true' do + let(:params) do + { name: 'John Doe', username: 'jduser', email: 'jd@example.com', password: 'mydummypass', force_random_password: true } + end + + it 'generates random password' do + user = service.execute + + expect(user.password).not_to eq 'mydummypass' + expect(user.password).to be_present + end + end + + context 'when skip_confirmation parameter is true' do + let(:params) do + { name: 'John Doe', username: 'jduser', email: 'jd@example.com', password: 'mydummypass', skip_confirmation: true } + end + + it 'confirms the user' do + expect(service.execute).to be_confirmed + end + end + + context 'when reset_password parameter is true' do + let(:params) do + { name: 'John Doe', username: 'jduser', email: 'jd@example.com', password: 'mydummypass', reset_password: true } + end + + it 'resets password even if a password parameter is given' do + expect(service.execute).to be_recently_sent_password_reset + end + + it 'sends a notification email' do + notification_service = spy(:notification_service) + + expect(service).to receive(:notification_service).and_return(notification_service) + + user = service.execute + + expect(notification_service).to have_received(:new_user).with(user, an_instance_of(String)) + end + end + end + + context 'with nil user' do + let(:params) do + { name: 'John Doe', username: 'jduser', email: 'jd@example.com', password: 'mydummypass', skip_confirmation: true } + end + let(:service) { described_class.new(nil, params) } + + context 'when "send_user_confirmation_email" application setting is true' do + before do + current_application_settings = double(:current_application_settings, send_user_confirmation_email: true, signup_enabled?: true) + allow(service).to receive(:current_application_settings).and_return(current_application_settings) + end + + it 'does not confirm the user' do + expect(service.execute).not_to be_confirmed + end + end + + context 'when "send_user_confirmation_email" application setting is false' do + before do + current_application_settings = double(:current_application_settings, send_user_confirmation_email: false, signup_enabled?: true) + allow(service).to receive(:current_application_settings).and_return(current_application_settings) + end + + it 'confirms the user' do + expect(service.execute).to be_confirmed + end + + it 'persists the given attributes' do + user = service.execute + user.reload + + expect(user).to have_attributes( + name: params[:name], + username: params[:username], + email: params[:email], + password: params[:password], + created_by_id: nil, + admin: false + ) + end + end + end + end +end diff --git a/spec/services/users/destroy_spec.rb b/spec/services/users/destroy_spec.rb index 922e82445d0..9a28c03d968 100644 --- a/spec/services/users/destroy_spec.rb +++ b/spec/services/users/destroy_spec.rb @@ -5,7 +5,7 @@ describe Users::DestroyService, services: true do let!(:user) { create(:user) } let!(:admin) { create(:admin) } let!(:namespace) { create(:namespace, owner: user) } - let!(:project) { create(:project, namespace: namespace) } + let!(:project) { create(:empty_project, namespace: namespace) } let(:service) { described_class.new(admin) } context 'no options are given' do @@ -25,7 +25,7 @@ describe Users::DestroyService, services: true do end context "a deleted user's issues" do - let(:project) { create :project } + let(:project) { create(:project) } before do project.add_developer(user) diff --git a/spec/services/users/refresh_authorized_projects_service_spec.rb b/spec/services/users/refresh_authorized_projects_service_spec.rb index 08733d6dcf1..b19374ef1a2 100644 --- a/spec/services/users/refresh_authorized_projects_service_spec.rb +++ b/spec/services/users/refresh_authorized_projects_service_spec.rb @@ -152,7 +152,7 @@ describe Users::RefreshAuthorizedProjectsService do context 'projects of groups the user is a member of' do let(:group) { create(:group) } - let!(:other_project) { create(:project, group: group) } + let!(:other_project) { create(:empty_project, group: group) } before do group.add_owner(user) @@ -166,7 +166,7 @@ describe Users::RefreshAuthorizedProjectsService do context 'projects of subgroups of groups the user is a member of' do let(:group) { create(:group) } let(:nested_group) { create(:group, parent: group) } - let!(:other_project) { create(:project, group: nested_group) } + let!(:other_project) { create(:empty_project, group: nested_group) } before do group.add_master(user) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 5ab8f0d981a..4eb5b150af5 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -9,7 +9,8 @@ require 'rspec/rails' require 'shoulda/matchers' require 'rspec/retry' -if ENV['RSPEC_PROFILING_POSTGRES_URL'] || ENV['RSPEC_PROFILING'] +if (ENV['RSPEC_PROFILING_POSTGRES_URL'] || ENV['RSPEC_PROFILING']) && + (!ENV.has_key?('CI') || ENV['CI_COMMIT_REF_NAME'] == 'master') require 'rspec_profiling/rspec' end diff --git a/spec/support/prometheus_helpers.rb b/spec/support/prometheus_helpers.rb index 4afdbd68304..cc79b11616a 100644 --- a/spec/support/prometheus_helpers.rb +++ b/spec/support/prometheus_helpers.rb @@ -1,10 +1,10 @@ module PrometheusHelpers def prometheus_memory_query(environment_slug) - %{(sum(container_memory_usage_bytes{container_name="app",environment="#{environment_slug}"}) / count(container_memory_usage_bytes{container_name="app",environment="#{environment_slug}"})) /1024/1024} + %{(sum(container_memory_usage_bytes{container_name!="POD",environment="#{environment_slug}"}) / count(container_memory_usage_bytes{container_name!="POD",environment="#{environment_slug}"})) /1024/1024} end def prometheus_cpu_query(environment_slug) - %{sum(rate(container_cpu_usage_seconds_total{container_name="app",environment="#{environment_slug}"}[2m])) / count(container_cpu_usage_seconds_total{container_name="app",environment="#{environment_slug}"}) * 100} + %{sum(rate(container_cpu_usage_seconds_total{container_name!="POD",environment="#{environment_slug}"}[2m])) / count(container_cpu_usage_seconds_total{container_name!="POD",environment="#{environment_slug}"}) * 100} end def prometheus_query_url(prometheus_query) diff --git a/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb b/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb index 81d06dc2a3d..ee492daee30 100644 --- a/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb +++ b/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb @@ -2,7 +2,7 @@ # It can take a `default_params`. shared_examples 'new issuable record that supports slash commands' do - let!(:project) { create(:project) } + let!(:project) { create(:project, :repository) } let(:user) { create(:user).tap { |u| project.team << [u, :master] } } let(:assignee) { create(:user) } let!(:milestone) { create(:milestone, project: project) } diff --git a/spec/support/stub_configuration.rb b/spec/support/stub_configuration.rb index f40ee862df8..444adcc1906 100644 --- a/spec/support/stub_configuration.rb +++ b/spec/support/stub_configuration.rb @@ -21,6 +21,10 @@ module StubConfiguration allow(Gitlab.config.incoming_email).to receive_messages(messages) end + def stub_mattermost_setting(messages) + allow(Gitlab.config.mattermost).to receive_messages(messages) + end + private # Modifies stubbed messages to also stub possible predicate versions |
