summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke Duncalfe <lduncalfe@eml.cc>2019-07-16 16:40:37 +1200
committerLuke Duncalfe <lduncalfe@eml.cc>2019-09-12 17:03:26 +1200
commiteff3e69c6a9bf806e7bdc534af0ac4b2944a0672 (patch)
tree98863eeb5ff1c00baba90481b0c72e165cb22433
parent7101ea724ad384bbce5e469f8052eed028cc4695 (diff)
downloadgitlab-ce-11090-export-design-management-data-ce.tar.gz
CE-specific changes to allow exporting Design data11090-export-design-management-data-ce
Moving a group of shared_examples from project_tree_restorer_spec to its own file in spec/support/shared_examples in order for these examples to be reused in an EE-specific test. https://gitlab.com/gitlab-org/gitlab-ee/issues/11090
-rw-r--r--app/helpers/export_helper.rb17
-rw-r--r--app/views/projects/_export.html.haml8
-rw-r--r--db/migrate/20190807023052_design_issue_id_nullable.rb11
-rw-r--r--db/schema.rb2
-rw-r--r--doc/user/project/issues/design_management.md2
-rw-r--r--doc/user/project/settings/import_export.md1
-rw-r--r--lib/gitlab/import_export/group_project_object_builder.rb63
-rw-r--r--lib/gitlab/import_export/import_export.yml13
-rw-r--r--lib/gitlab/import_export/relation_factory.rb22
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml13
-rw-r--r--spec/lib/gitlab/import_export/project_tree_restorer_spec.rb44
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml13
-rw-r--r--spec/support/shared_examples/lib/gitlab/import_export/project_tree_restorer_shared_examples.rb37
13 files changed, 180 insertions, 66 deletions
diff --git a/app/helpers/export_helper.rb b/app/helpers/export_helper.rb
new file mode 100644
index 00000000000..d03fa6eadb2
--- /dev/null
+++ b/app/helpers/export_helper.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module ExportHelper
+ # An EE-overwriteable list of descriptions
+ def project_export_descriptions
+ [
+ _('Project and wiki repositories'),
+ _('Project uploads'),
+ _('Project configuration, including services'),
+ _('Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities'),
+ _('LFS objects'),
+ _('Issue Boards')
+ ]
+ end
+end
+
+ExportHelper.prepend_if_ee('EE::ExportHelper')
diff --git a/app/views/projects/_export.html.haml b/app/views/projects/_export.html.haml
index e42772c2dd9..80105a61cb8 100644
--- a/app/views/projects/_export.html.haml
+++ b/app/views/projects/_export.html.haml
@@ -10,12 +10,8 @@
%p.append-bottom-0
%p= _('The following items will be exported:')
%ul
- %li= _('Project and wiki repositories')
- %li= _('Project uploads')
- %li= _('Project configuration, including services')
- %li= _('Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities')
- %li= _('LFS objects')
- %li= _('Issue Boards')
+ - project_export_descriptions.each do |desc|
+ %li= desc
%p= _('The following items will NOT be exported:')
%ul
%li= _('Job traces and artifacts')
diff --git a/db/migrate/20190807023052_design_issue_id_nullable.rb b/db/migrate/20190807023052_design_issue_id_nullable.rb
new file mode 100644
index 00000000000..4429e23d520
--- /dev/null
+++ b/db/migrate/20190807023052_design_issue_id_nullable.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class DesignIssueIdNullable < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ change_column_null :design_management_designs, :issue_id, true
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 3906976d296..c6fc2ffd688 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -1173,7 +1173,7 @@ ActiveRecord::Schema.define(version: 2019_09_10_000130) do
create_table "design_management_designs", force: :cascade do |t|
t.integer "project_id", null: false
- t.integer "issue_id", null: false
+ t.integer "issue_id"
t.string "filename", null: false
t.index ["issue_id", "filename"], name: "index_design_management_designs_on_issue_id_and_filename", unique: true
t.index ["project_id"], name: "index_design_management_designs_on_project_id"
diff --git a/doc/user/project/issues/design_management.md b/doc/user/project/issues/design_management.md
index 1324a90e00b..aaf68b06626 100644
--- a/doc/user/project/issues/design_management.md
+++ b/doc/user/project/issues/design_management.md
@@ -39,8 +39,6 @@ to be enabled:
The [`svg` extension is not yet supported](https://gitlab.com/gitlab-org/gitlab-ee/issues/12771).
- Design uploads are limited to 10 files at a time.
- [Designs cannot yet be deleted](https://gitlab.com/gitlab-org/gitlab-ee/issues/11089).
-- Design Management is
- [not yet supported in the project export](https://gitlab.com/gitlab-org/gitlab-ee/issues/11090).
- Design Management data
[isn't deleted when a project is destroyed](https://gitlab.com/gitlab-org/gitlab-ee/issues/13429) yet.
- Design Management data [won't be moved](https://gitlab.com/gitlab-org/gitlab-ee/issues/13426)
diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md
index 9c1a31fb7c3..9dbd7ba07a3 100644
--- a/doc/user/project/settings/import_export.md
+++ b/doc/user/project/settings/import_export.md
@@ -65,6 +65,7 @@ The following items will be exported:
- Project configuration, including services
- Issues with comments, merge requests with diffs and comments, labels, milestones, snippets,
and other project entities
+- Design Management files and data **(PREMIUM)**
- LFS objects
- Issue boards
diff --git a/lib/gitlab/import_export/group_project_object_builder.rb b/lib/gitlab/import_export/group_project_object_builder.rb
index 1c62591ed5a..16b063a0e2d 100644
--- a/lib/gitlab/import_export/group_project_object_builder.rb
+++ b/lib/gitlab/import_export/group_project_object_builder.rb
@@ -26,30 +26,60 @@ module Gitlab
end
def find
- find_object || @klass.create(project_attributes)
+ find_object || klass.create(project_attributes)
end
private
+ attr_reader :klass, :attributes, :group, :project
+
def find_object
- @klass.where(where_clause).first
+ klass.where(where_clause).first
end
def where_clause
- @attributes.slice('title').map do |key, value|
- scope_clause = table[:project_id].eq(@project.id)
- scope_clause = scope_clause.or(table[:group_id].eq(@group.id)) if @group
+ where_clauses.reduce(:and)
+ end
+
+ def where_clauses
+ [
+ where_clause_base,
+ where_clause_for_title,
+ where_clause_for_klass
+ ].compact
+ end
+
+ # Returns Arel clause `"{table_name}"."project_id" = {project.id}`
+ # or, if group is present:
+ # `"{table_name}"."project_id" = {project.id} OR "{table_name}"."group_id" = {group.id}`
+ def where_clause_base
+ clause = table[:project_id].eq(project.id)
+ clause = clause.or(table[:group_id].eq(group.id)) if group
+
+ clause
+ end
- table[key].eq(value).and(scope_clause)
- end.reduce(:or)
+ # Returns Arel clause `"{table_name}"."title" = '{attributes['title']}'`
+ # if attributes has 'title key, otherwise `nil`.
+ def where_clause_for_title
+ attrs_to_arel(attributes.slice('title'))
+ end
+
+ # Returns Arel clause:
+ # `"{table_name}"."{attrs.keys[0]}" = '{attrs.values[0]} AND {table_name}"."{attrs.keys[1]}" = '{attrs.values[1]}"`
+ # from the given Hash of attributes.
+ def attrs_to_arel(attrs)
+ attrs.map do |key, value|
+ table[key].eq(value)
+ end.reduce(:and)
end
def table
- @table ||= @klass.arel_table
+ @table ||= klass.arel_table
end
def project_attributes
- @attributes.except('group').tap do |atts|
+ attributes.except('group').tap do |atts|
if label?
atts['type'] = 'ProjectLabel' # Always create project labels
elsif milestone?
@@ -60,15 +90,17 @@ module Gitlab
claim_iid
end
end
+
+ atts['importing'] = true if klass.ancestors.include?(Importable)
end
end
def label?
- @klass == Label
+ klass == Label
end
def milestone?
- @klass == Milestone
+ klass == Milestone
end
# If an existing group milestone used the IID
@@ -79,7 +111,7 @@ module Gitlab
def claim_iid
# The milestone has to be a group milestone, as it's the only case where
# we set the IID as the maximum. The rest of them are fixed.
- milestone = @project.milestones.find_by(iid: @attributes['iid'])
+ milestone = project.milestones.find_by(iid: attributes['iid'])
return unless milestone
@@ -87,6 +119,13 @@ module Gitlab
milestone.ensure_project_iid!
milestone.save!
end
+
+ protected
+
+ # Returns Arel clause for a particular model or `nil`.
+ def where_clause_for_klass
+ # no-op
+ end
end
end
end
diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml
index 511b702553e..3742dca0016 100644
--- a/lib/gitlab/import_export/import_export.yml
+++ b/lib/gitlab/import_export/import_export.yml
@@ -246,7 +246,16 @@ preloads:
ee:
tree:
project:
- protected_branches:
+ - issues:
+ - designs:
+ - notes:
+ - :author
+ - events:
+ - :push_event_payload
+ - design_versions:
+ - design_versions:
+ - :design # Duplicate export of issues.designs in order to link the record to both Issue and DesignVersion
+ - protected_branches:
- :unprotect_access_levels
- protected_environments:
+ - protected_environments:
- :deploy_access_levels
diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb
index 0be49e27acb..96485666f24 100644
--- a/lib/gitlab/import_export/relation_factory.rb
+++ b/lib/gitlab/import_export/relation_factory.rb
@@ -32,13 +32,13 @@ module Gitlab
PROJECT_REFERENCES = %w[project_id source_project_id target_project_id].freeze
- BUILD_MODELS = %w[Ci::Build commit_status].freeze
+ BUILD_MODELS = %i[Ci::Build commit_status].freeze
IMPORTED_OBJECT_MAX_RETRIES = 5.freeze
EXISTING_OBJECT_CHECK = %i[milestone milestones label labels project_label project_labels group_label group_labels project_feature].freeze
- TOKEN_RESET_MODELS = %w[Project Namespace Ci::Trigger Ci::Build Ci::Runner ProjectHook].freeze
+ TOKEN_RESET_MODELS = %i[Project Namespace Ci::Trigger Ci::Build Ci::Runner ProjectHook].freeze
def self.create(*args)
new(*args).create
@@ -54,7 +54,7 @@ module Gitlab
end
def initialize(relation_sym:, relation_hash:, members_mapper:, user:, project:, excluded_keys: [])
- @relation_name = self.class.overrides[relation_sym] || relation_sym
+ @relation_name = self.class.overrides[relation_sym]&.to_sym || relation_sym
@relation_hash = relation_hash.except('noteable_id')
@members_mapper = members_mapper
@user = user
@@ -90,6 +90,10 @@ module Gitlab
OVERRIDES
end
+ def self.existing_object_check
+ EXISTING_OBJECT_CHECK
+ end
+
private
def setup_models
@@ -103,7 +107,7 @@ module Gitlab
update_group_references
remove_duplicate_assignees
- setup_pipeline if @relation_name == 'Ci::Pipeline'
+ setup_pipeline if @relation_name == :'Ci::Pipeline'
reset_tokens!
remove_encrypted_attributes!
@@ -182,14 +186,14 @@ module Gitlab
end
def update_group_references
- return unless EXISTING_OBJECT_CHECK.include?(@relation_name)
+ return unless self.class.existing_object_check.include?(@relation_name)
return unless @relation_hash['group_id']
@relation_hash['group_id'] = @project.namespace_id
end
def reset_tokens!
- return unless Gitlab::ImportExport.reset_tokens? && TOKEN_RESET_MODELS.include?(@relation_name.to_s)
+ return unless Gitlab::ImportExport.reset_tokens? && TOKEN_RESET_MODELS.include?(@relation_name)
# If we import/export a project to the same instance, tokens will have to be reset.
# We also have to reset them to avoid issues when the gitlab secrets file cannot be copied across.
@@ -253,7 +257,7 @@ module Gitlab
# Only find existing records to avoid mapping tables such as milestones
# Otherwise always create the record, skipping the extra SELECT clause.
@existing_or_new_object ||= begin
- if EXISTING_OBJECT_CHECK.include?(@relation_name)
+ if self.class.existing_object_check.include?(@relation_name)
attribute_hash = attribute_hash_for(['events'])
existing_object.assign_attributes(attribute_hash) if attribute_hash.any?
@@ -282,7 +286,7 @@ module Gitlab
end
def legacy_trigger?
- @relation_name == 'Ci::Trigger' && @relation_hash['owner_id'].nil?
+ @relation_name == :'Ci::Trigger' && @relation_hash['owner_id'].nil?
end
def find_or_create_object!
@@ -291,7 +295,7 @@ module Gitlab
# Can't use IDs as validation exists calling `group` or `project` attributes
finder_hash = parsed_relation_hash.tap do |hash|
hash['group'] = @project.group if relation_class.attribute_method?('group_id')
- hash['project'] = @project
+ hash['project'] = @project if relation_class.reflect_on_association(:project)
hash.delete('project_id')
end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index e496ab4cd35..480afede8f9 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -498,3 +498,16 @@ lists:
milestone_releases:
- milestone
- release
+design: &design
+- issue
+- design_versions
+- versions
+- notes
+designs: *design
+design_versions:
+- design
+- version
+versions:
+- issue
+- designs
+- design_versions
diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
index c0a9914ac55..fcc79279b6f 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -2,6 +2,8 @@ require 'spec_helper'
include ImportExport::CommonUtil
describe Gitlab::ImportExport::ProjectTreeRestorer do
+ let(:shared) { project.import_export_shared }
+
describe 'restore project tree' do
before(:context) do
# Using an admin for import, so we can check assignment of existing members
@@ -274,36 +276,6 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
end
end
- shared_examples 'restores project successfully' do
- it 'correctly restores project' do
- expect(shared.errors).to be_empty
- expect(restored_project_json).to be_truthy
- end
- end
-
- shared_examples 'restores project correctly' do |**results|
- it 'has labels' do
- expect(project.labels.size).to eq(results.fetch(:labels, 0))
- end
-
- it 'has label priorities' do
- expect(project.labels.find_by(title: 'A project label').priorities).not_to be_empty
- end
-
- it 'has milestones' do
- expect(project.milestones.size).to eq(results.fetch(:milestones, 0))
- end
-
- it 'has issues' do
- expect(project.issues.size).to eq(results.fetch(:issues, 0))
- end
-
- it 'does not set params that are excluded from import_export settings' do
- expect(project.import_type).to be_nil
- expect(project.creator_id).not_to eq 123
- end
- end
-
shared_examples 'restores group correctly' do |**results|
it 'has group label' do
expect(project.group.labels.size).to eq(results.fetch(:labels, 0))
@@ -322,7 +294,6 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
context 'Light JSON' do
let(:user) { create(:user) }
- let(:shared) { project.import_export_shared }
let!(:project) { create(:project, :builds_disabled, :issues_disabled, name: 'project', path: 'project') }
let(:project_tree_restorer) { described_class.new(user: user, shared: shared, project: project) }
let(:restored_project_json) { project_tree_restorer.restore }
@@ -341,6 +312,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
it_behaves_like 'restores project correctly',
issues: 1,
labels: 2,
+ label_with_priorities: 'A project label',
milestones: 1,
first_issue_labels: 1,
services: 1
@@ -363,7 +335,12 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
create(:ci_build, token: 'abcd')
end
- it_behaves_like 'restores project successfully'
+ it_behaves_like 'restores project correctly',
+ issues: 1,
+ labels: 2,
+ label_with_priorities: 'A project label',
+ milestones: 1,
+ first_issue_labels: 1
end
end
@@ -435,10 +412,10 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
restored_project_json
end
- it_behaves_like 'restores project successfully'
it_behaves_like 'restores project correctly',
issues: 2,
labels: 2,
+ label_with_priorities: 'A project label',
milestones: 2,
first_issue_labels: 1
@@ -534,7 +511,6 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
describe '#restored_project' do
let(:project) { create(:project) }
- let(:shared) { project.import_export_shared }
let(:tree_hash) { { 'visibility_level' => visibility } }
let(:restorer) { described_class.new(user: nil, shared: shared, project: project) }
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index e9750d23c53..a0cebe9fb57 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -729,3 +729,16 @@ ExternalPullRequest:
- target_repository
- source_sha
- target_sha
+DesignManagement::Design:
+- id
+- project_id
+- issue_id
+- filename
+DesignManagement::DesignVersion:
+- design_id
+- event
+- version_id
+DesignManagement::Version:
+- id
+- sha
+- issue_id
diff --git a/spec/support/shared_examples/lib/gitlab/import_export/project_tree_restorer_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/import_export/project_tree_restorer_shared_examples.rb
new file mode 100644
index 00000000000..f26a8554055
--- /dev/null
+++ b/spec/support/shared_examples/lib/gitlab/import_export/project_tree_restorer_shared_examples.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+# Shared examples for ProjectTreeRestorer (shared to allow the testing
+# of EE-specific features)
+RSpec.shared_examples 'restores project correctly' do |**results|
+ it 'restores the project' do
+ expect(shared.errors).to be_empty
+ expect(restored_project_json).to be_truthy
+ end
+
+ it 'has labels' do
+ labels_size = results.fetch(:labels, 0)
+
+ expect(project.labels.size).to eq(labels_size)
+ end
+
+ it 'has label priorities' do
+ label_with_priorities = results[:label_with_priorities]
+
+ if label_with_priorities
+ expect(project.labels.find_by(title: label_with_priorities).priorities).not_to be_empty
+ end
+ end
+
+ it 'has milestones' do
+ expect(project.milestones.size).to eq(results.fetch(:milestones, 0))
+ end
+
+ it 'has issues' do
+ expect(project.issues.size).to eq(results.fetch(:issues, 0))
+ end
+
+ it 'does not set params that are excluded from import_export settings' do
+ expect(project.import_type).to be_nil
+ expect(project.creator_id).not_to eq 123
+ end
+end