diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-18 10:34:06 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-18 10:34:06 +0000 |
commit | 859a6fb938bb9ee2a317c46dfa4fcc1af49608f0 (patch) | |
tree | d7f2700abe6b4ffcb2dcfc80631b2d87d0609239 /app/controllers | |
parent | 446d496a6d000c73a304be52587cd9bbc7493136 (diff) | |
download | gitlab-ce-859a6fb938bb9ee2a317c46dfa4fcc1af49608f0.tar.gz |
Add latest changes from gitlab-org/gitlab@13-9-stable-eev13.9.0-rc42
Diffstat (limited to 'app/controllers')
65 files changed, 571 insertions, 247 deletions
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 179e6ef60fb..7f7d38a09c5 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -238,11 +238,13 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController *::ApplicationSettingsHelper.visible_attributes, *::ApplicationSettingsHelper.external_authorization_service_attributes, *ApplicationSetting.repository_storages_weighted_attributes, + *ApplicationSetting.kroki_formats_attributes.keys.map { |key| "kroki_formats_#{key}".to_sym }, :lets_encrypt_notification_email, :lets_encrypt_terms_of_service_accepted, :domain_denylist_file, :raw_blob_request_limit, :issues_create_limit, + :notes_create_limit, :default_branch_name, disabled_oauth_sign_in_sources: [], import_sources: [], diff --git a/app/controllers/admin/cohorts_controller.rb b/app/controllers/admin/cohorts_controller.rb index a26dc554506..9bb73c822b0 100644 --- a/app/controllers/admin/cohorts_controller.rb +++ b/app/controllers/admin/cohorts_controller.rb @@ -1,19 +1,11 @@ # frozen_string_literal: true class Admin::CohortsController < Admin::ApplicationController - include Analytics::UniqueVisitsHelper - - track_unique_visits :index, target_id: 'i_analytics_cohorts' - feature_category :devops_reports + # Backwards compatibility. Remove it and routing in 14.0 + # @see https://gitlab.com/gitlab-org/gitlab/-/issues/299303 def index - if Gitlab::CurrentSettings.usage_ping_enabled - cohorts_results = Rails.cache.fetch('cohorts', expires_in: 1.day) do - CohortsService.new.execute - end - - @cohorts = CohortsSerializer.new.represent(cohorts_results) - end + redirect_to admin_users_path(tab: 'cohorts') end end diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 3fe972d1917..d0761083c8b 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -2,6 +2,7 @@ class Admin::UsersController < Admin::ApplicationController include RoutableActions + include Analytics::UniqueVisitsHelper before_action :user, except: [:index, :new, :create] before_action :check_impersonation_availability, only: :impersonate @@ -15,6 +16,10 @@ class Admin::UsersController < Admin::ApplicationController @users = @users.includes(:authorized_projects) # rubocop: disable CodeReuse/ActiveRecord @users = @users.sort_by_attribute(@sort = params[:sort]) @users = @users.page(params[:page]) + + @cohorts = load_cohorts + + track_cohorts_visit if params[:tab] == 'cohorts' end def show @@ -307,6 +312,22 @@ class Admin::UsersController < Admin::ApplicationController def log_impersonation_event Gitlab::AppLogger.info(_("User %{current_user_username} has started impersonating %{username}") % { current_user_username: current_user.username, username: user.username }) end + + def load_cohorts + if Gitlab::CurrentSettings.usage_ping_enabled + cohorts_results = Rails.cache.fetch('cohorts', expires_in: 1.day) do + CohortsService.new.execute + end + + CohortsSerializer.new.represent(cohorts_results) + end + end + + def track_cohorts_visit + if request.format.html? && request.headers['DNT'] != '1' + track_visit('i_analytics_cohorts') + end + end end Admin::UsersController.prepend_if_ee('EE::Admin::UsersController') diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 3cb7373a970..5f14d95ffed 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -556,4 +556,4 @@ class ApplicationController < ActionController::Base end end -ApplicationController.prepend_if_ee('EE::ApplicationController') +ApplicationController.prepend_ee_mod diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb index 9ee69c7c07f..79e45bcf929 100644 --- a/app/controllers/autocomplete_controller.rb +++ b/app/controllers/autocomplete_controller.rb @@ -18,7 +18,7 @@ class AutocompleteController < ApplicationController .new(params: params, current_user: current_user, project: project, group: group) .execute - render json: UserSerializer.new(params).represent(users, project: project) + render json: UserSerializer.new(params.merge({ current_user: current_user })).represent(users, project: project) end def user diff --git a/app/controllers/chaos_controller.rb b/app/controllers/chaos_controller.rb index e0d1f313fc7..0ec6a2cb38a 100644 --- a/app/controllers/chaos_controller.rb +++ b/app/controllers/chaos_controller.rb @@ -23,6 +23,15 @@ class ChaosController < ActionController::Base do_chaos :kill, Chaos::KillWorker end + def gc + gc_stat = Gitlab::Chaos.run_gc + + render json: { + worker_id: Prometheus::PidProvider.worker_id, + gc_stat: gc_stat + } + end + private def do_chaos(method, worker, *args) diff --git a/app/controllers/concerns/boards_actions.rb b/app/controllers/concerns/boards_actions.rb index b382e338a78..79e6f027c2f 100644 --- a/app/controllers/concerns/boards_actions.rb +++ b/app/controllers/concerns/boards_actions.rb @@ -34,16 +34,26 @@ module BoardsActions def boards strong_memoize(:boards) do - Boards::ListService.new(parent, current_user).execute + existing_boards = boards_finder.execute + if existing_boards.any? + existing_boards + else + # if no board exists, create one + [board_create_service.execute.payload] + end end end def board strong_memoize(:board) do - boards.find(params[:id]) + board_finder.execute.first end end + def board_type + board_klass.to_type + end + def serializer BoardSerializer.new(current_user: current_user) end diff --git a/app/controllers/concerns/boards_responses.rb b/app/controllers/concerns/boards_responses.rb index d8bc1320db4..6e6686f225c 100644 --- a/app/controllers/concerns/boards_responses.rb +++ b/app/controllers/concerns/boards_responses.rb @@ -66,7 +66,11 @@ module BoardsResponses end def respond_with_board - respond_with(@board) # rubocop:disable Gitlab/ModuleWithInstanceVariables + # rubocop:disable Gitlab/ModuleWithInstanceVariables + return render_404 unless @board + + respond_with(@board) + # rubocop:enable Gitlab/ModuleWithInstanceVariables end def serialize_as_json(resource) diff --git a/app/controllers/concerns/comment_and_close_flag.rb b/app/controllers/concerns/comment_and_close_flag.rb new file mode 100644 index 00000000000..e2f3272abbc --- /dev/null +++ b/app/controllers/concerns/comment_and_close_flag.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module CommentAndCloseFlag + extend ActiveSupport::Concern + + included do + before_action do + push_frontend_feature_flag(:remove_comment_close_reopen, @group) + end + end +end diff --git a/app/controllers/concerns/integrations_actions.rb b/app/controllers/concerns/integrations_actions.rb index baebedb8e5d..a3ea39d9c3d 100644 --- a/app/controllers/concerns/integrations_actions.rb +++ b/app/controllers/concerns/integrations_actions.rb @@ -34,10 +34,6 @@ module IntegrationsActions end end - def custom_integration_projects - Project.with_custom_integration_compared_to(integration).page(params[:page]).per(20) - end - def test render json: {}, status: :ok end diff --git a/app/controllers/concerns/membership_actions.rb b/app/controllers/concerns/membership_actions.rb index 816a93f14c6..9e3625d1b36 100644 --- a/app/controllers/concerns/membership_actions.rb +++ b/app/controllers/concerns/membership_actions.rb @@ -18,18 +18,26 @@ module MembershipActions def update update_params = params.require(root_params_key).permit(:access_level, :expires_at) member = membershipable.members_and_requesters.find(params[:id]) - member = Members::UpdateService + result = Members::UpdateService .new(current_user, update_params) .execute(member) - if member.expires? - render json: { - expires_in: helpers.distance_of_time_in_words_to_now(member.expires_at), - expires_soon: member.expires_soon?, - expires_at_formatted: member.expires_at.to_time.in_time_zone.to_s(:medium) - } + member = result[:member] + + member_data = if member.expires? + { + expires_in: helpers.distance_of_time_in_words_to_now(member.expires_at), + expires_soon: member.expires_soon?, + expires_at_formatted: member.expires_at.to_time.in_time_zone.to_s(:medium) + } + else + {} + end + + if result[:status] == :success + render json: member_data else - render json: {} + render json: { message: result[:message] }, status: :unprocessable_entity end end diff --git a/app/controllers/concerns/multiple_boards_actions.rb b/app/controllers/concerns/multiple_boards_actions.rb index 370b8c72bfe..5206f5759d8 100644 --- a/app/controllers/concerns/multiple_boards_actions.rb +++ b/app/controllers/concerns/multiple_boards_actions.rb @@ -65,6 +65,7 @@ module MultipleBoardsActions private def redirect_to_recent_board + return unless board_type == Board.to_type return if request.format.json? || !parent.multiple_issue_boards_available? || !latest_visited_board redirect_to board_path(latest_visited_board.board) diff --git a/app/controllers/concerns/notes_actions.rb b/app/controllers/concerns/notes_actions.rb index bfa7a30bc65..036d95622ef 100644 --- a/app/controllers/concerns/notes_actions.rb +++ b/app/controllers/concerns/notes_actions.rb @@ -31,9 +31,9 @@ module NotesActions # We know there's more data, so tell the frontend to poll again after 1ms set_polling_interval_header(interval: 1) if meta[:more] - # Only present an ETag for the empty response to ensure pagination works - # as expected - ::Gitlab::EtagCaching::Middleware.skip!(response) if notes.present? + # We might still want to investigate further adjusting ETag caching with paginated notes, but + # let's avoid ETag caching for now until we confirm the viability of paginated notes. + ::Gitlab::EtagCaching::Middleware.skip!(response) render json: meta.merge(notes: notes) end @@ -243,7 +243,8 @@ module NotesActions :type, :note, :line_code, # LegacyDiffNote - :position # DiffNote + :position, # DiffNote + :confidential ).tap do |create_params| create_params.merge!( params.permit(:merge_request_diff_head_sha, :in_reply_to_discussion_id) diff --git a/app/controllers/concerns/redis_tracking.rb b/app/controllers/concerns/redis_tracking.rb index d71935356b8..a7e75f802a8 100644 --- a/app/controllers/concerns/redis_tracking.rb +++ b/app/controllers/concerns/redis_tracking.rb @@ -7,30 +7,26 @@ # # include RedisTracking # -# track_redis_hll_event :index, :show, name: 'i_analytics_dev_ops_score', feature: :my_feature -# -# if the feature flag is enabled by default you should use -# track_redis_hll_event :index, :show, name: 'i_analytics_dev_ops_score', feature: :my_feature, feature_default_enabled: true +# track_redis_hll_event :index, :show, name: 'i_analytics_dev_ops_score' # # You can also pass custom conditions using `if:`, using the same format as with Rails callbacks. module RedisTracking extend ActiveSupport::Concern class_methods do - def track_redis_hll_event(*controller_actions, name:, feature:, feature_default_enabled: false, if: nil) + def track_redis_hll_event(*controller_actions, name:, if: nil) custom_conditions = Array.wrap(binding.local_variable_get('if')) conditions = [:trackable_request?, *custom_conditions] after_action only: controller_actions, if: conditions do - track_unique_redis_hll_event(name, feature, feature_default_enabled) + track_unique_redis_hll_event(name) end end end private - def track_unique_redis_hll_event(event_name, feature, feature_default_enabled) - return unless metric_feature_enabled?(feature, feature_default_enabled) + def track_unique_redis_hll_event(event_name) return unless visitor_id Gitlab::UsageDataCounters::HLLRedisCounter.track_event(event_name, values: visitor_id) @@ -40,10 +36,6 @@ module RedisTracking request.format.html? && request.headers['DNT'] != '1' end - def metric_feature_enabled?(feature, default_enabled) - Feature.enabled?(feature, default_enabled: default_enabled) - end - def visitor_id return cookies[:visitor_id] if cookies[:visitor_id].present? return unless current_user diff --git a/app/controllers/concerns/service_params.rb b/app/controllers/concerns/service_params.rb index c295290a123..3cab198c1f9 100644 --- a/app/controllers/concerns/service_params.rb +++ b/app/controllers/concerns/service_params.rb @@ -12,6 +12,7 @@ module ServiceParams :api_version, :bamboo_url, :branches_to_be_notified, + :labels_to_be_notified, :build_key, :build_type, :ca_pem, diff --git a/app/controllers/concerns/snippets_actions.rb b/app/controllers/concerns/snippets_actions.rb index c93e75b438b..0ee8d0c9307 100644 --- a/app/controllers/concerns/snippets_actions.rb +++ b/app/controllers/concerns/snippets_actions.rb @@ -15,7 +15,7 @@ module SnippetsActions skip_before_action :verify_authenticity_token, if: -> { action_name == 'show' && js_request? } - track_redis_hll_event :show, name: 'i_snippets_show', feature: :usage_data_i_snippets_show, feature_default_enabled: true + track_redis_hll_event :show, name: 'i_snippets_show' respond_to :html end diff --git a/app/controllers/concerns/spammable_actions.rb b/app/controllers/concerns/spammable_actions.rb index 4ec561014a8..b285faee9bc 100644 --- a/app/controllers/concerns/spammable_actions.rb +++ b/app/controllers/concerns/spammable_actions.rb @@ -3,9 +3,6 @@ module SpammableActions extend ActiveSupport::Concern - include Recaptcha::Verify - include Gitlab::Utils::StrongMemoize - included do before_action :authorize_submit_spammable!, only: :mark_as_spam end @@ -20,17 +17,11 @@ module SpammableActions private - def ensure_spam_config_loaded! - strong_memoize(:spam_config_loaded) do - Gitlab::Recaptcha.load_configurations! - end - end - def recaptcha_check_with_fallback(should_redirect = true, &fallback) if should_redirect && spammable.valid? redirect_to spammable_path - elsif render_recaptcha? - ensure_spam_config_loaded! + elsif spammable.render_recaptcha? + Gitlab::Recaptcha.load_configurations! respond_to do |format| format.html do @@ -50,33 +41,30 @@ module SpammableActions end def spammable_params - default_params = { request: request } - - recaptcha_check = recaptcha_response && - ensure_spam_config_loaded! && - verify_recaptcha(response: recaptcha_response) - - return default_params unless recaptcha_check - - { recaptcha_verified: true, - spam_log_id: params[:spam_log_id] }.merge(default_params) - end - - def recaptcha_response - # NOTE: This field name comes from `Recaptcha::ClientHelper#recaptcha_tags` in the recaptcha - # gem, which is called from the HAML `_recaptcha_form.html.haml` form. + # NOTE: For the legacy reCAPTCHA implementation based on the HTML/HAML form, the + # 'g-recaptcha-response' field name comes from `Recaptcha::ClientHelper#recaptcha_tags` in the + # recaptcha gem, which is called from the HAML `_recaptcha_form.html.haml` form. # - # It is used in the `Recaptcha::Verify#verify_recaptcha` if the `response` option is not - # passed explicitly. + # It is used in the `Recaptcha::Verify#verify_recaptcha` to extract the value from `params`, + # if the `response` option is not passed explicitly. # # Instead of relying on this behavior, we are extracting and passing it explicitly. This will # make it consistent with the newer, modern reCAPTCHA verification process as it will be # implemented via the GraphQL API and in Vue components via the native reCAPTCHA Javascript API, # which requires that the recaptcha response param be obtained and passed explicitly. # - # After this newer GraphQL/JS API process is fully supported by the backend, we can remove this - # (and other) HAML-specific support. - params['g-recaptcha-response'] + # It can also be expanded to multiple fields when we move to future alternative captcha + # implementations such as FriendlyCaptcha. See https://gitlab.com/gitlab-org/gitlab/-/issues/273480 + + # After this newer GraphQL/JS API process is fully supported by the backend, we can remove the + # check for the 'g-recaptcha-response' field and other HTML/HAML form-specific support. + captcha_response = params['g-recaptcha-response'] + + { + request: request, + spam_log_id: params[:spam_log_id], + captcha_response: captcha_response + } end def spammable @@ -90,11 +78,4 @@ module SpammableActions def authorize_submit_spammable! access_denied! unless current_user.admin? end - - def render_recaptcha? - return false if spammable.errors.count > 1 # re-render "new" template in case there are other errors - return false unless Gitlab::Recaptcha.enabled? - - spammable.needs_recaptcha? - end end diff --git a/app/controllers/concerns/wiki_actions.rb b/app/controllers/concerns/wiki_actions.rb index 1ae90edd8f7..4014e4f0024 100644 --- a/app/controllers/concerns/wiki_actions.rb +++ b/app/controllers/concerns/wiki_actions.rb @@ -36,8 +36,7 @@ module WikiActions # NOTE: We want to include wiki page views in the same counter as the other # Event-based wiki actions tracked through TrackUniqueEvents, so we use the same event name. - track_redis_hll_event :show, name: Gitlab::UsageDataCounters::TrackUniqueEvents::WIKI_ACTION.to_s, - feature: :track_unique_wiki_page_views, feature_default_enabled: true + track_redis_hll_event :show, name: Gitlab::UsageDataCounters::TrackUniqueEvents::WIKI_ACTION.to_s helper_method :view_file_button, :diff_file_html_data diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index a88cf64d842..29cb60ad3cc 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -33,6 +33,21 @@ class DashboardController < Dashboard::ApplicationController protected def load_events + @events = + if params[:filter] == "followed" + load_user_events + else + load_project_events + end + + Events::RenderService.new(current_user).execute(@events) + end + + def load_user_events + UserRecentEventsFinder.new(current_user, current_user.followees, event_filter, params).execute + end + + def load_project_events projects = if params[:filter] == "starred" ProjectsFinder.new(current_user: current_user, params: { starred: true }).execute @@ -40,12 +55,10 @@ class DashboardController < Dashboard::ApplicationController current_user.authorized_projects end - @events = EventCollection + EventCollection .new(projects, offset: params[:offset].to_i, filter: event_filter) .to_a .map(&:present) - - Events::RenderService.new(current_user).execute(@events) end def set_show_full_reference diff --git a/app/controllers/graphql_controller.rb b/app/controllers/graphql_controller.rb index 1852405e7cf..152f07b4c16 100644 --- a/app/controllers/graphql_controller.rb +++ b/app/controllers/graphql_controller.rb @@ -20,6 +20,7 @@ class GraphqlController < ApplicationController before_action :authorize_access_api! before_action(only: [:execute]) { authenticate_sessionless_user!(:api) } before_action :set_user_last_activity + before_action :track_vs_code_usage # Since we deactivate authentication from the main ApplicationController and # defer it to :authorize_access_api!, we need to override the bypass session @@ -64,6 +65,10 @@ class GraphqlController < ApplicationController Users::ActivityService.new(current_user).execute end + def track_vs_code_usage + Gitlab::UsageDataCounters::VSCodeExtensionActivityUniqueCounter.track_api_request_when_trackable(user_agent: request.user_agent, user: current_user) + end + def execute_multiplex GitlabSchema.multiplex(multiplex_queries, context: context) end diff --git a/app/controllers/groups/boards_controller.rb b/app/controllers/groups/boards_controller.rb index 093cdf258b2..fa109021b7d 100644 --- a/app/controllers/groups/boards_controller.rb +++ b/app/controllers/groups/boards_controller.rb @@ -3,6 +3,7 @@ class Groups::BoardsController < Groups::ApplicationController include BoardsActions include RecordUserLastActivity + include Gitlab::Utils::StrongMemoize before_action :authorize_read_board!, only: [:index, :show] before_action :assign_endpoint_vars @@ -14,10 +15,32 @@ class Groups::BoardsController < Groups::ApplicationController private + def board_klass + Board + end + + def boards_finder + strong_memoize :boards_finder do + Boards::ListService.new(parent, current_user) + end + end + + def board_finder + strong_memoize :board_finder do + Boards::ListService.new(parent, current_user, board_id: params[:id]) + end + end + + def board_create_service + strong_memoize :board_create_service do + Boards::CreateService.new(parent, current_user) + end + end + def assign_endpoint_vars - @boards_endpoint = group_boards_url(group) + @boards_endpoint = group_boards_path(group) @namespace_path = group.to_param - @labels_endpoint = group_labels_url(group) + @labels_endpoint = group_labels_path(group) end def authorize_read_board! diff --git a/app/controllers/groups/email_campaigns_controller.rb b/app/controllers/groups/email_campaigns_controller.rb new file mode 100644 index 00000000000..cbb0176ea7b --- /dev/null +++ b/app/controllers/groups/email_campaigns_controller.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +class Groups::EmailCampaignsController < Groups::ApplicationController + include InProductMarketingHelper + include Gitlab::Tracking::ControllerConcern + + EMAIL_CAMPAIGNS_SCHEMA_URL = 'iglu:com.gitlab/email_campaigns/jsonschema/1-0-0' + + feature_category :navigation + + before_action :check_params + + def index + track_click + redirect_to redirect_link + end + + private + + def track_click + data = { + namespace_id: group.id, + track: @track, + series: @series, + subject_line: subject_line(@track, @series) + } + + track_self_describing_event(EMAIL_CAMPAIGNS_SCHEMA_URL, data: data) + end + + def redirect_link + case @track + when :create + create_track_url + when :verify + project_pipelines_url(group.projects.first) + when :trial + 'https://about.gitlab.com/free-trial/' + when :team + group_group_members_url(group) + end + end + + def create_track_url + [ + new_project_url, + new_project_url(anchor: 'import_project'), + help_page_url('user/project/repository/repository_mirroring') + ][@series] + end + + def check_params + @track = params[:track]&.to_sym + @series = params[:series]&.to_i + + track_valid = @track.in?(Namespaces::InProductMarketingEmailsService::TRACKS.keys) + series_valid = @series.in?(0..Namespaces::InProductMarketingEmailsService::INTERVAL_DAYS.size - 1) + + render_404 unless track_valid && series_valid + end +end diff --git a/app/controllers/groups/settings/packages_and_registries_controller.rb b/app/controllers/groups/settings/packages_and_registries_controller.rb index dbc1e68742b..0135c03026c 100644 --- a/app/controllers/groups/settings/packages_and_registries_controller.rb +++ b/app/controllers/groups/settings/packages_and_registries_controller.rb @@ -4,11 +4,18 @@ module Groups module Settings class PackagesAndRegistriesController < Groups::ApplicationController before_action :authorize_admin_group! + before_action :verify_packages_enabled! feature_category :package_registry def index end + + private + + def verify_packages_enabled! + render_404 unless group.packages_feature_enabled? + end end end end diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 068815f7f07..9d7aebe4505 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -30,6 +30,7 @@ class GroupsController < Groups::ApplicationController before_action do push_frontend_feature_flag(:vue_issuables_list, @group) + push_frontend_feature_flag(:vue_notification_dropdown, @group, default_enabled: :yaml) end before_action do @@ -319,9 +320,7 @@ class GroupsController < Groups::ApplicationController private - def successful_creation_hooks - track_experiment_event(:onboarding_issues, 'created_namespace') - end + def successful_creation_hooks; end def groups if @group.supports_events? diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb index 5a5200452de..e995562f0c4 100644 --- a/app/controllers/help_controller.rb +++ b/app/controllers/help_controller.rb @@ -84,7 +84,16 @@ class HelpController < ApplicationController end def documentation_base_url - @documentation_base_url ||= Gitlab::CurrentSettings.current_application_settings.help_page_documentation_base_url.presence + @documentation_base_url ||= documentation_base_url_from_yml_configuration || documentation_base_url_from_db + end + + # DEPRECATED + def documentation_base_url_from_db + Gitlab::CurrentSettings.current_application_settings.help_page_documentation_base_url.presence + end + + def documentation_base_url_from_yml_configuration + ::Gitlab.config.gitlab_docs.host.presence if ::Gitlab.config.gitlab_docs.enabled end def documentation_file_path diff --git a/app/controllers/import/bulk_imports_controller.rb b/app/controllers/import/bulk_imports_controller.rb index 7394e8bf615..ef32ba4d119 100644 --- a/app/controllers/import/bulk_imports_controller.rb +++ b/app/controllers/import/bulk_imports_controller.rb @@ -22,7 +22,13 @@ class Import::BulkImportsController < ApplicationController def status respond_to do |format| format.json do - render json: { importable_data: serialized_importable_data } + data = importable_data + + pagination_headers.each do |header| + response.set_header(header, data.headers[header]) + end + + render json: { importable_data: serialized_data(data.parsed_response) } end format.html do @source_url = session[url_key] @@ -31,9 +37,8 @@ class Import::BulkImportsController < ApplicationController end def create - BulkImportService.new(current_user, create_params, credentials).execute - - render json: :ok + result = BulkImportService.new(current_user, create_params, credentials).execute + render json: result.to_json(only: [:id]) end def realtime_changes @@ -44,8 +49,12 @@ class Import::BulkImportsController < ApplicationController private - def serialized_importable_data - serializer.represent(importable_data, {}, Import::BulkImportEntity) + def pagination_headers + %w[x-next-page x-page x-per-page x-prev-page x-total x-total-pages] + end + + def serialized_data(data) + serializer.represent(data, {}, Import::BulkImportEntity) end def serializer @@ -53,7 +62,7 @@ class Import::BulkImportsController < ApplicationController end def importable_data - client.get('groups', query_params).parsed_response + client.get('groups', query_params) end # Default query string params used to fetch groups from GitLab source instance @@ -74,7 +83,9 @@ class Import::BulkImportsController < ApplicationController def client @client ||= BulkImports::Clients::Http.new( uri: session[url_key], - token: session[access_token_key] + token: session[access_token_key], + per_page: params[:per_page], + page: params[:page] ) end diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb index ad92645c23e..08a23dc8927 100644 --- a/app/controllers/invites_controller.rb +++ b/app/controllers/invites_controller.rb @@ -6,6 +6,7 @@ class InvitesController < ApplicationController before_action :member before_action :ensure_member_exists before_action :invite_details + before_action :set_invite_type, only: :show skip_before_action :authenticate_user!, only: :decline helper_method :member?, :current_user_matches_invite? @@ -15,11 +16,16 @@ class InvitesController < ApplicationController feature_category :authentication_and_authorization def show + experiment('members/invite_email', actor: member).track(:opened) if initial_invite_email? + accept if skip_invitation_prompt? end def accept if member.accept_invite!(current_user) + experiment('members/invite_email', actor: member).track(:accepted) if initial_invite_email? + session.delete(:invite_type) + redirect_to invite_details[:path], notice: _("You have been granted %{member_human_access} access to %{title} %{name}.") % { member_human_access: member.human_access, title: invite_details[:title], name: invite_details[:name] } else @@ -29,7 +35,7 @@ class InvitesController < ApplicationController def decline if member.decline_invite! - return render layout: 'devise_experimental_onboarding_issues' if !current_user && member.invite_to_unknown_user? && member.created_by + return render layout: 'signup_onboarding' if !current_user && member.invite_to_unknown_user? && member.created_by path = if current_user @@ -47,6 +53,14 @@ class InvitesController < ApplicationController private + def set_invite_type + session[:invite_type] = params[:invite_type] if params[:invite_type].in?([Members::InviteEmailExperiment::INVITE_TYPE]) + end + + def initial_invite_email? + session[:invite_type] == Members::InviteEmailExperiment::INVITE_TYPE + end + def skip_invitation_prompt? !member? && current_user_matches_invite? end diff --git a/app/controllers/jira_connect/users_controller.rb b/app/controllers/jira_connect/users_controller.rb index 571d9f87779..569dc42fed3 100644 --- a/app/controllers/jira_connect/users_controller.rb +++ b/app/controllers/jira_connect/users_controller.rb @@ -3,7 +3,7 @@ class JiraConnect::UsersController < ApplicationController feature_category :integrations - layout 'devise_experimental_onboarding_issues' + layout 'signup_onboarding' def show @jira_app_link = params.delete(:return_to) diff --git a/app/controllers/profiles/notifications_controller.rb b/app/controllers/profiles/notifications_controller.rb index a3e7638cdbc..0a73239709a 100644 --- a/app/controllers/profiles/notifications_controller.rb +++ b/app/controllers/profiles/notifications_controller.rb @@ -29,7 +29,7 @@ class Profiles::NotificationsController < Profiles::ApplicationController end def user_params - params.require(:user).permit(:notification_email, :notified_of_own_activity) + params.require(:user).permit(:notification_email, :email_opted_in, :notified_of_own_activity) end private diff --git a/app/controllers/profiles/preferences_controller.rb b/app/controllers/profiles/preferences_controller.rb index 4d88491e9a8..add5046e213 100644 --- a/app/controllers/profiles/preferences_controller.rb +++ b/app/controllers/profiles/preferences_controller.rb @@ -9,23 +9,18 @@ class Profiles::PreferencesController < Profiles::ApplicationController end def update - begin - result = Users::UpdateService.new(current_user, preferences_params.merge(user: user)).execute - - if result[:status] == :success - flash[:notice] = _('Preferences saved.') - else - flash[:alert] = _('Failed to save preferences.') - end - rescue ArgumentError => e - # Raised when `dashboard` is given an invalid value. - flash[:alert] = _("Failed to save preferences (%{error_message}).") % { error_message: e.message } - end + result = Users::UpdateService.new(current_user, preferences_params.merge(user: user)).execute + if result[:status] == :success + message = _('Preferences saved.') - respond_to do |format| - format.html { redirect_to profile_preferences_path } - format.js + render json: { type: :notice, message: message } + else + render status: :bad_request, json: { type: :alert, message: _('Failed to save preferences.') } end + rescue ArgumentError => e + # Raised when `dashboard` is given an invalid value. + message = _("Failed to save preferences (%{error_message}).") % { error_message: e.message } + render status: :bad_request, json: { type: :alert, message: message } end private diff --git a/app/controllers/projects/badges_controller.rb b/app/controllers/projects/badges_controller.rb index 855965ca6e1..f75ab5cdbf2 100644 --- a/app/controllers/projects/badges_controller.rb +++ b/app/controllers/projects/badges_controller.rb @@ -9,7 +9,7 @@ class Projects::BadgesController < Projects::ApplicationController feature_category :continuous_integration def pipeline - pipeline_status = Gitlab::Badge::Pipeline::Status + pipeline_status = Gitlab::Ci::Badge::Pipeline::Status .new(project, params[:ref], opts: { ignore_skipped: params[:ignore_skipped], key_text: params[:key_text], @@ -20,7 +20,7 @@ class Projects::BadgesController < Projects::ApplicationController end def coverage - coverage_report = Gitlab::Badge::Coverage::Report + coverage_report = Gitlab::Ci::Badge::Coverage::Report .new(project, params[:ref], opts: { job: params[:job], key_text: params[:key_text], diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index 8c66f45dd79..3bb00978aac 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -35,7 +35,7 @@ class Projects::BlobController < Projects::ApplicationController record_experiment_user(:ci_syntax_templates, namespace_id: @project.namespace_id) if params[:file_name] == @project.ci_config_path_or_default end - track_redis_hll_event :create, :update, name: 'g_edit_by_sfe', feature: :track_editor_edit_actions, feature_default_enabled: true + track_redis_hll_event :create, :update, name: 'g_edit_by_sfe' feature_category :source_code_management diff --git a/app/controllers/projects/boards_controller.rb b/app/controllers/projects/boards_controller.rb index 51c9bf3699a..d2e5d319f96 100644 --- a/app/controllers/projects/boards_controller.rb +++ b/app/controllers/projects/boards_controller.rb @@ -15,6 +15,28 @@ class Projects::BoardsController < Projects::ApplicationController private + def board_klass + Board + end + + def boards_finder + strong_memoize :boards_finder do + Boards::ListService.new(parent, current_user) + end + end + + def board_finder + strong_memoize :board_finder do + Boards::ListService.new(parent, current_user, board_id: params[:id]) + end + end + + def board_create_service + strong_memoize :board_create_service do + Boards::CreateService.new(parent, current_user) + end + end + def assign_endpoint_vars @boards_endpoint = project_boards_path(project) @bulk_issues_path = bulk_update_project_issues_path(project) diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb index a753d5705aa..6f3c96fa654 100644 --- a/app/controllers/projects/branches_controller.rb +++ b/app/controllers/projects/branches_controller.rb @@ -177,10 +177,8 @@ class Projects::BranchesController < Projects::ApplicationController def fetch_branches_by_mode return fetch_branches_for_overview if @mode == 'overview' - # active/stale/all view mode - @branches = BranchesFinder.new(@repository, params.merge(sort: @sort)).execute - @branches = @branches.select { |b| b.state.to_s == @mode } if %w[active stale].include?(@mode) - @branches = Kaminari.paginate_array(@branches).page(params[:page]) + @branches, @prev_path, @next_path = + Projects::BranchesByModeService.new(@project, params.merge(sort: @sort, mode: @mode)).execute end def fetch_branches_for_overview diff --git a/app/controllers/projects/ci/daily_build_group_report_results_controller.rb b/app/controllers/projects/ci/daily_build_group_report_results_controller.rb index d05ab1b4977..aabcb74cefa 100644 --- a/app/controllers/projects/ci/daily_build_group_report_results_controller.rb +++ b/app/controllers/projects/ci/daily_build_group_report_results_controller.rb @@ -40,7 +40,25 @@ class Projects::Ci::DailyBuildGroupReportResultsController < Projects::Applicati end def report_results - Ci::DailyBuildGroupReportResultsFinder.new(**finder_params).execute + if ::Gitlab::Ci::Features.use_coverage_data_new_finder?(project) + ::Ci::Testing::DailyBuildGroupReportResultsFinder.new( + params: new_finder_params, + current_user: current_user + ).execute + else + Ci::DailyBuildGroupReportResultsFinder.new(**finder_params).execute + end + end + + def new_finder_params + { + project: project, + coverage: true, + start_date: start_date, + end_date: end_date, + ref_path: params[:ref_path], + sort: true + } end def finder_params diff --git a/app/controllers/projects/ci/pipeline_editor_controller.rb b/app/controllers/projects/ci/pipeline_editor_controller.rb index ef9025ae52f..3552915b561 100644 --- a/app/controllers/projects/ci/pipeline_editor_controller.rb +++ b/app/controllers/projects/ci/pipeline_editor_controller.rb @@ -4,6 +4,7 @@ class Projects::Ci::PipelineEditorController < Projects::ApplicationController before_action :check_can_collaborate! before_action do push_frontend_feature_flag(:ci_config_visualization_tab, @project, default_enabled: :yaml) + push_frontend_feature_flag(:ci_config_merged_tab, @project, default_enabled: :yaml) end feature_category :pipeline_authoring diff --git a/app/controllers/projects/ci/prometheus_metrics/histograms_controller.rb b/app/controllers/projects/ci/prometheus_metrics/histograms_controller.rb new file mode 100644 index 00000000000..003441d4b91 --- /dev/null +++ b/app/controllers/projects/ci/prometheus_metrics/histograms_controller.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Projects + module Ci + module PrometheusMetrics + class HistogramsController < Projects::ApplicationController + feature_category :pipeline_authoring + + respond_to :json, only: [:create] + + def create + result = ::Ci::PrometheusMetrics::ObserveHistogramsService.new(project, permitted_params).execute + + render json: result.payload, status: result.http_status + end + + private + + def permitted_params + params.permit(histograms: [:name, :value]) + end + end + end + end +end diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index 2e48f2f0e45..ffdd9fca95b 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -18,8 +18,12 @@ class Projects::CommitController < Projects::ApplicationController before_action :define_commit_vars, only: [:show, :diff_for_path, :diff_files, :pipelines, :merge_requests] before_action :define_note_vars, only: [:show, :diff_for_path, :diff_files] before_action :authorize_edit_tree!, only: [:revert, :cherry_pick] + before_action only: [:pipelines] do + push_frontend_feature_flag(:ci_mini_pipeline_gl_dropdown, @project, type: :development, default_enabled: :yaml) + end BRANCH_SEARCH_LIMIT = 1000 + COMMIT_DIFFS_PER_PAGE = 75 feature_category :source_code_management diff --git a/app/controllers/projects/discussions_controller.rb b/app/controllers/projects/discussions_controller.rb index b9ab1076999..708b7a6c7ba 100644 --- a/app/controllers/projects/discussions_controller.rb +++ b/app/controllers/projects/discussions_controller.rb @@ -18,7 +18,7 @@ class Projects::DiscussionsController < Projects::ApplicationController end def unresolve - discussion.unresolve! + Discussions::UnresolveService.new(discussion, current_user).execute render_discussion end diff --git a/app/controllers/projects/forks_controller.rb b/app/controllers/projects/forks_controller.rb index 1c2930f6e9b..5576d5766c7 100644 --- a/app/controllers/projects/forks_controller.rb +++ b/app/controllers/projects/forks_controller.rb @@ -86,7 +86,7 @@ class Projects::ForksController < Projects::ApplicationController def fork_service strong_memoize(:fork_service) do - ::Projects::ForkService.new(project, current_user, namespace: fork_namespace) + ::Projects::ForkService.new(project, current_user, fork_params) end end @@ -96,6 +96,12 @@ class Projects::ForksController < Projects::ApplicationController end end + def fork_params + params.permit(:path, :name, :description, :visibility).tap do |param| + param[:namespace] = fork_namespace + end + end + def authorize_fork_namespace! access_denied! unless fork_namespace && fork_service.valid_fork_target? end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 3a0e40f9745..2816977277a 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -9,6 +9,7 @@ class Projects::IssuesController < Projects::ApplicationController include IssuesCalendar include SpammableActions include RecordUserLastActivity + include CommentAndCloseFlag ISSUES_EXCEPT_ACTIONS = %i[index calendar new create bulk_update import_csv export_csv service_desk].freeze SET_ISSUEABLES_INDEX_ONLY_ACTIONS = %i[index calendar service_desk].freeze @@ -41,7 +42,6 @@ class Projects::IssuesController < Projects::ApplicationController before_action :create_rate_limit, only: [:create] before_action do - push_frontend_feature_flag(:vue_issuable_sidebar, project.group) push_frontend_feature_flag(:tribute_autocomplete, @project) push_frontend_feature_flag(:vue_issuables_list, project) push_frontend_feature_flag(:usage_data_design_action, project, default_enabled: true) @@ -52,6 +52,7 @@ class Projects::IssuesController < Projects::ApplicationController real_time_enabled = Gitlab::ActionCable::Config.in_app? || Feature.enabled?(real_time_feature_flag, @project) push_to_gon_attributes(:features, real_time_feature_flag, real_time_enabled) + push_frontend_feature_flag(:confidential_notes, @project, default_enabled: :yaml) record_experiment_user(:invite_members_version_a) record_experiment_user(:invite_members_version_b) @@ -60,8 +61,7 @@ class Projects::IssuesController < Projects::ApplicationController around_action :allow_gitaly_ref_name_caching, only: [:discussions] before_action :run_null_hypothesis_experiment, - only: [:index, :new, :create], - if: -> { Feature.enabled?(:gitlab_experiments) } + only: [:index, :new, :create] respond_to :html @@ -106,7 +106,7 @@ class Projects::IssuesController < Projects::ApplicationController build_params = issue_create_params.merge( merge_request_to_resolve_discussions_of: params[:merge_request_to_resolve_discussions_of], discussion_to_resolve: params[:discussion_to_resolve], - confidential: !!Gitlab::Utils.to_boolean(params[:issue][:confidential]) + confidential: !!Gitlab::Utils.to_boolean(issue_create_params[:confidential]) ) service = ::Issues::BuildService.new(project, current_user, build_params) @@ -131,7 +131,7 @@ class Projects::IssuesController < Projects::ApplicationController service = ::Issues::CreateService.new(project, current_user, create_params) @issue = service.execute - create_vulnerability_issue_link(issue) + create_vulnerability_issue_feedback(issue) if service.discussions_to_resolve.count(&:resolved?) > 0 flash[:notice] = if service.discussion_to_resolve_id @@ -145,9 +145,6 @@ class Projects::IssuesController < Projects::ApplicationController format.html do recaptcha_check_with_fallback { render :new } end - format.js do - @link = @issue.attachment.url.to_js - end end end @@ -403,7 +400,7 @@ class Projects::IssuesController < Projects::ApplicationController end # Overridden in EE - def create_vulnerability_issue_link(issue); end + def create_vulnerability_issue_feedback(issue); end end Projects::IssuesController.prepend_if_ee('EE::Projects::IssuesController') diff --git a/app/controllers/projects/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb index d2703f5cc38..8a2ea51ba9d 100644 --- a/app/controllers/projects/jobs_controller.rb +++ b/app/controllers/projects/jobs_controller.rb @@ -15,9 +15,6 @@ class Projects::JobsController < Projects::ApplicationController before_action :verify_api_request!, only: :terminal_websocket_authorize before_action :authorize_create_proxy_build!, only: :proxy_websocket_authorize before_action :verify_proxy_request!, only: :proxy_websocket_authorize - before_action only: :index do - frontend_experimentation_tracking_data(:jobs_empty_state, 'click_button') - end layout 'project' diff --git a/app/controllers/projects/learn_gitlab_controller.rb b/app/controllers/projects/learn_gitlab_controller.rb new file mode 100644 index 00000000000..162ba9bd5cb --- /dev/null +++ b/app/controllers/projects/learn_gitlab_controller.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class Projects::LearnGitlabController < Projects::ApplicationController + before_action :authenticate_user! + before_action :check_experiment_enabled? + + feature_category :users + + def index + push_frontend_experiment(:learn_gitlab_a, subject: current_user) + push_frontend_experiment(:learn_gitlab_b, subject: current_user) + end + + private + + def check_experiment_enabled? + return access_denied! unless helpers.learn_gitlab_experiment_enabled?(project) + end +end diff --git a/app/controllers/projects/merge_requests/application_controller.rb b/app/controllers/projects/merge_requests/application_controller.rb index 9cac9f37eb7..e74717a44ab 100644 --- a/app/controllers/projects/merge_requests/application_controller.rb +++ b/app/controllers/projects/merge_requests/application_controller.rb @@ -20,7 +20,7 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont end def preloadable_mr_relations - [:metrics, :assignees, { author: :status }] + [:metrics, { assignees: :status }, { author: :status }] end def merge_request_params diff --git a/app/controllers/projects/merge_requests/creations_controller.rb b/app/controllers/projects/merge_requests/creations_controller.rb index 858bdc066c1..e79c19c3b67 100644 --- a/app/controllers/projects/merge_requests/creations_controller.rb +++ b/app/controllers/projects/merge_requests/creations_controller.rb @@ -12,9 +12,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap before_action :build_merge_request, except: [:create] before_action do - push_frontend_feature_flag(:merge_request_reviewers, @project, default_enabled: true) push_frontend_feature_flag(:mr_collapsed_approval_rules, @project) - push_frontend_feature_flag(:reviewer_approval_rules, @project, default_enabled: :yaml) end def new diff --git a/app/controllers/projects/merge_requests/diffs_controller.rb b/app/controllers/projects/merge_requests/diffs_controller.rb index 9180b3f6b62..98ef9d918ae 100644 --- a/app/controllers/projects/merge_requests/diffs_controller.rb +++ b/app/controllers/projects/merge_requests/diffs_controller.rb @@ -122,10 +122,7 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic end end - if render_merge_ref_head_diff? - return CompareService.new(@project, @merge_request.merge_ref_head.sha) - .execute(@project, @merge_request.target_branch) - end + return @merge_request.merge_head_diff if render_merge_ref_head_diff? if @start_sha @merge_request_diff.compare_with(@start_sha) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index d452a5e02e2..c9e9a34ad88 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -11,6 +11,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo include RecordUserLastActivity include SourcegraphDecorator include DiffHelper + include CommentAndCloseFlag skip_before_action :merge_request, only: [:index, :bulk_update, :export_csv] before_action :apply_diff_view_cookie!, only: [:show] @@ -22,37 +23,35 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo :coverage_reports, :terraform_reports, :accessibility_reports, - :codequality_reports + :codequality_reports, + :codequality_mr_diff_reports ] before_action :set_issuables_index, only: [:index] before_action :authenticate_user!, only: [:assign_related_issues] before_action :check_user_can_push_to_source_branch!, only: [:rebase] before_action only: [:show] do - push_frontend_feature_flag(:multiline_comments, @project, default_enabled: true) push_frontend_feature_flag(:file_identifier_hash) push_frontend_feature_flag(:batch_suggestions, @project, default_enabled: true) push_frontend_feature_flag(:approvals_commented_by, @project, default_enabled: true) push_frontend_feature_flag(:merge_request_widget_graphql, @project) push_frontend_feature_flag(:drag_comment_selection, @project, default_enabled: true) push_frontend_feature_flag(:unified_diff_components, @project, default_enabled: true) - push_frontend_feature_flag(:default_merge_ref_for_diffs, @project) - push_frontend_feature_flag(:core_security_mr_widget, @project, default_enabled: true) + push_frontend_feature_flag(:default_merge_ref_for_diffs, @project, default_enabled: :yaml) push_frontend_feature_flag(:core_security_mr_widget_counts, @project) - push_frontend_feature_flag(:core_security_mr_widget_downloads, @project, default_enabled: true) push_frontend_feature_flag(:remove_resolve_note, @project, default_enabled: true) push_frontend_feature_flag(:diffs_gradual_load, @project, default_enabled: true) - push_frontend_feature_flag(:codequality_mr_diff, @project) - push_frontend_feature_flag(:suggestions_custom_commit, @project) + push_frontend_feature_flag(:codequality_backend_comparison, @project, default_enabled: :yaml) + push_frontend_feature_flag(:suggestions_custom_commit, @project, default_enabled: true) + push_frontend_feature_flag(:local_file_reviews, default_enabled: :yaml) + push_frontend_feature_flag(:paginated_notes, @project, default_enabled: :yaml) + push_frontend_feature_flag(:ci_mini_pipeline_gl_dropdown, @project, type: :development, default_enabled: :yaml) record_experiment_user(:invite_members_version_a) record_experiment_user(:invite_members_version_b) end before_action do - push_frontend_feature_flag(:vue_issuable_sidebar, @project.group) - push_frontend_feature_flag(:merge_request_reviewers, @project, default_enabled: true) push_frontend_feature_flag(:mr_collapsed_approval_rules, @project) - push_frontend_feature_flag(:reviewer_approval_rules, @project, default_enabled: :yaml) end around_action :allow_gitaly_ref_name_caching, only: [:index, :show, :discussions] @@ -68,7 +67,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo :toggle_award_emoji, :toggle_subscription, :update ] - feature_category :code_testing, [:test_reports, :coverage_reports] + feature_category :code_testing, [:test_reports, :coverage_reports, :codequality_mr_diff_reports] feature_category :accessibility_testing, [:accessibility_reports] feature_category :infrastructure_as_code, [:terraform_reports] @@ -168,6 +167,14 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo } end + def sast_reports + reports_response(merge_request.compare_sast_reports(current_user), head_pipeline) + end + + def secret_detection_reports + reports_response(merge_request.compare_secret_detection_reports(current_user), head_pipeline) + end + def context_commits return render_404 unless project.context_commits_enabled? @@ -197,6 +204,10 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo end end + def codequality_mr_diff_reports + reports_response(@merge_request.find_codequality_mr_diff_reports) + end + def codequality_reports reports_response(@merge_request.compare_codequality_reports) end @@ -491,7 +502,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo params = request.query_parameters params[:view] = "inline" - if Feature.enabled?(:default_merge_ref_for_diffs, project) + if Feature.enabled?(:default_merge_ref_for_diffs, project, default_enabled: :yaml) params = params.merge(diff_head: true) end diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index 77fd7688caf..71a93701dc4 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -10,6 +10,7 @@ class Projects::NotesController < Projects::ApplicationController before_action :authorize_read_note! before_action :authorize_create_note!, only: [:create] before_action :authorize_resolve_note!, only: [:resolve, :unresolve] + before_action :create_rate_limit, only: [:create] feature_category :issue_tracking @@ -90,4 +91,20 @@ class Projects::NotesController < Projects::ApplicationController def whitelist_query_limiting Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-foss/issues/42383') end + + def create_rate_limit + key = :notes_create + return unless rate_limiter.throttled?(key, scope: [current_user], users_allowlist: rate_limit_users_allowlist) + + rate_limiter.log_request(request, "#{key}_request_limit".to_sym, current_user) + render plain: _('This endpoint has been requested too many times. Try again later.'), status: :too_many_requests + end + + def rate_limiter + ::Gitlab::ApplicationRateLimiter + end + + def rate_limit_users_allowlist + Gitlab::CurrentSettings.current_application_settings.notes_create_limit_allowlist + end end diff --git a/app/controllers/projects/pipelines/tests_controller.rb b/app/controllers/projects/pipelines/tests_controller.rb index 924d52898ea..1702783b10f 100644 --- a/app/controllers/projects/pipelines/tests_controller.rb +++ b/app/controllers/projects/pipelines/tests_controller.rb @@ -42,9 +42,13 @@ module Projects end def test_suite - builds.map do |build| + suite = builds.map do |build| build.collect_test_reports!(Gitlab::Ci::Reports::TestReports.new) end.sum + + Gitlab::Ci::Reports::TestFailureHistory.new(suite.failed.values, project).load! + + suite end # rubocop: enable CodeReuse/ActiveRecord end diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index ae8b3d9b51d..59b14bbb91d 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -13,15 +13,14 @@ class Projects::PipelinesController < Projects::ApplicationController before_action :authorize_create_pipeline!, only: [:new, :create, :config_variables] before_action :authorize_update_pipeline!, only: [:retry, :cancel] before_action do - push_frontend_feature_flag(:dag_pipeline_tab, project, default_enabled: true) push_frontend_feature_flag(:pipelines_security_report_summary, project) push_frontend_feature_flag(:new_pipeline_form, project, default_enabled: true) - push_frontend_feature_flag(:graphql_pipeline_header, project, type: :development, default_enabled: false) - push_frontend_feature_flag(:graphql_pipeline_details, project, type: :development, default_enabled: false) - push_frontend_feature_flag(:new_pipeline_form_prefilled_vars, project, type: :development, default_enabled: true) + push_frontend_feature_flag(:graphql_pipeline_details, project, type: :development, default_enabled: :yaml) + push_frontend_feature_flag(:graphql_pipeline_details_users, current_user, type: :development, default_enabled: :yaml) + push_frontend_feature_flag(:ci_mini_pipeline_gl_dropdown, project, type: :development, default_enabled: :yaml) + push_frontend_feature_flag(:jira_for_vulnerabilities, project, type: :development, default_enabled: :yaml) end before_action :ensure_pipeline, only: [:show] - before_action :push_experiment_to_gon, only: :index, if: :html_request? # Will be removed with https://gitlab.com/gitlab-org/gitlab/-/issues/225596 before_action :redirect_for_legacy_scope_filter, only: [:index], if: -> { request.format.html? } @@ -46,11 +45,7 @@ class Projects::PipelinesController < Projects::ApplicationController @pipelines_count = limited_pipelines_count(project) respond_to do |format| - format.html do - record_empty_pipeline_experiment - - render :index - end + format.html format.json do Gitlab::PollingInterval.set_header(response, interval: POLLING_INTERVAL) @@ -301,20 +296,6 @@ class Projects::PipelinesController < Projects::ApplicationController def index_params params.permit(:scope, :username, :ref, :status) end - - def record_empty_pipeline_experiment - return unless @pipelines_count.to_i == 0 - return if helpers.has_gitlab_ci?(@project) - - record_experiment_user(:pipelines_empty_state) - end - - def push_experiment_to_gon - return unless current_user - - push_frontend_experiment(:pipelines_empty_state, subject: current_user) - frontend_experimentation_tracking_data(:pipelines_empty_state, 'view', project.namespace_id, subject: current_user) - end end Projects::PipelinesController.prepend_if_ee('EE::Projects::PipelinesController') diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 5972b29a298..a7c7839dc9f 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -8,6 +8,10 @@ class Projects::ProjectMembersController < Projects::ApplicationController # Authorize before_action :authorize_admin_project_member!, except: [:index, :leave, :request_access] + before_action do + push_frontend_feature_flag(:vue_project_members_list, @project, default_enabled: :yaml) + end + feature_category :authentication_and_authorization def index diff --git a/app/controllers/projects/security/configuration_controller.rb b/app/controllers/projects/security/configuration_controller.rb new file mode 100644 index 00000000000..9366ca7b0ed --- /dev/null +++ b/app/controllers/projects/security/configuration_controller.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module Projects + module Security + class ConfigurationController < Projects::ApplicationController + feature_category :static_application_security_testing + + def show + return render_404 unless feature_enabled? + + render_403 unless can?(current_user, :read_security_configuration, project) + end + + private + + def feature_enabled? + ::Feature.enabled?(:secure_security_and_compliance_configuration_page_on_ce, @project, default_enabled: :yaml) + end + end + end +end + +Projects::Security::ConfigurationController.prepend_if_ee('EE::Projects::Security::ConfigurationController') diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb index 6ed9f74297d..b5c73f29784 100644 --- a/app/controllers/projects/services_controller.rb +++ b/app/controllers/projects/services_controller.rb @@ -14,7 +14,7 @@ class Projects::ServicesController < Projects::ApplicationController before_action only: :edit do push_frontend_feature_flag(:jira_issues_integration, @project, type: :licensed, default_enabled: true) push_frontend_feature_flag(:jira_vulnerabilities_integration, @project, type: :licensed, default_enabled: true) - push_frontend_feature_flag(:jira_for_vulnerabilities, @project, type: :development, default_enabled: false) + push_frontend_feature_flag(:jira_for_vulnerabilities, @project, type: :development, default_enabled: :yaml) end respond_to :html diff --git a/app/controllers/projects/settings/ci_cd_controller.rb b/app/controllers/projects/settings/ci_cd_controller.rb index 31533dfeea0..34b11c456b9 100644 --- a/app/controllers/projects/settings/ci_cd_controller.rb +++ b/app/controllers/projects/settings/ci_cd_controller.rb @@ -144,8 +144,8 @@ module Projects def define_badges_variables @ref = params[:ref] || @project.default_branch || 'master' - @badges = [Gitlab::Badge::Pipeline::Status, - Gitlab::Badge::Coverage::Report] + @badges = [Gitlab::Ci::Badge::Pipeline::Status, + Gitlab::Ci::Badge::Coverage::Report] @badges.map! do |badge| badge.new(@project, @ref).metadata diff --git a/app/controllers/projects/settings/repository_controller.rb b/app/controllers/projects/settings/repository_controller.rb index dd50ab1bc7a..821560e32ba 100644 --- a/app/controllers/projects/settings/repository_controller.rb +++ b/app/controllers/projects/settings/repository_controller.rb @@ -7,7 +7,6 @@ module Projects before_action :define_variables, only: [:create_deploy_token] before_action do push_frontend_feature_flag(:ajax_new_deploy_token, @project) - push_frontend_feature_flag(:deploy_keys_on_protected_branches, @project) end feature_category :source_code_management, [:show, :cleanup] diff --git a/app/controllers/projects/templates_controller.rb b/app/controllers/projects/templates_controller.rb index f4726638777..ab05c9694fd 100644 --- a/app/controllers/projects/templates_controller.rb +++ b/app/controllers/projects/templates_controller.rb @@ -24,10 +24,8 @@ class Projects::TemplatesController < Projects::ApplicationController end def names - templates = @template_type.dropdown_names(project) - respond_to do |format| - format.json { render json: templates } + format.json { render json: TemplateFinder.all_template_names_array(project, params[:template_type].to_s.pluralize) } end end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 0c40478d877..ebffb62cff3 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -14,7 +14,7 @@ class ProjectsController < Projects::ApplicationController around_action :allow_gitaly_ref_name_caching, only: [:index, :show] - before_action :whitelist_query_limiting, only: [:create] + before_action :whitelist_query_limiting, only: [:show, :create] before_action :authenticate_user!, except: [:index, :show, :activity, :refs, :resolve, :unfoldered_environment_names] before_action :redirect_git_extension, only: [:show] before_action :project, except: [:index, :new, :create, :resolve] @@ -31,8 +31,11 @@ class ProjectsController < Projects::ApplicationController # Project Export Rate Limit before_action :export_rate_limit, only: [:export, :download_export, :generate_new_export] + before_action do + push_frontend_feature_flag(:vue_notification_dropdown, @project, default_enabled: :yaml) + end + before_action only: [:edit] do - push_frontend_feature_flag(:approval_suggestions, @project, default_enabled: true) push_frontend_feature_flag(:allow_editing_commit_messages, @project) end @@ -71,6 +74,7 @@ class ProjectsController < Projects::ApplicationController @project = ::Projects::CreateService.new(current_user, project_params(attributes: project_params_create_attributes)).execute if @project.saved? + experiment(:new_project_readme, actor: current_user).track(:created, property: active_new_project_tab) redirect_to( project_path(@project, custom_import_params), notice: _("Project '%{project_name}' was successfully created.") % { project_name: @project.name } @@ -392,6 +396,14 @@ class ProjectsController < Projects::ApplicationController ] end + def project_setting_attributes + %i[ + show_default_award_emojis + squash_option + allow_editing_commit_messages + ] + end + def project_params_attributes [ :allow_merge_on_skipped_pipeline, @@ -429,11 +441,7 @@ class ProjectsController < Projects::ApplicationController :suggestion_commit_message, :packages_enabled, :service_desk_enabled, - project_setting_attributes: %i[ - show_default_award_emojis - squash_option - allow_editing_commit_messages - ] + project_setting_attributes: project_setting_attributes ] + [project_feature_attributes: project_feature_attributes] end @@ -493,17 +501,19 @@ class ProjectsController < Projects::ApplicationController render_404 unless Gitlab::CurrentSettings.project_export_enabled? end + # Redirect from localhost/group/project.git to localhost/group/project def redirect_git_extension - # Redirect from - # localhost/group/project.git - # to - # localhost/group/project - # - redirect_to request.original_url.sub(%r{\.git/?\Z}, '') if params[:format] == 'git' + return unless params[:format] == 'git' + + # `project` calls `find_routable!`, so this will trigger the usual not-found + # behaviour when the user isn't authorized to see the project + return unless project + + redirect_to(request.original_url.sub(%r{\.git/?\Z}, '')) end def whitelist_query_limiting - Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-foss/issues/42440') + Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab/-/issues/20826') end def present_project diff --git a/app/controllers/registrations/experience_levels_controller.rb b/app/controllers/registrations/experience_levels_controller.rb index 23126983eb5..b6ed0366177 100644 --- a/app/controllers/registrations/experience_levels_controller.rb +++ b/app/controllers/registrations/experience_levels_controller.rb @@ -2,9 +2,8 @@ module Registrations class ExperienceLevelsController < ApplicationController - layout 'devise_experimental_onboarding_issues' + layout 'signup_onboarding' - before_action :check_experiment_enabled before_action :ensure_namespace_path_param feature_category :navigation @@ -14,9 +13,8 @@ module Registrations if current_user.save hide_advanced_issues - record_experiment_user(:default_to_issues_board) - if experiment_enabled?(:default_to_issues_board) && learn_gitlab.available? + if learn_gitlab.available? redirect_to namespace_project_board_path(params[:namespace_path], learn_gitlab.project, learn_gitlab.board) else redirect_to group_path(params[:namespace_path]) @@ -28,10 +26,6 @@ module Registrations private - def check_experiment_enabled - access_denied! unless experiment_enabled?(:onboarding_issues) - end - def ensure_namespace_path_param redirect_to root_path unless params[:namespace_path].present? end diff --git a/app/controllers/registrations/welcome_controller.rb b/app/controllers/registrations/welcome_controller.rb index 4a6fef56ef5..a1a6a057171 100644 --- a/app/controllers/registrations/welcome_controller.rb +++ b/app/controllers/registrations/welcome_controller.rb @@ -16,9 +16,7 @@ module Registrations result = ::Users::SignupService.new(current_user, update_params).execute if result[:status] == :success - process_gitlab_com_tracking - - return redirect_to new_users_sign_up_group_path if experiment_enabled?(:onboarding_issues) && show_onboarding_issues_experiment? + return redirect_to new_users_sign_up_group_path if show_signup_onboarding? redirect_to path_for_signed_in_user(current_user) else @@ -36,14 +34,6 @@ module Registrations current_user.role.present? && !current_user.setup_for_company.nil? end - def process_gitlab_com_tracking - return false unless ::Gitlab.com? - return false unless show_onboarding_issues_experiment? - - track_experiment_event(:onboarding_issues, 'signed_up') - record_experiment_user(:onboarding_issues) - end - def update_params params.require(:user).permit(:role, :other_role, :setup_for_company) end @@ -61,11 +51,8 @@ module Registrations stored_location_for(user) || dashboard_projects_path end - def show_onboarding_issues_experiment? - !helpers.in_subscription_flow? && - !helpers.in_invitation_flow? && - !helpers.in_oauth_flow? && - !helpers.in_trial_flow? + def show_signup_onboarding? + false end end end diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index e7872eeac27..44c08863dd6 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -31,6 +31,8 @@ class RegistrationsController < Devise::RegistrationsController NotificationService.new.new_instance_access_request(new_user) end + after_request_hook(new_user) + yield new_user if block_given? end @@ -85,6 +87,10 @@ class RegistrationsController < Devise::RegistrationsController super end + def after_request_hook(user) + # overridden by EE module + end + def after_sign_up_path_for(user) Gitlab::AppLogger.info(user_created_message(confirmed: user.confirmed?)) diff --git a/app/controllers/repositories/git_http_controller.rb b/app/controllers/repositories/git_http_controller.rb index 3cf0a23b7f6..9ad700404ff 100644 --- a/app/controllers/repositories/git_http_controller.rb +++ b/app/controllers/repositories/git_http_controller.rb @@ -78,6 +78,8 @@ module Repositories def update_fetch_statistics return unless project return if Gitlab::Database.read_only? + return if Feature.enabled?(:disable_git_http_fetch_writes) + return unless repo_type.project? OnboardingProgressService.new(project.namespace).execute(action: :git_read) diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 196b1887ca7..820b00a902e 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -5,11 +5,11 @@ class SearchController < ApplicationController include SearchHelper include RedisTracking - track_redis_hll_event :show, name: 'i_search_total', feature: :search_track_unique_users, feature_default_enabled: true + track_redis_hll_event :show, name: 'i_search_total' around_action :allow_gitaly_ref_name_caching - before_action :block_anonymous_global_searches + before_action :block_anonymous_global_searches, except: :opensearch skip_before_action :authenticate_user! requires_cross_project_access if: -> do search_term_present = params[:search].present? || params[:term].present? @@ -67,6 +67,9 @@ class SearchController < ApplicationController end # rubocop: enable CodeReuse/ActiveRecord + def opensearch + end + private # overridden in EE diff --git a/app/controllers/snippets/notes_controller.rb b/app/controllers/snippets/notes_controller.rb index 8532257cb8d..8a4e8edbf3c 100644 --- a/app/controllers/snippets/notes_controller.rb +++ b/app/controllers/snippets/notes_controller.rb @@ -23,7 +23,7 @@ class Snippets::NotesController < ApplicationController # rubocop: disable CodeReuse/ActiveRecord def snippet - PersonalSnippet.find_by(id: params[:snippet_id]) + @snippet ||= PersonalSnippet.find_by(id: params[:snippet_id]) end # rubocop: enable CodeReuse/ActiveRecord alias_method :noteable, :snippet diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 62208d838c1..54d97f588fc 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true class UsersController < ApplicationController + include InternalRedirect include RoutableActions include RendersMemberAccess include RendersProjectsList @@ -13,13 +14,15 @@ class UsersController < ApplicationController contributed: false, snippets: true, calendar: false, + followers: false, + following: false, calendar_activities: true skip_before_action :authenticate_user! prepend_before_action(only: [:show]) { authenticate_sessionless_user!(:rss) } - before_action :user, except: [:exists, :suggests] + before_action :user, except: [:exists, :suggests, :ssh_keys] before_action :authorize_read_user_profile!, - only: [:calendar, :calendar_activities, :groups, :projects, :contributed, :starred, :snippets] + only: [:calendar, :calendar_activities, :groups, :projects, :contributed, :starred, :snippets, :followers, :following] feature_category :users @@ -41,7 +44,12 @@ class UsersController < ApplicationController # Get all keys of a user(params[:username]) in a text format # Helpful for sysadmins to put in respective servers + # + # Uses `UserFinder` rather than `find_routable!` because this endpoint should + # be publicly available regardless of instance visibility settings. def ssh_keys + user = UserFinder.new(params[:username]).find_by_username + render plain: user.all_ssh_keys.join("\n") end @@ -92,6 +100,18 @@ class UsersController < ApplicationController present_projects(@starred_projects) end + def followers + @user_followers = user.followers.page(params[:page]) + + present_users(@user_followers) + end + + def following + @user_following = user.followees.page(params[:page]) + + present_users(@user_following) + end + def present_projects(projects) skip_pagination = Gitlab::Utils.to_boolean(params[:skip_pagination]) skip_namespace = Gitlab::Utils.to_boolean(params[:skip_namespace]) @@ -141,6 +161,22 @@ class UsersController < ApplicationController render json: { exists: exists, suggests: suggestions } end + def follow + current_user.follow(user) + + redirect_path = referer_path(request) || @user + + redirect_to redirect_path + end + + def unfollow + current_user.unfollow(user) + + redirect_path = referer_path(request) || @user + + redirect_to redirect_path + end + private def user @@ -164,7 +200,7 @@ class UsersController < ApplicationController end def load_events - @events = UserRecentEventsFinder.new(current_user, user, params).execute + @events = UserRecentEventsFinder.new(current_user, user, nil, params).execute Events::RenderService.new(current_user).execute(@events, atom_request: request.format.atom?) end @@ -211,6 +247,17 @@ class UsersController < ApplicationController def authorize_read_user_profile! access_denied! unless can?(current_user, :read_user_profile, user) end + + def present_users(users) + respond_to do |format| + format.html { render 'show' } + format.json do + render json: { + html: view_to_html_string("shared/users/index", users: users) + } + end + end + end end UsersController.prepend_if_ee('EE::UsersController') diff --git a/app/controllers/whats_new_controller.rb b/app/controllers/whats_new_controller.rb index cba86c65848..12a52f30bd0 100644 --- a/app/controllers/whats_new_controller.rb +++ b/app/controllers/whats_new_controller.rb @@ -5,7 +5,6 @@ class WhatsNewController < ApplicationController skip_before_action :authenticate_user! - before_action :check_feature_flag before_action :check_valid_page_param, :set_pagination_headers, unless: -> { has_version_param? } feature_category :navigation @@ -13,17 +12,13 @@ class WhatsNewController < ApplicationController def index respond_to do |format| format.js do - render json: highlight_items + render json: highlights.items end end end private - def check_feature_flag - render_404 unless Feature.enabled?(:whats_new_drawer, current_user) - end - def check_valid_page_param render_404 if current_page < 1 end @@ -42,10 +37,6 @@ class WhatsNewController < ApplicationController end end - def highlight_items - highlights.map {|item| Gitlab::WhatsNew::ItemPresenter.present(item) } - end - def set_pagination_headers response.set_header('X-Next-Page', highlights.next_page) end |