Repository: fedora-infra/bodhi

Issue:
Allow to query Releases using Graphql
We want to query all releases using the `/graphql` endpoint.

Steps : 

- [x] Add grapqhql-sqlalchemy to the dev environment
- [x] Configure `/graphql` endpoint to use graphiql (see https://docs.graphene-python.org/projects/sqlalchemy/en/latest/tutorial/#creating-graphql-and-graphiql-views-in-flask) need to research for a generic solution or https://github.com/graphql-python/webob-graphql.
- [x] Create a new graphql_schemas.py module under `bodhi/server`
- [x] Add a Release class to the graphql_schemas.py using graphql-sqlalchmy examples
- [x] Add a Query class to the graphql service
- [x] Within the Query class add a method to query all releases (use the graphql_schemas.py class)
- [x] Test everything :smile: 

Expected results:

Using graphiql query all the releases, expected output is the same as `http https://bodhi.fedoraproject.org/releases/`


Discussion:

Resolving patch:
diff --git a/bodhi/server/config.py b/bodhi/server/config.py
--- a/bodhi/server/config.py
+++ b/bodhi/server/config.py
@@ -361,6 +361,9 @@ class BodhiConfig(dict):
         'initial_bug_msg': {
             'value': '%s has been submitted as an update to %s. %s',
             'validator': str},
+        'graphiql_enabled': {
+            'value': False,
+            'validator': _validate_bool},
         'greenwave_api_url': {
             'value': 'https://greenwave-web-greenwave.app.os.fedoraproject.org/api/v1.0',
             'validator': _validate_rstripped_str},
diff --git a/bodhi/server/graphql_schemas.py b/bodhi/server/graphql_schemas.py
new file mode 100644
--- /dev/null
+++ b/bodhi/server/graphql_schemas.py
@@ -0,0 +1,34 @@
+# Copyright © 2020 Red Hat Inc., and others.
+#
+# This file is part of Bodhi.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+"""Defines schemas related to GraphQL objects."""
+from graphene import relay, Field, String
+from graphene_sqlalchemy import SQLAlchemyObjectType
+
+from bodhi.server.models import Release as ReleaseModel
+
+
+class Release(SQLAlchemyObjectType):
+    """Type object representing a distribution release from bodhi.server.models like Fedora 27."""
+
+    class Meta:
+        """Allow to set different options to the class."""
+
+        model = ReleaseModel
+        interfaces = (relay.Node, )
+    state = Field(String)
+    package_manager = Field(String)
diff --git a/bodhi/server/services/graphql.py b/bodhi/server/services/graphql.py
--- a/bodhi/server/services/graphql.py
+++ b/bodhi/server/services/graphql.py
@@ -16,19 +16,43 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 """Defines API endpoints related to GraphQL objects."""
+import graphene
 from cornice import Service
+from webob_graphql import serve_graphql_request
+
+from bodhi.server.config import config
+from bodhi.server.graphql_schemas import Release
+
 
 graphql = Service(name='graphql', path='/graphql', description='graphql service')
 
 
 @graphql.get()
+@graphql.post()
 def graphql_get(request):
     """
-    Return "Hello World".
+    Perform a GET request.
 
     Args:
         request (pyramid.Request): The current request.
     Returns:
-        str: A string "Hello World".
+        The GraphQL response to the request.
     """
-    return "Hello World"
+    context = {'session': request.session}
+    return serve_graphql_request(
+        request, schema, graphiql_enabled=config.get('graphiql_enabled'),
+        context_value=context)
+
+
+class Query(graphene.ObjectType):
+    """Allow querying objects."""
+
+    allReleases = graphene.List(Release)
+
+    def resolve_allReleases(self, info):
+        """Answer Queries by fetching data from the Schema."""
+        query = Release.get_query(info)  # SQLAlchemy query
+        return query.all()
+
+
+schema = graphene.Schema(query=Query)


Test patch:
diff --git a/bodhi/tests/server/services/test_graphql.py b/bodhi/tests/server/services/test_graphql.py
--- a/bodhi/tests/server/services/test_graphql.py
+++ b/bodhi/tests/server/services/test_graphql.py
@@ -15,12 +15,80 @@
 # You should have received a copy of the GNU General Public License
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+from graphene.test import Client
+
 from bodhi.tests.server import base
+from bodhi.server.services.graphql import schema
+from bodhi.server.models import PackageManager, Release
 
 
 class TestGraphQLService(base.BasePyTestCase):
-    """This class contains tests for a /graphql endpoint"""
+    """This class contains tests for a /graphql endpoint."""
     def test_get(self):
-        """Ensure that "Hello World" is returned"""
-        res = self.app.get('/graphql')
-        assert res.body == b'"Hello World"'
+        """Ensure that a GraphQL response is returned"""
+        res = self.app.get('/graphql?query={%0A%20 allReleases{%0A%20%20%20 name%0A%20 }%0A}')
+        assert res.body == b'{"data":{"allReleases":[{"name":"F17"}]}}'
+
+    def test_allReleases(self):
+        """Testing allReleases."""
+        client = Client(schema)
+        release = Release(
+            name='F22', long_name='Fedora 22',
+            id_prefix='FEDORA', version='22',
+            dist_tag='f22', stable_tag='f22-updates',
+            testing_tag='f22-updates-testing',
+            candidate_tag='f22-updates-candidate',
+            pending_signing_tag='f22-updates-testing-signing',
+            pending_testing_tag='f22-updates-testing-pending',
+            pending_stable_tag='f22-updates-pending',
+            override_tag='f22-override',
+            branch='f22',
+            package_manager=PackageManager.dnf,
+            testing_repository='updates-testing')
+
+        self.db.add(release)
+        self.db.commit()
+
+        executed = client.execute("""{  allReleases{  name  }}""")
+        assert executed == {
+            "data": {
+                "allReleases": [{
+                    "name": "F17"
+                }, {
+                    "name": "F22"
+                }]
+            }
+        }
+
+    def test_enumfields(self):
+        """Testing enum fields on releases."""
+        client = Client(schema)
+        release = Release(
+            name='F22', long_name='Fedora 22',
+            id_prefix='FEDORA', version='22',
+            dist_tag='f22', stable_tag='f22-updates',
+            testing_tag='f22-updates-testing',
+            candidate_tag='f22-updates-candidate',
+            pending_signing_tag='f22-updates-testing-signing',
+            pending_testing_tag='f22-updates-testing-pending',
+            pending_stable_tag='f22-updates-pending',
+            override_tag='f22-override',
+            branch='f22',
+            package_manager=PackageManager.dnf,
+            testing_repository='updates-testing')
+
+        self.db.add(release)
+        self.db.commit()
+
+        executed = client.execute("""{  allReleases{  state    packageManager  }}""")
+        assert executed == {
+            'data': {
+                'allReleases': [{
+                    'packageManager': 'unspecified',
+                    'state': 'current'
+                }, {
+                    'packageManager': 'dnf',
+                    'state': 'disabled'
+                }]
+            }
+        }