diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-06-18 11:18:50 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-06-18 11:18:50 +0000 |
commit | 8c7f4e9d5f36cff46365a7f8c4b9c21578c1e781 (patch) | |
tree | a77e7fe7a93de11213032ed4ab1f33a3db51b738 /app/controllers | |
parent | 00b35af3db1abfe813a778f643dad221aad51fca (diff) | |
download | gitlab-ce-8c7f4e9d5f36cff46365a7f8c4b9c21578c1e781.tar.gz |
Add latest changes from gitlab-org/gitlab@13-1-stable-ee
Diffstat (limited to 'app/controllers')
72 files changed, 1315 insertions, 656 deletions
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 709834a2bec..94c82c25357 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -12,6 +12,10 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController before_action :whitelist_query_limiting, only: [:usage_data] + before_action only: [:ci_cd] do + push_frontend_feature_flag(:ci_instance_variables_ui, default_enabled: true) + end + VALID_SETTING_PANELS = %w(general integrations repository ci_cd reporting metrics_and_profiling network preferences).freeze @@ -216,6 +220,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController [ *::ApplicationSettingsHelper.visible_attributes, *::ApplicationSettingsHelper.external_authorization_service_attributes, + *ApplicationSetting.repository_storages_weighted_attributes, :lets_encrypt_notification_email, :lets_encrypt_terms_of_service_accepted, :domain_blacklist_file, diff --git a/app/controllers/admin/runners_controller.rb b/app/controllers/admin/runners_controller.rb index 4639d8adfe0..2449fa3128c 100644 --- a/app/controllers/admin/runners_controller.rb +++ b/app/controllers/admin/runners_controller.rb @@ -4,7 +4,7 @@ class Admin::RunnersController < Admin::ApplicationController before_action :runner, except: [:index, :tag_list] def index - finder = Admin::RunnersFinder.new(params: params) + finder = Ci::RunnersFinder.new(current_user: current_user, params: params) @runners = finder.execute @active_runners_count = Ci::Runner.online.count @sort = finder.sort_key diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index ee42baa8326..fc0acd8f99a 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -241,7 +241,8 @@ class Admin::UsersController < Admin::ApplicationController :theme_id, :twitter, :username, - :website_url + :website_url, + :note ] end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 54e3275662b..79a164a5574 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -12,6 +12,7 @@ class ApplicationController < ActionController::Base include WorkhorseHelper include EnforcesTwoFactorAuthentication include WithPerformanceBar + include Gitlab::SearchContext::ControllerConcern include SessionlessAuthentication include SessionsHelper include ConfirmEmailWarning diff --git a/app/controllers/clusters/base_controller.rb b/app/controllers/clusters/base_controller.rb index 8c13cc67be2..6b83400971d 100644 --- a/app/controllers/clusters/base_controller.rb +++ b/app/controllers/clusters/base_controller.rb @@ -7,7 +7,7 @@ class Clusters::BaseController < ApplicationController before_action :authorize_read_cluster! before_action do - push_frontend_feature_flag(:managed_apps_local_tiller) + push_frontend_feature_flag(:managed_apps_local_tiller, clusterable) end helper_method :clusterable diff --git a/app/controllers/clusters/clusters_controller.rb b/app/controllers/clusters/clusters_controller.rb index aa39d430b24..46dec5f3287 100644 --- a/app/controllers/clusters/clusters_controller.rb +++ b/app/controllers/clusters/clusters_controller.rb @@ -23,6 +23,7 @@ class Clusters::ClustersController < Clusters::BaseController respond_to do |format| format.html format.json do + Gitlab::PollingInterval.set_header(response, interval: STATUS_POLLING_INTERVAL) serializer = ClusterSerializer.new(current_user: current_user) render json: { diff --git a/app/controllers/concerns/enforces_two_factor_authentication.rb b/app/controllers/concerns/enforces_two_factor_authentication.rb index d486d734db8..6c443611a60 100644 --- a/app/controllers/concerns/enforces_two_factor_authentication.rb +++ b/app/controllers/concerns/enforces_two_factor_authentication.rb @@ -23,8 +23,7 @@ module EnforcesTwoFactorAuthentication def two_factor_authentication_required? Gitlab::CurrentSettings.require_two_factor_authentication? || - current_user.try(:require_two_factor_authentication_from_group?) || - current_user.try(:ultraauth_user?) + current_user.try(:require_two_factor_authentication_from_group?) end def current_user_requires_two_factor? diff --git a/app/controllers/concerns/find_snippet.rb b/app/controllers/concerns/find_snippet.rb new file mode 100644 index 00000000000..d51f1a1b3ad --- /dev/null +++ b/app/controllers/concerns/find_snippet.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module FindSnippet + extend ActiveSupport::Concern + include Gitlab::Utils::StrongMemoize + + private + + # rubocop:disable CodeReuse/ActiveRecord + def snippet + strong_memoize(:snippet) do + snippet_klass.inc_relations_for_view.find_by(id: snippet_id) + end + end + # rubocop:enable CodeReuse/ActiveRecord + + def snippet_klass + raise NotImplementedError + end + + def snippet_id + params[:id] + end +end diff --git a/app/controllers/concerns/integrations_actions.rb b/app/controllers/concerns/integrations_actions.rb index ff283f9bb62..cc9db7936e8 100644 --- a/app/controllers/concerns/integrations_actions.rb +++ b/app/controllers/concerns/integrations_actions.rb @@ -16,10 +16,12 @@ module IntegrationsActions def update saved = integration.update(service_params[:service]) + overwrite = Gitlab::Utils.to_boolean(params[:overwrite]) respond_to do |format| format.html do if saved + PropagateIntegrationWorker.perform_async(integration.id, overwrite) redirect_to scoped_edit_integration_path(integration), notice: success_message else render 'shared/integrations/edit' @@ -34,6 +36,10 @@ module IntegrationsActions end end + def custom_integration_projects + Project.with_custom_integration_compared_to(integration).page(params[:page]).per(20) + end + def test render json: {}, status: :ok end diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb index 0b1b3f2bcba..98fa8202e25 100644 --- a/app/controllers/concerns/issuable_actions.rb +++ b/app/controllers/concerns/issuable_actions.rb @@ -16,19 +16,6 @@ module IssuableActions end end - def permitted_keys - [ - :issuable_ids, - :assignee_id, - :milestone_id, - :state_event, - :subscription_event, - label_ids: [], - add_label_ids: [], - remove_label_ids: [] - ] - end - def show respond_to do |format| format.html do @@ -221,10 +208,20 @@ module IssuableActions end def bulk_update_params - permitted_keys_array = permitted_keys.dup - permitted_keys_array << { assignee_ids: [] } + params.require(:update).permit(bulk_update_permitted_keys) + end - params.require(:update).permit(permitted_keys_array) + def bulk_update_permitted_keys + [ + :issuable_ids, + :assignee_id, + :milestone_id, + :state_event, + :subscription_event, + assignee_ids: [], + add_label_ids: [], + remove_label_ids: [] + ] end def resource_name diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb index 5aa00af8910..9ef067e8797 100644 --- a/app/controllers/concerns/issuable_collections.rb +++ b/app/controllers/concerns/issuable_collections.rb @@ -5,7 +5,6 @@ module IssuableCollections include PaginatedCollection include SortingHelper include SortingPreference - include Gitlab::IssuableMetadata include Gitlab::Utils::StrongMemoize included do @@ -44,7 +43,7 @@ module IssuableCollections def set_pagination @issuables = @issuables.page(params[:page]) @issuables = per_page_for_relative_position if params[:sort] == 'relative_position' - @issuable_meta_data = issuable_meta_data(@issuables, collection_type, current_user) + @issuable_meta_data = Gitlab::IssuableMetadata.new(current_user, @issuables).data @total_pages = issuable_page_count(@issuables) end # rubocop:enable Gitlab/ModuleWithInstanceVariables diff --git a/app/controllers/concerns/issuable_collections_action.rb b/app/controllers/concerns/issuable_collections_action.rb index 78b3c6771b3..e3ac117660b 100644 --- a/app/controllers/concerns/issuable_collections_action.rb +++ b/app/controllers/concerns/issuable_collections_action.rb @@ -11,7 +11,7 @@ module IssuableCollectionsAction .non_archived .page(params[:page]) - @issuable_meta_data = issuable_meta_data(@issues, collection_type, current_user) + @issuable_meta_data = Gitlab::IssuableMetadata.new(current_user, @issues).data respond_to do |format| format.html @@ -22,7 +22,7 @@ module IssuableCollectionsAction def merge_requests @merge_requests = issuables_collection.page(params[:page]) - @issuable_meta_data = issuable_meta_data(@merge_requests, collection_type, current_user) + @issuable_meta_data = Gitlab::IssuableMetadata.new(current_user, @merge_requests).data end # rubocop:enable Gitlab/ModuleWithInstanceVariables diff --git a/app/controllers/concerns/known_sign_in.rb b/app/controllers/concerns/known_sign_in.rb index 97883d8d08c..c0b9605de58 100644 --- a/app/controllers/concerns/known_sign_in.rb +++ b/app/controllers/concerns/known_sign_in.rb @@ -26,6 +26,6 @@ module KnownSignIn end def notify_user - current_user.notification_service.unknown_sign_in(current_user, request.remote_ip) + current_user.notification_service.unknown_sign_in(current_user, request.remote_ip, current_user.current_sign_in_at) end end diff --git a/app/controllers/concerns/milestone_actions.rb b/app/controllers/concerns/milestone_actions.rb index dbc575a1487..29138e7b014 100644 --- a/app/controllers/concerns/milestone_actions.rb +++ b/app/controllers/concerns/milestone_actions.rb @@ -51,13 +51,7 @@ module MilestoneActions } end - # rubocop:disable Gitlab/ModuleWithInstanceVariables def milestone_redirect_path - if @milestone.global_milestone? - url_for(action: :show, title: @milestone.title) - else - url_for(action: :show) - end + url_for(action: :show) end - # rubocop:enable Gitlab/ModuleWithInstanceVariables end diff --git a/app/controllers/concerns/notes_actions.rb b/app/controllers/concerns/notes_actions.rb index d4b0d3b2674..d3dfb1813e4 100644 --- a/app/controllers/concerns/notes_actions.rb +++ b/app/controllers/concerns/notes_actions.rb @@ -13,9 +13,7 @@ module NotesActions end def index - current_fetched_at = Time.current.to_i - - notes_json = { notes: [], last_fetched_at: current_fetched_at } + notes_json = { notes: [], last_fetched_at: Time.current.to_i } notes = notes_finder .execute @@ -24,7 +22,7 @@ module NotesActions if notes_filter != UserPreference::NOTES_FILTERS[:only_comments] notes = ResourceEvents::MergeIntoNotesService - .new(noteable, current_user, last_fetched_at: current_fetched_at) + .new(noteable, current_user, last_fetched_at: last_fetched_at) .execute(notes) end diff --git a/app/controllers/concerns/preview_markdown.rb b/app/controllers/concerns/preview_markdown.rb index ba15d611c0d..2916762e31f 100644 --- a/app/controllers/concerns/preview_markdown.rb +++ b/app/controllers/concerns/preview_markdown.rb @@ -32,7 +32,7 @@ module PreviewMarkdown def markdown_context_params case controller_name - when 'wikis' then { pipeline: :wiki, project_wiki: @project_wiki, page_slug: params[:id] } + when 'wikis' then { pipeline: :wiki, wiki: wiki, page_slug: params[:id] } when 'snippets' then { skip_project_check: true } when 'groups' then { group: group } when 'projects' then projects_filter_params diff --git a/app/controllers/concerns/service_params.rb b/app/controllers/concerns/service_params.rb index e2c83f9a069..e78fa8f8250 100644 --- a/app/controllers/concerns/service_params.rb +++ b/app/controllers/concerns/service_params.rb @@ -6,6 +6,7 @@ module ServiceParams ALLOWED_PARAMS_CE = [ :active, :add_pusher, + :alert_events, :api_key, :api_url, :api_version, @@ -28,6 +29,8 @@ module ServiceParams :drone_url, :enable_ssl_verification, :external_wiki_url, + :google_iap_service_account_json, + :google_iap_audience_client_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 diff --git a/app/controllers/concerns/snippet_authorizations.rb b/app/controllers/concerns/snippet_authorizations.rb new file mode 100644 index 00000000000..9bbb0cc6faa --- /dev/null +++ b/app/controllers/concerns/snippet_authorizations.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module SnippetAuthorizations + extend ActiveSupport::Concern + + private + + def authorize_read_snippet! + return render_404 unless can?(current_user, :read_snippet, snippet) + end + + def authorize_update_snippet! + return render_404 unless can?(current_user, :update_snippet, snippet) + end + + def authorize_admin_snippet! + return render_404 unless can?(current_user, :admin_snippet, snippet) + end + + def authorize_create_snippet! + return render_404 unless can?(current_user, :create_snippet) + end +end diff --git a/app/controllers/concerns/snippets_actions.rb b/app/controllers/concerns/snippets_actions.rb index e78723bdda2..51fc12398d9 100644 --- a/app/controllers/concerns/snippets_actions.rb +++ b/app/controllers/concerns/snippets_actions.rb @@ -3,9 +3,18 @@ module SnippetsActions extend ActiveSupport::Concern include SendsBlob + include RendersNotes + include RendersBlob + include PaginatedCollection + include Gitlab::NoteableMetadata included do + skip_before_action :verify_authenticity_token, + if: -> { action_name == 'show' && js_request? } + before_action :redirect_if_binary, only: [:edit, :update] + + respond_to :html end def edit @@ -43,6 +52,58 @@ module SnippetsActions request.format.js? end + # rubocop:disable Gitlab/ModuleWithInstanceVariables + def show + conditionally_expand_blob(blob) + + respond_to do |format| + format.html do + @note = Note.new(noteable: @snippet, project: @snippet.project) + @noteable = @snippet + + @discussions = @snippet.discussions + @notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes), @noteable) + render 'show' + end + + format.json do + render_blob_json(blob) + end + + format.js do + if @snippet.embeddable? + render 'shared/snippets/show' + else + head :not_found + end + end + end + end + + def update + update_params = snippet_params.merge(spammable_params) + + service_response = Snippets::UpdateService.new(@snippet.project, current_user, update_params).execute(@snippet) + @snippet = service_response.payload[:snippet] + + handle_repository_error(:edit) + end + + def destroy + service_response = Snippets::DestroyService.new(current_user, @snippet).execute + + if service_response.success? + redirect_to gitlab_dashboard_snippets_path(@snippet), status: :found + elsif service_response.http_status == 403 + access_denied! + else + redirect_to gitlab_snippet_path(@snippet), + status: :found, + alert: service_response.message + end + end + # rubocop:enable Gitlab/ModuleWithInstanceVariables + private def content_disposition diff --git a/app/controllers/concerns/wiki_actions.rb b/app/controllers/concerns/wiki_actions.rb new file mode 100644 index 00000000000..b4b4fd84c37 --- /dev/null +++ b/app/controllers/concerns/wiki_actions.rb @@ -0,0 +1,232 @@ +# frozen_string_literal: true + +module WikiActions + include SendsBlob + include Gitlab::Utils::StrongMemoize + extend ActiveSupport::Concern + + included do + before_action :authorize_read_wiki! + before_action :authorize_create_wiki!, only: [:edit, :create] + before_action :authorize_admin_wiki!, only: :destroy + + before_action :wiki + before_action :page, only: [:show, :edit, :update, :history, :destroy] + before_action :load_sidebar, except: [:pages] + + 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) + end + end + + def new + redirect_to wiki_page_path(wiki, SecureRandom.uuid, random_title: true) + end + + # rubocop:disable Gitlab/ModuleWithInstanceVariables + def pages + @wiki_pages = Kaminari.paginate_array( + wiki.list_pages(sort: params[:sort], direction: params[:direction]) + ).page(params[:page]) + + @wiki_entries = WikiPage.group_by_directory(@wiki_pages) + + render 'shared/wikis/pages' + end + # rubocop:enable Gitlab/ModuleWithInstanceVariables + + # `#show` handles a number of scenarios: + # + # - If `id` matches a WikiPage, then show the wiki page. + # - If `id` is a file in the wiki repository, then send the file. + # - If we know the user wants to create a new page with the given `id`, + # then display a create form. + # - Otherwise show the empty wiki page and invite the user to create a page. + # + # rubocop:disable Gitlab/ModuleWithInstanceVariables + def show + if page + set_encoding_error unless valid_encoding? + + # Assign vars expected by MarkupHelper + @ref = params[:version_id] + @path = page.path + + render 'shared/wikis/show' + elsif file_blob + send_blob(wiki.repository, file_blob, allow_caching: container.public?) + elsif show_create_form? + # Assign a title to the WikiPage unless `id` is a randomly generated slug from #new + title = params[:id] unless params[:random_title].present? + + @page = build_page(title: title) + + render 'shared/wikis/edit' + else + render 'shared/wikis/empty' + end + end + # rubocop:enable Gitlab/ModuleWithInstanceVariables + + def edit + render 'shared/wikis/edit' + end + + # rubocop:disable Gitlab/ModuleWithInstanceVariables + def update + return render('shared/wikis/empty') unless can?(current_user, :create_wiki, container) + + @page = WikiPages::UpdateService.new(container: container, current_user: current_user, params: wiki_params).execute(page) + + if page.valid? + redirect_to( + wiki_page_path(wiki, page), + notice: _('Wiki was successfully updated.') + ) + else + render 'shared/wikis/edit' + end + rescue WikiPage::PageChangedError, WikiPage::PageRenameError, Gitlab::Git::Wiki::OperationError => e + @error = e + render 'shared/wikis/edit' + end + # rubocop:enable Gitlab/ModuleWithInstanceVariables + + # rubocop:disable Gitlab/ModuleWithInstanceVariables + def create + @page = WikiPages::CreateService.new(container: container, current_user: current_user, params: wiki_params).execute + + if page.persisted? + redirect_to( + wiki_page_path(wiki, page), + notice: _('Wiki was successfully updated.') + ) + else + render 'shared/wikis/edit' + end + rescue Gitlab::Git::Wiki::OperationError => e + @page = build_page(wiki_params) + @error = e + render 'shared/wikis/edit' + end + # rubocop:enable Gitlab/ModuleWithInstanceVariables + + # rubocop:disable Gitlab/ModuleWithInstanceVariables + def history + if page + @page_versions = Kaminari.paginate_array(page.versions(page: params[:page].to_i), + total_count: page.count_versions) + .page(params[:page]) + + render 'shared/wikis/history' + else + redirect_to( + wiki_path(wiki), + notice: _("Page not found") + ) + end + end + # rubocop:enable Gitlab/ModuleWithInstanceVariables + + # rubocop:disable Gitlab/ModuleWithInstanceVariables + def destroy + WikiPages::DestroyService.new(container: container, current_user: current_user).execute(page) + + redirect_to wiki_path(wiki), + status: :found, + notice: _("Page was successfully deleted") + rescue Gitlab::Git::Wiki::OperationError => e + @error = e + render 'shared/wikis/edit' + end + # rubocop:enable Gitlab/ModuleWithInstanceVariables + + private + + def container + raise NotImplementedError + end + + def show_create_form? + can?(current_user, :create_wiki, container) && + page.nil? && + # Always show the create form when the wiki has had at least one page created. + # Otherwise, we only show the form when the user has navigated from + # the 'empty wiki' page + (wiki.exists? || params[:view] == 'create') + end + + def wiki + strong_memoize(:wiki) do + wiki = Wiki.for_container(container, current_user) + + # Call #wiki to make sure the Wiki Repo is initialized + wiki.wiki + + wiki + end + rescue Wiki::CouldNotCreateWikiError + flash[:notice] = _("Could not create Wiki Repository at this time. Please try again later.") + redirect_to container + false + end + + def page + strong_memoize(:page) do + wiki.find_page(*page_params) + end + end + + # rubocop:disable Gitlab/ModuleWithInstanceVariables + def load_sidebar + @sidebar_page = wiki.find_sidebar(params[:version_id]) + + unless @sidebar_page # Fallback to default sidebar + @sidebar_wiki_entries, @sidebar_limited = wiki.sidebar_entries + end + end + # rubocop:enable Gitlab/ModuleWithInstanceVariables + + def wiki_params + params.require(:wiki).permit(:title, :content, :format, :message, :last_commit_sha) + end + + def build_page(args = {}) + WikiPage.new(wiki).tap do |page| + page.update_attributes(args) # rubocop:disable Rails/ActiveRecordAliases + end + end + + def page_params + keys = [:id] + keys << :version_id if params[:action] == 'show' + + params.values_at(*keys) + end + + def valid_encoding? + page_encoding == Encoding::UTF_8 + end + + def page_encoding + strong_memoize(:page_encoding) { page&.content&.encoding } + end + + def set_encoding_error + flash.now[:notice] = _("The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository.") + end + + def file_blob + strong_memoize(:file_blob) do + commit = wiki.repository.commit(wiki.default_branch) + + next unless commit + + wiki.repository.blob_at(commit.id, params[:id]) + end + end +end diff --git a/app/controllers/concerns/workhorse_import_export_upload.rb b/app/controllers/concerns/workhorse_import_export_upload.rb new file mode 100644 index 00000000000..3c52f4d7adf --- /dev/null +++ b/app/controllers/concerns/workhorse_import_export_upload.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module WorkhorseImportExportUpload + extend ActiveSupport::Concern + include WorkhorseRequest + + included do + skip_before_action :verify_authenticity_token, only: %i[authorize] + before_action :verify_workhorse_api!, only: %i[authorize] + end + + def authorize + set_workhorse_internal_api_content_type + + authorized = ImportExportUploader.workhorse_authorize( + has_length: false, + maximum_size: Gitlab::CurrentSettings.max_import_size.megabytes + ) + + render json: authorized + rescue SocketError + render json: _("Error uploading file"), status: :internal_server_error + end + + private + + def file_is_valid?(file) + return false unless file.is_a?(::UploadedFile) + + ImportExportUploader::EXTENSION_WHITELIST + .include?(File.extname(file.original_filename).delete('.')) + end +end diff --git a/app/controllers/dashboard/milestones_controller.rb b/app/controllers/dashboard/milestones_controller.rb index d34a07324da..14f9a026688 100644 --- a/app/controllers/dashboard/milestones_controller.rb +++ b/app/controllers/dashboard/milestones_controller.rb @@ -1,48 +1,32 @@ # frozen_string_literal: true class Dashboard::MilestonesController < Dashboard::ApplicationController - include MilestoneActions - before_action :projects before_action :groups, only: :index - before_action :milestone, only: [:show, :merge_requests, :participants, :labels] def index respond_to do |format| format.html do - @milestone_states = Milestone.states_count(@projects.select(:id), @groups.select(:id)) - @milestones = Kaminari.paginate_array(milestones).page(params[:page]) + @milestone_states = Milestone.states_count(@projects.select(:id), groups.select(:id)) + @milestones = milestones.page(params[:page]) end format.json do - render json: milestones + render json: milestones.to_json(only: [:id, :title], methods: :name) end end end - def show - end - private - def group_milestones - DashboardGroupMilestone.build_collection(groups, params) - end - - # See [#39545](https://gitlab.com/gitlab-org/gitlab-foss/issues/39545) for info about the deprecation of dynamic milestones - def dynamic_milestones - DashboardMilestone.build_collection(@projects, params) - end - def milestones - @milestones = group_milestones + dynamic_milestones - end - - def milestone - @milestone = DashboardMilestone.build(@projects, params[:title]) - render_404 unless @milestone + MilestonesFinder.new(search_params).execute end def groups @groups ||= GroupsFinder.new(current_user, all_available: false).execute end + + def search_params + params.permit(:state, :search_title).merge(group_ids: groups, project_ids: projects) + end end diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb index ebee8e9094e..8a8064b24c2 100644 --- a/app/controllers/dashboard/todos_controller.rb +++ b/app/controllers/dashboard/todos_controller.rb @@ -17,7 +17,9 @@ class Dashboard::TodosController < Dashboard::ApplicationController end def destroy - TodoService.new.mark_todos_as_done_by_ids(params[:id], current_user) + todo = current_user.todos.find(params[:id]) + + TodoService.new.resolve_todo(todo, current_user, resolved_by_action: :mark_done) respond_to do |format| format.html do @@ -31,7 +33,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController end def destroy_all - updated_ids = TodoService.new.mark_todos_as_done(@todos, current_user) + updated_ids = TodoService.new.resolve_todos(@todos, current_user, resolved_by_action: :mark_all_done) respond_to do |format| format.html { redirect_to dashboard_todos_path, status: :found, notice: _('Everything on your to-do list is marked as done.') } @@ -41,13 +43,13 @@ class Dashboard::TodosController < Dashboard::ApplicationController end def restore - TodoService.new.mark_todos_as_pending_by_ids(params[:id], current_user) + TodoService.new.restore_todo(current_user.todos.find(params[:id]), current_user) render json: todos_counts end def bulk_restore - TodoService.new.mark_todos_as_pending_by_ids(params[:ids], current_user) + TodoService.new.restore_todos(current_user.todos.for_ids(params[:ids]), current_user) render json: todos_counts end diff --git a/app/controllers/groups/boards_controller.rb b/app/controllers/groups/boards_controller.rb index a9bd24890ee..c618ee8566a 100644 --- a/app/controllers/groups/boards_controller.rb +++ b/app/controllers/groups/boards_controller.rb @@ -9,6 +9,7 @@ class Groups::BoardsController < Groups::ApplicationController 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 private diff --git a/app/controllers/groups/group_links_controller.rb b/app/controllers/groups/group_links_controller.rb index 52ee69edaa5..c395b93f4e7 100644 --- a/app/controllers/groups/group_links_controller.rb +++ b/app/controllers/groups/group_links_controller.rb @@ -27,7 +27,7 @@ class Groups::GroupLinksController < Groups::ApplicationController end def destroy - Groups::GroupLinks::DestroyService.new(nil, nil).execute(@group_link) + Groups::GroupLinks::DestroyService.new(group, current_user).execute(@group_link) respond_to do |format| format.html do diff --git a/app/controllers/groups/imports_controller.rb b/app/controllers/groups/imports_controller.rb new file mode 100644 index 00000000000..b611685f9bc --- /dev/null +++ b/app/controllers/groups/imports_controller.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class Groups::ImportsController < Groups::ApplicationController + include ContinueParams + + def show + if @group.import_state.nil? || @group.import_state.finished? + if continue_params[:to] + redirect_to continue_params[:to], notice: continue_params[:notice] + else + redirect_to group_path(@group), notice: s_('GroupImport|The group was successfully imported.') + end + elsif @group.import_state.failed? + redirect_to new_group_path(@group), alert: s_('GroupImport|Failed to import group.') + else + flash.now[:notice] = continue_params[:notice_now] + end + end +end diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb index 8cfbd293597..df3fb6b67c2 100644 --- a/app/controllers/groups/milestones_controller.rb +++ b/app/controllers/groups/milestones_controller.rb @@ -6,17 +6,17 @@ class Groups::MilestonesController < Groups::ApplicationController before_action :milestone, only: [:edit, :show, :update, :merge_requests, :participants, :labels, :destroy] before_action :authorize_admin_milestones!, only: [:edit, :new, :create, :update, :destroy] before_action do - push_frontend_feature_flag(:burnup_charts) + push_frontend_feature_flag(:burnup_charts, @group) end def index respond_to do |format| format.html do - @milestone_states = Milestone.states_count(group_projects_with_access, [group]) - @milestones = Kaminari.paginate_array(milestones).page(params[:page]) + @milestone_states = Milestone.states_count(group_projects_with_access.without_order, [group]) + @milestones = milestones.page(params[:page]) end format.json do - render json: milestones.map { |m| m.for_display.slice(:id, :title, :name) } + render json: milestones.to_json(only: [:id, :title], methods: :name) end end end @@ -29,7 +29,7 @@ class Groups::MilestonesController < Groups::ApplicationController @milestone = Milestones::CreateService.new(group, current_user, milestone_params).execute if @milestone.persisted? - redirect_to milestone_path + redirect_to milestone_path(@milestone) else render "new" end @@ -39,23 +39,15 @@ class Groups::MilestonesController < Groups::ApplicationController end def edit - render_404 if @milestone.legacy_group_milestone? end def update - # Keep this compatible with legacy group milestones where we have to update - # all projects milestones states at once. - milestones, update_params = get_milestones_for_update - milestones.each do |milestone| - Milestones::UpdateService.new(milestone.resource_parent, current_user, update_params).execute(milestone) - end + Milestones::UpdateService.new(@milestone.parent, current_user, milestone_params).execute(@milestone) - redirect_to milestone_path + redirect_to milestone_path(@milestone) end def destroy - return render_404 if @milestone.legacy_group_milestone? - Milestones::DestroyService.new(group, current_user).execute(@milestone) respond_to do |format| @@ -66,14 +58,6 @@ class Groups::MilestonesController < Groups::ApplicationController private - def get_milestones_for_update - if @milestone.legacy_group_milestone? - [@milestone.milestones, legacy_milestone_params] - else - [[@milestone], milestone_params] - end - end - def authorize_admin_milestones! return render_404 unless can?(current_user, :admin_milestone, group) end @@ -82,27 +66,21 @@ class Groups::MilestonesController < Groups::ApplicationController params.require(:milestone).permit(:title, :description, :start_date, :due_date, :state_event) end - def legacy_milestone_params - params.require(:milestone).permit(:state_event) + def milestones + MilestonesFinder.new(search_params).execute end - def milestone_path - if @milestone.legacy_group_milestone? - group_milestone_path(group, @milestone.safe_title, title: @milestone.title) - else - group_milestone_path(group, @milestone.iid) - end + def milestone + @milestone = group.milestones.find_by_iid(params[:id]) + + render_404 unless @milestone end - def milestones - milestones = MilestonesFinder.new(search_params).execute + def search_params + groups = request.format.json? ? group_ids(include_ancestors: true) : group_ids @sort = params[:sort] || 'due_date_asc' - MilestoneArray.sort(milestones + legacy_milestones, @sort) - end - - def legacy_milestones - GroupMilestone.build_collection(group, group_projects_with_access, params) + params.permit(:state, :search_title).merge(sort: @sort, group_ids: groups, project_ids: group_projects_with_access) end def group_projects_with_access @@ -116,23 +94,6 @@ class Groups::MilestonesController < Groups::ApplicationController group.self_and_descendants.public_or_visible_to_user(current_user).select(:id) end end - - def milestone - @milestone = - if params[:title] - GroupMilestone.build(group, group_projects_with_access, params[:title]) - else - group.milestones.find_by_iid(params[:id]) - end - - render_404 unless @milestone - end - - def search_params - groups = request.format.json? ? group_ids(include_ancestors: true) : group_ids - - params.permit(:state, :search_title).merge(group_ids: groups) - end end Groups::MilestonesController.prepend_if_ee('EE::Groups::MilestonesController') diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index d5f2239b16a..fba374dbb44 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -57,6 +57,8 @@ class GroupsController < Groups::ApplicationController @group = Groups::CreateService.new(current_user, group_params).execute if @group.persisted? + track_experiment_event(:onboarding_issues, 'created_namespace') + notice = if @group.chat_team.present? "Group '#{@group.name}' and its Mattermost team were successfully created." else @@ -72,7 +74,11 @@ class GroupsController < Groups::ApplicationController def show respond_to do |format| format.html do - render_show_html + if @group.import_state&.in_progress? + redirect_to group_import_path(@group) + else + render_show_html + end end format.atom do @@ -264,11 +270,12 @@ class GroupsController < Groups::ApplicationController def export_rate_limit prefixed_action = "group_#{params[:action]}".to_sym - if Gitlab::ApplicationRateLimiter.throttled?(prefixed_action, scope: [current_user, prefixed_action, @group]) + scope = params[:action] == :download_export ? @group : nil + + if Gitlab::ApplicationRateLimiter.throttled?(prefixed_action, scope: [current_user, scope].compact) Gitlab::ApplicationRateLimiter.log_request(request, "#{prefixed_action}_request_limit".to_sym, current_user) - flash[:alert] = _('This endpoint has been requested too many times. Try again later.') - redirect_to edit_group_path(@group) + render plain: _('This endpoint has been requested too many times. Try again later.'), status: :too_many_requests end end diff --git a/app/controllers/ide_controller.rb b/app/controllers/ide_controller.rb index 8a838db04f9..2bf7bdd1ae0 100644 --- a/app/controllers/ide_controller.rb +++ b/app/controllers/ide_controller.rb @@ -6,9 +6,11 @@ class IdeController < ApplicationController include ClientsidePreviewCSP include StaticObjectExternalStorageCSP + before_action do + push_frontend_feature_flag(:build_service_proxy) + end + def index Gitlab::UsageDataCounters::WebIdeCounter.increment_views_count end end - -IdeController.prepend_if_ee('EE::IdeController') diff --git a/app/controllers/import/base_controller.rb b/app/controllers/import/base_controller.rb index 04919a4b9d0..afdea4f7c9d 100644 --- a/app/controllers/import/base_controller.rb +++ b/app/controllers/import/base_controller.rb @@ -1,10 +1,86 @@ # frozen_string_literal: true class Import::BaseController < ApplicationController + include ActionView::Helpers::SanitizeHelper + before_action :import_rate_limit, only: [:create] + def status + respond_to do |format| + format.json do + render json: { imported_projects: serialized_imported_projects, + provider_repos: serialized_provider_repos, + incompatible_repos: serialized_incompatible_repos, + namespaces: serialized_namespaces } + end + format.html + end + end + + def realtime_changes + Gitlab::PollingInterval.set_header(response, interval: 3_000) + + render json: already_added_projects.to_json(only: [:id], methods: [:import_status]) + end + + protected + + def importable_repos + raise NotImplementedError + end + + def incompatible_repos + [] + end + + def provider_name + raise NotImplementedError + end + + def provider_url + raise NotImplementedError + end + private + def filter_attribute + :name + end + + def sanitized_filter_param + @filter ||= sanitize(params[:filter]) + end + + def filtered(collection) + return collection unless sanitized_filter_param + + collection.select { |item| item[filter_attribute].include?(sanitized_filter_param) } + end + + def serialized_provider_repos + Import::ProviderRepoSerializer.new(current_user: current_user).represent(importable_repos, provider: provider_name, provider_url: provider_url) + end + + def serialized_incompatible_repos + Import::ProviderRepoSerializer.new(current_user: current_user).represent(incompatible_repos, provider: provider_name, provider_url: provider_url) + end + + def serialized_imported_projects + ProjectSerializer.new.represent(already_added_projects, serializer: :import, provider_url: provider_url) + end + + def already_added_projects + @already_added_projects ||= filtered(find_already_added_projects(provider_name)) + end + + def serialized_namespaces + NamespaceSerializer.new.represent(namespaces) + end + + def namespaces + current_user.manageable_groups_with_routes + end + # rubocop: disable CodeReuse/ActiveRecord def find_already_added_projects(import_type) current_user.created_projects.where(import_type: import_type).with_import_state diff --git a/app/controllers/import/bitbucket_controller.rb b/app/controllers/import/bitbucket_controller.rb index c37e799de62..4886aeb5e3f 100644 --- a/app/controllers/import/bitbucket_controller.rb +++ b/app/controllers/import/bitbucket_controller.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class Import::BitbucketController < Import::BaseController + extend ::Gitlab::Utils::Override + include ActionView::Helpers::SanitizeHelper before_action :verify_bitbucket_import_enabled @@ -10,7 +12,7 @@ class Import::BitbucketController < Import::BaseController rescue_from Bitbucket::Error::Unauthorized, with: :bitbucket_unauthorized def callback - response = client.auth_code.get_token(params[:code], redirect_uri: users_import_bitbucket_callback_url) + response = oauth_client.auth_code.get_token(params[:code], redirect_uri: users_import_bitbucket_callback_url) session[:bitbucket_token] = response.token session[:bitbucket_expires_at] = response.expires_at @@ -22,9 +24,10 @@ class Import::BitbucketController < Import::BaseController # 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') @@ -38,6 +41,10 @@ class Import::BitbucketController < Import::BaseController render json: find_jobs('bitbucket') end + def realtime_changes + super + end + def create bitbucket_client = Bitbucket::Client.new(credentials) @@ -59,7 +66,7 @@ class Import::BitbucketController < Import::BaseController project = Gitlab::BitbucketImport::ProjectCreator.new(repo, project_name, target_namespace, current_user, credentials).execute if project.persisted? - render json: ProjectSerializer.new.represent(project) + render json: ProjectSerializer.new.represent(project, serializer: :import) else render json: { errors: project_save_error(project) }, status: :unprocessable_entity end @@ -68,16 +75,50 @@ class Import::BitbucketController < Import::BaseController end end + protected + + # rubocop: disable CodeReuse/ActiveRecord + override :importable_repos + def importable_repos + already_added_projects_names = already_added_projects.map(&:import_source) + + bitbucket_repos.reject { |repo| already_added_projects_names.include?(repo.full_name) || !repo.valid? } + end + # rubocop: enable CodeReuse/ActiveRecord + + override :incompatible_repos + def incompatible_repos + bitbucket_repos.reject { |repo| repo.valid? } + end + + override :provider_name + def provider_name + :bitbucket + end + + override :provider_url + def provider_url + provider.url + end + private - def client - @client ||= OAuth2::Client.new(provider.app_id, provider.app_secret, options) + def oauth_client + @oauth_client ||= OAuth2::Client.new(provider.app_id, provider.app_secret, options) end def provider Gitlab::Auth::OAuth::Provider.config_for('bitbucket') end + def client + @client ||= Bitbucket::Client.new(credentials) + end + + def bitbucket_repos + @bitbucket_repos ||= client.repos(filter: sanitized_filter_param).to_a + end + def options OmniAuth::Strategies::Bitbucket.default_options[:client_options].deep_symbolize_keys end @@ -91,7 +132,7 @@ class Import::BitbucketController < Import::BaseController end def go_to_bitbucket_for_permissions - redirect_to client.auth_code.authorize_url(redirect_uri: users_import_bitbucket_callback_url) + redirect_to oauth_client.auth_code.authorize_url(redirect_uri: users_import_bitbucket_callback_url) end def bitbucket_unauthorized diff --git a/app/controllers/import/bitbucket_server_controller.rb b/app/controllers/import/bitbucket_server_controller.rb index 5fb7b5dccc5..9aa8110257d 100644 --- a/app/controllers/import/bitbucket_server_controller.rb +++ b/app/controllers/import/bitbucket_server_controller.rb @@ -1,12 +1,16 @@ # frozen_string_literal: true class Import::BitbucketServerController < Import::BaseController + extend ::Gitlab::Utils::Override + include ActionView::Helpers::SanitizeHelper before_action :verify_bitbucket_server_import_enabled before_action :bitbucket_auth, except: [:new, :configure] before_action :validate_import_params, only: [:create] + rescue_from BitbucketServer::Connection::ConnectionError, with: :bitbucket_connection_error + # As a basic sanity check to prevent URL injection, restrict project # repository input and repository slugs to allowed characters. For Bitbucket: # @@ -24,7 +28,7 @@ class Import::BitbucketServerController < Import::BaseController end def create - repo = bitbucket_client.repo(@project_key, @repo_slug) + repo = client.repo(@project_key, @repo_slug) unless repo return render json: { errors: _("Project %{project_repo} could not be found") % { project_repo: "#{@project_key}/#{@repo_slug}" } }, status: :unprocessable_entity @@ -38,15 +42,13 @@ class Import::BitbucketServerController < Import::BaseController 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) + render json: ProjectSerializer.new.represent(project, serializer: :import) else render json: { errors: project_save_error(project) }, status: :unprocessable_entity end else render json: { errors: _('This namespace has already been taken! Please choose another one.') }, status: :unprocessable_entity end - rescue BitbucketServer::Connection::ConnectionError => error - render json: { errors: _("Unable to connect to server: %{error}") % { error: error } }, status: :unprocessable_entity end def configure @@ -59,7 +61,9 @@ class Import::BitbucketServerController < Import::BaseController # rubocop: disable CodeReuse/ActiveRecord def status - @collection = bitbucket_client.repos(page_offset: page_offset, limit: limit_per_page, filter: sanitized_filter_param) + 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 @@ -67,10 +71,6 @@ class Import::BitbucketServerController < Import::BaseController already_added_projects_names = @already_added_projects.pluck(:import_source) @repos.reject! { |repo| already_added_projects_names.include?(repo.browse_url) } - rescue BitbucketServer::Connection::ConnectionError => error - flash[:alert] = _("Unable to connect to server: %{error}") % { error: error } - clear_session_data - redirect_to new_import_bitbucket_server_path end # rubocop: enable CodeReuse/ActiveRecord @@ -78,6 +78,38 @@ class Import::BitbucketServerController < Import::BaseController render json: find_jobs('bitbucket_server') end + def realtime_changes + super + end + + protected + + # rubocop: disable CodeReuse/ActiveRecord + override :importable_repos + def importable_repos + # Use the import URL to filter beyond what BaseService#find_already_added_projects + already_added_projects = filter_added_projects('bitbucket_server', bitbucket_repos.map(&:browse_url)) + already_added_projects_names = already_added_projects.map(&:import_source) + + bitbucket_repos.reject { |repo| already_added_projects_names.include?(repo.browse_url) || !repo.valid? } + end + # rubocop: enable CodeReuse/ActiveRecord + + override :incompatible_repos + def incompatible_repos + bitbucket_repos.reject { |repo| repo.valid? } + end + + override :provider_name + def provider_name + :bitbucket_server + end + + override :provider_url + def provider_url + session[bitbucket_server_url_key] + end + private # rubocop: disable CodeReuse/ActiveRecord @@ -86,8 +118,12 @@ class Import::BitbucketServerController < Import::BaseController end # rubocop: enable CodeReuse/ActiveRecord - def bitbucket_client - @bitbucket_client ||= BitbucketServer::Client.new(credentials) + def client + @client ||= BitbucketServer::Client.new(credentials) + end + + def bitbucket_repos + @bitbucket_repos ||= client.repos(page_offset: page_offset, limit: limit_per_page, filter: sanitized_filter_param).to_a end def validate_import_params @@ -153,4 +189,23 @@ class Import::BitbucketServerController < Import::BaseController def sanitized_filter_param sanitize(params[:filter]) end + + def bitbucket_connection_error(error) + flash[:alert] = _("Unable to connect to server: %{error}") % { error: error } + clear_session_data + + respond_to do |format| + format.json do + render json: { + error: { + message: _("Unable to connect to server: %{error}") % { error: error }, + redirect: new_import_bitbucket_server_path + } + }, status: :unprocessable_entity + end + format.html do + redirect_to new_import_bitbucket_server_path + end + end + end end diff --git a/app/controllers/import/fogbugz_controller.rb b/app/controllers/import/fogbugz_controller.rb index 4fb6efde7ff..91779a5d6cc 100644 --- a/app/controllers/import/fogbugz_controller.rb +++ b/app/controllers/import/fogbugz_controller.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class Import::FogbugzController < Import::BaseController + extend ::Gitlab::Utils::Override + before_action :verify_fogbugz_import_enabled before_action :user_map, only: [:new_user_map, :create_user_map] before_action :verify_blocked_uri, only: :callback @@ -48,6 +50,8 @@ 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') @@ -57,6 +61,10 @@ class Import::FogbugzController < Import::BaseController end # rubocop: enable CodeReuse/ActiveRecord + def realtime_changes + super + end + def jobs render json: find_jobs('fogbugz') end @@ -69,12 +77,35 @@ class Import::FogbugzController < Import::BaseController project = Gitlab::FogbugzImport::ProjectCreator.new(repo, fb_session, current_user.namespace, current_user, umap).execute if project.persisted? - render json: ProjectSerializer.new.represent(project) + render json: ProjectSerializer.new.represent(project, serializer: :import) else render json: { errors: project_save_error(project) }, status: :unprocessable_entity end end + protected + + # rubocop: disable CodeReuse/ActiveRecord + override :importable_repos + def importable_repos + repos = client.repos + + already_added_projects_names = already_added_projects.map(&:import_source) + + repos.reject { |repo| already_added_projects_names.include? repo.name } + end + # rubocop: enable CodeReuse/ActiveRecord + + override :provider_name + def provider_name + :fogbugz + end + + override :provider_url + def provider_url + session[:fogbugz_uri] + end + private def client diff --git a/app/controllers/import/github_controller.rb b/app/controllers/import/github_controller.rb index 4e8ceae75bd..097edcd6075 100644 --- a/app/controllers/import/github_controller.rb +++ b/app/controllers/import/github_controller.rb @@ -76,7 +76,7 @@ class Import::GithubController < Import::BaseController def serialized_provider_repos repos = client_repos.reject { |repo| already_added_project_names.include? repo.full_name } - ProviderRepoSerializer.new(current_user: current_user).represent(repos, provider: provider, provider_url: provider_url) + Import::ProviderRepoSerializer.new(current_user: current_user).represent(repos, provider: provider, provider_url: provider_url) end def serialized_namespaces diff --git a/app/controllers/import/gitlab_controller.rb b/app/controllers/import/gitlab_controller.rb index 5ec8e9e6fc5..a95a67e208c 100644 --- a/app/controllers/import/gitlab_controller.rb +++ b/app/controllers/import/gitlab_controller.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class Import::GitlabController < Import::BaseController + extend ::Gitlab::Utils::Override + MAX_PROJECT_PAGES = 15 PER_PAGE_PROJECTS = 100 @@ -16,6 +18,8 @@ class Import::GitlabController < Import::BaseController # 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') @@ -37,7 +41,7 @@ class Import::GitlabController < Import::BaseController project = Gitlab::GitlabImport::ProjectCreator.new(repo, target_namespace, current_user, access_params).execute if project.persisted? - render json: ProjectSerializer.new.represent(project) + render json: ProjectSerializer.new.represent(project, serializer: :import) else render json: { errors: project_save_error(project) }, status: :unprocessable_entity end @@ -46,6 +50,29 @@ class Import::GitlabController < Import::BaseController end end + protected + + # rubocop: disable CodeReuse/ActiveRecord + override :importable_repos + def importable_repos + repos = client.projects(starting_page: 1, page_limit: MAX_PROJECT_PAGES, per_page: PER_PAGE_PROJECTS) + + already_added_projects_names = already_added_projects.map(&:import_source) + + repos.reject { |repo| already_added_projects_names.include? repo["path_with_namespace"] } + end + # rubocop: enable CodeReuse/ActiveRecord + + override :provider_name + def provider_name + :gitlab + end + + override :provider_url + def provider_url + 'https://gitlab.com' + end + private def client diff --git a/app/controllers/import/gitlab_groups_controller.rb b/app/controllers/import/gitlab_groups_controller.rb new file mode 100644 index 00000000000..330af68385e --- /dev/null +++ b/app/controllers/import/gitlab_groups_controller.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +class Import::GitlabGroupsController < ApplicationController + include WorkhorseImportExportUpload + + before_action :ensure_group_import_enabled + before_action :import_rate_limit, only: %i[create] + + def create + unless file_is_valid?(group_params[:file]) + return redirect_back_or_default(options: { alert: s_('GroupImport|Unable to process group import file') }) + end + + group_data = group_params.except(:file).merge( + visibility_level: closest_allowed_visibility_level, + import_export_upload: ImportExportUpload.new(import_file: group_params[:file]) + ) + + group = ::Groups::CreateService.new(current_user, group_data).execute + + if group.persisted? + if Groups::ImportExport::ImportService.new(group: group, user: current_user).async_execute + redirect_to( + group_path(group), + notice: s_("GroupImport|Group '%{group_name}' is being imported.") % { group_name: group.name } + ) + else + redirect_to group_path(group), alert: _("Group import could not be scheduled") + end + else + redirect_back_or_default( + options: { alert: s_("GroupImport|Group could not be imported: %{errors}") % { errors: group.errors.full_messages.to_sentence } } + ) + end + end + + private + + def group_params + params.permit(:path, :name, :parent_id, :file) + end + + def closest_allowed_visibility_level + if group_params[:parent_id].present? + parent_group = Group.find(group_params[:parent_id]) + + Gitlab::VisibilityLevel.closest_allowed_level(parent_group.visibility_level) + else + Gitlab::VisibilityLevel::PRIVATE + end + end + + def ensure_group_import_enabled + render_404 unless Feature.enabled?(:group_import_export, @group, default_enabled: true) + end + + def import_rate_limit + if Gitlab::ApplicationRateLimiter.throttled?(:group_import, scope: current_user) + Gitlab::ApplicationRateLimiter.log_request(request, :group_import_request_limit, current_user) + + flash[:alert] = _('This endpoint has been requested too many times. Try again later.') + redirect_to new_group_path + end + end +end diff --git a/app/controllers/import/gitlab_projects_controller.rb b/app/controllers/import/gitlab_projects_controller.rb index 6a3715a4675..39d053347f0 100644 --- a/app/controllers/import/gitlab_projects_controller.rb +++ b/app/controllers/import/gitlab_projects_controller.rb @@ -1,14 +1,11 @@ # frozen_string_literal: true class Import::GitlabProjectsController < Import::BaseController - include WorkhorseRequest + include WorkhorseImportExportUpload before_action :whitelist_query_limiting, only: [:create] before_action :verify_gitlab_project_import_enabled - skip_before_action :verify_authenticity_token, only: [:authorize] - before_action :verify_workhorse_api!, only: [:authorize] - def new @namespace = Namespace.find(project_params[:namespace_id]) return render_404 unless current_user.can?(:create_projects, @namespace) @@ -17,7 +14,7 @@ class Import::GitlabProjectsController < Import::BaseController end def create - unless file_is_valid? + unless file_is_valid?(project_params[:file]) return redirect_back_or_default(options: { alert: _("You need to upload a GitLab project export archive (ending in .gz).") }) end @@ -33,28 +30,8 @@ class Import::GitlabProjectsController < Import::BaseController end end - def authorize - set_workhorse_internal_api_content_type - - authorized = ImportExportUploader.workhorse_authorize( - has_length: false, - maximum_size: Gitlab::CurrentSettings.max_attachment_size.megabytes.to_i) - - render json: authorized - rescue SocketError - render json: _("Error uploading file"), status: :internal_server_error - end - private - def file_is_valid? - return false unless project_params[:file].is_a?(::UploadedFile) - - filename = project_params[:file].original_filename - - ImportExportUploader::EXTENSION_WHITELIST.include?(File.extname(filename).delete('.')) - end - def verify_gitlab_project_import_enabled render_404 unless gitlab_project_import_enabled? end diff --git a/app/controllers/projects/alert_management_controller.rb b/app/controllers/projects/alert_management_controller.rb index 0c0a91e136f..054dc8e6a35 100644 --- a/app/controllers/projects/alert_management_controller.rb +++ b/app/controllers/projects/alert_management_controller.rb @@ -2,10 +2,6 @@ class Projects::AlertManagementController < Projects::ApplicationController before_action :authorize_read_alert_management_alert! - before_action do - push_frontend_feature_flag(:alert_list_status_filtering_enabled) - push_frontend_feature_flag(:create_issue_from_alert_enabled) - end def index end diff --git a/app/controllers/projects/alerting/notifications_controller.rb b/app/controllers/projects/alerting/notifications_controller.rb index 358e7629958..fef8235628d 100644 --- a/app/controllers/projects/alerting/notifications_controller.rb +++ b/app/controllers/projects/alerting/notifications_controller.rb @@ -29,12 +29,22 @@ module Projects end def notify_service - Projects::Alerting::NotifyService - .new(project, current_user, notification_payload) + notify_service_class.new(project, current_user, notification_payload) + end + + def notify_service_class + # We are tracking the consolidation of these services in + # https://gitlab.com/groups/gitlab-org/-/epics/3360 + # to get rid of this workaround. + if Projects::Prometheus::Alerts::NotifyService.processable?(notification_payload) + Projects::Prometheus::Alerts::NotifyService + else + Projects::Alerting::NotifyService + end end def notification_payload - params.permit![:notification] + @notification_payload ||= params.permit![:notification] end end end diff --git a/app/controllers/projects/badges_controller.rb b/app/controllers/projects/badges_controller.rb index 66b51b17790..59a7dff680c 100644 --- a/app/controllers/projects/badges_controller.rb +++ b/app/controllers/projects/badges_controller.rb @@ -8,14 +8,21 @@ class Projects::BadgesController < Projects::ApplicationController def pipeline pipeline_status = Gitlab::Badge::Pipeline::Status - .new(project, params[:ref]) + .new(project, params[:ref], opts: { + key_text: params[:key_text], + key_width: params[:key_width] + }) render_badge pipeline_status end def coverage coverage_report = Gitlab::Badge::Coverage::Report - .new(project, params[:ref], params[:job]) + .new(project, params[:ref], opts: { + job: params[:job], + key_text: params[:key_text], + key_width: params[:key_width] + }) render_badge coverage_report end diff --git a/app/controllers/projects/blame_controller.rb b/app/controllers/projects/blame_controller.rb index b62ce940e9c..374b4921dbc 100644 --- a/app/controllers/projects/blame_controller.rb +++ b/app/controllers/projects/blame_controller.rb @@ -20,6 +20,7 @@ class Projects::BlameController < Projects::ApplicationController environment_params[:find_latest] = true @environment = EnvironmentsFinder.new(@project, current_user, environment_params).execute.last - @blame_groups = Gitlab::Blame.new(@blob, @commit).groups + @blame = Gitlab::Blame.new(@blob, @commit) + @blame = Gitlab::View::Presenter::Factory.new(@blame, project: @project, path: @path).fabricate! end end diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index 584320a66de..abc1d58cbf1 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -71,6 +71,7 @@ class Projects::BlobController < Projects::ApplicationController def update @path = params[:file_path] if params[:file_path].present? + create_commit(Files::UpdateService, success_path: -> { after_edit_path }, failure_view: :edit, failure_path: project_blob_path(@project, @id)) @@ -93,7 +94,6 @@ class Projects::BlobController < Projects::ApplicationController def destroy create_commit(Files::DeleteService, success_notice: _("The file has been successfully deleted."), success_path: -> { after_delete_path }, - failure_view: :show, failure_path: project_blob_path(@project, @id)) end @@ -115,6 +115,8 @@ class Projects::BlobController < Projects::ApplicationController private + attr_reader :branch_name + def blob @blob ||= @repository.blob_at(@commit.id, @path) @@ -254,3 +256,5 @@ class Projects::BlobController < Projects::ApplicationController params.permit(:full, :since, :to, :bottom, :unfold, :offset, :indent) end end + +Projects::BlobController.prepend_if_ee('EE::Projects::BlobController') diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb index cc595740696..7cfb4a508da 100644 --- a/app/controllers/projects/branches_controller.rb +++ b/app/controllers/projects/branches_controller.rb @@ -25,8 +25,9 @@ class Projects::BranchesController < Projects::ApplicationController @refs_pipelines = @project.ci_pipelines.latest_successful_for_refs(@branches.map(&:name)) @merged_branch_names = repository.merged_branch_names(@branches.map(&:name)) + @branch_pipeline_statuses = branch_pipeline_statuses - # https://gitlab.com/gitlab-org/gitlab-foss/issues/48097 + # https://gitlab.com/gitlab-org/gitlab/-/issues/22851 Gitlab::GitalyClient.allow_n_plus_1_calls do render end @@ -194,4 +195,15 @@ class Projects::BranchesController < Projects::ApplicationController confidential_issue_project end + + def branch_pipeline_statuses + latest_commits = @branches.map do |branch| + [branch.name, repository.commit(branch.dereferenced_target).sha] + end.to_h + + latest_pipelines = project.ci_pipelines.latest_pipeline_per_commit(latest_commits.values) + latest_commits.transform_values do |commit_sha| + latest_pipelines[commit_sha]&.detailed_status(current_user) + end.compact + end end diff --git a/app/controllers/projects/ci/daily_build_group_report_results_controller.rb b/app/controllers/projects/ci/daily_build_group_report_results_controller.rb index dfda5fca310..b36c5f1aea6 100644 --- a/app/controllers/projects/ci/daily_build_group_report_results_controller.rb +++ b/app/controllers/projects/ci/daily_build_group_report_results_controller.rb @@ -7,12 +7,13 @@ class Projects::Ci::DailyBuildGroupReportResultsController < Projects::Applicati REPORT_WINDOW = 90.days before_action :validate_feature_flag! - before_action :authorize_download_code! # Share the same authorization rules as the graphs controller + before_action :authorize_read_build_report_results! before_action :validate_param_type! def index respond_to do |format| - format.csv { send_data(render_csv(results), type: 'text/csv; charset=utf-8') } + format.csv { send_data(render_csv(report_results), type: 'text/csv; charset=utf-8') } + format.json { render json: render_json(report_results) } end end @@ -37,7 +38,11 @@ class Projects::Ci::DailyBuildGroupReportResultsController < Projects::Applicati ).render end - def results + def render_json(collection) + Ci::DailyBuildGroupReportResultSerializer.new.represent(collection, param_type: param_type) + end + + def report_results Ci::DailyBuildGroupReportResultsFinder.new(finder_params).execute end diff --git a/app/controllers/projects/discussions_controller.rb b/app/controllers/projects/discussions_controller.rb index 028390c7e2a..06231607f73 100644 --- a/app/controllers/projects/discussions_controller.rb +++ b/app/controllers/projects/discussions_controller.rb @@ -10,7 +10,7 @@ class Projects::DiscussionsController < Projects::ApplicationController before_action :authorize_resolve_discussion!, only: [:resolve, :unresolve] def resolve - Discussions::ResolveService.new(project, current_user, merge_request: merge_request).execute(discussion) + Discussions::ResolveService.new(project, current_user, one_or_more_discussions: discussion).execute render_discussion end diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index 5f4d88c57e9..4d774123ef1 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -10,7 +10,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController push_frontend_feature_flag(:prometheus_computed_alerts) end - before_action :authorize_read_environment! + before_action :authorize_read_environment!, except: [:metrics, :additional_metrics, :metrics_dashboard, :metrics_redirect] before_action :authorize_create_environment!, only: [:new, :create] before_action :authorize_stop_environment!, only: [:stop] before_action :authorize_update_environment!, only: [:edit, :update, :cancel_auto_stop] diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb index 34246f27241..a8b90f8685f 100644 --- a/app/controllers/projects/graphs_controller.rb +++ b/app/controllers/projects/graphs_controller.rb @@ -6,7 +6,7 @@ class Projects::GraphsController < Projects::ApplicationController # Authorize before_action :require_non_empty_project before_action :assign_ref_vars - before_action :authorize_download_code! + before_action :authorize_read_repository_graphs! def show respond_to do |format| @@ -54,7 +54,8 @@ class Projects::GraphsController < Projects::ApplicationController end def get_daily_coverage_options - return unless Feature.enabled?(:ci_download_daily_code_coverage, default_enabled: true) + return unless Feature.enabled?(:ci_download_daily_code_coverage, @project, default_enabled: true) + return unless can?(current_user, :read_build_report_results, project) date_today = Date.current report_window = Projects::Ci::DailyBuildGroupReportResultsController::REPORT_WINDOW @@ -70,6 +71,11 @@ class Projects::GraphsController < Projects::ApplicationController namespace_id: @project.namespace, project_id: @project, format: :csv + ), + graph_api_path: namespace_project_ci_daily_build_group_report_results_path( + namespace_id: @project.namespace, + project_id: @project, + format: :json ) } end diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb index d06e24ef39c..a30c455a7e4 100644 --- a/app/controllers/projects/group_links_controller.rb +++ b/app/controllers/projects/group_links_controller.rb @@ -5,10 +5,6 @@ class Projects::GroupLinksController < Projects::ApplicationController before_action :authorize_admin_project! before_action :authorize_admin_project_member!, only: [:update] - def index - redirect_to namespace_project_settings_members_path - end - def create group = Group.find(params[:link_group_id]) if params[:link_group_id].present? @@ -26,8 +22,7 @@ class Projects::GroupLinksController < Projects::ApplicationController def update @group_link = @project.project_group_links.find(params[:id]) - - @group_link.update(group_link_params) + Projects::GroupLinks::UpdateService.new(@group_link).execute(group_link_params) end def destroy diff --git a/app/controllers/projects/import/jira_controller.rb b/app/controllers/projects/import/jira_controller.rb index 711e23dc3ce..976ac7df976 100644 --- a/app/controllers/projects/import/jira_controller.rb +++ b/app/controllers/projects/import/jira_controller.rb @@ -4,59 +4,29 @@ module Projects module Import class JiraController < Projects::ApplicationController before_action :authenticate_user! - before_action :check_issues_available! before_action :authorize_read_project! - before_action :jira_import_enabled? - before_action :jira_integration_configured? - before_action :authorize_admin_project!, only: [:import] + before_action :validate_jira_import_settings! def show - jira_service = @project.jira_service - - if jira_service.present? && !@project.latest_jira_import&.in_progress? && current_user&.can?(:admin_project, @project) - jira_client = jira_service.client - jira_projects = jira_client.Project.all - - if jira_projects.present? - @jira_projects = jira_projects.map { |p| ["#{p.name} (#{p.key})", p.key] } - else - flash[:alert] = 'No projects have been returned from Jira. Please check your Jira configuration.' - end - end - - unless Feature.enabled?(:jira_issue_import_vue, @project, default_enabled: true) - flash[:notice] = _("Import %{status}") % { status: @project.jira_import_status } unless @project.latest_jira_import&.initial? - end - end - - def import - jira_project_key = jira_import_params[:jira_project_key] - - if jira_project_key.present? - response = ::JiraImport::StartImportService.new(current_user, @project, jira_project_key).execute - flash[:notice] = response.message if response.message.present? - else - flash[:alert] = 'No Jira project key has been provided.' - end - - redirect_to project_import_jira_path(@project) end private - def jira_import_enabled? - return if @project.jira_issues_import_feature_flag_enabled? + def validate_jira_import_settings! + Gitlab::JiraImport.validate_project_settings!(@project, user: current_user, configuration_check: false) + true + rescue Projects::ImportService::Error => e + flash[:notice] = e.message redirect_to project_issues_path(@project) - end - def jira_integration_configured? - return if Feature.enabled?(:jira_issue_import_vue, @project, default_enabled: true) - return if @project.jira_service + false + end - flash[:notice] = _("Configure the Jira integration first on your project's %{strong_start} Settings > Integrations > Jira%{strong_end} page." % - { strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }) - redirect_to project_issues_path(@project) + def jira_service + strong_memoize(:jira_service) do + @project.jira_service + end end def jira_import_params diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 3e9d956f7b1..693329848de 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -46,7 +46,6 @@ class Projects::IssuesController < Projects::ApplicationController before_action do push_frontend_feature_flag(:vue_issuable_sidebar, project.group) - push_frontend_feature_flag(:save_issuable_health_status, project.group, default_enabled: true) end before_action only: :show do diff --git a/app/controllers/projects/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb index e0457925b34..e1f6cbe3dca 100644 --- a/app/controllers/projects/jobs_controller.rb +++ b/app/controllers/projects/jobs_controller.rb @@ -14,6 +14,8 @@ class Projects::JobsController < Projects::ApplicationController 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 layout 'project' @@ -151,6 +153,10 @@ class Projects::JobsController < Projects::ApplicationController render json: Gitlab::Workhorse.channel_websocket(@build.terminal_specification) end + def proxy_websocket_authorize + render json: proxy_websocket_service(build_service_specification) + end + private def authorize_update_build! @@ -165,10 +171,19 @@ class Projects::JobsController < Projects::ApplicationController return access_denied! unless can?(current_user, :create_build_terminal, build) end + def authorize_create_proxy_build! + return access_denied! unless can?(current_user, :create_build_service_proxy, build) + end + def verify_api_request! Gitlab::Workhorse.verify_api_request!(request.headers) end + def verify_proxy_request! + verify_api_request! + set_workhorse_internal_api_content_type + end + def raw_send_params { type: 'text/plain; charset=utf-8', disposition: 'inline' } end @@ -202,6 +217,27 @@ class Projects::JobsController < Projects::ApplicationController 'attachment' end -end -Projects::JobsController.prepend_if_ee('EE::Projects::JobsController') + def build_service_specification + build.service_specification(service: params['service'], + port: params['port'], + path: params['path'], + subprotocols: proxy_subprotocol) + end + + def proxy_subprotocol + # This will allow to reuse the same subprotocol set + # in the original websocket connection + request.headers['HTTP_SEC_WEBSOCKET_PROTOCOL'].presence || ::Ci::BuildRunnerSession::TERMINAL_SUBPROTOCOL + end + + # This method provides the information to Workhorse + # about the service we want to proxy to. + # For security reasons, in case this operation is started by JS, + # it's important to use only sourced GitLab JS code + def proxy_websocket_service(service) + service[:url] = ::Gitlab::UrlHelpers.as_wss(service[:url]) + + ::Gitlab::Workhorse.channel_websocket(service) + end +end diff --git a/app/controllers/projects/merge_requests/diffs_controller.rb b/app/controllers/projects/merge_requests/diffs_controller.rb index 2331674f42c..1bf143c9a91 100644 --- a/app/controllers/projects/merge_requests/diffs_controller.rb +++ b/app/controllers/projects/merge_requests/diffs_controller.rb @@ -162,8 +162,13 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic def renderable_notes define_diff_comment_vars unless @notes - @notes + draft_notes = + if current_user + merge_request.draft_notes.authored_by(current_user) + else + [] + end + + @notes.concat(draft_notes) end end - -Projects::MergeRequests::DiffsController.prepend_if_ee('EE::Projects::MergeRequests::DiffsController') diff --git a/app/controllers/projects/merge_requests/drafts_controller.rb b/app/controllers/projects/merge_requests/drafts_controller.rb new file mode 100644 index 00000000000..f4846b1aa81 --- /dev/null +++ b/app/controllers/projects/merge_requests/drafts_controller.rb @@ -0,0 +1,129 @@ +# frozen_string_literal: true + +class Projects::MergeRequests::DraftsController < Projects::MergeRequests::ApplicationController + include Gitlab::Utils::StrongMemoize + + respond_to :json + + before_action :authorize_create_note!, only: [:create, :publish] + before_action :authorize_admin_draft!, only: [:update, :destroy] + before_action :authorize_admin_draft!, if: -> { action_name == 'publish' && params[:id].present? } + + def index + drafts = prepare_notes_for_rendering(draft_notes) + render json: DraftNoteSerializer.new(current_user: current_user).represent(drafts) + end + + def create + create_params = draft_note_params.merge(in_reply_to_discussion_id: params[:in_reply_to_discussion_id]) + create_service = DraftNotes::CreateService.new(merge_request, current_user, create_params) + + draft_note = create_service.execute + + prepare_notes_for_rendering(draft_note) + + render json: DraftNoteSerializer.new(current_user: current_user).represent(draft_note) + end + + def update + draft_note.update!(draft_note_params) + + prepare_notes_for_rendering(draft_note) + + render json: DraftNoteSerializer.new(current_user: current_user).represent(draft_note) + end + + def destroy + DraftNotes::DestroyService.new(merge_request, current_user).execute(draft_note) + + head :ok + end + + def publish + result = DraftNotes::PublishService.new(merge_request, current_user).execute(draft_note(allow_nil: true)) + + if result[:status] == :success + head :ok + else + render json: { message: result[:message] }, status: result[:status] + end + end + + def discard + DraftNotes::DestroyService.new(merge_request, current_user).execute + + head :ok + end + + private + + def draft_note(allow_nil: false) + strong_memoize(:draft_note) do + draft_notes.find(params[:id]) + end + rescue ActiveRecord::RecordNotFound => ex + # draft_note is allowed to be nil in #publish + raise ex unless allow_nil + end + + def draft_notes + return unless current_user + + strong_memoize(:draft_notes) do + merge_request.draft_notes.authored_by(current_user) + end + end + + # rubocop: disable CodeReuse/ActiveRecord + def merge_request + @merge_request ||= MergeRequestsFinder.new(current_user, project_id: @project.id).find_by!(iid: params[:merge_request_id]) + end + # rubocop: enable CodeReuse/ActiveRecord + + def draft_note_params + params.require(:draft_note).permit( + :commit_id, + :note, + :position, + :resolve_discussion + ).tap do |h| + # Old FE version will still be sending `draft_note[commit_id]` as 'undefined'. + # That can result to having a note linked to a commit with 'undefined' ID + # which is non-existent. + h[:commit_id] = nil if h[:commit_id] == 'undefined' + end + end + + def prepare_notes_for_rendering(notes) + return [] unless notes + + notes = Array.wrap(notes) + + # Preload author and access-level information + DraftNote.preload_author(notes) + user_ids = notes.map(&:author_id) + project.team.max_member_access_for_user_ids(user_ids) + + notes.map(&method(:render_draft_note)) + end + + def render_draft_note(note) + params = { target_id: merge_request.id, target_type: 'MergeRequest', text: note.note } + result = PreviewMarkdownService.new(@project, current_user, params).execute + markdown_params = { markdown_engine: result[:markdown_engine], issuable_state_filter_enabled: true } + + note.rendered_note = view_context.markdown(result[:text], markdown_params) + note.users_referenced = result[:users] + note.commands_changes = view_context.markdown(result[:commands]) + + note + end + + def authorize_admin_draft! + access_denied! unless can?(current_user, :admin_note, draft_note) + end + + def authorize_create_note! + access_denied! unless can?(current_user, :create_note, merge_request) + end +end diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 5613b5b9589..55556ea7d31 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -33,6 +33,9 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo push_frontend_feature_flag(:widget_visibility_polling, @project, default_enabled: true) push_frontend_feature_flag(:merge_ref_head_comments, @project) 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) end before_action do diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb index 56f1f1a1019..16d63cc184f 100644 --- a/app/controllers/projects/milestones_controller.rb +++ b/app/controllers/projects/milestones_controller.rb @@ -7,7 +7,7 @@ class Projects::MilestonesController < Projects::ApplicationController before_action :check_issuables_available! before_action :milestone, only: [:edit, :update, :destroy, :show, :merge_requests, :participants, :labels, :promote] before_action do - push_frontend_feature_flag(:burnup_charts) + push_frontend_feature_flag(:burnup_charts, @project) end # Allow read any milestone @@ -34,7 +34,7 @@ class Projects::MilestonesController < Projects::ApplicationController @milestones = @milestones.page(params[:page]) end format.json do - render json: @milestones.to_json(methods: :name) + render json: @milestones.to_json(only: [:id, :title], methods: :name) end end end diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index 678d0862f48..0b6c0db211e 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -12,8 +12,9 @@ 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(:filter_pipelines_search, default_enabled: true) - push_frontend_feature_flag(:dag_pipeline_tab) + 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(:pipelines_security_report_summary, project) end before_action :ensure_pipeline, only: [:show] @@ -95,7 +96,14 @@ class Projects::PipelinesController < Projects::ApplicationController end def dag - render_show + respond_to do |format| + format.html { render_show } + format.json do + render json: Ci::DagPipelineSerializer + .new(project: @project, current_user: @current_user) + .represent(@pipeline) + end + end end def failures @@ -269,7 +277,7 @@ class Projects::PipelinesController < Projects::ApplicationController end def index_params - params.permit(:scope, :username, :ref) + params.permit(:scope, :username, :ref, :status) end end diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb index fcbeb5c840c..a2581e72257 100644 --- a/app/controllers/projects/refs_controller.rb +++ b/app/controllers/projects/refs_controller.rb @@ -53,7 +53,7 @@ class Projects::RefsController < Projects::ApplicationController format.json do logs, next_offset = tree_summary.fetch_logs - response.headers["More-Logs-Offset"] = next_offset if next_offset + response.headers["More-Logs-Offset"] = next_offset.to_s if next_offset render json: logs end diff --git a/app/controllers/projects/releases_controller.rb b/app/controllers/projects/releases_controller.rb index 228b139d794..d3285b64dab 100644 --- a/app/controllers/projects/releases_controller.rb +++ b/app/controllers/projects/releases_controller.rb @@ -10,6 +10,7 @@ class Projects::ReleasesController < Projects::ApplicationController push_frontend_feature_flag(:release_evidence_collection, project, default_enabled: true) push_frontend_feature_flag(:release_show_page, project, default_enabled: true) push_frontend_feature_flag(:release_asset_link_editing, project, default_enabled: true) + push_frontend_feature_flag(:release_asset_link_type, project, default_enabled: true) end before_action :authorize_update_release!, only: %i[edit update] diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb index 92c6ce324f7..710ad546e64 100644 --- a/app/controllers/projects/services_controller.rb +++ b/app/controllers/projects/services_controller.rb @@ -2,6 +2,7 @@ class Projects::ServicesController < Projects::ApplicationController include ServiceParams + include InternalRedirect # Authorize before_action :authorize_admin_project! @@ -10,6 +11,9 @@ class Projects::ServicesController < Projects::ApplicationController before_action :web_hook_logs, only: [:edit, :update] 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) + end respond_to :html @@ -26,8 +30,8 @@ class Projects::ServicesController < Projects::ApplicationController respond_to do |format| format.html do if saved - redirect_to project_settings_integrations_path(@project), - notice: success_message + target_url = safe_redirect_path(params[:redirect_to]).presence || project_settings_integrations_path(@project) + redirect_to target_url, notice: success_message else render 'edit' end @@ -56,11 +60,10 @@ class Projects::ServicesController < Projects::ApplicationController return { error: true, message: _('Validations failed.'), service_response: @service.errors.full_messages.join(','), test_failed: false } end - data = @service.test_data(project, current_user) - outcome = @service.test(data) + result = Integrations::Test::ProjectService.new(@service, current_user, params[:event]).execute - unless outcome[:success] - return { error: true, message: _('Test failed.'), service_response: outcome[:result].to_s, test_failed: true } + unless result[:success] + return { error: true, message: _('Test failed.'), service_response: result[:message].to_s, test_failed: true } end {} diff --git a/app/controllers/projects/settings/operations_controller.rb b/app/controllers/projects/settings/operations_controller.rb index a9d1dc0759d..c2292511e0f 100644 --- a/app/controllers/projects/settings/operations_controller.rb +++ b/app/controllers/projects/settings/operations_controller.rb @@ -104,7 +104,7 @@ module Projects project_params = { incident_management_setting_attributes: ::Gitlab::Tracking::IncidentManagement.tracking_keys.keys, - metrics_setting_attributes: [:external_dashboard_url], + metrics_setting_attributes: [:external_dashboard_url, :dashboard_timezone], error_tracking_setting_attributes: [ :enabled, diff --git a/app/controllers/projects/snippets/application_controller.rb b/app/controllers/projects/snippets/application_controller.rb new file mode 100644 index 00000000000..3f488b07e96 --- /dev/null +++ b/app/controllers/projects/snippets/application_controller.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class Projects::Snippets::ApplicationController < Projects::ApplicationController + include FindSnippet + include SnippetAuthorizations + + private + + # This overrides the default snippet create authorization + # because ProjectSnippets are checked against the project rather + # than the user + def authorize_create_snippet! + return render_404 unless can?(current_user, :create_snippet, project) + end + + def snippet_klass + ProjectSnippet + end +end diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb index 9233f063f55..5ee6abef804 100644 --- a/app/controllers/projects/snippets_controller.rb +++ b/app/controllers/projects/snippets_controller.rb @@ -1,34 +1,19 @@ # frozen_string_literal: true -class Projects::SnippetsController < Projects::ApplicationController - include RendersNotes +class Projects::SnippetsController < Projects::Snippets::ApplicationController + include SnippetsActions include ToggleAwardEmoji include SpammableActions - include SnippetsActions - include RendersBlob - include PaginatedCollection - include Gitlab::NoteableMetadata - - skip_before_action :verify_authenticity_token, - if: -> { action_name == 'show' && js_request? } before_action :check_snippets_available! + before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji, :mark_as_spam] - # Allow create snippet before_action :authorize_create_snippet!, only: [:new, :create] - - # Allow read any snippet before_action :authorize_read_snippet!, except: [:new, :create, :index] - - # Allow modify snippet before_action :authorize_update_snippet!, only: [:edit, :update] - - # Allow destroy snippet before_action :authorize_admin_snippet!, only: [:destroy] - respond_to :html - def index @snippet_counts = Snippets::CountService .new(current_user, project: @project) @@ -56,61 +41,8 @@ class Projects::SnippetsController < Projects::ApplicationController handle_repository_error(:new) end - def update - update_params = snippet_params.merge(spammable_params) - - service_response = Snippets::UpdateService.new(project, current_user, update_params).execute(@snippet) - @snippet = service_response.payload[:snippet] - - handle_repository_error(:edit) - end - - def show - conditionally_expand_blob(blob) - - respond_to do |format| - format.html do - @note = @project.notes.new(noteable: @snippet) - @noteable = @snippet - - @discussions = @snippet.discussions - @notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes), @noteable) - render 'show' - end - - format.json do - render_blob_json(blob) - end - - format.js do - if @snippet.embeddable? - render 'shared/snippets/show' - else - head :not_found - end - end - end - end - - def destroy - service_response = Snippets::DestroyService.new(current_user, @snippet).execute - - if service_response.success? - redirect_to project_snippets_path(project), status: :found - elsif service_response.http_status == 403 - access_denied! - else - redirect_to project_snippet_path(project, @snippet), - status: :found, - alert: service_response.message - end - end - protected - def snippet - @snippet ||= @project.snippets.inc_relations_for_view.find(params[:id]) - end alias_method :awardable, :snippet alias_method :spammable, :snippet @@ -118,18 +50,6 @@ class Projects::SnippetsController < Projects::ApplicationController project_snippet_path(@project, @snippet) end - def authorize_read_snippet! - return render_404 unless can?(current_user, :read_snippet, @snippet) - end - - def authorize_update_snippet! - return render_404 unless can?(current_user, :update_snippet, @snippet) - end - - def authorize_admin_snippet! - return render_404 unless can?(current_user, :admin_snippet, @snippet) - end - def snippet_params params.require(:project_snippet).permit(:title, :content, :file_name, :private, :visibility_level, :description) end diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb index c89bfd110c4..df20daa8f7e 100644 --- a/app/controllers/projects/tags_controller.rb +++ b/app/controllers/projects/tags_controller.rb @@ -41,16 +41,20 @@ class Projects::TagsController < Projects::ApplicationController # rubocop: enable CodeReuse/ActiveRecord def create + # TODO: remove this with the release creation moved to it's own form https://gitlab.com/gitlab-org/gitlab/-/issues/214245 + evidence_pipeline = find_evidence_pipeline + result = ::Tags::CreateService.new(@project, current_user) .execute(params[:tag_name], params[:ref], params[:message]) if result[:status] == :success - # Release creation with Tags was deprecated in GitLab 11.7 + # TODO: remove this with the release creation moved to it's own form https://gitlab.com/gitlab-org/gitlab/-/issues/214245 if params[:release_description].present? release_params = { tag: params[:tag_name], name: params[:tag_name], - description: params[:release_description] + description: params[:release_description], + evidence_pipeline: evidence_pipeline } Releases::CreateService @@ -93,4 +97,14 @@ class Projects::TagsController < Projects::ApplicationController end end end + + private + + # TODO: remove this with the release creation moved to it's own form https://gitlab.com/gitlab-org/gitlab/-/issues/214245 + def find_evidence_pipeline + evidence_pipeline_sha = @project.repository.commit(params[:ref])&.sha + return unless evidence_pipeline_sha + + @project.ci_pipelines.for_sha(evidence_pipeline_sha).last + end end diff --git a/app/controllers/projects/web_ide_terminals_controller.rb b/app/controllers/projects/web_ide_terminals_controller.rb new file mode 100644 index 00000000000..08ea5c4bca8 --- /dev/null +++ b/app/controllers/projects/web_ide_terminals_controller.rb @@ -0,0 +1,98 @@ +# frozen_string_literal: true + +class Projects::WebIdeTerminalsController < Projects::ApplicationController + before_action :authenticate_user! + + before_action :build, except: [:check_config, :create] + before_action :authorize_create_web_ide_terminal! + before_action :authorize_read_web_ide_terminal!, except: [:check_config, :create] + before_action :authorize_update_web_ide_terminal!, only: [:cancel, :retry] + + def check_config + return respond_422 unless branch_sha + + result = ::Ci::WebIdeConfigService.new(project, current_user, sha: branch_sha).execute + + if result[:status] == :success + head :ok + else + respond_422 + end + end + + def show + render_terminal(build) + end + + def create + result = ::Ci::CreateWebIdeTerminalService.new(project, + current_user, + ref: params[:branch]) + .execute + + if result[:status] == :error + render status: :bad_request, json: result[:message] + else + pipeline = result[:pipeline] + current_build = pipeline.builds.last + + if current_build + Gitlab::UsageDataCounters::WebIdeCounter.increment_terminals_count + + render_terminal(current_build) + else + render status: :bad_request, json: pipeline.errors.full_messages + end + end + end + + def cancel + return respond_422 unless build.cancelable? + + build.cancel + + head :ok + end + + def retry + return respond_422 unless build.retryable? + + new_build = Ci::Build.retry(build, current_user) + + render_terminal(new_build) + end + + private + + def authorize_create_web_ide_terminal! + return access_denied! unless can?(current_user, :create_web_ide_terminal, project) + end + + def authorize_read_web_ide_terminal! + authorize_build_ability!(:read_web_ide_terminal) + end + + def authorize_update_web_ide_terminal! + authorize_build_ability!(:update_web_ide_terminal) + end + + def authorize_build_ability!(ability) + return access_denied! unless can?(current_user, ability, build) + end + + def build + @build ||= project.builds.find(params[:id]) + end + + def branch_sha + return unless params[:branch].present? + + project.commit(params[:branch])&.id + end + + def render_terminal(current_build) + render json: WebIdeTerminalSerializer + .new(project: project, current_user: current_user) + .represent(current_build) + end +end diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb index 508b1f5bd0a..85e643aa212 100644 --- a/app/controllers/projects/wikis_controller.rb +++ b/app/controllers/projects/wikis_controller.rb @@ -1,206 +1,11 @@ # frozen_string_literal: true class Projects::WikisController < Projects::ApplicationController + include WikiActions include PreviewMarkdown - include SendsBlob - include Gitlab::Utils::StrongMemoize - before_action :authorize_read_wiki! - before_action :authorize_create_wiki!, only: [:edit, :create] - before_action :authorize_admin_wiki!, only: :destroy - before_action :load_project_wiki - before_action :load_page, only: [:show, :edit, :update, :history, :destroy] - before_action only: [:show, :edit, :update] do - @valid_encoding = valid_encoding? - end - before_action only: [:edit, :update], unless: :valid_encoding? do - redirect_to(project_wiki_path(@project, @page)) - end - - def new - redirect_to project_wiki_path(@project, SecureRandom.uuid, random_title: true) - end - - def pages - @wiki_pages = Kaminari.paginate_array( - @project_wiki.list_pages(sort: params[:sort], direction: params[:direction]) - ).page(params[:page]) - - @wiki_entries = WikiPage.group_by_directory(@wiki_pages) - end - - # `#show` handles a number of scenarios: - # - # - If `id` matches a WikiPage, then show the wiki page. - # - If `id` is a file in the wiki repository, then send the file. - # - If we know the user wants to create a new page with the given `id`, - # then display a create form. - # - Otherwise show the empty wiki page and invite the user to create a page. - def show - if @page - set_encoding_error unless valid_encoding? - - # Assign vars expected by MarkupHelper - @ref = params[:version_id] - @path = @page.path - - render 'show' - elsif file_blob - send_blob(@project_wiki.repository, file_blob, allow_caching: @project.public?) - elsif show_create_form? - # Assign a title to the WikiPage unless `id` is a randomly generated slug from #new - title = params[:id] unless params[:random_title].present? - - @page = build_page(title: title) - - render 'edit' - else - render 'empty' - end - end - - def edit - end - - def update - return render('empty') unless can?(current_user, :create_wiki, @project) - - @page = WikiPages::UpdateService.new(container: @project, current_user: current_user, params: wiki_params).execute(@page) - - if @page.valid? - redirect_to( - project_wiki_path(@project, @page), - notice: _('Wiki was successfully updated.') - ) - else - render 'edit' - end - rescue WikiPage::PageChangedError, WikiPage::PageRenameError, Gitlab::Git::Wiki::OperationError => e - @error = e - render 'edit' - end - - def create - @page = WikiPages::CreateService.new(container: @project, current_user: current_user, params: wiki_params).execute - - if @page.persisted? - redirect_to( - project_wiki_path(@project, @page), - notice: _('Wiki was successfully updated.') - ) - else - render action: "edit" - end - rescue Gitlab::Git::Wiki::OperationError => e - @page = build_page(wiki_params) - @error = e - - render 'edit' - end - - def history - if @page - @page_versions = Kaminari.paginate_array(@page.versions(page: params[:page].to_i), - total_count: @page.count_versions) - .page(params[:page]) - else - redirect_to( - project_wiki_path(@project, :home), - notice: _("Page not found") - ) - end - end - - def destroy - WikiPages::DestroyService.new(container: @project, current_user: current_user).execute(@page) - - redirect_to project_wiki_path(@project, :home), - status: :found, - notice: _("Page was successfully deleted") - rescue Gitlab::Git::Wiki::OperationError => e - @error = e - render 'edit' - end + alias_method :container, :project def git_access end - - private - - def show_create_form? - can?(current_user, :create_wiki, @project) && - @page.nil? && - # Always show the create form when the wiki has had at least one page created. - # Otherwise, we only show the form when the user has navigated from - # the 'empty wiki' page - (@project_wiki.exists? || params[:view] == 'create') - end - - def load_project_wiki - @project_wiki = load_wiki - - # Call #wiki to make sure the Wiki Repo is initialized - @project_wiki.wiki - - @sidebar_page = @project_wiki.find_sidebar(params[:version_id]) - - unless @sidebar_page # Fallback to default sidebar - @sidebar_wiki_entries, @sidebar_limited = @project_wiki.sidebar_entries - end - rescue ProjectWiki::CouldNotCreateWikiError - flash[:notice] = _("Could not create Wiki Repository at this time. Please try again later.") - redirect_to project_path(@project) - false - end - - def load_wiki - ProjectWiki.new(@project, current_user) - end - - def wiki_params - params.require(:wiki).permit(:title, :content, :format, :message, :last_commit_sha) - end - - def build_page(args = {}) - WikiPage.new(@project_wiki).tap do |page| - page.update_attributes(args) # rubocop:disable Rails/ActiveRecordAliases - end - end - - def load_page - @page ||= find_page - end - - def find_page - @project_wiki.find_page(*page_params) - end - - def page_params - keys = [:id] - keys << :version_id if params[:action] == 'show' - - params.values_at(*keys) - end - - def valid_encoding? - page_encoding == Encoding::UTF_8 - end - - def page_encoding - strong_memoize(:page_encoding) { @page&.content&.encoding } - end - - def set_encoding_error - flash.now[:notice] = _("The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository.") - end - - def file_blob - strong_memoize(:file_blob) do - commit = @project_wiki.repository.commit(@project_wiki.default_branch) - - next unless commit - - @project_wiki.repository.blob_at(commit.id, params[:id]) - end - end end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 2f86b945b06..f0ddd62e996 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -34,6 +34,12 @@ class ProjectsController < Projects::ApplicationController # Project Export Rate Limit before_action :export_rate_limit, only: [:export, :download_export, :generate_new_export] + # Experiments + 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) + end + layout :determine_layout def index @@ -310,12 +316,11 @@ class ProjectsController < Projects::ApplicationController render 'projects/empty' if @project.empty_repo? else if can?(current_user, :read_wiki, @project) - @project_wiki = @project.wiki - @wiki_home = @project_wiki.find_page('home', params[:version_id]) + @wiki = @project.wiki + @wiki_home = @wiki.find_page('home', params[:version_id]) elsif @project.feature_available?(:issues, current_user) @issues = issuables_collection.page(params[:page]) - @collection_type = 'Issue' - @issuable_meta_data = issuable_meta_data(@issues, @collection_type, current_user) + @issuable_meta_data = Gitlab::IssuableMetadata.new(current_user, @issues).data end render :show @@ -357,6 +362,7 @@ class ProjectsController < Projects::ApplicationController def project_params_attributes [ + :allow_merge_on_skipped_pipeline, :avatar, :build_allow_git_fetch, :build_coverage_regex, @@ -483,11 +489,12 @@ class ProjectsController < Projects::ApplicationController def export_rate_limit prefixed_action = "project_#{params[:action]}".to_sym - if rate_limiter.throttled?(prefixed_action, scope: [current_user, prefixed_action, @project]) + project_scope = params[:action] == :download_export ? @project : nil + + if rate_limiter.throttled?(prefixed_action, scope: [current_user, project_scope].compact) rate_limiter.log_request(request, "#{prefixed_action}_request_limit".to_sym, current_user) - flash[:alert] = _('This endpoint has been requested too many times. Try again later.') - redirect_to edit_project_path(@project) + render plain: _('This endpoint has been requested too many times. Try again later.'), status: :too_many_requests end end diff --git a/app/controllers/registrations/experience_levels_controller.rb b/app/controllers/registrations/experience_levels_controller.rb new file mode 100644 index 00000000000..515d6b3f9aa --- /dev/null +++ b/app/controllers/registrations/experience_levels_controller.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module Registrations + class ExperienceLevelsController < ApplicationController + # This will need to be changed to simply 'devise' as part of + # https://gitlab.com/gitlab-org/growth/engineering/issues/64 + layout 'devise_experimental_separate_sign_up_flow' + + before_action :check_experiment_enabled + before_action :ensure_namespace_path_param + + def update + current_user.experience_level = params[:experience_level] + + if current_user.save + hide_advanced_issues + flash[:message] = I18n.t('devise.registrations.signed_up') + redirect_to group_path(params[:namespace_path]) + else + render :show + end + end + + private + + def check_experiment_enabled + access_denied! unless experiment_enabled?(:onboarding_issues) + end + + def ensure_namespace_path_param + redirect_to root_path unless params[:namespace_path].present? + end + + def hide_advanced_issues + return unless current_user.user_preference.novice? + + settings = cookies[:onboarding_issues_settings] + return unless settings + + modified_settings = Gitlab::Json.parse(settings).merge(hideAdvanced: true) + cookies[:onboarding_issues_settings] = modified_settings.to_json + end + end +end diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index ffbccbb01f2..6ab2924a8b5 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -63,6 +63,10 @@ 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? + set_flash_message! :notice, :signed_up redirect_to path_for_signed_in_user(current_user) else diff --git a/app/controllers/repositories/git_http_controller.rb b/app/controllers/repositories/git_http_controller.rb index e3dbe6fcbdf..6a27d63625e 100644 --- a/app/controllers/repositories/git_http_controller.rb +++ b/app/controllers/repositories/git_http_controller.rb @@ -9,7 +9,7 @@ module Repositories rescue_from Gitlab::GitAccess::ForbiddenError, with: :render_403_with_exception rescue_from Gitlab::GitAccess::NotFoundError, with: :render_404_with_exception - rescue_from Gitlab::GitAccess::ProjectCreationError, with: :render_422_with_exception + rescue_from Gitlab::GitAccessProject::CreationError, with: :render_422_with_exception rescue_from Gitlab::GitAccess::TimeoutError, with: :render_503_with_exception # GET /foo/bar.git/info/refs?service=git-upload-pack (git pull) diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 04d2b3068da..217f08dd648 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -5,6 +5,10 @@ class SearchController < ApplicationController include SearchHelper include RendersCommits + SCOPE_PRELOAD_METHOD = { + projects: :with_web_entity_associations + }.freeze + around_action :allow_gitaly_ref_name_caching skip_before_action :authenticate_user! @@ -28,12 +32,12 @@ class SearchController < ApplicationController @scope = search_service.scope @show_snippets = search_service.show_snippets? @search_results = search_service.search_results - @search_objects = search_service.search_objects + @search_objects = search_service.search_objects(preload_method) render_commits if @scope == 'commits' eager_load_user_status if @scope == 'users' - increment_navbar_searches_counter + increment_search_counters check_single_commit_result end @@ -47,22 +51,11 @@ 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? + private - render json: search_autocomplete_opts(term).to_json + def preload_method + SCOPE_PRELOAD_METHOD[@scope.to_sym] end - # rubocop: enable CodeReuse/ActiveRecord - - private def search_term_valid? unless search_service.valid_query_length? @@ -98,9 +91,11 @@ class SearchController < ApplicationController end end - def increment_navbar_searches_counter + def increment_search_counters + Gitlab::UsageDataCounters::SearchCounter.count(:all_searches) + return if params[:nav_source] != 'navbar' - Gitlab::UsageDataCounters::SearchCounter.increment_navbar_searches_count + Gitlab::UsageDataCounters::SearchCounter.count(:navbar_searches) end end diff --git a/app/controllers/snippets/application_controller.rb b/app/controllers/snippets/application_controller.rb new file mode 100644 index 00000000000..a533e46a75d --- /dev/null +++ b/app/controllers/snippets/application_controller.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class Snippets::ApplicationController < ApplicationController + include FindSnippet + include SnippetAuthorizations + + private + + def authorize_read_snippet! + return if can?(current_user, :read_snippet, snippet) + + if current_user + render_404 + else + authenticate_user! + end + end + + def snippet_klass + PersonalSnippet + end +end diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb index 425e0458b41..87d87390e57 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -1,19 +1,12 @@ # frozen_string_literal: true -class SnippetsController < ApplicationController - include RendersNotes - include ToggleAwardEmoji - include SpammableActions +class SnippetsController < Snippets::ApplicationController include SnippetsActions - include RendersBlob include PreviewMarkdown - include PaginatedCollection - include Gitlab::NoteableMetadata - - skip_before_action :verify_authenticity_token, - if: -> { action_name == 'show' && js_request? } + include ToggleAwardEmoji + include SpammableActions - before_action :snippet, only: [:show, :edit, :destroy, :update, :raw] + before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji, :mark_as_spam] before_action :authorize_create_snippet!, only: [:new, :create] before_action :authorize_read_snippet!, only: [:show, :raw] @@ -23,7 +16,6 @@ class SnippetsController < ApplicationController skip_before_action :authenticate_user!, only: [:index, :show, :raw] layout 'snippets' - respond_to :html def index if params[:username].present? @@ -60,62 +52,8 @@ class SnippetsController < ApplicationController end end - def update - service_response = Snippets::UpdateService.new(nil, current_user, snippet_params).execute(@snippet) - @snippet = service_response.payload[:snippet] - - handle_repository_error(:edit) - end - - def show - conditionally_expand_blob(blob) - - respond_to do |format| - format.html do - @note = Note.new(noteable: @snippet) - @noteable = @snippet - - @discussions = @snippet.discussions - @notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes), @noteable) - render 'show' - end - - format.json do - render_blob_json(blob) - end - - format.js do - if @snippet.embeddable? - render 'shared/snippets/show' - else - head :not_found - end - end - end - end - - def destroy - service_response = Snippets::DestroyService.new(current_user, @snippet).execute - - if service_response.success? - redirect_to dashboard_snippets_path, status: :found - elsif service_response.http_status == 403 - access_denied! - else - redirect_to snippet_path(@snippet), - status: :found, - alert: service_response.message - end - end - protected - # rubocop: disable CodeReuse/ActiveRecord - def snippet - @snippet ||= PersonalSnippet.inc_relations_for_view.find_by(id: params[:id]) - end - # rubocop: enable CodeReuse/ActiveRecord - alias_method :awardable, :snippet alias_method :spammable, :snippet @@ -123,28 +61,6 @@ class SnippetsController < ApplicationController snippet_path(@snippet) end - def authorize_read_snippet! - return if can?(current_user, :read_snippet, @snippet) - - if current_user - render_404 - else - authenticate_user! - end - end - - def authorize_update_snippet! - return render_404 unless can?(current_user, :update_snippet, @snippet) - end - - def authorize_admin_snippet! - return render_404 unless can?(current_user, :admin_snippet, @snippet) - end - - def authorize_create_snippet! - return render_404 unless can?(current_user, :create_snippet) - end - def snippet_params params.require(:personal_snippet).permit(:title, :content, :file_name, :private, :visibility_level, :description).merge(spammable_params) end |