Giter Club home page Giter Club logo

umbraco-graphql's People

Contributors

andrewmckaskill avatar benjaminc avatar jackpenney avatar peteduncanson avatar rasmusjp avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

umbraco-graphql's Issues

How to get to parent doctypes in a "nice" way?

In my example tree I have a layout similar to this:

Home
 - Destinations
	- Countries (country Doctype)
		- Greece (region Doctype )
			- Rhodes (resort Doctype)
				- Petes Amazing Hotel (hotel Doctype)

If I then query for my hotel and want to know what resort it is in currently I have to know about the tree structure (is this such a bad thing? I don't know but this is just an idea):

{
  content(id:2755) {
    ... on HotelPage {
      features,
      ancestors(includeSelf: true) {
        items {
          ... on ResortPage {
        		name   
          }
        }
      }
    }
  }
}

Similar to #3 I'd prefer to do something like this:

{
  content(id:2755) {
    ... on HotelPage {
      features,
	  resort {
		name
	  }
    }
  }
}

I'm not sure as yet how we might do that. The code would need to know which way to go (up or down the tree I guess) which we could do by seeing what child doctypes are allowed and many auto wiring them up? This could very quickly get into a mess if not careful and cause some crazy long graphs of possibilities or it could be amazing :)

Field Metrics Middleware to report on which fields are used

Facebook do two clever tricks with their APIS:

  1. Never delete a field until you 100% know that nothing is using it anymore
  2. Log which fields are getting accessed and from where so you can help achieve point 1

Point one is sort of handled in #1 and this issue is about how we might achieve point 2. For now this one can be filed until "one day maybe" but its one of those features that just adds weight to the strength of this setup, if we can cover these sort of situations then it shows a forward thinking solution rather than a developers weekend coding scratch.

I'd suggest that we inject in some new middle ware that could initially just log a count of how many times each field is accessed. Ideally this would just log into memory for speed and intermittently flush this to the database. This setup would have to allow for multiple instances too so I would suggest that when it flushed it simple adds its count onto the one stored in the database and then resets the memory count back to 0.

Triggers for a flush could be:

  1. if the counter gets above a threshold (so if the counter gets up to 1,000 then flush)
  2. if its been more than X minutes since we flushed (and the counter is not zero)
  3. on shut down events?

The amounts should be configurable of course with some sensible defaults baked in. Suggest that is stored in a config file.

These counts can get HUGE so we would need to use a suitably large int for them. I'd suggest we store a last accessed time too so we can see when that field was last hit.

This mod wouldn't tell you if anyone is using the field elsewhere (and to be fair this mod might make a nice Core change or a separate package at some point) but it will give you eyes on the usage for the sake of GraphQL.

A report for the back office might be nice but first step is getting something logged to the database.

Need a way to get multiple objects by id (basically we need a byIds method)

Trying to get multiple Hotels in my example. I can't seem to do it without having to use an alias for each on. Ideally I just want to pass in an array of ids and have an array of Hotels returned:

// With alias' but this returns an object with named fields 
// (hotel1 etc.) which you can't iterate over easily
{
  content {
    byType {
      hotel1: HotelPage(id: 2092) {
        pageTitle
      }
      hotel2: HotelPage(id: 2091) {
        pageTitle
      }
      hotel3: HotelPage(id: 2087) {
        pageTitle
      }
    }    
  }
}
// This is more what I want it to do
{
  content {
    byIds (ids: [123,234,355]) {
      ... on HotelPage {
		pageTitle
      }
    }    
  }
}

Change the way to nest Umbraco built in fields to ease readability of queries

While demo'ing GraphQL at the UK Umbraco Fest I made some bigger queries (see this video for an example: https://drive.google.com/file/d/1kOmHV2cxHo2ZnoEzRjp3ubSl5msQOmqq/view). They needed to get to multiple IPublishedContent items and then access that items build in Umbraco properties (url and name in this case).

I found the syntax for these very wordy as you have to nest it in via the _contentData object and it made the whole thing not read very well. I think we need a rethink on this one. Currently I'm thinking we either use underscores as a prefix for the built in fields or add "node" to the front of it or even "umb". Underscores win for me as it means they will appear at the top of the Intellisense and all grouped together.

So instead of this:

{
	content {
		byId( id: 123 ) {
			_contentData {
				name,
				url
			}
			parent {
				_contentData {
					name
				}
			}
		}
	}
}

I propose we use something like this:

{
	content {
		byId( id: 123 ) {
			_name,
			_url
			parent {
				_name
			}
		}
	}
}

Allow query by aliases name rather than using content(id:123)

As it stands the auto-generated schema maps the tree structure in Umbraco. However sometimes you want to just jump to some content straight away. Use case is we have a short list of hotels and want to just get the data for the hotels in the list as hotel objects. I can do this currently using something like this (which is amazing btw):

{
  content(id:2755) {
    ... on HotelPage {
      name,
	  starRating,
	  price
    }
  }
}

However I'd prefer the syntactical sugar of something like this:

{
  hotelPage(id:2755) {
      name,
	  starRating,
	  price
  }
}

Suggest we would need to either auto add the (id:123) option to all doctypes which under the hood would do a content(id:123) and then a cast of some sort?

Active development?

Hi @rasmusjp

Is this still under active development?
Are you intending on rolling GraphQL into Umbraco Headless?

If you are no longer working on this project, do you mind me forking it, so that we can try to provide a full, open source GraphQL implementation for our favourite CMS?

Thanks

Tom

Added config toggles to opt into different debug profiling

As per #5 (comment) we currently have just the one config toggle "EnableMetrics" which will include both sets of available metrics. Would be nice to have option of include one or the other as both together can be a bit over-whelming.

This could be via a toggle or I'm also keen on allowing for this to be included on a per-query basis if possible by asking for the stats in the query itself. I'll settle for a toggle for now.

How to handle permissions when not casting to a Doctype

We need to think about how we should handle permissions for generic PublishedContent
Currently requests 1. & 2. will use the permissions we've set on the home Doctype while example 3. they won't apply, even then we're accessing the same pieces of content.

I think we need a step where we lookup the doctype of what's being requested in cases like this and apply the appropriate doctype permissions.

Another option is to have a separate set of permissions for all generic requests but i don't think that's the best way to go about it because we could end up leaking details about doctypes we want totally hidden

# 1. Request for Home content 
{
  home {
     _contentData {
         createDate
  }
 }
}

# 2. Request on home content with cast
{
 content(id:1106) {
  ...on Home {
    _contentData {
     createDate
   }
  }
 }
}
# 3. Generic request for PublishedContent 
{
 content(id:1106) {
   _contentData {
    createDate
   }
  }
}

Thoughts?

V8 looking ahead

Should probably give some thought to V8, I don't think its ready yet but there will be a new public build out at the end of the month that we could "play" with. Only issues I think we will have so far is how we get the data (there is no more GetProperty("title") call, its now just .Value("title) instead. Secondly due to variants we will need to allow for cultures I believe. The admin UI for the back office might need to be in a Content App too.

Security investigation

Things to investigate, consider and eventually transfer into separated tasks:

  • Content restrictions (public access)
  • Permissions
  • What and how is returned in terms of "no access" (configurable?)

How to handle multi-lingual sites/content/fields

Multi-lingual can be done in a number of different ways and at some point its going to crop up about how this could support it.

  • Vorto, should be an easy one, it has all the values stored as JSON per field so we can just send all that down the wire?
  • By domain (See #8), if we lock down by domain then we would only return that
  • Dictionary items, do we return these as a collection once and your frontend has to do the swapping out or do we do this for you within GraphQL?

Remove ToDos from README.md and move them into issues (if they still apply)

Validate which items can be removed from the list below:

  • GraphQL Playground --- to be deprecated
  • Schema Stitching (extending types)
  • Metrics
  • Published Content
  • Published Media --- to be deprecated
  • Dictionary --- new issue created (#66)
  • Statistics (field usage etc.)
  • Deprecation (Content Types and Properties)
  • API Tokens (OAUTH) with permissions (for content types and properties)
  • Data Types --- to be deprecated
  • Document Types --- to be deprecated
  • Media Types --- to be deprecated
  • Member Types --- to be deprecated
  • Content
  • Media --- to be deprecated
  • Members --- to be deprecated (?)
  • Documentation

Discuss, decide, vote?

Setting permissions on Compositions

A side issue raise by our work on #10 is how to manage permissions for compositions.

I think you should be able to set permissions across the site via a composition, what I mean by that is on the Navigation Base composition in the demo site I should be able set permissions on the seoMetaDescription field to be readable and that should be site wide and respected by GraphQL, any doctype with that composition and therefore that field on it will now make that field public and readable. This should only happen IF I've opted in on the composition to allow for this.

If I only want some doctypes to have this field visible then I would set it per doctype.

Could I if I wanted to only opt out some doctypes when I want the default to be make something public. So I could select the Composition to be public and then uncheck it on the some doctype itself? That would mean that our checks would have to ensure that yes you can view this compsition and yes you can view this doctype too.

A way around this is to do a bit of a UI fudge, we allow you to select whole Compositions via our Permissions UI (as yet un-built) and all that does is write down the permissions per doctype under the hood and we completely ignore Compositions being a "thing" when it comes to permission, its just a mental model to help make the UI managable and helpful.

Do we need to worry about domains?

A little early to be worrying about this now but something that might need some thought. If I bind a node to a domain and then hit that domain's GraphQL endpoint do I only get back that domains data or can I traverse the whole tree and other root nodes?

Built in IPublishedContent properties can clash with user generated DocType aliases

If you create a field with an alias of "name" on your doc type and then try to run the code it will blow up with an error about there being multiple "name" fields. So we are going to have to namespace the built in IPublishedContent properties so they don't clash. Then on the front end we can have these fields in a nested object something like:

{
  person {
	name,
	nodeData {
		name,
		id,
		sortOrder,
		url
	}
  }
}

or alternatively we can just prefix them all with "node" but I think this is less clear/nice:

{
	person {
		name,
		nodeName,
		nodeId,
		nodeSortOrder,
		nodeUrl
	}
}

What's the syntax for filtering queries?

Hi,

I'm massively struggling to work out the syntax for filtering a result set with the in-built filters. Are you able to provide a sample query for returning filtered nodes from Umbraco?

For example, with a custom node called book in Umbraco and bookDirectory being the parent folder of books, what should the query be? I've tried lots of combinations, but not getting anything valid!

{
  umbraco {
    content {
      byType {
        bookDirectory {
          pageTitle
          bookStartsWithA: _children(
            filter: { _name_starts_with: "A" }
            orderBy: _name_ASC
          ) {
            items {
              ... on BookPublishedContent {
                _name
                author
              }
            }
          }
        }
      }
    }
  }
}

Support for Unicore

We should look into how we can support the upcoming .Net Core version of Umbraco.

My initial thoughts is to move the Umbraco specific parts to their own projects and be able to share as much code as possible.

GraphiQL javascript embedded resource is not being found

As the code stands now I can't get the GraphiQL UI to work as it appears it can't find the /umbraco/graphiql.js file referenced when we go to the /umbraco/graphql url.

Not sure what is up there as resources is not really something I do all that often let a long .html files which are a resource themselves then referencing other resources!

Supporting members

This was raised by someone at the USA Fest and I'd not really given it any thought. How can we access members. I've seen some commented out comments here and there about members in the code so assume its not "working" as yet. This defo needs to have authenication in there though. We shouldn't really be having members accessible via a public API. As a result until we get accounts sorted out properly then members can be on hold (see #10)

"Enhance/Customise" the GraphiQL interface to allow us to do a bit more with it

GraphiQL allows you to customise it via adding React components etc.

See: https://github.com/graphql/graphiql for details of how to customise

We will need this for a couple of reasons.

  1. For authentication we are hoping to pass in some token via a header or a separate querystring param. For that to work we ideally need a UI to allow you to cut and paste in a token (and ideally save it down somewhere). Useful for #10
  2. We now have debug information coming back (which is awesome) however its pretty scary the amount it returns. Pondered having a debug summary panel that would pull that data out and make it a bit more human friendly, tabs etc. See #5

Allow "flattening" of query responses to shorten object nesting?

@JackPenney found this the other week after I raised an issue with my queries looking a bit of a mess:

https://www.fourkitchens.com/blog/development/graphql-leveler-controlling-shape-query/

If I have a heavily nested query it would be nice to make the response a little nicer to read and "hoist" the value out of the nesting and into a higher up variable. The article explains it nicely with its _get helper example.

Trouble is this is in javascript and not c# so we can't use it as is but its something to look into.

Authorisation - will /schema always return the full schema or only what that user can see?

This was raised today during some work on #10 ideally when you hit GraphiQL you just want to see the schema that you can actually use (and are allowed to see). I'd hate to show people loads of fields that they then can't read as it would be frustrating and potentially dangerous as it gives away the existence of fields.

So, something to check. If I have two users with different permissions do they see different schemas when hitting through GraphiQL? This is dependant of course on us having the ability to limit fields per user as per #10

How to avoid type name clashing

As with #2 which deals with field name clashing, there's also a possibility that your document type names may clash with our graph types.

Right now it looks like the one you create just gets ignored, which might not be desired and will probably lead to a lot of confusion.

Unit testing

I wonder if it would be a good time to look at setting up unit testing and refactoring anything that would help setting this up?

Dynamically generated fields are great until you rename/move a field in Umbraco

This is going to be a common problem and one (it cropped up in Code First days all that time) that needs thinking about up front.

Dynamically generating the schema is fantastic while developing but once that API is out in the wild and being used by the client-side (or a by another 3rd party) you can't just go changing the API by renaming fields in Umbraco as you will potentially break the front-end.

As this is a common problem there are a couple of common solutions but which one to go with I'm not sure:

  • Don't change fields once they are out in the wild, this is the Facebook way, they never delete a field once its gone into production as they simply don't know who is using it.
  • Allow freezing the schema by writing it to disk or similar rather than auto generating it. This is what Models Builder and uSync do once you've gone through the initial development phase you then save it to disc and thats that. How you then manage a change I don't really know, you could use redirect (see below) but at least you won't be breaking anything and regenerating will be something you can plan and manage for.
  • Allow redirects for renamed fields, this is sort of how the Redirect Manager works in Umbraco now but we'd need it for fields not content. This has spaghetti code written all over it and something I'd suggest we avoid.
  • Other???

We can "punt" this idea for now but its here so folks know we've at least thought about it and give them some options up above.

Umbraco 8 support?

I bit confused, the about says:
"An implementation of GraphQL for Umbraco 8 using GraphQL for .NET."

But dependency is:
UmbracoCms.Core (>= 7.7.0 && < 8.0.0)

Any plans for Umbraco 8 support?

Supporting relations

I suspect this is a some day maybe job but worth thinking about how to support relations between content. Tried having a go today but got myself confused. Might be a great one to discus with someone with a laptop and a drink and bounce ideas off. Looking forward to a few chats with Stephan @ uwestfest next month.

Make umbraco8 the master branch

People are put off when they come to the repository and see the latest commit was in February. Show them there's stuff going on by moving master to umbraco7 and umbraco8 to master.

Add debug/performance stats to the bottom of the responses?

On of the big worries with anything like this is "is it fast enough" and of course "is it optimised" or "where should I optimise". Wonder if we should return some stats on time taken to generate a query and number SQL statements etc. (if possible to get) in the response?

Could either toggle this globally via umbracoDebug or per query I guess?

How to wire in your own strongly typed models?

Another question we are going to get is "I have my own models already and i want those available via GraphQL too, how can I do that and can they live side by side with the autogenerated ones?"

Lots to cover in all that. Yes we know that strongly typed models can be wired up into GraphQL but we need to give an entry point on how to do this and some examples I imagine. I'd be tempted to suggest going with attribute for fields using something like https://github.com/dlukez/graphql-dotnet-annotations or anything listed on https://graphql-dotnet.github.io/docs/guides/schema-generation/

Question is can we do it in such a way that the autogenerated stuff can work side by side with the custom stuff? Which would run first (autogenerated I imagine) or do we simply say its a opt-in option ie if you want to use your own models then you have to own the whole thing and wire everything up yourself? Seems a bit harsh but an easy route :)

graphql endpoints

I am using the umbraco 8 branch.
I think we need a different endpoint for graphql playground and the data calls as one of the tools im using tests to see if the url provided is json parseable before it starts to use the endpoint.
this is probably possible but Im not sure how to make it happen. Can someone help me out with this. Its a bit like the configurable endpoints issue #16

Call for maintainers

Hey all

As some of you might know, I published this code because of this thread on our.umbraco.com which was started after an open space discussion at Codegarden (which I unfortunately didn't attend) and I wanted to share what I had been playing around with for some time so we didn't had to start from scratch implementing GraphQL for Umbraco.

I've tried to run the project but I really don't have the motivation to do so and as I never used the project myself in a real project (I only did it as a learning experience), I really don't think I'm the right person to continue this project, so if anyone want to take over and make it a success, please let me know.

In the coming weeks I'll transfer the project to the Umbraco Community GitHub account or the GraphQL for Umbraco organization (depending of the responses I get).

Authorisation needs adding

We've done a lot of thought on how to do it. It breaks up nicely into a couple of chunks of effort which I guess we can link to from this one issue as and when needed.

Users need a way of controlling access to doctypes, fields and even content nodes (Umbraco users for instance can have their start node set, we should honour this). Ideally we would like to whitelist/blacklist nodes on the tree too.

For starters we hook up Authorization per field and per doctype. Checking out the docs for GraphQL.Authentication (which will need installing via Nuget) we can leverage the metadata on each GraphQL Field type to store a unique hash of the fields doctype alias, property type alias and permission, something like:

homepage:heroBanner:can_read

That would give us user granular control. But then we need a list of "Claims" of access for the user hitting the site. Lets assume they are passing in a Token of some sort. We can then use that to look up that users "Claims" which GraphQL.Authentication will then automagically compare for us against all the hashes we have set per field.

We can also do the same from DocTypes too and block out a whole type.

How the settings are "set" is a work in process which I'll put in another issues tomorrow, Janae has done some wire frames of a nice back office UI to manage it. Lets assume though that we have a object in memory (loaded from a custom DB table) which has all the users in with all their settings, this is the users "Claims". Each request we would get the Claims for the passed in Token (or default to a safe default users permissions) and then pass those Claims into GraphQL.Authentication to handle.

Ideally developers might want to roll their own Authentication lookup instead of ours (I'm thinking Active Directory or something) so the authentication would have to be swappable on start up for ease but that can come later.

first and last for content queries don't appear to be working

Using the demo site you can recreate this one:

{
  people {
    name,
    children(first:1) {
      items {
        name
      }
    }
  }
}

From the docs this "should" return the first item only however we get this back:

{
  "data": {
    "people": [
      {
        "name": "People",
        "children": {
          "items": [
            {
              "name": "Jan Skovgaard"
            },
            {
              "name": "Matt Brailsford"
            },
            {
              "name": "Lee Kelleher"
            },
            {
              "name": "Jeavon Leopold"
            },
            {
              "name": "Jeroen Breuer"
            }
          ]
        }
      }
    ]
  }
}

Same for "last" too:

{
  people {
    name,
    children(last:2) {
      items {
        name
      }
    }
  }
}

Create Umbraco Package (and nuget) that will install it all nicely

I'm trying to get this packaged up in time for the USA Festival in a few weeks. To get a MVP package out we (@Offroadcode) are working on a UI to manage access to individual fields that takes the form of a dashboard in the back office. Currently this is a separate repos (as we just cloned are base Package repos) and not sure if we should merge it in with this one or keep this as sort of the Core of the code (@rasmusjp any preference?).

See the repos here https://github.com/Offroadcode/GraphQL-for-Umbraco

Current aim is to have it use a Default user which will have access to zero fields out of the box. Site admins (well anyone with dev section access actually) will have to whitelist which fields the Default user can see. These fields will then be accessible publicly via the GraphQL endpoint and GraphiQL for the site. As 95% of sites will just want to power their frontend with this for now I think its totally ok to just roll out with Default user only and then we can add in the other user management and access token stuff later (ideally once I've had chance to talk this through over a beer with folks face to face at USA Fest, Denmark Fest and UK Fest) and gathered some thoughts/ideas.

So in short the MVP will need:

  • Basic UI to manage which fields to show per doctype
  • Back Office API's to save the selections from the UI
  • Ship with a default user
  • Create all the tables needed on start up as per #10
  • Packaged up as an Umbraco Package
  • Ideally available as a Nuget Package too

Traversing returns data you don't have access to

I you do traversing using children, siblings or ancestors you can get data returned which you don't have access to, e.g. if you request a specific doctype

{
  content {
    atRoot {
      all {
        ... on Home {
          _contentData {
            id
            children {
              items {
                _contentData {
                  id
                }
              }
            }
          }
        }
      }
    }
  }
}
# or using byType
{
  content {
    byType {
      Home(id: 1103) {
        _contentData {
          id
          children {
            items {
              _contentData {
                id
              }
            }
          }
        }
      }
    }
  }
}

It returns the data for the nodes, which you shouldn't have access to

{
  "data": {
    "content": {
      "byType": {
        "Home": {
          "_contentData": {
            "id": "1103",
            "children": {
              "items": [
                {
                  "_contentData": {
                    "id": "1104"
                  }
                },
                {
                  "_contentData": {
                    "id": "1113"
                  }
                },
                {
                  "_contentData": {
                    "id": "1119"
                  }
                },
                {
                  "_contentData": {
                    "id": "1122"
                  }
                },
                {
                  "_contentData": {
                    "id": "1126"
                  }
                }
              ]
            }
          }
        }
      }
    }
  }
}

But if you don't specify the doctype

{
  content {
    atRoot {
      all {
        _contentData {
          id
          children {
            items {
              _contentData {
                id
              }
            }
          }
        }
      }
    }
  }
}
# or
{
  content {
    byId(id: 1103) {
      _contentData {
        id
        children {
          items {
            _contentData {
              id
            }
          }
        }
      }
    }
  }
}

Then it returns the auth errors

{
  "errors": [
    {
      "message": "You are not authorized to run this query.",
      "locations": [
        {
          "line": 7,
          "column": 11
        }
      ],
      "extensions": {
        "code": "auth-required"
      }
    },
    {
      "message": "You are not authorized to run this query.",
      "locations": [
        {
          "line": 11,
          "column": 17
        }
      ],
      "extensions": {
        "code": "auth-required"
      }
    }
  ]
}

Ancestors filter not working

I've testing version 0.1.0 from nuget. It looks very promising! Thanks for putting this together and sharing it! I've come across a few bumps, tough.
I've been unable to use the ancestors filter. I can use the filter with children, but not with ancestors.

When I run this query:

{
  content {    
    byId(id: 1139) {  
      _contentData {  
        ancestors {  
          items {  
            _contentData {  
              level  
              url  
            }  
          }  
        }      
      }  
    }  
  }  
}

I get, as expected:

{
  "data": {
    "content": {
      "byId": {
        "_contentData": {
          "ancestors": {
            "items": [
              {
                "_contentData": {
                  "level": 4,
                  "url": "/en/report/chapter-1/"
                }
              },
              {
                "_contentData": {
                  "level": 3,
                  "url": "/en/report/"
                }
              },
              {
                "_contentData": {
                  "level": 2,
                  "url": "/en/"
                }
              },
              {
                "_contentData": {
                  "level": 1,
                  "url": "/"
                }
              }
            ]
          }
        }
      }
    }
  }
}

But when I try to filter the ancestors, like this:

{
  content {
    byId(id: 1139) {
      _contentData {
        ancestors(filter: {level: 2}) {
          items {
            _contentData {
              level
              url
            }
          }
        }    
      }
    }
  }
}

I get this "Error trying to resolve ancestors" message:

{
  "data": {
    "content": {
      "byId": {
        "_contentData": {
          "ancestors": null
        }
      }
    }
  },
  "errors": [
    {
      "message": "Error trying to resolve ancestors.",
      "locations": [
        {
          "line": 5,
          "column": 9
        }
      ],
      "path": [
        "content",
        "byId",
        "_contentData",
        "ancestors"
      ],
      "extensions": {
        "code": "INVALID_CAST"
      }
    }
  ]
}

Already tried other filter variations, and I always get the same error.

When trying to convert an item in a collection to a Type should it exclude results that don't match the Type?

Trying to get all the ancestors of a content item will return me an array of items:

{
  content(id:1115) {
    name,
    ancestors {
      items {
          name
        }
      }
    }
  }
}

Returns:

{
  "data": {
    "content": {
      "name": "Matt Brailsford",
      "ancestors": {
        "items": [
          {
            "name": "People"
          }
        ]
      }
    }
  }
}

If I want to try to convert that to a Type some odd things happen.

  1. If I have multiple items of different Types it will return the non-matching items in the array as empty objects rather than strip them out as I would expect. This might be correct as the conversion happens within the loop over the items? Sadly I can't repeat this in the demo as everything is in the root.

  2. If I try to convert to a Type that matched none of the content then I still get an empty item back, it doesn't return an empty array. This is actually the same issue as above but just fudged a bit (should it tell me in an error that it can't convert or find anything for that Type for instance? This is repeatable in the demo like so:

{
  content(id:1115) {
    name,
    ancestors {
      items {
        ... on Blogpost {	# Matt isn't nested under a Blogpost
          name
        }
      }
    }
  }
}

returns:


{
  "data": {
    "content": {
      "name": "Matt Brailsford",
      "ancestors": {
        "items": [
          {}	# should this even be returned?
        ]
      }
    }
  }
}

Performance tests and benchmarks

Perform performance tests, load tests and micro-benchmarks to validate and establish the initial state of the performance for the project.

Compare it with e.g. direct access via services, memory caches etc. Benchmark the "simple" cases e.g. listings and rich queries gathering a lot of information in the single query.

What to do when you rename or change a doctype?

This one was sparked off by re-reading Janaes post about switching DocTypes. When a developer changes a doctype the API would be broken for anyone external using it. Lets say we rename a Hotel doctype to Accommodation to make it more generic (a villa is not a hotel for instance). Now no one can have a query that references Hotel, they will all break (well return null anyway).

To be fair having it break might be ok but ideally we want some backwards compat. I pondered keeping a list of renames around or possibly a list of Aliases that we could set (in an xml file for example) and when we build our schema up on start up we can create a bunch of Types that just inherit from the new Type. So a Hotel(id:123) would work just like Accommodation(id:123) under the hood. Ideally we would return an Error in our response too so that users know that they shouldn't be using it. This would work but fields that have changed on the doctype would blow up too and theres no way around that that I can think of?

Ideally don't change doctypes if they are out in the wild!

Again this is here more so we know we've thought about it than somethign we should "fix" but something to think over.

Chat I had with Joe about this is here: https://gitter.im/graphql-dotnet/graphql-dotnet?at=5b881948f5402f32aab353f5

Custom fields not outputted in umbraco8 branch

On the demo site, when executing

{
  content {
    atRoot {
      Home {
        _children {
          items {
            ... on Blog {
              _name
              _children {
                items {
                  ... on Blogpost {
                    _name
                    pageTitle
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

this is returned

{
  "data": {
    "content": {
      "atRoot": {
        "Home": [
          {
            "_children": {
              "items": [
                {},
                {},
                {},
                {
                  "_name": "Blog",
                  "_children": {
                    "items": [
                      {
                        "_name": "My Blog Post",
                        "pageTitle": ""
                      },
                      {
                        "_name": "Another one",
                        "pageTitle": ""
                      },
                      {
                        "_name": "This will be great",
                        "pageTitle": ""
                      }
                    ]
                  }
                },
                {}
              ]
            }
          }
        ]
      }
    }
  }
}

I expected pageTitle to not be an empty string, because Umbraco shows that the fields have data in the UI. This is a fresh Umbraco installation with the demo website installed.

I traced the issue to
https://github.com/rasmusjp/umbraco-graphql/blob/09ea3aafc4d738a820f3c8d2b57501391cd924bd/src/Our.Umbraco.GraphQL/Types/PublishedPropertyFieldType.cs#L29-L34
where publishedProperty.GetValue(userContext.Culture) returns "" for pageTitle because culture is passed as argument. Removing it returns the correct value, but we've lost localization support. userContext.Culture = "en-US".

I'm in no way an expert in Umbraco, but the problem seems to be in Umbraco.Web.PublishedCache.NuCache.Property where (my comments in /* */)

public override object GetValue(string culture = null, string segment = null)
{
	_content.VariationContextAccessor.ContextualizeVariation(_variations, ref culture, ref segment);

	object value;
	lock (_locko)
	{
		var cacheValues = GetCacheValues(PropertyType.CacheLevel).For(culture, segment);

		// initial reference cache level always is .Content
		const PropertyCacheLevel initialCacheLevel = PropertyCacheLevel.Element;

                 /* cacheValues.ObjectInitialized is false on first run, so not returning here */
		if (cacheValues.ObjectInitialized) return cacheValues.ObjectValue;

                /* GetInterValue(culture, segment) is the interesting call here, see next code snippet */
		cacheValues.ObjectValue = PropertyType.ConvertInterToObject(_content, initialCacheLevel, GetInterValue(culture, segment), _isPreviewing);
		cacheValues.ObjectInitialized = true;
		value = cacheValues.ObjectValue;
	}

	return value;
}
private object GetInterValue(string culture, string segment)
{
	if (culture == "" && segment == "")
	{
		/* Not relevant */
	}

        /* (_sourceValues == null) == true! */
	if (_sourceValues == null)
		_sourceValues = new Dictionary<CompositeStringStringKey, SourceInterValue>();

	var k = new CompositeStringStringKey(culture, segment);

        /* Because _sourceValues have just been initialized, nothing is found and the statement below evaluates to true */
	if (!_sourceValues.TryGetValue(k, out var vvalue))
        {
                /* SourceValue is set to null, which might be the problem, next code snippet shows why */
		_sourceValues[k] = vvalue = new SourceInterValue { Culture = culture, Segment = segment, SourceValue = GetSourceValue(culture, segment) };
        }

        /* This will be false on first run */
	if (vvalue.InterInitialized) return vvalue.InterValue;

        /* Since vvalue.SourceValue == null, PropertyType.ConvertSourceToInter returns null */
	vvalue.InterValue = PropertyType.ConvertSourceToInter(_content, vvalue.SourceValue, _isPreviewing);
	vvalue.InterInitialized = true;
	return vvalue.InterValue;
}
public override object GetSourceValue(string culture = null, string segment = null)
{
	_content.VariationContextAccessor.ContextualizeVariation(_variations, ref culture, ref segment);

	if (culture == "" && segment == "")
		return _sourceValue;

	lock (_locko)
	{
		if (_sourceValues == null) return null;

                /* _sourceValues was just initialized in GetInterValue, so it's going to be empty. No SouceValue will be found, and thus null will be returned */
		if (_sourceValues.TryGetValue(new CompositeStringStringKey(culture, segment), out var sourceValue))
			return sourceValue.SourceValue;
		else
			return null; /* This is returned */
	}
}

It might be an Umbraco bug, or me doing something wrong, but filing it here because I'm clueless and just experimenting with your GraphQL api.

Restructuring the root query type

Right now the "content queries" are directly on the root query:

type UmbracoQuery {
    contentAtRoot: [PublishedContent]!
    contentById: PublishedContent
    people: [People]!
    products: [Products]!
}

To avoid cluttering the root type (e.g. if we want to implement #3, #8 and dictionary in #9), and make the data more discoverable I think we should move them to sub types.

I think something the types below will make the data more discoverable and make it easier to add new "query objects" to the root query without it being to cluttered

type ContentQuery {
    atRoot: [PublishedContent]!
    byId(id: ID!): PublishedContent
    byType: ContentByTypeQuery!
    byUrl(url: String!): PublishedContent
    site: PublishedContent
}

type ContentByTypeQuery {
    People(id: ID!): People
    Products(id: ID!): Products
}

# Not sure if this is needed
type MediaQuery {
    byId(id: ID!): PublishedContent
}

type DictionaryQuery {
    all: [DictionaryItem]!
    byKey: DictionaryItem
}

# Root query
type UmbracoQuery {
    content: ContentQuery!
    media: MediaQuery!
    dictionary: DictionaryQuery!
}

Maybe even adding a ContentAtRootQuery with all the types that's allow at root

type ContentAtRootQuery {
    all: [PublishedContent]!
    People: [People]!
    Products: [Products]!
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.