diff --git a/app/services/issues/import_csv_service.rb b/app/services/issues/import_csv_service.rb index ef08fafa7ccd2f8393d1e133e9b12451f13a621f..c01db5fcfe62c8a694d7087e8040380ec4cfe93c 100644 --- a/app/services/issues/import_csv_service.rb +++ b/app/services/issues/import_csv_service.rb @@ -21,8 +21,19 @@ def execute def process_csv csv_data = @csv_io.open(&:read).force_encoding(Encoding::UTF_8) - CSV.new(csv_data, col_sep: detect_col_sep(csv_data.lines.first), headers: true).each.with_index(2) do |row, line_no| - issue = Issues::CreateService.new(@project, @user, title: row[0], description: row[1]).execute + csv_parsing_params = { + col_sep: detect_col_sep(csv_data.lines.first), + headers: true, + header_converters: :symbol + } + + CSV.new(csv_data, csv_parsing_params).each.with_index(2) do |row, line_no| + issue_attributes = { + title: row[:title], + description: row[:description] + } + + issue = Issues::CreateService.new(@project, @user, issue_attributes).execute if issue.persisted? @results[:success] += 1 diff --git a/changelogs/unreleased/jhyson-issue-import-export-consistency.yml b/changelogs/unreleased/jhyson-issue-import-export-consistency.yml new file mode 100644 index 0000000000000000000000000000000000000000..0d2b8167d942779632747b89de0f488c2710cc71 --- /dev/null +++ b/changelogs/unreleased/jhyson-issue-import-export-consistency.yml @@ -0,0 +1,5 @@ +--- +title: Fix issue importer so it matches issue export format +merge_request: 25896 +author: +type: fixed diff --git a/doc/user/project/issues/csv_import.md b/doc/user/project/issues/csv_import.md index 56643dcee537c558f65820072be46946e9dac361..d67b135186f62fb94d9d419cde30e5941b841b94 100644 --- a/doc/user/project/issues/csv_import.md +++ b/doc/user/project/issues/csv_import.md @@ -3,7 +3,7 @@ > [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/23532) in GitLab 11.7. Issues can be imported to a project by uploading a CSV file with the columns -`title` and `description`, in that order. +`title` and `description`. The user uploading the CSV file will be set as the author of the imported issues. @@ -31,9 +31,9 @@ to you once the import is complete. When importing issues from a CSV file, it must be formatted in a certain way: -- **header row:** CSV files must contain a header row where the first column header - is `title` and the second is `description`. If additional columns are present, they - will be ignored. +- **header row:** CSV files must include the following headers: +`title` and `description`. The case of the headers does not matter. +- **columns:** Data from columns beyond `title` and `description` are not imported. - **separators:** The column separator is automatically detected from the header row. Supported separator characters are: commas (`,`), semicolons (`;`), and tabs (`\t`). The row separator can be either `CRLF` or `LF`. diff --git a/ee/spec/services/issues/export_csv_service_spec.rb b/ee/spec/services/issues/export_csv_service_spec.rb index 5594e1a648d0479c8ecd5d542d40789850741feb..a664a0a73427e5684b7ae648b2bbf1a6ebbd939d 100644 --- a/ee/spec/services/issues/export_csv_service_spec.rb +++ b/ee/spec/services/issues/export_csv_service_spec.rb @@ -54,6 +54,10 @@ def csv time_estimate: 72000) end + it 'includes the columns required for import' do + expect(csv.headers).to include('Title', 'Description') + end + specify 'iid' do expect(csv[0]['Issue ID']).to eq issue.iid.to_s end diff --git a/spec/fixtures/csv_gitlab_export.csv b/spec/fixtures/csv_gitlab_export.csv new file mode 100644 index 0000000000000000000000000000000000000000..65422509eef4a4b7a43446acb1f6885af7050343 --- /dev/null +++ b/spec/fixtures/csv_gitlab_export.csv @@ -0,0 +1,5 @@ +Issue ID,URL,Title,State,Description,Author,Author Username,Assignee,Assignee Username,Confidential,Locked,Due Date,Created At (UTC),Updated At (UTC),Closed At (UTC),Milestone,Weight,Labels,Time Estimate,Time Spent,Epic ID,Epic Title +1,http://localhost:3000/jashkenas/underscore/issues/1,Title,Open,,Elva Jerde,jamel,Tierra Effertz,aurora_hahn,No,No,,2020-01-17 10:36:26,2020-02-19 10:36:26,,v1.0,,"Brene,Cutlass,Escort,GVM",0,0,, +3,http://localhost:3000/jashkenas/underscore/issues/3,Nihil impedit neque quos totam ut aut enim cupiditate doloribus molestiae.,Open,Omnis aliquid sint laudantium quam.,Marybeth Goodwin,rocio.blanda,Annemarie Von,reynalda_howe,No,No,,2020-01-23 10:36:26,2020-02-19 10:36:27,,v1.0,,"Brene,Cutlass,Escort,GVM",0,0,, +34,http://localhost:3000/jashkenas/underscore/issues/34,Dismiss Cipher with no integrity,Open,,Marybeth Goodwin,rocio.blanda,"","",No,No,,2020-02-19 10:38:49,2020-02-19 10:38:49,,,,,0,0,, +35,http://localhost:3000/jashkenas/underscore/issues/35,Test Title,Open,Test Description,Marybeth Goodwin,rocio.blanda,"","",No,No,,2020-02-19 10:38:49,2020-02-19 10:38:49,,,,,0,0,, diff --git a/spec/services/issues/import_csv_service_spec.rb b/spec/services/issues/import_csv_service_spec.rb index e7370407d4c025ab42cae7bc7b680bf3f80a3ee9..aa43892a36d373c1facb4a52b71cae2d12d85bdd 100644 --- a/spec/services/issues/import_csv_service_spec.rb +++ b/spec/services/issues/import_csv_service_spec.rb @@ -27,6 +27,29 @@ end end + context 'with a file generated by Gitlab CSV export' do + let(:file) { fixture_file_upload('spec/fixtures/csv_gitlab_export.csv') } + + it 'imports the CSV without errors' do + expect_next_instance_of(Notify) do |instance| + expect(instance).to receive(:import_issues_csv_email) + end + + expect(subject[:success]).to eq(4) + expect(subject[:error_lines]).to eq([]) + expect(subject[:parse_error]).to eq(false) + end + + it 'correctly sets the issue attributes' do + expect { subject }.to change { project.issues.count }.by 4 + + expect(project.issues.reload.last).to have_attributes( + title: 'Test Title', + description: 'Test Description' + ) + end + end + context 'comma delimited file' do let(:file) { fixture_file_upload('spec/fixtures/csv_comma.csv') } @@ -39,6 +62,15 @@ expect(subject[:error_lines]).to eq([]) expect(subject[:parse_error]).to eq(false) end + + it 'correctly sets the issue attributes' do + expect { subject }.to change { project.issues.count }.by 3 + + expect(project.issues.reload.last).to have_attributes( + title: 'Title with quote"', + description: 'Description' + ) + end end context 'tab delimited file with error row' do @@ -53,6 +85,15 @@ expect(subject[:error_lines]).to eq([3]) expect(subject[:parse_error]).to eq(false) end + + it 'correctly sets the issue attributes' do + expect { subject }.to change { project.issues.count }.by 2 + + expect(project.issues.reload.last).to have_attributes( + title: 'Hello', + description: 'World' + ) + end end context 'semicolon delimited file with CRLF' do @@ -67,6 +108,15 @@ expect(subject[:error_lines]).to eq([4]) expect(subject[:parse_error]).to eq(false) end + + it 'correctly sets the issue attributes' do + expect { subject }.to change { project.issues.count }.by 3 + + expect(project.issues.reload.last).to have_attributes( + title: 'Hello', + description: 'World' + ) + end end end end