From fb8a25c78f7bbbca6cbabb7b45486e81cb50a1a3 Mon Sep 17 00:00:00 2001
From: Manuel VACELET <manuel.vacelet@enalean.com>
Date: Wed, 28 Jul 2021 10:13:25 +0200
Subject: [PATCH] request #22580 Get list of jira projects doesnt work with
 Jira Server

The while both APIs seems to be available on Jira Cloud, the design
selected was to clearly separate the 2 ways to get the information

It might be needed latter on if Jira Server API are more limited
than Jira Cloud.

Change-Id: I595bf5b238b2cbc8b1239475c43b7b0b1f372c4c
---
 .../Creation/JiraImporter/ClientWrapper.php   | 15 +++++++
 .../Creation/JiraImporter/JiraClient.php      |  2 +
 .../JiraImporter/JiraConnectionException.php  | 32 +++++++-------
 .../JiraImporter/JiraProjectBuilder.php       | 42 ++++++++++++++++++-
 .../JiraImporter/JiraProjectCollection.php    |  3 +-
 .../JiraProjectListController.php             |  4 +-
 6 files changed, 79 insertions(+), 19 deletions(-)

diff --git a/plugins/tracker/include/Tracker/Creation/JiraImporter/ClientWrapper.php b/plugins/tracker/include/Tracker/Creation/JiraImporter/ClientWrapper.php
index 1844ea16f1..bc72943024 100644
--- a/plugins/tracker/include/Tracker/Creation/JiraImporter/ClientWrapper.php
+++ b/plugins/tracker/include/Tracker/Creation/JiraImporter/ClientWrapper.php
@@ -41,6 +41,8 @@ class ClientWrapper implements JiraClient
      */
     public const JIRA_CORE_BASE_URL = '/rest/api/2';
 
+    private const DEPLOYMENT_TYPE_CLOUD = 'Cloud';
+
     /**
      * @var ClientInterface
      */
@@ -61,6 +63,10 @@ class ClientWrapper implements JiraClient
      * @var ?string
      */
     private $log_file;
+    /**
+     * @var ?bool
+     */
+    private $is_jira_cloud;
 
     public function __construct(ClientInterface $client, RequestFactoryInterface $factory, string $base_url)
     {
@@ -126,4 +132,13 @@ class ClientWrapper implements JiraClient
 
         return json_decode($body_contents, true, 512, JSON_OBJECT_AS_ARRAY & JSON_THROW_ON_ERROR);
     }
+
+    public function isJiraCloud(): bool
+    {
+        if ($this->is_jira_cloud === null) {
+            $json                = $this->getUrl(self::JIRA_CORE_BASE_URL . '/serverInfo');
+            $this->is_jira_cloud = isset($json['deploymentType']) && $json['deploymentType'] === self::DEPLOYMENT_TYPE_CLOUD;
+        }
+        return $this->is_jira_cloud;
+    }
 }
diff --git a/plugins/tracker/include/Tracker/Creation/JiraImporter/JiraClient.php b/plugins/tracker/include/Tracker/Creation/JiraImporter/JiraClient.php
index b4e02ce296..713e5f5702 100644
--- a/plugins/tracker/include/Tracker/Creation/JiraImporter/JiraClient.php
+++ b/plugins/tracker/include/Tracker/Creation/JiraImporter/JiraClient.php
@@ -29,4 +29,6 @@ interface JiraClient
      * @throws JiraConnectionException|\JsonException
      */
     public function getUrl(string $url): ?array;
+
+    public function isJiraCloud(): bool;
 }
diff --git a/plugins/tracker/include/Tracker/Creation/JiraImporter/JiraConnectionException.php b/plugins/tracker/include/Tracker/Creation/JiraImporter/JiraConnectionException.php
index 70b0ee5703..46dc6ae658 100644
--- a/plugins/tracker/include/Tracker/Creation/JiraImporter/JiraConnectionException.php
+++ b/plugins/tracker/include/Tracker/Creation/JiraImporter/JiraConnectionException.php
@@ -86,25 +86,27 @@ class JiraConnectionException extends \Exception
                 $jira_warnings = $body['warningMessages'];
             }
         } catch (\JsonException $exception) {
+            $message = sprintf(
+                'Query was not successful (code: %d, message: "%s"). Error response cannot be read, invalid json for %s',
+                $response->getStatusCode(),
+                $response->getReasonPhrase(),
+                (string) $request->getUri(),
+            );
             return new self(
-                sprintf(
-                    'Query was not successful (code: %d, message: "%s"). Error response cannot be read, invalid json for %s',
-                    $response->getStatusCode(),
-                    $response->getReasonPhrase(),
-                    (string) $request->getUri(),
-                ),
-                ''
+                $message,
+                $message
             );
         }
+        $message = sprintf(
+            'Query was not successful (code: %d, message: "%s"). Jira errors:' . PHP_EOL . '%s' . PHP_EOL . 'Jira warnings:' . PHP_EOL . '%s',
+            $response->getStatusCode(),
+            $response->getReasonPhrase(),
+            implode(PHP_EOL, $jira_errors),
+            implode(PHP_EOL, $jira_warnings),
+        );
         return new self(
-            sprintf(
-                'Query was not successful (code: %d, message: "%s"). Jira errors:' . PHP_EOL . '%s' . PHP_EOL . 'Jira warnings:' . PHP_EOL . '%s',
-                $response->getStatusCode(),
-                $response->getReasonPhrase(),
-                implode(PHP_EOL, $jira_errors),
-                implode(PHP_EOL, $jira_warnings),
-            ),
-            ''
+            $message,
+            $message
         );
     }
 
diff --git a/plugins/tracker/include/Tracker/Creation/JiraImporter/JiraProjectBuilder.php b/plugins/tracker/include/Tracker/Creation/JiraImporter/JiraProjectBuilder.php
index f8609d81b1..d19c37d7aa 100644
--- a/plugins/tracker/include/Tracker/Creation/JiraImporter/JiraProjectBuilder.php
+++ b/plugins/tracker/include/Tracker/Creation/JiraImporter/JiraProjectBuilder.php
@@ -28,10 +28,24 @@ use Psr\Log\LoggerInterface;
 class JiraProjectBuilder
 {
     /**
-     * @throws JiraConnectionException
      * @return array<array{id: string, label: string}>
+     * @throws UnexpectedFormatException
+     * @throws JiraConnectionException
+     * @throws \JsonException
      */
     public function build(JiraClient $jira_client, LoggerInterface $logger): array
+    {
+        if ($jira_client->isJiraCloud()) {
+            return $this->getCollectionFromJiraCloud($jira_client, $logger);
+        }
+        return $this->getCollectionFromJiraServer($jira_client);
+    }
+
+    /**
+     * @return array<array{id: string, label: string}>
+     * @throws UnexpectedFormatException
+     */
+    private function getCollectionFromJiraCloud(JiraClient $jira_client, LoggerInterface $logger): array
     {
         $iterator           = JiraCollectionBuilder::iterateUntilIsLast(
             $jira_client,
@@ -53,4 +67,30 @@ class JiraProjectBuilder
         }
         return $project_collection->getJiraProjects();
     }
+
+    /**
+     * @throws UnexpectedFormatException
+     * @throws JiraConnectionException
+     * @throws \JsonException
+     */
+    private function getCollectionFromJiraServer(JiraClient $jira_client): array
+    {
+        $json = $jira_client->getUrl(ClientWrapper::JIRA_CORE_BASE_URL . '/project');
+        if ($json === null) {
+            throw new UnexpectedFormatException(ClientWrapper::JIRA_CORE_BASE_URL . '/project is supposed to return a collection of projects, null received');
+        }
+        $project_collection = new JiraProjectCollection();
+        foreach ($json as $project_entry) {
+            if (! isset($project_entry['key'], $project_entry['name'])) {
+                throw new UnexpectedFormatException('`key` or `name` has not been founded in jira_representation');
+            }
+            $project_collection->addProject(
+                [
+                    'id' => (string) $project_entry['key'],
+                    'label' => (string) $project_entry['name']
+                ]
+            );
+        }
+        return $project_collection->getJiraProjects();
+    }
 }
diff --git a/plugins/tracker/include/Tracker/Creation/JiraImporter/JiraProjectCollection.php b/plugins/tracker/include/Tracker/Creation/JiraImporter/JiraProjectCollection.php
index e4cca630f1..5eb7f477ac 100644
--- a/plugins/tracker/include/Tracker/Creation/JiraImporter/JiraProjectCollection.php
+++ b/plugins/tracker/include/Tracker/Creation/JiraImporter/JiraProjectCollection.php
@@ -33,9 +33,10 @@ class JiraProjectCollection
     /**
      * @param array{id: string, label: string} $project
      */
-    public function addProject(array $project): void
+    public function addProject(array $project): self
     {
         $this->jira_projects[] = $project;
+        return $this;
     }
 
     /**
diff --git a/plugins/tracker/include/Tracker/Creation/JiraImporter/JiraProjectListController.php b/plugins/tracker/include/Tracker/Creation/JiraImporter/JiraProjectListController.php
index 20c9a931fc..2f2cdd7b5f 100644
--- a/plugins/tracker/include/Tracker/Creation/JiraImporter/JiraProjectListController.php
+++ b/plugins/tracker/include/Tracker/Creation/JiraImporter/JiraProjectListController.php
@@ -79,9 +79,9 @@ class JiraProjectListController implements DispatchableWithRequest, Dispatchable
             $projects = $this->jira_project_builder->build($wrapper, $this->logger);
             $layout->sendJSON($projects);
         } catch (JiraConnectionException $exception) {
-            $layout->send400JSONErrors(['error' => $exception->getI18nMessage()]);
+            $layout->send400JSONErrors(['error' => $exception->getI18nMessage(), 'type' => get_class($exception)]);
         } catch (\Exception $exception) {
-            $layout->send400JSONErrors(['error' => $exception->getMessage()]);
+            $layout->send400JSONErrors(['error' => $exception->getMessage(), 'type' => get_class($exception)]);
         }
     }
 
-- 
2.31.1

