Commit 3fbe2a5e authored by Rémy Coutable's avatar Rémy Coutable

Merge branch 'fix/labels-milestones-import' into 'master'

Fix Importing labels and milestones associations - Import/Export

Fixes https://gitlab.com/gitlab-org/gitlab-ce/issues/19510 and https://gitlab.com/gitlab-org/gitlab-ce/issues/19447
- [x] [CHANGELOG](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CHANGELOG) entry added
- Tests
  - [x] Added for this feature/bug
  - [x] All builds are passing
- [x] Conform by the [style guides](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#style-guides)
- [x] Branch has no merge conflicts with `master` (if you do - rebase it please)
- [x] [Squashed related commits together](https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits)

See merge request !5426
Signed-off-by: default avatarRémy Coutable <remy@rymai.me>
parent 950ac084
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
v 8.10.3 (unreleased) v 8.10.3 (unreleased)
- Fix Import/Export issue importing milestones and labels not associated properly. !5426
v 8.10.2 v 8.10.2
- User can now search branches by name. !5144 - User can now search branches by name. !5144
......
class LabelLink < ActiveRecord::Base class LabelLink < ActiveRecord::Base
include Importable
belongs_to :target, polymorphic: true belongs_to :target, polymorphic: true
belongs_to :label belongs_to :label
validates :target, presence: true validates :target, presence: true, unless: :importing?
validates :label, presence: true validates :label, presence: true, unless: :importing?
end end
...@@ -1238,6 +1238,16 @@ class Project < ActiveRecord::Base ...@@ -1238,6 +1238,16 @@ class Project < ActiveRecord::Base
authorized_for_user_by_shared_projects?(user, min_access_level) authorized_for_user_by_shared_projects?(user, min_access_level)
end end
def append_or_update_attribute(name, value)
old_values = public_send(name.to_s)
if Project.reflect_on_association(name).try(:macro) == :has_many && old_values.any?
update_attribute(name, old_values + value)
else
update_attribute(name, value)
end
end
private private
def authorized_for_user_by_group?(user, min_access_level) def authorized_for_user_by_group?(user, min_access_level)
......
...@@ -2,7 +2,7 @@ module Gitlab ...@@ -2,7 +2,7 @@ module Gitlab
module ImportExport module ImportExport
extend self extend self
VERSION = '0.1.2' VERSION = '0.1.3'
FILENAME_LIMIT = 50 FILENAME_LIMIT = 50
def export_path(relative_path:) def export_path(relative_path:)
......
...@@ -5,8 +5,9 @@ project_tree: ...@@ -5,8 +5,9 @@ project_tree:
- notes: - notes:
- :author - :author
- :events - :events
- :labels - label_links:
- milestones: - :label
- milestone:
- :events - :events
- snippets: - snippets:
- notes: - notes:
...@@ -20,6 +21,10 @@ project_tree: ...@@ -20,6 +21,10 @@ project_tree:
- :events - :events
- :merge_request_diff - :merge_request_diff
- :events - :events
- label_links:
- :label
- milestone:
- :events
- pipelines: - pipelines:
- notes: - notes:
- :author - :author
...@@ -31,6 +36,9 @@ project_tree: ...@@ -31,6 +36,9 @@ project_tree:
- :services - :services
- :hooks - :hooks
- :protected_branches - :protected_branches
- :labels
- milestones:
- :events
# Only include the following attributes for the models specified. # Only include the following attributes for the models specified.
included_attributes: included_attributes:
...@@ -55,6 +63,10 @@ excluded_attributes: ...@@ -55,6 +63,10 @@ excluded_attributes:
- :expired_at - :expired_at
merge_request_diff: merge_request_diff:
- :st_diffs - :st_diffs
issues:
- :milestone_id
merge_requests:
- :milestone_id
methods: methods:
statuses: statuses:
......
module Gitlab
module ImportExport
# Generates a hash that conforms with http://apidock.com/rails/Hash/to_json
# and its peculiar options.
class JsonHashBuilder
def self.build(model_objects, attributes_finder)
new(model_objects, attributes_finder).build
end
def initialize(model_objects, attributes_finder)
@model_objects = model_objects
@attributes_finder = attributes_finder
end
def build
process_model_objects(@model_objects)
end
private
# Called when the model is actually a hash containing other relations (more models)
# Returns the config in the right format for calling +to_json+
#
# +model_object_hash+ - A model relationship such as:
# {:merge_requests=>[:merge_request_diff, :notes]}
def process_model_objects(model_object_hash)
json_config_hash = {}
current_key = model_object_hash.keys.first
model_object_hash.values.flatten.each do |model_object|
@attributes_finder.parse(current_key) { |hash| json_config_hash[current_key] ||= hash }
handle_model_object(current_key, model_object, json_config_hash)
end
json_config_hash
end
# Creates or adds to an existing hash an individual model or list
#
# +current_key+ main model that will be a key in the hash
# +model_object+ model or list of models to include in the hash
# +json_config_hash+ the original hash containing the root model
def handle_model_object(current_key, model_object, json_config_hash)
model_or_sub_model = model_object.is_a?(Hash) ? process_model_objects(model_object) : model_object
if json_config_hash[current_key]
add_model_value(current_key, model_or_sub_model, json_config_hash)
else
create_model_value(current_key, model_or_sub_model, json_config_hash)
end
end
# Constructs a new hash that will hold the configuration for that particular object
# It may include exceptions or other attribute detail configuration, parsed by +@attributes_finder+
#
# +current_key+ main model that will be a key in the hash
# +value+ existing model to be included in the hash
# +json_config_hash+ the original hash containing the root model
def create_model_value(current_key, value, json_config_hash)
parsed_hash = { include: value }
parse_hash(value, parsed_hash)
json_config_hash[current_key] = parsed_hash
end
# Calls attributes finder to parse the hash and add any attributes to it
#
# +value+ existing model to be included in the hash
# +parsed_hash+ the original hash
def parse_hash(value, parsed_hash)
@attributes_finder.parse(value) do |hash|
parsed_hash = { include: hash_or_merge(value, hash) }
end
end
# Adds new model configuration to an existing hash with key +current_key+
# It may include exceptions or other attribute detail configuration, parsed by +@attributes_finder+
#
# +current_key+ main model that will be a key in the hash
# +value+ existing model to be included in the hash
# +json_config_hash+ the original hash containing the root model
def add_model_value(current_key, value, json_config_hash)
@attributes_finder.parse(value) { |hash| value = { value => hash } }
add_to_array(current_key, json_config_hash, value)
end
# Adds new model configuration to an existing hash with key +current_key+
# it creates a new array if it was previously a single value
#
# +current_key+ main model that will be a key in the hash
# +value+ existing model to be included in the hash
# +json_config_hash+ the original hash containing the root model
def add_to_array(current_key, json_config_hash, value)
old_values = json_config_hash[current_key][:include]
json_config_hash[current_key][:include] = ([old_values] + [value]).compact.flatten
end
# Construct a new hash or merge with an existing one a model configuration
# This is to fulfil +to_json+ requirements.
#
# +hash+ hash containing configuration generated mainly from +@attributes_finder+
# +value+ existing model to be included in the hash
def hash_or_merge(value, hash)
value.is_a?(Hash) ? value.merge(hash) : { value => hash }
end
end
end
end
...@@ -47,7 +47,7 @@ module Gitlab ...@@ -47,7 +47,7 @@ module Gitlab
relation_key = relation.is_a?(Hash) ? relation.keys.first : relation relation_key = relation.is_a?(Hash) ? relation.keys.first : relation
relation_hash = create_relation(relation_key, @tree_hash[relation_key.to_s]) relation_hash = create_relation(relation_key, @tree_hash[relation_key.to_s])
saved << restored_project.update_attribute(relation_key, relation_hash) saved << restored_project.append_or_update_attribute(relation_key, relation_hash)
end end
saved.all? saved.all?
end end
...@@ -78,7 +78,7 @@ module Gitlab ...@@ -78,7 +78,7 @@ module Gitlab
relation_key = relation.keys.first.to_s relation_key = relation.keys.first.to_s
return if tree_hash[relation_key].blank? return if tree_hash[relation_key].blank?
tree_hash[relation_key].each do |relation_item| [tree_hash[relation_key]].flatten.each do |relation_item|
relation.values.flatten.each do |sub_relation| relation.values.flatten.each do |sub_relation|
# We just use author to get the user ID, do not attempt to create an instance. # We just use author to get the user ID, do not attempt to create an instance.
next if sub_relation == :author next if sub_relation == :author
......
...@@ -29,87 +29,12 @@ module Gitlab ...@@ -29,87 +29,12 @@ module Gitlab
def build_hash(model_list) def build_hash(model_list)
model_list.map do |model_objects| model_list.map do |model_objects|
if model_objects.is_a?(Hash) if model_objects.is_a?(Hash)
build_json_config_hash(model_objects) Gitlab::ImportExport::JsonHashBuilder.build(model_objects, @attributes_finder)
else else
@attributes_finder.find(model_objects) @attributes_finder.find(model_objects)
end end
end end
end end
# Called when the model is actually a hash containing other relations (more models)
# Returns the config in the right format for calling +to_json+
# +model_object_hash+ - A model relationship such as:
# {:merge_requests=>[:merge_request_diff, :notes]}
def build_json_config_hash(model_object_hash)
@json_config_hash = {}
model_object_hash.values.flatten.each do |model_object|
current_key = model_object_hash.keys.first
@attributes_finder.parse(current_key) { |hash| @json_config_hash[current_key] ||= hash }
handle_model_object(current_key, model_object)
process_sub_model(current_key, model_object) if model_object.is_a?(Hash)
end
@json_config_hash
end
# If the model is a hash, process the sub_models, which could also be hashes
# If there is a list, add to an existing array, otherwise use hash syntax
# +current_key+ main model that will be a key in the hash
# +model_object+ model or list of models to include in the hash
def process_sub_model(current_key, model_object)
sub_model_json = build_json_config_hash(model_object).dup
@json_config_hash.slice!(current_key)
if @json_config_hash[current_key] && @json_config_hash[current_key][:include]
@json_config_hash[current_key][:include] << sub_model_json
else
@json_config_hash[current_key] = { include: sub_model_json }
end
end
# Creates or adds to an existing hash an individual model or list
# +current_key+ main model that will be a key in the hash
# +model_object+ model or list of models to include in the hash
def handle_model_object(current_key, model_object)
if @json_config_hash[current_key]
add_model_value(current_key, model_object)
else
create_model_value(current_key, model_object)
end
end
# Constructs a new hash that will hold the configuration for that particular object
# It may include exceptions or other attribute detail configuration, parsed by +@attributes_finder+
# +current_key+ main model that will be a key in the hash
# +value+ existing model to be included in the hash
def create_model_value(current_key, value)
parsed_hash = { include: value }
@attributes_finder.parse(value) do |hash|
parsed_hash = { include: hash_or_merge(value, hash) }
end
@json_config_hash[current_key] = parsed_hash
end
# Adds new model configuration to an existing hash with key +current_key+
# It may include exceptions or other attribute detail configuration, parsed by +@attributes_finder+
# +current_key+ main model that will be a key in the hash
# +value+ existing model to be included in the hash
def add_model_value(current_key, value)
@attributes_finder.parse(value) { |hash| value = { value => hash } }
old_values = @json_config_hash[current_key][:include]
@json_config_hash[current_key][:include] = ([old_values] + [value]).compact.flatten
end
# Construct a new hash or merge with an existing one a model configuration
# This is to fulfil +to_json+ requirements.
# +value+ existing model to be included in the hash
# +hash+ hash containing configuration generated mainly from +@attributes_finder+
def hash_or_merge(value, hash)
value.is_a?(Hash) ? value.merge(hash) : { value => hash }
end
end end
end end
end end
...@@ -13,6 +13,10 @@ module Gitlab ...@@ -13,6 +13,10 @@ module Gitlab
BUILD_MODELS = %w[Ci::Build commit_status].freeze BUILD_MODELS = %w[Ci::Build commit_status].freeze
IMPORTED_OBJECT_MAX_RETRIES = 5.freeze
EXISTING_OBJECT_CHECK = %i[milestone milestones label labels].freeze
def self.create(*args) def self.create(*args)
new(*args).create new(*args).create
end end
...@@ -22,24 +26,35 @@ module Gitlab ...@@ -22,24 +26,35 @@ module Gitlab
@relation_hash = relation_hash.except('id', 'noteable_id') @relation_hash = relation_hash.except('id', 'noteable_id')
@members_mapper = members_mapper @members_mapper = members_mapper
@user = user @user = user
@imported_object_retries = 0
end end
# Creates an object from an actual model with name "relation_sym" with params from # Creates an object from an actual model with name "relation_sym" with params from
# the relation_hash, updating references with new object IDs, mapping users using # the relation_hash, updating references with new object IDs, mapping users using
# the "members_mapper" object, also updating notes if required. # the "members_mapper" object, also updating notes if required.
def create def create
set_note_author if @relation_name == :notes setup_models
generate_imported_object
end
private
def setup_models
if @relation_name == :notes
set_note_author
# TODO: note attatchments not supported yet
@relation_hash['attachment'] = nil
end
update_user_references update_user_references
update_project_references update_project_references
reset_ci_tokens if @relation_name == 'Ci::Trigger' reset_ci_tokens if @relation_name == 'Ci::Trigger'
@relation_hash['data'].deep_symbolize_keys! if @relation_name == :events && @relation_hash['data'] @relation_hash['data'].deep_symbolize_keys! if @relation_name == :events && @relation_hash['data']
set_st_diffs if @relation_name == :merge_request_diff set_st_diffs if @relation_name == :merge_request_diff
generate_imported_object
end end
private
def update_user_references def update_user_references
USER_REFERENCES.each do |reference| USER_REFERENCES.each do |reference|
if @relation_hash[reference] if @relation_hash[reference]
...@@ -112,10 +127,14 @@ module Gitlab ...@@ -112,10 +127,14 @@ module Gitlab
end end
def imported_object def imported_object
imported_object = relation_class.new(parsed_relation_hash) yield(existing_or_new_object) if block_given?
yield(imported_object) if block_given? existing_or_new_object.importing = true if existing_or_new_object.respond_to?(:importing)
imported_object.importing = true if imported_object.respond_to?(:importing) existing_or_new_object
imported_object rescue ActiveRecord::RecordNotUnique
# as the operation is not atomic, retry in the unlikely scenario an INSERT is
# performed on the same object between the SELECT and the INSERT
@imported_object_retries += 1
retry if @imported_object_retries < IMPORTED_OBJECT_MAX_RETRIES
end end
def update_note_for_missing_author(author_name) def update_note_for_missing_author(author_name)
...@@ -134,6 +153,20 @@ module Gitlab ...@@ -134,6 +153,20 @@ module Gitlab
def set_st_diffs def set_st_diffs
@relation_hash['st_diffs'] = @relation_hash.delete('utf8_st_diffs') @relation_hash['st_diffs'] = @relation_hash.delete('utf8_st_diffs')
end end
def existing_or_new_object
# 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)
existing_object = relation_class.find_or_initialize_by(parsed_relation_hash.slice('title', 'project_id'))
existing_object.assign_attributes(parsed_relation_hash)
existing_object
else
relation_class.new(parsed_relation_hash)
end
end
end
end end
end end
end end
...@@ -18,7 +18,6 @@ ...@@ -18,7 +18,6 @@
"position": 0, "position": 0,
"branch_name": null, "branch_name": null,
"description": "Aliquam enim illo et possimus.", "description": "Aliquam enim illo et possimus.",
"milestone_id": 18,
"state": "opened", "state": "opened",
"iid": 10, "iid": 10,
"updated_by_id": null, "updated_by_id": null,
...@@ -27,6 +26,52 @@ ...@@ -27,6 +26,52 @@
"due_date": null, "due_date": null,
"moved_to_id": null, "moved_to_id": null,
"test_ee_field": "test", "test_ee_field": "test",
"milestone": {
"id": 1,
"title": "v0.0",
"project_id": 8,
"description": "test milestone",
"due_date": null,
"created_at": "2016-06-14T15:02:04.415Z",
"updated_at": "2016-06-14T15:02:04.415Z",
"state": "active",
"iid": 1,
"events": [
{
"id": 487,
"target_type": "Milestone",
"target_id": 1,
"title": null,
"data": null,
"project_id": 46,
"created_at": "2016-06-14T15:02:04.418Z",
"updated_at": "2016-06-14T15:02:04.418Z",
"action": 1,
"author_id": 18
}
]
},
"label_links": [
{
"id": 2,
"label_id": 2,
"target_id": 3,
"target_type": "Issue",
"created_at": "2016-07-22T08:57:02.840Z",
"updated_at": "2016-07-22T08:57:02.840Z",
"label": {
"id": 2,
"title": "test2",
"color": "#428bca",
"project_id": 8,
"created_at": "2016-07-22T08:55:44.161Z",
"updated_at": "2016-07-22T08:55:44.161Z",
"template": false,
"description": "",
"priority": null
}
}
],
"notes": [ "notes": [
{ {
"id": 351, "id": 351,
...@@ -233,7 +278,6 @@ ...@@ -233,7 +278,6 @@
"position": 0, "position": 0,
"branch_name": null, "branch_name": null,
"description": "Voluptate vel reprehenderit facilis omnis voluptas magnam tenetur.", "description": "Voluptate vel reprehenderit facilis omnis voluptas magnam tenetur.",
"milestone_id": 16,
"state": "opened", "state": "opened",
"iid": 9, "iid": 9,
"updated_by_id": null, "updated_by_id": null,
...@@ -447,7 +491,6 @@ ...@@ -447,7 +491,6 @@
"position": 0, "position": 0,
"branch_name": null, "branch_name": null,
"description": "Ea recusandae neque autem tempora.", "description": "Ea recusandae neque autem tempora.",
"milestone_id": 16,
"state": "closed", "state": "closed",
"iid": 8, "iid": 8,
"updated_by_id": null, "updated_by_id": null,
...@@ -661,7 +704,6 @@ ...@@ -661,7 +704,6 @@
"position": 0, "position": 0,
"branch_name": null, "branch_name": null,
"description": "Maiores architecto quos in dolorem.", "description": "Maiores architecto quos in dolorem.",
"milestone_id": 17,
"state": "opened", "state": "opened",
"iid": 7, "iid": 7,
"updated_by_id": null, "updated_by_id": null,
...@@ -875,7 +917,6 @@ ...@@ -875,7 +917,6 @@
"position": 0, "position": 0,
"branch_name": null, "branch_name": null,
"description": "Ut aut ut et tenetur velit aut id modi.", "description": "Ut aut ut et tenetur velit aut id modi.",
"milestone_id": 16,
"state": "opened", "state": "opened",
"iid": 6, "iid": 6,
"updated_by_id": null, "updated_by_id": null,
...@@ -1089,7 +1130,6 @@ ...@@ -1089,7 +1130,6 @@
"position": 0, "position": 0,
"branch_name": null, "branch_name": null,
"description": "Dicta nisi nihil non ipsa velit.", "description": "Dicta nisi nihil non ipsa velit.",
"milestone_id": 20,
"state": "closed", "state": "closed",
"iid": 5, "iid": 5,
"updated_by_id": null, "updated_by_id": null,
...@@ -1303,7 +1343,6 @@ ...@@ -1303,7 +1343,6 @@
"position": 0, "position": 0,
"branch_name": null, "branch_name": null,
"description": "Ut et explicabo vel voluptatem consequuntur ut sed.", "description": "Ut et explicabo vel voluptatem consequuntur ut sed.",
"milestone_id": 19,
"state": "closed", "state": "closed",
"iid": 4, "iid": 4,
"updated_by_id": null, "updated_by_id": null,
...@@ -1517,7 +1556,6 @@ ...@@ -1517,7 +1556,6 @@
"position": 0, "position": 0,
"branch_name": null, "branch_name": null,
"description": "Non asperiores velit accusantium voluptate.", "description": "Non asperiores velit accusantium voluptate.",
"milestone_id": 18,
"state": "closed", "state": "closed",
"iid": 3, "iid": 3,
"updated_by_id": null, "updated_by_id": null,
...@@ -1731,7 +1769,6 @@ ...@@ -1731,7 +1769,6 @@
"position": 0, "position": 0,
"branch_name": null, "branch_name": null,
"description": "Molestiae corporis magnam et fugit aliquid nulla quia.", "description": "Molestiae corporis magnam et fugit aliquid nulla quia.",
"milestone_id": 17,
"state": "closed", "state": "closed",
"iid": 2, "iid": 2,
"updated_by_id": null, "updated_by_id": null,
...@@ -1945,7 +1982,6 @@ ...@@ -1945,7 +1982,6 @@
"position": 0, "position": 0,
"branch_name": null, "branch_name": null,
"description": "Quod ad architecto qui est sed quia.", "description": "Quod ad architecto qui est sed quia.",
"milestone_id": 20,
"state": "closed", "state": "closed",
"iid": 1, "iid": 1,
"updated_by_id": null, "updated_by_id": null,
...@@ -2259,117 +2295,6 @@ ...@@ -2259,117 +2295,6 @@
"author_id": 25 "author_id": 25
} }
] ]
},
{
"id": 18,
"title": "v2.0",
"project_id": 5,
"description": "Error dolorem rerum aut nulla.",
"due_date": null,
"created_at": "2016-06-14T15:02:04.576Z",
"updated_at": "2016-06-14T15:02:04.576Z",
"state": "active",
"iid": 3,
"events": [
{
"id": 242,
"target_type": "Milestone",
"target_id": 18,
"title": null,
"data": null,
"project_id": 36,
"created_at": "2016-06-14T15:02:04.579Z",
"updated_at": "2016-06-14T15:02:04.579Z",
"action": 1,
"author_id": 1
},
{
"id": 58,
"target_type": "Milestone",
"target_id": 18,
"title": null,
"data": null,
"project_id": 5,
"created_at": "2016-06-14T15:02:04.579Z",
"updated_at": "2016-06-14T15:02:04.579Z",
"action": 1,
"author_id": 22
}
]
},
{
"id": 17,
"title": "v1.0",
"project_id": 5,
"description": "Molestiae perspiciatis voluptates doloremque commodi veniam consequatur.",
"due_date": null,
"created_at": "2016-06-14T15:02:04.569Z",
"updated_at": "2016-06-14T15:02:04.569Z",
"state": "active",
"iid": 2,
"events": [
{
"id": 243,
"target_type": "Milestone",
"target_id": 17,
"title": null,
"data": null,
"project_id": 36,
"created_at": "2016-06-14T15:02:04.570Z",
"updated_at": "2016-06-14T15:02:04.570Z",
"action": 1,
"author_id": 1
},
{
"id": 57,
"target_type": "Milestone",
"target_id": 17,
"title": null,
"data": null,
"project_id": 5,
"created_at": "2016-06-14T15:02:04.570Z",
"updated_at": "2016-06-14T15:02:04.570Z",
"action": 1,
"author_id": 20
}
]
},
{
"id": 16,
"title": "v0.0",
"project_id": 5,
"description": "Velit numquam et sed sit.",
"due_date": null,
"created_at": "2016-06-14T15:02:04.561Z",
"updated_at": "2016-06-14T15:02:04.561Z",
"state": "closed",
"iid": 1,
"events": [
{
"id": 244,
"target_type": "Milestone",
"target_id": 16,
"title": null,
"data": null,
"project_id": 36,
"created_at": "2016-06-14T15:02:04.563Z",
"updated_at": "2016-06-14T15:02:04.563Z",
"action": 1,
"author_id": 26
},
{
"id": 56,
"target_type": "Milestone",
"target_id": 16,
"title": null,
"data": null,
"project_id": 5,
"created_at": "2016-06-14T15:02:04.563Z",
"updated_at": "2016-06-14T15:02:04.563Z",
"action": 1,
"author_id": 26
}
]
} }
], ],
"snippets": [ "snippets": [
...@@ -2471,7 +2396,6 @@ ...@@ -2471,7 +2396,6 @@
"title": "Cannot be automatically merged", "title": "Cannot be automatically merged",
"created_at": "2016-06-14T15:02:36.568Z", "created_at": "2016-06-14T15:02:36.568Z",
"updated_at": "2016-06-14T15:02:56.815Z", "updated_at": "2016-06-14T15:02:56.815Z",
"milestone_id": null,
"state": "opened", "state": "opened",
"merge_status": "unchecked", "merge_status": "unchecked",
"target_project_id": 5, "target_project_id": 5,
...@@ -2909,7 +2833,6 @@ ...@@ -2909,7 +2833,6 @@
"title": "Can be automatically merged", "title": "Can be automatically merged",
"created_at": "2016-06-14T15:02:36.418Z", "created_at": "2016-06-14T15:02:36.418Z",
"updated_at": "2016-06-14T15:02:57.013Z", "updated_at": "2016-06-14T15:02:57.013Z",
"milestone_id": null,
"state": "opened", "state": "opened",
"merge_status": "unchecked", "merge_status": "unchecked",
"target_project_id": 5, "target_project_id": 5,
...@@ -3194,7 +3117,6 @@ ...@@ -3194,7 +3117,6 @@
"title": "Qui accusantium et inventore facilis doloribus occaecati officiis.", "title": "Qui accusantium et inventore facilis doloribus occaecati officiis.",
"created_at": "2016-06-14T15:02:25.168Z", "created_at": "2016-06-14T15:02:25.168Z",
"updated_at": "2016-06-14T15:02:59.521Z", "updated_at": "2016-06-14T15:02:59.521Z",
"milestone_id": 17,
"state": "opened", "state": "opened",
"merge_status": "unchecked", "merge_status": "unchecked",
"target_project_id": 5, "target_project_id": 5,
...@@ -3479,7 +3401,6 @@ ...@@ -3479,7 +3401,6 @@
"title": "In voluptas aut sequi voluptatem ullam vel corporis illum consequatur.", "title": "In voluptas aut sequi voluptatem ullam vel corporis illum consequatur.",
"created_at": "2016-06-14T15:02:24.760Z", "created_at": "2016-06-14T15:02:24.760Z",
"updated_at": "2016-06-14T15:02:59.749Z", "updated_at": "2016-06-14T15:02:59.749Z",
"milestone_id": 20,
"state": "opened", "state": "opened",
"merge_status": "unchecked", "merge_status": "unchecked",
"target_project_id": 5, "target_project_id": 5,
...@@ -4170,7 +4091,6 @@ ...@@ -4170,7 +4091,6 @@
"title": "Voluptates consequatur eius nemo amet libero animi illum delectus tempore.", "title": "Voluptates consequatur eius nemo amet libero animi illum delectus tempore.",
"created_at": "2016-06-14T15:02:24.415Z", "created_at": "2016-06-14T15:02:24.415Z",
"updated_at": "2016-06-14T15:02:59.958Z", "updated_at": "2016-06-14T15:02:59.958Z",
"milestone_id": 17,
"state": "opened", "state": "opened",
"merge_status": "unchecked", "merge_status": "unchecked",
"target_project_id": 5, "target_project_id": 5,
...@@ -4719,7 +4639,6 @@ ...@@ -4719,7 +4639,6 @@
"title": "In a rerum harum nihil accusamus aut quia nobis non.", "title": "In a rerum harum nihil accusamus aut quia nobis non.",
"created_at": "2016-06-14T15:02:24.000Z", "created_at": "2016-06-14T15:02:24.000Z",
"updated_at": "2016-06-14T15:03:00.225Z", "updated_at": "2016-06-14T15:03:00.225Z",
"milestone_id": 19,
"state": "opened", "state": "opened",
"merge_status": "unchecked", "merge_status": "unchecked",
"target_project_id": 5, "target_project_id": 5,
...@@ -5219,7 +5138,6 @@ ...@@ -5219,7 +5138,6 @@
"title": "Corporis provident similique perspiciatis dolores eos animi.", "title": "Corporis provident similique perspiciatis dolores eos animi.",
"created_at": "2016-06-14T15:02:23.767Z", "created_at": "2016-06-14T15:02:23.767Z",
"updated_at": "2016-06-14T15:03:00.475Z", "updated_at": "2016-06-14T15:03:00.475Z",
"milestone_id": 18,
"state": "opened", "state": "opened",
"merge_status": "unchecked", "merge_status": "unchecked",
"target_project_id": 5, "target_project_id": 5,
...@@ -5480,7 +5398,6 @@ ...@@ -5480,7 +5398,6 @@
"title": "Eligendi reprehenderit doloribus quia et sit id.", "title": "Eligendi reprehenderit doloribus quia et sit id.",
"created_at": "2016-06-14T15:02:23.014Z", "created_at": "2016-06-14T15:02:23.014Z",
"updated_at": "2016-06-14T15:03:00.685Z", "updated_at": "2016-06-14T15:03:00.685Z",
"milestone_id": 20,
"state": "opened", "state": "opened",
"merge_status": "unchecked", "merge_status": "unchecked",
"target_project_id": 5, "target_project_id": 5,
...@@ -6171,7 +6088,6 @@ ...@@ -6171,7 +6088,6 @@
"title": "Et ipsam voluptas velit sequi illum ut.", "title": "Et ipsam voluptas velit sequi illum ut.",
"created_at": "2016-06-14T15:02:22.825Z", "created_at": "2016-06-14T15:02:22.825Z",
"updated_at": "2016-06-14T15:03:00.904Z", "updated_at": "2016-06-14T15:03:00.904Z",
"milestone_id": 16,
"state": "opened", "state": "opened",
"merge_status": "unchecked", "merge_status": "unchecked",
"target_project_id": 5, "target_project_id": 5,
......
...@@ -60,6 +60,18 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do ...@@ -60,6 +60,18 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
expect { restored_project_json }.to change(MergeRequestDiff.where.not(st_diffs: nil), :count).by(9) expect { restored_project_json }.to change(MergeRequestDiff.where.not(st_diffs: nil), :count).by(9)
end end
it 'has labels associated to label links, associated to issues' do
restored_project_json
expect(Label.first.label_links.first.target).not_to be_nil
end
it 'has milestones associated to issues' do
restored_project_json
expect(Milestone.find_by_description('test milestone').issues).not_to be_empty
end
end end
end end
end end
...@@ -31,10 +31,6 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do ...@@ -31,10 +31,6 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
expect(saved_project_json).to include({ "visibility_level" => 20 }) expect(saved_project_json).to include({ "visibility_level" => 20 })
end end
it 'has events' do
expect(saved_project_json['milestones'].first['events']).not_to be_empty
end
it 'has milestones' do it 'has milestones' do
expect(saved_project_json['milestones']).not_to be_empty expect(saved_project_json['milestones']).not_to be_empty
end end
...@@ -43,8 +39,12 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do ...@@ -43,8 +39,12 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
expect(saved_project_json['merge_requests']).not_to be_empty expect(saved_project_json['merge_requests']).not_to be_empty
end end
it 'has labels' do it 'has merge request\'s milestones' do
expect(saved_project_json['labels']).not_to be_empty expect(saved_project_json['merge_requests'].first['milestone']).not_to be_empty
end
it 'has events' do
expect(saved_project_json['merge_requests'].first['milestone']['events']).not_to be_empty
end end
it 'has snippets' do it 'has snippets' do
...@@ -103,6 +103,14 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do ...@@ -103,6 +103,14 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
expect(saved_project_json['pipelines'].first['notes']).not_to be_empty expect(saved_project_json['pipelines'].first['notes']).not_to be_empty
end end
it 'has labels with no associations' do
expect(saved_project_json['labels']).not_to be_empty
end
it 'has labels associated to records' do
expect(saved_project_json['issues'].first['label_links'].first['label']).not_to be_empty
end
it 'does not complain about non UTF-8 characters in MR diffs' do it 'does not complain about non UTF-8 characters in MR diffs' do
ActiveRecord::Base.connection.execute("UPDATE merge_request_diffs SET st_diffs = '---\n- :diff: !binary |-\n LS0tIC9kZXYvbnVsbAorKysgYi9pbWFnZXMvbnVjb3IucGRmCkBAIC0wLDAg\n KzEsMTY3OSBAQAorJVBERi0xLjUNJeLjz9MNCisxIDAgb2JqDTw8L01ldGFk\n YXR'") ActiveRecord::Base.connection.execute("UPDATE merge_request_diffs SET st_diffs = '---\n- :diff: !binary |-\n LS0tIC9kZXYvbnVsbAorKysgYi9pbWFnZXMvbnVjb3IucGRmCkBAIC0wLDAg\n KzEsMTY3OSBAQAorJVBERi0xLjUNJeLjz9MNCisxIDAgb2JqDTw8L01ldGFk\n YXR'")
...@@ -113,19 +121,19 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do ...@@ -113,19 +121,19 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
def setup_project def setup_project
issue = create(:issue, assignee: user) issue = create(:issue, assignee: user)
label = create(:label)
snippet = create(:project_snippet) snippet = create(:project_snippet)
release = create(:release) release = create(:release)
project = create(:project, project = create(:project,
:public, :public,
issues: [issue], issues: [issue],
labels: [label],
snippets: [snippet], snippets: [snippet],
releases: [release] releases: [release]
) )
label = create(:label, project: project)
merge_request = create(:merge_request, source_project: project) create(:label_link, label: label, target: issue)
milestone = create(:milestone, project: project)
merge_request = create(:merge_request, source_project: project, milestone: milestone)
commit_status = create(:commit_status, project: project) commit_status = create(:commit_status, project: project)
ci_pipeline = create(:ci_pipeline, ci_pipeline = create(:ci_pipeline,
...@@ -135,7 +143,7 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do ...@@ -135,7 +143,7 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
statuses: [commit_status]) statuses: [commit_status])
create(:ci_build, pipeline: ci_pipeline, project: project) create(:ci_build, pipeline: ci_pipeline, project: project)
milestone = create(:milestone, project: project) create(:milestone, project: project)
create(:note, noteable: issue, project: project) create(:note, noteable: issue, project: project)
create(:note, noteable: merge_request, project: project) create(:note, noteable: merge_request, project: project)
create(:note, noteable: snippet, project: project) create(:note, noteable: snippet, project: project)
......
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