Lesson 3: Publishing workflows
Learn how to create a publishing workflow to your app using Keystons’s select
and timestamp
fields.
Where we left off
In the last lesson we added a post
list to our blog app and setup our first connection between posts and users with Keystone’s relationship field. Here's the code:
// keystone.tsimport { list, config } from '@keystone-6/core';import { text, relationship } from '@keystone-6/core/fields';const lists = {User: list({fields: {name: text({ validation: { isRequired: true } }),email: text({ validation: { isRequired: true }, isIndexed: 'unique' }),posts: relationship({ ref: 'Post.author', many: true }),},}),Post: list({fields: {title: text(),author: relationship({ref: 'User.posts',ui: {displayMode: 'cards',cardFields: ['name', 'email'],inlineEdit: { fields: ['name', 'email'] },linkToItem: true,inlineCreate: { fields: ['name', 'email'] },},}),},}),};export default config({db: {provider: 'sqlite',url: 'file:./keystone.db',},lists,});
We’re now going to extend our Keystone schema to support publishing needs.
Build a publishing workflow
Our publishing workflow needs the ability to reflect:
- When the post should be published
- The post’s status – whether it’s still being drafted, or is ready for publishing
These will give us what we need to conditionally display and order posts in a frontend app.
Add a publish date
Keystone’s timestamp
field will let editors associate a date and time with the post:
import { list, config } from '@keystone-6/core';import { text, timestamp, relationship } from '@keystone-6/core/fields';const lists = {Post: list({fields: {title: text(),publishedAt: timestamp(),author: relationship({}),},}),};
Add a published status
Keystone’s select
field gives editors the ability to choose a value from a predetermined set of options. Let’s use this to capture our post's publish status.
To set the the field’s desired values we add options
to the field’s configuration. They will be the only options available to editors in Admin UI and through Keystone’s auto-generated GraphQL types:
import { list, config } from '@keystone-6/core';import { text, relationship, timestamp, select } from '@keystone-6/core/fields';const lists = {Post: list({fields: {title: text(),publishedAt: timestamp(),author: relationship({}),status: select({options: [{ label: 'Published', value: 'published' },{ label: 'Draft', value: 'draft' },],}),},}),};
Let’s take a look at how everything comes together in Admin UI:
These defaults give us all the basic functionality we need, but there's room for improvement in the select field:
- It would be helpful to present all the
status
options in a way that doesn't make editors click on the input to find out what options are available - Setting a default value for new posts could reduce the likelihood of editors accidentally publishing their work before it's done
Set a default value
Adding a defaultValue
to the select field’s config will ensure that newly created posts start out with a preferred status. Let's set the default to draft
:
defaultValue: 'draft',
Implement a segmented control input
When a select field only has a few values to choose from, changing the UI's displayMode
from the default select
to segmented-control
will expose all those values in the interface so editor's don't have to click the input in order to know which value to choose. Our limited set of draft
/published
options is a perfect fit for this!
ui: { displayMode: 'segmented-control' },
This now gives us:
const lists = {Post: list({status: select({options: [{ label: 'Published', value: 'published' },{ label: 'Draft', value: 'draft' },],defaultValue: 'draft',ui: { displayMode: 'segmented-control' },}),author: relationship({ ref: 'User.posts' }),},}),};
Let's put it all together to see the changes:
Protip: You can use hooks to automatically update publishedAt
when a post’s status moves from draft
to published
.
Looking at the GraphQL API
We can now query these values using Keystone's GraphQL API playground to show all the published posts
query getAllPosts {posts {title}}
And/or posts that have a published status, and were published after a certain date:
query getPublishedPosts {posts(where: { status: { equals: "published" } }) {title}}
What we have now
We’ve successfully added two new fields to our post
type that give us the information our system will need to display posts:
// keystone.tsimport { list, config } from '@keystone-6/core';import { text, timestamp, select, relationship } from '@keystone-6/core/fields';const lists = {User: list({fields: {name: text({ validation: { isRequired: true } }),email: text({ validation: { isRequired: true }, isIndexed: 'unique' }),posts: relationship({ ref: 'Post.author', many: true }),},}),Post: list({fields: {title: text(),publishedAt: timestamp(),status: select({options: [{ label: 'Published', value: 'published' },{ label: 'Draft', value: 'draft' },],defaultValue: 'draft',ui: { displayMode: 'segmented-control' },}),author: relationship({ ref: 'User.posts' }),},}),};export default config({db: {provider: 'sqlite',url: 'file:./keystone.db',},lists,});