947fc1b84a
flag=extended_submission_state [fsc-max-nodes=15] [fsc-timeout=45] refs PFS-19811 Change-Id: Ia151a868913e6cd1d3998e0602595509b45b16d9 Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/290850 Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> QA-Review: Petra Ashment <pashment@instructure.com> Product-Review: Jody Sailor Reviewed-by: Spencer Olson <solson@instructure.com> Reviewed-by: Kai Bjorkman <kbjorkman@instructure.com> |
||
---|---|---|
.. | ||
graphql_helpers | ||
interfaces | ||
loaders | ||
mutations | ||
tracers | ||
types | ||
README.md | ||
audit_log_field_extension.rb | ||
canvas_schema.rb | ||
dynamo_connection.rb | ||
dynamo_query.rb | ||
graphql_helpers.rb | ||
graphql_node_loader.rb | ||
graphql_postgres_timeout.rb | ||
patched_array_connection.rb | ||
types.rb |
README.md
GraphQL in Canvas
Canvas has a "first-class" GraphQL data graph that is publicly exposed on an API endpoint. This is already well-documented.
Apollo Federation
In addition to the standard GraphQL endpoint, Canvas exposes a "subgraph" endpoint whose schema is suitable for use in an Apollo Federation. This is the same schema, but extended according to the Apollo Federation specification, and with some Federation directives applied to various fields and types.
The apollo-federation gem is used to add Federation directives to this subgraph. While it is important that the public-facing graph does not include Federation extensions, the gem's features can be used freely on any type or field. They are simply ignored in the public-facing graph, and do not show up in its schema.
Promoting an Object Type to a Federation Entity
A Federation entity is an object type whose definition spans multiple subgraphs. One subgraph provides its canonical definition, and others extend it.
To promote an existing type to an entity with its canonical definition in
Canvas, declare one or more key
fields and implement ::resolve_reference
.
E.g.:
module Types
class CourseType < ApplicationObjectType
key fields: "id"
def self.resolve_reference(reference, context)
legacy_id = GraphQLHelpers.parse_relay_id(reference[:id], "Course")
GraphQLNodeLoader.load("Course", legacy_id, context)
end
end
end
We expect to promote many types in exactly this way, so we've built a helper
for it. If a type's id
field is a Relay-style "global" id (as most are),
and you want to promote that type with only a single @key
directive on
the id
field, i.e. @key(fields: "id")
, you can just use key_field_id
like
so:
module Types
class CourseType < ApplicationObjectType
key_field_id
end
end
See the gem usage docs for more examples and guidance on using Federation features, including how to extend entities whose canonical definition resides in an external subgraph.
Smoke Testing the Federation Subgraph
In deployed environments, only an Apollo API Gateway will be able to query the federation subgraph. However if you need to smoke test it locally, this is the way.
-
Generate two RSA keypairs and designate one your signing key, the other your encryption key.
-
Copy
config/vault_contents.yml.example
toconfig/vault_contents.yml
, then replace thedevelopment.'app-canvas/data/secrets'.data.inst_access_signature.private_key
with the base64-encoded representation of the private key of your signing keypair, and thedevelopment.'app-canvas/data/secrets'.data.inst_access_signature.encryption_public_key
with the base64-encoded representation of the public key of your encryption keypair. -
Start up your Canvas server and get yourself an API access token, e.g. by following the "Manual Token Generation" section of the OAuth docs.
-
Export that thing as
API_TOKEN
and use it to get yourself an unencrypted InstAccess token, e.g.:
$ curl 'http://localhost:3000/api/v1/inst_access_tokens?unencrypted=1' \
-X POST \
-H 'Authorization: Bearer $API_TOKEN'
- Now export that thing as
INST_ACCESS
and use it to issue a query to the subgraph, e.g.:
$ curl http://localhost:3000/api/graphql/subgraph \
-X POST \
-H "Accept: application/json" \
-H "Content-type: application/json" \
-H "Authorization: Bearer $INST_ACCESS" \
--data '
{
"query": "query ($_representations: [_Any!]!) { _entities(representations: $_representations) { ... on Course { name } } }",
"variables": {
"_representations": [
{
"__typename": "Course",
"id": "Q291cnNlLTE="
}
]
}
}'
The above query should return a result that includes the name of Course 1, as long as it exists and the user you used to get the initial access token has permission to read it.