diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-07-20 12:26:25 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-07-20 12:26:25 +0000 |
commit | a09983ae35713f5a2bbb100981116d31ce99826e (patch) | |
tree | 2ee2af7bd104d57086db360a7e6d8c9d5d43667a /app/controllers | |
parent | 18c5ab32b738c0b6ecb4d0df3994000482f34bd8 (diff) | |
download | gitlab-ce-a09983ae35713f5a2bbb100981116d31ce99826e.tar.gz |
Add latest changes from gitlab-org/gitlab@13-2-stable-ee
Diffstat (limited to 'app/controllers')
101 files changed, 1164 insertions, 483 deletions
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 94c82c25357..41a6616d10c 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -227,6 +227,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController :raw_blob_request_limit, :namespace_storage_size_limit, :issues_create_limit, + :default_branch_name, disabled_oauth_sign_in_sources: [], import_sources: [], repository_storages: [], diff --git a/app/controllers/admin/clusters_controller.rb b/app/controllers/admin/clusters_controller.rb index 5b1902fad51..9a642e53d86 100644 --- a/app/controllers/admin/clusters_controller.rb +++ b/app/controllers/admin/clusters_controller.rb @@ -10,6 +10,11 @@ class Admin::ClustersController < Clusters::ClustersController def clusterable @clusterable ||= InstanceClusterablePresenter.fabricate(Clusters::Instance.new, current_user: current_user) end -end -Admin::ClustersController.prepend_if_ee('EE::Admin::ClustersController') + def metrics_dashboard_params + { + cluster: cluster, + cluster_type: :admin + } + end +end diff --git a/app/controllers/admin/jobs_controller.rb b/app/controllers/admin/jobs_controller.rb index a3a18a115e9..7b50a45a9cd 100644 --- a/app/controllers/admin/jobs_controller.rb +++ b/app/controllers/admin/jobs_controller.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class Admin::JobsController < Admin::ApplicationController + BUILDS_PER_PAGE = 30 + def index # We need all builds for tabs counters @all_builds = Ci::JobsFinder.new(current_user: current_user).execute @@ -8,7 +10,7 @@ class Admin::JobsController < Admin::ApplicationController @scope = params[:scope] @builds = Ci::JobsFinder.new(current_user: current_user, params: params).execute @builds = @builds.eager_load_everything - @builds = @builds.page(params[:page]).per(30) + @builds = @builds.page(params[:page]).per(BUILDS_PER_PAGE).without_count end def cancel_all diff --git a/app/controllers/admin/services_controller.rb b/app/controllers/admin/services_controller.rb index 08ef992e604..e0137accd2d 100644 --- a/app/controllers/admin/services_controller.rb +++ b/app/controllers/admin/services_controller.rb @@ -4,13 +4,18 @@ class Admin::ServicesController < Admin::ApplicationController include ServiceParams before_action :service, only: [:edit, :update] + before_action :whitelist_query_limiting, only: [:index] + before_action only: :edit do + push_frontend_feature_flag(:integration_form_refactor, default_enabled: true) + end def index @services = Service.find_or_create_templates.sort_by(&:title) + @existing_instance_types = Service.instances.pluck(:type) # rubocop: disable CodeReuse/ActiveRecord end def edit - unless service.present? + if service.nil? || Service.instance_exists_for?(service.type) redirect_to admin_application_settings_services_path, alert: "Service is unknown or it doesn't exist" end @@ -34,4 +39,8 @@ class Admin::ServicesController < Admin::ApplicationController @service ||= Service.find_by(id: params[:id], template: true) end # rubocop: enable CodeReuse/ActiveRecord + + def whitelist_query_limiting + Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab/-/issues/220357') + end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 79a164a5574..2595b646964 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -22,6 +22,7 @@ class ApplicationController < ActionController::Base include Impersonation include Gitlab::Logging::CloudflareHelper include Gitlab::Utils::StrongMemoize + include ControllerWithFeatureCategory before_action :authenticate_user!, except: [:route_not_found] before_action :enforce_terms!, if: :should_enforce_terms? @@ -305,7 +306,7 @@ class ApplicationController < ActionController::Base return if session[:impersonator_id] || !current_user&.allow_password_authentication? if current_user&.password_expired? - return redirect_to new_profile_password_path + redirect_to new_profile_password_path end end @@ -329,13 +330,6 @@ class ApplicationController < ActionController::Base end end - def event_filter - @event_filter ||= - EventFilter.new(params[:event_filter].presence || cookies[:event_filter]).tap do |new_event_filter| - cookies[:event_filter] = new_event_filter.filter - end - end - # JSON for infinite scroll via Pager object def pager_json(partial, count, locals = {}) html = render_to_string( @@ -370,7 +364,7 @@ class ApplicationController < ActionController::Base def require_email if current_user && current_user.temp_oauth_email? && session[:impersonator_id].nil? - return redirect_to profile_path, notice: _('Please complete your profile with email address') + redirect_to profile_path, notice: _('Please complete your profile with email address') end end diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb index 0df201ab506..99fa17e202a 100644 --- a/app/controllers/autocomplete_controller.rb +++ b/app/controllers/autocomplete_controller.rb @@ -4,10 +4,6 @@ class AutocompleteController < ApplicationController skip_before_action :authenticate_user!, only: [:users, :award_emojis, :merge_request_target_branches] def users - project = Autocomplete::ProjectFinder - .new(current_user, params) - .execute - group = Autocomplete::GroupFinder .new(current_user, project, params) .execute @@ -50,8 +46,20 @@ class AutocompleteController < ApplicationController end end + def deploy_keys_with_owners + deploy_keys = DeployKeys::CollectKeysService.new(project, current_user).execute + + render json: DeployKeySerializer.new.represent(deploy_keys, { with_owner: true, user: current_user }) + end + private + def project + @project ||= Autocomplete::ProjectFinder + .new(current_user, params) + .execute + end + def target_branch_params params.permit(:group_id, :project_id).select { |_, v| v.present? } end diff --git a/app/controllers/chaos_controller.rb b/app/controllers/chaos_controller.rb index ac008165c16..e0d1f313fc7 100644 --- a/app/controllers/chaos_controller.rb +++ b/app/controllers/chaos_controller.rb @@ -45,7 +45,6 @@ class ChaosController < ActionController::Base unless Devise.secure_compare(chaos_secret_configured, chaos_secret_request) render plain: "To experience chaos, please set a valid `X-Chaos-Secret` header or `token` param", status: :unauthorized - return end end diff --git a/app/controllers/clusters/clusters_controller.rb b/app/controllers/clusters/clusters_controller.rb index 46dec5f3287..2e8b3d764ca 100644 --- a/app/controllers/clusters/clusters_controller.rb +++ b/app/controllers/clusters/clusters_controller.rb @@ -2,6 +2,8 @@ class Clusters::ClustersController < Clusters::BaseController include RoutableActions + include Metrics::Dashboard::PrometheusApiProxy + include MetricsDashboard before_action :cluster, only: [:cluster_status, :show, :update, :destroy, :clear_cache] before_action :generate_gcp_authorize_url, only: [:new] @@ -290,6 +292,29 @@ class Clusters::ClustersController < Clusters::BaseController @gcp_cluster = cluster.present(current_user: current_user) end + def proxyable + cluster.cluster + end + + # During first iteration of dashboard variables implementation + # cluster health case was omitted. Existing service for now is tied to + # environment, which is not always present for cluster health dashboard. + # It is planned to break coupling to environment https://gitlab.com/gitlab-org/gitlab/-/issues/213833. + # It is also planned to move cluster health to metrics dashboard section https://gitlab.com/gitlab-org/gitlab/-/issues/220214 + # but for now I've used dummy class to stub variable substitution service, as there are no variables + # in cluster health dashboard + def proxy_variable_substitution_service + @empty_service ||= Class.new(BaseService) do + def initialize(proxyable, params) + @proxyable, @params = proxyable, params + end + + def execute + success(params: @params) + end + end + end + def user_cluster cluster = Clusters::BuildService.new(clusterable.subject).execute cluster.build_platform_kubernetes diff --git a/app/controllers/concerns/controller_with_feature_category.rb b/app/controllers/concerns/controller_with_feature_category.rb new file mode 100644 index 00000000000..f8985cf0950 --- /dev/null +++ b/app/controllers/concerns/controller_with_feature_category.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module ControllerWithFeatureCategory + extend ActiveSupport::Concern + include Gitlab::ClassAttributes + + class_methods do + def feature_category(category, config = {}) + validate_config!(config) + + category_config = Config.new(category, config[:only], config[:except], config[:if], config[:unless]) + # Add the config to the beginning. That way, the last defined one takes precedence. + feature_category_configuration.unshift(category_config) + end + + def feature_category_for_action(action) + category_config = feature_category_configuration.find { |config| config.matches?(action) } + + category_config&.category || superclass_feature_category_for_action(action) + end + + private + + def validate_config!(config) + invalid_keys = config.keys - [:only, :except, :if, :unless] + if invalid_keys.any? + raise ArgumentError, "unknown arguments: #{invalid_keys} " + end + + if config.key?(:only) && config.key?(:except) + raise ArgumentError, "cannot configure both `only` and `except`" + end + end + + def feature_category_configuration + class_attributes[:feature_category_config] ||= [] + end + + def superclass_feature_category_for_action(action) + return unless superclass.respond_to?(:feature_category_for_action) + + superclass.feature_category_for_action(action) + end + end +end diff --git a/app/controllers/concerns/controller_with_feature_category/config.rb b/app/controllers/concerns/controller_with_feature_category/config.rb new file mode 100644 index 00000000000..624691ee4f6 --- /dev/null +++ b/app/controllers/concerns/controller_with_feature_category/config.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +module ControllerWithFeatureCategory + class Config + attr_reader :category + + def initialize(category, only, except, if_proc, unless_proc) + @category = category.to_sym + @only, @except = only&.map(&:to_s), except&.map(&:to_s) + @if_proc, @unless_proc = if_proc, unless_proc + end + + def matches?(action) + included?(action) && !excluded?(action) && + if_proc?(action) && !unless_proc?(action) + end + + private + + attr_reader :only, :except, :if_proc, :unless_proc + + def if_proc?(action) + if_proc.nil? || if_proc.call(action) + end + + def unless_proc?(action) + unless_proc.present? && unless_proc.call(action) + end + + def included?(action) + only.nil? || only.include?(action) + end + + def excluded?(action) + except.present? && except.include?(action) + end + end +end diff --git a/app/controllers/concerns/filters_events.rb b/app/controllers/concerns/filters_events.rb new file mode 100644 index 00000000000..c82d0318fd3 --- /dev/null +++ b/app/controllers/concerns/filters_events.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module FiltersEvents + def event_filter + @event_filter ||= new_event_filter.tap { |ef| cookies[:event_filter] = ef.filter } + end + + private + + def new_event_filter + active_filter = params[:event_filter].presence || cookies[:event_filter] + EventFilter.new(active_filter) + end +end diff --git a/app/controllers/concerns/integrations_actions.rb b/app/controllers/concerns/integrations_actions.rb index cc9db7936e8..46febc44807 100644 --- a/app/controllers/concerns/integrations_actions.rb +++ b/app/controllers/concerns/integrations_actions.rb @@ -8,6 +8,9 @@ module IntegrationsActions before_action :not_found, unless: :integrations_enabled? before_action :integration, only: [:edit, :update, :test] + before_action only: :edit do + push_frontend_feature_flag(:integration_form_refactor, default_enabled: true) + end end def edit @@ -51,9 +54,8 @@ module IntegrationsActions end def integration - # Using instance variable `@service` still required as it's used in ServiceParams - # and app/views/shared/_service_settings.html.haml. Should be removed once - # those 2 are refactored to use `@integration`. + # Using instance variable `@service` still required as it's used in ServiceParams. + # Should be removed once that is refactored to use `@integration`. @integration = @service ||= find_or_initialize_integration(params[:id]) # rubocop:disable Gitlab/ModuleWithInstanceVariables end diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb index 98fa8202e25..c4dbce00593 100644 --- a/app/controllers/concerns/issuable_actions.rb +++ b/app/controllers/concerns/issuable_actions.rb @@ -110,9 +110,13 @@ module IssuableActions def bulk_update result = Issuable::BulkUpdateService.new(parent, current_user, bulk_update_params).execute(resource_name) - quantity = result[:count] - render json: { notice: "#{quantity} #{resource_name.pluralize(quantity)} updated" } + if result.success? + quantity = result.payload[:count] + render json: { notice: "#{quantity} #{resource_name.pluralize(quantity)} updated" } + elsif result.error? + render json: { errors: result.message }, status: result.http_status + end end # rubocop:disable CodeReuse/ActiveRecord @@ -193,13 +197,13 @@ module IssuableActions def authorize_destroy_issuable! unless can?(current_user, :"destroy_#{issuable.to_ability_name}", issuable) - return access_denied! + access_denied! end end def authorize_admin_issuable! unless can?(current_user, :"admin_#{resource_name}", parent) - return access_denied! + access_denied! end end diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb index 9ef067e8797..4f61e5ed711 100644 --- a/app/controllers/concerns/issuable_collections.rb +++ b/app/controllers/concerns/issuable_collections.rb @@ -81,34 +81,36 @@ module IssuableCollections # rubocop:disable Gitlab/ModuleWithInstanceVariables def finder_options - params[:state] = default_state if params[:state].blank? - - options = { - scope: params[:scope], - state: params[:state], - confidential: Gitlab::Utils.to_boolean(params[:confidential]), - sort: set_sort_order - } - - # Used by view to highlight active option - @sort = options[:sort] - - # When a user looks for an exact iid, we do not filter by search but only by iid - if params[:search] =~ /^#(?<iid>\d+)\z/ - options[:iids] = Regexp.last_match[:iid] - params[:search] = nil + strong_memoize(:finder_options) do + params[:state] = default_state if params[:state].blank? + + options = { + scope: params[:scope], + state: params[:state], + confidential: Gitlab::Utils.to_boolean(params[:confidential]), + sort: set_sort_order + } + + # Used by view to highlight active option + @sort = options[:sort] + + # When a user looks for an exact iid, we do not filter by search but only by iid + if params[:search] =~ /^#(?<iid>\d+)\z/ + options[:iids] = Regexp.last_match[:iid] + params[:search] = nil + end + + if @project + options[:project_id] = @project.id + options[:attempt_project_search_optimizations] = true + elsif @group + options[:group_id] = @group.id + options[:include_subgroups] = true + options[:attempt_group_search_optimizations] = true + end + + params.permit(finder_type.valid_params).merge(options) end - - if @project - options[:project_id] = @project.id - options[:attempt_project_search_optimizations] = true - elsif @group - options[:group_id] = @group.id - options[:include_subgroups] = true - options[:attempt_group_search_optimizations] = true - end - - params.permit(finder_type.valid_params).merge(options) end # rubocop:enable Gitlab/ModuleWithInstanceVariables @@ -147,7 +149,10 @@ module IssuableCollections when 'Issue' common_attributes + [:project, project: :namespace] when 'MergeRequest' - common_attributes + [:target_project, :latest_merge_request_diff, source_project: :route, head_pipeline: :project, target_project: :namespace] + common_attributes + [ + :target_project, :latest_merge_request_diff, :approvals, :approved_by_users, + source_project: :route, head_pipeline: :project, target_project: :namespace + ] end end # rubocop:enable Gitlab/ModuleWithInstanceVariables diff --git a/app/controllers/concerns/known_sign_in.rb b/app/controllers/concerns/known_sign_in.rb index c0b9605de58..cacc7e4628f 100644 --- a/app/controllers/concerns/known_sign_in.rb +++ b/app/controllers/concerns/known_sign_in.rb @@ -2,19 +2,34 @@ module KnownSignIn include Gitlab::Utils::StrongMemoize + include CookiesHelper + + KNOWN_SIGN_IN_COOKIE = :known_sign_in + KNOWN_SIGN_IN_COOKIE_EXPIRY = 14.days private def verify_known_sign_in - return unless current_user + return unless Gitlab::CurrentSettings.notify_on_unknown_sign_in? && current_user + + notify_user unless known_device? || known_remote_ip? - notify_user unless known_remote_ip? + update_cookie end def known_remote_ip? known_ip_addresses.include?(request.remote_ip) end + def known_device? + cookies.encrypted[KNOWN_SIGN_IN_COOKIE] == current_user.id + end + + def update_cookie + set_secure_cookie(KNOWN_SIGN_IN_COOKIE, current_user.id, + type: COOKIE_TYPE_ENCRYPTED, httponly: true, expires: KNOWN_SIGN_IN_COOKIE_EXPIRY) + end + def sessions strong_memoize(:session) do ActiveSession.list(current_user).reject(&:is_impersonated) diff --git a/app/controllers/concerns/membership_actions.rb b/app/controllers/concerns/membership_actions.rb index 4ab02005b45..8c7f156f7f8 100644 --- a/app/controllers/concerns/membership_actions.rb +++ b/app/controllers/concerns/membership_actions.rb @@ -31,7 +31,10 @@ module MembershipActions def destroy member = membershipable.members_and_requesters.find(params[:id]) - Members::DestroyService.new(current_user).execute(member) + # !! is used in case unassign_issuables contains empty string which would result in nil + unassign_issuables = !!ActiveRecord::Type::Boolean.new.cast(params.delete(:unassign_issuables)) + + Members::DestroyService.new(current_user).execute(member, unassign_issuables: unassign_issuables) respond_to do |format| format.html do diff --git a/app/controllers/concerns/metrics/dashboard/prometheus_api_proxy.rb b/app/controllers/concerns/metrics/dashboard/prometheus_api_proxy.rb new file mode 100644 index 00000000000..e0e3f628cc5 --- /dev/null +++ b/app/controllers/concerns/metrics/dashboard/prometheus_api_proxy.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +module Metrics::Dashboard::PrometheusApiProxy + extend ActiveSupport::Concern + include RenderServiceResults + + included do + before_action :authorize_read_prometheus!, only: [:prometheus_proxy] + end + + def prometheus_proxy + variable_substitution_result = + proxy_variable_substitution_service.new(proxyable, permit_params).execute + + if variable_substitution_result[:status] == :error + return error_response(variable_substitution_result) + end + + prometheus_result = Prometheus::ProxyService.new( + proxyable, + proxy_method, + proxy_path, + variable_substitution_result[:params] + ).execute + + return continue_polling_response if prometheus_result.nil? + return error_response(prometheus_result) if prometheus_result[:status] == :error + + success_response(prometheus_result) + end + + private + + def proxyable + raise NotImplementedError, "#{self.class} must implement method: #{__callee__}" + end + + def proxy_variable_substitution_service + raise NotImplementedError, "#{self.class} must implement method: #{__callee__}" + end + + def permit_params + params.permit! + end + + def proxy_method + request.method + end + + def proxy_path + params[:proxy_path] + end +end diff --git a/app/controllers/concerns/metrics_dashboard.rb b/app/controllers/concerns/metrics_dashboard.rb index 1aea0e294a5..28d0692d748 100644 --- a/app/controllers/concerns/metrics_dashboard.rb +++ b/app/controllers/concerns/metrics_dashboard.rb @@ -13,7 +13,7 @@ module MetricsDashboard result = dashboard_finder.find( project_for_dashboard, current_user, - metrics_dashboard_params.to_h.symbolize_keys + decoded_params ) if result @@ -41,7 +41,7 @@ module MetricsDashboard end def amend_dashboard(dashboard) - project_dashboard = project_for_dashboard && !dashboard[:system_dashboard] + project_dashboard = project_for_dashboard && !dashboard[:out_of_the_box_dashboard] dashboard[:can_edit] = project_dashboard ? can_edit?(dashboard) : false dashboard[:project_blob_path] = project_dashboard ? dashboard_project_blob_path(dashboard) : nil @@ -114,4 +114,14 @@ module MetricsDashboard json: result.slice(:all_dashboards, :message, :status) } end + + def decoded_params + params = metrics_dashboard_params + + if params[:dashboard_path] + params[:dashboard_path] = CGI.unescape(params[:dashboard_path]) + end + + params + end end diff --git a/app/controllers/concerns/notes_actions.rb b/app/controllers/concerns/notes_actions.rb index d3dfb1813e4..f4fc7decb60 100644 --- a/app/controllers/concerns/notes_actions.rb +++ b/app/controllers/concerns/notes_actions.rb @@ -5,6 +5,11 @@ module NotesActions include Gitlab::Utils::StrongMemoize extend ActiveSupport::Concern + # last_fetched_at is an integer number of microseconds, which is the same + # precision as PostgreSQL "timestamp" fields. It's important for them to have + # identical precision for accurate pagination + MICROSECOND = 1_000_000 + included do before_action :set_polling_interval_header, only: [:index] before_action :require_noteable!, only: [:index, :create] @@ -13,30 +18,20 @@ module NotesActions end def index - notes_json = { notes: [], last_fetched_at: Time.current.to_i } - - notes = notes_finder - .execute - .inc_relations_for_view - - if notes_filter != UserPreference::NOTES_FILTERS[:only_comments] - notes = - ResourceEvents::MergeIntoNotesService - .new(noteable, current_user, last_fetched_at: last_fetched_at) - .execute(notes) - end - + notes, meta = gather_notes notes = prepare_notes_for_rendering(notes) notes = notes.select { |n| n.readable_by?(current_user) } - - notes_json[:notes] = + notes = if use_note_serializer? note_serializer.represent(notes) else notes.map { |note| note_json(note) } end - render json: notes_json + # We know there's more data, so tell the frontend to poll again after 1ms + set_polling_interval_header(interval: 1) if meta[:more] + + render json: meta.merge(notes: notes) end # rubocop:disable Gitlab/ModuleWithInstanceVariables @@ -101,6 +96,48 @@ module NotesActions private + # Lower bound (last_fetched_at as specified in the request) is already set in + # the finder. Here, we select between returning all notes since then, or a + # page's worth of notes. + def gather_notes + if Feature.enabled?(:paginated_notes, project) + gather_some_notes + else + gather_all_notes + end + end + + def gather_all_notes + now = Time.current + notes = merge_resource_events(notes_finder.execute.inc_relations_for_view) + + [notes, { last_fetched_at: (now.to_i * MICROSECOND) + now.usec }] + end + + def gather_some_notes + paginator = Gitlab::UpdatedNotesPaginator.new( + notes_finder.execute.inc_relations_for_view, + last_fetched_at: last_fetched_at + ) + + notes = paginator.notes + + # Fetch all the synthetic notes in the same time range as the real notes. + # Although we don't limit the number, their text is under our control so + # should be fairly cheap to process. + notes = merge_resource_events(notes, fetch_until: paginator.next_fetched_at) + + [notes, paginator.metadata] + end + + def merge_resource_events(notes, fetch_until: nil) + return notes if notes_filter == UserPreference::NOTES_FILTERS[:only_comments] + + ResourceEvents::MergeIntoNotesService + .new(noteable, current_user, last_fetched_at: last_fetched_at, fetch_until: fetch_until) + .execute(notes) + end + def note_html(note) render_to_string( "shared/notes/_note", @@ -226,11 +263,11 @@ module NotesActions end def update_note_params - params.require(:note).permit(:note) + params.require(:note).permit(:note, :position) end - def set_polling_interval_header - Gitlab::PollingInterval.set_header(response, interval: 6_000) + def set_polling_interval_header(interval: 6000) + Gitlab::PollingInterval.set_header(response, interval: interval) end def noteable @@ -242,7 +279,14 @@ module NotesActions end def last_fetched_at - request.headers['X-Last-Fetched-At'] + strong_memoize(:last_fetched_at) do + microseconds = request.headers['X-Last-Fetched-At'].to_i + + seconds = microseconds / MICROSECOND + frac = microseconds % MICROSECOND + + Time.zone.at(seconds, frac) + end end def notes_filter diff --git a/app/controllers/concerns/renders_member_access.rb b/app/controllers/concerns/renders_member_access.rb index 955ac1a1bc8..745830181c1 100644 --- a/app/controllers/concerns/renders_member_access.rb +++ b/app/controllers/concerns/renders_member_access.rb @@ -7,12 +7,6 @@ module RendersMemberAccess groups end - def prepare_projects_for_rendering(projects) - preload_max_member_access_for_collection(Project, projects) - - projects - end - private # rubocop: disable CodeReuse/ActiveRecord diff --git a/app/controllers/concerns/renders_projects_list.rb b/app/controllers/concerns/renders_projects_list.rb new file mode 100644 index 00000000000..be45c676ad6 --- /dev/null +++ b/app/controllers/concerns/renders_projects_list.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module RendersProjectsList + def prepare_projects_for_rendering(projects) + preload_max_member_access_for_collection(Project, projects) + + # Call the forks count method on every project, so the BatchLoader would load them all at + # once when the entities are rendered + projects.each(&:forks_count) + + projects + end +end diff --git a/app/controllers/concerns/service_params.rb b/app/controllers/concerns/service_params.rb index e78fa8f8250..a19c43a227a 100644 --- a/app/controllers/concerns/service_params.rb +++ b/app/controllers/concerns/service_params.rb @@ -22,8 +22,8 @@ module ServiceParams :comment_on_event_enabled, :comment_detail, :confidential_issues_events, + :confluence_url, :default_irc_uri, - :description, :device, :disable_diffs, :drone_url, @@ -31,6 +31,7 @@ module ServiceParams :external_wiki_url, :google_iap_service_account_json, :google_iap_audience_client_id, + :inherit_from_id, # We're using `issues_events` and `merge_requests_events` # in the view so we still need to explicitly state them # here. `Service#event_names` would only give @@ -61,7 +62,6 @@ module ServiceParams :sound, :subdomain, :teamcity_url, - :title, :token, :type, :url, diff --git a/app/controllers/concerns/snippets/blobs_actions.rb b/app/controllers/concerns/snippets/blobs_actions.rb new file mode 100644 index 00000000000..db56ce8f193 --- /dev/null +++ b/app/controllers/concerns/snippets/blobs_actions.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +module Snippets::BlobsActions + extend ActiveSupport::Concern + + include Gitlab::Utils::StrongMemoize + include ExtractsRef + include Snippets::SendBlob + + included do + before_action :authorize_read_snippet!, only: [:raw] + before_action :ensure_repository + before_action :ensure_blob + end + + def raw + send_snippet_blob(snippet, blob) + end + + private + + def repository_container + snippet + end + + # rubocop:disable Gitlab/ModuleWithInstanceVariables + def blob + strong_memoize(:blob) do + assign_ref_vars + + next unless @commit + + @repo.blob_at(@commit.id, @path) + end + end + # rubocop:enable Gitlab/ModuleWithInstanceVariables + + def ensure_blob + render_404 unless blob + end + + def ensure_repository + unless snippet.repo_exists? + Gitlab::AppLogger.error(message: "Snippet raw blob attempt with no repo", snippet: snippet.id) + + respond_422 + end + end + + def snippet_id + params[:snippet_id] + end +end diff --git a/app/controllers/concerns/snippets/send_blob.rb b/app/controllers/concerns/snippets/send_blob.rb new file mode 100644 index 00000000000..4f432430aaa --- /dev/null +++ b/app/controllers/concerns/snippets/send_blob.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Snippets::SendBlob + include SendsBlob + + def send_snippet_blob(snippet, blob) + workhorse_set_content_type! + + send_blob( + snippet.repository, + blob, + inline: content_disposition == 'inline', + allow_caching: snippet.public? + ) + end + + private + + def content_disposition + @disposition ||= params[:inline] == 'false' ? 'attachment' : 'inline' + end +end diff --git a/app/controllers/concerns/snippets_actions.rb b/app/controllers/concerns/snippets_actions.rb index 51fc12398d9..048b18c5c61 100644 --- a/app/controllers/concerns/snippets_actions.rb +++ b/app/controllers/concerns/snippets_actions.rb @@ -2,11 +2,13 @@ module SnippetsActions extend ActiveSupport::Concern - include SendsBlob + include RendersNotes include RendersBlob include PaginatedCollection include Gitlab::NoteableMetadata + include Snippets::SendBlob + include SnippetsSort included do skip_before_action :verify_authenticity_token, @@ -25,6 +27,10 @@ module SnippetsActions render 'edit' end + # This endpoint is being replaced by Snippets::BlobController#raw + # Support for old raw links will be maintainted via this action but + # it will only return the first blob found, + # see: https://gitlab.com/gitlab-org/gitlab/-/issues/217775 def raw workhorse_set_content_type! @@ -39,12 +45,7 @@ module SnippetsActions filename: Snippet.sanitized_file_name(blob.name) ) else - send_blob( - snippet.repository, - blob, - inline: content_disposition == 'inline', - allow_caching: snippet.public? - ) + send_snippet_blob(snippet, blob) end end @@ -106,10 +107,6 @@ module SnippetsActions private - def content_disposition - @disposition ||= params[:inline] == 'false' ? 'attachment' : 'inline' - end - # rubocop:disable Gitlab/ModuleWithInstanceVariables def blob return unless snippet diff --git a/app/controllers/concerns/snippets_sort.rb b/app/controllers/concerns/snippets_sort.rb new file mode 100644 index 00000000000..f122c843af7 --- /dev/null +++ b/app/controllers/concerns/snippets_sort.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module SnippetsSort + extend ActiveSupport::Concern + + def sort_param + params[:sort].presence || 'updated_desc' + end +end diff --git a/app/controllers/concerns/wiki_actions.rb b/app/controllers/concerns/wiki_actions.rb index 7eef12fadfe..a5182000f5b 100644 --- a/app/controllers/concerns/wiki_actions.rb +++ b/app/controllers/concerns/wiki_actions.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true module WikiActions + include DiffHelper + include PreviewMarkdown include SendsBlob include Gitlab::Utils::StrongMemoize extend ActiveSupport::Concern @@ -11,16 +13,23 @@ module WikiActions before_action :authorize_admin_wiki!, only: :destroy before_action :wiki - before_action :page, only: [:show, :edit, :update, :history, :destroy] + before_action :page, only: [:show, :edit, :update, :history, :destroy, :diff] before_action :load_sidebar, except: [:pages] + before_action :set_content_class before_action only: [:show, :edit, :update] do @valid_encoding = valid_encoding? end before_action only: [:edit, :update], unless: :valid_encoding? do - redirect_to wiki_page_path(wiki, page) + if params[:id].present? + redirect_to wiki_page_path(wiki, page || params[:id]) + else + redirect_to wiki_path(wiki) + end end + + helper_method :view_file_button, :diff_file_html_data end def new @@ -133,6 +142,19 @@ module WikiActions # rubocop:enable Gitlab/ModuleWithInstanceVariables # rubocop:disable Gitlab/ModuleWithInstanceVariables + def diff + return render_404 unless page + + apply_diff_view_cookie! + + @diffs = page.diffs(diff_options) + @diff_notes_disabled = true + + render 'shared/wikis/diff' + end + # rubocop:disable Gitlab/ModuleWithInstanceVariables + + # rubocop:disable Gitlab/ModuleWithInstanceVariables def destroy WikiPages::DestroyService.new(container: container, current_user: current_user).execute(page) @@ -203,7 +225,7 @@ module WikiActions def page_params keys = [:id] - keys << :version_id if params[:action] == 'show' + keys << :version_id if %w[show diff].include?(params[:action]) params.values_at(*keys) end @@ -229,4 +251,25 @@ module WikiActions wiki.repository.blob_at(commit.id, params[:id]) end end + + def set_content_class + @content_class = 'limit-container-width' unless fluid_layout # rubocop:disable Gitlab/ModuleWithInstanceVariables + end + + # Override CommitsHelper#view_file_button + def view_file_button(commit_sha, *args) + path = wiki_page_path(wiki, page, version_id: page.version.id) + + helpers.link_to(path, class: 'btn') do + helpers.raw(_('View page @ ')) + helpers.content_tag(:span, Commit.truncate_sha(commit_sha), class: 'commit-sha') + end + end + + # Override DiffHelper#diff_file_html_data + def diff_file_html_data(_project, _diff_file_path, diff_commit_id) + { + blob_diff_path: wiki_page_path(wiki, page, action: :diff, version_id: diff_commit_id), + view: diff_view + } + end end diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb index 25c48fadf49..ad64b6c4f94 100644 --- a/app/controllers/dashboard/projects_controller.rb +++ b/app/controllers/dashboard/projects_controller.rb @@ -3,9 +3,10 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController include ParamsBackwardCompatibility include RendersMemberAccess - include OnboardingExperimentHelper + include RendersProjectsList include SortingHelper include SortingPreference + include FiltersEvents prepend_before_action(only: [:index]) { authenticate_sessionless_user!(:rss) } before_action :set_non_archived_param diff --git a/app/controllers/dashboard/snippets_controller.rb b/app/controllers/dashboard/snippets_controller.rb index aa09fcdbe61..a8ca3dbd0e7 100644 --- a/app/controllers/dashboard/snippets_controller.rb +++ b/app/controllers/dashboard/snippets_controller.rb @@ -3,6 +3,7 @@ class Dashboard::SnippetsController < Dashboard::ApplicationController include PaginatedCollection include Gitlab::NoteableMetadata + include SnippetsSort skip_cross_project_access_check :index @@ -11,7 +12,7 @@ class Dashboard::SnippetsController < Dashboard::ApplicationController .new(current_user, author: current_user) .execute - @snippets = SnippetsFinder.new(current_user, author: current_user, scope: params[:scope]) + @snippets = SnippetsFinder.new(current_user, author: current_user, scope: params[:scope], sort: sort_param) .execute .page(params[:page]) .inc_author diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb index 8a8064b24c2..db40b0bed77 100644 --- a/app/controllers/dashboard/todos_controller.rb +++ b/app/controllers/dashboard/todos_controller.rb @@ -3,11 +3,14 @@ class Dashboard::TodosController < Dashboard::ApplicationController include ActionView::Helpers::NumberHelper include PaginatedCollection + include Analytics::UniqueVisitsHelper before_action :authorize_read_project!, only: :index before_action :authorize_read_group!, only: :index before_action :find_todos, only: [:index, :destroy_all] + track_unique_visits :index, target_id: 'u_analytics_todos' + def index @sort = params[:sort] @todos = @todos.page(params[:page]) diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index dd9e6488bc5..07cc31fb7d3 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -2,6 +2,7 @@ class DashboardController < Dashboard::ApplicationController include IssuableCollectionsAction + include FiltersEvents prepend_before_action(only: [:issues]) { authenticate_sessionless_user!(:rss) } prepend_before_action(only: [:issues_calendar]) { authenticate_sessionless_user!(:ics) } diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb index 705a586d614..f1f41e67a4c 100644 --- a/app/controllers/explore/projects_controller.rb +++ b/app/controllers/explore/projects_controller.rb @@ -4,6 +4,7 @@ class Explore::ProjectsController < Explore::ApplicationController include PageLimiter include ParamsBackwardCompatibility include RendersMemberAccess + include RendersProjectsList include SortingHelper include SortingPreference diff --git a/app/controllers/groups/application_controller.rb b/app/controllers/groups/application_controller.rb index 84c8d7ada43..9c2e361e92f 100644 --- a/app/controllers/groups/application_controller.rb +++ b/app/controllers/groups/application_controller.rb @@ -30,25 +30,25 @@ class Groups::ApplicationController < ApplicationController def authorize_admin_group! unless can?(current_user, :admin_group, group) - return render_404 + render_404 end end def authorize_create_deploy_token! unless can?(current_user, :create_deploy_token, group) - return render_404 + render_404 end end def authorize_destroy_deploy_token! unless can?(current_user, :destroy_deploy_token, group) - return render_404 + render_404 end end def authorize_admin_group_member! unless can?(current_user, :admin_group_member, group) - return render_403 + render_403 end end diff --git a/app/controllers/groups/boards_controller.rb b/app/controllers/groups/boards_controller.rb index c618ee8566a..23d4f0d24e9 100644 --- a/app/controllers/groups/boards_controller.rb +++ b/app/controllers/groups/boards_controller.rb @@ -8,7 +8,6 @@ class Groups::BoardsController < Groups::ApplicationController before_action :assign_endpoint_vars before_action do push_frontend_feature_flag(:multi_select_board, default_enabled: true) - push_frontend_feature_flag(:sfc_issue_boards, default_enabled: true) push_frontend_feature_flag(:boards_with_swimlanes, group, default_enabled: false) end diff --git a/app/controllers/groups/clusters_controller.rb b/app/controllers/groups/clusters_controller.rb index 2165dee45fb..33bfc24885f 100644 --- a/app/controllers/groups/clusters_controller.rb +++ b/app/controllers/groups/clusters_controller.rb @@ -17,6 +17,12 @@ class Groups::ClustersController < Clusters::ClustersController def group @group ||= find_routable!(Group, params[:group_id] || params[:id]) end -end -Groups::ClustersController.prepend_if_ee('EE::Groups::ClustersController') + def metrics_dashboard_params + { + cluster: cluster, + cluster_type: :group, + group: group + } + end +end diff --git a/app/controllers/groups/runners_controller.rb b/app/controllers/groups/runners_controller.rb index 635c248024e..edebffe2912 100644 --- a/app/controllers/groups/runners_controller.rb +++ b/app/controllers/groups/runners_controller.rb @@ -23,9 +23,13 @@ class Groups::RunnersController < Groups::ApplicationController end def destroy - @runner.destroy + if @runner.belongs_to_more_than_one_project? + redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), status: :found, alert: _('Runner was not deleted because it is assigned to multiple projects.') + else + @runner.destroy - redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), status: :found + redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), status: :found + end end def resume @@ -47,7 +51,9 @@ class Groups::RunnersController < Groups::ApplicationController private def runner - @runner ||= @group.runners.find(params[:id]) + @runner ||= Ci::RunnersFinder.new(current_user: current_user, group: @group, params: {}).execute + .except(:limit, :offset) + .find(params[:id]) end def runner_params diff --git a/app/controllers/groups/settings/ci_cd_controller.rb b/app/controllers/groups/settings/ci_cd_controller.rb index 18f336eae78..bf3a38ce57b 100644 --- a/app/controllers/groups/settings/ci_cd_controller.rb +++ b/app/controllers/groups/settings/ci_cd_controller.rb @@ -11,7 +11,15 @@ module Groups end before_action :define_variables, only: [:show] + NUMBER_OF_RUNNERS_PER_PAGE = 4 + def show + runners_finder = Ci::RunnersFinder.new(current_user: current_user, group: @group, params: params) + # We need all runners for count + @all_group_runners = runners_finder.execute.except(:limit, :offset) + @group_runners = runners_finder.execute.page(params[:page]).per(NUMBER_OF_RUNNERS_PER_PAGE) + + @sort = runners_finder.sort_key end def update diff --git a/app/controllers/groups/variables_controller.rb b/app/controllers/groups/variables_controller.rb index 11e3cfb01e4..02b015e8e53 100644 --- a/app/controllers/groups/variables_controller.rb +++ b/app/controllers/groups/variables_controller.rb @@ -9,7 +9,7 @@ module Groups def show respond_to do |format| format.json do - render status: :ok, json: { variables: GroupVariableSerializer.new.represent(@group.variables) } + render status: :ok, json: { variables: ::Ci::GroupVariableSerializer.new.represent(@group.variables) } end end end @@ -29,7 +29,7 @@ module Groups private def render_group_variables - render status: :ok, json: { variables: GroupVariableSerializer.new.represent(@group.variables) } + render status: :ok, json: { variables: ::Ci::GroupVariableSerializer.new.represent(@group.variables) } end def render_error diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index fba374dbb44..2162d397da3 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -7,6 +7,7 @@ class GroupsController < Groups::ApplicationController include PreviewMarkdown include RecordUserLastActivity include SendFileUpload + include FiltersEvents extend ::Gitlab::Utils::Override respond_to :html diff --git a/app/controllers/ide_controller.rb b/app/controllers/ide_controller.rb index 2bf7bdd1ae0..2c17f5b5542 100644 --- a/app/controllers/ide_controller.rb +++ b/app/controllers/ide_controller.rb @@ -8,6 +8,7 @@ class IdeController < ApplicationController before_action do push_frontend_feature_flag(:build_service_proxy) + push_frontend_feature_flag(:schema_linting) end def index diff --git a/app/controllers/import/base_controller.rb b/app/controllers/import/base_controller.rb index afdea4f7c9d..bc05030f8af 100644 --- a/app/controllers/import/base_controller.rb +++ b/app/controllers/import/base_controller.rb @@ -30,7 +30,7 @@ class Import::BaseController < ApplicationController end def incompatible_repos - [] + raise NotImplementedError end def provider_name @@ -87,15 +87,6 @@ class Import::BaseController < ApplicationController end # rubocop: enable CodeReuse/ActiveRecord - # rubocop: disable CodeReuse/ActiveRecord - def find_jobs(import_type) - current_user.created_projects - .with_import_state - .where(import_type: import_type) - .to_json(only: [:id], methods: [:import_status]) - end - # rubocop: enable CodeReuse/ActiveRecord - # deprecated: being replaced by app/services/import/base_service.rb def find_or_create_namespace(names, owner) names = params[:target_namespace].presence || names diff --git a/app/controllers/import/bitbucket_controller.rb b/app/controllers/import/bitbucket_controller.rb index 4886aeb5e3f..0ffd9ef8bdd 100644 --- a/app/controllers/import/bitbucket_controller.rb +++ b/app/controllers/import/bitbucket_controller.rb @@ -22,23 +22,8 @@ class Import::BitbucketController < Import::BaseController redirect_to status_import_bitbucket_url end - # rubocop: disable CodeReuse/ActiveRecord def status - return super if Feature.enabled?(:new_import_ui) - - bitbucket_client = Bitbucket::Client.new(credentials) - repos = bitbucket_client.repos(filter: sanitized_filter_param) - @repos, @incompatible_repos = repos.partition { |repo| repo.valid? } - - @already_added_projects = find_already_added_projects('bitbucket') - already_added_projects_names = @already_added_projects.pluck(:import_source) - - @repos.to_a.reject! { |repo| already_added_projects_names.include?(repo.full_name) } - end - # rubocop: enable CodeReuse/ActiveRecord - - def jobs - render json: find_jobs('bitbucket') + super end def realtime_changes diff --git a/app/controllers/import/bitbucket_server_controller.rb b/app/controllers/import/bitbucket_server_controller.rb index 9aa8110257d..bee78cb3283 100644 --- a/app/controllers/import/bitbucket_server_controller.rb +++ b/app/controllers/import/bitbucket_server_controller.rb @@ -7,6 +7,7 @@ class Import::BitbucketServerController < Import::BaseController before_action :verify_bitbucket_server_import_enabled before_action :bitbucket_auth, except: [:new, :configure] + before_action :normalize_import_params, only: [:create] before_action :validate_import_params, only: [:create] rescue_from BitbucketServer::Connection::ConnectionError, with: :bitbucket_connection_error @@ -34,48 +35,25 @@ class Import::BitbucketServerController < Import::BaseController return render json: { errors: _("Project %{project_repo} could not be found") % { project_repo: "#{@project_key}/#{@repo_slug}" } }, status: :unprocessable_entity end - project_name = params[:new_name].presence || repo.name - namespace_path = params[:new_namespace].presence || current_user.username - target_namespace = find_or_create_namespace(namespace_path, current_user) + result = Import::BitbucketServerService.new(client, current_user, params).execute(credentials) - if current_user.can?(:create_projects, target_namespace) - project = Gitlab::BitbucketServerImport::ProjectCreator.new(@project_key, @repo_slug, repo, project_name, target_namespace, current_user, credentials).execute - - if project.persisted? - render json: ProjectSerializer.new.represent(project, serializer: :import) - else - render json: { errors: project_save_error(project) }, status: :unprocessable_entity - end + if result[:status] == :success + render json: ProjectSerializer.new.represent(result[:project], serializer: :import) else - render json: { errors: _('This namespace has already been taken! Please choose another one.') }, status: :unprocessable_entity + render json: { errors: result[:message] }, status: result[:http_status] end end def configure session[personal_access_token_key] = params[:personal_access_token] - session[bitbucket_server_username_key] = params[:bitbucket_username] + session[bitbucket_server_username_key] = params[:bitbucket_server_username] session[bitbucket_server_url_key] = params[:bitbucket_server_url] redirect_to status_import_bitbucket_server_path end - # rubocop: disable CodeReuse/ActiveRecord def status - return super if Feature.enabled?(:new_import_ui) - - @collection = client.repos(page_offset: page_offset, limit: limit_per_page, filter: sanitized_filter_param) - @repos, @incompatible_repos = @collection.partition { |repo| repo.valid? } - - # Use the import URL to filter beyond what BaseService#find_already_added_projects - @already_added_projects = filter_added_projects('bitbucket_server', @repos.map(&:browse_url)) - already_added_projects_names = @already_added_projects.pluck(:import_source) - - @repos.reject! { |repo| already_added_projects_names.include?(repo.browse_url) } - end - # rubocop: enable CodeReuse/ActiveRecord - - def jobs - render json: find_jobs('bitbucket_server') + super end def realtime_changes @@ -126,9 +104,15 @@ class Import::BitbucketServerController < Import::BaseController @bitbucket_repos ||= client.repos(page_offset: page_offset, limit: limit_per_page, filter: sanitized_filter_param).to_a end + def normalize_import_params + project_key, repo_slug = params[:repo_id].split('/') + params[:bitbucket_server_project] = project_key + params[:bitbucket_server_repo] = repo_slug + end + def validate_import_params - @project_key = params[:project] - @repo_slug = params[:repository] + @project_key = params[:bitbucket_server_project] + @repo_slug = params[:bitbucket_server_repo] return render_validation_error('Missing project key') unless @project_key.present? && @repo_slug.present? return render_validation_error('Missing repository slug') unless @repo_slug.present? diff --git a/app/controllers/import/fogbugz_controller.rb b/app/controllers/import/fogbugz_controller.rb index 91779a5d6cc..a34bc9c953f 100644 --- a/app/controllers/import/fogbugz_controller.rb +++ b/app/controllers/import/fogbugz_controller.rb @@ -50,14 +50,7 @@ class Import::FogbugzController < Import::BaseController return redirect_to new_import_fogbugz_path end - return super if Feature.enabled?(:new_import_ui) - - @repos = client.repos - - @already_added_projects = find_already_added_projects('fogbugz') - already_added_projects_names = @already_added_projects.pluck(:import_source) - - @repos.reject! { |repo| already_added_projects_names.include? repo.name } + super end # rubocop: enable CodeReuse/ActiveRecord @@ -65,10 +58,6 @@ class Import::FogbugzController < Import::BaseController super end - def jobs - render json: find_jobs('fogbugz') - end - def create repo = client.repo(params[:repo_id]) fb_session = { uri: session[:fogbugz_uri], token: session[:fogbugz_token] } @@ -96,6 +85,11 @@ class Import::FogbugzController < Import::BaseController end # rubocop: enable CodeReuse/ActiveRecord + override :incompatible_repos + def incompatible_repos + [] + end + override :provider_name def provider_name :fogbugz diff --git a/app/controllers/import/gitea_controller.rb b/app/controllers/import/gitea_controller.rb index 42c23fb29a7..efeff8439e4 100644 --- a/app/controllers/import/gitea_controller.rb +++ b/app/controllers/import/gitea_controller.rb @@ -21,15 +21,17 @@ class Import::GiteaController < Import::GithubController super end - private + protected - def host_key - :"#{provider}_host_url" + override :provider_name + def provider_name + :gitea end - override :provider - def provider - :gitea + private + + def host_key + :"#{provider_name}_host_url" end override :provider_url diff --git a/app/controllers/import/github_controller.rb b/app/controllers/import/github_controller.rb index 097edcd6075..ac6b8c06d66 100644 --- a/app/controllers/import/github_controller.rb +++ b/app/controllers/import/github_controller.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class Import::GithubController < Import::BaseController + extend ::Gitlab::Utils::Override + include ImportHelper include ActionView::Helpers::SanitizeHelper @@ -34,18 +36,11 @@ class Import::GithubController < Import::BaseController # Improving in https://gitlab.com/gitlab-org/gitlab-foss/issues/55585 client_repos - respond_to do |format| - format.json do - render json: { imported_projects: serialized_imported_projects, - provider_repos: serialized_provider_repos, - namespaces: serialized_namespaces } - end - format.html - end + super end def create - result = Import::GithubService.new(client, current_user, import_params).execute(access_params, provider) + result = Import::GithubService.new(client, current_user, import_params).execute(access_params, provider_name) if result[:status] == :success render json: serialized_imported_projects(result[:project]) @@ -55,44 +50,51 @@ class Import::GithubController < Import::BaseController end def realtime_changes - Gitlab::PollingInterval.set_header(response, interval: 3_000) - - render json: already_added_projects.to_json(only: [:id], methods: [:import_status]) + super end - private + protected - def import_params - params.permit(permitted_import_params) - end + # rubocop: disable CodeReuse/ActiveRecord + override :importable_repos + def importable_repos + already_added_projects_names = already_added_projects.pluck(:import_source) - def permitted_import_params - [:repo_id, :new_name, :target_namespace] + client_repos.reject { |repo| already_added_projects_names.include?(repo.full_name) } end + # rubocop: enable CodeReuse/ActiveRecord - def serialized_imported_projects(projects = already_added_projects) - ProjectSerializer.new.represent(projects, serializer: :import, provider_url: provider_url) + override :incompatible_repos + def incompatible_repos + [] end - def serialized_provider_repos - repos = client_repos.reject { |repo| already_added_project_names.include? repo.full_name } - Import::ProviderRepoSerializer.new(current_user: current_user).represent(repos, provider: provider, provider_url: provider_url) + override :provider_name + def provider_name + :github end - def serialized_namespaces - NamespaceSerializer.new.represent(namespaces) + override :provider_url + def provider_url + strong_memoize(:provider_url) do + provider = Gitlab::Auth::OAuth::Provider.config_for('github') + + provider&.dig('url').presence || 'https://github.com' + end end - def already_added_projects - @already_added_projects ||= filtered(find_already_added_projects(provider)) + private + + def import_params + params.permit(permitted_import_params) end - def already_added_project_names - @already_added_projects_names ||= already_added_projects.pluck(:import_source) # rubocop:disable CodeReuse/ActiveRecord + def permitted_import_params + [:repo_id, :new_name, :target_namespace] end - def namespaces - current_user.manageable_groups_with_routes + def serialized_imported_projects(projects = already_added_projects) + ProjectSerializer.new.represent(projects, serializer: :import, provider_url: provider_url) end def expire_etag_cache @@ -118,29 +120,29 @@ class Import::GithubController < Import::BaseController end def import_enabled? - __send__("#{provider}_import_enabled?") # rubocop:disable GitlabSecurity/PublicSend + __send__("#{provider_name}_import_enabled?") # rubocop:disable GitlabSecurity/PublicSend end def realtime_changes_path - public_send("realtime_changes_import_#{provider}_path", format: :json) # rubocop:disable GitlabSecurity/PublicSend + public_send("realtime_changes_import_#{provider_name}_path", format: :json) # rubocop:disable GitlabSecurity/PublicSend end def new_import_url - public_send("new_import_#{provider}_url", extra_import_params) # rubocop:disable GitlabSecurity/PublicSend + public_send("new_import_#{provider_name}_url", extra_import_params) # rubocop:disable GitlabSecurity/PublicSend end def status_import_url - public_send("status_import_#{provider}_url", extra_import_params) # rubocop:disable GitlabSecurity/PublicSend + public_send("status_import_#{provider_name}_url", extra_import_params) # rubocop:disable GitlabSecurity/PublicSend end def callback_import_url - public_send("users_import_#{provider}_callback_url", extra_import_params) # rubocop:disable GitlabSecurity/PublicSend + public_send("users_import_#{provider_name}_callback_url", extra_import_params) # rubocop:disable GitlabSecurity/PublicSend end def provider_unauthorized session[access_token_key] = nil redirect_to new_import_url, - alert: "Access denied to your #{Gitlab::ImportSources.title(provider.to_s)} account." + alert: "Access denied to your #{Gitlab::ImportSources.title(provider_name.to_s)} account." end def provider_rate_limit(exception) @@ -151,29 +153,16 @@ class Import::GithubController < Import::BaseController end def access_token_key - :"#{provider}_access_token" + :"#{provider_name}_access_token" end def access_params { github_access_token: session[access_token_key] } end - # The following methods are overridden in subclasses - def provider - :github - end - - def provider_url - strong_memoize(:provider_url) do - provider = Gitlab::Auth::OAuth::Provider.config_for('github') - - provider&.dig('url').presence || 'https://github.com' - end - end - # rubocop: disable CodeReuse/ActiveRecord def logged_in_with_provider? - current_user.identities.exists?(provider: provider) + current_user.identities.exists?(provider: provider_name) end # rubocop: enable CodeReuse/ActiveRecord @@ -202,12 +191,6 @@ class Import::GithubController < Import::BaseController def filter_attribute :name end - - def filtered(collection) - return collection unless sanitized_filter_param - - collection.select { |item| item[filter_attribute].include?(sanitized_filter_param) } - end end Import::GithubController.prepend_if_ee('EE::Import::GithubController') diff --git a/app/controllers/import/gitlab_controller.rb b/app/controllers/import/gitlab_controller.rb index a95a67e208c..cc68eb02741 100644 --- a/app/controllers/import/gitlab_controller.rb +++ b/app/controllers/import/gitlab_controller.rb @@ -16,21 +16,8 @@ class Import::GitlabController < Import::BaseController redirect_to status_import_gitlab_url end - # rubocop: disable CodeReuse/ActiveRecord def status - return super if Feature.enabled?(:new_import_ui) - - @repos = client.projects(starting_page: 1, page_limit: MAX_PROJECT_PAGES, per_page: PER_PAGE_PROJECTS) - - @already_added_projects = find_already_added_projects('gitlab') - already_added_projects_names = @already_added_projects.pluck(:import_source) - - @repos = @repos.to_a.reject { |repo| already_added_projects_names.include? repo["path_with_namespace"] } - end - # rubocop: enable CodeReuse/ActiveRecord - - def jobs - render json: find_jobs('gitlab') + super end def create @@ -63,6 +50,11 @@ class Import::GitlabController < Import::BaseController end # rubocop: enable CodeReuse/ActiveRecord + override :incompatible_repos + def incompatible_repos + [] + end + override :provider_name def provider_name :gitlab diff --git a/app/controllers/instance_statistics/cohorts_controller.rb b/app/controllers/instance_statistics/cohorts_controller.rb index 4b4e39db2e1..0de62a56b01 100644 --- a/app/controllers/instance_statistics/cohorts_controller.rb +++ b/app/controllers/instance_statistics/cohorts_controller.rb @@ -1,8 +1,12 @@ # frozen_string_literal: true class InstanceStatistics::CohortsController < InstanceStatistics::ApplicationController + include Analytics::UniqueVisitsHelper + before_action :authenticate_usage_ping_enabled_or_admin! + track_unique_visits :index, target_id: 'i_analytics_cohorts' + def index if Gitlab::CurrentSettings.usage_ping_enabled cohorts_results = Rails.cache.fetch('cohorts', expires_in: 1.day) do diff --git a/app/controllers/instance_statistics/dev_ops_score_controller.rb b/app/controllers/instance_statistics/dev_ops_score_controller.rb index 238f7fa7707..b98a1bf7f99 100644 --- a/app/controllers/instance_statistics/dev_ops_score_controller.rb +++ b/app/controllers/instance_statistics/dev_ops_score_controller.rb @@ -1,6 +1,10 @@ # frozen_string_literal: true class InstanceStatistics::DevOpsScoreController < InstanceStatistics::ApplicationController + include Analytics::UniqueVisitsHelper + + track_unique_visits :index, target_id: 'i_analytics_dev_ops_score' + # rubocop: disable CodeReuse/ActiveRecord def index @metric = DevOpsScore::Metric.order(:created_at).last&.present diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb index a78d87eceea..5bd9ac7f275 100644 --- a/app/controllers/invites_controller.rb +++ b/app/controllers/invites_controller.rb @@ -1,12 +1,17 @@ # frozen_string_literal: true class InvitesController < ApplicationController + include Gitlab::Utils::StrongMemoize + before_action :member skip_before_action :authenticate_user!, only: :decline + helper_method :member?, :current_user_matches_invite? + respond_to :html def show + accept if skip_invitation_prompt? end def accept @@ -38,6 +43,20 @@ class InvitesController < ApplicationController private + def skip_invitation_prompt? + !member? && current_user_matches_invite? + end + + def current_user_matches_invite? + @member.invite_email == current_user.email + end + + def member? + strong_memoize(:is_member) do + @member.source.users.include?(current_user) + end + end + def member return @member if defined?(@member) diff --git a/app/controllers/oauth/applications_controller.rb b/app/controllers/oauth/applications_controller.rb index 2c3e60d12b7..6532501733a 100644 --- a/app/controllers/oauth/applications_controller.rb +++ b/app/controllers/oauth/applications_controller.rb @@ -17,6 +17,8 @@ class Oauth::ApplicationsController < Doorkeeper::ApplicationsController before_action :add_gon_variables before_action :load_scopes, only: [:index, :create, :edit, :update] + around_action :set_locale + helper_method :can? layout 'profile' @@ -70,4 +72,8 @@ class Oauth::ApplicationsController < Doorkeeper::ApplicationsController params[:owner] = current_user end end + + def set_locale(&block) + Gitlab::I18n.with_user_locale(current_user, &block) + end end diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index 4c595313cb6..706a4843117 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -200,7 +200,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController def fail_login(user) error_message = user.errors.full_messages.to_sentence - return redirect_to omniauth_error_path(oauth['provider'], error: error_message) + redirect_to omniauth_error_path(oauth['provider'], error: error_message) end def fail_auth0_login diff --git a/app/controllers/profiles/keys_controller.rb b/app/controllers/profiles/keys_controller.rb index b9cb71ae89a..99e1b9027fa 100644 --- a/app/controllers/profiles/keys_controller.rb +++ b/app/controllers/profiles/keys_controller.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true class Profiles::KeysController < Profiles::ApplicationController - skip_before_action :authenticate_user!, only: [:get_keys] - def index @keys = current_user.keys.order_id_desc @key = Key.new @@ -33,25 +31,6 @@ class Profiles::KeysController < Profiles::ApplicationController end end - # Get all keys of a user(params[:username]) in a text format - # Helpful for sysadmins to put in respective servers - def get_keys - if params[:username].present? - begin - user = UserFinder.new(params[:username]).find_by_username - if user.present? - render plain: user.all_ssh_keys.join("\n") - else - return render_404 - end - rescue => e - render html: e.message - end - else - return render_404 - end - end - private def key_params diff --git a/app/controllers/profiles/personal_access_tokens_controller.rb b/app/controllers/profiles/personal_access_tokens_controller.rb index f1c07cd9a1d..30f25e8fdaa 100644 --- a/app/controllers/profiles/personal_access_tokens_controller.rb +++ b/app/controllers/profiles/personal_access_tokens_controller.rb @@ -40,14 +40,18 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController params.require(:personal_access_token).permit(:name, :expires_at, scopes: []) end - # rubocop: disable CodeReuse/ActiveRecord def set_index_vars @scopes = Gitlab::Auth.available_scopes_for(current_user) @inactive_personal_access_tokens = finder(state: 'inactive').execute - @active_personal_access_tokens = finder(state: 'active').execute.order(:expires_at) + @active_personal_access_tokens = active_personal_access_tokens @new_personal_access_token = PersonalAccessToken.redis_getdel(current_user.id) end - # rubocop: enable CodeReuse/ActiveRecord + + def active_personal_access_tokens + finder(state: 'active', sort: 'expires_at_asc').execute + end end + +Profiles::PersonalAccessTokensController.prepend_if_ee('EE::Profiles::PersonalAccessTokensController') diff --git a/app/controllers/profiles/preferences_controller.rb b/app/controllers/profiles/preferences_controller.rb index 1477d79c911..8653fe3b6ed 100644 --- a/app/controllers/profiles/preferences_controller.rb +++ b/app/controllers/profiles/preferences_controller.rb @@ -48,6 +48,7 @@ class Profiles::PreferencesController < Profiles::ApplicationController :time_display_relative, :time_format_in_24h, :show_whitespace_in_diffs, + :view_diffs_file_by_file, :tab_width, :sourcegraph_enabled, :render_whitespace_in_code diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb index b1f285f76d7..518d414be1b 100644 --- a/app/controllers/projects/application_controller.rb +++ b/app/controllers/projects/application_controller.rb @@ -42,7 +42,7 @@ class Projects::ApplicationController < ApplicationController def authorize_action!(action) unless can?(current_user, action, project) - return access_denied! + access_denied! end end @@ -81,10 +81,6 @@ class Projects::ApplicationController < ApplicationController end end - def apply_diff_view_cookie! - set_secure_cookie(:diff_view, params.delete(:view), permanent: true) if params[:view].present? - end - def require_pages_enabled! not_found unless @project.pages_available? end diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index 14dca1bdc30..7f14522e61b 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -9,6 +9,7 @@ class Projects::BlobController < Projects::ApplicationController include ActionView::Helpers::SanitizeHelper include RedirectsForMissingPathOnTree include SourcegraphDecorator + include DiffHelper prepend_before_action :authenticate_user!, only: [:edit] @@ -129,7 +130,7 @@ class Projects::BlobController < Projects::ApplicationController end end - return redirect_to_tree_root_for_missing_path(@project, @ref, @path) + redirect_to_tree_root_for_missing_path(@project, @ref, @path) end end @@ -207,14 +208,14 @@ class Projects::BlobController < Projects::ApplicationController def set_last_commit_sha @last_commit_sha = Gitlab::Git::Commit - .last_for_path(@repository, @ref, @path).sha + .last_for_path(@repository, @ref, @path, literal_pathspec: true).sha end def show_html environment_params = @repository.branch_exists?(@ref) ? { ref: @ref } : { commit: @commit } environment_params[:find_latest] = true @environment = EnvironmentsFinder.new(@project, current_user, environment_params).execute.last - @last_commit = @repository.last_commit_for_path(@commit.id, @blob.path) + @last_commit = @repository.last_commit_for_path(@commit.id, @blob.path, literal_pathspec: true) @code_navigation_path = Gitlab::CodeNavigationPath.new(@project, @blob.commit_id).full_json_path_for(@blob.path) render 'show' diff --git a/app/controllers/projects/boards_controller.rb b/app/controllers/projects/boards_controller.rb index 8fa823e0be1..db05da0bb7f 100644 --- a/app/controllers/projects/boards_controller.rb +++ b/app/controllers/projects/boards_controller.rb @@ -9,7 +9,6 @@ class Projects::BoardsController < Projects::ApplicationController before_action :assign_endpoint_vars before_action do push_frontend_feature_flag(:multi_select_board, default_enabled: true) - push_frontend_feature_flag(:sfc_issue_boards, default_enabled: true) end private diff --git a/app/controllers/projects/ci/lints_controller.rb b/app/controllers/projects/ci/lints_controller.rb index b50afa12da0..73b3eb9c205 100644 --- a/app/controllers/projects/ci/lints_controller.rb +++ b/app/controllers/projects/ci/lints_controller.rb @@ -14,7 +14,7 @@ class Projects::Ci::LintsController < Projects::ApplicationController @errors = result.errors if result.valid? - @config_processor = result.content + @config_processor = result.config @stages = @config_processor.stages @builds = @config_processor.builds @jobs = @config_processor.jobs diff --git a/app/controllers/projects/clusters_controller.rb b/app/controllers/projects/clusters_controller.rb index 079d30127d6..8acf5235c1a 100644 --- a/app/controllers/projects/clusters_controller.rb +++ b/app/controllers/projects/clusters_controller.rb @@ -23,6 +23,13 @@ class Projects::ClustersController < Clusters::ClustersController def repository @repository ||= project.repository end -end -Projects::ClustersController.prepend_if_ee('EE::Projects::ClustersController') + def metrics_dashboard_params + params.permit(:embedded, :group, :title, :y_label).merge( + { + cluster: cluster, + cluster_type: :project + } + ) + end +end diff --git a/app/controllers/projects/confluences_controller.rb b/app/controllers/projects/confluences_controller.rb new file mode 100644 index 00000000000..d563b34a362 --- /dev/null +++ b/app/controllers/projects/confluences_controller.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +class Projects::ConfluencesController < Projects::ApplicationController + before_action :ensure_confluence + + def show + end + + private + + def ensure_confluence + render_404 unless project.has_confluence? + end +end diff --git a/app/controllers/projects/cycle_analytics_controller.rb b/app/controllers/projects/cycle_analytics_controller.rb index f13c75ac4cc..898d888c978 100644 --- a/app/controllers/projects/cycle_analytics_controller.rb +++ b/app/controllers/projects/cycle_analytics_controller.rb @@ -4,10 +4,13 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController include ActionView::Helpers::DateHelper include ActionView::Helpers::TextHelper include CycleAnalyticsParams + include Analytics::UniqueVisitsHelper before_action :whitelist_query_limiting, only: [:show] before_action :authorize_read_cycle_analytics! + track_unique_visits :show, target_id: 'p_analytics_valuestream' + def show @cycle_analytics = ::CycleAnalytics::ProjectLevel.new(@project, options: options(cycle_analytics_project_params)) diff --git a/app/controllers/projects/deployments_controller.rb b/app/controllers/projects/deployments_controller.rb index 766e2f86ea2..1344cf775e4 100644 --- a/app/controllers/projects/deployments_controller.rb +++ b/app/controllers/projects/deployments_controller.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class Projects::DeploymentsController < Projects::ApplicationController - before_action :authorize_read_environment! before_action :authorize_read_deployment! # rubocop: disable CodeReuse/ActiveRecord diff --git a/app/controllers/projects/environments/prometheus_api_controller.rb b/app/controllers/projects/environments/prometheus_api_controller.rb index 98fcc594d6e..f0bb5360f84 100644 --- a/app/controllers/projects/environments/prometheus_api_controller.rb +++ b/app/controllers/projects/environments/prometheus_api_controller.rb @@ -1,51 +1,17 @@ # frozen_string_literal: true class Projects::Environments::PrometheusApiController < Projects::ApplicationController - include RenderServiceResults + include Metrics::Dashboard::PrometheusApiProxy - before_action :authorize_read_prometheus! - before_action :environment - - def proxy - variable_substitution_result = - variable_substitution_service.new(environment, permit_params).execute - - if variable_substitution_result[:status] == :error - return error_response(variable_substitution_result) - end - - prometheus_result = Prometheus::ProxyService.new( - environment, - proxy_method, - proxy_path, - variable_substitution_result[:params] - ).execute - - return continue_polling_response if prometheus_result.nil? - return error_response(prometheus_result) if prometheus_result[:status] == :error - - success_response(prometheus_result) - end + before_action :proxyable private - def variable_substitution_service - Prometheus::ProxyVariableSubstitutionService - end - - def permit_params - params.permit! - end - - def environment - @environment ||= project.environments.find(params[:id]) + def proxyable + @proxyable ||= project.environments.find(params[:id]) end - def proxy_method - request.method - end - - def proxy_path - params[:proxy_path] + def proxy_variable_substitution_service + Prometheus::ProxyVariableSubstitutionService end end diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index 4d774123ef1..d5da24a76de 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -1,6 +1,10 @@ # frozen_string_literal: true class Projects::EnvironmentsController < Projects::ApplicationController + # Metrics dashboard code is getting decoupled from environments and is being moved + # into app/controllers/projects/metrics_dashboard_controller.rb + # See https://gitlab.com/gitlab-org/gitlab/-/issues/226002 for more details. + include MetricsDashboard layout 'project' diff --git a/app/controllers/projects/forks_controller.rb b/app/controllers/projects/forks_controller.rb index ebc81976529..b93f6384e0c 100644 --- a/app/controllers/projects/forks_controller.rb +++ b/app/controllers/projects/forks_controller.rb @@ -3,6 +3,7 @@ class Projects::ForksController < Projects::ApplicationController include ContinueParams include RendersMemberAccess + include RendersProjectsList include Gitlab::Utils::StrongMemoize # Authorize diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb index a8b90f8685f..9b889f9e837 100644 --- a/app/controllers/projects/graphs_controller.rb +++ b/app/controllers/projects/graphs_controller.rb @@ -2,12 +2,15 @@ class Projects::GraphsController < Projects::ApplicationController include ExtractsPath + include Analytics::UniqueVisitsHelper # Authorize before_action :require_non_empty_project before_action :assign_ref_vars before_action :authorize_read_repository_graphs! + track_unique_visits :charts, target_id: 'p_analytics_repo' + def show respond_to do |format| format.html diff --git a/app/controllers/projects/imports_controller.rb b/app/controllers/projects/imports_controller.rb index 67a7daf8445..deba71c9dd3 100644 --- a/app/controllers/projects/imports_controller.rb +++ b/app/controllers/projects/imports_controller.rb @@ -5,7 +5,8 @@ class Projects::ImportsController < Projects::ApplicationController include ImportUrlParams # Authorize - before_action :authorize_admin_project! + before_action :authorize_admin_project!, only: [:new, :create] + before_action :require_namespace_project_creation_permission, only: :show before_action :require_no_repo, only: [:new, :create] before_action :redirect_if_progress, only: [:new, :create] before_action :redirect_if_no_import, only: :show @@ -51,6 +52,10 @@ class Projects::ImportsController < Projects::ApplicationController end end + def require_namespace_project_creation_permission + render_404 unless current_user.can?(:admin_project, @project) || current_user.can?(:create_projects, @project.namespace) + end + def redirect_if_progress if @project.import_in_progress? redirect_to project_import_path(@project) diff --git a/app/controllers/projects/incident_management/pager_duty_incidents_controller.rb b/app/controllers/projects/incident_management/pager_duty_incidents_controller.rb new file mode 100644 index 00000000000..dac1640dd08 --- /dev/null +++ b/app/controllers/projects/incident_management/pager_duty_incidents_controller.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module Projects + module IncidentManagement + class PagerDutyIncidentsController < Projects::ApplicationController + respond_to :json + + skip_before_action :verify_authenticity_token + skip_before_action :project + + prepend_before_action :project_without_auth + + def create + result = webhook_processor.execute(params[:token]) + + head result.http_status + end + + private + + def project_without_auth + @project ||= Project + .find_by_full_path("#{params[:namespace_id]}/#{params[:project_id]}") + end + + def webhook_processor + ::IncidentManagement::PagerDuty::ProcessWebhookService.new(project, nil, payload) + end + + def payload + @payload ||= params.permit![:pager_duty_incident].to_h + end + end + end +end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 693329848de..12b5a538bc9 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -11,11 +11,11 @@ class Projects::IssuesController < Projects::ApplicationController include RecordUserLastActivity def issue_except_actions - %i[index calendar new create bulk_update import_csv export_csv] + %i[index calendar new create bulk_update import_csv export_csv service_desk] end def set_issuables_index_only_actions - %i[index calendar] + %i[index calendar service_desk] end prepend_before_action(only: [:index]) { authenticate_sessionless_user!(:rss) } @@ -46,10 +46,17 @@ class Projects::IssuesController < Projects::ApplicationController 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) end before_action only: :show do push_frontend_feature_flag(:real_time_issue_sidebar, @project) + push_frontend_feature_flag(:confidential_apollo_sidebar, @project) + end + + before_action only: :index do + push_frontend_feature_flag(:scoped_labels, @project) end around_action :allow_gitaly_ref_name_caching, only: [:discussions] @@ -216,6 +223,11 @@ class Projects::IssuesController < Projects::ApplicationController redirect_to project_issues_path(project) end + def service_desk + @issues = @issuables # rubocop:disable Gitlab/ModuleWithInstanceVariables + @users.push(User.support_bot) # rubocop:disable Gitlab/ModuleWithInstanceVariables + end + protected def sorting_field @@ -313,6 +325,17 @@ class Projects::IssuesController < Projects::ApplicationController private + def finder_options + options = super + + return options unless service_desk? + + options.reject! { |key| key == 'author_username' || key == 'author_id' } + options[:author_id] = User.support_bot + + options + end + def branch_link(branch) project_compare_path(project, from: project.default_branch, to: branch[:name]) end @@ -330,6 +353,10 @@ class Projects::IssuesController < Projects::ApplicationController def rate_limiter ::Gitlab::ApplicationRateLimiter end + + def service_desk? + action_name == 'service_desk' + 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 e1f6cbe3dca..3f7f8da3478 100644 --- a/app/controllers/projects/jobs_controller.rb +++ b/app/controllers/projects/jobs_controller.rb @@ -11,9 +11,6 @@ class Projects::JobsController < Projects::ApplicationController before_action :authorize_erase_build!, only: [:erase] before_action :authorize_use_build_terminal!, only: [:terminal, :terminal_websocket_authorize] before_action :verify_api_request!, only: :terminal_websocket_authorize - before_action only: [:show] do - push_frontend_feature_flag(:job_log_json, project, default_enabled: true) - end before_action :authorize_create_proxy_build!, only: :proxy_websocket_authorize before_action :verify_proxy_request!, only: :proxy_websocket_authorize @@ -55,15 +52,10 @@ class Projects::JobsController < Projects::ApplicationController format.json do build.trace.being_watched! - # TODO: when the feature flag is removed we should not pass - # content_format to serialize method. - content_format = Feature.enabled?(:job_log_json, @project, default_enabled: true) ? :json : :html - build_trace = Ci::BuildTrace.new( build: @build, stream: stream, - state: params[:state], - content_format: content_format) + state: params[:state]) render json: BuildTraceSerializer .new(project: @project, current_user: @current_user) diff --git a/app/controllers/projects/logs_controller.rb b/app/controllers/projects/logs_controller.rb index ba509235417..b9027b3a2cb 100644 --- a/app/controllers/projects/logs_controller.rb +++ b/app/controllers/projects/logs_controller.rb @@ -2,15 +2,16 @@ module Projects class LogsController < Projects::ApplicationController + include ::Gitlab::Utils::StrongMemoize + before_action :authorize_read_pod_logs! - before_action :environment before_action :ensure_deployments, only: %i(k8s elasticsearch) def index - if environment.nil? - render :empty_logs - else + if environment || cluster render :index + else + render :empty_logs end end @@ -39,8 +40,9 @@ module Projects end end - def index_params - params.permit(:environment_name) + # cluster is selected either via environment or directly by id + def cluster_params + params.permit(:environment_name, :cluster_id) end def k8s_params @@ -52,22 +54,36 @@ module Projects end def environment - @environment ||= if index_params.key?(:environment_name) - EnvironmentsFinder.new(project, current_user, name: index_params[:environment_name]).find.first - else - project.default_environment - end + strong_memoize(:environment) do + if cluster_params.key?(:environment_name) + EnvironmentsFinder.new(project, current_user, name: cluster_params[:environment_name]).find.first + else + project.default_environment + end + end end def cluster - environment.deployment_platform&.cluster + strong_memoize(:cluster) do + if gitlab_managed_apps_logs? + clusters = ClusterAncestorsFinder.new(project, current_user).execute + clusters.find { |cluster| cluster.id == cluster_params[:cluster_id].to_i } + else + environment&.deployment_platform&.cluster + end + end end def namespace - environment.deployment_namespace + if gitlab_managed_apps_logs? + Gitlab::Kubernetes::Helm::NAMESPACE + else + environment.deployment_namespace + end end def ensure_deployments + return if gitlab_managed_apps_logs? return if cluster && namespace.present? render status: :bad_request, json: { @@ -75,5 +91,9 @@ module Projects message: _('Environment does not have deployments') } end + + def gitlab_managed_apps_logs? + cluster_params.key?(:cluster_id) + end end end diff --git a/app/controllers/projects/merge_requests/application_controller.rb b/app/controllers/projects/merge_requests/application_controller.rb index b7e99cb7ed0..0bb4e0fb5ee 100644 --- a/app/controllers/projects/merge_requests/application_controller.rb +++ b/app/controllers/projects/merge_requests/application_controller.rb @@ -48,12 +48,9 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont end def set_pipeline_variables - @pipelines = - if can?(current_user, :read_pipeline, @merge_request.source_project) - @merge_request.all_pipelines - else - Ci::Pipeline.none - end + @pipelines = Ci::PipelinesForMergeRequestFinder + .new(@merge_request, current_user) + .execute end def close_merge_request_if_no_source_project diff --git a/app/controllers/projects/merge_requests/creations_controller.rb b/app/controllers/projects/merge_requests/creations_controller.rb index 28aa1b300aa..3e077c1af37 100644 --- a/app/controllers/projects/merge_requests/creations_controller.rb +++ b/app/controllers/projects/merge_requests/creations_controller.rb @@ -32,13 +32,13 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap end def pipelines - @pipelines = @merge_request.all_pipelines + @pipelines = Ci::PipelinesForMergeRequestFinder.new(@merge_request, current_user).execute Gitlab::PollingInterval.set_header(response, interval: 10_000) render json: { pipelines: PipelineSerializer - .new(project: @project, current_user: @current_user) + .new(project: @project, current_user: current_user) .represent(@pipelines) } end diff --git a/app/controllers/projects/merge_requests/diffs_controller.rb b/app/controllers/projects/merge_requests/diffs_controller.rb index 1bf143c9a91..98b0abc89e9 100644 --- a/app/controllers/projects/merge_requests/diffs_controller.rb +++ b/app/controllers/projects/merge_requests/diffs_controller.rb @@ -8,6 +8,7 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic before_action :commit before_action :define_diff_vars before_action :define_diff_comment_vars, except: [:diffs_batch, :diffs_metadata] + before_action :update_diff_discussion_positions! around_action :allow_gitaly_ref_name_caching @@ -171,4 +172,12 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic @notes.concat(draft_notes) end + + def update_diff_discussion_positions! + return unless Feature.enabled?(:merge_ref_head_comments, @merge_request.target_project, default_enabled: true) + return unless Feature.enabled?(:merge_red_head_comments_position_on_demand, @merge_request.target_project, default_enabled: true) + return if @merge_request.has_any_diff_note_positions? + + Discussions::CaptureDiffNotePositionsService.new(@merge_request).execute + end end diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 6c1ffc35276..e65e5531b88 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -35,15 +35,23 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo push_frontend_feature_flag(:mr_commit_neighbor_nav, @project, default_enabled: true) push_frontend_feature_flag(:multiline_comments, @project) push_frontend_feature_flag(:file_identifier_hash) - push_frontend_feature_flag(:batch_suggestions, @project) + push_frontend_feature_flag(:batch_suggestions, @project, default_enabled: true) end before_action do push_frontend_feature_flag(:vue_issuable_sidebar, @project.group) + push_frontend_feature_flag(:junit_pipeline_view, @project.group) end around_action :allow_gitaly_ref_name_caching, only: [:index, :show, :discussions] + feature_category :source_code_management, + unless: -> (action) { action.ends_with?("_reports") } + feature_category :code_testing, + only: [:test_reports, :coverage_reports, :terraform_reports] + feature_category :accessibility_testing, + only: [:accessibility_reports] + def index @merge_requests = @issuables @@ -76,7 +84,9 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo @issuable_sidebar = serializer.represent(@merge_request, serializer: 'sidebar') @current_user_data = UserSerializer.new(project: @project).represent(current_user, {}, MergeRequestUserEntity).to_json @show_whitespace_default = current_user.nil? || current_user.show_whitespace_in_diffs + @file_by_file_default = Feature.enabled?(:view_diffs_file_by_file) && current_user&.view_diffs_file_by_file @coverage_path = coverage_reports_project_merge_request_path(@project, @merge_request, format: :json) if @merge_request.has_coverage_reports? + @endpoint_metadata_url = endpoint_metadata_url(@project, @merge_request) set_pipeline_variables @@ -108,8 +118,8 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo # or from cache if already merged @commits = set_commits_for_rendering( - @merge_request.recent_commits.with_latest_pipeline(@merge_request.source_branch), - commits_count: @merge_request.commits_count + @merge_request.recent_commits.with_latest_pipeline(@merge_request.source_branch).with_markdown_cache, + commits_count: @merge_request.commits_count ) render json: { html: view_to_html_string('projects/merge_requests/_commits') } @@ -178,7 +188,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo end def update - @merge_request = ::MergeRequests::UpdateService.new(project, current_user, merge_request_params).execute(@merge_request) + @merge_request = ::MergeRequests::UpdateService.new(project, current_user, merge_request_update_params).execute(@merge_request) respond_to do |format| format.html do @@ -312,6 +322,10 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo private + def merge_request_update_params + merge_request_params.merge!(params.permit(:merge_request_diff_head_sha)) + end + def head_pipeline strong_memoize(:head_pipeline) do pipeline = @merge_request.head_pipeline @@ -422,6 +436,13 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo def authorize_read_actual_head_pipeline! return render_404 unless can?(current_user, :read_build, merge_request.actual_head_pipeline) end + + def endpoint_metadata_url(project, merge_request) + params = request.query_parameters + params[:view] = cookies[:diff_view] if params[:view].blank? && cookies[:diff_view].present? + + diffs_metadata_project_json_merge_request_path(project, merge_request, 'json', params) + end end Projects::MergeRequestsController.prepend_if_ee('EE::Projects::MergeRequestsController') diff --git a/app/controllers/projects/metrics_dashboard_controller.rb b/app/controllers/projects/metrics_dashboard_controller.rb new file mode 100644 index 00000000000..235ee1dfbf2 --- /dev/null +++ b/app/controllers/projects/metrics_dashboard_controller.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true +module Projects + class MetricsDashboardController < Projects::ApplicationController + # Metrics dashboard code is in the process of being decoupled from environments + # and is getting moved to this controller. Some code may be duplicated from + # app/controllers/projects/environments_controller.rb + # See https://gitlab.com/gitlab-org/gitlab/-/issues/226002 for more details. + + before_action :authorize_metrics_dashboard! + before_action do + push_frontend_feature_flag(:prometheus_computed_alerts) + end + + def show + if environment + render 'projects/environments/metrics' + else + render_404 + end + end + + private + + def environment + @environment ||= + if params[:environment] + project.environments.find(params[:environment]) + else + project.default_environment + end + end + end +end diff --git a/app/controllers/projects/pipelines/application_controller.rb b/app/controllers/projects/pipelines/application_controller.rb new file mode 100644 index 00000000000..92887750813 --- /dev/null +++ b/app/controllers/projects/pipelines/application_controller.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +# Abstract class encapsulating common logic for creating new controllers in a pipeline context + +module Projects + module Pipelines + class ApplicationController < Projects::ApplicationController + include Gitlab::Utils::StrongMemoize + + before_action :pipeline + before_action :authorize_read_pipeline! + + private + + def pipeline + strong_memoize(:pipeline) do + project.all_pipelines.find(params[:pipeline_id]).tap do |pipeline| + render_404 unless can?(current_user, :read_pipeline, pipeline) + end + end + end + end + end +end diff --git a/app/controllers/projects/pipelines/stages_controller.rb b/app/controllers/projects/pipelines/stages_controller.rb new file mode 100644 index 00000000000..ce08b49ce9f --- /dev/null +++ b/app/controllers/projects/pipelines/stages_controller.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module Projects + module Pipelines + class StagesController < Projects::Pipelines::ApplicationController + before_action :authorize_update_pipeline! + + def play_manual + ::Ci::PlayManualStageService + .new(@project, current_user, pipeline: pipeline) + .execute(stage) + + respond_to do |format| + format.json do + render json: StageSerializer + .new(project: @project, current_user: @current_user) + .represent(stage) + end + end + end + + private + + def stage + @pipeline_stage ||= pipeline.find_stage_by_name!(params[:stage_name]) + end + end + end +end diff --git a/app/controllers/projects/pipelines/tests_controller.rb b/app/controllers/projects/pipelines/tests_controller.rb new file mode 100644 index 00000000000..f03274bf32e --- /dev/null +++ b/app/controllers/projects/pipelines/tests_controller.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +module Projects + module Pipelines + class TestsController < Projects::Pipelines::ApplicationController + before_action :validate_feature_flag! + before_action :authorize_read_build! + before_action :builds, only: [:show] + + def summary + respond_to do |format| + format.json do + render json: TestReportSummarySerializer + .new(project: project, current_user: @current_user) + .represent(pipeline.test_report_summary) + end + end + end + + def show + respond_to do |format| + format.json do + render json: TestSuiteSerializer + .new(project: project, current_user: @current_user) + .represent(test_suite, details: true) + end + end + end + + private + + def validate_feature_flag! + render_404 unless Feature.enabled?(:build_report_summary, project) + end + + # rubocop: disable CodeReuse/ActiveRecord + def builds + pipeline.latest_builds.where(id: build_params) + end + + def build_params + return [] unless params[:build_ids] + + params[:build_ids].split(",") + end + + def test_suite + if builds.present? + builds.map do |build| + build.collect_test_reports!(Gitlab::Ci::Reports::TestReports.new) + end.sum + else + render_404 + end + end + # rubocop: enable CodeReuse/ActiveRecord + end + end +end diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index 0b6c0db211e..d8e11ddd423 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -2,6 +2,7 @@ class Projects::PipelinesController < Projects::ApplicationController include ::Gitlab::Utils::StrongMemoize + include Analytics::UniqueVisitsHelper before_action :whitelist_query_limiting, only: [:create, :retry] before_action :pipeline, except: [:index, :new, :create, :charts] @@ -12,14 +13,20 @@ class Projects::PipelinesController < Projects::ApplicationController before_action :authorize_update_pipeline!, only: [:retry, :cancel] before_action do push_frontend_feature_flag(:junit_pipeline_view, project) + push_frontend_feature_flag(:build_report_summary, project) push_frontend_feature_flag(:filter_pipelines_search, project, default_enabled: true) - push_frontend_feature_flag(:dag_pipeline_tab, project, default_enabled: false) + push_frontend_feature_flag(:dag_pipeline_tab, project, default_enabled: true) push_frontend_feature_flag(:pipelines_security_report_summary, project) end before_action :ensure_pipeline, only: [:show] + # 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? } + around_action :allow_gitaly_ref_name_caching, only: [:index, :show] + track_unique_visits :charts, target_id: 'p_analytics_pipelines' + wrap_parameters Ci::Pipeline POLLING_INTERVAL = 10_000 @@ -31,9 +38,6 @@ class Projects::PipelinesController < Projects::ApplicationController .page(params[:page]) .per(30) - @running_count = limited_pipelines_count(project, 'running') - @pending_count = limited_pipelines_count(project, 'pending') - @finished_count = limited_pipelines_count(project, 'finished') @pipelines_count = limited_pipelines_count(project) respond_to do |format| @@ -44,10 +48,7 @@ class Projects::PipelinesController < Projects::ApplicationController render json: { pipelines: serialize_pipelines, count: { - all: @pipelines_count, - running: @running_count, - pending: @pending_count, - finished: @finished_count + all: @pipelines_count } } end @@ -186,7 +187,7 @@ class Projects::PipelinesController < Projects::ApplicationController format.json do render json: TestReportSerializer .new(current_user: @current_user) - .represent(pipeline_test_report, project: project) + .represent(pipeline_test_report, project: project, details: true) end end end @@ -226,6 +227,12 @@ class Projects::PipelinesController < Projects::ApplicationController render_404 unless pipeline end + def redirect_for_legacy_scope_filter + return unless %w[running pending].include?(params[:scope]) + + redirect_to url_for(safe_params.except(:scope).merge(status: safe_params[:scope])), status: :moved_permanently + end + # rubocop: disable CodeReuse/ActiveRecord def pipeline @pipeline ||= if params[:id].blank? && params[:latest] diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb index a2581e72257..db770d3e438 100644 --- a/app/controllers/projects/refs_controller.rb +++ b/app/controllers/projects/refs_controller.rb @@ -11,10 +11,6 @@ class Projects::RefsController < Projects::ApplicationController before_action :assign_ref_vars before_action :authorize_download_code! - before_action only: [:logs_tree] do - push_frontend_feature_flag(:vue_file_list_lfs_badge, default_enabled: true) - end - def switch respond_to do |format| format.html do @@ -57,22 +53,11 @@ class Projects::RefsController < Projects::ApplicationController render json: logs end - - # Deprecated due to https://gitlab.com/gitlab-org/gitlab/-/issues/36863 - # Will be removed soon https://gitlab.com/gitlab-org/gitlab/-/merge_requests/29895 - format.js do - @logs, _ = tree_summary.summarize - @more_log_url = more_url(tree_summary.next_offset) if tree_summary.more? - end end end private - def more_url(offset) - logs_file_project_ref_path(@project, @ref, @path, offset: offset) - end - def validate_ref_id return not_found! if params[:id].present? && params[:id] !~ Gitlab::PathRegex.git_reference_regex end diff --git a/app/controllers/projects/releases_controller.rb b/app/controllers/projects/releases_controller.rb index d3285b64dab..d58755c2655 100644 --- a/app/controllers/projects/releases_controller.rb +++ b/app/controllers/projects/releases_controller.rb @@ -13,6 +13,7 @@ class Projects::ReleasesController < Projects::ApplicationController push_frontend_feature_flag(:release_asset_link_type, project, default_enabled: true) end before_action :authorize_update_release!, only: %i[edit update] + before_action :authorize_create_release!, only: :new def index respond_to do |format| @@ -25,11 +26,11 @@ class Projects::ReleasesController < Projects::ApplicationController def show return render_404 unless Feature.enabled?(:release_show_page, project, default_enabled: true) + end - respond_to do |format| - format.html do - render :show - end + def new + unless Feature.enabled?(:new_release_page, project) + redirect_to(new_project_tag_path(@project)) end end @@ -37,22 +38,12 @@ class Projects::ReleasesController < Projects::ApplicationController redirect_to link.url end - protected + private def releases ReleasesFinder.new(@project, current_user).execute end - def edit - respond_to do |format| - format.html do - render :edit - end - end - end - - private - def authorize_update_release! access_denied! unless can?(current_user, :update_release, release) end diff --git a/app/controllers/projects/service_desk_controller.rb b/app/controllers/projects/service_desk_controller.rb new file mode 100644 index 00000000000..bcd190bbc2c --- /dev/null +++ b/app/controllers/projects/service_desk_controller.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +class Projects::ServiceDeskController < Projects::ApplicationController + before_action :authorize_admin_project! + + def show + json_response + end + + def update + Projects::UpdateService.new(project, current_user, { service_desk_enabled: params[:service_desk_enabled] }).execute + + result = ServiceDeskSettings::UpdateService.new(project, current_user, setting_params).execute + + if result[:status] == :success + json_response + else + render json: { message: result[:message] }, status: :unprocessable_entity + end + end + + private + + def setting_params + params.permit(:issue_template_key, :outgoing_name, :project_key) + end + + def json_response + respond_to do |format| + service_desk_settings = project.service_desk_setting + + service_desk_attributes = + { + service_desk_address: project.service_desk_address, + service_desk_enabled: project.service_desk_enabled, + issue_template_key: service_desk_settings&.issue_template_key, + template_file_missing: service_desk_settings&.issue_template_missing?, + outgoing_name: service_desk_settings&.outgoing_name, + project_key: service_desk_settings&.project_key + } + + format.json { render json: service_desk_attributes } + end + end +end diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb index 710ad546e64..6b7e253595c 100644 --- a/app/controllers/projects/services_controller.rb +++ b/app/controllers/projects/services_controller.rb @@ -12,7 +12,8 @@ class Projects::ServicesController < Projects::ApplicationController before_action :set_deprecation_notice_for_prometheus_service, only: [:edit, :update] before_action :redirect_deprecated_prometheus_service, only: [:update] before_action only: :edit do - push_frontend_feature_flag(:integration_form_refactor) + push_frontend_feature_flag(:integration_form_refactor, default_enabled: true) + push_frontend_feature_flag(:jira_issues_integration, @project, { default_enabled: true }) end respond_to :html @@ -20,17 +21,19 @@ class Projects::ServicesController < Projects::ApplicationController layout "project_settings" def edit + @admin_integration = Service.instance_for(service.type) end def update @service.attributes = service_params[:service] + @service.inherit_from_id = nil if service_params[:service][:inherit_from_id].blank? saved = @service.save(context: :manual_change) respond_to do |format| format.html do if saved - target_url = safe_redirect_path(params[:redirect_to]).presence || project_settings_integrations_path(@project) + target_url = safe_redirect_path(params[:redirect_to]).presence || edit_project_service_path(@project, @service) redirect_to target_url, notice: success_message else render 'edit' @@ -60,7 +63,7 @@ class Projects::ServicesController < Projects::ApplicationController return { error: true, message: _('Validations failed.'), service_response: @service.errors.full_messages.join(','), test_failed: false } end - result = Integrations::Test::ProjectService.new(@service, current_user, params[:event]).execute + result = ::Integrations::Test::ProjectService.new(@service, current_user, params[:event]).execute unless result[:success] return { error: true, message: _('Test failed.'), service_response: result[:message].to_s, test_failed: true } diff --git a/app/controllers/projects/settings/operations_controller.rb b/app/controllers/projects/settings/operations_controller.rb index c2292511e0f..d7a6f1b0139 100644 --- a/app/controllers/projects/settings/operations_controller.rb +++ b/app/controllers/projects/settings/operations_controller.rb @@ -6,13 +6,13 @@ module Projects before_action :authorize_admin_operations! before_action :authorize_read_prometheus_alerts!, only: [:reset_alerting_token] - respond_to :json, only: [:reset_alerting_token] + before_action do + push_frontend_feature_flag(:pagerduty_webhook, project) + end - helper_method :error_tracking_setting + respond_to :json, only: [:reset_alerting_token, :reset_pagerduty_token] - def show - render locals: { prometheus_service: prometheus_service } - end + helper_method :error_tracking_setting def update result = ::Projects::Operations::UpdateService.new(project, current_user, update_params).execute @@ -42,14 +42,29 @@ module Projects end end + def reset_pagerduty_token + result = ::Projects::Operations::UpdateService + .new(project, current_user, pagerduty_token_params) + .execute + + if result[:status] == :success + pagerduty_token = project.incident_management_setting&.pagerduty_token + webhook_url = project_incidents_pagerduty_url(project, token: pagerduty_token) + + render json: { pagerduty_webhook_url: webhook_url, pagerduty_token: pagerduty_token } + else + render json: {}, status: :unprocessable_entity + end + end + private def alerting_params { alerting_setting_attributes: { regenerate_token: true } } end - def prometheus_service - project.find_or_initialize_service(::PrometheusService.to_param) + def pagerduty_token_params + { incident_management_setting_attributes: { regenerate_token: true } } end def render_update_response(result) diff --git a/app/controllers/projects/snippets/blobs_controller.rb b/app/controllers/projects/snippets/blobs_controller.rb new file mode 100644 index 00000000000..148fc7c96f8 --- /dev/null +++ b/app/controllers/projects/snippets/blobs_controller.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class Projects::Snippets::BlobsController < Projects::Snippets::ApplicationController + include Snippets::BlobsActions +end diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb index 5ee6abef804..49840e847f2 100644 --- a/app/controllers/projects/snippets_controller.rb +++ b/app/controllers/projects/snippets_controller.rb @@ -15,11 +15,11 @@ class Projects::SnippetsController < Projects::Snippets::ApplicationController before_action :authorize_admin_snippet!, only: [:destroy] def index - @snippet_counts = Snippets::CountService + @snippet_counts = ::Snippets::CountService .new(current_user, project: @project) .execute - @snippets = SnippetsFinder.new(current_user, project: @project, scope: params[:scope]) + @snippets = SnippetsFinder.new(current_user, project: @project, scope: params[:scope], sort: sort_param) .execute .page(params[:page]) .inc_author @@ -35,7 +35,7 @@ class Projects::SnippetsController < Projects::Snippets::ApplicationController def create create_params = snippet_params.merge(spammable_params) - service_response = Snippets::CreateService.new(project, current_user, create_params).execute + service_response = ::Snippets::CreateService.new(project, current_user, create_params).execute @snippet = service_response.payload[:snippet] handle_repository_error(:new) diff --git a/app/controllers/projects/stages_controller.rb b/app/controllers/projects/stages_controller.rb deleted file mode 100644 index c8db5b1277f..00000000000 --- a/app/controllers/projects/stages_controller.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -class Projects::StagesController < Projects::PipelinesController - before_action :authorize_update_pipeline! - - def play_manual - ::Ci::PlayManualStageService - .new(@project, current_user, pipeline: pipeline) - .execute(stage) - - respond_to do |format| - format.json do - render json: StageSerializer - .new(project: @project, current_user: @current_user) - .represent(stage) - end - end - end - - private - - def stage - @pipeline_stage ||= pipeline.find_stage_by_name!(params[:stage_name]) - end -end diff --git a/app/controllers/projects/static_site_editor_controller.rb b/app/controllers/projects/static_site_editor_controller.rb index 74f28c3da67..9ec50ff8196 100644 --- a/app/controllers/projects/static_site_editor_controller.rb +++ b/app/controllers/projects/static_site_editor_controller.rb @@ -9,6 +9,9 @@ class Projects::StaticSiteEditorController < Projects::ApplicationController prepend_before_action :authenticate_user!, only: [:show] before_action :assign_ref_and_path, only: [:show] before_action :authorize_edit_tree!, only: [:show] + before_action do + push_frontend_feature_flag(:sse_image_uploads) + end def show @config = Gitlab::StaticSiteEditor::Config.new(@repository, @ref, @path, params[:return_url]) diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb index 9cb345724cc..638e1a05c18 100644 --- a/app/controllers/projects/tree_controller.rb +++ b/app/controllers/projects/tree_controller.rb @@ -15,26 +15,14 @@ class Projects::TreeController < Projects::ApplicationController before_action :authorize_download_code! before_action :authorize_edit_tree!, only: [:create_dir] - before_action only: [:show] do - push_frontend_feature_flag(:vue_file_list_lfs_badge, default_enabled: true) - end - def show - return render_404 unless @repository.commit(@ref) + return render_404 unless @commit if tree.entries.empty? if @repository.blob_at(@commit.id, @path) - return redirect_to project_blob_path(@project, File.join(@ref, @path)) + redirect_to project_blob_path(@project, File.join(@ref, @path)) elsif @path.present? - return redirect_to_tree_root_for_missing_path(@project, @ref, @path) - end - end - - respond_to do |format| - format.html do - lfs_blob_ids if Feature.disabled?(:vue_file_list, @project, default_enabled: true) - - @last_commit = @repository.last_commit_for_path(@commit.id, @tree.path) || @commit + redirect_to_tree_root_for_missing_path(@project, @ref, @path) end end end diff --git a/app/controllers/projects/variables_controller.rb b/app/controllers/projects/variables_controller.rb index 1dffc57fcf0..2cc030d18fc 100644 --- a/app/controllers/projects/variables_controller.rb +++ b/app/controllers/projects/variables_controller.rb @@ -6,7 +6,7 @@ class Projects::VariablesController < Projects::ApplicationController def show respond_to do |format| format.json do - render status: :ok, json: { variables: VariableSerializer.new.represent(@project.variables) } + render status: :ok, json: { variables: ::Ci::VariableSerializer.new.represent(@project.variables) } end end end @@ -26,7 +26,7 @@ class Projects::VariablesController < Projects::ApplicationController private def render_variables - render status: :ok, json: { variables: VariableSerializer.new.represent(@project.variables) } + render status: :ok, json: { variables: ::Ci::VariableSerializer.new.represent(@project.variables) } end def render_error diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb index 85e643aa212..d0aa733cadb 100644 --- a/app/controllers/projects/wikis_controller.rb +++ b/app/controllers/projects/wikis_controller.rb @@ -2,7 +2,6 @@ class Projects::WikisController < Projects::ApplicationController include WikiActions - include PreviewMarkdown alias_method :container, :project diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index f0ddd62e996..a5666cb70ac 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -8,6 +8,7 @@ class ProjectsController < Projects::ApplicationController include SendFileUpload include RecordUserLastActivity include ImportUrlParams + include FiltersEvents prepend_before_action(only: [:show]) { authenticate_sessionless_user!(:rss) } @@ -21,7 +22,6 @@ class ProjectsController < Projects::ApplicationController before_action :assign_ref_vars, if: -> { action_name == 'show' && repo_exists? } before_action :tree, if: -> { action_name == 'show' && repo_exists? && project_view_files? } - before_action :lfs_blob_ids, if: :show_blob_ids?, only: :show before_action :project_export_enabled, only: [:export, :download_export, :remove_export, :generate_new_export] before_action :present_project, only: [:edit] before_action :authorize_download_code!, only: [:refs] @@ -38,6 +38,7 @@ class ProjectsController < Projects::ApplicationController before_action only: [:new, :create] do frontend_experimentation_tracking_data(:new_create_project_ui, 'click_tab') push_frontend_feature_flag(:new_create_project_ui) if experiment_enabled?(:new_create_project_ui) + push_frontend_feature_flag(:service_desk_custom_address, @project) end layout :determine_layout @@ -301,10 +302,6 @@ class ProjectsController < Projects::ApplicationController private - def show_blob_ids? - repo_exists? && project_view_files? && Feature.disabled?(:vue_file_list, @project, default_enabled: true) - end - # Render project landing depending of which features are available # So if page is not available in the list it renders the next page # @@ -395,6 +392,7 @@ class ProjectsController < Projects::ApplicationController :initialize_with_readme, :autoclose_referenced_issues, :suggestion_commit_message, + :service_desk_enabled, project_feature_attributes: %i[ builds_access_level @@ -409,6 +407,7 @@ class ProjectsController < Projects::ApplicationController ], project_setting_attributes: %i[ show_default_award_emojis + squash_option ] ] end diff --git a/app/controllers/registrations/experience_levels_controller.rb b/app/controllers/registrations/experience_levels_controller.rb index 515d6b3f9aa..97239b1bbac 100644 --- a/app/controllers/registrations/experience_levels_controller.rb +++ b/app/controllers/registrations/experience_levels_controller.rb @@ -33,12 +33,13 @@ module Registrations def hide_advanced_issues return unless current_user.user_preference.novice? + return unless learn_gitlab.available? - settings = cookies[:onboarding_issues_settings] - return unless settings + Boards::UpdateService.new(learn_gitlab.project, current_user, label_ids: [learn_gitlab.label.id]).execute(learn_gitlab.board) + end - modified_settings = Gitlab::Json.parse(settings).merge(hideAdvanced: true) - cookies[:onboarding_issues_settings] = modified_settings.to_json + def learn_gitlab + @learn_gitlab ||= LearnGitlab.new(current_user) end end end diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 6ab2924a8b5..b1c1fe3ba74 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -64,8 +64,8 @@ class RegistrationsController < Devise::RegistrationsController if result[:status] == :success track_experiment_event(:signup_flow, 'end') # We want this event to be tracked when the user is _in_ the experimental group - track_experiment_event(:onboarding_issues, 'signed_up') if ::Gitlab.com? && !helpers.in_subscription_flow? && !helpers.in_invitation_flow? - return redirect_to new_users_sign_up_group_path if experiment_enabled?(:onboarding_issues) && !helpers.in_subscription_flow? && !helpers.in_invitation_flow? + track_experiment_event(:onboarding_issues, 'signed_up') if ::Gitlab.com? && show_onboarding_issues_experiment? + return redirect_to new_users_sign_up_group_path if experiment_enabled?(:onboarding_issues) && show_onboarding_issues_experiment? set_flash_message! :notice, :signed_up redirect_to path_for_signed_in_user(current_user) @@ -210,6 +210,10 @@ class RegistrationsController < Devise::RegistrationsController 'devise' end end + + def show_onboarding_issues_experiment? + !helpers.in_subscription_flow? && !helpers.in_invitation_flow? && !helpers.in_oauth_flow? + end end RegistrationsController.prepend_if_ee('EE::RegistrationsController') diff --git a/app/controllers/root_controller.rb b/app/controllers/root_controller.rb index 24452f9a188..14469877e14 100644 --- a/app/controllers/root_controller.rb +++ b/app/controllers/root_controller.rb @@ -13,10 +13,15 @@ class RootController < Dashboard::ProjectsController before_action :redirect_unlogged_user, if: -> { current_user.nil? } before_action :redirect_logged_user, if: -> { current_user.present? } + # We only need to load the projects when the user is logged in but did not + # configure a dashboard. In which case we render projects. We can do that straight + # from the #index action. + skip_before_action :projects def index # n+1: https://gitlab.com/gitlab-org/gitlab-foss/issues/40260 Gitlab::GitalyClient.allow_n_plus_1_calls do + projects super end end diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 217f08dd648..ff6d9350a5c 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -51,6 +51,21 @@ class SearchController < ApplicationController render json: { count: count } end + # rubocop: disable CodeReuse/ActiveRecord + def autocomplete + term = params[:term] + + if params[:project_id].present? + @project = Project.find_by(id: params[:project_id]) + @project = nil unless can?(current_user, :read_project, @project) + end + + @ref = params[:project_ref] if params[:project_ref].present? + + render json: search_autocomplete_opts(term).to_json + end + # rubocop: enable CodeReuse/ActiveRecord + private def preload_method diff --git a/app/controllers/snippets/blobs_controller.rb b/app/controllers/snippets/blobs_controller.rb new file mode 100644 index 00000000000..d7c4bbcf8f2 --- /dev/null +++ b/app/controllers/snippets/blobs_controller.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class Snippets::BlobsController < Snippets::ApplicationController + include Snippets::BlobsActions + + skip_before_action :authenticate_user!, only: [:raw] +end diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb index 87d87390e57..e68b821459d 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -21,7 +21,7 @@ class SnippetsController < Snippets::ApplicationController if params[:username].present? @user = UserFinder.new(params[:username]).find_by_username! - @snippets = SnippetsFinder.new(current_user, author: @user, scope: params[:scope]) + @snippets = SnippetsFinder.new(current_user, author: @user, scope: params[:scope], sort: sort_param) .execute .page(params[:page]) .inc_author diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 5ee97885071..95ea31fa977 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -3,6 +3,7 @@ class UsersController < ApplicationController include RoutableActions include RendersMemberAccess + include RendersProjectsList include ControllerWithCrossProjectAccessCheck include Gitlab::NoteableMetadata @@ -36,6 +37,12 @@ class UsersController < ApplicationController end end + # Get all keys of a user(params[:username]) in a text format + # Helpful for sysadmins to put in respective servers + def ssh_keys + render plain: user.all_ssh_keys.join("\n") + end + def activity respond_to do |format| format.html { render 'show' } |