diff options
97 files changed, 1008 insertions, 334 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 44beccd966a..c971df3ba5f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,4 @@ -image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.5.3-golang-1.11-git-2.21-chrome-73.0-node-10.x-yarn-1.12-postgresql-9.6-graphicsmagick-1.3.29" +image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.6.3-golang-1.11-git-2.21-chrome-73.0-node-10.x-yarn-1.12-postgresql-9.6-graphicsmagick-1.3.29" variables: MYSQL_ALLOW_EMPTY_PASSWORD: "1" diff --git a/.gitlab/ci/cng.gitlab-ci.yml b/.gitlab/ci/cng.gitlab-ci.yml index c384bcdcdfc..d624e8d09f6 100644 --- a/.gitlab/ci/cng.gitlab-ci.yml +++ b/.gitlab/ci/cng.gitlab-ci.yml @@ -1,5 +1,5 @@ cloud-native-image: - image: ruby:2.5-alpine + image: ruby:2.6-alpine before_script: [] dependencies: [] stage: post-test diff --git a/.gitlab/ci/frontend.gitlab-ci.yml b/.gitlab/ci/frontend.gitlab-ci.yml index fbf8925e30a..986ba7558d5 100644 --- a/.gitlab/ci/frontend.gitlab-ci.yml +++ b/.gitlab/ci/frontend.gitlab-ci.yml @@ -16,7 +16,7 @@ gitlab:assets:compile: <<: *assets-compile-cache extends: .dedicated-no-docs-pull-cache-job - image: dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.5.3-git-2.21-chrome-73.0-node-8.x-yarn-1.12-graphicsmagick-1.3.29-docker-18.06.1 + image: dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.6.3-git-2.21-chrome-73.0-node-8.x-yarn-1.12-graphicsmagick-1.3.29-docker-18.06.1 dependencies: - setup-test-env services: diff --git a/.gitlab/ci/global.gitlab-ci.yml b/.gitlab/ci/global.gitlab-ci.yml index 466c47b37c7..cf87f5eb39c 100644 --- a/.gitlab/ci/global.gitlab-ci.yml +++ b/.gitlab/ci/global.gitlab-ci.yml @@ -9,7 +9,7 @@ - gitlab-org .default-cache: &default-cache - key: "debian-stretch-ruby-2.5.3-node-10.x" + key: "debian-stretch-ruby-2.6.3-node-10.x" paths: - vendor/ruby - .yarn-cache/ @@ -47,7 +47,7 @@ .single-script-job-dedicated-runner: extends: .dedicated-runner - image: ruby:2.5-alpine + image: ruby:2.6-alpine stage: test cache: {} dependencies: [] diff --git a/.gitlab/ci/qa.gitlab-ci.yml b/.gitlab/ci/qa.gitlab-ci.yml index 85c6409186e..122ed622ee2 100644 --- a/.gitlab/ci/qa.gitlab-ci.yml +++ b/.gitlab/ci/qa.gitlab-ci.yml @@ -1,5 +1,5 @@ package-and-qa: - image: ruby:2.5-alpine + image: ruby:2.6-alpine stage: qa when: manual before_script: [] diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml index 35c5f67427e..29534e40a14 100644 --- a/.gitlab/ci/rails.gitlab-ci.yml +++ b/.gitlab/ci/rails.gitlab-ci.yml @@ -86,7 +86,7 @@ .rspec-metadata-pg-10: &rspec-metadata-pg-10 <<: *rspec-metadata <<: *use-pg-10 - image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.5.3-golang-1.11-git-2.21-chrome-73.0-node-10.x-yarn-1.12-postgresql-10-graphicsmagick-1.3.29" + image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.6.3-golang-1.11-git-2.21-chrome-73.0-node-10.x-yarn-1.12-postgresql-10-graphicsmagick-1.3.29" .rspec-metadata-mysql: &rspec-metadata-mysql <<: *rspec-metadata @@ -108,7 +108,8 @@ - git fetch https://gitlab.com/gitlab-org/gitlab-ce.git v9.3.0 - git checkout -f FETCH_HEAD - sed -i "s/gem 'oj', '~> 2.17.4'//" Gemfile - - bundle update google-protobuf grpc + - sed -i "s/gem 'bootsnap', '~> 1.0.0'/gem 'bootsnap'/" Gemfile + - bundle update google-protobuf grpc bootsnap - bundle install $BUNDLE_INSTALL_FLAGS - date - cp config/gitlab.yml.example config/gitlab.yml @@ -183,7 +184,7 @@ static-analysis: script: - scripts/static-analysis cache: - key: "debian-stretch-ruby-2.5.3-node-10.x-and-rubocop" + key: "debian-stretch-ruby-2.6.3-node-10.x-and-rubocop" paths: - vendor/ruby - .yarn-cache/ diff --git a/.gitlab/ci/review.gitlab-ci.yml b/.gitlab/ci/review.gitlab-ci.yml index ae16549ef6b..3a024c44fe2 100644 --- a/.gitlab/ci/review.gitlab-ci.yml +++ b/.gitlab/ci/review.gitlab-ci.yml @@ -54,7 +54,7 @@ build-qa-image: - time docker push ${QA_IMAGE} .review-build-cng-base: &review-build-cng-base - image: ruby:2.5-alpine + image: ruby:2.6-alpine stage: test when: manual before_script: diff --git a/.gitlab/ci/test-metadata.gitlab-ci.yml b/.gitlab/ci/test-metadata.gitlab-ci.yml index 3ba7af956b5..4b595083ec6 100644 --- a/.gitlab/ci/test-metadata.gitlab-ci.yml +++ b/.gitlab/ci/test-metadata.gitlab-ci.yml @@ -56,7 +56,7 @@ update-tests-metadata: flaky-examples-check: extends: .dedicated-runner - image: ruby:2.5-alpine + image: ruby:2.6-alpine services: [] before_script: [] variables: diff --git a/.ruby-version b/.ruby-version index aedc15bb0c6..ec1cf33c3f6 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.5.3 +2.6.3 diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js index bac124be34c..63658d49a05 100644 --- a/app/assets/javascripts/notes/stores/actions.js +++ b/app/assets/javascripts/notes/stores/actions.js @@ -268,11 +268,20 @@ export const saveNote = ({ commit, dispatch }, noteData) => { const { errors } = res; const commandsChanges = res.commands_changes; - if (hasQuickActions && errors && Object.keys(errors).length) { - eTagPoll.makeRequest(); + if (errors && Object.keys(errors).length) { + /* + The following reply means that quick actions have been successfully applied: - $('.js-gfm-input').trigger('clear-commands-cache.atwho'); - Flash(__('Commands applied'), 'notice', noteData.flashContainer); + {"commands_changes":{},"valid":false,"errors":{"commands_only":["Commands applied"]}} + */ + if (hasQuickActions) { + eTagPoll.makeRequest(); + + $('.js-gfm-input').trigger('clear-commands-cache.atwho'); + Flash(__('Commands applied'), 'notice', noteData.flashContainer); + } else { + throw new Error(__('Failed to save comment!')); + } } if (commandsChanges) { diff --git a/app/assets/javascripts/notes/stores/utils.js b/app/assets/javascripts/notes/stores/utils.js index 029fde348fb..ed4cef4a917 100644 --- a/app/assets/javascripts/notes/stores/utils.js +++ b/app/assets/javascripts/notes/stores/utils.js @@ -2,7 +2,8 @@ import AjaxCache from '~/lib/utils/ajax_cache'; import { trimFirstCharOfLineContent } from '~/diffs/store/utils'; import { sprintf, __ } from '~/locale'; -const REGEX_QUICK_ACTIONS = /^\/\w+.*$/gm; +// factory function because global flag makes RegExp stateful +const createQuickActionsRegex = () => /^\/\w+.*$/gm; export const findNoteObjectById = (notes, id) => notes.filter(n => n.id === id)[0]; @@ -27,9 +28,9 @@ export const getQuickActionText = note => { return text; }; -export const hasQuickActions = note => REGEX_QUICK_ACTIONS.test(note); +export const hasQuickActions = note => createQuickActionsRegex().test(note); -export const stripQuickActions = note => note.replace(REGEX_QUICK_ACTIONS, '').trim(); +export const stripQuickActions = note => note.replace(createQuickActionsRegex(), '').trim(); export const prepareDiffLines = diffLines => diffLines.map(line => ({ ...trimFirstCharOfLineContent(line) })); diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown.js b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown.js index c1f6edf2f27..a20a0526f12 100644 --- a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown.js +++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown.js @@ -1,4 +1,10 @@ -const defaultTimezone = 'UTC'; +const defaultTimezone = { name: 'UTC', offset: 0 }; +const defaults = { + $inputEl: null, + $dropdownEl: null, + onSelectTimezone: null, + displayFormat: item => item.name, +}; export const formatUtcOffset = offset => { const parsed = parseInt(offset, 10); @@ -11,23 +17,28 @@ export const formatUtcOffset = offset => { export const formatTimezone = item => `[UTC ${formatUtcOffset(item.offset)}] ${item.name}`; -const defaults = { - $inputEl: null, - $dropdownEl: null, - onSelectTimezone: null, +export const findTimezoneByIdentifier = (tzList = [], identifier = null) => { + if (tzList && tzList.length && identifier && identifier.length) { + return tzList.find(tz => tz.identifier === identifier) || null; + } + return null; }; export default class TimezoneDropdown { - constructor({ $dropdownEl, $inputEl, onSelectTimezone } = defaults) { + constructor({ $dropdownEl, $inputEl, onSelectTimezone, displayFormat } = defaults) { this.$dropdown = $dropdownEl; this.$dropdownToggle = this.$dropdown.find('.dropdown-toggle-text'); this.$input = $inputEl; this.timezoneData = this.$dropdown.data('data'); + this.onSelectTimezone = onSelectTimezone; + this.displayFormat = displayFormat || defaults.displayFormat; + + this.initialTimezone = + findTimezoneByIdentifier(this.timezoneData, this.$input.val()) || defaultTimezone; + this.initDefaultTimezone(); this.initDropdown(); - - this.onSelectTimezone = onSelectTimezone; } initDropdown() { @@ -35,7 +46,7 @@ export default class TimezoneDropdown { data: this.timezoneData, filterable: true, selectable: true, - toggleLabel: item => item.name, + toggleLabel: this.displayFormat, search: { fields: ['name'], }, @@ -43,20 +54,17 @@ export default class TimezoneDropdown { text: item => formatTimezone(item), }); - this.setDropdownToggle(); + this.setDropdownToggle(this.displayFormat(this.initialTimezone)); } initDefaultTimezone() { - const initialValue = this.$input.val(); - - if (!initialValue) { - this.$input.val(defaultTimezone); + if (!this.$input.val()) { + this.$input.val(defaultTimezone.name); } } - setDropdownToggle() { - const initialValue = this.$input.val(); - this.$dropdownToggle.text(initialValue); + setDropdownToggle(dropdownText) { + this.$dropdownToggle.text(dropdownText); } updateInputValue({ selectedObj, e }) { diff --git a/app/assets/javascripts/profile/profile.js b/app/assets/javascripts/profile/profile.js index deacff5abe7..6e3800021b4 100644 --- a/app/assets/javascripts/profile/profile.js +++ b/app/assets/javascripts/profile/profile.js @@ -2,6 +2,9 @@ import $ from 'jquery'; import axios from '~/lib/utils/axios_utils'; import flash from '../flash'; import { parseBoolean } from '~/lib/utils/common_utils'; +import TimezoneDropdown, { + formatTimezone, +} from '~/pages/projects/pipeline_schedules/shared/components/timezone_dropdown'; export default class Profile { constructor({ form } = {}) { @@ -10,6 +13,14 @@ export default class Profile { this.setRepoRadio(); this.bindEvents(); this.initAvatarGlCrop(); + + this.$inputEl = $('#user_timezone'); + + this.timezoneDropdown = new TimezoneDropdown({ + $inputEl: this.$inputEl, + $dropdownEl: $('.js-timezone-dropdown'), + displayFormat: selectedItem => formatTimezone(selectedItem), + }); } initAvatarGlCrop() { diff --git a/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue index c0659a0173a..10e2c8453e2 100644 --- a/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue +++ b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue @@ -178,7 +178,7 @@ export default { /> <div ref="userStatusForm" class="form-group position-relative m-0"> <div class="input-group"> - <span class="input-group-btn"> + <span class="input-group-prepend"> <button ref="toggleEmojiMenuButton" v-gl-tooltip.bottom @@ -211,7 +211,7 @@ export default { @keyup.enter.prevent @click="hideEmojiMenu" /> - <span v-show="isDirty" class="input-group-btn"> + <span v-show="isDirty" class="input-group-append"> <button v-gl-tooltip.bottom :title="s__('SetStatusModal|Clear status')" diff --git a/app/assets/stylesheets/framework/ci_variable_list.scss b/app/assets/stylesheets/framework/ci_variable_list.scss index d9b0e4558ad..28d7492b99c 100644 --- a/app/assets/stylesheets/framework/ci_variable_list.scss +++ b/app/assets/stylesheets/framework/ci_variable_list.scss @@ -47,6 +47,7 @@ display: flex; align-items: flex-start; width: 100%; + padding-bottom: $gl-padding; @include media-breakpoint-down(xs) { display: block; diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index a6179e2a96e..a57cd6737f8 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -581,10 +581,15 @@ .emoji-menu-toggle-button { @include emoji-menu-toggle-button; + padding: $gl-vert-padding $gl-btn-padding; } .input-group { - height: 34px; + &, + .input-group-prepend, + .input-group-append { + height: $input-height; + } } } diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss index 323a3dbecd5..e2bb1eb67c0 100644 --- a/app/assets/stylesheets/framework/modal.scss +++ b/app/assets/stylesheets/framework/modal.scss @@ -66,12 +66,6 @@ margin-top: $grid-size; } } - - @include media-breakpoint-up(sm) { - .btn:nth-child(1) { - margin-left: auto; - } - } } body.modal-open { diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 09f75cd827f..f2b67a693c3 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -471,6 +471,11 @@ $note-form-margin-left: 72px; vertical-align: top; white-space: normal; + // Fixes subpixel rounding issue https://gitlab.com/gitlab-org/gitlab-ce/issues/53973 + // background-color is needed for dark code preference + padding-bottom: 1px; + background-color: $white-light; + &.parallel { border-width: 1px; diff --git a/app/controllers/profiles/preferences_controller.rb b/app/controllers/profiles/preferences_controller.rb index 0e30df1b15b..62f98d9e549 100644 --- a/app/controllers/profiles/preferences_controller.rb +++ b/app/controllers/profiles/preferences_controller.rb @@ -44,7 +44,9 @@ class Profiles::PreferencesController < Profiles::ApplicationController :project_view, :theme_id, :first_day_of_week, - :preferred_language + :preferred_language, + :time_display_relative, + :time_format_in_24h ] end end diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb index b9c52618d4b..1d16ddb1608 100644 --- a/app/controllers/profiles_controller.rb +++ b/app/controllers/profiles_controller.rb @@ -15,7 +15,7 @@ class ProfilesController < Profiles::ApplicationController result = Users::UpdateService.new(current_user, user_params.merge(user: @user)).execute if result[:status] == :success - message = "Profile was successfully updated" + message = s_("Profiles|Profile was successfully updated") format.html { redirect_back_or_default(default: { action: 'show' }, options: { notice: message }) } format.json { render json: { message: message } } @@ -31,7 +31,7 @@ class ProfilesController < Profiles::ApplicationController user.reset_incoming_email_token! end - flash[:notice] = "Incoming email token was successfully reset" + flash[:notice] = s_("Profiles|Incoming email token was successfully reset") redirect_to profile_personal_access_tokens_path end @@ -41,7 +41,7 @@ class ProfilesController < Profiles::ApplicationController user.reset_feed_token! end - flash[:notice] = 'Feed token was successfully reset' + flash[:notice] = s_('Profiles|Feed token was successfully reset') redirect_to profile_personal_access_tokens_path end @@ -106,6 +106,7 @@ class ProfilesController < Profiles::ApplicationController :organization, :private_profile, :include_private_contributions, + :timezone, status: [:emoji, :message] ) end diff --git a/app/graphql/gitlab_schema.rb b/app/graphql/gitlab_schema.rb index a12568d5d31..897e12c1b56 100644 --- a/app/graphql/gitlab_schema.rb +++ b/app/graphql/gitlab_schema.rb @@ -7,6 +7,9 @@ class GitlabSchema < GraphQL::Schema AUTHENTICATED_COMPLEXITY = 250 ADMIN_COMPLEXITY = 300 + ANONYMOUS_MAX_DEPTH = 10 + AUTHENTICATED_MAX_DEPTH = 15 + use BatchLoader::GraphQL use Gitlab::Graphql::Authorize use Gitlab::Graphql::Present @@ -23,21 +26,36 @@ class GitlabSchema < GraphQL::Schema mutation(Types::MutationType) - def self.execute(query_str = nil, **kwargs) - kwargs[:max_complexity] ||= max_query_complexity(kwargs[:context]) + class << self + def execute(query_str = nil, **kwargs) + kwargs[:max_complexity] ||= max_query_complexity(kwargs[:context]) + kwargs[:max_depth] ||= max_query_depth(kwargs[:context]) - super(query_str, **kwargs) - end + super(query_str, **kwargs) + end + + private + + def max_query_complexity(ctx) + current_user = ctx&.fetch(:current_user, nil) + + if current_user&.admin + ADMIN_COMPLEXITY + elsif current_user + AUTHENTICATED_COMPLEXITY + else + DEFAULT_MAX_COMPLEXITY + end + end - def self.max_query_complexity(ctx) - current_user = ctx&.fetch(:current_user, nil) + def max_query_depth(ctx) + current_user = ctx&.fetch(:current_user, nil) - if current_user&.admin - ADMIN_COMPLEXITY - elsif current_user - AUTHENTICATED_COMPLEXITY - else - DEFAULT_MAX_COMPLEXITY + if current_user + AUTHENTICATED_MAX_DEPTH + else + ANONYMOUS_MAX_DEPTH + end end end end diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index ebf28dc842c..7b4832b84a8 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -265,6 +265,7 @@ class JiraService < IssueTrackerService def find_remote_link(issue, url) links = jira_request { issue.remotelink.all } + return unless links links.find { |link| link.object["url"] == url } end diff --git a/app/models/repository.rb b/app/models/repository.rb index be17b54ff12..1c02e68f2f6 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -465,7 +465,7 @@ class Repository def after_import expire_content_cache - DetectRepositoryLanguagesWorker.perform_async(project.id, project.owner.id) + DetectRepositoryLanguagesWorker.perform_async(project.id) end # Runs code after a new commit has been pushed. diff --git a/app/models/user.rb b/app/models/user.rb index 4a1bf5514fe..60f69659a6b 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -230,6 +230,9 @@ class User < ApplicationRecord delegate :notes_filter_for, to: :user_preference delegate :set_notes_filter, to: :user_preference delegate :first_day_of_week, :first_day_of_week=, to: :user_preference + delegate :timezone, :timezone=, to: :user_preference + delegate :time_display_relative, :time_display_relative=, to: :user_preference + delegate :time_format_in_24h, :time_format_in_24h=, to: :user_preference accepts_nested_attributes_for :user_preference, update_only: true diff --git a/app/models/user_preference.rb b/app/models/user_preference.rb index 282b192167f..f1326f4c8cb 100644 --- a/app/models/user_preference.rb +++ b/app/models/user_preference.rb @@ -10,6 +10,10 @@ class UserPreference < ApplicationRecord validates :issue_notes_filter, :merge_request_notes_filter, inclusion: { in: NOTES_FILTERS.values }, presence: true + default_value_for :timezone, value: Time.zone.tzinfo.name, allows_nil: false + default_value_for :time_display_relative, value: true, allows_nil: false + default_value_for :time_format_in_24h, value: false, allows_nil: false + class << self def notes_filters { diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index 76544249688..3218c04b219 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -488,6 +488,10 @@ class ProjectPolicy < BasePolicy def team_access_level return -1 if @user.nil? + lookup_access_level! + end + + def lookup_access_level! # NOTE: max_member_access has its own cache project.team.max_member_access(@user.id) end diff --git a/app/services/git/branch_push_service.rb b/app/services/git/branch_push_service.rb index da053ce80c7..abf11f253f6 100644 --- a/app/services/git/branch_push_service.rb +++ b/app/services/git/branch_push_service.rb @@ -48,7 +48,7 @@ module Git def enqueue_detect_repository_languages return unless default_branch? - DetectRepositoryLanguagesWorker.perform_async(project.id, current_user.id) + DetectRepositoryLanguagesWorker.perform_async(project.id) end # Only stop environments if the ref is a branch that is being deleted diff --git a/app/services/members/create_service.rb b/app/services/members/create_service.rb index f6bad74736c..d6b17ec10be 100644 --- a/app/services/members/create_service.rb +++ b/app/services/members/create_service.rb @@ -23,7 +23,16 @@ module Members members.each do |member| if member.errors.any? - errors << "#{member.user.username}: #{member.errors.full_messages.to_sentence}" + current_error = + # Invited users may not have an associated user + if member.user.present? + "#{member.user.username}: " + else + "" + end + + current_error += member.errors.full_messages.to_sentence + errors << current_error else after_execute(member: member) end diff --git a/app/services/projects/after_import_service.rb b/app/services/projects/after_import_service.rb index afb9048e87b..bbdde4408d2 100644 --- a/app/services/projects/after_import_service.rb +++ b/app/services/projects/after_import_service.rb @@ -9,7 +9,7 @@ module Projects end def execute - Projects::HousekeepingService.new(@project, :gc).execute do + Projects::HousekeepingService.new(@project).execute do repository.delete_all_refs_except(RESERVED_REF_PREFIXES) end rescue Projects::HousekeepingService::LeaseTaken => e diff --git a/app/services/projects/repository_languages_service.rb b/app/services/projects/repository_languages_service.rb index e75851c7da4..05f43c2264b 100644 --- a/app/services/projects/repository_languages_service.rb +++ b/app/services/projects/repository_languages_service.rb @@ -11,7 +11,7 @@ module Projects def perform_language_detection if persisted_repository_languages.blank? - ::DetectRepositoryLanguagesWorker.perform_async(project.id, current_user.id) + ::DetectRepositoryLanguagesWorker.perform_async(project.id) else project.update_column(:detected_repository_languages, true) end diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index bc34af88928..f016a157daf 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -100,6 +100,8 @@ %span.light archived: %strong project is read-only + = render_if_exists "shared_runner_status", project: @project + %li %span.light access: %strong diff --git a/app/views/ci/variables/_index.html.haml b/app/views/ci/variables/_index.html.haml index dc9ccb6cc39..464b9faf282 100644 --- a/app/views/ci/variables/_index.html.haml +++ b/app/views/ci/variables/_index.html.haml @@ -10,6 +10,7 @@ .hide.alert.alert-danger.js-ci-variable-error-box %ul.ci-variable-list + = render 'ci/variables/variable_header' - @variables.each.each do |variable| = render 'ci/variables/variable_row', form_field: 'variables', variable: variable = render 'ci/variables/variable_row', form_field: 'variables' diff --git a/app/views/ci/variables/_variable_header.html.haml b/app/views/ci/variables/_variable_header.html.haml new file mode 100644 index 00000000000..d3b7a5ae883 --- /dev/null +++ b/app/views/ci/variables/_variable_header.html.haml @@ -0,0 +1,16 @@ +- only_key_value = local_assigns.fetch(:only_key_value, false) + +%li.ci-variable-row.m-0.d-none.d-sm-block + .d-flex.w-100.align-items-center.pb-2 + .bold.table-section.section-15.append-right-10 + = s_('CiVariables|Type') + .bold.table-section.section-15.append-right-10 + = s_('CiVariables|Key') + .bold.table-section.section-15.append-right-10 + = s_('CiVariables|Value') + - unless only_key_value + .bold.table-section.section-20 + = s_('CiVariables|State') + .bold.table-section.section-20 + = s_('CiVariables|Masked') + = render_if_exists 'ci/variables/environment_scope_header' diff --git a/app/views/ci/variables/_variable_row.html.haml b/app/views/ci/variables/_variable_row.html.haml index 37257b3aa1c..b4930b41c09 100644 --- a/app/views/ci/variables/_variable_row.html.haml +++ b/app/views/ci/variables/_variable_row.html.haml @@ -20,18 +20,18 @@ - masked_input_name = "#{form_field}[variables_attributes][][masked]" %li.js-row.ci-variable-row{ data: { is_persisted: "#{!id.nil?}" } } - .ci-variable-row-body + .ci-variable-row-body.border-bottom %input.js-ci-variable-input-id{ type: "hidden", name: id_input_name, value: id } %input.js-ci-variable-input-destroy{ type: "hidden", name: destroy_input_name } - %select.js-ci-variable-input-variable-type.ci-variable-body-item.form-control.select-control{ name: variable_type_input_name } + %select.js-ci-variable-input-variable-type.ci-variable-body-item.form-control.select-control.table-section.section-15{ name: variable_type_input_name } = options_for_select(ci_variable_type_options, variable_type) - %input.js-ci-variable-input-key.ci-variable-body-item.qa-ci-variable-input-key.form-control{ type: "text", + %input.js-ci-variable-input-key.ci-variable-body-item.qa-ci-variable-input-key.form-control.table-section.section-15{ type: "text", name: key_input_name, value: key, placeholder: s_('CiVariables|Input variable key') } - .ci-variable-body-item.gl-show-field-errors + .ci-variable-body-item.gl-show-field-errors.table-section.section-15.border-top-0.p-0 .form-control.js-secret-value-placeholder.qa-ci-variable-input-value{ class: ('hide' unless id) } - = '*' * 20 + = '*' * 17 %textarea.js-ci-variable-input-value.js-secret-value.qa-ci-variable-input-value.form-control{ class: ('hide' if id), rows: 1, name: value_input_name, @@ -41,7 +41,7 @@ = s_("CiVariables|Cannot use Masked Variable with current value") = link_to icon('question-circle'), help_page_path('ci/variables/README', anchor: 'masked-variables'), target: '_blank', rel: 'noopener noreferrer' - unless only_key_value - .ci-variable-body-item.ci-variable-protected-item + .ci-variable-body-item.ci-variable-protected-item.table-section.section-20.mr-0.border-top-0 .append-right-default = s_("CiVariable|Protected") %button{ type: 'button', @@ -55,7 +55,7 @@ %span.toggle-icon = sprite_icon('status_success_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-checked') = sprite_icon('status_failed_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-unchecked') - .ci-variable-body-item.ci-variable-masked-item + .ci-variable-body-item.ci-variable-masked-item.table-section.section-20.mr-0.border-top-0 .append-right-default = s_("CiVariable|Masked") %button{ type: 'button', @@ -70,5 +70,5 @@ = sprite_icon('status_success_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-checked') = sprite_icon('status_failed_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-unchecked') = render_if_exists 'ci/variables/environment_scope', form_field: form_field, variable: variable - %button.js-row-remove-button.ci-variable-row-remove-button{ type: 'button', 'aria-label': s_('CiVariables|Remove variable row') } - = icon('minus-circle') + %button.js-row-remove-button.ci-variable-row-remove-button.table-section.section-5.border-top-0{ type: 'button', 'aria-label': s_('CiVariables|Remove variable row') } + = icon('minus-circle') diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml index 09cc713e3af..021c0b6c429 100644 --- a/app/views/groups/group_members/index.html.haml +++ b/app/views/groups/group_members/index.html.haml @@ -14,6 +14,8 @@ = render 'shared/members/requests', membership_source: @group, requesters: @requesters + = render_if_exists 'groups/group_members/ldap_sync' + .clearfix %h5.member.existing-title Existing members diff --git a/app/views/layouts/_piwik.html.haml b/app/views/layouts/_piwik.html.haml index a888e8ae187..473b14ce626 100644 --- a/app/views/layouts/_piwik.html.haml +++ b/app/views/layouts/_piwik.html.haml @@ -7,7 +7,7 @@ (function() { var u="//#{extra_config.piwik_url}/"; _paq.push(['setTrackerUrl', u+'piwik.php']); - _paq.push(['setSiteId', #{extra_config.piwik_site_id}]); + _paq.push(['setSiteId', "#{extra_config.piwik_site_id}"]); var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0]; g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s); })(); diff --git a/app/views/layouts/nav/sidebar/_profile.html.haml b/app/views/layouts/nav/sidebar/_profile.html.haml index 1e3bb8f1224..2061eac917f 100644 --- a/app/views/layouts/nav/sidebar/_profile.html.haml +++ b/app/views/layouts/nav/sidebar/_profile.html.haml @@ -4,7 +4,7 @@ = link_to profile_path, title: _('Profile Settings') do .avatar-container.s40.settings-avatar = image_tag avatar_icon_for_user(current_user, 40), class: "avatar s40 avatar-tile", alt: current_user.name - .sidebar-context-title User Settings + .sidebar-context-title= _('User Settings') %ul.sidebar-top-level-items = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do = link_to profile_path do diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml index bfe1c3ddf33..58f2eb229ba 100644 --- a/app/views/profiles/preferences/show.html.haml +++ b/app/views/profiles/preferences/show.html.haml @@ -82,5 +82,31 @@ = f.label :first_day_of_week, class: 'label-bold' do = _('First day of the week') = f.select :first_day_of_week, first_day_of_week_choices_with_default, {}, class: 'form-control' + - if Feature.enabled?(:user_time_settings) + .col-sm-12 + %hr + .col-lg-4.profile-settings-sidebar + %h4.prepend-top-0= s_('Preferences|Time preferences') + %p= s_('Preferences|These settings will update how dates and times are displayed for you.') + .col-lg-8 + .form-group + %h5= s_('Preferences|Time format') + .checkbox-icon-inline-wrapper.form-check + - time_format_label = capture do + = s_('Preferences|Display time in 24-hour format') + = f.check_box :time_format_in_24h, class: 'form-check-input' + = f.label :time_format_in_24h do + = time_format_label + %h5= s_('Preferences|Time display') + .checkbox-icon-inline-wrapper.form-check + - time_display_label = capture do + = s_('Preferences|Use relative times') + = f.check_box :time_display_relative, class: 'form-check-input' + = f.label :time_display_relative do + = time_display_label + .text-muted + = s_('Preferences|For example: 30 mins ago.') + .col-lg-4.profile-settings-sidebar + .col-lg-8 .form-group = f.submit _('Save changes'), class: 'btn btn-success' diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index 02c750a92c3..917e7acc353 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -64,6 +64,18 @@ prepend: emoji_button, append: reset_message_button, placeholder: s_("Profiles|What's your status?") + - if Feature.enabled?(:user_time_settings) + %hr + .row.user-time-preferences + .col-lg-4.profile-settings-sidebar + %h4.prepend-top-0= s_("Profiles|Time settings") + %p= s_("Profiles|You can set your current timezone here") + .col-lg-8 + -# TODO: might need an entry in user/profile.md to describe some of these settings + -# https://gitlab.com/gitlab-org/gitlab-ce/issues/60070 + %h5= ("Time zone") + = dropdown_tag(_("Select a timezone"), options: { toggle_class: 'btn js-timezone-dropdown input-lg', title: _("Select a timezone"), filter: true, placeholder: s_("OfSearchInADropdown|Filter"), data: { data: timezone_data } } ) + %input.hidden{ :type => 'hidden', :id => 'user_timezone', :name => 'user[timezone]', value: @user.timezone } %hr .row @@ -80,8 +92,8 @@ = f.text_field :name, required: true, readonly: true, wrapper: { class: 'col-md-9 qa-full-name' }, help: s_("Profiles|Your name was automatically set based on your %{provider_label} account, so people you know can recognize you") % { provider_label: attribute_provider_label(:name) } - else - = f.text_field :name, label: 'Full name', required: true, title: s_("Profiles|Using emojis in names seems fun, but please try to set a status message instead"), wrapper: { class: 'col-md-9 qa-full-name' }, help: s_("Profiles|Enter your name, so people you know can recognize you") - = f.text_field :id, readonly: true, label: 'User ID', wrapper: { class: 'col-md-3' } + = f.text_field :name, label: s_('Profiles|Full name'), required: true, title: s_("Profiles|Using emojis in names seems fun, but please try to set a status message instead"), wrapper: { class: 'col-md-9 qa-full-name' }, help: s_("Profiles|Enter your name, so people you know can recognize you") + = f.text_field :id, readonly: true, label: s_('Profiles|User ID'), wrapper: { class: 'col-md-3' } = render_if_exists 'profiles/email_settings', form: f = f.text_field :skype, class: 'input-md', placeholder: s_("Profiles|username") @@ -91,18 +103,18 @@ - if @user.read_only_attribute?(:location) = f.text_field :location, readonly: true, help: s_("Profiles|Your location was automatically set based on your %{provider_label} account") % { provider_label: attribute_provider_label(:location) } - else - = f.text_field :location, class: 'input-lg', placeholder: s_("Profiles|City, country") - = f.text_field :organization, class: 'input-md', help: s_("Profiles|Who you represent or work for") - = f.text_area :bio, rows: 4, maxlength: 250, help: s_("Profiles|Tell us about yourself in fewer than 250 characters") + = f.text_field :location, label: s_('Profiles|Location'), class: 'input-lg', placeholder: s_("Profiles|City, country") + = f.text_field :organization, label: s_('Profiles|Organization'), class: 'input-md', help: s_("Profiles|Who you represent or work for") + = f.text_area :bio, label: s_('Profiles|Bio'), rows: 4, maxlength: 250, help: s_("Profiles|Tell us about yourself in fewer than 250 characters") %hr - %h5= ("Private profile") + %h5= s_("Private profile") .checkbox-icon-inline-wrapper - private_profile_label = capture do = s_("Profiles|Don't display activity-related personal information on your profiles") = f.check_box :private_profile, label: private_profile_label, inline: true, wrapper_class: 'mr-0' = link_to icon('question-circle'), help_page_path('user/profile/index.md', anchor: 'private-profile') %h5= s_("Profiles|Private contributions") - = f.check_box :include_private_contributions, label: 'Include private contributions on my profile', wrapper_class: 'mb-2', inline: true + = f.check_box :include_private_contributions, label: s_('Profiles|Include private contributions on my profile'), wrapper_class: 'mb-2', inline: true .help-block = s_("Profiles|Choose to show contributions of private projects on your public profile without any project, repository or organization information") .prepend-top-default.append-bottom-default diff --git a/app/views/repository_check_mailer/notify.text.haml b/app/views/repository_check_mailer/notify.text.haml index 6b64b337b0e..a2e04fa710f 100644 --- a/app/views/repository_check_mailer/notify.text.haml +++ b/app/views/repository_check_mailer/notify.text.haml @@ -3,3 +3,5 @@ = _("View details: %{details_url}") % { details_url: admin_projects_url(last_repository_check_failed: 1) } = _("You are receiving this message because you are a GitLab administrator for %{url}.") % { url: Gitlab.config.gitlab.url } + += render_if_exists 'repository_check_mailer/email_additional_text' diff --git a/app/views/shared/issuable/form/_contribution.html.haml b/app/views/shared/issuable/form/_contribution.html.haml index bc9a1edc39c..a78231b37ce 100644 --- a/app/views/shared/issuable/form/_contribution.html.haml +++ b/app/views/shared/issuable/form/_contribution.html.haml @@ -15,6 +15,6 @@ = form.check_box :allow_collaboration, disabled: !issuable.can_allow_collaboration?(current_user), class: 'form-check-input' = form.label :allow_collaboration, class: 'form-check-label' do = _('Allow commits from members who can merge to the target branch.') - = link_to 'About this feature', help_page_path('user/project/merge_requests/allow_collaboration') + = link_to 'About this feature', help_page_path('user/project/merge_requests/allow_collaboration'), target: '_blank', rel: 'noopener noreferrer nofollow' .form-text.text-muted = allow_collaboration_unavailable_reason(issuable) diff --git a/app/workers/detect_repository_languages_worker.rb b/app/workers/detect_repository_languages_worker.rb index 64bc9776d48..838c3be78f0 100644 --- a/app/workers/detect_repository_languages_worker.rb +++ b/app/workers/detect_repository_languages_worker.rb @@ -12,13 +12,12 @@ class DetectRepositoryLanguagesWorker attr_reader :project # rubocop: disable CodeReuse/ActiveRecord - def perform(project_id, user_id) + def perform(project_id, user_id = nil) @project = Project.find_by(id: project_id) - user = User.find_by(id: user_id) - return unless project && user + return unless project try_obtain_lease do - ::Projects::DetectRepositoryLanguagesService.new(project, user).execute + ::Projects::DetectRepositoryLanguagesService.new(project).execute end end # rubocop: enable CodeReuse/ActiveRecord diff --git a/changelogs/unreleased/27987-use-findorcreateservice-to-create-labels.yml b/changelogs/unreleased/27987-use-findorcreateservice-to-create-labels.yml new file mode 100644 index 00000000000..8d3501e0171 --- /dev/null +++ b/changelogs/unreleased/27987-use-findorcreateservice-to-create-labels.yml @@ -0,0 +1,5 @@ +--- +title: Use FindOrCreateService to create labels and check for existing ones +merge_request: 27987 +author: Matt Duren +type: fixed diff --git a/changelogs/unreleased/53973-fix-subpixel-border-issue.yml b/changelogs/unreleased/53973-fix-subpixel-border-issue.yml new file mode 100644 index 00000000000..0dae7047236 --- /dev/null +++ b/changelogs/unreleased/53973-fix-subpixel-border-issue.yml @@ -0,0 +1,5 @@ +--- +title: Fix MR discussion border missing in chrome sometimes +merge_request: 28185 +author: +type: fixed diff --git a/changelogs/unreleased/57654-add-time-preferences-for-user-fe.yml b/changelogs/unreleased/57654-add-time-preferences-for-user-fe.yml new file mode 100644 index 00000000000..f4ce3a51724 --- /dev/null +++ b/changelogs/unreleased/57654-add-time-preferences-for-user-fe.yml @@ -0,0 +1,5 @@ +--- +title: Add time preferences for user +merge_request: 25381 +author: +type: added diff --git a/changelogs/unreleased/58404-set-default-max-depth-for-GraphQL.yml b/changelogs/unreleased/58404-set-default-max-depth-for-GraphQL.yml new file mode 100644 index 00000000000..7e95158a0e0 --- /dev/null +++ b/changelogs/unreleased/58404-set-default-max-depth-for-GraphQL.yml @@ -0,0 +1,5 @@ +--- +title: 58404 - setup max depth for GraphQL +merge_request: 25737 +author: Ken Ding +type: added diff --git a/changelogs/unreleased/60180-jira-service-fix-nil-on-find-call.yml b/changelogs/unreleased/60180-jira-service-fix-nil-on-find-call.yml new file mode 100644 index 00000000000..6891a9ca83c --- /dev/null +++ b/changelogs/unreleased/60180-jira-service-fix-nil-on-find-call.yml @@ -0,0 +1,5 @@ +--- +title: 'Resolved JIRA service: NoMethodError: undefined method ''find'' for nil:NilClass' +merge_request: 28206 +author: +type: fixed diff --git a/changelogs/unreleased/60425-fix-500-when-accessing-charts-with-anonymous-user.yml b/changelogs/unreleased/60425-fix-500-when-accessing-charts-with-anonymous-user.yml new file mode 100644 index 00000000000..4274dc5918c --- /dev/null +++ b/changelogs/unreleased/60425-fix-500-when-accessing-charts-with-anonymous-user.yml @@ -0,0 +1,5 @@ +--- +title: "Fix 500 error when accessing charts with an anonymous user" +merge_request: 28091 +author: Diego Silva +type: fixed diff --git a/changelogs/unreleased/61494-set-status-modal-visual-bugs.yml b/changelogs/unreleased/61494-set-status-modal-visual-bugs.yml new file mode 100644 index 00000000000..4126b8f93c1 --- /dev/null +++ b/changelogs/unreleased/61494-set-status-modal-visual-bugs.yml @@ -0,0 +1,5 @@ +--- +title: Fix visual issues in set status modal +merge_request: 28147 +author: +type: fixed diff --git a/changelogs/unreleased/add-warning-to-backup-rake-task.yml b/changelogs/unreleased/add-warning-to-backup-rake-task.yml new file mode 100644 index 00000000000..7ddeae3f9fd --- /dev/null +++ b/changelogs/unreleased/add-warning-to-backup-rake-task.yml @@ -0,0 +1,5 @@ +--- +title: Add warning that gitlab-secrets isn't included in backup +merge_request: +author: +type: other diff --git a/changelogs/unreleased/antonyliu-i18n-user-profile.yml b/changelogs/unreleased/antonyliu-i18n-user-profile.yml new file mode 100644 index 00000000000..f9065ee5697 --- /dev/null +++ b/changelogs/unreleased/antonyliu-i18n-user-profile.yml @@ -0,0 +1,5 @@ +--- +title: 'i18n: externalize strings from user profile settings' +merge_request: 28088 +author: Antony Liu +type: other diff --git a/changelogs/unreleased/sh-fix-invited-members.yml b/changelogs/unreleased/sh-fix-invited-members.yml new file mode 100644 index 00000000000..96e43e1aa53 --- /dev/null +++ b/changelogs/unreleased/sh-fix-invited-members.yml @@ -0,0 +1,5 @@ +--- +title: Fix Error 500 when inviting user already present +merge_request: 28198 +author: +type: fixed diff --git a/changelogs/unreleased/sh-upgrade-ruby-2-6-3-ce.yml b/changelogs/unreleased/sh-upgrade-ruby-2-6-3-ce.yml new file mode 100644 index 00000000000..9ad5c9ebb64 --- /dev/null +++ b/changelogs/unreleased/sh-upgrade-ruby-2-6-3-ce.yml @@ -0,0 +1,5 @@ +--- +title: Upgrade Ruby version to 2.6.3 +merge_request: 28117 +author: +type: performance diff --git a/changelogs/unreleased/winh-notes-error-handling.yml b/changelogs/unreleased/winh-notes-error-handling.yml new file mode 100644 index 00000000000..6f23dd459d4 --- /dev/null +++ b/changelogs/unreleased/winh-notes-error-handling.yml @@ -0,0 +1,5 @@ +--- +title: Handle errors in successful notes reply +merge_request: 28082 +author: +type: fixed diff --git a/config/initializers/01_secret_token.rb b/config/initializers/01_secret_token.rb index 02bded43083..4328ca509ba 100644 --- a/config/initializers/01_secret_token.rb +++ b/config/initializers/01_secret_token.rb @@ -28,7 +28,8 @@ def create_tokens secret_key_base: file_secret_key || generate_new_secure_token, otp_key_base: env_secret_key || file_secret_key || generate_new_secure_token, db_key_base: generate_new_secure_token, - openid_connect_signing_key: generate_new_rsa_private_key + openid_connect_signing_key: generate_new_rsa_private_key, + lets_encrypt_private_key: generate_lets_encrypt_private_key } missing_secrets = set_missing_keys(defaults) @@ -49,6 +50,10 @@ def generate_new_rsa_private_key OpenSSL::PKey::RSA.new(2048).to_pem end +def generate_lets_encrypt_private_key + OpenSSL::PKey::RSA.new(4096).to_pem +end + def warn_missing_secret(secret) warn "Missing Rails.application.secrets.#{secret} for #{Rails.env} environment. The secret will be generated and stored in config/secrets.yml." end diff --git a/db/migrate/20190325105715_add_fields_to_user_preferences.rb b/db/migrate/20190325105715_add_fields_to_user_preferences.rb new file mode 100644 index 00000000000..9ea3b4f9cd8 --- /dev/null +++ b/db/migrate/20190325105715_add_fields_to_user_preferences.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddFieldsToUserPreferences < ActiveRecord::Migration[5.0] + include Gitlab::Database::MigrationHelpers + + disable_ddl_transaction! + + DOWNTIME = false + + def up + add_column(:user_preferences, :timezone, :string) + add_column(:user_preferences, :time_display_relative, :boolean) + add_column(:user_preferences, :time_format_in_24h, :boolean) + end + + def down + remove_column(:user_preferences, :timezone) + remove_column(:user_preferences, :time_display_relative) + remove_column(:user_preferences, :time_format_in_24h) + end +end diff --git a/db/migrate/20190412155659_add_merge_request_blocks.rb b/db/migrate/20190412155659_add_merge_request_blocks.rb new file mode 100644 index 00000000000..9e7f370d1cf --- /dev/null +++ b/db/migrate/20190412155659_add_merge_request_blocks.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +class AddMergeRequestBlocks < ActiveRecord::Migration[5.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def change + create_table :merge_request_blocks, id: :bigserial do |t| + t.references :blocking_merge_request, + index: false, null: false, + foreign_key: { to_table: :merge_requests, on_delete: :cascade } + + t.references :blocked_merge_request, + index: true, null: false, + foreign_key: { to_table: :merge_requests, on_delete: :cascade } + + t.index [:blocking_merge_request_id, :blocked_merge_request_id], + unique: true, + name: 'index_mr_blocks_on_blocking_and_blocked_mr_ids' + + t.timestamps_with_timezone + end + end +end diff --git a/db/schema.rb b/db/schema.rb index deaf406fe3d..159e7e03cf4 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -1227,6 +1227,15 @@ ActiveRecord::Schema.define(version: 20190506135400) do t.index ["user_id"], name: "index_merge_request_assignees_on_user_id", using: :btree end + create_table "merge_request_blocks", force: :cascade do |t| + t.integer "blocking_merge_request_id", null: false + t.integer "blocked_merge_request_id", null: false + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.index ["blocked_merge_request_id"], name: "index_merge_request_blocks_on_blocked_merge_request_id", using: :btree + t.index ["blocking_merge_request_id", "blocked_merge_request_id"], name: "index_mr_blocks_on_blocking_and_blocked_mr_ids", unique: true, using: :btree + end + create_table "merge_request_diff_commits", id: false, force: :cascade do |t| t.datetime_with_timezone "authored_date" t.datetime_with_timezone "committed_date" @@ -2246,6 +2255,9 @@ ActiveRecord::Schema.define(version: 20190506135400) do t.integer "first_day_of_week" t.string "issues_sort" t.string "merge_requests_sort" + t.string "timezone" + t.boolean "time_display_relative" + t.boolean "time_format_in_24h" t.index ["user_id"], name: "index_user_preferences_on_user_id", unique: true, using: :btree end @@ -2511,6 +2523,8 @@ ActiveRecord::Schema.define(version: 20190506135400) do add_foreign_key "members", "users", name: "fk_2e88fb7ce9", on_delete: :cascade add_foreign_key "merge_request_assignees", "merge_requests", on_delete: :cascade add_foreign_key "merge_request_assignees", "users", on_delete: :cascade + add_foreign_key "merge_request_blocks", "merge_requests", column: "blocked_merge_request_id", on_delete: :cascade + add_foreign_key "merge_request_blocks", "merge_requests", column: "blocking_merge_request_id", on_delete: :cascade add_foreign_key "merge_request_diff_commits", "merge_request_diffs", on_delete: :cascade add_foreign_key "merge_request_diff_files", "merge_request_diffs", on_delete: :cascade add_foreign_key "merge_request_diffs", "merge_requests", name: "fk_8483f3258f", on_delete: :cascade diff --git a/doc/administration/integration/plantuml.md b/doc/administration/integration/plantuml.md index d383d1efe70..b7b820abb40 100644 --- a/doc/administration/integration/plantuml.md +++ b/doc/administration/integration/plantuml.md @@ -57,8 +57,7 @@ you can change these defaults by editing the `/etc/tomcat7/server.xml` file. You need to enable PlantUML integration from Settings under Admin Area. To do that, login with an Admin account and do following: - - in GitLab go to **Admin Area** and then **Settings** - - scroll to bottom of the page until PlantUML section + - in GitLab go to **Admin Area**->**Settings**->**Integrations**->**PlantUML** - check **Enable PlantUML** checkbox - set the PlantUML instance as **PlantUML URL** diff --git a/doc/ci/caching/index.md b/doc/ci/caching/index.md index e079483e2b5..3f72fe3e9eb 100644 --- a/doc/ci/caching/index.md +++ b/doc/ci/caching/index.md @@ -289,7 +289,7 @@ jobs inherit it. Gems are installed in `vendor/ruby/` and are cached per-branch: # # https://gitlab.com/gitlab-org/gitlab-ce/tree/master/lib/gitlab/ci/templates/Ruby.gitlab-ci.yml # -image: ruby:2.5 +image: ruby:2.6 # Cache gems in between builds cache: diff --git a/doc/development/architecture.md b/doc/development/architecture.md index 7945a48fe40..88c16a8db22 100644 --- a/doc/development/architecture.md +++ b/doc/development/architecture.md @@ -1,6 +1,3 @@ -<script src="https://unpkg.com/mermaid@8.0.0/dist/mermaid.min.js"></script> -<script>mermaid.initialize({startOnLoad:true});</script> - # GitLab Architecture Overview ## Software delivery @@ -213,107 +210,75 @@ To serve repositories over SSH there's an add-on application called gitlab-shell ### Components -<div class="mermaid"> +```mermaid graph TB - HTTP[HTTP/HTTPS] -- TCP 80, 443 --> NGINX(NGINX) - SSH --TCP 22 --> GitLabShell(GitLab Shell) - SMTP(SMTP Gateway) - Geo(GitLab Geo Node) -- TCP 22, 80, 443 --> NGINX - - subgraph GitLab - GitLabShell --TCP 8080 -->Unicorn["Unicorn (GitLab Rails)"] - GitLabShell --> Gitaly - GitLabShell --> Redis - Unicorn --> PgBouncer(PgBouncer) - Unicorn --> Redis - Unicorn --> Gitaly - Redis --> Sidekiq - Sidekiq("Sidekiq (GitLab Rails, ES Indexer)") --> PgBouncer - GitLabWorkhorse(GitLab Workhorse) --> Unicorn - GitLabWorkhorse --> Redis - GitLabWorkhorse --> Gitaly - Gitaly --> Redis - NGINX --> GitLabWorkhorse - NGINX -- TCP 8090 --> GitLabPages(GitLab Pages) - NGINX --> Grafana(Grafana) - Grafana -- TCP 9090 --> Prometheus(Prometheus) - Prometheus -- TCP 80, 443 --> Unicorn - RedisExporter(Redis Exporter) --> Redis - Prometheus -- TCP 9121 --> RedisExporter - PostgreSQLExporter(PostgreSQL Exporter) --> PostgreSQL - PgBouncerExporter(PgBouncer Exporter) --> PgBouncer - Prometheus -- TCP 9187 --> PostgreSQLExporter - Prometheus -- TCP 9100 --> NodeExporter(Node Exporter) - Prometheus -- TCP 9168 --> GitLabMonitor(GitLab Monitor) - Prometheus -- TCP 9127 --> PgBouncerExporter - GitLabMonitor --> PostgreSQL - GitLabMonitor --> GitLabShell - GitLabMonitor --> Sidekiq - PgBouncer --> Consul(Consul) - PostgreSQL --> Consul - PgBouncer --> PostgreSQL - NGINX --> Registry(Registry) - Unicorn --> Registry - NGINX --> Mattermost(Mattermost) - Mattermost --- Unicorn - Prometheus --> Alertmanager - Migrations --> PostgreSQL - Runner(Runner) --> NGINX - Unicorn -- TCP 9200 --> ElasticSearch - Sidekiq -- TCP 9200 --> ElasticSearch - Sidekiq -- TCP 80, 443 --> Sentry - Unicorn -- TCP 80, 443 --> Sentry - Sidekiq -- UDP 6831 --> Jaeger - Unicorn -- UDP 6831 --> Jaeger - Gitaly -- UDP 6831 --> Jaeger - GitLabShell -- UDP 6831 --> Jaeger - GitLabWorkhorse -- UDP 6831 --> Jaeger - Alertmanager -- TCP 25 --> SMTP - Sidekiq -- TCP 25 --> SMTP - Unicorn -- TCP 25 --> SMTP - Unicorn -- TCP 369 --> LDAP - Sidekiq -- TCP 369 --> LDAP - Unicorn -- TCP 443 --> ObjectStorage("Object Storage") - Sidekiq -- TCP 443 --> ObjectStorage - GitLabWorkhorse -- TCP 443 --> ObjectStorage - Registry -- TCP 443 --> ObjectStorage - Geo -- TCP 5432 --> PostgreSQL - end - - HTTPK8s(HTTP/HTTPS) -- TCP 80, 443 --> LoadBalancerK8s(Load Balancer) - LoadBalancerK8s -- TCP 80, 443 --> nginx-ingressK8s - subgraph Kubernetes - PrometheusK8s(Prometheus) - TillerK8s(Tiller) - nginx-ingressK8s(NGINX Ingress) - Cert-ManagerK8s(Cert-Manager) - GitLabRunnerK8s(GitLab Runner) - GitLabRunnerK8s --> NGINX - JupyterHubK8s(JupyterHub) - nginx-ingressK8s --> JupyterHubK8s - KnativeK8s(Knative) - end - -classDef defaultoff stroke-dasharray: 5, 5 -class ElasticSearch defaultoff -class Grafana defaultoff -class PrometheusK8s defaultoff -class TillerK8s defaultoff -class nginx-ingressK8s defaultoff -class Cert-ManagerK8s defaultoff -class GitLabRunnerK8s defaultoff -class JupyterHubK8s defaultoff -class KnativeK8s defaultoff -class HTTPK8s defaultoff -class LoadBalancerK8s defaultoff -class Sentry defaultoff -class Jaeger defaultoff -class Alertmanager defaultoff -class SMTP defaultoff -class ObjectStorage defaultoff -class Geo defaultoff -</div> + HTTP[HTTP/HTTPS] -- TCP 80, 443 --> NGINX[NGINX] + SSH -- TCP 22 --> GitLabShell[GitLab Shell] + SMTP[SMTP Gateway] + Geo[GitLab Geo Node] -- TCP 22, 80, 443 --> NGINX + + GitLabShell --TCP 8080 -->Unicorn["Unicorn (GitLab Rails)"] + GitLabShell --> Gitaly + GitLabShell --> Redis + Unicorn --> PgBouncer[PgBouncer] + Unicorn --> Redis + Unicorn --> Gitaly + Redis --> Sidekiq + Sidekiq["Sidekiq (GitLab Rails, ES Indexer)"] --> PgBouncer + GitLabWorkhorse[GitLab Workhorse] --> Unicorn + GitLabWorkhorse --> Redis + GitLabWorkhorse --> Gitaly + Gitaly --> Redis + NGINX --> GitLabWorkhorse + NGINX -- TCP 8090 --> GitLabPages[GitLab Pages] + NGINX --> Grafana[Grafana] + Grafana -- TCP 9090 --> Prometheus[Prometheus] + Prometheus -- TCP 80, 443 --> Unicorn + RedisExporter[Redis Exporter] --> Redis + Prometheus -- TCP 9121 --> RedisExporter + PostgreSQLExporter[PostgreSQL Exporter] --> PostgreSQL + PgBouncerExporter[PgBouncer Exporter] --> PgBouncer + Prometheus -- TCP 9187 --> PostgreSQLExporter + Prometheus -- TCP 9100 --> NodeExporter[Node Exporter] + Prometheus -- TCP 9168 --> GitLabMonito[GitLab Monitor] + Prometheus -- TCP 9127 --> PgBouncerExporter + GitLabMonitor --> PostgreSQL + GitLabMonitor --> GitLabShell + GitLabMonitor --> Sidekiq + PgBouncer --> Consul + PostgreSQL --> Consul + PgBouncer --> PostgreSQL + NGINX --> Registry + Unicorn --> Registry + NGINX --> Mattermost + Mattermost --- Unicorn + Prometheus --> Alertmanager + Migrations --> PostgreSQL + Runner -- TCP 443 --> NGINX + Unicorn -- TCP 9200 --> ElasticSearch + Sidekiq -- TCP 9200 --> ElasticSearch + Sidekiq -- TCP 80, 443 --> Sentry + Unicorn -- TCP 80, 443 --> Sentry + Sidekiq -- UDP 6831 --> Jaeger + Unicorn -- UDP 6831 --> Jaeger + Gitaly -- UDP 6831 --> Jaeger + GitLabShell -- UDP 6831 --> Jaeger + GitLabWorkhorse -- UDP 6831 --> Jaeger + Alertmanager -- TCP 25 --> SMTP + Sidekiq -- TCP 25 --> SMTP + Unicorn -- TCP 25 --> SMTP + Unicorn -- TCP 369 --> LDAP + Sidekiq -- TCP 369 --> LDAP + Unicorn -- TCP 443 --> ObjectStorage["Object Storage"] + Sidekiq -- TCP 443 --> ObjectStorage + GitLabWorkhorse -- TCP 443 --> ObjectStorage + Registry -- TCP 443 --> ObjectStorage + Geo -- TCP 5432 --> PostgreSQL + +``` + +--- **Legend**: @@ -324,7 +289,7 @@ class Geo defaultoff | Component | Description | [Omnibus GitLab](https://docs.gitlab.com/omnibus/README.html) | [GitLab chart](https://docs.gitlab.com/charts/) | [Minikube Minimal](https://docs.gitlab.com/charts/development/minikube/#deploying-gitlab-with-minimal-settings) | [GitLab.com](https://gitlab.com) | CE/EE | | --------- | ----------- |:--------------------:|:------------------:|:-----:|:--------:|:--------:| -| NGINX | Routes requests to appropriate components, terminates SSL | [✅](https://docs.gitlab.com/omnibus/settings/nginx.html) | [✅](https://docs.gitlab.com/charts/charts/nginx/index.html) | [⚙](https://docs.gitlab.com/charts/charts/nginx/index.html) | [✅](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/#service-architecture) |CE & EE | +| NGINX | Routes requests to appropriate components, terminates SSL | [✅](https://docs.gitlab.com/omnibus/settings/nginx.html) | [✅](https://docs.gitlab.com/charts/charts/nginx/index.html) | [⚙](https://docs.gitlab.com/charts/charts/nginx/index.html) | [✅](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/#service-architecture) | CE & EE | | Unicorn (GitLab Rails) | Handles requests for the web interface and API | [✅](https://docs.gitlab.com/omnibus/settings/unicorn.html) | [✅](https://docs.gitlab.com/charts/charts/gitlab/unicorn/index.html) | [✅](https://docs.gitlab.com/charts/charts/gitlab/unicorn/index.html) | [✅](https://docs.gitlab.com/ee/user/gitlab_com/#unicorn) | CE & EE | | Sidekiq | Background jobs processor | [✅](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-config-template/gitlab.rb.template) | [✅](https://docs.gitlab.com/charts/charts/gitlab/sidekiq/index.html) | [✅](https://docs.gitlab.com/charts/charts/gitlab/sidekiq/index.html) | [✅](https://docs.gitlab.com/ee/user/gitlab_com/#sidekiq) | CE & EE | | Gitaly | Git RPC service for handling all git calls made by GitLab | [✅](https://docs.gitlab.com/ee/administration/gitaly/) | [✅](https://docs.gitlab.com/charts/charts/gitlab/gitaly/index.html) | [✅](https://docs.gitlab.com/charts/charts/gitlab/gitaly/index.html) | [✅](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/#service-architecture) | CE & EE | @@ -342,18 +307,18 @@ class Geo defaultoff | Redis Exporter | Prometheus endpoint with Redis metrics | [✅](https://docs.gitlab.com/ee/administration/monitoring/prometheus/redis_exporter.html) | [✅](https://docs.gitlab.com/charts/charts/redis/index.html) | [✅](https://docs.gitlab.com/charts/charts/redis/index.html) | [✅](https://about.gitlab.com/handbook/engineering/monitoring/) | CE & EE | | PostgreSQL Exporter | Prometheus endpoint with PostgreSQL metrics | [✅](https://docs.gitlab.com/ee/administration/monitoring/prometheus/postgres_exporter.html) | [✅](https://github.com/helm/charts/tree/master/stable/postgresql) | [✅](https://github.com/helm/charts/tree/master/stable/postgresql) | [✅](https://about.gitlab.com/handbook/engineering/monitoring/) | CE & EE | | PgBouncer Exporter | Prometheus endpoint with PgBouncer metrics | [⚙](https://docs.gitlab.com/ee/administration/monitoring/prometheus/pgbouncer_exporter.html) | [❌](https://docs.gitlab.com/charts/installation/deployment.html#postgresql) | [❌](https://docs.gitlab.com/charts/installation/deployment.html#postgresql) | [✅](https://about.gitlab.com/handbook/engineering/monitoring/) | CE & EE | -| GitLab Monitor | Tracks a variety of GitLab metrics | [✅](https://docs.gitlab.com/ee/administration/monitoring/prometheus/gitlab_monitor_exporter.html) | [❌](https://gitlab.com/charts/gitlab/issues/319) | [❌](https://gitlab.com/charts/gitlab/issues/319) | [✅](https://about.gitlab.com/handbook/engineering/monitoring/) | CE & EE | +| GitLab Monitor | Generates a variety of GitLab metrics | [✅](https://docs.gitlab.com/ee/administration/monitoring/prometheus/gitlab_monitor_exporter.html) | [❌](https://gitlab.com/charts/gitlab/issues/319) | [❌](https://gitlab.com/charts/gitlab/issues/319) | [✅](https://about.gitlab.com/handbook/engineering/monitoring/) | CE & EE | | Mattermost | Open-source Slack alternative | [⚙](https://docs.gitlab.com/omnibus/gitlab-mattermost/) | [⤓](https://docs.mattermost.com/install/install-mmte-helm-gitlab-helm.html) | [⤓](https://docs.mattermost.com/install/install-mmte-helm-gitlab-helm.html) | [⤓](https://docs.gitlab.com/ee/user/project/integrations/mattermost_slash_commands.html#manual-configuration), [⤓](https://docs.gitlab.com/ee/user/project/integrations/mattermost.html) | CE & EE | -| Minio | Object storage service | [⤓](https://min.io/download) | [✅](https://docs.gitlab.com/charts/charts/minio/index.html) | [✅](https://docs.gitlab.com/charts/charts/minio/index.html) | [❌](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/#storage-architecture) | CE & EE | +| Minio | Object storage service | [⤓](https://min.io/download) | [✅](https://docs.gitlab.com/charts/charts/minio/index.html) | [✅](https://docs.gitlab.com/charts/charts/minio/index.html) | [✅](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/#storage-architecture) | CE & EE | | Runner | Executes GitLab CI jobs | [⤓](https://docs.gitlab.com/runner/) | [✅](https://docs.gitlab.com/runner/) | [⚙](https://docs.gitlab.com/runner/) | [✅](https://docs.gitlab.com/ee/user/gitlab_com/#shared-runners) | CE & EE | -| Migrations | Database migrations | [✅](https://docs.gitlab.com/omnibus/settings/database.html#disabling-automatic-database-migration) | [✅](https://docs.gitlab.com/charts/charts/gitlab/migrations/index.html) | [✅](https://docs.gitlab.com/charts/charts/gitlab/migrations/index.html) | [✅](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/#database-architecture) | CE & EE | +| DB Migrations | Database migrations | [✅](https://docs.gitlab.com/omnibus/settings/database.html#disabling-automatic-database-migration) | [✅](https://docs.gitlab.com/charts/charts/gitlab/migrations/index.html) | [✅](https://docs.gitlab.com/charts/charts/gitlab/migrations/index.html) | [✅](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/#database-architecture) | CE & EE | | Certificate Management | TLS Settings, Let's Encrypt | [✅](https://docs.gitlab.com/omnibus/settings/ssl.html) | [✅](https://docs.gitlab.com/charts/installation/tls.html) | [⚙](https://docs.gitlab.com/charts/installation/tls.html) | [✅](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/#secrets-management) | CE & EE | | GitLab Geo Node | Geographically distributed GitLab nodes | [⚙](https://docs.gitlab.com/ee/administration/geo/replication/index.html#setup-instructions) | [❌](https://gitlab.com/charts/gitlab/issues/8) | [❌](https://gitlab.com/charts/gitlab/issues/8) | ✅ | EE Only | | LDAP Authentication | Authenticate users against centralized LDAP directory | [⤓](https://docs.gitlab.com/ee/administration/auth/ldap.html) | [⤓](https://docs.gitlab.com/charts/charts/globals.html#ldap) | [⤓](https://docs.gitlab.com/charts/charts/globals.html#ldap) | [❌](https://about.gitlab.com/pricing/#gitlab-com) | CE & EE | | Outbound email (SMTP) | Send email messages to users | [⤓](https://docs.gitlab.com/omnibus/settings/smtp.html) | [⤓](https://docs.gitlab.com/charts/installation/command-line-options.html#outgoing-email-configuration) | [⤓](https://docs.gitlab.com/charts/installation/command-line-options.html#outgoing-email-configuration) | [✅](https://docs.gitlab.com/ee/user/gitlab_com/#mail-configuration) | CE & EE | | Inbound email (SMTP) | Receive messages to update issues | [⤓](https://docs.gitlab.com/ee/administration/incoming_email.html) | [⤓](https://docs.gitlab.com/charts/installation/command-line-options.html#incoming-email-configuration) | [⤓](https://docs.gitlab.com/charts/installation/command-line-options.html#incoming-email-configuration) | [✅](https://docs.gitlab.com/ee/user/gitlab_com/#mail-configuration) | CE & EE | | ElasticSearch | Improved search within GitLab | [⤓](https://docs.gitlab.com/ee/integration/elasticsearch.html) | [⤓](https://docs.gitlab.com/ee/integration/elasticsearch.html) | [⤓](https://docs.gitlab.com/ee/integration/elasticsearch.html) | [❌](https://gitlab.com/groups/gitlab-org/-/epics/153) | EE Only | -| Sentry: GitLab instance | Tracking errors generated by the GitLab instance | [⤓](https://docs.gitlab.com/omnibus/settings/configuration.html#error-reporting-and-logging-with-sentry) | [❌](https://gitlab.com/charts/gitlab/issues/1319) | [❌](https://gitlab.com/charts/gitlab/issues/1319) | [✅](https://about.gitlab.com/handbook/support/workflows/services/gitlab_com/500_errors.html#searching-sentry) | CE & EE | +| Sentry: GitLab instance | Track errors generated by the GitLab instance | [⤓](https://docs.gitlab.com/omnibus/settings/configuration.html#error-reporting-and-logging-with-sentry) | [❌](https://gitlab.com/charts/gitlab/issues/1319) | [❌](https://gitlab.com/charts/gitlab/issues/1319) | [✅](https://about.gitlab.com/handbook/support/workflows/services/gitlab_com/500_errors.html#searching-sentry) | CE & EE | | Jaeger: GitLab instance | View traces generated by the GitLab instance | [❌](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/4104) | [❌](https://gitlab.com/charts/gitlab/issues/1320) | [❌](https://gitlab.com/charts/gitlab/issues/1320) | [❌](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/4104) | CE & EE | | Sentry: deployed apps | Error tracking for deployed apps | [⤓](https://docs.gitlab.com/ee/user/project/operations/error_tracking.html) | [⤓](https://docs.gitlab.com/ee/user/project/operations/error_tracking.html) | [⤓](https://docs.gitlab.com/ee/user/project/operations/error_tracking.html) | [⤓](https://docs.gitlab.com/ee/user/project/operations/error_tracking.html) | CE & EE | | Jaeger: deployed apps | Distributed tracing for deployed apps | [⤓](https://docs.gitlab.com/ee/user/project/operations/tracing.html) | [⤓](https://docs.gitlab.com/ee/user/project/operations/tracing.html) | [⤓](https://docs.gitlab.com/ee/user/project/operations/tracing.html) | [⤓](https://docs.gitlab.com/ee/user/project/operations/tracing.html) | EE Only | diff --git a/doc/install/installation.md b/doc/install/installation.md index c694f0ed691..f16bc04af34 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -163,9 +163,9 @@ Download Ruby and compile it: ```sh mkdir /tmp/ruby && cd /tmp/ruby -curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.5/ruby-2.5.3.tar.gz -echo 'f919a9fbcdb7abecd887157b49833663c5c15fda ruby-2.5.3.tar.gz' | shasum -c - && tar xzf ruby-2.5.3.tar.gz -cd ruby-2.5.3 +curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.6/ruby-2.6.3.tar.gz +echo '2347ed6ca5490a104ebd5684d2b9b5eefa6cd33c ruby-2.6.3.tar.gz' | shasum -c - && tar xzf ruby-2.6.3.tar.gz +cd ruby-2.6.3 ./configure --disable-install-rdoc make diff --git a/doc/install/requirements.md b/doc/install/requirements.md index 672723aaf12..f6a52205a0e 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -169,7 +169,7 @@ So for a machine with 2 cores, 3 unicorn workers is ideal. For all machines that have 2GB and up we recommend a minimum of three unicorn workers. If you have a 1GB machine we recommend to configure only two Unicorn workers to prevent excessive swapping. -To change the Unicorn workers when you have the Omnibus package (which defaults to the recommendation above) please see [the Unicorn settings in the Omnibus GitLab documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/unicorn.md#unicorn-settings). +To change the Unicorn workers when you have the Omnibus package (which defaults to the recommendation above) please see [the Unicorn settings in the Omnibus GitLab documentation](https://docs.gitlab.com/omnibus/settings/unicorn.html). ## Redis and Sidekiq @@ -201,14 +201,13 @@ you decide to run GitLab Runner and the GitLab Rails application on the same machine. It is also not safe to install everything on a single machine, because of the -[security reasons] - especially when you plan to use shell executor with GitLab +[security reasons](https://docs.gitlab.com/runner/security/) +- especially when you plan to use shell executor with GitLab Runner. We recommend using a separate machine for each GitLab Runner, if you plan to use the CI features. -[security reasons]: https://gitlab.com/gitlab-org/gitlab-runner/blob/master/docs/security/index.md - ## Supported web browsers We support the current and the previous major release of: diff --git a/doc/update/mysql_to_postgresql.md b/doc/update/mysql_to_postgresql.md index e607dbceeea..b83abcd36f7 100644 --- a/doc/update/mysql_to_postgresql.md +++ b/doc/update/mysql_to_postgresql.md @@ -45,6 +45,22 @@ For other distributions, follow the instructions in PostgreSQL's [download page](https://www.postgresql.org/download/) to add their repository and then install `pgloader`. +If you are migrating to a Docker based installation, you will need to install +pgloader within the container as it is not included in the container image. + +1. Start a shell session in the context of the running container: + + ``` bash + docker exec -it gitlab bash + ``` + +1. Install pgloader: + + ``` bash + apt-get update + apt-get -y install pgloader + ``` + ## Omnibus GitLab installations For [Omnibus GitLab packages](https://about.gitlab.com/install/), you'll first diff --git a/doc/update/upgrading_from_source.md b/doc/update/upgrading_from_source.md index fea89669831..5118726cb0a 100644 --- a/doc/update/upgrading_from_source.md +++ b/doc/update/upgrading_from_source.md @@ -52,9 +52,9 @@ Download Ruby and compile it: ```bash mkdir /tmp/ruby && cd /tmp/ruby -curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.5/ruby-2.5.3.tar.gz -echo 'f919a9fbcdb7abecd887157b49833663c5c15fda ruby-2.5.3.tar.gz' | shasum -c - && tar xzf ruby-2.5.3.tar.gz -cd ruby-2.5.3 +curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.6/ruby-2.6.3.tar.gz +echo '2347ed6ca5490a104ebd5684d2b9b5eefa6cd33c ruby-2.6.3.tar.gz' | shasum -c - && tar xzf ruby-2.6.3.tar.gz +cd ruby-2.6.3 ./configure --disable-install-rdoc make diff --git a/doc/user/admin_area/settings/usage_statistics.md b/doc/user/admin_area/settings/usage_statistics.md index e165a120162..8b5d80efb0d 100644 --- a/doc/user/admin_area/settings/usage_statistics.md +++ b/doc/user/admin_area/settings/usage_statistics.md @@ -48,6 +48,8 @@ You can view the exact JSON payload in the administration panel. To view the pay 1. Expand **Settings** in the left sidebar and click on **Metrics and profiling**. 1. Expand **Usage statistics** and click on the **Preview payload** button. +You can see how [the usage ping data maps to different stages of the product](https://gitlab.com/gitlab-data/analytics/blob/master/transform/snowflake-dbt/data/ping_metrics_to_stage_mapping_data.csv). + ### Deactivate the usage ping The usage ping is opt-out. If you want to deactivate this feature, go to diff --git a/doc/user/project/pages/lets_encrypt_for_gitlab_pages.md b/doc/user/project/pages/lets_encrypt_for_gitlab_pages.md index ea22f3e905b..da1b7c59c8e 100644 --- a/doc/user/project/pages/lets_encrypt_for_gitlab_pages.md +++ b/doc/user/project/pages/lets_encrypt_for_gitlab_pages.md @@ -145,8 +145,8 @@ Now that your certificate has been issued, let's add it to your Pages site: 1. Visit your website at `https://example.com`. To force `https` connections on your site, navigate to your -project's **Settings > Pages** and check **Force domains with SSL -certificates to use HTTPS**. +project's **Settings > Pages** and check **Force HTTPS (requires +valid certificates)**. ## Renewal diff --git a/lib/gitlab/bitbucket_import/importer.rb b/lib/gitlab/bitbucket_import/importer.rb index 769d3279f91..c9f0ed66a54 100644 --- a/lib/gitlab/bitbucket_import/importer.rb +++ b/lib/gitlab/bitbucket_import/importer.rb @@ -135,7 +135,7 @@ module Gitlab def create_labels LABELS.each do |label_params| - label = ::Labels::CreateService.new(label_params).execute(project: project) + label = ::Labels::FindOrCreateService.new(nil, project, label_params).execute(skip_authorization: true) if label.valid? @labels[label_params[:title]] = label else diff --git a/lib/tasks/gitlab/backup.rake b/lib/tasks/gitlab/backup.rake index 3977fc7ad8c..c531eb1d216 100644 --- a/lib/tasks/gitlab/backup.rake +++ b/lib/tasks/gitlab/backup.rake @@ -20,6 +20,11 @@ namespace :gitlab do backup.pack backup.cleanup backup.remove_old + + puts "Warning: Your gitlab.rb and gitlab-secrets.json files contain sensitive data \n" \ + "and are not included in this backup. You will need these files to restore a backup.\n" \ + "Please back them up manually.".color(:red) + puts "Backup task is done." end # Restore backup of GitLab system @@ -68,6 +73,9 @@ namespace :gitlab do Rake::Task['cache:clear'].invoke backup.cleanup + puts "Warning: Your gitlab.rb and gitlab-secrets.json files contain sensitive data \n" \ + "and are not included in this backup. You will need to restore these files manually.".color(:red) + puts "Restore task is done." end namespace :repo do diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 2907430bd51..71ec8bcb9ba 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -1894,9 +1894,24 @@ msgstr "" msgid "CiVariables|Input variable value" msgstr "" +msgid "CiVariables|Key" +msgstr "" + +msgid "CiVariables|Masked" +msgstr "" + msgid "CiVariables|Remove variable row" msgstr "" +msgid "CiVariables|State" +msgstr "" + +msgid "CiVariables|Type" +msgstr "" + +msgid "CiVariables|Value" +msgstr "" + msgid "CiVariable|* (All environments)" msgstr "" @@ -4196,6 +4211,9 @@ msgstr "" msgid "Failed to remove user key." msgstr "" +msgid "Failed to save comment!" +msgstr "" + msgid "Failed to save merge conflicts resolutions. Please try again!" msgstr "" @@ -6974,12 +6992,33 @@ msgstr "" msgid "Preferences saved." msgstr "" +msgid "Preferences|Display time in 24-hour format" +msgstr "" + +msgid "Preferences|For example: 30 mins ago." +msgstr "" + msgid "Preferences|Navigation theme" msgstr "" +msgid "Preferences|These settings will update how dates and times are displayed for you." +msgstr "" + msgid "Preferences|This feature is experimental and translations are not complete yet" msgstr "" +msgid "Preferences|Time display" +msgstr "" + +msgid "Preferences|Time format" +msgstr "" + +msgid "Preferences|Time preferences" +msgstr "" + +msgid "Preferences|Use relative times" +msgstr "" + msgid "Press %{key}-C to copy" msgstr "" @@ -7022,6 +7061,9 @@ msgstr "" msgid "Private - The group and its projects can only be viewed by members." msgstr "" +msgid "Private profile" +msgstr "" + msgid "Private projects can be created in your personal namespace with:" msgstr "" @@ -7061,6 +7103,9 @@ msgstr "" msgid "Profiles|Avatar will be removed. Are you sure?" msgstr "" +msgid "Profiles|Bio" +msgstr "" + msgid "Profiles|Change username" msgstr "" @@ -7121,6 +7166,18 @@ msgstr "" msgid "Profiles|Enter your name, so people you know can recognize you" msgstr "" +msgid "Profiles|Feed token was successfully reset" +msgstr "" + +msgid "Profiles|Full name" +msgstr "" + +msgid "Profiles|Include private contributions on my profile" +msgstr "" + +msgid "Profiles|Incoming email token was successfully reset" +msgstr "" + msgid "Profiles|Increase your account's security by enabling Two-Factor Authentication (2FA)" msgstr "" @@ -7133,6 +7190,9 @@ msgstr "" msgid "Profiles|Learn more" msgstr "" +msgid "Profiles|Location" +msgstr "" + msgid "Profiles|Made a private contribution" msgstr "" @@ -7142,6 +7202,9 @@ msgstr "" msgid "Profiles|No file chosen" msgstr "" +msgid "Profiles|Organization" +msgstr "" + msgid "Profiles|Path" msgstr "" @@ -7151,6 +7214,9 @@ msgstr "" msgid "Profiles|Private contributions" msgstr "" +msgid "Profiles|Profile was successfully updated" +msgstr "" + msgid "Profiles|Public Avatar" msgstr "" @@ -7190,6 +7256,9 @@ msgstr "" msgid "Profiles|This information will appear on your profile" msgstr "" +msgid "Profiles|Time settings" +msgstr "" + msgid "Profiles|Two-Factor Authentication" msgstr "" @@ -7211,6 +7280,9 @@ msgstr "" msgid "Profiles|Use a private email - %{email}" msgstr "" +msgid "Profiles|User ID" +msgstr "" + msgid "Profiles|Username change failed - %{message}" msgstr "" @@ -7232,6 +7304,9 @@ msgstr "" msgid "Profiles|You can change your avatar here or remove the current avatar to revert to %{gravatar_link}" msgstr "" +msgid "Profiles|You can set your current timezone here" +msgstr "" + msgid "Profiles|You can upload your avatar here" msgstr "" diff --git a/qa/Dockerfile b/qa/Dockerfile index ca7f9accb70..74be373b8e8 100644 --- a/qa/Dockerfile +++ b/qa/Dockerfile @@ -1,4 +1,4 @@ -FROM ruby:2.5-stretch +FROM ruby:2.6-stretch LABEL maintainer "Grzegorz Bizon <grzegorz@gitlab.com>" ENV DEBIAN_FRONTEND noninteractive diff --git a/spec/controllers/projects/graphs_controller_spec.rb b/spec/controllers/projects/graphs_controller_spec.rb index d390e84c9b0..b5248c7f0c8 100644 --- a/spec/controllers/projects/graphs_controller_spec.rb +++ b/spec/controllers/projects/graphs_controller_spec.rb @@ -28,6 +28,21 @@ describe Projects::GraphsController do end describe 'charts' do + context 'with an anonymous user' do + let(:project) { create(:project, :repository, :public) } + + before do + sign_out(user) + end + + it 'renders charts with 200 status code' do + get(:charts, params: { namespace_id: project.namespace.path, project_id: project.path, id: 'master' }) + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to render_template(:charts) + end + end + context 'when languages were previously detected' do let(:project) { create(:project, :repository, detected_repository_languages: true) } let!(:repository_language) { create(:repository_language, project: project) } diff --git a/spec/features/dashboard/user_filters_projects_spec.rb b/spec/features/dashboard/user_filters_projects_spec.rb index 5b17c49db2d..4410c8f887f 100644 --- a/spec/features/dashboard/user_filters_projects_spec.rb +++ b/spec/features/dashboard/user_filters_projects_spec.rb @@ -151,34 +151,22 @@ describe 'Dashboard > User filters projects' do end describe 'Sorting' do - before do - [ - { name: 'Red ribbon army', created_at: 2.days.ago }, - { name: 'Cell saga', created_at: Time.now }, - { name: 'Frieza saga', created_at: 10.days.ago } - ].each do |item| - project = create(:project, name: item[:name], namespace: user.namespace, created_at: item[:created_at]) - project.add_developer(user) - end + let(:desc_sorted_project_names) { %w[Treasure Victorialand] } + before do user.toggle_star(project) user.toggle_star(project2) user2.toggle_star(project2) end - it 'includes sorting direction' do + it 'has all sorting options', :js do sorting_dropdown = page.find('.filtered-search-block #filtered-search-sorting-dropdown') expect(sorting_dropdown).to have_css '.reverse-sort-btn' - end - - it 'has all sorting options', :js do - sorting_dropdown = page.find('.filtered-search-block #filtered-search-sorting-dropdown') - sorting_option_labels = ['Last updated', 'Created date', 'Name', 'Stars'] sorting_dropdown.click - sorting_option_labels.each do |label| + ['Last updated', 'Created date', 'Name', 'Stars'].each do |label| expect(sorting_dropdown).to have_content(label) end end @@ -194,16 +182,11 @@ describe 'Dashboard > User filters projects' do it 'sorts the project list' do select_dropdown_option '#filtered-search-sorting-dropdown', 'Name' - desc = ['Victorialand', 'Treasure', 'Red ribbon army', 'Frieza saga', 'Cell saga'] - asc = ['Cell saga', 'Frieza saga', 'Red ribbon army', 'Treasure', 'Victorialand'] - - click_sort_direction - - expect_to_see_projects(desc) + expect_to_see_projects(desc_sorted_project_names) click_sort_direction - expect_to_see_projects(asc) + expect_to_see_projects(desc_sorted_project_names.reverse) end end @@ -211,16 +194,11 @@ describe 'Dashboard > User filters projects' do it 'sorts the project list' do select_dropdown_option '#filtered-search-sorting-dropdown', 'Last updated' - desc = ["Frieza saga", "Red ribbon army", "Victorialand", "Treasure", "Cell saga"] - asc = ["Cell saga", "Treasure", "Victorialand", "Red ribbon army", "Frieza saga"] + expect_to_see_projects(desc_sorted_project_names) click_sort_direction - expect_to_see_projects(desc) - - click_sort_direction - - expect_to_see_projects(asc) + expect_to_see_projects(desc_sorted_project_names.reverse) end end @@ -228,16 +206,11 @@ describe 'Dashboard > User filters projects' do it 'sorts the project list' do select_dropdown_option '#filtered-search-sorting-dropdown', 'Created date' - desc = ["Frieza saga", "Red ribbon army", "Victorialand", "Treasure", "Cell saga"] - asc = ["Cell saga", "Treasure", "Victorialand", "Red ribbon army", "Frieza saga"] - - click_sort_direction - - expect_to_see_projects(desc) + expect_to_see_projects(desc_sorted_project_names) click_sort_direction - expect_to_see_projects(asc) + expect_to_see_projects(desc_sorted_project_names.reverse) end end @@ -245,16 +218,11 @@ describe 'Dashboard > User filters projects' do it 'sorts the project list' do select_dropdown_option '#filtered-search-sorting-dropdown', 'Stars' - desc = ["Red ribbon army", "Cell saga", "Frieza saga", "Victorialand", "Treasure"] - asc = ["Treasure", "Victorialand", "Red ribbon army", "Cell saga", "Frieza saga"] - - click_sort_direction - - expect_to_see_projects(desc) + expect_to_see_projects(desc_sorted_project_names) click_sort_direction - expect_to_see_projects(asc) + expect_to_see_projects(desc_sorted_project_names.reverse) end end end diff --git a/spec/features/issuables/markdown_references/jira_spec.rb b/spec/features/issuables/markdown_references/jira_spec.rb index cc04798248c..8eaccfc0949 100644 --- a/spec/features/issuables/markdown_references/jira_spec.rb +++ b/spec/features/issuables/markdown_references/jira_spec.rb @@ -1,6 +1,6 @@ require "rails_helper" -describe "Jira", :js, :quarantine do +describe "Jira", :js do let(:user) { create(:user) } let(:actual_project) { create(:project, :public, :repository) } let(:merge_request) { create(:merge_request, target_project: actual_project, source_project: actual_project) } diff --git a/spec/features/profiles/user_edit_preferences_spec.rb b/spec/features/profiles/user_edit_preferences_spec.rb new file mode 100644 index 00000000000..2d2da222998 --- /dev/null +++ b/spec/features/profiles/user_edit_preferences_spec.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe 'User edit preferences profile' do + let(:user) { create(:user) } + + before do + stub_feature_flags(user_time_settings: true) + sign_in(user) + visit(profile_preferences_path) + end + + it 'allows the user to toggle their time format preference' do + field = page.find_field("user[time_format_in_24h]") + + expect(field).not_to be_checked + + field.click + + expect(field).to be_checked + end + + it 'allows the user to toggle their time display preference' do + field = page.find_field("user[time_display_relative]") + + expect(field).to be_checked + + field.click + + expect(field).not_to be_checked + end +end diff --git a/spec/features/profiles/user_edit_profile_spec.rb b/spec/features/profiles/user_edit_profile_spec.rb index b43711f6ef6..a53da94ef7d 100644 --- a/spec/features/profiles/user_edit_profile_spec.rb +++ b/spec/features/profiles/user_edit_profile_spec.rb @@ -327,5 +327,37 @@ describe 'User edit profile' do end end end + + context 'User time preferences', :js do + let(:issue) { create(:issue, project: project)} + let(:project) { create(:project) } + + before do + stub_feature_flags(user_time_settings: true) + end + + it 'shows the user time preferences form' do + expect(page).to have_content('Time settings') + end + + it 'allows the user to select a time zone from a dropdown list of options' do + expect(page.find('.user-time-preferences .dropdown')).not_to have_css('.show') + + page.find('.user-time-preferences .js-timezone-dropdown').click + + expect(page.find('.user-time-preferences .dropdown')).to have_css('.show') + + page.find("a", text: "Nuku'alofa").click + + tz = page.find('.user-time-preferences #user_timezone', visible: false) + + expect(tz.value).to eq('Pacific/Tongatapu') + end + + it 'timezone defaults to servers default' do + timezone_name = Time.zone.tzinfo.name + expect(page.find('.user-time-preferences #user_timezone', visible: false).value).to eq(timezone_name) + end + end end end diff --git a/spec/frontend/notes/components/discussion_notes_spec.js b/spec/frontend/notes/components/discussion_notes_spec.js index 392c1b6533e..7e037beca9d 100644 --- a/spec/frontend/notes/components/discussion_notes_spec.js +++ b/spec/frontend/notes/components/discussion_notes_spec.js @@ -1,4 +1,4 @@ -import { mount, createLocalVue } from '@vue/test-utils'; +import { shallowMount, createLocalVue } from '@vue/test-utils'; import '~/behaviors/markdown/render_gfm'; import { SYSTEM_NOTE } from '~/notes/constants'; import DiscussionNotes from '~/notes/components/discussion_notes.vue'; @@ -8,6 +8,7 @@ import PlaceholderSystemNote from '~/vue_shared/components/notes/placeholder_sys import SystemNote from '~/vue_shared/components/notes/system_note.vue'; import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue'; import createStore from '~/notes/stores'; +import { setTestTimeout } from 'helpers/timeout'; import { noteableDataMock, discussionMock, @@ -17,6 +18,8 @@ import { const localVue = createLocalVue(); describe('DiscussionNotes', () => { + setTestTimeout(500); + let wrapper; const createComponent = props => { @@ -24,7 +27,7 @@ describe('DiscussionNotes', () => { store.dispatch('setNoteableData', noteableDataMock); store.dispatch('setNotesData', notesDataMock); - wrapper = mount(DiscussionNotes, { + wrapper = shallowMount(DiscussionNotes, { localVue, store, propsData: { diff --git a/spec/frontend/notes/stores/utils_spec.js b/spec/frontend/notes/stores/utils_spec.js new file mode 100644 index 00000000000..b31b7491334 --- /dev/null +++ b/spec/frontend/notes/stores/utils_spec.js @@ -0,0 +1,17 @@ +import { hasQuickActions } from '~/notes/stores/utils'; + +describe('hasQuickActions', () => { + it.each` + input | expected + ${'some comment'} | ${false} + ${'/quickaction'} | ${true} + ${'some comment with\n/quickaction'} | ${true} + `('returns $expected for $input', ({ input, expected }) => { + expect(hasQuickActions(input)).toBe(expected); + }); + + it('is stateless', () => { + expect(hasQuickActions('some comment')).toBe(hasQuickActions('some comment')); + expect(hasQuickActions('/quickaction')).toBe(hasQuickActions('/quickaction')); + }); +}); diff --git a/spec/graphql/gitlab_schema_spec.rb b/spec/graphql/gitlab_schema_spec.rb index 05f10fb40f0..c138c87c4ac 100644 --- a/spec/graphql/gitlab_schema_spec.rb +++ b/spec/graphql/gitlab_schema_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' describe GitlabSchema do + let(:user) { build :user } + it 'uses batch loading' do expect(field_instrumenters).to include(BatchLoader::GraphQL) end @@ -33,43 +35,75 @@ describe GitlabSchema do expect(connection).to eq(Gitlab::Graphql::Connections::KeysetConnection) end - context 'for different types of users' do - it 'returns DEFAULT_MAX_COMPLEXITY for no context' do - expect(GraphQL::Schema) - .to receive(:execute) - .with('query', hash_including(max_complexity: GitlabSchema::DEFAULT_MAX_COMPLEXITY)) + describe '.execute' do + context 'for different types of users' do + context 'when no context' do + it 'returns DEFAULT_MAX_COMPLEXITY' do + expect(GraphQL::Schema) + .to receive(:execute) + .with('query', hash_including(max_complexity: GitlabSchema::DEFAULT_MAX_COMPLEXITY)) - described_class.execute('query') - end + described_class.execute('query') + end + end - it 'returns DEFAULT_MAX_COMPLEXITY for no user' do - expect(GraphQL::Schema) - .to receive(:execute) - .with('query', hash_including(max_complexity: GitlabSchema::DEFAULT_MAX_COMPLEXITY)) + context 'when no user' do + it 'returns DEFAULT_MAX_COMPLEXITY' do + expect(GraphQL::Schema) + .to receive(:execute) + .with('query', hash_including(max_complexity: GitlabSchema::DEFAULT_MAX_COMPLEXITY)) - described_class.execute('query', context: {}) - end + described_class.execute('query', context: {}) + end - it 'returns AUTHENTICATED_COMPLEXITY for a logged in user' do - user = build :user + it 'returns ANONYMOUS_MAX_DEPTH' do + expect(GraphQL::Schema) + .to receive(:execute) + .with('query', hash_including(max_depth: GitlabSchema::ANONYMOUS_MAX_DEPTH)) - expect(GraphQL::Schema).to receive(:execute).with('query', hash_including(max_complexity: GitlabSchema::AUTHENTICATED_COMPLEXITY)) + described_class.execute('query', context: {}) + end + end - described_class.execute('query', context: { current_user: user }) - end + context 'when a logged in user' do + it 'returns AUTHENTICATED_COMPLEXITY' do + expect(GraphQL::Schema).to receive(:execute).with('query', hash_including(max_complexity: GitlabSchema::AUTHENTICATED_COMPLEXITY)) - it 'returns ADMIN_COMPLEXITY for an admin user' do - user = build :user, :admin + described_class.execute('query', context: { current_user: user }) + end - expect(GraphQL::Schema).to receive(:execute).with('query', hash_including(max_complexity: GitlabSchema::ADMIN_COMPLEXITY)) + it 'returns AUTHENTICATED_MAX_DEPTH' do + expect(GraphQL::Schema).to receive(:execute).with('query', hash_including(max_depth: GitlabSchema::AUTHENTICATED_MAX_DEPTH)) - described_class.execute('query', context: { current_user: user }) - end + described_class.execute('query', context: { current_user: user }) + end + end + + context 'when an admin user' do + it 'returns ADMIN_COMPLEXITY' do + user = build :user, :admin + + expect(GraphQL::Schema).to receive(:execute).with('query', hash_including(max_complexity: GitlabSchema::ADMIN_COMPLEXITY)) + + described_class.execute('query', context: { current_user: user }) + end + end + + context 'when max_complexity passed on the query' do + it 'returns what was passed on the query' do + expect(GraphQL::Schema).to receive(:execute).with('query', hash_including(max_complexity: 1234)) + + described_class.execute('query', max_complexity: 1234) + end + end - it 'returns what was passed on the query' do - expect(GraphQL::Schema).to receive(:execute).with('query', { max_complexity: 1234 }) + context 'when max_depth passed on the query' do + it 'returns what was passed on the query' do + expect(GraphQL::Schema).to receive(:execute).with('query', hash_including(max_depth: 1234)) - described_class.execute('query', max_complexity: 1234) + described_class.execute('query', max_depth: 1234) + end + end end end diff --git a/spec/initializers/secret_token_spec.rb b/spec/initializers/secret_token_spec.rb index 726ce07a2d1..77bc28a6b07 100644 --- a/spec/initializers/secret_token_spec.rb +++ b/spec/initializers/secret_token_spec.rb @@ -45,11 +45,21 @@ describe 'create_tokens' do expect(keys).to all(match(RSA_KEY)) end + it "generates private key for Let's Encrypt" do + create_tokens + + keys = secrets.values_at(:lets_encrypt_private_key) + + expect(keys.uniq).to eq(keys) + expect(keys).to all(match(RSA_KEY)) + end + it 'warns about the secrets to add to secrets.yml' do expect(self).to receive(:warn_missing_secret).with('secret_key_base') expect(self).to receive(:warn_missing_secret).with('otp_key_base') expect(self).to receive(:warn_missing_secret).with('db_key_base') expect(self).to receive(:warn_missing_secret).with('openid_connect_signing_key') + expect(self).to receive(:warn_missing_secret).with('lets_encrypt_private_key') create_tokens end @@ -78,6 +88,7 @@ describe 'create_tokens' do before do secrets.db_key_base = 'db_key_base' secrets.openid_connect_signing_key = 'openid_connect_signing_key' + secrets.lets_encrypt_private_key = 'lets_encrypt_private_key' allow(File).to receive(:exist?).with('.secret').and_return(true) allow(File).to receive(:read).with('.secret').and_return('file_key') diff --git a/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js b/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js index 481b1a4d4b0..2839922fbd3 100644 --- a/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js +++ b/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js @@ -113,7 +113,7 @@ describe('AjaxFormVariableList', () => { it('hides secret values', done => { mock.onPatch(VARIABLE_PATCH_ENDPOINT).reply(200, {}); - const row = container.querySelector('.js-row:first-child'); + const row = container.querySelector('.js-row'); const valueInput = row.querySelector('.js-ci-variable-input-value'); const valuePlaceholder = row.querySelector('.js-secret-value-placeholder'); diff --git a/spec/javascripts/notes/stores/actions_spec.js b/spec/javascripts/notes/stores/actions_spec.js index 39901276b8c..7a9f32ddcff 100644 --- a/spec/javascripts/notes/stores/actions_spec.js +++ b/spec/javascripts/notes/stores/actions_spec.js @@ -794,6 +794,51 @@ describe('Actions Notes Store', () => { }); }); + describe('saveNote', () => { + const payload = { endpoint: TEST_HOST, data: { 'note[note]': 'some text' } }; + + describe('if response contains errors', () => { + const res = { errors: { something: ['went wrong'] } }; + + it('throws an error', done => { + actions + .saveNote( + { + commit() {}, + dispatch: () => Promise.resolve(res), + }, + payload, + ) + .then(() => done.fail('Expected error to be thrown!')) + .catch(error => { + expect(error.message).toBe('Failed to save comment!'); + }) + .then(done) + .catch(done.fail); + }); + }); + + describe('if response contains no errors', () => { + const res = { valid: true }; + + it('returns the response', done => { + actions + .saveNote( + { + commit() {}, + dispatch: () => Promise.resolve(res), + }, + payload, + ) + .then(data => { + expect(data).toBe(res); + }) + .then(done) + .catch(done.fail); + }); + }); + }); + describe('submitSuggestion', () => { const discussionId = 'discussion-id'; const noteId = 'note-id'; diff --git a/spec/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown_spec.js b/spec/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown_spec.js index a89952ee435..5f4dba5ecb9 100644 --- a/spec/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown_spec.js +++ b/spec/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown_spec.js @@ -3,6 +3,7 @@ import GLDropdown from '~/gl_dropdown'; // eslint-disable-line no-unused-vars import TimezoneDropdown, { formatUtcOffset, formatTimezone, + findTimezoneByIdentifier, } from '~/pages/projects/pipeline_schedules/shared/components/timezone_dropdown'; describe('Timezone Dropdown', function() { @@ -12,6 +13,7 @@ describe('Timezone Dropdown', function() { let $dropdownEl = null; let $wrapper = null; const tzListSel = '.dropdown-content ul li a.is-active'; + const tzDropdownToggleText = '.dropdown-toggle-text'; describe('Initialize', () => { describe('with dropdown already loaded', () => { @@ -94,6 +96,36 @@ describe('Timezone Dropdown', function() { expect(onSelectTimezone).toHaveBeenCalled(); }); + + it('will correctly set the dropdown label if a timezone identifier is set on the inputEl', () => { + $inputEl.val('America/St_Johns'); + + // eslint-disable-next-line no-new + new TimezoneDropdown({ + $inputEl, + $dropdownEl, + displayFormat: selectedItem => formatTimezone(selectedItem), + }); + + expect($wrapper.find(tzDropdownToggleText).html()).toEqual('[UTC - 2.5] Newfoundland'); + }); + + it('will call a provided `displayFormat` handler to format the dropdown value', () => { + const displayFormat = jasmine.createSpy('displayFormat'); + // eslint-disable-next-line no-new + new TimezoneDropdown({ + $inputEl, + $dropdownEl, + displayFormat, + }); + + $wrapper + .find(tzListSel) + .first() + .trigger('click'); + + expect(displayFormat).toHaveBeenCalled(); + }); }); }); @@ -164,4 +196,49 @@ describe('Timezone Dropdown', function() { ).toEqual('[UTC 0] Accra'); }); }); + + describe('findTimezoneByIdentifier', () => { + const tzList = [ + { + identifier: 'Asia/Tokyo', + name: 'Sapporo', + offset: 32400, + }, + { + identifier: 'Asia/Hong_Kong', + name: 'Hong Kong', + offset: 28800, + }, + { + identifier: 'Asia/Dhaka', + name: 'Dhaka', + offset: 21600, + }, + ]; + + const identifier = 'Asia/Dhaka'; + it('returns the correct object if the identifier exists', () => { + const res = findTimezoneByIdentifier(tzList, identifier); + + expect(res).toBeTruthy(); + expect(res).toBe(tzList[2]); + }); + + it('returns null if it doesnt find the identifier', () => { + const res = findTimezoneByIdentifier(tzList, 'Australia/Melbourne'); + + expect(res).toBeNull(); + }); + + it('returns null if there is no identifier given', () => { + expect(findTimezoneByIdentifier(tzList)).toBeNull(); + expect(findTimezoneByIdentifier(tzList, '')).toBeNull(); + }); + + it('returns null if there is an empty or invalid array given', () => { + expect(findTimezoneByIdentifier([], identifier)).toBeNull(); + expect(findTimezoneByIdentifier(null, identifier)).toBeNull(); + expect(findTimezoneByIdentifier(undefined, identifier)).toBeNull(); + }); + }); }); diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb index e1a2bae5fe8..a02c00e3340 100644 --- a/spec/lib/gitlab/bitbucket_import/importer_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb @@ -222,6 +222,46 @@ describe Gitlab::BitbucketImport::Importer do body: {}.to_json) end + context 'creating labels on project' do + before do + allow(importer).to receive(:import_wiki) + end + + it 'creates labels as expected' do + expect { importer.execute }.to change { Label.count }.from(0).to(Gitlab::BitbucketImport::Importer::LABELS.size) + end + + it 'does not fail if label is already existing' do + label = Gitlab::BitbucketImport::Importer::LABELS.first + ::Labels::CreateService.new(label).execute(project: project) + + expect { importer.execute }.not_to raise_error + end + + it 'does not create new labels' do + Gitlab::BitbucketImport::Importer::LABELS.each do |label| + create(:label, project: project, title: label[:title]) + end + + expect { importer.execute }.not_to change { Label.count } + end + + it 'does not update existing ones' do + label_title = Gitlab::BitbucketImport::Importer::LABELS.first[:title] + existing_label = create(:label, project: project, title: label_title) + # Reload label from database so we avoid timestamp comparison issues related to time precision when comparing + # attributes later. + existing_label.reload + + Timecop.freeze(Time.now + 1.minute) do + importer.execute + + label_after_import = project.labels.find(existing_label.id) + expect(label_after_import.attributes).to eq(existing_label.attributes) + end + end + end + it 'maps statuses to open or closed' do allow(importer).to receive(:import_wiki) diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index 4e910e67ac2..aa975c8bb0b 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -35,7 +35,7 @@ describe Gitlab::UsageData do subject { described_class.data } it "gathers usage data" do - expect(subject.keys).to match_array(%i( + expect(subject.keys).to include(*%i( active_user_count counts recorded_at @@ -68,7 +68,7 @@ describe Gitlab::UsageData do expect(count_data[:boards]).to eq(1) expect(count_data[:projects]).to eq(3) - expect(count_data.keys).to match_array(%i( + expect(count_data.keys).to include(*%i( assignee_lists boards ci_builds diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb index 4a7eee1fbf3..04ae9390436 100644 --- a/spec/models/project_services/jira_service_spec.rb +++ b/spec/models/project_services/jira_service_spec.rb @@ -166,6 +166,13 @@ describe JiraService do ).once end + it 'does not fail if remote_link.all on issue returns nil' do + allow(JIRA::Resource::Remotelink).to receive(:all).and_return(nil) + + expect { @jira_service.close_issue(resource, ExternalIssue.new('JIRA-123', project)) } + .not_to raise_error(NoMethodError) + end + # Check https://developer.atlassian.com/jiradev/jira-platform/guides/other/guide-jira-remote-issue-links/fields-in-remote-issue-links # for more information it 'creates Remote Link reference in JIRA for comment' do diff --git a/spec/models/user_preference_spec.rb b/spec/models/user_preference_spec.rb index b2ef17a81d4..e09c91e874a 100644 --- a/spec/models/user_preference_spec.rb +++ b/spec/models/user_preference_spec.rb @@ -73,4 +73,10 @@ describe UserPreference do it_behaves_like 'a sort_by preference' end end + + describe '#timezone' do + it 'returns server time as default' do + expect(user_preference.timezone).to eq(Time.zone.tzinfo.name) + end + end end diff --git a/spec/requests/api/graphql/gitlab_schema_spec.rb b/spec/requests/api/graphql/gitlab_schema_spec.rb index b63b4fb34df..dd518274f82 100644 --- a/spec/requests/api/graphql/gitlab_schema_spec.rb +++ b/spec/requests/api/graphql/gitlab_schema_spec.rb @@ -3,15 +3,43 @@ require 'spec_helper' describe 'GitlabSchema configurations' do include GraphqlHelpers - it 'shows an error if complexity is too high' do - project = create(:project, :repository) - query = graphql_query_for('project', { 'fullPath' => project.full_path }, %w(id name description)) + let(:project) { create(:project, :repository) } + let(:query) { graphql_query_for('project', { 'fullPath' => project.full_path }, %w(id name description)) } + let(:current_user) { create(:user) } - allow(GitlabSchema).to receive(:max_query_complexity).and_return 1 + describe '#max_complexity' do + context 'when complexity is too high' do + it 'shows an error' do + allow(GitlabSchema).to receive(:max_query_complexity).and_return 1 - post_graphql(query, current_user: nil) + post_graphql(query, current_user: nil) - expect(graphql_errors.first['message']).to include('which exceeds max complexity of 1') + expect(graphql_errors.first['message']).to include('which exceeds max complexity of 1') + end + end + end + + describe '#max_depth' do + context 'when query depth is too high' do + it 'shows error' do + errors = [{ "message" => "Query has depth of 2, which exceeds max depth of 1" }] + allow(GitlabSchema).to receive(:max_query_depth).and_return 1 + + post_graphql(query) + + expect(graphql_errors).to eq(errors) + end + end + + context 'when query depth is within range' do + it 'has no error' do + allow(GitlabSchema).to receive(:max_query_depth).and_return 5 + + post_graphql(query) + + expect(graphql_errors).to be_nil + end + end end context 'when IntrospectionQuery' do diff --git a/spec/services/members/create_service_spec.rb b/spec/services/members/create_service_spec.rb index 2c4fb131ed9..674fe0f666e 100644 --- a/spec/services/members/create_service_spec.rb +++ b/spec/services/members/create_service_spec.rb @@ -44,7 +44,18 @@ describe Members::CreateService do result = described_class.new(user, params).execute(project) expect(result[:status]).to eq(:error) - expect(result[:message]).to include(project_user.username) + expect(result[:message]).to include("#{project_user.username}: Access level is not included in the list") expect(project.users).not_to include project_user end + + it 'does not add a member with an existing invite' do + invited_member = create(:project_member, :invited, project: project) + + params = { user_ids: invited_member.invite_email, + access_level: Gitlab::Access::GUEST } + result = described_class.new(user, params).execute(project) + + expect(result[:status]).to eq(:error) + expect(result[:message]).to eq('Invite email has already been taken') + end end diff --git a/spec/services/projects/after_import_service_spec.rb b/spec/services/projects/after_import_service_spec.rb index 95c11f71c5e..51d3fd18881 100644 --- a/spec/services/projects/after_import_service_spec.rb +++ b/spec/services/projects/after_import_service_spec.rb @@ -15,7 +15,7 @@ describe Projects::AfterImportService do describe '#execute' do before do allow(Projects::HousekeepingService) - .to receive(:new).with(project, :gc).and_return(housekeeping_service) + .to receive(:new).with(project).and_return(housekeeping_service) allow(housekeeping_service) .to receive(:execute).and_yield diff --git a/spec/services/projects/detect_repository_languages_service_spec.rb b/spec/services/projects/detect_repository_languages_service_spec.rb index e3e561c971c..df5eed18ac0 100644 --- a/spec/services/projects/detect_repository_languages_service_spec.rb +++ b/spec/services/projects/detect_repository_languages_service_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' describe Projects::DetectRepositoryLanguagesService, :clean_gitlab_redis_shared_state do set(:project) { create(:project, :repository) } - subject { described_class.new(project, project.owner) } + subject { described_class.new(project) } describe '#execute' do context 'without previous detection' do diff --git a/spec/services/projects/repository_languages_service_spec.rb b/spec/services/projects/repository_languages_service_spec.rb index 09c61363ad2..46c5095327d 100644 --- a/spec/services/projects/repository_languages_service_spec.rb +++ b/spec/services/projects/repository_languages_service_spec.rb @@ -10,7 +10,7 @@ describe Projects::RepositoryLanguagesService do context 'when a project is without detected programming languages' do it 'schedules a worker and returns the empty result' do - expect(::DetectRepositoryLanguagesWorker).to receive(:perform_async).with(project.id, project.owner.id) + expect(::DetectRepositoryLanguagesWorker).to receive(:perform_async).with(project.id) expect(service.execute).to eq([]) end end @@ -19,7 +19,7 @@ describe Projects::RepositoryLanguagesService do let!(:repository_language) { create(:repository_language, project: project) } it 'does not schedule a worker and returns the detected languages' do - expect(::DetectRepositoryLanguagesWorker).not_to receive(:perform_async).with(project.id, project.owner.id) + expect(::DetectRepositoryLanguagesWorker).not_to receive(:perform_async).with(project.id) languages = service.execute diff --git a/spec/services/users/update_service_spec.rb b/spec/services/users/update_service_spec.rb index e04c05418b0..9384287f98a 100644 --- a/spec/services/users/update_service_spec.rb +++ b/spec/services/users/update_service_spec.rb @@ -13,6 +13,15 @@ describe Users::UpdateService do expect(user.name).to eq('New Name') end + it 'updates time preferences' do + result = update_user(user, timezone: 'Europe/Warsaw', time_display_relative: true, time_format_in_24h: false) + + expect(result).to eq(status: :success) + expect(user.reload.timezone).to eq('Europe/Warsaw') + expect(user.time_display_relative).to eq(true) + expect(user.time_format_in_24h).to eq(false) + end + it 'returns an error result when record cannot be updated' do result = {} expect do diff --git a/spec/support/features/variable_list_shared_examples.rb b/spec/support/features/variable_list_shared_examples.rb index 693b796fbdc..92a19dd22a2 100644 --- a/spec/support/features/variable_list_shared_examples.rb +++ b/spec/support/features/variable_list_shared_examples.rb @@ -17,7 +17,7 @@ shared_examples 'variable list' do visit page_path # We check the first row because it re-sorts to alphabetical order on refresh - page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do + page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do expect(find('.js-ci-variable-input-key').value).to eq('key') expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key_value') end @@ -38,7 +38,7 @@ shared_examples 'variable list' do visit page_path # We check the first row because it re-sorts to alphabetical order on refresh - page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do + page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do expect(find('.js-ci-variable-input-key').value).to eq('key') expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key_value') expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true') @@ -59,7 +59,7 @@ shared_examples 'variable list' do visit page_path # We check the first row because it re-sorts to alphabetical order on refresh - page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do + page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do expect(find('.js-ci-variable-input-key').value).to eq('key') expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key_value') expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true') @@ -116,19 +116,19 @@ shared_examples 'variable list' do page.within('.js-ci-variable-list-section') do expect(first('.js-ci-variable-input-key').value).to eq(variable.key) expect(first('.js-ci-variable-input-value', visible: false).value).to eq(variable.value) - expect(page).to have_content('*' * 20) + expect(page).to have_content('*' * 17) click_button('Reveal value') expect(first('.js-ci-variable-input-key').value).to eq(variable.key) expect(first('.js-ci-variable-input-value').value).to eq(variable.value) - expect(page).not_to have_content('*' * 20) + expect(page).not_to have_content('*' * 17) click_button('Hide value') expect(first('.js-ci-variable-input-key').value).to eq(variable.key) expect(first('.js-ci-variable-input-value', visible: false).value).to eq(variable.value) - expect(page).to have_content('*' * 20) + expect(page).to have_content('*' * 17) end end @@ -149,7 +149,7 @@ shared_examples 'variable list' do page.within('.js-ci-variable-list-section') do click_button('Reveal value') - page.within('.js-row:nth-child(1)') do + page.within('.js-row:nth-child(2)') do find('.js-ci-variable-input-key').set('new_key') find('.js-ci-variable-input-value').set('new_value') end @@ -159,7 +159,7 @@ shared_examples 'variable list' do visit page_path - page.within('.js-row:nth-child(1)') do + page.within('.js-row:nth-child(2)') do expect(find('.js-ci-variable-input-key').value).to eq('new_key') expect(find('.js-ci-variable-input-value', visible: false).value).to eq('new_value') end @@ -181,7 +181,7 @@ shared_examples 'variable list' do visit page_path # We check the first row because it re-sorts to alphabetical order on refresh - page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do + page.within('.js-ci-variable-list-section .js-row:nth-child(3)') do find('.ci-variable-protected-item .js-project-feature-toggle').click expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true') @@ -193,7 +193,7 @@ shared_examples 'variable list' do visit page_path # We check the first row because it re-sorts to alphabetical order on refresh - page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do + page.within('.js-ci-variable-list-section .js-row:nth-child(3)') do expect(find('.js-ci-variable-input-key').value).to eq('unprotected_key') expect(find('.js-ci-variable-input-value', visible: false).value).to eq('unprotected_value') expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true') @@ -215,7 +215,7 @@ shared_examples 'variable list' do visit page_path - page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do + page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do find('.ci-variable-protected-item .js-project-feature-toggle').click expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('false') @@ -226,7 +226,7 @@ shared_examples 'variable list' do visit page_path - page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do + page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do expect(find('.js-ci-variable-input-key').value).to eq('protected_key') expect(find('.js-ci-variable-input-value', visible: false).value).to eq('protected_value') expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('false') @@ -234,7 +234,7 @@ shared_examples 'variable list' do end it 'edits variable to be unmasked' do - page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do + page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true') find('.ci-variable-masked-item .js-project-feature-toggle').click @@ -247,13 +247,13 @@ shared_examples 'variable list' do visit page_path - page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do + page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false') end end it 'edits variable to be masked' do - page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do + page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true') find('.ci-variable-masked-item .js-project-feature-toggle').click @@ -266,7 +266,7 @@ shared_examples 'variable list' do visit page_path - page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do + page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false') find('.ci-variable-masked-item .js-project-feature-toggle').click @@ -279,7 +279,7 @@ shared_examples 'variable list' do visit page_path - page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do + page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true') end end @@ -302,7 +302,7 @@ shared_examples 'variable list' do expect(page).to have_selector('.js-row', count: 4) # Remove the `akey` variable - page.within('.js-row:nth-child(2)') do + page.within('.js-row:nth-child(3)') do first('.js-row-remove-button').click end diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb index b49d743fb9a..f15944652fd 100644 --- a/spec/support/helpers/graphql_helpers.rb +++ b/spec/support/helpers/graphql_helpers.rb @@ -102,6 +102,7 @@ module GraphqlHelpers def all_graphql_fields_for(class_name, parent_types = Set.new) allow_unlimited_graphql_complexity + allow_unlimited_graphql_depth type = GitlabSchema.types[class_name.to_s] return "" unless type @@ -190,4 +191,9 @@ module GraphqlHelpers allow_any_instance_of(GitlabSchema).to receive(:max_complexity).and_return nil allow(GitlabSchema).to receive(:max_query_complexity).with(any_args).and_return nil end + + def allow_unlimited_graphql_depth + allow_any_instance_of(GitlabSchema).to receive(:max_depth).and_return nil + allow(GitlabSchema).to receive(:max_query_depth).with(any_args).and_return nil + end end diff --git a/spec/support/helpers/project_forks_helper.rb b/spec/support/helpers/project_forks_helper.rb index 9a86560da2a..bcb11a09b36 100644 --- a/spec/support/helpers/project_forks_helper.rb +++ b/spec/support/helpers/project_forks_helper.rb @@ -1,5 +1,11 @@ module ProjectForksHelper def fork_project(project, user = nil, params = {}) + Gitlab::GitalyClient.allow_n_plus_1_calls do + fork_project_direct(project, user, params) + end + end + + def fork_project_direct(project, user = nil, params = {}) # Load the `fork_network` for the project to fork as there might be one that # wasn't loaded yet. project.reload unless project.fork_network @@ -44,11 +50,16 @@ module ProjectForksHelper end def fork_project_with_submodules(project, user = nil, params = {}) - forked_project = fork_project(project, user, params) - TestEnv.copy_repo(forked_project, - bare_repo: TestEnv.forked_repo_path_bare, - refs: TestEnv::FORKED_BRANCH_SHA) - forked_project.repository.after_import - forked_project + Gitlab::GitalyClient.allow_n_plus_1_calls do + forked_project = fork_project_direct(project, user, params) + TestEnv.copy_repo( + forked_project, + bare_repo: TestEnv.forked_repo_path_bare, + refs: TestEnv::FORKED_BRANCH_SHA + ) + forked_project.repository.after_import + + forked_project + end end end diff --git a/spec/workers/detect_repository_languages_worker_spec.rb b/spec/workers/detect_repository_languages_worker_spec.rb index dbf32555985..755eb8dbf6b 100644 --- a/spec/workers/detect_repository_languages_worker_spec.rb +++ b/spec/workers/detect_repository_languages_worker_spec.rb @@ -4,7 +4,6 @@ require 'spec_helper' describe DetectRepositoryLanguagesWorker do set(:project) { create(:project) } - let(:user) { project.owner } subject { described_class.new } @@ -14,19 +13,13 @@ describe DetectRepositoryLanguagesWorker do allow(::Projects::DetectRepositoryLanguagesService).to receive(:new).and_return(service) expect(service).to receive(:execute) - subject.perform(project.id, user.id) + subject.perform(project.id) end context 'when invalid ids are used' do it 'does not raise when the project could not be found' do expect do - subject.perform(-1, user.id) - end.not_to raise_error - end - - it 'does not raise when the user could not be found' do - expect do - subject.perform(project.id, -1) + subject.perform(-1) end.not_to raise_error end end |