diff options
310 files changed, 2621 insertions, 1219 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 62468ad0f0e..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 @@ -206,10 +211,9 @@ rake ee_compat_check: - /^[\d-]+-stable(-ee)?$/ allow_failure: yes cache: - key: "ruby233-ee_compat_check_repo" + key: "ee_compat_check_repo" paths: - - ee_compat_check/repo/ - - vendor/ruby + - ee_compat_check/ee-repo/ artifacts: name: "${CI_JOB_NAME}_${CI_COMIT_REF_NAME}_${CI_COMMIT_SHA}" when: on_failure @@ -313,7 +317,7 @@ bundler:audit: - master@gitlab/gitlabhq - master@gitlab/gitlab-ee script: - - "bundle exec bundle-audit check --update" + - "bundle exec bundle-audit check --update --ignore CVE-2016-4658" migration paths: stage: test 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) diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 0d91a54c7d4..1d0ba9ea182 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -0.3.0 +0.4.0 diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION index 347f5833ee6..9df886c42a1 100644 --- a/GITLAB_WORKHORSE_VERSION +++ b/GITLAB_WORKHORSE_VERSION @@ -1 +1 @@ -1.4.1 +1.4.2 @@ -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/modal/filters.js b/app/assets/javascripts/boards/components/modal/filters.js index 2e22b1eca47..b214b5a7199 100644 --- a/app/assets/javascripts/boards/components/modal/filters.js +++ b/app/assets/javascripts/boards/components/modal/filters.js @@ -17,7 +17,7 @@ export default { this.filteredSearch.handleInputPlaceholder(); this.filteredSearch.toggleClearSearchButton(); }, - beforeDestroy() { + destroyed() { this.filteredSearch.cleanup(); FilteredSearchContainer.container = document; this.store.path = ''; diff --git a/app/assets/javascripts/boards/components/modal/index.js b/app/assets/javascripts/boards/components/modal/index.js index 4800407be1c..91c08cde13a 100644 --- a/app/assets/javascripts/boards/components/modal/index.js +++ b/app/assets/javascripts/boards/components/modal/index.js @@ -65,8 +65,15 @@ require('./empty_state'); }, filter: { handler() { - this.page = 1; - this.loadIssues(true); + if (this.$el.tagName) { + this.page = 1; + this.filterLoading = true; + + this.loadIssues(true) + .then(() => { + this.filterLoading = false; + }); + } }, deep: true, }, @@ -140,14 +147,14 @@ require('./empty_state'); :image="blankStateImage" :issue-link-base="issueLinkBase" :root-path="rootPath" - v-if="!loading && showList"></modal-list> + v-if="!loading && showList && !filterLoading"></modal-list> <empty-state v-if="showEmptyState" :image="blankStateImage" :new-issue-path="newIssuePath"></empty-state> <section class="add-issues-list text-center" - v-if="loading"> + v-if="loading || filterLoading"> <div class="add-issues-list-loading"> <i class="fa fa-spinner fa-spin"></i> </div> 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/boards/stores/modal_store.js b/app/assets/javascripts/boards/stores/modal_store.js index 7ee266a831f..9b009483a3c 100644 --- a/app/assets/javascripts/boards/stores/modal_store.js +++ b/app/assets/javascripts/boards/stores/modal_store.js @@ -15,6 +15,7 @@ searchTerm: '', loading: false, loadingNewPage: false, + filterLoading: false, page: 1, perPage: 50, filter: { 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/droplab/droplab_filter.js b/app/assets/javascripts/droplab/droplab_filter.js index 9b40a3f20a4..7f7d93f3e27 100644 --- a/app/assets/javascripts/droplab/droplab_filter.js +++ b/app/assets/javascripts/droplab/droplab_filter.js @@ -56,10 +56,12 @@ require('../window')(function(w){ this.hookInput = hookInput; this.hookInput.trigger.addEventListener('keyup.dl', this.keydownWrapper); + this.hookInput.trigger.addEventListener('mousedown.dl', this.keydownWrapper); }, destroy: function destroy(){ this.hookInput.trigger.removeEventListener('keyup.dl', this.keydownWrapper); + this.hookInput.trigger.removeEventListener('mousedown.dl', this.keydownWrapper); } }; }); 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/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js index d9692269c38..811f90c5a87 100644 --- a/app/assets/javascripts/merge_request_tabs.js +++ b/app/assets/javascripts/merge_request_tabs.js @@ -127,9 +127,6 @@ require('./flash'); if (this.diffViewType() === 'parallel') { this.expandViewContainer(); } - $.scrollTo('.merge-request-details .merge-request-tabs', { - offset: 0, - }); } else if (action === 'pipelines') { if (this.pipelinesLoaded) { return; 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/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index cda46223492..50849e95541 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -68,23 +68,19 @@ } @mixin btn-green { - @include btn-color($green-light, $border-green-light, $green-normal, $border-green-normal, $green-dark, $border-green-dark, $white-light); + @include btn-color($green-500, $green-600, $green-600, $green-700, $green-700, $green-800, $white-light); } @mixin btn-blue { - @include btn-color($blue-light, $border-blue-light, $blue-normal, $border-blue-normal, $blue-dark, $border-blue-dark, $white-light); -} - -@mixin btn-blue-medium { - @include btn-color($blue-medium-light, $border-blue-light, $blue-medium, $border-blue-normal, $blue-medium-dark, $border-blue-dark, $white-light); + @include btn-color($blue-500, $blue-600, $blue-600, $blue-700, $blue-700, $blue-800, $white-light); } @mixin btn-orange { - @include btn-color($orange-light, $border-orange-light, $orange-normal, $border-orange-normal, $orange-dark, $border-orange-dark, $white-light); + @include btn-color($orange-500, $orange-600, $orange-600, $orange-700, $orange-700, $orange-800, $white-light); } @mixin btn-red { - @include btn-color($red-light, $border-red-light, $red-normal, $border-red-normal, $red-dark, $border-red-dark, $white-light); + @include btn-color($red-500, $red-600, $red-600, $red-700, $red-700, $red-800, $white-light); } @mixin btn-gray { @@ -145,11 +141,11 @@ &.btn-new, &.btn-create, &.btn-save { - @include btn-outline($white-light, $border-green-light, $border-green-light, $green-light, $white-light, $border-green-light, $green-normal, $border-green-normal); + @include btn-outline($white-light, $green-600, $green-500, $green-500, $white-light, $green-600, $green-600, $green-700); } &.btn-remove { - @include btn-outline($white-light, $border-red-light, $border-red-light, $red-light, $white-light, $border-red-light, $red-normal, $border-red-normal); + @include btn-outline($white-light, $red-500, $red-500, $red-500, $white-light, $red-600, $red-600, $red-700); } } @@ -157,11 +153,8 @@ @include btn-gray; } - &.btn-primary { - @include btn-blue-medium; - } - &.btn-info, + &.btn-primary, &.btn-register { @include btn-blue; } @@ -171,11 +164,11 @@ } &.btn-close { - @include btn-outline($white-light, $border-orange-light, $border-orange-light, $orange-light, $white-light, $border-orange-light, $orange-normal, $border-orange-normal); + @include btn-outline($white-light, $orange-600, $orange-500, $orange-500, $white-light, $orange-600, $orange-600, $orange-700); } &.btn-spam { - @include btn-outline($white-light, $border-red-light, $border-red-light, $red-light, $white-light, $border-red-light, $red-normal, $border-red-normal); + @include btn-outline($white-light, $red-500, $red-500, $red-500, $white-light, $red-600, $red-600, $red-700); } &.btn-danger, @@ -360,7 +353,7 @@ .btn-inverted { &-secondary { - @include btn-outline($white-light, $border-blue-light, $border-blue-light, $blue-light, $white-light, $border-blue-light, $blue-normal, $border-blue-normal); + @include btn-outline($white-light, $blue-500, $blue-500, $blue-500, $white-light, $blue-600, $blue-600, $blue-700); } } diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 186bb9ac616..da5b754aec7 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -195,7 +195,7 @@ text-decoration: none; .badge { - background-color: darken($row-hover, 5%); + background-color: darken($dropdown-link-hover-bg, 5%); } } diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss index 2890fcd088b..432024779fd 100644 --- a/app/assets/stylesheets/framework/forms.scss +++ b/app/assets/stylesheets/framework/forms.scss @@ -177,34 +177,34 @@ label { } .gl-field-error { - color: $red-normal; + color: $red-500; } .gl-show-field-errors { .gl-field-success-outline { - border: 1px solid $green-normal; + border: 1px solid $green-600; &:focus { - box-shadow: 0 0 0 1px $green-normal inset, 0 1px 1px $gl-field-focus-shadow inset, 0 0 4px 0 $green-normal; + box-shadow: 0 0 0 1px $green-600 inset, 0 1px 1px $gl-field-focus-shadow inset, 0 0 4px 0 $green-600; border: 0 none; } } .gl-field-error-outline { - border: 1px solid $red-normal; + border: 1px solid $red-500; &:focus { - box-shadow: 0 0 0 1px $red-normal inset, 0 1px 1px $gl-field-focus-shadow inset, 0 0 4px 0 $gl-field-focus-shadow-error; + box-shadow: 0 0 0 1px $red-500 inset, 0 1px 1px $gl-field-focus-shadow inset, 0 0 4px 0 $gl-field-focus-shadow-error; border: 0 none; } } .gl-field-success-message { - color: $green-normal; + color: $green-600; } .gl-field-error-message { - color: $red-normal; + color: $red-500; } .gl-field-hint { diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 6660a022260..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; } } } @@ -265,15 +259,7 @@ header { } .impersonation i { - color: $red-normal; - } -} - -.page-sidebar-pinned.right-sidebar-expanded { - @media (max-width: $screen-md-max) { - .header-content .title { - width: 300px; - } + color: $red-500; } } diff --git a/app/assets/stylesheets/framework/icons.scss b/app/assets/stylesheets/framework/icons.scss index db8d231a82a..87667f39ab8 100644 --- a/app/assets/stylesheets/framework/icons.scss +++ b/app/assets/stylesheets/framework/icons.scss @@ -1,8 +1,8 @@ .ci-status-icon-success { - color: $gl-success; + color: $green-500; svg { - fill: $gl-success; + fill: $green-500; } } @@ -17,18 +17,18 @@ .ci-status-icon-pending, .ci-status-icon-failed_with_warnings, .ci-status-icon-success_with_warnings { - color: $gl-warning; + color: $orange-500; svg { - fill: $gl-warning; + fill: $orange-500; } } .ci-status-icon-running { - color: $blue-normal; + color: $blue-400; svg { - fill: $blue-normal; + fill: $blue-400; } } diff --git a/app/assets/stylesheets/framework/issue_box.scss b/app/assets/stylesheets/framework/issue_box.scss index 46632f15f35..1537b0744cc 100644 --- a/app/assets/stylesheets/framework/issue_box.scss +++ b/app/assets/stylesheets/framework/issue_box.scss @@ -33,7 +33,7 @@ } &.status-box-open { - background-color: $green-light; + background-color: $green-500; } &.status-box-expired { diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss index 4d5a2ca52f0..20c7bc93c28 100644 --- a/app/assets/stylesheets/framework/layout.scss +++ b/app/assets/stylesheets/framework/layout.scss @@ -76,28 +76,28 @@ body { /* Stripe the background colors so that adjacent alert-warnings are distinct from one another */ .alert-warning { transition: background-color 0.15s, border-color 0.15s; - background-color: lighten($gl-warning, 4%); - border-color: lighten($gl-warning, 4%); + background-color: $orange-500; + border-color: $orange-500; } .alert-warning + .alert-warning { - background-color: $gl-warning; - border-color: $gl-warning; + background-color: $orange-600; + border-color: $orange-600; } .alert-warning + .alert-warning + .alert-warning { - background-color: darken($gl-warning, 4%); - border-color: darken($gl-warning, 4%); + background-color: $orange-700; + border-color: $orange-700; } .alert-warning + .alert-warning + .alert-warning + .alert-warning { - background-color: darken($gl-warning, 8%); - border-color: darken($gl-warning, 8%); + background-color: $orange-800; + border-color: $orange-800; } .alert-warning:only-of-type { - background-color: $gl-warning; - border-color: $gl-warning; + background-color: $orange-500; + border-color: $orange-500; } } diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index 7adbb0a4188..15dc0aa6a52 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -122,7 +122,7 @@ ul.content-list { } .member-group-link { - color: $blue-normal; + color: $blue-600; } .description { diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index 205d23b1329..5ab505034b6 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -416,14 +416,16 @@ .page-with-layout-nav { .right-sidebar { - top: ($header-height * 2) + 2; + top: ($header-height + 1) * 2; } - .build-sidebar { - top: ($header-height * 3) + 3; + &.page-with-sub-nav { + .right-sidebar { + top: ($header-height + 1) * 3; - &.affix { - top: 0; + &.affix { + top: 0; + } } } } diff --git a/app/assets/stylesheets/framework/tw_bootstrap.scss b/app/assets/stylesheets/framework/tw_bootstrap.scss index 12a86a64645..e54cc2866a7 100644 --- a/app/assets/stylesheets/framework/tw_bootstrap.scss +++ b/app/assets/stylesheets/framework/tw_bootstrap.scss @@ -176,6 +176,10 @@ summary { &.panel-without-border { border: 0; } + + &.panel-without-margin { + margin: 0; + } } .panel-succes .panel-heading, diff --git a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss index 0fc89d5976a..c9f345d24be 100644 --- a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss +++ b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss @@ -31,6 +31,7 @@ $border-radius-small: 3px !default; // $text-color: $gl-text-color; $link-color: $gl-link-color; +$link-hover-color: $gl-link-hover-color; //== Typography @@ -73,7 +74,7 @@ $pagination-hover-color: $gl-text-color; $pagination-hover-bg: $row-hover; $pagination-hover-border: $border-color; -$pagination-active-color: $blue-dark; +$pagination-active-color: $blue-600; $pagination-active-bg: $white-light; $pagination-active-border: $border-color; @@ -135,8 +136,8 @@ $well-border: #eee; // //## -$code-color: #c7254e; -$code-bg: #f9f2f4; +$code-color: $red-600; +$code-bg: lighten($red-50, 2%); $kbd-color: $white-light; $kbd-bg: #333; diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 82c9c76c4c0..97794a47df8 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -26,27 +26,49 @@ $gray-dark: darken($gray-light, $darken-dark-factor); $gray-darker: #eee; $gray-darkest: #c4c4c4; -$green-light: #3cbd70; -$green-normal: darken($green-light, $darken-normal-factor); -$green-dark: darken($green-light, $darken-dark-factor); - -$blue-light: #2ea8e5; -$blue-normal: darken($blue-light, $darken-normal-factor); -$blue-dark: darken($blue-light, $darken-dark-factor); - -$blue-medium-light: #3498cb; -$blue-medium: darken($blue-medium-light, $darken-normal-factor); -$blue-medium-dark: darken($blue-medium-light, $darken-dark-factor); - -$blue-light-transparent: rgba(44, 159, 216, 0.05); - -$orange-light: #fc8a51; -$orange-normal: darken($orange-light, $darken-normal-factor); -$orange-dark: darken($orange-light, $darken-dark-factor); - -$red-light: #eb4d5c; -$red-normal: darken($red-light, $darken-normal-factor); -$red-dark: darken($red-light, $darken-dark-factor); +$green-50: #e4f5eb; +$green-100: #bae6cc; +$green-200: #8dd5aa; +$green-300: #5fc488; +$green-400: #3cb76f; +$green-500: #1aaa55; +$green-600: #168f48; +$green-700: #12753a; +$green-800: #0e5a2d; +$green-900: #0a4020; + +$blue-50: #e4eff9; +$blue-100: #bcd7f1; +$blue-200: #8fbce8; +$blue-300: #62a1df; +$blue-400: #418cd8; +$blue-500: #1f78d1; +$blue-600: #1b69b6; +$blue-700: #17599c; +$blue-800: #134a81; +$blue-900: #0f3b66; + +$orange-50: #fff2e1; +$orange-100: #fedfb3; +$orange-200: #feca81; +$orange-300: #fdb44f; +$orange-400: #fca429; +$orange-500: #fc9403; +$orange-600: #de7e00; +$orange-700: #c26700; +$orange-800: #a35100; +$orange-900: #853b00; + +$red-50: #fbe7e4; +$red-100: #f4c4bc; +$red-200: #ed9d90; +$red-300: #e67664; +$red-400: #e05842; +$red-500: #db3b21; +$red-600: #c0341d; +$red-700: #a62d19; +$red-800: #8b2615; +$red-900: #711e11; $black: #000; $black-transparent: rgba(0, 0, 0, 0.3); @@ -58,32 +80,11 @@ $border-gray-light: darken($gray-light, $darken-border-factor); $border-gray-normal: darken($gray-normal, $darken-border-factor); $border-gray-dark: darken($white-normal, $darken-border-factor); -$border-green-extra-light: #9adb84; -$border-green-light: darken($green-light, $darken-border-factor); -$border-green-normal: darken($green-normal, $darken-border-factor); -$border-green-dark: darken($green-dark, $darken-border-factor); - -$border-blue-light: darken($blue-light, $darken-border-factor); -$border-blue-normal: darken($blue-normal, $darken-border-factor); -$border-blue-dark: darken($blue-dark, $darken-border-factor); - -$border-orange-light: darken($orange-light, $darken-border-factor); -$border-orange-normal: darken($orange-normal, $darken-border-factor); -$border-orange-dark: darken($orange-dark, $darken-border-factor); - -$border-red-light: darken($red-light, $darken-border-factor); -$border-red-normal: darken($red-normal, $darken-border-factor); -$border-red-dark: darken($red-dark, $darken-border-factor); - -$warning-message-bg: #fbf2d9; -$warning-message-color: #9e8e60; -$warning-message-border: #f0e2bb; - /* * UI elements */ $border-color: #e5e5e5; -$focus-border-color: #3aabf0; +$focus-border-color: $blue-300; $well-expand-item: #e8f2f7; $well-inner-border: #eef0f2; $well-light-border: #f1f1f1; @@ -96,10 +97,11 @@ $gl-font-size: 14px; $gl-text-color: rgba(0, 0, 0, .85); $gl-text-color-secondary: rgba(0, 0, 0, .55); $gl-text-color-disabled: rgba(0, 0, 0, .35); -$gl-text-green: #4a2; -$gl-text-red: #d12f19; -$gl-text-orange: #d90; -$gl-link-color: #3777b0; +$gl-text-green: $green-600; +$gl-text-red: $red-500; +$gl-text-orange: $orange-600; +$gl-link-color: $blue-600; +$gl-link-hover-color: $blue-800; $gl-grayish-blue: #7f8fa4; $gl-gray: $gl-text-color; $gl-gray-dark: #313236; @@ -116,9 +118,9 @@ $list-text-disabled-color: $gl-text-color-disabled; $list-border-light: #eee; $list-border: rgba(0, 0, 0, 0.05); $list-text-height: 42px; -$list-warning-row-bg: #fcf8e3; -$list-warning-row-border: #faebcc; -$list-warning-row-color: #8a6d3b; +$list-warning-row-bg: $orange-50; +$list-warning-row-border: $orange-100; +$list-warning-row-color: $orange-700; /* * Markdown @@ -145,24 +147,24 @@ $gl-sidebar-padding: 22px; /* * Misc */ -$row-hover: #f7faff; -$row-hover-border: #b2d7ff; +$row-hover: lighten($blue-50, 2%); +$row-hover-border: $blue-100; $progress-color: #c0392b; $header-height: 50px; $fixed-layout-width: 1280px; $limited-layout-width: 990px; $gl-avatar-size: 40px; -$error-exclamation-point: #e62958; +$error-exclamation-point: $red-500; $border-radius-default: 2px; $settings-icon-size: 18px; -$provider-btn-not-active-color: #4688f1; -$link-underline-blue: #4a8bee; -$active-item-blue: #4a8bee; +$provider-btn-not-active-color: $blue-500; +$link-underline-blue: $blue-500; +$active-item-blue: $blue-500; $layout-link-gray: #7e7c7c; $btn-side-margin: 10px; $btn-sm-side-margin: 7px; $btn-xs-side-margin: 5px; -$issue-status-expired: #cea61b; +$issue-status-expired: $orange-500; $issuable-sidebar-color: $gl-text-color-secondary; $show-aside-bg: #eee; $show-aside-color: #777; @@ -191,10 +193,10 @@ $user-mention-color: #2fa0bb; $time-color: #999; $project-member-show-color: #aaa; $gl-promo-color: #aaa; -$error-bg: #c67; -$warning-message-bg: #ffffe6; -$warning-message-border: #ed9; -$warning-message-color: #b90; +$error-bg: $red-400; +$warning-message-bg: $orange-50; +$warning-message-border: $orange-100; +$warning-message-color: $orange-700; $control-group-descr-color: #666; $table-permission-x-bg: #d9edf7; $username-color: #666; @@ -209,30 +211,30 @@ $tanuki-yellow: #fca326; /* * State colors: */ -$gl-primary: $blue-normal; -$gl-success: $green-normal; +$gl-primary: $blue-500; +$gl-success: $green-500; $gl-success-focus: rgba($gl-success, .4); -$gl-info: $blue-normal; -$gl-warning: $orange-normal; -$gl-danger: $red-normal; +$gl-info: $blue-500; +$gl-warning: $orange-500; +$gl-danger: $red-500; $gl-btn-active-background: rgba(0, 0, 0, 0.16); $gl-btn-active-gradient: inset 0 2px 3px $gl-btn-active-background; /* * Commit Diff Colors */ -$added: #63c363; -$deleted: #f77; -$line-added: #ecfdf0; -$line-added-dark: #c7f0d2; -$line-removed: #fbe9eb; -$line-removed-dark: #fac5cd; -$line-number-old: #f9d7dc; -$line-number-new: #ddfbe6; -$line-number-select: #fbf2da; -$line-target-blue: #f6faff; -$line-select-yellow: #fcf8e7; -$line-select-yellow-dark: #f0e2bd; +$added: $green-300; +$deleted: $red-300; +$line-added: $green-50; +$line-added-dark: $green-100; +$line-removed: $red-50; +$line-removed-dark: $red-100; +$line-number-old: lighten($red-100, 5%); +$line-number-new: lighten($green-100, 5%); +$line-number-select: lighten($orange-100, 5%); +$line-target-blue: $blue-50; +$line-select-yellow: $orange-50; +$line-select-yellow-dark: $orange-100; $dark-diff-match-bg: rgba(255, 255, 255, 0.3); $dark-diff-match-color: rgba(255, 255, 255, 0.1); $file-mode-changed: #777; @@ -272,7 +274,7 @@ $dropdown-toggle-active-border-color: darken($border-color, 14%); /* * Filtered Search */ -$dropdown-hover-color: #3b86ff; +$dropdown-hover-color: $blue-400; /* * Buttons @@ -295,10 +297,10 @@ $award-emoji-menu-shadow: rgba(0,0,0,.175); /* * Search Box */ -$search-input-border-color: rgba(#4688f1, .8); +$search-input-border-color: rgba($blue-400, .8); $search-input-focus-shadow-color: $dropdown-input-focus-shadow; $search-input-width: 220px; -$location-badge-active-bg: #4f91f8; +$location-badge-active-bg: $blue-500; $location-icon-color: #e7e9ed; /* @@ -360,18 +362,18 @@ $builds-trace-bg: #111; /* * Callout */ -$callout-danger-bg: #fdf7f7; -$callout-danger-border: #eed3d7; -$callout-danger-color: #b94a48; -$callout-warning-bg: #faf8f0; -$callout-warning-border: #faebcc; -$callout-warning-color: #8a6d3b; -$callout-info-bg: #f4f8fa; -$callout-info-border: #bce8f1; -$callout-info-color: #34789a; -$callout-success-bg: #dff0d8; -$callout-success-border: #5ca64d; -$callout-success-color: #3c763d; +$callout-danger-bg: $red-50; +$callout-danger-border: $red-100; +$callout-danger-color: $red-700; +$callout-warning-bg: $orange-50; +$callout-warning-border: $orange-100; +$callout-warning-color: $orange-700; +$callout-info-bg: $blue-50; +$callout-info-border: $blue-100; +$callout-info-color: $blue-700; +$callout-success-bg: $green-50; +$callout-success-border: $green-100; +$callout-success-color: $green-700; /* * Commit Page @@ -391,7 +393,7 @@ $common-green: $gl-text-green; /* * Editor */ -$editor-cancel-color: #b94a48; +$editor-cancel-color: $red-600; /* * Events @@ -415,10 +417,10 @@ $logs-p-color: #333; * Forms */ $input-danger-bg: #f2dede; -$input-danger-border: #d66; +$input-danger-border: $red-400; $input-group-addon-bg: #f7f8fa; $gl-field-focus-shadow: rgba(0, 0, 0, 0.075); -$gl-field-focus-shadow-error: rgba(210, 40, 82, 0.6); +$gl-field-focus-shadow-error: rgba($red-500, 0.6); /* * Help @@ -452,14 +454,14 @@ $label-border-radius: 100px; /* * Lint */ -$lint-incorrect-color: red; -$lint-correct-color: #47a447; +$lint-incorrect-color: $red-500; +$lint-correct-color: $green-500; /* * Login */ $login-brand-holder-color: #888; -$login-devise-error-color: #a00; +$login-devise-error-color: $red-700; /* * Nav @@ -473,33 +475,33 @@ $nav-toggle-gray: #666; */ $notify-details: #777; $notify-footer: #777; -$notify-new-file: #090; -$notify-deleted-file: #b00; +$notify-new-file: $green-600; +$notify-deleted-file: $red-700; /* * Projects */ $project-option-descr-color: #54565b; $project-breadcrumb-color: #999; -$project-private-forks-notice-odd: #2aa056; +$project-private-forks-notice-odd: $green-600; $project-network-controls-color: #888; /* * Runners */ -$runner-state-shared-bg: #32b186; -$runner-state-specific-bg: #3498db; -$runner-status-online-color: $green-normal; +$runner-state-shared-bg: $green-400; +$runner-state-specific-bg: $blue-400; +$runner-status-online-color: $green-600; $runner-status-offline-color: $gray-darkest; -$runner-status-paused-color: $red-normal; +$runner-status-paused-color: $red-500; /* Stat Graph */ $stat-graph-common-bg: #f3f3f3; -$stat-graph-area-fill: #1db34f; +$stat-graph-area-fill: $green-500; $stat-graph-axis-fill: #aaa; -$stat-graph-orange-fill: #f17f49; +$stat-graph-orange-fill: $orange-500; $stat-graph-selection-fill: #333; $stat-graph-selection-stroke: #333; @@ -513,7 +515,7 @@ $select2-drop-shadow2: rgba(31, 37, 50, 0.317647); /* * Todo */ -$todo-alert-blue: #428bca; +$todo-alert-blue: $blue-500; $todo-body-pre-color: #777; $todo-body-border: #ddd; diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index f9ee33019cd..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; @@ -487,9 +492,9 @@ right: -3px; top: -3px; width: 17px; - background-color: $blue-light; + background-color: $blue-500; color: $white-light; - border: 1px solid $border-blue-light; + border: 1px solid $blue-600; font-size: 9px; line-height: 15px; border-radius: 50%; diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index a24292a7c8c..969fc75c6eb 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -366,9 +366,3 @@ right: 0; margin-top: -17px; } - -@media (min-width: $screen-md-min) { - .sub-nav.build { - width: calc(100% + #{$gutter_width}); - } -} 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/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index ddc0e78c7b6..c1a9bc4be28 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -520,12 +520,12 @@ &.over_estimate { .meter-fill { - background: $red-light; + background: $red-500; } .time-remaining, .compare-value.spent { - color: $red-light; + color: $red-500; } } } diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index b27741a928d..b2f45625a2a 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -69,21 +69,17 @@ ul.related-merge-requests > li { height: 20px; border-radius: 3px; line-height: 18px; - border: 1px solid; &.merged { - border-color: darken($blue-normal, 10%); - background: $blue-normal; + background: $blue-500; } &.closed { - border-color: darken($red-normal, 10%); - background: $red-normal; + background: $red-500; } &.open { - border: 1px solid darken($green-normal, 10%); - background: $green-normal; + background: $green-500; } } diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index 71ed5b1361a..8249e02b64a 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -85,11 +85,11 @@ } .username .validation-success { - color: $green-normal; + color: $green-600; } .username .validation-error { - color: $red-normal; + color: $red-500; } } } diff --git a/app/assets/stylesheets/pages/merge_conflicts.scss b/app/assets/stylesheets/pages/merge_conflicts.scss index 5a9f199fb34..35cefd449f1 100644 --- a/app/assets/stylesheets/pages/merge_conflicts.scss +++ b/app/assets/stylesheets/pages/merge_conflicts.scss @@ -255,7 +255,7 @@ $colors: ( &.saved { .editor { - border-top: solid 2px $border-green-extra-light; + border-top: solid 2px $green-200; } } diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 7c3172421c1..6630904ec92 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -535,7 +535,7 @@ } .fa-info-circle { - color: $orange-normal; + color: $orange-500; padding-right: 5px; } } diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss index 27c47d36818..efbd9365fd9 100644 --- a/app/assets/stylesheets/pages/milestone.scss +++ b/app/assets/stylesheets/pages/milestone.scss @@ -63,7 +63,7 @@ } .remaining-days { - color: $orange-light; + color: $orange-600; } .milestone-stats-and-buttons { diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index e238f0865f6..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 { @@ -462,17 +446,18 @@ ul.notes { background: $white-light; padding: 1px 5px; font-size: 12px; - color: $gl-link-color; + color: $blue-500; margin-left: -55px; position: absolute; z-index: 10; width: 23px; height: 23px; - border: 1px solid $border-color; + border: 1px solid $blue-500; transition: transform .1s ease-in-out; &:hover { - background: $gl-info; + background: $blue-500; + border-color: $blue-600; color: $white-light; transform: scale(1.15); } diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index a20db153d09..a4fe652b52f 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -673,51 +673,71 @@ // Dropdown button animation in mini pipeline graph &.ci-status-icon-success { - border-color: $gl-success; - color: $gl-success; + border-color: $green-500; + color: $green-500; &:hover, &:focus, &:active { - background-color: rgba($gl-success, 0.1); - border-color: $gl-success; + background-color: $green-50; + border-color: $green-600; + color: $green-600; + + svg { + fill: $green-600; + } } } &.ci-status-icon-failed { - border-color: $gl-danger; - color: $gl-danger; + border-color: $red-500; + color: $red-500; &:hover, &:focus, &:active { - background-color: rgba($gl-danger, 0.1); - border-color: $gl-danger; + background-color: $red-50; + border-color: $red-600; + color: $red-600; + + svg { + fill: $red-600; + } } } &.ci-status-icon-pending, &.ci-status-icon-success_with_warnings { - border-color: $gl-warning; - color: $gl-warning; + border-color: $orange-500; + color: $orange-500; &:hover, &:focus, &:active { - background-color: rgba($gl-warning, 0.1); - border-color: $gl-warning; + background-color: $orange-50; + border-color: $orange-600; + color: $orange-600; + + svg { + fill: $orange-600; + } } } &.ci-status-icon-running { - border-color: $blue-normal; - color: $blue-normal; + border-color: $blue-400; + color: $blue-400; &:hover, &:focus, &:active { - background-color: rgba($blue-normal, 0.1); - border-color: $blue-normal; + background-color: $blue-50; + border-color: $blue-600; + color: $blue-600; + + svg { + fill: $blue-600; + } } } diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss index 1a983d8c9ef..703c5fc8869 100644 --- a/app/assets/stylesheets/pages/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -74,7 +74,6 @@ display: inline; a { - color: $blue-dark; text-decoration: none; } } diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index efa47be9a73..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; @@ -582,54 +568,55 @@ pre.light-well { /* * Projects list rendered on dashboard and user page */ - .projects-list { @include basic-list; + display: flex; + flex-direction: column; .project-row { - border-color: $white-normal; - - .project-full-name { - @include str-truncated; + display: flex; + align-items: center; + } - @media (max-width: $screen-xs-max) { - max-width: 50%; - } - } + h3 { + font-size: $gl-font-size; + } - .controls { - line-height: $list-text-height; + a { + color: $gl-text-color; + } - .badge { - @media (max-width: $screen-xs-max) { - display: none; - } - } + .avatar-container, + .controls { + flex: 0 0 auto; + } - a:hover { - text-decoration: none; - } + .avatar-container { + align-self: flex-start; + } - > span { - margin-left: 10px; - } + .project-details { + min-width: 0; - svg { - position: relative; - top: 2px; - } + p, + .commit-row-message { + @include str-truncated(100%); + margin-bottom: 0; } + } - .description p { - @media (max-width: $screen-xs-max) { - max-width: 50%; - } - } + .controls { + margin-left: auto; } - .bottom { - padding-top: $gl-padding; - padding-bottom: 0; + .ci-status-link { + display: inline-block; + line-height: 17px; + vertical-align: middle; + + &:hover { + text-decoration: none; + } } } diff --git a/app/assets/stylesheets/pages/sherlock.scss b/app/assets/stylesheets/pages/sherlock.scss index bed6470dbd3..23a9c2ada80 100644 --- a/app/assets/stylesheets/pages/sherlock.scss +++ b/app/assets/stylesheets/pages/sherlock.scss @@ -28,6 +28,6 @@ table .sherlock-code { } .sherlock-line-samples-table .slow { - color: $red-light; + color: $red-500; font-weight: bold; } diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss index 6f31d4ed789..4a284247143 100644 --- a/app/assets/stylesheets/pages/status.scss +++ b/app/assets/stylesheets/pages/status.scss @@ -21,42 +21,41 @@ &.ci-failed, &.ci-failed_with_warnings { - color: $gl-danger; - border-color: $gl-danger; + color: $red-500; + border-color: $red-500; &:not(span):hover { - background-color: rgba($gl-danger, .07); + background-color: $red-50; + color: $red-600; + border-color: $red-600; + + svg { + fill: $red-600; + } } svg { - fill: $gl-danger; + fill: $red-500; } } &.ci-success, &.ci-success_with_warnings { - color: $gl-success; - border-color: $gl-success; + color: $green-600; + border-color: $green-500; &:not(span):hover { - background-color: rgba($gl-success, .07); - } - - svg { - fill: $gl-success; - } - } - - &.ci-info { - color: $gl-info; - border-color: $gl-info; + background-color: $green-50; + color: $green-700; + border-color: $green-600; - &:not(span):hover { - background-color: rgba($gl-info, .07); + svg { + fill: $green-600; + } } svg { - fill: $gl-info; + fill: $green-500; } } @@ -75,28 +74,41 @@ } &.ci-pending { - color: $gl-warning; - border-color: $gl-warning; + color: $orange-600; + border-color: $orange-500; &:not(span):hover { - background-color: rgba($gl-warning, .07); + background-color: $orange-50; + color: $orange-700; + border-color: $orange-600; + + svg { + fill: $orange-600; + } } svg { - fill: $gl-warning; + fill: $orange-500; } } + &.ci-info, &.ci-running { - color: $blue-normal; - border-color: $blue-normal; + color: $blue-500; + border-color: $blue-500; &:not(span):hover { - background-color: rgba($blue-normal, .07); + background-color: $blue-50; + color: $blue-600; + border-color: $blue-600; + + svg { + fill: $blue-600; + } } svg { - fill: $blue-normal; + fill: $blue-500; } } 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/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb index 5053b937c02..bd3f51fc658 100644 --- a/app/helpers/milestones_helper.rb +++ b/app/helpers/milestones_helper.rb @@ -89,10 +89,12 @@ module MilestonesHelper content = time_ago.gsub(/\d+/) { |match| "<strong>#{match}</strong>" } content.slice!("about ") content << " remaining" + content.html_safe elsif milestone.start_date && milestone.start_date.past? days = milestone.elapsed_days content = content_tag(:strong, days) content << " #{'day'.pluralize(days)} elapsed" + content.html_safe end end diff --git a/app/helpers/namespaces_helper.rb b/app/helpers/namespaces_helper.rb index 2e3a15bc1b9..7f656b8caae 100644 --- a/app/helpers/namespaces_helper.rb +++ b/app/helpers/namespaces_helper.rb @@ -6,7 +6,13 @@ module NamespacesHelper def namespaces_options(selected = :current_user, display_path: false, extra_group: nil) groups = current_user.owned_groups + current_user.masters_groups - groups << extra_group if extra_group && !Group.exists?(name: extra_group.name) + unless extra_group.nil? || extra_group.is_a?(Group) + extra_group = Group.find(extra_group) if Namespace.find(extra_group).kind == 'group' + end + + if extra_group && extra_group.is_a?(Group) && (!Group.exists?(name: extra_group.name) || Ability.allowed?(current_user, :read_group, extra_group)) + groups |= [extra_group] + end users = [current_user.namespace] diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb index a8f167cbff2..991fd949b94 100644 --- a/app/helpers/nav_helper.rb +++ b/app/helpers/nav_helper.rb @@ -31,7 +31,11 @@ module NavHelper end def layout_nav_class - "page-with-layout-nav" if defined?(nav) && nav + class_name = '' + class_name << " page-with-layout-nav" if defined?(nav) && nav + class_name << " page-with-sub-nav" if content_for?(:sub_nav) + + class_name end def nav_control_class 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/merge_request.rb b/app/models/merge_request.rb index 4759829a15c..5ff83944d8c 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -154,8 +154,10 @@ class MergeRequest < ActiveRecord::Base # # Returns an ActiveRecord::Relation. def self.in_projects(relation) - source = where(source_project_id: relation).select(:id) - target = where(target_project_id: relation).select(:id) + # unscoping unnecessary conditions that'll be applied + # when executing `where("merge_requests.id IN (#{union.to_sql})")` + source = unscoped.where(source_project_id: relation).select(:id) + target = unscoped.where(target_project_id: relation).select(:id) union = Gitlab::SQL::Union.new([source, target]) where("merge_requests.id IN (#{union.to_sql})") diff --git a/app/models/project.rb b/app/models/project.rb index 04641dd58a0..f1bba56d32c 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -314,20 +314,15 @@ class Project < ActiveRecord::Base ntable = Namespace.arel_table pattern = "%#{query}%" - projects = select(:id).where( + # unscoping unnecessary conditions that'll be applied + # when executing `where("projects.id IN (#{union.to_sql})")` + projects = unscoped.select(:id).where( ptable[:path].matches(pattern). or(ptable[:name].matches(pattern)). or(ptable[:description].matches(pattern)) ) - # We explicitly remove any eager loading clauses as they're: - # - # 1. Not needed by this query - # 2. Combined with .joins(:namespace) lead to all columns from the - # projects & namespaces tables being selected, leading to a SQL error - # due to the columns of all UNION'd queries no longer being the same. - namespaces = select(:id). - except(:includes). + namespaces = unscoped.select(:id). joins(:namespace). where(ntable[:name].matches(pattern)) 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 5d19d873f43..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, @@ -115,7 +116,9 @@ class User < ActiveRecord::Base validates :notification_email, email: true, if: ->(user) { user.notification_email != user.email } validates :public_email, presence: true, uniqueness: true, email: true, allow_blank: true validates :bio, length: { maximum: 255 }, allow_blank: true - validates :projects_limit, presence: true, numericality: { greater_than_or_equal_to: 0 } + validates :projects_limit, + presence: true, + numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: Gitlab::Database::MAX_INT_VALUE } validates :username, namespace: true, presence: true, @@ -126,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? } @@ -139,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 @@ -384,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 @@ -399,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 @@ -797,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/admin/users/_access_levels.html.haml b/app/views/admin/users/_access_levels.html.haml index 7855239dfe5..794aaec89bd 100644 --- a/app/views/admin/users/_access_levels.html.haml +++ b/app/views/admin/users/_access_levels.html.haml @@ -2,7 +2,7 @@ %legend Access .form-group = f.label :projects_limit, class: 'control-label' - .col-sm-10= f.number_field :projects_limit, min: 0, class: 'form-control' + .col-sm-10= f.number_field :projects_limit, min: 0, max: Gitlab::Database::MAX_INT_VALUE, class: 'form-control' .form-group = f.label :can_create_group, class: 'control-label' 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/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml index e31fa5fbe95..52d6ebd8a14 100644 --- a/app/views/dashboard/todos/index.html.haml +++ b/app/views/dashboard/todos/index.html.haml @@ -68,12 +68,11 @@ = link_to todos_filter_path(sort: sort_value_oldest_created) do = sort_title_oldest_created - .js-todos-all - if @todos.any? .js-todos-list-container .js-todos-options{ data: { per_page: @todos.limit_value, current_page: @todos.current_page, total_pages: @todos.total_pages } } - .panel.panel-default.panel-small.panel-without-border + .panel.panel-default.panel-without-border.panel-without-margin %ul.content-list.todos-list = render @todos = paginate @todos, theme: "gitlab" 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/_page.html.haml b/app/views/layouts/_page.html.haml index a35a918d501..b7df11681d3 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -3,8 +3,9 @@ .layout-nav .container-fluid = render "layouts/nav/#{nav}" - .content-wrapper{ class: "#{layout_nav_class}" } + - if content_for?(:sub_nav) = yield :sub_nav + .content-wrapper{ class: layout_nav_class } .alert-wrapper = render "layouts/broadcast" = render "layouts/flash" 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/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml index 4192013eab5..b597c7f7a12 100644 --- a/app/views/projects/builds/_sidebar.html.haml +++ b/app/views/projects/builds/_sidebar.html.haml @@ -1,6 +1,6 @@ - builds = @build.pipeline.builds.to_a -%aside.right-sidebar.right-sidebar-expanded.build-sidebar.js-build-sidebar.js-right-sidebar{ data: { "offset-top" => "151", "spy" => "affix" } } +%aside.right-sidebar.right-sidebar-expanded.build-sidebar.js-build-sidebar.js-right-sidebar{ data: { "offset-top" => "153", "spy" => "affix" } } .block.build-sidebar-header.visible-xs-block.visible-sm-block.append-bottom-default Job %strong ##{@build.id} diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index 307010edb58..d5fe771613c 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -1,6 +1,6 @@ - @no_container = true - page_title "#{@build.name} (##{@build.id})", "Jobs" -= render "projects/pipelines/head", build_subnav: true += render "projects/pipelines/head" %div{ class: container_class } .build-page diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index d129da943f8..34a1214a350 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -23,7 +23,7 @@ - if current_user.can_select_namespace? .input-group-addon = root_url - = f.select :namespace_id, namespaces_options(namespace_id_from(params) || :current_user, display_path: true), {}, {class: 'select2 js-select-namespace', tabindex: 1} + = f.select :namespace_id, namespaces_options(namespace_id_from(params) || :current_user, display_path: true, extra_group: namespace_id_from(params)), {}, { class: 'select2 js-select-namespace', tabindex: 1} - else .input-group-addon.static-namespace diff --git a/app/views/projects/pipelines/_head.html.haml b/app/views/projects/pipelines/_head.html.haml index a5acb7ac4a5..bc57f7f1c46 100644 --- a/app/views/projects/pipelines/_head.html.haml +++ b/app/views/projects/pipelines/_head.html.haml @@ -1,7 +1,7 @@ = content_for :sub_nav do .scrolling-tabs-container.sub-nav-scroll = render 'shared/nav_scroll' - .nav-links.sub-nav.scrolling-tabs{ class: ('build' if local_assigns.fetch(:build_subnav, false)) } + .nav-links.sub-nav.scrolling-tabs %ul{ class: (container_class) } - if project_nav_tab? :pipelines = nav_link(path: 'pipelines#index', controller: :pipelines) do @@ -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/projects/wikis/_sidebar.html.haml b/app/views/projects/wikis/_sidebar.html.haml index cfacb9e7b66..713b758727e 100644 --- a/app/views/projects/wikis/_sidebar.html.haml +++ b/app/views/projects/wikis/_sidebar.html.haml @@ -1,4 +1,4 @@ -%aside.right-sidebar.right-sidebar-expanded.wiki-sidebar.js-wiki-sidebar.js-right-sidebar{ data: { "offset-top" => "101", "spy" => "affix" } } +%aside.right-sidebar.right-sidebar-expanded.wiki-sidebar.js-wiki-sidebar.js-right-sidebar{ data: { "offset-top" => "102", "spy" => "affix" } } .block.wiki-sidebar-header.append-bottom-default %a.gutter-toggle.pull-right.visible-xs-block.visible-sm-block.js-sidebar-wiki-toggle{ href: "#" } = icon('angle-double-right') 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/groups/_group.html.haml b/app/views/shared/groups/_group.html.haml index a95020a9be8..09f946f1d88 100644 --- a/app/views/shared/groups/_group.html.haml +++ b/app/views/shared/groups/_group.html.haml @@ -17,7 +17,7 @@ .stats %span = icon('bookmark') - = number_with_delimiter(group.projects.count) + = number_with_delimiter(group.projects.non_archived.count) %span = icon('users') diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 25a4aec0a38..f19f362f514 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -3,7 +3,7 @@ = page_specific_javascript_bundle_tag('common_vue') = page_specific_javascript_bundle_tag('issuable') -%aside.right-sidebar.js-right-sidebar{ data: { "offset-top" => "101", "spy" => "affix" }, class: sidebar_gutter_collapsed_class, 'aria-live' => 'polite' } +%aside.right-sidebar.js-right-sidebar{ data: { "offset-top" => "102", "spy" => "affix" }, class: sidebar_gutter_collapsed_class, 'aria-live' => 'polite' } .issuable-sidebar - can_edit_issuable = can?(current_user, :"admin_#{issuable.to_ability_name}", @project) .block.issuable-sidebar-header @@ -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/shared/projects/_list.html.haml b/app/views/shared/projects/_list.html.haml index c57282c5742..c0699b13719 100644 --- a/app/views/shared/projects/_list.html.haml +++ b/app/views/shared/projects/_list.html.haml @@ -10,7 +10,7 @@ .js-projects-list-holder - if projects.any? - %ul.projects-list.content-list + %ul.projects-list - projects.each_with_index do |project, i| - css_class = (i >= projects_limit) ? 'hide' : nil = render "shared/projects/project", project: project, skip_namespace: skip_namespace, diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml index df21857e1ad..059aeebaf34 100644 --- a/app/views/shared/projects/_project.html.haml +++ b/app/views/shared/projects/_project.html.haml @@ -10,44 +10,44 @@ %li.project-row{ class: css_class } = cache(cache_key) do + - if avatar + .avatar-container.s40 + - if use_creator_avatar + = image_tag avatar_icon(project.creator.email, 40), class: "avatar s40", alt:'' + - else + = project_icon(project, alt: '', class: 'avatar project-avatar s40') + .project-details + %h3.prepend-top-0.append-bottom-0 + = link_to project_path(project), class: dom_class(project) do + %span.project-full-name + %span.namespace-name + - if project.namespace && !skip_namespace + = project.namespace.human_name + \/ + %span.project-name.filter-title + = project.name + + - if show_last_commit_as_description + .description.prepend-top-5 + = link_to_gfm project.commit.title, namespace_project_commit_path(project.namespace, project, project.commit), + class: "commit-row-message" + - elsif project.description.present? + .description.prepend-top-5 + = markdown_field(project, :description) + .controls - if project.archived - %span.label.label-warning archived + %span.prepend-left-10.label.label-warning archived - if project.pipeline_status.has_status? - %span + %span.prepend-left-10 = render_project_pipeline_status(project.pipeline_status) - if forks - %span + %span.prepend-left-10 = icon('code-fork') = number_with_delimiter(project.forks_count) - if stars - %span + %span.prepend-left-10 = icon('star') = number_with_delimiter(project.star_count) - %span.visibility-icon.has-tooltip{ data: { container: 'body', placement: 'left' }, title: visibility_icon_description(project) } + %span.prepend-left-10.visibility-icon.has-tooltip{ data: { container: 'body', placement: 'left' }, title: visibility_icon_description(project) } = visibility_level_icon(project.visibility_level, fw: true) - - .title - = link_to project_path(project), class: dom_class(project) do - - if avatar - .dash-project-avatar - .avatar-container.s40 - - if use_creator_avatar - = image_tag avatar_icon(project.creator.email, 40), class: "avatar s40", alt:'' - - else - = project_icon(project, alt: '', class: 'avatar project-avatar s40') - %span.project-full-name - %span.namespace-name - - if project.namespace && !skip_namespace - = project.namespace.human_name - \/ - %span.project-name.filter-title - = project.name - - - if show_last_commit_as_description - .description - = link_to_gfm project.commit.title, namespace_project_commit_path(project.namespace, project, project.commit), - class: "commit-row-message" - - elsif project.description.present? - .description - = markdown_field(project, :description) 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/23862-fix-group-project-count.yml b/changelogs/unreleased/23862-fix-group-project-count.yml new file mode 100644 index 00000000000..7b2e9f9bfa6 --- /dev/null +++ b/changelogs/unreleased/23862-fix-group-project-count.yml @@ -0,0 +1,4 @@ +--- +title: Adding non_archived scope for counting projects +merge_request: 8305 +author: Naveen Kumar 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/27910-admin-can-create-project-in-all-groups.yml b/changelogs/unreleased/27910-admin-can-create-project-in-all-groups.yml new file mode 100644 index 00000000000..40fd8dacc82 --- /dev/null +++ b/changelogs/unreleased/27910-admin-can-create-project-in-all-groups.yml @@ -0,0 +1,4 @@ +--- +title: Allow admin to view all namespaces +merge_request: +author: George Andrinopoulos diff --git a/changelogs/unreleased/28614-harmonious-color-palette.yml b/changelogs/unreleased/28614-harmonious-color-palette.yml new file mode 100644 index 00000000000..b436e7129a4 --- /dev/null +++ b/changelogs/unreleased/28614-harmonious-color-palette.yml @@ -0,0 +1,4 @@ +--- +title: Update color palette to a more harmonious and consistent one. +merge_request: 10154 +author: diff --git a/changelogs/unreleased/28634-todos-margin.yml b/changelogs/unreleased/28634-todos-margin.yml new file mode 100644 index 00000000000..f4221ce4350 --- /dev/null +++ b/changelogs/unreleased/28634-todos-margin.yml @@ -0,0 +1,4 @@ +--- +title: Remove extra margin at bottom of todos page +merge_request: +author: diff --git a/changelogs/unreleased/29116-maxint-error.yml b/changelogs/unreleased/29116-maxint-error.yml new file mode 100644 index 00000000000..06e976617d5 --- /dev/null +++ b/changelogs/unreleased/29116-maxint-error.yml @@ -0,0 +1,4 @@ +--- +title: Fix projects_limit RangeError on user create +merge_request: +author: Alexander Randa diff --git a/changelogs/unreleased/29897-remove-force-scroll-for-mr-changes-diff.yml b/changelogs/unreleased/29897-remove-force-scroll-for-mr-changes-diff.yml new file mode 100644 index 00000000000..d1da96096f8 --- /dev/null +++ b/changelogs/unreleased/29897-remove-force-scroll-for-mr-changes-diff.yml @@ -0,0 +1,4 @@ +--- +title: Remove forced scroll into view when switching to Changes MR tab +merge_request: +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/add-issue-modal-loading-indicator.yml b/changelogs/unreleased/add-issue-modal-loading-indicator.yml new file mode 100644 index 00000000000..5398399c018 --- /dev/null +++ b/changelogs/unreleased/add-issue-modal-loading-indicator.yml @@ -0,0 +1,4 @@ +--- +title: Shows loading icon in issue boards modal when changing filters +merge_request: +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/projects-list-line-breaks.yml b/changelogs/unreleased/projects-list-line-breaks.yml new file mode 100644 index 00000000000..179d7081293 --- /dev/null +++ b/changelogs/unreleased/projects-list-line-breaks.yml @@ -0,0 +1,4 @@ +--- +title: Fixed projects list lines breaking +merge_request: +author: 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/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. ![Triggers page overview](img/triggers_page.png) +## 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). ![Tooltip usage](img/tooltip-usage.png) ### 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. ![Tooltip placement location](img/tooltip-placement.png) 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/features/steps/project/issues/award_emoji.rb b/features/steps/project/issues/award_emoji.rb index 1762d5bdf95..e55dc2913c3 100644 --- a/features/steps/project/issues/award_emoji.rb +++ b/features/steps/project/issues/award_emoji.rb @@ -45,7 +45,7 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps end step 'I have new comment with emoji added' do - expect(page).to have_selector ".emoji[title=':smile:']" + expect(page).to have_selector 'gl-emoji[data-name="smile"]' end step 'I have award added' do diff --git a/features/steps/shared/diff_note.rb b/features/steps/shared/diff_note.rb index 11fa85ed2fe..071aa2e3eff 100644 --- a/features/steps/shared/diff_note.rb +++ b/features/steps/shared/diff_note.rb @@ -196,7 +196,7 @@ module SharedDiffNote step 'The diff comment preview tab should display rendered Markdown' do page.within(diff_file_selector) do find('.js-md-preview-button').click - expect(find('.js-md-preview')).to have_css('img.emoji', visible: true) + expect(find('.js-md-preview')).to have_css('gl-emoji', visible: true) end end @@ -210,7 +210,7 @@ module SharedDiffNote step 'I should see a diff comment with an emoji image' do page.within("#{diff_file_selector} .note") do - expect(page).to have_xpath("//img[@alt=':smile:']") + expect(page).to have_xpath("//gl-emoji[@data-name='smile']") end end diff --git a/features/steps/shared/markdown.rb b/features/steps/shared/markdown.rb index a036d9b884f..875d27d9383 100644 --- a/features/steps/shared/markdown.rb +++ b/features/steps/shared/markdown.rb @@ -40,7 +40,7 @@ module SharedMarkdown step 'The Markdown preview tab should display rendered Markdown' do page.within('.gfm-form') do find('.js-md-preview-button').click - expect(find('.js-md-preview')).to have_css('img.emoji', visible: true) + expect(find('.js-md-preview')).to have_css('gl-emoji', visible: true) end end diff --git a/features/steps/shared/note.rb b/features/steps/shared/note.rb index 1870f6bc0c3..fd925e0d447 100644 --- a/features/steps/shared/note.rb +++ b/features/steps/shared/note.rb @@ -95,7 +95,7 @@ module SharedNote step 'The comment preview tab should be display rendered Markdown' do page.within(".js-main-target-form") do find('.js-md-preview-button').click - expect(find('.js-md-preview')).to have_css('img.emoji', visible: true) + expect(find('.js-md-preview')).to have_css('gl-emoji', visible: true) end end 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 e0fdf3f3d64..496ee0bdcb0 100644 --- a/lib/gitlab/ee_compat_check.rb +++ b/lib/gitlab/ee_compat_check.rb @@ -5,35 +5,44 @@ module Gitlab CE_REPO = 'https://gitlab.com/gitlab-org/gitlab-ce.git'.freeze EE_REPO = 'https://gitlab.com/gitlab-org/gitlab-ee.git'.freeze CHECK_DIR = Rails.root.join('ee_compat_check') - MAX_FETCH_DEPTH = 500 IGNORED_FILES_REGEX = /(VERSION|CHANGELOG\.md:\d+)/.freeze - - attr_reader :repo_dir, :patches_dir, :ce_repo, :ce_branch + PLEASE_READ_THIS_BANNER = %Q{ + ============================================================ + ===================== PLEASE READ THIS ===================== + ============================================================ + }.freeze + THANKS_FOR_READING_BANNER = %Q{ + ============================================================ + ==================== THANKS FOR READING ==================== + ============================================================\n + }.freeze + + attr_reader :ee_repo_dir, :patches_dir, :ce_repo, :ce_branch, :ee_branch_found + attr_reader :failed_files def initialize(branch:, ce_repo: CE_REPO) - @repo_dir = CHECK_DIR.join('repo') + @ee_repo_dir = CHECK_DIR.join('ee-repo') @patches_dir = CHECK_DIR.join('patches') @ce_branch = branch @ce_repo = ce_repo end def check - ensure_ee_repo ensure_patches_dir - generate_patch(ce_branch, ce_patch_full_path) - Dir.chdir(repo_dir) do - step("In the #{repo_dir} directory") + ensure_ee_repo + Dir.chdir(ee_repo_dir) do + step("In the #{ee_repo_dir} directory") status = catch(:halt_check) do ce_branch_compat_check! - delete_ee_branch_locally! + delete_ee_branches_locally! ee_branch_presence_check! ee_branch_compat_check! end - delete_ee_branch_locally! + delete_ee_branches_locally! if status.nil? true @@ -46,11 +55,13 @@ module Gitlab private def ensure_ee_repo - if Dir.exist?(repo_dir) - step("#{repo_dir} already exists") + if Dir.exist?(ee_repo_dir) + step("#{ee_repo_dir} already exists") else - cmd = %W[git clone --branch master --single-branch --depth 200 #{EE_REPO} #{repo_dir}] - step("Cloning #{EE_REPO} into #{repo_dir}", cmd) + step( + "Cloning #{EE_REPO} into #{ee_repo_dir}", + %W[git clone --branch master --single-branch --depth=200 #{EE_REPO} #{ee_repo_dir}] + ) end end @@ -61,23 +72,18 @@ module Gitlab def generate_patch(branch, patch_path) FileUtils.rm(patch_path, force: true) - depth = 0 - loop do - depth += 50 - cmd = %W[git fetch --depth #{depth} origin --prune +refs/heads/master:refs/remotes/origin/master] - Gitlab::Popen.popen(cmd) - _, status = Gitlab::Popen.popen(%w[git merge-base FETCH_HEAD HEAD]) + find_merge_base_with_master(branch: branch) - raise "#{branch} is too far behind master, please rebase it!" if depth >= MAX_FETCH_DEPTH - break if status.zero? - end + step( + "Generating the patch against origin/master in #{patch_path}", + %w[git format-patch origin/master --stdout] + ) do |output, status| + throw(:halt_check, :ko) unless status.zero? - step("Generating the patch against master in #{patch_path}") - output, status = Gitlab::Popen.popen(%w[git format-patch FETCH_HEAD --stdout]) - throw(:halt_check, :ko) unless status.zero? + File.write(patch_path, output) - File.write(patch_path, output) - throw(:halt_check, :ko) unless File.exist?(patch_path) + throw(:halt_check, :ko) unless File.exist?(patch_path) + end end def ce_branch_compat_check! @@ -88,9 +94,17 @@ module Gitlab end def ee_branch_presence_check! - status = step("Fetching origin/#{ee_branch}", %W[git fetch origin #{ee_branch}]) + _, status = step("Fetching origin/#{ee_branch_prefix}", %W[git fetch origin #{ee_branch_prefix}]) - unless status.zero? + if status.zero? + @ee_branch_found = ee_branch_prefix + else + _, status = step("Fetching origin/#{ee_branch_suffix}", %W[git fetch origin #{ee_branch_suffix}]) + end + + if status.zero? + @ee_branch_found = ee_branch_suffix + else puts puts ce_branch_doesnt_apply_cleanly_and_no_ee_branch_msg @@ -99,9 +113,9 @@ module Gitlab end def ee_branch_compat_check! - step("Checking out origin/#{ee_branch}", %W[git checkout -b #{ee_branch} FETCH_HEAD]) + step("Checking out origin/#{ee_branch_found}", %W[git checkout -b #{ee_branch_found} FETCH_HEAD]) - generate_patch(ee_branch, ee_patch_full_path) + generate_patch(ee_branch_found, ee_patch_full_path) unless check_patch(ee_patch_full_path).zero? puts @@ -111,41 +125,77 @@ module Gitlab end puts - puts applies_cleanly_msg(ee_branch) + puts applies_cleanly_msg(ee_branch_found) end def check_patch(patch_path) step("Checking out master", %w[git checkout master]) - step("Reseting to latest master", %w[git reset --hard origin/master]) - - step("Checking if #{patch_path} applies cleanly to EE/master") - output, status = Gitlab::Popen.popen(%W[git apply --check --3way #{patch_path}]) - - unless status.zero? - failed_files = output.lines.reduce([]) do |memo, line| - if line.start_with?('error: patch failed:') - file = line.sub(/\Aerror: patch failed: /, '') - memo << file unless file =~ IGNORED_FILES_REGEX + step("Resetting to latest master", %w[git reset --hard origin/master]) + step( + "Checking if #{patch_path} applies cleanly to EE/master", + %W[git apply --check --3way #{patch_path}] + ) do |output, status| + unless status.zero? + @failed_files = output.lines.reduce([]) do |memo, line| + if line.start_with?('error: patch failed:') + file = line.sub(/\Aerror: patch failed: /, '') + memo << file unless file =~ IGNORED_FILES_REGEX + end + memo end - memo + + status = 0 if failed_files.empty? end - if failed_files.empty? - status = 0 - else - puts "\nConflicting files:" - failed_files.each do |file| - puts " - #{file}" - end + status + end + end + + def delete_ee_branches_locally! + command(%w[git checkout master]) + command(%W[git branch --delete --force #{ee_branch_prefix}]) + command(%W[git branch --delete --force #{ee_branch_suffix}]) + end + + def merge_base_found? + step( + "Finding merge base with master", + %w[git merge-base origin/master HEAD] + ) do |output, status| + if status.zero? + puts "Merge base was found: #{output}" + true end end + end + + def find_merge_base_with_master(branch:) + return if merge_base_found? + + # Start with (Math.exp(3).to_i = 20) until (Math.exp(6).to_i = 403) + # In total we go (20 + 54 + 148 + 403 = 625) commits deeper + depth = 20 + success = + (3..6).any? do |factor| + depth += Math.exp(factor).to_i + # Repository is initially cloned with a depth of 20 so we need to fetch + # deeper in the case the branch has more than 20 commits on top of master + fetch(branch: branch, depth: depth) + fetch(branch: 'master', depth: depth) + + merge_base_found? + end - status + raise "\n#{branch} is too far behind master, please rebase it!\n" unless success end - def delete_ee_branch_locally! - command(%w[git checkout master]) - step("Deleting the local #{ee_branch} branch", %W[git branch -D #{ee_branch}]) + def fetch(branch:, depth:) + step( + "Fetching deeper...", + %W[git fetch --depth=#{depth} --prune origin +refs/heads/#{branch}:refs/remotes/origin/#{branch}] + ) do |output, status| + raise "Fetch failed: #{output}" unless status.zero? + end end def ce_patch_name @@ -156,12 +206,16 @@ module Gitlab @ce_patch_full_path ||= patches_dir.join(ce_patch_name) end - def ee_branch - @ee_branch ||= "#{ce_branch}-ee" + def ee_branch_suffix + @ee_branch_suffix ||= "#{ce_branch}-ee" + end + + def ee_branch_prefix + @ee_branch_prefix ||= "ee-#{ce_branch}" 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 @@ -178,98 +232,125 @@ module Gitlab if cmd start = Time.now puts "\n$ #{cmd.join(' ')}" - status = command(cmd) - puts "\nFinished in #{Time.now - start} seconds" - status + + output, status = command(cmd) + puts "\n==> Finished in #{Time.now - start} seconds" + + if block_given? + yield(output, status) + else + [output, status] + end end end def command(cmd) - output, status = Gitlab::Popen.popen(cmd) - puts output - - status + Gitlab::Popen.popen(cmd) end def applies_cleanly_msg(branch) - <<-MSG.strip_heredoc - ================================================================= + %Q{ + #{PLEASE_READ_THIS_BANNER} 🎉 Congratulations!! 🎉 - The #{branch} branch applies cleanly to EE/master! + The `#{branch}` branch applies cleanly to EE/master! - Much ❤️!! - =================================================================\n - MSG + Much ❤️! For more information, see + https://docs.gitlab.com/ce/development/limit_ee_conflicts.html#check-the-rake-ee_compat_check-in-your-merge-requests + #{THANKS_FOR_READING_BANNER} + } end def ce_branch_doesnt_apply_cleanly_and_no_ee_branch_msg - <<-MSG.strip_heredoc - ================================================================= + %Q{ + #{PLEASE_READ_THIS_BANNER} 💥 Oh no! 💥 - The #{ce_branch} branch does not apply cleanly to the current - EE/master, and no #{ee_branch} branch was found in the EE repository. + The `#{ce_branch}` branch does not apply cleanly to the current + EE/master, and no `#{ee_branch_prefix}` or `#{ee_branch_suffix}` branch + was found in the EE repository. - Please create a #{ee_branch} branch that includes changes from - #{ce_branch} but also specific changes than can be applied cleanly - to EE/master. + #{conflicting_files_msg} + + We advise you to create a `#{ee_branch_prefix}` or `#{ee_branch_suffix}` + branch that includes changes from `#{ce_branch}` but also specific changes + than can be applied cleanly to EE/master. In some cases, the conflicts + are trivial and you can ignore the warning from this job. As always, + use your best judgment! There are different ways to create such branch: - 1. Create a new branch based on the CE branch and rebase it on top of EE/master + 1. Create a new branch from master and cherry-pick your CE commits # In the EE repo - $ git fetch #{ce_repo} #{ce_branch} - $ git checkout -b #{ee_branch} FETCH_HEAD - - # You can squash the #{ce_branch} commits into a single "Port of #{ce_branch} to EE" commit - # before rebasing to limit the conflicts-resolving steps during the rebase $ git fetch origin - $ git rebase origin/master + $ git checkout -b #{ee_branch_prefix} origin/master + $ git fetch #{ce_repo} #{ce_branch} + $ git cherry-pick SHA # Repeat for all the commits you want to pick - At this point you will likely have conflicts. - Solve them, and continue/finish the rebase. + You can squash the `#{ce_branch}` commits into a single "Port of #{ce_branch} to EE" commit. - You can squash the #{ce_branch} commits into a single "Port of #{ce_branch} to EE". + 2. Apply your branch's patch to EE - 2. Create a new branch from master and cherry-pick your CE commits + # In the CE repo + $ git fetch origin master + $ git format-patch origin/master --stdout > #{ce_branch}.patch # In the EE repo - $ git fetch origin - $ git checkout -b #{ee_branch} origin/master - $ git fetch #{ce_repo} #{ce_branch} - $ git cherry-pick SHA # Repeat for all the commits you want to pick + $ git fetch origin master + $ git checkout -b #{ee_branch_prefix} origin/master + $ git apply --3way path/to/#{ce_branch}.patch + + At this point you might have conflicts such as: - You can squash the #{ce_branch} commits into a single "Port of #{ce_branch} to EE" commit. + error: patch failed: lib/gitlab/ee_compat_check.rb:5 + Falling back to three-way merge... + Applied patch to 'lib/gitlab/ee_compat_check.rb' with conflicts. + U lib/gitlab/ee_compat_check.rb - Don't forget to push your branch to #{EE_REPO}: + Resolve them, stage the changes and commit them. + + ⚠️ Don't forget to push your branch to gitlab-ee: # In the EE repo - $ git push origin #{ee_branch} + $ git push origin #{ee_branch_prefix} + + ⚠️ Also, don't forget to create a new merge request on gitlab-ce and + cross-link it with the CE merge request. - You can then retry this failed build, and hopefully it should pass. + Once this is done, you can retry this failed build, and it should pass. - Stay 💪 ! - =================================================================\n - MSG + Stay 💪 ! For more information, see + https://docs.gitlab.com/ce/development/limit_ee_conflicts.html#check-the-rake-ee_compat_check-in-your-merge-requests + #{THANKS_FOR_READING_BANNER} + } end def ee_branch_doesnt_apply_cleanly_msg - <<-MSG.strip_heredoc - ================================================================= + %Q{ + #{PLEASE_READ_THIS_BANNER} 💥 Oh no! 💥 - The #{ce_branch} does not apply cleanly to the current - EE/master, and even though a #{ee_branch} branch exists in the EE - repository, it does not apply cleanly either to EE/master! + The `#{ce_branch}` does not apply cleanly to the current EE/master, and + even though a `#{ee_branch_found}` branch + exists in the EE repository, it does not apply cleanly either to + EE/master! + + #{conflicting_files_msg} - Please update the #{ee_branch}, push it again to #{EE_REPO}, and + Please update the `#{ee_branch_found}`, push it again to gitlab-ee, and retry this build. - Stay 💪 ! - =================================================================\n - MSG + Stay 💪 ! For more information, see + https://docs.gitlab.com/ce/development/limit_ee_conflicts.html#check-the-rake-ee_compat_check-in-your-merge-requests + #{THANKS_FOR_READING_BANNER} + } + end + + def conflicting_files_msg + failed_files.reduce("The conflicts detected were as follows:\n") do |memo, file| + memo << "\n - #{file}" + end end end end 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/admin/admin_broadcast_messages_spec.rb b/spec/features/admin/admin_broadcast_messages_spec.rb index bc957ec72e1..d6c63f66a9b 100644 --- a/spec/features/admin/admin_broadcast_messages_spec.rb +++ b/spec/features/admin/admin_broadcast_messages_spec.rb @@ -45,7 +45,7 @@ feature 'Admin Broadcast Messages', feature: true do page.within('.broadcast-message-preview') do expect(page).to have_selector('strong', text: 'Markdown') - expect(page).to have_selector('img.emoji') + expect(page).to have_selector('gl-emoji[data-name="tada"]') end end 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/explore/groups_list_spec.rb b/spec/features/explore/groups_list_spec.rb index 773ae4b38bc..9daaaa8e555 100644 --- a/spec/features/explore/groups_list_spec.rb +++ b/spec/features/explore/groups_list_spec.rb @@ -7,6 +7,7 @@ describe 'Explore Groups page', js: true, feature: true do let!(:group) { create(:group) } let!(:public_group) { create(:group, :public) } let!(:private_group) { create(:group, :private) } + let!(:empty_project) { create(:empty_project, group: public_group) } before do group.add_owner(user) @@ -43,4 +44,23 @@ describe 'Explore Groups page', js: true, feature: true do expect(page).not_to have_content(private_group.full_name) expect(page.all('.js-groups-list-holder .content-list li').length).to eq 2 end + + it 'shows non-archived projects count' do + # Initially project is not archived + expect(find('.js-groups-list-holder .content-list li:first-child .stats span:first-child')).to have_text("1") + + # Archive project + empty_project.archive! + visit explore_groups_path + + # Check project count + expect(find('.js-groups-list-holder .content-list li:first-child .stats span:first-child')).to have_text("0") + + # Unarchive project + empty_project.unarchive! + visit explore_groups_path + + # Check project count + expect(find('.js-groups-list-holder .content-list li:first-child .stats span:first-child')).to have_text("1") + end 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/members/user_requests_access_spec.rb b/spec/features/projects/members/user_requests_access_spec.rb index b64c15e0adc..de25d45f447 100644 --- a/spec/features/projects/members/user_requests_access_spec.rb +++ b/spec/features/projects/members/user_requests_access_spec.rb @@ -61,7 +61,7 @@ feature 'Projects > Members > User requests access', feature: true do click_link('Settings') end - page.within('.page-with-layout-nav .sub-nav') do + page.within('.sub-nav') do click_link('Members') end end 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/helpers/namespaces_helper_spec.rb b/spec/helpers/namespaces_helper_spec.rb new file mode 100644 index 00000000000..e5143a0263d --- /dev/null +++ b/spec/helpers/namespaces_helper_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' + +describe NamespacesHelper, type: :helper do + let!(:admin) { create(:admin) } + let!(:admin_group) { create(:group, :private) } + let!(:user) { create(:user) } + let!(:user_group) { create(:group, :private) } + + before do + admin_group.add_owner(admin) + user_group.add_owner(user) + end + + describe '#namespaces_options' do + it 'returns groups without being a member for admin' do + allow(helper).to receive(:current_user).and_return(admin) + + options = helper.namespaces_options(user_group.id, display_path: true, extra_group: user_group.id) + + expect(options).to include(admin_group.name) + expect(options).to include(user_group.name) + end + + it 'returns only allowed namespaces for user' do + allow(helper).to receive(:current_user).and_return(user) + + options = helper.namespaces_options + + expect(options).not_to include(admin_group.name) + expect(options).to include(user_group.name) + end + end +end 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/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/lib/gitlab/git/blob_snippet_spec.rb b/spec/lib/gitlab/git/blob_snippet_spec.rb index 17d6be470ac..d6d365f6492 100644 --- a/spec/lib/gitlab/git/blob_snippet_spec.rb +++ b/spec/lib/gitlab/git/blob_snippet_spec.rb @@ -3,7 +3,7 @@ require "spec_helper" describe Gitlab::Git::BlobSnippet, seed_helper: true do - describe :data do + describe '#data' do context 'empty lines' do let(:snippet) { Gitlab::Git::BlobSnippet.new('master', nil, nil, nil) } diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb index 8049e2c120d..b883526151e 100644 --- a/spec/lib/gitlab/git/blob_spec.rb +++ b/spec/lib/gitlab/git/blob_spec.rb @@ -5,7 +5,7 @@ require "spec_helper" describe Gitlab::Git::Blob, seed_helper: true do let(:repository) { Gitlab::Git::Repository.new(TEST_REPO_PATH) } - describe :initialize do + describe 'initialize' do let(:blob) { Gitlab::Git::Blob.new(name: 'test') } it 'handles nil data' do @@ -15,7 +15,7 @@ describe Gitlab::Git::Blob, seed_helper: true do end end - describe :find do + describe '.find' do context 'file in subdir' do let(:blob) { Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, "files/ruby/popen.rb") } @@ -101,7 +101,7 @@ describe Gitlab::Git::Blob, seed_helper: true do end end - describe :raw do + describe '.raw' do let(:raw_blob) { Gitlab::Git::Blob.raw(repository, SeedRepo::RubyBlob::ID) } it { expect(raw_blob.id).to eq(SeedRepo::RubyBlob::ID) } it { expect(raw_blob.data[0..10]).to eq("require \'fi") } @@ -222,7 +222,7 @@ describe Gitlab::Git::Blob, seed_helper: true do end end - describe :lfs_pointers do + describe 'lfs_pointers' do context 'file a valid lfs pointer' do let(:blob) do Gitlab::Git::Blob.find( diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb index e1be6784c20..5cf4631fbfc 100644 --- a/spec/lib/gitlab/git/commit_spec.rb +++ b/spec/lib/gitlab/git/commit_spec.rb @@ -65,7 +65,7 @@ describe Gitlab::Git::Commit, seed_helper: true do end context 'Class methods' do - describe :find do + describe '.find' do it "should return first head commit if without params" do expect(Gitlab::Git::Commit.last(repository).id).to eq( repository.raw.head.target.oid @@ -103,7 +103,7 @@ describe Gitlab::Git::Commit, seed_helper: true do end end - describe :last_for_path do + describe '.last_for_path' do context 'no path' do subject { Gitlab::Git::Commit.last_for_path(repository, 'master') } @@ -132,7 +132,7 @@ describe Gitlab::Git::Commit, seed_helper: true do end end - describe "where" do + describe '.where' do context 'path is empty string' do subject do commits = Gitlab::Git::Commit.where( @@ -230,7 +230,7 @@ describe Gitlab::Git::Commit, seed_helper: true do end end - describe :between do + describe '.between' do subject do commits = Gitlab::Git::Commit.between(repository, SeedRepo::Commit::PARENT_ID, SeedRepo::Commit::ID) commits.map { |c| c.id } @@ -243,7 +243,7 @@ describe Gitlab::Git::Commit, seed_helper: true do it { is_expected.not_to include(SeedRepo::FirstCommit::ID) } end - describe :find_all do + describe '.find_all' do context 'max_count' do subject do commits = Gitlab::Git::Commit.find_all( @@ -304,7 +304,7 @@ describe Gitlab::Git::Commit, seed_helper: true do end end - describe :init_from_rugged do + describe '#init_from_rugged' do let(:gitlab_commit) { Gitlab::Git::Commit.new(rugged_commit) } subject { gitlab_commit } @@ -314,7 +314,7 @@ describe Gitlab::Git::Commit, seed_helper: true do end end - describe :init_from_hash do + describe '#init_from_hash' do let(:commit) { Gitlab::Git::Commit.new(sample_commit_hash) } subject { commit } @@ -329,7 +329,7 @@ describe Gitlab::Git::Commit, seed_helper: true do end end - describe :stats do + describe '#stats' do subject { commit.stats } describe '#additions' do @@ -343,25 +343,25 @@ describe Gitlab::Git::Commit, seed_helper: true do end end - describe :to_diff do + describe '#to_diff' do subject { commit.to_diff } it { is_expected.not_to include "From #{SeedRepo::Commit::ID}" } it { is_expected.to include 'diff --git a/files/ruby/popen.rb b/files/ruby/popen.rb'} end - describe :has_zero_stats? do + describe '#has_zero_stats?' do it { expect(commit.has_zero_stats?).to eq(false) } end - describe :to_patch do + describe '#to_patch' do subject { commit.to_patch } it { is_expected.to include "From #{SeedRepo::Commit::ID}" } it { is_expected.to include 'diff --git a/files/ruby/popen.rb b/files/ruby/popen.rb'} end - describe :to_hash do + describe '#to_hash' do let(:hash) { commit.to_hash } subject { hash } @@ -373,7 +373,7 @@ describe Gitlab::Git::Commit, seed_helper: true do end end - describe :diffs do + describe '#diffs' do subject { commit.diffs } it { is_expected.to be_kind_of Gitlab::Git::DiffCollection } @@ -381,7 +381,7 @@ describe Gitlab::Git::Commit, seed_helper: true do it { expect(subject.first).to be_kind_of Gitlab::Git::Diff } end - describe :ref_names do + describe '#ref_names' do let(:commit) { Gitlab::Git::Commit.find(repository, 'master') } subject { commit.ref_names(repository) } diff --git a/spec/lib/gitlab/git/compare_spec.rb b/spec/lib/gitlab/git/compare_spec.rb index f66b68e4218..e28debe1494 100644 --- a/spec/lib/gitlab/git/compare_spec.rb +++ b/spec/lib/gitlab/git/compare_spec.rb @@ -5,7 +5,7 @@ describe Gitlab::Git::Compare, seed_helper: true do let(:compare) { Gitlab::Git::Compare.new(repository, SeedRepo::BigCommit::ID, SeedRepo::Commit::ID, false) } let(:compare_straight) { Gitlab::Git::Compare.new(repository, SeedRepo::BigCommit::ID, SeedRepo::Commit::ID, true) } - describe :commits do + describe '#commits' do subject do compare.commits.map(&:id) end @@ -42,7 +42,7 @@ describe Gitlab::Git::Compare, seed_helper: true do end end - describe :diffs do + describe '#diffs' do subject do compare.diffs.map(&:new_path) end @@ -67,7 +67,7 @@ describe Gitlab::Git::Compare, seed_helper: true do end end - describe :same do + describe '#same' do subject do compare.same end @@ -81,7 +81,7 @@ describe Gitlab::Git::Compare, seed_helper: true do end end - describe :commits_straight do + describe '#commits', 'straight compare' do subject do compare_straight.commits.map(&:id) end @@ -94,7 +94,7 @@ describe Gitlab::Git::Compare, seed_helper: true do it { is_expected.not_to include(SeedRepo::BigCommit::PARENT_ID) } end - describe :diffs_straight do + describe '#diffs', 'straight compare' do subject do compare_straight.diffs.map(&:new_path) end diff --git a/spec/lib/gitlab/git/diff_collection_spec.rb b/spec/lib/gitlab/git/diff_collection_spec.rb index 47bdd7310d5..122c93dcd69 100644 --- a/spec/lib/gitlab/git/diff_collection_spec.rb +++ b/spec/lib/gitlab/git/diff_collection_spec.rb @@ -24,7 +24,7 @@ describe Gitlab::Git::DiffCollection, seed_helper: true do it { is_expected.to be_kind_of ::Array } end - describe :decorate! do + describe '#decorate!' do let(:file_count) { 3 } it 'modifies the array in place' do @@ -302,7 +302,7 @@ describe Gitlab::Git::DiffCollection, seed_helper: true do end end - describe :each do + describe '#each' do context 'when diff are too large' do let(:collection) do Gitlab::Git::DiffCollection.new([{ diff: 'a' * 204800 }]) diff --git a/spec/lib/gitlab/git/tree_spec.rb b/spec/lib/gitlab/git/tree_spec.rb index 688e2a75373..83d2ff8f9b3 100644 --- a/spec/lib/gitlab/git/tree_spec.rb +++ b/spec/lib/gitlab/git/tree_spec.rb @@ -11,7 +11,7 @@ describe Gitlab::Git::Tree, seed_helper: true do it { expect(tree.select(&:file?).size).to eq(10) } it { expect(tree.select(&:submodule?).size).to eq(2) } - describe :dir do + describe '#dir?' do let(:dir) { tree.select(&:dir?).first } it { expect(dir).to be_kind_of Gitlab::Git::Tree } @@ -41,7 +41,7 @@ describe Gitlab::Git::Tree, seed_helper: true do end end - describe :file do + describe '#file?' do let(:file) { tree.select(&:file?).first } it { expect(file).to be_kind_of Gitlab::Git::Tree } @@ -50,21 +50,21 @@ describe Gitlab::Git::Tree, seed_helper: true do it { expect(file.name).to eq('.gitignore') } end - describe :readme do + describe '#readme?' do let(:file) { tree.select(&:readme?).first } it { expect(file).to be_kind_of Gitlab::Git::Tree } it { expect(file.name).to eq('README.md') } end - describe :contributing do + describe '#contributing?' do let(:file) { tree.select(&:contributing?).first } it { expect(file).to be_kind_of Gitlab::Git::Tree } it { expect(file.name).to eq('CONTRIBUTING.md') } end - describe :submodule do + describe '#submodule?' do let(:submodule) { tree.select(&:submodule?).first } it { expect(submodule).to be_kind_of Gitlab::Git::Tree } diff --git a/spec/lib/gitlab/git/util_spec.rb b/spec/lib/gitlab/git/util_spec.rb index 8d43b570e98..bcca4d4c746 100644 --- a/spec/lib/gitlab/git/util_spec.rb +++ b/spec/lib/gitlab/git/util_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::Git::Util do - describe :count_lines do + describe '#count_lines' do [ ["", 0], ["foo", 1], diff --git a/spec/lib/gitlab/ldap/user_spec.rb b/spec/lib/gitlab/ldap/user_spec.rb index 2f3bd4393b7..346cf0d117c 100644 --- a/spec/lib/gitlab/ldap/user_spec.rb +++ b/spec/lib/gitlab/ldap/user_spec.rb @@ -57,7 +57,7 @@ describe Gitlab::LDAP::User, lib: true do end end - describe :find_or_create do + describe 'find or create' do it "finds the user if already existing" do create(:omniauth_user, extern_uid: 'my-uid', provider: 'ldapmain') 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/milestone_spec.rb b/spec/models/milestone_spec.rb index 3cee2b7714f..f3f48f951a8 100644 --- a/spec/models/milestone_spec.rb +++ b/spec/models/milestone_spec.rb @@ -109,7 +109,7 @@ describe Milestone, models: true do it { expect(milestone.percent_complete(user)).to eq(75) } end - describe :items_count do + describe '#is_empty?' do before do milestone.issues << create(:issue, project: project) milestone.issues << create(:closed_issue, project: project) diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 67d48557184..09aa6e9337f 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -163,7 +163,7 @@ describe Namespace, models: true do end end - describe :rm_dir do + describe '#rm_dir', 'callback' do let!(:project) { create(:empty_project, namespace: namespace) } let!(:path) { File.join(Gitlab.config.repositories.storages.default['path'], namespace.full_path) } diff --git a/spec/models/pages_domain_spec.rb b/spec/models/pages_domain_spec.rb index e6a4583a8fb..c6c45d78990 100644 --- a/spec/models/pages_domain_spec.rb +++ b/spec/models/pages_domain_spec.rb @@ -5,7 +5,7 @@ describe PagesDomain, models: true do it { is_expected.to belong_to(:project) } end - describe :validate_domain do + describe 'validate domain' do subject { build(:pages_domain, domain: domain) } context 'is unique' do @@ -75,7 +75,7 @@ describe PagesDomain, models: true do end end - describe :url do + describe '#url' do subject { domain.url } context 'without the certificate' do @@ -91,7 +91,7 @@ describe PagesDomain, models: true do end end - describe :has_matching_key? do + describe '#has_matching_key?' do subject { domain.has_matching_key? } context 'for matching key' do @@ -107,7 +107,7 @@ describe PagesDomain, models: true do end end - describe :has_intermediates? do + describe '#has_intermediates?' do subject { domain.has_intermediates? } context 'for self signed' do @@ -133,7 +133,7 @@ describe PagesDomain, models: true do end end - describe :expired? do + describe '#expired?' do subject { domain.expired? } context 'for valid' do @@ -149,7 +149,7 @@ describe PagesDomain, models: true do end end - describe :subject do + describe '#subject' do let(:domain) { build(:pages_domain, :with_certificate) } subject { domain.subject } @@ -157,7 +157,7 @@ describe PagesDomain, models: true do it { is_expected.to eq('/CN=test-certificate') } end - describe :certificate_text do + describe '#certificate_text' do let(:domain) { build(:pages_domain, :with_certificate) } subject { domain.certificate_text } diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 274e4f00a0a..585b87b828d 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -1083,7 +1083,7 @@ describe Repository, models: true do end end - describe :skip_merged_commit do + describe 'skip_merges option' do subject { repository.commits(Gitlab::Git::BRANCH_REF_PREFIX + "'test'", limit: 100, skip_merges: true).map{ |k| k.id } } it { is_expected.not_to include('e56497bb5f03a90a51293fc6d516788730953899') } diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 90378179e32..a9e37be1157 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -81,6 +81,7 @@ describe User, models: true do it { is_expected.to validate_numericality_of(:projects_limit) } it { is_expected.to allow_value(0).for(:projects_limit) } it { is_expected.not_to allow_value(-1).for(:projects_limit) } + it { is_expected.not_to allow_value(Gitlab::Database::MAX_INT_VALUE + 1).for(:projects_limit) } it { is_expected.to validate_length_of(:bio).is_at_most(255) } @@ -360,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/projects_spec.rb b/spec/requests/api/projects_spec.rb index c481b7e72b1..a3de4702ad0 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -902,7 +902,7 @@ describe API::Projects, :api do end end - describe :fork_admin do + describe 'fork management' do let(:project_fork_target) { create(:empty_project) } let(:project_fork_source) { create(:empty_project, :public) } diff --git a/spec/requests/api/v3/projects_spec.rb b/spec/requests/api/v3/projects_spec.rb index d8bb562587d..b1aa793ec00 100644 --- a/spec/requests/api/v3/projects_spec.rb +++ b/spec/requests/api/v3/projects_spec.rb @@ -949,7 +949,7 @@ describe API::V3::Projects, api: true do end end - describe :fork_admin do + describe 'fork management' do let(:project_fork_target) { create(:empty_project) } let(:project_fork_source) { create(:empty_project, :public) } 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 92b84308f73..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 @@ -17,7 +17,7 @@ describe Milestones::CloseService, services: true do it { expect(milestone).to be_valid } it { expect(milestone).to be_closed } - describe :event do + describe 'event' do let(:event) { Event.recent.first } it { expect(event.milestone).to be_truthy } 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 8e614211116..f8eb34f2ef4 100644 --- a/spec/services/projects/fork_service_spec.rb +++ b/spec/services/projects/fork_service_spec.rb @@ -1,12 +1,13 @@ require 'spec_helper' describe Projects::ForkService, services: true do - describe :fork_by_user do + describe 'fork by user' do before do @from_namespace = create(:namespace) @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 @@ -100,13 +101,14 @@ describe Projects::ForkService, services: true do end end - describe :fork_to_namespace do + describe 'fork to namespace' 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 |