-
Notifications
You must be signed in to change notification settings - Fork 638
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
404s with headless preview for unsaved drafts and disabled entries #4581
Comments
Hm, yeah, that’s a little tricky. Element API is just generating an element query based on the criteria you passed, and if the entry isn’t fully saved yet, then it’s not going to show up in element query results, unless the You could work around it by doing this:
Let me know if that works for you. |
@brandonkelly Ah thanks, i somehow thought the token stuff would be completely handled by Craft core. So: yes that worked, but with a few quick tests i saw (maybe incorrectly) a couple of problems:
So i'm going to try something like this to get the criteria from the token (yet not fully tested and only semi understood):
Do you think that's a way to approach it? |
Yeah maybe. I’m going to think on this as well, and see if there’s something we can do to make it easier. |
@brandonkelly Thanks, just finished some kind of Proof of Concept with this, which worked well (with some very impressed clients, this one is a real game changer), so it's ok for me right now , but yes, it would be nice to have some consistent behavior between twig and headless previews, especially thinking of previewing not only single entry views, but also some kind of list/index views. |
Sweet! |
This should be a lot easier starting in Craft 3.2.5, as element queries will now include the previewed elements in the results even if the To test this change early, change your "require": {
"craftcms/cms": "dev-develop#621c751b3a5ee404a7111faed3839303fa81caf5 as 3.2.4.1",
"...": "..."
} Then run |
Ok! Caught just before out the door, so I tried it. And, at first look at least it works: previews show again on New entry, in Live Vue on Gatsby. However, one issue: the new Entry apparently has a null for the Title field, and CraftQL/or probably the actual GraphQL engine it fronts for doesn't like this. Simply putting an empty string into title of your created Entry should fix this, as other text fields appear to dummy up this way automatically. Well, I have to test that, will have to be later, as there's only a Redactor field in my quick runup. But you get the idea, and can fix if simpler text fields also might not be defaulting to non-null... Here's how Hal sees this error: in green... |
Und...I tested with a plain text field added to the Entrytype schema, and this is also fine. Actually in reading more carefully the Hal/CraftQL error in pic above, it's a little clearer about the exact problem, I suppose. Titles aren't allowed to be null by constraint. Thus again the blank title needs to be assigned for your dummy Entry, seems. Which made me think of another possible issue, so tested that also. I made the new test simple text field required. But this did not cause a problem, as probably evident to you, because required is a constraint only on saving. So, the faulting titles constraint is earlier, probably on the database, and satisfying it so that the title isn't null may be all that's needed to have this upgrade become fully functional. Cheers. |
Just fixed a bug caused by that previous commit, where previewing normal entry drafts could give you duplicate query results. Updated the @narration-sd This seems like something the GraphQL API should just be able to work around, considering not all element types must have titles (which is why it can be stored as |
@brandonkelly Thanks, with just a few tests that looks great, both for single entry and index views. Will do a few more later on. Not quite sure though what is the best idea of building headless routes in this scenario. Currently using something like |
Yeah I think the ID is generally going to be safer; that’s what I’ve been using locally. |
Yeah, you'd think so. This is one of several reasons I ran a retest with a simple Text field 'just like' Title. CraftQL returns a null fine for that as well as any more complex field I've ever tried. So it has to be something special about Title, and for that, it seems it must be picking something up from Craft in its GQL schema building which marks Title so. Tracing the layers of set theory that monster is coded of is no fun, of course. I am a little curious that this isn't a general problem for you that I've found? |
amplifying last point: have you tried Previewing a fresh empty New entry??? |
I’ve been testing with the following template: <!DOCTYPE html>
<html lang="en-US">
<title>Headless Preview Test</title>
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<script>
$.get('/api/entry/{{ craft.app.request.getQueryParam('id') }}.json?token={{ craft.app.request.getQueryParam('token') }}', function(response) {
$('<pre/>', {
html: JSON.stringify(response, null, 2),
}).appendTo(document.body);
});
</script> Which points to the following Element API endpoint: 'api/entry/<id:\d+>.json' => function($id) {
return [
'elementType' => Entry::class,
'criteria' => ['id' => $id],
'one' => true,
'paginate' => false,
'transformer' => function(Entry $entry) {
return ['title' => $entry->title];
},
];
}, And I get this preview output when creating a new entry (before it’s fully saved): {
"title": null
} |
Well, that’s Element API. It’s CraftQL that’s having the problem here... And indeed, CQL code says the message is about a '_definition.GraphQLNonNull'' type. Of course it's into the weeds for where that would be set |
that's CQL = CraftQL; fixed it |
...and yes, he's doing it, straight up and all on his own, not asking Craft.
I guess because 'we know' titles can't be empty -- wasn't this true at one past time if memory serves? And since he's using this same Schema created for upserts, then he'd want to guard. Getting this changed -- seems likely to be a drawn-out process. Any reason not to just put an empty string into your created New entry, as asked?? Since emptiness is fine if null is? And then we can be on the air. I'm all wired up to dev-master and can test it.... |
p.s. here's a little more of the code in this area of CraftQL -- the non-null assumption is made on some other fields people are likely enough to take an interest in and query, so they should probably get the same gets-empty-string treatment. Have a glance for ->nonNull() ...
|
but if you want a pre-validation, happy to test with just <title> preset to empty string, to prove the cause on a one-liner for you in dev-master... |
...and...yes, there's a further problem here. I was thinking you're operating pretty far into overload, so why not a) provide a temp fix inside Live Vue as I have to degree possible (impossible fully...) for the not-yet-accepted PR on other issue b) cook up a pre-PR that at least shows where a real solution may be placed. But on the b) part....
[ok so far, but I think have found the actual problem, and it's not an option setting on MySql after all, which is what followed but now redacting...see next +1 below] |
Just to add -- do your own analysis to assure, please -- but I took another weekend moment to list out and check the returned new entry for the ->nonNull() status properties I see in Mark's code. Result: it looks like |
Well, think I've located where this null thing is happening. See line 172-174 of services/Content.php:
The result of this for an empty-string title is not to set -- and thus the ddl default for title will be used, which is...null. Lots of opportunity to think what's least intrusive to fix this, and only your breadth of knowledge about consequences can decide, @brandonkelly, so no PR. If you're not using null title as a flag somewhere, maybe just allowing empty string here could make it simple to push an empty string around line 96 of EntryController::actionCreateDraft where I tried it, but I somehow doubt it's that straightforward. Anyway, hope it's been useful to have run these things down, towards getting this solved. |
It did occur to me for a moment falling asleep last night that the easiest/cleanest way to fix this might be just to switch that condition and action around, just mentioned on line 172 of services/Content.php. So that Seems this would have the added advantage of avoiding need for special case on |
@narration-sd I don’t want to stop storing element titles as |
Great, that's the other end, and. I swapped in on the hash, ran things up, and...it works. CraftQL is now happy again, and Live Vue has previews and shares on fresh New Entries. Besides the rest. Thanks, @brandonkelly , looks like last loose board on this roofing is now nailed down. |
@brandonkelly apologies to dig this back up, but we're still seeing issues with this in Craft 3.3.6. If I make a new draft entry (and don't save it) and attempt to live preview it, my backend makes an call to my Element API with the relevant I don't want to swap my frontend URLs to use IDs rather than slugs – is there any way I can work around this? If I disable the |
@mattandrews Can you post your endpoint’s full |
Yep, sorry! Here's what I'm using on a typical query:
(We allow |
And how are you passing your slug to the JS? Does your preview target URL contain |
So my frontend URL for the above query looks like |
Can you post that code? |
Sure. This is probably a bit of a deep-dive, apologies in advance: The "Updates" section has this URL config: Our NodeJS frontend app receives these pageviews and calls our Element API here. The relevant parts are:
The requestParams (eg. Craft-related tokens)
( All of this builds a URL which looks like this:
Now it's over to our PHP endpoint for the Element API. Here's a simplified version of the above code showing just the relevant parts:
That API call returns the following:
If I remove this line:
... then it works when trying to preview unsaved drafts, but if I preview a new draft of an existing entry, it shows me content from a different entry (looks like the newest item with the same Entry Type as the one I'm editing). Hope this helps and apologies if this ends up just being some obvious misconfiguration on my part. |
Hah ok… I just tested this locally and it’s definitely still working, so something must be getting lost along the way here. Here’s the endpoint config I used to test: 'test/<slug:[^\/]*>.json' => function(string $slug) {
return [
'elementType' => Entry::class,
'criteria' => [
'section' => 'news',
'slug' => $slug,
],
'one' => true,
'pretty' => true,
'transformer' => function(Entry $entry) {
return [
'title' => $entry->title,
'slug' => $entry->slug,
'id' => $entry->id,
];
},
];
}, I’m pinging that directly via a preview target, with the URL Format |
Aha, I think we're onto something. I tried using your code above and still had no luck (eg. it's not something weird about how I'm setting the endpoint criteria). The key was your comment "via a preview target". I'm just using a regular entry URL config, no preview targets. But I just tried adding a preview target for this section using the exact same URL config, and it worked: Now when I attempt a live preview, I get the same 404 error as before for the "Primary Entry" but if I toggle to "Matt test" preview target, I see my preview page as expected. The URLs of the preview iframe are: Primary Entry:
Preview target
So possibly something isn't working here with the slug where it's being slugified from my Title field for preview targets, but not for primary entry URIs? |
Aha! That makes perfect sense, because Craft explicitly avoids updating an entry draft’s URI, so that it will always match the source entry – and if a URI is to change, that will only happen once the draft is actually published. cms/src/validators/ElementUriValidator.php Lines 47 to 51 in 49e143d
Though I think we can tweak that condition a bit, to ignore unsaved drafts (the state that entries are in before you click “Save” for the first time). Because in their case, there is no source entry whose URI the draft needs to try to stay consistent with. |
@mattandrews just tweaked this behavior for Craft 3.4, so going forward, unsaved entry URIs will be updated on each autosave. |
And this is why I love Craft. Thanks so much for the quick fix (and taking the time go through this with everyone here), @brandonkelly! |
My pleasure. Thanks for identifying the bug! |
Description
Don't know whether i missed something in my setup or that's an issue, but couldn't get a headless preview working for unsaved drafts.
So the default preview for an unsaved draft works fine with a url like
http://temp2.local/en/news/__temp_9hWhjyD2oqL6eaGscUyxndcGZ6gMksJ9cP1X?x-craft-preview=qtdgDQI8ut&token=jIUdZHOcTRKHNeA_zLjgTKoBDd0JWDdY
However trying to get a preview from a simple vue js app via element api fails with
{ "error": { "code": 404, "message": "No element exists that matches the endpoint criteria" } }
The element api url is
http://temp2.local/en/api/v1/entry/__temp_9hWhjyD2oqL6eaGscUyxndcGZ6gMksJ9cP1X?token=jIUdZHOcTRKHNeA_zLjgTKoBDd0JWDdY
The config is
Once the entry is saved, and a new draft is created, the headless preview for that draft works fine:
http://temp2.local/en/api/v1/entry/headless-preview?token=R2Wp36Lr_Z8Zu7utYDnv6ERNDH9a3zWP
gets the correct draft data.When the source entry is disabled or the post date is set to a future date, the default preview for a new draft works, but the headless version fails again with 404.
Additional info
The text was updated successfully, but these errors were encountered: