Customizing the GraphQL schema of a Gatsby project with nested types breaks functionality of transformer plugins - graphql

In a project where I'm sourcing data from NetlifyCMS I need to extend the GraphQL schema of Gatsby because there are optional fields in my CMS, which would cause errors trying to query non-existing data from GraphQL.
The below code extends the GraphQL types to always include the optional fields
// gatsby-node.js
// ADD OPTIONAL TYPES
// note: I have also tried exports.sourceNodes
exports.createSchemaCustomization = ({ actions }) => {
const { createTypes } = actions
const typeDefs = `
type SettingsJson implements Node {
tags: String
name: String
phone: String
email: String
additional_info: [SettingsJsonAdditional_info]
}
type SettingsJsonAdditional_info {
type: String
text: String
}
`
createTypes(typeDefs)
}
Sourcing and transforming the tags, name, phone and email work as expected. Queries return null if an optional field has not been filled out, and the query returns the correct value after being set. However, querying for additional_info always returns null even when containing data.
This is the json-file generated by NetlifyCMS:
{
"name": "Name Nameson",
"phone": "+41 1234 5678",
"email": "mail#example.com",
"additional-info": [
{
"type": "Booking",
"text": "Booker McBookerson <book#book.com>"
}
]
}
The following GraphQL query shows that the data is not being transformed properly when extending the GraphQL schema myself.
Query
query {
file(relativePath: {eq: "settings/contacts.json"}) {
internal {
content
}
childSettingsJson {
name
phone
email
additional_info {
type
text
}
}
}
}
Response
{
"data": {
"file": {
"internal": {
"content": "{\n \"name\": \"Name Nameson\",\n \"phone\": \"+41 1234 5678\",\n \"email\": \"mail#example.com\",\n \"additional-info\": [\n {\n \"type\": \"Booking\",\n \"text\": \"Booker McBookerson <book#book.com>\"\n },\n {\n \"type\": \"Booking2\",\n \"text\": \"Booker2 McBookerson <book#book.com>\"\n }\n ]\n}"
},
"childSettingsJson": {
"name": "Name Nameson",
"phone": "+41 1234 5678",
"email": "mail#example.com",
"additional_info": null
}
}
},
"extensions": {}
}
When the types are inferred by the transformer plugin itself I get the expected data when querying
// ...
"additional_info": [
{
"type": "Booking",
"text": "Booker McBookerson <book#book.com>"
}
]
// ...
This example uses json files with gatsby-transformer-json. I have tried with gatsby-transformer-yaml too with the same results.
Is it possible to add my array of SettingsJsonAdditional_info to the schema to get the "optional field" functionality I'm looking for?

One kind of hacky solution I have found is to make the transformer plugin infer the types by adding a dummy-file that will be sourced and transformed along with "real" files.
// dummy.json
{
"name": "dummy",
"phone": "dummy",
"email": "dummy",
"tags": "dummy",
"additional-info": [
{
"type": "dummy",
"text": "dummy"
}
]
}
This file can be hidden from NetlifyCMS (by simply not including a UI entry for it in the config.yml file of NetlifyCMS. It will guarantee that you can always query for the fields included in this file without getting GraphQL "field doesn't exist" errors.

Related

Use Postman to test Appsync Subscription

I have been able to successfully execute Appsync GraphQL queries and mutations from Postman. However, i'm struggling to connect to subscriptions which are websocket urls.
How can I achieve the same ?
Since Postman supports WebSockets testing GraphQL subscriptions is achievable as well. Such a testing requires two steps:
connection to a server,
sending a start message.
Establishing a connection:
Create a new WebSocket request.
Put your server URL ws:// or wss://.
Add custom header parameter Sec-WebSocket-Protocol: graphql-ws. Other headers may depend on your server configuration.
Press the "Connect" button.
When the connection is established we may start a subscription.
In the "New message" field put the command.
Press the "Send" button.
The start message should look like this:
{
"id":"1",
"payload": {
"operationName": "MySubscription",
"query": "subscription MySubscription {
someSubscription {
__typename
someField1
someField2 {
__typename
someField21
someField22
}
}
}",
"variables": null
},
"type": "start"
}
operationName is just the name of your subscription, I guess it's optional. And someSubscription must be a subscription type from your schema.
query reminds regular GraphQL syntax with one difference:
__typename keyword precedes every field list.
For example, the query from the payload in regular syntax looks like the following:
subscription MySubscription {
someSubscription {
someField1
someField2 {
someField21
someField22
}
}
}
Example message with parameters (variables):
{
"id":"1",
"payload": {
"operationName": "MySubscription",
"query": "subscription MySubscription($param1: String!) {
someSubscription((param1: $param1)) {
__typename
someField
}
}",
"variables": {
"param1": "MyValue"
}
},
"type": "start"
}
It also reminds regular GraphQL syntax as described above.
variables is an object with your parameters.
#Vladimir's answer is spot on. Adding a few notes for folks still having trouble.
Full document here # https://docs.aws.amazon.com/appsync/latest/devguide/real-time-websocket-client.html
Step 1 - establish connection:
make sure to base64 encode values in "header" and "payload" querystrings
header example:
{
"host":"example1234567890000.appsync-api.us-east-1.amazonaws.com",
"x-api-key":"da2-12345678901234567890123456"
}
payload: You can pass in empty payload
{}
Step 2 - register subscription:
Include the authorization in the message. Escape line feeds properly "\n" throws an error but "\\n" works. it throws the following error - misleading.
Don't forget to stringify value in "data" field.
{
"type": "error",
"payload": {
"errors": [
{
"errorType": "UnsupportedOperation",
"message": "unknown not supported through the realtime channel"
}
]
}
}
{
"id": "2",
"payload": {
"data": "{\"query\":\"subscription onCreateMessage { changeNotification{ __typename changeType from } }\",\"variables\":{}}",
"extensions":{
"authorization":{
"host":"example1234567890000.appsync-api.us-east-1.amazonaws.com",
"x-api-key":"da2-12345678901234567890123456"
}
}
},
"type": "start"
}

grpc/protobuffer ask for specific fields

GraphQL lets you ask for specific fields, the response contains only the fields that you had asked for. For example:
a graphql query like:
{
hero {
name
}
}
will return:
{
"data": {
"hero": {
"name": "R2-D2"
}
}
}
where as a graphQl query like:
{
hero {
name
friends {
name
}
}
}
would return:
{
"data": {
"hero": {
"name": "R2-D2",
"friends": [
{
"name": "Luke"
},
{
"name": "Han Solo"
},
{
"name": "Leia"
}
]
}
}
}
Is there a similar mechanism/library/pattern that can be used in gRPC to achieve the same?
FieldMask is similar in protobuf. It is a list of fields to retain, so the first example would be paths: "hero.name" and the second would be paths: ["hero.name", "hero.friends.name"].
It is probably most frequently used to specify which fields should be changed in an update. But it can equally be used to specify the fields that should be returned.
The server can either process the FieldMask directly (e.g., only using the listed fields in a SELECT SQL query), or it can retrieve all the information and filter the result using FieldMaskUtil.merge() to copy just the requested fields into a new proto message to return to the client.

is there a way to group queries in graphQL?

I'm trying to group graphQL queries to have a more organized response.
I want to make a query for allEmployees and get back something in the following format
GraphQL Query
{
Employees:allEmployees{
id
firstName
lastName
}
}
Response
{
"data": {
"Employees": [
"new":[
{
"id": "1",
"firstName": "James",
"lastName": "Test"
},
{
"id": "3",
"firstName": "Charles",
"lastName": "Tes"
}
],
"updated":[
{
"id": "4",
"lastName": "Test"
},
],
"deleted":[
{
"id": "1",
},
],
}
}
}
I've looked into a few options to get named sub-request( like new, updated and deleted) via aliases on fragments but that doesn't seem to be a thing. I've looked at unions, but that doesn't seem to be what I'm looking for.
Ideally I would love to query graphql like...
{
Employees:{
new: allEmployees(status:"new"){
id
firstName
lastName
}
updated: allEmployees(status:"updated"){
id
firstName
lastName
}
deleted: allEmployees(status:"deleted"){
id
}
}
but I don't think it is possible to pass a nested query like this.
Is there anyway to do something like this? I'm using graphql with ruby via the graphql-ruby gem.
please let me know if anyone needs more information?
Thanks
Edit
To clarify. We have multiple entities that will follow the new, updated, deleted pattern. Looking to try and get a response where the results are nested inside a parent name/alias (Employees, Users)
{
"data": {
"Employees": [
"new":[...],
"updated":[...],
"deleted":[...],
],
"Users": [
"new":[...],
"updated":[...],
"deleted":[...],
],
...
}
That is why we would want to nest
GraphQL definitely supports nested queries and multiple top-level queries, and graphql-ruby supports these just fine.
If your GraphQL schema looks like:
type Employee {
id: ID!
firstName: String
lastName: String
}
enum Status { NEW, UPDATED, DELETED }
type Query {
allEmployees(status: Status): [Employee!]!
}
then you could write a query
fragment EmployeeData on Employee { id firstName lastName }
query Everyone {
new: allEmployees(status: NEW) { ... EmployeeData }
updated: allEmployees(status: UPDATED) { ... EmployeeData }
deleted: allEmployees(status: DELETED) { ... EmployeeData }
}
That wouldn't have quite the specific form you're looking for – there aren't good ways to add or remove arbitrary levels in your query, like adding an "Employees" label or removing layers from React-style connection records – but it can retrieve the data you're looking for.

Issue while including enum type in unions within avro schema

I am working with Apache Kafka to send messages to Kafka topics. I am trying to use unions in Avro Schemas including enum types for message validation. But I am facing an issue with the usage of enum types within union. I am using Kafka REST API through POSTMAN tool to post a record/message to a topic with schema validation. Below is the request payload including schema and records inline -
{
"key_schema": "{\"type\": \"record\", \"name\": \"key\", \"fields\": [{\"name\": \"keyInput\", \"type\": \"string\"}]}",
"value_schema": "{\"type\": \"record\", \"name\": \"value\", \"fields\": [{\"name\": \"valueInput1\", \"type\": \"string\"},{\"name\": \"valueInput2\",\"type\":[{\"type\":\"enum\",\"name\":\"actorobjType\",\"symbols\":[\"Agent\",\"Group\"]},\"null\"],\"default\":null}]}",
"records": [
{
"key": {
"keyInput": "testUser-key"
},
"value": {
"valueInput1": "testUser-value",
"valueInput2": "Agent"
}
}
]
}
I am getting the following error when I am trying to insert a record using above request payload -
{
"error_code": 42203,
"message": "Conversion of JSON to Avro failed: Failed to convert JSON to Avro: Expected start-union. Got VALUE_STRING"
}
After searching in different sites including stackoverflow, I came through a suggestion
asking to explicitly specify the type while passing the record as below -
{
"key_schema": "{\"type\": \"record\", \"name\": \"key\", \"fields\": [{\"name\": \"keyInput\", \"type\": \"string\"}]}",
"value_schema": "{\"type\": \"record\", \"name\": \"value\", \"fields\": [{\"name\": \"valueInput1\", \"type\": \"string\"},{\"name\": \"valueInput2\",\"type\":[{\"type\":\"enum\",\"name\":\"actorobjType\",\"symbols\":[\"Agent\",\"Group\"]},\"null\"],\"default\":null}]}",
"records": [
{
"key": {
"keyInput": "testUser-key"
},
"value": {
"valueInput1": "testUser-value",
"valueInput2": {
"enum": "Agent"
}
}
}
]
}
But then I face the below error -
{
"error_code": 42203,
"message": "Conversion of JSON to Avro failed: Failed to convert JSON to Avro: Unknown union branch enum"
}
The same suggestion worked fine for unions with other types like string and map, but with unions including enum, that does not seem to work.
I also thought there may be some other type which needs to be used for enum specification, Hence I tried some other words like below -
"valueInput2": {
"string": "Agent"
}
and
"valueInput2": {
"enumeration": "Agent"
}
But none of them seem to work. Please help me resolve this.
I ended up here, and davis michael's answer gave a hint, which helped me eventually figure it out.
Within the context of the question,
"valueInput2": {
"actorobjType": "Agent"
}
As ENUM type is not exist in JSON format, value type should be changed to correct one:
namespace + type name
In your case, it will be namespace + actorobjtype : "agent"

Spring Data ElasticSearch Build In IN query returning partial match

I am new to elastic search spring data, Today I was trying to get In query working with Spring data ES repository.
I have to do a lookup for list of user names, and if its exactly match in the index, need to get those users back as result.
I tried to use the built in repository 'In' method to do so, but it returns partial matches, please help me to make this working like SQL IN query.
Here is my repository code:
public interface UserRepository extends ElasticsearchRepository<EsUser, String>
{
public List<EsUser> findByUserAccountUserNameIn(Collection<String> terms);
}
REQUEST:
{"terms":["vijay", "arun"], "type":"NAME"}
RESPONSE:
[
{
"userId": "236000",
"fbId": "",
"userAccount": {
"userName": "arun",
"urlFriendlyName": "arun",
},
"userProfile": {
},
"userStats": {
}
},
{
"userId": "6228",
"userAccount": {
"userName": "vijay",
"urlFriendlyName": "vijay",
},
"userProfile": {
},
"userStats": {
}
},
{
"userId": "236000",
"fbId": "",
"userAccount": {
"userName": "arun singh",
"urlFriendlyName": "arun-singh",
},
"userProfile": {
},
"userStats": {
}
}
{
"userId": "236000",
"fbId": "",
"userAccount": {
"userName": "vijay mohan",
"urlFriendlyName": "vijay-mohan",
},
"userProfile": {
},
"userStats": {
}
}
]
This is because your userAccount.userName field is an analyzed string, and thus, the two tokens arun and singh have been indexed. Your query then matches the first token, which is normal.
In order to prevent this and guarantee an exact match you need to declare your field as not_analyzed, like this:
#Field(index = FieldIndex.not_analyzed)
private String userName;
Then you'll need to delete your index and the associated template in /_template, restart your application so a new template and index are created with the proper field mapping.
Then your query will work.

Resources