Repository: qiskit-community/ecosystem (python)

Issue:
tags with spaces break in two
### Steps to reproduce the problem

If a tag has a space (such as `paper implementation`), project label breaks (such as `paper` and `implementation`)

### What is the current behavior?

<img width="1180" alt="Screenshot 2022-11-22 at 15 31 27" src="https://user-images.githubusercontent.com/766693/203340258-74774c28-7d41-47b6-b777-407683dda600.png">

### What is the expected behavior?
The space should be honoured. 


Pull request description:
use split for parsing labels
Fixes #272 

Using `split` makes it much easier to parse the labels and honours spaces.

Patch:
diff --git a/.github/ISSUE_TEMPLATE/submission.yml b/.github/ISSUE_TEMPLATE/submission.yml
index ce53767..8ec9dca 100644
--- a/.github/ISSUE_TEMPLATE/submission.yml
+++ b/.github/ISSUE_TEMPLATE/submission.yml
@@ -76,19 +76,17 @@ body:
         - plugin
         - tutorial
         - paper implementation
-        - fork
         - provider
-        - algorithm
-        - demo
+        - algorithms
         - simulation
-        - sdk
+        - circuit simulator
         - chemistry
         - physics
         - machine learning
         - circuit
-        - pulse
-        - qasm
+        - openpulse
+        - openqasm
         - finance
-        - hardware
         - game
         - notebook
+        - converter
diff --git a/ecosystem/daos/jsondao.py b/ecosystem/daos/jsondao.py
index d5f5771..ce254c2 100644
--- a/ecosystem/daos/jsondao.py
+++ b/ecosystem/daos/jsondao.py
@@ -1,5 +1,7 @@
 """DAO for json db."""
 from typing import Optional, List
+import os
+import json
 
 from tinydb import TinyDB, Query
 
@@ -16,8 +18,9 @@ class JsonDAO:
         Args:
             path: path to store database in
         """
-        self.path = path if path is not None else "./"
+        self.path = path if path is not None else os.getcwd()
         self.database = TinyDB("{}/members.json".format(self.path), indent=4)
+        self.labels_json_path = os.path.join(self.path, "labels.json")
 
     def insert(self, repo: Repository) -> int:
         """Inserts repository into database.
@@ -28,6 +31,7 @@ class JsonDAO:
         Return: int
         """
         table = self.database.table(repo.tier)
+        self.update_labels(repo.labels)
         return table.insert(repo.to_dict())
 
     def get_repos_by_tier(self, tier: str) -> List[Repository]:
@@ -93,11 +97,27 @@ class JsonDAO:
         table = self.database.table(tier)
         return table.update({"stars": stars}, Query().url == url)
 
+    def update_labels(self, labels: List[str]) -> List[int]:
+        """Updates labels db."""
+        with open(self.labels_json_path, "r") as labels_file:
+            label_dscs = {
+                label["name"]: label["description"] for label in json.load(labels_file)
+            }
+
+        merged = {**{l: "" for l in labels}, **label_dscs}
+        new_label_list = [
+            {"name": name, "description": dsc} for name, dsc in merged.items()
+        ]
+        with open(self.labels_json_path, "w") as labels_file:
+            json.dump(
+                sorted(new_label_list, key=lambda x: x["name"]), labels_file, indent=4
+            )
+
     def add_repo_test_result(
         self, repo_url: str, tier: str, test_result: TestResult
     ) -> Optional[List[int]]:
         """Adds test result for repository.
-        Overwrites latest test results and adds to historical test results.
+        Overwrites the latest test results and adds to historical test results.
 
         Args:
             repo_url: url of the repo
diff --git a/ecosystem/resources/labels.json b/ecosystem/resources/labels.json
new file mode 100644
index 0000000..75ebf86
--- /dev/null
+++ b/ecosystem/resources/labels.json
@@ -0,0 +1,90 @@
+[
+  {
+    "name": "QAMP",
+    "description": "Project from the Qiskit Advocate Mentorship Program (QAMP)"
+  },
+  {
+    "name": "QEC",
+    "description": "Related to or tools for performing Quantum Error Correction"
+  },
+  {
+    "name": "algorithms",
+    "description": "Extends the Qiskit algorithms library"
+  },
+  {
+    "name": "chemistry",
+    "description": "Related to or with applications in chemistry"
+  },
+  {
+    "name": "circuit",
+    "description": "Extends the Qiskit circuit library, implements a circuit transformation or analysis"
+  },
+  {
+    "name": "circuit simulator",
+    "description": "Local circuit simulators that can run without connection to any online backend"
+  },
+  {
+    "name": "converter",
+    "description": "Tools for converting Qiskit objects (such as QuantumCircuit) into other frameworks"
+  },
+  {
+    "name": "error mitigation",
+    "description": "Tools or algorithms for mitigating errors"
+  },
+  {
+    "name": "finance",
+    "description": "Related to or with applications in finance"
+  },
+  {
+    "name": "game",
+    "description": "Games or playful education resources that use Qiskit"
+  },
+  {
+    "name": "hardware",
+    "description": "Related with quantum hardware design or quantum control"
+  },
+    {
+    "name": "julia",
+    "description": "Tools written in the Julia programming language"
+  },
+  {
+    "name": "machine learning",
+    "description": "Related to or with applications in machine learning"
+  },
+  {
+    "name": "openqasm",
+    "description": "Supports OpenQASM as an exchange format"
+  },
+  {
+    "name": "optimization",
+    "description": "Related to or with applications in optimization"
+  },
+  {
+    "name": "paper implementation",
+    "description": "Implements ideas from peer-reviewed papers (owned or from third parties)"
+  },
+    {
+    "name": "partners",
+    "description": "Software in maintained as part of the Qiskit Partnership Program"
+  },
+  {
+    "name": "physics",
+    "description": "Related to or with applications in physics"
+  },
+  {
+    "name": "plugin",
+    "description": "Transpiler or synthesis Qiskit plugin"
+  },
+  {
+    "name": "prototype",
+    "description": "Software or tool written by the Qiskit prototypes team"
+  },
+    {
+    "name": "provider",
+    "description": "Implements a Qiskit provider interface, either local or remote"
+  },
+  {
+    "name": "simulation",
+    "description": "Software for quantum simulation"
+  }
+]
diff --git a/ecosystem/resources/members.json b/ecosystem/resources/members.json
index f25dd36..41e66f6 100644
--- a/ecosystem/resources/members.json
+++ b/ecosystem/resources/members.json
@@ -6,7 +6,7 @@
             "created_at": 1636403010.377695,
             "description": "Aer provides high-performance quantum computing simulators with realistic noise models.",
             "labels": [
-                "simulation"
+                "circuit simulator"
             ],
             "licence": "Apache 2.0",
             "name": "qiskit-aer",
@@ -24,7 +24,7 @@
             "alternatives": null,
             "contact_info": null,
             "created_at": 1636403009.538761,
-            "description": "Qiskit Optimization is an open-source framework that covers the whole range from high-level modeling of optimization problems, with automatic conversion of problems to different required representations, to a suite of easy-to-use quantum optimization algorithms that are ready to run on classical simulators, as well as on real quantum devices via Qiskit.",
+            "description": "Framework that covers the whole range from high-level modeling of optimization problems, with automatic conversion of problems to different required representations, to a suite of easy-to-use quantum optimization algorithms that are ready to run on classical simulators, as well as on real quantum devices via Qiskit.",
             "labels": [
                 "algorithms",
                 "optimization"
@@ -126,9 +126,9 @@
             "alternatives": null,
             "contact_info": null,
             "created_at": 1628883441.121108,
-            "description": "Qiskit Dynamics is an open-source project for building, transforming, and solving time-dependent quantum systems in Qiskit.",
+            "description": "Dynamics is an open-source project for building, transforming, and solving time-dependent quantum systems in Qiskit.",
             "labels": [
-                "differential-equations"
+                "simulation"
             ],
             "licence": "Apache 2.0",
             "name": "qiskit-dynamics",
@@ -148,7 +148,7 @@
             "alternatives": "_No response_",
             "affiliations": "OpenQASM specification contributors",
             "labels": [
-                "qasm"
+                "openqasm"
             ],
             "created_at": 1660223774.444544,
             "updated_at": 1660223774.44455,
@@ -1575,7 +1575,7 @@
             "alternatives": null,
             "affiliations": null,
             "labels": [
-                "library"
+                "error mitigation"
             ],
             "created_at": 1669737592.874388,
             "updated_at": 1669737592.874389,
@@ -1824,7 +1824,7 @@
             "alternatives": null,
             "affiliations": null,
             "labels": [
-                "library"
+                "converter"
             ],
             "created_at": 1669737592.854217,
             "updated_at": 1669737592.854218,
@@ -2012,7 +2012,8 @@
             "alternatives": null,
             "affiliations": null,
             "labels": [
-                "library"
+                "algorithms",
+                "converter"
             ],
             "created_at": 1669737592.986728,
             "updated_at": 1669737592.986729,
@@ -2190,7 +2191,7 @@
             "alternatives": null,
             "affiliations": null,
             "labels": [
-                "library"
+                "convert"
             ],
             "created_at": 1669737593.179157,
             "updated_at": 1669737593.179158,
@@ -2450,7 +2451,7 @@
             "alternatives": null,
             "affiliations": null,
             "labels": [
-                "library"
+                "hardware"
             ],
             "created_at": 1669737593.11913,
             "updated_at": 1669737593.119131,
@@ -2958,14 +2959,12 @@
         "15": {
             "name": "QiskitBot",
             "url": "https://github.com/infiniteregrets/QiskitBot",
-            "description": "A discord bot that allows you to execute Quantum Circuits, look up the IBMQ Qiskit's Documentation, and search questions on the Quantum Computing StackExchange",
+            "description": "A discord bot that allows you to execute Quantum Circuits, look up the Qiskit's Documentation, and search questions on the Quantum Computing StackExchange",
             "licence": "Apache 2.0",
             "contact_info": null,
             "alternatives": null,
             "affiliations": null,
-            "labels": [
-                "tool"
-            ],
+            "labels": [],
             "created_at": 1641938992.363096,
             "updated_at": 1641938992.363099,
             "tests_results": [],
@@ -2984,7 +2983,7 @@
             "alternatives": null,
             "affiliations": null,
             "labels": [
-                "library"
+                "machine learning"
             ],
             "created_at": 1669737592.629617,
             "updated_at": 1669737592.629618,
@@ -3731,10 +3730,10 @@
         "21": {
             "name": "bosonic-qiskit",
             "url": "https://github.com/C2QA/bosonic-qiskit",
-            "description": "NQI C2QA project to simulate hybrid boson-qubit systems within QisKit.",
+            "description": "NQI C2QA project to simulate hybrid boson-qubit systems within Qiskit.",
             "licence": "BSD 2-Clause Simplified or FreeBSD license",
             "labels": [
-                "sdk",
+                "simulation",
                 "physics"
             ],
             "created_at": 1669737592.701088,
@@ -4809,7 +4808,7 @@
             "alternatives": "",
             "affiliations": "IBM",
             "labels": [
-                "runtime",
+                "provider",
                 "partner"
             ],
             "created_at": 1654696489.147673,
@@ -5292,14 +5291,14 @@
         "11": {
             "name": "qiskit-qec",
             "url": "https://github.com/qiskit-community/qiskit-qec",
-            "description": "Qiskit Framework for Quantum Error Correction is an open-source framework for developers, experimentalist and theorists of Quantum Error Correction (QEC).",
+            "description": "Framework for Quantum Error Correction is an open-source framework for developers, experimentalist and theorists of Quantum Error Correction (QEC).",
             "licence": "Apache 2.0",
             "contact_info": "_No response_",
             "alternatives": "_No response_",
             "affiliations": "_No response_",
             "labels": [
-                "algorithm",
-                "sdk",
+                "algorithms",
+                "QEC",
                 "circuit"
             ],
             "created_at": 1662992390.122591,
@@ -5319,8 +5318,7 @@
             "created_at": 1628883441.119202,
             "description": "Qiskit Metal is an open-source framework for engineers and scientists to design superconducting quantum devices with ease.",
             "labels": [
-                "hardware",
-                "design"
+                "hardware"
             ],
             "licence": "Apache 2.0",
             "name": "qiskit-metal",
diff --git a/ecosystem/utils/submission_parser.py b/ecosystem/utils/submission_parser.py
index 2fde1eb..3b5d0ed 100644
--- a/ecosystem/utils/submission_parser.py
+++ b/ecosystem/utils/submission_parser.py
@@ -28,7 +28,7 @@ def parse_submission_issue(body_of_issue: str) -> Repository:
     licence = parse[9]
     affiliations = parse[11]
 
-    labels = re.findall(r"([\w\]+)([\w\-\_]+)", parse[13])
+    labels = [l.strip() for l in parse[13].split(",")]
 
     return Repository(
         name,


Test patch:
diff --git a/tests/daos/test_dao.py b/tests/daos/test_dao.py
index f006f01..00afac6 100644
--- a/tests/daos/test_dao.py
+++ b/tests/daos/test_dao.py
@@ -1,5 +1,6 @@
 """Tests for entities."""
 import os
+import json
 from unittest import TestCase
 
 from ecosystem.daos import JsonDAO
@@ -27,7 +28,9 @@ class TestJsonDao(TestCase):
     def setUp(self) -> None:
         self.path = "../resources"
         self.members_path = "{}/members.json".format(self.path)
+        self.labels_path = "{}/labels.json".format(self.path)
         self._delete_members_json()
+        self._create_dummy_labels_json()
         if not os.path.exists(self.path):
             os.makedirs(self.path)
 
@@ -44,6 +47,16 @@ class TestJsonDao(TestCase):
         if not os.path.exists(self.path):
             os.makedirs(self.path)
 
+    def _delete_labels_json(self):
+        """Deletes labels file.
+        Function: JsonDao
+                -> delete
+        """
+        if os.path.exists(self.labels_path):
+            os.remove(self.labels_path)
+        if not os.path.exists(self.path):
+            os.makedirs(self.path)
+
     def test_start_update(self):
         """Test update start for repo."""
         self._delete_members_json()
@@ -219,6 +232,17 @@ class TestJsonDao(TestCase):
             ),
         )
         self.assertEqual(res, [1])
+        self.assertLabelsFile(
+            [
+                {"description": "description for label 1", "name": "label 1"},
+                {"description": "description for label 2", "name": "label 2"},
+                {"description": "description for label 4", "name": "label 4"},
+                {"description": "", "name": "mock"},
+                {"description": "", "name": "tests"},
+                {"description": "", "name": "wsdt"},
+            ]
+        )
+
         recovered_repo = dao.get_by_url(main_repo.url, tier=main_repo.tier)
         self.assertEqual(
             recovered_repo.tests_results,
@@ -350,3 +374,18 @@ class TestJsonDao(TestCase):
         self.assertEqual(test_results[0].test_type, TestType.DEV_COMPATIBLE)
         self.assertEqual(test_results[1].test_type, TestType.STABLE_COMPATIBLE)
         self.assertEqual(test_results[2].test_type, TestType.STANDARD)
+
+    def assertLabelsFile(self, result):  # pylint: disable=invalid-name
+        """Asserts the content of labels.json matches the result dict"""
+        with open(self.labels_path, "r") as labels_file:
+            content = json.load(labels_file)
+        self.assertEqual(content, result)
+
+    def _create_dummy_labels_json(self):
+        dummy_data = [
+            {"name": "label 1", "description": "description for label 1"},
+            {"name": "label 2", "description": "description for label 2"},
+            {"name": "label 4", "description": "description for label 4"},
+        ]
+        with open(self.labels_path, "w") as labels_file:
+            json.dump(dummy_data, labels_file)
diff --git a/tests/resources/issue.md b/tests/resources/issue.md
index b570147..476d617 100644
--- a/tests/resources/issue.md
+++ b/tests/resources/issue.md
@@ -24,4 +24,4 @@ _No response_
 
 ### Tags
 
-tool, tutorial
+tool, tutorial, paper implementation
diff --git a/tests/test_manager.py b/tests/test_manager.py
index bc08ee6..47ece37 100644
--- a/tests/test_manager.py
+++ b/tests/test_manager.py
@@ -79,7 +79,7 @@ class TestManager(TestCase):
             os.makedirs(self.path)
 
     def test_parser_issue(self):
-        """ "Tests issue parsing function.
+        """Tests issue parsing function.
         Function: Manager
                 -> parser_issue
         Args:
@@ -114,7 +114,8 @@ class TestManager(TestCase):
             output_value[6], "::set-output name=SUBMISSION_AFFILIATIONS::_No response_"
         )
         self.assertEqual(
-            output_value[7], "::set-output name=SUBMISSION_LABELS::['tool', 'tutorial']"
+            output_value[7],
+            "::set-output name=SUBMISSION_LABELS::['tool', 'tutorial', 'paper implementation']",
         )
 
     @responses.activate
diff --git a/tests/utils/test_utils.py b/tests/utils/test_utils.py
index 6f57249..70f9fd2 100644
--- a/tests/utils/test_utils.py
+++ b/tests/utils/test_utils.py
@@ -36,4 +36,6 @@ class TestUtils(TestCase):
         self.assertEqual(parsed_result.alternatives, "tititata")
         self.assertEqual(parsed_result.licence, "Apache License 2.0")
         self.assertEqual(parsed_result.affiliations, "_No response_")
-        self.assertEqual(parsed_result.labels, ["tool", "tutorial"])
+        self.assertEqual(
+            parsed_result.labels, ["tool", "tutorial", "paper implementation"]
+        )