diff --git a/app/services/issuable/import_csv/base_service.rb b/app/services/issuable/import_csv/base_service.rb index 4a2078a4e6036400d1a5ebadaf79946bf0313931..9b41c88159fb3e3ca13eb6738663bf6b895829f1 100644 --- a/app/services/issuable/import_csv/base_service.rb +++ b/app/services/issuable/import_csv/base_service.rb @@ -23,7 +23,8 @@ def process_csv with_csv_lines.each do |row, line_no| issuable_attributes = { title: row[:title], - description: row[:description] + description: row[:description], + due_date: row[:due_date] } if create_issuable(issuable_attributes).persisted? diff --git a/app/services/issues/build_service.rb b/app/services/issues/build_service.rb index 1ebf9bb6ba24bac79f87920c6fa356305fd41481..75bd2b88e86e0df427705e46c25973154c6b8e4d 100644 --- a/app/services/issues/build_service.rb +++ b/app/services/issues/build_service.rb @@ -81,8 +81,9 @@ def model_klass ::Issue end - def allowed_issue_params - allowed_params = [ + def public_params + # Additional params may be assigned later (in a CreateService for example) + public_issue_params = [ :title, :description, :confidential @@ -90,17 +91,17 @@ def allowed_issue_params params[:work_item_type] = WorkItems::Type.find_by(id: params[:work_item_type_id]) if params[:work_item_type_id].present? # rubocop: disable CodeReuse/ActiveRecord - allowed_params << :milestone_id if can?(current_user, :admin_issue, project) - allowed_params << :issue_type if create_issue_type_allowed?(project, params[:issue_type]) - allowed_params << :work_item_type if create_issue_type_allowed?(project, params[:work_item_type]&.base_type) + public_issue_params << :milestone_id if can?(current_user, :admin_issue, project) + public_issue_params << :issue_type if create_issue_type_allowed?(project, params[:issue_type]) + public_issue_params << :work_item_type if create_issue_type_allowed?(project, params[:work_item_type]&.base_type) - params.slice(*allowed_params) + params.slice(*public_issue_params) end def build_issue_params { author: current_user } .merge(issue_params_with_info_from_discussions) - .merge(allowed_issue_params) + .merge(public_params) .with_indifferent_access end diff --git a/doc/user/project/issues/csv_import.md b/doc/user/project/issues/csv_import.md index 2fe3d78194ccc36ae4af7c53a4782f3ab0ff4698..1ae57c9a883a2d4fceee0e233813e2ff09c03d60 100644 --- a/doc/user/project/issues/csv_import.md +++ b/doc/user/project/issues/csv_import.md @@ -6,9 +6,20 @@ info: To determine the technical writer assigned to the Stage/Group associated w # Importing issues from CSV **(FREE)** -Issues can be imported to a project by uploading a CSV file with the columns -`title` and `description`. Other columns are **not** imported. If you want to -retain columns such as labels and milestones, consider the [Move Issue feature](managing_issues.md#move-an-issue). +You can import issues to a project by uploading a CSV file with the following columns: + +| Name | Required? | Description | +|:--------------|:-----------------------|:-------------------------------------------------| +| `title` | **{check-circle}** Yes | Issue title. | +| `description` | **{check-circle}** Yes | Issue description. | +| `due_date` | **{dotted-circle}** No | Issue due date in `YYYY-MM-DD` format. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/91317) in GitLab 15.2. | + +Data in other columns is not imported. + +You can use the `description` field to embed [quick actions](../quick_actions.md) to add other data to the issue. +For example, labels, assignees, and milestones. + +Alternatively, you can [move an issue](managing_issues.md#move-an-issue). Moving issues preserves more data. The user uploading the CSV file is set as the author of the imported issues. @@ -44,16 +55,22 @@ To import issues, GitLab requires CSV files have a specific format: | double-quote character | The double-quote (`"`) character is used to quote fields, enabling the use of the column separator in a field (see the third line in the sample CSV data below). To insert a double-quote (`"`) in a quoted field use two double-quote characters in succession (`""`). | | data rows | After the header row, following rows must use the same column order. The issue title is required, but the description is optional. | -If you have special characters in a field, (such as `\n` or `,`), surround the -characters with double quotes (`"`). +If you have special characters (for example, `,` or `\n`) or multiple lines in a field (for example, +when using [quick actions](../quick_actions.md)), surround the characters with double quotes (`"`). + +When using [quick actions](../quick_actions.md), each action must be on a separate line. Sample CSV data: ```plaintext -title,description -My Issue Title,My Issue Description -Another Title,"A description, with a comma" -"One More Title","One More Description" +title,description,due date +My Issue Title,My Issue Description,2022-06-28 +Another Title,"A description, with a comma", +"One More Title","One More Description", +An Issue with Quick Actions,"Hey can we change the frontend? + +/assign @sjones +/label ~frontend ~documentation", ``` ### File size diff --git a/spec/fixtures/csv_complex.csv b/spec/fixtures/csv_complex.csv new file mode 100644 index 0000000000000000000000000000000000000000..60d8aa5d6f75ecd5729fb5e20a4a012539966ae3 --- /dev/null +++ b/spec/fixtures/csv_complex.csv @@ -0,0 +1,6 @@ +title,description,due date +Issue in ä¸æ–‡,Test description, +"Hello","World", +"Title with quote""","Description +/assign @csv_assignee +/estimate 1h",2022-06-28 diff --git a/spec/services/issues/import_csv_service_spec.rb b/spec/services/issues/import_csv_service_spec.rb index fa40b75190f96268c033c3b4c129530f6c7b1de6..9ad1d7dba9f9417ae2c9311200e60f60dfabd9ba 100644 --- a/spec/services/issues/import_csv_service_spec.rb +++ b/spec/services/issues/import_csv_service_spec.rb @@ -5,6 +5,7 @@ RSpec.describe Issues::ImportCsvService do let(:project) { create(:project) } let(:user) { create(:user) } + let(:assignee) { create(:user, username: 'csv_assignee') } let(:service) do uploader = FileUploader.new(project) uploader.store!(file) @@ -16,4 +17,27 @@ let(:issuables) { project.issues } let(:email_method) { :import_issues_csv_email } end + + describe '#execute' do + let(:file) { fixture_file_upload('spec/fixtures/csv_complex.csv') } + + subject { service.execute } + + it 'sets all issueable attributes and executes quick actions' do + project.add_developer(user) + project.add_developer(assignee) + + expect { subject }.to change { issuables.count }.by 3 + + expect(issuables.reload).to include( + have_attributes( + title: 'Title with quote"', + description: 'Description', + time_estimate: 3600, + assignees: include(assignee), + due_date: Date.new(2022, 6, 28) + ) + ) + end + end end diff --git a/spec/support/services/issuable_import_csv_service_shared_examples.rb b/spec/support/services/issuable_import_csv_service_shared_examples.rb index 0711819896977b7e70381bc4ee2cdf6844475ab2..0dea6cfb7291594a52b9323464799656a9bba930 100644 --- a/spec/support/services/issuable_import_csv_service_shared_examples.rb +++ b/spec/support/services/issuable_import_csv_service_shared_examples.rb @@ -37,6 +37,10 @@ end describe '#execute' do + before do + project.add_developer(user) + end + context 'invalid file extension' do let(:file) { fixture_file_upload('spec/fixtures/banana_sample.gif') }