From 4ddf802cd229959db42f054e039efc329f083d8d Mon Sep 17 00:00:00 2001
From: Lee Tickett <lee@tickett.net>
Date: Mon, 4 Jul 2022 14:19:55 +0000
Subject: [PATCH] Add due_date and improve CSV issue import docs

Changelog: added
---
 .../issuable/import_csv/base_service.rb       |  3 +-
 app/services/issues/build_service.rb          | 15 ++++----
 doc/user/project/issues/csv_import.md         | 35 ++++++++++++++-----
 spec/fixtures/csv_complex.csv                 |  6 ++++
 .../issues/import_csv_service_spec.rb         | 24 +++++++++++++
 ...able_import_csv_service_shared_examples.rb |  4 +++
 6 files changed, 70 insertions(+), 17 deletions(-)
 create mode 100644 spec/fixtures/csv_complex.csv

diff --git a/app/services/issuable/import_csv/base_service.rb b/app/services/issuable/import_csv/base_service.rb
index 4a2078a4e603..9b41c88159fb 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 1ebf9bb6ba24..75bd2b88e86e 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 2fe3d78194cc..1ae57c9a883a 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 000000000000..60d8aa5d6f75
--- /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 fa40b75190f9..9ad1d7dba9f9 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 071181989697..0dea6cfb7291 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') }
 
-- 
GitLab