Repository: nautobot/nautobot

Issue:
Saved GraphQL query REST "run" endpoint reports "GraphQLQuery matching query does not exist."
<!--
    NOTE: IF YOUR ISSUE DOES NOT FOLLOW THIS TEMPLATE, IT WILL BE CLOSED.

    This form is only for reporting reproducible bugs. If you need assistance
    with Nautobot installation, or if you have a general question, please start a
    discussion instead: https://github.com/nautobot/nautobot/discussions

    Please describe the environment in which you are running Nautobot. Be sure
    that you are running an unmodified instance of the latest stable release
    before submitting a bug report, and that any plugins have been disabled.
-->
### Environment
* Python version: 3.6.14
* Nautobot version: 1.1.0

<!--
    Describe in detail the exact steps that someone else can take to reproduce
    this bug using the current stable release of Nautobot. Begin with the
    creation of any necessary database objects and call out every operation
    being performed explicitly. If reporting a bug in the REST API, be sure to
    reconstruct the raw HTTP request(s) being made: Don't rely on a client
    library such as pynautobot.
-->
### Steps to Reproduce
1. Create a saved GraphQL query and take note of its UUID
2. Verify that the query is retrievable from a GET to `api/extras/graphql-queries/<uuid>/`
3. Issue a POST to `api/extras/graphql-queries/<uuid>/run/`

<!-- What did you expect to happen? -->
### Expected Behavior

Specified GraphQL query should run and report its results

<!-- What happened instead? -->
### Observed Behavior

500 error with body:

```json
{
  "error": "GraphQLQuery matching query does not exist.",
  "exception": "DoesNotExist",
  "nautobot_version": "1.1.0",
  "python_version": "3.6.14"
}
```


Discussion:

Resolving patch:
diff --git a/nautobot/extras/api/serializers.py b/nautobot/extras/api/serializers.py
--- a/nautobot/extras/api/serializers.py
+++ b/nautobot/extras/api/serializers.py
@@ -738,7 +738,7 @@ class Meta:
 
 class GraphQLQuerySerializer(ValidatedModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name="extras-api:graphqlquery-detail")
-    variables = serializers.DictField(allow_null=True, default={})
+    variables = serializers.DictField(required=False, allow_null=True, default={})
 
     class Meta:
         model = GraphQLQuery
@@ -752,6 +752,14 @@ class Meta:
         )
 
 
+class GraphQLQueryInputSerializer(serializers.Serializer):
+    variables = serializers.DictField(allow_null=True, default={})
+
+
+class GraphQLQueryOutputSerializer(serializers.Serializer):
+    data = serializers.DictField(default={})
+
+
 # Computed Fields
 
 
diff --git a/nautobot/extras/api/views.py b/nautobot/extras/api/views.py
--- a/nautobot/extras/api/views.py
+++ b/nautobot/extras/api/views.py
@@ -434,11 +434,16 @@ class GraphQLQueryViewSet(ModelViewSet):
     serializer_class = serializers.GraphQLQuerySerializer
     filterset_class = filters.GraphQLQueryFilterSet
 
-    @swagger_auto_schema(method="post", request_body=serializers.GraphQLQuerySerializer)
+    @swagger_auto_schema(
+        method="post",
+        request_body=serializers.GraphQLQueryInputSerializer,
+        responses={"200": serializers.GraphQLQueryOutputSerializer},
+    )
     @action(detail=True, methods=["post"])
     def run(self, request, pk):
         try:
-            result = execute_saved_query(pk, variables=request.data, request=request).to_dict()
+            query = get_object_or_404(self.queryset, pk=pk)
+            result = execute_saved_query(query.slug, variables=request.data.get("variables"), request=request).to_dict()
             return Response(result)
         except GraphQLError as error:
             return Response(


Test patch:
diff --git a/nautobot/extras/tests/test_api.py b/nautobot/extras/tests/test_api.py
--- a/nautobot/extras/tests/test_api.py
+++ b/nautobot/extras/tests/test_api.py
@@ -852,11 +852,11 @@ class GraphQLQueryTest(APIViewTestCases.APIViewTestCase):
 
     @classmethod
     def setUpTestData(cls):
-        graphqlqueries = (
+        cls.graphqlqueries = (
             GraphQLQuery(
                 name="graphql-query-1",
                 slug="graphql-query-1",
-                query="{ query: sites {name} }",
+                query="{ sites {name} }",
             ),
             GraphQLQuery(
                 name="graphql-query-2",
@@ -867,7 +867,7 @@ def setUpTestData(cls):
                 name="graphql-query-3",
                 slug="graphql-query-3",
                 query="""
-query ($device: String!) {
+query ($device: [String!]) {
   devices(name: $device) {
     config_context
     name
@@ -958,10 +958,26 @@ def setUpTestData(cls):
             ),
         )
 
-        for query in graphqlqueries:
+        for query in cls.graphqlqueries:
             query.full_clean()
             query.save()
 
+    def test_run_saved_query(self):
+        """Exercise the /run/ API endpoint."""
+        self.add_permissions("extras.add_graphqlquery")
+        self.add_permissions("extras.change_graphqlquery")
+        self.add_permissions("extras.view_graphqlquery")
+
+        url = reverse("extras-api:graphqlquery-run", kwargs={"pk": self.graphqlqueries[0].pk})
+        response = self.client.post(url, **self.header)
+        self.assertHttpStatus(response, status.HTTP_200_OK)
+        self.assertEqual({"data": {"sites": []}}, response.data)
+
+        url = reverse("extras-api:graphqlquery-run", kwargs={"pk": self.graphqlqueries[2].pk})
+        response = self.client.post(url, **self.header)
+        self.assertHttpStatus(response, status.HTTP_200_OK)
+        self.assertEqual({"data": {"devices": []}}, response.data)
+
 
 class RelationshipTest(APIViewTestCases.APIViewTestCase):
     model = Relationship