From a9ced7da447785c57477b3d8dbccc73a78cface1 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Thu, 9 Apr 2020 12:09:24 +0000 Subject: Add latest changes from gitlab-org/gitlab@master --- .../javascripts/boards/components/board_card.vue | 6 +++ .../javascripts/boards/components/board_sidebar.js | 2 + app/controllers/groups_controller.rb | 40 ++++++++++++++++++- app/controllers/projects/import/jira_controller.rb | 4 +- app/graphql/mutations/jira_import/start.rb | 8 ++-- .../resolvers/projects/jira_imports_resolver.rb | 4 +- app/graphql/types/jira_import_type.rb | 14 ++----- .../concerns/import_state/sidekiq_job_tracker.rb | 2 +- app/models/jira_import_data.rb | 46 ---------------------- app/models/jira_import_state.rb | 11 ++++++ app/models/project.rb | 14 ++----- .../groups/import_export/export_service.rb | 13 ++++++ app/services/jira_import/start_import_service.rb | 37 ++++++++--------- app/views/layouts/nav/_dashboard.html.haml | 2 +- .../concerns/gitlab/jira_import/import_worker.rb | 2 +- .../gitlab/jira_import/advance_stage_worker.rb | 2 +- .../jira_import/stage/finish_import_worker.rb | 5 +-- .../jira_import/stage/import_attachments_worker.rb | 2 +- .../jira_import/stage/import_issues_worker.rb | 2 +- .../jira_import/stage/import_notes_worker.rb | 2 +- .../jira_import/stage/start_import_worker.rb | 7 ++-- 21 files changed, 112 insertions(+), 113 deletions(-) delete mode 100644 app/models/jira_import_data.rb (limited to 'app') diff --git a/app/assets/javascripts/boards/components/board_card.vue b/app/assets/javascripts/boards/components/board_card.vue index 5735c8ded3d..246d3b9dcd1 100644 --- a/app/assets/javascripts/boards/components/board_card.vue +++ b/app/assets/javascripts/boards/components/board_card.vue @@ -2,6 +2,7 @@ /* eslint-disable vue/require-default-prop */ import IssueCardInner from './issue_card_inner.vue'; import eventHub from '../eventhub'; +import sidebarEventHub from '~/sidebar/event_hub'; import boardsStore from '../stores/boards_store'; export default { @@ -73,6 +74,11 @@ export default { showIssue(e) { if (e.target.classList.contains('js-no-trigger')) return; + // If no issues are opened, close all sidebars first + if (!boardsStore.detail?.issue?.id) { + sidebarEventHub.$emit('sidebar.closeAll'); + } + // If CMD or CTRL is clicked const isMultiSelect = this.canMultiSelect && (e.ctrlKey || e.metaKey); diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js index a3a9753f1b5..66a5e134205 100644 --- a/app/assets/javascripts/boards/components/board_sidebar.js +++ b/app/assets/javascripts/boards/components/board_sidebar.js @@ -103,12 +103,14 @@ export default Vue.extend({ eventHub.$on('sidebar.addAssignee', this.addAssignee); eventHub.$on('sidebar.removeAllAssignees', this.removeAllAssignees); eventHub.$on('sidebar.saveAssignees', this.saveAssignees); + eventHub.$on('sidebar.closeAll', this.closeSidebar); }, beforeDestroy() { eventHub.$off('sidebar.removeAssignee', this.removeAssignee); eventHub.$off('sidebar.addAssignee', this.addAssignee); eventHub.$off('sidebar.removeAllAssignees', this.removeAllAssignees); eventHub.$off('sidebar.saveAssignees', this.saveAssignees); + eventHub.$off('sidebar.closeAll', this.closeSidebar); }, mounted() { new IssuableContext(this.currentUser); diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 7175eefcde7..44120fda17c 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -6,18 +6,20 @@ class GroupsController < Groups::ApplicationController include ParamsBackwardCompatibility include PreviewMarkdown include RecordUserLastActivity + include SendFileUpload extend ::Gitlab::Utils::Override respond_to :html prepend_before_action(only: [:show, :issues]) { authenticate_sessionless_user!(:rss) } prepend_before_action(only: [:issues_calendar]) { authenticate_sessionless_user!(:ics) } + prepend_before_action :ensure_export_enabled, only: [:export, :download_export] before_action :authenticate_user!, only: [:new, :create] before_action :group, except: [:index, :new, :create] # Authorize - before_action :authorize_admin_group!, only: [:edit, :update, :destroy, :projects, :transfer] + before_action :authorize_admin_group!, only: [:edit, :update, :destroy, :projects, :transfer, :export, :download_export] before_action :authorize_create_group!, only: [:new] before_action :group_projects, only: [:projects, :activity, :issues, :merge_requests] @@ -29,6 +31,8 @@ class GroupsController < Groups::ApplicationController push_frontend_feature_flag(:vue_issuables_list, @group) end + before_action :export_rate_limit, only: [:export, :download_export] + skip_cross_project_access_check :index, :new, :create, :edit, :update, :destroy, :projects # When loading show as an atom feed, we render events that could leak cross @@ -134,6 +138,25 @@ class GroupsController < Groups::ApplicationController end # rubocop: enable CodeReuse/ActiveRecord + def export + export_service = Groups::ImportExport::ExportService.new(group: @group, user: current_user) + + if export_service.async_execute + redirect_to edit_group_path(@group), notice: _('Group export started.') + else + redirect_to edit_group_path(@group), alert: _('Group export could not be started.') + end + end + + def download_export + if @group.export_file_exists? + send_upload(@group.export_file, attachment: @group.export_file.filename) + else + redirect_to edit_group_path(@group), + alert: _('Group export link has expired. Please generate a new export from your group settings.') + end + end + protected def render_show_html @@ -234,6 +257,21 @@ class GroupsController < Groups::ApplicationController url_for(safe_params) end + def export_rate_limit + prefixed_action = "group_#{params[:action]}".to_sym + + if Gitlab::ApplicationRateLimiter.throttled?(prefixed_action, scope: [current_user, prefixed_action, @group]) + 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) + end + end + + def ensure_export_enabled + render_404 unless Feature.enabled?(:group_import_export, @group, default_enabled: true) + end + private def groups diff --git a/app/controllers/projects/import/jira_controller.rb b/app/controllers/projects/import/jira_controller.rb index c8f53cef5b2..b5adef399c7 100644 --- a/app/controllers/projects/import/jira_controller.rb +++ b/app/controllers/projects/import/jira_controller.rb @@ -9,7 +9,7 @@ module Projects def show return if Feature.enabled?(:jira_issue_import_vue, @project) - unless @project.import_state&.in_progress? + unless @project.latest_jira_import&.in_progress? jira_client = @project.jira_service.client jira_projects = jira_client.Project.all @@ -20,7 +20,7 @@ module Projects end end - flash[:notice] = _("Import %{status}") % { status: @project.import_state.status } if @project.import_state.present? && !@project.import_state.none? + flash[:notice] = _("Import %{status}") % { status: @project.jira_import_status } unless @project.latest_jira_import&.initial? end def import diff --git a/app/graphql/mutations/jira_import/start.rb b/app/graphql/mutations/jira_import/start.rb index ffd3ce53b57..6b80c9f8ca4 100644 --- a/app/graphql/mutations/jira_import/start.rb +++ b/app/graphql/mutations/jira_import/start.rb @@ -30,11 +30,11 @@ module Mutations service_response = ::JiraImport::StartImportService .new(context[:current_user], project, jira_project_key) .execute - import_data = service_response.payload[:import_data] - + jira_import = service_response.success? ? service_response.payload[:import_data] : nil + errors = service_response.error? ? [service_response.message] : [] { - jira_import: import_data.errors.blank? ? import_data.projects.last : nil, - errors: errors_on_object(import_data) + jira_import: jira_import, + errors: errors } end diff --git a/app/graphql/resolvers/projects/jira_imports_resolver.rb b/app/graphql/resolvers/projects/jira_imports_resolver.rb index e7403745bea..9f71d4f187e 100644 --- a/app/graphql/resolvers/projects/jira_imports_resolver.rb +++ b/app/graphql/resolvers/projects/jira_imports_resolver.rb @@ -8,11 +8,9 @@ module Resolvers alias_method :project, :object def resolve(**args) - return JiraImportData.none unless project&.import_data.present? - authorize!(project) - project.import_data.becomes(JiraImportData).projects + project.jira_imports end def authorized_resource?(project) diff --git a/app/graphql/types/jira_import_type.rb b/app/graphql/types/jira_import_type.rb index 01ec6184844..ccd463370b6 100644 --- a/app/graphql/types/jira_import_type.rb +++ b/app/graphql/types/jira_import_type.rb @@ -8,20 +8,12 @@ module Types graphql_name 'JiraImport' field :scheduled_at, Types::TimeType, null: true, - description: 'Timestamp of when the Jira import was created/started' + method: :created_at, + description: 'Timestamp of when the Jira import was created' field :scheduled_by, Types::UserType, null: true, description: 'User that started the Jira import' field :jira_project_key, GraphQL::STRING_TYPE, null: false, - description: 'Project key for the imported Jira project', - method: :key - - def scheduled_at - DateTime.parse(object.scheduled_at) - end - - def scheduled_by - ::Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.scheduled_by['user_id']).find - end + description: 'Project key for the imported Jira project' end # rubocop: enable Graphql/AuthorizeTypes end diff --git a/app/models/concerns/import_state/sidekiq_job_tracker.rb b/app/models/concerns/import_state/sidekiq_job_tracker.rb index 6bb07b7c06a..55f171d158d 100644 --- a/app/models/concerns/import_state/sidekiq_job_tracker.rb +++ b/app/models/concerns/import_state/sidekiq_job_tracker.rb @@ -16,7 +16,7 @@ module ImportState end def self.jid_by(project_id:, status:) - select(:jid).with_status(status).find_by(project_id: project_id) + select(:jid).where(status: status).find_by(project_id: project_id) end end end diff --git a/app/models/jira_import_data.rb b/app/models/jira_import_data.rb deleted file mode 100644 index b39ca7290be..00000000000 --- a/app/models/jira_import_data.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -class JiraImportData < ProjectImportData - JiraProjectDetails = Struct.new(:key, :scheduled_at, :scheduled_by) - - FORCE_IMPORT_KEY = 'force-import' - - def projects - return [] unless data - - projects = data.dig('jira', 'projects')&.map do |p| - JiraProjectDetails.new(p['key'], p['scheduled_at'], p['scheduled_by']) - end - - projects&.sort_by { |jp| jp.scheduled_at } || [] - end - - def <<(project) - self.data ||= { 'jira' => { 'projects' => [] } } - self.data['jira'] ||= { 'projects' => [] } - self.data['jira']['projects'] = [] if data['jira']['projects'].blank? || !data['jira']['projects'].is_a?(Array) - - self.data['jira']['projects'] << project.to_h - self.data.deep_stringify_keys! - end - - def force_import! - self.data ||= {} - self.data.deep_merge!({ 'jira' => { FORCE_IMPORT_KEY => true } }) - self.data.deep_stringify_keys! - end - - def force_import? - !!data&.dig('jira', FORCE_IMPORT_KEY) && !projects.blank? - end - - def finish_import! - return if data&.dig('jira', FORCE_IMPORT_KEY).nil? - - data['jira'].delete(FORCE_IMPORT_KEY) - end - - def current_project - projects.last - end -end diff --git a/app/models/jira_import_state.rb b/app/models/jira_import_state.rb index 713feec013f..ec1b8f03d36 100644 --- a/app/models/jira_import_state.rb +++ b/app/models/jira_import_state.rb @@ -22,6 +22,8 @@ class JiraImportState < ApplicationRecord message: _('Cannot have multiple Jira imports running at the same time') } + alias_method :scheduled_by, :user + state_machine :status, initial: :initial do event :schedule do transition initial: :scheduled @@ -46,6 +48,11 @@ class JiraImportState < ApplicationRecord end end + before_transition any => :finished do |state, _| + InternalId.flush_records!(project: state.project) + state.project.update_project_counter_caches + end + after_transition any => :finished do |state, _| if state.jid.present? Gitlab::SidekiqStatus.unset(state.jid) @@ -67,4 +74,8 @@ class JiraImportState < ApplicationRecord def in_progress? scheduled? || started? end + + def non_initial? + !initial? + end end diff --git a/app/models/project.rb b/app/models/project.rb index 15b8d5db214..1f968cdfad1 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -859,9 +859,7 @@ class Project < ApplicationRecord end def jira_import_status - return import_status if jira_force_import? - - import_data&.becomes(JiraImportData)&.projects.blank? ? 'none' : 'finished' + latest_jira_import&.status || 'initial' end def human_import_status_name @@ -875,8 +873,6 @@ class Project < ApplicationRecord elsif gitlab_project_import? # Do not retry on Import/Export until https://gitlab.com/gitlab-org/gitlab-foss/issues/26189 is solved. RepositoryImportWorker.set(retry: false).perform_async(self.id) - elsif jira_import? - Gitlab::JiraImport::Stage::StartImportWorker.perform_async(self.id) else RepositoryImportWorker.perform_async(self.id) end @@ -909,7 +905,7 @@ class Project < ApplicationRecord # This method is overridden in EE::Project model def remove_import_data - import_data&.destroy unless jira_import? + import_data&.destroy end def ci_config_path=(value) @@ -972,11 +968,7 @@ class Project < ApplicationRecord end def jira_import? - import_type == 'jira' && Feature.enabled?(:jira_issue_import, self) - end - - def jira_force_import? - jira_import? && import_data&.becomes(JiraImportData)&.force_import? + import_type == 'jira' && latest_jira_import.present? && Feature.enabled?(:jira_issue_import, self) end def gitlab_project_import? diff --git a/app/services/groups/import_export/export_service.rb b/app/services/groups/import_export/export_service.rb index 0bf54844430..86e2eeda21f 100644 --- a/app/services/groups/import_export/export_service.rb +++ b/app/services/groups/import_export/export_service.rb @@ -10,9 +10,15 @@ module Groups @shared = @params[:shared] || Gitlab::ImportExport::Shared.new(@group) end + def async_execute + GroupExportWorker.perform_async(@current_user.id, @group.id, @params) + end + def execute validate_user_permissions + remove_existing_export! if @group.export_file_exists? + save! ensure cleanup @@ -30,6 +36,13 @@ module Groups end end + def remove_existing_export! + import_export_upload = @group.import_export_upload + + import_export_upload.remove_export_file! + import_export_upload.save + end + def save! if savers.all?(&:save) notify_success diff --git a/app/services/jira_import/start_import_service.rb b/app/services/jira_import/start_import_service.rb index 91a7956e585..fbbd2d883f0 100644 --- a/app/services/jira_import/start_import_service.rb +++ b/app/services/jira_import/start_import_service.rb @@ -20,23 +20,27 @@ module JiraImport private def create_and_schedule_import - import_data = project.create_or_update_import_data(data: {}).becomes(JiraImportData) - jira_project_details = JiraImportData::JiraProjectDetails.new( - jira_project_key, - Time.now.strftime('%Y-%m-%d %H:%M:%S'), - { user_id: user.id, name: user.name } - ) - import_data << jira_project_details - import_data.force_import! - + jira_import = build_jira_import project.import_type = 'jira' - project.import_state.schedule if project.save! + project.save! && jira_import.schedule! - ServiceResponse.success(payload: { import_data: import_data } ) + ServiceResponse.success(payload: { import_data: jira_import } ) rescue => ex # in case project.save! raises an erorr Gitlab::ErrorTracking.track_exception(ex, project_id: project.id) build_error_response(ex.message) + jira_import.do_fail! + end + + def build_jira_import + project.jira_imports.build( + user: user, + jira_project_key: jira_project_key, + # we do not have the jira_project_name or jira_project_xid yet so just set a mock value, + # we will once https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28190 + jira_project_name: jira_project_key, + jira_project_xid: 0 + ) end def validate @@ -48,18 +52,11 @@ module JiraImport end def build_error_response(message) - import_data = JiraImportData.new(project: project) - import_data.errors.add(:base, message) - ServiceResponse.error( - message: import_data.errors.full_messages.to_sentence, - http_status: 400, - payload: { import_data: import_data } - ) + ServiceResponse.error(message: message, http_status: 400) end def import_in_progress? - import_state = project.import_state || project.create_import_state - import_state.in_progress? + project.latest_jira_import&.in_progress? end end end diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index 2efb304b397..6fc06030d7a 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -30,7 +30,7 @@ = _('Groups') - if dashboard_nav_link?(:activity) = nav_link(path: 'dashboard#activity') do - = link_to activity_dashboard_path do + = link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity' do = _('Activity') - if dashboard_nav_link?(:milestones) diff --git a/app/workers/concerns/gitlab/jira_import/import_worker.rb b/app/workers/concerns/gitlab/jira_import/import_worker.rb index 7cc650bfc29..169d3797b88 100644 --- a/app/workers/concerns/gitlab/jira_import/import_worker.rb +++ b/app/workers/concerns/gitlab/jira_import/import_worker.rb @@ -28,7 +28,7 @@ module Gitlab return false unless project return false if Feature.disabled?(:jira_issue_import, project) - project.import_state.started? + project.latest_jira_import&.started? end end end diff --git a/app/workers/gitlab/jira_import/advance_stage_worker.rb b/app/workers/gitlab/jira_import/advance_stage_worker.rb index c83a57bcd83..c3a64669c60 100644 --- a/app/workers/gitlab/jira_import/advance_stage_worker.rb +++ b/app/workers/gitlab/jira_import/advance_stage_worker.rb @@ -17,7 +17,7 @@ module Gitlab }.freeze def find_import_state(project_id) - ProjectImportState.jid_by(project_id: project_id, status: :started) + JiraImportState.jid_by(project_id: project_id, status: :started) end private diff --git a/app/workers/gitlab/jira_import/stage/finish_import_worker.rb b/app/workers/gitlab/jira_import/stage/finish_import_worker.rb index f053037e78a..1d57b77ac7e 100644 --- a/app/workers/gitlab/jira_import/stage/finish_import_worker.rb +++ b/app/workers/gitlab/jira_import/stage/finish_import_worker.rb @@ -9,11 +9,8 @@ module Gitlab private def import(project) - project.after_import - ensure JiraImport.cache_cleanup(project.id) - project.import_data.becomes(JiraImportData).finish_import! - project.import_data.save! + project.latest_jira_import&.finish! end end end diff --git a/app/workers/gitlab/jira_import/stage/import_attachments_worker.rb b/app/workers/gitlab/jira_import/stage/import_attachments_worker.rb index 3b209a279b5..905ef3efa67 100644 --- a/app/workers/gitlab/jira_import/stage/import_attachments_worker.rb +++ b/app/workers/gitlab/jira_import/stage/import_attachments_worker.rb @@ -13,7 +13,7 @@ module Gitlab # new job waiter will have zero jobs_remaining by default, so it will just pass on to next stage fake_waiter = JobWaiter.new - project.import_state.refresh_jid_expiration + project.latest_jira_import.refresh_jid_expiration Gitlab::JiraImport::AdvanceStageWorker.perform_async(project.id, { fake_waiter.key => fake_waiter.jobs_remaining }, :notes) end end diff --git a/app/workers/gitlab/jira_import/stage/import_issues_worker.rb b/app/workers/gitlab/jira_import/stage/import_issues_worker.rb index 7e257afc4d9..7a5eb6c1e3a 100644 --- a/app/workers/gitlab/jira_import/stage/import_issues_worker.rb +++ b/app/workers/gitlab/jira_import/stage/import_issues_worker.rb @@ -11,7 +11,7 @@ module Gitlab def import(project) jobs_waiter = Gitlab::JiraImport::IssuesImporter.new(project).execute - project.import_state.refresh_jid_expiration + project.latest_jira_import.refresh_jid_expiration Gitlab::JiraImport::AdvanceStageWorker.perform_async( project.id, diff --git a/app/workers/gitlab/jira_import/stage/import_notes_worker.rb b/app/workers/gitlab/jira_import/stage/import_notes_worker.rb index 9eef0d31a8c..b34e64b203b 100644 --- a/app/workers/gitlab/jira_import/stage/import_notes_worker.rb +++ b/app/workers/gitlab/jira_import/stage/import_notes_worker.rb @@ -12,7 +12,7 @@ module Gitlab # fake notes import workers for now # new job waiter will have zero jobs_remaining by default, so it will just pass on to next stage jobs_waiter = JobWaiter.new - project.import_state.refresh_jid_expiration + project.latest_jira_import.refresh_jid_expiration Gitlab::JiraImport::AdvanceStageWorker.perform_async(project.id, { jobs_waiter.key => jobs_waiter.jobs_remaining }, :finish) end diff --git a/app/workers/gitlab/jira_import/stage/start_import_worker.rb b/app/workers/gitlab/jira_import/stage/start_import_worker.rb index 80f0221c53d..1561ad90cc1 100644 --- a/app/workers/gitlab/jira_import/stage/start_import_worker.rb +++ b/app/workers/gitlab/jira_import/stage/start_import_worker.rb @@ -16,7 +16,7 @@ module Gitlab return unless start_import - Gitlab::Import::SetAsyncJid.set_jid(project.import_state) + Gitlab::Import::SetAsyncJid.set_jid(project.latest_jira_import) Gitlab::JiraImport::Stage::ImportLabelsWorker.perform_async(project.id) end @@ -26,14 +26,13 @@ module Gitlab def start_import return false unless project return false if Feature.disabled?(:jira_issue_import, project) - return false unless project.jira_force_import? - return true if start(project.import_state) + return true if start(project.latest_jira_import) Gitlab::Import::Logger.info( { project_id: project.id, project_path: project.full_path, - state: project&.import_status, + state: project&.jira_import_status, message: 'inconsistent state while importing' } ) -- cgit v1.2.1