summaryrefslogtreecommitdiff
path: root/app/controllers/concerns
diff options
context:
space:
mode:
Diffstat (limited to 'app/controllers/concerns')
-rw-r--r--app/controllers/concerns/controller_with_feature_category.rb45
-rw-r--r--app/controllers/concerns/controller_with_feature_category/config.rb38
-rw-r--r--app/controllers/concerns/filters_events.rb14
-rw-r--r--app/controllers/concerns/integrations_actions.rb8
-rw-r--r--app/controllers/concerns/issuable_actions.rb12
-rw-r--r--app/controllers/concerns/issuable_collections.rb61
-rw-r--r--app/controllers/concerns/known_sign_in.rb19
-rw-r--r--app/controllers/concerns/membership_actions.rb5
-rw-r--r--app/controllers/concerns/metrics/dashboard/prometheus_api_proxy.rb53
-rw-r--r--app/controllers/concerns/metrics_dashboard.rb14
-rw-r--r--app/controllers/concerns/notes_actions.rb84
-rw-r--r--app/controllers/concerns/renders_member_access.rb6
-rw-r--r--app/controllers/concerns/renders_projects_list.rb13
-rw-r--r--app/controllers/concerns/service_params.rb4
-rw-r--r--app/controllers/concerns/snippets/blobs_actions.rb53
-rw-r--r--app/controllers/concerns/snippets/send_blob.rb22
-rw-r--r--app/controllers/concerns/snippets_actions.rb19
-rw-r--r--app/controllers/concerns/snippets_sort.rb9
-rw-r--r--app/controllers/concerns/wiki_actions.rb49
19 files changed, 446 insertions, 82 deletions
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