Commit 565a2d73 authored by Sean McGivern's avatar Sean McGivern

Merge branch '35580-cannot-import-project-with-milestones' into 'master'

fix the import :milestone from adding the group_id

Closes #35580

See merge request gitlab-org/gitlab-ce!14657
parents 143ace07 de025ad2
---
title: Fix the project import with issues and milestones
merge_request: 14657
author:
type: fixed
...@@ -2,7 +2,7 @@ module Gitlab ...@@ -2,7 +2,7 @@ module Gitlab
module ImportExport module ImportExport
class ProjectTreeRestorer class ProjectTreeRestorer
# Relations which cannot have both group_id and project_id at the same time # Relations which cannot have both group_id and project_id at the same time
RESTRICT_PROJECT_AND_GROUP = %i(milestones).freeze RESTRICT_PROJECT_AND_GROUP = %i(milestone milestones).freeze
def initialize(user:, shared:, project:) def initialize(user:, shared:, project:)
@path = File.join(shared.export_path, 'project.json') @path = File.join(shared.export_path, 'project.json')
......
...@@ -37,7 +37,7 @@ module Gitlab ...@@ -37,7 +37,7 @@ module Gitlab
def initialize(relation_sym:, relation_hash:, members_mapper:, user:, project:) def initialize(relation_sym:, relation_hash:, members_mapper:, user:, project:)
@relation_name = OVERRIDES[relation_sym] || relation_sym @relation_name = OVERRIDES[relation_sym] || relation_sym
@relation_hash = relation_hash.except('noteable_id').merge('project_id' => project.id) @relation_hash = relation_hash.except('noteable_id')
@members_mapper = members_mapper @members_mapper = members_mapper
@user = user @user = user
@project = project @project = project
...@@ -58,22 +58,21 @@ module Gitlab ...@@ -58,22 +58,21 @@ module Gitlab
private private
def setup_models def setup_models
if @relation_name == :notes case @relation_name
set_note_author when :merge_request_diff then setup_st_diff_commits
when :merge_request_diff_files then setup_diff
# attachment is deprecated and note uploads are handled by Markdown uploader when :notes then setup_note
@relation_hash['attachment'] = nil when :project_label, :project_labels then setup_label
when :milestone, :milestones then setup_milestone
else
@relation_hash['project_id'] = @project.id
end end
update_user_references update_user_references
update_project_references update_project_references
handle_group_label if group_label?
reset_tokens! reset_tokens!
remove_encrypted_attributes! remove_encrypted_attributes!
set_st_diff_commits if @relation_name == :merge_request_diff
set_diff if @relation_name == :merge_request_diff_files
end end
def update_user_references def update_user_references
...@@ -84,6 +83,12 @@ module Gitlab ...@@ -84,6 +83,12 @@ module Gitlab
end end
end end
def setup_note
set_note_author
# attachment is deprecated and note uploads are handled by Markdown uploader
@relation_hash['attachment'] = nil
end
# Sets the author for a note. If the user importing the project # Sets the author for a note. If the user importing the project
# has admin access, an actual mapping with new project members # has admin access, an actual mapping with new project members
# will be used. Otherwise, a note stating the original author name # will be used. Otherwise, a note stating the original author name
...@@ -136,11 +141,9 @@ module Gitlab ...@@ -136,11 +141,9 @@ module Gitlab
@relation_hash['target_project_id'] && @relation_hash['target_project_id'] == @relation_hash['source_project_id'] @relation_hash['target_project_id'] && @relation_hash['target_project_id'] == @relation_hash['source_project_id']
end end
def group_label? def setup_label
@relation_hash['type'] == 'GroupLabel' return unless @relation_hash['type'] == 'GroupLabel'
end
def handle_group_label
# If there's no group, move the label to a project label # If there's no group, move the label to a project label
if @relation_hash['group_id'] if @relation_hash['group_id']
@relation_hash['project_id'] = nil @relation_hash['project_id'] = nil
...@@ -150,6 +153,14 @@ module Gitlab ...@@ -150,6 +153,14 @@ module Gitlab
end end
end end
def setup_milestone
if @relation_hash['group_id']
@relation_hash['group_id'] = @project.group.id
else
@relation_hash['project_id'] = @project.id
end
end
def reset_tokens! 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.to_s)
...@@ -198,14 +209,14 @@ module Gitlab ...@@ -198,14 +209,14 @@ module Gitlab
relation_class: relation_class) relation_class: relation_class)
end end
def set_st_diff_commits def setup_st_diff_commits
@relation_hash['st_diffs'] = @relation_hash.delete('utf8_st_diffs') @relation_hash['st_diffs'] = @relation_hash.delete('utf8_st_diffs')
HashUtil.deep_symbolize_array!(@relation_hash['st_diffs']) HashUtil.deep_symbolize_array!(@relation_hash['st_diffs'])
HashUtil.deep_symbolize_array_with_date!(@relation_hash['st_commits']) HashUtil.deep_symbolize_array_with_date!(@relation_hash['st_commits'])
end end
def set_diff def setup_diff
@relation_hash['diff'] = @relation_hash.delete('utf8_diff') @relation_hash['diff'] = @relation_hash.delete('utf8_diff')
end end
...@@ -250,7 +261,13 @@ module Gitlab ...@@ -250,7 +261,13 @@ module Gitlab
end end
def find_or_create_object! def find_or_create_object!
finder_attributes = @relation_name == :group_label ? %w[title group_id] : %w[title project_id] finder_attributes = if @relation_name == :group_label
%w[title group_id]
elsif parsed_relation_hash['project_id']
%w[title project_id]
else
%w[title group_id]
end
finder_hash = parsed_relation_hash.slice(*finder_attributes) finder_hash = parsed_relation_hash.slice(*finder_attributes)
if label? if label?
......
{
"description": "Nisi et repellendus ut enim quo accusamus vel magnam.",
"visibility_level": 10,
"archived": false,
"milestones": [
{
"id": 1,
"title": "Project milestone",
"project_id": 8,
"description": "Project-level milestone",
"due_date": null,
"created_at": "2016-06-14T15:02:04.415Z",
"updated_at": "2016-06-14T15:02:04.415Z",
"state": "active",
"iid": 1,
"group_id": null
}
],
"labels": [
{
"id": 2,
"title": "project label",
"color": "#428bca",
"project_id": 8,
"created_at": "2016-07-22T08:55:44.161Z",
"updated_at": "2016-07-22T08:55:44.161Z",
"template": false,
"description": "",
"type": "ProjectLabel",
"priorities": [
{
"id": 1,
"project_id": 5,
"label_id": 1,
"priority": 1,
"created_at": "2016-10-18T09:35:43.338Z",
"updated_at": "2016-10-18T09:35:43.338Z"
}
]
}
],
"issues": [
{
"id": 1,
"title": "Fugiat est minima quae maxime non similique.",
"assignee_id": null,
"project_id": 8,
"author_id": 1,
"created_at": "2017-07-07T18:13:01.138Z",
"updated_at": "2017-08-15T18:37:40.807Z",
"branch_name": null,
"description": "Quam totam fuga numquam in eveniet.",
"state": "opened",
"iid": 1,
"updated_by_id": 1,
"confidential": false,
"deleted_at": null,
"due_date": null,
"moved_to_id": null,
"lock_version": null,
"time_estimate": 0,
"closed_at": null,
"last_edited_at": null,
"last_edited_by_id": null,
"group_milestone_id": null,
"milestone": {
"id": 1,
"title": "Project milestone",
"project_id": 8,
"description": "Project-level milestone",
"due_date": null,
"created_at": "2016-06-14T15:02:04.415Z",
"updated_at": "2016-06-14T15:02:04.415Z",
"state": "active",
"iid": 1,
"group_id": null
},
"label_links": [
{
"id": 11,
"label_id": 6,
"target_id": 1,
"target_type": "Issue",
"created_at": "2017-08-15T18:37:40.795Z",
"updated_at": "2017-08-15T18:37:40.795Z",
"label": {
"id": 6,
"title": "group label",
"color": "#A8D695",
"project_id": null,
"created_at": "2017-08-15T18:37:19.698Z",
"updated_at": "2017-08-15T18:37:19.698Z",
"template": false,
"description": "",
"group_id": 5,
"type": "GroupLabel",
"priorities": []
}
},
{
"id": 11,
"label_id": 2,
"target_id": 1,
"target_type": "Issue",
"created_at": "2017-08-15T18:37:40.795Z",
"updated_at": "2017-08-15T18:37:40.795Z",
"label": {
"id": 6,
"title": "project label",
"color": "#A8D695",
"project_id": null,
"created_at": "2017-08-15T18:37:19.698Z",
"updated_at": "2017-08-15T18:37:19.698Z",
"template": false,
"description": "",
"group_id": 5,
"type": "ProjectLabel",
"priorities": []
}
}
]
},
{
"id": 2,
"title": "Fugiat est minima quae maxime non similique.",
"assignee_id": null,
"project_id": 8,
"author_id": 1,
"created_at": "2017-07-07T18:13:01.138Z",
"updated_at": "2017-08-15T18:37:40.807Z",
"branch_name": null,
"description": "Quam totam fuga numquam in eveniet.",
"state": "opened",
"iid": 2,
"updated_by_id": 1,
"confidential": false,
"deleted_at": null,
"due_date": null,
"moved_to_id": null,
"lock_version": null,
"time_estimate": 0,
"closed_at": null,
"last_edited_at": null,
"last_edited_by_id": null,
"group_milestone_id": null,
"milestone": {
"id": 2,
"title": "A group milestone",
"description": "Group-level milestone",
"due_date": null,
"created_at": "2016-06-14T15:02:04.415Z",
"updated_at": "2016-06-14T15:02:04.415Z",
"state": "active",
"iid": 1,
"group_id": 100
},
"label_links": [
{
"id": 11,
"label_id": 2,
"target_id": 1,
"target_type": "Issue",
"created_at": "2017-08-15T18:37:40.795Z",
"updated_at": "2017-08-15T18:37:40.795Z",
"label": {
"id": 2,
"title": "project label",
"color": "#A8D695",
"project_id": null,
"created_at": "2017-08-15T18:37:19.698Z",
"updated_at": "2017-08-15T18:37:19.698Z",
"template": false,
"description": "",
"group_id": 5,
"type": "ProjectLabel",
"priorities": []
}
}
]
}
],
"snippets": [
],
"hooks": [
]
}
...@@ -5,9 +5,9 @@ ...@@ -5,9 +5,9 @@
"milestones": [ "milestones": [
{ {
"id": 1, "id": 1,
"title": "test milestone", "title": "Project milestone",
"project_id": 8, "project_id": 8,
"description": "test milestone", "description": "Project-level milestone",
"due_date": null, "due_date": null,
"created_at": "2016-06-14T15:02:04.415Z", "created_at": "2016-06-14T15:02:04.415Z",
"updated_at": "2016-06-14T15:02:04.415Z", "updated_at": "2016-06-14T15:02:04.415Z",
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
"labels": [ "labels": [
{ {
"id": 2, "id": 2,
"title": "test2", "title": "A project label",
"color": "#428bca", "color": "#428bca",
"project_id": 8, "project_id": 8,
"created_at": "2016-07-22T08:55:44.161Z", "created_at": "2016-07-22T08:55:44.161Z",
...@@ -63,28 +63,19 @@ ...@@ -63,28 +63,19 @@
"last_edited_at": null, "last_edited_at": null,
"last_edited_by_id": null, "last_edited_by_id": null,
"group_milestone_id": null, "group_milestone_id": null,
"milestone": {
"id": 1,
"title": "Project milestone",
"project_id": 8,
"description": "Project-level milestone",
"due_date": null,
"created_at": "2016-06-14T15:02:04.415Z",
"updated_at": "2016-06-14T15:02:04.415Z",
"state": "active",
"iid": 1,
"group_id": null
},
"label_links": [ "label_links": [
{
"id": 11,
"label_id": 6,
"target_id": 1,
"target_type": "Issue",
"created_at": "2017-08-15T18:37:40.795Z",
"updated_at": "2017-08-15T18:37:40.795Z",
"label": {
"id": 6,
"title": "group label",
"color": "#A8D695",
"project_id": null,
"created_at": "2017-08-15T18:37:19.698Z",
"updated_at": "2017-08-15T18:37:19.698Z",
"template": false,
"description": "",
"group_id": 5,
"type": "GroupLabel",
"priorities": []
}
},
{ {
"id": 11, "id": 11,
"label_id": 2, "label_id": 2,
...@@ -94,14 +85,14 @@ ...@@ -94,14 +85,14 @@
"updated_at": "2017-08-15T18:37:40.795Z", "updated_at": "2017-08-15T18:37:40.795Z",
"label": { "label": {
"id": 6, "id": 6,
"title": "project label", "title": "Another project label",
"color": "#A8D695", "color": "#A8D695",
"project_id": null, "project_id": null,
"created_at": "2017-08-15T18:37:19.698Z", "created_at": "2017-08-15T18:37:19.698Z",
"updated_at": "2017-08-15T18:37:19.698Z", "updated_at": "2017-08-15T18:37:19.698Z",
"template": false, "template": false,
"description": "", "description": "",
"group_id": 5, "group_id": null,
"type": "ProjectLabel", "type": "ProjectLabel",
"priorities": [] "priorities": []
} }
...@@ -109,10 +100,6 @@ ...@@ -109,10 +100,6 @@
] ]
} }
], ],
"snippets": [ "snippets": [],
"hooks": []
],
"hooks": [
]
} }
...@@ -24,7 +24,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do ...@@ -24,7 +24,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
context 'JSON' do context 'JSON' do
it 'restores models based on JSON' do it 'restores models based on JSON' do
expect(@restored_project_json).to be true expect(@restored_project_json).to be_truthy
end end
it 'restore correct project features' do it 'restore correct project features' do
...@@ -182,6 +182,53 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do ...@@ -182,6 +182,53 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
end end
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.first.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 'has issue with group label and project label' do
labels = project.issues.first.labels
expect(labels.where(type: "ProjectLabel").count).to eq(results.fetch(:first_issue_labels, 0))
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))
end
it 'has group milestone' do
expect(project.group.milestones.size).to eq(results.fetch(:milestones, 0))
end
it 'has issue with group label' do
labels = project.issues.first.labels
expect(labels.where(type: "GroupLabel").count).to eq(results.fetch(:first_issue_labels, 0))
end
end
context 'Light JSON' do context 'Light JSON' do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: "", project_path: 'path') } let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: "", project_path: 'path') }
...@@ -190,33 +237,45 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do ...@@ -190,33 +237,45 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
let(:restored_project_json) { project_tree_restorer.restore } let(:restored_project_json) { project_tree_restorer.restore }
before do before do
project_tree_restorer.instance_variable_set(:@path, "spec/lib/gitlab/import_export/project.light.json")
allow(shared).to receive(:export_path).and_return('spec/lib/gitlab/import_export/') allow(shared).to receive(:export_path).and_return('spec/lib/gitlab/import_export/')
end end
context 'project.json file access check' do context 'with a simple project' do
it 'does not read a symlink' do before do
Dir.mktmpdir do |tmpdir| project_tree_restorer.instance_variable_set(:@path, "spec/lib/gitlab/import_export/project.light.json")
setup_symlink(tmpdir, 'project.json')
allow(shared).to receive(:export_path).and_call_original restored_project_json
end
it_behaves_like 'restores project correctly',
issues: 1,
labels: 1,
milestones: 1,
first_issue_labels: 1
restored_project_json context 'project.json file access check' do
it 'does not read a symlink' do
Dir.mktmpdir do |tmpdir|
setup_symlink(tmpdir, 'project.json')
allow(shared).to receive(:export_path).and_call_original
expect(shared.errors.first).to be_nil restored_project_json
expect(shared.errors).to be_empty
end
end end
end end
end
context 'when there is an existing build with build token' do context 'when there is an existing build with build token' do
it 'restores project json correctly' do before do
create(:ci_build, token: 'abcd') create(:ci_build, token: 'abcd')
end
expect(restored_project_json).to be true it_behaves_like 'restores project successfully'
end end
end end
context 'with group' do context 'with a project that has a group' do
let!(:project) do let!(:project) do
create(:project, create(:project,
:builds_disabled, :builds_disabled,
...@@ -227,43 +286,22 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do ...@@ -227,43 +286,22 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
end end
before do before do
project_tree_restorer.instance_variable_set(:@path, "spec/lib/gitlab/import_export/project.light.json") project_tree_restorer.instance_variable_set(:@path, "spec/lib/gitlab/import_export/project.group.json")
restored_project_json restored_project_json
end end
it 'correctly restores project' do it_behaves_like 'restores project successfully'
expect(restored_project_json).to be_truthy it_behaves_like 'restores project correctly',
expect(shared.errors).to be_empty issues: 2,
end labels: 1,
milestones: 1,
first_issue_labels: 1
it 'has labels' do it_behaves_like 'restores group correctly',
expect(project.labels.count).to eq(2) labels: 1,
end milestones: 1,
first_issue_labels: 1
it 'creates group label' do
expect(project.group.labels.count).to eq(1)
end
it 'has label priorities' do
expect(project.labels.first.priorities).not_to be_empty
end
it 'has milestones' do
expect(project.milestones.count).to eq(1)
end
it 'has issue' do
expect(project.issues.count).to eq(1)
expect(project.issues.first.labels.count).to eq(2)
end
it 'has issue with group label and project label' do
labels = project.issues.first.labels
expect(labels.where(type: "GroupLabel").count).to eq(1)
expect(labels.where(type: "ProjectLabel").count).to eq(1)
end
end end
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment